diff --git a/BUILD b/BUILD index 3c683b19..dd59d8ac 100644 --- a/BUILD +++ b/BUILD @@ -2,7 +2,9 @@ # DeepMind Lab, a machine-learning research environment # forked from ioquake/ioq3. -licenses(["restricted"]) # GPLv2 +licenses(["restricted"]) # GPLv2, code/tools/lcc is separate + +exports_files(["LICENSE"]) CODE_DIR = "engine/code" @@ -545,280 +547,188 @@ cc_binary( deps = ["@zlib_archive//:zlib"], ) -# To link with the SDL frontend, depend on :game_lib_sdl -# and add "-lGL" to the linker flags. +cc_library( + name = "level_cache_types", + hdrs = ["public/level_cache_types.h"], + visibility = ["//visibility:public"], +) + +IOQ3_COMMON_SRCS = [ + CODE_DIR + "/asm/ftola.c", + CODE_DIR + "/asm/qasm-inline.h", + CODE_DIR + "/asm/snapvector.c", + CODE_DIR + "/cgame/cg_public.h", + CODE_DIR + "/client/libmumblelink.c", + CODE_DIR + "/deepmind/context.h", + CODE_DIR + "/deepmind/dm_public.h", + CODE_DIR + "/deepmind/dmlab_load_model.c", + CODE_DIR + "/deepmind/dmlab_load_model.h", + CODE_DIR + "/deepmind/dmlab_recording.c", + CODE_DIR + "/deepmind/dmlab_recording.h", + CODE_DIR + "/deepmind/dmlab_save_model.c", + CODE_DIR + "/deepmind/dmlab_save_model.h", + CODE_DIR + "/game/bg_public.h", + CODE_DIR + "/game/g_public.h", + CODE_DIR + "/renderergl2/tr_image_dds.c", + CODE_DIR + "/sys/con_log.c", + CODE_DIR + "/sys/con_passive.c", + CODE_DIR + "/sys/sys_main.c", + CODE_DIR + "/sys/sys_unix.c", + CODE_DIR + "/ui/ui_public.h", +] + glob( + include = [ + CODE_DIR + "/botlib/*.c", + CODE_DIR + "/botlib/*.h", + CODE_DIR + "/client/*.h", + CODE_DIR + "/client/cl_*.c", + CODE_DIR + "/client/snd_*.c", + CODE_DIR + "/qcommon/*.c", + CODE_DIR + "/qcommon/*.h", + CODE_DIR + "/renderercommon/*.c", + CODE_DIR + "/renderercommon/*.h", + CODE_DIR + "/renderergl1/*.c", + CODE_DIR + "/renderergl1/*.h", + CODE_DIR + "/server/*.c", + CODE_DIR + "/server/*.h", + CODE_DIR + "/sys/*.h", + ], + exclude = [ + CODE_DIR + "/client/fx_*.h", + CODE_DIR + "/renderercommon/tr_types.h", + CODE_DIR + "/renderercommon/tr_public.h", + CODE_DIR + "/renderergl1/tr_local.h", + CODE_DIR + "/renderergl1/tr_subs.c", + CODE_DIR + "/server/sv_rankings.c", + CODE_DIR + "/qcommon/vm_armv7l.c", + CODE_DIR + "/qcommon/vm_none.c", + CODE_DIR + "/qcommon/vm_powerpc*.c", + CODE_DIR + "/qcommon/vm_powerpc_asm.h", + CODE_DIR + "/qcommon/vm_sparc.c", + CODE_DIR + "/qcommon/vm_sparc.h", + ], +) + +IOQ3_COMMON_DEPS = [ + ":level_cache_types", + ":qcommon_hdrs", + "//deepmind/include:context_hdrs", + "//third_party/rl_api:env_c_api", + "@jpeg_archive//:jpeg", + "@sdl_system//:sdl2", + "@zlib_archive//:zlib", +] + +IOQ3_COMMON_TEXTUAL_HDRS = [ + CODE_DIR + "/renderercommon/tr_types.h", + CODE_DIR + "/renderercommon/tr_public.h", + CODE_DIR + "/renderergl1/tr_local.h", +] + +IOQ3_COMMON_COPTS = [ + "-std=c99", + "-fno-strict-aliasing", + ARCH_VAR, + STANDALONE_VAR, +] + +IOQ3_COMMON_DEFINES = [ + "BOTLIB", + "_GNU_SOURCE", +] + cc_library( name = "game_lib_sdl", - srcs = [ - CODE_DIR + "/asm/ftola.c", - CODE_DIR + "/asm/qasm-inline.h", - CODE_DIR + "/asm/snapvector.c", - CODE_DIR + "/cgame/cg_public.h", - CODE_DIR + "/client/libmumblelink.c", - CODE_DIR + "/deepmind/dm_public.h", + srcs = IOQ3_COMMON_SRCS + [ CODE_DIR + "/deepmind/dmlab_connect.c", - CODE_DIR + "/game/bg_public.h", - CODE_DIR + "/game/g_public.h", CODE_DIR + "/sdl/sdl_icon.h", CODE_DIR + "/sdl/sdl_input.c", CODE_DIR + "/sdl/sdl_snd.c", - CODE_DIR + "/sys/con_log.c", - CODE_DIR + "/sys/con_passive.c", - CODE_DIR + "/sys/sys_main.c", - CODE_DIR + "/sys/sys_unix.c", - CODE_DIR + "/ui/ui_public.h", ## OpenGL rendering CODE_DIR + "/sdl/sdl_gamma.c", CODE_DIR + "/sdl/sdl_glimp.c", - ] + glob( - [ - CODE_DIR + "/botlib/*.c", - CODE_DIR + "/client/cl_*.c", - CODE_DIR + "/client/snd_*.c", - CODE_DIR + "/qcommon/*.c", - CODE_DIR + "/renderercommon/*.c", - CODE_DIR + "/renderergl1/*.c", - CODE_DIR + "/server/*.c", - ], - exclude = [ - CODE_DIR + "/renderergl1/tr_subs.c", - CODE_DIR + "/server/sv_rankings.c", - CODE_DIR + "/qcommon/vm_none.c", - CODE_DIR + "/qcommon/vm_powerpc*.c", - CODE_DIR + "/qcommon/vm_sparc.c", - ], - ), - hdrs = [ - "public/dmlab.h", - CODE_DIR + "/deepmind/context.h", - ] + glob( - [ - CODE_DIR + "/botlib/*.h", - CODE_DIR + "/client/*.h", - CODE_DIR + "/qcommon/*.h", - CODE_DIR + "/sys/*.h", - CODE_DIR + "/server/*.h", - CODE_DIR + "/renderercommon/*.h", - CODE_DIR + "/renderergl1/*.h", - ], - exclude = [ - CODE_DIR + "/client/fx_*.h", - CODE_DIR + "/qcommon/vm_powerpc_asm.h", - CODE_DIR + "/qcommon/vm_sparc.h", - CODE_DIR + "/renderercommon/tr_types.h", - CODE_DIR + "/renderercommon/tr_public.h", - CODE_DIR + "/renderergl1/tr_local.h", - ], - ), - copts = [ - "-std=c99", - "-fno-strict-aliasing", - ARCH_VAR, - STANDALONE_VAR, - ], - defines = [ - "BOTLIB", - "_GNU_SOURCE", - ], - textual_hdrs = [ - CODE_DIR + "/renderercommon/tr_types.h", - CODE_DIR + "/renderercommon/tr_public.h", - CODE_DIR + "/renderergl1/tr_local.h", - ], - deps = [ - ":qcommon_hdrs", - "//deepmind/include:context_headers", - "//third_party/rl_api:env_c_api", - "@jpeg_archive//:jpeg", - "@sdl_system//:sdl2", ], + hdrs = ["public/dmlab.h"], + copts = IOQ3_COMMON_COPTS, + defines = IOQ3_COMMON_DEFINES, + linkopts = ["-lGL"], + textual_hdrs = IOQ3_COMMON_TEXTUAL_HDRS, + deps = IOQ3_COMMON_DEPS, ) -# To link with the headless OSMesa frontend, depend on :game_lib_headless_osmesa -# and add "-lGL -lOSMesa" to the linker flags. cc_library( name = "game_lib_headless_osmesa", - srcs = [ - CODE_DIR + "/asm/ftola.c", - CODE_DIR + "/asm/qasm-inline.h", - CODE_DIR + "/asm/snapvector.c", - CODE_DIR + "/cgame/cg_public.h", - CODE_DIR + "/client/libmumblelink.c", - CODE_DIR + "/deepmind/dm_public.h", + srcs = IOQ3_COMMON_SRCS + [ CODE_DIR + "/deepmind/dmlab_connect.c", - CODE_DIR + "/game/bg_public.h", - CODE_DIR + "/game/g_public.h", CODE_DIR + "/null/null_input.c", CODE_DIR + "/null/null_snddma.c", - CODE_DIR + "/sys/con_log.c", - CODE_DIR + "/sys/con_passive.c", - CODE_DIR + "/sys/sys_main.c", - CODE_DIR + "/sys/sys_unix.c", - CODE_DIR + "/ui/ui_public.h", ## OpenGL rendering CODE_DIR + "/deepmind/headless_osmesa_glimp.c", CODE_DIR + "/deepmind/glimp_common.h", CODE_DIR + "/deepmind/glimp_common.c", - ] + glob( - [ - CODE_DIR + "/botlib/*.c", - CODE_DIR + "/client/cl_*.c", - CODE_DIR + "/client/snd_*.c", - CODE_DIR + "/qcommon/*.c", - CODE_DIR + "/renderercommon/*.c", - CODE_DIR + "/renderergl1/*.c", - CODE_DIR + "/server/*.c", - ], - exclude = [ - CODE_DIR + "/renderergl1/tr_subs.c", - CODE_DIR + "/server/sv_rankings.c", - CODE_DIR + "/qcommon/vm_none.c", - CODE_DIR + "/qcommon/vm_powerpc*.c", - CODE_DIR + "/qcommon/vm_sparc.c", - ], - ), - hdrs = [ - "public/dmlab.h", - CODE_DIR + "/deepmind/context.h", - ] + glob( - [ - CODE_DIR + "/botlib/*.h", - CODE_DIR + "/client/*.h", - CODE_DIR + "/qcommon/*.h", - CODE_DIR + "/sys/*.h", - CODE_DIR + "/server/*.h", - CODE_DIR + "/renderercommon/*.h", - CODE_DIR + "/renderergl1/*.h", - ], - exclude = [ - CODE_DIR + "/client/fx_*.h", - CODE_DIR + "/qcommon/vm_powerpc_asm.h", - CODE_DIR + "/qcommon/vm_sparc.h", - CODE_DIR + "/renderercommon/tr_types.h", - CODE_DIR + "/renderercommon/tr_public.h", - CODE_DIR + "/renderergl1/tr_local.h", - ], - ), - copts = [ - "-std=c99", - "-fno-strict-aliasing", - ARCH_VAR, - STANDALONE_VAR, - ], - defines = [ - "BOTLIB", - "_GNU_SOURCE", - ], - textual_hdrs = [ - CODE_DIR + "/renderercommon/tr_types.h", - CODE_DIR + "/renderercommon/tr_public.h", - CODE_DIR + "/renderergl1/tr_local.h", - ], - deps = [ - ":qcommon_hdrs", - "//deepmind/include:context_headers", - "//third_party/rl_api:env_c_api", - "@jpeg_archive//:jpeg", - "@sdl_system//:sdl2", ], + hdrs = ["public/dmlab.h"], + copts = IOQ3_COMMON_COPTS, + defines = IOQ3_COMMON_DEFINES, + linkopts = ["-lOSMesa"], + textual_hdrs = IOQ3_COMMON_TEXTUAL_HDRS, + deps = IOQ3_COMMON_DEPS, ) -# To link with the headless GLX frontend, depend on :game_lib_headless_glx -# and add "-lGL -lX11" to the linker flags. cc_library( name = "game_lib_headless_glx", - srcs = [ - CODE_DIR + "/asm/ftola.c", - CODE_DIR + "/asm/qasm-inline.h", - CODE_DIR + "/asm/snapvector.c", - CODE_DIR + "/cgame/cg_public.h", - CODE_DIR + "/client/libmumblelink.c", - CODE_DIR + "/deepmind/dm_public.h", + srcs = IOQ3_COMMON_SRCS + [ CODE_DIR + "/deepmind/dmlab_connect.c", - CODE_DIR + "/game/bg_public.h", - CODE_DIR + "/game/g_public.h", CODE_DIR + "/null/null_input.c", CODE_DIR + "/null/null_snddma.c", - CODE_DIR + "/sys/con_log.c", - CODE_DIR + "/sys/con_passive.c", - CODE_DIR + "/sys/sys_main.c", - CODE_DIR + "/sys/sys_unix.c", - CODE_DIR + "/ui/ui_public.h", ## OpenGL rendering CODE_DIR + "/deepmind/headless_native_glimp.c", CODE_DIR + "/deepmind/glimp_common.h", CODE_DIR + "/deepmind/glimp_common.c", - ] + glob( - [ - CODE_DIR + "/botlib/*.c", - CODE_DIR + "/client/cl_*.c", - CODE_DIR + "/client/snd_*.c", - CODE_DIR + "/qcommon/*.c", - CODE_DIR + "/renderercommon/*.c", - CODE_DIR + "/renderergl1/*.c", - CODE_DIR + "/server/*.c", - ], - exclude = [ - CODE_DIR + "/renderergl1/tr_subs.c", - CODE_DIR + "/server/sv_rankings.c", - CODE_DIR + "/qcommon/vm_none.c", - CODE_DIR + "/qcommon/vm_powerpc*.c", - CODE_DIR + "/qcommon/vm_sparc.c", - ], - ), - hdrs = [ - "public/dmlab.h", - CODE_DIR + "/deepmind/context.h", - ] + glob( - [ - CODE_DIR + "/botlib/*.h", - CODE_DIR + "/client/*.h", - CODE_DIR + "/qcommon/*.h", - CODE_DIR + "/sys/*.h", - CODE_DIR + "/server/*.h", - CODE_DIR + "/renderercommon/*.h", - CODE_DIR + "/renderergl1/*.h", - ], - exclude = [ - CODE_DIR + "/client/fx_*.h", - CODE_DIR + "/qcommon/vm_powerpc_asm.h", - CODE_DIR + "/qcommon/vm_sparc.h", - CODE_DIR + "/renderercommon/tr_types.h", - CODE_DIR + "/renderercommon/tr_public.h", - CODE_DIR + "/renderergl1/tr_local.h", - ], - ), - copts = [ - "-std=c99", - "-fno-strict-aliasing", - ARCH_VAR, - STANDALONE_VAR, ], - defines = [ - "BOTLIB", - "_GNU_SOURCE", + hdrs = ["public/dmlab.h"], + copts = IOQ3_COMMON_COPTS, + defines = IOQ3_COMMON_DEFINES, + linkopts = [ + "-lGL", + "-lX11", ], - textual_hdrs = [ - CODE_DIR + "/renderercommon/tr_types.h", - CODE_DIR + "/renderercommon/tr_public.h", - CODE_DIR + "/renderergl1/tr_local.h", + textual_hdrs = IOQ3_COMMON_TEXTUAL_HDRS, + deps = IOQ3_COMMON_DEPS, +) + +cc_library( + name = "game_lib_headless_egl", + srcs = IOQ3_COMMON_SRCS + [ + CODE_DIR + "/deepmind/dmlab_connect.c", + CODE_DIR + "/null/null_input.c", + CODE_DIR + "/null/null_snddma.c", + + ## OpenGL rendering + CODE_DIR + "/deepmind/headless_egl_glimp.c", + CODE_DIR + "/deepmind/glimp_common.h", + CODE_DIR + "/deepmind/glimp_common.c", ], - deps = [ - ":qcommon_hdrs", - "//deepmind/include:context_headers", - "//third_party/rl_api:env_c_api", - "@jpeg_archive//:jpeg", - "@sdl_system//:sdl2", + hdrs = ["public/dmlab.h"], + copts = IOQ3_COMMON_COPTS, + defines = IOQ3_COMMON_DEFINES, + linkopts = [ + "-lEGL", + "-lGL", ], + textual_hdrs = IOQ3_COMMON_TEXTUAL_HDRS, + deps = IOQ3_COMMON_DEPS + ["//third_party/GL/util:egl_util"], ) ASSETS = [ "assets/default.cfg", "assets/q3config.cfg", -] + glob(["assets/game_scripts/**/*.lua"]) +] + glob([ + "assets/game_scripts/**/*.lua", + "assets/game_scripts/**/*.png", +]) MAPS = glob(["assets/maps/*.map"]) @@ -829,13 +739,14 @@ genrule( cmd = "cp -t $(@D)/baselab $(SRCS); " + "for s in $(SRCS); do " + " BM=$$(basename $${s}); M=$${BM/.map/}; " + - " $(location //deepmind/level_generation:compile_map_sh).runfiles/org_deepmind_lab/deepmind/level_generation/compile_map_sh $(@D)/baselab/$${M}; " + + " $(location //deepmind/level_generation:compile_map_sh).runfiles/org_deepmind_lab/deepmind/level_generation/compile_map_sh -a $(@D)/baselab/$${M}; " + "done", tools = [ "//:bspc", "//deepmind/level_generation:compile_map_sh", "//q3map2", ], + visibility = ["//testing:__subpackages__"], ) genrule( @@ -897,6 +808,7 @@ genrule( ], outs = ["baselab/vm.pk3"], cmd = "A=$$(pwd); mkdir $(@D)/vm; ln -s -r -t $(@D)/vm -- $(SRCS); (cd $(@D); zip -r $${A}/$(OUTS) -- vm)", + visibility = ["//testing:__subpackages__"], ) cc_binary( @@ -911,10 +823,7 @@ cc_binary( ":non_pk3_assets", ":vm_pk3", ], - linkopts = [ - "-lGL", - "-lm", - ], + linkopts = ["-lm"], deps = [ ":game_lib_sdl", "//deepmind/engine:callbacks", @@ -924,45 +833,48 @@ cc_binary( ) config_setting( - name = "dmlab_lib_sdl", - values = {"define": "headless=false"}, + name = "dmlab_glmode_egl", + values = {"define": "glmode=egl"}, ) config_setting( - name = "dmlab_headless_hw", - values = {"define": "headless=glx"}, + name = "dmlab_glmode_glx", + values = {"define": "glmode=glx"}, ) -config_setting( - name = "dmlab_headless_sw", - values = {"define": "headless=osmesa"}, +cc_binary( + name = "libdmlab_headless_hw.so", + linkopts = [ + "-Wl,--version-script", + ":dmlab.lds", + ], + linkshared = 1, + linkstatic = 1, + visibility = ["//testing:__subpackages__"], + deps = [ + ":dmlab.lds", + "//deepmind/engine:callbacks", + "//deepmind/engine:context", + "@zlib_archive//:zlib", + ] + select({ + "dmlab_glmode_egl": [":game_lib_headless_egl"], + "dmlab_glmode_glx": [":game_lib_headless_glx"], + "//conditions:default": [":game_lib_headless_egl"], + }), ) cc_binary( - name = "libdmlab.so", - linkopts = select({ - "//conditions:default": ["-lOSMesa"], - ":dmlab_headless_hw": [ - "-lGL", - "-lX11", - ], - ":dmlab_lib_sdl": [ - "-lGL", - "-lX11", - ], - }) + [ + name = "libdmlab_headless_sw.so", + linkopts = [ "-Wl,--version-script", ":dmlab.lds", ], linkshared = 1, linkstatic = 1, - deps = select({ - "//conditions:default": [":game_lib_headless_osmesa"], - ":dmlab_lib_sdl": [":game_lib_sdl"], - ":dmlab_headless_hw": [":game_lib_headless_glx"], - ":dmlab_headless_sw": [":game_lib_headless_osmesa"], - }) + [ + visibility = ["//testing:__subpackages__"], + deps = [ ":dmlab.lds", + ":game_lib_headless_osmesa", "//deepmind/engine:callbacks", "//deepmind/engine:context", "@zlib_archive//:zlib", @@ -973,14 +885,18 @@ cc_library( name = "dmlablib", srcs = ["public/dmlab_so_loader.cc"], hdrs = ["public/dmlab.h"], - copts = ["-DDMLAB_SO_LOCATION=\\\"libdmlab.so\\\""], data = [ ":assets", - ":libdmlab.so", + ":libdmlab_headless_hw.so", + ":libdmlab_headless_sw.so", "//deepmind/level_generation:compile_map_sh", ], linkopts = ["-ldl"], - deps = ["//third_party/rl_api:env_c_api"], + visibility = ["//testing:__subpackages__"], + deps = [ + ":level_cache_types", + "//third_party/rl_api:env_c_api", + ], ) cc_binary( @@ -1004,37 +920,46 @@ cc_binary( ], linkshared = 1, linkstatic = 1, + visibility = [ + "//python/pip_package:__subpackages__", + "//python/tests:__subpackages__", + ], deps = [ ":dmlablib", "@python_system//:python", ], ) -py_test( - name = "python_module_test", - srcs = ["python/dmlab_module_test.py"], - data = [":deepmind_lab.so"], - imports = ["python"], - main = "python/dmlab_module_test.py", -) - -py_binary( - name = "python_benchmark", - srcs = ["python/benchmark.py"], - data = [":deepmind_lab.so"], - main = "python/benchmark.py", -) - py_binary( - name = "random_agent", + name = "python_random_agent", srcs = ["python/random_agent.py"], data = [":deepmind_lab.so"], main = "python/random_agent.py", + visibility = ["//python/tests:__subpackages__"], ) -py_test( - name = "random_agent_test", - srcs = ["python/random_agent_test.py"], - main = "python/random_agent_test.py", - deps = [":random_agent"], +LOAD_TEST_SCRIPTS = [ + level_script[len("assets/game_scripts/"):-len(".lua")] + for level_script in glob(["assets/game_scripts/*.lua"]) +] + +SKIPPED_TESTS = [ + "concept_task_01_test", + "concept_task_03_test", +] + +test_suite( + name = "load_level_test", + tests = ["load_level_test_" + level_name for level_name in LOAD_TEST_SCRIPTS if level_name not in SKIPPED_TESTS], ) + +[ + cc_test( + name = "load_level_test_" + level_name, + size = "large", + args = [level_name], + deps = ["//testing:load_level_test_lib"], + ) + for level_name in LOAD_TEST_SCRIPTS + if level_name not in SKIPPED_TESTS +] diff --git a/README.md b/README.md index ddb07c1e..c1d340af 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# DeepMind Lab +# DeepMind Lab *DeepMind Lab* is a 3D learning environment based on id Software's [Quake III Arena](https://github.com/id-Software/Quake-III-Arena) via @@ -43,7 +43,7 @@ You can reach us at [lab@deepmind.com](mailto:lab@deepmind.com). ## Getting started on Linux -* Get [Bazel from bazel.io](http://bazel.io/docs/install.html). +* Get [Bazel from bazel.io](https://docs.bazel.build/versions/master/install.html). * Clone DeepMind Lab, e.g. by running @@ -52,14 +52,7 @@ $ git clone https://github.com/deepmind/lab $ cd lab ``` -* For a live example of a random agent, run - -```shell -lab$ bazel run :random_agent --define headless=false -- \ - --length=10000 --width=640 --height=480 -``` - -Here is some [more detailed build documentation](docs/build.md), +Here is some [more detailed build documentation](/docs/users/build.md), including how to install dependencies if you don't have them. ### Play as a human @@ -67,9 +60,13 @@ including how to install dependencies if you don't have them. To test the game using human input controls, run ```shell -lab$ bazel run :game -- --level_script tests/demo_map +lab$ bazel run :game -- --level_script=test_levels/empty_room_test --level_setting=logToStdErr=true +# or: +lab$ bazel run :game -- -l test_levels/empty_room_test -s logToStdErr=true ``` +Leave the `logToStdErr` setting off to disable most log output. + ### Train an agent *DeepMind Lab* ships with an example random agent in @@ -78,15 +75,15 @@ which can be used as a starting point for implementing a learning agent. To let this agent interact with DeepMind Lab for training, run ```shell -lab$ bazel run :random_agent +lab$ bazel run :python_random_agent ``` -The Python API for the agent-environment interaction is described -in [docs/python_api.md](docs/python_api.md). +The [Python API](/docs/users/python_api.md) +is used for agent-environment interactions. *DeepMind Lab* ships with different levels implementing different tasks. These -tasks can be configured using Lua scripts, -as described in [docs/lua_api.md](docs/lua_api.md). +tasks can be configured using Lua scripts, as described in the +[Lua API](/docs/developers/reference/lua_api.md). ----------------- @@ -104,14 +101,14 @@ with those projects are best fixed upstream and then merged into *DeepMind Lab*. * *q3map2* is taken from [github.com/TTimo/GtkRadiant](https://github.com/TTimo/GtkRadiant), - revision 8557f1820f8e0c7cef9d52a78b2847fa401a4a95. A few minor local + revision 69972f94582f0723c9ceaabf6751a911bf1fdcc3. A few minor local modifications add synchronization and use C99 constructs to replace formerly non-portable or undefined behaviour. We also expect this code to be stable. * *ioquake3* is taken from [github.com/ioquake/ioq3](https://github.com/ioquake/ioq3), - revision 0672905ef1b8f6ca219341e7252044dd727753dd. The code contains extensive + revision 690c5a4dac3c3d0d59ee271aadd8f19a29a9f338. The code contains extensive modifications and additions. We aim to merge upstream changes occasionally. We are very grateful to the maintainers of these repositories for all their hard diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md new file mode 100644 index 00000000..c89b9cd1 --- /dev/null +++ b/RELEASE_NOTES.md @@ -0,0 +1,32 @@ +# DeepMind Lab Release Notes + +## Current Release + +### New Features: + +1. Lua customizable themes. +2. String observations. +3. Add ability for script to generate events visible to the EnvCApi. +4. Updated EnvCApi to propagate error messages. +5. Added customization point to enable reading files from external sources. + +### Minor Improvements: + +1. Fixed ramp jump velocity in level lt_space_bounce_hard. +2. Fixed Lua function 'addScore' from module 'dmlab.system.game' to allow + negative scores added to a player. +3. Minor removal of undefined behaviour in the engine. +4. Change LuaSnippetEmitter methods to use table call conventions. +5. Added config variable for monochromatic lightmaps ('r_monolightmaps'). + Enabled by default. +6. Python module 'observation_spec' now returns current observation spec. +7. Added config variable to limit texture size ('r_textureMaxSize'). +8. Increased score range to include all 32 bit integers. +9. api:modifyTexture must now return whether the texture was modified. +10. Internal engine events are propagated to the script via gameEvent. +11. Added ability to adjust rewards. +12. Added ability to raycast between different points on the map. +13. Reduced inaccuracies related to angle conversion and normalization. +14. Added flag "sv_rateLimit" to disable rate limiting for networked games. + +## release-2016-12-06 Initial release diff --git a/WORKSPACE b/WORKSPACE index a15bffdb..5c0362ff 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -1,10 +1,15 @@ workspace(name = "org_deepmind_lab") -new_git_repository( - name = "googletest", - build_file = "googletest.BUILD", - commit = "d8fe70f477d8a99745b69f7650f75eacf96866f9", - remote = "https://github.com/google/googletest.git", +http_archive( + name = "com_google_googletest", + strip_prefix = "googletest-master", + urls = ["https://github.com/google/googletest/archive/master.zip"], +) + +http_archive( + name = "com_google_absl", + strip_prefix = "abseil-cpp-master", + urls = ["https://github.com/abseil/abseil-cpp/archive/master.zip"], ) new_http_archive( @@ -18,8 +23,8 @@ new_http_archive( new_http_archive( name = "glib_archive", build_file = "glib.BUILD", - strip_prefix = "glib-2.38.2", sha256 = "056a9854c0966a0945e16146b3345b7a82562a5ba4d5516fd10398732aea5734", + strip_prefix = "glib-2.38.2", url = "http://ftp.gnome.org/pub/gnome/sources/glib/2.38/glib-2.38.2.tar.xz", ) diff --git a/assets/game_scripts/common/color_bots.lua b/assets/game_scripts/common/color_bots.lua new file mode 100644 index 00000000..8daf3f6c --- /dev/null +++ b/assets/game_scripts/common/color_bots.lua @@ -0,0 +1,95 @@ +local colors = require 'common.colors' +local game = require 'dmlab.system.game' +local image = require 'dmlab.system.image' + +local SINGLE_BOT_SKIN_TEXTURE = 'models/players/crash_color/skin_base.tga' +local SATURATION = 1.0 +local VALUE = 1.0 + +local color_bots = {} + +color_bots.BOT_NAMES = { + 'Cygni', + 'Leonis', + 'Epsilon', + 'Cephei', + 'Centauri', + 'Draconis', +} + +color_bots.BOT_NAMES_COLOR = { + 'CygniColor', + 'LeonisColor', + 'EpsilonColor', + 'CepheiColor', + 'CentauriColor', + 'DraconisColor', +} + +local characterSkinData = nil + +local function characterSkins() + if not characterSkinData then + local playerDir = game:runFiles() .. '/baselab/game_scripts/player/' + characterSkinData = { + image.load(playerDir .. 'dm_character_skin_mask_a.png'), + image.load(playerDir .. 'dm_character_skin_mask_b.png'), + image.load(playerDir .. 'dm_character_skin_mask_c.png'), + } + end + return characterSkinData +end + +--[[ Returns a list of bots compatible with the addBot API. + +Keyword arguments: + + * count - Number [0, #color_bots.BOT_NAMES] - Number of bots to create. + * color - Boolean (false) - Whether to create the bots with custom colors. + * skill - Number - Skill level of bot. In range [1, 5] +]] +function color_bots:makeBots(kwargs) + assert(kwargs.count, 'Missing count') + local bots = {} + local botNames = kwargs.color and self.BOT_NAMES_COLOR or self.BOT_NAMES + for i, name in ipairs(botNames) do + if i == kwargs.count + 1 then + break + end + bots[#bots + 1] = {name = name, skill = kwargs.skill or 4.0} + end + return bots +end + +local playerSkin = nil + +-- Required to inform bot api of the skin texture used by the bots. Call +-- from modifyTexture API if color bots are desirded. +function color_bots:findSkin(name, texture) + if name == SINGLE_BOT_SKIN_TEXTURE then + playerSkin = texture:clone() + end +end + +-- Required to set the color of the bots. Call from call mapLoaded API. +function color_bots:colorizeBots(hue) + assert(playerSkin, 'findSkin must be called during modifyTexture.') + local skin = playerSkin:clone() + local skins = characterSkins() + local hueAngle = 360 / #skins + local shape = skin:shape() + for i, charachterSkin in ipairs(skins) do + local r, g, b = colors.hsvToRgb(hue, SATURATION, VALUE) + local skinC = charachterSkin:clone() + skinC:select(3, 1):mul(r / 255.0) + skinC:select(3, 2):mul(g / 255.0) + skinC:select(3, 3):mul(b / 255.0) + skin:cadd(skinC) + hue = (hue + hueAngle) % 360 + end + game:updateTexture(SINGLE_BOT_SKIN_TEXTURE, skin) + collectgarbage() + collectgarbage() +end + +return color_bots diff --git a/assets/game_scripts/common/colors.lua b/assets/game_scripts/common/colors.lua new file mode 100644 index 00000000..5b243662 --- /dev/null +++ b/assets/game_scripts/common/colors.lua @@ -0,0 +1,61 @@ +-- Utilities for color conversion. + +local colors = {} + +--[[ Converts an HSV color value to RGB. + +Conversion formula adapted from +http://en.wikipedia.org/wiki/HSV_color_space. Assumes h in [0, 360), s and v are +contained in the set [0, 1]. + +Returns r, g, b each in the set [0, 255]. +]] +function colors.hsvToRgb(h, s, v) + local i = math.floor(h / 60) + local f = h / 60 - i + local p = v * (1 - s) + local q = v * (1 - f * s) + local t = v * (1 - (1 - f) * s) + + i = i % 6 + local r, g, b + if i == 0 then r, g, b = v, t, p + elseif i == 1 then r, g, b = q, v, p + elseif i == 2 then r, g, b = p, v, t + elseif i == 3 then r, g, b = p, q, v + elseif i == 4 then r, g, b = t, p, v + elseif i == 5 then r, g, b = v, p, q + end + + return r * 255, g * 255, b * 255 +end + + +--[[ Converts an HSL color value to RGB. + +Based on formula at https://en.wikipedia.org/wiki/HSL_and_HSV#From_HSL. +Assumes h ∈ [0°, 360°), s ∈ [0, 1] and v ∈ [0, 1]. + +Returns r, g, b each in the set [0, 255]. +]] +function colors.hslToRgb(h, s, l) + local c = (1 - math.abs(2 * l - 1)) * s + local hprime = h / 60 + local x = c * (1 - math.abs(hprime % 2 - 1)) + + local m = l - 0.5 * c + c = c + m + x = x + m + local r, g, b + if hprime <= 1 then r, g, b = c, x, m + elseif hprime <= 2 then r, g, b = x, c, m + elseif hprime <= 3 then r, g, b = m, c, x + elseif hprime <= 4 then r, g, b = m, x, c + elseif hprime <= 5 then r, g, b = x, m, c + elseif hprime < 6 then r, g, b = c, m, x + end + + return r * 255, g * 255, b * 255 +end + +return colors diff --git a/assets/game_scripts/common/combinatorics.lua b/assets/game_scripts/common/combinatorics.lua new file mode 100644 index 00000000..099731ba --- /dev/null +++ b/assets/game_scripts/common/combinatorics.lua @@ -0,0 +1,109 @@ +-- Utility functions for combinatorics operations. +-- See https://en.wikipedia.org/wiki/Combinatorics + +local combinatorics = {} + +--[[ Returns "n choose k" = n! / (k!(n - k)!). + +This is the number of unique ways to sample without replacement k elements from +a bucket of n elements. + +See https://en.wikipedia.org/wiki/Combination. +--]] +function combinatorics.choose(k, n) + assert(0 <= k and k <= n) + + --[[ We can repeatedly use the identity, + C(n, k) = n/k C(n - 1, k - 1) + to calculate C(n, k) while avoiding large numbers (and corresponding + floating point error). + + C(n, k) = product[i=0..k-1] (n-i)/(k-i) * C(n - k, 0) + = product[i=0..k-1] (n-i)/(k-i) + + Moreover, since C(n, k) = C(n, n - k), we can ensure we loop at most n/2 + times by setting k' = min(k, n-k). Then, + C(n, k) = C(n, k') + --]] + local kPrime = math.min(k, n - k) + local accumulator = 1 + for i = 0, kPrime - 1 do + accumulator = accumulator * ((n - i) / (kPrime - i)) + end + return accumulator +end + +--[[ Return the `idx`th selection from an enumeration of all possible +selections {a,b} with 1 <= a < b <= n. + +`idx` is a number between 1 and C(n,2) = n*(n-1)/2. + +Given n items, there are C(n,2) = n*(n-1)/2 ways to select two of those +items. We enumerate all those ways and assign each an index from 1 to C(n,2). +We return the `idx`th pair from that enumeration. +--]] +function combinatorics.twoItemSelection(idx, n) + assert(1 <= idx and idx <= combinatorics.choose(2, n)) + + --[[ + First, enumerate possible combinatorics of {x,y} with 0 <= x <= y <= n-2 by + drawing them in the triangular configuration below. + + Then, use triangular numbers to map from i = idx-1 to {x,y} coordinates + (see https://en.wikipedia.org/wiki/Triangular_number). + + 0 1 2 3 . . n-2 + 0 * + 1 * * + 2 * * * + . * * * * + . * * i * * --- x <== The `i`th triangular point. + . * * * * * * We map it to coordinates {x,y}. + n-2 * * * * * * * + | + y + + If R is the number of points in first r rows, then + R = 1 + 2 + 3 + ... + r + R = r * (r + 1) / 2 + 0 = r^2 + r - 2R + r = [-1 + sqrt(1 - 4*1*(-2R))] / 2, by quadratic formula, + and since r must be positive + r = [-1 + sqrt(1 + 8R)] / 2 + r = -0.5 + sqrt(0.25 + 2R) + This is the "triangular root" of x, which is monotonically increasing, + so the floor of it will give us the closest row. + + x = floor( -0.5 + sqrt(2*i + 0.25) ) + + The column is given by subtracting the number of points in the first + x rows from i. + + y = i - [x * (x + 1) / 2] + --]] + local i = idx - 1 + local x = math.floor(-0.5 + math.sqrt(0.25 + 2 * i)) + local y = i - x * (x + 1) / 2 + + -- We want {a, b} where 1 <= a < b <= n, so we need to adjust the {x,y} + -- indices to match the coordinate system below. + -- + -- Note that `a` runs from 1..n-1, and `b` runs from 2..n because a < b. + -- + -- n . . . 4 3 2 + -- n-1 * + -- . * * + -- . * * * + -- . * * * * + -- 3 * *idx* * --- a <== The `idx`th triangular point. + -- 2 * * * * * * We map it to coordinates {a,b}. + -- 1 * * * * * * * + -- | + -- b + -- + local a = n - x - 1 + local b = n - y + return {a, b} +end + +return combinatorics diff --git a/assets/game_scripts/common/game_rewards.lua b/assets/game_scripts/common/game_rewards.lua new file mode 100644 index 00000000..f2b1ecc7 --- /dev/null +++ b/assets/game_scripts/common/game_rewards.lua @@ -0,0 +1,82 @@ +local events = require 'dmlab.system.events' +local tensor = require 'dmlab.system.tensor' + +local game_rewards = {} + +game_rewards.REASONS = { + -- `playerId` touched reward pickup. + 'PICKUP_REWARD', + + -- `playerId` touched goal pickup. + 'PICKUP_GOAL', + + -- Level triggered a reward at `playerId`. + 'TARGET_SCORE', + + -- `playerId` tagged self. + 'TAG_SELF', + + -- `playerId` tagged `otherPlayerId` + 'TAG_PLAYER', + + -- `playerId` picked up enemy flag. + 'CTF_FLAG_BONUS', + + -- `playerId` captured the opposing team's flag. + 'CTF_CAPTURE_BONUS', + + -- `playerId` is part of a team that has captured the opposing team's flag. + 'CTF_TEAM_BONUS', + + -- `playerId` tagged opponent(`otherPlayerId`) flag carrier. + 'CTF_FRAG_CARRIER_BONUS', + + -- `playerId` returned their team flag to the team's base. + 'CTF_RECOVERY_BONUS', + + -- `playerId` is on the same team as the flag carrier and tagged opponent + -- (`otherPlayerId`) who damaged our flag carrier. + 'CTF_CARRIER_DANGER_PROTECT_BONUS', + + -- `playerId` tagged opponent(`otherPlayerId`) while `playerId` or + -- `otherPlayerId` is near `playerId`'s flag. + 'CTF_FLAG_DEFENSE_BONUS', + + -- `playerId` tagged opponent(`otherPlayerId`) while `playerId` or + -- `otherPlayerId` is near `playerId`'s flag carrier. + 'CTF_CARRIER_PROTECT_BONUS', + + -- `playerId` returned the team flag just before payerId's team captured + -- the opposing team's flag. + 'CTF_RETURN_FLAG_ASSIST_BONUS', + + -- `playerId` tagged opponent team's flag carrier just before a capturing + -- event occurred. + 'CTF_FRAG_CARRIER_ASSIST_BONUS', +} + +function game_rewards:initScoreOveride(kwargs) + for _, reason in ipairs(self.REASONS) do + kwargs['reward.' .. reason] = false + kwargs['reward.red.' .. reason] = false + kwargs['reward.blue.' .. reason] = false + end +end + +function game_rewards:overrideScore(kwargs, rewardInfo) + local playerId = rewardInfo.playerId + local reason = rewardInfo.reason + local team = rewardInfo.team + local location = rewardInfo.location or {0, 0, 0} + local otherPlayerId = rewardInfo.otherPlayerId or -1 + local score = kwargs['reward.' .. team .. '.' .. reason] or + kwargs['reward.' .. reason] or rewardInfo.score + score = tonumber(score) + local d = tensor.DoubleTensor + events:add('reward', reason, team, d{score}, d{playerId + 1}, d(location), + d{otherPlayerId + 1}) + return score +end + + +return game_rewards diff --git a/assets/game_scripts/common/game_types.lua b/assets/game_scripts/common/game_types.lua new file mode 100644 index 00000000..a8eb38e9 --- /dev/null +++ b/assets/game_scripts/common/game_types.lua @@ -0,0 +1,9 @@ +-- These must match gametype_t found in 'engine/code/game/bg_public.h' +return { + FREE_FOR_ALL = 0, + TOURNAMENT = 1, + SINGLE_PLAYER_FREE_FOR_ALL = 2, + TEAM_DEATHMATCH = 3, + CAPTURE_THE_FLAG = 4, + ONE_FLAG_CAPTURE_THE_FLAG = 5, +} diff --git a/assets/game_scripts/common/geometric_pickups.lua b/assets/game_scripts/common/geometric_pickups.lua new file mode 100644 index 00000000..7b9892f8 --- /dev/null +++ b/assets/game_scripts/common/geometric_pickups.lua @@ -0,0 +1,103 @@ +local random = require 'common.random' +local pickups = require 'common.pickups' + +local geometric_pickups = {} + +-- Matches the 'fut_obj_*' models available in assets/models. +local OBJECTS = { + 'fut_obj_barbell_', + 'fut_obj_coil_', + 'fut_obj_cone_', + 'fut_obj_crossbar_', + 'fut_obj_cube_', + 'fut_obj_cylinder_', + 'fut_obj_doubleprism_', + 'fut_obj_glowball_', + 'fut_obj_potcone_', + 'fut_obj_prismjack_', + 'fut_obj_sphere_', + 'fut_obj_toroid_', +} + +local OBJECT_MODS = 3 +local EAT_REWARD_MIN = -1 +local EAT_REWARD_MAX = 1 + +-- Returns a table with the names of all of the available pickups. +function geometric_pickups.createPickupNames() + local pickupNames = {} + + for i = 1, #OBJECTS do + for j = 1, OBJECT_MODS do + pickupNames[#pickupNames + 1] = OBJECTS[i] .. string.format('%02d', j) + end + end + + return pickupNames +end + +-- Returns a table with all available objects as pickups (see common.pickups). +function geometric_pickups.createPickups() + local geoPickups = {} + + local pickupNames = geometric_pickups.createPickupNames() + for k, v in ipairs(pickupNames) do + geoPickups[v] = { + name = v, + classname = v, + model = 'models/' .. v .. '.md3', + quantity = 0, + type = pickups.type.REWARD + } + end + + return geoPickups +end + +-- Assigns random rewards (within supplied inclusive range) to pickups table. +function geometric_pickups.randomisePickupRewards( + pickupTable, minReward, maxReward) + minReward = minReward or EAT_REWARD_MIN + maxReward = maxReward or EAT_REWARD_MAX + assert(minReward <= maxReward) + + for k, v in pairs(pickupTable) do + v.quantity = random:uniformInt(0, maxReward - minReward) + minReward + end +end + +--[[ Returns a table with the requested number of pickup chosen from the list of +possible pickups. Returned table contains unique elements unless the number of +elements requested is greater than the size of the available elements, then +elements will be repeated. +]] +function geometric_pickups.randomUniquePickupNames(numPickups) + local pickupNames = geometric_pickups.createPickupNames() + assert(0 <= numPickups, "Invalid numPickups!") + random:shuffleInPlace(pickupNames, numPickups) + local randElements = {} + for i = 1, numPickups do + randElements[i] = pickupNames[(i - 1) % #pickupNames + 1] + end + return randElements +end + +--[[ Returns a shuffled list of pickups. List contains 'setSize' copies of +'numTypes' types of object, selected at random from 'allEdibles'. +]] +function geometric_pickups.randomPickups(allEdibles, numTypes, setSize) + local pickupKeys = geometric_pickups.randomUniquePickupNames(numTypes) + local pickupsList = {} + + for j = 1, #pickupKeys do + local obj_type = pickupKeys[j] + for i = 1, setSize do + pickupsList[#pickupsList + 1] = obj_type + end + end + random:shuffleInPlace(pickupsList) + + return pickupsList +end + +return geometric_pickups diff --git a/assets/game_scripts/common/helpers.lua b/assets/game_scripts/common/helpers.lua index 197088d5..d34ac3c2 100644 --- a/assets/game_scripts/common/helpers.lua +++ b/assets/game_scripts/common/helpers.lua @@ -1,34 +1,180 @@ -local random = require 'common.random' -- Common utilities. +local io = require 'io' + +local SECONDS_IN_MINUTE = 60 +local SECONDS_IN_HOUR = SECONDS_IN_MINUTE * 60 local helpers = {} --- Shuffles an array in place. (Uses the 'common.random'.) -function helpers.shuffleInPlace(array) - for i = 1, #array - 1 do - local j = random.uniformInt(i, #array) - array[j], array[i] = array[i], array[j] + +-- Returns an array of strings split according to single character separator. +-- Skips empty fields. +function helpers.split(str, sep) + local words = {} + for word in string.gmatch(str, '([^' .. sep .. ']+)') do + words[#words + 1] = word end + return words +end - return array +-- Converts a space-delimited string of numerical values to a table of numbers. +-- e.g. "1.000 2.00 3.00" becomes {1, 2, 3} +function helpers.spawnVarToNumberTable(spaceSeperatedString) + local result = {} + for v in string.gmatch(spaceSeperatedString, "%S+") do + local index = #result + 1 + result[index] = tonumber(v) + end + return result end --- Returns a shuffled copy of an array. (Uses the 'common.random'.) -function helpers.shuffle(array) - local ret = {} - for i, obj in ipairs(array) do - ret[i] = obj +--[[ Joins a path with a base directory. + +* pathJoin('base', 'path') => 'base/path' +* pathJoin('base/', 'path') => 'base/path' +* pathJoin('base', '/path') => '/path' +* pathJoin('base/', '/path') => '/path' +]] +function helpers.pathJoin(base, path) + if path:sub(1, 1) == '/' then + return path + elseif base:sub(#base) == '/' then + return base .. path + else + return base .. '/' .. path end - return helpers.shuffleInPlace(ret) end --- Returns an array of strings split according to single character separator. --- Skips empty fields. -function helpers.split(str, sep) - words = {} - for word in string.gmatch(str, '([^' .. sep .. ']+)') do - words[#words + 1] = word +function helpers.dirname(str) + return str:match(".-/.-") and str:gsub("(.*/)(.*)", "%1") or '' +end + +-- Returns whether a file exists by opening it for read. +function helpers.fileExists(file) + local f = io.open(file, "r") + if f ~= nil then + io.close(f) + return true + else + return false end - return words +end + +-- Given the name of a Lua module from game_scripts, e.g. 'common.helpers', +-- returns the full path to the source Lua file on disk, or nil. +function helpers.findFileInLuaPath(moduleName) + local moduleName = moduleName:gsub('[.]', '/') + for element in package.path:gmatch('[^;]+') do + if element:find('/game_scripts/') then + local path = element:gsub('[?]', moduleName) + if helpers.fileExists(path) then + return path + end + end + end + return nil +end + +--[[ Returns a shallow copy of a table. + +That is, copies the first level of key value pairs, but does not recurse down +into the table. +]] +function helpers.shallowCopy(input) + if input == nil then return nil end + assert(type(input) == 'table') + local output = {} + for k, v in pairs(input) do + output[k] = v + end + setmetatable(output, getmetatable(input)) + return output +end + +--[[ Returns a deep copy of a table. + +Performs a deep copy of the key-value pairs of a table, recursing for each +value, depth-first, up to the specified depth, or indefinitely if that parameter +is nil. +]] +function helpers.deepCopy(input, depth) + if type(input) == 'table' then + if depth == nil or depth > 0 then + local output = {} + local below = depth ~= nil and depth - 1 or nil + for k, v in pairs(input) do + output[k] = helpers.deepCopy(v, below) + end + setmetatable(output, helpers.deepCopy(getmetatable(input), below)) + return output + end + end + return input +end + +-- Naive recursive pretty-printer. +-- Prints the table hierarchically. Assumes all the keys are simple values. +function helpers.tostring(input, spacing, limit) + limit = limit or 5 + if limit < 0 then + return '' + end + spacing = spacing or '' + if type(input) == 'table' then + local res = '{\n' + for k, v in pairs(input) do + if type(k) ~= 'string' or string.sub(k, 1, 2) ~= '__' then + res = res .. spacing .. ' [\'' .. tostring(k) .. '\'] = ' .. + helpers.tostring(v, spacing .. ' ', limit - 1) + end + end + return res .. spacing .. '}\n' + else + return tostring(input) .. '\n' + end +end + +--[[ Convert a number of seconds into a string of the format hhh:mm:ss, where + +* 'hhh' represents the hours, +* 'mm' the minutes, and +* 'ss' the seconds. + +Omits 'hhh:' if hours is 0, and also 'mm:' if hours and minutes are zero. +Rounds-up timeSeconds to the closest second. +--]] +function helpers.secondsToTimeString(timeSeconds) + local s = math.ceil(timeSeconds) + if s < SECONDS_IN_MINUTE then + return string.format('%d', s) + elseif s < SECONDS_IN_HOUR then + return string.format('%d:%.2d', + s / SECONDS_IN_MINUTE, s % SECONDS_IN_MINUTE) + else + return string.format('%d:%.2d:%.2d', + s / SECONDS_IN_HOUR, + s / SECONDS_IN_MINUTE % SECONDS_IN_MINUTE, + s % SECONDS_IN_MINUTE) + end +end + +--[[ Converts from a string to its most likely type. + +Numbers are returned as numbers. +"true" and "false" are returned as booleans. +Everything else is unchanged. +]] +function helpers.fromString(input) + local number = tonumber(input) + if number ~= nil then + return number + end + if input == "true" then + return true + end + if input == "false" then + return false + end + return input end return helpers diff --git a/assets/game_scripts/common/human_recognisable_pickups.lua b/assets/game_scripts/common/human_recognisable_pickups.lua new file mode 100644 index 00000000..8b85dc0e --- /dev/null +++ b/assets/game_scripts/common/human_recognisable_pickups.lua @@ -0,0 +1,376 @@ +-- Objects created by this code will only have their correct appearance if the +-- level has decorators/human_recognisable_pickups applied. + +local combinatorics = require 'common.combinatorics' +local helpers = require 'common.helpers' +local image = require 'dmlab.system.image' +local pickups = require 'common.pickups' +local random = require 'common.random' +local set = require 'common.set' +local tensor = require 'dmlab.system.tensor' +local game = require 'dmlab.system.game' + +local PATTERN_DIR = game:runFiles() .. '/baselab/game_scripts/patterns/' + +local hrp = {} + +local PREFIX = 'HRP_' + +local SHAPES = { + 'apple2', + 'ball', + 'balloon', + 'banana', + 'bottle', + 'cake', + 'can', + 'car', + 'cassette', + 'chair', + 'cherries', + 'cow', + 'flower', + 'fork', + 'fridge', + 'guitar', + 'hair_brush', + 'hammer', + 'hat', + 'ice_lolly', + 'jug', + 'key', + 'knife', + 'ladder', + 'mug', + 'pencil', + 'pig', + 'pincer', + 'plant', + 'saxophone', + 'shoe', + 'spoon', + 'suitcase', + 'tennis_racket', + 'tomato', + 'toothbrush', + 'tree', + 'tv', + 'wine_glass', + 'zebra', +} + +local SHAPES_SET = set.Set(SHAPES) + +local TWO_COLOR_PATTERNS = { + 'chequered', + 'crosses', + 'diagonal_stripe', + 'discs', + 'hex', + 'pinstripe', + 'spots', + 'swirls', +} + +local PATTERNS = helpers.shallowCopy(TWO_COLOR_PATTERNS) +PATTERNS[#PATTERNS + 1] = 'solid' + +local PATTERNS_SET = set.Set(PATTERNS) + +local SCALES = { + small = 0.62, + medium = 1.0, + large = 1.62, +} + +-- Per object scaling for objects where the default large size exceeds the +-- bounds of a maze cell. +local CUSTOM_SCALES = { + banana = {large = 1.58}, + car = {large = 1.02, medium = 0.8}, + cassette = {large = 1.45, medium = 0.95}, + chair = {large = 1.15, medium = 0.84}, + cow = {large = 1.06, medium = 0.81}, + hat = {large = 1.55}, + pig = {large = 1.52}, +} + +local COLORS = { + {0, 0, 0}, + {0, 0, 170}, + {0, 170, 0}, + {0, 170, 170}, + {170, 0, 0}, + {170, 0, 170}, + {170, 85, 0}, + {170, 170, 170}, + {85, 85, 85}, + {85, 85, 255}, + {85, 255, 85}, + {85, 255, 255}, + {255, 85, 85}, + {255, 85, 255}, + {255, 255, 85}, + {255, 255, 255}, +} + +-- Store loaded textures. +hrp._patternTextures = {} + +-- Convert a scale name to a number, accounting for per-shape overrides. +local function scaleNameToNumber(scale, shape) + return CUSTOM_SCALES[shape] and CUSTOM_SCALES[shape][scale] or SCALES[scale] +end + +-- Returns normalised float texture, loading once from disk if need be. +function hrp.getPatternTexture(patternName, width, height) + if not PATTERNS_SET[patternName] then + error("Unknown pattern: " .. tostring(patternName)) + end + + if not hrp._patternTextures[patternName] then + local floatImage + if patternName == 'solid' then + floatImage = tensor.FloatTensor(width, height, 4):fill(1.0) + else + local path = PATTERN_DIR .. patternName .. '_d.png' + local byteImage = image.load(path) + local shape = byteImage:shape() + if shape[1] ~= width or shape[2] ~= height then + byteImage = image.scale(byteImage, width, height) + end + floatImage = byteImage:float():mul(1 / 255) + end + assert(floatImage:shape()[3] == 4, + 'Pattern textures must be in RGBA format.') + hrp._patternTextures[patternName] = floatImage + end + return hrp._patternTextures[patternName] +end + +function hrp.create(kwargs) + local shape = kwargs.shape or error("Missing shape") + if not SHAPES_SET[shape] then error("Unknown shape: " .. tostring(shape)) end + + local id = hrp._nextId + hrp._nextId = hrp._nextId + 1 + + local scale = 1.0 + if kwargs.scale then + if type(kwargs.scale) == 'number' then + scale = kwargs.scale + else + assert(type(kwargs.scale) == 'string') + scale = scaleNameToNumber(kwargs.scale, shape) + assert(scale, '"' .. tostring(kwargs.scale) .. '" is not a valid scale.') + end + end + + local transformSuffix = scale ~= 1 and '%scale{' .. scale .. '}' or '' + + local obj = { + name = shape, + classname = shape, + model = string.format('%s%d:models/hr_%s.md3%s', + PREFIX, id, shape, transformSuffix), + quantity = kwargs.quantity or 0, + type = pickups.type.REWARD, + tag = kwargs.moveType or pickups.moveType.BOB, + } + -- Register the requested configuration against the ID for later lookup by + -- modifyTexture. + hrp._objectConfigs[id] = { + pattern = kwargs.pattern or error("Missing pattern"), + color1 = kwargs.color1 or error("Missing color1"), + color2 = kwargs.color2 or error("Missing color2"), + } + return obj +end + +function hrp.replaceModelName(modelName) + if modelName:sub(1, #PREFIX) == PREFIX then + local prefixTexture, newModelName = modelName:match('(.*:)(.*)') + return newModelName, prefixTexture + end +end + +function hrp.replaceTextureName(textureName) + if textureName:sub(1, #PREFIX) == PREFIX then + -- Strip prefix and id. + return textureName:match('.*:(.*)') + end +end + +local function pattern(patternChannel, color1, color2) + local invChannel = patternChannel:clone():mul(-1):add(1) + return patternChannel:clone():mul(color1):cadd(invChannel:mul(color2)) +end + +function hrp.applyPatternAndColors(objTexture, patternTexture, color1, color2) + local floatObjTexture = objTexture:float() + local r = floatObjTexture:select(3, 1) + local g = floatObjTexture:select(3, 2) + local b = floatObjTexture:select(3, 3) + local a = floatObjTexture:select(3, 4):clone():mul(1 / 255) + local invA = a:clone():mul(-1):add(1) + + -- Generate colorised copies of the pattern for each channel. + local patternR = pattern(patternTexture:select(3, 1), color1[1], color2[1]) + local patternG = pattern(patternTexture:select(3, 2), color1[2], color2[2]) + local patternB = pattern(patternTexture:select(3, 3), color1[3], color2[3]) + + -- Combine original texture with pattern according to original alpha. + r:cmul(invA):cadd(patternR:cmul(a)) + g:cmul(invA):cadd(patternG:cmul(a)) + b:cmul(invA):cadd(patternB:cmul(a)) + + objTexture:copy(floatObjTexture:byte()) + + collectgarbage() + collectgarbage() +end + +function hrp.modifyTexture(textureName, texture) + if textureName:sub(1, #PREFIX) == PREFIX then + local id = tonumber(textureName:match('(%d+):')) + local config = hrp._objectConfigs[id] + local shape = texture:shape() + local patternTexture = hrp.getPatternTexture(config.pattern, shape[1], + shape[2]) + hrp.applyPatternAndColors(texture, patternTexture, config.color1, + config.color2) + return true + end + return false +end + +function hrp.shapes() + return SHAPES +end + +function hrp.patterns() + return PATTERNS +end + +function hrp.twoColorPatterns() + return TWO_COLOR_PATTERNS +end + +function hrp.colors() + return COLORS +end + + +-- Return an array of n object specifications, all of which have a unique set +-- of colors, shapes, and patterns. +function hrp.uniquePickups(n) + local shapes = hrp.shapes() + local patterns = hrp.twoColorPatterns() + local colors = hrp.colors() + + return hrp.specifyUniquePickups(n, shapes, patterns, colors) +end + + +-- Return an array of n object specifications, all of which have a unique set +-- of colors, shapes, and patterns. +function hrp.specifyUniquePickups(n, shapes, patterns, colors, colorNames) + -- There are `count` unique pickups: #shapes * #patterns * choose(2, #colors) + -- where choose(2, #colors) is the number of ways to select two different + -- colors, ignoring order. + local count = #shapes * #patterns * combinatorics.choose(2, #colors) + assert(n <= count, "Requesting more unique pickups than can be generated.") + assert(colorNames == nil or #colorNames == #colors, + "Must have as many color names as colors.") + + local result = {} + local idGenerator = random:shuffledIndexGenerator(count) + for i = 1, n do + -- id represents one of the unique pickups. 0 <= id < count. + -- We will convert id to its shape, pattern, and two colors. + local id = idGenerator() - 1 + + -- Modulo-out the shape and pattern dimensions from id. + local shape = (id % #shapes) + 1 + id = math.floor(id / #shapes) + + local pattern = (id % #patterns) + 1 + id = math.floor(id / #patterns) + + -- Remaining id value is between 0 and C(#colors,2) - 1. + -- colors[0] < colors[1], so randomly swap them for more variability. + local colorPair = combinatorics.twoItemSelection(id + 1, #colors) + local swap = random:uniformInt(0, 1) == 0 + local color1 = swap and colorPair[2] or colorPair[1] + local color2 = swap and colorPair[1] or colorPair[2] + result[i] = { + shape = shapes[shape], + pattern = patterns[pattern], + color1 = colors[color1], + color2 = colors[color2] + } + if colorNames then + result[i].colorName1 = colorNames[color1] + result[i].colorName2 = colorNames[color2] + end + end + return result +end + +-- Return an array of n object specifications, all of which have a unique +-- shape, and which also have patterns of colors applied. +function hrp.uniquelyShapedPickups(n) + local shapes = hrp.shapes() + local patterns = hrp.twoColorPatterns() + local colors = hrp.colors() + + return hrp.specifyUniquelyShapedPickups(n, shapes, patterns, colors) +end + +-- Return an array of n object specifications, all of which have a unique +-- shape, and which also have patterns of colors applied. +function hrp.specifyUniquelyShapedPickups(n, shapes, patterns, colors, + colorNames) + assert(n <= #shapes, "Requesting more unique pickups than can be generated.") + assert(colorNames == nil or #colorNames == #colors, + "Must have as many color names as colors.") + + local result = {} + local idGenerator = random:shuffledIndexGenerator(#shapes) + for i = 1, n do + -- id represents one of the unique pickups. 0 <= id < count. + -- We will convert id to its shape, pattern, and two colors. + local shape = idGenerator() + + local pattern = random:uniformInt(1, #patterns) + local colorPairIndex = + random:uniformInt(1, combinatorics.choose(2, #colors) - 1) + + -- Remaining id value is between 0 and C(#colors,2) - 1. + -- colors[0] < colors[1], so randomly swap them for more variability. + local colorPair = combinatorics.twoItemSelection(colorPairIndex, #colors) + local swap = random:uniformInt(0, 1) == 0 + local color1 = swap and colorPair[2] or colorPair[1] + local color2 = swap and colorPair[1] or colorPair[2] + result[i] = { + shape = shapes[shape], + pattern = patterns[pattern], + color1 = colors[color1], + color2 = colors[color2] + } + if colorNames then + result[i].colorName1 = colorNames[color1] + result[i].colorName2 = colorNames[color2] + end + end + return result +end + +function hrp.reset() + hrp._nextId = 1 + hrp._objectConfigs = {} +end + +hrp.reset() + +return hrp diff --git a/assets/game_scripts/common/inventory.lua b/assets/game_scripts/common/inventory.lua new file mode 100644 index 00000000..098f5a09 --- /dev/null +++ b/assets/game_scripts/common/inventory.lua @@ -0,0 +1,166 @@ +-- Enums extracted from "engine/code/qcommon/q_shared.h" + +local inventory = {} + +inventory.GADGETS = { + IMPULSE = 2, -- Contact gadget. + RAPID = 3, -- Rapid fire gadget. + ORB = 6, -- Area damage gadget. (Knocks players) + BEAM = 7, -- Accurate and very rapid fire beam. + DISC = 8, -- Powerful but long period between firing. +} + +inventory.UNLIMITED = -1 + +inventory.POWERUPS = { + RED_FLAG = 8, + BLUE_FLAG = 9, +} + +local function gadgetsToStat(gadgets) + local result = 0 + for i, gadget in ipairs(gadgets) do + result = result + 2 ^ (gadget - 1) + end + return result +end + +local function statToGadgets(stat) + local result = {} + local counter = 1 + while stat > 0 do + local old = stat / 2 + local new = math.floor(old) + if old ~= new then + result[#result + 1] = counter + end + counter = counter + 1 + stat = new + end + return result +end + +local STATS = { + HEALTH = 1, -- Health of player + GADGETS = 3, -- Mask of current gadgets held. + ARMOR = 4, -- Quantity of armour. + MAX_HEALTH = 7, -- If health is greater than this value it decreases to it + -- over time. +} + +local View = {} +local ViewMT = {__index = View} + +function View:gadgetAmount(gadget) + return self._loadOut.amounts[gadget] +end + +function View:setGadgetAmount(gadget, amount) + self._loadOut.amounts[gadget] = amount +end + +-- Returns list of gadgets. +function View:gadgets() + return statToGadgets(self._loadOut.stats[STATS.GADGETS]) +end + +-- Sets list of gadgets. +function View:setGadgets(gadgets) + self._loadOut.stats[STATS.GADGETS] = gadgetsToStat(gadgets) +end + +-- Adds gadget with optional amount. +function View:addGadget(gadget, amount) + if amount then + self:setGadgetAmount(gadget, amount) + end + local gadgets = self:gadgets() + for i, v in gadgets do + if v == gadget then + return + end + end + gadgets[#gadgets + 1] = gadget + self:setGadgets(gadgets) +end + +-- Removes a gadget if present, sets the gadgets amount to 0. +function View:removeGadget(gadget) + local gadgets = self:gadgets() + local newGadgets = {} + for i, v in gadgets do + if v ~= gadget then + newGadgets[#newGadgets + 1] = v + end + end + self:setGadgets(newGadgets) + self:setGadgetAmount(gadget, 0) +end + +-- Returns whether powerup is active. +function View:hasPowerUp(powerUp) + return self._loadOut.powerups[powerUp] ~= 0 +end + +-- Returns player's armor +function View:armor() + return self._loadOut.stats[STATS.ARMOR] +end + +-- Sets player's armor +function View:setArmor(amount) + self._loadOut.stats[STATS.ARMOR] = amount +end + +-- Gets player's health. +function View:health() + return self._loadOut.stats[STATS.HEALTH] +end + +-- Sets player's health. +function View:setHealth(amount) + self._loadOut.stats[STATS.HEALTH] = amount +end + +-- Gets player's max health. If health() > maxHealth() health will reduce until +-- it matches maxHealth(). +function View:maxHealth() + return self._loadOut.stats[STATS.MAX_HEALTH] +end + +-- Sets player's max health. +function View:setMaxHealth(amount) + self._loadOut.stats[STATS.MAX_HEALTH] = amount +end + +-- Returns player's eye position in world units. +function View:eyePos() + local x, y, z = unpack(self._loadOut.position) + return {x, y, z + self._loadOut.height} +end + +-- Returns players view direction in Euler angles degrees. +function View:eyeAngles() + return self._loadOut.angles +end + +-- Returns players id. +function View:playerId() + return self._loadOut.playerId +end + +-- Returns players gadget. +function View:gadget() + return self._loadOut.gadget +end + +function View:loadOut() + return self._loadOut +end + +function inventory.View(loadOut) + return setmetatable({_loadOut = loadOut}, ViewMT) +end + + +return inventory diff --git a/assets/game_scripts/common/make_map.lua b/assets/game_scripts/common/make_map.lua index d46d56a8..e3dc0aff 100644 --- a/assets/game_scripts/common/make_map.lua +++ b/assets/game_scripts/common/make_map.lua @@ -1,36 +1,53 @@ local map_maker = require 'dmlab.system.map_maker' - -local LEVEL_DATA = '/tmp/dmlab_level_data' +local random = require 'common.random' +local themes = require 'themes.themes' +local texture_sets = require 'themes.texture_sets' local make_map = {} -local pickups = { +local PICKUPS = { A = 'apple_reward', G = 'goal', } -function make_map.makeMap(mapName, mapEntityLayer, mapVariationsLayer) - os.execute('mkdir -p ' .. LEVEL_DATA .. '/baselab') - assert(mapName) +local SKYBOX_TEXTURE_NAME = 'map/lab_games/sky/lg_sky_03' + +function make_map.makeMap(kwargs) + assert(kwargs.mapName) + local skyboxTextureName = nil + if kwargs.useSkybox then + skyboxTextureName = SKYBOX_TEXTURE_NAME + end map_maker:mapFromTextLevel{ - entityLayer = mapEntityLayer, - variationsLayer = mapVariationsLayer, - outputDir = LEVEL_DATA .. '/baselab', - mapName = mapName, - callback = function(i, j, c, maker) - if pickups[c] then - return maker:makeEntity(i, j, pickups[c]) + entityLayer = kwargs.mapEntityLayer, + variationsLayer = kwargs.mapVariationsLayer, + mapName = kwargs.mapName, + allowBots = kwargs.allowBots, + skyboxTextureName = skyboxTextureName, + theme = kwargs.theme or themes.fromTextureSet{ + textureSet = kwargs.textureSet or texture_sets.MISHMASH, + decalFrequency = kwargs.decalFrequency, + floorModelFrequency = kwargs.floorModelFrequency, + }, + callback = kwargs.callback or function(i, j, c, maker) + local pickup = kwargs.pickups and kwargs.pickups[c] or PICKUPS[c] + if pickup then + return maker:makeEntity{ + i = i, + j = j, + classname = pickup, + } end end } - return mapName -end - -function make_map.commandLine(old_command_line) - return old_command_line .. '+set sv_pure 0 +set fs_steampath ' .. LEVEL_DATA + return kwargs.mapName end function make_map.seedRng(value) map_maker:randomGen():seed(value) end +function make_map.random() + return random(map_maker:randomGen()) +end + return make_map diff --git a/assets/game_scripts/common/pickups.lua b/assets/game_scripts/common/pickups.lua index 80de5789..62420217 100644 --- a/assets/game_scripts/common/pickups.lua +++ b/assets/game_scripts/common/pickups.lua @@ -1,68 +1,75 @@ local pickups = {} +-- Must match itemType_t in engine/code/game/bg_public.h. pickups.type = { - kInvalid = 0, - kWeapon = 1, - kAmmo = 2, - kArmor = 3, - kHealth = 4, - kPowerUp = 5, - kHoldable = 6, - kPersistant_PowerUp = 7, - kTeam = 8, - kReward = 9, - kGoal = 10 + INVALID = 0, + WEAPON = 1, + AMMO = 2, + ARMOR = 3, + HEALTH = 4, + POWER_UP = 5, + HOLDABLE = 6, + PERSISTANT_POWERUP = 7, + TEAM = 8, + REWARD = 9, + GOAL = 10 +} + +-- Must match reward_mv_t in engine/code/game/bg_public.h. +pickups.moveType = { + BOB = 0, + STATIC = 1 } pickups.defaults = { apple_reward = { name = 'Apple', - class_name = 'apple_reward', - model_name = 'models/apple.md3', + classname = 'apple_reward', + model = 'models/apple.md3', quantity = 1, - type = pickups.type.kReward + type = pickups.type.REWARD }, lemon_reward = { name = 'Lemon', - class_name = 'lemon_reward', - model_name = 'models/lemon.md3', + classname = 'lemon_reward', + model = 'models/lemon.md3', quantity = -1, - type = pickups.type.kReward + type = pickups.type.REWARD }, strawberry_reward = { name = 'Strawberry', - class_name = 'strawberry_reward', - model_name = 'models/strawberry.md3', + classname = 'strawberry_reward', + model = 'models/strawberry.md3', quantity = 2, - type = pickups.type.kReward + type = pickups.type.REWARD }, fungi_reward = { name = 'Fungi', - class_name = 'fungi_reward', - model_name = 'models/toadstool.md3', + classname = 'fungi_reward', + model = 'models/toadstool.md3', quantity = -10, - type = pickups.type.kReward + type = pickups.type.REWARD }, watermelon_goal = { name = 'Watermelon', - class_name = 'watermelon_goal', - model_name = 'models/watermelon.md3', + classname = 'watermelon_goal', + model = 'models/watermelon.md3', quantity = 20, - type = pickups.type.kGoal + type = pickups.type.GOAL }, goal = { name = 'Goal', - class_name = 'goal', - model_name = 'models/goal_object_02.md3', + classname = 'goal', + model = 'models/goal_object_02.md3', quantity = 10, - type = pickups.type.kGoal + type = pickups.type.GOAL }, - goal_mango = { + mango_goal = { name = 'Mango', - class_name = 'goal', - model_name = 'models/mango.md3', + classname = 'mango_goal', + model = 'models/mango.md3', quantity = 100, - type = pickups.type.kGoal + type = pickups.type.GOAL } } diff --git a/assets/game_scripts/common/position_trigger.lua b/assets/game_scripts/common/position_trigger.lua new file mode 100644 index 00000000..2b3302d1 --- /dev/null +++ b/assets/game_scripts/common/position_trigger.lua @@ -0,0 +1,156 @@ +local maze_generation = require 'dmlab.system.maze_generation' + +local PositionTrigger = {} +local PositionTriggerMT = {__index = PositionTrigger} + +--[[ Create a position trigger class that starts at currentTime, if specified. + +You must call `update(currentPosition)` whenever the `currentPosition` changes. +This call will make any callbacks on any triggers that have fired with the new +position. +--]] +function PositionTrigger.new() + return setmetatable({_triggers = {}}, PositionTriggerMT) +end + +--[[ Start trigger `kwargs.name` that will fire when currentPosition overlaps +a square in `kwargs.maze` that matches `kwargs.triggerWhenEnter` or +`kwargs.triggerWhenExit`. + +Once the trigger fires, `kwargs.callback` will be called and the trigger will be +removed unless `true` is returned. + +Keyword arguments: + +* `name` (string) The name of the trigger. +* `maze` (string) A text layout of the maze. +* `triggerWhenEnter` (character) The character in `maze` that sets off the + trigger, the first time the position is on that character in `maze`. +* `triggerWhenExit` (character) The character in `maze` that sets off the + trigger, when position stops being that character in `maze`. +* `callback` (function) Callback that's called when the trigger goes off. + If the callback returns `true` then the trigger is re-engaged to go off + under the same enter/exit conditions. By default, however, the trigger is + removed. + +Example: Print a happy message in 2 seconds. + +```Lua +local PositionTrigger = require 'common.position_trigger' + +local HAPPINESS_LAYER = [ [ +..LL.LL.. +.L..L..L. +..L...L.. +...L.L... +....L.... +] ] + +myTriggers = PositionTrigger.new() +myTriggers:start{ + name = 'my happiness trigger', + maze = HAPPINESS_LAYER, + triggerWhenEnter = 'L', + callback = function() print('HAPPINESS!') io.flush() end, +} + +function api:myFunctionCalledEveryFrame(currentPosition) + myPositionTrigger:update(currentPosition) +end + +``` +--]] +function PositionTrigger:start(kwargs) + assert(kwargs.name) + assert(kwargs.maze) + assert(kwargs.triggerWhenExit or kwargs.triggerWhenEnter) + assert(kwargs.callback) + local sizeI, sizeJ = self:mazeSize() + self._triggers[kwargs.name] = { + maze = maze_generation:mazeGeneration{entity = kwargs.maze}, + triggerWhenExit = kwargs.triggerWhenExit, + triggerWhenEnter = kwargs.triggerWhenEnter, + callback = kwargs.callback, + } + + -- All mazes must have the same size, since `update(currentPosition)` assumes + -- the same maze for all position triggers. + assert(sizeI == nil or + self._triggers[kwargs.name].maze:size() == sizeI, sizeJ) +end + +-- Remove `name` from the active triggers. +function PositionTrigger:remove(name) + self._triggers[name] = nil +end + +-- Remove all triggers, but maintain the current position. +function PositionTrigger:removeAll() + self._triggers = {} +end + +-- Returns true if there exists an active trigger called `name`. +function PositionTrigger:exists(name) + return self._triggers[name] ~= nil +end + +-- All mazes must have the same size. Return the size of the first trigger's +-- maze, or nil if no triggers exist. +function PositionTrigger:mazeSize() + for _, v in pairs(self._triggers) do + return v.maze:size() + end + return nil, nil +end + +-- Return the size of the first text maze, which has to be consistent for all +-- triggers. +function PositionTrigger:mazeCoordinatesFromWorldPosition(worldPosition) + for _, v in pairs(self._triggers) do + return v.maze:fromWorldPos(worldPosition[1], worldPosition[2]) + end + return nil, nil +end + +--[[ Provide the new current position in world space. +Check if any triggers have fired. +For triggers that have fired, call their callbacks and remove them unless +the callback returns `true`. Must be called manually by the owner of this class. +--]] +function PositionTrigger:update(currentPosition) + -- If we have no triggers, or if the maze coordinates haven't changed, then + -- there is nothing to do. + local nextI, nextJ = self:mazeCoordinatesFromWorldPosition(currentPosition) + if nextI == nil then return end + if self._currentI == nextI and self._currentJ == nextJ then return end + + -- Check if we've transitioned cell types. If so, see if we've triggered + -- the enter or exit criteria. If so, make callback. + local triggersToRemove = {} + for name, v in pairs(self._triggers) do + local currentCell = self._currentI and + v.maze:getEntityCell(self._currentI, self._currentJ) + local nextCell = v.maze:getEntityCell(nextI, nextJ) + if currentCell ~= nextCell then + if (v.triggerWhenExit and v.triggerWhenExit == currentCell) or + (v.triggerWhenEnter and v.triggerWhenEnter == nextCell) then + local persist = v.callback() + if not persist then + triggersToRemove[#triggersToRemove + 1] = name + end + end + end + end + + -- Remove any triggers that have fired. + for _, name in pairs(triggersToRemove) do + self:remove(name) + end + + -- Update our current maze coordinates. + self._currentI = nextI + self._currentJ = nextJ +end + +return PositionTrigger + diff --git a/assets/game_scripts/common/random.lua b/assets/game_scripts/common/random.lua index 14d86f9f..b81153ce 100644 --- a/assets/game_scripts/common/random.lua +++ b/assets/game_scripts/common/random.lua @@ -3,28 +3,203 @@ local sys_random = require 'dmlab.system.random' +local MAP_COUNT = 100000 +local THEME_COUNT = 1000 + local random = {} --- Set the seed of the underlying pseudo-random-bit generator. The argument --- may be a number or a string representation of a number. Beware of precision --- loss when using very large numeric values. --- --- It is probably useful to call this function with the per-episode seed in --- the "init" callback so that episodes play out reproducibly. -function random.seed(value) - sys_random:seed(value) +--[[ Set the seed of the underlying pseudo-random-bit generator. The argument +may be a number or a string representation of a number. Beware of precision loss +when using very large numeric values. + +It is probably useful to call this function with the per-episode seed in the +"start" callback so that episodes play out reproducibly. ]] +function random:seed(value) + return self._rng:seed(value) +end + +-- Returns an integer sampled uniformly at random from the closed range +-- [1, MAP_COUNT]. +function random:mapGenerationSeed() + return self._rng:uniformInt(1, MAP_COUNT) +end + + +-- Returns an integer sampled uniformly at random from the closed range +-- [1, THEME_COUNT]. +function random:themeGenerationSeed() + return self._rng:uniformInt(1, THEME_COUNT) end -- Returns an integer sampled uniformly at random from the closed range -- [lower, upper]. -function random.uniformInt(lower, upper) - return sys_random:uniformInt(lower, upper) +function random:uniformInt(lower, upper) + return self._rng:uniformInt(lower, upper) end -- Returns a real number sampled uniformly at random from the half-open range -- [lower, upper). -function random.uniformReal(lower, upper) - return sys_random:uniformReal(lower, upper) +function random:uniformReal(lower, upper) + return self._rng:uniformReal(lower, upper) +end + +-- Returns an integer in the range [1, n] where the probability of each +-- integer i is the ith weight divided by the sum of the n weights. +function random:discreteDistribution(weights) + return self._rng:discreteDistribution(weights) +end + +-- Returns a real number sampled from the random distrubution centered around +-- mean with standard distribution stddev. +function random:normal(mean, stddev) + return self._rng:normal(mean, stddev) +end + +-- Returns an 8-bit triplet representing a random RGB color: +-- {0~255, 0~255, 0~255}. +function random:color() + local function uniform255() return self._rng:uniformInt(0, 255) end + return {uniform255(), uniform255(), uniform255()} +end + +-- Returns an element sampled uniformly at random from a table. You can specify +-- a consecutive part of the table to sample by specifying startIndex and +-- endIndex. By default the entire table will be used. +function random:choice(t, startIndex, endIndex) + t = t or {} + if not startIndex or startIndex < 1 then + startIndex = 1 + end + if not endIndex or endIndex > #t then + endIndex = #t + end + if startIndex > endIndex then + return nil + end + return t[self._rng:uniformInt(startIndex, endIndex)] +end + +-- Shuffles a Lua array in place. If n is given, the shuffle stops after the +-- first n elements have been placed. 'n' is clamped to the size of the array. +function random:shuffleInPlace(array, n) + local c = (n and n < #array) and n or #array - 1 + for i = 1, c do + local j = self._rng:uniformInt(i, #array) + array[j], array[i] = array[i], array[j] + end + return array +end + +-- Returns a shuffled copy of a Lua array. +function random:shuffle(array) + local ret = {} + for i, obj in ipairs(array) do + ret[i] = obj + end + return self:shuffleInPlace(ret) +end + +function random:generator() + return self._rng +end + +--[[ Returns a 'sampling without replacement' number generator with the integer +range range [1, count]. + +Generator's memory grows linearly. Initialization and calling costs are both +O(1). + +The generator returns nil when no new samples are available and resets to its +initial state. +--]] +function random:shuffledIndexGenerator(count) + -- The number of values that have been generated so far. + local current = 0 + + -- A sparse array of shuffled values. + local elements = {} + + --[[ + All values <= current that are *not* in + the completed shuffling. + completed These values are at indices that *are* in + shuffling the completed shuffling. + + elements [+++++++++++|--+---------+--++----------+----------+--] + . . . + . . . + 1 current count + + Invariant A: All values <= current will be in `elements`. + Invariant B: Every value in the completed shuffling has + elements[value] ~= nil. + + The algorithm starts with current = 0 and elements empty, so the + invariants start true. + + The algorithm increments as follows, + 1. Increment current + current = current + 1 + + 2. Generate `random`, a random value in the range [current, count] + random = randomNumberInRange(current, count) + + 3. Basic operation: + (a) output `random` as the next completed shuffling value; that is, + insert `random` at elements[current]. + elements[random] is nil ==> elements[current] = random + + (b) insert `current` at elements[random]. + elements[current] is nil ==> elements[random] = current + + Exception operations: + - If elements[current] already has a value, then (by invariant B), + current is already in the completed shuffling. In this case we + should avoid (b) and instead push the existing elements[current] + back into elements[random]. + elements[current] has a value ==> + elements[random] = elements[current] + + - If elements[random] already has a value, then (by invariant B), + random is already in the completed shuffling. In this case we + should avoid (a) and instead output the deferred value + elements[random] in the completed shuffling. + elements[random] has a value ==> + elements[current] = elements[random] + + Note that both the basic and exception operations maintain the invariants, + so by induction the invariants are always true. + --]] + return function() + -- If we've shuffled all the elements, return nil for one call and reset + -- to initial conditions. The caller can start again if a new shuffling is + -- desired. + if count == current then + elements = {} + current = 0 + return nil + end + + -- Step 1. + current = current + 1 + + -- Step 2. + local random = self._rng:uniformInt(current, count) + + -- Step 3. + local currentStartValue = elements[current] + elements[current] = elements[random] or random + elements[random] = currentStartValue or current + + -- Return the tail of the completed shuffling that we just generated. + return elements[current] + end +end + +local randomMT = {__index = random} + +function randomMT:__call(rng) + return setmetatable({_rng = rng}, randomMT) end -return random +return setmetatable({_rng = sys_random}, randomMT) diff --git a/assets/game_scripts/common/screen_message.lua b/assets/game_scripts/common/screen_message.lua index 79dfb587..3c4d2bc0 100644 --- a/assets/game_scripts/common/screen_message.lua +++ b/assets/game_scripts/common/screen_message.lua @@ -1,11 +1,11 @@ local screen_message = { -- Alignment for screen_message alignment parameter. - kAlignLeft = 0, - kAlignRight = 1, - kAlignCenter = 2, + ALIGN_LEFT = 0, + ALIGN_RIGHT = 1, + ALIGN_CENTER = 2, -- Default border to stop text from being to close to the edge. - kBorderSize = 5, + BORDER_SIZE = 5, } return screen_message diff --git a/assets/game_scripts/common/set.lua b/assets/game_scripts/common/set.lua new file mode 100644 index 00000000..59c44fc3 --- /dev/null +++ b/assets/game_scripts/common/set.lua @@ -0,0 +1,65 @@ +-- Provides common set-like operations. +local set = {} + +-- Insert the elements of list into existingSet. +function set.insert(existingSet, list) + for _, v in ipairs(list) do + existingSet[v] = true + end + return existingSet +end + +-- Turn a list L into a set S. For any V in L, S[V] = true. +function set.Set(list) + return set.insert({}, list) +end + +function set.toList(lhs) + local list = {} + for key, _ in pairs(lhs) do + list[#list + 1] = key + end + return list +end + +function set.isSame(lhs, rhs) + for k, _ in pairs(lhs) do + if not rhs[k] then return false end + end + for k, _ in pairs(rhs) do + if not lhs[k] then return false end + end + return true +end + +function set.intersect(lhs, rhs) + local intersection = {} + for key, _ in pairs(lhs) do + intersection[key] = rhs[key] + end + return intersection +end + +-- Returns lhs - rhs +function set.difference(lhs, rhs) + local out = {} + for key, _ in pairs(lhs) do + if not rhs[key] then + out[key] = true + end + end + return out +end + +function set.union(lhs, rhs) + local out = {} + for key, _ in pairs(lhs) do + out[key] = true + end + for key, _ in pairs(rhs) do + out[key] = true + end + return out +end + +return set diff --git a/assets/game_scripts/common/timer.lua b/assets/game_scripts/common/timer.lua new file mode 100644 index 00000000..77ded73f --- /dev/null +++ b/assets/game_scripts/common/timer.lua @@ -0,0 +1,105 @@ +local Timer = {} +local TimerMT = {__index = Timer} + +--[[ Create a timer class that starts at currentTime, if specified, or 0 if not. + +The returned `t` must have `t:update(currentTime)` called on it whenever +`currentTime` changes. This call will trigger any callbacks on timers that have +expired. +--]] +function Timer.new(currentTime) + return setmetatable({_currentTime = currentTime or 0, _timers = {}}, TimerMT) +end + +--[[ Start timer `kwargs.name` that will fire in `kwargs.time` seconds. + +Once the timer fires, `kwargs.callback` will be called and the timer will be +removed, unless the callback returns `true`. + +Keyword arguments: + +* `name` (string) The name of the timer. +* `time` (number) The number of seconds from now until this timer goes off. +* `callback` (function) Callback that's called when the timer goes off. + If the callback returns `true` then the timer is re-engaged to go off again + after the same amount of time. By default, however, the timer is removed. + +Example: Print a lovely message in 2 seconds. + +```Lua +local Timer = require 'common.timer' + +myTimer = Timer.new() +myTimer:start{ + name = 'my love timer', + time = 2, + callback = function() print('LOVE!') end, +} + +function api:myFunctionCalledEveryFrame(timeSeconds) + myTimer:update(timeSeconds) +end + +``` +--]] +function Timer:start(kwargs) + assert(kwargs.name) + assert(kwargs.time) + assert(kwargs.callback) + self._timers[kwargs.name] = { + triggerTime = self._currentTime + kwargs.time, + time = kwargs.time, + callback = kwargs.callback, + } +end + +-- Remove `name` from the active timers. +function Timer:remove(name) + self._timers[name] = nil +end + +-- Remove all timers, but maintain the current time. +function Timer:removeAll() + self._timers = {} +end + +-- Returns true if there exists an active timer called `name`. +function Timer:exists(name) + return self._timers[name] ~= nil +end + +-- Returns the time remaining for the timer called `name`. +-- If no such timer exists (perhaps because the timer has already fired and been +-- removed), returns 0. +function Timer:timeRemaining(name) + if not self:exists(name) then + return 0 + end + return self._timers[name].triggerTime - self._currentTime +end + +-- Provide the new current time. Check if any timers have fired. For timers that +-- have fired, call their callbacks and remove them unless the callback returns +-- `true`. Must be called manually by the owner of this class. +function Timer:update(currentTime) + assert(self._currentTime <= currentTime) + self._currentTime = currentTime + + local timersToRemove = {} + for name, v in pairs(self._timers) do + if currentTime >= v.triggerTime then + local persist = v.callback() + if persist then + -- Re-engage the timer. Ensure time is not in the past. + v.triggerTime = math.max(currentTime, v.triggerTime + v.time) + else + timersToRemove[#timersToRemove + 1] = name + end + end + end + for _, name in pairs(timersToRemove) do + self:remove(name) + end +end + +return Timer diff --git a/assets/game_scripts/common/transform.lua b/assets/game_scripts/common/transform.lua new file mode 100644 index 00000000..d534dc5c --- /dev/null +++ b/assets/game_scripts/common/transform.lua @@ -0,0 +1,44 @@ +-- Utility functions to construct transformation matrices as tensors. + +local sys_transform = require 'dmlab.system.transform' + +local transform = {} + +-- Returns a 4x4 tensor with the coefficients of a column-major transformation +-- matrix which applies a translation by vector 'ofs'. +function transform.translate(ofs) + return sys_transform.translate(ofs) +end + +-- Returns a 4x4 tensor with the coefficients of a column-major transformation +-- matrix which applies a rotation of 'angle' degrees around vector 'axis'. +function transform.rotate(angle, axis) + return sys_transform.rotate(angle, axis) +end + +-- Returns a 4x4 tensor with the coefficients of a column-major transformation +-- matrix which applies a rotation of 'angle' degrees around the X axis. +function transform.rotateX(angle) + return sys_transform.rotate(angle, {1, 0, 0}) +end + +-- Returns a 4x4 tensor with the coefficients of a column-major transformation +-- matrix which applies a rotation of 'angle' degrees around the Y axis. +function transform.rotateY(angle) + return sys_transform.rotate(angle, {0, 1, 0}) +end + +-- Returns a 4x4 tensor with the coefficients of a column-major transformation +-- matrix which applies a rotation of 'angle' degrees around the Z axis. +function transform.rotateZ(angle) + return sys_transform.rotate(angle, {0, 0, 1}) +end + +-- Returns a 4x4 tensor with the coefficients of a column-major transformation +-- matrix which applies scale factors in vector 'scl' to their corresponding +-- coordinates. +function transform.scale(scl) + return sys_transform.scale(scl) +end + +return transform diff --git a/assets/game_scripts/decorators/custom_decals_decoration.lua b/assets/game_scripts/decorators/custom_decals_decoration.lua new file mode 100644 index 00000000..440673fa --- /dev/null +++ b/assets/game_scripts/decorators/custom_decals_decoration.lua @@ -0,0 +1,45 @@ +local datasets_selector = require 'datasets.selector' +local texture_sets = require 'themes.texture_sets' +local tensor = require 'dmlab.system.tensor' +local decals = require 'themes.decals' + +local decorator = {} + +local SUBSTITUTION = {} + +function decorator.randomize(name, rng) + local dataset = decorator._dataset + if name ~= decorator._datasetName then + dataset = datasets_selector.loadDataset(name) + end + assert(dataset) + decorator._dataset = dataset + decorator._datasetName = name + local gen = rng:shuffledIndexGenerator(dataset:getSize()) + for _, k in ipairs(decals.images) do + SUBSTITUTION[k] = gen() + end +end + +function decorator.decorate(api) + local loadTexture = api.loadTexture + function api:loadTexture(textureName) + local index = SUBSTITUTION[textureName] + if index then + local result = decorator._dataset:getImage(index) + local shape = result:shape() + local outTensor = tensor.ByteTensor(shape[1], shape[2], 4) + -- Copying by 3 select calls is faster than via a one narrow. + outTensor:select(3, 1):copy(result:select(3, 1)) + outTensor:select(3, 2):copy(result:select(3, 2)) + outTensor:select(3, 3):copy(result:select(3, 3)) + outTensor:select(3, 4):fill(255) + return outTensor + end + if loadTexture then + return loadTexture(self, textureName) + end + end +end + +return decorator diff --git a/assets/game_scripts/decorators/custom_floors.lua b/assets/game_scripts/decorators/custom_floors.lua new file mode 100644 index 00000000..b94002c3 --- /dev/null +++ b/assets/game_scripts/decorators/custom_floors.lua @@ -0,0 +1,51 @@ +--[[ Provide simple re-coloring of floor textures. + +Use along with a theme using the CUSTOMIZABLE_FLOORS texture set: + +``` +local theme = themes.fromTextureSet{ + textureSet = texture_sets.CUSTOMIZABLE_FLOORS, + randomizeFloorTextures = false, +} +``` + +RGB values floor variations can be specified via setVariationColor() and will +be automatically applied. Variation 0 represents the default corridor. +]] +local decorator = {} + +local colorMappings = {} + +function decorator.setVariationColor(variation, color) + assert(#color == 3, 'Expected RGB color') + colorMappings[variation] = color +end + +function decorator.setVariationColors(variationToColor) + colorMappings = {} + for variation, color in pairs(variationToColor) do + decorator.setVariationColor(variation, color) + end +end + +function decorator.decorate(api) + local modifyTexture = api.modifyTexture + function api:modifyTexture(textureName, texture) + local res = false + if modifyTexture then + res = modifyTexture(self, textureName, texture) + end + + local variation = textureName:match('lg_floor_placeholder_(.)_d%.tga') + local color = variation and colorMappings[variation] + if color then + texture:select(3, 1):mul(color[1] / 255) + texture:select(3, 2):mul(color[2] / 255) + texture:select(3, 3):mul(color[3] / 255) + return true + end + return res + end +end + +return decorator diff --git a/assets/game_scripts/decorators/custom_observations.lua b/assets/game_scripts/decorators/custom_observations.lua index 8069d17d..7f207761 100644 --- a/assets/game_scripts/decorators/custom_observations.lua +++ b/assets/game_scripts/decorators/custom_observations.lua @@ -1,10 +1,19 @@ -local tensor = require 'dmlab.system.tensor' +local debug_observations = require 'decorators.debug_observations' local game = require 'dmlab.system.game' -local custom_observations = {} +local inventory = require 'common.inventory' +local tensor = require 'dmlab.system.tensor' + local obs = {} local obsSpec = {} +local instructionObservation = '' + +local custom_observations = {} -function custom_observations.add_spec(name, type, shape, callback) +custom_observations.playerNames = {''} +custom_observations.playerInventory = {} +custom_observations.playerTeams = {} + +function custom_observations.addSpec(name, type, shape, callback) obsSpec[#obsSpec + 1] = {name = name, type = type, shape = shape} obs[name] = callback end @@ -23,14 +32,32 @@ local function angularVelocity() return tensor.DoubleTensor(game:playerInfo().anglesVel) end --- Decorate the api with a player translation velocity and angular velocity --- observation. These observations are relative to the player. +local function languageChannel() + return instructionObservation or '' +end + +local function teamScore() + local info = game:playerInfo() + return tensor.DoubleTensor{info.teamScore, info.otherTeamScore} +end + +--[[ Decorate the api to support custom observations: + +1. Player translational velocity (VEL.TRANS). +2. Player angular velocity (VEL.ROT). +3. Language channel for, e.g. giving instructions to the agent (INSTR). +4. See debug_observations.lua for those. +]] function custom_observations.decorate(api) local init = api.init function api:init(params) - custom_observations.add_spec('VEL.TRANS', 'Doubles', {3}, velocity) - custom_observations.add_spec('VEL.ROT', 'Doubles', {3}, angularVelocity) - return init and init(params) + custom_observations.addSpec('VEL.TRANS', 'Doubles', {3}, velocity) + custom_observations.addSpec('VEL.ROT', 'Doubles', {3}, angularVelocity) + custom_observations.addSpec('INSTR', 'String', {0}, languageChannel) + custom_observations.addSpec('TEAM.SCORE', 'Doubles', {0}, teamScore) + api.setInstruction('') + debug_observations.extend(custom_observations) + return init and init(api, params) end local customObservationSpec = api.customObservationSpec @@ -42,10 +69,38 @@ function custom_observations.decorate(api) return specs end + local team = api.team + function api:team(playerId, playerName) + custom_observations.playerNames[playerId] = playerName + local result = team and team(self, playerId, playerName) or 'p' + custom_observations.playerTeams[playerId] = result + return result + end + + local spawnInventory = api.spawnInventory + function api:spawnInventory(loadOut) + local view = inventory.View(loadOut) + custom_observations.playerInventory[view:playerId()] = view + return spawnInventory and spawnInventory(self, loadOut) + end + + local updateInventory = api.updateInventory + function api:updateInventory(loadOut) + local view = inventory.View(loadOut) + custom_observations.playerInventory[view:playerId()] = view + return updateInventory and updateInventory(self, loadOut) + end + local customObservation = api.customObservation function api:customObservation(name) return obs[name] and obs[name]() or customObservation(api, name) end + + -- Levels can call this to define the language channel observation string + -- returned to the agent. + function api.setInstruction(text) + instructionObservation = text + end end return custom_observations diff --git a/assets/game_scripts/decorators/debug_observations.lua b/assets/game_scripts/decorators/debug_observations.lua new file mode 100644 index 00000000..a16a0959 --- /dev/null +++ b/assets/game_scripts/decorators/debug_observations.lua @@ -0,0 +1,209 @@ +local game = require 'dmlab.system.game' +local tensor = require 'dmlab.system.tensor' +local game_entities = require 'dmlab.system.game_entities' +local inventory = require 'common.inventory' + +local debug_observations = {} +local names = {} +local inventories = {} +local teams = {} + +local function playerPosition() + return tensor.DoubleTensor(game:playerInfo().pos) +end + +local function playerOrientation() + return tensor.DoubleTensor(game:playerInfo().angles) +end + +local function playerId() + return tensor.DoubleTensor{game:playerInfo().playerId} +end + +local function playersId() + local playerIds = {} + for playerId, name in pairs(names) do + playerIds[#playerIds + 1] = playerId + end + return tensor.DoubleTensor(playerIds) +end + + +local function playersName() + return table.concat(names, '\n') +end + +local function playersHealth() + local playerHealth = {} + for playerId, inv in pairs(inventories) do + playerHealth[#playerHealth + 1] = inv:health() + end + return tensor.DoubleTensor(playerHealth) +end + +local function playersArmor() + local playerArmor = {} + for playerId, inv in pairs(inventories) do + playerArmor[#playerArmor + 1] = inv:armor() + end + return tensor.DoubleTensor(playerArmor) +end + +local function playersGadget() + local playerGadget = {} + for playerId, inv in pairs(inventories) do + playerGadget[#playerGadget + 1] = inv:gadget() + end + return tensor.DoubleTensor(playerGadget) +end + +local function playersGadgetAmount() + local playerGadgetAmount = {} + for playerId, inv in pairs(inventories) do + playerGadgetAmount[#playerGadgetAmount + 1] = + inv:gadgetAmount(inv:gadget()) + end + return tensor.DoubleTensor(playerGadgetAmount) +end + +local function playersEyePos() + local eyePos = {} + for playerId, inv in pairs(inventories) do + eyePos[#eyePos + 1] = inv:eyePos() + end + return tensor.DoubleTensor(eyePos) +end + +local function playersEyeRot() + local eyeRot = {} + for playerId, inv in pairs(inventories) do + eyeRot[#eyeRot + 1] = inv:eyeAngles() + end + return tensor.DoubleTensor(eyeRot) +end + +local TEAM_LOOKUP = {r = 1, b = 2} +local function playersTeam() + local playerTeam = {} + for playerId, team in pairs(teams) do + playerTeam[#playerTeam + 1] = TEAM_LOOKUP[team] or 0 + end + return tensor.DoubleTensor(playerTeam) +end + +local function playersHoldingFlag() + local playerHoldingFlag = {} + for playerId, inv in pairs(inventories) do + local holding = 0 + if inv:hasPowerUp(inventory.POWERUPS.RED_FLAG) then + holding = holding + 1 + end + if inv:hasPowerUp(inventory.POWERUPS.BLUE_FLAG) then + holding = holding + 2 + end + playerHoldingFlag[#playerHoldingFlag + 1] = holding + end + return tensor.DoubleTensor(playerHoldingFlag) +end + +local FLAG_STATE = { + NONE = 0, + HOME = 1, + CARRIED = 2, + DROPPED = 3, +} + +local function redFlag() + for i, ent in ipairs(game_entities:entities{'team_CTF_redflag'}) do + if ent.visible then + local x, y, z = unpack(ent.position) + local player_id = 0 + local flagState = i == 1 and FLAG_STATE.HOME or FLAG_STATE.DROPPED + return tensor.DoubleTensor{x, y, z, player_id, flagState} + end + end + for playerId, inv in pairs(inventories) do + if inv:hasPowerUp(inventory.POWERUPS.RED_FLAG) then + local x, y, z = unpack(inv:eyePos()) + return tensor.DoubleTensor{x, y, z, inv:playerId(), FLAG_STATE.CARRIED} + end + end + return tensor.DoubleTensor{0, 0, 0, 0, FLAG_STATE.NONE} +end + +local function blueFlag() + for i, ent in ipairs(game_entities:entities{'team_CTF_blueflag'}) do + if ent.visible then + local x, y, z = unpack(ent.position) + local player_id = 0 + local flagState = i == 1 and FLAG_STATE.HOME or FLAG_STATE.DROPPED + return tensor.DoubleTensor{x, y, z, player_id, flagState} + end + end + + for _, inv in pairs(inventories) do + if inv:hasPowerUp(inventory.POWERUPS.BLUE_FLAG) then + local x, y, z = unpack(inv:eyePos()) + return tensor.DoubleTensor{x, y, z, inv:playerId(), FLAG_STATE.CARRIED} + end + end + return tensor.DoubleTensor{0, 0, 0, 0, FLAG_STATE.NONE} +end + +local HOME_FLAG_STATE = { + NONE = 0, + HOME = 1, + AWAY = 2, +} + +local function redFlagHome() + for i, ent in ipairs(game_entities:entities{'team_CTF_redflag'}) do + local x, y, z = unpack(ent.position) + local state = ent.visible and HOME_FLAG_STATE.HOME or HOME_FLAG_STATE.AWAY + return tensor.DoubleTensor{x, y, z, state} + end + return tensor.DoubleTensor{0, 0, 0, FLAG_STATE.NONE} +end + +local function blueFlagHome() + for i, ent in ipairs(game_entities:entities{'team_CTF_blueflag'}) do + local x, y, z = unpack(ent.position) + local state = ent.visible and HOME_FLAG_STATE.HOME or HOME_FLAG_STATE.AWAY + return tensor.DoubleTensor{x, y, z, state} + end + return tensor.DoubleTensor{0, 0, 0, HOME_FLAG_STATE.NONE} +end + +--[[ Extend custom_observations to contain debug observations. ]] +function debug_observations.extend(custom_observations) + local co = custom_observations + names = co.playerNames + inventories = co.playerInventory + teams = co.playerTeams + + co.addSpec('DEBUG.POS.TRANS', 'Doubles', {3}, playerPosition) + co.addSpec('DEBUG.POS.ROT', 'Doubles', {3}, playerOrientation) + co.addSpec('DEBUG.PLAYER_ID', 'Doubles', {1}, playerId) + co.addSpec('DEBUG.PLAYERS.ARMOR', 'Doubles', {0}, playersArmor) + co.addSpec('DEBUG.PLAYERS.GADGET', 'Doubles', {0}, playersGadget) + co.addSpec('DEBUG.PLAYERS.GADGET_AMOUNT', 'Doubles', {0}, playersGadgetAmount) + co.addSpec('DEBUG.PLAYERS.HEALTH', 'Doubles', {0}, playersHealth) + co.addSpec('DEBUG.PLAYERS.HOLDING_FLAG', 'Doubles', {0}, playersHoldingFlag) + co.addSpec('DEBUG.PLAYERS.ID', 'Doubles', {0}, playersId) + co.addSpec('DEBUG.PLAYERS.EYE.POS', 'Doubles', {0, 3}, playersEyePos) + co.addSpec('DEBUG.PLAYERS.EYE.ROT', 'Doubles', {0, 3}, playersEyeRot) + -- New line separated string. + co.addSpec('DEBUG.PLAYERS.NAME', 'String', {1}, playersName) + co.addSpec('DEBUG.PLAYERS.TEAM', 'Doubles', {0}, playersTeam) + + -- Flag information (x, y, z, playerId, + -- {NONE = 0, HOME = 1, CARRIED = 2, DROPPED = 3}) + co.addSpec('DEBUG.FLAGS.RED', 'Doubles', {5}, redFlag) + co.addSpec('DEBUG.FLAGS.BLUE', 'Doubles', {5}, blueFlag) + + -- Flag information (x, y, z, {NONE = 0, HOME = 1, AWAY = 2}) + co.addSpec('DEBUG.FLAGS.RED_HOME', 'Doubles', {4}, redFlagHome) + co.addSpec('DEBUG.FLAGS.BLUE_HOME', 'Doubles', {4}, blueFlagHome) +end + +return debug_observations diff --git a/assets/game_scripts/decorators/human_recognisable_pickups.lua b/assets/game_scripts/decorators/human_recognisable_pickups.lua new file mode 100644 index 00000000..9e3eb839 --- /dev/null +++ b/assets/game_scripts/decorators/human_recognisable_pickups.lua @@ -0,0 +1,66 @@ +-- Carries out transformations specified by common.human_recognisable_pickups. + +local hrp = require 'common.human_recognisable_pickups' +local model = require 'dmlab.system.model' +local transform = require 'common.transform' +local decorator = {} + +function decorator.decorate(api) + local replaceModelName = api.replaceModelName + function api:replaceModelName(modelName) + local replace = replaceModelName and {replaceModelName(self, modelName)} + or {nil} + if replace[1] == nil then + replace = {hrp.replaceModelName(modelName)} + end + return unpack(replace) + end + + local function buildTransform(transformSuffix) + -- Matches name{amount}. + local name, amount = string.match(transformSuffix, '(.*){(.*)}') + assert(name == 'scale', 'Only scale operation supported') + amount = tonumber(amount) + assert(name ~= nil, 'Amount must be a number') + return transform.scale({amount, amount, amount}) + end + + local createModel = api.createModel + function api:createModel(modelName) + -- Matches prefix%transformSuffix. + local prefix, transformSuffix = string.match(modelName, '(.+)%%(.+)') + if transformSuffix ~= nil then + local modelNameActual, prefix = self:replaceModelName(prefix) + if modelNameActual ~= nil then + local modelRaw = model:loadMD3(modelNameActual) + -- Custom loaded models must have their textures updated with the + -- correct prefix. + for _, v in pairs(modelRaw.surfaces) do + v.shaderName = prefix .. v.shaderName + end + return model:hierarchy{ + transform = buildTransform(transformSuffix), + model = modelRaw + } + end + end + return createModel and createModel(self, modelName) + end + + local replaceTextureName = api.replaceTextureName + function api:replaceTextureName(textureName) + return replaceTextureName and replaceTextureName(self, textureName) or + hrp.replaceTextureName(textureName) + end + + local modifyTexture = api.modifyTexture + function api:modifyTexture(textureName, texture) + local res = false + if modifyTexture then + res = modifyTexture(self, textureName, texture) + end + return hrp.modifyTexture(textureName, texture) or res + end +end + +return decorator diff --git a/assets/game_scripts/decorators/setting_overrides.lua b/assets/game_scripts/decorators/setting_overrides.lua new file mode 100644 index 00000000..9958b500 --- /dev/null +++ b/assets/game_scripts/decorators/setting_overrides.lua @@ -0,0 +1,76 @@ +local helpers = require 'common.helpers' +local timeout = require 'decorators.timeout' + +-- These parameters may or may not be specified in the init `params`, depending +-- on the execution environment. We should ignore them without raising an +-- 'Invalid setting' error. +local PARAMS_WHITELIST = { + episodeLengthSeconds = true, + invocationMode = true, + levelGenerator = true, + playerId = true, + players = true, + randomSeed = true, + datasetPath = true, +} + +-- Some scripts still pass these in, though they are not used. +-- Output a warning but not an error. +local PARAMS_DEPRECATED = { + __platform__ = true, + textureRandomization = true, +} + +local setting_overrides = {} + +--[[ Decorate the api with an init(params) function that overrides values in +apiParams with the corresponding values in params. This lets command-line and +Python environments override the apiParams settings. + +Keyword arguments: + +* `api` the class to decorate. Gets an `init(params)` function added to it. +* `apiParams` the parameters that hold values overridden by the `params` + passed into `init(params)` +* `decorateWithTimeout` (boolean, default nil) if true, decorate `api` with + the timeout decorator, and use the (potentially updated) version of + `apiParams.episodeLengthSeconds`. +]] +function setting_overrides.decorate(kwargs) + local api = kwargs.api + local apiParams = kwargs.apiParams + local decorateWithTimeout = kwargs.decorateWithTimeout + assert(api ~= nil and apiParams ~= nil) + + -- Preserve the existing init call. + local apiInit = api.init + + -- Override the init() function to parse known settings. + function api:init(params) + -- Override known settings. + for k, v in pairs(params) do + if apiParams[k] ~= nil then + apiParams[k] = helpers.fromString(v) + elseif PARAMS_DEPRECATED[k] then + io.stderr:write('WARNING: "' .. k .. '" (here set to "' .. v .. '")' .. + ' has been deprecated.\n') + elseif not PARAMS_WHITELIST[k] then + error('Invalid setting: "' .. k .. '" = "' .. v .. '"') + end + end + + -- Decorate with timeout. We do this at the end because episodeLengthSeconds + -- may have been overridden. + if decorateWithTimeout and apiParams.episodeLengthSeconds then + timeout.decorate(api, apiParams.episodeLengthSeconds) + end + + -- Call version of `init` that existed before decoration. + if apiInit then + return apiInit(api, params) + end + end +end + +return setting_overrides + diff --git a/assets/game_scripts/decorators/test_only.lua b/assets/game_scripts/decorators/test_only.lua new file mode 100644 index 00000000..1f9014dd --- /dev/null +++ b/assets/game_scripts/decorators/test_only.lua @@ -0,0 +1,20 @@ +local test_only = {} + +--[[ Decorate the api so that an error is raised if the environment is not +invoked in an evaluation context (e.g. from the Testbed or a human agent). + +This may be used to prevent researchers from accidentally training agents +against evaluation levels that they are not meant to train against. +]] +function test_only.decorate(api) + local init = api.init + function api:init(params) + assert(params.invocationMode == "testbed", + "This level must only be used during evaluation. " .. + "For training, use the *_train.lua version of the level.") + return init and init(api, params) + end + return api +end + +return test_only diff --git a/assets/game_scripts/decorators/timeout.lua b/assets/game_scripts/decorators/timeout.lua index 7ee66483..a4dab19e 100644 --- a/assets/game_scripts/decorators/timeout.lua +++ b/assets/game_scripts/decorators/timeout.lua @@ -1,28 +1,27 @@ +local helpers = require 'common.helpers' local screen_message = require 'common.screen_message' local timeout = {} --- Function for displaying a timer in the top right of the screen. --- 'args' is a table which can be passed through from api:screenMessages(args). --- 'width' (number) Screen width. --- 'time_seconds' (number). Rounded up and if greater than 60, then minutes are --- displayed, too. --- Returns --- A table, suitable for use as an entry in the array returned by --- api:screenMessages -local function timeDisplay(args, time_seconds) - local s = math.ceil(time_seconds) - local time_remaining - if s < 60 then - time_remaining = string.format('%.2d', s % 60) - else - time_remaining = string.format('%.2d:%.2d', s / 60 % 60, s % 60) - end +--[[ Function for displaying a timer in the top right of the screen. + +Arguments: + +* 'args' (table) Passed through from api:screenMessages(args). +* 'timeSeconds' (number). Rounded up and if greater than 60, then minutes are + displayed, too. + +Returns: + +* A table, suitable for use as an entry in the array returned by + api:screenMessages. +]] +local function timeDisplay(args, timeSeconds) return { - message = time_remaining, - x = args.width - screen_message.kBorderSize, + message = helpers.secondsToTimeString(timeSeconds), + x = args.width - screen_message.BORDER_SIZE, y = 0, - alignment = screen_message.kAlignRight + alignment = screen_message.ALIGN_RIGHT } end @@ -45,9 +44,9 @@ function timeout.decorate(api, episodeLength) end local hasEpisodeFinished = api.hasEpisodeFinished - function api:hasEpisodeFinished(time_seconds) - timeRemaining = episodeLength - time_seconds - return hasEpisodeFinished and hasEpisodeFinished(api, time_seconds) or + function api:hasEpisodeFinished(timeSeconds) + timeRemaining = episodeLength - timeSeconds + return hasEpisodeFinished and hasEpisodeFinished(api, timeSeconds) or timeRemaining <= 0 end end diff --git a/assets/game_scripts/demo_levels/extra_entities.lua b/assets/game_scripts/demo_levels/extra_entities.lua new file mode 100644 index 00000000..12213b2e --- /dev/null +++ b/assets/game_scripts/demo_levels/extra_entities.lua @@ -0,0 +1,88 @@ +local make_map = require 'common.make_map' +local pickups = require 'common.pickups' +local custom_observations = require 'decorators.custom_observations' +local game = require 'dmlab.system.game' +local timeout = require 'decorators.timeout' +local api = {} + +local MAP_ENTITIES = [[ +********* +* * +* * +* * +* P * +* * +* * +* * +********* +]] + +function api:init(params) + make_map.seedRng(1) + api._map = make_map.makeMap{ + mapName = 'empty_room', + mapEntityLayer = MAP_ENTITIES, + useSkybox = true, + theme = 'TETRIS' + } +end + +function api:nextMap() + return self._map +end + +function api:updateSpawnVars(spawnVars) + if spawnVars.classname == 'info_player_start' then + -- Spawn facing East. + spawnVars.angle = '0' + spawnVars.randomAngleRange = '0' + end + -- This will also print the origins of the extra entities. + print(spawnVars.origin) + io.flush() + return spawnVars +end + +function api:extraEntities() + -- List of entities to create. + local vars = { + { + classname = 'apple_reward', + origin = '550 450 30', + }, + { + classname = 'apple_reward', + origin = '600 450 30', + }, + { + classname = 'apple_reward', + origin = '650 450 30', + }, + { + classname = 'apple_reward', + origin = '700 450 30', + } + } + -- `updateSpawnVars` is not called by default; so call it anyway. + local varsResult = {} + for i, v in ipairs(vars) do + varsResult[#varsResult + 1] = self:updateSpawnVars(v) + end + return varsResult +end + +function api:createPickup(classname) + if classname == 'apple_reward' then + return { + name = 'Apple', + classname = 'apple_reward', + model = 'models/apple.md3', + quantity = 1, + type = pickups.type.REWARD, + } + end +end + +timeout.decorate(api, 60 * 60) +custom_observations.decorate(api) +return api diff --git a/assets/game_scripts/demo_levels/hrp_demo.lua b/assets/game_scripts/demo_levels/hrp_demo.lua new file mode 100644 index 00000000..369e77c2 --- /dev/null +++ b/assets/game_scripts/demo_levels/hrp_demo.lua @@ -0,0 +1,58 @@ +local make_map = require 'common.make_map' +local pickups = require 'common.pickups' +local random = require 'common.random' +local hrp = require 'common.human_recognisable_pickups' + +local custom_observations = require 'decorators.custom_observations' +local pickup_decorator = require 'decorators.human_recognisable_pickups' + +local api = {} + +function api:updateSpawnVars(spawnVars) + if spawnVars.classname == 'info_player_start' then + spawnVars.angle = '0' + spawnVars.randomAngleRange = '0' + elseif spawnVars.classname == 'recognisable_object' then + -- If there's no id set, canPickup won't be called. + spawnVars.id = '1' + end + return spawnVars +end + +-- Intercept object creation to override by using code from +-- common.human_recognizable_pickups. +function api:createPickup(className) + if className == 'recognisable_object' then + return hrp.create{ + shape = "cherries", + color1 = {255, 0, 0}, + color2 = {0, 255, 0}, + pattern = "chequered", + } + end + return pickups.defaults[className] +end + +function api:canPickup(id) + -- Turn off pickups so you can get up close and personal. + return false +end + +-- Sets all objects in the map to have the class name 'recognisable_object', +-- which we'll customise in api:createPickup(). +function api:nextMap() + hrp.reset() + + local map = 'PO' + return make_map.makeMap{ + mapName = 'hrpdemo_map', + mapEntityLayer = map, + useSkybox = true, + pickups = {O = 'recognisable_object'} + } +end + +custom_observations.decorate(api) +pickup_decorator.decorate(api) + +return api diff --git a/assets/game_scripts/demo_levels/hrp_gallery.lua b/assets/game_scripts/demo_levels/hrp_gallery.lua new file mode 100644 index 00000000..854e15b5 --- /dev/null +++ b/assets/game_scripts/demo_levels/hrp_gallery.lua @@ -0,0 +1,103 @@ +local make_map = require 'common.make_map' +local pickups = require 'common.pickups' +local random = require 'common.random' +local hrp = require 'common.human_recognisable_pickups' + +local custom_observations = require 'decorators.custom_observations' +local pickup_decorator = require 'decorators.human_recognisable_pickups' + +local timeout = require 'decorators.timeout' + +local SCALES = {"small", "medium", "large"} +local MOVE_TYPE = {pickups.moveType.STATIC, pickups.moveType.BOB} + +local api = {} + +local function nameToPickupId(name) + return tonumber(name:match('^pickup:(%d+)$')) +end + +function api:init(settings) + make_map.seedRng(1) -- Use a fixed seed since this is a simple demo level. + random:seed(1) + self._shapes = hrp.shapes() + table.sort(self._shapes) + self._patterns = hrp.patterns() + table.sort(self._patterns) + self._map = self:_makeMap() +end + +function api:nextMap() + return self._map +end + +function api:updateSpawnVars(spawnVars) + if spawnVars.classname == 'info_player_start' then + -- Spawn looking along the row of objects + spawnVars.angle = '0' + spawnVars.randomAngleRange = '0' + else + local id = nameToPickupId(spawnVars.classname) + spawnVars.id = id and tostring(id) or nil + end + return spawnVars +end + +function api:createPickup(classname) + -- If classname is 'pickup:x', return self._pickups[x] + local id = nameToPickupId(classname) + return id and self._pickups[id] or pickups.defaults[classname] +end + +function api:canPickup(id) + -- Turn off pickups so you can get up close and personal. + return false +end + +function api:_makePickup(c) + if c == 'O' then + -- Make new pickup and classname to reference it. + local id = #self._pickups + 1 + self._pickups[id] = hrp.create{ + shape = self._shapes[id], + color1 = random:color(), + color2 = random:color(), + pattern = self._patterns[(id - 1) % #self._patterns + 1], + scale = SCALES[(id - 1) % #SCALES + 1], + moveType = MOVE_TYPE[(id - 1) % #MOVE_TYPE + 1], + } + return 'pickup:' .. id + end +end + +function api:_makeMap() + hrp.reset() + self._pickups = {} + + -- Repeat 'O' enough time to show each shape once, add spawn on next row. + local map = ' ' .. string.rep(' ', #self._shapes) .. ' \n' .. + ' ' .. string.rep('O', #self._shapes) .. ' \n' .. + 'P' .. string.rep(' ', #self._shapes) .. ' ' + local var = ' ' .. string.rep(' ', #self._shapes) .. ' \n' .. + ' ' .. string.rep('AB', #self._shapes / 2) .. ' \n' .. + ' ' .. string.rep(' ', #self._shapes) .. ' ' + + return make_map.makeMap{ + mapName = 'hrpgallery_map', + mapEntityLayer = map, + mapVariationsLayer = var, + useSkybox = true, + callback = function (i, j, c, maker) + local pickup = self:_makePickup(c) + if pickup then + return maker:makeEntity{i = i, j = j, classname = pickup} + end + end, + } +end + +custom_observations.decorate(api) +pickup_decorator.decorate(api) +timeout.decorate(api, 60 * 60) -- 60 minutes. + +return api diff --git a/assets/game_scripts/demo_levels/map_generation/make_map_from_text.lua b/assets/game_scripts/demo_levels/map_generation/make_map_from_text.lua new file mode 100644 index 00000000..e055bd60 --- /dev/null +++ b/assets/game_scripts/demo_levels/map_generation/make_map_from_text.lua @@ -0,0 +1,62 @@ +-- Demonstration of creating a fixed level described using text. + +local make_map = require 'common.make_map' +local pickups = require 'common.pickups' +local texture_sets = require 'themes.texture_sets' +local api = {} + +--[[ Text map contents: + +'P' - Player spawn point. Player is spawned with random orientation. +'A' - Apple pickup. 1 reward point when picked up. +'G' - Goal object. 10 reward points and the level restarts. +'I' - Door. Open and closes West-East corridors. +'H' - Door. Open and closes North-South corridors. +'*' - Walls. + +Lights are placed randomly through out and decals are randomly placed on the +walls according to the theme. +]] +local TEXT_MAP = [[ +************** +*G * A ***** * +** * * * +***** I * +* * * * +* ** ***** * +* * * * +******H******* +* I P * +************** +]] + +-- Called only once at start up. Settings not recognised by DM Lab internal +-- are forwarded through the params dictionary. +function api:init(params) + -- Seed the map so only one map is created with lights and decals placed in + -- the same place each run. + make_map.random():seed(1) + api._map = make_map.makeMap{ + mapName = "demo_map_settings", + mapEntityLayer = TEXT_MAP, + useSkybox = true, + textureSet = texture_sets.TETRIS + } +end + +-- `make_map` has default pickup types A = apple_reward and G = goal. +-- This callback is used to create pickups with those names. +function api:createPickup(classname) + return pickups.defaults[classname] +end + +-- On first call we return the name of the map. On subsequent calls we return +-- an empty string. This informs the engine to only perform a quik map restart +-- instead. +function api:nextMap() + local mapName = api._map + api._map = '' + return mapName +end + +return api diff --git a/assets/game_scripts/demo_levels/map_generation/random_length.lua b/assets/game_scripts/demo_levels/map_generation/random_length.lua new file mode 100644 index 00000000..1acecdaf --- /dev/null +++ b/assets/game_scripts/demo_levels/map_generation/random_length.lua @@ -0,0 +1,46 @@ +-- Demonstration of using text to create a different level each time the player +-- reaches a goal object. + +local random = require 'common.random' +local make_map = require 'common.make_map' +local pickups = require 'common.pickups' +local api = {} + +-- `make_map` has default pickup types A = apple_reward and G = goal. +-- This callback is used to create pickups with those names. +function api:createPickup(className) + return pickups.defaults[className] +end + +-- Called at the begining of each episode. +function api:start(episode, seed) + random:seed(seed) + -- When converting from text to map the theme variations, wall decorations and + -- light positions are chosen randomly. + make_map.random():seed(random:mapGenerationSeed()) +end + +-- Called each time a map is needed to be generated. +-- The first map is generated after start is called and the following each time +-- the goal object is picked up. +function api:nextMap() + + -- All maps must have at least one spawn point. + local map = 'P' + + -- Add between [0, 5] apples. + for i = 0, make_map.random():uniformInt(0, 5) do + map = map .. ' A' + end + + -- Add a door and a goal object. + -- When reaching the goal object the map is finished and a new map is + -- requested. + map = map .. ' I G' + return make_map.makeMap{ + mapName = 'random_length', + mapEntityLayer = map + } +end + +return api diff --git a/assets/game_scripts/demo_levels/replace_model.lua b/assets/game_scripts/demo_levels/replace_model.lua new file mode 100644 index 00000000..4f90f682 --- /dev/null +++ b/assets/game_scripts/demo_levels/replace_model.lua @@ -0,0 +1,82 @@ +-- Demonstration of modifying a texture of a model multiple times. +-- In the level there should apples and lemons of varying colors. +local tensor = require 'dmlab.system.tensor' +local pickups = require 'common.pickups' +local api = {} + +function api:start(episode, seed) + api._count = 0 +end + +function api:nextMap() + return 'seekavoid_arena_01' +end + +local COLORS = { + {255, 168, 0}, + {0, 255, 28}, + {255, 98, 0}, + {250, 0, 255}, + {255, 34, 0}, + {0, 255, 160}, + {0, 223, 255}, +} + +local PREFIX = 'PICKUP_' + +function api:createPickup(classname) + local obj_orig = pickups.defaults[classname] + if obj_orig ~= nil then + local obj = {} + for k, v in pairs(obj_orig) do + obj[k] = v + end + local color = api._count % #COLORS + 1 + api._count = api._count + 1 + obj.classname = obj.classname .. '_' .. color + obj.model = PREFIX .. color .. ':' .. obj.model + return obj + end + return obj_orig +end + +function api:replaceModelName(modelName) + if modelName:sub(1, #PREFIX) == PREFIX then + local prefixTexture, newModelName = modelName:match('(.*:)(.*)') + return newModelName, prefixTexture + end +end + +function api:replaceTextureName(textureName) + -- Remove the texture's prefix. This will load a new copy of the same texture + -- ready for modifyTexture. + if textureName:sub(1, #PREFIX) == PREFIX then + -- Strip prefix and color id. + return textureName:match('.*:(.*)') + end +end + +function api:modifyTexture(textureName, texture) + if textureName:sub(1, #PREFIX) ~= PREFIX then + return false + end + local color = tonumber(textureName:match('(%d+):')) + local r, g, b = unpack(COLORS[color]) + -- Make texture black and white. + texture:mul(1 / 3) + local red = texture:select(3, 1) + local green = texture:select(3, 2) + local blue = texture:select(3, 3) + red:cadd(green):cadd(blue) + green:copy(red) + blue:copy(red) + + -- Set texture color. + red:mul(r / 255) + green:mul(g / 255) + blue:mul(b / 255) + return true +end + +return api + diff --git a/assets/game_scripts/demo_levels/replace_texture.lua b/assets/game_scripts/demo_levels/replace_texture.lua new file mode 100644 index 00000000..7b62ad35 --- /dev/null +++ b/assets/game_scripts/demo_levels/replace_texture.lua @@ -0,0 +1,74 @@ +-- Demonstration of modifying a texture in multiple ways. +-- In the level there should be a blue picture on the wall. + +local game = require 'dmlab.system.game' +local make_map = require 'common.make_map' +local tensor = require 'dmlab.system.tensor' +local api = {} + +function api:start(episode, seed) + make_map.seedRng(1) + api._count = 0 +end + +function api:nextMap() + if api._map then + return api._map + end + api._map = make_map.makeMap{ + mapName = 'demo_rectangle', + mapEntityLayer = 'P ', + useSkybox = true, + } + return api._map +end + +local TEXTURE_NAME = 'textures/decal/lab_games/dec_img_style02_013' +function api:replaceTextureName(textureName) + if textureName == TEXTURE_NAME then + textureName = textureName .. '%01' + end + return textureName +end + +function api:loadTexture(textureName) + if textureName == TEXTURE_NAME .. '%01' then + return tensor.ByteTensor(8, 8, 4):fill(255) + end +end + +local colourIndex = 0 +local columnIndex = 0 + +local function changeTexture(textureData) + for i = 0, 2 do + local c = (math.floor(colourIndex) == i) and 255 or 0 + textureData:select(3, i + 1):fill(c) + end + textureData:select(2, math.floor(columnIndex) + 1):fill(255) + colourIndex = (colourIndex + 0.01) % 3 + columnIndex = (columnIndex + 0.01) % 8 +end + +function api:modifyTexture(textureName, tensorData) + if textureName == TEXTURE_NAME then + assert(tensorData == tensor.ByteTensor(8, 8, 4):fill(255)) + -- Make texture red. + changeTexture(tensorData) + return true + end + return false +end + +local textureData = tensor.ByteTensor(8, 8, 4):fill(255) + +function api:modifyControl(actions) + if actions.crouchJump > 0 then + actions.crouchJump = 0 + changeTexture(textureData) + game:updateTexture(TEXTURE_NAME, textureData) + end + return actions +end + +return api diff --git a/assets/game_scripts/demo_levels/screen_decoration/rectangles.lua b/assets/game_scripts/demo_levels/screen_decoration/rectangles.lua new file mode 100644 index 00000000..0a6a08d8 --- /dev/null +++ b/assets/game_scripts/demo_levels/screen_decoration/rectangles.lua @@ -0,0 +1,52 @@ +-- Demonstration of rendering rectangles in screen space. +local make_map = require 'common.make_map' + +local api = {} + +function api:nextMap() + return make_map.makeMap{mapName = 'rectangles', mapEntityLayer = " P "} +end + +--[[ Renders rectangles to the screen. + +Coordinate system is 0,0 Top Left. Width and height is always 640, 480. + +Keyword Args + + * width(640) - Ideal screen width textures are streched to match this. + * height(480) - Ideal screen height textures are streched to match this. + +Returns list of rectangles to be rendered. +]] + +function api:filledRectangles(args) + local rectangles = { + -- Green rectangle in top right 8th of the screen. + { + x = args.width * 0.75, + y = 0, + width = args.width * 0.25, + height = args.height * 0.25, + rgba = {0.0, 1.0, 0.0, 1.0} + }, + -- Blue semi-transparent top left 8th of the screen. + { + x = 0, + y = 0, + width = args.width * 0.25, + height = args.height * 0.25, + rgba = {0.0, 0.0, 1.0, 0.5} + }, + -- Red semi-transparent center 8th of the screen. + { + x = args.width * 0.375, + y = args.height * 0.375, + width = args.width * 0.25, + height = args.height * 0.25, + rgba = {1.0, 0.0, 0.0, 0.5} + }, + } + return rectangles +end + +return api diff --git a/assets/game_scripts/demo_levels/screen_decoration/text.lua b/assets/game_scripts/demo_levels/screen_decoration/text.lua new file mode 100644 index 00000000..b1800fc6 --- /dev/null +++ b/assets/game_scripts/demo_levels/screen_decoration/text.lua @@ -0,0 +1,79 @@ +-- Demonstration of rendering rectangles in screen space. +local make_map = require 'common.make_map' +local screen_message = require 'common.screen_message' + +local api = {} + +function api:nextMap() + return make_map.makeMap{mapName = 'text', mapEntityLayer = " P "} +end + +--[[ Renders fixed width strings to the screen. + +Coordinate system is 0,0 Top Left. Width and height is always 640, 480. + +Keyword Args + + * max_string_length(79) Strings will be truncated to this length. + * line_height(20) - Multiple are best separated by this distance. + * width(640) - Ideal screen width textures are streched to match this. + * height(480) - Ideal screen height textures are streched to match this. + +Returns text to be rendered. +]] +function api:screenMessages(args) + return { + -- Right text with shadow. + { + x = screen_message.BORDER_SIZE, + y = args.height * 0.5 - args.line_height * 0.5, + message = 'Right Text', + alignment = screen_message.ALIGN_RIGHT, + rgba = {1, 1, 1, 1}, + }, + -- Center text custom drop shadow. + { + x = args.width * 0.5 + 1, + y = args.height * 0.5 - args.line_height * 0.5 + 1, + message = 'Center Text', + alignment = screen_message.ALIGN_CENTER, + rgba = {0.5, 0, 0, 1}, + shadow = false, + }, + -- Center text white + { + x = args.width * 0.5 - 1, + y = args.height * 0.5 - args.line_height * 0.5 - 1, + message = 'Center Text', + alignment = screen_message.ALIGN_CENTER, + rgba = {1, 1, 1, 1}, + shadow = false, + }, + -- Right text with shadow. + { + x = args.width - screen_message.BORDER_SIZE, + y = args.height * 0.5 - args.line_height * 0.5, + message = 'Right Text', + alignment = screen_message.ALIGN_RIGHT, + rgba = {1, 1, 1, 1}, + }, + -- Multiline text + { + x = args.width * 0.5 - 1, + y = args.BORDER_SIZE, + message = 'Multiline Line 1', + alignment = screen_message.ALIGN_LEFT, + rgba = {0.5, 1, 0.5, 1}, + }, + { + x = args.width * 0.5 - 1, + y = screen_message.BORDER_SIZE + args.line_height, + message = 'Multiline Line 2', + alignment = screen_message.ALIGN_LEFT, + rgba = {0.5, 1, 0.5, 1}, + }, + } +end + +return api + diff --git a/assets/game_scripts/demo_levels/set_instruction.lua b/assets/game_scripts/demo_levels/set_instruction.lua new file mode 100644 index 00000000..bf4a5a82 --- /dev/null +++ b/assets/game_scripts/demo_levels/set_instruction.lua @@ -0,0 +1,42 @@ +local make_map = require 'common.make_map' +local pickups = require 'common.pickups' +local custom_observations = require 'decorators.custom_observations' + +local api = {} + +function api:start(episode, seed) + make_map.seedRng(0) -- Use a fixed seed since this is a simple demo level. + api._count = 0 +end + +function api:createPickup(className) + return pickups.defaults[className] +end + +function api:updateSpawnVars(spawnVars) + if spawnVars.classname == 'info_player_start' then + -- Spawn facing the apples. + spawnVars.angle = '0' + spawnVars.randomAngleRange = '0' + end + return spawnVars +end + +function api:nextMap() + api._count = api._count + 1 + local map = 'P' + for i = 1, api._count do + map = map .. 'A' + end + map = map .. 'G' + local str = api._count == 1 and ' apple' or ' apples' + api.setInstruction(api._count .. str) + return make_map.makeMap{ + mapName = 'instruction_map', + mapEntityLayer = map + } +end + +custom_observations.decorate(api) + +return api diff --git a/assets/game_scripts/factories/lt_factory.lua b/assets/game_scripts/factories/lt_factory.lua index 034c04de..6a924000 100644 --- a/assets/game_scripts/factories/lt_factory.lua +++ b/assets/game_scripts/factories/lt_factory.lua @@ -1,126 +1,68 @@ -local random = require 'common.random' +local color_bots = require 'common.color_bots' +local colors = require 'common.colors' local custom_observations = require 'decorators.custom_observations' -local timeout = require 'decorators.timeout' - -local BOT_NAMES_COLOR = { - 'CygniColor', - 'LeonisColor', - 'EpsilonColor', - 'CepheiColor', - 'CentauriColor', - 'DraconisColor' -} - -local BOT_NAMES = { - 'Cygni', - 'Leonis', - 'Epsilon', - 'Cephei', - 'Centauri', - 'Draconis' -} - ---[[ Converts an HSV color value to RGB. Conversion formula adapted from -http://en.wikipedia.org/wiki/HSV_color_space. Assumes h, s and v are contained -in the set [0, 1]. Returns r, g, b each in the set [0, 255]. -]] -local function hsvToRgb(h, s, v) - local r, g, b - - local i = math.floor(h * 6); - local f = h * 6 - i; - local p = v * (1 - s); - local q = v * (1 - f * s); - local t = v * (1 - (1 - f) * s); - - i = i % 6 - - if i == 0 then r, g, b = v, t, p - elseif i == 1 then r, g, b = q, v, p - elseif i == 2 then r, g, b = p, v, t - elseif i == 3 then r, g, b = p, q, v - elseif i == 4 then r, g, b = t, p, v - elseif i == 5 then r, g, b = v, p, q - end - - return r * 255, g * 255, b * 255 -end - -local SATURATION = 1.0 -local VALUE = 1.0 +local random = require 'common.random' +local setting_overrides = require 'decorators.setting_overrides' local factory = {} --[[ Creates a Laser tag API. + Keyword arguments: * `mapName` (string) - Name of map to load. -* `botCount` (number, [-1, 6], default 4) - Number of bots. (-1 for all). +* `botCount` (number, [-1, 6]) - Number of bots. (-1 for all). * `skill` (number, [1.0, 5.0], default 4.0) - Skill level of bot. * `episodeLengthSeconds` (number, default 600) - Episode length in seconds. * `color` (boolean, default false) - Change color of bots each episode. ]] function factory.createLevelApi(kwargs) - assert(kwargs.mapName) - kwargs.botCount = kwargs.botCount or 4 + assert(kwargs.botCount, "must supply botCount") kwargs.skill = kwargs.skill or 4.0 kwargs.episodeLengthSeconds = kwargs.episodeLengthSeconds or 600 kwargs.color = kwargs.color or false - assert(kwargs.botCount <= (kwargs.color and #BOT_NAMES_COLOR or #BOT_NAMES)) + assert(kwargs.botCount <= #color_bots.BOT_NAMES) local api = {} function api:nextMap() - return kwargs.mapName + local map = kwargs.mapName + kwargs.mapName = '' + return map end function api:start(episode, seed, params) - random.seed(seed) + random:seed(seed) if kwargs.color then -- Pick a random angle. - api.bot_hue_degrees_ = random.uniformInt(0, 359) + api._botHueDegrees = random:uniformInt(0, 359) end end if kwargs.color then - function api:modifyTexture(name, tensor) - if string.match(name, 'players/crash_color/dm_character_skin_mask') then - - local hue = api.bot_hue_degrees_ - - -- Based on the mask name, determine the hue using a triad distribution. - if string.sub(name, -string.len('mask_a.tga')) == 'mask_a.tga' then - hue = hue / 360.0 - elseif string.sub(name, -string.len('mask_b.tga')) == 'mask_b.tga' then - hue = ((hue + 120) % 360) / 360.0 - elseif string.sub(name, -string.len('mask_c.tga')) == 'mask_c.tga' then - hue = ((hue + 240) % 360) / 360.0 - else - logging.raiseError('Unrecognised mask: ' .. name) - return - end + function api:modifyTexture(name, skin) + color_bots:findSkin(name, skin) + return false + end - local r, g, b = hsvToRgb(hue, SATURATION, VALUE) - tensor:select(3, 1):fill(r) - tensor:select(3, 2):fill(g) - tensor:select(3, 3):fill(b) - end + function api:mapLoaded() + color_bots:colorizeBots(api._botHueDegrees) end end - function api:addBots() - local bots = {} - for i, name in ipairs(kwargs.color and BOT_NAMES_COLOR or BOT_NAMES) do - if i == kwargs.botCount + 1 then - break - end - bots[#bots + 1] = {name = name, skill = kwargs.skill} - end - return bots + return color_bots:makeBots{ + count = kwargs.botCount, + color = kwargs.color, + skill = kwargs.skill, + } end custom_observations.decorate(api) - timeout.decorate(api, kwargs.episodeLengthSeconds) + setting_overrides.decorate{ + api = api, + apiParams = kwargs, + decorateWithTimeout = true + } return api end diff --git a/assets/game_scripts/factories/random_goal_factory.lua b/assets/game_scripts/factories/random_goal_factory.lua index 67890480..4e9c417a 100644 --- a/assets/game_scripts/factories/random_goal_factory.lua +++ b/assets/game_scripts/factories/random_goal_factory.lua @@ -1,10 +1,13 @@ -local maze_gen = require 'dmlab.system.maze_generation' local game = require 'dmlab.system.game' -local random = require 'common.random' -local pickups = require 'common.pickups' +local map_maker = require 'dmlab.system.map_maker' +local maze_generation = require 'dmlab.system.maze_generation' local helpers = require 'common.helpers' +local pickups = require 'common.pickups' local custom_observations = require 'decorators.custom_observations' local timeout = require 'decorators.timeout' +local random = require 'common.random' +local map_maker = require 'dmlab.system.map_maker' +local randomMap = random(map_maker:randomGen()) local factory = {} @@ -20,27 +23,27 @@ Keyword arguments: function factory.createLevelApi(kwargs) kwargs.scatteredRewardDensity = kwargs.scatteredRewardDensity or 0.1 kwargs.episodeLengthSeconds = kwargs.episodeLengthSeconds or 600 - local maze = maze_gen.MazeGeneration{entity = kwargs.entityLayer} + local maze = maze_generation.mazeGeneration{entity = kwargs.entityLayer} local api = {} - function api:createPickup(class_name) - return pickups.defaults[class_name] + function api:createPickup(class) + return pickups.defaults[class] end function api:start(episode, seed, params) - api._time_remaining = kwargs.episodeLengthSeconds - random.seed(seed) + random:seed(seed) + randomMap:seed(seed) + api._timeRemaining = kwargs.episodeLengthSeconds local height, width = maze:size() height = (height - 1) / 2 width = (width - 1) / 2 - api._goal = {random.uniformInt(1, height) * 2, - random.uniformInt(1, width) * 2} + api._goal = {random:uniformInt(1, height) * 2, + random:uniformInt(1, width) * 2} - local goal_location - local all_spawn_locations = {} - local fruit_locations = {} - local fruit_locations_reverse = {} + local goalLocation + local allSpawnLocations = {} + local fruitLocations = {} maze:visitFill{cell = api._goal, func = function(row, col, distance) if row % 2 == 1 or col % 2 == 1 then return @@ -49,29 +52,22 @@ function factory.createLevelApi(kwargs) col = col / 2 - 1 -- Axis is flipped in DeepMind Lab. row = height - row - 1 - local key = ''.. (col * 100 + 50) .. ' ' .. (row * 100 + 50) .. ' ' + local key = '' .. (col * 100 + 50) .. ' ' .. (row * 100 + 50) .. ' ' if distance == 0 then - goal_location = key .. '20' + goalLocation = key .. '20' end if distance > 0 then - fruit_locations[#fruit_locations + 1] = key .. '20' + fruitLocations[#fruitLocations + 1] = key .. '20' end if distance > 8 then - all_spawn_locations[#all_spawn_locations + 1] = key .. '30' + allSpawnLocations[#allSpawnLocations + 1] = key .. '30' end end} - helpers.shuffleInPlace(fruit_locations) - api._goal_location = goal_location - api._fruit_locations = fruit_locations - api._all_spawn_locations = all_spawn_locations - end - - function api:pickup(spawn_id) - api._count = api._count + 1 - if api._count == api._finish_count then - game:finishMap() - end + random:shuffleInPlace(fruitLocations) + api._goalLocation = goalLocation + api._fruitLocations = fruitLocations + api._allSpawnLocations = allSpawnLocations end function api:updateSpawnVars(spawnVars) @@ -84,39 +80,41 @@ function factory.createLevelApi(kwargs) return spawnVars end - function api:hasEpisodeFinished(time_seconds) - api._time_remaining = kwargs.episodeLengthSeconds - time_seconds - return api._time_remaining <= 0 + function api:hasEpisodeFinished(timeSeconds) + api._timeRemaining = kwargs.episodeLengthSeconds - timeSeconds + return api._timeRemaining <= 0 end function api:nextMap() api._newSpawnVars = {} local maxFruit = math.floor(kwargs.scatteredRewardDensity * - #api._fruit_locations + 0.5) - for i, fruit_location in ipairs(api._fruit_locations) do + #api._fruitLocations + 0.5) + for i, fruitLocation in ipairs(api._fruitLocations) do if i > maxFruit then break end - api._newSpawnVars[fruit_location] = { + api._newSpawnVars[fruitLocation] = { classname = 'apple_reward', - origin = fruit_location + origin = fruitLocation } end - local spawn_location = api._all_spawn_locations[ - random.uniformInt(1, #api._all_spawn_locations)] + local spawnLocation = api._allSpawnLocations[ + random:uniformInt(1, #api._allSpawnLocations)] api._newSpawnVarsPlayerStart = { classname = 'info_player_start', - origin = spawn_location + origin = spawnLocation } - api._newSpawnVars[api._goal_location] = { + api._newSpawnVars[api._goalLocation] = { classname = 'goal', - origin = api._goal_location + origin = api._goalLocation } - - return kwargs.mapName + -- Fast map restarts. + local map = kwargs.mapName + kwargs.mapName = '' + return map end custom_observations.decorate(api) diff --git a/assets/game_scripts/factories/seek_avoid_factory.lua b/assets/game_scripts/factories/seek_avoid_factory.lua index bcb83775..3287aa41 100644 --- a/assets/game_scripts/factories/seek_avoid_factory.lua +++ b/assets/game_scripts/factories/seek_avoid_factory.lua @@ -1,9 +1,11 @@ local game = require 'dmlab.system.game' -local random = require 'common.random' local pickups = require 'common.pickups' local helpers = require 'common.helpers' local custom_observations = require 'decorators.custom_observations' local timeout = require 'decorators.timeout' +local random = require 'common.random' +local map_maker = require 'dmlab.system.map_maker' +local randomMap = random(map_maker:randomGen()) local factory = {} @@ -18,12 +20,13 @@ function factory.createLevelApi(kwargs) local api = {} - function api:createPickup(class_name) - return pickups.defaults[class_name] + function api:createPickup(classname) + return pickups.defaults[classname] end function api:start(episode, seed, params) - random.seed(seed) + random:seed(seed) + randomMap:seed(random:mapGenerationSeed()) api._has_goal = false api._count = 0 api._finish_count = 0 @@ -42,16 +45,16 @@ function factory.createLevelApi(kwargs) local possibleClassNames = helpers.split(spawnVars.random_items, ',') if #possibleClassNames > 0 then classname = possibleClassNames[ - random.uniformInt(1, #possibleClassNames)] + random:uniformInt(1, #possibleClassNames)] end end local pickup = pickups.defaults[spawnVars.classname] if pickup then - if pickup.type == pickups.type.kReward and pickup.quantity > 0 then + if pickup.type == pickups.type.REWARD and pickup.quantity > 0 then api._finish_count = api._finish_count + 1 spawnVars.id = tostring(api._finish_count) end - if pickup.type == pickups.type.kGoal then + if pickup.type == pickups.type.GOAL then api._has_goal = true end end @@ -60,7 +63,10 @@ function factory.createLevelApi(kwargs) end function api:nextMap() - return kwargs.mapName + -- Fast map restarts. + local map = kwargs.mapName + kwargs.mapName = '' + return map end custom_observations.decorate(api) diff --git a/assets/game_scripts/lt_chasm.lua b/assets/game_scripts/lt_chasm.lua index 9b74511a..92cb605a 100644 --- a/assets/game_scripts/lt_chasm.lua +++ b/assets/game_scripts/lt_chasm.lua @@ -1,3 +1,6 @@ local factory = require 'factories.lt_factory' -return factory.createLevelApi{mapName = 'lt_chasm'} +return factory.createLevelApi{ + mapName = 'lt_chasm', + botCount = 4, +} diff --git a/assets/game_scripts/lt_hallway_slope.lua b/assets/game_scripts/lt_hallway_slope.lua index 0f9b0cc1..186a4524 100644 --- a/assets/game_scripts/lt_hallway_slope.lua +++ b/assets/game_scripts/lt_hallway_slope.lua @@ -1,3 +1,6 @@ local factory = require 'factories.lt_factory' -return factory.createLevelApi{mapName = 'lt_hallway_slope'} +return factory.createLevelApi{ + mapName = 'lt_hallway_slope', + botCount = 4, +} diff --git a/assets/game_scripts/lt_horseshoe_color.lua b/assets/game_scripts/lt_horseshoe_color.lua index 04166d06..1c541940 100644 --- a/assets/game_scripts/lt_horseshoe_color.lua +++ b/assets/game_scripts/lt_horseshoe_color.lua @@ -1,3 +1,7 @@ local factory = require 'factories.lt_factory' -return factory.createLevelApi{mapName = 'lt_horseshoe_color', color = true} +return factory.createLevelApi{ + mapName = 'lt_horseshoe_color', + color = true, + botCount = 4, +} diff --git a/assets/game_scripts/lt_space_bounce_hard.lua b/assets/game_scripts/lt_space_bounce_hard.lua index 00302c92..9fa7d3a6 100644 --- a/assets/game_scripts/lt_space_bounce_hard.lua +++ b/assets/game_scripts/lt_space_bounce_hard.lua @@ -3,5 +3,6 @@ local factory = require 'factories.lt_factory' return factory.createLevelApi{ mapName = 'lt_space_bounce_01', skill = 5, - color = true + color = true, + botCount = 4, } diff --git a/assets/game_scripts/patterns/chequered_d.png b/assets/game_scripts/patterns/chequered_d.png new file mode 100644 index 00000000..50c6fec2 Binary files /dev/null and b/assets/game_scripts/patterns/chequered_d.png differ diff --git a/assets/game_scripts/patterns/crosses_d.png b/assets/game_scripts/patterns/crosses_d.png new file mode 100644 index 00000000..9990a865 Binary files /dev/null and b/assets/game_scripts/patterns/crosses_d.png differ diff --git a/assets/game_scripts/patterns/diagonal_stripe_d.png b/assets/game_scripts/patterns/diagonal_stripe_d.png new file mode 100644 index 00000000..f86b4282 Binary files /dev/null and b/assets/game_scripts/patterns/diagonal_stripe_d.png differ diff --git a/assets/game_scripts/patterns/discs_d.png b/assets/game_scripts/patterns/discs_d.png new file mode 100644 index 00000000..da26fad2 Binary files /dev/null and b/assets/game_scripts/patterns/discs_d.png differ diff --git a/assets/game_scripts/patterns/hex_d.png b/assets/game_scripts/patterns/hex_d.png new file mode 100644 index 00000000..d5162fd7 Binary files /dev/null and b/assets/game_scripts/patterns/hex_d.png differ diff --git a/assets/game_scripts/patterns/pinstripe_d.png b/assets/game_scripts/patterns/pinstripe_d.png new file mode 100644 index 00000000..d74630d8 Binary files /dev/null and b/assets/game_scripts/patterns/pinstripe_d.png differ diff --git a/assets/game_scripts/patterns/spots_d.png b/assets/game_scripts/patterns/spots_d.png new file mode 100644 index 00000000..df3d77d8 Binary files /dev/null and b/assets/game_scripts/patterns/spots_d.png differ diff --git a/assets/game_scripts/patterns/swirls_d.png b/assets/game_scripts/patterns/swirls_d.png new file mode 100644 index 00000000..ba30f8a0 Binary files /dev/null and b/assets/game_scripts/patterns/swirls_d.png differ diff --git a/assets/game_scripts/player/dm_character_skin_mask_a.png b/assets/game_scripts/player/dm_character_skin_mask_a.png new file mode 100644 index 00000000..2c21b88a Binary files /dev/null and b/assets/game_scripts/player/dm_character_skin_mask_a.png differ diff --git a/assets/game_scripts/player/dm_character_skin_mask_b.png b/assets/game_scripts/player/dm_character_skin_mask_b.png new file mode 100644 index 00000000..1193a2ce Binary files /dev/null and b/assets/game_scripts/player/dm_character_skin_mask_b.png differ diff --git a/assets/game_scripts/player/dm_character_skin_mask_c.png b/assets/game_scripts/player/dm_character_skin_mask_c.png new file mode 100644 index 00000000..ec8541c2 Binary files /dev/null and b/assets/game_scripts/player/dm_character_skin_mask_c.png differ diff --git a/assets/game_scripts/random_maze.lua b/assets/game_scripts/random_maze.lua index e464c787..8b4a9f5b 100644 --- a/assets/game_scripts/random_maze.lua +++ b/assets/game_scripts/random_maze.lua @@ -1,4 +1,4 @@ -local maze_gen = require 'dmlab.system.maze_generation' +local maze_generation = require 'dmlab.system.maze_generation' local tensor = require 'dmlab.system.tensor' local random = require 'common.random' local make_map = require 'common.make_map' @@ -13,11 +13,11 @@ local api = {} local function getRandomEvenCoodinate(rows, cols) -- Shape must be bigger than 3 and odd. - assert (rows > 2 and rows % 2 == 1) - assert (cols > 2 and cols % 2 == 1) + assert(rows > 2 and rows % 2 == 1) + assert(cols > 2 and cols % 2 == 1) return { - random.uniformInt(1, math.floor(rows / 2)) * 2, - random.uniformInt(1, math.floor(cols / 2)) * 2 + random:uniformInt(1, math.floor(rows / 2)) * 2, + random:uniformInt(1, math.floor(cols / 2)) * 2 } end @@ -49,7 +49,7 @@ local function generateTensorMaze(rows, cols) maze(r, c):val(1) local vistableCells = findVisitableCells(r, c, maze) if #vistableCells > 0 then - local choice = vistableCells[random.uniformInt(1, #vistableCells)] + local choice = vistableCells[random:uniformInt(1, #vistableCells)] maze(unpack(choice[2])):val(1) stack[#stack + 1] = choice[1] else @@ -59,24 +59,20 @@ local function generateTensorMaze(rows, cols) return maze end -function api:commandLine(oldCommandLine) - return make_map.commandLine(oldCommandLine) -end - -function api:createPickup(className) - return pickups.defaults[className] +function api:createPickup(classname) + return pickups.defaults[classname] end function api:start(episode, seed, params) - random.seed(seed) + random:seed(seed) local rows, cols = 15, 15 local mazeT = generateTensorMaze(rows, cols) - local maze = maze_gen.MazeGeneration{height = rows, width = cols} + local maze = maze_generation.mazeGeneration{height = rows, width = cols} local variations = {'.', 'A', 'B', 'C'} mazeT:applyIndexed(function(val, index) local row, col = unpack(index) - if 1 < row and row < rows and 1 < col and row < rows and - random.uniformReal(0, 1) < 0.15 then + if 1 < row and row < rows and 1 < col and col < cols and + random:uniformReal(0, 1) < 0.15 then maze:setEntityCell(row, col, ' ') else maze:setEntityCell(row, col, val == 0 and '*' or ' ') @@ -109,8 +105,11 @@ function api:start(episode, seed, params) print(maze:entityLayer()) io.flush() - api._maze_name = make_map.makeMap('map_' .. episode .. '_' .. seed, - maze:entityLayer(), maze:variationsLayer()) + api._maze_name = make_map.makeMap{ + mapName = 'map_random_maze', + mapEntityLayer = maze:entityLayer(), + mapVariationsLayer = maze:variationsLayer() + } end function api:nextMap() diff --git a/assets/game_scripts/test_levels/callbacks_test.lua b/assets/game_scripts/test_levels/callbacks_test.lua new file mode 100644 index 00000000..b36db60c --- /dev/null +++ b/assets/game_scripts/test_levels/callbacks_test.lua @@ -0,0 +1,98 @@ +-- Tested in deepmind/engine/callbacks_test.cc. +local tensor = require 'dmlab.system.tensor' +local api = {} + +api._count = 0 + +api._observations = { + LOCATION = tensor.Tensor{10, 20, 30}, + ORDER = tensor.ByteTensor(), + EPISODE = tensor.Tensor{0}, +} + +local models = { + cube = { + surfaces = { + cube_surface = { + vertices = tensor.FloatTensor{ + { -0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 0.0, 0.0 }, + { 0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 1.0, 0.0 }, + { 0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 1.0, 1.0 }, + { -0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 0.0, 1.0 }, + { 0.5, -0.5, -0.5, 1.0, 0.0, 0.0, 0.0, 0.0 }, + { 0.5, -0.5, 0.5, 1.0, 0.0, 0.0, 1.0, 0.0 }, + { 0.5, 0.5, 0.5, 1.0, 0.0, 0.0, 1.0, 1.0 }, + { 0.5, 0.5, -0.5, 1.0, 0.0, 0.0, 0.0, 1.0 }, + { 0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 0.0, 0.0 }, + { -0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 0.0 }, + { -0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 1.0 }, + { 0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 0.0, 1.0 }, + { -0.5, -0.5, 0.5, -1.0, 0.0, 0.0, 0.0, 0.0 }, + { -0.5, -0.5, -0.5, -1.0, 0.0, 0.0, 1.0, 0.0 }, + { -0.5, 0.5, -0.5, -1.0, 0.0, 0.0, 1.0, 1.0 }, + { -0.5, 0.5, 0.5, -1.0, 0.0, 0.0, 0.0, 1.0 }, + { -0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 0.0, 0.0 }, + { 0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 1.0, 0.0 }, + { 0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 1.0, 1.0 }, + { -0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 1.0 }, + { 0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 0.0, 0.0 }, + { -0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 1.0, 0.0 }, + { -0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 1.0, 1.0 }, + { 0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 0.0, 1.0 } + }, + indices = tensor.Int32Tensor{ + { 1, 2, 3 }, + { 1, 3, 4 }, + { 5, 6, 7 }, + { 5, 7, 8 }, + { 9, 10, 11 }, + { 9, 11, 12 }, + { 13, 14, 15 }, + { 13, 15, 16 }, + { 17, 18, 19 }, + { 17, 19, 20 }, + { 21, 22, 23 }, + { 21, 23, 24 } + }, + shaderName = 'textures/model/beam' + } + } + } +} + +function api:customObservationSpec() + return { + {name = 'LOCATION', type = 'Doubles', shape = {3}}, + {name = 'ORDER', type = 'Bytes', shape = {0}}, + {name = 'EPISODE', type = 'Doubles', shape = {1}}, + } +end + +function api:init(settings) + api._settings = settings + local order = settings.order or '' + api._observations.ORDER = tensor.ByteTensor{order:byte(1, -1)} +end + +function api:customObservation(name) + return api._observations[name] +end + +function api:start(episode, seed) + api._observations.EPISODE:val(episode) +end + +function api:commandLine(oldCommandLine) + return oldCommandLine .. ' ' .. api._settings.command +end + +function api:nextMap() + api._count = api._count + 1 + return 'lt_chasm_' .. api._count +end + +function api:createModel(modelName) + return models[modelName] +end + +return api diff --git a/assets/game_scripts/test_levels/debug_observation_test.lua b/assets/game_scripts/test_levels/debug_observation_test.lua new file mode 100644 index 00000000..13f2224a --- /dev/null +++ b/assets/game_scripts/test_levels/debug_observation_test.lua @@ -0,0 +1,39 @@ +local factory = require 'factories.lt_factory' +local make_map = require 'common.make_map' +local tensor = require 'dmlab.system.tensor' + +local MAP = [[ +******** +* *P * +*P * +******** +]] + +local api = factory.createLevelApi{ + episodeLengthSeconds = 60 * 5, + botCount = 1 +} + +local spawnCount = 1 +function api:updateSpawnVars(spawnVars) + if spawnVars.classname == 'info_player_start' then + -- Spawn facing East. + spawnVars.angle = '0' + spawnVars.randomAngleRange = '0' + -- Make Bot spawn on first 'P' and player on second 'P'. + spawnVars.nohumans = spawnCount == 1 and '1' or '0' + spawnVars.nobots = spawnCount == 2 and '1' or '0' + spawnCount = spawnCount + 1 + end + return spawnVars +end + +function api:nextMap() + return make_map.makeMap{ + mapName = 'empty_room', + mapEntityLayer = MAP, + allowBots = true, + } +end + +return api diff --git a/assets/game_scripts/test_levels/empty_room_test.lua b/assets/game_scripts/test_levels/empty_room_test.lua new file mode 100644 index 00000000..a66f8460 --- /dev/null +++ b/assets/game_scripts/test_levels/empty_room_test.lua @@ -0,0 +1,46 @@ +-- Tested externally. +local make_map = require 'common.make_map' +local pickups = require 'common.pickups' +local custom_observations = require 'decorators.custom_observations' +local game = require 'dmlab.system.game' +local timeout = require 'decorators.timeout' +local api = {} + +local MAP_ENTITIES = [[ +********* +* * +* * +* * +* P * +* * +* * +* * +********* +]] + +function api:init(params) + make_map.seedRng(1) + api._map = make_map.makeMap{ + mapName = "empty_room", + mapEntityLayer = MAP_ENTITIES, + useSkybox = true, + theme = "TETRIS" + } +end + +function api:nextMap() + return self._map +end + +function api:updateSpawnVars(spawnVars) + if spawnVars.classname == "info_player_start" then + -- Spawn facing East. + spawnVars.angle = "0" + spawnVars.randomAngleRange = "0" + end + return spawnVars +end + +timeout.decorate(api, 60 * 60) +custom_observations.decorate(api) +return api diff --git a/assets/game_scripts/test_levels/entity_info_test.lua b/assets/game_scripts/test_levels/entity_info_test.lua new file mode 100644 index 00000000..47529932 --- /dev/null +++ b/assets/game_scripts/test_levels/entity_info_test.lua @@ -0,0 +1,58 @@ +-- Tested in python/episode_time_test.py. +local custom_observations = require 'decorators.custom_observations' +local game_entities = require 'dmlab.system.game_entities' +local helpers = require 'common.helpers' +local make_map = require 'common.make_map' +local pickups = require 'common.pickups' +local tensor = require 'dmlab.system.tensor' +local api = {} + +function api:createPickup(classname) + return pickups.defaults[classname] +end + +local MAP_ENTITIES = [[PAAALLL]] + +function api:updateSpawnVars(spawnVars) + if spawnVars.classname == "info_player_start" then + -- Spawn facing East. + spawnVars.angle = "0" + spawnVars.randomAngleRange = "0" + end + return spawnVars +end + +function api:nextMap() + make_map.seedRng(1) + api._map = make_map.makeMap{ + mapName = "empty_room", + mapEntityLayer = MAP_ENTITIES, + useSkybox = true, + pickups = {L = 'lemon_reward', A = 'apple_reward'}, + } + return api._map +end + +local PICKUP_CLASSES = {'lemon_reward', 'apple_reward'} + +local function observePickups() + local entities = game_entities:entities(PICKUP_CLASSES) + local result = tensor.DoubleTensor(#entities, 5) + for i, entity in pairs(entities) do + local x, y, z = unpack(entity.position) + local v = entity.visible and 1 or 0 + local row = result(i) + row(1):val(x) + row(2):val(y) + row(3):val(z) + row(4):val(v) + row(5):val(pickups.defaults[entity.classname].quantity) + end + return result +end + +custom_observations.decorate(api) +custom_observations.addSpec('DEBUG.PICKUPS', 'Doubles', {0, 5}, observePickups) + +return api + diff --git a/assets/game_scripts/test_levels/episode_time_test.lua b/assets/game_scripts/test_levels/episode_time_test.lua new file mode 100644 index 00000000..316e8875 --- /dev/null +++ b/assets/game_scripts/test_levels/episode_time_test.lua @@ -0,0 +1,23 @@ +-- Tested in python/episode_time_test.py. +local game = require 'dmlab.system.game' +local tensor = require 'dmlab.system.tensor' + +local api = {} + +function api:customObservationSpec() + return { + {name = 'EPISODE_TIME_SECONDS', type = 'Doubles', shape = {1}}, + } +end + +function api:customObservation(name) + if name == 'EPISODE_TIME_SECONDS' then + return tensor.Tensor{game:episodeTimeSeconds()} + end +end + +function api:nextMap() + return 'lookat_test' +end + +return api diff --git a/assets/game_scripts/test_levels/event_test.lua b/assets/game_scripts/test_levels/event_test.lua new file mode 100644 index 00000000..d7a4a19b --- /dev/null +++ b/assets/game_scripts/test_levels/event_test.lua @@ -0,0 +1,32 @@ +-- Tested in python/dmlab_module_test.py. +local events = require 'dmlab.system.events' +local tensor = require 'dmlab.system.tensor' + +local api = {} + +local map = 'seekavoid_arena_01' + +function api:nextMap() + local result = map + map = '' + return result +end + +function api:start(episode, seed) + events:add('TEXT', 'EPISODE ' .. episode) + events:add('DOUBLE', tensor.DoubleTensor{{1, 0}, {0, 1}}) + events:add('BYTE', tensor.ByteTensor{2, 2}) + events:add('ALL', 'Text', tensor.ByteTensor{3}, tensor.DoubleTensor{7}) +end + + +function api:hasEpisodeFinished(time) + if time >= 1.0 then + events:add('LOG', 'Episode ended') + return true + else + return false + end +end + +return api diff --git a/assets/game_scripts/test_levels/extra_entities_test.lua b/assets/game_scripts/test_levels/extra_entities_test.lua new file mode 100644 index 00000000..bbcd7482 --- /dev/null +++ b/assets/game_scripts/test_levels/extra_entities_test.lua @@ -0,0 +1,82 @@ +-- Tested externally. +local make_map = require 'common.make_map' +local pickups = require 'common.pickups' +local custom_observations = require 'decorators.custom_observations' +local game = require 'dmlab.system.game' +local timeout = require 'decorators.timeout' +local api = {} + +local MAP_ENTITIES = [[ +********* +* * +* * +* * +* P * +* * +* * +* * +********* +]] + +function api:init(params) + make_map.seedRng(1) + api._map = make_map.makeMap{ + mapName = 'empty_room', + mapEntityLayer = MAP_ENTITIES, + useSkybox = true, + theme = 'TETRIS' + } +end + +function api:nextMap() + return self._map +end + +function api:updateSpawnVars(spawnVars) + if spawnVars.classname == 'info_player_start' then + -- Spawn facing East. + spawnVars.angle = '0' + spawnVars.randomAngleRange = '0' + end + return spawnVars +end + +function api:extraEntities() + return { + { + classname = 'apple_reward', + origin = '550 450 30', + count = '1', + }, + { + classname = 'apple_reward', + origin = '600 450 30', + count = '2', -- Override reward for testing purposes. + }, + { + classname = 'apple_reward', + origin = '650 450 30', + count = '3', + }, + { + classname = 'apple_reward', + origin = '700 450 30', + count = '4', + }, + { + classname = 'apple_reward', + origin = '750 450 30', + count = '5', + } + } +end + +-- Create apple explicitly +function api:createPickup(classname) + return pickups.defaults[classname] +end + +timeout.decorate(api, 60 * 60) +custom_observations.decorate(api) +return api + diff --git a/assets/game_scripts/test_levels/extra_entities_with_bots_test.lua b/assets/game_scripts/test_levels/extra_entities_with_bots_test.lua new file mode 100644 index 00000000..dd10b262 --- /dev/null +++ b/assets/game_scripts/test_levels/extra_entities_with_bots_test.lua @@ -0,0 +1,76 @@ +-- Tested externally. +local helpers = require 'common.helpers' +local make_map = require 'common.make_map' +local custom_observations = require 'decorators.custom_observations' +local events = require 'dmlab.system.events' +local game = require 'dmlab.system.game' +local timeout = require 'decorators.timeout' +local api = {} + +local MAP_NO_WEAPON = [[ +********* +* P* +* ***** * +* ***** * +* ***** * +* ***** * +* ***** * +*P * +********* +]] + +function api:init(params) + make_map.seedRng(1) + api._map = make_map.makeMap{ + mapName = 'empty_room', + mapEntityLayer = MAP_NO_WEAPON, + useSkybox = true, + theme = 'TETRIS', + allowBots = true + } + self._spawnWeapons = params.spawnWeapons +end + +function api:nextMap() + return self._map +end + +function api:addBots() + local bots = { + {name = 'Cygni', skill = 5.0} + } + return bots +end + +function api:extraEntities() + if helpers.fromString(self._spawnWeapons) then + -- List of entities to create. + local vars = { + { + classname = 'weapon_rocketlauncher', + origin = '750 150 30', + spawnflags = "1" + }, + { + classname = 'weapon_lightning', + origin = '150 750 30', + spawnflags = "1" + } + } + return vars + else + return nil + end +end + +function api:rewardOverride(args) + if args.reason == "TAG_PLAYER" and args.playerId == 1 and + args.otherPlayerId == 0 then + events:add('PLAYER_TAGGED', 'Player tagged by bot') + end +end + +timeout.decorate(api, 60 * 60) +custom_observations.decorate(api) +return api + diff --git a/assets/game_scripts/test_levels/lookat_test.lua b/assets/game_scripts/test_levels/lookat_test.lua new file mode 100644 index 00000000..b44d4b5f --- /dev/null +++ b/assets/game_scripts/test_levels/lookat_test.lua @@ -0,0 +1,44 @@ +-- Tested in python/lookat_test.py. +local game = require 'dmlab.system.game' +local tensor = require 'dmlab.system.tensor' +local random = require 'common.random' +local pickups = require 'common.pickups' +local custom_observations = require 'decorators.custom_observations' + +local api = {} +local lookAt = tensor.Tensor(4) + +function api:createPickup(classname) + return pickups.defaults[classname] +end + +function api:start(episode, seed) + random:seed(seed) +end + +function api:nextMap() + return 'lookat_test' +end + +function api:customObservationSpec() + return { + {name = 'LOOK_AT', type = 'Doubles', shape = lookAt:shape()}, + } +end + +function api:customObservation(name) + if name == 'LOOK_AT' then + return lookAt + end +end + +function api:lookat(entity, lookedAt, position) + lookAt(1):val(position[1]) + lookAt(2):val(position[2]) + lookAt(3):val(position[3]) + lookAt(4):val(lookedAt and 1.0 or 0.0) +end + +custom_observations.decorate(api) + +return api diff --git a/assets/game_scripts/test_levels/model_test.lua b/assets/game_scripts/test_levels/model_test.lua new file mode 100644 index 00000000..d8592ad8 --- /dev/null +++ b/assets/game_scripts/test_levels/model_test.lua @@ -0,0 +1,70 @@ +-- Tested in deepmind/model_generation/lua_model_test.cc. +local model = require 'dmlab.system.model' +local transform = require 'common.transform' + +local api = {} + +function api:createModel(modelName) + local models = { + cone = model:cone{ + phiSegments = 4, + radiusSegments = 4, + heightSegments = 4, + shaderName = 'textures/model/beam' + }, + cube = model:cube{ + segments = 4, + shaderName = 'textures/model/beam' + }, + cylinder = model:cylinder{ + phiSegments = 4, + radiusSegments = 4, + heightSegments = 4, + shaderName = 'textures/model/beam' + }, + sphere = model:sphere{ + phiSegments = 4, + thetaSegments = 4, + shaderName = 'textures/model/beam' + }, + hierarchy = model:hierarchy{ + transform = transform.rotateX(180), + model = model:cone{ + radius = 10, + height = 20, + shaderName = 'textures/model/apple_d' + }, + children = { + centre_bottom_centre_p = { + model = model:sphere{ + radius = 8, + shaderName = 'textures/model/pig_d' + }, + children = { + centre_top_left_p = { + locator = 'centre_bottom_centre_s', + model = model:cylinder{ + radius = 0.5, + height = 5, + shaderName = 'textures/model/cherry_d' + }, + children = { + centre_top_centre_p = { + locator = 'centre_bottom_centre_s', + model = model:cone{ + radius = 3, + height = 2, + shaderName = 'textures/model/apple_d' + } + } + } + } + } + } + } + } + } + return models[modelName] +end + +return api diff --git a/assets/game_scripts/test_levels/raycast_test.lua b/assets/game_scripts/test_levels/raycast_test.lua new file mode 100644 index 00000000..81c47265 --- /dev/null +++ b/assets/game_scripts/test_levels/raycast_test.lua @@ -0,0 +1,64 @@ +local make_map = require 'common.make_map' +local pickups = require 'common.pickups' +local custom_observations = require 'decorators.custom_observations' +local game = require 'dmlab.system.game' +local tensor = require 'dmlab.system.tensor' +local helpers = require 'common.helpers' +local api = {} + +local MAP_ENTITIES = [[ +***** +*A A* +* * * +*P*A* +***** +]] + + +function api:init(params) + make_map.seedRng(1) + self._map = make_map.makeMap{ + mapName = "empty_room", + mapEntityLayer = MAP_ENTITIES, + useSkybox = true, + theme = "MISHMASH" + } + self.rewards = {} +end + +function api:nextMap() + return self._map +end + +function api:createPickup(classname) + return pickups.defaults[classname] +end + +function api:updateSpawnVars(spawnVars) + if spawnVars.classname == "info_player_start" then + -- Spawn facing north. + spawnVars.angle = "90" + spawnVars.randomAngleRange = "0" + else + self.rewards[#self.rewards + 1] = + helpers.spawnVarToNumberTable(spawnVars.origin) + end + return spawnVars +end + +function api:customObservationSpec() + return {{name = 'RAYCASTS', type = 'Doubles', shape = {0}}} +end + +function api:customObservation(name) + assert(name == 'RAYCASTS', 'Bad observation name') + local playerInfo = game:playerInfo() + local result = tensor.DoubleTensor(#self.rewards) + for i, pos in ipairs(self.rewards) do + result(i):val(game:raycast(playerInfo.pos, pos)) + end + return result +end + +custom_observations.decorate(api) +return api diff --git a/assets/game_scripts/test_levels/recording_test.lua b/assets/game_scripts/test_levels/recording_test.lua new file mode 100644 index 00000000..4b7eb207 --- /dev/null +++ b/assets/game_scripts/test_levels/recording_test.lua @@ -0,0 +1,43 @@ +-- Tested in testing/recording_test.cc. +local game = require 'dmlab.system.game' +local make_map = require 'common.make_map' +local pickups = require 'common.pickups' + +local api = {} + +function api:start(episode, seed) + make_map.seedRng(seed) + api._count = 0 +end + +function api:createPickup(classname) + return pickups.defaults[classname] +end + +function api:nextMap() + api._count = api._count + 1 + + return make_map.makeMap{ + mapName = 'recording_test', + mapEntityLayer = string.rep('A', api._count) .. 'P' + } +end + +function api:updateSpawnVars(spawnVars) + if spawnVars.classname == 'info_player_start' then + -- Spawn facing south. + spawnVars.angle = '180' + spawnVars.randomAngleRange = '0' + end + return spawnVars +end + +function api:hasEpisodeFinished(time_seconds) + -- Each map lasts one second. + if time_seconds >= api._count then + game:finishMap() + end + return false +end + +return api diff --git a/assets/game_scripts/test_levels/seed_test.lua b/assets/game_scripts/test_levels/seed_test.lua new file mode 100644 index 00000000..1c8593cf --- /dev/null +++ b/assets/game_scripts/test_levels/seed_test.lua @@ -0,0 +1,38 @@ +-- Tested in testing/load_level_test.cc. +local asserts = require 'testing.asserts' +local test_runner = require 'testing.test_runner' +local random = require 'common.random' + +local tests = {} +local testLevelScript = false + +function tests.testSeed() + assert(testLevelScript) + + local seed = 2 + + local api = require(testLevelScript) + api:init{invocationMode = "testbed"} + api:start(0, seed) + local rand1 = random:uniformReal(0, 1) + local rand2 = random:uniformReal(0, 1) + + -- If we generate the exact same random numbers again then it's very likely + -- that the random number generated was seeded. + api:start(0, seed) + asserts.EQ(rand1, random:uniformReal(0, 1)) + asserts.EQ(rand2, random:uniformReal(0, 1)) +end + +local run_tests = test_runner.run(tests) + +-- Override the test runner initialiser to set the name of the level to test. +local init = run_tests.init +function run_tests:init(params) + assert(params.testLevelScript) + testLevelScript = params.testLevelScript + + return init(run_tests, params) +end + +return run_tests diff --git a/assets/game_scripts/test_levels/spawn_inventory_test.lua b/assets/game_scripts/test_levels/spawn_inventory_test.lua new file mode 100644 index 00000000..cf9ed256 --- /dev/null +++ b/assets/game_scripts/test_levels/spawn_inventory_test.lua @@ -0,0 +1,57 @@ +local api = {} + +-- Tested externally. +local game = require 'dmlab.system.game' +local make_map = require 'common.make_map' +local pickups = require 'common.pickups' +local inventory = require 'common.inventory' +local custom_observations = require 'decorators.custom_observations' +local timeout = require 'decorators.timeout' +local api = {} + +local MAP_ENTITIES = [[ +********* +* * +* * +* * +* P * +* * +* * +* * +********* +]] + +function api:init(params) + make_map.seedRng(1) + api._map = make_map.makeMap{ + mapName = "empty_room", + mapEntityLayer = MAP_ENTITIES, + useSkybox = true, + theme = "TETRIS" + } +end + +function api:nextMap() + return self._map +end + +function api:updateSpawnVars(spawnVars) + if spawnVars.classname == "info_player_start" then + -- Spawn facing East. + spawnVars.angle = "0" + spawnVars.randomAngleRange = "0" + end + return spawnVars +end + +function api:spawnInventory(loadOut) + local view = inventory.View(loadOut) + view:setGadgetAmount(inventory.GADGETS.ORB, inventory.UNLIMITED) + view:setGadgets{inventory.GADGETS.ORB} + return view:loadOut() +end + +timeout.decorate(api, 60 * 60) +custom_observations.decorate(api) +return api + diff --git a/assets/game_scripts/test_levels/text_observation_test.lua b/assets/game_scripts/test_levels/text_observation_test.lua new file mode 100644 index 00000000..45c53e79 --- /dev/null +++ b/assets/game_scripts/test_levels/text_observation_test.lua @@ -0,0 +1,17 @@ +-- Tested in python/dmlab_module_test.py. +local api = {} + +function api:nextMap() + return 'seekavoid_arena_01' +end + +function api:customObservationSpec() + return {{name = 'CUSTOM_TEXT', type = 'String', shape = {0}}} +end + +function api:customObservation(name) + assert(name == 'CUSTOM_TEXT') + return 'Example Output' +end + +return api diff --git a/assets/game_scripts/test_levels/update_inventory_test.lua b/assets/game_scripts/test_levels/update_inventory_test.lua new file mode 100644 index 00000000..c6257e17 --- /dev/null +++ b/assets/game_scripts/test_levels/update_inventory_test.lua @@ -0,0 +1,81 @@ +local api = {} + +-- Tested externally. +local game = require 'dmlab.system.game' +local make_map = require 'common.make_map' +local inventory = require 'common.inventory' +local custom_observations = require 'decorators.custom_observations' +local timeout = require 'decorators.timeout' +local tensor = require 'dmlab.system.tensor' +local api = {} + +local MAP_ENTITIES = [[ +********* +* * +* * +* * +* P * +* * +* * +* * +********* +]] + +function api:init(params) + self._playerView = {} + make_map.seedRng(1) + api._map = make_map.makeMap{ + mapName = "empty_room", + mapEntityLayer = MAP_ENTITIES, + useSkybox = true, + theme = "TETRIS" + } +end + +function api:nextMap() + return self._map +end + +function api:customObservationSpec() + return { + {name = 'DEBUG.AMOUNT', type = 'Doubles', shape = {1}}, + {name = 'DEBUG.GADGET', type = 'Doubles', shape = {1}}, + } +end + +function api:customObservation(name) + local view = self._playerView[1] + if name == 'DEBUG.AMOUNT' then + return tensor.Tensor{view:gadgetAmount(view:gadget())} + elseif name == 'DEBUG.GADGET' then + return tensor.Tensor{view:gadget()} + end +end + +function api:updateSpawnVars(spawnVars) + if spawnVars.classname == "info_player_start" then + -- Spawn facing East. + spawnVars.angle = "0" + spawnVars.randomAngleRange = "0" + end + return spawnVars +end + +function api:spawnInventory(loadOut) + local view = inventory.View(loadOut) + view:setGadgets{inventory.GADGETS.ORB, inventory.GADGETS.RAPID} + view:setGadgetAmount(inventory.GADGETS.ORB, 2) + view:setGadgetAmount(inventory.GADGETS.RAPID, 10) + self._playerView[view:playerId()] = view + return view:loadOut() +end + +function api:updateInventory(loadOut) + local view = inventory.View(loadOut) + self._playerView[view:playerId()] = view +end + +timeout.decorate(api, 60 * 60) +custom_observations.decorate(api) +return api + diff --git a/assets/game_scripts/testing/asserts.lua b/assets/game_scripts/testing/asserts.lua new file mode 100644 index 00000000..4a6876e0 --- /dev/null +++ b/assets/game_scripts/testing/asserts.lua @@ -0,0 +1,95 @@ +local asserts = {} + +local function fail(message, optional) + optional = optional and (' ' .. optional) or '' + error(message .. optional, 3) +end + +local function areTablesEqual(target, expect) + local targetSize = 0 + for k, v in pairs(target) do + targetSize = targetSize + 1 + end + for k, v in pairs(expect) do + if type(v) == 'table' then + if not areTablesEqual(target[k], v) then + return false + end + elseif target[k] ~= v then + return false + end + targetSize = targetSize - 1 + end + if targetSize ~= 0 then + -- There were values in target not present in expect + return false + end + return true +end + +function asserts.EQ(target, expect, msg) + if target ~= expect then + fail('Expected: ' .. tostring(expect) .. '. ' .. + 'Actual: ' .. tostring(target) .. '.', msg) + end +end + +function asserts.NE(target, expect, msg) + if target == expect then + fail('Expected values to differ: ' .. tostring(expect), msg) + end +end + +-- Assert target > expect +function asserts.GT(target, expect, msg) + if target <= expect then + fail('Expected: ' .. tostring(target) .. ' > ' .. tostring(expect), msg) + end +end + +-- Assert target >= expect +function asserts.GE(target, expect, msg) + if target < expect then + fail('Expected: ' .. tostring(target) .. ' >= ' .. tostring(expect), msg) + end +end + +-- Assert target < expect +function asserts.LT(target, expect, msg) + if target >= expect then + fail('Expected: ' .. tostring(target) .. ' < ' .. tostring(expect), msg) + end +end + +-- Assert target <= expect +function asserts.LE(target, expect, msg) + if target > expect then + fail('Expected: ' .. tostring(target) .. ' <= ' .. tostring(expect), msg) + end +end + + +function asserts.tablesEQ(target, expect, msg) + if type(target) ~= 'table' then fail('1st argument should be a table') end + if type(expect) ~= 'table' then fail('2nd argument should be a table') end + if not areTablesEqual(target, expect) then + fail("Expected equal table values.", msg) + end +end + +function asserts.tablesNE(target, expect, msg) + if type(target) ~= 'table' then fail('1st argument should be a table') end + if type(expect) ~= 'table' then fail('2nd argument should be a table') end + if areTablesEqual(target, expect) then + fail("Expected tables with different values.", msg) + end +end + +function asserts.shouldFail(fn) + local status, out = pcall(fn) + if status then + error("Expected an error but call succeeded.", 2) + end +end + +return asserts diff --git a/assets/game_scripts/testing/test_runner.lua b/assets/game_scripts/testing/test_runner.lua new file mode 100644 index 00000000..5ffd92d9 --- /dev/null +++ b/assets/game_scripts/testing/test_runner.lua @@ -0,0 +1,49 @@ +--[[ Useful for running Lua tests in DM Lab. + +In the build file: + +``` +cc_test( + name = "test_script", + args = ["lua_tests/test_script.lua"], + data = [":test_script.lua"], + deps = ["/labyrinth/testing:lua_unit_test_lib"], +) +``` + +In test_script.lua: + +``` +local tests = {} + +function tests.testAssert() + assert(true) +end + +return test_runner.run(tests) +``` +]] + +local test_runner = {} + +function test_runner.run(tests) + local function runAllTests() + local statusAll = true + local errors = '' + for name, test in pairs(tests) do + print('[ LuaTest ] - ' .. name) + local status, msg = xpcall(test, debug.traceback) + if not status then + errors = errors .. 'function tests.' .. name .. '\n' .. msg .. '\n' + print('[ Failed ] - ' .. name) + statusAll = false + else + print('[ Success ] - ' .. name) + end + end + return (statusAll and 0 or 1), errors + end + return {init = runAllTests} +end + +return test_runner diff --git a/assets/game_scripts/tests/callbacks_test.lua b/assets/game_scripts/tests/callbacks_test.lua deleted file mode 100644 index 858f5306..00000000 --- a/assets/game_scripts/tests/callbacks_test.lua +++ /dev/null @@ -1,43 +0,0 @@ -local tensor = require 'dmlab.system.tensor' -local api = {} - -api._count = 0 - -api._observations = { - LOCATION = tensor.Tensor{10, 20, 30}, - ORDER = tensor.ByteTensor(), - EPISODE = tensor.Tensor{0}, -} - -function api:customObservationSpec() - return { - {name = 'LOCATION', type = 'Doubles', shape = {3}}, - {name = 'ORDER', type = 'Bytes', shape = {0}}, - {name = 'EPISODE', type = 'Doubles', shape = {1}}, - } -end - -function api:init(settings) - api._settings = settings - local order = settings.order or '' - api._observations.ORDER = tensor.ByteTensor{order:byte(1,-1)} -end - -function api:customObservation(name) - return api._observations[name] -end - -function api:start(episode, seed) - api._observations.EPISODE:val(episode) -end - -function api:commandLine(oldCommandLine) - return oldCommandLine .. ' ' .. api._settings.command -end - -function api:nextMap() - api._count = api._count + 1 - return 'lt_chasm_' .. api._count -end - -return api diff --git a/assets/game_scripts/tests/demo_map.lua b/assets/game_scripts/tests/demo_map.lua deleted file mode 100644 index 6da418fb..00000000 --- a/assets/game_scripts/tests/demo_map.lua +++ /dev/null @@ -1,27 +0,0 @@ -local make_map = require 'common.make_map' -local pickups = require 'common.pickups' -local api = {} - -function api:start(episode, seed) - make_map.seedRng(seed) - api._count = 0 -end - -function api:commandLine(oldCommandLine) - return make_map.commandLine(oldCommandLine) -end - -function api:createPickup(className) - return pickups.defaults[className] -end - -function api:nextMap() - map = "G I A P" - api._count = api._count + 1 - for i = 0, api._count do - map = map.." A" - end - return make_map.makeMap("demo_map_" .. api._count, map) -end - -return api diff --git a/assets/game_scripts/themes/decals.lua b/assets/game_scripts/themes/decals.lua new file mode 100644 index 00000000..44f372c0 --- /dev/null +++ b/assets/game_scripts/themes/decals.lua @@ -0,0 +1,18 @@ +local NUMBER_OF_STYLES = 4 +local NUMBER_OF_IMAGES_PER_STYLE = 20 +local DEFAULT_WALL_DECALS = {} +local DEFAULT_WALL_IMAGES = {} +for i = 1, NUMBER_OF_STYLES do + for j = 1, NUMBER_OF_IMAGES_PER_STYLE do + local img = string.format('decal/lab_games/dec_img_style%02d_%03d', i, j) + DEFAULT_WALL_DECALS[#DEFAULT_WALL_DECALS + 1] = { + tex = img .. '_nonsolid' + } + DEFAULT_WALL_IMAGES[#DEFAULT_WALL_IMAGES + 1] = 'textures/' .. img + end +end + +return { + decals = DEFAULT_WALL_DECALS, + images = DEFAULT_WALL_IMAGES, +} diff --git a/assets/game_scripts/themes/texture_sets.lua b/assets/game_scripts/themes/texture_sets.lua new file mode 100644 index 00000000..ff9c28a0 --- /dev/null +++ b/assets/game_scripts/themes/texture_sets.lua @@ -0,0 +1,223 @@ +local decals = require 'themes.decals' + +local texture_sets = {} + +texture_sets.MISHMASH = { + floor = { + {tex = 'map/lab_games/lg_style_01_floor_orange'}, + {tex = 'map/lab_games/lg_style_01_floor_orange_bright'}, + {tex = 'map/lab_games/lg_style_01_floor_blue'}, + {tex = 'map/lab_games/lg_style_01_floor_blue_bright'}, + {tex = 'map/lab_games/lg_style_02_floor_blue'}, + {tex = 'map/lab_games/lg_style_02_floor_blue_bright'}, + {tex = 'map/lab_games/lg_style_02_floor_green'}, + {tex = 'map/lab_games/lg_style_02_floor_green_bright'}, + {tex = 'map/lab_games/lg_style_03_floor_green'}, + {tex = 'map/lab_games/lg_style_03_floor_green_bright'}, + {tex = 'map/lab_games/lg_style_03_floor_blue'}, + {tex = 'map/lab_games/lg_style_03_floor_blue_bright'}, + {tex = 'map/lab_games/lg_style_04_floor_blue'}, + {tex = 'map/lab_games/lg_style_04_floor_blue_bright'}, + {tex = 'map/lab_games/lg_style_04_floor_orange'}, + {tex = 'map/lab_games/lg_style_04_floor_orange_bright'}, + {tex = 'map/lab_games/lg_style_05_floor_blue'}, + {tex = 'map/lab_games/lg_style_05_floor_blue_bright'}, + {tex = 'map/lab_games/lg_style_05_floor_orange'}, + {tex = 'map/lab_games/lg_style_05_floor_orange_bright'}, + }, + ceiling = {{tex = 'map/lab_games/fake_sky'}}, + wall = { + {tex = 'map/lab_games/lg_style_01_wall_green'}, + {tex = 'map/lab_games/lg_style_01_wall_green_bright'}, + {tex = 'map/lab_games/lg_style_01_wall_red'}, + {tex = 'map/lab_games/lg_style_01_wall_red_bright'}, + {tex = 'map/lab_games/lg_style_02_wall_yellow'}, + {tex = 'map/lab_games/lg_style_02_wall_yellow_bright'}, + {tex = 'map/lab_games/lg_style_02_wall_blue'}, + {tex = 'map/lab_games/lg_style_02_wall_blue_bright'}, + {tex = 'map/lab_games/lg_style_03_wall_orange'}, + {tex = 'map/lab_games/lg_style_03_wall_orange_bright'}, + {tex = 'map/lab_games/lg_style_03_wall_gray'}, + {tex = 'map/lab_games/lg_style_03_wall_gray_bright'}, + {tex = 'map/lab_games/lg_style_04_wall_green'}, + {tex = 'map/lab_games/lg_style_04_wall_green_bright'}, + {tex = 'map/lab_games/lg_style_04_wall_red'}, + {tex = 'map/lab_games/lg_style_04_wall_red_bright'}, + {tex = 'map/lab_games/lg_style_05_wall_red'}, + {tex = 'map/lab_games/lg_style_05_wall_red_bright'}, + {tex = 'map/lab_games/lg_style_05_wall_yellow'}, + {tex = 'map/lab_games/lg_style_05_wall_yellow_bright'} + }, + wallDecals = decals.decals, +} + +texture_sets.TRON = { + floor = { + {tex = 'map/lab_games/lg_style_01_floor_orange'}, + {tex = 'map/lab_games/lg_style_01_floor_orange_bright'}, + {tex = 'map/lab_games/lg_style_01_floor_blue'}, + {tex = 'map/lab_games/lg_style_01_floor_blue_bright'}, + }, + ceiling = {{tex = 'map/lab_games/fake_sky'}}, + wall = { + {tex = 'map/lab_games/lg_style_01_wall_green'}, + {tex = 'map/lab_games/lg_style_01_wall_green_bright'}, + {tex = 'map/lab_games/lg_style_01_wall_red'}, + {tex = 'map/lab_games/lg_style_01_wall_red_bright'}, + }, + wallDecals = decals.decals, +} + +texture_sets.MINESWEEPER = { + floor = { + {tex = 'map/lab_games/lg_style_04_floor_blue'}, + {tex = 'map/lab_games/lg_style_04_floor_blue_bright'}, + {tex = 'map/lab_games/lg_style_04_floor_orange'}, + {tex = 'map/lab_games/lg_style_04_floor_orange_bright'}, + }, + ceiling = {{tex = 'map/lab_games/fake_sky'}}, + wall = { + {tex = 'map/lab_games/lg_style_04_wall_green'}, + {tex = 'map/lab_games/lg_style_04_wall_green_bright'}, + {tex = 'map/lab_games/lg_style_04_wall_red'}, + {tex = 'map/lab_games/lg_style_04_wall_red_bright'}, + }, + wallDecals = decals.decals, + floorModels = { + {mod = 'models/fut_obj_barbell_01.md3'}, + {mod = 'models/fut_obj_cylinder_01.md3'}, + }, +} + + +texture_sets.TETRIS = { + floor = { + {tex = 'map/lab_games/lg_style_02_floor_blue'}, + {tex = 'map/lab_games/lg_style_02_floor_blue_bright'}, + {tex = 'map/lab_games/lg_style_02_floor_green'}, + {tex = 'map/lab_games/lg_style_02_floor_green_bright'}, + }, + ceiling = {{tex = 'map/lab_games/fake_sky'}}, + wall = { + {tex = 'map/lab_games/lg_style_02_wall_yellow'}, + {tex = 'map/lab_games/lg_style_02_wall_yellow_bright'}, + {tex = 'map/lab_games/lg_style_02_wall_blue'}, + {tex = 'map/lab_games/lg_style_02_wall_blue_bright'}, + }, + wallDecals = decals.decals, +} + +texture_sets.GO = { + floor = { + {tex = 'map/lab_games/lg_style_03_floor_green'}, + {tex = 'map/lab_games/lg_style_03_floor_green_bright'}, + {tex = 'map/lab_games/lg_style_03_floor_blue'}, + {tex = 'map/lab_games/lg_style_03_floor_blue_bright'}, + }, + ceiling = {{tex = 'map/lab_games/fake_sky'}}, + wall = { + {tex = 'map/lab_games/lg_style_03_wall_orange'}, + {tex = 'map/lab_games/lg_style_03_wall_orange_bright'}, + {tex = 'map/lab_games/lg_style_03_wall_gray'}, + {tex = 'map/lab_games/lg_style_03_wall_gray_bright'}, + }, + wallDecals = decals.decals, + floorModels = { + {mod = 'models/fut_obj_barbell_01.md3'}, + {mod = 'models/fut_obj_coil_01.md3'}, + {mod = 'models/fut_obj_cone_01.md3'}, + {mod = 'models/fut_obj_crossbar_01.md3'}, + {mod = 'models/fut_obj_cube_01.md3'}, + {mod = 'models/fut_obj_cylinder_01.md3'}, + {mod = 'models/fut_obj_doubleprism_01.md3'}, + {mod = 'models/fut_obj_glowball_01.md3'} + } +} + +texture_sets.PACMAN = { + floor = { + {tex = 'map/lab_games/lg_style_05_floor_blue'}, + {tex = 'map/lab_games/lg_style_05_floor_blue_bright'}, + {tex = 'map/lab_games/lg_style_05_floor_orange'}, + {tex = 'map/lab_games/lg_style_05_floor_orange_bright'}, + }, + ceiling = {{tex = 'map/lab_games/fake_sky'}}, + wall = { + {tex = 'map/lab_games/lg_style_05_wall_red'}, + {tex = 'map/lab_games/lg_style_05_wall_red_bright'}, + {tex = 'map/lab_games/lg_style_05_wall_yellow'}, + {tex = 'map/lab_games/lg_style_05_wall_yellow_bright'}, + }, + wallDecals = decals.decals, + floorModels = { + {mod = 'models/fut_obj_toroid_01.md3'}, + {mod = 'models/fut_obj_cylinder_01.md3'}, + {mod = 'models/fut_obj_crossbar_01.md3'}, + {mod = 'models/fut_obj_cube_01.md3'}, + }, +} + +texture_sets.INVISIBLE_WALLS = { + floor = {{tex = 'map/lab_games/lg_style_01_floor_orange'}}, + ceiling = {{tex = "map/lab_games/fake_sky"}}, + wall = {{tex = 'map/poltergeist'}}, +} + +texture_sets.CUSTOMIZABLE_FLOORS = { + variations = { + A = {floor = {{tex = 'map/lab_games/lg_style_01_floor_placeholder_A'}}}, + B = {floor = {{tex = 'map/lab_games/lg_style_01_floor_placeholder_B'}}}, + C = {floor = {{tex = 'map/lab_games/lg_style_01_floor_placeholder_C'}}}, + D = {floor = {{tex = 'map/lab_games/lg_style_01_floor_placeholder_D'}}}, + E = {floor = {{tex = 'map/lab_games/lg_style_01_floor_placeholder_E'}}}, + F = {floor = {{tex = 'map/lab_games/lg_style_01_floor_placeholder_F'}}}, + }, + floor = {{tex = 'map/lab_games/lg_style_01_floor_placeholder_0'}}, + ceiling = {{tex = 'map/lab_games/fake_sky'}}, + wall = { + {tex = 'map/lab_games/lg_style_01_wall_green'}, + {tex = 'map/lab_games/lg_style_01_wall_green_bright'}, + {tex = 'map/lab_games/lg_style_01_wall_red'}, + {tex = 'map/lab_games/lg_style_01_wall_red_bright'}, + {tex = 'map/lab_games/lg_style_02_wall_yellow'}, + {tex = 'map/lab_games/lg_style_02_wall_yellow_bright'}, + {tex = 'map/lab_games/lg_style_02_wall_blue'}, + {tex = 'map/lab_games/lg_style_02_wall_blue_bright'}, + {tex = 'map/lab_games/lg_style_03_wall_orange'}, + {tex = 'map/lab_games/lg_style_03_wall_orange_bright'}, + {tex = 'map/lab_games/lg_style_03_wall_gray'}, + {tex = 'map/lab_games/lg_style_03_wall_gray_bright'}, + {tex = 'map/lab_games/lg_style_04_wall_green'}, + {tex = 'map/lab_games/lg_style_04_wall_green_bright'}, + {tex = 'map/lab_games/lg_style_04_wall_red'}, + {tex = 'map/lab_games/lg_style_04_wall_red_bright'}, + {tex = 'map/lab_games/lg_style_05_wall_red'}, + {tex = 'map/lab_games/lg_style_05_wall_red_bright'}, + {tex = 'map/lab_games/lg_style_05_wall_yellow'}, + {tex = 'map/lab_games/lg_style_05_wall_yellow_bright'} + }, + wallDecals = decals.decals, +} + +texture_sets.CAPTURE_THE_FLAG = { + variations = { + A = { + floor = {{tex = 'map/lab_games/lg_style_01_floor_red_team_d'}}, + wall = {{tex = 'map/lab_games/lg_style_01_wall_red'}}, + }, + B = { + floor = {{tex = 'map/lab_games/lg_style_01_floor_blue_team_d'}}, + wall = {{tex = 'map/lab_games/lg_style_02_wall_blue'}}, + }, + E = { + floor = {{tex = 'map/lab_games/lg_style_04_floor_orange'}}, + wall = {{tex = 'map/lab_games/lg_style_03_wall_orange'}}, + }, + }, + floor = {{tex = 'map/lab_games/lg_style_03_floor_green'}}, + ceiling = {{tex = 'map/lab_games/fake_sky'}}, + wall = {{tex = 'map/lab_games/lg_style_01_wall_green'}}, + wallDecals = decals.decals, +} + +return texture_sets diff --git a/assets/game_scripts/themes/themes.lua b/assets/game_scripts/themes/themes.lua new file mode 100644 index 00000000..7b04ca3c --- /dev/null +++ b/assets/game_scripts/themes/themes.lua @@ -0,0 +1,98 @@ +local map_maker = require 'dmlab.system.map_maker' +local random = require 'common.random' +local randomMap = random(map_maker:randomGen()) + +local themes = {} + +function themes.fromTextureSet(opts) + assert(opts.textureSet, "Must supply a textureSet") + local ts = opts.textureSet + local decalFrequency = opts.decalFrequency or 0.1 + local floorModelFrequency = opts.floorModelFrequency or 0.05 + local theme = {} + local themeVariation = {} + local riser = ((ts.riser and ts.riser[1]) + or {tex = 'map/lab_games/lg_style_02_wall_blue'}) + local tread = ((ts.tread and ts.tread[1]) + or {tex = 'map/black_d', width = 64, height = 64}) + themeVariation.default = { + floor = ts.floor[1], + ceiling = ts.ceiling[1], + wallN = ts.wall[1], + wallE = ts.wall[1], + wallS = ts.wall[1], + wallW = ts.wall[1], + riser = riser, + tread = tread, + } + + local wallDecorations = {} + if ts.wallDecals then + for i = 1, #ts.wallDecals do + wallDecorations[#wallDecorations + 1] = ts.wallDecals[i] + end + end + + local function variationSet(ts, variation, property) + return ts.variations and ts.variations[variation] and + ts.variations[variation][property] or ts[property] + end + + function theme:mazeVariation(variation) + if not themeVariation[variation] then + local wall = randomMap:choice(variationSet(ts, variation, 'wall')) + themeVariation[variation] = { + floor = randomMap:choice(variationSet(ts, variation, 'floor')), + ceiling = randomMap:choice(variationSet(ts, variation, 'ceiling')), + wallN = wall, + wallE = wall, + wallS = wall, + wallW = wall, + } + end + return themeVariation[variation] + end + + -- Only create function if it will return anything. + if decalFrequency > 0 and ts.wallDecals and #ts.wallDecals > 0 then + function theme:placeWallDecals(allWallLocations) + local decorationCount = math.floor(math.min(0.5 + + decalFrequency * #allWallLocations, #ts.wallDecals)) + local wallDecals = {} + if decorationCount > 0 then + randomMap:shuffleInPlace(allWallLocations) + local wallDecalsShuffled = randomMap:shuffle(ts.wallDecals) + for i = 1, decorationCount do + wallDecals[i] = { + index = allWallLocations[i].index, + decal = wallDecalsShuffled[i], + } + end + end + return wallDecals + end + end + + -- Only create function if it will return anything. + if floorModelFrequency > 0 and ts.floorModels and #ts.floorModels > 0 then + function theme:placeFloorModels(allFloorLocations) + local decorationCount = math.floor(floorModelFrequency * + #allFloorLocations) + local floorModels = {} + if decorationCount > 0 and #ts.floorModels > 0 then + randomMap:shuffleInPlace(allFloorLocations) + for i = 1, decorationCount do + floorModels[i] = { + index = allFloorLocations[i].index, + model = randomMap:choice(ts.floorModels) + } + end + end + return floorModels + end + end + + return theme +end + +return themes diff --git a/assets/icons/iconf_blu1.tga b/assets/icons/iconf_blu1.tga new file mode 100644 index 00000000..d4434643 Binary files /dev/null and b/assets/icons/iconf_blu1.tga differ diff --git a/assets/icons/iconf_blu2.tga b/assets/icons/iconf_blu2.tga new file mode 100644 index 00000000..33455cf2 Binary files /dev/null and b/assets/icons/iconf_blu2.tga differ diff --git a/assets/icons/iconf_blu3.tga b/assets/icons/iconf_blu3.tga new file mode 100644 index 00000000..63ce1c68 Binary files /dev/null and b/assets/icons/iconf_blu3.tga differ diff --git a/assets/icons/iconf_red1.tga b/assets/icons/iconf_red1.tga new file mode 100644 index 00000000..8b495e55 Binary files /dev/null and b/assets/icons/iconf_red1.tga differ diff --git a/assets/icons/iconf_red2.tga b/assets/icons/iconf_red2.tga new file mode 100644 index 00000000..61043978 Binary files /dev/null and b/assets/icons/iconf_red2.tga differ diff --git a/assets/icons/iconf_red3.tga b/assets/icons/iconf_red3.tga new file mode 100644 index 00000000..6be2591b Binary files /dev/null and b/assets/icons/iconf_red3.tga differ diff --git a/assets/maps/big_screen.map b/assets/maps/big_screen.map new file mode 100644 index 00000000..a268275c --- /dev/null +++ b/assets/maps/big_screen.map @@ -0,0 +1,217 @@ +// entity 0 +{ +"classname" "worldspawn" + +// skybox brush 0 +{ +( 4232 3896 -136 ) ( 4752 3896 -136 ) ( 4232 3424 -136 ) map/lg_sky_01_dn 140 257 0 -0.507812 0.460938 0 0 0 +( 4232 3896 232 ) ( 4232 3424 232 ) ( 4232 3424 216 ) map/lg_sky_01_dn 257 0 0 -0.460938 0.007812 0 0 0 +( 4752 3896 232 ) ( 4232 3896 232 ) ( 4232 3896 216 ) map/lg_sky_01_dn 140 0 0 -0.507812 0.007812 0 0 0 +( 4752 3424 232 ) ( 4752 3896 232 ) ( 4752 3896 216 ) map/lg_sky_01_dn 257 0 0 -0.460938 0.007812 0 0 0 +( 4232 3424 232 ) ( 4752 3424 232 ) ( 4752 3424 216 ) map/lg_sky_01_dn 140 0 0 -0.507812 0.007812 0 0 0 +( 4752 3896 -144 ) ( 4232 3896 -144 ) ( 4232 3424 -144 ) map/lg_sky_01_dn 140 257 0 -0.507812 0.460938 0 0 0 +} +// skybox brush 1 +{ +( 4232 3896 224 ) ( 4232 3424 224 ) ( 4752 3896 224 ) map/lg_sky_01_up 140 257 0 -0.507812 0.460938 0 0 0 +( 4232 3896 232 ) ( 4232 3424 232 ) ( 4232 3424 216 ) map/lg_sky_01_up 257 0 0 -0.460938 0.007812 0 0 0 +( 4752 3896 232 ) ( 4232 3896 232 ) ( 4232 3896 216 ) map/lg_sky_01_up 140 0 0 -0.507812 0.007812 0 0 0 +( 4752 3424 232 ) ( 4752 3896 232 ) ( 4752 3896 216 ) map/lg_sky_01_up 257 0 0 -0.460938 0.007812 0 0 0 +( 4232 3424 232 ) ( 4752 3424 232 ) ( 4752 3424 216 ) map/lg_sky_01_up 140 0 0 -0.507812 0.007812 0 0 0 +( 4232 3424 232 ) ( 4232 3896 232 ) ( 4752 3896 232 ) map/lg_sky_01_up 140 257 0 -0.507812 0.460938 0 0 0 +} +// skybox brush 2 +{ +( 4752 3432 232 ) ( 4232 3432 232 ) ( 4752 3432 216 ) map/lg_sky_01_bk 140 631 0 -0.507812 0.367188 0 0 0 +( 4232 3896 232 ) ( 4232 3424 232 ) ( 4232 3424 216 ) map/lg_sky_01_bk 29 631 0 -0.007812 0.367188 0 0 0 +( 4752 3424 232 ) ( 4752 3896 232 ) ( 4752 3896 216 ) map/lg_sky_01_bk 29 631 0 -0.007812 0.367188 0 0 0 +( 4232 3424 232 ) ( 4752 3424 232 ) ( 4752 3424 216 ) map/lg_sky_01_bk 140 631 0 -0.507812 0.367188 0 0 0 +( 4232 3424 232 ) ( 4232 3896 232 ) ( 4752 3896 232 ) map/lg_sky_01_bk 140 29 0 -0.507812 0.007812 0 0 0 +( 4752 3896 -144 ) ( 4232 3896 -144 ) ( 4232 3424 -144 ) map/lg_sky_01_bk 140 29 0 -0.507812 0.007812 0 0 0 +} +// skybox brush 3 +{ +( 4744 3896 232 ) ( 4744 3424 232 ) ( 4744 3896 216 ) map/lg_sky_01_rt 257 631 0 -0.460938 0.367188 0 0 0 +( 4752 3896 232 ) ( 4232 3896 232 ) ( 4232 3896 216 ) map/lg_sky_01_rt 27 631 0 -0.007812 0.367188 0 0 0 +( 4752 3424 232 ) ( 4752 3896 232 ) ( 4752 3896 216 ) map/lg_sky_01_rt 257 631 0 -0.460938 0.367188 0 0 0 +( 4232 3424 232 ) ( 4752 3424 232 ) ( 4752 3424 216 ) map/lg_sky_01_rt 27 631 0 -0.007812 0.367188 0 0 0 +( 4232 3424 232 ) ( 4232 3896 232 ) ( 4752 3896 232 ) map/lg_sky_01_rt 27 257 0 -0.007812 0.460938 0 0 0 +( 4752 3896 -144 ) ( 4232 3896 -144 ) ( 4232 3424 -144 ) map/lg_sky_01_rt 27 257 0 -0.007812 0.460938 0 0 0 +} +// skybox brush 4 +{ +( 4232 3888 232 ) ( 4752 3888 232 ) ( 4232 3888 216 ) map/lg_sky_01_ft 140 631 0 -0.507812 0.367188 0 0 0 +( 4232 3896 232 ) ( 4232 3424 232 ) ( 4232 3424 216 ) map/lg_sky_01_ft 29 631 0 -0.007812 0.367188 0 0 0 +( 4752 3896 232 ) ( 4232 3896 232 ) ( 4232 3896 216 ) map/lg_sky_01_ft 140 631 0 -0.507812 0.367188 0 0 0 +( 4752 3424 232 ) ( 4752 3896 232 ) ( 4752 3896 216 ) map/lg_sky_01_ft 29 631 0 -0.007812 0.367188 0 0 0 +( 4232 3424 232 ) ( 4232 3896 232 ) ( 4752 3896 232 ) map/lg_sky_01_ft 140 29 0 -0.507812 0.007812 0 0 0 +( 4752 3896 -144 ) ( 4232 3896 -144 ) ( 4232 3424 -144 ) map/lg_sky_01_ft 140 29 0 -0.507812 0.007812 0 0 0 +} +// skybox brush 5 +{ +( 4240 3424 232 ) ( 4240 3896 232 ) ( 4240 3424 216 ) map/lg_sky_01_lf 257 631 0 -0.460938 0.367188 0 0 0 +( 4232 3896 232 ) ( 4232 3424 232 ) ( 4232 3424 216 ) map/lg_sky_01_lf 257 631 0 -0.460938 0.367188 0 0 0 +( 4752 3896 232 ) ( 4232 3896 232 ) ( 4232 3896 216 ) map/lg_sky_01_lf 27 631 0 -0.007812 0.367188 0 0 0 +( 4232 3424 232 ) ( 4752 3424 232 ) ( 4752 3424 216 ) map/lg_sky_01_lf 27 631 0 -0.007812 0.367188 0 0 0 +( 4232 3424 232 ) ( 4232 3896 232 ) ( 4752 3896 232 ) map/lg_sky_01_lf 27 257 0 -0.007812 0.460938 0 0 0 +( 4752 3896 -144 ) ( 4232 3896 -144 ) ( 4232 3424 -144 ) map/lg_sky_01_lf 27 257 0 -0.007812 0.460938 0 0 0 +} + +// brush 0 +{ +( 256 -520 -1024 ) ( -256 -520 -1024 ) ( -256 -1032 -1024 ) map/lg_sky_02 272 0 0 -4.000000 0.015625 0 0 0 +( -256 -1032 1024 ) ( -256 -520 1024 ) ( 256 -520 1024 ) map/lg_sky_02 272 0 0 -4.000000 0.015625 0 0 0 +( -128 -1032 8 ) ( 384 -1032 8 ) ( 384 -1032 0 ) map/lg_sky_02 272 256 0 -4.000000 4.000000 0 0 0 +( 1088 -1032 -184 ) ( 1088 -520 -184 ) ( 1088 -520 -192 ) map/lg_sky_02 0 256 0 -0.015625 4.000000 0 0 0 +( -960 -520 -176 ) ( -960 -1032 -176 ) ( -960 -1032 -184 ) map/lg_sky_02 0 256 0 -0.015625 4.000000 0 0 0 +( 256 -1024 8 ) ( -256 -1024 8 ) ( 256 -1024 0 ) map/lg_sky_02 272 256 0 -4.000000 4.000000 0 0 0 +} +// brush 1 +{ +( 1088 248 -184 ) ( 1088 -264 -184 ) ( 1088 248 -192 ) map/lg_sky_02 256 256 0 -4.031250 4.000000 0 0 0 +( 1096 1032 8 ) ( 584 1032 8 ) ( 584 1032 0 ) map/lg_sky_02 0 256 0 -0.015625 4.000000 0 0 0 +( 1096 -248 8 ) ( 1096 264 8 ) ( 1096 264 0 ) map/lg_sky_02 256 256 0 -4.031250 4.000000 0 0 0 +( 584 -1032 8 ) ( 1096 -1032 8 ) ( 1096 -1032 0 ) map/lg_sky_02 0 256 0 -0.015625 4.000000 0 0 0 +( 584 -264 1024 ) ( 584 248 1024 ) ( 1096 248 1024 ) map/lg_sky_02 0 256 0 -0.015625 4.031250 0 0 0 +( 1096 248 -1024 ) ( 584 248 -1024 ) ( 584 -264 -1024 ) map/lg_sky_02 0 256 0 -0.015625 4.031250 0 0 0 +} +// brush 2 +{ +( 32 0 64 ) ( -32 0 64 ) ( -32 -64 64 ) map/lab_games/lg_style_01_4tile_d.tga 0 0 0 0.031200 0.031200 0 0 0 +( -32 -64 72 ) ( -32 0 72 ) ( 32 0 72 ) map/lab_games/lg_style_01_4tile_d.tga 0 0 0 0.031200 0.031200 0 0 0 +( -32 -64 8 ) ( 32 -64 8 ) ( 32 -64 0 ) map/lab_games/lg_style_01_4tile_d.tga 0 0 0 0.031200 0.031200 0 0 0 +( 32 -64 8 ) ( 32 0 8 ) ( 32 0 0 ) map/lab_games/lg_style_01_4tile_d.tga 0 0 0 0.031200 0.031200 0 0 0 +( 32 0 8 ) ( -32 0 8 ) ( -32 0 0 ) map/lab_games/lg_style_01_4tile_d.tga 0 0 0 0.031200 0.031200 0 0 0 +( -32 0 8 ) ( -32 -64 8 ) ( -32 -64 0 ) map/lab_games/lg_style_01_4tile_d.tga 0 0 0 0.031200 0.031200 0 0 0 +} +// brush 3 +{ +( -832 1032 8 ) ( -1344 1032 8 ) ( -832 1032 0 ) map/lg_sky_02 272 256 0 -4.000000 4.000000 0 0 0 +( 1088 1024 -176 ) ( 1088 1536 -176 ) ( 1088 1536 -184 ) map/lg_sky_02 0 256 0 -0.015625 4.000000 0 0 0 +( -128 1024 8 ) ( 384 1024 8 ) ( 384 1024 0 ) map/lg_sky_02 272 256 0 -4.000000 4.000000 0 0 0 +( -960 1536 -184 ) ( -960 1024 -184 ) ( -960 1024 -192 ) map/lg_sky_02 0 256 0 -0.015625 4.000000 0 0 0 +( 384 1536 1024 ) ( 384 1024 1024 ) ( -128 1024 1024 ) map/lg_sky_02 272 0 0 -4.000000 0.015625 0 0 0 +( -128 1024 -1024 ) ( 384 1024 -1024 ) ( 384 1536 -1024 ) map/lg_sky_02 272 0 0 -4.000000 0.015625 0 0 0 +} +// brush 4 +{ +( 384 256 1032 ) ( 384 -256 1032 ) ( -128 -256 1032 ) map/lg_sky_02 271 256 0 -4.031250 4.031250 0 0 0 +( 376 1032 848 ) ( -136 1032 848 ) ( -136 1032 840 ) map/lg_sky_02 271 0 0 -4.031250 0.015625 0 0 0 +( -968 200 848 ) ( -968 -312 848 ) ( -968 -312 840 ) map/lg_sky_02 256 0 0 -4.031250 0.015625 0 0 0 +( -120 -1032 848 ) ( 392 -1032 848 ) ( 392 -1032 840 ) map/lg_sky_02 271 0 0 -4.031250 0.015625 0 0 0 +( 1096 -184 848 ) ( 1096 328 848 ) ( 1096 328 840 ) map/lg_sky_02 256 0 0 -4.031250 0.015625 0 0 0 +( 384 -256 1024 ) ( 384 256 1024 ) ( -128 -256 1024 ) map/lg_sky_02 271 256 0 -4.031250 4.031250 0 0 0 +} +// brush 5 +{ +( -200 -200 -1024 ) ( -200 328 -1024 ) ( 328 328 -1024 ) map/lg_sky_02 271 256 0 -4.031250 4.031250 0 0 0 +( 328 328 -1032 ) ( -200 328 -1032 ) ( -200 -200 -1032 ) map/lg_sky_02 271 256 0 -4.031250 4.031250 0 0 0 +( 1096 328 -1032 ) ( 1096 -200 -1032 ) ( 1096 -200 -1024 ) map/lg_sky_02 256 0 0 -4.031250 0.015625 0 0 0 +( 264 -1032 -1032 ) ( -264 -1032 -1032 ) ( -264 -1032 -1024 ) map/lg_sky_02 271 0 0 -4.031250 0.015625 0 0 0 +( -968 -200 -1032 ) ( -968 328 -1032 ) ( -968 328 -1024 ) map/lg_sky_02 256 0 0 -4.031250 0.015625 0 0 0 +( -120 1032 -1032 ) ( 408 1032 -1032 ) ( 408 1032 -1024 ) map/lg_sky_02 271 0 0 -4.031250 0.015625 0 0 0 +} +// brush 6 +{ +( -1472 -256 -1024 ) ( -960 -256 -1024 ) ( -960 256 -1024 ) map/lg_sky_02 0 256 0 -0.015625 4.000000 0 0 0 +( -960 256 1024 ) ( -960 -256 1024 ) ( -1472 -256 1024 ) map/lg_sky_02 0 256 0 -0.015625 4.000000 0 0 0 +( -960 1024 8 ) ( -1472 1024 8 ) ( -1472 1024 0 ) map/lg_sky_02 0 256 0 -0.015625 4.000000 0 0 0 +( -1472 -1024 8 ) ( -960 -1024 8 ) ( -960 -1024 0 ) map/lg_sky_02 0 256 0 -0.015625 4.000000 0 0 0 +( -960 -1032 8 ) ( -960 -520 8 ) ( -960 -520 0 ) map/lg_sky_02 256 256 0 -4.000000 4.000000 0 0 0 +( -968 1024 -176 ) ( -968 512 -176 ) ( -968 1024 -184 ) map/lg_sky_02 256 256 0 -4.000000 4.000000 0 0 0 +} + +// brush 0 +{ +patchDef2 +{ +map/slot1 +( 3 3 0 0 0 ) +( +( ( -56 58.5 67 0 1 ) ( -56 58.5 123 0 0.5 ) ( -56 58.5 179 0 0 ) ) +( ( 0 58.5 67 0.5 1 ) ( 0 58.5 123 0.5 0.5 ) ( 0 58.5 179 0.5 0 ) ) +( ( 56 58.5 67 1 1 ) ( 56 58.5 123 1 0.5 ) ( 56 58.5 179 1 0 ) ) +) +} +} +} + +//skybox +{ +"origin" "4464 3656 -96" +"classname" "_skybox" +} + +// skybox entity 1 +{ +"modelscale" "1.5" +"angle" "90" +"_remap" "*;textures/map/lab_games/stadium_d" +"classname" "misc_model" +"origin" "4464 3656 -34" +"model" "models/stadium.md3" +} + +// entity 1 +{ +"classname" "trigger_lookat" +"targetname" "big_screen" +"target" "lua_callback" +"origin" "0 -216 16" +// brush 0 +{ +patchDef2 +{ +common/caulk +( 3 3 0 0 0 ) +( +( ( -56 58 67 0 1 ) ( -56 58 123 0 0.5 ) ( -56 58 179 0 0 ) ) +( ( 0 58 67 0.5 1 ) ( 0 58 123 0.5 0.5 ) ( 0 58 179 0.5 0 ) ) +( ( 56 58 67 1 1 ) ( 56 58 123 1 0.5 ) ( 56 58 179 1 0 ) ) +) +} +} +} +// entity 2 +{ +"classname" "target_callback" +"targetname" "lua_callback" +"origin" "-16 120 120" +} +// entity 3 +{ +"classname" "func_button" +"wait" "-1" +"target" "kill" +"angle" "-2" +"lip" "4" +// brush 0 +{ +( 256 256 40 ) ( -256 256 40 ) ( -256 -256 40 ) map/ghost 0 0 0 0.031200 0.031200 0 12 0 +( -248 -256 48 ) ( 264 -256 48 ) ( 264 -256 40 ) map/ghost 0 3 0 0.031200 0.031200 0 12 0 +( 256 -280 48 ) ( 256 232 48 ) ( 256 232 40 ) map/ghost 0 3 0 0.031200 0.031200 0 12 0 +( 248 256 48 ) ( -264 256 48 ) ( -264 256 40 ) map/ghost 0 3 0 0.031200 0.031200 0 12 0 +( -256 264 48 ) ( -256 -248 48 ) ( -256 -248 40 ) map/ghost 0 3 0 0.031200 0.031200 0 12 0 +( -256 256 48 ) ( 256 256 48 ) ( -256 -256 48 ) map/ghost 0 0 0 0.031200 0.031200 0 12 0 +} +} +// entity 4 +{ +"classname" "target_kill" +"origin" "-136 128 128" +"targetname" "kill" +} +// entity 5 +{ +"classname" "info_player_start" +"angle" "90" +"origin" "0 -32 96" +"spawn_orientation_segment" "40" +} +// entity 6 +{ +"classname" "misc_model" +"modelscale" "1.5" +"model" "models/fut_view_screen.md3" +"origin" "0 64 24" +} diff --git a/assets/maps/concept_task_01.map b/assets/maps/concept_task_01.map new file mode 100644 index 00000000..72f55259 --- /dev/null +++ b/assets/maps/concept_task_01.map @@ -0,0 +1,230 @@ +// entity 0 +{ +"classname" "worldspawn" +"wait" "-1" +"target" "frag_1" +"targetShaderName" "scripts/apple6" +"targetShaderNewName" "scripts/strawberry_invis" +// brush 0 +{ +( -576 568 128 ) ( -576 -768 128 ) ( -576 -768 120 ) map/lab_games/fake_sky -256 0 0 0.050000 0.050000 0 0 0 +( 1024 576 128 ) ( -576 576 128 ) ( -576 576 120 ) map/lab_games/fake_sky 0 0 0 0.050000 0.050000 0 0 0 +( 1024 -768 128 ) ( 1024 568 128 ) ( 1024 568 120 ) map/lab_games/fake_sky -256 0 0 0.050000 0.050000 0 0 0 +( -576 -768 128 ) ( 1024 -768 128 ) ( 1024 -768 120 ) map/lab_games/fake_sky 0 0 0 0.050000 0.050000 0 0 0 +( -576 -768 192 ) ( -576 568 192 ) ( 1024 568 192 ) map/lab_games/fake_sky 0 256 0 0.050000 0.050000 0 0 0 +( -576 568 184 ) ( -576 -768 184 ) ( 1024 568 184 ) map/lab_games/fake_sky 0 256 0 0.050000 0.050000 0 0 0 +} +// brush 1 +{ +( -576 568 128 ) ( -576 -768 128 ) ( -576 -768 120 ) map/lab_games/lg_style_01_floor_orange -256 0 0 0.050000 0.050000 0 0 0 +( 1024 576 128 ) ( -576 576 128 ) ( -576 576 120 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.050000 0.050000 0 0 0 +( 1024 -768 128 ) ( 1024 568 128 ) ( 1024 568 120 ) map/lab_games/lg_style_01_floor_orange -256 0 0 0.050000 0.050000 0 0 0 +( -576 -768 128 ) ( 1024 -768 128 ) ( 1024 -768 120 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.050000 0.050000 0 0 0 +( 1024 568 0 ) ( -576 568 0 ) ( -576 -768 0 ) map/lab_games/lg_style_01_floor_orange 0 256 0 0.050000 0.050000 0 0 0 +( -576 568 8 ) ( 1024 568 8 ) ( -576 -768 8 ) map/lab_games/lg_style_01_floor_orange 0 256 0 0.050000 0.050000 0 0 0 +} +// brush 2 +{ +( -576 568 128 ) ( -576 -768 128 ) ( -576 -768 120 ) map/lab_games/lg_style_01_wall_green 0 47 0 0.171500 0.171500 0 0 0 +( 1024 576 128 ) ( -576 576 128 ) ( -576 576 120 ) map/lab_games/lg_style_01_wall_green 0 47 0 0.171500 0.171500 0 0 0 +( -576 -768 128 ) ( 1024 -768 128 ) ( 1024 -768 120 ) map/lab_games/lg_style_01_wall_green 0 47 0 0.171500 0.171500 0 0 0 +( -576 -768 192 ) ( -576 568 192 ) ( 1024 568 192 ) map/lab_games/lg_style_01_wall_green 0 47 0 0.171500 0.171500 0 0 0 +( 1024 568 0 ) ( -576 568 0 ) ( -576 -768 0 ) map/lab_games/lg_style_01_wall_green 0 47 0 0.171500 0.171500 0 0 0 +( -568 -768 128 ) ( -568 568 128 ) ( -568 -768 120 ) map/lab_games/lg_style_01_wall_green 0 47 0 0.171500 0.171500 0 0 0 +} +// brush 3 +{ +( -576 568 128 ) ( 1024 568 128 ) ( -576 568 120 ) map/lab_games/lg_style_01_wall_green 0 47 0 0.171500 0.171500 0 0 0 +( 1024 568 0 ) ( -576 568 0 ) ( -576 -768 0 ) map/lab_games/lg_style_01_wall_green 0 47 0 0.171500 0.171500 0 0 0 +( -576 -768 192 ) ( -576 568 192 ) ( 1024 568 192 ) map/lab_games/lg_style_01_wall_green 0 47 0 0.171500 0.171500 0 0 0 +( 1024 -768 128 ) ( 1024 568 128 ) ( 1024 568 120 ) map/lab_games/lg_style_01_wall_green 0 47 0 0.171500 0.171500 0 0 0 +( 1024 576 128 ) ( -576 576 128 ) ( -576 576 120 ) map/lab_games/lg_style_01_wall_green 0 47 0 0.171500 0.171500 0 0 0 +( -576 568 128 ) ( -576 -768 128 ) ( -576 -768 120 ) map/lab_games/lg_style_01_wall_green 0 47 0 0.171500 0.171500 0 0 0 +} +// brush 4 +{ +( 1016 568 128 ) ( 1016 -768 128 ) ( 1016 568 120 ) map/lab_games/lg_style_01_wall_green 0 47 0 0.171500 0.171500 0 0 0 +( 1024 568 0 ) ( -576 568 0 ) ( -576 -768 0 ) map/lab_games/lg_style_01_wall_green 0 47 0 0.171500 0.171500 0 0 0 +( -576 -768 192 ) ( -576 568 192 ) ( 1024 568 192 ) map/lab_games/lg_style_01_wall_green 0 47 0 0.171500 0.171500 0 0 0 +( -576 -768 128 ) ( 1024 -768 128 ) ( 1024 -768 120 ) map/lab_games/lg_style_01_wall_green 0 47 0 0.171500 0.171500 0 0 0 +( 1024 -768 128 ) ( 1024 568 128 ) ( 1024 568 120 ) map/lab_games/lg_style_01_wall_green 0 47 0 0.171500 0.171500 0 0 0 +( 1024 576 128 ) ( -576 576 128 ) ( -576 576 120 ) map/lab_games/lg_style_01_wall_green 0 47 0 0.171500 0.171500 0 0 0 +} +// brush 5 +{ +( -576 568 128 ) ( -576 -768 128 ) ( -576 -768 120 ) map/lab_games/lg_style_01_wall_green 0 47 0 0.171500 0.171500 0 0 0 +( 1024 -768 128 ) ( 1024 568 128 ) ( 1024 568 120 ) map/lab_games/lg_style_01_wall_green 0 47 0 0.171500 0.171500 0 0 0 +( -576 -768 128 ) ( 1024 -768 128 ) ( 1024 -768 120 ) map/lab_games/lg_style_01_wall_green 0 47 0 0.171500 0.171500 0 0 0 +( -576 -768 192 ) ( -576 568 192 ) ( 1024 568 192 ) map/lab_games/lg_style_01_wall_green 0 47 0 0.171500 0.171500 0 0 0 +( 1024 568 0 ) ( -576 568 0 ) ( -576 -768 0 ) map/lab_games/lg_style_01_wall_green 0 47 0 0.171500 0.171500 0 0 0 +( 1024 -760 128 ) ( -576 -760 128 ) ( 1024 -760 120 ) map/lab_games/lg_style_01_wall_green 0 47 0 0.171500 0.171500 0 0 0 +} +} +// entity 1 +{ +"script_id" "21" +"origin" "192 -128 32" +"classname" "info_player_start" +} +// entity 2 +{ +"script_id" "1" +"origin" "288 -128 24" +"classname" "reward_object" +"angle" "225" +} +// entity 3 +{ +"light" "1200" +"origin" "192 -128 152" +"classname" "light" +} +// entity 4 +{ +"light" "1200" +"origin" "-159 191 152" +"classname" "light" +} +// entity 5 +{ +"light" "1200" +"origin" "576 191 152" +"classname" "light" +} +// entity 6 +{ +"light" "1200" +"origin" "-159 -419 152" +"classname" "light" +} +// entity 7 +{ +"light" "1200" +"origin" "576 -419 152" +"classname" "light" +} +// entity 8 +{ +"script_id" "2" +"classname" "reward_object" +"origin" "384 -240 24" +"angle" "135" +} +// entity 9 +{ +"script_id" "3" +"classname" "reward_object" +"origin" "64 -160 24" +} +// entity 10 +{ +"script_id" "4" +"classname" "reward_object" +"origin" "48 56 24" +} +// entity 11 +{ +"script_id" "5" +"classname" "reward_object" +"origin" "368 80 24" +"angle" "270" +} +// entity 12 +{ +"script_id" "6" +"classname" "reward_object" +"origin" "352 -24 24" +} +// entity 13 +{ +"script_id" "7" +"origin" "272 64 24" +"classname" "reward_object" +"angle" "180" +} +// entity 14 +{ +"script_id" "8" +"origin" "72 -288 24" +"classname" "reward_object" +"angle" "135" +} +// entity 15 +{ +"script_id" "9" +"origin" "-56 -48 24" +"classname" "reward_object" +"angle" "180" +} +// entity 16 +{ +"script_id" "10" +"origin" "288 -312 24" +"classname" "reward_object" +} +// entity 17 +{ +"script_id" "11" +"classname" "reward_object" +"origin" "128 144 24" +"angle" "45" +} +// entity 18 +{ +"script_id" "12" +"classname" "reward_object" +"origin" "480 -216 24" +"angle" "315" +} +// entity 19 +{ +"script_id" "13" +"classname" "reward_object" +"origin" "-120 -224 24" +"angle" "45" +} +// entity 20 +{ +"script_id" "14" +"origin" "-64 -440 24" +"classname" "reward_object" +"angle" "315" +} +// entity 21 +{ +"script_id" "15" +"origin" "432 104 24" +"classname" "reward_object" +} +// entity 22 +{ +"script_id" "16" +"origin" "8 -360 24" +"classname" "reward_object" +} +// entity 23 +{ +"script_id" "17" +"origin" "296 -208 24" +"classname" "reward_object" +} +// entity 24 +{ +"script_id" "18" +"origin" "392 -88 24" +"classname" "reward_object" +"angle" "90" +} +// entity 25 +{ +"script_id" "19" +"origin" "-48 -136 24" +"classname" "reward_object" +"angle" "225" +} +// entity 26 +{ +"script_id" "20" +"origin" "144 24 24" +"classname" "reward_object" +} diff --git a/assets/maps/concept_task_03_one_room.map b/assets/maps/concept_task_03_one_room.map new file mode 100644 index 00000000..f37863ec --- /dev/null +++ b/assets/maps/concept_task_03_one_room.map @@ -0,0 +1,203 @@ +// entity 0 +{ +"classname" "worldspawn" +// brush 0 +{ +( 192 -16 0 ) ( -128 -16 0 ) ( -128 -272 0 ) map/fut_flat_wall_yellow_blank_d 5093 0 0 -0.065972 0.007812 0 0 0 +( -128 -272 80 ) ( -128 -16 80 ) ( 192 -16 80 ) map/fut_flat_wall_yellow_blank_d 5093 0 0 -0.065972 0.007812 0 0 0 +( -120 -280 64 ) ( 200 -280 64 ) ( 200 -280 0 ) map/fut_flat_wall_yellow_blank_d 5093 0 0 -0.065972 0.078125 0 0 0 +( 336 -280 88 ) ( 336 -24 88 ) ( 336 -24 24 ) map/fut_flat_wall_yellow_blank_d 0 0 0 -0.000868 0.078125 0 0 0 +( -272 -8 88 ) ( -272 -264 88 ) ( -272 -264 24 ) map/fut_flat_wall_yellow_blank_d 0 0 0 -0.000868 0.078125 0 0 0 +( 264 -272 64 ) ( -56 -272 64 ) ( 264 -272 0 ) map/fut_flat_wall_yellow_blank_d 5093 0 0 -0.065972 0.078125 0 0 0 +} +// brush 1 +{ +( 336 200 0 ) ( 16 200 0 ) ( 16 -56 0 ) map/fut_flat_wall_yellow_blank_d 0 512 0 -0.000977 0.531250 0 0 0 +( 16 -56 80 ) ( 16 200 80 ) ( 336 200 80 ) map/fut_flat_wall_yellow_blank_d 0 512 0 -0.000977 0.531250 0 0 0 +( 16 -280 64 ) ( 336 -280 64 ) ( 336 -280 0 ) map/fut_flat_wall_yellow_blank_d 0 0 0 -0.000977 0.078125 0 0 0 +( 344 -64 64 ) ( 344 192 64 ) ( 344 192 0 ) map/fut_flat_wall_yellow_blank_d 4096 0 0 -0.066406 0.078125 0 0 0 +( 336 280 64 ) ( 16 280 64 ) ( 16 280 0 ) map/fut_flat_wall_yellow_blank_d 0 0 0 -0.000977 0.078125 0 0 0 +( 336 200 88 ) ( 336 -56 88 ) ( 336 200 24 ) map/fut_flat_wall_yellow_blank_d 4096 0 0 -0.066406 0.078125 0 0 0 +} +// brush 2 +{ +( 64 272 0 ) ( -256 272 0 ) ( -256 16 0 ) map/fut_flat_wall_yellow_blank_d 5093 0 0 -0.065972 0.007812 0 0 0 +( -256 16 80 ) ( -256 272 80 ) ( 64 272 80 ) map/fut_flat_wall_yellow_blank_d 5093 0 0 -0.065972 0.007812 0 0 0 +( 336 8 88 ) ( 336 264 88 ) ( 336 264 24 ) map/fut_flat_wall_yellow_blank_d 0 0 0 -0.000868 0.078125 0 0 0 +( 96 280 64 ) ( -224 280 64 ) ( -224 280 0 ) map/fut_flat_wall_yellow_blank_d 5093 0 0 -0.065972 0.078125 0 0 0 +( -272 280 88 ) ( -272 24 88 ) ( -272 24 24 ) map/fut_flat_wall_yellow_blank_d 0 0 0 -0.000868 0.078125 0 0 0 +( -176 272 64 ) ( 144 272 64 ) ( -176 272 0 ) map/fut_flat_wall_yellow_blank_d 5093 0 0 -0.065972 0.078125 0 0 0 +} +// brush 3 +{ +( -16 128 0 ) ( -336 128 0 ) ( -336 -128 0 ) map/fut_flat_wall_yellow_blank_d 0 512 0 -0.000977 0.531250 0 0 0 +( -336 -128 80 ) ( -336 128 80 ) ( -16 128 80 ) map/fut_flat_wall_yellow_blank_d 0 512 0 -0.000977 0.531250 0 0 0 +( -328 -280 64 ) ( -8 -280 64 ) ( -8 -280 0 ) map/fut_flat_wall_yellow_blank_d 0 0 0 -0.000977 0.078125 0 0 0 +( -24 280 64 ) ( -344 280 64 ) ( -344 280 0 ) map/fut_flat_wall_yellow_blank_d 0 0 0 -0.000977 0.078125 0 0 0 +( -280 136 64 ) ( -280 -120 64 ) ( -280 -120 0 ) map/fut_flat_wall_yellow_blank_d 4096 0 0 -0.066406 0.078125 0 0 0 +( -272 -128 88 ) ( -272 128 88 ) ( -272 -128 24 ) map/fut_flat_wall_yellow_blank_d 4096 0 0 -0.066406 0.078125 0 0 0 +} +// brush 4 +{ +( -192 128 0 ) ( 128 128 0 ) ( -192 -128 0 ) map/script_highlight 0 0 0 0 0 0 0 0 +( -280 264 64 ) ( -280 8 64 ) ( -280 8 0 ) map/script_highlight 0 0 0 0 0 0 0 0 +( 0 280 64 ) ( -320 280 64 ) ( -320 280 0 ) map/script_highlight 0 0 0 0 0 0 0 0 +( 344 -264 64 ) ( 344 -8 64 ) ( 344 -8 0 ) map/script_highlight 0 0 0 0 0 0 0 0 +( -64 -280 64 ) ( 256 -280 64 ) ( 256 -280 0 ) map/script_highlight 0 0 0 0 0 0 0 0 +( 128 128 -8 ) ( -192 128 -8 ) ( -192 -128 -8 ) map/script_highlight 0 0 0 0 0 0 0 0 +} +// brush 5 +{ +( -192 128 80 ) ( -192 -128 80 ) ( 128 128 80 ) map/fut_ceiling_tile_02_d 0 0 0 0.031250 0.031250 0 0 0 +( -280 264 80 ) ( -280 8 80 ) ( -280 8 16 ) map/fut_ceiling_tile_02_d 0 0 0 0.031250 0.031250 0 0 0 +( 0 280 80 ) ( -320 280 80 ) ( -320 280 16 ) map/fut_ceiling_tile_02_d 0 0 0 0.031250 0.031250 0 0 0 +( 344 -264 80 ) ( 344 -8 80 ) ( 344 -8 16 ) map/fut_ceiling_tile_02_d 0 0 0 0.031250 0.031250 0 0 0 +( -64 -280 80 ) ( 256 -280 80 ) ( 256 -280 16 ) map/fut_ceiling_tile_02_d 0 0 0 0.031250 0.031250 0 0 0 +( -192 -128 88 ) ( -192 128 88 ) ( 128 128 88 ) map/fut_ceiling_tile_02_d 0 0 0 0.031250 0.031250 0 0 0 +} +} +// entity 1 +{ +"angle" "0" +"spawn_orientation_segment" "40" +"origin" "-232 0 24" +"classname" "info_player_start" +} +// entity 2 +{ +"classname" "light" +"origin" "-176 184 64" +"light" "100" +} +// entity 3 +{ +"origin" "-240 240 16" +"classname" "apple_reward" +"script_id" "1" +} +// entity 4 +{ +"script_id" "2" +"classname" "apple_reward" +"origin" "-112 128 16" +} +// entity 5 +{ +"origin" "-112 240 16" +"classname" "apple_reward" +"script_id" "3" +} +// entity 6 +{ +"script_id" "4" +"classname" "apple_reward" +"origin" "-240 -128 16" +} +// entity 7 +{ +"script_id" "5" +"classname" "apple_reward" +"origin" "32 240 16" +} +// entity 8 +{ +"origin" "168 240 16" +"classname" "apple_reward" +"script_id" "6" +} +// entity 9 +{ +"script_id" "7" +"classname" "apple_reward" +"origin" "304 128 16" +} +// entity 10 +{ +"origin" "168 128 16" +"classname" "apple_reward" +"script_id" "8" +} +// entity 11 +{ +"script_id" "9" +"classname" "apple_reward" +"origin" "56 0 16" +} +// entity 12 +{ +"origin" "-112 0 16" +"classname" "apple_reward" +"script_id" "10" +} +// entity 13 +{ +"script_id" "11" +"classname" "box" +"origin" "32 -128 16" +} +// entity 24 +{ +"light" "100" +"origin" "-32 184 64" +"classname" "light" +} +// entity 25 +{ +"classname" "light" +"origin" "96 184 64" +"light" "100" +} +// entity 26 +{ +"light" "100" +"origin" "248 184 64" +"classname" "light" +} +// entity 27 +{ +"classname" "light" +"origin" "248 -184 64" +"light" "100" +} +// entity 28 +{ +"light" "100" +"origin" "96 -184 64" +"classname" "light" +} +// entity 29 +{ +"classname" "light" +"origin" "-32 -184 64" +"light" "100" +} +// entity 30 +{ +"light" "100" +"origin" "-176 -184 64" +"classname" "light" +} +// entity 31 +{ +"classname" "light" +"origin" "-176 0 64" +"light" "100" +} +// entity 32 +{ +"light" "100" +"origin" "-32 0 64" +"classname" "light" +} +// entity 33 +{ +"classname" "light" +"origin" "96 0 64" +"light" "100" +} +// entity 34 +{ +"light" "100" +"origin" "248 0 64" +"classname" "light" +} diff --git a/assets/maps/concept_task_03_two_rooms.map b/assets/maps/concept_task_03_two_rooms.map new file mode 100644 index 00000000..18eb4613 --- /dev/null +++ b/assets/maps/concept_task_03_two_rooms.map @@ -0,0 +1,455 @@ +// entity 0 +{ +"target" "reward_door" +"classname" "worldspawn" +// brush 0 +{ +( 192 -16 0 ) ( -128 -16 0 ) ( -128 -272 0 ) map/fut_flat_wall_yellow_blank_d 5093 0 0 -0.065972 0.007812 0 0 0 +( -128 -272 80 ) ( -128 -16 80 ) ( 192 -16 80 ) map/fut_flat_wall_yellow_blank_d 5093 0 0 -0.065972 0.007812 0 0 0 +( -120 -280 64 ) ( 200 -280 64 ) ( 200 -280 0 ) map/fut_flat_wall_yellow_blank_d 5093 0 0 -0.065972 0.078125 0 0 0 +( 336 -280 88 ) ( 336 -24 88 ) ( 336 -24 24 ) map/fut_flat_wall_yellow_blank_d 0 0 0 -0.000868 0.078125 0 0 0 +( -272 -8 88 ) ( -272 -264 88 ) ( -272 -264 24 ) map/fut_flat_wall_yellow_blank_d 0 0 0 -0.000868 0.078125 0 0 0 +( 264 -272 64 ) ( -56 -272 64 ) ( 264 -272 0 ) map/fut_flat_wall_yellow_blank_d 5093 0 0 -0.065972 0.078125 0 0 0 +} +// brush 1 +{ +( 336 200 0 ) ( 16 200 0 ) ( 16 -56 0 ) map/fut_flat_wall_yellow_blank_d 0 315 0 -0.002604 0.203125 0 0 0 +( 16 -56 80 ) ( 16 200 80 ) ( 336 200 80 ) map/fut_flat_wall_yellow_blank_d 0 315 0 -0.002604 0.203125 0 0 0 +( 32 64 64 ) ( 352 64 64 ) ( 352 64 0 ) map/fut_flat_wall_yellow_blank_d 0 0 0 -0.002604 0.078125 0 0 0 +( 344 -72 64 ) ( 344 184 64 ) ( 344 184 0 ) map/fut_flat_wall_yellow_blank_d 945 0 0 -0.067708 0.078125 0 0 0 +( 336 280 64 ) ( 16 280 64 ) ( 16 280 0 ) map/fut_flat_wall_yellow_blank_d 0 0 0 -0.002604 0.078125 0 0 0 +( 336 200 88 ) ( 336 -56 88 ) ( 336 200 24 ) map/fut_flat_wall_yellow_blank_d 945 0 0 -0.067708 0.078125 0 0 0 +} +// brush 2 +{ +( 64 272 0 ) ( -256 272 0 ) ( -256 16 0 ) map/fut_flat_wall_yellow_blank_d 5093 0 0 -0.065972 0.007812 0 0 0 +( -256 16 80 ) ( -256 272 80 ) ( 64 272 80 ) map/fut_flat_wall_yellow_blank_d 5093 0 0 -0.065972 0.007812 0 0 0 +( 336 8 88 ) ( 336 264 88 ) ( 336 264 24 ) map/fut_flat_wall_yellow_blank_d 0 0 0 -0.000868 0.078125 0 0 0 +( 96 280 64 ) ( -224 280 64 ) ( -224 280 0 ) map/fut_flat_wall_yellow_blank_d 5093 0 0 -0.065972 0.078125 0 0 0 +( -272 280 88 ) ( -272 24 88 ) ( -272 24 24 ) map/fut_flat_wall_yellow_blank_d 0 0 0 -0.000868 0.078125 0 0 0 +( -176 272 64 ) ( 144 272 64 ) ( -176 272 0 ) map/fut_flat_wall_yellow_blank_d 5093 0 0 -0.065972 0.078125 0 0 0 +} +// brush 3 +{ +( -16 128 0 ) ( -336 128 0 ) ( -336 -128 0 ) map/fut_flat_wall_yellow_blank_d 0 512 0 -0.000977 0.531250 0 0 0 +( -336 -128 80 ) ( -336 128 80 ) ( -16 128 80 ) map/fut_flat_wall_yellow_blank_d 0 512 0 -0.000977 0.531250 0 0 0 +( -328 -280 64 ) ( -8 -280 64 ) ( -8 -280 0 ) map/fut_flat_wall_yellow_blank_d 0 0 0 -0.000977 0.078125 0 0 0 +( -24 280 64 ) ( -344 280 64 ) ( -344 280 0 ) map/fut_flat_wall_yellow_blank_d 0 0 0 -0.000977 0.078125 0 0 0 +( -280 136 64 ) ( -280 -120 64 ) ( -280 -120 0 ) map/fut_flat_wall_yellow_blank_d 4096 0 0 -0.066406 0.078125 0 0 0 +( -272 -128 88 ) ( -272 128 88 ) ( -272 -128 24 ) map/fut_flat_wall_yellow_blank_d 4096 0 0 -0.066406 0.078125 0 0 0 +} +// brush 4 +{ +( -192 128 0 ) ( 128 128 0 ) ( -192 -128 0 ) map/script_highlight 0 0 0 0 0 0 0 0 +( -280 264 64 ) ( -280 8 64 ) ( -280 8 0 ) map/script_highlight 0 0 0 0 0 0 0 0 +( 0 280 64 ) ( -320 280 64 ) ( -320 280 0 ) map/script_highlight 0 0 0 0 0 0 0 0 +( 344 -264 64 ) ( 344 -8 64 ) ( 344 -8 0 ) map/script_highlight 0 0 0 0 0 0 0 0 +( -64 -280 64 ) ( 256 -280 64 ) ( 256 -280 0 ) map/script_highlight 0 0 0 0 0 0 0 0 +( 128 128 -8 ) ( -192 128 -8 ) ( -192 -128 -8 ) map/script_highlight 0 0 0 0 0 0 0 0 +} +// brush 5 +{ +( -192 128 80 ) ( -192 -128 80 ) ( 128 128 80 ) map/fut_ceiling_tile_02_d 0 0 0 0.031250 0.031250 0 0 0 +( -280 264 80 ) ( -280 8 80 ) ( -280 8 16 ) map/fut_ceiling_tile_02_d 0 0 0 0.031250 0.031250 0 0 0 +( 0 280 80 ) ( -320 280 80 ) ( -320 280 16 ) map/fut_ceiling_tile_02_d 0 0 0 0.031250 0.031250 0 0 0 +( 344 -264 80 ) ( 344 -8 80 ) ( 344 -8 16 ) map/fut_ceiling_tile_02_d 0 0 0 0.031250 0.031250 0 0 0 +( -64 -280 80 ) ( 256 -280 80 ) ( 256 -280 16 ) map/fut_ceiling_tile_02_d 0 0 0 0.031250 0.031250 0 0 0 +( -192 -128 88 ) ( -192 128 88 ) ( 128 128 88 ) map/fut_ceiling_tile_02_d 0 0 0 0.031250 0.031250 0 0 0 +} +// brush 6 +{ +( 336 -136 88 ) ( 336 -392 88 ) ( 336 -136 24 ) map/fut_flat_wall_yellow_blank_d 2126 0 0 -0.067708 0.078125 0 0 0 +( 336 -64 64 ) ( 16 -64 64 ) ( 16 -64 0 ) map/fut_flat_wall_yellow_blank_d 0 0 0 -0.002604 0.078125 0 0 0 +( 344 -408 64 ) ( 344 -152 64 ) ( 344 -152 0 ) map/fut_flat_wall_yellow_blank_d 2126 0 0 -0.067708 0.078125 0 0 0 +( 32 -280 64 ) ( 352 -280 64 ) ( 352 -280 0 ) map/fut_flat_wall_yellow_blank_d 0 0 0 -0.002604 0.078125 0 0 0 +( 16 -400 80 ) ( 16 -144 80 ) ( 336 -144 80 ) map/fut_flat_wall_yellow_blank_d 0 708 0 -0.002604 0.203125 0 0 0 +( 336 -144 0 ) ( 16 -144 0 ) ( 16 -400 0 ) map/fut_flat_wall_yellow_blank_d 0 708 0 -0.002604 0.203125 0 0 0 +} +// brush 7 +{ +( 352 32 0 ) ( 896 32 0 ) ( 352 -64 0 ) map/script_highlight 0 0 0 0 0 0 0 0 +( 344 32 1792 ) ( 344 -64 1792 ) ( 344 -64 64 ) map/script_highlight 0 0 0 0 0 0 0 0 +( 64 64 1792 ) ( -480 64 1792 ) ( -480 64 64 ) map/script_highlight 0 0 0 0 0 0 0 0 +( 448 -64 1792 ) ( 448 32 1792 ) ( 448 32 64 ) map/script_highlight 0 0 0 0 0 0 0 0 +( -480 -64 1792 ) ( 64 -64 1792 ) ( 64 -64 64 ) map/script_highlight 0 0 0 0 0 0 0 0 +( 896 32 -8 ) ( 352 32 -8 ) ( 352 -64 -8 ) map/script_highlight 0 0 0 0 0 0 0 0 +} +// brush 8 +{ +( 352 32 80 ) ( 352 -64 80 ) ( 896 32 80 ) map/fut_ceiling_tile_02_d 0 0 0 0.031200 0.031200 0 0 0 +( 344 32 1792 ) ( 344 -64 1792 ) ( 344 -64 64 ) map/fut_ceiling_tile_02_d 0 0 0 0.031200 0.031200 0 0 0 +( 64 64 1792 ) ( -480 64 1792 ) ( -480 64 64 ) map/fut_ceiling_tile_02_d 0 0 0 0.031200 0.031200 0 0 0 +( 448 -64 1792 ) ( 448 32 1792 ) ( 448 32 64 ) map/fut_ceiling_tile_02_d 0 0 0 0.031200 0.031200 0 0 0 +( -480 -64 1792 ) ( 64 -64 1792 ) ( 64 -64 64 ) map/fut_ceiling_tile_02_d 0 0 0 0.031200 0.031200 0 0 0 +( 352 -64 88 ) ( 352 32 88 ) ( 896 32 88 ) map/fut_ceiling_tile_02_d 0 0 0 0.031200 0.031200 0 0 0 +} +// brush 9 +{ +( -488 64 1792 ) ( 56 64 1792 ) ( -488 64 64 ) map/fut_flat_wall_yellow_blank_d 662 0 0 -0.058105 0.078125 0 0 0 +( 344 40 1792 ) ( 344 -56 1792 ) ( 344 -56 64 ) map/fut_flat_wall_yellow_blank_d 134 0 0 -0.055176 0.078125 0 0 0 +( 976 72 1792 ) ( 432 72 1792 ) ( 432 72 64 ) map/fut_flat_wall_yellow_blank_d 662 0 0 -0.058105 0.078125 0 0 0 +( 448 -56 1792 ) ( 448 40 1792 ) ( 448 40 64 ) map/fut_flat_wall_yellow_blank_d 134 0 0 -0.055176 0.078125 0 0 0 +( 352 -56 80 ) ( 352 40 80 ) ( 896 40 80 ) map/fut_flat_wall_yellow_blank_d 662 72 0 -0.058105 0.882812 0 0 0 +( 896 40 0 ) ( 352 40 0 ) ( 352 -56 0 ) map/fut_flat_wall_yellow_blank_d 662 72 0 -0.058105 0.882812 0 0 0 +} +// brush 10 +{ +( 896 -96 0 ) ( 352 -96 0 ) ( 352 -192 0 ) map/fut_flat_wall_yellow_blank_d 662 -81 0 -0.058105 0.882812 0 0 0 +( 352 -192 80 ) ( 352 -96 80 ) ( 896 -96 80 ) map/fut_flat_wall_yellow_blank_d 662 -81 0 -0.058105 0.882812 0 0 0 +( 448 -192 1792 ) ( 448 -96 1792 ) ( 448 -96 64 ) map/fut_flat_wall_yellow_blank_d -280 0 0 -0.055176 0.078125 0 0 0 +( 128 -64 1792 ) ( -416 -64 1792 ) ( -416 -64 64 ) map/fut_flat_wall_yellow_blank_d 662 0 0 -0.058105 0.078125 0 0 0 +( 344 -96 1792 ) ( 344 -192 1792 ) ( 344 -192 64 ) map/fut_flat_wall_yellow_blank_d -280 0 0 -0.055176 0.078125 0 0 0 +( 352 -72 1792 ) ( 896 -72 1792 ) ( 352 -72 64 ) map/fut_flat_wall_yellow_blank_d 662 0 0 -0.058105 0.078125 0 0 0 +} +// brush 11 +{ +( 712 -216 0 ) ( 392 -216 0 ) ( 392 -472 0 ) map/fut_flat_wall_yellow_blank_d 1003 708 0 -0.002604 0.203125 0 0 0 +( 392 -472 80 ) ( 392 -216 80 ) ( 712 -216 80 ) map/fut_flat_wall_yellow_blank_d 1003 708 0 -0.002604 0.203125 0 0 0 +( 400 -280 64 ) ( 720 -280 64 ) ( 720 -280 0 ) map/fut_flat_wall_yellow_blank_d 1003 0 0 -0.002604 0.078125 0 0 0 +( 704 -64 64 ) ( 384 -64 64 ) ( 384 -64 0 ) map/fut_flat_wall_yellow_blank_d 1003 0 0 -0.002604 0.078125 0 0 0 +( 448 -216 64 ) ( 448 -472 64 ) ( 448 -472 0 ) map/fut_flat_wall_yellow_blank_d 77 0 0 -0.067708 0.078125 0 0 0 +( 456 -456 88 ) ( 456 -200 88 ) ( 456 -456 24 ) map/fut_flat_wall_yellow_blank_d 77 0 0 -0.067708 0.078125 0 0 0 +} +// brush 12 +{ +( 536 -128 88 ) ( 536 128 88 ) ( 856 128 88 ) map/fut_ceiling_tile_02_d -768 0 0 0.031250 0.031250 0 0 0 +( 664 -280 80 ) ( 984 -280 80 ) ( 984 -280 16 ) map/fut_ceiling_tile_02_d -768 0 0 0.031250 0.031250 0 0 0 +( 1072 -264 80 ) ( 1072 -8 80 ) ( 1072 -8 16 ) map/fut_ceiling_tile_02_d 0 0 0 0.031250 0.031250 0 0 0 +( 728 280 80 ) ( 408 280 80 ) ( 408 280 16 ) map/fut_ceiling_tile_02_d -768 0 0 0.031250 0.031250 0 0 0 +( 448 264 80 ) ( 448 8 80 ) ( 448 8 16 ) map/fut_ceiling_tile_02_d 0 0 0 0.031250 0.031250 0 0 0 +( 536 128 80 ) ( 536 -128 80 ) ( 856 128 80 ) map/fut_ceiling_tile_02_d -768 0 0 0.031250 0.031250 0 0 0 +} +// brush 13 +{ +( 456 -120 88 ) ( 456 136 88 ) ( 456 -120 24 ) map/fut_flat_wall_yellow_blank_d -79 0 0 -0.067708 0.078125 0 0 0 +( 448 136 64 ) ( 448 -120 64 ) ( 448 -120 0 ) map/fut_flat_wall_yellow_blank_d -79 0 0 -0.067708 0.078125 0 0 0 +( 704 280 64 ) ( 384 280 64 ) ( 384 280 0 ) map/fut_flat_wall_yellow_blank_d 1003 0 0 -0.002604 0.078125 0 0 0 +( 400 64 64 ) ( 720 64 64 ) ( 720 64 0 ) map/fut_flat_wall_yellow_blank_d 1003 0 0 -0.002604 0.078125 0 0 0 +( 392 -128 80 ) ( 392 128 80 ) ( 712 128 80 ) map/fut_flat_wall_yellow_blank_d 1003 315 0 -0.002604 0.203125 0 0 0 +( 712 128 0 ) ( 392 128 0 ) ( 392 -128 0 ) map/fut_flat_wall_yellow_blank_d 1003 315 0 -0.002604 0.203125 0 0 0 +} +// brush 14 +{ +( 856 128 -8 ) ( 536 128 -8 ) ( 536 -128 -8 ) map/script_highlight 0 0 0 0 0 0 0 0 +( 664 -280 64 ) ( 984 -280 64 ) ( 984 -280 0 ) map/script_highlight 0 0 0 0 0 0 0 0 +( 1072 -264 64 ) ( 1072 -8 64 ) ( 1072 -8 0 ) map/script_highlight 0 0 0 0 0 0 0 0 +( 728 280 64 ) ( 408 280 64 ) ( 408 280 0 ) map/script_highlight 0 0 0 0 0 0 0 0 +( 448 264 64 ) ( 448 8 64 ) ( 448 8 0 ) map/script_highlight 0 0 0 0 0 0 0 0 +( 536 128 0 ) ( 856 128 0 ) ( 536 -128 0 ) map/script_highlight 0 0 0 0 0 0 0 0 +} +// brush 15 +{ +( 552 272 64 ) ( 872 272 64 ) ( 552 272 0 ) map/fut_flat_wall_yellow_blank_d -256 0 0 -0.065972 0.078125 0 0 0 +( 456 280 88 ) ( 456 24 88 ) ( 456 24 24 ) map/fut_flat_wall_yellow_blank_d 0 0 0 -0.000868 0.078125 0 0 0 +( 824 280 64 ) ( 504 280 64 ) ( 504 280 0 ) map/fut_flat_wall_yellow_blank_d -256 0 0 -0.065972 0.078125 0 0 0 +( 1064 8 88 ) ( 1064 264 88 ) ( 1064 264 24 ) map/fut_flat_wall_yellow_blank_d 0 0 0 -0.000868 0.078125 0 0 0 +( 472 16 80 ) ( 472 272 80 ) ( 792 272 80 ) map/fut_flat_wall_yellow_blank_d -256 0 0 -0.065972 0.007812 0 0 0 +( 792 272 0 ) ( 472 272 0 ) ( 472 16 0 ) map/fut_flat_wall_yellow_blank_d -256 0 0 -0.065972 0.007812 0 0 0 +} +// brush 16 +{ +( 992 -272 64 ) ( 672 -272 64 ) ( 992 -272 0 ) map/fut_flat_wall_yellow_blank_d -256 0 0 -0.065972 0.078125 0 0 0 +( 456 -8 88 ) ( 456 -264 88 ) ( 456 -264 24 ) map/fut_flat_wall_yellow_blank_d 0 0 0 -0.000868 0.078125 0 0 0 +( 1064 -280 88 ) ( 1064 -24 88 ) ( 1064 -24 24 ) map/fut_flat_wall_yellow_blank_d 0 0 0 -0.000868 0.078125 0 0 0 +( 608 -280 64 ) ( 928 -280 64 ) ( 928 -280 0 ) map/fut_flat_wall_yellow_blank_d -256 0 0 -0.065972 0.078125 0 0 0 +( 600 -272 80 ) ( 600 -16 80 ) ( 920 -16 80 ) map/fut_flat_wall_yellow_blank_d -256 0 0 -0.065972 0.007812 0 0 0 +( 920 -16 0 ) ( 600 -16 0 ) ( 600 -272 0 ) map/fut_flat_wall_yellow_blank_d -256 0 0 -0.065972 0.007812 0 0 0 +} +// brush 17 +{ +( 1064 200 88 ) ( 1064 -56 88 ) ( 1064 200 24 ) map/fut_flat_wall_yellow_blank_d 0 0 0 -0.066406 0.078125 0 0 0 +( 1064 280 64 ) ( 744 280 64 ) ( 744 280 0 ) map/fut_flat_wall_yellow_blank_d 689 0 0 -0.000977 0.078125 0 0 0 +( 1072 -64 64 ) ( 1072 192 64 ) ( 1072 192 0 ) map/fut_flat_wall_yellow_blank_d 0 0 0 -0.066406 0.078125 0 0 0 +( 744 -280 64 ) ( 1064 -280 64 ) ( 1064 -280 0 ) map/fut_flat_wall_yellow_blank_d 689 0 0 -0.000977 0.078125 0 0 0 +( 744 -56 80 ) ( 744 200 80 ) ( 1064 200 80 ) map/fut_flat_wall_yellow_blank_d 689 512 0 -0.000977 0.531250 0 0 0 +( 1064 200 0 ) ( 744 200 0 ) ( 744 -56 0 ) map/fut_flat_wall_yellow_blank_d 689 512 0 -0.000977 0.531250 0 0 0 +} +} +// entity 1 +{ +"angle" "0" +"spawn_orientation_segment" "40" +"origin" "-232 0 24" +"classname" "info_player_start" +} +// entity 2 +{ +"classname" "light" +"origin" "-176 184 64" +"light" "100" +} +// entity 3 +{ +"origin" "-240 240 16" +"classname" "apple_reward" +"script_id" "1" +} +// entity 4 +{ +"script_id" "2" +"classname" "apple_reward" +"origin" "-112 128 16" +} +// entity 5 +{ +"origin" "-112 240 16" +"classname" "apple_reward" +"script_id" "3" +} +// entity 6 +{ +"script_id" "4" +"classname" "apple_reward" +"origin" "-240 -128 16" +} +// entity 7 +{ +"script_id" "5" +"classname" "apple_reward" +"origin" "32 240 16" +} +// entity 8 +{ +"origin" "168 240 16" +"classname" "apple_reward" +"script_id" "6" +} +// entity 9 +{ +"script_id" "7" +"classname" "apple_reward" +"origin" "304 128 16" +} +// entity 10 +{ +"origin" "168 128 16" +"classname" "apple_reward" +"script_id" "8" +} +// entity 11 +{ +"script_id" "9" +"classname" "apple_reward" +"origin" "56 0 16" +} +// entity 12 +{ +"origin" "-112 0 16" +"classname" "apple_reward" +"script_id" "10" +} +// entity 13 +{ +"script_id" "1" +"classname" "cake" +"origin" "32 -128 16" +} +// entity 14 +{ +"origin" "168 -128 16" +"classname" "cake" +"script_id" "2" +} +// entity 15 +{ +"script_id" "3" +"classname" "cake" +"origin" "-112 -128 16" +} +// entity 16 +{ +"origin" "-240 -240 16" +"classname" "cake" +"script_id" "4" +} +// entity 17 +{ +"script_id" "5" +"classname" "cake" +"origin" "32 -240 15" +} +// entity 18 +{ +"origin" "-112 -240 16" +"classname" "cake" +"script_id" "6" +} +// entity 19 +{ +"script_id" "7" +"classname" "cake" +"origin" "168 -240 16" +} +// entity 20 +{ +"origin" "304 -128 16" +"classname" "cake" +"script_id" "8" +} +// entity 21 +{ +"script_id" "9" +"classname" "cake" +"origin" "-240 128 16" +} +// entity 22 +{ +"script_id" "10" +"classname" "cake" +"origin" "32 128 16" +} +// entity 23 +{ +"light" "100" +"origin" "-32 184 64" +"classname" "light" +} +// entity 24 +{ +"classname" "light" +"origin" "96 184 64" +"light" "100" +} +// entity 25 +{ +"light" "100" +"origin" "248 184 64" +"classname" "light" +} +// entity 26 +{ +"classname" "light" +"origin" "248 -184 64" +"light" "100" +} +// entity 27 +{ +"light" "100" +"origin" "96 -184 64" +"classname" "light" +} +// entity 28 +{ +"classname" "light" +"origin" "-32 -184 64" +"light" "100" +} +// entity 29 +{ +"light" "100" +"origin" "-176 -184 64" +"classname" "light" +} +// entity 30 +{ +"classname" "light" +"origin" "-176 0 64" +"light" "100" +} +// entity 31 +{ +"light" "100" +"origin" "-32 0 64" +"classname" "light" +} +// entity 32 +{ +"classname" "light" +"origin" "96 0 64" +"light" "100" +} +// entity 33 +{ +"light" "100" +"origin" "248 0 64" +"classname" "light" +} +// entity 46 +{ +"origin" "760 128 16" +"classname" "box" +"script_id" "11" +"target" "box_door" +} +// entity 47 +{ +"light" "100" +"origin" "824 184 64" +"classname" "light" +} +// entity 48 +{ +"classname" "light" +"origin" "976 184 64" +"light" "100" +} +// entity 49 +{ +"light" "100" +"origin" "824 0 64" +"classname" "light" +} +// entity 50 +{ +"classname" "light" +"origin" "976 0 64" +"light" "100" +} +// entity 56 +{ +"light" "100" +"origin" "440 0 64" +"classname" "light" +} +// entity 57 +{ +"angle" "-1" +"classname" "func_door" +"wait" "3600" +"targetname" "box_door" +// brush 0 +{ +( 344 64 48 ) ( 336 64 48 ) ( 336 64 16 ) map/fut_utility_panel_01_d -515 0 0 0.031189 0.031197 0 0 0 +( 344 -64 48 ) ( 344 64 48 ) ( 344 64 16 ) map/fut_utility_panel_01_d 1002 0 -180 0.031250 -0.031197 0 0 0 +( 336 -64 48 ) ( 344 -64 48 ) ( 344 -64 16 ) map/fut_utility_panel_01_d -515 0 0 0.031189 0.031197 0 0 0 +( 336 64 48 ) ( 336 -64 48 ) ( 336 -64 16 ) map/fut_utility_panel_01_d 1002 0 -180 0.031250 -0.031197 0 0 0 +( 336 64 80 ) ( 344 64 80 ) ( 344 -64 80 ) map/fut_utility_panel_01_d 1002 516 -90 0.031250 0.031219 0 0 0 +( 344 -64 -8 ) ( 344 64 -8 ) ( 336 64 -8 ) map/fut_utility_panel_01_d 1002 516 -90 0.031250 0.031219 0 0 0 +} +} +// entity 79 +{ +"light" "100" +"origin" "696 0 64" +"classname" "light" +} +// entity 82 +{ +"light" "100" +"origin" "552 -184 64" +"classname" "light" +} +// entity 83 +{ +"classname" "light" +"origin" "696 -184 64" +"light" "100" +} +// entity 87 +{ +"classname" "light" +"origin" "552 0 64" +"light" "100" +} +// entity 88 +{ +"light" "100" +"origin" "696 184 64" +"classname" "light" +} +// entity 92 +{ +"classname" "light" +"origin" "552 184 64" +"light" "100" +} diff --git a/assets/maps/ctf_bounce.map b/assets/maps/ctf_bounce.map new file mode 100644 index 00000000..b7e67835 --- /dev/null +++ b/assets/maps/ctf_bounce.map @@ -0,0 +1,1197 @@ +// entity 0 +{ +"classname" "worldspawn" +// brush 0 +{ +( 4752 3896 -144 ) ( 4232 3896 -144 ) ( 4232 3424 -144 ) map/lab_games/sky/lg_sky_02_dn 140 257 0 -0.507812 0.460938 0 0 0 +( 4232 3424 232 ) ( 4752 3424 232 ) ( 4752 3424 216 ) map/lab_games/sky/lg_sky_02_dn 140 0 0 -0.507812 0.007812 0 0 0 +( 4752 3424 232 ) ( 4752 3896 232 ) ( 4752 3896 216 ) map/lab_games/sky/lg_sky_02_dn 257 0 0 -0.460938 0.007812 0 0 0 +( 4752 3896 232 ) ( 4232 3896 232 ) ( 4232 3896 216 ) map/lab_games/sky/lg_sky_02_dn 140 0 0 -0.507812 0.007812 0 0 0 +( 4232 3896 232 ) ( 4232 3424 232 ) ( 4232 3424 216 ) map/lab_games/sky/lg_sky_02_dn 257 0 0 -0.460938 0.007812 0 0 0 +( 4232 3896 -136 ) ( 4752 3896 -136 ) ( 4232 3424 -136 ) map/lab_games/sky/lg_sky_02_dn 140 257 0 -0.507812 0.460938 0 0 0 +} +// brush 1 +{ +( 4232 3424 232 ) ( 4232 3896 232 ) ( 4752 3896 232 ) map/lab_games/sky/lg_sky_02_up 140 257 0 -0.507812 0.460938 0 0 0 +( 4232 3424 232 ) ( 4752 3424 232 ) ( 4752 3424 216 ) map/lab_games/sky/lg_sky_02_up 140 0 0 -0.507812 0.007812 0 0 0 +( 4752 3424 232 ) ( 4752 3896 232 ) ( 4752 3896 216 ) map/lab_games/sky/lg_sky_02_up 257 0 0 -0.460938 0.007812 0 0 0 +( 4752 3896 232 ) ( 4232 3896 232 ) ( 4232 3896 216 ) map/lab_games/sky/lg_sky_02_up 140 0 0 -0.507812 0.007812 0 0 0 +( 4232 3896 232 ) ( 4232 3424 232 ) ( 4232 3424 216 ) map/lab_games/sky/lg_sky_02_up 257 0 0 -0.460938 0.007812 0 0 0 +( 4232 3896 224 ) ( 4232 3424 224 ) ( 4752 3896 224 ) map/lab_games/sky/lg_sky_02_up 140 257 0 -0.507812 0.460938 0 0 0 +} +// brush 2 +{ +( 4752 3896 -144 ) ( 4232 3896 -144 ) ( 4232 3424 -144 ) map/lab_games/sky/lg_sky_02_bk 140 29 0 -0.507812 0.007812 0 0 0 +( 4232 3424 232 ) ( 4232 3896 232 ) ( 4752 3896 232 ) map/lab_games/sky/lg_sky_02_bk 140 29 0 -0.507812 0.007812 0 0 0 +( 4232 3424 232 ) ( 4752 3424 232 ) ( 4752 3424 216 ) map/lab_games/sky/lg_sky_02_bk 140 631 0 -0.507812 0.367188 0 0 0 +( 4752 3424 232 ) ( 4752 3896 232 ) ( 4752 3896 216 ) map/lab_games/sky/lg_sky_02_bk 29 631 0 -0.007812 0.367188 0 0 0 +( 4232 3896 232 ) ( 4232 3424 232 ) ( 4232 3424 216 ) map/lab_games/sky/lg_sky_02_bk 29 631 0 -0.007812 0.367188 0 0 0 +( 4752 3432 232 ) ( 4232 3432 232 ) ( 4752 3432 216 ) map/lab_games/sky/lg_sky_02_bk 140 631 0 -0.507812 0.367188 0 0 0 +} +// brush 3 +{ +( 4752 3896 -144 ) ( 4232 3896 -144 ) ( 4232 3424 -144 ) map/lab_games/sky/lg_sky_02_rt 27 257 0 -0.007812 0.460938 0 0 0 +( 4232 3424 232 ) ( 4232 3896 232 ) ( 4752 3896 232 ) map/lab_games/sky/lg_sky_02_rt 27 257 0 -0.007812 0.460938 0 0 0 +( 4232 3424 232 ) ( 4752 3424 232 ) ( 4752 3424 216 ) map/lab_games/sky/lg_sky_02_rt 27 631 0 -0.007812 0.367188 0 0 0 +( 4752 3424 232 ) ( 4752 3896 232 ) ( 4752 3896 216 ) map/lab_games/sky/lg_sky_02_rt 257 631 0 -0.460938 0.367188 0 0 0 +( 4752 3896 232 ) ( 4232 3896 232 ) ( 4232 3896 216 ) map/lab_games/sky/lg_sky_02_rt 27 631 0 -0.007812 0.367188 0 0 0 +( 4744 3896 232 ) ( 4744 3424 232 ) ( 4744 3896 216 ) map/lab_games/sky/lg_sky_02_rt 257 631 0 -0.460938 0.367188 0 0 0 +} +// brush 4 +{ +( 4752 3896 -144 ) ( 4232 3896 -144 ) ( 4232 3424 -144 ) map/lab_games/sky/lg_sky_02_ft 140 29 0 -0.507812 0.007812 0 0 0 +( 4232 3424 232 ) ( 4232 3896 232 ) ( 4752 3896 232 ) map/lab_games/sky/lg_sky_02_ft 140 29 0 -0.507812 0.007812 0 0 0 +( 4752 3424 232 ) ( 4752 3896 232 ) ( 4752 3896 216 ) map/lab_games/sky/lg_sky_02_ft 29 631 0 -0.007812 0.367188 0 0 0 +( 4752 3896 232 ) ( 4232 3896 232 ) ( 4232 3896 216 ) map/lab_games/sky/lg_sky_02_ft 140 631 0 -0.507812 0.367188 0 0 0 +( 4232 3896 232 ) ( 4232 3424 232 ) ( 4232 3424 216 ) map/lab_games/sky/lg_sky_02_ft 29 631 0 -0.007812 0.367188 0 0 0 +( 4232 3888 232 ) ( 4752 3888 232 ) ( 4232 3888 216 ) map/lab_games/sky/lg_sky_02_ft 140 631 0 -0.507812 0.367188 0 0 0 +} +// brush 5 +{ +( 4752 3896 -144 ) ( 4232 3896 -144 ) ( 4232 3424 -144 ) map/lab_games/sky/lg_sky_02_lf 27 257 0 -0.007812 0.460938 0 0 0 +( 4232 3424 232 ) ( 4232 3896 232 ) ( 4752 3896 232 ) map/lab_games/sky/lg_sky_02_lf 27 257 0 -0.007812 0.460938 0 0 0 +( 4232 3424 232 ) ( 4752 3424 232 ) ( 4752 3424 216 ) map/lab_games/sky/lg_sky_02_lf 27 631 0 -0.007812 0.367188 0 0 0 +( 4752 3896 232 ) ( 4232 3896 232 ) ( 4232 3896 216 ) map/lab_games/sky/lg_sky_02_lf 27 631 0 -0.007812 0.367188 0 0 0 +( 4232 3896 232 ) ( 4232 3424 232 ) ( 4232 3424 216 ) map/lab_games/sky/lg_sky_02_lf 257 631 0 -0.460938 0.367188 0 0 0 +( 4240 3424 232 ) ( 4240 3896 232 ) ( 4240 3424 216 ) map/lab_games/sky/lg_sky_02_lf 257 631 0 -0.460938 0.367188 0 0 0 +} +// brush 6 +{ +( 3328 3072 -1536 ) ( -2816 3072 -1536 ) ( -2816 -1792 -1536 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( -3072 -3072 256 ) ( 3072 -3072 256 ) ( 3072 -3072 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( 3328 -1792 256 ) ( 3328 3072 256 ) ( 3328 3072 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( 3328 3072 256 ) ( -2816 3072 256 ) ( -2816 3072 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( -2816 3072 256 ) ( -2816 -1792 256 ) ( -2816 -1792 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( -2816 3072 -1528 ) ( 3328 3072 -1528 ) ( -2816 -1792 -1528 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 7 +{ +( 3328 3072 -1536 ) ( -2816 3072 -1536 ) ( -2816 -1792 -1536 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( -2816 -1792 1536 ) ( -2816 3072 1536 ) ( 3328 3072 1536 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( -3072 -3072 256 ) ( 3072 -3072 256 ) ( 3072 -3072 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( 3328 -1792 256 ) ( 3328 3072 256 ) ( 3328 3072 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( -2816 3072 256 ) ( -2816 -1792 256 ) ( -2816 -1792 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( 3072 -3064 256 ) ( -3072 -3064 256 ) ( 3072 -3064 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 8 +{ +( 3328 3072 -1536 ) ( -2816 3072 -1536 ) ( -2816 -1792 -1536 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( -2816 -1792 1536 ) ( -2816 3072 1536 ) ( 3328 3072 1536 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( -3072 -3072 256 ) ( 3072 -3072 256 ) ( 3072 -3072 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( 3328 -1792 256 ) ( 3328 3072 256 ) ( 3328 3072 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( 3328 3072 256 ) ( -2816 3072 256 ) ( -2816 3072 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( 3320 3072 256 ) ( 3320 -1792 256 ) ( 3320 3072 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 9 +{ +( 3328 3072 -1536 ) ( -2816 3072 -1536 ) ( -2816 -1792 -1536 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( -2816 -1792 1536 ) ( -2816 3072 1536 ) ( 3328 3072 1536 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( 3328 -1792 256 ) ( 3328 3072 256 ) ( 3328 3072 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( 3328 3072 256 ) ( -2816 3072 256 ) ( -2816 3072 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( -2816 3072 256 ) ( -2816 -1792 256 ) ( -2816 -1792 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( -2816 3064 256 ) ( 3328 3064 256 ) ( -2816 3064 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 10 +{ +( 3328 3072 -1536 ) ( -2816 3072 -1536 ) ( -2816 -1792 -1536 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( -2816 -1792 1536 ) ( -2816 3072 1536 ) ( 3328 3072 1536 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( -3072 -3072 256 ) ( 3072 -3072 256 ) ( 3072 -3072 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( 3328 3072 256 ) ( -2816 3072 256 ) ( -2816 3072 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( -2816 3072 256 ) ( -2816 -1792 256 ) ( -2816 -1792 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( -2808 -1792 256 ) ( -2808 3072 256 ) ( -2808 -1792 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 11 +{ +( 1280 -512 224 ) ( -1152 -512 224 ) ( -1152 -1536 224 ) map/lab_games/lg_style_01_floor_blue_team_d 0 -80 0 0.190000 0.190000 0 0 0 +( -1152 -1536 256 ) ( -1152 -512 256 ) ( 1280 -512 256 ) map/lab_games/lg_style_01_floor_blue_team_d 0 -80 0 0.190000 0.190000 0 0 0 +( -1168 -1344 1536 ) ( 1264 -1344 1536 ) ( 1264 -1344 -1536 ) map/lab_games/lg_style_01_floor_blue_team_d 0 0 0 0.190000 0.190000 0 0 0 +( 448 -1552 1536 ) ( 448 -528 1536 ) ( 448 -528 -1536 ) map/lab_games/lg_style_01_floor_blue_team_d 80 0 0 0.190000 0.190000 0 0 0 +( 1280 -896 1536 ) ( -1152 -896 1536 ) ( -1152 -896 -1536 ) map/lab_games/lg_style_01_floor_blue_team_d 0 0 0 0.190000 0.190000 0 0 0 +( -448 -512 1536 ) ( -448 -1536 1536 ) ( -448 -1536 -1536 ) map/lab_games/lg_style_01_floor_blue_team_d 80 0 0 0.190000 0.190000 0 0 0 +} +// brush 12 +{ +( -448 1728 1536 ) ( -448 704 1536 ) ( -448 704 -1536 ) map/lab_games/lg_style_01_floor_red_team_d -445 0 0 0.190000 0.190000 0 0 0 +( 1280 1344 1536 ) ( -1152 1344 1536 ) ( -1152 1344 -1536 ) map/lab_games/lg_style_01_floor_red_team_d 0 0 0 0.190000 0.190000 0 0 0 +( 448 688 1536 ) ( 448 1712 1536 ) ( 448 1712 -1536 ) map/lab_games/lg_style_01_floor_red_team_d -445 0 0 0.190000 0.190000 0 0 0 +( -1168 896 1536 ) ( 1264 896 1536 ) ( 1264 896 -1536 ) map/lab_games/lg_style_01_floor_red_team_d 0 0 0 0.190000 0.190000 0 0 0 +( -1152 704 256 ) ( -1152 1728 256 ) ( 1280 1728 256 ) map/lab_games/lg_style_01_floor_red_team_d 0 445 0 0.190000 0.190000 0 0 0 +( 1280 1728 224 ) ( -1152 1728 224 ) ( -1152 704 224 ) map/lab_games/lg_style_01_floor_red_team_d 0 445 0 0.190000 0.190000 0 0 0 +} +// brush 13 +{ +( 1280 1152 -480 ) ( -1152 1152 -480 ) ( -1152 128 -480 ) map/lab_games/lg_style_01_floor_orange 0 485 0 0.190000 0.190000 0 0 0 +( -1152 128 -448 ) ( -1152 1152 -448 ) ( 1280 1152 -448 ) map/lab_games/lg_style_01_floor_orange 0 485 0 0.190000 0.190000 0 0 0 +( -1168 -256 832 ) ( 1264 -256 832 ) ( 1264 -256 -2240 ) map/lab_games/lg_style_01_floor_orange 0 -633 0 0.190000 0.190000 0 0 0 +( 256 80 832 ) ( 256 1104 832 ) ( 256 1104 -2240 ) map/lab_games/lg_style_01_floor_orange -485 -633 0 0.190000 0.190000 0 0 0 +( 1216 256 832 ) ( -1216 256 832 ) ( -1216 256 -2240 ) map/lab_games/lg_style_01_floor_orange 0 -633 0 0.190000 0.190000 0 0 0 +( -256 1136 832 ) ( -256 112 832 ) ( -256 112 -2240 ) map/lab_games/lg_style_01_floor_orange -485 -633 0 0.190000 0.190000 0 0 0 +} +// brush 14 +{ +( 640 512 -64 ) ( 640 -576 -64 ) ( 640 -576 -96 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 1152 576 -64 ) ( 640 576 -64 ) ( 640 576 -96 ) map/lab_games/lg_style_01_floor_orange 336 0 0 0.190000 0.190000 0 0 0 +( 1152 -576 -64 ) ( 1152 512 -64 ) ( 1152 512 -96 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 640 -576 -64 ) ( 1152 -576 -64 ) ( 1152 -576 -96 ) map/lab_games/lg_style_01_floor_orange 336 0 0 0.190000 0.190000 0 0 0 +( 640 -576 -64 ) ( 640 512 -64 ) ( 1152 512 -64 ) map/lab_games/lg_style_01_floor_orange 336 0 0 0.190000 0.190000 0 0 0 +( 1152 512 -96 ) ( 640 512 -96 ) ( 640 -576 -96 ) map/lab_games/lg_style_01_floor_orange 336 0 0 0.190000 0.190000 0 0 0 +} +// brush 15 +{ +( -128 1136 2304 ) ( -128 112 2304 ) ( -128 112 -768 ) map/lab_games/lg_style_01_floor_orange -485 970 0 0.190000 0.190000 0 0 0 +( 1216 128 2304 ) ( -1216 128 2304 ) ( -1216 128 -768 ) map/lab_games/lg_style_01_floor_orange 0 970 0 0.190000 0.190000 0 0 0 +( 128 80 2304 ) ( 128 1104 2304 ) ( 128 1104 -768 ) map/lab_games/lg_style_01_floor_orange -485 970 0 0.190000 0.190000 0 0 0 +( -1152 -128 2304 ) ( 1280 -128 2304 ) ( 1280 -128 -768 ) map/lab_games/lg_style_01_floor_orange 0 970 0 0.190000 0.190000 0 0 0 +( -1152 128 1024 ) ( -1152 1152 1024 ) ( 1280 1152 1024 ) map/lab_games/lg_style_01_floor_orange 0 485 0 0.190000 0.190000 0 0 0 +( 1280 1152 992 ) ( -1152 1152 992 ) ( -1152 128 992 ) map/lab_games/lg_style_01_floor_orange 0 485 0 0.190000 0.190000 0 0 0 +} +// brush 16 +{ +( 1304 1688 -2032 ) ( -1712 1688 -2032 ) ( -1712 -1136 -2032 ) map/nodrop -49 16 0 0.031200 0.031200 0 0 0 +( -1712 -1136 -512 ) ( -1712 1688 -512 ) ( 1304 1688 -512 ) map/nodrop -49 16 0 0.031200 0.031200 0 0 0 +( 304 -3064 -616 ) ( 3320 -3064 -616 ) ( 3320 -3064 -624 ) map/nodrop -49 38 0 0.031200 0.031200 0 0 0 +( 3320 -3064 -616 ) ( 3320 -240 -616 ) ( 3320 -240 -624 ) map/nodrop -16 38 0 0.031200 0.031200 0 0 0 +( 216 3064 -616 ) ( -2800 3064 -616 ) ( -2800 3064 -624 ) map/nodrop -49 38 0 0.031200 0.031200 0 0 0 +( -2808 3064 -616 ) ( -2808 240 -616 ) ( -2808 240 -624 ) map/nodrop -16 38 0 0.031200 0.031200 0 0 0 +} +// brush 17 +{ +( 424 1344 256 ) ( -448 1344 256 ) ( -448 1336 256 ) map/floor_fill_d 0 45 0 0.190000 0.190000 0 0 0 +( -448 1336 280 ) ( -448 1344 280 ) ( 424 1344 280 ) map/floor_fill_d 0 45 0 0.190000 0.190000 0 0 0 +( -448 1336 272 ) ( 424 1336 272 ) ( 424 1336 256 ) map/floor_fill_d 0 -23 0 0.190000 0.190000 0 0 0 +( 448 1336 272 ) ( 448 1344 272 ) ( 448 1344 256 ) map/floor_fill_d -45 -23 0 0.190000 0.190000 0 0 0 +( 424 1344 272 ) ( -448 1344 272 ) ( -448 1344 256 ) map/floor_fill_d 0 -23 0 0.190000 0.190000 0 0 0 +( -448 1344 272 ) ( -448 1336 272 ) ( -448 1336 256 ) map/floor_fill_d -45 -23 0 0.190000 0.190000 0 0 0 +} +// brush 18 +{ +( 1152 568 -48 ) ( 1144 568 -48 ) ( 1144 568 -64 ) map/floor_fill_d 4 20 0 0.190002 0.189999 0 0 0 +( 1152 728 -48 ) ( 1152 1600 -48 ) ( 1152 1600 -64 ) map/floor_fill_d -25 19 -180 0.190002 -0.189999 0 0 0 +( 1128 -568 -48 ) ( 1136 -568 -48 ) ( 1136 -568 -64 ) map/floor_fill_d 4 20 0 0.190002 0.189999 0 0 0 +( 1144 200 -48 ) ( 1144 -672 -48 ) ( 1144 -672 -64 ) map/floor_fill_d -25 19 -180 0.190002 -0.189999 0 0 0 +( 1144 200 -40 ) ( 1152 200 -40 ) ( 1152 -672 -40 ) map/floor_fill_d -25 -4 -90 0.190002 0.190002 0 0 0 +( 1152 -672 -64 ) ( 1152 200 -64 ) ( 1144 200 -64 ) map/floor_fill_d -25 -4 -90 0.190002 0.190002 0 0 0 +} +// brush 19 +{ +( -1144 -672 -64 ) ( -1144 200 -64 ) ( -1152 200 -64 ) map/floor_fill_d 38 -56 -90 0.190002 0.190002 0 0 0 +( -1152 200 -40 ) ( -1144 200 -40 ) ( -1144 -672 -40 ) map/floor_fill_d 38 -56 -90 0.190002 0.190002 0 0 0 +( -1152 200 -48 ) ( -1152 -672 -48 ) ( -1152 -672 -64 ) map/floor_fill_d 39 19 -180 0.190002 -0.189999 0 0 0 +( -1168 -576 -48 ) ( -1160 -576 -48 ) ( -1160 -576 -64 ) map/floor_fill_d 56 20 0 0.190002 0.189999 0 0 0 +( -1144 728 -48 ) ( -1144 1600 -48 ) ( -1144 1600 -64 ) map/floor_fill_d 39 19 -180 0.190002 -0.189999 0 0 0 +( -1144 568 -48 ) ( -1152 568 -48 ) ( -1152 568 -64 ) map/floor_fill_d 56 20 0 0.190002 0.189999 0 0 0 +} +// brush 20 +{ +( 424 -1336 256 ) ( -448 -1336 256 ) ( -448 -1344 256 ) map/floor_fill_d 0 20 0 0.190000 0.190000 0 0 0 +( -448 -1344 280 ) ( -448 -1336 280 ) ( 424 -1336 280 ) map/floor_fill_d 0 20 0 0.190000 0.190000 0 0 0 +( -448 -1344 272 ) ( 424 -1344 272 ) ( 424 -1344 256 ) map/floor_fill_d 0 -23 0 0.190000 0.190000 0 0 0 +( 448 -1344 272 ) ( 448 -1336 272 ) ( 448 -1336 256 ) map/floor_fill_d -20 -23 0 0.190000 0.190000 0 0 0 +( 424 -1336 272 ) ( -448 -1336 272 ) ( -448 -1336 256 ) map/floor_fill_d 0 -23 0 0.190000 0.190000 0 0 0 +( -448 -1336 272 ) ( -448 -1344 272 ) ( -448 -1344 256 ) map/floor_fill_d -20 -23 0 0.190000 0.190000 0 0 0 +} +// brush 21 +{ +( -448 -1328 272 ) ( -448 -1336 272 ) ( -448 -1336 256 ) map/floor_fill_d -62 -23 0 0.190000 0.190000 0 0 0 +( 440 -896 272 ) ( -432 -896 272 ) ( -432 -896 256 ) map/floor_fill_d 0 -23 0 0.190000 0.190000 0 0 0 +( -440 -1352 272 ) ( -440 -1344 272 ) ( -440 -1344 256 ) map/floor_fill_d -62 -23 0 0.190000 0.190000 0 0 0 +( -448 -1336 272 ) ( 424 -1336 272 ) ( 424 -1336 256 ) map/floor_fill_d 0 -23 0 0.190000 0.190000 0 0 0 +( -448 -1336 280 ) ( -448 -1328 280 ) ( 424 -1328 280 ) map/floor_fill_d 0 62 0 0.190000 0.190000 0 0 0 +( 424 -1328 256 ) ( -448 -1328 256 ) ( -448 -1336 256 ) map/floor_fill_d 0 62 0 0.190000 0.190000 0 0 0 +} +// brush 22 +{ +( 1312 -1328 256 ) ( 440 -1328 256 ) ( 440 -1336 256 ) map/floor_fill_d -65 62 0 0.190000 0.190000 0 0 0 +( 440 -1336 280 ) ( 440 -1328 280 ) ( 1312 -1328 280 ) map/floor_fill_d -65 62 0 0.190000 0.190000 0 0 0 +( 440 -1336 272 ) ( 1312 -1336 272 ) ( 1312 -1336 256 ) map/floor_fill_d -65 -23 0 0.190000 0.190000 0 0 0 +( 448 -1352 272 ) ( 448 -1344 272 ) ( 448 -1344 256 ) map/floor_fill_d -62 -23 0 0.190000 0.190000 0 0 0 +( 1328 -896 272 ) ( 456 -896 272 ) ( 456 -896 256 ) map/floor_fill_d -65 -23 0 0.190000 0.190000 0 0 0 +( 440 -1328 272 ) ( 440 -1336 272 ) ( 440 -1336 256 ) map/floor_fill_d -62 -23 0 0.190000 0.190000 0 0 0 +} +// brush 23 +{ +( 440 904 272 ) ( 440 896 272 ) ( 440 896 256 ) map/floor_fill_d -33 -23 0 0.190000 0.190000 0 0 0 +( 1328 1336 272 ) ( 456 1336 272 ) ( 456 1336 256 ) map/floor_fill_d -65 -23 0 0.190000 0.190000 0 0 0 +( 448 880 272 ) ( 448 888 272 ) ( 448 888 256 ) map/floor_fill_d -33 -23 0 0.190000 0.190000 0 0 0 +( 440 896 272 ) ( 1312 896 272 ) ( 1312 896 256 ) map/floor_fill_d -65 -23 0 0.190000 0.190000 0 0 0 +( 440 896 280 ) ( 440 904 280 ) ( 1312 904 280 ) map/floor_fill_d -65 33 0 0.190000 0.190000 0 0 0 +( 1312 904 256 ) ( 440 904 256 ) ( 440 896 256 ) map/floor_fill_d -65 33 0 0.190000 0.190000 0 0 0 +} +// brush 24 +{ +( 424 904 256 ) ( -448 904 256 ) ( -448 896 256 ) map/floor_fill_d 0 33 0 0.190000 0.190000 0 0 0 +( -448 896 280 ) ( -448 904 280 ) ( 424 904 280 ) map/floor_fill_d 0 33 0 0.190000 0.190000 0 0 0 +( -448 896 272 ) ( 424 896 272 ) ( 424 896 256 ) map/floor_fill_d 0 -23 0 0.190000 0.190000 0 0 0 +( -440 880 272 ) ( -440 888 272 ) ( -440 888 256 ) map/floor_fill_d -33 -23 0 0.190000 0.190000 0 0 0 +( 440 1336 272 ) ( -432 1336 272 ) ( -432 1336 256 ) map/floor_fill_d 0 -23 0 0.190000 0.190000 0 0 0 +( -448 904 272 ) ( -448 896 272 ) ( -448 896 256 ) map/floor_fill_d -33 -23 0 0.190000 0.190000 0 0 0 +} +// brush 25 +{ +( 640 576 -48 ) ( 632 576 -48 ) ( 632 576 -64 ) map/floor_fill_d -160 20 0 0.190002 0.189999 0 0 0 +( 1144 1872 -48 ) ( 1144 2744 -48 ) ( 1144 2744 -64 ) map/floor_fill_d 171 19 -180 0.190002 -0.189999 0 0 0 +( -232 568 -48 ) ( -224 568 -48 ) ( -224 568 -64 ) map/floor_fill_d -160 20 0 0.190002 0.189999 0 0 0 +( -1144 1344 -48 ) ( -1144 472 -48 ) ( -1144 472 -64 ) map/floor_fill_d 171 19 -180 0.190002 -0.189999 0 0 0 +( 640 1344 -40 ) ( 648 1344 -40 ) ( 648 472 -40 ) map/floor_fill_d 170 160 -90 0.190002 0.190002 0 0 0 +( 648 472 -64 ) ( 648 1344 -64 ) ( 640 1344 -64 ) map/floor_fill_d 170 160 -90 0.190002 0.190002 0 0 0 +} +// brush 26 +{ +( 648 -672 -64 ) ( 648 200 -64 ) ( 640 200 -64 ) map/floor_fill_d -218 -95 -90 0.190002 0.190002 0 0 0 +( 640 200 -40 ) ( 648 200 -40 ) ( 648 -672 -40 ) map/floor_fill_d -218 -95 -90 0.190002 0.190002 0 0 0 +( -1144 200 -48 ) ( -1144 -672 -48 ) ( -1144 -672 -64 ) map/floor_fill_d -217 18 -180 0.190002 -0.189999 0 0 0 +( 1552 -576 -48 ) ( 1560 -576 -48 ) ( 1560 -576 -64 ) map/floor_fill_d 95 20 0 0.190002 0.189999 0 0 0 +( 1144 728 -48 ) ( 1144 1600 -48 ) ( 1144 1600 -64 ) map/floor_fill_d -217 18 -180 0.190002 -0.189999 0 0 0 +( 640 -568 -48 ) ( 632 -568 -48 ) ( 632 -568 -64 ) map/floor_fill_d 95 20 0 0.190002 0.189999 0 0 0 +} +// brush 27 +{ +( -640 512 -64 ) ( -640 -576 -64 ) ( -640 -576 -96 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 256 -384 -64 ) ( -256 -384 -64 ) ( -256 -384 -96 ) map/lab_games/lg_style_01_floor_orange 336 0 0 0.190000 0.190000 0 0 0 +( 640 -616 -64 ) ( 640 472 -64 ) ( 640 472 -96 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 640 -576 -64 ) ( 1152 -576 -64 ) ( 1152 -576 -96 ) map/lab_games/lg_style_01_floor_orange 336 0 0 0.190000 0.190000 0 0 0 +( 640 -576 -64 ) ( 640 512 -64 ) ( 1152 512 -64 ) map/lab_games/lg_style_01_floor_orange 336 0 0 0.190000 0.190000 0 0 0 +( 1152 512 -96 ) ( 640 512 -96 ) ( 640 -576 -96 ) map/lab_games/lg_style_01_floor_orange 336 0 0 0.190000 0.190000 0 0 0 +} +// brush 28 +{ +( 1152 576 -96 ) ( 640 576 -96 ) ( 640 -512 -96 ) map/lab_games/lg_style_01_floor_orange 336 336 0 0.190000 0.190000 0 0 0 +( 640 -512 -64 ) ( 640 576 -64 ) ( 1152 576 -64 ) map/lab_games/lg_style_01_floor_orange 336 336 0 0.190000 0.190000 0 0 0 +( -216 384 -64 ) ( 296 384 -64 ) ( 296 384 -96 ) map/lab_games/lg_style_01_floor_orange 336 0 0 0.190000 0.190000 0 0 0 +( 640 -536 -64 ) ( 640 552 -64 ) ( 640 552 -96 ) map/lab_games/lg_style_01_floor_orange -336 0 0 0.190000 0.190000 0 0 0 +( 1152 576 -64 ) ( 640 576 -64 ) ( 640 576 -96 ) map/lab_games/lg_style_01_floor_orange 336 0 0 0.190000 0.190000 0 0 0 +( -640 560 -64 ) ( -640 -528 -64 ) ( -640 -528 -96 ) map/lab_games/lg_style_01_floor_orange -336 0 0 0.190000 0.190000 0 0 0 +} +// brush 29 +{ +( 1152 512 -96 ) ( 640 512 -96 ) ( 640 -576 -96 ) map/lab_games/lg_style_01_floor_orange 336 0 0 0.190000 0.190000 0 0 0 +( 640 -576 -64 ) ( 640 512 -64 ) ( 1152 512 -64 ) map/lab_games/lg_style_01_floor_orange 336 0 0 0.190000 0.190000 0 0 0 +( 640 -576 -64 ) ( 1152 -576 -64 ) ( 1152 -576 -96 ) map/lab_games/lg_style_01_floor_orange 336 0 0 0.190000 0.190000 0 0 0 +( -640 -664 -64 ) ( -640 424 -64 ) ( -640 424 -96 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 296 576 -64 ) ( -216 576 -64 ) ( -216 576 -96 ) map/lab_games/lg_style_01_floor_orange 336 0 0 0.190000 0.190000 0 0 0 +( -1152 496 -64 ) ( -1152 -592 -64 ) ( -1152 -592 -96 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 30 +{ +( 656 152 -448 ) ( 656 1024 -448 ) ( 648 1024 -448 ) map/floor_fill_d -233 202 -90 0.190002 0.190002 0 0 0 +( 648 1024 -424 ) ( 656 1024 -424 ) ( 656 152 -424 ) map/floor_fill_d -233 202 -90 0.190002 0.190002 0 0 0 +( -256 1024 -432 ) ( -256 152 -432 ) ( -256 152 -448 ) map/floor_fill_d -232 -210 -180 0.190002 -0.189999 0 0 0 +( -240 248 -432 ) ( -232 248 -432 ) ( -232 248 -448 ) map/floor_fill_d -202 -209 0 0.190002 0.189999 0 0 0 +( 256 1552 -432 ) ( 256 2424 -432 ) ( 256 2424 -448 ) map/floor_fill_d -232 -210 -180 0.190002 -0.189999 0 0 0 +( 648 256 -432 ) ( 640 256 -432 ) ( 640 256 -448 ) map/floor_fill_d -202 -209 0 0.190002 0.189999 0 0 0 +} +// brush 31 +{ +( 648 -248 -432 ) ( 640 -248 -432 ) ( 640 -248 -448 ) map/floor_fill_d -202 -209 0 0.190002 0.189999 0 0 0 +( 256 1048 -432 ) ( 256 1920 -432 ) ( 256 1920 -448 ) map/floor_fill_d -68 -210 -180 0.190002 -0.189999 0 0 0 +( -240 -256 -432 ) ( -232 -256 -432 ) ( -232 -256 -448 ) map/floor_fill_d -202 -209 0 0.190002 0.189999 0 0 0 +( -256 520 -432 ) ( -256 -352 -432 ) ( -256 -352 -448 ) map/floor_fill_d -68 -210 -180 0.190002 -0.189999 0 0 0 +( 648 520 -424 ) ( 656 520 -424 ) ( 656 -352 -424 ) map/floor_fill_d -69 202 -90 0.190002 0.190002 0 0 0 +( 656 -352 -448 ) ( 656 520 -448 ) ( 648 520 -448 ) map/floor_fill_d -69 202 -90 0.190002 0.190002 0 0 0 +} +// brush 32 +{ +( 656 -344 -448 ) ( 656 528 -448 ) ( 648 528 -448 ) map/floor_fill_d -26 202 -90 0.190002 0.190002 0 0 0 +( 648 528 -424 ) ( 656 528 -424 ) ( 656 -344 -424 ) map/floor_fill_d -26 202 -90 0.190002 0.190002 0 0 0 +( -256 528 -432 ) ( -256 -344 -432 ) ( -256 -344 -448 ) map/floor_fill_d -25 -210 -180 0.190002 -0.189999 0 0 0 +( -240 -248 -432 ) ( -232 -248 -432 ) ( -232 -248 -448 ) map/floor_fill_d -202 -209 0 0.190002 0.189999 0 0 0 +( -248 1072 -432 ) ( -248 1944 -432 ) ( -248 1944 -448 ) map/floor_fill_d -25 -210 -180 0.190002 -0.189999 0 0 0 +( 672 248 -432 ) ( 664 248 -432 ) ( 664 248 -448 ) map/floor_fill_d -202 -209 0 0.190002 0.189999 0 0 0 +} +// brush 33 +{ +( 1176 248 -432 ) ( 1168 248 -432 ) ( 1168 248 -448 ) map/floor_fill_d -38 -209 0 0.190002 0.189999 0 0 0 +( 256 1072 -432 ) ( 256 1944 -432 ) ( 256 1944 -448 ) map/floor_fill_d -25 -210 -180 0.190002 -0.189999 0 0 0 +( 264 -248 -432 ) ( 272 -248 -432 ) ( 272 -248 -448 ) map/floor_fill_d -38 -209 0 0.190002 0.189999 0 0 0 +( 248 528 -432 ) ( 248 -344 -432 ) ( 248 -344 -448 ) map/floor_fill_d -25 -210 -180 0.190002 -0.189999 0 0 0 +( 1152 528 -424 ) ( 1160 528 -424 ) ( 1160 -344 -424 ) map/floor_fill_d -25 38 -90 0.190002 0.190002 0 0 0 +( 1160 -344 -448 ) ( 1160 528 -448 ) ( 1152 528 -448 ) map/floor_fill_d -25 38 -90 0.190002 0.190002 0 0 0 +} +// brush 34 +{ +( 648 128 1040 ) ( 640 128 1040 ) ( 640 128 1024 ) map/floor_fill_d -202 -141 0 0.190002 0.189999 0 0 0 +( 128 1424 1040 ) ( 128 2296 1040 ) ( 128 2296 1024 ) map/floor_fill_d -137 -142 -180 0.190002 -0.189999 0 0 0 +( -240 120 1040 ) ( -232 120 1040 ) ( -232 120 1024 ) map/floor_fill_d -202 -141 0 0.190002 0.189999 0 0 0 +( -128 896 1040 ) ( -128 24 1040 ) ( -128 24 1024 ) map/floor_fill_d -137 -142 -180 0.190002 -0.189999 0 0 0 +( 648 896 1048 ) ( 656 896 1048 ) ( 656 24 1048 ) map/floor_fill_d -138 202 -90 0.190002 0.190002 0 0 0 +( 656 24 1024 ) ( 656 896 1024 ) ( 648 896 1024 ) map/floor_fill_d -138 202 -90 0.190002 0.190002 0 0 0 +} +// brush 35 +{ +( 656 -224 1024 ) ( 656 648 1024 ) ( 648 648 1024 ) map/floor_fill_d -163 202 -90 0.190002 0.190002 0 0 0 +( 648 648 1048 ) ( 656 648 1048 ) ( 656 -224 1048 ) map/floor_fill_d -163 202 -90 0.190002 0.190002 0 0 0 +( -128 648 1040 ) ( -128 -224 1040 ) ( -128 -224 1024 ) map/floor_fill_d -162 -142 -180 0.190002 -0.189999 0 0 0 +( -240 -128 1040 ) ( -232 -128 1040 ) ( -232 -128 1024 ) map/floor_fill_d -202 -141 0 0.190002 0.189999 0 0 0 +( 128 1176 1040 ) ( 128 2048 1040 ) ( 128 2048 1024 ) map/floor_fill_d -162 -142 -180 0.190002 -0.189999 0 0 0 +( 648 -120 1040 ) ( 640 -120 1040 ) ( 640 -120 1024 ) map/floor_fill_d -202 -141 0 0.190002 0.189999 0 0 0 +} +// brush 36 +{ +( 656 120 1040 ) ( 648 120 1040 ) ( 648 120 1024 ) map/floor_fill_d -202 -141 0 0.190002 0.189999 0 0 0 +( -120 1184 1040 ) ( -120 2056 1040 ) ( -120 2056 1024 ) map/floor_fill_d -119 -142 -180 0.190002 -0.189999 0 0 0 +( -240 -120 1040 ) ( -232 -120 1040 ) ( -232 -120 1024 ) map/floor_fill_d -202 -141 0 0.190002 0.189999 0 0 0 +( -128 656 1040 ) ( -128 -216 1040 ) ( -128 -216 1024 ) map/floor_fill_d -119 -142 -180 0.190002 -0.189999 0 0 0 +( 648 656 1048 ) ( 656 656 1048 ) ( 656 -216 1048 ) map/floor_fill_d -120 202 -90 0.190002 0.190002 0 0 0 +( 656 -216 1024 ) ( 656 656 1024 ) ( 648 656 1024 ) map/floor_fill_d -120 202 -90 0.190002 0.190002 0 0 0 +} +// brush 37 +{ +( 904 -216 1024 ) ( 904 656 1024 ) ( 896 656 1024 ) map/floor_fill_d -120 227 -90 0.190002 0.190002 0 0 0 +( 896 656 1048 ) ( 904 656 1048 ) ( 904 -216 1048 ) map/floor_fill_d -120 227 -90 0.190002 0.190002 0 0 0 +( 120 656 1040 ) ( 120 -216 1040 ) ( 120 -216 1024 ) map/floor_fill_d -119 -142 -180 0.190002 -0.189999 0 0 0 +( 8 -120 1040 ) ( 16 -120 1040 ) ( 16 -120 1024 ) map/floor_fill_d -227 -141 0 0.190002 0.189999 0 0 0 +( 128 1184 1040 ) ( 128 2056 1040 ) ( 128 2056 1024 ) map/floor_fill_d -119 -142 -180 0.190002 -0.189999 0 0 0 +( 904 120 1040 ) ( 896 120 1040 ) ( 896 120 1024 ) map/floor_fill_d -227 -141 0 0.190002 0.189999 0 0 0 +} +// brush 38 +{ +( -2816 3072 1528 ) ( -2816 -1792 1528 ) ( 3328 3072 1528 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( -2816 3072 256 ) ( -2816 -1792 256 ) ( -2816 -1792 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( 3328 3072 256 ) ( -2816 3072 256 ) ( -2816 3072 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( 3328 -1792 256 ) ( 3328 3072 256 ) ( 3328 3072 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( -3072 -3072 256 ) ( 3072 -3072 256 ) ( 3072 -3072 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +( -2816 -1792 1536 ) ( -2816 3072 1536 ) ( 3328 3072 1536 ) map/lab_games/sky/lg_sky_01 0 0 0 0.190000 0.190000 0 0 0 +} +} +// entity 1 +{ +"classname" "_skybox" +"origin" "4464 3656 -96" +} +// entity 2 +{ +"model" "models/stadium.md3" +"origin" "4464 3656 -34" +"classname" "misc_model" +"_remap" "*;textures/map/lab_games/stadium_d" +"angle" "90" +"modelscale" "1.5" +} +// entity 3 +{ +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"modelscale" "1" +"model" "models/signal_line.md3" +"origin" "4488 3616 -48" +"classname" "misc_model" +"angle" "113" +} +// entity 4 +{ +"classname" "misc_model" +"origin" "4536 3640 -120" +"model" "models/signal_line.md3" +"modelscale" "1.1" +"_remap" "*;textures/map/lab_games/signal_pulse_blue" +"angle" "38" +} +// entity 5 +{ +"angle" "28" +"classname" "misc_model" +"origin" "4496 3648 -88" +"model" "models/signal_line.md3" +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"modelscale" "0.61" +} +// entity 6 +{ +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"modelscale" "0.6" +"model" "models/signal_line.md3" +"origin" "4440 3664 -120" +"classname" "misc_model" +"angle" "159" +} +// entity 7 +{ +"classname" "trigger_push" +"target" "landing1" +// brush 0 +{ +( 384 -1024 312 ) ( 384 -912 312 ) ( 384 -912 256 ) common/caulk 44 9 -180 0.031250 -0.031189 0 0 0 +( 256 -1024 312 ) ( 432 -1024 312 ) ( 432 -1024 256 ) common/caulk 61 9 -180 0.031128 -0.031189 0 0 0 +( 320 -912 312 ) ( 320 -1024 312 ) ( 320 -1024 256 ) common/caulk 44 9 -180 0.031250 -0.031189 0 0 0 +( 432 -960 312 ) ( 256 -960 312 ) ( 256 -960 256 ) common/caulk 61 9 -180 0.031128 -0.031189 0 0 0 +( 432 -912 312 ) ( 432 -1024 312 ) ( 256 -1024 312 ) common/caulk 38 -61 -180 0.031250 0.031250 0 0 0 +( 256 -1024 272 ) ( 432 -1024 272 ) ( 432 -912 272 ) common/caulk 38 -61 -180 0.031250 0.031250 0 0 0 +} +} +// entity 8 +{ +"classname" "target_position" +"origin" "632 -456 760" +"targetname" "landing1" +} +// entity 9 +{ +"angle" "135" +"model" "models/bounce_pad.md3" +"origin" "352 -992 280" +"classname" "misc_model" +} +// entity 10 +{ +"target" "t1" +"classname" "trigger_push" +// brush 0 +{ +( -256 1024 272 ) ( -432 1024 272 ) ( -432 912 272 ) common/caulk 38 3 0 0.031250 0.031250 0 0 0 +( -432 912 312 ) ( -432 1024 312 ) ( -256 1024 312 ) common/caulk 38 3 0 0.031250 0.031250 0 0 0 +( -432 960 312 ) ( -256 960 312 ) ( -256 960 256 ) common/caulk 61 9 0 0.031128 0.031189 0 0 0 +( -320 912 312 ) ( -320 1024 312 ) ( -320 1024 256 ) common/caulk -20 9 0 0.031250 0.031189 0 0 0 +( -256 1024 312 ) ( -432 1024 312 ) ( -432 1024 256 ) common/caulk 61 9 0 0.031128 0.031189 0 0 0 +( -384 1024 312 ) ( -384 912 312 ) ( -384 912 256 ) common/caulk -20 9 0 0.031250 0.031189 0 0 0 +} +} +// entity 11 +{ +"targetname" "t1" +"origin" "-632 456 760" +"classname" "target_position" +} +// entity 12 +{ +"classname" "misc_model" +"origin" "-352 992 280" +"model" "models/bounce_pad.md3" +"angle" "315" +} +// entity 13 +{ +"target" "t3" +"classname" "trigger_push" +// brush 0 +{ +( -96 96 -432 ) ( -96 -80 -432 ) ( 16 -80 -432 ) common/caulk -25 2 90 0.031250 0.031250 0 0 0 +( 16 -80 -392 ) ( -96 -80 -392 ) ( -96 96 -392 ) common/caulk -25 2 90 0.031250 0.031250 0 0 0 +( -32 -80 -504 ) ( -32 96 -504 ) ( -32 96 -560 ) common/caulk -46 28 0 0.031128 0.031189 0 0 0 +( 16 32 -392 ) ( -96 32 -392 ) ( -96 32 -448 ) common/caulk -19 28 -180 0.031250 -0.031189 0 0 0 +( -96 96 -392 ) ( -96 -80 -392 ) ( -96 -80 -448 ) common/caulk -46 28 0 0.031128 0.031189 0 0 0 +( -96 -32 -392 ) ( 16 -32 -392 ) ( 16 -32 -448 ) common/caulk -19 28 -180 0.031250 -0.031189 0 0 0 +} +} +// entity 14 +{ +"targetname" "t3" +"origin" "472 0 120" +"classname" "target_position" +} +// entity 15 +{ +"classname" "misc_model" +"origin" "-64 0 -424" +"model" "models/bounce_pad.md3" +"angle" "90" +} +// entity 16 +{ +"classname" "trigger_push" +"target" "t4" +// brush 0 +{ +( -32 96 -392 ) ( -32 -16 -392 ) ( -32 -16 -448 ) common/caulk -20 28 0 0.031250 0.031189 0 0 0 +( 96 96 -392 ) ( -80 96 -392 ) ( -80 96 -448 ) common/caulk 16 28 0 0.031128 0.031189 0 0 0 +( 32 -16 -392 ) ( 32 96 -392 ) ( 32 96 -448 ) common/caulk -20 28 0 0.031250 0.031189 0 0 0 +( -80 32 -504 ) ( 96 32 -504 ) ( 96 32 -560 ) common/caulk 16 28 0 0.031128 0.031189 0 0 0 +( -80 -16 -392 ) ( -80 96 -392 ) ( 96 96 -392 ) common/caulk 38 3 0 0.031250 0.031250 0 0 0 +( 96 96 -432 ) ( -80 96 -432 ) ( -80 -16 -432 ) common/caulk 38 3 0 0.031250 0.031250 0 0 0 +} +} +// entity 17 +{ +"classname" "target_position" +"origin" "96 -680 376" +"targetname" "t4" +} +// entity 18 +{ +"angle" "360" +"model" "models/bounce_pad.md3" +"origin" "0 64 -424" +"classname" "misc_model" +} +// entity 19 +{ +"classname" "trigger_push" +"target" "t6" +// brush 0 +{ +( 96 32 -392 ) ( -16 32 -392 ) ( -16 32 -448 ) common/caulk -20 27 0 0.031250 0.031189 0 0 0 +( 96 -96 -392 ) ( 96 80 -392 ) ( 96 80 -448 ) common/caulk -47 27 -180 0.031128 -0.031189 0 0 0 +( -16 -32 -392 ) ( 96 -32 -392 ) ( 96 -32 -448 ) common/caulk -19 27 0 0.031250 0.031189 0 0 0 +( 32 80 -504 ) ( 32 -96 -504 ) ( 32 -96 -560 ) common/caulk -47 27 -180 0.031128 -0.031189 0 0 0 +( -16 80 -392 ) ( 96 80 -392 ) ( 96 -96 -392 ) common/caulk -26 3 -90 0.031250 0.031250 0 0 0 +( 96 -96 -432 ) ( 96 80 -432 ) ( -16 80 -432 ) common/caulk -26 3 -90 0.031250 0.031250 0 0 0 +} +} +// entity 20 +{ +"classname" "target_position" +"origin" "-472 0 120" +"targetname" "t6" +} +// entity 21 +{ +"angle" "270" +"model" "models/bounce_pad.md3" +"origin" "64 0 -424" +"classname" "misc_model" +} +// entity 22 +{ +"classname" "trigger_push" +"target" "t8" +// brush 0 +{ +( 768 -384 -8 ) ( 656 -384 -8 ) ( 656 -384 -64 ) common/caulk 44 -11 0 0.031250 0.031189 0 0 0 +( 768 -512 -8 ) ( 768 -336 -8 ) ( 768 -336 -64 ) common/caulk 29 -10 -180 0.031250 -0.031189 0 0 0 +( 656 -448 -8 ) ( 768 -448 -8 ) ( 768 -448 -64 ) common/caulk 44 -11 0 0.031250 0.031189 0 0 0 +( 704 -336 -120 ) ( 704 -512 -120 ) ( 704 -512 -176 ) common/caulk 30 -10 -180 0.031250 -0.031189 0 0 0 +( 656 -336 -8 ) ( 768 -336 -8 ) ( 768 -512 -8 ) common/caulk 39 -62 -90 0.031250 0.031250 0 0 0 +( 768 -512 -48 ) ( 768 -336 -48 ) ( 656 -336 -48 ) common/caulk 39 -62 -90 0.031250 0.031250 0 0 0 +} +} +// entity 23 +{ +"classname" "target_position" +"origin" "328 -760 504" +"targetname" "t8" +} +// entity 24 +{ +"angle" "315" +"model" "models/bounce_pad.md3" +"origin" "736 -416 -40" +"classname" "misc_model" +} +// entity 25 +{ +"target" "t10" +"classname" "trigger_push" +// brush 0 +{ +( -96 -96 -432 ) ( 80 -96 -432 ) ( 80 16 -432 ) common/caulk 37 3 -180 0.031250 0.031250 0 0 0 +( 80 16 -392 ) ( 80 -96 -392 ) ( -96 -96 -392 ) common/caulk 37 3 -180 0.031250 0.031250 0 0 0 +( 80 -32 -504 ) ( -96 -32 -504 ) ( -96 -32 -560 ) common/caulk 16 27 -180 0.031128 -0.031189 0 0 0 +( -32 16 -392 ) ( -32 -96 -392 ) ( -32 -96 -448 ) common/caulk -20 28 -180 0.031250 -0.031189 0 0 0 +( -96 -96 -392 ) ( 80 -96 -392 ) ( 80 -96 -448 ) common/caulk 16 27 -180 0.031128 -0.031189 0 0 0 +( 32 -96 -392 ) ( 32 16 -392 ) ( 32 16 -448 ) common/caulk -20 28 -180 0.031250 -0.031189 0 0 0 +} +} +// entity 26 +{ +"targetname" "t10" +"origin" "-96 680 376" +"classname" "target_position" +} +// entity 27 +{ +"classname" "misc_model" +"origin" "0 -64 -424" +"model" "models/bounce_pad.md3" +"angle" "180" +} +// entity 28 +{ +"classname" "trigger_push" +"target" "t11" +// brush 0 +{ +( -768 64 -8 ) ( -656 64 -8 ) ( -656 64 -64 ) common/caulk -20 -11 -180 0.031250 -0.031189 0 0 0 +( -768 192 -8 ) ( -768 16 -8 ) ( -768 16 -64 ) common/caulk -34 -11 0 0.031250 0.031189 0 0 0 +( -656 128 -8 ) ( -768 128 -8 ) ( -768 128 -64 ) common/caulk -20 -11 -180 0.031250 -0.031189 0 0 0 +( -704 16 -120 ) ( -704 192 -120 ) ( -704 192 -176 ) common/caulk -34 -11 0 0.031250 0.031189 0 0 0 +( -656 16 -8 ) ( -768 16 -8 ) ( -768 192 -8 ) common/caulk -24 0 90 0.031250 0.031250 0 0 0 +( -768 192 -48 ) ( -768 16 -48 ) ( -656 16 -48 ) common/caulk -24 0 90 0.031250 0.031250 0 0 0 +} +} +// entity 29 +{ +"target" "t12" +"classname" "trigger_push" +// brush 0 +{ +( 768 -192 -48 ) ( 768 -16 -48 ) ( 656 -16 -48 ) common/caulk -25 -60 -90 0.031250 0.031250 0 0 0 +( 656 -16 -8 ) ( 768 -16 -8 ) ( 768 -192 -8 ) common/caulk -25 -60 -90 0.031250 0.031250 0 0 0 +( 704 -16 -120 ) ( 704 -192 -120 ) ( 704 -192 -176 ) common/caulk -56 -9 -180 0.031128 -0.031189 0 0 0 +( 656 -128 -8 ) ( 768 -128 -8 ) ( 768 -128 -64 ) common/caulk 44 -11 0 0.031250 0.031189 0 0 0 +( 768 -192 -8 ) ( 768 -16 -8 ) ( 768 -16 -64 ) common/caulk -56 -9 -180 0.031128 -0.031189 0 0 0 +( 768 -64 -8 ) ( 656 -64 -8 ) ( 656 -64 -64 ) common/caulk 44 -11 0 0.031250 0.031189 0 0 0 +} +} +// entity 30 +{ +"targetname" "t12" +"origin" "-24 -96 360" +"classname" "target_position" +} +// entity 31 +{ +"classname" "misc_model" +"origin" "736 -96 -40" +"model" "models/bounce_pad.md3" +"angle" "270" +} +// entity 32 +{ +"classname" "team_CTF_blueplayer" +"origin" "-352 -1248 288" +} +// entity 33 +{ +"origin" "0 -1248 288" +"classname" "team_CTF_blueflag" +} +// entity 34 +{ +"origin" "-224 -1248 288" +"classname" "team_CTF_blueplayer" +} +// entity 35 +{ +"classname" "team_CTF_blueplayer" +"origin" "224 -1248 288" +} +// entity 36 +{ +"origin" "352 -1248 288" +"classname" "team_CTF_blueplayer" +} +// entity 37 +{ +"classname" "team_CTF_redplayer" +"origin" "352 1248 288" +} +// entity 38 +{ +"origin" "224 1248 288" +"classname" "team_CTF_redplayer" +} +// entity 39 +{ +"classname" "team_CTF_redplayer" +"origin" "-352 1248 288" +} +// entity 40 +{ +"origin" "-224 1248 288" +"classname" "team_CTF_redplayer" +} +// entity 41 +{ +"classname" "team_CTF_redflag" +"origin" "0 1248 288" +} +// entity 42 +{ +"classname" "trigger_push" +"target" "t13" +// brush 0 +{ +( 112 48 1080 ) ( 112 160 1080 ) ( 112 160 1024 ) common/caulk 43 57 -180 0.031250 -0.031189 0 0 0 +( -16 48 1080 ) ( 160 48 1080 ) ( 160 48 1024 ) common/caulk -37 57 -180 0.031128 -0.031189 0 0 0 +( 48 160 1080 ) ( 48 48 1080 ) ( 48 48 1024 ) common/caulk 43 57 -180 0.031250 -0.031189 0 0 0 +( 160 112 1080 ) ( -16 112 1080 ) ( -16 112 1024 ) common/caulk -37 57 -180 0.031128 -0.031189 0 0 0 +( 160 160 1080 ) ( 160 48 1080 ) ( -16 48 1080 ) common/caulk -26 -60 -180 0.031250 0.031250 0 0 0 +( -16 48 1040 ) ( 160 48 1040 ) ( 160 160 1040 ) common/caulk -26 -60 -180 0.031250 0.031250 0 0 0 +} +} +// entity 43 +{ +"classname" "target_position" +"origin" "80 392 1224" +"targetname" "t13" +} +// entity 44 +{ +"angle" "180" +"model" "models/bounce_pad.md3" +"origin" "80 80 1048" +"classname" "misc_model" +} +// entity 45 +{ +"target" "t14" +"classname" "trigger_push" +// brush 0 +{ +( 16 -48 1040 ) ( -160 -48 1040 ) ( -160 -160 1040 ) common/caulk 38 -59 0 0.031250 0.031250 0 0 0 +( -160 -160 1080 ) ( -160 -48 1080 ) ( 16 -48 1080 ) common/caulk 38 -59 0 0.031250 0.031250 0 0 0 +( -160 -112 1080 ) ( 16 -112 1080 ) ( 16 -112 1024 ) common/caulk 27 57 0 0.031128 0.031128 0 0 0 +( -48 -160 1080 ) ( -48 -48 1080 ) ( -48 -48 1024 ) common/caulk 43 57 0 0.031250 0.031128 0 0 0 +( 16 -48 1080 ) ( -160 -48 1080 ) ( -160 -48 1024 ) common/caulk 27 57 0 0.031128 0.031128 0 0 0 +( -112 -48 1080 ) ( -112 -160 1080 ) ( -112 -160 1024 ) common/caulk 43 57 0 0.031250 0.031128 0 0 0 +} +} +// entity 46 +{ +"targetname" "t14" +"origin" "-80 -392 1224" +"classname" "target_position" +} +// entity 47 +{ +"classname" "misc_model" +"origin" "-80 -80 1048" +"model" "models/bounce_pad.md3" +"angle" "360" +} +// entity 48 +{ +"classname" "target_kill" +"origin" "-896 952 -688" +"targetname" "kill1" +} +// entity 49 +{ +"wait" "0.00001" +"target" "kill1" +"classname" "trigger_multiple" +// brush 0 +{ +( 960 904 -528 ) ( -832 904 -528 ) ( -832 -888 -528 ) map/poltergeist 0 0 0 0.031200 0.031200 0 0 0 +( -832 -888 -520 ) ( -832 904 -520 ) ( 960 904 -520 ) map/poltergeist 0 0 0 0.031200 0.031200 0 0 0 +( 1592 -3064 -520 ) ( 3384 -3064 -520 ) ( 3384 -3064 -536 ) map/poltergeist 0 107 0 0.031200 0.031200 0 0 0 +( 3320 -3080 -520 ) ( 3320 -1288 -520 ) ( 3320 -1288 -536 ) map/poltergeist 0 107 0 0.031200 0.031200 0 0 0 +( -1064 3064 -520 ) ( -2856 3064 -520 ) ( -2856 3064 -536 ) map/poltergeist 0 107 0 0.031200 0.031200 0 0 0 +( -2808 3304 -520 ) ( -2808 1512 -520 ) ( -2808 1512 -536 ) map/poltergeist 0 107 0 0.031200 0.031200 0 0 0 +} +} +// entity 50 +{ +"classname" "weapon_rocketlauncher" +"origin" "0 0 1048" +} +// entity 51 +{ +"classname" "weapon_lightning" +"origin" "-224 -992 280" +} +// entity 52 +{ +"origin" "224 -992 280" +"classname" "weapon_lightning" +} +// entity 53 +{ +"origin" "-224 992 280" +"classname" "weapon_lightning" +} +// entity 54 +{ +"classname" "weapon_lightning" +"origin" "224 992 280" +} +// entity 55 +{ +"classname" "misc_model" +"origin" "0 1248 256" +"model" "models/pickup_platform.md3" +} +// entity 56 +{ +"model" "models/pickup_platform.md3" +"origin" "0 -1248 256" +"classname" "misc_model" +} +// entity 57 +{ +"target" "t15" +"classname" "trigger_push" +// brush 0 +{ +( -768 512 -48 ) ( -768 336 -48 ) ( -656 336 -48 ) common/caulk 39 2 90 0.031250 0.031250 0 0 0 +( -656 336 -8 ) ( -768 336 -8 ) ( -768 512 -8 ) common/caulk 39 2 90 0.031250 0.031250 0 0 0 +( -704 336 -120 ) ( -704 512 -120 ) ( -704 512 -176 ) common/caulk 30 -11 0 0.031250 0.031189 0 0 0 +( -656 448 -8 ) ( -768 448 -8 ) ( -768 448 -64 ) common/caulk -20 -11 -180 0.031250 -0.031189 0 0 0 +( -768 512 -8 ) ( -768 336 -8 ) ( -768 336 -64 ) common/caulk 29 -11 0 0.031250 0.031189 0 0 0 +( -768 384 -8 ) ( -656 384 -8 ) ( -656 384 -64 ) common/caulk -20 -11 -180 0.031250 -0.031189 0 0 0 +} +} +// entity 58 +{ +"targetname" "t15" +"origin" "-328 760 504" +"classname" "target_position" +} +// entity 59 +{ +"classname" "misc_model" +"origin" "-736 416 -40" +"model" "models/bounce_pad.md3" +"angle" "135" +} +// entity 60 +{ +"origin" "-352 -1248 288" +"classname" "team_CTF_bluespawn" +} +// entity 61 +{ +"classname" "team_CTF_bluespawn" +"origin" "-224 -1248 288" +} +// entity 62 +{ +"origin" "224 -1248 288" +"classname" "team_CTF_bluespawn" +} +// entity 63 +{ +"classname" "team_CTF_bluespawn" +"origin" "352 -1248 288" +} +// entity 64 +{ +"origin" "352 1248 288" +"classname" "team_CTF_redspawn" +} +// entity 65 +{ +"classname" "team_CTF_redspawn" +"origin" "224 1248 288" +} +// entity 66 +{ +"origin" "-224 1248 288" +"classname" "team_CTF_redspawn" +} +// entity 67 +{ +"classname" "team_CTF_redspawn" +"origin" "-352 1248 288" +} +// entity 68 +{ +"model" "models/pickup_platform.md3" +"origin" "-224 992 256" +"classname" "misc_model" +} +// entity 69 +{ +"classname" "misc_model" +"origin" "224 992 256" +"model" "models/pickup_platform.md3" +} +// entity 70 +{ +"model" "models/pickup_platform.md3" +"origin" "224 -992 256" +"classname" "misc_model" +} +// entity 71 +{ +"classname" "misc_model" +"origin" "-224 -992 256" +"model" "models/pickup_platform.md3" +} +// entity 72 +{ +"model" "models/pickup_platform.md3" +"origin" "0 0 1024" +"classname" "misc_model" +} +// entity 73 +{ +"target" "t16" +"classname" "trigger_push" +// brush 0 +{ +( -1056 96 -48 ) ( -1056 -80 -48 ) ( -944 -80 -48 ) common/caulk 39 -63 90 0.031250 0.031250 0 0 0 +( -944 -80 -8 ) ( -1056 -80 -8 ) ( -1056 96 -8 ) common/caulk 39 -63 90 0.031250 0.031250 0 0 0 +( -992 -80 -120 ) ( -992 96 -120 ) ( -992 96 -176 ) common/caulk 30 -11 0 0.031250 0.031189 0 0 0 +( -944 32 -8 ) ( -1056 32 -8 ) ( -1056 32 -64 ) common/caulk 44 -11 -180 0.031250 -0.031189 0 0 0 +( -1056 96 -8 ) ( -1056 -80 -8 ) ( -1056 -80 -64 ) common/caulk 30 -11 0 0.031250 0.031189 0 0 0 +( -1056 -32 -8 ) ( -944 -32 -8 ) ( -944 -32 -64 ) common/caulk 44 -11 -180 0.031250 -0.031189 0 0 0 +} +} +// entity 74 +{ +"targetname" "t16" +"origin" "-280 0 1288" +"classname" "target_position" +} +// entity 75 +{ +"classname" "misc_model" +"origin" "-1024 0 -40" +"model" "models/bounce_pad.md3" +"angle" "90" +} +// entity 76 +{ +"classname" "trigger_push" +"target" "t17" +// brush 0 +{ +( 1056 32 -8 ) ( 944 32 -8 ) ( 944 32 -64 ) common/caulk 44 -10 0 0.031250 0.031189 0 0 0 +( 1056 -96 -8 ) ( 1056 80 -8 ) ( 1056 80 -64 ) common/caulk 30 -10 -180 0.031250 -0.031189 0 0 0 +( 944 -32 -8 ) ( 1056 -32 -8 ) ( 1056 -32 -64 ) common/caulk 44 -10 0 0.031250 0.031189 0 0 0 +( 992 80 -120 ) ( 992 -96 -120 ) ( 992 -96 -176 ) common/caulk 30 -10 -180 0.031250 -0.031189 0 0 0 +( 944 80 -8 ) ( 1056 80 -8 ) ( 1056 -96 -8 ) common/caulk 37 -63 -90 0.031250 0.031250 0 0 0 +( 1056 -96 -48 ) ( 1056 80 -48 ) ( 944 80 -48 ) common/caulk 37 -63 -90 0.031250 0.031250 0 0 0 +} +} +// entity 77 +{ +"classname" "target_position" +"origin" "280 0 1288" +"targetname" "t17" +} +// entity 78 +{ +"angle" "270" +"model" "models/bounce_pad.md3" +"origin" "1024 0 -40" +"classname" "misc_model" +} +// entity 79 +{ +"classname" "misc_model" +"origin" "352 1248 256" +"model" "models/teleport_platform.md3" +} +// entity 80 +{ +"model" "models/teleport_platform.md3" +"origin" "224 1248 256" +"classname" "misc_model" +} +// entity 81 +{ +"classname" "misc_model" +"origin" "-224 1248 256" +"model" "models/teleport_platform.md3" +} +// entity 82 +{ +"model" "models/teleport_platform.md3" +"origin" "-352 1248 256" +"classname" "misc_model" +} +// entity 83 +{ +"classname" "misc_model" +"origin" "-352 -1248 256" +"model" "models/teleport_platform.md3" +} +// entity 84 +{ +"classname" "misc_model" +"origin" "-224 -1248 256" +"model" "models/teleport_platform.md3" +} +// entity 85 +{ +"model" "models/teleport_platform.md3" +"origin" "224 -1248 256" +"classname" "misc_model" +} +// entity 86 +{ +"classname" "misc_model" +"origin" "352 -1248 256" +"model" "models/teleport_platform.md3" +} +// entity 87 +{ +"classname" "item_health_large" +"origin" "-208 208 -432" +} +// entity 88 +{ +"origin" "208 -208 -432" +"classname" "item_health_large" +} +// entity 89 +{ +"classname" "item_armor_combat" +"origin" "208 208 -432" +} +// entity 90 +{ +"origin" "-208 -208 -432" +"classname" "item_armor_combat" +} +// entity 91 +{ +"classname" "trigger_push" +"target" "t18" +// brush 0 +{ +( 320 1024 312 ) ( 320 912 312 ) ( 320 912 256 ) common/caulk -20 9 0 0.031250 0.031189 0 0 0 +( 448 1024 312 ) ( 272 1024 312 ) ( 272 1024 256 ) common/caulk -27 9 0 0.031128 0.031189 0 0 0 +( 384 912 312 ) ( 384 1024 312 ) ( 384 1024 256 ) common/caulk -20 9 0 0.031250 0.031189 0 0 0 +( 272 960 312 ) ( 448 960 312 ) ( 448 960 256 ) common/caulk -27 9 0 0.031128 0.031189 0 0 0 +( 272 912 312 ) ( 272 1024 312 ) ( 448 1024 312 ) common/caulk -26 3 0 0.031250 0.031250 0 0 0 +( 448 1024 272 ) ( 272 1024 272 ) ( 272 912 272 ) common/caulk -26 3 0 0.031250 0.031250 0 0 0 +} +} +// entity 92 +{ +"classname" "target_position" +"origin" "632 456 760" +"targetname" "t18" +} +// entity 93 +{ +"angle" "45" +"model" "models/bounce_pad.md3" +"origin" "352 992 280" +"classname" "misc_model" +} +// entity 94 +{ +"target" "t19" +"classname" "trigger_push" +// brush 0 +{ +( -448 -1024 272 ) ( -272 -1024 272 ) ( -272 -912 272 ) common/caulk -26 -60 -180 0.031250 0.031250 0 0 0 +( -272 -912 312 ) ( -272 -1024 312 ) ( -448 -1024 312 ) common/caulk -26 -60 -180 0.031250 0.031250 0 0 0 +( -272 -960 312 ) ( -448 -960 312 ) ( -448 -960 256 ) common/caulk -27 8 -180 0.031128 -0.031189 0 0 0 +( -384 -912 312 ) ( -384 -1024 312 ) ( -384 -1024 256 ) common/caulk 44 9 -180 0.031250 -0.031189 0 0 0 +( -448 -1024 312 ) ( -272 -1024 312 ) ( -272 -1024 256 ) common/caulk -27 8 -180 0.031128 -0.031189 0 0 0 +( -320 -1024 312 ) ( -320 -912 312 ) ( -320 -912 256 ) common/caulk 44 9 -180 0.031250 -0.031189 0 0 0 +} +} +// entity 95 +{ +"targetname" "t19" +"origin" "-632 -456 760" +"classname" "target_position" +} +// entity 96 +{ +"classname" "misc_model" +"origin" "-352 -992 280" +"model" "models/bounce_pad.md3" +"angle" "225" +} +// entity 97 +{ +"target" "t20" +"classname" "trigger_push" +// brush 0 +{ +( -704 -512 -48 ) ( -704 -336 -48 ) ( -816 -336 -48 ) common/caulk -26 0 -90 0.031250 0.031250 0 0 0 +( -816 -336 -8 ) ( -704 -336 -8 ) ( -704 -512 -8 ) common/caulk -26 0 -90 0.031250 0.031250 0 0 0 +( -768 -336 -120 ) ( -768 -512 -120 ) ( -768 -512 -176 ) common/caulk -34 54 -180 0.031250 -0.031189 0 0 0 +( -816 -448 -8 ) ( -704 -448 -8 ) ( -704 -448 -64 ) common/caulk -20 53 0 0.031250 0.031189 0 0 0 +( -704 -512 -8 ) ( -704 -336 -8 ) ( -704 -336 -64 ) common/caulk -35 54 -180 0.031250 -0.031189 0 0 0 +( -704 -384 -8 ) ( -816 -384 -8 ) ( -816 -384 -64 ) common/caulk -20 53 0 0.031250 0.031189 0 0 0 +} +} +// entity 98 +{ +"targetname" "t20" +"origin" "-328 -760 504" +"classname" "target_position" +} +// entity 99 +{ +"classname" "misc_model" +"origin" "-736 -416 -40" +"model" "models/bounce_pad.md3" +"angle" "45" +} +// entity 100 +{ +"target" "t21" +"classname" "trigger_push" +// brush 0 +{ +( 768 448 -8 ) ( 656 448 -8 ) ( 656 448 -64 ) common/caulk -20 -11 0 0.031250 0.031189 0 0 0 +( 768 320 -8 ) ( 768 496 -8 ) ( 768 496 -64 ) common/caulk -35 -10 -180 0.031250 -0.031189 0 0 0 +( 656 384 -8 ) ( 768 384 -8 ) ( 768 384 -64 ) common/caulk -20 -11 0 0.031250 0.031189 0 0 0 +( 704 496 -120 ) ( 704 320 -120 ) ( 704 320 -176 ) common/caulk -34 -10 -180 0.031250 -0.031189 0 0 0 +( 656 496 -8 ) ( 768 496 -8 ) ( 768 320 -8 ) common/caulk -25 1 -90 0.031250 0.031250 0 0 0 +( 768 320 -48 ) ( 768 496 -48 ) ( 656 496 -48 ) common/caulk -25 1 -90 0.031250 0.031250 0 0 0 +} +} +// entity 101 +{ +"classname" "target_position" +"origin" "328 760 504" +"targetname" "t21" +} +// entity 102 +{ +"angle" "225" +"model" "models/bounce_pad.md3" +"origin" "736 416 -40" +"classname" "misc_model" +} +// entity 103 +{ +"targetname" "t11" +"origin" "24 96 360" +"classname" "target_position" +} +// entity 104 +{ +"classname" "misc_model" +"origin" "-736 96 -40" +"model" "models/bounce_pad.md3" +"angle" "90" +} +// entity 105 +{ +"classname" "item_armor_combat" +"origin" "-208 0 -432" +} +// entity 106 +{ +"origin" "0 208 -432" +"classname" "item_health_large" +} +// entity 107 +{ +"origin" "208 0 -432" +"classname" "item_armor_combat" +} +// entity 108 +{ +"classname" "item_health_large" +"origin" "0 -208 -432" +} +// entity 109 +{ +"classname" "item_armor_shard" +"origin" "1112 32 -32" +} +// entity 110 +{ +"origin" "1112 -32 -32" +"classname" "item_armor_shard" +} +// entity 111 +{ +"classname" "item_armor_shard" +"origin" "1112 -96 -32" +} +// entity 112 +{ +"origin" "1112 96 -32" +"classname" "item_armor_shard" +} +// entity 113 +{ +"classname" "item_armor_shard" +"origin" "-1112 32 -32" +} +// entity 114 +{ +"origin" "-1112 96 -32" +"classname" "item_armor_shard" +} +// entity 115 +{ +"classname" "item_armor_shard" +"origin" "-1112 -32 -32" +} +// entity 116 +{ +"origin" "-1112 -96 -32" +"classname" "item_armor_shard" +} diff --git a/assets/maps/em_eat.map b/assets/maps/em_eat.map new file mode 100644 index 00000000..fb4be296 --- /dev/null +++ b/assets/maps/em_eat.map @@ -0,0 +1,369 @@ +// entity 0 +{ +"classname" "worldspawn" +// brush 0 +{ +( 264 -272 64 ) ( -56 -272 64 ) ( 264 -272 0 ) map/lab_games/lg_style_01_wall_green 5093 0 0 -0.065972 0.078125 0 0 0 +( -272 -8 88 ) ( -272 -264 88 ) ( -272 -264 24 ) map/lab_games/lg_style_01_wall_green 0 0 0 -0.000868 0.078125 0 0 0 +( 336 -280 88 ) ( 336 -24 88 ) ( 336 -24 24 ) map/lab_games/lg_style_01_wall_green 0 0 0 -0.000868 0.078125 0 0 0 +( -120 -280 64 ) ( 200 -280 64 ) ( 200 -280 0 ) map/lab_games/lg_style_01_wall_green 5093 0 0 -0.065972 0.078125 0 0 0 +( -128 -272 80 ) ( -128 -16 80 ) ( 192 -16 80 ) map/lab_games/lg_style_01_wall_green 5093 0 0 -0.065972 0.007812 0 0 0 +( 192 -16 0 ) ( -128 -16 0 ) ( -128 -272 0 ) map/lab_games/lg_style_01_wall_green 5093 0 0 -0.065972 0.007812 0 0 0 +} +// brush 1 +{ +( 336 200 88 ) ( 336 -56 88 ) ( 336 200 24 ) map/lab_games/lg_style_01_wall_green 4096 0 0 -0.066406 0.078125 0 0 0 +( 336 280 64 ) ( 16 280 64 ) ( 16 280 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 -0.000977 0.078125 0 0 0 +( 344 -64 64 ) ( 344 192 64 ) ( 344 192 0 ) map/lab_games/lg_style_01_wall_green 4096 0 0 -0.066406 0.078125 0 0 0 +( 16 -280 64 ) ( 336 -280 64 ) ( 336 -280 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 -0.000977 0.078125 0 0 0 +( 16 -56 80 ) ( 16 200 80 ) ( 336 200 80 ) map/lab_games/lg_style_01_wall_green 0 512 0 -0.000977 0.531250 0 0 0 +( 336 200 0 ) ( 16 200 0 ) ( 16 -56 0 ) map/lab_games/lg_style_01_wall_green 0 512 0 -0.000977 0.531250 0 0 0 +} +// brush 2 +{ +( -176 272 64 ) ( 144 272 64 ) ( -176 272 0 ) map/lab_games/lg_style_01_wall_green 5093 0 0 -0.065972 0.078125 0 0 0 +( -272 280 88 ) ( -272 24 88 ) ( -272 24 24 ) map/lab_games/lg_style_01_wall_green 0 0 0 -0.000868 0.078125 0 0 0 +( 96 280 64 ) ( -224 280 64 ) ( -224 280 0 ) map/lab_games/lg_style_01_wall_green 5093 0 0 -0.065972 0.078125 0 0 0 +( 336 8 88 ) ( 336 264 88 ) ( 336 264 24 ) map/lab_games/lg_style_01_wall_green 0 0 0 -0.000868 0.078125 0 0 0 +( -256 16 80 ) ( -256 272 80 ) ( 64 272 80 ) map/lab_games/lg_style_01_wall_green 5093 0 0 -0.065972 0.007812 0 0 0 +( 64 272 0 ) ( -256 272 0 ) ( -256 16 0 ) map/lab_games/lg_style_01_wall_green 5093 0 0 -0.065972 0.007812 0 0 0 +} +// brush 3 +{ +( -272 -128 88 ) ( -272 128 88 ) ( -272 -128 24 ) map/lab_games/lg_style_01_wall_green 4096 0 0 -0.066406 0.078125 0 0 0 +( -280 136 64 ) ( -280 -120 64 ) ( -280 -120 0 ) map/lab_games/lg_style_01_wall_green 4096 0 0 -0.066406 0.078125 0 0 0 +( -24 280 64 ) ( -344 280 64 ) ( -344 280 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 -0.000977 0.078125 0 0 0 +( -328 -280 64 ) ( -8 -280 64 ) ( -8 -280 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 -0.000977 0.078125 0 0 0 +( -336 -128 80 ) ( -336 128 80 ) ( -16 128 80 ) map/lab_games/lg_style_01_wall_green 0 512 0 -0.000977 0.531250 0 0 0 +( -16 128 0 ) ( -336 128 0 ) ( -336 -128 0 ) map/lab_games/lg_style_01_wall_green 0 512 0 -0.000977 0.531250 0 0 0 +} +// brush 4 +{ +( 128 128 -8 ) ( -192 128 -8 ) ( -192 -128 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -64 -280 64 ) ( 256 -280 64 ) ( 256 -280 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 344 -264 64 ) ( 344 -8 64 ) ( 344 -8 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 0 280 64 ) ( -320 280 64 ) ( -320 280 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -280 264 64 ) ( -280 8 64 ) ( -280 8 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -192 128 0 ) ( 128 128 0 ) ( -192 -128 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 5 +{ +( -192 -128 88 ) ( -192 128 88 ) ( 128 128 88 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( -64 -280 80 ) ( 256 -280 80 ) ( 256 -280 16 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( 344 -264 80 ) ( 344 -8 80 ) ( 344 -8 16 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( 0 280 80 ) ( -320 280 80 ) ( -320 280 16 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( -280 264 80 ) ( -280 8 80 ) ( -280 8 16 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( -192 128 80 ) ( -192 -128 80 ) ( 128 128 80 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +} +// brush 6 +{ +( 1424 224 -176 ) ( 904 224 -176 ) ( 904 -248 -176 ) map/lab_games/sky/lg_sky_02_dn 756 485 0 -0.507812 0.460938 0 0 0 +( 904 -248 200 ) ( 1424 -248 200 ) ( 1424 -248 184 ) map/lab_games/sky/lg_sky_02_dn 756 0 0 -0.507812 0.007812 0 0 0 +( 1424 -248 200 ) ( 1424 224 200 ) ( 1424 224 184 ) map/lab_games/sky/lg_sky_02_dn 485 0 0 -0.460938 0.007812 0 0 0 +( 1424 224 200 ) ( 904 224 200 ) ( 904 224 184 ) map/lab_games/sky/lg_sky_02_dn 756 0 0 -0.507812 0.007812 0 0 0 +( 904 224 200 ) ( 904 -248 200 ) ( 904 -248 184 ) map/lab_games/sky/lg_sky_02_dn 485 0 0 -0.460938 0.007812 0 0 0 +( 904 224 -168 ) ( 1424 224 -168 ) ( 904 -248 -168 ) map/lab_games/sky/lg_sky_02_dn 756 485 0 -0.507812 0.460938 0 0 0 +} +// brush 7 +{ +( 904 -248 200 ) ( 904 224 200 ) ( 1424 224 200 ) map/lab_games/sky/lg_sky_02_up 756 485 0 -0.507812 0.460938 0 0 0 +( 904 -248 200 ) ( 1424 -248 200 ) ( 1424 -248 184 ) map/lab_games/sky/lg_sky_02_up 756 0 0 -0.507812 0.007812 0 0 0 +( 1424 -248 200 ) ( 1424 224 200 ) ( 1424 224 184 ) map/lab_games/sky/lg_sky_02_up 485 0 0 -0.460938 0.007812 0 0 0 +( 1424 224 200 ) ( 904 224 200 ) ( 904 224 184 ) map/lab_games/sky/lg_sky_02_up 756 0 0 -0.507812 0.007812 0 0 0 +( 904 224 200 ) ( 904 -248 200 ) ( 904 -248 184 ) map/lab_games/sky/lg_sky_02_up 485 0 0 -0.460938 0.007812 0 0 0 +( 904 224 192 ) ( 904 -248 192 ) ( 1424 224 192 ) map/lab_games/sky/lg_sky_02_up 756 485 0 -0.507812 0.460938 0 0 0 +} +// brush 8 +{ +( 1424 224 -176 ) ( 904 224 -176 ) ( 904 -248 -176 ) map/lab_games/sky/lg_sky_02_bk 756 0 0 -0.507812 0.007812 0 0 0 +( 904 -248 200 ) ( 904 224 200 ) ( 1424 224 200 ) map/lab_games/sky/lg_sky_02_bk 756 0 0 -0.507812 0.007812 0 0 0 +( 904 -248 200 ) ( 1424 -248 200 ) ( 1424 -248 184 ) map/lab_games/sky/lg_sky_02_bk 756 544 0 -0.507812 0.367188 0 0 0 +( 1424 -248 200 ) ( 1424 224 200 ) ( 1424 224 184 ) map/lab_games/sky/lg_sky_02_bk 0 544 0 -0.007812 0.367188 0 0 0 +( 904 224 200 ) ( 904 -248 200 ) ( 904 -248 184 ) map/lab_games/sky/lg_sky_02_bk 0 544 0 -0.007812 0.367188 0 0 0 +( 1424 -240 200 ) ( 904 -240 200 ) ( 1424 -240 184 ) map/lab_games/sky/lg_sky_02_bk 756 544 0 -0.507812 0.367188 0 0 0 +} +// brush 9 +{ +( 1424 224 -176 ) ( 904 224 -176 ) ( 904 -248 -176 ) map/lab_games/sky/lg_sky_02_rt 0 485 0 -0.007812 0.460938 0 0 0 +( 904 -248 200 ) ( 904 224 200 ) ( 1424 224 200 ) map/lab_games/sky/lg_sky_02_rt 0 485 0 -0.007812 0.460938 0 0 0 +( 904 -248 200 ) ( 1424 -248 200 ) ( 1424 -248 184 ) map/lab_games/sky/lg_sky_02_rt 0 544 0 -0.007812 0.367188 0 0 0 +( 1424 -248 200 ) ( 1424 224 200 ) ( 1424 224 184 ) map/lab_games/sky/lg_sky_02_rt 485 544 0 -0.460938 0.367188 0 0 0 +( 1424 224 200 ) ( 904 224 200 ) ( 904 224 184 ) map/lab_games/sky/lg_sky_02_rt 0 544 0 -0.007812 0.367188 0 0 0 +( 1416 224 200 ) ( 1416 -248 200 ) ( 1416 224 184 ) map/lab_games/sky/lg_sky_02_rt 485 544 0 -0.460938 0.367188 0 0 0 +} +// brush 10 +{ +( 1424 224 -176 ) ( 904 224 -176 ) ( 904 -248 -176 ) map/lab_games/sky/lg_sky_02_ft 756 0 0 -0.507812 0.007812 0 0 0 +( 904 -248 200 ) ( 904 224 200 ) ( 1424 224 200 ) map/lab_games/sky/lg_sky_02_ft 756 0 0 -0.507812 0.007812 0 0 0 +( 1424 -248 200 ) ( 1424 224 200 ) ( 1424 224 184 ) map/lab_games/sky/lg_sky_02_ft 0 544 0 -0.007812 0.367188 0 0 0 +( 1424 224 200 ) ( 904 224 200 ) ( 904 224 184 ) map/lab_games/sky/lg_sky_02_ft 756 544 0 -0.507812 0.367188 0 0 0 +( 904 224 200 ) ( 904 -248 200 ) ( 904 -248 184 ) map/lab_games/sky/lg_sky_02_ft 0 544 0 -0.007812 0.367188 0 0 0 +( 904 216 200 ) ( 1424 216 200 ) ( 904 216 184 ) map/lab_games/sky/lg_sky_02_ft 756 544 0 -0.507812 0.367188 0 0 0 +} +// brush 11 +{ +( 1424 224 -176 ) ( 904 224 -176 ) ( 904 -248 -176 ) map/lab_games/sky/lg_sky_02_lf 0 485 0 -0.007812 0.460938 0 0 0 +( 904 -248 200 ) ( 904 224 200 ) ( 1424 224 200 ) map/lab_games/sky/lg_sky_02_lf 0 485 0 -0.007812 0.460938 0 0 0 +( 904 -248 200 ) ( 1424 -248 200 ) ( 1424 -248 184 ) map/lab_games/sky/lg_sky_02_lf 0 544 0 -0.007812 0.367188 0 0 0 +( 1424 224 200 ) ( 904 224 200 ) ( 904 224 184 ) map/lab_games/sky/lg_sky_02_lf 0 544 0 -0.007812 0.367188 0 0 0 +( 904 224 200 ) ( 904 -248 200 ) ( 904 -248 184 ) map/lab_games/sky/lg_sky_02_lf 485 544 0 -0.460938 0.367188 0 0 0 +( 912 -248 200 ) ( 912 224 200 ) ( 912 -248 184 ) map/lab_games/sky/lg_sky_02_lf 485 544 0 -0.460938 0.367188 0 0 0 +} +} +// entity 1 +{ +"classname" "info_player_start" +"origin" "-232 0 24" +"spawn_orientation_segment" "40" +"angle" "0" +"randomAngleRange" "0" +} +// entity 2 +{ +"light" "100" +"origin" "-176 184 64" +"classname" "light" +} +// entity 3 +{ +"classname" "apple_reward" +"origin" "-240 240 16" +} +// entity 4 +{ +"origin" "-112 128 16" +"classname" "apple_reward" +} +// entity 5 +{ +"classname" "apple_reward" +"origin" "-112 240 16" +} +// entity 6 +{ +"origin" "-240 -128 16" +"classname" "apple_reward" +} +// entity 7 +{ +"origin" "32 240 16" +"classname" "apple_reward" +} +// entity 8 +{ +"classname" "apple_reward" +"origin" "168 240 16" +} +// entity 9 +{ +"origin" "304 128 16" +"classname" "apple_reward" +} +// entity 10 +{ +"classname" "apple_reward" +"origin" "168 128 16" +} +// entity 11 +{ +"origin" "56 0 16" +"classname" "apple_reward" +} +// entity 12 +{ +"classname" "apple_reward" +"origin" "-112 0 16" +} +// entity 13 +{ +"origin" "32 -128 16" +"classname" "apple_reward" +} +// entity 14 +{ +"classname" "apple_reward" +"origin" "168 -128 16" +} +// entity 15 +{ +"origin" "-112 -128 16" +"classname" "apple_reward" +} +// entity 16 +{ +"classname" "apple_reward" +"origin" "-240 -240 16" +} +// entity 17 +{ +"origin" "32 -240 16" +"classname" "apple_reward" +} +// entity 18 +{ +"classname" "apple_reward" +"origin" "-112 -240 16" +} +// entity 19 +{ +"origin" "168 -240 16" +"classname" "apple_reward" +} +// entity 20 +{ +"classname" "apple_reward" +"origin" "304 -128 16" +} +// entity 21 +{ +"origin" "-240 128 16" +"classname" "apple_reward" +} +// entity 22 +{ +"model" "models/teleport_platform.md3" +"origin" "280 0 0" +"classname" "misc_model" +} +// entity 23 +{ +"classname" "misc_teleporter_dest" +"targetname" "teleport_dest_start" +"origin" "-232 0 24" +"spawn_orientation_segment" "40" +} +// entity 24 +{ +"origin" "32 128 16" +"classname" "apple_reward" +} +// entity 25 +{ +"classname" "light" +"origin" "-32 184 64" +"light" "100" +} +// entity 26 +{ +"light" "100" +"origin" "96 184 64" +"classname" "light" +} +// entity 27 +{ +"classname" "light" +"origin" "248 184 64" +"light" "100" +} +// entity 28 +{ +"light" "100" +"origin" "248 -184 64" +"classname" "light" +} +// entity 29 +{ +"classname" "light" +"origin" "96 -184 64" +"light" "100" +} +// entity 30 +{ +"light" "100" +"origin" "-32 -184 64" +"classname" "light" +} +// entity 31 +{ +"classname" "light" +"origin" "-176 -184 64" +"light" "100" +} +// entity 32 +{ +"light" "100" +"origin" "-176 0 64" +"classname" "light" +} +// entity 33 +{ +"classname" "light" +"origin" "-32 0 64" +"light" "100" +} +// entity 34 +{ +"light" "100" +"origin" "96 0 64" +"classname" "light" +} +// entity 35 +{ +"classname" "light" +"origin" "248 0 64" +"light" "100" +} +// entity 36 +{ +"classname" "trigger_multiple" +"target" "teleport_dest_start" +"id" "1" +// brush 0 +{ +( 256 24 56 ) ( 256 -24 56 ) ( 256 -24 8 ) common/caulk 0 0 0 0.031250 0.031250 0 0 0 +( 304 24 56 ) ( 256 24 56 ) ( 256 24 8 ) common/caulk 0 0 0 0.031250 0.031250 0 0 0 +( 304 -24 56 ) ( 304 24 56 ) ( 304 24 8 ) common/caulk 0 0 0 0.031250 0.031250 0 0 0 +( 256 -24 56 ) ( 304 -24 56 ) ( 304 -24 8 ) common/caulk 0 0 0 0.031250 0.031250 0 0 0 +( 256 -24 56 ) ( 256 24 56 ) ( 304 24 56 ) common/caulk 0 0 0 0.031250 0.031250 0 0 0 +( 304 24 8 ) ( 256 24 8 ) ( 256 -24 8 ) common/caulk 0 0 0 0.031250 0.031250 0 0 0 +} +} +// entity 37 +{ +"classname" "_skybox" +"origin" "1136 -16 -128" +} +// entity 38 +{ +"model" "models/stadium.md3" +"origin" "1136 -16 -66" +"classname" "misc_model" +"_remap" "*;textures/map/lab_games/stadium_d" +"angle" "90" +} +// entity 39 +{ +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"modelscale" "1" +"model" "models/signal_line.md3" +"origin" "1160 -56 -80" +"classname" "misc_model" +"angle" "113" +} +// entity 40 +{ +"classname" "misc_model" +"origin" "1208 -32 -152" +"model" "models/signal_line.md3" +"modelscale" "1.1" +"_remap" "*;textures/map/lab_games/signal_pulse_blue" +"angle" "38" +} +// entity 41 +{ +"angle" "28" +"classname" "misc_model" +"origin" "1168 -24 -96" +"model" "models/signal_line.md3" +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"modelscale" "0.61" +} +// entity 42 +{ +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"modelscale" "0.6" +"model" "models/signal_line.md3" +"origin" "1112 -8 -152" +"classname" "misc_model" +"angle" "159" +} diff --git a/assets/maps/em_non_match.map b/assets/maps/em_non_match.map new file mode 100644 index 00000000..76926463 --- /dev/null +++ b/assets/maps/em_non_match.map @@ -0,0 +1,479 @@ +// entity 0 +{ +"procedural_obj" "3" +"classname" "worldspawn" +// brush 0 +{ +( -256 256 0 ) ( -8 256 0 ) ( -256 -256 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -264 264 32 ) ( -264 -248 32 ) ( -264 -248 16 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -8 136 32 ) ( -256 136 32 ) ( -256 136 16 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 136 -256 32 ) ( 136 256 32 ) ( 136 256 16 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -240 -264 32 ) ( 8 -264 32 ) ( 8 -264 16 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -8 256 -8 ) ( -256 256 -8 ) ( -256 -256 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 1 +{ +( 88 -32 0 ) ( 40 -32 0 ) ( 40 -88 0 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +( 40 -88 10 ) ( 40 -32 10 ) ( 88 -32 10 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +( 48 -96 32 ) ( 96 -96 32 ) ( 96 -96 0 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +( 96 -96 32 ) ( 96 -40 32 ) ( 96 -40 0 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +( 80 -32 32 ) ( 32 -32 32 ) ( 32 -32 0 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +( 32 -24 32 ) ( 32 -80 32 ) ( 32 -80 0 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +} +// brush 2 +{ +( 384 384 128 ) ( 384 -128 128 ) ( 632 384 128 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.190000 0 0 0 +( 376 384 32 ) ( 376 -128 32 ) ( 376 -128 16 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.190000 0 0 0 +( 632 136 32 ) ( 384 136 32 ) ( 384 136 16 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.190000 0 0 0 +( 648 -128 32 ) ( 648 384 32 ) ( 648 384 16 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.190000 0 0 0 +( 384 -256 32 ) ( 632 -256 32 ) ( 632 -256 16 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.190000 0 0 0 +( 384 -128 136 ) ( 384 384 136 ) ( 632 384 136 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.190000 0 0 0 +} +// brush 3 +{ +( 384 384 0 ) ( 632 384 0 ) ( 384 -128 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 376 384 32 ) ( 376 -128 32 ) ( 376 -128 16 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 632 136 32 ) ( 384 136 32 ) ( 384 136 16 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 648 -128 32 ) ( 648 384 32 ) ( 648 384 16 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 384 -256 32 ) ( 632 -256 32 ) ( 632 -256 16 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 632 384 -8 ) ( 384 384 -8 ) ( 384 -128 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 4 +{ +( 248 8 136 ) ( 248 -136 136 ) ( 248 -136 128 ) map/lab_games/sky/lg_sky_01 0 0 0 0.062500 0.190000 0 0 0 +( 376 8 136 ) ( 312 8 136 ) ( 312 8 128 ) map/lab_games/sky/lg_sky_01 0 0 0 0.062500 0.190000 0 0 0 +( 376 -136 136 ) ( 376 8 136 ) ( 376 8 128 ) map/lab_games/sky/lg_sky_01 0 0 0 0.062500 0.190000 0 0 0 +( 312 -136 136 ) ( 376 -136 136 ) ( 376 -136 128 ) map/lab_games/sky/lg_sky_01 0 0 0 0.062500 0.190000 0 0 0 +( 312 -136 136 ) ( 312 8 136 ) ( 376 8 136 ) map/lab_games/sky/lg_sky_01 0 0 0 0.062500 0.190000 0 0 0 +( 376 8 128 ) ( 312 8 128 ) ( 312 -136 128 ) map/lab_games/sky/lg_sky_01 0 0 0 0.062500 0.190000 0 0 0 +} +// brush 5 +{ +( 376 8 -8 ) ( 248 8 -8 ) ( 248 -136 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 248 -136 0 ) ( 248 8 0 ) ( 376 8 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 248 -136 0 ) ( 376 -136 0 ) ( 376 -136 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 376 -136 0 ) ( 376 8 0 ) ( 376 8 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 376 8 0 ) ( 248 8 0 ) ( 248 8 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 248 8 0 ) ( 248 -136 0 ) ( 248 -136 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 6 +{ +( 8 -232 0 ) ( 8 -152 0 ) ( 0 -152 0 ) map/poltergeist -48 0 0 0.500000 0.500000 0 0 0 +( 8 -152 80 ) ( 8 -232 80 ) ( 0 -232 80 ) map/poltergeist -48 0 0 0.500000 0.500000 0 0 0 +( 8 -224 80 ) ( 8 -224 0 ) ( 0 -224 0 ) map/poltergeist -48 0 0 0.500000 0.500000 0 0 0 +( 8 -152 80 ) ( 8 -152 0 ) ( 8 -232 0 ) map/poltergeist 0 0 0 0.500000 0.500000 0 0 0 +( 8 -160 0 ) ( 8 -160 80 ) ( 0 -160 80 ) map/poltergeist -48 0 0 0.500000 0.500000 0 0 0 +( 0 -152 0 ) ( 0 -152 80 ) ( 0 -232 80 ) map/poltergeist 0 0 0 0.500000 0.500000 0 0 0 +} +// brush 7 +{ +( 64 -160 0 ) ( 64 -152 0 ) ( -8 -152 0 ) map/poltergeist 0 0 0 0.500000 0.500000 0 0 0 +( -8 -152 80 ) ( 64 -152 80 ) ( 64 -160 80 ) map/poltergeist 0 0 0 0.500000 0.500000 0 0 0 +( 0 -152 48 ) ( 0 -160 48 ) ( 0 -160 0 ) map/poltergeist 0 0 0 0.500000 0.500000 0 0 0 +( -8 -160 48 ) ( 64 -160 48 ) ( 64 -160 0 ) map/poltergeist 0 0 0 0.500000 0.500000 0 0 0 +( 80 -160 48 ) ( 80 -152 48 ) ( 80 -152 0 ) map/poltergeist 0 0 0 0.500000 0.500000 0 0 0 +( 64 -152 48 ) ( -8 -152 48 ) ( -8 -152 0 ) map/poltergeist 0 0 0 0.500000 0.500000 0 0 0 +} +// brush 8 +{ +( -256 256 128 ) ( -256 -256 128 ) ( -8 256 128 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( -264 256 32 ) ( -264 -256 32 ) ( -264 -256 16 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( 0 136 32 ) ( -248 136 32 ) ( -248 136 16 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( 136 -256 32 ) ( 136 256 32 ) ( 136 256 16 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( -232 -264 32 ) ( 16 -264 32 ) ( 16 -264 16 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( -256 -256 136 ) ( -256 256 136 ) ( -8 256 136 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +} +// brush 9 +{ +( 128 256 0 ) ( -120 256 0 ) ( -120 -256 0 ) map/lab_games/lg_style_01_wall_blue 4 512 0 -0.001953 0.500000 0 0 0 +( -120 -256 128 ) ( -120 256 128 ) ( 128 256 128 ) map/lab_games/lg_style_01_wall_blue 4 512 0 -0.001953 0.500000 0 0 0 +( -128 -264 8 ) ( 120 -264 8 ) ( 120 -264 -8 ) map/lab_games/lg_style_01_wall_blue 4 0 0 -0.001953 0.125000 0 0 0 +( 136 16 8 ) ( 136 528 8 ) ( 136 528 -8 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.125000 0 0 0 +( 128 136 8 ) ( -120 136 8 ) ( -120 136 -8 ) map/lab_games/lg_style_01_wall_blue 4 0 0 -0.001953 0.125000 0 0 0 +( 128 592 8 ) ( 128 80 8 ) ( 128 592 -8 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.125000 0 0 0 +} +// brush 10 +{ +( -8 248 0 ) ( -256 248 0 ) ( -256 -264 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.007812 0 0 0 +( -256 -264 128 ) ( -256 248 128 ) ( -8 248 128 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.007812 0 0 0 +( -256 -264 32 ) ( -8 -264 32 ) ( -8 -264 16 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.125000 0 0 0 +( 128 -248 32 ) ( 128 264 32 ) ( 128 264 16 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.003906 0.125000 0 0 0 +( -256 248 32 ) ( -256 -264 32 ) ( -256 -264 16 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.003906 0.125000 0 0 0 +( -8 -256 32 ) ( -256 -256 32 ) ( -8 -256 16 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.125000 0 0 0 +} +// brush 11 +{ +( -8 136 0 ) ( -256 136 0 ) ( -256 -376 0 ) map/lab_games/lg_style_01_wall_blue 0 -1 0 -0.125000 0.007812 0 0 0 +( -256 -376 128 ) ( -256 136 128 ) ( -8 136 128 ) map/lab_games/lg_style_01_wall_blue 0 -1 0 -0.125000 0.007812 0 0 0 +( 136 -368 32 ) ( 136 144 32 ) ( 136 144 16 ) map/lab_games/lg_style_01_wall_blue -2 0 0 -0.003906 0.125000 0 0 0 +( 128 136 32 ) ( -120 136 32 ) ( -120 136 16 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.125000 0 0 0 +( -256 136 32 ) ( -256 -376 32 ) ( -256 -376 16 ) map/lab_games/lg_style_01_wall_blue -2 0 0 -0.003906 0.125000 0 0 0 +( -256 128 32 ) ( -8 128 32 ) ( -256 128 16 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.125000 0 0 0 +} +// brush 12 +{ +( -16 256 0 ) ( -264 256 0 ) ( -264 -256 0 ) map/lab_games/lg_style_01_wall_blue 0 512 0 -0.001953 0.500000 0 0 0 +( -264 -256 128 ) ( -264 256 128 ) ( -16 256 128 ) map/lab_games/lg_style_01_wall_blue 0 512 0 -0.001953 0.500000 0 0 0 +( -264 -256 32 ) ( -16 -256 32 ) ( -16 -256 16 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.001953 0.125000 0 0 0 +( -16 136 32 ) ( -264 136 32 ) ( -264 136 16 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.001953 0.125000 0 0 0 +( -264 128 32 ) ( -264 -384 32 ) ( -264 -384 16 ) map/lab_games/lg_style_01_wall_blue 2048 0 0 -0.125000 0.125000 0 0 0 +( -256 -248 32 ) ( -256 264 32 ) ( -256 -248 16 ) map/lab_games/lg_style_01_wall_blue 2048 0 0 -0.125000 0.125000 0 0 0 +} +// brush 13 +{ +( 376 8 0 ) ( 320 8 0 ) ( 320 0 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.007812 0 0 0 +( 320 0 128 ) ( 320 8 128 ) ( 376 8 128 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.007812 0 0 0 +( 328 0 32 ) ( 384 0 32 ) ( 384 0 24 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.125000 0 0 0 +( 376 0 32 ) ( 376 8 32 ) ( 376 8 24 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.125000 0 0 0 +( 376 8 32 ) ( 320 8 32 ) ( 320 8 24 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.125000 0 0 0 +( 256 8 32 ) ( 256 0 32 ) ( 256 0 24 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.125000 0 0 0 +} +// brush 14 +{ +( 256 8 0 ) ( 248 8 0 ) ( 248 -128 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.125000 0 0 0 +( 248 -128 128 ) ( 248 8 128 ) ( 256 8 128 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.125000 0 0 0 +( 248 -136 128 ) ( 256 -136 128 ) ( 256 -136 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.125000 0 0 0 +( 256 -128 128 ) ( 256 8 128 ) ( 256 8 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.125000 0 0 0 +( 256 8 128 ) ( 248 8 128 ) ( 248 8 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.125000 0 0 0 +( 248 16 128 ) ( 248 -120 128 ) ( 248 -120 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.125000 0 0 0 +} +// brush 15 +{ +( 384 -512 32 ) ( 384 0 32 ) ( 384 -512 16 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.128906 0.125000 0 0 0 +( 376 232 32 ) ( 376 -280 32 ) ( 376 -280 16 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.128906 0.125000 0 0 0 +( 624 128 32 ) ( 376 128 32 ) ( 376 128 16 ) map/lab_games/lg_style_01_wall_blue 98 0 0 -0.121094 0.125000 0 0 0 +( 368 0 32 ) ( 616 0 32 ) ( 616 0 16 ) map/lab_games/lg_style_01_wall_blue 98 0 0 -0.121094 0.125000 0 0 0 +( 376 -256 128 ) ( 376 256 128 ) ( 624 256 128 ) map/lab_games/lg_style_01_wall_blue 98 0 0 -0.121094 0.257812 0 0 0 +( 624 256 0 ) ( 376 256 0 ) ( 376 -256 0 ) map/lab_games/lg_style_01_wall_blue 98 0 0 -0.121094 0.257812 0 0 0 +} +// brush 16 +{ +( 632 136 0 ) ( 384 136 0 ) ( 384 -376 0 ) map/lab_games/lg_style_01_wall_blue 0 -1 0 -0.125000 0.007812 0 0 0 +( 384 -376 128 ) ( 384 136 128 ) ( 632 136 128 ) map/lab_games/lg_style_01_wall_blue 0 -1 0 -0.125000 0.007812 0 0 0 +( 640 -376 32 ) ( 640 136 32 ) ( 640 136 16 ) map/lab_games/lg_style_01_wall_blue -2 0 0 -0.003906 0.125000 0 0 0 +( 632 136 32 ) ( 384 136 32 ) ( 384 136 16 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.125000 0 0 0 +( 384 136 32 ) ( 384 -376 32 ) ( 384 -376 16 ) map/lab_games/lg_style_01_wall_blue -2 0 0 -0.003906 0.125000 0 0 0 +( 384 128 32 ) ( 632 128 32 ) ( 384 128 16 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.125000 0 0 0 +} +// brush 17 +{ +( 640 384 0 ) ( 392 384 0 ) ( 392 -128 0 ) map/lab_games/lg_style_01_wall_blue -1015 768 0 -0.001953 0.500000 0 0 0 +( 392 -128 128 ) ( 392 384 128 ) ( 640 384 128 ) map/lab_games/lg_style_01_wall_blue -1015 768 0 -0.001953 0.500000 0 0 0 +( 392 -256 32 ) ( 640 -256 32 ) ( 640 -256 16 ) map/lab_games/lg_style_01_wall_blue -1015 0 0 -0.001953 0.125000 0 0 0 +( 648 -128 32 ) ( 648 384 32 ) ( 648 384 16 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.125000 0 0 0 +( 640 136 32 ) ( 392 136 32 ) ( 392 136 16 ) map/lab_games/lg_style_01_wall_blue -1015 0 0 -0.001953 0.125000 0 0 0 +( 640 8 32 ) ( 640 -504 32 ) ( 640 8 16 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.125000 0 0 0 +} +// brush 18 +{ +( 632 -248 32 ) ( 384 -248 32 ) ( 632 -248 16 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.125000 0 0 0 +( 384 256 32 ) ( 384 -256 32 ) ( 384 -256 16 ) map/lab_games/lg_style_01_wall_blue 1 0 0 -0.003906 0.125000 0 0 0 +( 640 -256 32 ) ( 640 256 32 ) ( 640 256 16 ) map/lab_games/lg_style_01_wall_blue 1 0 0 -0.003906 0.125000 0 0 0 +( 384 -256 32 ) ( 632 -256 32 ) ( 632 -256 16 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.125000 0 0 0 +( 384 -256 128 ) ( 384 256 128 ) ( 632 256 128 ) map/lab_games/lg_style_01_wall_blue 0 1 0 -0.125000 0.007812 0 0 0 +( 632 256 0 ) ( 384 256 0 ) ( 384 -256 0 ) map/lab_games/lg_style_01_wall_blue 0 1 0 -0.125000 0.007812 0 0 0 +} +// brush 19 +{ +( 624 0 0 ) ( 376 0 0 ) ( 376 -512 0 ) map/lab_games/lg_style_01_wall_blue 98 0 0 -0.121094 0.250000 0 0 0 +( 376 -512 128 ) ( 376 0 128 ) ( 624 0 128 ) map/lab_games/lg_style_01_wall_blue 98 0 0 -0.121094 0.250000 0 0 0 +( 368 -256 32 ) ( 616 -256 32 ) ( 616 -256 16 ) map/lab_games/lg_style_01_wall_blue 98 0 0 -0.121094 0.125000 0 0 0 +( 624 -128 32 ) ( 376 -128 32 ) ( 376 -128 16 ) map/lab_games/lg_style_01_wall_blue 98 0 0 -0.121094 0.125000 0 0 0 +( 376 -24 32 ) ( 376 -536 32 ) ( 376 -536 16 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.125000 0 0 0 +( 384 -632 32 ) ( 384 -120 32 ) ( 384 -632 16 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.125000 0 0 0 +} +// brush 20 +{ +( 376 -128 0 ) ( 320 -128 0 ) ( 320 -136 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.007812 0 0 0 +( 320 -136 128 ) ( 320 -128 128 ) ( 376 -128 128 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.007812 0 0 0 +( 344 -136 32 ) ( 400 -136 32 ) ( 400 -136 24 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.125000 0 0 0 +( 376 -136 32 ) ( 376 -128 32 ) ( 376 -128 24 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.125000 0 0 0 +( 320 -128 32 ) ( 264 -128 32 ) ( 264 -128 24 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.125000 0.125000 0 0 0 +( 256 -136 32 ) ( 256 -144 32 ) ( 256 -144 24 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.125000 0 0 0 +} +// brush 21 +{ +( 904 224 -168 ) ( 1424 224 -168 ) ( 904 -248 -168 ) map/lab_games/sky/lg_sky_02_dn 756 485 0 -0.507812 0.460938 0 0 0 +( 904 224 200 ) ( 904 -248 200 ) ( 904 -248 184 ) map/lab_games/sky/lg_sky_02_dn 485 0 0 -0.460938 0.007812 0 0 0 +( 1424 224 200 ) ( 904 224 200 ) ( 904 224 184 ) map/lab_games/sky/lg_sky_02_dn 756 0 0 -0.507812 0.007812 0 0 0 +( 1424 -248 200 ) ( 1424 224 200 ) ( 1424 224 184 ) map/lab_games/sky/lg_sky_02_dn 485 0 0 -0.460938 0.007812 0 0 0 +( 904 -248 200 ) ( 1424 -248 200 ) ( 1424 -248 184 ) map/lab_games/sky/lg_sky_02_dn 756 0 0 -0.507812 0.007812 0 0 0 +( 1424 224 -176 ) ( 904 224 -176 ) ( 904 -248 -176 ) map/lab_games/sky/lg_sky_02_dn 756 485 0 -0.507812 0.460938 0 0 0 +} +// brush 22 +{ +( 904 224 192 ) ( 904 -248 192 ) ( 1424 224 192 ) map/lab_games/sky/lg_sky_02_up 756 485 0 -0.507812 0.460938 0 0 0 +( 904 224 200 ) ( 904 -248 200 ) ( 904 -248 184 ) map/lab_games/sky/lg_sky_02_up 485 0 0 -0.460938 0.007812 0 0 0 +( 1424 224 200 ) ( 904 224 200 ) ( 904 224 184 ) map/lab_games/sky/lg_sky_02_up 756 0 0 -0.507812 0.007812 0 0 0 +( 1424 -248 200 ) ( 1424 224 200 ) ( 1424 224 184 ) map/lab_games/sky/lg_sky_02_up 485 0 0 -0.460938 0.007812 0 0 0 +( 904 -248 200 ) ( 1424 -248 200 ) ( 1424 -248 184 ) map/lab_games/sky/lg_sky_02_up 756 0 0 -0.507812 0.007812 0 0 0 +( 904 -248 200 ) ( 904 224 200 ) ( 1424 224 200 ) map/lab_games/sky/lg_sky_02_up 756 485 0 -0.507812 0.460938 0 0 0 +} +// brush 23 +{ +( 1424 -240 200 ) ( 904 -240 200 ) ( 1424 -240 184 ) map/lab_games/sky/lg_sky_02_bk 756 544 0 -0.507812 0.367188 0 0 0 +( 904 224 200 ) ( 904 -248 200 ) ( 904 -248 184 ) map/lab_games/sky/lg_sky_02_bk 0 544 0 -0.007812 0.367188 0 0 0 +( 1424 -248 200 ) ( 1424 224 200 ) ( 1424 224 184 ) map/lab_games/sky/lg_sky_02_bk 0 544 0 -0.007812 0.367188 0 0 0 +( 904 -248 200 ) ( 1424 -248 200 ) ( 1424 -248 184 ) map/lab_games/sky/lg_sky_02_bk 756 544 0 -0.507812 0.367188 0 0 0 +( 904 -248 200 ) ( 904 224 200 ) ( 1424 224 200 ) map/lab_games/sky/lg_sky_02_bk 756 0 0 -0.507812 0.007812 0 0 0 +( 1424 224 -176 ) ( 904 224 -176 ) ( 904 -248 -176 ) map/lab_games/sky/lg_sky_02_bk 756 0 0 -0.507812 0.007812 0 0 0 +} +// brush 24 +{ +( 1416 224 200 ) ( 1416 -248 200 ) ( 1416 224 184 ) map/lab_games/sky/lg_sky_02_rt 485 544 0 -0.460938 0.367188 0 0 0 +( 1424 224 200 ) ( 904 224 200 ) ( 904 224 184 ) map/lab_games/sky/lg_sky_02_rt 0 544 0 -0.007812 0.367188 0 0 0 +( 1424 -248 200 ) ( 1424 224 200 ) ( 1424 224 184 ) map/lab_games/sky/lg_sky_02_rt 485 544 0 -0.460938 0.367188 0 0 0 +( 904 -248 200 ) ( 1424 -248 200 ) ( 1424 -248 184 ) map/lab_games/sky/lg_sky_02_rt 0 544 0 -0.007812 0.367188 0 0 0 +( 904 -248 200 ) ( 904 224 200 ) ( 1424 224 200 ) map/lab_games/sky/lg_sky_02_rt 0 485 0 -0.007812 0.460938 0 0 0 +( 1424 224 -176 ) ( 904 224 -176 ) ( 904 -248 -176 ) map/lab_games/sky/lg_sky_02_rt 0 485 0 -0.007812 0.460938 0 0 0 +} +// brush 25 +{ +( 904 216 200 ) ( 1424 216 200 ) ( 904 216 184 ) map/lab_games/sky/lg_sky_02_ft 756 544 0 -0.507812 0.367188 0 0 0 +( 904 224 200 ) ( 904 -248 200 ) ( 904 -248 184 ) map/lab_games/sky/lg_sky_02_ft 0 544 0 -0.007812 0.367188 0 0 0 +( 1424 224 200 ) ( 904 224 200 ) ( 904 224 184 ) map/lab_games/sky/lg_sky_02_ft 756 544 0 -0.507812 0.367188 0 0 0 +( 1424 -248 200 ) ( 1424 224 200 ) ( 1424 224 184 ) map/lab_games/sky/lg_sky_02_ft 0 544 0 -0.007812 0.367188 0 0 0 +( 904 -248 200 ) ( 904 224 200 ) ( 1424 224 200 ) map/lab_games/sky/lg_sky_02_ft 756 0 0 -0.507812 0.007812 0 0 0 +( 1424 224 -176 ) ( 904 224 -176 ) ( 904 -248 -176 ) map/lab_games/sky/lg_sky_02_ft 756 0 0 -0.507812 0.007812 0 0 0 +} +// brush 26 +{ +( 912 -248 200 ) ( 912 224 200 ) ( 912 -248 184 ) map/lab_games/sky/lg_sky_02_lf 485 544 0 -0.460938 0.367188 0 0 0 +( 904 224 200 ) ( 904 -248 200 ) ( 904 -248 184 ) map/lab_games/sky/lg_sky_02_lf 485 544 0 -0.460938 0.367188 0 0 0 +( 1424 224 200 ) ( 904 224 200 ) ( 904 224 184 ) map/lab_games/sky/lg_sky_02_lf 0 544 0 -0.007812 0.367188 0 0 0 +( 904 -248 200 ) ( 1424 -248 200 ) ( 1424 -248 184 ) map/lab_games/sky/lg_sky_02_lf 0 544 0 -0.007812 0.367188 0 0 0 +( 904 -248 200 ) ( 904 224 200 ) ( 1424 224 200 ) map/lab_games/sky/lg_sky_02_lf 0 485 0 -0.007812 0.460938 0 0 0 +( 1424 224 -176 ) ( 904 224 -176 ) ( 904 -248 -176 ) map/lab_games/sky/lg_sky_02_lf 0 485 0 -0.007812 0.460938 0 0 0 +} +// brush 27 +{ +( 0 104 48 ) ( 0 32 48 ) ( 0 32 0 ) map/poltergeist 0 0 0 0.500000 0.500000 0 0 0 +( 16 96 48 ) ( 8 96 48 ) ( 8 96 0 ) map/poltergeist 0 0 0 0.500000 0.500000 0 0 0 +( 8 24 32 ) ( 8 96 32 ) ( 8 96 -16 ) map/poltergeist 0 0 0 0.500000 0.500000 0 0 0 +( 8 32 48 ) ( 16 32 48 ) ( 16 32 0 ) map/poltergeist 0 0 0 0.500000 0.500000 0 0 0 +( 8 24 80 ) ( 8 96 80 ) ( 16 96 80 ) map/poltergeist 0 0 0 0.500000 0.500000 0 0 0 +( 16 96 0 ) ( 8 96 0 ) ( 8 24 0 ) map/poltergeist 0 0 0 0.500000 0.500000 0 0 0 +} +// brush 28 +{ +( 80 96 0 ) ( 72 96 0 ) ( 72 24 0 ) map/poltergeist -16 0 0 0.500000 0.500000 0 0 0 +( 72 24 80 ) ( 72 96 80 ) ( 80 96 80 ) map/poltergeist -16 0 0 0.500000 0.500000 0 0 0 +( 72 32 48 ) ( 80 32 48 ) ( 80 32 0 ) map/poltergeist -16 0 0 0.500000 0.500000 0 0 0 +( 80 24 48 ) ( 80 96 48 ) ( 80 96 0 ) map/poltergeist 0 0 0 0.500000 0.500000 0 0 0 +( 80 96 48 ) ( 72 96 48 ) ( 72 96 0 ) map/poltergeist -16 0 0 0.500000 0.500000 0 0 0 +( 72 96 48 ) ( 72 24 48 ) ( 72 24 0 ) map/poltergeist 0 0 0 0.500000 0.500000 0 0 0 +} +// brush 29 +{ +( 72 104 48 ) ( 0 104 48 ) ( 0 104 0 ) map/poltergeist -16 0 0 0.500000 0.500000 0 0 0 +( 80 96 48 ) ( 80 104 48 ) ( 80 104 0 ) map/poltergeist 0 0 0 0.500000 0.500000 0 0 0 +( -8 96 48 ) ( 64 96 48 ) ( 64 96 0 ) map/poltergeist -16 0 0 0.500000 0.500000 0 0 0 +( 0 104 48 ) ( 0 96 48 ) ( 0 96 0 ) map/poltergeist 0 0 0 0.500000 0.500000 0 0 0 +( 0 104 80 ) ( 72 104 80 ) ( 72 96 80 ) map/poltergeist -16 0 0 0.500000 0.500000 0 0 0 +( 72 96 0 ) ( 72 104 0 ) ( 0 104 0 ) map/poltergeist -16 0 0 0.500000 0.500000 0 0 0 +} +// brush 30 +{ +( 72 24 0 ) ( 72 32 0 ) ( 0 32 0 ) map/poltergeist -16 0 0 0.500000 0.500000 0 0 0 +( 0 32 80 ) ( 72 32 80 ) ( 72 24 80 ) map/poltergeist -16 0 0 0.500000 0.500000 0 0 0 +( 0 32 48 ) ( 0 24 48 ) ( 0 24 0 ) map/poltergeist 0 0 0 0.500000 0.500000 0 0 0 +( 8 24 48 ) ( 80 24 48 ) ( 80 24 0 ) map/poltergeist -16 0 0 0.500000 0.500000 0 0 0 +( 80 24 48 ) ( 80 32 48 ) ( 80 32 0 ) map/poltergeist 0 0 0 0.500000 0.500000 0 0 0 +( 64 32 48 ) ( -8 32 48 ) ( -8 32 0 ) map/poltergeist -16 0 0 0.500000 0.500000 0 0 0 +} +// brush 31 +{ +( 64 -224 48 ) ( -8 -224 48 ) ( -8 -224 0 ) map/poltergeist -16 0 0 0.500000 0.500000 0 0 0 +( 80 -232 48 ) ( 80 -224 48 ) ( 80 -224 0 ) map/poltergeist 0 0 0 0.500000 0.500000 0 0 0 +( 8 -232 48 ) ( 80 -232 48 ) ( 80 -232 0 ) map/poltergeist -16 0 0 0.500000 0.500000 0 0 0 +( 0 -224 48 ) ( 0 -232 48 ) ( 0 -232 0 ) map/poltergeist 0 0 0 0.500000 0.500000 0 0 0 +( 0 -224 80 ) ( 72 -224 80 ) ( 72 -232 80 ) map/poltergeist -16 0 0 0.500000 0.500000 0 0 0 +( 72 -232 0 ) ( 72 -224 0 ) ( 0 -224 0 ) map/poltergeist -16 0 0 0.500000 0.500000 0 0 0 +} +// brush 32 +{ +( 80 -160 0 ) ( 72 -160 0 ) ( 72 -232 0 ) map/poltergeist -16 0 0 0.500000 0.500000 0 0 0 +( 72 -232 80 ) ( 72 -160 80 ) ( 80 -160 80 ) map/poltergeist -16 0 0 0.500000 0.500000 0 0 0 +( 72 -224 48 ) ( 80 -224 48 ) ( 80 -224 0 ) map/poltergeist -16 0 0 0.500000 0.500000 0 0 0 +( 80 -232 48 ) ( 80 -160 48 ) ( 80 -160 0 ) map/poltergeist 0 0 0 0.500000 0.500000 0 0 0 +( 80 -160 48 ) ( 72 -160 48 ) ( 72 -160 0 ) map/poltergeist -16 0 0 0.500000 0.500000 0 0 0 +( 72 -160 48 ) ( 72 -232 48 ) ( 72 -232 0 ) map/poltergeist 0 0 0 0.500000 0.500000 0 0 0 +} +} +// entity 1 +{ +"spawn_id" "1" +"origin" "-232 -64 24" +"classname" "info_player_start" +"spawn_orientation_segment" "20" +"randomAngleRange" "0" +} +// entity 2 +{ +"target" "teleport_dest_second_room" +"classname" "trigger_teleport" +"id" "1" +// brush 0 +{ +( 80 -40 8 ) ( 48 -40 8 ) ( 48 -80 8 ) common/caulk -12 -16 0 0.062500 0.062500 0 4 0 +( 48 -80 48 ) ( 48 -40 48 ) ( 80 -40 48 ) common/caulk -12 -16 0 0.062500 0.062500 0 4 0 +( 48 -88 24 ) ( 80 -88 24 ) ( 80 -88 16 ) common/caulk -12 16 0 0.062500 0.062500 0 4 0 +( 88 -80 24 ) ( 88 -40 24 ) ( 88 -40 16 ) common/caulk 16 16 0 0.062500 0.062500 0 4 0 +( 80 -40 24 ) ( 48 -40 24 ) ( 48 -40 16 ) common/caulk -12 16 0 0.062500 0.062500 0 4 0 +( 40 -40 24 ) ( 40 -80 24 ) ( 40 -80 16 ) common/caulk 16 16 0 0.062500 0.062500 0 4 0 +} +} +// entity 3 +{ +"classname" "light" +"origin" "-128 64 112" +"light" "150" +} +// entity 4 +{ +"classname" "light" +"origin" "-128 -192 112" +"light" "150" +} +// entity 5 +{ +"classname" "light" +"origin" "40 64 104" +"light" "200" +} +// entity 6 +{ +"light" "150" +"origin" "592 -64 112" +"classname" "light" +} +// entity 7 +{ +"classname" "misc_teleporter_dest" +"origin" "288 -64 48" +"targetname" "teleport_dest_second_room" +"spawn_orientation_segment" "20" +} +// entity 8 +{ +"classname" "light" +"origin" "464 24 112" +"light" "150" +} +// entity 9 +{ +"origin" "600 64 48" +"classname" "pickup_choice_1" +} +// entity 10 +{ +"classname" "pickup_choice_2" +"origin" "600 -192 48" +} +// entity 11 +{ +"light" "150" +"origin" "464 -168 112" +"classname" "light" +} +// entity 12 +{ +"light" "200" +"origin" "40 -192 104" +"classname" "light" +} +// entity 13 +{ +"classname" "pickup_example_2" +"origin" "40 -192 40" +} +// entity 14 +{ +"origin" "40 64 40" +"classname" "pickup_example_1" +} +// entity 15 +{ +"light" "150" +"origin" "344 -64 112" +"classname" "light" +} +// entity 16 +{ +"origin" "1136 -16 -128" +"classname" "_skybox" +} +// entity 17 +{ +"angle" "90" +"_remap" "*;textures/map/lab_games/stadium_d" +"classname" "misc_model" +"origin" "1136 -16 -82" +"model" "models/stadium.md3" +} +// entity 18 +{ +"angle" "113" +"classname" "misc_model" +"origin" "1160 -56 -80" +"model" "models/signal_line.md3" +"modelscale" "1" +"_remap" "*;textures/map/lab_games/signal_pulse_red" +} +// entity 19 +{ +"angle" "38" +"_remap" "*;textures/map/lab_games/signal_pulse_blue" +"modelscale" "1.1" +"model" "models/signal_line.md3" +"origin" "1208 -32 -152" +"classname" "misc_model" +} +// entity 20 +{ +"modelscale" "0.61" +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"model" "models/signal_line.md3" +"origin" "1168 -24 -88" +"classname" "misc_model" +"angle" "28" +} +// entity 21 +{ +"angle" "159" +"classname" "misc_model" +"origin" "1112 -8 -152" +"model" "models/signal_line.md3" +"modelscale" "0.6" +"_remap" "*;textures/map/lab_games/signal_pulse_red" +} +// entity 22 +{ +"model" "models/pickup_platform.md3" +"origin" "600 64 0" +"classname" "misc_model" +} +// entity 23 +{ +"classname" "misc_model" +"origin" "600 -192 0" +"model" "models/pickup_platform.md3" +} +// entity 24 +{ +"classname" "misc_model" +"origin" "40 64 0" +"model" "models/pickup_platform.md3" +} +// entity 25 +{ +"model" "models/pickup_platform.md3" +"origin" "40 -192 0" +"classname" "misc_model" +} +// entity 26 +{ +"model" "models/teleport_platform.md3" +"origin" "64 -64 0" +"classname" "misc_model" +} diff --git a/assets/maps/em_tmaze_a1.map b/assets/maps/em_tmaze_a1.map new file mode 100644 index 00000000..967176cf --- /dev/null +++ b/assets/maps/em_tmaze_a1.map @@ -0,0 +1,238 @@ +// entity 0 +{ +"classname" "worldspawn" +// brush 0 +{ +( -200 208 8 ) ( -200 80 8 ) ( -200 80 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +( 56 128 8 ) ( -72 128 8 ) ( -72 128 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.093750 0 0 0 +( -72 64 8 ) ( 56 64 8 ) ( 56 64 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.093750 0 0 0 +( -72 64 96 ) ( -72 192 96 ) ( 56 192 96 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.062500 0 0 0 +( 56 192 0 ) ( -72 192 0 ) ( -72 64 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.062500 0 0 0 +( -192 16 -24 ) ( -192 144 -24 ) ( -192 16 -32 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +} +// brush 1 +{ +( -200 152 8 ) ( -200 24 8 ) ( -200 24 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.001563 0.093750 0 0 0 +( 96 136 8 ) ( -32 136 8 ) ( -32 136 0 ) map/lab_games/lg_style_01_wall_blue 2048 0 0 -0.062500 0.093750 0 0 0 +( 136 8 8 ) ( 136 136 8 ) ( 136 136 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.001563 0.093750 0 0 0 +( -64 8 96 ) ( -64 136 96 ) ( 64 136 96 ) map/lab_games/lg_style_01_wall_blue 2048 0 0 -0.062500 0.007812 0 0 0 +( 64 136 0 ) ( -64 136 0 ) ( -64 8 0 ) map/lab_games/lg_style_01_wall_blue 2048 0 0 -0.062500 0.007812 0 0 0 +( -136 128 8 ) ( -8 128 8 ) ( -136 128 0 ) map/lab_games/lg_style_01_wall_blue 2048 0 0 -0.062500 0.093750 0 0 0 +} +// brush 2 +{ +( 24 128 8 ) ( -104 128 8 ) ( -104 128 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.093750 0 0 0 +( 136 64 8 ) ( 136 192 8 ) ( 136 192 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +( -104 64 8 ) ( 24 64 8 ) ( 24 64 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.093750 0 0 0 +( -104 64 96 ) ( -104 192 96 ) ( 24 192 96 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.062500 0 0 0 +( 24 192 0 ) ( -104 192 0 ) ( -104 64 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.062500 0 0 0 +( 128 128 8 ) ( 128 0 8 ) ( 128 128 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +} +// brush 3 +{ +( -200 208 -24 ) ( -200 80 -24 ) ( -200 80 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( 56 136 -24 ) ( -72 136 -24 ) ( -72 136 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( 136 64 -24 ) ( 136 192 -24 ) ( 136 192 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( -64 56 -24 ) ( 64 56 -24 ) ( 64 56 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( -64 64 104 ) ( -64 192 104 ) ( 64 192 104 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( -64 192 96 ) ( -64 64 96 ) ( 64 192 96 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +} +// brush 4 +{ +( 64 64 -8 ) ( -64 64 -8 ) ( -64 -64 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -64 -64 8 ) ( 64 -64 8 ) ( 64 -64 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 0 -64 8 ) ( 0 64 8 ) ( 0 64 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 64 56 8 ) ( -64 56 8 ) ( -64 56 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -72 64 8 ) ( -72 -64 8 ) ( -72 -64 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -64 64 0 ) ( 64 64 0 ) ( -64 -64 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 5 +{ +( -64 -64 104 ) ( -64 64 104 ) ( 64 64 104 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( -64 -64 -24 ) ( 64 -64 -24 ) ( 64 -64 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( 0 -64 -24 ) ( 0 64 -24 ) ( 0 64 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( 64 56 -24 ) ( -64 56 -24 ) ( -64 56 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( -72 64 -24 ) ( -72 -64 -24 ) ( -72 -64 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( -64 64 96 ) ( -64 -64 96 ) ( 64 64 96 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +} +// brush 6 +{ +( 64 64 0 ) ( -64 64 0 ) ( -64 -64 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.007812 0 0 0 +( -64 -64 96 ) ( -64 64 96 ) ( 64 64 96 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.007812 0 0 0 +( -128 -64 8 ) ( 0 -64 8 ) ( 0 -64 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +( 0 -64 40 ) ( 0 64 40 ) ( 0 64 32 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.093750 0 0 0 +( -64 64 40 ) ( -64 -64 40 ) ( -64 -64 32 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.093750 0 0 0 +( 64 -56 8 ) ( -64 -56 8 ) ( 64 -56 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +} +// brush 7 +{ +( 8 64 0 ) ( -120 64 0 ) ( -120 -64 0 ) map/lab_games/lg_style_01_wall_blue 0 546 0 -0.058594 0.117188 0 0 0 +( -120 -64 96 ) ( -120 64 96 ) ( 8 64 96 ) map/lab_games/lg_style_01_wall_blue 0 546 0 -0.058594 0.117188 0 0 0 +( -120 -64 8 ) ( 8 -64 8 ) ( 8 -64 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.058594 0.093750 0 0 0 +( 8 -56 40 ) ( 8 72 40 ) ( 8 72 32 ) map/lab_games/lg_style_01_wall_blue 1092 0 0 -0.058594 0.093750 0 0 0 +( 8 64 8 ) ( -120 64 8 ) ( -120 64 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.058594 0.093750 0 0 0 +( 0 72 -24 ) ( 0 -56 -24 ) ( 0 72 -32 ) map/lab_games/lg_style_01_wall_blue 1092 0 0 -0.058594 0.093750 0 0 0 +} +// brush 8 +{ +( 56 64 0 ) ( -72 64 0 ) ( -72 -64 0 ) map/lab_games/lg_style_01_wall_blue 955 546 0 -0.058594 0.117188 0 0 0 +( -72 -64 96 ) ( -72 64 96 ) ( 56 64 96 ) map/lab_games/lg_style_01_wall_blue 955 546 0 -0.058594 0.117188 0 0 0 +( -72 -56 8 ) ( 56 -56 8 ) ( 56 -56 0 ) map/lab_games/lg_style_01_wall_blue 955 0 0 -0.058594 0.093750 0 0 0 +( 56 64 8 ) ( -72 64 8 ) ( -72 64 0 ) map/lab_games/lg_style_01_wall_blue 955 0 0 -0.058594 0.093750 0 0 0 +( -72 72 8 ) ( -72 -56 8 ) ( -72 -56 0 ) map/lab_games/lg_style_01_wall_blue 1092 0 0 -0.058594 0.093750 0 0 0 +( -64 -56 40 ) ( -64 72 40 ) ( -64 -56 32 ) map/lab_games/lg_style_01_wall_blue 1092 0 0 -0.058594 0.093750 0 0 0 +} +// brush 9 +{ +( -64 64 8 ) ( -192 64 8 ) ( -64 64 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +( 0 184 0 ) ( -128 184 0 ) ( -128 56 0 ) map/lab_games/lg_style_01_wall_blue 0 512 0 -0.062500 0.125000 0 0 0 +( -128 56 96 ) ( -128 184 96 ) ( 0 184 96 ) map/lab_games/lg_style_01_wall_blue 0 512 0 -0.062500 0.125000 0 0 0 +( 272 56 8 ) ( 400 56 8 ) ( 400 56 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +( 136 56 8 ) ( 136 184 8 ) ( 136 184 0 ) map/lab_games/lg_style_01_wall_blue 1024 0 0 -0.062500 0.093750 0 0 0 +( 8 200 40 ) ( 8 72 40 ) ( 8 72 32 ) map/lab_games/lg_style_01_wall_blue 1024 0 0 -0.062500 0.093750 0 0 0 +} +// brush 10 +{ +( -200 64 136 ) ( -200 56 136 ) ( -200 56 -8 ) map/lab_games/lg_style_01_wall_blue 1092 0 0 -0.058594 0.093750 0 0 0 +( -64 64 136 ) ( -192 64 136 ) ( -192 64 -8 ) map/lab_games/lg_style_01_wall_blue 1024 0 0 -0.062500 0.093750 0 0 0 +( -64 -56 168 ) ( -64 -48 168 ) ( -64 -48 24 ) map/lab_games/lg_style_01_wall_blue 1092 0 0 -0.058594 0.093750 0 0 0 +( -136 56 136 ) ( -8 56 136 ) ( -8 56 -8 ) map/lab_games/lg_style_01_wall_blue 1024 0 0 -0.062500 0.093750 0 0 0 +( -192 56 96 ) ( -192 64 96 ) ( -64 64 96 ) map/lab_games/lg_style_01_wall_blue 1024 546 0 -0.062500 0.117188 0 0 0 +( -64 64 0 ) ( -192 64 0 ) ( -192 56 0 ) map/lab_games/lg_style_01_wall_blue 1024 546 0 -0.062500 0.117188 0 0 0 +} +// brush 11 +{ +( -64 192 0 ) ( 64 192 0 ) ( -64 64 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 64 192 -8 ) ( -64 192 -8 ) ( -64 64 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -64 56 8 ) ( 64 56 8 ) ( 64 56 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 136 64 8 ) ( 136 192 8 ) ( 136 192 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 56 136 8 ) ( -72 136 8 ) ( -72 136 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -200 208 8 ) ( -200 80 8 ) ( -200 80 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 12 +{ +( 776 320 -176 ) ( 256 320 -176 ) ( 256 -152 -176 ) map/lab_games/sky/lg_sky_02_dn -520 693 0 -0.507812 0.460938 0 0 0 +( 256 -152 200 ) ( 776 -152 200 ) ( 776 -152 184 ) map/lab_games/sky/lg_sky_02_dn -520 0 0 -0.507812 0.007812 0 0 0 +( 776 -152 200 ) ( 776 320 200 ) ( 776 320 184 ) map/lab_games/sky/lg_sky_02_dn 693 0 0 -0.460938 0.007812 0 0 0 +( 776 320 200 ) ( 256 320 200 ) ( 256 320 184 ) map/lab_games/sky/lg_sky_02_dn -520 0 0 -0.507812 0.007812 0 0 0 +( 256 320 200 ) ( 256 -152 200 ) ( 256 -152 184 ) map/lab_games/sky/lg_sky_02_dn 693 0 0 -0.460938 0.007812 0 0 0 +( 256 320 -168 ) ( 776 320 -168 ) ( 256 -152 -168 ) map/lab_games/sky/lg_sky_02_dn -520 693 0 -0.507812 0.460938 0 0 0 +} +// brush 13 +{ +( 256 -152 200 ) ( 256 320 200 ) ( 776 320 200 ) map/lab_games/sky/lg_sky_02_up -520 693 0 -0.507812 0.460938 0 0 0 +( 256 -152 200 ) ( 776 -152 200 ) ( 776 -152 184 ) map/lab_games/sky/lg_sky_02_up -520 0 0 -0.507812 0.007812 0 0 0 +( 776 -152 200 ) ( 776 320 200 ) ( 776 320 184 ) map/lab_games/sky/lg_sky_02_up 693 0 0 -0.460938 0.007812 0 0 0 +( 776 320 200 ) ( 256 320 200 ) ( 256 320 184 ) map/lab_games/sky/lg_sky_02_up -520 0 0 -0.507812 0.007812 0 0 0 +( 256 320 200 ) ( 256 -152 200 ) ( 256 -152 184 ) map/lab_games/sky/lg_sky_02_up 693 0 0 -0.460938 0.007812 0 0 0 +( 256 320 192 ) ( 256 -152 192 ) ( 776 320 192 ) map/lab_games/sky/lg_sky_02_up -520 693 0 -0.507812 0.460938 0 0 0 +} +// brush 14 +{ +( 776 320 -176 ) ( 256 320 -176 ) ( 256 -152 -176 ) map/lab_games/sky/lg_sky_02_bk -520 0 0 -0.507812 0.007812 0 0 0 +( 256 -152 200 ) ( 256 320 200 ) ( 776 320 200 ) map/lab_games/sky/lg_sky_02_bk -520 0 0 -0.507812 0.007812 0 0 0 +( 256 -152 200 ) ( 776 -152 200 ) ( 776 -152 184 ) map/lab_games/sky/lg_sky_02_bk -520 544 0 -0.507812 0.367188 0 0 0 +( 776 -152 200 ) ( 776 320 200 ) ( 776 320 184 ) map/lab_games/sky/lg_sky_02_bk 0 544 0 -0.007812 0.367188 0 0 0 +( 256 320 200 ) ( 256 -152 200 ) ( 256 -152 184 ) map/lab_games/sky/lg_sky_02_bk 0 544 0 -0.007812 0.367188 0 0 0 +( 776 -144 200 ) ( 256 -144 200 ) ( 776 -144 184 ) map/lab_games/sky/lg_sky_02_bk -520 544 0 -0.507812 0.367188 0 0 0 +} +// brush 15 +{ +( 776 320 -176 ) ( 256 320 -176 ) ( 256 -152 -176 ) map/lab_games/sky/lg_sky_02_rt 1018 693 0 -0.007812 0.460938 0 0 0 +( 256 -152 200 ) ( 256 320 200 ) ( 776 320 200 ) map/lab_games/sky/lg_sky_02_rt 1018 693 0 -0.007812 0.460938 0 0 0 +( 256 -152 200 ) ( 776 -152 200 ) ( 776 -152 184 ) map/lab_games/sky/lg_sky_02_rt 1018 544 0 -0.007812 0.367188 0 0 0 +( 776 -152 200 ) ( 776 320 200 ) ( 776 320 184 ) map/lab_games/sky/lg_sky_02_rt 693 544 0 -0.460938 0.367188 0 0 0 +( 776 320 200 ) ( 256 320 200 ) ( 256 320 184 ) map/lab_games/sky/lg_sky_02_rt 1018 544 0 -0.007812 0.367188 0 0 0 +( 768 320 200 ) ( 768 -152 200 ) ( 768 320 184 ) map/lab_games/sky/lg_sky_02_rt 693 544 0 -0.460938 0.367188 0 0 0 +} +// brush 16 +{ +( 776 320 -176 ) ( 256 320 -176 ) ( 256 -152 -176 ) map/lab_games/sky/lg_sky_02_ft -520 0 0 -0.507812 0.007812 0 0 0 +( 256 -152 200 ) ( 256 320 200 ) ( 776 320 200 ) map/lab_games/sky/lg_sky_02_ft -520 0 0 -0.507812 0.007812 0 0 0 +( 776 -152 200 ) ( 776 320 200 ) ( 776 320 184 ) map/lab_games/sky/lg_sky_02_ft 0 544 0 -0.007812 0.367188 0 0 0 +( 776 320 200 ) ( 256 320 200 ) ( 256 320 184 ) map/lab_games/sky/lg_sky_02_ft -520 544 0 -0.507812 0.367188 0 0 0 +( 256 320 200 ) ( 256 -152 200 ) ( 256 -152 184 ) map/lab_games/sky/lg_sky_02_ft 0 544 0 -0.007812 0.367188 0 0 0 +( 256 312 200 ) ( 776 312 200 ) ( 256 312 184 ) map/lab_games/sky/lg_sky_02_ft -520 544 0 -0.507812 0.367188 0 0 0 +} +// brush 17 +{ +( 776 320 -176 ) ( 256 320 -176 ) ( 256 -152 -176 ) map/lab_games/sky/lg_sky_02_lf 1018 693 0 -0.007812 0.460938 0 0 0 +( 256 -152 200 ) ( 256 320 200 ) ( 776 320 200 ) map/lab_games/sky/lg_sky_02_lf 1018 693 0 -0.007812 0.460938 0 0 0 +( 256 -152 200 ) ( 776 -152 200 ) ( 776 -152 184 ) map/lab_games/sky/lg_sky_02_lf 1018 544 0 -0.007812 0.367188 0 0 0 +( 776 320 200 ) ( 256 320 200 ) ( 256 320 184 ) map/lab_games/sky/lg_sky_02_lf 1018 544 0 -0.007812 0.367188 0 0 0 +( 256 320 200 ) ( 256 -152 200 ) ( 256 -152 184 ) map/lab_games/sky/lg_sky_02_lf 693 544 0 -0.460938 0.367188 0 0 0 +( 264 -152 200 ) ( 264 320 200 ) ( 264 -152 184 ) map/lab_games/sky/lg_sky_02_lf 693 544 0 -0.460938 0.367188 0 0 0 +} +} +// entity 1 +{ +"spawn_orientation_segment" "20" +"angle" "90" +"origin" "-32 -32 24" +"classname" "info_player_start" +} +// entity 2 +{ +"classname" "light" +"origin" "-32 96 80" +"light" "500" +} +// entity 3 +{ +"classname" "placeholder_1" +"origin" "-176 96 16" +} +// entity 4 +{ +"classname" "placeholder_2" +"origin" "112 96 16" +} +// entity 5 +{ +"classname" "_skybox" +"origin" "488 80 -128" +} +// entity 6 +{ +"model" "models/stadium.md3" +"origin" "488 80 -82" +"classname" "misc_model" +"_remap" "*;textures/map/lab_games/stadium_d" +"angle" "90" +} +// entity 7 +{ +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"modelscale" "1" +"model" "models/signal_line.md3" +"origin" "512 40 -80" +"classname" "misc_model" +"angle" "113" +} +// entity 8 +{ +"classname" "misc_model" +"origin" "560 64 -152" +"model" "models/signal_line.md3" +"modelscale" "1.1" +"_remap" "*;textures/map/lab_games/signal_pulse_blue" +"angle" "38" +} +// entity 9 +{ +"angle" "28" +"classname" "misc_model" +"origin" "520 72 -88" +"model" "models/signal_line.md3" +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"modelscale" "0.61" +} +// entity 10 +{ +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"modelscale" "0.6" +"model" "models/signal_line.md3" +"origin" "464 88 -152" +"classname" "misc_model" +"angle" "159" +} diff --git a/assets/maps/em_tmaze_a2.map b/assets/maps/em_tmaze_a2.map new file mode 100644 index 00000000..d1da46a0 --- /dev/null +++ b/assets/maps/em_tmaze_a2.map @@ -0,0 +1,256 @@ +// entity 0 +{ +"classname" "worldspawn" +// brush 0 +{ +( -64 192 96 ) ( -64 64 96 ) ( 64 192 96 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( -64 64 104 ) ( -64 192 104 ) ( 64 192 104 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( -64 56 -24 ) ( 64 56 -24 ) ( 64 56 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( 200 64 -24 ) ( 200 192 -24 ) ( 200 192 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( 56 136 -24 ) ( -72 136 -24 ) ( -72 136 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( -264 216 -24 ) ( -264 88 -24 ) ( -264 88 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +} +// brush 1 +{ +( -64 64 0 ) ( 64 64 0 ) ( -64 -64 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -72 64 8 ) ( -72 -64 8 ) ( -72 -64 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 64 56 8 ) ( -64 56 8 ) ( -64 56 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 0 -64 8 ) ( 0 64 8 ) ( 0 64 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -72 -136 8 ) ( 56 -136 8 ) ( 56 -136 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 64 64 -8 ) ( -64 64 -8 ) ( -64 -64 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 2 +{ +( -64 64 96 ) ( -64 -64 96 ) ( 64 64 96 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( -72 64 -24 ) ( -72 -64 -24 ) ( -72 -64 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( 64 56 -24 ) ( -64 56 -24 ) ( -64 56 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( 0 -64 -24 ) ( 0 64 -24 ) ( 0 64 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( -64 -136 -24 ) ( 64 -136 -24 ) ( 64 -136 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( -64 -64 104 ) ( -64 64 104 ) ( 64 64 104 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +} +// brush 3 +{ +( -264 216 8 ) ( -264 88 8 ) ( -264 88 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 120 136 8 ) ( -8 136 8 ) ( -8 136 0 ) map/lab_games/lg_style_01_floor_orange -3 0 0 0.190000 0.190000 0 0 0 +( 200 64 8 ) ( 200 192 8 ) ( 200 192 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 0 56 8 ) ( 128 56 8 ) ( 128 56 0 ) map/lab_games/lg_style_01_floor_orange -3 0 0 0.190000 0.190000 0 0 0 +( 128 192 -8 ) ( 0 192 -8 ) ( 0 64 -8 ) map/lab_games/lg_style_01_floor_orange -3 0 0 0.190000 0.190000 0 0 0 +( 0 192 0 ) ( 128 192 0 ) ( 0 64 0 ) map/lab_games/lg_style_01_floor_orange -3 0 0 0.190000 0.190000 0 0 0 +} +// brush 4 +{ +( -64 -136 40 ) ( -64 -8 40 ) ( -64 -136 32 ) map/lab_games/lg_style_01_wall_blue 1024 0 0 -0.062500 0.093750 0 0 0 +( -72 80 40 ) ( -72 -48 40 ) ( -72 -48 32 ) map/lab_games/lg_style_01_wall_blue 1024 0 0 -0.062500 0.093750 0 0 0 +( 56 64 8 ) ( -72 64 8 ) ( -72 64 0 ) map/lab_games/lg_style_01_wall_blue 2048 0 0 -0.062500 0.093750 0 0 0 +( -73 -136 8 ) ( 55 -136 8 ) ( 55 -136 0 ) map/lab_games/lg_style_01_wall_blue 2048 0 0 -0.062500 0.093750 0 0 0 +( -73 -64 96 ) ( -73 64 96 ) ( 55 64 96 ) map/lab_games/lg_style_01_wall_blue 2048 341 0 -0.062500 0.187500 0 0 0 +( 55 64 0 ) ( -73 64 0 ) ( -73 -64 0 ) map/lab_games/lg_style_01_wall_blue 2048 341 0 -0.062500 0.187500 0 0 0 +} +// brush 5 +{ +( -135 128 8 ) ( -7 128 8 ) ( -135 128 0 ) map/lab_games/lg_style_01_wall_blue 3072 0 0 -0.062500 0.093750 0 0 0 +( 64 136 0 ) ( -64 136 0 ) ( -64 8 0 ) map/lab_games/lg_style_01_wall_blue 3072 0 0 -0.062500 0.007812 0 0 0 +( -64 8 96 ) ( -64 136 96 ) ( 64 136 96 ) map/lab_games/lg_style_01_wall_blue 3072 0 0 -0.062500 0.007812 0 0 0 +( 192 8 8 ) ( 192 136 8 ) ( 192 136 0 ) map/lab_games/lg_style_01_wall_blue 7167 0 0 -0.001116 0.093750 0 0 0 +( 105 136 8 ) ( -23 136 8 ) ( -23 136 0 ) map/lab_games/lg_style_01_wall_blue 3072 0 0 -0.062500 0.093750 0 0 0 +( -256 152 8 ) ( -256 24 8 ) ( -256 24 0 ) map/lab_games/lg_style_01_wall_blue 7167 0 0 -0.001116 0.093750 0 0 0 +} +// brush 6 +{ +( 0 0 -24 ) ( 0 -128 -24 ) ( 0 0 -32 ) map/lab_games/lg_style_01_wall_blue 1024 0 0 -0.062500 0.093750 0 0 0 +( 8 64 8 ) ( -120 64 8 ) ( -120 64 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.059896 0.093750 0 0 0 +( 8 -48 40 ) ( 8 80 40 ) ( 8 80 32 ) map/lab_games/lg_style_01_wall_blue 1024 0 0 -0.062500 0.093750 0 0 0 +( -121 -136 8 ) ( 7 -136 8 ) ( 7 -136 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.059896 0.093750 0 0 0 +( -121 -64 96 ) ( -121 64 96 ) ( 7 64 96 ) map/lab_games/lg_style_01_wall_blue 0 341 0 -0.059896 0.187500 0 0 0 +( 7 64 0 ) ( -121 64 0 ) ( -121 -64 0 ) map/lab_games/lg_style_01_wall_blue 0 341 0 -0.059896 0.187500 0 0 0 +} +// brush 7 +{ +( 65 -128 8 ) ( -63 -128 8 ) ( 65 -128 0 ) map/lab_games/lg_style_01_wall_blue 16 0 0 -0.062500 0.093750 0 0 0 +( -64 -8 40 ) ( -64 -136 40 ) ( -64 -136 32 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.093750 0 0 0 +( 0 -136 40 ) ( 0 -8 40 ) ( 0 -8 32 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.093750 0 0 0 +( -128 -136 8 ) ( 0 -136 8 ) ( 0 -136 0 ) map/lab_games/lg_style_01_wall_blue 16 0 0 -0.062500 0.093750 0 0 0 +( -64 -136 96 ) ( -64 -8 96 ) ( 64 -8 96 ) map/lab_games/lg_style_01_wall_blue 16 0 0 -0.062500 0.007812 0 0 0 +( 64 -8 0 ) ( -64 -8 0 ) ( -64 -136 0 ) map/lab_games/lg_style_01_wall_blue 16 0 0 -0.062500 0.007812 0 0 0 +} +// brush 8 +{ +( -256 16 -24 ) ( -256 144 -24 ) ( -256 16 -32 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +( -8 192 0 ) ( -136 192 0 ) ( -136 64 0 ) map/lab_games/lg_style_01_wall_blue 682 0 0 -0.008789 0.062500 0 0 0 +( -136 64 96 ) ( -136 192 96 ) ( -8 192 96 ) map/lab_games/lg_style_01_wall_blue 682 0 0 -0.008789 0.062500 0 0 0 +( -135 64 8 ) ( -7 64 8 ) ( -7 64 0 ) map/lab_games/lg_style_01_wall_blue 682 0 0 -0.008789 0.093750 0 0 0 +( -7 128 8 ) ( -135 128 8 ) ( -135 128 0 ) map/lab_games/lg_style_01_wall_blue 682 0 0 -0.008789 0.093750 0 0 0 +( -264 208 8 ) ( -264 80 8 ) ( -264 80 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +} +// brush 9 +{ +( -64 64 0 ) ( -192 64 0 ) ( -192 56 0 ) map/lab_games/lg_style_01_wall_blue 2088 327 0 -0.065104 0.195312 0 0 0 +( -192 56 96 ) ( -192 64 96 ) ( -64 64 96 ) map/lab_games/lg_style_01_wall_blue 2088 327 0 -0.065104 0.195312 0 0 0 +( -136 56 136 ) ( -8 56 136 ) ( -8 56 -8 ) map/lab_games/lg_style_01_wall_blue 2088 0 0 -0.065104 0.093750 0 0 0 +( -72 -56 168 ) ( -72 -48 168 ) ( -72 -48 24 ) map/lab_games/lg_style_01_wall_blue 983 0 0 -0.065104 0.093750 0 0 0 +( -127 64 136 ) ( -255 64 136 ) ( -255 64 -8 ) map/lab_games/lg_style_01_wall_blue 2088 0 0 -0.065104 0.093750 0 0 0 +( -264 56 136 ) ( -264 48 136 ) ( -264 48 -8 ) map/lab_games/lg_style_01_wall_blue 983 0 0 -0.065104 0.093750 0 0 0 +} +// brush 10 +{ +( 192 128 8 ) ( 192 0 8 ) ( 192 128 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +( 88 192 0 ) ( -40 192 0 ) ( -40 64 0 ) map/lab_games/lg_style_01_wall_blue 585 0 0 -0.006836 0.062500 0 0 0 +( -40 64 96 ) ( -40 192 96 ) ( 88 192 96 ) map/lab_games/lg_style_01_wall_blue 585 0 0 -0.006836 0.062500 0 0 0 +( -39 64 8 ) ( 89 64 8 ) ( 89 64 0 ) map/lab_games/lg_style_01_wall_blue 585 0 0 -0.006836 0.093750 0 0 0 +( 200 64 8 ) ( 200 192 8 ) ( 200 192 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +( 89 128 8 ) ( -39 128 8 ) ( -39 128 0 ) map/lab_games/lg_style_01_wall_blue 585 0 0 -0.006836 0.093750 0 0 0 +} +// brush 11 +{ +( -7 64 8 ) ( -135 64 8 ) ( -7 64 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +( -8 184 0 ) ( -136 184 0 ) ( -136 56 0 ) map/lab_games/lg_style_01_wall_blue 0 356 0 -0.062500 0.179688 0 0 0 +( -136 56 96 ) ( -136 184 96 ) ( -8 184 96 ) map/lab_games/lg_style_01_wall_blue 0 356 0 -0.062500 0.179688 0 0 0 +( 256 56 8 ) ( 384 56 8 ) ( 384 56 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +( 200 56 8 ) ( 200 184 8 ) ( 200 184 0 ) map/lab_games/lg_style_01_wall_blue 1068 0 0 -0.059896 0.093750 0 0 0 +( 0 200 40 ) ( 0 72 40 ) ( 0 72 32 ) map/lab_games/lg_style_01_wall_blue 1068 0 0 -0.059896 0.093750 0 0 0 +} +// brush 12 +{ +( 944 304 -176 ) ( 424 304 -176 ) ( 424 -168 -176 ) map/lab_games/sky/lg_sky_02_dn -189 658 0 -0.507812 0.460938 0 0 0 +( 424 -168 200 ) ( 944 -168 200 ) ( 944 -168 184 ) map/lab_games/sky/lg_sky_02_dn -189 0 0 -0.507812 0.007812 0 0 0 +( 944 -168 200 ) ( 944 304 200 ) ( 944 304 184 ) map/lab_games/sky/lg_sky_02_dn 658 0 0 -0.460938 0.007812 0 0 0 +( 944 304 200 ) ( 424 304 200 ) ( 424 304 184 ) map/lab_games/sky/lg_sky_02_dn -189 0 0 -0.507812 0.007812 0 0 0 +( 424 304 200 ) ( 424 -168 200 ) ( 424 -168 184 ) map/lab_games/sky/lg_sky_02_dn 658 0 0 -0.460938 0.007812 0 0 0 +( 424 304 -168 ) ( 944 304 -168 ) ( 424 -168 -168 ) map/lab_games/sky/lg_sky_02_dn -189 658 0 -0.507812 0.460938 0 0 0 +} +// brush 13 +{ +( 424 -168 200 ) ( 424 304 200 ) ( 944 304 200 ) map/lab_games/sky/lg_sky_02_up -189 658 0 -0.507812 0.460938 0 0 0 +( 424 -168 200 ) ( 944 -168 200 ) ( 944 -168 184 ) map/lab_games/sky/lg_sky_02_up -189 0 0 -0.507812 0.007812 0 0 0 +( 944 -168 200 ) ( 944 304 200 ) ( 944 304 184 ) map/lab_games/sky/lg_sky_02_up 658 0 0 -0.460938 0.007812 0 0 0 +( 944 304 200 ) ( 424 304 200 ) ( 424 304 184 ) map/lab_games/sky/lg_sky_02_up -189 0 0 -0.507812 0.007812 0 0 0 +( 424 304 200 ) ( 424 -168 200 ) ( 424 -168 184 ) map/lab_games/sky/lg_sky_02_up 658 0 0 -0.460938 0.007812 0 0 0 +( 424 304 192 ) ( 424 -168 192 ) ( 944 304 192 ) map/lab_games/sky/lg_sky_02_up -189 658 0 -0.507812 0.460938 0 0 0 +} +// brush 14 +{ +( 944 304 -176 ) ( 424 304 -176 ) ( 424 -168 -176 ) map/lab_games/sky/lg_sky_02_bk -189 0 0 -0.507812 0.007812 0 0 0 +( 424 -168 200 ) ( 424 304 200 ) ( 944 304 200 ) map/lab_games/sky/lg_sky_02_bk -189 0 0 -0.507812 0.007812 0 0 0 +( 424 -168 200 ) ( 944 -168 200 ) ( 944 -168 184 ) map/lab_games/sky/lg_sky_02_bk -189 544 0 -0.507812 0.367188 0 0 0 +( 944 -168 200 ) ( 944 304 200 ) ( 944 304 184 ) map/lab_games/sky/lg_sky_02_bk 0 544 0 -0.007812 0.367188 0 0 0 +( 424 304 200 ) ( 424 -168 200 ) ( 424 -168 184 ) map/lab_games/sky/lg_sky_02_bk 0 544 0 -0.007812 0.367188 0 0 0 +( 944 -160 200 ) ( 424 -160 200 ) ( 944 -160 184 ) map/lab_games/sky/lg_sky_02_bk -189 544 0 -0.507812 0.367188 0 0 0 +} +// brush 15 +{ +( 944 304 -176 ) ( 424 304 -176 ) ( 424 -168 -176 ) map/lab_games/sky/lg_sky_02_rt -3 658 0 -0.007812 0.460938 0 0 0 +( 424 -168 200 ) ( 424 304 200 ) ( 944 304 200 ) map/lab_games/sky/lg_sky_02_rt -3 658 0 -0.007812 0.460938 0 0 0 +( 424 -168 200 ) ( 944 -168 200 ) ( 944 -168 184 ) map/lab_games/sky/lg_sky_02_rt -3 544 0 -0.007812 0.367188 0 0 0 +( 944 -168 200 ) ( 944 304 200 ) ( 944 304 184 ) map/lab_games/sky/lg_sky_02_rt 658 544 0 -0.460938 0.367188 0 0 0 +( 944 304 200 ) ( 424 304 200 ) ( 424 304 184 ) map/lab_games/sky/lg_sky_02_rt -3 544 0 -0.007812 0.367188 0 0 0 +( 936 304 200 ) ( 936 -168 200 ) ( 936 304 184 ) map/lab_games/sky/lg_sky_02_rt 658 544 0 -0.460938 0.367188 0 0 0 +} +// brush 16 +{ +( 944 304 -176 ) ( 424 304 -176 ) ( 424 -168 -176 ) map/lab_games/sky/lg_sky_02_ft -189 0 0 -0.507812 0.007812 0 0 0 +( 424 -168 200 ) ( 424 304 200 ) ( 944 304 200 ) map/lab_games/sky/lg_sky_02_ft -189 0 0 -0.507812 0.007812 0 0 0 +( 944 -168 200 ) ( 944 304 200 ) ( 944 304 184 ) map/lab_games/sky/lg_sky_02_ft 0 544 0 -0.007812 0.367188 0 0 0 +( 944 304 200 ) ( 424 304 200 ) ( 424 304 184 ) map/lab_games/sky/lg_sky_02_ft -189 544 0 -0.507812 0.367188 0 0 0 +( 424 304 200 ) ( 424 -168 200 ) ( 424 -168 184 ) map/lab_games/sky/lg_sky_02_ft 0 544 0 -0.007812 0.367188 0 0 0 +( 424 296 200 ) ( 944 296 200 ) ( 424 296 184 ) map/lab_games/sky/lg_sky_02_ft -189 544 0 -0.507812 0.367188 0 0 0 +} +// brush 17 +{ +( 944 304 -176 ) ( 424 304 -176 ) ( 424 -168 -176 ) map/lab_games/sky/lg_sky_02_lf -3 658 0 -0.007812 0.460938 0 0 0 +( 424 -168 200 ) ( 424 304 200 ) ( 944 304 200 ) map/lab_games/sky/lg_sky_02_lf -3 658 0 -0.007812 0.460938 0 0 0 +( 424 -168 200 ) ( 944 -168 200 ) ( 944 -168 184 ) map/lab_games/sky/lg_sky_02_lf -3 544 0 -0.007812 0.367188 0 0 0 +( 944 304 200 ) ( 424 304 200 ) ( 424 304 184 ) map/lab_games/sky/lg_sky_02_lf -3 544 0 -0.007812 0.367188 0 0 0 +( 424 304 200 ) ( 424 -168 200 ) ( 424 -168 184 ) map/lab_games/sky/lg_sky_02_lf 658 544 0 -0.460938 0.367188 0 0 0 +( 432 -168 200 ) ( 432 304 200 ) ( 432 -168 184 ) map/lab_games/sky/lg_sky_02_lf 658 544 0 -0.460938 0.367188 0 0 0 +} +} +// entity 1 +{ +"classname" "info_player_start" +"origin" "-32 -96 24" +"angle" "90" +"spawn_orientation_segment" "20" +} +// entity 2 +{ +"classname" "light" +"origin" "-32 96 80" +"light" "500" +} +// entity 3 +{ +"light" "500" +"origin" "-208 96 80" +"classname" "light" +} +// entity 4 +{ +"light" "500" +"origin" "144 96 80" +"classname" "light" +} +// entity 5 +{ +"light" "500" +"origin" "-32 -32 80" +"classname" "light" +} +// entity 6 +{ +"classname" "placeholder_1" +"origin" "-232 96 16" +} +// entity 7 +{ +"classname" "placeholder_2" +"origin" "168 96 16" +} +// entity 8 +{ +"classname" "_skybox" +"origin" "656 64 -128" +} +// entity 9 +{ +"model" "models/stadium.md3" +"origin" "656 64 -82" +"classname" "misc_model" +"_remap" "*;textures/map/lab_games/stadium_d" +"angle" "90" +} +// entity 10 +{ +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"modelscale" "1" +"model" "models/signal_line.md3" +"origin" "680 24 -80" +"classname" "misc_model" +"angle" "113" +} +// entity 11 +{ +"classname" "misc_model" +"origin" "728 48 -152" +"model" "models/signal_line.md3" +"modelscale" "1.1" +"_remap" "*;textures/map/lab_games/signal_pulse_blue" +"angle" "38" +} +// entity 12 +{ +"angle" "28" +"classname" "misc_model" +"origin" "688 56 -88" +"model" "models/signal_line.md3" +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"modelscale" "0.61" +} +// entity 13 +{ +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"modelscale" "0.6" +"model" "models/signal_line.md3" +"origin" "632 72 -152" +"classname" "misc_model" +"angle" "159" +} diff --git a/assets/maps/em_tmaze_a3.map b/assets/maps/em_tmaze_a3.map new file mode 100644 index 00000000..8d21f4e9 --- /dev/null +++ b/assets/maps/em_tmaze_a3.map @@ -0,0 +1,256 @@ +// entity 0 +{ +"classname" "worldspawn" +// brush 0 +{ +( -328 216 -24 ) ( -328 88 -24 ) ( -328 88 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( 56 136 -24 ) ( -72 136 -24 ) ( -72 136 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( 264 64 -24 ) ( 264 192 -24 ) ( 264 192 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( -64 56 -24 ) ( 64 56 -24 ) ( 64 56 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( -64 64 104 ) ( -64 192 104 ) ( 64 192 104 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( -64 192 96 ) ( -64 64 96 ) ( 64 192 96 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +} +// brush 1 +{ +( 64 64 -8 ) ( -64 64 -8 ) ( -64 -64 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -72 -136 8 ) ( 56 -136 8 ) ( 56 -136 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 0 -64 8 ) ( 0 64 8 ) ( 0 64 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 64 56 8 ) ( -64 56 8 ) ( -64 56 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -72 64 8 ) ( -72 -64 8 ) ( -72 -64 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -64 64 0 ) ( 64 64 0 ) ( -64 -64 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 2 +{ +( -64 -64 104 ) ( -64 64 104 ) ( 64 64 104 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( -64 -136 -24 ) ( 64 -136 -24 ) ( 64 -136 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( 0 -64 -24 ) ( 0 64 -24 ) ( 0 64 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( 64 56 -24 ) ( -64 56 -24 ) ( -64 56 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( -72 64 -24 ) ( -72 -64 -24 ) ( -72 -64 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +( -64 64 96 ) ( -64 -64 96 ) ( 64 64 96 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031200 0 0 0 +} +// brush 3 +{ +( 0 192 0 ) ( 128 192 0 ) ( 0 64 0 ) map/lab_games/lg_style_01_floor_orange -3 0 0 0.190000 0.190000 0 0 0 +( 128 192 -8 ) ( 0 192 -8 ) ( 0 64 -8 ) map/lab_games/lg_style_01_floor_orange -3 0 0 0.190000 0.190000 0 0 0 +( 0 56 8 ) ( 128 56 8 ) ( 128 56 0 ) map/lab_games/lg_style_01_floor_orange -3 0 0 0.190000 0.190000 0 0 0 +( 264 64 8 ) ( 264 192 8 ) ( 264 192 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 120 136 8 ) ( -8 136 8 ) ( -8 136 0 ) map/lab_games/lg_style_01_floor_orange -3 0 0 0.190000 0.190000 0 0 0 +( -328 216 8 ) ( -328 88 8 ) ( -328 88 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 4 +{ +( 55 64 0 ) ( -73 64 0 ) ( -73 -64 0 ) map/lab_games/lg_style_01_wall_blue 2048 341 0 -0.062500 0.187500 0 0 0 +( -73 -64 96 ) ( -73 64 96 ) ( 55 64 96 ) map/lab_games/lg_style_01_wall_blue 2048 341 0 -0.062500 0.187500 0 0 0 +( -73 -136 8 ) ( 55 -136 8 ) ( 55 -136 0 ) map/lab_games/lg_style_01_wall_blue 2048 0 0 -0.062500 0.093750 0 0 0 +( 56 64 8 ) ( -72 64 8 ) ( -72 64 0 ) map/lab_games/lg_style_01_wall_blue 2048 0 0 -0.062500 0.093750 0 0 0 +( -72 80 40 ) ( -72 -48 40 ) ( -72 -48 32 ) map/lab_games/lg_style_01_wall_blue 1024 0 0 -0.062500 0.093750 0 0 0 +( -64 -136 40 ) ( -64 -8 40 ) ( -64 -136 32 ) map/lab_games/lg_style_01_wall_blue 1024 0 0 -0.062500 0.093750 0 0 0 +} +// brush 5 +{ +( -328 152 8 ) ( -328 24 8 ) ( -328 24 0 ) map/lab_games/lg_style_01_wall_blue 7167 0 0 -0.001116 0.093750 0 0 0 +( 105 136 8 ) ( -23 136 8 ) ( -23 136 0 ) map/lab_games/lg_style_01_wall_blue 3072 0 0 -0.062500 0.093750 0 0 0 +( 264 8 8 ) ( 264 136 8 ) ( 264 136 0 ) map/lab_games/lg_style_01_wall_blue 7167 0 0 -0.001116 0.093750 0 0 0 +( -64 8 96 ) ( -64 136 96 ) ( 64 136 96 ) map/lab_games/lg_style_01_wall_blue 3072 0 0 -0.062500 0.007812 0 0 0 +( 64 136 0 ) ( -64 136 0 ) ( -64 8 0 ) map/lab_games/lg_style_01_wall_blue 3072 0 0 -0.062500 0.007812 0 0 0 +( -135 128 8 ) ( -7 128 8 ) ( -135 128 0 ) map/lab_games/lg_style_01_wall_blue 3072 0 0 -0.062500 0.093750 0 0 0 +} +// brush 6 +{ +( 7 64 0 ) ( -121 64 0 ) ( -121 -64 0 ) map/lab_games/lg_style_01_wall_blue 0 341 0 -0.059896 0.187500 0 0 0 +( -121 -64 96 ) ( -121 64 96 ) ( 7 64 96 ) map/lab_games/lg_style_01_wall_blue 0 341 0 -0.059896 0.187500 0 0 0 +( -121 -136 8 ) ( 7 -136 8 ) ( 7 -136 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.059896 0.093750 0 0 0 +( 8 -48 40 ) ( 8 80 40 ) ( 8 80 32 ) map/lab_games/lg_style_01_wall_blue 1024 0 0 -0.062500 0.093750 0 0 0 +( 8 64 8 ) ( -120 64 8 ) ( -120 64 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.059896 0.093750 0 0 0 +( 0 0 -24 ) ( 0 -128 -24 ) ( 0 0 -32 ) map/lab_games/lg_style_01_wall_blue 1024 0 0 -0.062500 0.093750 0 0 0 +} +// brush 7 +{ +( 64 -8 0 ) ( -64 -8 0 ) ( -64 -136 0 ) map/lab_games/lg_style_01_wall_blue 16 0 0 -0.062500 0.007812 0 0 0 +( -64 -136 96 ) ( -64 -8 96 ) ( 64 -8 96 ) map/lab_games/lg_style_01_wall_blue 16 0 0 -0.062500 0.007812 0 0 0 +( -128 -136 8 ) ( 0 -136 8 ) ( 0 -136 0 ) map/lab_games/lg_style_01_wall_blue 16 0 0 -0.062500 0.093750 0 0 0 +( 0 -136 40 ) ( 0 -8 40 ) ( 0 -8 32 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.093750 0 0 0 +( -64 -8 40 ) ( -64 -136 40 ) ( -64 -136 32 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.093750 0 0 0 +( 65 -128 8 ) ( -63 -128 8 ) ( 65 -128 0 ) map/lab_games/lg_style_01_wall_blue 16 0 0 -0.062500 0.093750 0 0 0 +} +// brush 8 +{ +( -328 208 8 ) ( -328 80 8 ) ( -328 80 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +( -71 128 8 ) ( -199 128 8 ) ( -199 128 0 ) map/lab_games/lg_style_01_wall_blue 568 0 0 -0.008789 0.093750 0 0 0 +( -199 64 8 ) ( -71 64 8 ) ( -71 64 0 ) map/lab_games/lg_style_01_wall_blue 568 0 0 -0.008789 0.093750 0 0 0 +( -200 64 96 ) ( -200 192 96 ) ( -72 192 96 ) map/lab_games/lg_style_01_wall_blue 568 0 0 -0.008789 0.062500 0 0 0 +( -72 192 0 ) ( -200 192 0 ) ( -200 64 0 ) map/lab_games/lg_style_01_wall_blue 568 0 0 -0.008789 0.062500 0 0 0 +( -320 16 -24 ) ( -320 144 -24 ) ( -320 16 -32 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +} +// brush 9 +{ +( -328 56 136 ) ( -328 48 136 ) ( -328 48 -8 ) map/lab_games/lg_style_01_wall_blue 983 0 0 -0.065104 0.093750 0 0 0 +( -135 64 136 ) ( -263 64 136 ) ( -263 64 -8 ) map/lab_games/lg_style_01_wall_blue 2088 0 0 -0.065104 0.093750 0 0 0 +( -72 -56 168 ) ( -72 -48 168 ) ( -72 -48 24 ) map/lab_games/lg_style_01_wall_blue 983 0 0 -0.065104 0.093750 0 0 0 +( -136 56 136 ) ( -8 56 136 ) ( -8 56 -8 ) map/lab_games/lg_style_01_wall_blue 2088 0 0 -0.065104 0.093750 0 0 0 +( -192 56 96 ) ( -192 64 96 ) ( -64 64 96 ) map/lab_games/lg_style_01_wall_blue 2088 327 0 -0.065104 0.195312 0 0 0 +( -64 64 0 ) ( -192 64 0 ) ( -192 56 0 ) map/lab_games/lg_style_01_wall_blue 2088 327 0 -0.065104 0.195312 0 0 0 +} +// brush 10 +{ +( 153 128 8 ) ( 25 128 8 ) ( 25 128 0 ) map/lab_games/lg_style_01_wall_blue 731 0 0 -0.006836 0.093750 0 0 0 +( 264 64 8 ) ( 264 192 8 ) ( 264 192 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +( 25 64 8 ) ( 153 64 8 ) ( 153 64 0 ) map/lab_games/lg_style_01_wall_blue 731 0 0 -0.006836 0.093750 0 0 0 +( 24 64 96 ) ( 24 192 96 ) ( 152 192 96 ) map/lab_games/lg_style_01_wall_blue 731 0 0 -0.006836 0.062500 0 0 0 +( 152 192 0 ) ( 24 192 0 ) ( 24 64 0 ) map/lab_games/lg_style_01_wall_blue 731 0 0 -0.006836 0.062500 0 0 0 +( 256 128 8 ) ( 256 0 8 ) ( 256 128 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +} +// brush 11 +{ +( 0 200 40 ) ( 0 72 40 ) ( 0 72 32 ) map/lab_games/lg_style_01_wall_blue 1068 0 0 -0.059896 0.093750 0 0 0 +( 264 56 8 ) ( 264 184 8 ) ( 264 184 0 ) map/lab_games/lg_style_01_wall_blue 1068 0 0 -0.059896 0.093750 0 0 0 +( 256 56 8 ) ( 384 56 8 ) ( 384 56 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +( -136 56 96 ) ( -136 184 96 ) ( -8 184 96 ) map/lab_games/lg_style_01_wall_blue 0 356 0 -0.062500 0.179688 0 0 0 +( -8 184 0 ) ( -136 184 0 ) ( -136 56 0 ) map/lab_games/lg_style_01_wall_blue 0 356 0 -0.062500 0.179688 0 0 0 +( -7 64 8 ) ( -135 64 8 ) ( -7 64 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +} +// brush 12 +{ +( 968 264 -176 ) ( 448 264 -176 ) ( 448 -208 -176 ) map/lab_games/sky/lg_sky_02_dn -141 571 0 -0.507812 0.460938 0 0 0 +( 448 -208 200 ) ( 968 -208 200 ) ( 968 -208 184 ) map/lab_games/sky/lg_sky_02_dn -141 0 0 -0.507812 0.007812 0 0 0 +( 968 -208 200 ) ( 968 264 200 ) ( 968 264 184 ) map/lab_games/sky/lg_sky_02_dn 571 0 0 -0.460938 0.007812 0 0 0 +( 968 264 200 ) ( 448 264 200 ) ( 448 264 184 ) map/lab_games/sky/lg_sky_02_dn -141 0 0 -0.507812 0.007812 0 0 0 +( 448 264 200 ) ( 448 -208 200 ) ( 448 -208 184 ) map/lab_games/sky/lg_sky_02_dn 571 0 0 -0.460938 0.007812 0 0 0 +( 448 264 -168 ) ( 968 264 -168 ) ( 448 -208 -168 ) map/lab_games/sky/lg_sky_02_dn -141 571 0 -0.507812 0.460938 0 0 0 +} +// brush 13 +{ +( 448 -208 200 ) ( 448 264 200 ) ( 968 264 200 ) map/lab_games/sky/lg_sky_02_up -141 571 0 -0.507812 0.460938 0 0 0 +( 448 -208 200 ) ( 968 -208 200 ) ( 968 -208 184 ) map/lab_games/sky/lg_sky_02_up -141 0 0 -0.507812 0.007812 0 0 0 +( 968 -208 200 ) ( 968 264 200 ) ( 968 264 184 ) map/lab_games/sky/lg_sky_02_up 571 0 0 -0.460938 0.007812 0 0 0 +( 968 264 200 ) ( 448 264 200 ) ( 448 264 184 ) map/lab_games/sky/lg_sky_02_up -141 0 0 -0.507812 0.007812 0 0 0 +( 448 264 200 ) ( 448 -208 200 ) ( 448 -208 184 ) map/lab_games/sky/lg_sky_02_up 571 0 0 -0.460938 0.007812 0 0 0 +( 448 264 192 ) ( 448 -208 192 ) ( 968 264 192 ) map/lab_games/sky/lg_sky_02_up -141 571 0 -0.507812 0.460938 0 0 0 +} +// brush 14 +{ +( 968 264 -176 ) ( 448 264 -176 ) ( 448 -208 -176 ) map/lab_games/sky/lg_sky_02_bk -141 0 0 -0.507812 0.007812 0 0 0 +( 448 -208 200 ) ( 448 264 200 ) ( 968 264 200 ) map/lab_games/sky/lg_sky_02_bk -141 0 0 -0.507812 0.007812 0 0 0 +( 448 -208 200 ) ( 968 -208 200 ) ( 968 -208 184 ) map/lab_games/sky/lg_sky_02_bk -141 544 0 -0.507812 0.367188 0 0 0 +( 968 -208 200 ) ( 968 264 200 ) ( 968 264 184 ) map/lab_games/sky/lg_sky_02_bk 0 544 0 -0.007812 0.367188 0 0 0 +( 448 264 200 ) ( 448 -208 200 ) ( 448 -208 184 ) map/lab_games/sky/lg_sky_02_bk 0 544 0 -0.007812 0.367188 0 0 0 +( 968 -200 200 ) ( 448 -200 200 ) ( 968 -200 184 ) map/lab_games/sky/lg_sky_02_bk -141 544 0 -0.507812 0.367188 0 0 0 +} +// brush 15 +{ +( 968 264 -176 ) ( 448 264 -176 ) ( 448 -208 -176 ) map/lab_games/sky/lg_sky_02_rt -3 571 0 -0.007812 0.460938 0 0 0 +( 448 -208 200 ) ( 448 264 200 ) ( 968 264 200 ) map/lab_games/sky/lg_sky_02_rt -3 571 0 -0.007812 0.460938 0 0 0 +( 448 -208 200 ) ( 968 -208 200 ) ( 968 -208 184 ) map/lab_games/sky/lg_sky_02_rt -3 544 0 -0.007812 0.367188 0 0 0 +( 968 -208 200 ) ( 968 264 200 ) ( 968 264 184 ) map/lab_games/sky/lg_sky_02_rt 571 544 0 -0.460938 0.367188 0 0 0 +( 968 264 200 ) ( 448 264 200 ) ( 448 264 184 ) map/lab_games/sky/lg_sky_02_rt -3 544 0 -0.007812 0.367188 0 0 0 +( 960 264 200 ) ( 960 -208 200 ) ( 960 264 184 ) map/lab_games/sky/lg_sky_02_rt 571 544 0 -0.460938 0.367188 0 0 0 +} +// brush 16 +{ +( 968 264 -176 ) ( 448 264 -176 ) ( 448 -208 -176 ) map/lab_games/sky/lg_sky_02_ft -141 0 0 -0.507812 0.007812 0 0 0 +( 448 -208 200 ) ( 448 264 200 ) ( 968 264 200 ) map/lab_games/sky/lg_sky_02_ft -141 0 0 -0.507812 0.007812 0 0 0 +( 968 -208 200 ) ( 968 264 200 ) ( 968 264 184 ) map/lab_games/sky/lg_sky_02_ft 0 544 0 -0.007812 0.367188 0 0 0 +( 968 264 200 ) ( 448 264 200 ) ( 448 264 184 ) map/lab_games/sky/lg_sky_02_ft -141 544 0 -0.507812 0.367188 0 0 0 +( 448 264 200 ) ( 448 -208 200 ) ( 448 -208 184 ) map/lab_games/sky/lg_sky_02_ft 0 544 0 -0.007812 0.367188 0 0 0 +( 448 256 200 ) ( 968 256 200 ) ( 448 256 184 ) map/lab_games/sky/lg_sky_02_ft -141 544 0 -0.507812 0.367188 0 0 0 +} +// brush 17 +{ +( 968 264 -176 ) ( 448 264 -176 ) ( 448 -208 -176 ) map/lab_games/sky/lg_sky_02_lf -3 571 0 -0.007812 0.460938 0 0 0 +( 448 -208 200 ) ( 448 264 200 ) ( 968 264 200 ) map/lab_games/sky/lg_sky_02_lf -3 571 0 -0.007812 0.460938 0 0 0 +( 448 -208 200 ) ( 968 -208 200 ) ( 968 -208 184 ) map/lab_games/sky/lg_sky_02_lf -3 544 0 -0.007812 0.367188 0 0 0 +( 968 264 200 ) ( 448 264 200 ) ( 448 264 184 ) map/lab_games/sky/lg_sky_02_lf -3 544 0 -0.007812 0.367188 0 0 0 +( 448 264 200 ) ( 448 -208 200 ) ( 448 -208 184 ) map/lab_games/sky/lg_sky_02_lf 571 544 0 -0.460938 0.367188 0 0 0 +( 456 -208 200 ) ( 456 264 200 ) ( 456 -208 184 ) map/lab_games/sky/lg_sky_02_lf 571 544 0 -0.460938 0.367188 0 0 0 +} +} +// entity 1 +{ +"angle" "90" +"origin" "-32 -96 24" +"classname" "info_player_start" +"spawn_orientation_segment" "20" +} +// entity 2 +{ +"light" "500" +"origin" "-32 96 80" +"classname" "light" +} +// entity 3 +{ +"classname" "light" +"origin" "-208 96 80" +"light" "500" +} +// entity 4 +{ +"classname" "light" +"origin" "144 96 80" +"light" "500" +} +// entity 5 +{ +"classname" "light" +"origin" "-32 -32 80" +"light" "500" +} +// entity 6 +{ +"classname" "placeholder_1" +"origin" "-296 96 16" +} +// entity 7 +{ +"classname" "placeholder_2" +"origin" "232 96 16" +} +// entity 8 +{ +"classname" "_skybox" +"origin" "680 24 -128" +} +// entity 9 +{ +"model" "models/stadium.md3" +"origin" "680 24 -82" +"classname" "misc_model" +"_remap" "*;textures/map/lab_games/stadium_d" +"angle" "90" +} +// entity 10 +{ +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"modelscale" "1" +"model" "models/signal_line.md3" +"origin" "704 -16 -80" +"classname" "misc_model" +"angle" "113" +} +// entity 11 +{ +"classname" "misc_model" +"origin" "752 8 -152" +"model" "models/signal_line.md3" +"modelscale" "1.1" +"_remap" "*;textures/map/lab_games/signal_pulse_blue" +"angle" "38" +} +// entity 12 +{ +"angle" "28" +"classname" "misc_model" +"origin" "712 16 -88" +"model" "models/signal_line.md3" +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"modelscale" "0.61" +} +// entity 13 +{ +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"modelscale" "0.6" +"model" "models/signal_line.md3" +"origin" "656 32 -152" +"classname" "misc_model" +"angle" "159" +} diff --git a/assets/maps/em_tmaze_a4.map b/assets/maps/em_tmaze_a4.map new file mode 100644 index 00000000..34e12348 --- /dev/null +++ b/assets/maps/em_tmaze_a4.map @@ -0,0 +1,268 @@ +// entity 0 +{ +"classname" "worldspawn" +// brush 0 +{ +( -392 216 -24 ) ( -392 88 -24 ) ( -392 88 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031250 0 0 0 +( 56 136 -24 ) ( -72 136 -24 ) ( -72 136 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031250 0 0 0 +( 328 64 -24 ) ( 328 192 -24 ) ( 328 192 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031250 0 0 0 +( -64 56 -24 ) ( 64 56 -24 ) ( 64 56 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031250 0 0 0 +( -64 64 104 ) ( -64 192 104 ) ( 64 192 104 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031250 0 0 0 +( -64 192 96 ) ( -64 64 96 ) ( 64 192 96 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031250 0 0 0 +} +// brush 1 +{ +( 64 64 -8 ) ( -64 64 -8 ) ( -64 -64 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -72 -136 8 ) ( 56 -136 8 ) ( 56 -136 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 0 -64 8 ) ( 0 64 8 ) ( 0 64 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 64 56 8 ) ( -64 56 8 ) ( -64 56 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -72 64 8 ) ( -72 -64 8 ) ( -72 -64 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -64 64 0 ) ( 64 64 0 ) ( -64 -64 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 2 +{ +( -64 -64 104 ) ( -64 64 104 ) ( 64 64 104 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031250 0 0 0 +( -64 -136 -24 ) ( 64 -136 -24 ) ( 64 -136 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031250 0 0 0 +( 0 -64 -24 ) ( 0 64 -24 ) ( 0 64 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031250 0 0 0 +( 64 56 -24 ) ( -64 56 -24 ) ( -64 56 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031250 0 0 0 +( -72 64 -24 ) ( -72 -64 -24 ) ( -72 -64 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031250 0 0 0 +( -64 64 96 ) ( -64 -64 96 ) ( 64 64 96 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031250 0 0 0 +} +// brush 3 +{ +( 0 192 0 ) ( 128 192 0 ) ( 0 64 0 ) map/lab_games/lg_style_01_floor_orange -3 0 0 0.190000 0.190000 0 0 0 +( 128 192 -8 ) ( 0 192 -8 ) ( 0 64 -8 ) map/lab_games/lg_style_01_floor_orange -3 0 0 0.190000 0.190000 0 0 0 +( 0 56 8 ) ( 128 56 8 ) ( 128 56 0 ) map/lab_games/lg_style_01_floor_orange -3 0 0 0.190000 0.190000 0 0 0 +( 328 64 8 ) ( 328 192 8 ) ( 328 192 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 120 136 8 ) ( -8 136 8 ) ( -8 136 0 ) map/lab_games/lg_style_01_floor_orange -3 0 0 0.190000 0.190000 0 0 0 +( -392 216 8 ) ( -392 88 8 ) ( -392 88 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 4 +{ +( 55 64 0 ) ( -73 64 0 ) ( -73 -64 0 ) map/lab_games/lg_style_01_wall_blue 0 327 0 -0.003906 0.195312 0 0 0 +( -73 -64 96 ) ( -73 64 96 ) ( 55 64 96 ) map/lab_games/lg_style_01_wall_blue 0 327 0 -0.003906 0.195312 0 0 0 +( -73 -136 8 ) ( 55 -136 8 ) ( 55 -136 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.003906 0.093750 0 0 0 +( 56 64 8 ) ( -72 64 8 ) ( -72 64 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.003906 0.093750 0 0 0 +( -72 80 40 ) ( -72 -48 40 ) ( -72 -48 32 ) map/lab_games/lg_style_01_wall_blue 655 0 0 -0.097656 0.093750 0 0 0 +( -64 -136 40 ) ( -64 -8 40 ) ( -64 -136 32 ) map/lab_games/lg_style_01_wall_blue 655 0 0 -0.097656 0.093750 0 0 0 +} +// brush 5 +{ +( -392 152 8 ) ( -392 24 8 ) ( -392 24 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.001302 0.093750 0 0 0 +( 105 136 8 ) ( -23 136 8 ) ( -23 136 0 ) map/lab_games/lg_style_01_wall_blue 2798 0 0 -0.117188 0.093750 0 0 0 +( 328 8 8 ) ( 328 136 8 ) ( 328 136 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.001302 0.093750 0 0 0 +( -64 8 96 ) ( -64 136 96 ) ( 64 136 96 ) map/lab_games/lg_style_01_wall_blue 2798 0 0 -0.117188 0.007812 0 0 0 +( 64 136 0 ) ( -64 136 0 ) ( -64 8 0 ) map/lab_games/lg_style_01_wall_blue 2798 0 0 -0.117188 0.007812 0 0 0 +( -199 128 8 ) ( -71 128 8 ) ( -199 128 0 ) map/lab_games/lg_style_01_wall_blue 2798 0 0 -0.117188 0.093750 0 0 0 +} +// brush 6 +{ +( 7 64 0 ) ( -121 64 0 ) ( -121 -64 0 ) map/lab_games/lg_style_01_wall_blue 0 327 0 -0.003906 0.195312 0 0 0 +( -121 -64 96 ) ( -121 64 96 ) ( 7 64 96 ) map/lab_games/lg_style_01_wall_blue 0 327 0 -0.003906 0.195312 0 0 0 +( -121 -136 8 ) ( 7 -136 8 ) ( 7 -136 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.003906 0.093750 0 0 0 +( 8 -48 40 ) ( 8 80 40 ) ( 8 80 32 ) map/lab_games/lg_style_01_wall_blue 655 0 0 -0.097656 0.093750 0 0 0 +( 8 64 8 ) ( -120 64 8 ) ( -120 64 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.003906 0.093750 0 0 0 +( 0 0 -24 ) ( 0 -128 -24 ) ( 0 0 -32 ) map/lab_games/lg_style_01_wall_blue 655 0 0 -0.097656 0.093750 0 0 0 +} +// brush 7 +{ +( 64 -8 0 ) ( -64 -8 0 ) ( -64 -136 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.007812 0 0 0 +( -64 -136 96 ) ( -64 -8 96 ) ( 64 -8 96 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.007812 0 0 0 +( -128 -136 8 ) ( 0 -136 8 ) ( 0 -136 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +( 0 -136 40 ) ( 0 -8 40 ) ( 0 -8 32 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.093750 0 0 0 +( -64 -8 40 ) ( -64 -136 40 ) ( -64 -136 32 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.093750 0 0 0 +( 65 -128 8 ) ( -63 -128 8 ) ( 65 -128 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +} +// brush 8 +{ +( -392 208 8 ) ( -392 80 8 ) ( -392 80 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +( -135 128 8 ) ( -263 128 8 ) ( -263 128 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.093750 0 0 0 +( -263 64 8 ) ( -135 64 8 ) ( -135 64 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.093750 0 0 0 +( -264 64 96 ) ( -264 192 96 ) ( -136 192 96 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.062500 0 0 0 +( -136 192 0 ) ( -264 192 0 ) ( -264 64 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.062500 0 0 0 +( -384 16 -24 ) ( -384 144 -24 ) ( -384 16 -32 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +} +// brush 9 +{ +( -392 56 136 ) ( -392 48 136 ) ( -392 48 -8 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.002604 0.093750 0 0 0 +( -199 64 136 ) ( -327 64 136 ) ( -327 64 -8 ) map/lab_games/lg_style_01_wall_blue 2380 0 0 -0.104167 0.093750 0 0 0 +( -72 -56 168 ) ( -72 -48 168 ) ( -72 -48 24 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.002604 0.093750 0 0 0 +( -136 56 136 ) ( -8 56 136 ) ( -8 56 -8 ) map/lab_games/lg_style_01_wall_blue 2380 0 0 -0.104167 0.093750 0 0 0 +( -192 56 96 ) ( -192 64 96 ) ( -64 64 96 ) map/lab_games/lg_style_01_wall_blue 2380 0 0 -0.104167 0.007812 0 0 0 +( -64 64 0 ) ( -192 64 0 ) ( -192 56 0 ) map/lab_games/lg_style_01_wall_blue 2380 0 0 -0.104167 0.007812 0 0 0 +} +// brush 10 +{ +( 217 128 8 ) ( 89 128 8 ) ( 89 128 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.093750 0 0 0 +( 328 64 8 ) ( 328 192 8 ) ( 328 192 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +( 89 64 8 ) ( 217 64 8 ) ( 217 64 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.093750 0 0 0 +( 88 64 96 ) ( 88 192 96 ) ( 216 192 96 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.062500 0 0 0 +( 216 192 0 ) ( 88 192 0 ) ( 88 64 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.062500 0 0 0 +( 320 128 8 ) ( 320 0 8 ) ( 320 128 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +} +// brush 11 +{ +( 0 200 40 ) ( 0 72 40 ) ( 0 72 32 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.002604 0.093750 0 0 0 +( 328 56 8 ) ( 328 184 8 ) ( 328 184 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.002604 0.093750 0 0 0 +( 256 56 8 ) ( 384 56 8 ) ( 384 56 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.106771 0.093750 0 0 0 +( -136 56 96 ) ( -136 184 96 ) ( -8 184 96 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.106771 0.007812 0 0 0 +( -8 184 0 ) ( -136 184 0 ) ( -136 56 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.106771 0.007812 0 0 0 +( 57 64 8 ) ( -71 64 8 ) ( 57 64 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.106771 0.093750 0 0 0 +} +// brush 12 +{ +( 1048 240 -176 ) ( 528 240 -176 ) ( 528 -232 -176 ) map/lab_games/sky/lg_sky_02_dn 15 519 0 -0.507812 0.460938 0 0 0 +( 528 -232 200 ) ( 1048 -232 200 ) ( 1048 -232 184 ) map/lab_games/sky/lg_sky_02_dn 15 0 0 -0.507812 0.007812 0 0 0 +( 1048 -232 200 ) ( 1048 240 200 ) ( 1048 240 184 ) map/lab_games/sky/lg_sky_02_dn 519 0 0 -0.460938 0.007812 0 0 0 +( 1048 240 200 ) ( 528 240 200 ) ( 528 240 184 ) map/lab_games/sky/lg_sky_02_dn 15 0 0 -0.507812 0.007812 0 0 0 +( 528 240 200 ) ( 528 -232 200 ) ( 528 -232 184 ) map/lab_games/sky/lg_sky_02_dn 519 0 0 -0.460938 0.007812 0 0 0 +( 528 240 -168 ) ( 1048 240 -168 ) ( 528 -232 -168 ) map/lab_games/sky/lg_sky_02_dn 15 519 0 -0.507812 0.460938 0 0 0 +} +// brush 13 +{ +( 528 -232 200 ) ( 528 240 200 ) ( 1048 240 200 ) map/lab_games/sky/lg_sky_02_up 15 519 0 -0.507812 0.460938 0 0 0 +( 528 -232 200 ) ( 1048 -232 200 ) ( 1048 -232 184 ) map/lab_games/sky/lg_sky_02_up 15 0 0 -0.507812 0.007812 0 0 0 +( 1048 -232 200 ) ( 1048 240 200 ) ( 1048 240 184 ) map/lab_games/sky/lg_sky_02_up 519 0 0 -0.460938 0.007812 0 0 0 +( 1048 240 200 ) ( 528 240 200 ) ( 528 240 184 ) map/lab_games/sky/lg_sky_02_up 15 0 0 -0.507812 0.007812 0 0 0 +( 528 240 200 ) ( 528 -232 200 ) ( 528 -232 184 ) map/lab_games/sky/lg_sky_02_up 519 0 0 -0.460938 0.007812 0 0 0 +( 528 240 192 ) ( 528 -232 192 ) ( 1048 240 192 ) map/lab_games/sky/lg_sky_02_up 15 519 0 -0.507812 0.460938 0 0 0 +} +// brush 14 +{ +( 1048 240 -176 ) ( 528 240 -176 ) ( 528 -232 -176 ) map/lab_games/sky/lg_sky_02_bk 15 -1023 0 -0.507812 0.007812 0 0 0 +( 528 -232 200 ) ( 528 240 200 ) ( 1048 240 200 ) map/lab_games/sky/lg_sky_02_bk 15 -1023 0 -0.507812 0.007812 0 0 0 +( 528 -232 200 ) ( 1048 -232 200 ) ( 1048 -232 184 ) map/lab_games/sky/lg_sky_02_bk 15 544 0 -0.507812 0.367188 0 0 0 +( 1048 -232 200 ) ( 1048 240 200 ) ( 1048 240 184 ) map/lab_games/sky/lg_sky_02_bk -1023 544 0 -0.007812 0.367188 0 0 0 +( 528 240 200 ) ( 528 -232 200 ) ( 528 -232 184 ) map/lab_games/sky/lg_sky_02_bk -1023 544 0 -0.007812 0.367188 0 0 0 +( 1048 -224 200 ) ( 528 -224 200 ) ( 1048 -224 184 ) map/lab_games/sky/lg_sky_02_bk 15 544 0 -0.507812 0.367188 0 0 0 +} +// brush 15 +{ +( 1048 240 -176 ) ( 528 240 -176 ) ( 528 -232 -176 ) map/lab_games/sky/lg_sky_02_rt -3 519 0 -0.007812 0.460938 0 0 0 +( 528 -232 200 ) ( 528 240 200 ) ( 1048 240 200 ) map/lab_games/sky/lg_sky_02_rt -3 519 0 -0.007812 0.460938 0 0 0 +( 528 -232 200 ) ( 1048 -232 200 ) ( 1048 -232 184 ) map/lab_games/sky/lg_sky_02_rt -3 544 0 -0.007812 0.367188 0 0 0 +( 1048 -232 200 ) ( 1048 240 200 ) ( 1048 240 184 ) map/lab_games/sky/lg_sky_02_rt 519 544 0 -0.460938 0.367188 0 0 0 +( 1048 240 200 ) ( 528 240 200 ) ( 528 240 184 ) map/lab_games/sky/lg_sky_02_rt -3 544 0 -0.007812 0.367188 0 0 0 +( 1040 240 200 ) ( 1040 -232 200 ) ( 1040 240 184 ) map/lab_games/sky/lg_sky_02_rt 519 544 0 -0.460938 0.367188 0 0 0 +} +// brush 16 +{ +( 1048 240 -176 ) ( 528 240 -176 ) ( 528 -232 -176 ) map/lab_games/sky/lg_sky_02_ft 15 -1023 0 -0.507812 0.007812 0 0 0 +( 528 -232 200 ) ( 528 240 200 ) ( 1048 240 200 ) map/lab_games/sky/lg_sky_02_ft 15 -1023 0 -0.507812 0.007812 0 0 0 +( 1048 -232 200 ) ( 1048 240 200 ) ( 1048 240 184 ) map/lab_games/sky/lg_sky_02_ft -1023 544 0 -0.007812 0.367188 0 0 0 +( 1048 240 200 ) ( 528 240 200 ) ( 528 240 184 ) map/lab_games/sky/lg_sky_02_ft 15 544 0 -0.507812 0.367188 0 0 0 +( 528 240 200 ) ( 528 -232 200 ) ( 528 -232 184 ) map/lab_games/sky/lg_sky_02_ft -1023 544 0 -0.007812 0.367188 0 0 0 +( 528 232 200 ) ( 1048 232 200 ) ( 528 232 184 ) map/lab_games/sky/lg_sky_02_ft 15 544 0 -0.507812 0.367188 0 0 0 +} +// brush 17 +{ +( 1048 240 -176 ) ( 528 240 -176 ) ( 528 -232 -176 ) map/lab_games/sky/lg_sky_02_lf -3 519 0 -0.007812 0.460938 0 0 0 +( 528 -232 200 ) ( 528 240 200 ) ( 1048 240 200 ) map/lab_games/sky/lg_sky_02_lf -3 519 0 -0.007812 0.460938 0 0 0 +( 528 -232 200 ) ( 1048 -232 200 ) ( 1048 -232 184 ) map/lab_games/sky/lg_sky_02_lf -3 544 0 -0.007812 0.367188 0 0 0 +( 1048 240 200 ) ( 528 240 200 ) ( 528 240 184 ) map/lab_games/sky/lg_sky_02_lf -3 544 0 -0.007812 0.367188 0 0 0 +( 528 240 200 ) ( 528 -232 200 ) ( 528 -232 184 ) map/lab_games/sky/lg_sky_02_lf 519 544 0 -0.460938 0.367188 0 0 0 +( 536 -232 200 ) ( 536 240 200 ) ( 536 -232 184 ) map/lab_games/sky/lg_sky_02_lf 519 544 0 -0.460938 0.367188 0 0 0 +} +} +// entity 1 +{ +"angle" "90" +"origin" "-32 -96 24" +"classname" "info_player_start" +"spawn_orientation_segment" "20" +} +// entity 2 +{ +"light" "500" +"origin" "-32 96 80" +"classname" "light" +} +// entity 3 +{ +"classname" "light" +"origin" "-192 96 80" +"light" "500" +} +// entity 4 +{ +"classname" "light" +"origin" "128 96 80" +"light" "500" +} +// entity 5 +{ +"classname" "light" +"origin" "-32 -32 80" +"light" "500" +} +// entity 6 +{ +"light" "500" +"origin" "288 96 80" +"classname" "light" +} +// entity 7 +{ +"light" "500" +"origin" "-352 96 80" +"classname" "light" +} +// entity 8 +{ +"classname" "placeholder_1" +"origin" "-360 96 16" +} +// entity 9 +{ +"classname" "placeholder_2" +"origin" "296 96 16" +} +// entity 10 +{ +"classname" "_skybox" +"origin" "760 0 -128" +} +// entity 11 +{ +"model" "models/stadium.md3" +"origin" "760 0 -82" +"classname" "misc_model" +"_remap" "*;textures/map/lab_games/stadium_d" +"angle" "90" +} +// entity 12 +{ +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"modelscale" "1" +"model" "models/signal_line.md3" +"origin" "784 -40 -80" +"classname" "misc_model" +"angle" "113" +} +// entity 13 +{ +"classname" "misc_model" +"origin" "832 -16 -152" +"model" "models/signal_line.md3" +"modelscale" "1.1" +"_remap" "*;textures/map/lab_games/signal_pulse_blue" +"angle" "38" +} +// entity 14 +{ +"angle" "28" +"classname" "misc_model" +"origin" "792 -8 -88" +"model" "models/signal_line.md3" +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"modelscale" "0.61" +} +// entity 15 +{ +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"modelscale" "0.6" +"model" "models/signal_line.md3" +"origin" "736 8 -152" +"classname" "misc_model" +"angle" "159" +} diff --git a/assets/maps/em_tmaze_a5.map b/assets/maps/em_tmaze_a5.map new file mode 100644 index 00000000..a341e7d4 --- /dev/null +++ b/assets/maps/em_tmaze_a5.map @@ -0,0 +1,268 @@ +// entity 0 +{ +"classname" "worldspawn" +// brush 0 +{ +( -456 216 -24 ) ( -456 88 -24 ) ( -456 88 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031250 0 0 0 +( 56 136 -24 ) ( -72 136 -24 ) ( -72 136 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031250 0 0 0 +( 392 56 -24 ) ( 392 184 -24 ) ( 392 184 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031250 0 0 0 +( -64 56 -24 ) ( 64 56 -24 ) ( 64 56 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031250 0 0 0 +( -64 64 104 ) ( -64 192 104 ) ( 64 192 104 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031250 0 0 0 +( -64 192 96 ) ( -64 64 96 ) ( 64 192 96 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031250 0 0 0 +} +// brush 1 +{ +( 64 64 -8 ) ( -64 64 -8 ) ( -64 -64 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -72 -136 8 ) ( 56 -136 8 ) ( 56 -136 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 0 -64 8 ) ( 0 64 8 ) ( 0 64 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 64 56 8 ) ( -64 56 8 ) ( -64 56 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -72 64 8 ) ( -72 -64 8 ) ( -72 -64 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -64 64 0 ) ( 64 64 0 ) ( -64 -64 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 2 +{ +( -64 -64 104 ) ( -64 64 104 ) ( 64 64 104 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031250 0 0 0 +( -64 -136 -24 ) ( 64 -136 -24 ) ( 64 -136 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031250 0 0 0 +( 0 -64 -24 ) ( 0 64 -24 ) ( 0 64 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031250 0 0 0 +( 64 56 -24 ) ( -64 56 -24 ) ( -64 56 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031250 0 0 0 +( -72 64 -24 ) ( -72 -64 -24 ) ( -72 -64 -32 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031250 0 0 0 +( -64 64 96 ) ( -64 -64 96 ) ( 64 64 96 ) map/lab_games/sky/lg_sky_01 0 0 0 0.031250 0.031250 0 0 0 +} +// brush 3 +{ +( 0 192 0 ) ( 128 192 0 ) ( 0 64 0 ) map/lab_games/lg_style_01_floor_orange -3 0 0 0.190000 0.190000 0 0 0 +( 128 192 -8 ) ( 0 192 -8 ) ( 0 64 -8 ) map/lab_games/lg_style_01_floor_orange -3 0 0 0.190000 0.190000 0 0 0 +( 0 56 8 ) ( 128 56 8 ) ( 128 56 0 ) map/lab_games/lg_style_01_floor_orange -3 0 0 0.190000 0.190000 0 0 0 +( 392 56 8 ) ( 392 184 8 ) ( 392 184 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 120 136 8 ) ( -8 136 8 ) ( -8 136 0 ) map/lab_games/lg_style_01_floor_orange -3 0 0 0.190000 0.190000 0 0 0 +( -456 216 8 ) ( -456 88 8 ) ( -456 88 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 4 +{ +( 55 64 0 ) ( -73 64 0 ) ( -73 -64 0 ) map/lab_games/lg_style_01_wall_blue 2048 341 0 -0.062500 0.187500 0 0 0 +( -73 -64 96 ) ( -73 64 96 ) ( 55 64 96 ) map/lab_games/lg_style_01_wall_blue 2048 341 0 -0.062500 0.187500 0 0 0 +( -73 -136 8 ) ( 55 -136 8 ) ( 55 -136 0 ) map/lab_games/lg_style_01_wall_blue 2048 0 0 -0.062500 0.093750 0 0 0 +( 56 64 8 ) ( -72 64 8 ) ( -72 64 0 ) map/lab_games/lg_style_01_wall_blue 2048 0 0 -0.062500 0.093750 0 0 0 +( -72 80 40 ) ( -72 -48 40 ) ( -72 -48 32 ) map/lab_games/lg_style_01_wall_blue 1024 0 0 -0.062500 0.093750 0 0 0 +( -64 -136 40 ) ( -64 -8 40 ) ( -64 -136 32 ) map/lab_games/lg_style_01_wall_blue 1024 0 0 -0.062500 0.093750 0 0 0 +} +// brush 5 +{ +( -456 152 8 ) ( -456 24 8 ) ( -456 24 0 ) map/lab_games/lg_style_01_wall_blue 7167 0 0 -0.001116 0.093750 0 0 0 +( 105 136 8 ) ( -23 136 8 ) ( -23 136 0 ) map/lab_games/lg_style_01_wall_blue 3072 0 0 -0.062500 0.093750 0 0 0 +( 392 8 8 ) ( 392 136 8 ) ( 392 136 0 ) map/lab_games/lg_style_01_wall_blue 7167 0 0 -0.001116 0.093750 0 0 0 +( -64 8 96 ) ( -64 136 96 ) ( 64 136 96 ) map/lab_games/lg_style_01_wall_blue 3072 0 0 -0.062500 0.007812 0 0 0 +( 64 136 0 ) ( -64 136 0 ) ( -64 8 0 ) map/lab_games/lg_style_01_wall_blue 3072 0 0 -0.062500 0.007812 0 0 0 +( -199 128 8 ) ( -71 128 8 ) ( -199 128 0 ) map/lab_games/lg_style_01_wall_blue 3072 0 0 -0.062500 0.093750 0 0 0 +} +// brush 6 +{ +( 7 64 0 ) ( -121 64 0 ) ( -121 -64 0 ) map/lab_games/lg_style_01_wall_blue 0 341 0 -0.059896 0.187500 0 0 0 +( -121 -64 96 ) ( -121 64 96 ) ( 7 64 96 ) map/lab_games/lg_style_01_wall_blue 0 341 0 -0.059896 0.187500 0 0 0 +( -121 -136 8 ) ( 7 -136 8 ) ( 7 -136 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.059896 0.093750 0 0 0 +( 8 -48 40 ) ( 8 80 40 ) ( 8 80 32 ) map/lab_games/lg_style_01_wall_blue 1024 0 0 -0.062500 0.093750 0 0 0 +( 8 64 8 ) ( -120 64 8 ) ( -120 64 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.059896 0.093750 0 0 0 +( 0 0 -24 ) ( 0 -128 -24 ) ( 0 0 -32 ) map/lab_games/lg_style_01_wall_blue 1024 0 0 -0.062500 0.093750 0 0 0 +} +// brush 7 +{ +( 64 -8 0 ) ( -64 -8 0 ) ( -64 -136 0 ) map/lab_games/lg_style_01_wall_blue 16 0 0 -0.062500 0.007812 0 0 0 +( -64 -136 96 ) ( -64 -8 96 ) ( 64 -8 96 ) map/lab_games/lg_style_01_wall_blue 16 0 0 -0.062500 0.007812 0 0 0 +( -128 -136 8 ) ( 0 -136 8 ) ( 0 -136 0 ) map/lab_games/lg_style_01_wall_blue 16 0 0 -0.062500 0.093750 0 0 0 +( 0 -136 40 ) ( 0 -8 40 ) ( 0 -8 32 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.093750 0 0 0 +( -64 -8 40 ) ( -64 -136 40 ) ( -64 -136 32 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.007812 0.093750 0 0 0 +( 65 -128 8 ) ( -63 -128 8 ) ( 65 -128 0 ) map/lab_games/lg_style_01_wall_blue 16 0 0 -0.062500 0.093750 0 0 0 +} +// brush 8 +{ +( -456 208 8 ) ( -456 80 8 ) ( -456 80 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +( -199 128 8 ) ( -327 128 8 ) ( -327 128 0 ) map/lab_games/lg_style_01_wall_blue 340 0 0 -0.008789 0.093750 0 0 0 +( -327 64 8 ) ( -199 64 8 ) ( -199 64 0 ) map/lab_games/lg_style_01_wall_blue 340 0 0 -0.008789 0.093750 0 0 0 +( -328 64 96 ) ( -328 192 96 ) ( -200 192 96 ) map/lab_games/lg_style_01_wall_blue 340 0 0 -0.008789 0.062500 0 0 0 +( -200 192 0 ) ( -328 192 0 ) ( -328 64 0 ) map/lab_games/lg_style_01_wall_blue 340 0 0 -0.008789 0.062500 0 0 0 +( -448 16 -24 ) ( -448 144 -24 ) ( -448 16 -32 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +} +// brush 9 +{ +( -456 56 136 ) ( -456 48 136 ) ( -456 48 -8 ) map/lab_games/lg_style_01_wall_blue 983 0 0 -0.065104 0.093750 0 0 0 +( -263 64 136 ) ( -391 64 136 ) ( -391 64 -8 ) map/lab_games/lg_style_01_wall_blue 2088 0 0 -0.065104 0.093750 0 0 0 +( -72 -56 168 ) ( -72 -48 168 ) ( -72 -48 24 ) map/lab_games/lg_style_01_wall_blue 983 0 0 -0.065104 0.093750 0 0 0 +( -136 56 136 ) ( -8 56 136 ) ( -8 56 -8 ) map/lab_games/lg_style_01_wall_blue 2088 0 0 -0.065104 0.093750 0 0 0 +( -192 56 96 ) ( -192 64 96 ) ( -64 64 96 ) map/lab_games/lg_style_01_wall_blue 2088 327 0 -0.065104 0.195312 0 0 0 +( -64 64 0 ) ( -192 64 0 ) ( -192 56 0 ) map/lab_games/lg_style_01_wall_blue 2088 327 0 -0.065104 0.195312 0 0 0 +} +// brush 10 +{ +( 281 128 8 ) ( 153 128 8 ) ( 153 128 0 ) map/lab_games/lg_style_01_wall_blue 1023 0 0 -0.006836 0.093750 0 0 0 +( 392 64 8 ) ( 392 192 8 ) ( 392 192 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +( 153 64 8 ) ( 281 64 8 ) ( 281 64 0 ) map/lab_games/lg_style_01_wall_blue 1023 0 0 -0.006836 0.093750 0 0 0 +( 152 64 96 ) ( 152 192 96 ) ( 280 192 96 ) map/lab_games/lg_style_01_wall_blue 1023 0 0 -0.006836 0.062500 0 0 0 +( 280 192 0 ) ( 152 192 0 ) ( 152 64 0 ) map/lab_games/lg_style_01_wall_blue 1023 0 0 -0.006836 0.062500 0 0 0 +( 384 128 8 ) ( 384 0 8 ) ( 384 128 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +} +// brush 11 +{ +( 0 200 40 ) ( 0 72 40 ) ( 0 72 32 ) map/lab_games/lg_style_01_wall_blue 1068 0 0 -0.059896 0.093750 0 0 0 +( 392 56 8 ) ( 392 184 8 ) ( 392 184 0 ) map/lab_games/lg_style_01_wall_blue 1068 0 0 -0.059896 0.093750 0 0 0 +( 256 56 8 ) ( 384 56 8 ) ( 384 56 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +( -136 56 96 ) ( -136 184 96 ) ( -8 184 96 ) map/lab_games/lg_style_01_wall_blue 0 356 0 -0.062500 0.179688 0 0 0 +( -8 184 0 ) ( -136 184 0 ) ( -136 56 0 ) map/lab_games/lg_style_01_wall_blue 0 356 0 -0.062500 0.179688 0 0 0 +( 121 64 8 ) ( -7 64 8 ) ( 121 64 0 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.062500 0.093750 0 0 0 +} +// brush 12 +{ +( 1064 232 -176 ) ( 544 232 -176 ) ( 544 -240 -176 ) map/lab_games/sky/lg_sky_02_dn 47 502 0 -0.507812 0.460938 0 0 0 +( 544 -240 200 ) ( 1064 -240 200 ) ( 1064 -240 184 ) map/lab_games/sky/lg_sky_02_dn 47 0 0 -0.507812 0.007812 0 0 0 +( 1064 -240 200 ) ( 1064 232 200 ) ( 1064 232 184 ) map/lab_games/sky/lg_sky_02_dn 502 0 0 -0.460938 0.007812 0 0 0 +( 1064 232 200 ) ( 544 232 200 ) ( 544 232 184 ) map/lab_games/sky/lg_sky_02_dn 47 0 0 -0.507812 0.007812 0 0 0 +( 544 232 200 ) ( 544 -240 200 ) ( 544 -240 184 ) map/lab_games/sky/lg_sky_02_dn 502 0 0 -0.460938 0.007812 0 0 0 +( 544 232 -168 ) ( 1064 232 -168 ) ( 544 -240 -168 ) map/lab_games/sky/lg_sky_02_dn 47 502 0 -0.507812 0.460938 0 0 0 +} +// brush 13 +{ +( 544 -240 200 ) ( 544 232 200 ) ( 1064 232 200 ) map/lab_games/sky/lg_sky_02_up 47 502 0 -0.507812 0.460938 0 0 0 +( 544 -240 200 ) ( 1064 -240 200 ) ( 1064 -240 184 ) map/lab_games/sky/lg_sky_02_up 47 0 0 -0.507812 0.007812 0 0 0 +( 1064 -240 200 ) ( 1064 232 200 ) ( 1064 232 184 ) map/lab_games/sky/lg_sky_02_up 502 0 0 -0.460938 0.007812 0 0 0 +( 1064 232 200 ) ( 544 232 200 ) ( 544 232 184 ) map/lab_games/sky/lg_sky_02_up 47 0 0 -0.507812 0.007812 0 0 0 +( 544 232 200 ) ( 544 -240 200 ) ( 544 -240 184 ) map/lab_games/sky/lg_sky_02_up 502 0 0 -0.460938 0.007812 0 0 0 +( 544 232 192 ) ( 544 -240 192 ) ( 1064 232 192 ) map/lab_games/sky/lg_sky_02_up 47 502 0 -0.507812 0.460938 0 0 0 +} +// brush 14 +{ +( 1064 232 -176 ) ( 544 232 -176 ) ( 544 -240 -176 ) map/lab_games/sky/lg_sky_02_bk 47 -1023 0 -0.507812 0.007812 0 0 0 +( 544 -240 200 ) ( 544 232 200 ) ( 1064 232 200 ) map/lab_games/sky/lg_sky_02_bk 47 -1023 0 -0.507812 0.007812 0 0 0 +( 544 -240 200 ) ( 1064 -240 200 ) ( 1064 -240 184 ) map/lab_games/sky/lg_sky_02_bk 47 544 0 -0.507812 0.367188 0 0 0 +( 1064 -240 200 ) ( 1064 232 200 ) ( 1064 232 184 ) map/lab_games/sky/lg_sky_02_bk -1023 544 0 -0.007812 0.367188 0 0 0 +( 544 232 200 ) ( 544 -240 200 ) ( 544 -240 184 ) map/lab_games/sky/lg_sky_02_bk -1023 544 0 -0.007812 0.367188 0 0 0 +( 1064 -232 200 ) ( 544 -232 200 ) ( 1064 -232 184 ) map/lab_games/sky/lg_sky_02_bk 47 544 0 -0.507812 0.367188 0 0 0 +} +// brush 15 +{ +( 1064 232 -176 ) ( 544 232 -176 ) ( 544 -240 -176 ) map/lab_games/sky/lg_sky_02_rt -2 502 0 -0.007812 0.460938 0 0 0 +( 544 -240 200 ) ( 544 232 200 ) ( 1064 232 200 ) map/lab_games/sky/lg_sky_02_rt -2 502 0 -0.007812 0.460938 0 0 0 +( 544 -240 200 ) ( 1064 -240 200 ) ( 1064 -240 184 ) map/lab_games/sky/lg_sky_02_rt -2 544 0 -0.007812 0.367188 0 0 0 +( 1064 -240 200 ) ( 1064 232 200 ) ( 1064 232 184 ) map/lab_games/sky/lg_sky_02_rt 502 544 0 -0.460938 0.367188 0 0 0 +( 1064 232 200 ) ( 544 232 200 ) ( 544 232 184 ) map/lab_games/sky/lg_sky_02_rt -2 544 0 -0.007812 0.367188 0 0 0 +( 1056 232 200 ) ( 1056 -240 200 ) ( 1056 232 184 ) map/lab_games/sky/lg_sky_02_rt 502 544 0 -0.460938 0.367188 0 0 0 +} +// brush 16 +{ +( 1064 232 -176 ) ( 544 232 -176 ) ( 544 -240 -176 ) map/lab_games/sky/lg_sky_02_ft 47 -1023 0 -0.507812 0.007812 0 0 0 +( 544 -240 200 ) ( 544 232 200 ) ( 1064 232 200 ) map/lab_games/sky/lg_sky_02_ft 47 -1023 0 -0.507812 0.007812 0 0 0 +( 1064 -240 200 ) ( 1064 232 200 ) ( 1064 232 184 ) map/lab_games/sky/lg_sky_02_ft -1023 544 0 -0.007812 0.367188 0 0 0 +( 1064 232 200 ) ( 544 232 200 ) ( 544 232 184 ) map/lab_games/sky/lg_sky_02_ft 47 544 0 -0.507812 0.367188 0 0 0 +( 544 232 200 ) ( 544 -240 200 ) ( 544 -240 184 ) map/lab_games/sky/lg_sky_02_ft -1023 544 0 -0.007812 0.367188 0 0 0 +( 544 224 200 ) ( 1064 224 200 ) ( 544 224 184 ) map/lab_games/sky/lg_sky_02_ft 47 544 0 -0.507812 0.367188 0 0 0 +} +// brush 17 +{ +( 1064 232 -176 ) ( 544 232 -176 ) ( 544 -240 -176 ) map/lab_games/sky/lg_sky_02_lf -2 502 0 -0.007812 0.460938 0 0 0 +( 544 -240 200 ) ( 544 232 200 ) ( 1064 232 200 ) map/lab_games/sky/lg_sky_02_lf -2 502 0 -0.007812 0.460938 0 0 0 +( 544 -240 200 ) ( 1064 -240 200 ) ( 1064 -240 184 ) map/lab_games/sky/lg_sky_02_lf -2 544 0 -0.007812 0.367188 0 0 0 +( 1064 232 200 ) ( 544 232 200 ) ( 544 232 184 ) map/lab_games/sky/lg_sky_02_lf -2 544 0 -0.007812 0.367188 0 0 0 +( 544 232 200 ) ( 544 -240 200 ) ( 544 -240 184 ) map/lab_games/sky/lg_sky_02_lf 502 544 0 -0.460938 0.367188 0 0 0 +( 552 -240 200 ) ( 552 232 200 ) ( 552 -240 184 ) map/lab_games/sky/lg_sky_02_lf 502 544 0 -0.460938 0.367188 0 0 0 +} +} +// entity 1 +{ +"angle" "90" +"origin" "-32 -96 24" +"classname" "info_player_start" +"spawn_orientation_segment" "20" +} +// entity 2 +{ +"light" "500" +"origin" "-32 96 80" +"classname" "light" +} +// entity 3 +{ +"classname" "light" +"origin" "-192 96 80" +"light" "500" +} +// entity 4 +{ +"classname" "light" +"origin" "128 96 80" +"light" "500" +} +// entity 5 +{ +"classname" "light" +"origin" "-32 -32 80" +"light" "500" +} +// entity 6 +{ +"light" "500" +"origin" "288 96 80" +"classname" "light" +} +// entity 7 +{ +"light" "500" +"origin" "-352 96 80" +"classname" "light" +} +// entity 8 +{ +"classname" "placeholder_1" +"origin" "-424 96 16" +} +// entity 9 +{ +"classname" "placeholder_2" +"origin" "360 96 16" +} +// entity 10 +{ +"classname" "_skybox" +"origin" "776 -8 -128" +} +// entity 11 +{ +"model" "models/stadium.md3" +"origin" "776 -8 -82" +"classname" "misc_model" +"_remap" "*;textures/map/lab_games/stadium_d" +"angle" "90" +} +// entity 12 +{ +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"modelscale" "1" +"model" "models/signal_line.md3" +"origin" "800 -48 -80" +"classname" "misc_model" +"angle" "113" +} +// entity 13 +{ +"classname" "misc_model" +"origin" "848 -24 -152" +"model" "models/signal_line.md3" +"modelscale" "1.1" +"_remap" "*;textures/map/lab_games/signal_pulse_blue" +"angle" "38" +} +// entity 14 +{ +"angle" "28" +"classname" "misc_model" +"origin" "808 -16 -88" +"model" "models/signal_line.md3" +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"modelscale" "0.61" +} +// entity 15 +{ +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"modelscale" "0.6" +"model" "models/signal_line.md3" +"origin" "752 0 -152" +"classname" "misc_model" +"angle" "159" +} diff --git a/assets/maps/em_watermaze.map b/assets/maps/em_watermaze.map new file mode 100644 index 00000000..5cd71ba3 --- /dev/null +++ b/assets/maps/em_watermaze.map @@ -0,0 +1,463 @@ +// entity 0 +{ +"classname" "worldspawn" +// brush 0 +{ +( 768 -768 0 ) ( -768 -768 0 ) ( 768 768 0 ) map/lab_games/lg_style_01_floor_blue 0 0 0 0.190000 0.190000 0 0 0 +( -768 -768 -8 ) ( 768 -768 -8 ) ( 768 768 -8 ) map/lab_games/lg_style_01_floor_blue 0 0 0 0.190000 0.190000 0 0 0 +( 768 0 -8 ) ( 768 0 200 ) ( 768 768 200 ) map/lab_games/lg_style_01_floor_blue 0 0 0 0.190000 0.190000 0 0 0 +( 710 294 -8 ) ( 710 294 200 ) ( 416 1004 200 ) map/lab_games/lg_style_01_floor_blue 0 0 0 0.190000 0.190000 0 0 0 +( 543 543 -8 ) ( 543 543 200 ) ( 0 1086 200 ) map/lab_games/lg_style_01_floor_blue 0 0 0 0.190000 0.190000 0 0 0 +( 294 710 -8 ) ( 294 710 200 ) ( -416 1004 200 ) map/lab_games/lg_style_01_floor_blue 0 0 0 0.190000 0.190000 0 0 0 +( 0 768 -8 ) ( 0 768 200 ) ( -768 768 200 ) map/lab_games/lg_style_01_floor_blue 0 0 0 0.190000 0.190000 0 0 0 +( -294 710 -8 ) ( -294 710 200 ) ( -1004 416 200 ) map/lab_games/lg_style_01_floor_blue 0 0 0 0.190000 0.190000 0 0 0 +( -543 543 -8 ) ( -543 543 200 ) ( -1086 0 200 ) map/lab_games/lg_style_01_floor_blue 0 0 0 0.190000 0.190000 0 0 0 +( -710 294 -8 ) ( -710 294 200 ) ( -1004 -416 200 ) map/lab_games/lg_style_01_floor_blue 0 0 0 0.190000 0.190000 0 0 0 +( -768 0 -8 ) ( -768 0 200 ) ( -768 -768 200 ) map/lab_games/lg_style_01_floor_blue 0 0 0 0.190000 0.190000 0 0 0 +( -710 -294 -8 ) ( -710 -294 200 ) ( -416 -1004 200 ) map/lab_games/lg_style_01_floor_blue 0 0 0 0.190000 0.190000 0 0 0 +( -543 -543 -8 ) ( -543 -543 200 ) ( 0 -1086 200 ) map/lab_games/lg_style_01_floor_blue 0 0 0 0.190000 0.190000 0 0 0 +( -294 -710 -8 ) ( -294 -710 200 ) ( 416 -1004 200 ) map/lab_games/lg_style_01_floor_blue 0 0 0 0.190000 0.190000 0 0 0 +( 0 -768 -8 ) ( 0 -768 200 ) ( 768 -768 200 ) map/lab_games/lg_style_01_floor_blue 0 0 0 0.190000 0.190000 0 0 0 +( 294 -710 -8 ) ( 294 -710 200 ) ( 1004 -416 200 ) map/lab_games/lg_style_01_floor_blue 0 0 0 0.190000 0.190000 0 0 0 +( 543 -543 -8 ) ( 543 -543 200 ) ( 1086 0 200 ) map/lab_games/lg_style_01_floor_blue 0 0 0 0.190000 0.190000 0 0 0 +( 710 -294 -8 ) ( 710 -294 200 ) ( 1004 416 200 ) map/lab_games/lg_style_01_floor_blue 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 1 +{ +( 768 -768 192 ) ( 768 768 192 ) ( -768 -768 192 ) map/lab_games/fake_sky 0 0 0 0.031250 0.031200 0 0 0 +( 768 768 200 ) ( 768 -768 200 ) ( -768 -768 200 ) map/lab_games/fake_sky 0 0 0 0.031250 0.031200 0 0 0 +( 768 0 -8 ) ( 768 0 200 ) ( 768 768 200 ) map/lab_games/fake_sky 0 0 0 0.031250 0.031200 0 0 0 +( 710 294 -8 ) ( 710 294 200 ) ( 416 1004 200 ) map/lab_games/fake_sky 0 0 0 0.031250 0.031200 0 0 0 +( 543 543 -8 ) ( 543 543 200 ) ( 0 1086 200 ) map/lab_games/fake_sky 0 0 0 0.031250 0.031200 0 0 0 +( 294 710 -8 ) ( 294 710 200 ) ( -416 1004 200 ) map/lab_games/fake_sky 0 0 0 0.031250 0.031200 0 0 0 +( 0 768 -8 ) ( 0 768 200 ) ( -768 768 200 ) map/lab_games/fake_sky 0 0 0 0.031250 0.031200 0 0 0 +( -294 710 -8 ) ( -294 710 200 ) ( -1004 416 200 ) map/lab_games/fake_sky 0 0 0 0.031250 0.031200 0 0 0 +( -543 543 -8 ) ( -543 543 200 ) ( -1086 0 200 ) map/lab_games/fake_sky 0 0 0 0.031250 0.031200 0 0 0 +( -710 294 -8 ) ( -710 294 200 ) ( -1004 -416 200 ) map/lab_games/fake_sky 0 0 0 0.031250 0.031200 0 0 0 +( -768 0 -8 ) ( -768 0 200 ) ( -768 -768 200 ) map/lab_games/fake_sky 0 0 0 0.031250 0.031200 0 0 0 +( -710 -294 -8 ) ( -710 -294 200 ) ( -416 -1004 200 ) map/lab_games/fake_sky 0 0 0 0.031250 0.031200 0 0 0 +( -543 -543 -8 ) ( -543 -543 200 ) ( 0 -1086 200 ) map/lab_games/fake_sky 0 0 0 0.031250 0.031200 0 0 0 +( -294 -710 -8 ) ( -294 -710 200 ) ( 416 -1004 200 ) map/lab_games/fake_sky 0 0 0 0.031250 0.031200 0 0 0 +( 0 -768 -8 ) ( 0 -768 200 ) ( 768 -768 200 ) map/lab_games/fake_sky 0 0 0 0.031250 0.031200 0 0 0 +( 294 -710 -8 ) ( 294 -710 200 ) ( 1004 -416 200 ) map/lab_games/fake_sky 0 0 0 0.031250 0.031200 0 0 0 +( 543 -543 -8 ) ( 543 -543 200 ) ( 1086 0 200 ) map/lab_games/fake_sky 0 0 0 0.031250 0.031200 0 0 0 +( 710 -294 -8 ) ( 710 -294 200 ) ( 1004 416 200 ) map/lab_games/fake_sky 0 0 0 0.031250 0.031200 0 0 0 +} +// brush 2 +{ +( 703 -291 200 ) ( 703 -291 -8 ) ( 997 419 200 ) map/lab_games/lg_style_01_wall_blue 149 984 0 -0.153318 0.190000 0 0 0 +( 768 768 200 ) ( 768 -768 200 ) ( -768 -768 200 ) map/lab_games/lg_style_01_wall_blue -191 586 0 -0.063487 0.190000 0 0 0 +( -768 -768 -8 ) ( 768 -768 -8 ) ( 768 768 -8 ) map/lab_games/lg_style_01_wall_blue -191 586 0 -0.063484 0.190000 0 0 0 +( 768 0 -8 ) ( 768 0 200 ) ( 768 768 200 ) map/lab_games/lg_style_01_wall_blue 547 984 0 -0.009720 0.190000 0 0 0 +( 543 -543 -8 ) ( 543 -543 200 ) ( 1086 0 200 ) map/lab_games/lg_style_01_wall_blue 306 984 0 -0.006868 0.190000 0 0 0 +( 710 -294 -8 ) ( 710 -294 200 ) ( 1004 416 200 ) map/lab_games/lg_style_01_wall_blue 922 984 0 -0.136728 0.190000 0 0 0 +} +// brush 3 +{ +( 537 -537 200 ) ( 537 -537 -8 ) ( 1080 6 200 ) map/lab_games/lg_style_01_wall_blue 668 984 0 -0.120634 0.190000 0 0 0 +( 768 768 200 ) ( 768 -768 200 ) ( -768 -768 200 ) map/lab_games/lg_style_01_wall_blue 354 334 0 -0.120635 0.190000 0 0 0 +( -768 -768 -8 ) ( 768 -768 -8 ) ( 768 768 -8 ) map/lab_games/lg_style_01_wall_blue 354 334 0 -0.120634 0.190000 0 0 0 +( 294 -710 -8 ) ( 294 -710 200 ) ( 1004 -416 200 ) map/lab_games/lg_style_01_wall_blue -644 984 0 -0.010001 0.190000 0 0 0 +( 543 -543 -8 ) ( 543 -543 200 ) ( 1086 0 200 ) map/lab_games/lg_style_01_wall_blue 21 984 0 -0.106492 0.190000 0 0 0 +( 710 -294 -8 ) ( 710 -294 200 ) ( 1004 416 200 ) map/lab_games/lg_style_01_wall_blue 631 984 0 -0.009999 0.190000 0 0 0 +} +// brush 4 +{ +( 291 -703 200 ) ( 291 -703 -8 ) ( 1001 -409 200 ) map/lab_games/lg_style_01_wall_blue -150 984 0 -0.153316 0.190000 0 0 0 +( 768 768 200 ) ( 768 -768 200 ) ( -768 -768 200 ) map/lab_games/lg_style_01_wall_blue -150 95 0 -0.153315 0.190000 0 0 0 +( -768 -768 -8 ) ( 768 -768 -8 ) ( 768 768 -8 ) map/lab_games/lg_style_01_wall_blue -150 95 0 -0.153318 0.190000 0 0 0 +( 0 -768 -8 ) ( 0 -768 200 ) ( 768 -768 200 ) map/lab_games/lg_style_01_wall_blue -549 984 0 -0.009720 0.190000 0 0 0 +( 294 -710 -8 ) ( 294 -710 200 ) ( 1004 -416 200 ) map/lab_games/lg_style_01_wall_blue 100 984 0 -0.136728 0.190000 0 0 0 +( 543 -543 -8 ) ( 543 -543 200 ) ( 1086 0 200 ) map/lab_games/lg_style_01_wall_blue 334 984 0 -0.006871 0.190000 0 0 0 +} +// brush 5 +{ +( 0 -760 200 ) ( 0 -760 -8 ) ( 768 -760 200 ) map/lab_games/lg_style_01_wall_blue 0 984 0 -0.169191 0.190000 0 0 0 +( 768 768 200 ) ( 768 -768 200 ) ( -768 -768 200 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.169186 0.190000 0 0 0 +( -768 -768 -8 ) ( 768 -768 -8 ) ( 768 768 -8 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.169193 0.190000 0 0 0 +( -294 -710 -8 ) ( -294 -710 200 ) ( 416 -1004 200 ) map/lab_games/lg_style_01_wall_blue -958 984 0 -0.009434 0.190000 0 0 0 +( 0 -768 -8 ) ( 0 -768 200 ) ( 768 -768 200 ) map/lab_games/lg_style_01_wall_blue 0 984 0 -0.150323 0.190000 0 0 0 +( 294 -710 -8 ) ( 294 -710 200 ) ( 1004 -416 200 ) map/lab_games/lg_style_01_wall_blue -66 984 0 -0.009433 0.190000 0 0 0 +} +// brush 6 +{ +( -291 -703 200 ) ( -291 -703 -8 ) ( 419 -997 200 ) map/lab_games/lg_style_01_wall_blue 149 984 0 -0.153316 0.190000 0 0 0 +( 768 768 200 ) ( 768 -768 200 ) ( -768 -768 200 ) map/lab_games/lg_style_01_wall_blue 149 95 0 -0.153318 0.190000 0 0 0 +( -768 -768 -8 ) ( 768 -768 -8 ) ( 768 768 -8 ) map/lab_games/lg_style_01_wall_blue 149 95 0 -0.153319 0.190000 0 0 0 +( -543 -543 -8 ) ( -543 -543 200 ) ( 0 -1086 200 ) map/lab_games/lg_style_01_wall_blue 301 984 0 -0.006869 0.190000 0 0 0 +( -294 -710 -8 ) ( -294 -710 200 ) ( 416 -1004 200 ) map/lab_games/lg_style_01_wall_blue -102 984 0 -0.136730 0.190000 0 0 0 +( 0 -768 -8 ) ( 0 -768 200 ) ( 768 -768 200 ) map/lab_games/lg_style_01_wall_blue -477 984 0 -0.009720 0.190000 0 0 0 +} +// brush 7 +{ +( -537 -537 200 ) ( -537 -537 -8 ) ( 6 -1080 200 ) map/lab_games/lg_style_01_wall_blue 668 984 0 -0.120634 0.190000 0 0 0 +( 768 768 200 ) ( 768 -768 200 ) ( -768 -768 200 ) map/lab_games/lg_style_01_wall_blue -355 334 0 -0.120635 0.190000 0 0 0 +( -768 -768 -8 ) ( 768 -768 -8 ) ( 768 768 -8 ) map/lab_games/lg_style_01_wall_blue -355 334 0 -0.120635 0.190000 0 0 0 +( -710 -294 -8 ) ( -710 -294 200 ) ( -416 -1004 200 ) map/lab_games/lg_style_01_wall_blue 638 984 0 -0.010000 0.190000 0 0 0 +( -543 -543 -8 ) ( -543 -543 200 ) ( 0 -1086 200 ) map/lab_games/lg_style_01_wall_blue 21 984 0 -0.106492 0.190000 0 0 0 +( -294 -710 -8 ) ( -294 -710 200 ) ( 416 -1004 200 ) map/lab_games/lg_style_01_wall_blue -383 984 0 -0.010001 0.190000 0 0 0 +} +// brush 8 +{ +( -703 -291 200 ) ( -703 -291 -8 ) ( -409 -1001 200 ) map/lab_games/lg_style_01_wall_blue 149 984 0 -0.153315 0.190000 0 0 0 +( 768 768 200 ) ( 768 -768 200 ) ( -768 -768 200 ) map/lab_games/lg_style_01_wall_blue -834 586 0 -0.063486 0.190000 0 0 0 +( -768 -768 -8 ) ( 768 -768 -8 ) ( 768 768 -8 ) map/lab_games/lg_style_01_wall_blue -834 586 0 -0.063486 0.190000 0 0 0 +( -768 0 -8 ) ( -768 0 200 ) ( -768 -768 200 ) map/lab_games/lg_style_01_wall_blue 548 984 0 -0.009720 0.190000 0 0 0 +( -710 -294 -8 ) ( -710 -294 200 ) ( -416 -1004 200 ) map/lab_games/lg_style_01_wall_blue 922 984 0 -0.136727 0.190000 0 0 0 +( -543 -543 -8 ) ( -543 -543 200 ) ( 0 -1086 200 ) map/lab_games/lg_style_01_wall_blue 306 984 0 -0.006868 0.190000 0 0 0 +} +// brush 9 +{ +( -760 0 200 ) ( -760 0 -8 ) ( -760 -768 200 ) map/lab_games/lg_style_01_wall_blue 1023 984 0 -0.169191 0.190000 0 0 0 +( 768 768 200 ) ( 768 -768 200 ) ( -768 -768 200 ) map/lab_games/lg_style_01_wall_blue 0 511 0 -0.003906 0.190000 0 0 0 +( -768 -768 -8 ) ( 768 -768 -8 ) ( 768 768 -8 ) map/lab_games/lg_style_01_wall_blue 0 512 0 -0.003906 0.190000 0 0 0 +( -710 294 -8 ) ( -710 294 200 ) ( -1004 -416 200 ) map/lab_games/lg_style_01_wall_blue 957 984 0 -0.009434 0.190000 0 0 0 +( -768 0 -8 ) ( -768 0 200 ) ( -768 -768 200 ) map/lab_games/lg_style_01_wall_blue 0 984 0 -0.150323 0.190000 0 0 0 +( -710 -294 -8 ) ( -710 -294 200 ) ( -416 -1004 200 ) map/lab_games/lg_style_01_wall_blue 67 984 0 -0.009434 0.190000 0 0 0 +} +// brush 10 +{ +( -703 291 200 ) ( -703 291 -8 ) ( -997 -419 200 ) map/lab_games/lg_style_01_wall_blue 874 984 0 -0.153314 0.190000 0 0 0 +( 768 768 200 ) ( 768 -768 200 ) ( -768 -768 200 ) map/lab_games/lg_style_01_wall_blue -834 437 0 -0.063486 0.190000 0 0 0 +( -768 -768 -8 ) ( 768 -768 -8 ) ( 768 768 -8 ) map/lab_games/lg_style_01_wall_blue -834 437 0 -0.063484 0.190000 0 0 0 +( -543 543 -8 ) ( -543 543 200 ) ( -1086 0 200 ) map/lab_games/lg_style_01_wall_blue 707 984 0 -0.006869 0.190000 0 0 0 +( -710 294 -8 ) ( -710 294 200 ) ( -1004 -416 200 ) map/lab_games/lg_style_01_wall_blue 101 984 0 -0.136731 0.190000 0 0 0 +( -768 0 -8 ) ( -768 0 200 ) ( -768 -768 200 ) map/lab_games/lg_style_01_wall_blue 476 984 0 -0.009720 0.190000 0 0 0 +} +// brush 11 +{ +( -537 537 200 ) ( -537 537 -8 ) ( -1080 -6 200 ) map/lab_games/lg_style_01_wall_blue 355 984 0 -0.120634 0.190000 0 0 0 +( 768 768 200 ) ( 768 -768 200 ) ( -768 -768 200 ) map/lab_games/lg_style_01_wall_blue -356 689 0 -0.120632 0.190000 0 0 0 +( -768 -768 -8 ) ( 768 -768 -8 ) ( 768 768 -8 ) map/lab_games/lg_style_01_wall_blue -356 689 0 -0.120632 0.190000 0 0 0 +( -294 710 -8 ) ( -294 710 200 ) ( -1004 416 200 ) map/lab_games/lg_style_01_wall_blue -386 984 0 -0.010000 0.190000 0 0 0 +( -543 543 -8 ) ( -543 543 200 ) ( -1086 0 200 ) map/lab_games/lg_style_01_wall_blue 1002 984 0 -0.106492 0.190000 0 0 0 +( -710 294 -8 ) ( -710 294 200 ) ( -1004 -416 200 ) map/lab_games/lg_style_01_wall_blue 385 984 0 -0.010000 0.190000 0 0 0 +} +// brush 12 +{ +( -291 703 200 ) ( -291 703 -8 ) ( -1001 409 200 ) map/lab_games/lg_style_01_wall_blue 149 984 0 -0.153315 0.190000 0 0 0 +( 768 768 200 ) ( 768 -768 200 ) ( -768 -768 200 ) map/lab_games/lg_style_01_wall_blue 149 928 0 -0.153319 0.190000 0 0 0 +( -768 -768 -8 ) ( 768 -768 -8 ) ( 768 768 -8 ) map/lab_games/lg_style_01_wall_blue 149 928 0 -0.153318 0.190000 0 0 0 +( 0 768 -8 ) ( 0 768 200 ) ( -768 768 200 ) map/lab_games/lg_style_01_wall_blue -476 984 0 -0.009720 0.190000 0 0 0 +( -294 710 -8 ) ( -294 710 200 ) ( -1004 416 200 ) map/lab_games/lg_style_01_wall_blue -102 984 0 -0.136731 0.190000 0 0 0 +( -543 543 -8 ) ( -543 543 200 ) ( -1086 0 200 ) map/lab_games/lg_style_01_wall_blue 726 984 0 -0.006868 0.190000 0 0 0 +} +// brush 13 +{ +( 0 760 200 ) ( 0 760 -8 ) ( -768 760 200 ) map/lab_games/lg_style_01_wall_blue 0 984 0 -0.169191 0.190000 0 0 0 +( 768 768 200 ) ( 768 -768 200 ) ( -768 -768 200 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.169190 0.190000 0 0 0 +( -768 -768 -8 ) ( 768 -768 -8 ) ( 768 768 -8 ) map/lab_games/lg_style_01_wall_blue 0 0 0 -0.169191 0.190000 0 0 0 +( 294 710 -8 ) ( 294 710 200 ) ( -416 1004 200 ) map/lab_games/lg_style_01_wall_blue -66 984 0 -0.009433 0.190000 0 0 0 +( 0 768 -8 ) ( 0 768 200 ) ( -768 768 200 ) map/lab_games/lg_style_01_wall_blue 0 984 0 -0.150323 0.190000 0 0 0 +( -294 710 -8 ) ( -294 710 200 ) ( -1004 416 200 ) map/lab_games/lg_style_01_wall_blue -957 984 0 -0.009434 0.190000 0 0 0 +} +// brush 14 +{ +( 291 703 200 ) ( 291 703 -8 ) ( -419 997 200 ) map/lab_games/lg_style_01_wall_blue -150 984 0 -0.153315 0.190000 0 0 0 +( 768 768 200 ) ( 768 -768 200 ) ( -768 -768 200 ) map/lab_games/lg_style_01_wall_blue -150 928 0 -0.153318 0.190000 0 0 0 +( -768 -768 -8 ) ( 768 -768 -8 ) ( 768 768 -8 ) map/lab_games/lg_style_01_wall_blue -150 928 0 -0.153317 0.190000 0 0 0 +( 543 543 -8 ) ( 543 543 200 ) ( 0 1086 200 ) map/lab_games/lg_style_01_wall_blue 712 984 0 -0.006869 0.190000 0 0 0 +( 294 710 -8 ) ( 294 710 200 ) ( -416 1004 200 ) map/lab_games/lg_style_01_wall_blue 100 984 0 -0.136727 0.190000 0 0 0 +( 0 768 -8 ) ( 0 768 200 ) ( -768 768 200 ) map/lab_games/lg_style_01_wall_blue -548 984 0 -0.009721 0.190000 0 0 0 +} +// brush 15 +{ +( 537 537 200 ) ( 537 537 -8 ) ( -6 1080 200 ) map/lab_games/lg_style_01_wall_blue 355 984 0 -0.120635 0.190000 0 0 0 +( 768 768 200 ) ( 768 -768 200 ) ( -768 -768 200 ) map/lab_games/lg_style_01_wall_blue 354 689 0 -0.120635 0.190000 0 0 0 +( -768 -768 -8 ) ( 768 -768 -8 ) ( 768 768 -8 ) map/lab_games/lg_style_01_wall_blue 355 689 0 -0.120630 0.190000 0 0 0 +( 710 294 -8 ) ( 710 294 200 ) ( 416 1004 200 ) map/lab_games/lg_style_01_wall_blue 382 984 0 -0.010001 0.190000 0 0 0 +( 543 543 -8 ) ( 543 543 200 ) ( 0 1086 200 ) map/lab_games/lg_style_01_wall_blue 1002 984 0 -0.106495 0.190000 0 0 0 +( 294 710 -8 ) ( 294 710 200 ) ( -416 1004 200 ) map/lab_games/lg_style_01_wall_blue -642 984 0 -0.010001 0.190000 0 0 0 +} +// brush 16 +{ +( 703 291 200 ) ( 703 291 -8 ) ( 409 1001 200 ) map/lab_games/lg_style_01_wall_blue 874 984 0 -0.153318 0.190000 0 0 0 +( 768 768 200 ) ( 768 -768 200 ) ( -768 -768 200 ) map/lab_games/lg_style_01_wall_blue -191 437 0 -0.063486 0.190000 0 0 0 +( -768 -768 -8 ) ( 768 -768 -8 ) ( 768 768 -8 ) map/lab_games/lg_style_01_wall_blue -191 437 0 -0.063486 0.190000 0 0 0 +( 768 0 -8 ) ( 768 0 200 ) ( 768 768 200 ) map/lab_games/lg_style_01_wall_blue 475 984 0 -0.009720 0.190000 0 0 0 +( 710 294 -8 ) ( 710 294 200 ) ( 416 1004 200 ) map/lab_games/lg_style_01_wall_blue 101 984 0 -0.136728 0.190000 0 0 0 +( 543 543 -8 ) ( 543 543 200 ) ( 0 1086 200 ) map/lab_games/lg_style_01_wall_blue 717 984 0 -0.006868 0.190000 0 0 0 +} +// brush 17 +{ +( 760 0 200 ) ( 760 0 -8 ) ( 760 768 200 ) map/lab_games/lg_style_01_wall_blue 1023 984 0 -0.169191 0.190000 0 0 0 +( 768 768 200 ) ( 768 -768 200 ) ( -768 -768 200 ) map/lab_games/lg_style_01_wall_blue 0 511 0 -0.003906 0.190000 0 0 0 +( -768 -768 -8 ) ( 768 -768 -8 ) ( 768 768 -8 ) map/lab_games/lg_style_01_wall_blue 0 512 0 -0.003906 0.190000 0 0 0 +( 768 0 -8 ) ( 768 0 200 ) ( 768 768 200 ) map/lab_games/lg_style_01_wall_blue 0 984 0 -0.150323 0.190000 0 0 0 +( 710 294 -8 ) ( 710 294 200 ) ( 416 1004 200 ) map/lab_games/lg_style_01_wall_blue 955 984 0 -0.009434 0.190000 0 0 0 +( 710 -294 -8 ) ( 710 -294 200 ) ( 1004 416 200 ) map/lab_games/lg_style_01_wall_blue 68 984 0 -0.009435 0.190000 0 0 0 +} +// brush 18 +{ +( 710 -294 0 ) ( 710 -294 208 ) ( 1004 416 208 ) map/water_d 0 128 0 0.062500 0.062500 0 0 0 +( 543 -543 0 ) ( 543 -543 208 ) ( 1086 0 208 ) map/water_d 0 128 0 0.062500 0.062500 0 0 0 +( 294 -710 0 ) ( 294 -710 208 ) ( 1004 -416 208 ) map/water_d 0 128 0 0.062500 0.062500 0 0 0 +( 0 -768 0 ) ( 0 -768 208 ) ( 768 -768 208 ) map/water_d 0 128 0 0.062500 0.062500 0 0 0 +( -294 -710 0 ) ( -294 -710 208 ) ( 416 -1004 208 ) map/water_d 0 128 0 0.062500 0.062500 0 0 0 +( -543 -543 0 ) ( -543 -543 208 ) ( 0 -1086 208 ) map/water_d 0 128 0 0.062500 0.062500 0 0 0 +( -710 -294 0 ) ( -710 -294 208 ) ( -416 -1004 208 ) map/water_d 0 128 0 0.062500 0.062500 0 0 0 +( -768 0 0 ) ( -768 0 208 ) ( -768 -768 208 ) map/water_d 0 128 0 0.062500 0.062500 0 0 0 +( -710 294 0 ) ( -710 294 208 ) ( -1004 -416 208 ) map/water_d 0 128 0 0.062500 0.062500 0 0 0 +( -543 543 0 ) ( -543 543 208 ) ( -1086 0 208 ) map/water_d 0 128 0 0.062500 0.062500 0 0 0 +( -294 710 0 ) ( -294 710 208 ) ( -1004 416 208 ) map/water_d 0 128 0 0.062500 0.062500 0 0 0 +( 0 768 0 ) ( 0 768 208 ) ( -768 768 208 ) map/water_d 0 128 0 0.062500 0.062500 0 0 0 +( 294 710 0 ) ( 294 710 208 ) ( -416 1004 208 ) map/water_d 0 128 0 0.062500 0.062500 0 0 0 +( 543 543 0 ) ( 543 543 208 ) ( 0 1086 208 ) map/water_d 0 128 0 0.062500 0.062500 0 0 0 +( 710 294 0 ) ( 710 294 208 ) ( 416 1004 208 ) map/water_d 0 128 0 0.062500 0.062500 0 0 0 +( 768 0 0 ) ( 768 0 208 ) ( 768 768 208 ) map/water_d 0 128 0 0.062500 0.062500 0 0 0 +( -768 -768 0 ) ( 768 -768 0 ) ( 768 768 0 ) map/water_d 0 0 0 0.062500 0.062500 0 0 0 +( 768 -768 16 ) ( -768 -768 16 ) ( 768 768 16 ) map/water_d 0 0 0 0.062500 0.062500 0 0 0 +} +// brush 19 +{ +( 710 -294 0 ) ( 710 -294 208 ) ( 1004 416 208 ) map/water_d 0 0 0 0.500000 0.500000 0 0 0 +( 543 -543 0 ) ( 543 -543 208 ) ( 1086 0 208 ) map/water_d 0 0 0 0.500000 0.500000 0 0 0 +( 294 -710 0 ) ( 294 -710 208 ) ( 1004 -416 208 ) map/water_d 0 0 0 0.500000 0.500000 0 0 0 +( 0 -768 0 ) ( 0 -768 208 ) ( 768 -768 208 ) map/water_d 0 0 0 0.500000 0.500000 0 0 0 +( -294 -710 0 ) ( -294 -710 208 ) ( 416 -1004 208 ) map/water_d 0 0 0 0.500000 0.500000 0 0 0 +( -543 -543 0 ) ( -543 -543 208 ) ( 0 -1086 208 ) map/water_d 0 0 0 0.500000 0.500000 0 0 0 +( -710 -294 0 ) ( -710 -294 208 ) ( -416 -1004 208 ) map/water_d 0 0 0 0.500000 0.500000 0 0 0 +( -768 0 0 ) ( -768 0 208 ) ( -768 -768 208 ) map/water_d 0 0 0 0.500000 0.500000 0 0 0 +( -710 294 0 ) ( -710 294 208 ) ( -1004 -416 208 ) map/water_d 0 0 0 0.500000 0.500000 0 0 0 +( -543 543 0 ) ( -543 543 208 ) ( -1086 0 208 ) map/water_d 0 0 0 0.500000 0.500000 0 0 0 +( -294 710 0 ) ( -294 710 208 ) ( -1004 416 208 ) map/water_d 0 0 0 0.500000 0.500000 0 0 0 +( 0 768 0 ) ( 0 768 208 ) ( -768 768 208 ) map/water_d 0 0 0 0.500000 0.500000 0 0 0 +( 294 710 0 ) ( 294 710 208 ) ( -416 1004 208 ) map/water_d 0 0 0 0.500000 0.500000 0 0 0 +( 543 543 0 ) ( 543 543 208 ) ( 0 1086 208 ) map/water_d 0 0 0 0.500000 0.500000 0 0 0 +( 710 294 0 ) ( 710 294 208 ) ( 416 1004 208 ) map/water_d 0 0 0 0.500000 0.500000 0 0 0 +( 768 0 0 ) ( 768 0 208 ) ( 768 768 208 ) map/water_d 0 0 0 0.500000 0.500000 0 0 0 +( -768 -768 0 ) ( 768 -768 0 ) ( 768 768 0 ) map/water_d 0 0 0 0.500000 0.500000 0 0 0 +( 768 -768 16 ) ( -768 -768 16 ) ( 768 768 16 ) map/water_d 0 0 0 0.500000 0.500000 0 0 0 +} +} +// entity 1 +{ +"light" "500" +"origin" "0 0 168" +"classname" "light" +} +// entity 2 +{ +"classname" "light" +"origin" "0 416 168" +"light" "1500" +} +// entity 3 +{ +"classname" "light" +"origin" "-416 0 168" +"light" "1500" +} +// entity 4 +{ +"light" "1500" +"origin" "416 0 168" +"classname" "light" +} +// entity 5 +{ +"angle" "315" +"origin" "-488 488 24" +"classname" "info_player_start" +} +// entity 6 +{ +"origin" "-728 0 64" +"classname" "info_player_start" +} +// entity 7 +{ +"angle" "45" +"classname" "info_player_start" +"origin" "-496 -496 24" +} +// entity 8 +{ +"angle" "90" +"origin" "0 -712 16" +"classname" "info_player_start" +} +// entity 9 +{ +"angle" "135" +"origin" "504 -504 24" +"classname" "info_player_start" +} +// entity 10 +{ +"angle" "180" +"classname" "info_player_start" +"origin" "712 0 24" +} +// entity 11 +{ +"angle" "225" +"classname" "info_player_start" +"origin" "504 504 24" +} +// entity 12 +{ +"angle" "270" +"classname" "info_player_start" +"origin" "0 720 24" +} +// entity 13 +{ +"classname" "light" +"origin" "0 -416 168" +"light" "1500" +} +// entity 14 +{ +"target" "reward" +"targetname" "timer" +"origin" "-40 136 80" +"classname" "func_timer" +"wait" "0" +"random" "0" +} +// entity 15 +{ +"classname" "target_callback" +"targetname" "reward" +} +// entity 16 +{ +"lip" "56" +"wait" "0.001" +"speed" "700" +"random_platform" "1" +"angle" "-1" +"classname" "func_button" +"target" "timer" +// brush 0 +{ +( 64 64 192 ) ( 64 -64 192 ) ( -64 -64 192 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +( -64 -64 0 ) ( 64 -64 0 ) ( 64 64 0 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +( 64 0 128 ) ( 64 0 192 ) ( 64 64 192 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +( 45 45 128 ) ( 45 45 192 ) ( 0 90 192 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +( 0 64 128 ) ( 0 64 192 ) ( -64 64 192 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +( -45 45 128 ) ( -45 45 192 ) ( -90 0 192 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +( -64 0 128 ) ( -64 0 192 ) ( -64 -64 192 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +( -45 -45 128 ) ( -45 -45 192 ) ( 0 -90 192 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +( 0 -64 128 ) ( 0 -64 192 ) ( 64 -64 192 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +( 45 -45 128 ) ( 45 -45 192 ) ( 90 0 192 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +} +} +// entity 17 +{ +"random_platform" "1" +"classname" "func_plat" +"height" "96" +"dmg" "0" +"targetname" "platform" +"speed" "700" +// brush 0 +{ +( 63 64 94 ) ( 63 -64 94 ) ( -65 -64 94 ) map/fut_utility_panel_01_d 0 0 0 0.500000 0.500000 0 0 0 +( -64 -64 0 ) ( 64 -64 0 ) ( 64 64 0 ) map/fut_utility_panel_01_d 0 0 0 0.500000 0.500000 0 0 0 +( 64 0 0 ) ( 64 0 64 ) ( 64 64 64 ) map/fut_utility_panel_01_d 0 0 0 0.500000 0.500000 0 0 0 +( 45 45 0 ) ( 45 45 64 ) ( 0 90 64 ) map/fut_utility_panel_01_d 0 0 0 0.500000 0.500000 0 0 0 +( 0 64 0 ) ( 0 64 64 ) ( -64 64 64 ) map/fut_utility_panel_01_d 0 0 0 0.500000 0.500000 0 0 0 +( -45 45 0 ) ( -45 45 64 ) ( -90 0 64 ) map/fut_utility_panel_01_d 0 0 0 0.500000 0.500000 0 0 0 +( -64 0 0 ) ( -64 0 64 ) ( -64 -64 64 ) map/fut_utility_panel_01_d 0 0 0 0.500000 0.500000 0 0 0 +( -45 -45 0 ) ( -45 -45 64 ) ( 0 -90 64 ) map/fut_utility_panel_01_d 0 0 0 0.500000 0.500000 0 0 0 +( 0 -64 0 ) ( 0 -64 64 ) ( 64 -64 64 ) map/fut_utility_panel_01_d 0 0 0 0.500000 0.500000 0 0 0 +( 45 -45 0 ) ( 45 -45 64 ) ( 90 0 64 ) map/fut_utility_panel_01_d 0 0 0 0.500000 0.500000 0 0 0 +} +} +// entity 18 +{ +"random_platform" "1" +"classname" "func_button" +"target" "platform" +"wait" "0.25" +"angle" "-2" +"speed" "700" +// brush 0 +{ +( 45 -45 0 ) ( 45 -45 64 ) ( 90 0 64 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +( 0 -64 0 ) ( 0 -64 64 ) ( 64 -64 64 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +( -45 -45 0 ) ( -45 -45 64 ) ( 0 -90 64 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +( -64 0 0 ) ( -64 0 64 ) ( -64 -64 64 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +( -45 45 0 ) ( -45 45 64 ) ( -90 0 64 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +( 0 64 0 ) ( 0 64 64 ) ( -64 64 64 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +( 45 45 0 ) ( 45 45 64 ) ( 0 90 64 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +( 64 0 0 ) ( 64 0 64 ) ( 64 64 64 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +( -64 -64 0 ) ( 64 -64 0 ) ( 64 64 0 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +( 64 64 24 ) ( 64 -64 24 ) ( -64 -64 24 ) map/ghost 0 0 0 0.500000 0.500000 0 12 0 +} +} +// entity 19 +{ +"angle" "270" +"classname" "misc_model" +"origin" "0 -760 96" +"model" "models/fut_ldm_img_frame_arc.md3" +"modelscale" "0.5" +} +// entity 20 +{ +"modelscale" "0.5" +"model" "models/fut_ldm_img_frame_crc.md3" +"origin" "760 0 96" +"classname" "misc_model" +"angle" "360" +} +// entity 21 +{ +"angle" "180" +"classname" "misc_model" +"origin" "-760 0 96" +"model" "models/fut_ldm_img_frame_clc.md3" +"modelscale" "0.5" +} +// entity 22 +{ +"modelscale" "0.5" +"model" "models/fut_ldm_img_frame_sqc.md3" +"origin" "0 760 96" +"classname" "misc_model" +"angle" "90" +} +// entity 23 +{ +"angle" "225" +"classname" "misc_model" +"origin" "-536 -536 96" +"model" "models/fut_ldm_img_frame_hxc.md3" +"modelscale" "0.5" +} +// entity 24 +{ +"modelscale" "0.5" +"model" "models/fut_ldm_img_frame_trc.md3" +"origin" "536 528 96" +"classname" "misc_model" +"angle" "45" +} +// entity 25 +{ +"angle" "135" +"classname" "misc_model" +"origin" "-536 536 96" +"model" "models/fut_ldm_img_frame_dic.md3" +"modelscale" "0.5" +} +// entity 26 +{ +"angle" "315" +"classname" "misc_model" +"origin" "536 -536 96" +"model" "models/fut_ldm_img_frame_stc.md3" +"modelscale" "0.5" +} +// entity 27 +{ +"radius" "200" +"targetname" "platform" +"origin" "0 0 120" +"classname" "func_lua_mover" +"random_platform" "1" +"id" "1" +} diff --git a/assets/maps/lookat_test.map b/assets/maps/lookat_test.map new file mode 100644 index 00000000..ef189672 --- /dev/null +++ b/assets/maps/lookat_test.map @@ -0,0 +1,105 @@ +// entity 0 +{ + "light" "100" + "worldtype" "2" + "classname" "worldspawn" + // brush 0 + { + ( 0 0 1 ) ( 0 1 1 ) ( 1 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.062500 0.062500 0 0 0 + ( 0 0 0 ) ( 1 0 0 ) ( 0 1 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.062500 0.062500 0 0 0 + ( 0 300 0 ) ( 1 300 0 ) ( 0 300 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.062500 0.062500 0 0 0 + ( 0 0 0 ) ( 0 0 1 ) ( 1 0 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.062500 0.062500 0 0 0 + ( 300 0 0 ) ( 300 0 1 ) ( 300 1 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.062500 0.062500 0 0 0 + ( 0 0 0 ) ( 0 1 0 ) ( 0 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.062500 0.062500 0 0 0 + } + // brush 1 + { + ( 0 0 100 ) ( 0 1 100 ) ( 1 0 100 ) map/lab_games/fake_sky 0 0 0 0.062500 0.062500 0 0 0 + ( 0 0 99 ) ( 1 0 99 ) ( 0 1 99 ) map/lab_games/fake_sky 0 0 0 0.062500 0.062500 0 0 0 + ( 0 300 0 ) ( 1 300 0 ) ( 0 300 1 ) map/lab_games/fake_sky 0 0 0 0.062500 0.062500 0 0 0 + ( 0 0 0 ) ( 0 0 1 ) ( 1 0 0 ) map/lab_games/fake_sky 0 0 0 0.062500 0.062500 0 0 0 + ( 300 0 0 ) ( 300 0 1 ) ( 300 1 0 ) map/lab_games/fake_sky 0 0 0 0.062500 0.062500 0 0 0 + ( 0 0 0 ) ( 0 1 0 ) ( 0 0 1 ) map/lab_games/fake_sky 0 0 0 0.062500 0.062500 0 0 0 + } + // brush 2 + { + ( 0 0 100 ) ( 0 1 100 ) ( 1 0 100 ) map/lab_games/lg_style_02_wall_yellow 0 0 0 0.250000 0.250000 0 0 0 + ( 0 0 0 ) ( 1 0 0 ) ( 0 1 0 ) map/lab_games/lg_style_02_wall_yellow 0 0 0 0.250000 0.250000 0 0 0 + ( 0 1 0 ) ( 1 1 0 ) ( 0 1 1 ) map/lab_games/lg_style_02_wall_yellow 0 0 0 0.250000 0.250000 0 0 0 + ( 0 0 0 ) ( 0 0 1 ) ( 1 0 0 ) map/lab_games/lg_style_02_wall_yellow 0 0 0 0.250000 0.250000 0 0 0 + ( 300 0 0 ) ( 300 0 1 ) ( 300 1 0 ) map/lab_games/lg_style_02_wall_yellow 0 0 0 0.250000 0.250000 0 0 0 + ( 0 0 0 ) ( 0 1 0 ) ( 0 0 1 ) map/lab_games/lg_style_02_wall_yellow 0 0 0 0.250000 0.250000 0 0 0 + } + // brush 3 + { + ( 0 0 100 ) ( 0 1 100 ) ( 1 0 100 ) map/lab_games/lg_style_02_wall_yellow 0 0 0 0.250000 0.250000 0 0 0 + ( 0 0 0 ) ( 1 0 0 ) ( 0 1 0 ) map/lab_games/lg_style_02_wall_yellow 0 0 0 0.250000 0.250000 0 0 0 + ( 0 300 0 ) ( 1 300 0 ) ( 0 300 1 ) map/lab_games/lg_style_02_wall_yellow 0 0 0 0.250000 0.250000 0 0 0 + ( 0 299 0 ) ( 0 299 1 ) ( 1 299 0 ) map/lab_games/lg_style_02_wall_yellow 0 0 0 0.250000 0.250000 0 0 0 + ( 300 0 0 ) ( 300 0 1 ) ( 300 1 0 ) map/lab_games/lg_style_02_wall_yellow 0 0 0 0.250000 0.250000 0 0 0 + ( 0 0 0 ) ( 0 1 0 ) ( 0 0 1 ) map/lab_games/lg_style_02_wall_yellow 0 0 0 0.250000 0.250000 0 0 0 + } + // brush 4 + { + ( 0 0 100 ) ( 0 1 100 ) ( 1 0 100 ) map/lab_games/lg_style_02_wall_yellow 0 0 0 0.250000 0.250000 0 0 0 + ( 0 0 0 ) ( 1 0 0 ) ( 0 1 0 ) map/lab_games/lg_style_02_wall_yellow 0 0 0 0.250000 0.250000 0 0 0 + ( 0 300 0 ) ( 1 300 0 ) ( 0 300 1 ) map/lab_games/lg_style_02_wall_yellow 0 0 0 0.250000 0.250000 0 0 0 + ( 0 0 0 ) ( 0 0 1 ) ( 1 0 0 ) map/lab_games/lg_style_02_wall_yellow 0 0 0 0.250000 0.250000 0 0 0 + ( 1 0 0 ) ( 1 0 1 ) ( 1 1 0 ) map/lab_games/lg_style_02_wall_yellow 0 0 0 0.250000 0.250000 0 0 0 + ( 0 0 0 ) ( 0 1 0 ) ( 0 0 1 ) map/lab_games/lg_style_02_wall_yellow 0 0 0 0.250000 0.250000 0 0 0 + } + // brush 5 + { + ( 0 0 100 ) ( 0 1 100 ) ( 1 0 100 ) map/lab_games/lg_style_02_wall_yellow 0 0 0 0.250000 0.250000 0 0 0 + ( 0 0 0 ) ( 1 0 0 ) ( 0 1 0 ) map/lab_games/lg_style_02_wall_yellow 0 0 0 0.250000 0.250000 0 0 0 + ( 0 300 0 ) ( 1 300 0 ) ( 0 300 1 ) map/lab_games/lg_style_02_wall_yellow 0 0 0 0.250000 0.250000 0 0 0 + ( 0 0 0 ) ( 0 0 1 ) ( 1 0 0 ) map/lab_games/lg_style_02_wall_yellow 0 0 0 0.250000 0.250000 0 0 0 + ( 300 0 0 ) ( 300 0 1 ) ( 300 1 0 ) map/lab_games/lg_style_02_wall_yellow 0 0 0 0.250000 0.250000 0 0 0 + ( 299 0 0 ) ( 299 1 0 ) ( 299 0 1 ) map/lab_games/lg_style_02_wall_yellow 0 0 0 0.250000 0.250000 0 0 0 + } +} +// entity 1 +{ + "spawnflags" "0" + "style" "0" + "light" "100.000000" + "origin" "150.000000 150.000000 70.000000" + "classname" "light" +} +// entity 2 +{ + "classname" "info_player_deathmatch" + "angle" "0.0000" + "randomAngleRange" "0" + "origin" "150.000000 150.000000 40.000000" +} +// entity 3 +{ + "targetname" "balloon" + "classname" "misc_model" + "origin" "250.000 150.000 40.000" + "model" "models/hr_balloon.md3" +} +// entity 4 +{ + "count" "1" + "targetname" "reward" + "origin" "250 150 40" + "classname" "target_score" + "max_count" "2" +} +// entity 5 +{ + "classname" "trigger_lookat" + "wait" "0.75" + "target" "reward" + // brush 0 + { + ( 288 168 24 ) ( 264 168 24 ) ( 264 136 24 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 + ( 264 136 64 ) ( 264 168 64 ) ( 288 168 64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 + ( 264 136 32 ) ( 288 136 32 ) ( 288 136 24 ) common/caulk 0 -16 0 0.500000 0.500000 0 4 0 + ( 288 136 32 ) ( 288 168 32 ) ( 288 168 24 ) common/caulk 0 -16 0 0.500000 0.500000 0 4 0 + ( 288 168 32 ) ( 264 168 32 ) ( 264 168 24 ) common/caulk 0 -16 0 0.500000 0.500000 0 4 0 + ( 264 168 32 ) ( 264 136 32 ) ( 264 136 24 ) common/caulk 0 -16 0 0.500000 0.500000 0 4 0 + } +} \ No newline at end of file diff --git a/assets/maps/lt_space_bounce_01.map b/assets/maps/lt_space_bounce_01.map index 90c446e8..bff473f1 100644 --- a/assets/maps/lt_space_bounce_01.map +++ b/assets/maps/lt_space_bounce_01.map @@ -300,24 +300,6 @@ } // brush 33 { -( 1592 320 -480 ) ( 1592 -1472 -480 ) ( 1592 320 -496 ) map/nebula 248 256 0 -5.593750 6.031250 0 0 0 -( 1072 848 -1544 ) ( -928 848 -1544 ) ( -928 -944 -1544 ) map/nebula 0 248 0 -0.015625 5.593750 0 0 0 -( -928 -944 1544 ) ( -928 848 1544 ) ( 1072 848 1544 ) map/nebula 0 248 0 -0.015625 5.593750 0 0 0 -( -400 -1472 128 ) ( 1600 -1472 128 ) ( 1600 -1472 112 ) map/nebula 0 256 0 -0.015625 6.031250 0 0 0 -( 1600 -1472 128 ) ( 1600 320 128 ) ( 1600 320 112 ) map/nebula 248 256 0 -5.593750 6.031250 0 0 0 -( 552 1392 128 ) ( -1448 1392 128 ) ( -1448 1392 112 ) map/nebula 0 256 0 -0.015625 6.031250 0 0 0 -} -// brush 34 -{ -( 1600 -1464 128 ) ( -400 -1464 128 ) ( 1600 -1464 112 ) map/nebula 268 256 0 -5.953125 6.031250 0 0 0 -( 1072 848 -1544 ) ( -928 848 -1544 ) ( -928 -944 -1544 ) map/nebula 268 0 0 -5.953125 0.015625 0 0 0 -( -928 -944 1544 ) ( -928 848 1544 ) ( 1072 848 1544 ) map/nebula 268 0 0 -5.953125 0.015625 0 0 0 -( -400 -1472 128 ) ( 1600 -1472 128 ) ( 1600 -1472 112 ) map/nebula 268 256 0 -5.953125 6.031250 0 0 0 -( 1600 -1472 128 ) ( 1600 320 128 ) ( 1600 320 112 ) map/nebula 0 256 0 -0.015625 6.031250 0 0 0 -( -1448 1392 128 ) ( -1448 -400 128 ) ( -1448 -400 112 ) map/nebula 0 256 0 -0.015625 6.031250 0 0 0 -} -// brush 35 -{ ( -928 848 -1536 ) ( 1072 848 -1536 ) ( -928 -944 -1536 ) map/nebula 268 248 0 -5.953125 5.593750 0 0 0 ( 1072 848 -1544 ) ( -928 848 -1544 ) ( -928 -944 -1544 ) map/nebula 268 248 0 -5.953125 5.593750 0 0 0 ( -400 -1472 -1256 ) ( 1600 -1472 -1256 ) ( 1600 -1472 -1272 ) map/nebula 268 0 0 -5.953125 0.015625 0 0 0 @@ -325,7 +307,7 @@ ( 552 1392 -1256 ) ( -1448 1392 -1256 ) ( -1448 1392 -1272 ) map/nebula 268 0 0 -5.953125 0.015625 0 0 0 ( -1448 1392 -1256 ) ( -1448 -400 -1256 ) ( -1448 -400 -1272 ) map/nebula 248 0 0 -5.593750 0.015625 0 0 0 } -// brush 36 +// brush 34 { ( 1816 -904 -168 ) ( 2336 -904 -168 ) ( 1816 -1376 -168 ) map/nebula_dn 251 42 0 -1.015625 0.921875 0 0 0 ( 1816 -904 200 ) ( 1816 -1376 200 ) ( 1816 -1376 184 ) map/nebula_dn 42 0 0 -0.921875 0.015625 0 0 0 @@ -334,7 +316,7 @@ ( 1816 -1376 200 ) ( 2336 -1376 200 ) ( 2336 -1376 184 ) map/nebula_dn 251 0 0 -1.015625 0.015625 0 0 0 ( 2336 -904 -176 ) ( 1816 -904 -176 ) ( 1816 -1376 -176 ) map/nebula_dn 251 42 0 -1.015625 0.921875 0 0 0 } -// brush 37 +// brush 35 { ( 1816 -904 192 ) ( 1816 -1376 192 ) ( 2336 -904 192 ) map/nebula_up 251 42 0 -1.015625 0.921875 0 0 0 ( 1816 -904 200 ) ( 1816 -1376 200 ) ( 1816 -1376 184 ) map/nebula_up 42 0 0 -0.921875 0.015625 0 0 0 @@ -343,7 +325,7 @@ ( 1816 -1376 200 ) ( 2336 -1376 200 ) ( 2336 -1376 184 ) map/nebula_up 251 0 0 -1.015625 0.015625 0 0 0 ( 1816 -1376 200 ) ( 1816 -904 200 ) ( 2336 -904 200 ) map/nebula_up 251 42 0 -1.015625 0.921875 0 0 0 } -// brush 38 +// brush 36 { ( 2336 -1368 200 ) ( 1816 -1368 200 ) ( 2336 -1368 184 ) map/nebula_bk 251 272 0 -1.015625 0.734375 0 0 0 ( 1816 -904 200 ) ( 1816 -1376 200 ) ( 1816 -1376 184 ) map/nebula_bk 0 272 0 -0.015625 0.734375 0 0 0 @@ -352,7 +334,7 @@ ( 1816 -1376 200 ) ( 1816 -904 200 ) ( 2336 -904 200 ) map/nebula_bk 251 0 0 -1.015625 0.015625 0 0 0 ( 2336 -904 -176 ) ( 1816 -904 -176 ) ( 1816 -1376 -176 ) map/nebula_bk 251 0 0 -1.015625 0.015625 0 0 0 } -// brush 39 +// brush 37 { ( 2328 -904 200 ) ( 2328 -1376 200 ) ( 2328 -904 184 ) map/nebula_rt 42 272 0 -0.921875 0.734375 0 0 0 ( 2336 -904 200 ) ( 1816 -904 200 ) ( 1816 -904 184 ) map/nebula_rt 0 272 0 -0.015625 0.734375 0 0 0 @@ -361,7 +343,7 @@ ( 1816 -1376 200 ) ( 1816 -904 200 ) ( 2336 -904 200 ) map/nebula_rt 0 42 0 -0.015625 0.921875 0 0 0 ( 2336 -904 -176 ) ( 1816 -904 -176 ) ( 1816 -1376 -176 ) map/nebula_rt 0 42 0 -0.015625 0.921875 0 0 0 } -// brush 40 +// brush 38 { ( 1816 -912 200 ) ( 2336 -912 200 ) ( 1816 -912 184 ) map/nebula_ft 251 272 0 -1.015625 0.734375 0 0 0 ( 1816 -904 200 ) ( 1816 -1376 200 ) ( 1816 -1376 184 ) map/nebula_ft 0 272 0 -0.015625 0.734375 0 0 0 @@ -370,7 +352,7 @@ ( 1816 -1376 200 ) ( 1816 -904 200 ) ( 2336 -904 200 ) map/nebula_ft 251 0 0 -1.015625 0.015625 0 0 0 ( 2336 -904 -176 ) ( 1816 -904 -176 ) ( 1816 -1376 -176 ) map/nebula_ft 251 0 0 -1.015625 0.015625 0 0 0 } -// brush 41 +// brush 39 { ( 1824 -1376 200 ) ( 1824 -904 200 ) ( 1824 -1376 184 ) map/nebula_lf 42 272 0 -0.921875 0.734375 0 0 0 ( 1816 -904 200 ) ( 1816 -1376 200 ) ( 1816 -1376 184 ) map/nebula_lf 42 272 0 -0.921875 0.734375 0 0 0 @@ -379,7 +361,7 @@ ( 1816 -1376 200 ) ( 1816 -904 200 ) ( 2336 -904 200 ) map/nebula_lf 0 42 0 -0.015625 0.921875 0 0 0 ( 2336 -904 -176 ) ( 1816 -904 -176 ) ( 1816 -1376 -176 ) map/nebula_lf 0 42 0 -0.015625 0.921875 0 0 0 } -// brush 42 +// brush 40 { ( -1048 992 1432 ) ( -1064 992 1432 ) ( -1064 984 1432 ) common/caulk 0 0 0 0.031200 0.031200 0 0 0 ( -1064 984 1448 ) ( -1064 992 1448 ) ( -1048 992 1448 ) common/caulk 0 0 0 0.031200 0.031200 0 0 0 @@ -388,16 +370,7 @@ ( -1048 992 1448 ) ( -1064 992 1448 ) ( -1064 992 1432 ) common/caulk 0 0 0 0.031200 0.031200 0 0 0 ( -1064 992 1448 ) ( -1064 984 1448 ) ( -1064 984 1432 ) common/caulk 0 0 0 0.031200 0.031200 0 0 0 } -// brush 43 -{ -( -1448 1392 984 ) ( -1448 -400 984 ) ( -1448 -400 968 ) map/nebula 248 0 0 -5.593750 0.015625 0 0 0 -( 552 1392 984 ) ( -1448 1392 984 ) ( -1448 1392 968 ) map/nebula 268 0 0 -5.953125 0.015625 0 0 0 -( 1600 -1472 984 ) ( 1600 320 984 ) ( 1600 320 968 ) map/nebula 248 0 0 -5.593750 0.015625 0 0 0 -( -400 -1472 984 ) ( 1600 -1472 984 ) ( 1600 -1472 968 ) map/nebula 268 0 0 -5.953125 0.015625 0 0 0 -( -928 -944 1544 ) ( -928 848 1544 ) ( 1072 848 1544 ) map/nebula 268 248 0 -5.953125 5.593750 0 0 0 -( -928 848 1536 ) ( -928 -944 1536 ) ( 1072 848 1536 ) map/nebula 268 248 0 -5.953125 5.593750 0 0 0 -} -// brush 44 +// brush 41 { ( 320 256 88 ) ( 320 -256 88 ) ( 320 -256 56 ) map/green_glow 0 0 0 0.031200 0.031200 0 0 0 ( 336 256 80 ) ( 320 256 80 ) ( 320 256 48 ) map/green_glow 0 0 0 0.031200 0.031200 0 0 0 @@ -406,7 +379,7 @@ ( 320 -256 88 ) ( 320 256 88 ) ( 336 256 88 ) map/green_glow 0 0 0 0.031200 0.031200 0 0 0 ( 336 256 48 ) ( 320 256 48 ) ( 320 -256 48 ) map/green_glow 0 0 0 0.031200 0.031200 0 0 0 } -// brush 45 +// brush 42 { ( -272 272 64 ) ( -272 256 64 ) ( -272 256 48 ) map/green_glow 0 0 0 0.031200 0.031200 0 0 0 ( 336 272 64 ) ( -272 272 64 ) ( -272 272 48 ) map/green_glow 0 0 0 0.031200 0.031200 0 0 0 @@ -415,7 +388,7 @@ ( -272 256 88 ) ( -272 272 88 ) ( 336 272 88 ) map/green_glow 0 0 0 0.031200 0.031200 0 0 0 ( 336 272 48 ) ( -272 272 48 ) ( -272 256 48 ) map/green_glow 0 0 0 0.031200 0.031200 0 0 0 } -// brush 46 +// brush 43 { ( -272 -256 64 ) ( -272 -272 64 ) ( -272 -272 48 ) map/green_glow 0 0 0 0.031200 0.031200 0 0 0 ( 336 -256 64 ) ( -272 -256 64 ) ( -272 -256 48 ) map/green_glow 0 0 0 0.031200 0.031200 0 0 0 @@ -424,7 +397,7 @@ ( -272 -272 88 ) ( -272 -256 88 ) ( 336 -256 88 ) map/green_glow 0 0 0 0.031200 0.031200 0 0 0 ( 336 -256 48 ) ( -272 -256 48 ) ( -272 -272 48 ) map/green_glow 0 0 0 0.031200 0.031200 0 0 0 } -// brush 47 +// brush 44 { ( -272 256 64 ) ( -272 -256 64 ) ( -272 -256 48 ) map/green_glow 0 0 0 0.031200 0.031200 0 0 0 ( -256 256 64 ) ( -272 256 64 ) ( -272 256 48 ) map/green_glow 0 0 0 0.031200 0.031200 0 0 0 @@ -433,7 +406,7 @@ ( -272 -256 88 ) ( -272 256 88 ) ( -256 256 88 ) map/green_glow 0 0 0 0.031200 0.031200 0 0 0 ( -256 256 48 ) ( -272 256 48 ) ( -272 -256 48 ) map/green_glow 0 0 0 0.031200 0.031200 0 0 0 } -// brush 48 +// brush 45 { ( -1440 1384 -120 ) ( -1440 -1440 -120 ) ( -1440 -1440 -128 ) map/nodrop 0 0 0 0.031200 0.031200 0 0 0 ( 1576 1384 -120 ) ( -1440 1384 -120 ) ( -1440 1384 -128 ) map/nodrop 0 0 0 0.031200 0.031200 0 0 0 @@ -442,6 +415,33 @@ ( -1424 -1456 -80 ) ( -1424 1368 -80 ) ( 1592 1368 -80 ) map/nodrop 0 0 0 0.031200 0.031200 0 0 0 ( 1592 1368 -1536 ) ( -1424 1368 -1536 ) ( -1424 -1456 -1536 ) map/nodrop 0 0 0 0.031200 0.031200 0 0 0 } +// brush 46 +{ +( -1448 1392 128 ) ( -1448 -400 128 ) ( -1448 -400 112 ) map/nebula 0 256 0 -0.015625 6.031250 0 0 0 +( 1600 -1472 128 ) ( 1600 320 128 ) ( 1600 320 112 ) map/nebula 0 256 0 -0.015625 6.031250 0 0 0 +( -400 -1472 128 ) ( 1600 -1472 128 ) ( 1600 -1472 112 ) map/nebula 268 256 0 -5.953125 6.031250 0 0 0 +( -928 -944 1544 ) ( -928 848 1544 ) ( 1072 848 1544 ) map/nebula 268 0 0 -5.953125 0.015625 0 0 0 +( 1072 848 -1544 ) ( -928 848 -1544 ) ( -928 -944 -1544 ) map/nebula 268 0 0 -5.953125 0.015625 0 0 0 +( 1600 -1464 128 ) ( -400 -1464 128 ) ( 1600 -1464 112 ) map/nebula 268 256 0 -5.953125 6.031250 0 0 0 +} +// brush 47 +{ +( -928 848 1536 ) ( -928 -944 1536 ) ( 1072 848 1536 ) map/nebula 268 248 0 -5.953125 5.593750 0 0 0 +( -928 -944 1544 ) ( -928 848 1544 ) ( 1072 848 1544 ) map/nebula 268 248 0 -5.953125 5.593750 0 0 0 +( -400 -1472 984 ) ( 1600 -1472 984 ) ( 1600 -1472 968 ) map/nebula 268 0 0 -5.953125 0.015625 0 0 0 +( 1600 -1472 984 ) ( 1600 320 984 ) ( 1600 320 968 ) map/nebula 248 0 0 -5.593750 0.015625 0 0 0 +( 552 1392 984 ) ( -1448 1392 984 ) ( -1448 1392 968 ) map/nebula 268 0 0 -5.953125 0.015625 0 0 0 +( -1448 1392 984 ) ( -1448 -400 984 ) ( -1448 -400 968 ) map/nebula 248 0 0 -5.593750 0.015625 0 0 0 +} +// brush 48 +{ +( 1592 320 -480 ) ( 1592 -1472 -480 ) ( 1592 320 -496 ) map/nebula 248 256 0 -5.593750 6.031250 0 0 0 +( 1072 848 -1544 ) ( -928 848 -1544 ) ( -928 -944 -1544 ) map/nebula 0 248 0 -0.015625 5.593750 0 0 0 +( -928 -944 1544 ) ( -928 848 1544 ) ( 1072 848 1544 ) map/nebula 0 248 0 -0.015625 5.593750 0 0 0 +( -400 -1472 128 ) ( 1600 -1472 128 ) ( 1600 -1472 112 ) map/nebula 0 256 0 -0.015625 6.031250 0 0 0 +( 1600 -1472 128 ) ( 1600 320 128 ) ( 1600 320 112 ) map/nebula 248 256 0 -5.593750 6.031250 0 0 0 +( 552 1392 128 ) ( -1448 1392 128 ) ( -1448 1392 112 ) map/nebula 0 256 0 -0.015625 6.031250 0 0 0 +} } // entity 1 { @@ -471,12 +471,6 @@ } // entity 4 { -"classname" "target_position" -"origin" "-192 -520 368" -"targetname" "landing2" -} -// entity 5 -{ "target" "landing21" "classname" "trigger_push" // brush 0 @@ -489,13 +483,13 @@ ( 216 -176 120 ) ( 216 -288 120 ) ( 216 -288 64 ) common/caulk -20 0 0 0.031200 0.031200 0 0 0 } } -// entity 6 +// entity 5 { "targetname" "landing21" -"origin" "336 -280 632" +"origin" "432 -376 672" "classname" "target_position" } -// entity 7 +// entity 6 { "target" "landing3" "classname" "trigger_push" @@ -509,13 +503,7 @@ ( -216 -728 416 ) ( -216 -840 416 ) ( -216 -840 360 ) common/caulk -56 15 0 0.031200 0.031200 0 0 0 } } -// entity 8 -{ -"targetname" "landing3" -"origin" "208 -760 632" -"classname" "target_position" -} -// entity 9 +// entity 7 { "target" "landing5" "classname" "trigger_push" @@ -529,13 +517,13 @@ ( -320 -480 360 ) ( -496 -480 360 ) ( -496 -592 360 ) common/caulk -14 -59 0 0.031200 0.031200 0 0 0 } } -// entity 10 +// entity 8 { "classname" "target_position" "origin" "-280 -320 576" "targetname" "landing5" } -// entity 11 +// entity 9 { "target" "landing7" "classname" "trigger_push" @@ -549,13 +537,13 @@ ( 520 672 488 ) ( 520 560 488 ) ( 520 560 432 ) common/caulk 0 -45 0 0.031200 0.031200 0 0 0 } } -// entity 12 +// entity 10 { "targetname" "landing7" -"origin" "416 640 624" +"origin" "368 640 576" "classname" "target_position" } -// entity 13 +// entity 11 { "classname" "trigger_push" "target" "landing8" @@ -569,13 +557,13 @@ ( -464 528 712 ) ( -640 528 712 ) ( -640 416 712 ) common/caulk 56 -6 0 0.031200 0.031200 0 0 0 } } -// entity 14 +// entity 12 { "classname" "target_position" "origin" "-560 368 784" "targetname" "landing8" } -// entity 15 +// entity 13 { "target" "landing10" "classname" "trigger_push" @@ -589,13 +577,7 @@ ( 752 -368 600 ) ( 752 -480 600 ) ( 752 -480 544 ) common/caulk 52 24 0 0.031200 0.031200 0 0 0 } } -// entity 16 -{ -"targetname" "landing10" -"origin" "784 -232 640" -"classname" "target_position" -} -// entity 17 +// entity 14 { "classname" "trigger_push" "target" "landing14" @@ -609,13 +591,13 @@ ( 320 160 96 ) ( 144 160 96 ) ( 144 48 96 ) common/caulk 17 36 0 0.031200 0.031200 0 0 0 } } -// entity 18 +// entity 15 { "classname" "target_position" -"origin" "480 128 456" +"origin" "584 128 544" "targetname" "landing14" } -// entity 19 +// entity 16 { "target" "landing15" "classname" "trigger_push" @@ -629,13 +611,7 @@ ( -240 56 128 ) ( -240 -56 128 ) ( -240 -56 72 ) common/caulk 33 0 0 0.031200 0.031200 0 0 0 } } -// entity 20 -{ -"targetname" "landing15" -"origin" "-320 24 312" -"classname" "target_position" -} -// entity 21 +// entity 17 { "classname" "trigger_push" "target" "landing13" @@ -649,13 +625,13 @@ ( 184 608 504 ) ( 8 608 504 ) ( 8 496 504 ) common/caulk -41 60 0 0.031200 0.031200 0 0 0 } } -// entity 22 +// entity 18 { "classname" "target_position" -"origin" "-104 576 704" +"origin" "-152 576 784" "targetname" "landing13" } -// entity 23 +// entity 19 { "classname" "trigger_push" "target" "landing12" @@ -669,13 +645,7 @@ ( -544 136 288 ) ( -720 136 288 ) ( -720 24 288 ) common/caulk 61 35 0 0.031200 0.031200 0 0 0 } } -// entity 24 -{ -"classname" "target_position" -"origin" "-672 416 792" -"targetname" "landing12" -} -// entity 25 +// entity 20 { "target" "landing22" "classname" "trigger_push" @@ -689,38 +659,32 @@ ( -576 -160 352 ) ( -576 -272 352 ) ( -576 -272 296 ) common/caulk -22 -51 0 0.031200 0.031200 0 0 0 } } -// entity 26 -{ -"classname" "target_position" -"origin" "-544 -352 424" -"targetname" "landing22" -} -// entity 27 +// entity 21 { "classname" "info_player_start" "origin" "712 -608 544" } -// entity 28 +// entity 22 { "origin" "712 240 432" "classname" "info_player_start" } -// entity 29 +// entity 23 { "classname" "info_player_start" "origin" "136 448 512" } -// entity 30 +// entity 24 { "origin" "-432 688 712" "classname" "info_player_start" } -// entity 31 +// entity 25 { "classname" "info_player_start" "origin" "-552 -48 296" } -// entity 32 +// entity 26 { "target" "landing16" "classname" "trigger_push" @@ -734,145 +698,145 @@ ( 576 168 480 ) ( 576 280 480 ) ( 576 280 424 ) common/caulk 39 -46 -180 0.031219 -0.031204 0 0 0 } } -// entity 33 +// entity 27 { "targetname" "landing16" "origin" "408 200 536" "classname" "target_position" } -// entity 34 +// entity 28 { "classname" "light" "origin" "88 0 1152" "light" "5000" } -// entity 35 +// entity 29 { "origin" "192 -24 88" "classname" "weapon_lightning" } -// entity 36 +// entity 30 { "classname" "misc_model" "origin" "544 200 432" "model" "models/bounce_pad.md3" "angle" "270" } -// entity 37 +// entity 31 { "angle" "270" "model" "models/bounce_pad.md3" "origin" "552 640 432" "classname" "misc_model" } -// entity 38 +// entity 32 { "angle" "270" "model" "models/bounce_pad.md3" "origin" "88 576 512" "classname" "misc_model" } -// entity 39 +// entity 33 { "angle" "180" "model" "models/bounce_pad.md3" "origin" "784 -400 544" "classname" "misc_model" } -// entity 40 +// entity 34 { "angle" "45" "model" "models/bounce_pad.md3" "origin" "248 -200 88" "classname" "misc_model" } -// entity 41 +// entity 35 { "angle" "360" "model" "models/bounce_pad.md3" "origin" "-192 -192 88" "classname" "misc_model" } -// entity 42 +// entity 36 { "angle" "90" "model" "models/bounce_pad.md3" "origin" "224 128 88" "classname" "misc_model" } -// entity 43 +// entity 37 { "angle" "270" "model" "models/bounce_pad.md3" "origin" "-208 24 88" "classname" "misc_model" } -// entity 44 +// entity 38 { "angle" "360" "model" "models/bounce_pad.md3" "origin" "-544 -192 296" "classname" "misc_model" } -// entity 45 +// entity 39 { "angle" "360" "model" "models/bounce_pad.md3" "origin" "-560 496 712" "classname" "misc_model" } -// entity 46 +// entity 40 { "angle" "90" "model" "models/bounce_pad.md3" "origin" "-184 -760 360" "classname" "misc_model" } -// entity 47 +// entity 41 { "angle" "135" "model" "models/bounce_pad.md3" "origin" "-416 -512 360" "classname" "misc_model" } -// entity 48 +// entity 42 { "angle" "180" "model" "models/bounce_pad.md3" "origin" "-640 104 296" "classname" "misc_model" } -// entity 49 +// entity 43 { "classname" "weapon_lightning" "origin" "720 152 424" } -// entity 50 +// entity 44 { "origin" "144 528 504" "classname" "weapon_lightning" } -// entity 51 +// entity 45 { "classname" "weapon_lightning" "origin" "-592 648 704" } -// entity 52 +// entity 46 { "origin" "-552 8 288" "classname" "weapon_lightning" } -// entity 53 +// entity 47 { "classname" "weapon_lightning" "origin" "-416 -624 352" } -// entity 54 +// entity 48 { "origin" "936 -736 536" "classname" "weapon_lightning" } -// entity 55 +// entity 49 { "classname" "trigger_push" "target" "landing20" @@ -886,81 +850,81 @@ ( 480 -240 424 ) ( 656 -240 424 ) ( 656 -128 424 ) common/caulk 3 37 -180 0.031189 0.031219 0 0 0 } } -// entity 56 +// entity 50 { "classname" "target_position" -"origin" "576 -256 536" +"origin" "576 -384 632" "targetname" "landing20" } -// entity 57 +// entity 51 { "angle" "360" "model" "models/bounce_pad.md3" "origin" "576 -208 432" "classname" "misc_model" } -// entity 58 +// entity 52 { "origin" "2056 -1136 24" "classname" "_skybox" } -// entity 59 +// entity 53 { "_color" "0.3 1 0.3" "light" "2400" "origin" "48 56 248" "classname" "light" } -// entity 60 +// entity 54 { "_color" "0 0 1" "light" "800" "origin" "128 568 608" "classname" "light" } -// entity 61 +// entity 55 { "classname" "light" "origin" "720 440 576" "light" "800" "_color" "0.5 0 1" } -// entity 62 +// entity 56 { "_color" "0.5 0 1" "light" "800" "origin" "704 0 576" "classname" "light" } -// entity 63 +// entity 57 { "classname" "light" "origin" "720 -600 776" "light" "800" "_color" "1 0.3 0" } -// entity 64 +// entity 58 { "_color" "0 1 1" "light" "800" "origin" "-400 -640 488" "classname" "light" } -// entity 65 +// entity 59 { "classname" "light" "origin" "-544 -8 408" "light" "800" "_color" "1 0 0" } -// entity 66 +// entity 60 { "classname" "light" "origin" "-488 656 904" "light" "800" "_color" "1 1 0.4" } -// entity 67 +// entity 61 { "speed" "55" "targetname" "ufo" @@ -975,7 +939,7 @@ ( -80 1216 1520 ) ( -80 1016 1520 ) ( -80 1016 1480 ) map/ghost 0 0 0 0.031200 0.031200 0 0 0 } } -// entity 68 +// entity 62 { "speed" "75" "_remap" "*;textures/model/mothership_d" @@ -985,7 +949,7 @@ "origin" "360 1136 1432" "classname" "misc_model" } -// entity 69 +// entity 63 { "classname" "misc_model" "origin" "80 -520 1136" @@ -994,7 +958,7 @@ "target" "ufo2" "_remap" "*;textures/model/mothership_d" } -// entity 70 +// entity 64 { "targetname" "ufo3" "classname" "func_rotating" @@ -1009,7 +973,7 @@ ( -880 -272 -512 ) ( -880 -472 -512 ) ( -880 -472 -552 ) map/ghost -52 -104 0 0.031200 0.031200 0 0 0 } } -// entity 71 +// entity 65 { "target" "ufo3" "_remap" "*;textures/model/mothership_d" @@ -1018,7 +982,7 @@ "origin" "-440 -352 -600" "classname" "misc_model" } -// entity 72 +// entity 66 { "target" "ufo4" "speed" "45" @@ -1034,7 +998,7 @@ ( 328 456 -1272 ) ( 144 456 -1272 ) ( 144 256 -1272 ) map/ghost 117 -39 0 0.031200 0.031200 0 0 0 } } -// entity 73 +// entity 67 { "origin" "584 376 -1192" "targetname" "ufo4" @@ -1043,12 +1007,12 @@ "modelscale" "3" "_remap" "*;textures/model/mothership_d" } -// entity 74 +// entity 68 { "origin" "104 112 136" "classname" "misc_model" } -// entity 75 +// entity 69 { "classname" "func_rotating" "targetname" "ufo2" @@ -1063,48 +1027,43 @@ ( 464 -416 1072 ) ( 280 -416 1072 ) ( 280 -616 1072 ) map/ghost 109 -83 0 0.031200 0.031200 0 0 0 } } -// entity 76 +// entity 70 { "classname" "info_player_start" "origin" "-488 -672 364" } -// entity 77 +// entity 71 { "origin" "-224 -624 364" "classname" "info_player_start" } -// entity 78 -{ -"origin" "984 -480 544" -"classname" "info_player_start" -} -// entity 79 +// entity 72 { "classname" "info_player_start" "origin" "840 632 432" } -// entity 80 +// entity 73 { "classname" "info_player_start" "origin" "-800 640 712" } -// entity 81 +// entity 74 { "origin" "-648 -200 296" "classname" "info_player_start" } -// entity 82 +// entity 75 { "classname" "info_player_start" "origin" "0 -216 88" "angle" "90" } -// entity 83 +// entity 76 { "origin" "280 576 512" "classname" "info_player_start" } -// entity 84 +// entity 77 { "wait" "0.00001" "classname" "trigger_multiple" @@ -1119,3 +1078,44 @@ ( -1440 1576 -120 ) ( -1440 -216 -120 ) ( -1440 -216 -136 ) map/poltergeist 0 0 0 0.031200 0.031200 0 0 0 } } +// entity 78 +{ +"origin" "984 -480 544" +"classname" "info_player_start" +} +// entity 79 +{ +"targetname" "landing15" +"origin" "-448 24 336" +"classname" "target_position" +} +// entity 80 +{ +"targetname" "landing22" +"origin" "-544 -424 464" +"classname" "target_position" +} +// entity 81 +{ +"targetname" "landing2" +"origin" "-192 -480 424" +"classname" "target_position" +} +// entity 82 +{ +"classname" "target_position" +"origin" "-640 544 840" +"targetname" "landing12" +} +// entity 83 +{ +"classname" "target_position" +"origin" "320 -760 680" +"targetname" "landing3" +} +// entity 84 +{ +"classname" "target_position" +"origin" "784 -280 648" +"targetname" "landing10" +} diff --git a/assets/maps/probabilistic_fruit_01.map b/assets/maps/probabilistic_fruit_01.map new file mode 100644 index 00000000..361eea4a --- /dev/null +++ b/assets/maps/probabilistic_fruit_01.map @@ -0,0 +1,852 @@ +// entity 0 +{ +"classname" "worldspawn" +// brush 0 +{ +( 344 384 192 ) ( -384 384 192 ) ( -384 -144 192 ) map/lab_games/sky/lg_sky_01 0 0 0 0.062500 0.062500 0 0 0 +( -384 -144 200 ) ( -384 384 200 ) ( 344 384 200 ) map/lab_games/sky/lg_sky_01 0 0 0 0.062500 0.062500 0 0 0 +( -408 -256 320 ) ( 320 -256 320 ) ( 320 -256 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.062500 0.062500 0 0 0 +( 128 -176 320 ) ( 128 352 320 ) ( 128 352 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.062500 0.062500 0 0 0 +( 328 320 320 ) ( -400 320 320 ) ( -400 320 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.062500 0.062500 0 0 0 +( -320 376 320 ) ( -320 -152 320 ) ( -320 -152 0 ) map/lab_games/sky/lg_sky_01 0 0 0 0.062500 0.062500 0 0 0 +} +// brush 1 +{ +( -320 -88 0 ) ( -336 -88 0 ) ( -336 -384 0 ) map/lab_games/lg_style_01_wall_green 0 568 0 -0.005208 0.187500 0 0 0 +( -336 -384 192 ) ( -336 -88 192 ) ( -320 -88 192 ) map/lab_games/lg_style_01_wall_green 0 568 0 -0.005208 0.187500 0 0 0 +( -328 -256 200 ) ( -312 -256 200 ) ( -312 -256 192 ) map/lab_games/lg_style_01_wall_green 0 0 0 -0.005208 0.187500 0 0 0 +( -320 -368 200 ) ( -320 -72 200 ) ( -320 -72 192 ) map/lab_games/lg_style_01_wall_green 1706 0 0 -0.187500 0.187500 0 0 0 +( -320 320 200 ) ( -336 320 200 ) ( -336 320 192 ) map/lab_games/lg_style_01_wall_green 0 0 0 -0.005208 0.187500 0 0 0 +( -336 -104 200 ) ( -336 -400 200 ) ( -336 -400 192 ) map/lab_games/lg_style_01_wall_green 1706 0 0 -0.187500 0.187500 0 0 0 +} +// brush 2 +{ +( 128 -88 200 ) ( 128 -384 200 ) ( 128 -384 192 ) map/lab_games/lg_style_01_wall_green 1706 0 0 -0.187500 0.187500 0 0 0 +( 144 320 200 ) ( 128 320 200 ) ( 128 320 192 ) map/lab_games/lg_style_01_wall_green 0 0 0 -0.005208 0.187500 0 0 0 +( 144 -448 200 ) ( 144 -152 200 ) ( 144 -152 192 ) map/lab_games/lg_style_01_wall_green 1706 0 0 -0.187500 0.187500 0 0 0 +( 144 -256 200 ) ( 160 -256 200 ) ( 160 -256 192 ) map/lab_games/lg_style_01_wall_green 0 0 0 -0.005208 0.187500 0 0 0 +( 128 -384 192 ) ( 128 -88 192 ) ( 144 -88 192 ) map/lab_games/lg_style_01_wall_green 0 568 0 -0.005208 0.187500 0 0 0 +( 144 -88 0 ) ( 128 -88 0 ) ( 128 -384 0 ) map/lab_games/lg_style_01_wall_green 0 568 0 -0.005208 0.187500 0 0 0 +} +// brush 3 +{ +( 256 336 0 ) ( -320 336 0 ) ( -320 320 0 ) map/lab_games/lg_style_01_wall_green 585 0 0 -0.218750 0.187500 0 0 0 +( -320 320 192 ) ( -320 336 192 ) ( 256 336 192 ) map/lab_games/lg_style_01_wall_green 585 0 0 -0.218750 0.187500 0 0 0 +( -448 320 192 ) ( 128 320 192 ) ( 128 320 0 ) map/lab_games/lg_style_01_wall_green 585 0 0 -0.218750 0.187500 0 0 0 +( 128 320 192 ) ( 128 336 192 ) ( 128 336 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 -0.007812 0.187500 0 0 0 +( 256 336 192 ) ( -320 336 192 ) ( -320 336 0 ) map/lab_games/lg_style_01_wall_green 585 0 0 -0.218750 0.187500 0 0 0 +( -320 336 192 ) ( -320 320 192 ) ( -320 320 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 -0.007812 0.187500 0 0 0 +} +// brush 4 +{ +( 256 -256 0 ) ( -320 -256 0 ) ( -320 -272 0 ) map/lab_games/lg_style_01_wall_green 585 0 0 -0.218750 0.187500 0 0 0 +( -320 -272 192 ) ( -320 -256 192 ) ( 256 -256 192 ) map/lab_games/lg_style_01_wall_green 585 0 0 -0.218750 0.187500 0 0 0 +( -320 -272 192 ) ( 256 -272 192 ) ( 256 -272 0 ) map/lab_games/lg_style_01_wall_green 585 0 0 -0.218750 0.187500 0 0 0 +( 128 -272 184 ) ( 128 -256 184 ) ( 128 -256 -8 ) map/lab_games/lg_style_01_wall_green 0 0 0 -0.007812 0.187500 0 0 0 +( 256 -256 192 ) ( -320 -256 192 ) ( -320 -256 0 ) map/lab_games/lg_style_01_wall_green 585 0 0 -0.218750 0.187500 0 0 0 +( -320 -256 184 ) ( -320 -272 184 ) ( -320 -272 -8 ) map/lab_games/lg_style_01_wall_green 0 0 0 -0.007812 0.187500 0 0 0 +} +// brush 5 +{ +( -256 320 -24 ) ( -320 320 -24 ) ( -320 -256 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -320 -256 0 ) ( -320 320 0 ) ( -256 320 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -320 -256 0 ) ( -256 -256 0 ) ( -256 -256 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -256 -256 -16 ) ( -256 320 -16 ) ( -256 320 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -256 320 0 ) ( -320 320 0 ) ( -320 320 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -320 320 0 ) ( -320 -256 0 ) ( -320 -256 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 6 +{ +( -192 320 -16 ) ( -192 -256 -16 ) ( -192 -256 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -128 320 0 ) ( -192 320 0 ) ( -192 320 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -128 -256 -16 ) ( -128 320 -16 ) ( -128 320 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -192 -256 0 ) ( -128 -256 0 ) ( -128 -256 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -192 -256 0 ) ( -192 320 0 ) ( -128 320 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -128 320 -24 ) ( -192 320 -24 ) ( -192 -256 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 7 +{ +( 0 320 -24 ) ( -64 320 -24 ) ( -64 -256 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -64 -256 0 ) ( -64 320 0 ) ( 0 320 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -64 -256 0 ) ( 0 -256 0 ) ( 0 -256 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 0 -256 -16 ) ( 0 320 -16 ) ( 0 320 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 0 320 0 ) ( -64 320 0 ) ( -64 320 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -64 320 -16 ) ( -64 -256 -16 ) ( -64 -256 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 8 +{ +( 64 320 -16 ) ( 64 -256 -16 ) ( 64 -256 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 128 320 0 ) ( 64 320 0 ) ( 64 320 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 128 -256 0 ) ( 128 320 0 ) ( 128 320 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 64 -256 0 ) ( 128 -256 0 ) ( 128 -256 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 64 -256 0 ) ( 64 320 0 ) ( 128 320 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 128 320 -24 ) ( 64 320 -24 ) ( 64 -256 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 9 +{ +( -256 320 -16 ) ( -256 -256 -16 ) ( -256 -256 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -192 320 0 ) ( -256 320 0 ) ( -256 320 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -192 -256 -16 ) ( -192 320 -16 ) ( -192 320 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -216 256 0 ) ( -152 256 0 ) ( -152 256 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -256 -256 0 ) ( -256 320 0 ) ( -192 320 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -192 320 -24 ) ( -256 320 -24 ) ( -256 -256 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 10 +{ +( -192 192 -24 ) ( -256 192 -24 ) ( -256 -384 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -256 -384 0 ) ( -256 192 0 ) ( -192 192 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -216 128 0 ) ( -152 128 0 ) ( -152 128 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -192 -384 -16 ) ( -192 192 -16 ) ( -192 192 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -192 192 0 ) ( -256 192 0 ) ( -256 192 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -256 192 -16 ) ( -256 -384 -16 ) ( -256 -384 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 11 +{ +( -256 64 -16 ) ( -256 -512 -16 ) ( -256 -512 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -192 64 0 ) ( -256 64 0 ) ( -256 64 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -192 -512 -16 ) ( -192 64 -16 ) ( -192 64 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -216 0 0 ) ( -152 0 0 ) ( -152 0 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -256 -512 0 ) ( -256 64 0 ) ( -192 64 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -192 64 -24 ) ( -256 64 -24 ) ( -256 -512 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 12 +{ +( -192 -64 -24 ) ( -256 -64 -24 ) ( -256 -640 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -256 -640 0 ) ( -256 -64 0 ) ( -192 -64 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -216 -128 0 ) ( -152 -128 0 ) ( -152 -128 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -192 -640 -16 ) ( -192 -64 -16 ) ( -192 -64 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -192 -64 0 ) ( -256 -64 0 ) ( -256 -64 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -256 -64 -16 ) ( -256 -640 -16 ) ( -256 -640 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 13 +{ +( -256 -192 -16 ) ( -256 -768 -16 ) ( -256 -768 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -192 -192 0 ) ( -256 -192 0 ) ( -256 -192 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -192 -768 -16 ) ( -192 -192 -16 ) ( -192 -192 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -216 -256 0 ) ( -152 -256 0 ) ( -152 -256 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -256 -768 0 ) ( -256 -192 0 ) ( -192 -192 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -192 -192 -24 ) ( -256 -192 -24 ) ( -256 -768 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 14 +{ +( -64 320 -24 ) ( -128 320 -24 ) ( -128 -256 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -128 -256 0 ) ( -128 320 0 ) ( -64 320 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -88 256 0 ) ( -24 256 0 ) ( -24 256 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -64 -256 -16 ) ( -64 320 -16 ) ( -64 320 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -64 320 0 ) ( -128 320 0 ) ( -128 320 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -128 320 -16 ) ( -128 -256 -16 ) ( -128 -256 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 15 +{ +( -128 192 -16 ) ( -128 -384 -16 ) ( -128 -384 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -64 192 0 ) ( -128 192 0 ) ( -128 192 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -64 -384 -16 ) ( -64 192 -16 ) ( -64 192 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -88 128 0 ) ( -24 128 0 ) ( -24 128 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -128 -384 0 ) ( -128 192 0 ) ( -64 192 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -64 192 -24 ) ( -128 192 -24 ) ( -128 -384 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 16 +{ +( -64 64 -24 ) ( -128 64 -24 ) ( -128 -512 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -128 -512 0 ) ( -128 64 0 ) ( -64 64 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -88 0 0 ) ( -24 0 0 ) ( -24 0 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -64 -512 -16 ) ( -64 64 -16 ) ( -64 64 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -64 64 0 ) ( -128 64 0 ) ( -128 64 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -128 64 -16 ) ( -128 -512 -16 ) ( -128 -512 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 17 +{ +( -128 -64 -16 ) ( -128 -640 -16 ) ( -128 -640 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -64 -64 0 ) ( -128 -64 0 ) ( -128 -64 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -64 -640 -16 ) ( -64 -64 -16 ) ( -64 -64 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -88 -128 0 ) ( -24 -128 0 ) ( -24 -128 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -128 -640 0 ) ( -128 -64 0 ) ( -64 -64 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -64 -64 -24 ) ( -128 -64 -24 ) ( -128 -640 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 18 +{ +( -64 -192 -24 ) ( -128 -192 -24 ) ( -128 -768 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -128 -768 0 ) ( -128 -192 0 ) ( -64 -192 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -88 -256 0 ) ( -24 -256 0 ) ( -24 -256 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -64 -768 -16 ) ( -64 -192 -16 ) ( -64 -192 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -64 -192 0 ) ( -128 -192 0 ) ( -128 -192 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( -128 -192 -16 ) ( -128 -768 -16 ) ( -128 -768 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 19 +{ +( 0 320 -16 ) ( 0 -256 -16 ) ( 0 -256 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 64 320 0 ) ( 0 320 0 ) ( 0 320 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 64 -256 -16 ) ( 64 320 -16 ) ( 64 320 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 40 256 0 ) ( 104 256 0 ) ( 104 256 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 0 -256 0 ) ( 0 320 0 ) ( 64 320 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 64 320 -24 ) ( 0 320 -24 ) ( 0 -256 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 20 +{ +( 64 192 -24 ) ( 0 192 -24 ) ( 0 -384 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 0 -384 0 ) ( 0 192 0 ) ( 64 192 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 40 128 0 ) ( 104 128 0 ) ( 104 128 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 64 -384 -16 ) ( 64 192 -16 ) ( 64 192 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 64 192 0 ) ( 0 192 0 ) ( 0 192 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 0 192 -16 ) ( 0 -384 -16 ) ( 0 -384 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 21 +{ +( 0 64 -16 ) ( 0 -512 -16 ) ( 0 -512 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 64 64 0 ) ( 0 64 0 ) ( 0 64 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 64 -512 -16 ) ( 64 64 -16 ) ( 64 64 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 40 0 0 ) ( 104 0 0 ) ( 104 0 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 0 -512 0 ) ( 0 64 0 ) ( 64 64 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 64 64 -24 ) ( 0 64 -24 ) ( 0 -512 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 22 +{ +( 64 -64 -24 ) ( 0 -64 -24 ) ( 0 -640 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 0 -640 0 ) ( 0 -64 0 ) ( 64 -64 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 40 -128 0 ) ( 104 -128 0 ) ( 104 -128 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 64 -640 -16 ) ( 64 -64 -16 ) ( 64 -64 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 64 -64 0 ) ( 0 -64 0 ) ( 0 -64 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 0 -64 -16 ) ( 0 -640 -16 ) ( 0 -640 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 23 +{ +( 0 -192 -16 ) ( 0 -768 -16 ) ( 0 -768 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 64 -192 0 ) ( 0 -192 0 ) ( 0 -192 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 64 -768 -16 ) ( 64 -192 -16 ) ( 64 -192 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 40 -256 0 ) ( 104 -256 0 ) ( 104 -256 -8 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 0 -768 0 ) ( 0 -192 0 ) ( 64 -192 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +( 64 -192 -24 ) ( 0 -192 -24 ) ( 0 -768 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.190000 0 0 0 +} +// brush 24 +{ +( -336 336 -16 ) ( -336 -272 -16 ) ( -336 -272 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.187500 0 0 0 +( 136 336 -16 ) ( -336 336 -16 ) ( -336 336 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.187500 0 0 0 +( 144 -272 -16 ) ( 144 336 -16 ) ( 144 336 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.187500 0 0 0 +( -336 -272 -16 ) ( 136 -272 -16 ) ( 136 -272 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.187500 0 0 0 +( -336 -272 -16 ) ( -336 336 -16 ) ( 136 336 -16 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.187500 0 0 0 +( 136 336 -24 ) ( -336 336 -24 ) ( -336 -272 -24 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.190000 0.187500 0 0 0 +} +// brush 25 +{ +( 144 -256 -24 ) ( 128 -256 -24 ) ( 128 -272 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( 128 -272 200 ) ( 128 -256 200 ) ( 144 -256 200 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( 128 -272 -16 ) ( 144 -272 -16 ) ( 144 -272 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( 144 -272 -16 ) ( 144 -256 -16 ) ( 144 -256 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( 144 -256 -16 ) ( 128 -256 -16 ) ( 128 -256 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( 128 -256 -16 ) ( 128 -272 -16 ) ( 128 -272 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +} +// brush 26 +{ +( -320 -256 -24 ) ( -336 -256 -24 ) ( -336 -272 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( -336 -272 200 ) ( -336 -256 200 ) ( -320 -256 200 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( -336 -272 200 ) ( -320 -272 200 ) ( -320 -272 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( -320 -272 200 ) ( -320 -256 200 ) ( -320 -256 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( -320 -256 200 ) ( -336 -256 200 ) ( -336 -256 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( -336 -256 200 ) ( -336 -272 200 ) ( -336 -272 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +} +// brush 27 +{ +( -320 336 -24 ) ( -336 336 -24 ) ( -336 320 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( -336 320 200 ) ( -336 336 200 ) ( -320 336 200 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( -336 320 200 ) ( -320 320 200 ) ( -320 320 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( -320 320 200 ) ( -320 336 200 ) ( -320 336 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( -320 336 200 ) ( -336 336 200 ) ( -336 336 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( -336 336 200 ) ( -336 320 200 ) ( -336 320 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +} +// brush 28 +{ +( 144 336 -24 ) ( 128 336 -24 ) ( 128 320 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( 128 320 200 ) ( 128 336 200 ) ( 144 336 200 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( 128 320 200 ) ( 144 320 200 ) ( 144 320 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( 144 320 200 ) ( 144 336 200 ) ( 144 336 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( 144 336 200 ) ( 128 336 200 ) ( 128 336 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( 128 336 200 ) ( 128 320 200 ) ( 128 320 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +} +// brush 29 +{ +( 128 336 192 ) ( -320 336 192 ) ( -320 320 192 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( -320 320 200 ) ( -320 336 200 ) ( 128 336 200 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( -320 320 200 ) ( 128 320 200 ) ( 128 320 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( 128 320 200 ) ( 128 336 200 ) ( 128 336 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( 128 336 200 ) ( -320 336 200 ) ( -320 336 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( -320 336 200 ) ( -320 320 200 ) ( -320 320 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +} +// brush 30 +{ +( 144 320 192 ) ( 128 320 192 ) ( 128 -256 192 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( 128 -256 200 ) ( 128 320 200 ) ( 144 320 200 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( 128 -256 200 ) ( 144 -256 200 ) ( 144 -256 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( 144 -256 200 ) ( 144 320 200 ) ( 144 320 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( 144 320 200 ) ( 128 320 200 ) ( 128 320 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( 128 320 200 ) ( 128 -256 200 ) ( 128 -256 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +} +// brush 31 +{ +( 128 -256 192 ) ( -320 -256 192 ) ( -320 -272 192 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( -320 -272 200 ) ( -320 -256 200 ) ( 128 -256 200 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( -320 -272 200 ) ( 128 -272 200 ) ( 128 -272 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( 128 -272 200 ) ( 128 -256 200 ) ( 128 -256 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( 128 -256 200 ) ( -320 -256 200 ) ( -320 -256 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( -320 -256 200 ) ( -320 -272 200 ) ( -320 -272 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +} +// brush 32 +{ +( -328 320 192 ) ( -336 320 192 ) ( -336 -256 192 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( -336 -256 200 ) ( -336 320 200 ) ( -328 320 200 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( -336 -256 200 ) ( -328 -256 200 ) ( -328 -256 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( -320 -256 200 ) ( -320 320 200 ) ( -320 320 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( -328 320 200 ) ( -336 320 200 ) ( -336 320 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +( -336 320 200 ) ( -336 -256 200 ) ( -336 -256 -24 ) common/caulk 0 0 0 0.500000 0.500000 0 0 0 +} +// brush 33 +{ +( 1424 224 -176 ) ( 904 224 -176 ) ( 904 -248 -176 ) map/lab_games/sky/lg_sky_02_dn 756 485 0 -0.507812 0.460938 0 0 0 +( 904 -248 200 ) ( 1424 -248 200 ) ( 1424 -248 184 ) map/lab_games/sky/lg_sky_02_dn 756 0 0 -0.507812 0.007812 0 0 0 +( 1424 -248 200 ) ( 1424 224 200 ) ( 1424 224 184 ) map/lab_games/sky/lg_sky_02_dn 485 0 0 -0.460938 0.007812 0 0 0 +( 1424 224 200 ) ( 904 224 200 ) ( 904 224 184 ) map/lab_games/sky/lg_sky_02_dn 756 0 0 -0.507812 0.007812 0 0 0 +( 904 224 200 ) ( 904 -248 200 ) ( 904 -248 184 ) map/lab_games/sky/lg_sky_02_dn 485 0 0 -0.460938 0.007812 0 0 0 +( 904 224 -168 ) ( 1424 224 -168 ) ( 904 -248 -168 ) map/lab_games/sky/lg_sky_02_dn 756 485 0 -0.507812 0.460938 0 0 0 +} +// brush 34 +{ +( 904 -248 200 ) ( 904 224 200 ) ( 1424 224 200 ) map/lab_games/sky/lg_sky_02_up 756 485 0 -0.507812 0.460938 0 0 0 +( 904 -248 200 ) ( 1424 -248 200 ) ( 1424 -248 184 ) map/lab_games/sky/lg_sky_02_up 756 0 0 -0.507812 0.007812 0 0 0 +( 1424 -248 200 ) ( 1424 224 200 ) ( 1424 224 184 ) map/lab_games/sky/lg_sky_02_up 485 0 0 -0.460938 0.007812 0 0 0 +( 1424 224 200 ) ( 904 224 200 ) ( 904 224 184 ) map/lab_games/sky/lg_sky_02_up 756 0 0 -0.507812 0.007812 0 0 0 +( 904 224 200 ) ( 904 -248 200 ) ( 904 -248 184 ) map/lab_games/sky/lg_sky_02_up 485 0 0 -0.460938 0.007812 0 0 0 +( 904 224 192 ) ( 904 -248 192 ) ( 1424 224 192 ) map/lab_games/sky/lg_sky_02_up 756 485 0 -0.507812 0.460938 0 0 0 +} +// brush 35 +{ +( 1424 224 -176 ) ( 904 224 -176 ) ( 904 -248 -176 ) map/lab_games/sky/lg_sky_02_bk 756 0 0 -0.507812 0.007812 0 0 0 +( 904 -248 200 ) ( 904 224 200 ) ( 1424 224 200 ) map/lab_games/sky/lg_sky_02_bk 756 0 0 -0.507812 0.007812 0 0 0 +( 904 -248 200 ) ( 1424 -248 200 ) ( 1424 -248 184 ) map/lab_games/sky/lg_sky_02_bk 756 544 0 -0.507812 0.367188 0 0 0 +( 1424 -248 200 ) ( 1424 224 200 ) ( 1424 224 184 ) map/lab_games/sky/lg_sky_02_bk 0 544 0 -0.007812 0.367188 0 0 0 +( 904 224 200 ) ( 904 -248 200 ) ( 904 -248 184 ) map/lab_games/sky/lg_sky_02_bk 0 544 0 -0.007812 0.367188 0 0 0 +( 1424 -240 200 ) ( 904 -240 200 ) ( 1424 -240 184 ) map/lab_games/sky/lg_sky_02_bk 756 544 0 -0.507812 0.367188 0 0 0 +} +// brush 36 +{ +( 1424 224 -176 ) ( 904 224 -176 ) ( 904 -248 -176 ) map/lab_games/sky/lg_sky_02_rt 0 485 0 -0.007812 0.460938 0 0 0 +( 904 -248 200 ) ( 904 224 200 ) ( 1424 224 200 ) map/lab_games/sky/lg_sky_02_rt 0 485 0 -0.007812 0.460938 0 0 0 +( 904 -248 200 ) ( 1424 -248 200 ) ( 1424 -248 184 ) map/lab_games/sky/lg_sky_02_rt 0 544 0 -0.007812 0.367188 0 0 0 +( 1424 -248 200 ) ( 1424 224 200 ) ( 1424 224 184 ) map/lab_games/sky/lg_sky_02_rt 485 544 0 -0.460938 0.367188 0 0 0 +( 1424 224 200 ) ( 904 224 200 ) ( 904 224 184 ) map/lab_games/sky/lg_sky_02_rt 0 544 0 -0.007812 0.367188 0 0 0 +( 1416 224 200 ) ( 1416 -248 200 ) ( 1416 224 184 ) map/lab_games/sky/lg_sky_02_rt 485 544 0 -0.460938 0.367188 0 0 0 +} +// brush 37 +{ +( 1424 224 -176 ) ( 904 224 -176 ) ( 904 -248 -176 ) map/lab_games/sky/lg_sky_02_ft 756 0 0 -0.507812 0.007812 0 0 0 +( 904 -248 200 ) ( 904 224 200 ) ( 1424 224 200 ) map/lab_games/sky/lg_sky_02_ft 756 0 0 -0.507812 0.007812 0 0 0 +( 1424 -248 200 ) ( 1424 224 200 ) ( 1424 224 184 ) map/lab_games/sky/lg_sky_02_ft 0 544 0 -0.007812 0.367188 0 0 0 +( 1424 224 200 ) ( 904 224 200 ) ( 904 224 184 ) map/lab_games/sky/lg_sky_02_ft 756 544 0 -0.507812 0.367188 0 0 0 +( 904 224 200 ) ( 904 -248 200 ) ( 904 -248 184 ) map/lab_games/sky/lg_sky_02_ft 0 544 0 -0.007812 0.367188 0 0 0 +( 904 216 200 ) ( 1424 216 200 ) ( 904 216 184 ) map/lab_games/sky/lg_sky_02_ft 756 544 0 -0.507812 0.367188 0 0 0 +} +// brush 38 +{ +( 1424 224 -176 ) ( 904 224 -176 ) ( 904 -248 -176 ) map/lab_games/sky/lg_sky_02_lf 0 485 0 -0.007812 0.460938 0 0 0 +( 904 -248 200 ) ( 904 224 200 ) ( 1424 224 200 ) map/lab_games/sky/lg_sky_02_lf 0 485 0 -0.007812 0.460938 0 0 0 +( 904 -248 200 ) ( 1424 -248 200 ) ( 1424 -248 184 ) map/lab_games/sky/lg_sky_02_lf 0 544 0 -0.007812 0.367188 0 0 0 +( 1424 224 200 ) ( 904 224 200 ) ( 904 224 184 ) map/lab_games/sky/lg_sky_02_lf 0 544 0 -0.007812 0.367188 0 0 0 +( 904 224 200 ) ( 904 -248 200 ) ( 904 -248 184 ) map/lab_games/sky/lg_sky_02_lf 485 544 0 -0.460938 0.367188 0 0 0 +( 912 -248 200 ) ( 912 224 200 ) ( 912 -248 184 ) map/lab_games/sky/lg_sky_02_lf 485 544 0 -0.460938 0.367188 0 0 0 +} +// brush 39 +{ +( -128 -128 0 ) ( -128 -192 0 ) ( -128 -192 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -64 -128 0 ) ( -128 -128 0 ) ( -128 -128 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -64 -192 0 ) ( -64 -128 0 ) ( -64 -128 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -128 -192 0 ) ( -64 -192 0 ) ( -64 -192 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -128 -192 0 ) ( -128 -128 0 ) ( -64 -128 0 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.062500 0 0 0 +( -64 -128 -4 ) ( -128 -128 -4 ) ( -128 -192 -4 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.062500 0 0 0 +} +// brush 40 +{ +( -192 -128 -4 ) ( -256 -128 -4 ) ( -256 -192 -4 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.062500 0 0 0 +( -256 -192 0 ) ( -256 -128 0 ) ( -192 -128 0 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.062500 0 0 0 +( -256 -192 0 ) ( -192 -192 0 ) ( -192 -192 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -192 -192 0 ) ( -192 -128 0 ) ( -192 -128 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -192 -128 0 ) ( -256 -128 0 ) ( -256 -128 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -256 -128 0 ) ( -256 -192 0 ) ( -256 -192 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +} +// brush 41 +{ +( 0 -128 0 ) ( 0 -192 0 ) ( 0 -192 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( 64 -128 0 ) ( 0 -128 0 ) ( 0 -128 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( 64 -192 0 ) ( 64 -128 0 ) ( 64 -128 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( 0 -192 0 ) ( 64 -192 0 ) ( 64 -192 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( 0 -192 0 ) ( 0 -128 0 ) ( 64 -128 0 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.062500 0 0 0 +( 64 -128 -4 ) ( 0 -128 -4 ) ( 0 -192 -4 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.062500 0 0 0 +} +// brush 42 +{ +( -64 0 -4 ) ( -128 0 -4 ) ( -128 -64 -4 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.062500 0 0 0 +( -128 -64 0 ) ( -128 0 0 ) ( -64 0 0 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.062500 0 0 0 +( -128 -64 0 ) ( -64 -64 0 ) ( -64 -64 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -64 -64 0 ) ( -64 0 0 ) ( -64 0 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -64 0 0 ) ( -128 0 0 ) ( -128 0 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -128 0 0 ) ( -128 -64 0 ) ( -128 -64 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +} +// brush 43 +{ +( -256 0 0 ) ( -256 -64 0 ) ( -256 -64 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -192 0 0 ) ( -256 0 0 ) ( -256 0 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -192 -64 0 ) ( -192 0 0 ) ( -192 0 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -256 -64 0 ) ( -192 -64 0 ) ( -192 -64 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -256 -64 0 ) ( -256 0 0 ) ( -192 0 0 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.062500 0 0 0 +( -192 0 -4 ) ( -256 0 -4 ) ( -256 -64 -4 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.062500 0 0 0 +} +// brush 44 +{ +( 64 0 -4 ) ( 0 0 -4 ) ( 0 -64 -4 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.062500 0 0 0 +( 0 -64 0 ) ( 0 0 0 ) ( 64 0 0 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.062500 0 0 0 +( 0 -64 0 ) ( 64 -64 0 ) ( 64 -64 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( 64 -64 0 ) ( 64 0 0 ) ( 64 0 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( 64 0 0 ) ( 0 0 0 ) ( 0 0 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( 0 0 0 ) ( 0 -64 0 ) ( 0 -64 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +} +// brush 45 +{ +( -128 128 0 ) ( -128 64 0 ) ( -128 64 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -64 128 0 ) ( -128 128 0 ) ( -128 128 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -64 64 0 ) ( -64 128 0 ) ( -64 128 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -128 64 0 ) ( -64 64 0 ) ( -64 64 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -128 64 0 ) ( -128 128 0 ) ( -64 128 0 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.062500 0 0 0 +( -64 128 -4 ) ( -128 128 -4 ) ( -128 64 -4 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.062500 0 0 0 +} +// brush 46 +{ +( -192 128 -4 ) ( -256 128 -4 ) ( -256 64 -4 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.062500 0 0 0 +( -256 64 0 ) ( -256 128 0 ) ( -192 128 0 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.062500 0 0 0 +( -256 64 0 ) ( -192 64 0 ) ( -192 64 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -192 64 0 ) ( -192 128 0 ) ( -192 128 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -192 128 0 ) ( -256 128 0 ) ( -256 128 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -256 128 0 ) ( -256 64 0 ) ( -256 64 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +} +// brush 47 +{ +( 0 128 0 ) ( 0 64 0 ) ( 0 64 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( 64 128 0 ) ( 0 128 0 ) ( 0 128 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( 64 64 0 ) ( 64 128 0 ) ( 64 128 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( 0 64 0 ) ( 64 64 0 ) ( 64 64 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( 0 64 0 ) ( 0 128 0 ) ( 64 128 0 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.062500 0 0 0 +( 64 128 -4 ) ( 0 128 -4 ) ( 0 64 -4 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.062500 0 0 0 +} +// brush 48 +{ +( -64 256 -4 ) ( -128 256 -4 ) ( -128 192 -4 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.062500 0 0 0 +( -128 192 0 ) ( -128 256 0 ) ( -64 256 0 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.062500 0 0 0 +( -128 192 0 ) ( -64 192 0 ) ( -64 192 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -64 192 0 ) ( -64 256 0 ) ( -64 256 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -64 256 0 ) ( -128 256 0 ) ( -128 256 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -128 256 0 ) ( -128 192 0 ) ( -128 192 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +} +// brush 49 +{ +( -256 256 0 ) ( -256 192 0 ) ( -256 192 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -192 256 0 ) ( -256 256 0 ) ( -256 256 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -192 192 0 ) ( -192 256 0 ) ( -192 256 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -256 192 0 ) ( -192 192 0 ) ( -192 192 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( -256 192 0 ) ( -256 256 0 ) ( -192 256 0 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.062500 0 0 0 +( -192 256 -4 ) ( -256 256 -4 ) ( -256 192 -4 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.062500 0 0 0 +} +// brush 50 +{ +( 64 256 -4 ) ( 0 256 -4 ) ( 0 192 -4 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.062500 0 0 0 +( 0 192 0 ) ( 0 256 0 ) ( 64 256 0 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.062500 0 0 0 +( 0 192 0 ) ( 64 192 0 ) ( 64 192 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( 64 192 0 ) ( 64 256 0 ) ( 64 256 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( 64 256 0 ) ( 0 256 0 ) ( 0 256 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +( 0 256 0 ) ( 0 192 0 ) ( 0 192 -16 ) map/ctp_tech_lava_d 0 0 0 -0.062500 0.003906 0 0 0 +} +} +// entity 1 +{ +"angle" "90" +"origin" "-96 -224 24" +"classname" "info_player_start" +} +// entity 2 +{ +"origin" "-296 224 24" +"classname" "apple_reward" +"random_items" "apple_reward,lemon_reward,strawberry_reward,fungi_reward" +} +// entity 3 +{ +"origin" "-160 96 24" +"classname" "lemon_reward" +"random_items" "apple_reward,lemon_reward,strawberry_reward,fungi_reward" +} +// entity 4 +{ +"origin" "-24 224 24" +"classname" "fungi_reward" +"random_items" "apple_reward,lemon_reward,strawberry_reward,fungi_reward" +} +// entity 5 +{ +"origin" "96 96 24" +"classname" "strawberry_reward" +"random_items" "apple_reward,lemon_reward,strawberry_reward,fungi_reward" +} +// entity 6 +{ +"classname" "strawberry_reward" +"origin" "-160 -32 24" +"random_items" "apple_reward,lemon_reward,strawberry_reward,fungi_reward" +} +// entity 7 +{ +"origin" "-288 -160 24" +"classname" "strawberry_reward" +"random_items" "apple_reward,lemon_reward,strawberry_reward,fungi_reward" +} +// entity 8 +{ +"classname" "strawberry_reward" +"origin" "-288 96 24" +"random_items" "apple_reward,lemon_reward,strawberry_reward,fungi_reward" +} +// entity 9 +{ +"classname" "apple_reward" +"origin" "-160 -160 24" +} +// entity 10 +{ +"origin" "96 -32 24" +"classname" "apple_reward" +"random_items" "apple_reward,lemon_reward,strawberry_reward,fungi_reward" +} +// entity 11 +{ +"classname" "fungi_reward" +"origin" "-288 -32 24" +"random_items" "apple_reward,lemon_reward,strawberry_reward,fungi_reward" +} +// entity 12 +{ +"origin" "96 224 24" +"classname" "strawberry_reward" +"random_items" "apple_reward,lemon_reward,strawberry_reward,fungi_reward" +} +// entity 13 +{ +"classname" "strawberry_reward" +"origin" "96 -160 24" +"random_items" "apple_reward,lemon_reward,strawberry_reward,fungi_reward" +} +// entity 14 +{ +"classname" "apple_reward" +"origin" "-32 96 24" +"random_items" "apple_reward,lemon_reward,strawberry_reward,fungi_reward" +} +// entity 15 +{ +"classname" "lemon_reward" +"origin" "-32 -32 24" +"random_items" "apple_reward,lemon_reward,strawberry_reward,fungi_reward" +} +// entity 16 +{ +"classname" "lemon_reward" +"origin" "-160 224 24" +"random_items" "apple_reward,lemon_reward,strawberry_reward,fungi_reward" +} +// entity 17 +{ +"classname" "lemon_reward" +"origin" "32 -96 24" +"random_items" "apple_reward,lemon_reward,strawberry_reward,fungi_reward" +} +// entity 18 +{ +"classname" "lemon_reward" +"origin" "-232 -96 24" +"random_items" "apple_reward,lemon_reward,strawberry_reward,fungi_reward" +} +// entity 19 +{ +"classname" "strawberry_reward" +"origin" "-104 -96 24" +"random_items" "apple_reward,lemon_reward,strawberry_reward,fungi_reward" +} +// entity 20 +{ +"origin" "-224 160 24" +"classname" "apple_reward" +"random_items" "apple_reward,lemon_reward,strawberry_reward,fungi_reward" +} +// entity 21 +{ +"origin" "32 160 24" +"classname" "fungi_reward" +"random_items" "apple_reward,lemon_reward,strawberry_reward,fungi_reward" +} +// entity 22 +{ +"classname" "strawberry_reward" +"origin" "-224 32 24" +"random_items" "apple_reward,lemon_reward,strawberry_reward,fungi_reward" +} +// entity 23 +{ +"origin" "32 32 24" +"classname" "strawberry_reward" +"random_items" "apple_reward,lemon_reward,strawberry_reward,fungi_reward" +} +// entity 24 +{ +"classname" "apple_reward" +"origin" "-96 32 24" +"random_items" "apple_reward,lemon_reward,strawberry_reward,fungi_reward" +} +// entity 25 +{ +"classname" "apple_reward" +"origin" "-96 160 24" +"random_items" "apple_reward,lemon_reward,strawberry_reward,fungi_reward" +} +// entity 26 +{ +"classname" "target_kill" +"origin" "-294 304 154" +"targetname" "kill" +} +// entity 27 +{ +"classname" "mango_goal" +"origin" "-100 288 24" +} +// entity 28 +{ +"classname" "light" +"origin" "-96 192 176" +"light" "600" +} +// entity 29 +{ +"light" "600" +"origin" "-96 -128 176" +"classname" "light" +} +// entity 30 +{ +"classname" "_skybox" +"origin" "1136 -16 -128" +} +// entity 31 +{ +"model" "models/stadium.md3" +"origin" "1136 -16 -82" +"classname" "misc_model" +"_remap" "*;textures/map/lab_games/stadium_d" +"angle" "90" +} +// entity 32 +{ +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"modelscale" "1" +"model" "models/signal_line.md3" +"origin" "1160 -56 -80" +"classname" "misc_model" +"angle" "113" +} +// entity 33 +{ +"classname" "misc_model" +"origin" "1208 -32 -152" +"model" "models/signal_line.md3" +"modelscale" "1.1" +"_remap" "*;textures/map/lab_games/signal_pulse_blue" +"angle" "38" +} +// entity 34 +{ +"angle" "28" +"classname" "misc_model" +"origin" "1168 -24 -88" +"model" "models/signal_line.md3" +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"modelscale" "0.61" +} +// entity 35 +{ +"_remap" "*;textures/map/lab_games/signal_pulse_red" +"modelscale" "0.6" +"model" "models/signal_line.md3" +"origin" "1112 -8 -152" +"classname" "misc_model" +"angle" "159" +} +// entity 36 +{ +"target" "kill" +"classname" "trigger_multiple" +// brush 0 +{ +( -128 -136 8 ) ( -128 -192 8 ) ( -128 -192 0 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( -64 -128 -8 ) ( -120 -128 -8 ) ( -120 -128 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( -64 -192 -8 ) ( -64 -136 -8 ) ( -64 -136 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( -120 -192 -8 ) ( -64 -192 -8 ) ( -64 -192 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( -120 -192 16 ) ( -120 -136 16 ) ( -64 -136 16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( -63 -136 -17 ) ( -119 -136 -17 ) ( -119 -192 -17 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +} +} +// entity 37 +{ +"target" "kill" +"classname" "trigger_multiple" +// brush 0 +{ +( 0 -136 15 ) ( 0 -192 15 ) ( 0 -192 7 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( 64 -128 -8 ) ( 8 -128 -8 ) ( 8 -128 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( 64 -192 -8 ) ( 64 -136 -8 ) ( 64 -136 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( 8 -192 -8 ) ( 64 -192 -8 ) ( 64 -192 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( 8 -192 16 ) ( 8 -136 16 ) ( 64 -136 16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( 65 -136 -1 ) ( 9 -136 -1 ) ( 9 -192 -1 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +} +} +// entity 38 +{ +"target" "kill" +"classname" "trigger_multiple" +// brush 0 +{ +( -256 -136 -8 ) ( -256 -192 -8 ) ( -256 -192 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( -192 -128 -8 ) ( -248 -128 -8 ) ( -248 -128 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( -192 -192 15 ) ( -192 -136 15 ) ( -192 -136 7 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( -248 -192 -8 ) ( -192 -192 -8 ) ( -192 -192 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( -248 -192 16 ) ( -248 -136 16 ) ( -192 -136 16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( -192 -136 -1 ) ( -248 -136 -1 ) ( -248 -192 -1 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +} +} +// entity 39 +{ +"classname" "trigger_multiple" +"target" "kill" +// brush 0 +{ +( -63 -8 -17 ) ( -119 -8 -17 ) ( -119 -64 -17 ) common/caulk 0 33 0 0.190000 0.190000 0 0 0 +( -120 -64 16 ) ( -120 -8 16 ) ( -64 -8 16 ) common/caulk 0 33 0 0.190000 0.190000 0 0 0 +( -120 -64 -8 ) ( -64 -64 -8 ) ( -64 -64 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( -64 -64 -8 ) ( -64 -8 -8 ) ( -64 -8 -16 ) common/caulk -33 0 0 0.190000 0.190000 0 0 0 +( -64 0 -8 ) ( -120 0 -8 ) ( -120 0 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( -128 -8 8 ) ( -128 -64 8 ) ( -128 -64 0 ) common/caulk -33 0 0 0.190000 0.190000 0 0 0 +} +} +// entity 40 +{ +"classname" "trigger_multiple" +"target" "kill" +// brush 0 +{ +( 65 -8 -1 ) ( 9 -8 -1 ) ( 9 -64 -1 ) common/caulk 0 33 0 0.190000 0.190000 0 0 0 +( 8 -64 16 ) ( 8 -8 16 ) ( 64 -8 16 ) common/caulk 0 33 0 0.190000 0.190000 0 0 0 +( 8 -64 -8 ) ( 64 -64 -8 ) ( 64 -64 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( 64 -64 -8 ) ( 64 -8 -8 ) ( 64 -8 -16 ) common/caulk -33 0 0 0.190000 0.190000 0 0 0 +( 64 0 -8 ) ( 8 0 -8 ) ( 8 0 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( 0 -8 15 ) ( 0 -64 15 ) ( 0 -64 7 ) common/caulk -33 0 0 0.190000 0.190000 0 0 0 +} +} +// entity 41 +{ +"classname" "trigger_multiple" +"target" "kill" +// brush 0 +{ +( -192 -8 -1 ) ( -248 -8 -1 ) ( -248 -64 -1 ) common/caulk 0 33 0 0.190000 0.190000 0 0 0 +( -248 -64 16 ) ( -248 -8 16 ) ( -192 -8 16 ) common/caulk 0 33 0 0.190000 0.190000 0 0 0 +( -248 -64 -8 ) ( -192 -64 -8 ) ( -192 -64 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( -192 -64 15 ) ( -192 -8 15 ) ( -192 -8 7 ) common/caulk -33 0 0 0.190000 0.190000 0 0 0 +( -192 0 -8 ) ( -248 0 -8 ) ( -248 0 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( -256 -8 -8 ) ( -256 -64 -8 ) ( -256 -64 -16 ) common/caulk -33 0 0 0.190000 0.190000 0 0 0 +} +} +// entity 42 +{ +"target" "kill" +"classname" "trigger_multiple" +// brush 0 +{ +( -128 120 8 ) ( -128 64 8 ) ( -128 64 0 ) common/caulk -2 0 0 0.190000 0.190000 0 0 0 +( -64 128 -8 ) ( -120 128 -8 ) ( -120 128 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( -64 64 -8 ) ( -64 120 -8 ) ( -64 120 -16 ) common/caulk -2 0 0 0.190000 0.190000 0 0 0 +( -120 64 -8 ) ( -64 64 -8 ) ( -64 64 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( -120 64 16 ) ( -120 120 16 ) ( -64 120 16 ) common/caulk 0 2 0 0.190000 0.190000 0 0 0 +( -63 120 -17 ) ( -119 120 -17 ) ( -119 64 -17 ) common/caulk 0 2 0 0.190000 0.190000 0 0 0 +} +} +// entity 43 +{ +"target" "kill" +"classname" "trigger_multiple" +// brush 0 +{ +( 0 120 15 ) ( 0 64 15 ) ( 0 64 7 ) common/caulk -2 0 0 0.190000 0.190000 0 0 0 +( 64 128 -8 ) ( 8 128 -8 ) ( 8 128 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( 64 64 -8 ) ( 64 120 -8 ) ( 64 120 -16 ) common/caulk -2 0 0 0.190000 0.190000 0 0 0 +( 8 64 -8 ) ( 64 64 -8 ) ( 64 64 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( 8 64 16 ) ( 8 120 16 ) ( 64 120 16 ) common/caulk 0 2 0 0.190000 0.190000 0 0 0 +( 65 120 -1 ) ( 9 120 -1 ) ( 9 64 -1 ) common/caulk 0 2 0 0.190000 0.190000 0 0 0 +} +} +// entity 44 +{ +"target" "kill" +"classname" "trigger_multiple" +// brush 0 +{ +( -256 120 -8 ) ( -256 64 -8 ) ( -256 64 -16 ) common/caulk -2 0 0 0.190000 0.190000 0 0 0 +( -192 128 -8 ) ( -248 128 -8 ) ( -248 128 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( -192 64 15 ) ( -192 120 15 ) ( -192 120 7 ) common/caulk -2 0 0 0.190000 0.190000 0 0 0 +( -248 64 -8 ) ( -192 64 -8 ) ( -192 64 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( -248 64 16 ) ( -248 120 16 ) ( -192 120 16 ) common/caulk 0 2 0 0.190000 0.190000 0 0 0 +( -192 120 -1 ) ( -248 120 -1 ) ( -248 64 -1 ) common/caulk 0 2 0 0.190000 0.190000 0 0 0 +} +} +// entity 45 +{ +"classname" "trigger_multiple" +"target" "kill" +// brush 0 +{ +( -63 248 -17 ) ( -119 248 -17 ) ( -119 192 -17 ) common/caulk 0 35 0 0.190000 0.190000 0 0 0 +( -120 192 16 ) ( -120 248 16 ) ( -64 248 16 ) common/caulk 0 35 0 0.190000 0.190000 0 0 0 +( -120 192 -8 ) ( -64 192 -8 ) ( -64 192 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( -64 192 -8 ) ( -64 248 -8 ) ( -64 248 -16 ) common/caulk -35 0 0 0.190000 0.190000 0 0 0 +( -64 256 -8 ) ( -120 256 -8 ) ( -120 256 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( -128 248 8 ) ( -128 192 8 ) ( -128 192 0 ) common/caulk -35 0 0 0.190000 0.190000 0 0 0 +} +} +// entity 46 +{ +"classname" "trigger_multiple" +"target" "kill" +// brush 0 +{ +( 65 248 -1 ) ( 9 248 -1 ) ( 9 192 -1 ) common/caulk 0 35 0 0.190000 0.190000 0 0 0 +( 8 192 16 ) ( 8 248 16 ) ( 64 248 16 ) common/caulk 0 35 0 0.190000 0.190000 0 0 0 +( 8 192 -8 ) ( 64 192 -8 ) ( 64 192 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( 64 192 -8 ) ( 64 248 -8 ) ( 64 248 -16 ) common/caulk -35 0 0 0.190000 0.190000 0 0 0 +( 64 256 -8 ) ( 8 256 -8 ) ( 8 256 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( 0 248 15 ) ( 0 192 15 ) ( 0 192 7 ) common/caulk -35 0 0 0.190000 0.190000 0 0 0 +} +} +// entity 47 +{ +"classname" "trigger_multiple" +"target" "kill" +// brush 0 +{ +( -192 248 -1 ) ( -248 248 -1 ) ( -248 192 -1 ) common/caulk 0 35 0 0.190000 0.190000 0 0 0 +( -248 192 16 ) ( -248 248 16 ) ( -192 248 16 ) common/caulk 0 35 0 0.190000 0.190000 0 0 0 +( -248 192 -8 ) ( -192 192 -8 ) ( -192 192 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( -192 192 15 ) ( -192 248 15 ) ( -192 248 7 ) common/caulk -35 0 0 0.190000 0.190000 0 0 0 +( -192 256 -8 ) ( -248 256 -8 ) ( -248 256 -16 ) common/caulk 0 0 0 0.190000 0.190000 0 0 0 +( -256 248 -8 ) ( -256 192 -8 ) ( -256 192 -16 ) common/caulk -35 0 0 0.190000 0.190000 0 0 0 +} +} diff --git a/assets/models/bush_desert_01.md3 b/assets/models/bush_desert_01.md3 new file mode 100644 index 00000000..cd38e1ad Binary files /dev/null and b/assets/models/bush_desert_01.md3 differ diff --git a/assets/models/cactus_desert_01.md3 b/assets/models/cactus_desert_01.md3 new file mode 100644 index 00000000..bbfd54ce Binary files /dev/null and b/assets/models/cactus_desert_01.md3 differ diff --git a/assets/models/flags/b_flag.md3 b/assets/models/flags/b_flag.md3 new file mode 100644 index 00000000..82324a30 Binary files /dev/null and b/assets/models/flags/b_flag.md3 differ diff --git a/assets/models/flags/b_flag.tga b/assets/models/flags/b_flag.tga new file mode 100644 index 00000000..f8054130 Binary files /dev/null and b/assets/models/flags/b_flag.tga differ diff --git a/assets/models/flags/n_flag.md3 b/assets/models/flags/n_flag.md3 new file mode 100644 index 00000000..87d7a55d Binary files /dev/null and b/assets/models/flags/n_flag.md3 differ diff --git a/assets/models/flags/n_flag.tga b/assets/models/flags/n_flag.tga new file mode 100644 index 00000000..5c566fa9 Binary files /dev/null and b/assets/models/flags/n_flag.tga differ diff --git a/assets/models/flags/r_flag.md3 b/assets/models/flags/r_flag.md3 new file mode 100644 index 00000000..1c644bb3 Binary files /dev/null and b/assets/models/flags/r_flag.md3 differ diff --git a/assets/models/flags/r_flag.tga b/assets/models/flags/r_flag.tga new file mode 100644 index 00000000..b0c9a3e7 Binary files /dev/null and b/assets/models/flags/r_flag.tga differ diff --git a/assets/models/fut_teleport.md3 b/assets/models/fut_teleport.md3 deleted file mode 100644 index 6068c35d..00000000 Binary files a/assets/models/fut_teleport.md3 and /dev/null differ diff --git a/assets/models/hr_apple2.md3 b/assets/models/hr_apple2.md3 new file mode 100644 index 00000000..68f313ee Binary files /dev/null and b/assets/models/hr_apple2.md3 differ diff --git a/assets/models/hr_ball.md3 b/assets/models/hr_ball.md3 new file mode 100644 index 00000000..5844368f Binary files /dev/null and b/assets/models/hr_ball.md3 differ diff --git a/assets/models/hr_balloon.md3 b/assets/models/hr_balloon.md3 new file mode 100644 index 00000000..53761ec2 Binary files /dev/null and b/assets/models/hr_balloon.md3 differ diff --git a/assets/models/hr_banana.md3 b/assets/models/hr_banana.md3 new file mode 100644 index 00000000..e994b152 Binary files /dev/null and b/assets/models/hr_banana.md3 differ diff --git a/assets/models/hr_bee.md3 b/assets/models/hr_bee.md3 new file mode 100644 index 00000000..b310d0b1 Binary files /dev/null and b/assets/models/hr_bee.md3 differ diff --git a/assets/models/hr_bottle.md3 b/assets/models/hr_bottle.md3 new file mode 100644 index 00000000..059be1df Binary files /dev/null and b/assets/models/hr_bottle.md3 differ diff --git a/assets/models/hr_cake.md3 b/assets/models/hr_cake.md3 new file mode 100644 index 00000000..fd04bc65 Binary files /dev/null and b/assets/models/hr_cake.md3 differ diff --git a/assets/models/hr_can.md3 b/assets/models/hr_can.md3 new file mode 100644 index 00000000..7e6ee30d Binary files /dev/null and b/assets/models/hr_can.md3 differ diff --git a/assets/models/hr_car.md3 b/assets/models/hr_car.md3 new file mode 100644 index 00000000..138b9526 Binary files /dev/null and b/assets/models/hr_car.md3 differ diff --git a/assets/models/hr_cassette.md3 b/assets/models/hr_cassette.md3 new file mode 100644 index 00000000..f10ca115 Binary files /dev/null and b/assets/models/hr_cassette.md3 differ diff --git a/assets/models/hr_chair.md3 b/assets/models/hr_chair.md3 new file mode 100644 index 00000000..5ec746a3 Binary files /dev/null and b/assets/models/hr_chair.md3 differ diff --git a/assets/models/hr_cherries.md3 b/assets/models/hr_cherries.md3 new file mode 100644 index 00000000..8aa83625 Binary files /dev/null and b/assets/models/hr_cherries.md3 differ diff --git a/assets/models/hr_cow.md3 b/assets/models/hr_cow.md3 new file mode 100644 index 00000000..364c8b9e Binary files /dev/null and b/assets/models/hr_cow.md3 differ diff --git a/assets/models/hr_flower.md3 b/assets/models/hr_flower.md3 new file mode 100644 index 00000000..c14d8aa9 Binary files /dev/null and b/assets/models/hr_flower.md3 differ diff --git a/assets/models/hr_fork.md3 b/assets/models/hr_fork.md3 new file mode 100644 index 00000000..aaa54385 Binary files /dev/null and b/assets/models/hr_fork.md3 differ diff --git a/assets/models/hr_fridge.md3 b/assets/models/hr_fridge.md3 new file mode 100644 index 00000000..7e079d1c Binary files /dev/null and b/assets/models/hr_fridge.md3 differ diff --git a/assets/models/hr_guitar.md3 b/assets/models/hr_guitar.md3 new file mode 100644 index 00000000..2fb10e65 Binary files /dev/null and b/assets/models/hr_guitar.md3 differ diff --git a/assets/models/hr_hair_brush.md3 b/assets/models/hr_hair_brush.md3 new file mode 100644 index 00000000..a18513dd Binary files /dev/null and b/assets/models/hr_hair_brush.md3 differ diff --git a/assets/models/hr_hammer.md3 b/assets/models/hr_hammer.md3 new file mode 100644 index 00000000..291d128c Binary files /dev/null and b/assets/models/hr_hammer.md3 differ diff --git a/assets/models/hr_hat.md3 b/assets/models/hr_hat.md3 new file mode 100644 index 00000000..083d12ba Binary files /dev/null and b/assets/models/hr_hat.md3 differ diff --git a/assets/models/hr_ice_lolly.md3 b/assets/models/hr_ice_lolly.md3 new file mode 100644 index 00000000..0921b718 Binary files /dev/null and b/assets/models/hr_ice_lolly.md3 differ diff --git a/assets/models/hr_ice_lolly_lrg.md3 b/assets/models/hr_ice_lolly_lrg.md3 new file mode 100644 index 00000000..7aa8a07e Binary files /dev/null and b/assets/models/hr_ice_lolly_lrg.md3 differ diff --git a/assets/models/hr_jug.md3 b/assets/models/hr_jug.md3 new file mode 100644 index 00000000..82b3bf55 Binary files /dev/null and b/assets/models/hr_jug.md3 differ diff --git a/assets/models/hr_key.md3 b/assets/models/hr_key.md3 new file mode 100644 index 00000000..5a7380b0 Binary files /dev/null and b/assets/models/hr_key.md3 differ diff --git a/assets/models/hr_key_lrg.md3 b/assets/models/hr_key_lrg.md3 new file mode 100644 index 00000000..970ca212 Binary files /dev/null and b/assets/models/hr_key_lrg.md3 differ diff --git a/assets/models/hr_knife.md3 b/assets/models/hr_knife.md3 new file mode 100644 index 00000000..e137299b Binary files /dev/null and b/assets/models/hr_knife.md3 differ diff --git a/assets/models/hr_ladder.md3 b/assets/models/hr_ladder.md3 new file mode 100644 index 00000000..00e95850 Binary files /dev/null and b/assets/models/hr_ladder.md3 differ diff --git a/assets/models/hr_mug.md3 b/assets/models/hr_mug.md3 new file mode 100644 index 00000000..4c5d5f32 Binary files /dev/null and b/assets/models/hr_mug.md3 differ diff --git a/assets/models/hr_pencil.md3 b/assets/models/hr_pencil.md3 new file mode 100644 index 00000000..28eea9e7 Binary files /dev/null and b/assets/models/hr_pencil.md3 differ diff --git a/assets/models/hr_pig.md3 b/assets/models/hr_pig.md3 new file mode 100644 index 00000000..b75b2512 Binary files /dev/null and b/assets/models/hr_pig.md3 differ diff --git a/assets/models/hr_pincer.md3 b/assets/models/hr_pincer.md3 new file mode 100644 index 00000000..f44c85cc Binary files /dev/null and b/assets/models/hr_pincer.md3 differ diff --git a/assets/models/hr_plant.md3 b/assets/models/hr_plant.md3 new file mode 100644 index 00000000..5c720ee4 Binary files /dev/null and b/assets/models/hr_plant.md3 differ diff --git a/assets/models/hr_saxophone.md3 b/assets/models/hr_saxophone.md3 new file mode 100644 index 00000000..891eb66b Binary files /dev/null and b/assets/models/hr_saxophone.md3 differ diff --git a/assets/models/hr_shoe.md3 b/assets/models/hr_shoe.md3 new file mode 100644 index 00000000..b22001fc Binary files /dev/null and b/assets/models/hr_shoe.md3 differ diff --git a/assets/models/hr_spoon.md3 b/assets/models/hr_spoon.md3 new file mode 100644 index 00000000..f9e4c2b8 Binary files /dev/null and b/assets/models/hr_spoon.md3 differ diff --git a/assets/models/hr_suitcase.md3 b/assets/models/hr_suitcase.md3 new file mode 100644 index 00000000..b2659bfc Binary files /dev/null and b/assets/models/hr_suitcase.md3 differ diff --git a/assets/models/hr_tennis_racket.md3 b/assets/models/hr_tennis_racket.md3 new file mode 100644 index 00000000..c98bfe5c Binary files /dev/null and b/assets/models/hr_tennis_racket.md3 differ diff --git a/assets/models/hr_tomato.md3 b/assets/models/hr_tomato.md3 new file mode 100644 index 00000000..bf935722 Binary files /dev/null and b/assets/models/hr_tomato.md3 differ diff --git a/assets/models/hr_toothbrush.md3 b/assets/models/hr_toothbrush.md3 new file mode 100644 index 00000000..0ae99158 Binary files /dev/null and b/assets/models/hr_toothbrush.md3 differ diff --git a/assets/models/hr_tree.md3 b/assets/models/hr_tree.md3 new file mode 100644 index 00000000..5d6a0631 Binary files /dev/null and b/assets/models/hr_tree.md3 differ diff --git a/assets/models/hr_tv.md3 b/assets/models/hr_tv.md3 new file mode 100644 index 00000000..ec73887b Binary files /dev/null and b/assets/models/hr_tv.md3 differ diff --git a/assets/models/hr_wine_glass.md3 b/assets/models/hr_wine_glass.md3 new file mode 100644 index 00000000..e31ee23b Binary files /dev/null and b/assets/models/hr_wine_glass.md3 differ diff --git a/assets/models/hr_zebra.md3 b/assets/models/hr_zebra.md3 new file mode 100644 index 00000000..1f0891a8 Binary files /dev/null and b/assets/models/hr_zebra.md3 differ diff --git a/assets/models/mushroom_desert_01a.md3 b/assets/models/mushroom_desert_01a.md3 new file mode 100644 index 00000000..b3aa91cb Binary files /dev/null and b/assets/models/mushroom_desert_01a.md3 differ diff --git a/assets/models/players/crash_color/dm_character_skin_mask_a.tga b/assets/models/players/crash_color/dm_character_skin_mask_a.tga deleted file mode 100644 index 1d484ac9..00000000 Binary files a/assets/models/players/crash_color/dm_character_skin_mask_a.tga and /dev/null differ diff --git a/assets/models/players/crash_color/dm_character_skin_mask_c.tga b/assets/models/players/crash_color/dm_character_skin_mask_c.tga deleted file mode 100644 index 316483e6..00000000 Binary files a/assets/models/players/crash_color/dm_character_skin_mask_c.tga and /dev/null differ diff --git a/assets/models/players/crash_color/head_skin2.skin b/assets/models/players/crash_color/head_skin2.skin new file mode 100644 index 00000000..7636979a --- /dev/null +++ b/assets/models/players/crash_color/head_skin2.skin @@ -0,0 +1,2 @@ +h_face,models/players/crash_color/skin_base2.tga +tag_head diff --git a/assets/models/players/crash_color/head_skin2_blue.skin b/assets/models/players/crash_color/head_skin2_blue.skin new file mode 100644 index 00000000..7636979a --- /dev/null +++ b/assets/models/players/crash_color/head_skin2_blue.skin @@ -0,0 +1,2 @@ +h_face,models/players/crash_color/skin_base2.tga +tag_head diff --git a/assets/models/players/crash_color/head_skin2_red.skin b/assets/models/players/crash_color/head_skin2_red.skin new file mode 100644 index 00000000..7636979a --- /dev/null +++ b/assets/models/players/crash_color/head_skin2_red.skin @@ -0,0 +1,2 @@ +h_face,models/players/crash_color/skin_base2.tga +tag_head diff --git a/assets/models/players/crash_color/head_skin3.skin b/assets/models/players/crash_color/head_skin3.skin new file mode 100644 index 00000000..eab9c3fd --- /dev/null +++ b/assets/models/players/crash_color/head_skin3.skin @@ -0,0 +1,2 @@ +h_face,models/players/crash_color/skin_base3.tga +tag_head diff --git a/assets/models/players/crash_color/head_skin3_blue.skin b/assets/models/players/crash_color/head_skin3_blue.skin new file mode 100644 index 00000000..eab9c3fd --- /dev/null +++ b/assets/models/players/crash_color/head_skin3_blue.skin @@ -0,0 +1,2 @@ +h_face,models/players/crash_color/skin_base3.tga +tag_head diff --git a/assets/models/players/crash_color/head_skin3_red.skin b/assets/models/players/crash_color/head_skin3_red.skin new file mode 100644 index 00000000..eab9c3fd --- /dev/null +++ b/assets/models/players/crash_color/head_skin3_red.skin @@ -0,0 +1,2 @@ +h_face,models/players/crash_color/skin_base3.tga +tag_head diff --git a/assets/models/players/crash_color/head_skin4.skin b/assets/models/players/crash_color/head_skin4.skin new file mode 100644 index 00000000..e46f4110 --- /dev/null +++ b/assets/models/players/crash_color/head_skin4.skin @@ -0,0 +1,2 @@ +h_face,models/players/crash_color/skin_base4.tga +tag_head diff --git a/assets/models/players/crash_color/head_skin4_blue.skin b/assets/models/players/crash_color/head_skin4_blue.skin new file mode 100644 index 00000000..e46f4110 --- /dev/null +++ b/assets/models/players/crash_color/head_skin4_blue.skin @@ -0,0 +1,2 @@ +h_face,models/players/crash_color/skin_base4.tga +tag_head diff --git a/assets/models/players/crash_color/head_skin4_red.skin b/assets/models/players/crash_color/head_skin4_red.skin new file mode 100644 index 00000000..e46f4110 --- /dev/null +++ b/assets/models/players/crash_color/head_skin4_red.skin @@ -0,0 +1,2 @@ +h_face,models/players/crash_color/skin_base4.tga +tag_head diff --git a/assets/models/players/crash_color/head_skin5.skin b/assets/models/players/crash_color/head_skin5.skin new file mode 100644 index 00000000..75dffdef --- /dev/null +++ b/assets/models/players/crash_color/head_skin5.skin @@ -0,0 +1,2 @@ +h_face,models/players/crash_color/skin_base5.tga +tag_head diff --git a/assets/models/players/crash_color/head_skin5_blue.skin b/assets/models/players/crash_color/head_skin5_blue.skin new file mode 100644 index 00000000..75dffdef --- /dev/null +++ b/assets/models/players/crash_color/head_skin5_blue.skin @@ -0,0 +1,2 @@ +h_face,models/players/crash_color/skin_base5.tga +tag_head diff --git a/assets/models/players/crash_color/head_skin5_red.skin b/assets/models/players/crash_color/head_skin5_red.skin new file mode 100644 index 00000000..75dffdef --- /dev/null +++ b/assets/models/players/crash_color/head_skin5_red.skin @@ -0,0 +1,2 @@ +h_face,models/players/crash_color/skin_base5.tga +tag_head diff --git a/assets/models/players/crash_color/head_skin6.skin b/assets/models/players/crash_color/head_skin6.skin new file mode 100644 index 00000000..2fc69a3a --- /dev/null +++ b/assets/models/players/crash_color/head_skin6.skin @@ -0,0 +1,2 @@ +h_face,models/players/crash_color/skin_base6.tga +tag_head diff --git a/assets/models/players/crash_color/head_skin6_blue.skin b/assets/models/players/crash_color/head_skin6_blue.skin new file mode 100644 index 00000000..2fc69a3a --- /dev/null +++ b/assets/models/players/crash_color/head_skin6_blue.skin @@ -0,0 +1,2 @@ +h_face,models/players/crash_color/skin_base6.tga +tag_head diff --git a/assets/models/players/crash_color/head_skin6_red.skin b/assets/models/players/crash_color/head_skin6_red.skin new file mode 100644 index 00000000..2fc69a3a --- /dev/null +++ b/assets/models/players/crash_color/head_skin6_red.skin @@ -0,0 +1,2 @@ +h_face,models/players/crash_color/skin_base6.tga +tag_head diff --git a/assets/models/players/crash_color/head_skin7.skin b/assets/models/players/crash_color/head_skin7.skin new file mode 100644 index 00000000..940a551a --- /dev/null +++ b/assets/models/players/crash_color/head_skin7.skin @@ -0,0 +1,2 @@ +h_face,models/players/crash_color/skin_base7.tga +tag_head diff --git a/assets/models/players/crash_color/head_skin7_blue.skin b/assets/models/players/crash_color/head_skin7_blue.skin new file mode 100644 index 00000000..940a551a --- /dev/null +++ b/assets/models/players/crash_color/head_skin7_blue.skin @@ -0,0 +1,2 @@ +h_face,models/players/crash_color/skin_base7.tga +tag_head diff --git a/assets/models/players/crash_color/head_skin7_red.skin b/assets/models/players/crash_color/head_skin7_red.skin new file mode 100644 index 00000000..940a551a --- /dev/null +++ b/assets/models/players/crash_color/head_skin7_red.skin @@ -0,0 +1,2 @@ +h_face,models/players/crash_color/skin_base7.tga +tag_head diff --git a/assets/models/players/crash_color/icon_skin1.tga b/assets/models/players/crash_color/icon_skin1.tga new file mode 100644 index 00000000..4f900194 Binary files /dev/null and b/assets/models/players/crash_color/icon_skin1.tga differ diff --git a/assets/models/players/crash_color/icon_skin2.tga b/assets/models/players/crash_color/icon_skin2.tga new file mode 100644 index 00000000..4f900194 Binary files /dev/null and b/assets/models/players/crash_color/icon_skin2.tga differ diff --git a/assets/models/players/crash_color/icon_skin3.tga b/assets/models/players/crash_color/icon_skin3.tga new file mode 100644 index 00000000..4f900194 Binary files /dev/null and b/assets/models/players/crash_color/icon_skin3.tga differ diff --git a/assets/models/players/crash_color/icon_skin4.tga b/assets/models/players/crash_color/icon_skin4.tga new file mode 100644 index 00000000..4f900194 Binary files /dev/null and b/assets/models/players/crash_color/icon_skin4.tga differ diff --git a/assets/models/players/crash_color/icon_skin5.tga b/assets/models/players/crash_color/icon_skin5.tga new file mode 100644 index 00000000..4f900194 Binary files /dev/null and b/assets/models/players/crash_color/icon_skin5.tga differ diff --git a/assets/models/players/crash_color/icon_skin6.tga b/assets/models/players/crash_color/icon_skin6.tga new file mode 100644 index 00000000..4f900194 Binary files /dev/null and b/assets/models/players/crash_color/icon_skin6.tga differ diff --git a/assets/models/players/crash_color/icon_skin7.tga b/assets/models/players/crash_color/icon_skin7.tga new file mode 100644 index 00000000..4f900194 Binary files /dev/null and b/assets/models/players/crash_color/icon_skin7.tga differ diff --git a/assets/models/players/crash_color/lower_skin1.skin b/assets/models/players/crash_color/lower_skin1.skin new file mode 100644 index 00000000..504f7a48 --- /dev/null +++ b/assets/models/players/crash_color/lower_skin1.skin @@ -0,0 +1,2 @@ +l_lower,models/players/crash_color/skin_base1.tga +tag_torso diff --git a/assets/models/players/crash_color/lower_skin1_blue.skin b/assets/models/players/crash_color/lower_skin1_blue.skin new file mode 100644 index 00000000..504f7a48 --- /dev/null +++ b/assets/models/players/crash_color/lower_skin1_blue.skin @@ -0,0 +1,2 @@ +l_lower,models/players/crash_color/skin_base1.tga +tag_torso diff --git a/assets/models/players/crash_color/lower_skin1_red.skin b/assets/models/players/crash_color/lower_skin1_red.skin new file mode 100644 index 00000000..504f7a48 --- /dev/null +++ b/assets/models/players/crash_color/lower_skin1_red.skin @@ -0,0 +1,2 @@ +l_lower,models/players/crash_color/skin_base1.tga +tag_torso diff --git a/assets/models/players/crash_color/lower_skin2.skin b/assets/models/players/crash_color/lower_skin2.skin new file mode 100644 index 00000000..76b82ea4 --- /dev/null +++ b/assets/models/players/crash_color/lower_skin2.skin @@ -0,0 +1,2 @@ +l_lower,models/players/crash_color/skin_base2.tga +tag_torso diff --git a/assets/models/players/crash_color/lower_skin2_blue.skin b/assets/models/players/crash_color/lower_skin2_blue.skin new file mode 100644 index 00000000..76b82ea4 --- /dev/null +++ b/assets/models/players/crash_color/lower_skin2_blue.skin @@ -0,0 +1,2 @@ +l_lower,models/players/crash_color/skin_base2.tga +tag_torso diff --git a/assets/models/players/crash_color/lower_skin2_red.skin b/assets/models/players/crash_color/lower_skin2_red.skin new file mode 100644 index 00000000..76b82ea4 --- /dev/null +++ b/assets/models/players/crash_color/lower_skin2_red.skin @@ -0,0 +1,2 @@ +l_lower,models/players/crash_color/skin_base2.tga +tag_torso diff --git a/assets/models/players/crash_color/lower_skin3.skin b/assets/models/players/crash_color/lower_skin3.skin new file mode 100644 index 00000000..e22bd830 --- /dev/null +++ b/assets/models/players/crash_color/lower_skin3.skin @@ -0,0 +1,2 @@ +l_lower,models/players/crash_color/skin_base3.tga +tag_torso diff --git a/assets/models/players/crash_color/lower_skin3_blue.skin b/assets/models/players/crash_color/lower_skin3_blue.skin new file mode 100644 index 00000000..e22bd830 --- /dev/null +++ b/assets/models/players/crash_color/lower_skin3_blue.skin @@ -0,0 +1,2 @@ +l_lower,models/players/crash_color/skin_base3.tga +tag_torso diff --git a/assets/models/players/crash_color/lower_skin3_red.skin b/assets/models/players/crash_color/lower_skin3_red.skin new file mode 100644 index 00000000..e22bd830 --- /dev/null +++ b/assets/models/players/crash_color/lower_skin3_red.skin @@ -0,0 +1,2 @@ +l_lower,models/players/crash_color/skin_base3.tga +tag_torso diff --git a/assets/models/players/crash_color/lower_skin4.skin b/assets/models/players/crash_color/lower_skin4.skin new file mode 100644 index 00000000..72c9d01f --- /dev/null +++ b/assets/models/players/crash_color/lower_skin4.skin @@ -0,0 +1,2 @@ +l_lower,models/players/crash_color/skin_base4.tga +tag_torso diff --git a/assets/models/players/crash_color/lower_skin4_blue.skin b/assets/models/players/crash_color/lower_skin4_blue.skin new file mode 100644 index 00000000..72c9d01f --- /dev/null +++ b/assets/models/players/crash_color/lower_skin4_blue.skin @@ -0,0 +1,2 @@ +l_lower,models/players/crash_color/skin_base4.tga +tag_torso diff --git a/assets/models/players/crash_color/lower_skin4_red.skin b/assets/models/players/crash_color/lower_skin4_red.skin new file mode 100644 index 00000000..72c9d01f --- /dev/null +++ b/assets/models/players/crash_color/lower_skin4_red.skin @@ -0,0 +1,2 @@ +l_lower,models/players/crash_color/skin_base4.tga +tag_torso diff --git a/assets/models/players/crash_color/lower_skin5.skin b/assets/models/players/crash_color/lower_skin5.skin new file mode 100644 index 00000000..82e29dfc --- /dev/null +++ b/assets/models/players/crash_color/lower_skin5.skin @@ -0,0 +1,2 @@ +l_lower,models/players/crash_color/skin_base5.tga +tag_torso diff --git a/assets/models/players/crash_color/lower_skin5_blue.skin b/assets/models/players/crash_color/lower_skin5_blue.skin new file mode 100644 index 00000000..82e29dfc --- /dev/null +++ b/assets/models/players/crash_color/lower_skin5_blue.skin @@ -0,0 +1,2 @@ +l_lower,models/players/crash_color/skin_base5.tga +tag_torso diff --git a/assets/models/players/crash_color/lower_skin5_red.skin b/assets/models/players/crash_color/lower_skin5_red.skin new file mode 100644 index 00000000..82e29dfc --- /dev/null +++ b/assets/models/players/crash_color/lower_skin5_red.skin @@ -0,0 +1,2 @@ +l_lower,models/players/crash_color/skin_base5.tga +tag_torso diff --git a/assets/models/players/crash_color/lower_skin6.skin b/assets/models/players/crash_color/lower_skin6.skin new file mode 100644 index 00000000..2e64e349 --- /dev/null +++ b/assets/models/players/crash_color/lower_skin6.skin @@ -0,0 +1,2 @@ +l_lower,models/players/crash_color/skin_base6.tga +tag_torso diff --git a/assets/models/players/crash_color/lower_skin6_blue.skin b/assets/models/players/crash_color/lower_skin6_blue.skin new file mode 100644 index 00000000..2e64e349 --- /dev/null +++ b/assets/models/players/crash_color/lower_skin6_blue.skin @@ -0,0 +1,2 @@ +l_lower,models/players/crash_color/skin_base6.tga +tag_torso diff --git a/assets/models/players/crash_color/lower_skin6_red.skin b/assets/models/players/crash_color/lower_skin6_red.skin new file mode 100644 index 00000000..2e64e349 --- /dev/null +++ b/assets/models/players/crash_color/lower_skin6_red.skin @@ -0,0 +1,2 @@ +l_lower,models/players/crash_color/skin_base6.tga +tag_torso diff --git a/assets/models/players/crash_color/lower_skin7.skin b/assets/models/players/crash_color/lower_skin7.skin new file mode 100644 index 00000000..3884915b --- /dev/null +++ b/assets/models/players/crash_color/lower_skin7.skin @@ -0,0 +1,2 @@ +l_lower,models/players/crash_color/skin_base7.tga +tag_torso diff --git a/assets/models/players/crash_color/lower_skin7_blue.skin b/assets/models/players/crash_color/lower_skin7_blue.skin new file mode 100644 index 00000000..3884915b --- /dev/null +++ b/assets/models/players/crash_color/lower_skin7_blue.skin @@ -0,0 +1,2 @@ +l_lower,models/players/crash_color/skin_base7.tga +tag_torso diff --git a/assets/models/players/crash_color/lower_skin7_red.skin b/assets/models/players/crash_color/lower_skin7_red.skin new file mode 100644 index 00000000..3884915b --- /dev/null +++ b/assets/models/players/crash_color/lower_skin7_red.skin @@ -0,0 +1,2 @@ +l_lower,models/players/crash_color/skin_base7.tga +tag_torso diff --git a/assets/models/players/crash_color/skin1/head_blue.skin b/assets/models/players/crash_color/skin1/head_blue.skin new file mode 100644 index 00000000..5a371e68 --- /dev/null +++ b/assets/models/players/crash_color/skin1/head_blue.skin @@ -0,0 +1,2 @@ +h_face,models/players/crash_color/skin_base1.tga +tag_head diff --git a/assets/models/players/crash_color/skin1/head_default.skin b/assets/models/players/crash_color/skin1/head_default.skin new file mode 100644 index 00000000..5a371e68 --- /dev/null +++ b/assets/models/players/crash_color/skin1/head_default.skin @@ -0,0 +1,2 @@ +h_face,models/players/crash_color/skin_base1.tga +tag_head diff --git a/assets/models/players/crash_color/skin1/head_red.skin b/assets/models/players/crash_color/skin1/head_red.skin new file mode 100644 index 00000000..5a371e68 --- /dev/null +++ b/assets/models/players/crash_color/skin1/head_red.skin @@ -0,0 +1,2 @@ +h_face,models/players/crash_color/skin_base1.tga +tag_head diff --git a/assets/models/players/crash_color/skin_base.tga b/assets/models/players/crash_color/skin_base.tga index 30978cc3..4b3e87a3 100644 Binary files a/assets/models/players/crash_color/skin_base.tga and b/assets/models/players/crash_color/skin_base.tga differ diff --git a/assets/models/players/crash_color/skin_base1.tga b/assets/models/players/crash_color/skin_base1.tga new file mode 100644 index 00000000..4b3e87a3 Binary files /dev/null and b/assets/models/players/crash_color/skin_base1.tga differ diff --git a/assets/models/players/crash_color/skin_base2.tga b/assets/models/players/crash_color/skin_base2.tga new file mode 100644 index 00000000..4b3e87a3 Binary files /dev/null and b/assets/models/players/crash_color/skin_base2.tga differ diff --git a/assets/models/players/crash_color/skin_base3.tga b/assets/models/players/crash_color/skin_base3.tga new file mode 100644 index 00000000..4b3e87a3 Binary files /dev/null and b/assets/models/players/crash_color/skin_base3.tga differ diff --git a/assets/models/players/crash_color/skin_base4.tga b/assets/models/players/crash_color/skin_base4.tga new file mode 100644 index 00000000..4b3e87a3 Binary files /dev/null and b/assets/models/players/crash_color/skin_base4.tga differ diff --git a/assets/models/players/crash_color/skin_base5.tga b/assets/models/players/crash_color/skin_base5.tga new file mode 100644 index 00000000..4b3e87a3 Binary files /dev/null and b/assets/models/players/crash_color/skin_base5.tga differ diff --git a/assets/models/players/crash_color/skin_base6.tga b/assets/models/players/crash_color/skin_base6.tga new file mode 100644 index 00000000..4b3e87a3 Binary files /dev/null and b/assets/models/players/crash_color/skin_base6.tga differ diff --git a/assets/models/players/crash_color/skin_base7.tga b/assets/models/players/crash_color/skin_base7.tga new file mode 100644 index 00000000..4b3e87a3 Binary files /dev/null and b/assets/models/players/crash_color/skin_base7.tga differ diff --git a/assets/models/players/crash_color/upper_skin1.skin b/assets/models/players/crash_color/upper_skin1.skin new file mode 100644 index 00000000..a87c97fa --- /dev/null +++ b/assets/models/players/crash_color/upper_skin1.skin @@ -0,0 +1,4 @@ +u_body,models/players/crash_color/skin_base1..tga +tag_head, +tag_weapon, +tag_torso, diff --git a/assets/models/players/crash_color/upper_skin1_blue.skin b/assets/models/players/crash_color/upper_skin1_blue.skin new file mode 100644 index 00000000..a87c97fa --- /dev/null +++ b/assets/models/players/crash_color/upper_skin1_blue.skin @@ -0,0 +1,4 @@ +u_body,models/players/crash_color/skin_base1..tga +tag_head, +tag_weapon, +tag_torso, diff --git a/assets/models/players/crash_color/upper_skin1_red.skin b/assets/models/players/crash_color/upper_skin1_red.skin new file mode 100644 index 00000000..a87c97fa --- /dev/null +++ b/assets/models/players/crash_color/upper_skin1_red.skin @@ -0,0 +1,4 @@ +u_body,models/players/crash_color/skin_base1..tga +tag_head, +tag_weapon, +tag_torso, diff --git a/assets/models/players/crash_color/upper_skin2.skin b/assets/models/players/crash_color/upper_skin2.skin new file mode 100644 index 00000000..5bdbbb87 --- /dev/null +++ b/assets/models/players/crash_color/upper_skin2.skin @@ -0,0 +1,4 @@ +u_body,models/players/crash_color/skin_base2..tga +tag_head, +tag_weapon, +tag_torso, diff --git a/assets/models/players/crash_color/upper_skin2_blue.skin b/assets/models/players/crash_color/upper_skin2_blue.skin new file mode 100644 index 00000000..5bdbbb87 --- /dev/null +++ b/assets/models/players/crash_color/upper_skin2_blue.skin @@ -0,0 +1,4 @@ +u_body,models/players/crash_color/skin_base2..tga +tag_head, +tag_weapon, +tag_torso, diff --git a/assets/models/players/crash_color/upper_skin2_red.skin b/assets/models/players/crash_color/upper_skin2_red.skin new file mode 100644 index 00000000..5bdbbb87 --- /dev/null +++ b/assets/models/players/crash_color/upper_skin2_red.skin @@ -0,0 +1,4 @@ +u_body,models/players/crash_color/skin_base2..tga +tag_head, +tag_weapon, +tag_torso, diff --git a/assets/models/players/crash_color/upper_skin3.skin b/assets/models/players/crash_color/upper_skin3.skin new file mode 100644 index 00000000..92733a87 --- /dev/null +++ b/assets/models/players/crash_color/upper_skin3.skin @@ -0,0 +1,4 @@ +u_body,models/players/crash_color/skin_base3..tga +tag_head, +tag_weapon, +tag_torso, diff --git a/assets/models/players/crash_color/upper_skin3_blue.skin b/assets/models/players/crash_color/upper_skin3_blue.skin new file mode 100644 index 00000000..92733a87 --- /dev/null +++ b/assets/models/players/crash_color/upper_skin3_blue.skin @@ -0,0 +1,4 @@ +u_body,models/players/crash_color/skin_base3..tga +tag_head, +tag_weapon, +tag_torso, diff --git a/assets/models/players/crash_color/upper_skin3_red.skin b/assets/models/players/crash_color/upper_skin3_red.skin new file mode 100644 index 00000000..92733a87 --- /dev/null +++ b/assets/models/players/crash_color/upper_skin3_red.skin @@ -0,0 +1,4 @@ +u_body,models/players/crash_color/skin_base3..tga +tag_head, +tag_weapon, +tag_torso, diff --git a/assets/models/players/crash_color/upper_skin4.skin b/assets/models/players/crash_color/upper_skin4.skin new file mode 100644 index 00000000..9b9ce4e8 --- /dev/null +++ b/assets/models/players/crash_color/upper_skin4.skin @@ -0,0 +1,4 @@ +u_body,models/players/crash_color/skin_base4..tga +tag_head, +tag_weapon, +tag_torso, diff --git a/assets/models/players/crash_color/upper_skin4_blue.skin b/assets/models/players/crash_color/upper_skin4_blue.skin new file mode 100644 index 00000000..9b9ce4e8 --- /dev/null +++ b/assets/models/players/crash_color/upper_skin4_blue.skin @@ -0,0 +1,4 @@ +u_body,models/players/crash_color/skin_base4..tga +tag_head, +tag_weapon, +tag_torso, diff --git a/assets/models/players/crash_color/upper_skin4_red.skin b/assets/models/players/crash_color/upper_skin4_red.skin new file mode 100644 index 00000000..9b9ce4e8 --- /dev/null +++ b/assets/models/players/crash_color/upper_skin4_red.skin @@ -0,0 +1,4 @@ +u_body,models/players/crash_color/skin_base4..tga +tag_head, +tag_weapon, +tag_torso, diff --git a/assets/models/players/crash_color/upper_skin5.skin b/assets/models/players/crash_color/upper_skin5.skin new file mode 100644 index 00000000..2ac63fcf --- /dev/null +++ b/assets/models/players/crash_color/upper_skin5.skin @@ -0,0 +1,4 @@ +u_body,models/players/crash_color/skin_base5..tga +tag_head, +tag_weapon, +tag_torso, diff --git a/assets/models/players/crash_color/upper_skin5_blue.skin b/assets/models/players/crash_color/upper_skin5_blue.skin new file mode 100644 index 00000000..2ac63fcf --- /dev/null +++ b/assets/models/players/crash_color/upper_skin5_blue.skin @@ -0,0 +1,4 @@ +u_body,models/players/crash_color/skin_base5..tga +tag_head, +tag_weapon, +tag_torso, diff --git a/assets/models/players/crash_color/upper_skin5_red.skin b/assets/models/players/crash_color/upper_skin5_red.skin new file mode 100644 index 00000000..2ac63fcf --- /dev/null +++ b/assets/models/players/crash_color/upper_skin5_red.skin @@ -0,0 +1,4 @@ +u_body,models/players/crash_color/skin_base5..tga +tag_head, +tag_weapon, +tag_torso, diff --git a/assets/models/players/crash_color/upper_skin6.skin b/assets/models/players/crash_color/upper_skin6.skin new file mode 100644 index 00000000..a6329a64 --- /dev/null +++ b/assets/models/players/crash_color/upper_skin6.skin @@ -0,0 +1,4 @@ +u_body,models/players/crash_color/skin_base6..tga +tag_head, +tag_weapon, +tag_torso, diff --git a/assets/models/players/crash_color/upper_skin6_blue.skin b/assets/models/players/crash_color/upper_skin6_blue.skin new file mode 100644 index 00000000..a6329a64 --- /dev/null +++ b/assets/models/players/crash_color/upper_skin6_blue.skin @@ -0,0 +1,4 @@ +u_body,models/players/crash_color/skin_base6..tga +tag_head, +tag_weapon, +tag_torso, diff --git a/assets/models/players/crash_color/upper_skin6_red.skin b/assets/models/players/crash_color/upper_skin6_red.skin new file mode 100644 index 00000000..a6329a64 --- /dev/null +++ b/assets/models/players/crash_color/upper_skin6_red.skin @@ -0,0 +1,4 @@ +u_body,models/players/crash_color/skin_base6..tga +tag_head, +tag_weapon, +tag_torso, diff --git a/assets/models/players/crash_color/upper_skin7.skin b/assets/models/players/crash_color/upper_skin7.skin new file mode 100644 index 00000000..5d504b02 --- /dev/null +++ b/assets/models/players/crash_color/upper_skin7.skin @@ -0,0 +1,4 @@ +u_body,models/players/crash_color/skin_base7..tga +tag_head, +tag_weapon, +tag_torso, diff --git a/assets/models/players/crash_color/upper_skin7_blue.skin b/assets/models/players/crash_color/upper_skin7_blue.skin new file mode 100644 index 00000000..5d504b02 --- /dev/null +++ b/assets/models/players/crash_color/upper_skin7_blue.skin @@ -0,0 +1,4 @@ +u_body,models/players/crash_color/skin_base7..tga +tag_head, +tag_weapon, +tag_torso, diff --git a/assets/models/players/crash_color/upper_skin7_red.skin b/assets/models/players/crash_color/upper_skin7_red.skin new file mode 100644 index 00000000..5d504b02 --- /dev/null +++ b/assets/models/players/crash_color/upper_skin7_red.skin @@ -0,0 +1,4 @@ +u_body,models/players/crash_color/skin_base7..tga +tag_head, +tag_weapon, +tag_torso, diff --git a/assets/models/rock_desert_01.md3 b/assets/models/rock_desert_01.md3 new file mode 100644 index 00000000..53e805c8 Binary files /dev/null and b/assets/models/rock_desert_01.md3 differ diff --git a/assets/q3config.cfg b/assets/q3config.cfg index 7db6cd09..30855325 100644 --- a/assets/q3config.cfg +++ b/assets/q3config.cfg @@ -158,7 +158,7 @@ seta cg_voipTeamOnly "1" seta cg_weaponBarStyle "0" seta cg_weaponOrder "/1/2/4/3/6/7/8/9/5/" seta cg_zoomfov "22.5" -seta cl_allowDownload "0" +seta cl_allowDownload "1" seta cl_anonymous "0" seta cl_autoRecordDemo "0" seta cl_aviFrameRate "25" @@ -199,7 +199,7 @@ seta color2 "5" seta com_altivec "0" seta com_ansiColor "0" seta com_blood "0" -seta com_busyWait "0" +seta com_busyWait "1" seta com_hunkMegs "128" seta com_introplayed "1" seta com_logToStdErr "0" @@ -231,7 +231,7 @@ seta elimination_startHealth "200" seta elimination_warmup "7" seta fraglimit "0" seta g_admin "admin.dat" -seta g_adminLog "admin.log" +seta g_adminLog "" seta g_adminMaxBan "2w" seta g_adminNameProtect "1" seta g_adminParseSay "1" @@ -250,7 +250,7 @@ seta g_floodMinTime "2000" seta g_friendlyfire "0" seta g_lagLightning "1" seta g_lms_mode "0" -seta g_log "games.log" +seta g_log "" seta g_logsync "0" seta g_mappools "0\maps_dm.cfg\1\maps_tourney.cfg\3\maps_tdm.cfg\4\maps_ctf.cfg\5\maps_oneflag.cfg\6\maps_obelisk.cfg\7\maps_harvester.cfg\8\maps_elimination.cfg\9\maps_ctf.cfg\10\maps_lms.cfg\11\maps_dd.cfg\12\maps_dom.cfg\" seta g_maxGameClients "0" @@ -375,6 +375,7 @@ seta r_flaresDlightShrink "1" seta r_flareSun "0" seta r_fullscreen "0" seta r_gamma "1" +seta r_gpuDeviceIndex "0" seta r_greyscale "0" seta r_iconmip "1" seta r_ignoreFastPath "1" @@ -391,7 +392,7 @@ seta r_lodCurveError "250" seta r_marksOnTriangleMeshes "0" seta r_mockvr "0" seta r_mode "-1" -seta r_monolightmaps "0" +seta r_monolightmaps "1" seta r_motionblur "0" seta r_noborder "0" seta r_ntsc "0" @@ -418,6 +419,7 @@ seta r_subdivisions "1" seta r_suggestiveThemes "1" seta r_swapInterval "0" seta r_texturebits "0" +seta r_textureMaxSize "256" seta r_textureMode "GL_NEAREST_MIPMAP_NEAREST" seta r_tvConsoleMode "0" seta r_tvMode "0" @@ -457,21 +459,26 @@ seta server8 "" seta server9 "" seta sex "male" seta snaps "20" +seta sv_allowDownload "1" seta sv_banFile "serverbans.dat" -seta sv_dlRate "100" +seta sv_dlRate "8192" seta sv_dlURL "" -seta sv_floodProtect "1" +seta sv_floodProtect "0" seta sv_fps "20" seta sv_hostname "noname" seta sv_lanForceRate "1" seta sv_master3 "" seta sv_master4 "" seta sv_master5 "" -seta sv_maxclients "8" +seta sv_maxclients "64" seta sv_maxPing "0" +seta sv_rateLimit "0" seta sv_maxRate "0" seta sv_minPing "0" seta sv_minRate "0" +seta sv_pure "0" +seta sv_reconnectlimit "0" +seta sv_timeout "3600" seta team_headmodel "crash" seta team_model "crash" seta timelimit "0" diff --git a/assets/scripts/decal.shader b/assets/scripts/decal.shader new file mode 100644 index 00000000..afe611b4 --- /dev/null +++ b/assets/scripts/decal.shader @@ -0,0 +1,720 @@ +textures/decal/lab_games/dec_img_style01_001_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style01_001 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style01_001 + } +} + +textures/decal/lab_games/dec_img_style01_002_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style01_002 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style01_002 + } +} + +textures/decal/lab_games/dec_img_style01_003_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style01_003 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style01_003 + } +} + +textures/decal/lab_games/dec_img_style01_004_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style01_004 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style01_004 + } +} + +textures/decal/lab_games/dec_img_style01_005_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style01_005 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style01_005 + } +} + +textures/decal/lab_games/dec_img_style01_006_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style01_006 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style01_006 + } +} + +textures/decal/lab_games/dec_img_style01_007_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style01_007 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style01_007 + } +} + +textures/decal/lab_games/dec_img_style01_008_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style01_008 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style01_008 + } +} + +textures/decal/lab_games/dec_img_style01_009_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style01_009 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style01_009 + } +} + +textures/decal/lab_games/dec_img_style01_010_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style01_010 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style01_010 + } +} + +textures/decal/lab_games/dec_img_style01_011_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style01_011 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style01_011 + } +} + +textures/decal/lab_games/dec_img_style01_012_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style01_012 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style01_012 + } +} + +textures/decal/lab_games/dec_img_style01_013_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style01_013 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style01_013 + } +} + +textures/decal/lab_games/dec_img_style01_014_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style01_014 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style01_014 + } +} + +textures/decal/lab_games/dec_img_style01_015_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style01_015 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style01_015 + } +} + +textures/decal/lab_games/dec_img_style01_016_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style01_016 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style01_016 + } +} + +textures/decal/lab_games/dec_img_style01_017_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style01_017 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style01_017 + } +} + +textures/decal/lab_games/dec_img_style01_018_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style01_018 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style01_018 + } +} + +textures/decal/lab_games/dec_img_style01_019_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style01_019 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style01_019 + } +} + +textures/decal/lab_games/dec_img_style01_020_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style01_020 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style01_020 + } +} + +textures/decal/lab_games/dec_img_style02_001_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style02_001 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style02_001 + } +} + +textures/decal/lab_games/dec_img_style02_002_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style02_002 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style02_002 + } +} + +textures/decal/lab_games/dec_img_style02_003_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style02_003 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style02_003 + } +} + +textures/decal/lab_games/dec_img_style02_004_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style02_004 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style02_004 + } +} + +textures/decal/lab_games/dec_img_style02_005_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style02_005 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style02_005 + } +} + +textures/decal/lab_games/dec_img_style02_006_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style02_006 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style02_006 + } +} + +textures/decal/lab_games/dec_img_style02_007_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style02_007 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style02_007 + } +} + +textures/decal/lab_games/dec_img_style02_008_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style02_008 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style02_008 + } +} + +textures/decal/lab_games/dec_img_style02_009_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style02_009 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style02_009 + } +} + +textures/decal/lab_games/dec_img_style02_010_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style02_010 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style02_010 + } +} + +textures/decal/lab_games/dec_img_style02_011_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style02_011 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style02_011 + } +} + +textures/decal/lab_games/dec_img_style02_012_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style02_012 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style02_012 + } +} + +textures/decal/lab_games/dec_img_style02_013_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style02_013 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style02_013 + } +} + +textures/decal/lab_games/dec_img_style02_014_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style02_014 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style02_014 + } +} + +textures/decal/lab_games/dec_img_style02_015_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style02_015 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style02_015 + } +} + +textures/decal/lab_games/dec_img_style02_016_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style02_016 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style02_016 + } +} + +textures/decal/lab_games/dec_img_style02_017_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style02_017 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style02_017 + } +} + +textures/decal/lab_games/dec_img_style02_018_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style02_018 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style02_018 + } +} + +textures/decal/lab_games/dec_img_style02_019_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style02_019 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style02_019 + } +} + +textures/decal/lab_games/dec_img_style02_020_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style02_020 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style02_020 + } +} + +textures/decal/lab_games/dec_img_style03_001_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style03_001 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style03_001 + } +} + +textures/decal/lab_games/dec_img_style03_002_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style03_002 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style03_002 + } +} + +textures/decal/lab_games/dec_img_style03_003_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style03_003 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style03_003 + } +} + +textures/decal/lab_games/dec_img_style03_004_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style03_004 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style03_004 + } +} + +textures/decal/lab_games/dec_img_style03_005_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style03_005 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style03_005 + } +} + +textures/decal/lab_games/dec_img_style03_006_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style03_006 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style03_006 + } +} + +textures/decal/lab_games/dec_img_style03_007_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style03_007 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style03_007 + } +} + +textures/decal/lab_games/dec_img_style03_008_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style03_008 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style03_008 + } +} + +textures/decal/lab_games/dec_img_style03_009_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style03_009 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style03_009 + } +} + +textures/decal/lab_games/dec_img_style03_010_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style03_010 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style03_010 + } +} + +textures/decal/lab_games/dec_img_style03_011_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style03_011 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style03_011 + } +} + +textures/decal/lab_games/dec_img_style03_012_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style03_012 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style03_012 + } +} + +textures/decal/lab_games/dec_img_style03_013_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style03_013 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style03_013 + } +} + +textures/decal/lab_games/dec_img_style03_014_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style03_014 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style03_014 + } +} + +textures/decal/lab_games/dec_img_style03_015_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style03_015 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style03_015 + } +} + +textures/decal/lab_games/dec_img_style03_016_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style03_016 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style03_016 + } +} + +textures/decal/lab_games/dec_img_style03_017_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style03_017 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style03_017 + } +} + +textures/decal/lab_games/dec_img_style03_018_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style03_018 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style03_018 + } +} + +textures/decal/lab_games/dec_img_style03_019_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style03_019 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style03_019 + } +} + +textures/decal/lab_games/dec_img_style03_020_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style03_020 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style03_020 + } +} + +textures/decal/lab_games/dec_img_style04_001_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style04_001 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style04_001 + } +} + +textures/decal/lab_games/dec_img_style04_002_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style04_002 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style04_002 + } +} + +textures/decal/lab_games/dec_img_style04_003_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style04_003 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style04_003 + } +} + +textures/decal/lab_games/dec_img_style04_004_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style04_004 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style04_004 + } +} + +textures/decal/lab_games/dec_img_style04_005_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style04_005 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style04_005 + } +} + +textures/decal/lab_games/dec_img_style04_006_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style04_006 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style04_006 + } +} + +textures/decal/lab_games/dec_img_style04_007_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style04_007 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style04_007 + } +} + +textures/decal/lab_games/dec_img_style04_008_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style04_008 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style04_008 + } +} + +textures/decal/lab_games/dec_img_style04_009_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style04_009 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style04_009 + } +} + +textures/decal/lab_games/dec_img_style04_010_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style04_010 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style04_010 + } +} + +textures/decal/lab_games/dec_img_style04_011_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style04_011 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style04_011 + } +} + +textures/decal/lab_games/dec_img_style04_012_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style04_012 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style04_012 + } +} + +textures/decal/lab_games/dec_img_style04_013_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style04_013 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style04_013 + } +} + +textures/decal/lab_games/dec_img_style04_014_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style04_014 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style04_014 + } +} + +textures/decal/lab_games/dec_img_style04_015_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style04_015 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style04_015 + } +} + +textures/decal/lab_games/dec_img_style04_016_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style04_016 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style04_016 + } +} + +textures/decal/lab_games/dec_img_style04_017_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style04_017 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style04_017 + } +} + +textures/decal/lab_games/dec_img_style04_018_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style04_018 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style04_018 + } +} + +textures/decal/lab_games/dec_img_style04_019_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style04_019 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style04_019 + } +} + +textures/decal/lab_games/dec_img_style04_020_nonsolid +{ + qer_editorimage textures/decal/lab_games/dec_img_style04_020 + surfaceparm nonsolid + { + map textures/decal/lab_games/dec_img_style04_020 + } +} + diff --git a/assets/scripts/fut_teleport_d.shader b/assets/scripts/fut_teleport_d.shader deleted file mode 100644 index 448451cb..00000000 --- a/assets/scripts/fut_teleport_d.shader +++ /dev/null @@ -1,30 +0,0 @@ -textures/model/fut_teleport_d -{ - qer_editorimage textures/model/fut_teleport_d.tga - surfaceparm metalsteps - { - map textures/map/water_env.tga - blendfunc add - tcGen environment - } - { - map textures/model/fut_teleport_d.tga - blendfunc GL_ZERO GL_SRC_ALPHA - } - - { - map textures/model/fut_teleport_d.tga - } - { - map $lightmap - blendfunc filter - tcGen lightmap - } - { - map textures/model/fut_teleport_e.tga - rgbGen wave sin 0.3 0.3 0.0 0.5 - blendfunc add - } -} - - diff --git a/assets/scripts/lab_floors.shader b/assets/scripts/lab_floors.shader new file mode 100644 index 00000000..6acf92d2 --- /dev/null +++ b/assets/scripts/lab_floors.shader @@ -0,0 +1,183 @@ +// FLOORS FOR DYNAMIC COLOURING + +textures/map/lab_floors/lg_style_06_floor_1 +{ + qer_editorimage textures/map/lab_games/lg_style_01_floor_orange_d.tga + nopicmip + q3map_lightmapSampleSize 8 + { + map textures/map/lab_games/lg_style_01_floor_orange_d.tga + } + { + map textures/map/lab_games/lg_circuit_r.tga + blendfunc add + rgbgen const ( 0.06 0.06 0.06 ) + tcgen environment + } + { + map $lightmap + blendFunc GL_DST_COLOR GL_ZERO + rgbGen identity + } + { + map textures/map/lab_games/lg_style_01_floor_light_m.tga + blendfunc add + rgbgen const ( 0.00 0.58 1.00 ) + } +} + +textures/map/lab_floors/lg_style_06_floor_2 +{ + qer_editorimage textures/map/lab_games/lg_style_01_floor_orange_bright_d.tga + nopicmip + q3map_lightmapSampleSize 8 + { + map textures/map/lab_games/lg_style_01_floor_orange_bright_d.tga + } + { + map textures/map/lab_games/lg_circuit_r.tga + blendfunc add + rgbgen const ( 0.06 0.06 0.06 ) + tcgen environment + } + { + map $lightmap + blendFunc GL_DST_COLOR GL_ZERO + rgbGen identity + } + { + map textures/map/lab_games/lg_style_01_floor_light_m.tga + blendfunc add + rgbgen const ( 0.00 0.58 1.00 ) + } +} + +textures/map/lab_floors/lg_style_06_floor_3 +{ + qer_editorimage textures/map/lab_games/lg_style_01_floor_blue_d.tga + nopicmip + q3map_lightmapSampleSize 8 + { + map textures/map/lab_games/lg_style_01_floor_blue_d.tga + } + { + map textures/map/lab_games/lg_circuit_r.tga + blendfunc add + rgbgen const ( 0.06 0.06 0.06 ) + tcgen environment + } + { + map $lightmap + blendFunc GL_DST_COLOR GL_ZERO + rgbGen identity + } + { + map textures/map/lab_games/lg_style_01_floor_light_m + blendfunc add + rgbgen const ( 0.00 0.58 1.00 ) + } +} + +textures/map/lab_floors/lg_style_06_floor_4 +{ + qer_editorimage textures/map/lab_games/lg_style_01_floor_blue_bright_d.tga + nopicmip + q3map_lightmapSampleSize 8 + { + map textures/map/lab_games/lg_style_01_floor_blue_bright_d.tga + } + { + map textures/map/lab_games/lg_circuit_r.tga + blendfunc add + rgbgen const ( 0.06 0.06 0.06 ) + tcgen environment + } + { + map $lightmap + blendFunc GL_DST_COLOR GL_ZERO + rgbGen identity + } + { + map textures/map/lab_games/lg_style_01_floor_light_m + blendfunc add + rgbgen const ( 0.00 0.58 1.00 ) + } +} + +textures/map/lab_floors/lg_style_06_floor_5 +{ + qer_editorimage textures/map/lab_games/lg_style_02_floor_blue_d.tga + nopicmip + q3map_lightmapSampleSize 8 + { + map textures/map/lab_games/lg_style_02_floor_blue_d.tga + } + { + map textures/map/lab_games/lg_circuit_r.tga + blendfunc add + rgbgen const ( 0.06 0.06 0.06 ) + tcgen environment + } + { + map $lightmap + blendFunc GL_DST_COLOR GL_ZERO + rgbGen identity + } + { + map textures/map/lab_games/lg_style_02_floor_light_m.tga + blendfunc add + rgbGen wave sin 1.0 1.0 0.0 0.15 + } +} + +textures/map/lab_floors/lg_style_06_floor_6 +{ + qer_editorimage textures/map/lab_games/lg_style_02_floor_blue_bright_d.tga + nopicmip + q3map_lightmapSampleSize 8 + { + map textures/map/lab_games/lg_style_02_floor_blue_bright_d.tga + } + { + map textures/map/lab_games/lg_circuit_r.tga + blendfunc add + rgbgen const ( 0.06 0.06 0.06 ) + tcgen environment + } + { + map $lightmap + blendFunc GL_DST_COLOR GL_ZERO + rgbGen identity + } + { + map textures/map/lab_games/lg_style_02_floor_light_m.tga + blendfunc add + rgbGen wave sin 1.0 1.0 0.0 0.15 + } +} + +textures/map/lab_floors/lg_style_06_floor_7 +{ + qer_editorimage textures/map/lab_games/lg_style_02_floor_green_d.tga + nopicmip + q3map_lightmapSampleSize 8 + { + map textures/map/lab_games/lg_style_02_floor_green_d.tga + } + { + map textures/map/lab_games/lg_circuit_r.tga + blendfunc add + rgbgen const ( 0.06 0.06 0.06 ) + tcgen environment + } + { + map $lightmap + blendFunc GL_DST_COLOR GL_ZERO + rgbGen identity + } + { + map textures/map/lab_games/lg_style_02_floor_light_m.tga + blendfunc add + rgbGen wave sin 1.0 1.0 0.0 0.15 + } +} diff --git a/assets/scripts/lab_floors_placeholders.shader b/assets/scripts/lab_floors_placeholders.shader new file mode 100644 index 00000000..96de62ed --- /dev/null +++ b/assets/scripts/lab_floors_placeholders.shader @@ -0,0 +1,181 @@ +textures/map/lab_games/lg_style_01_floor_placeholder_0 +{ + qer_editorimage textures/map/lab_games/lg_floor_placeholder_0_d.tga + nopicmip + q3map_lightmapSampleSize 8 + { + map textures/map/lab_games/lg_floor_placeholder_0_d.tga + } + { + map textures/map/lab_games/lg_circuit_r.tga + blendfunc add + rgbgen const ( 0.06 0.06 0.06 ) + tcgen environment + } + { + map $lightmap + blendFunc GL_DST_COLOR GL_ZERO + rgbGen identity + } + { + map textures/map/lab_games/lg_style_01_floor_light_m.tga + blendfunc add + rgbgen const ( 0.50 0.50 0.50 ) + } +} + +textures/map/lab_games/lg_style_01_floor_placeholder_A +{ + qer_editorimage textures/map/lab_games/lg_floor_placeholder_A_d.tga + nopicmip + q3map_lightmapSampleSize 8 + { + map textures/map/lab_games/lg_floor_placeholder_A_d.tga + } + { + map textures/map/lab_games/lg_circuit_r.tga + blendfunc add + rgbgen const ( 0.06 0.06 0.06 ) + tcgen environment + } + { + map $lightmap + blendFunc GL_DST_COLOR GL_ZERO + rgbGen identity + } + { + map textures/map/lab_games/lg_style_01_floor_light_m.tga + blendfunc add + rgbgen const ( 0.50 0.50 0.50 ) + } +} + +textures/map/lab_games/lg_style_01_floor_placeholder_B +{ + qer_editorimage textures/map/lab_games/lg_floor_placeholder_B_d.tga + nopicmip + q3map_lightmapSampleSize 8 + { + map textures/map/lab_games/lg_floor_placeholder_B_d.tga + } + { + map textures/map/lab_games/lg_circuit_r.tga + blendfunc add + rgbgen const ( 0.06 0.06 0.06 ) + tcgen environment + } + { + map $lightmap + blendFunc GL_DST_COLOR GL_ZERO + rgbGen identity + } + { + map textures/map/lab_games/lg_style_01_floor_light_m.tga + blendfunc add + rgbgen const ( 0.50 0.50 0.50 ) + } +} + +textures/map/lab_games/lg_style_01_floor_placeholder_C +{ + qer_editorimage textures/map/lab_games/lg_floor_placeholder_C_d.tga + nopicmip + q3map_lightmapSampleSize 8 + { + map textures/map/lab_games/lg_floor_placeholder_C_d.tga + } + { + map textures/map/lab_games/lg_circuit_r.tga + blendfunc add + rgbgen const ( 0.06 0.06 0.06 ) + tcgen environment + } + { + map $lightmap + blendFunc GL_DST_COLOR GL_ZERO + rgbGen identity + } + { + map textures/map/lab_games/lg_style_01_floor_light_m.tga + blendfunc add + rgbgen const ( 0.50 0.50 0.50 ) + } +} + +textures/map/lab_games/lg_style_01_floor_placeholder_D +{ + qer_editorimage textures/map/lab_games/lg_floor_placeholder_D_d.tga + nopicmip + q3map_lightmapSampleSize 8 + { + map textures/map/lab_games/lg_floor_placeholder_D_d.tga + } + { + map textures/map/lab_games/lg_circuit_r.tga + blendfunc add + rgbgen const ( 0.06 0.06 0.06 ) + tcgen environment + } + { + map $lightmap + blendFunc GL_DST_COLOR GL_ZERO + rgbGen identity + } + { + map textures/map/lab_games/lg_style_01_floor_light_m.tga + blendfunc add + rgbgen const ( 0.50 0.50 0.50 ) + } +} + +textures/map/lab_games/lg_style_01_floor_placeholder_E +{ + qer_editorimage textures/map/lab_games/lg_floor_placeholder_E_d.tga + nopicmip + q3map_lightmapSampleSize 8 + { + map textures/map/lab_games/lg_floor_placeholder_E_d.tga + } + { + map textures/map/lab_games/lg_circuit_r.tga + blendfunc add + rgbgen const ( 0.06 0.06 0.06 ) + tcgen environment + } + { + map $lightmap + blendFunc GL_DST_COLOR GL_ZERO + rgbGen identity + } + { + map textures/map/lab_games/lg_style_01_floor_light_m.tga + blendfunc add + rgbgen const ( 0.50 0.50 0.50 ) + } +} + +textures/map/lab_games/lg_style_01_floor_placeholder_F +{ + qer_editorimage textures/map/lab_games/lg_floor_placeholder_F_d.tga + nopicmip + q3map_lightmapSampleSize 8 + { + map textures/map/lab_games/lg_floor_placeholder_F_d.tga + } + { + map textures/map/lab_games/lg_circuit_r.tga + blendfunc add + rgbgen const ( 0.06 0.06 0.06 ) + tcgen environment + } + { + map $lightmap + blendFunc GL_DST_COLOR GL_ZERO + rgbGen identity + } + { + map textures/map/lab_games/lg_style_01_floor_light_m.tga + blendfunc add + rgbgen const ( 0.50 0.50 0.50 ) + } +} diff --git a/assets/scripts/lab_games.shader b/assets/scripts/lab_games.shader index fcb437c0..9caccc52 100644 --- a/assets/scripts/lab_games.shader +++ b/assets/scripts/lab_games.shader @@ -2020,6 +2020,35 @@ textures/map/ghost } } +textures/map/water_d +{ + qer_editorimage textures/map/glass_d.tga + surfaceparm trans + { + map textures/map/glass_d.tga + blendfunc blend + } + { + map textures/map/caustics_d.tga + blendfunc add + tcMod scale 0.7 0.7 + tcMod stretch sin 1.0 0.02 0 0.1 + tcMod turb 0 0.04 0 0.12 + } + { + map textures/map/caustics_d.tga + blendfunc add + tcMod scale 0.5 0.5 + tcMod stretch sin 1.0 0.04 0 0.2 + tcMod turb 0 0.06 0.5 0.1 + } + { + map textures/map/water_env.tga + blendfunc add + tcGen environment + } +} + // SPECIFIC OBJECTS textures/model/mothership_d diff --git a/assets/scripts/psychlab.shader b/assets/scripts/psychlab.shader new file mode 100644 index 00000000..6da34d42 --- /dev/null +++ b/assets/scripts/psychlab.shader @@ -0,0 +1,8 @@ +textures/map/slot1 +{ + nomipmaps + qer_editorimage textures/map/slot1 + { + map textures/map/slot1 + } +} diff --git a/assets/scripts/shaderlist.txt b/assets/scripts/shaderlist.txt index 33e07345..42e255f5 100644 --- a/assets/scripts/shaderlist.txt +++ b/assets/scripts/shaderlist.txt @@ -1,8 +1,12 @@ ctp_tech_lava_d +decal fut_objects ghost lab_games +lab_floors +lab_floors_placeholders dm_lab poltergeist +psychlab skies skies_nat_lab diff --git a/assets/textures/map/desert_day_sky_bk.tga b/assets/textures/map/desert_day_sky_bk.tga new file mode 100644 index 00000000..2b04237a Binary files /dev/null and b/assets/textures/map/desert_day_sky_bk.tga differ diff --git a/assets/textures/map/desert_day_sky_dn.tga b/assets/textures/map/desert_day_sky_dn.tga new file mode 100644 index 00000000..9f1f5824 Binary files /dev/null and b/assets/textures/map/desert_day_sky_dn.tga differ diff --git a/assets/textures/map/desert_day_sky_ft.tga b/assets/textures/map/desert_day_sky_ft.tga new file mode 100644 index 00000000..2b04237a Binary files /dev/null and b/assets/textures/map/desert_day_sky_ft.tga differ diff --git a/assets/textures/map/desert_day_sky_lf.tga b/assets/textures/map/desert_day_sky_lf.tga new file mode 100644 index 00000000..2b04237a Binary files /dev/null and b/assets/textures/map/desert_day_sky_lf.tga differ diff --git a/assets/textures/map/desert_day_sky_rt.tga b/assets/textures/map/desert_day_sky_rt.tga new file mode 100644 index 00000000..2b04237a Binary files /dev/null and b/assets/textures/map/desert_day_sky_rt.tga differ diff --git a/assets/textures/map/desert_day_sky_up.tga b/assets/textures/map/desert_day_sky_up.tga new file mode 100644 index 00000000..720c7f99 Binary files /dev/null and b/assets/textures/map/desert_day_sky_up.tga differ diff --git a/assets/textures/map/desert_night_sky_bk.tga b/assets/textures/map/desert_night_sky_bk.tga new file mode 100644 index 00000000..c32359c0 Binary files /dev/null and b/assets/textures/map/desert_night_sky_bk.tga differ diff --git a/assets/textures/map/desert_night_sky_dn.tga b/assets/textures/map/desert_night_sky_dn.tga new file mode 100644 index 00000000..f5ea4817 Binary files /dev/null and b/assets/textures/map/desert_night_sky_dn.tga differ diff --git a/assets/textures/map/desert_night_sky_ft.tga b/assets/textures/map/desert_night_sky_ft.tga new file mode 100644 index 00000000..c32359c0 Binary files /dev/null and b/assets/textures/map/desert_night_sky_ft.tga differ diff --git a/assets/textures/map/desert_night_sky_lf.tga b/assets/textures/map/desert_night_sky_lf.tga new file mode 100644 index 00000000..c32359c0 Binary files /dev/null and b/assets/textures/map/desert_night_sky_lf.tga differ diff --git a/assets/textures/map/desert_night_sky_rt.tga b/assets/textures/map/desert_night_sky_rt.tga new file mode 100644 index 00000000..c32359c0 Binary files /dev/null and b/assets/textures/map/desert_night_sky_rt.tga differ diff --git a/assets/textures/map/desert_night_sky_up.tga b/assets/textures/map/desert_night_sky_up.tga new file mode 100644 index 00000000..56b49e89 Binary files /dev/null and b/assets/textures/map/desert_night_sky_up.tga differ diff --git a/assets/textures/map/desert_rockface_01_d.tga b/assets/textures/map/desert_rockface_01_d.tga new file mode 100644 index 00000000..b169bf5b Binary files /dev/null and b/assets/textures/map/desert_rockface_01_d.tga differ diff --git a/assets/textures/map/desert_sandground_01_d.tga b/assets/textures/map/desert_sandground_01_d.tga new file mode 100644 index 00000000..ad688e42 Binary files /dev/null and b/assets/textures/map/desert_sandground_01_d.tga differ diff --git a/assets/textures/map/floor_fill_d.tga b/assets/textures/map/floor_fill_d.tga new file mode 100644 index 00000000..6a7943b0 Binary files /dev/null and b/assets/textures/map/floor_fill_d.tga differ diff --git a/assets/textures/map/fut_ceiling_tile_02_d.tga b/assets/textures/map/fut_ceiling_tile_02_d.tga new file mode 100644 index 00000000..f8ca9d82 Binary files /dev/null and b/assets/textures/map/fut_ceiling_tile_02_d.tga differ diff --git a/assets/textures/map/fut_flat_wall_yellow_blank_d.tga b/assets/textures/map/fut_flat_wall_yellow_blank_d.tga new file mode 100644 index 00000000..bb712db6 Binary files /dev/null and b/assets/textures/map/fut_flat_wall_yellow_blank_d.tga differ diff --git a/assets/textures/map/glass_d.tga b/assets/textures/map/glass_d.tga new file mode 100644 index 00000000..cfbc1c75 Binary files /dev/null and b/assets/textures/map/glass_d.tga differ diff --git a/assets/textures/map/grey_clang.tga b/assets/textures/map/grey_clang.tga new file mode 100644 index 00000000..80a8f48c Binary files /dev/null and b/assets/textures/map/grey_clang.tga differ diff --git a/assets/textures/map/lab_games/lg_floor_placeholder_0_d.tga b/assets/textures/map/lab_games/lg_floor_placeholder_0_d.tga new file mode 100644 index 00000000..50e35535 Binary files /dev/null and b/assets/textures/map/lab_games/lg_floor_placeholder_0_d.tga differ diff --git a/assets/textures/map/lab_games/lg_floor_placeholder_A_d.tga b/assets/textures/map/lab_games/lg_floor_placeholder_A_d.tga new file mode 100644 index 00000000..50e35535 Binary files /dev/null and b/assets/textures/map/lab_games/lg_floor_placeholder_A_d.tga differ diff --git a/assets/textures/map/lab_games/lg_floor_placeholder_B_d.tga b/assets/textures/map/lab_games/lg_floor_placeholder_B_d.tga new file mode 100644 index 00000000..50e35535 Binary files /dev/null and b/assets/textures/map/lab_games/lg_floor_placeholder_B_d.tga differ diff --git a/assets/textures/map/lab_games/lg_floor_placeholder_C_d.tga b/assets/textures/map/lab_games/lg_floor_placeholder_C_d.tga new file mode 100644 index 00000000..50e35535 Binary files /dev/null and b/assets/textures/map/lab_games/lg_floor_placeholder_C_d.tga differ diff --git a/assets/textures/map/lab_games/lg_floor_placeholder_D_d.tga b/assets/textures/map/lab_games/lg_floor_placeholder_D_d.tga new file mode 100644 index 00000000..50e35535 Binary files /dev/null and b/assets/textures/map/lab_games/lg_floor_placeholder_D_d.tga differ diff --git a/assets/textures/map/lab_games/lg_floor_placeholder_E_d.tga b/assets/textures/map/lab_games/lg_floor_placeholder_E_d.tga new file mode 100644 index 00000000..50e35535 Binary files /dev/null and b/assets/textures/map/lab_games/lg_floor_placeholder_E_d.tga differ diff --git a/assets/textures/map/lab_games/lg_floor_placeholder_F_d.tga b/assets/textures/map/lab_games/lg_floor_placeholder_F_d.tga new file mode 100644 index 00000000..50e35535 Binary files /dev/null and b/assets/textures/map/lab_games/lg_floor_placeholder_F_d.tga differ diff --git a/assets/textures/map/lg_sky_01_bk.tga b/assets/textures/map/lg_sky_01_bk.tga new file mode 100644 index 00000000..e95023f3 Binary files /dev/null and b/assets/textures/map/lg_sky_01_bk.tga differ diff --git a/assets/textures/map/lg_sky_01_dn.tga b/assets/textures/map/lg_sky_01_dn.tga new file mode 100644 index 00000000..bc0a13fe Binary files /dev/null and b/assets/textures/map/lg_sky_01_dn.tga differ diff --git a/assets/textures/map/lg_sky_01_ft.tga b/assets/textures/map/lg_sky_01_ft.tga new file mode 100644 index 00000000..17cd7c7b Binary files /dev/null and b/assets/textures/map/lg_sky_01_ft.tga differ diff --git a/assets/textures/map/lg_sky_01_lf.tga b/assets/textures/map/lg_sky_01_lf.tga new file mode 100644 index 00000000..7aaa2036 Binary files /dev/null and b/assets/textures/map/lg_sky_01_lf.tga differ diff --git a/assets/textures/map/lg_sky_01_rt.tga b/assets/textures/map/lg_sky_01_rt.tga new file mode 100644 index 00000000..6e9f7a51 Binary files /dev/null and b/assets/textures/map/lg_sky_01_rt.tga differ diff --git a/assets/textures/map/lg_sky_01_up.tga b/assets/textures/map/lg_sky_01_up.tga new file mode 100644 index 00000000..45baecba Binary files /dev/null and b/assets/textures/map/lg_sky_01_up.tga differ diff --git a/assets/textures/map/lg_sky_02_bk.tga b/assets/textures/map/lg_sky_02_bk.tga new file mode 100644 index 00000000..4fdc3dbb Binary files /dev/null and b/assets/textures/map/lg_sky_02_bk.tga differ diff --git a/assets/textures/map/lg_sky_02_dn.tga b/assets/textures/map/lg_sky_02_dn.tga new file mode 100644 index 00000000..bc0a13fe Binary files /dev/null and b/assets/textures/map/lg_sky_02_dn.tga differ diff --git a/assets/textures/map/lg_sky_02_ft.tga b/assets/textures/map/lg_sky_02_ft.tga new file mode 100644 index 00000000..ee2ea579 Binary files /dev/null and b/assets/textures/map/lg_sky_02_ft.tga differ diff --git a/assets/textures/map/lg_sky_02_lf.tga b/assets/textures/map/lg_sky_02_lf.tga new file mode 100644 index 00000000..66443eaf Binary files /dev/null and b/assets/textures/map/lg_sky_02_lf.tga differ diff --git a/assets/textures/map/lg_sky_02_rt.tga b/assets/textures/map/lg_sky_02_rt.tga new file mode 100644 index 00000000..5b861f25 Binary files /dev/null and b/assets/textures/map/lg_sky_02_rt.tga differ diff --git a/assets/textures/map/lg_sky_02_up.tga b/assets/textures/map/lg_sky_02_up.tga new file mode 100644 index 00000000..de49a04f Binary files /dev/null and b/assets/textures/map/lg_sky_02_up.tga differ diff --git a/assets/textures/map/water_env.tga b/assets/textures/map/water_env.tga new file mode 100644 index 00000000..1e51be7c Binary files /dev/null and b/assets/textures/map/water_env.tga differ diff --git a/assets/textures/model/apple2_d.tga b/assets/textures/model/apple2_d.tga new file mode 100644 index 00000000..cb715f4c Binary files /dev/null and b/assets/textures/model/apple2_d.tga differ diff --git a/assets/textures/model/banana_d.tga b/assets/textures/model/banana_d.tga new file mode 100644 index 00000000..3fe8335e Binary files /dev/null and b/assets/textures/model/banana_d.tga differ diff --git a/assets/models/players/crash_color/dm_character_skin_mask_b.tga b/assets/textures/model/bee_d.tga similarity index 58% rename from assets/models/players/crash_color/dm_character_skin_mask_b.tga rename to assets/textures/model/bee_d.tga index 8ea525b3..c9ae4bf5 100644 Binary files a/assets/models/players/crash_color/dm_character_skin_mask_b.tga and b/assets/textures/model/bee_d.tga differ diff --git a/assets/textures/model/bottle_d.tga b/assets/textures/model/bottle_d.tga new file mode 100644 index 00000000..b854ae0e Binary files /dev/null and b/assets/textures/model/bottle_d.tga differ diff --git a/assets/textures/model/bush_desert_01_d.tga b/assets/textures/model/bush_desert_01_d.tga new file mode 100644 index 00000000..a58f5c9d Binary files /dev/null and b/assets/textures/model/bush_desert_01_d.tga differ diff --git a/assets/textures/model/cactus_d.tga b/assets/textures/model/cactus_d.tga new file mode 100644 index 00000000..f084a6cf Binary files /dev/null and b/assets/textures/model/cactus_d.tga differ diff --git a/assets/textures/model/car_d.tga b/assets/textures/model/car_d.tga new file mode 100644 index 00000000..0263b8e8 Binary files /dev/null and b/assets/textures/model/car_d.tga differ diff --git a/assets/textures/model/cherry_d.tga b/assets/textures/model/cherry_d.tga new file mode 100644 index 00000000..e580f6a6 Binary files /dev/null and b/assets/textures/model/cherry_d.tga differ diff --git a/assets/textures/model/cow_d.tga b/assets/textures/model/cow_d.tga new file mode 100644 index 00000000..1041cbaf Binary files /dev/null and b/assets/textures/model/cow_d.tga differ diff --git a/assets/textures/model/flower_d.tga b/assets/textures/model/flower_d.tga new file mode 100644 index 00000000..dc1536a4 Binary files /dev/null and b/assets/textures/model/flower_d.tga differ diff --git a/assets/textures/model/fork_d.tga b/assets/textures/model/fork_d.tga new file mode 100644 index 00000000..e6168860 Binary files /dev/null and b/assets/textures/model/fork_d.tga differ diff --git a/assets/textures/model/fridge_d.tga b/assets/textures/model/fridge_d.tga new file mode 100644 index 00000000..f1347ade Binary files /dev/null and b/assets/textures/model/fridge_d.tga differ diff --git a/assets/textures/model/fut_teleport_d.tga b/assets/textures/model/fut_teleport_d.tga deleted file mode 100644 index 4aa80746..00000000 Binary files a/assets/textures/model/fut_teleport_d.tga and /dev/null differ diff --git a/assets/textures/model/fut_teleport_e.tga b/assets/textures/model/fut_teleport_e.tga deleted file mode 100644 index 0a7efe43..00000000 Binary files a/assets/textures/model/fut_teleport_e.tga and /dev/null differ diff --git a/assets/textures/model/hammer_d.tga b/assets/textures/model/hammer_d.tga new file mode 100644 index 00000000..fcd07c0d Binary files /dev/null and b/assets/textures/model/hammer_d.tga differ diff --git a/assets/textures/model/jug_d.tga b/assets/textures/model/jug_d.tga new file mode 100644 index 00000000..02f09d4f Binary files /dev/null and b/assets/textures/model/jug_d.tga differ diff --git a/assets/textures/model/key_d.tga b/assets/textures/model/key_d.tga new file mode 100644 index 00000000..0c50916c Binary files /dev/null and b/assets/textures/model/key_d.tga differ diff --git a/assets/textures/model/knife_d.tga b/assets/textures/model/knife_d.tga new file mode 100644 index 00000000..e6168860 Binary files /dev/null and b/assets/textures/model/knife_d.tga differ diff --git a/assets/textures/model/mushroom_desert_white_d.tga b/assets/textures/model/mushroom_desert_white_d.tga new file mode 100644 index 00000000..e33b147e Binary files /dev/null and b/assets/textures/model/mushroom_desert_white_d.tga differ diff --git a/assets/textures/model/pig_d.tga b/assets/textures/model/pig_d.tga new file mode 100644 index 00000000..6f1a04ce Binary files /dev/null and b/assets/textures/model/pig_d.tga differ diff --git a/assets/textures/model/pincer_d.tga b/assets/textures/model/pincer_d.tga new file mode 100644 index 00000000..9384a2a3 Binary files /dev/null and b/assets/textures/model/pincer_d.tga differ diff --git a/assets/textures/model/plant_d.tga b/assets/textures/model/plant_d.tga new file mode 100644 index 00000000..9de3c8d2 Binary files /dev/null and b/assets/textures/model/plant_d.tga differ diff --git a/assets/textures/model/rock_desert_d.tga b/assets/textures/model/rock_desert_d.tga new file mode 100644 index 00000000..f18695e4 Binary files /dev/null and b/assets/textures/model/rock_desert_d.tga differ diff --git a/assets/textures/model/saxophone_d.tga b/assets/textures/model/saxophone_d.tga new file mode 100644 index 00000000..3d0e84b2 Binary files /dev/null and b/assets/textures/model/saxophone_d.tga differ diff --git a/assets/textures/model/shoe_d.tga b/assets/textures/model/shoe_d.tga new file mode 100644 index 00000000..d4db90a5 Binary files /dev/null and b/assets/textures/model/shoe_d.tga differ diff --git a/assets/textures/model/spoon_d.tga b/assets/textures/model/spoon_d.tga new file mode 100644 index 00000000..e6168860 Binary files /dev/null and b/assets/textures/model/spoon_d.tga differ diff --git a/assets/textures/model/strawberry_d.tga b/assets/textures/model/strawberry_d.tga old mode 100755 new mode 100644 diff --git a/assets/textures/model/tennis_racket_d.tga b/assets/textures/model/tennis_racket_d.tga new file mode 100644 index 00000000..455d63b6 Binary files /dev/null and b/assets/textures/model/tennis_racket_d.tga differ diff --git a/assets/textures/model/tomato_d.tga b/assets/textures/model/tomato_d.tga new file mode 100644 index 00000000..3d6a6952 Binary files /dev/null and b/assets/textures/model/tomato_d.tga differ diff --git a/assets/textures/model/tree_d.tga b/assets/textures/model/tree_d.tga new file mode 100644 index 00000000..4290b7e0 Binary files /dev/null and b/assets/textures/model/tree_d.tga differ diff --git a/assets/textures/model/wine_glass_d.tga b/assets/textures/model/wine_glass_d.tga new file mode 100644 index 00000000..e6168860 Binary files /dev/null and b/assets/textures/model/wine_glass_d.tga differ diff --git a/assets/textures/model/zebra_d.tga b/assets/textures/model/zebra_d.tga new file mode 100644 index 00000000..07ba53bc Binary files /dev/null and b/assets/textures/model/zebra_d.tga differ diff --git a/assets_oa/scripts/bots.txt b/assets_oa/scripts/bots.txt index 156cfba5..689b9629 100644 --- a/assets_oa/scripts/bots.txt +++ b/assets_oa/scripts/bots.txt @@ -1,71 +1,121 @@ { -name Tauri +name Cygni model crash -aifile bots/crash_c.c +aifile bots/major_c.c } { -name Centauri +name Leonis model crash -aifile bots/dark_c.c +aifile bots/gargoyle_c.c } { -name Draconis +name Epsilon model crash -aifile bots/ayumi_c.c +aifile bots/beret_c.c } { -name Epsilon +name Cephei model crash -aifile bots/beret_c.c +aifile bots/liz_c.c } { -name Leonis +name Centauri model crash -aifile bots/gargoyle_c.c +aifile bots/dark_c.c } { -name Cephei +name Draconis model crash -aifile bots/liz_c.c +aifile bots/ayumi_c.c } { -name Cygni +name Tauri model crash -aifile bots/major_c.c +aifile bots/crash_c.c } { -name TauriColor +name CygniColor +funname Cygni model crash_color -aifile bots/crash_c.c +aifile bots/major_c.c +} +{ +name LeonisColor +funname Leonis +model crash_color +aifile bots/gargoyle_c.c +} +{ +name EpsilonColor +funname Epsilon +model crash_color +aifile bots/beret_c.c +} +{ +name CepheiColor +funname Cephei +model crash_color +aifile bots/liz_c.c } { name CentauriColor +funname Centauri model crash_color aifile bots/dark_c.c } { name DraconisColor +funname Draconis model crash_color aifile bots/ayumi_c.c } { -name EpsilonColor +name TauriColor +funname Tauri model crash_color -aifile bots/beret_c.c +aifile bots/crash_c.c } { -name LeonisColor -model crash_color +name CygniColorAlt +funname Cygni +model crash_color/skin1 +aifile bots/major_c.c +} + +{ +name LeonisColorAlt +funname Leonis +model crash_color/skin2 aifile bots/gargoyle_c.c } { -name CepheiColor -model crash_color +name EpsilonColorAlt +funname Epsilon +model crash_color/skin3 +aifile bots/beret_c.c +} +{ +name CepheiColorAlt +funname Cephei +model crash_color/skin4 aifile bots/liz_c.c } { -name CygniColor -model crash_color -aifile bots/major_c.c +name CentauriColorAlt +funname Centauri +model crash_color/skin5 +aifile bots/dark_c.c +} +{ +name DraconisColorAlt +funname Draconis +model crash_color/skin6 +aifile bots/ayumi_c.c +} +{ +name TauriColorAlt +funname Tauri +model crash_color/skin7 +aifile bots/crash_c.c } diff --git a/assets_oa/scripts/character.shader b/assets_oa/scripts/character.shader index 17aa4250..d3b54394 100644 --- a/assets_oa/scripts/character.shader +++ b/assets_oa/scripts/character.shader @@ -4,7 +4,7 @@ models/players/crash/redskin nopicmip { map models/players/crash/redskin.tga - blendfunc gl_src_alpha gl_one_minus_src_alpha + blendfunc blend alphaFunc GE128 depthwrite } @@ -15,7 +15,7 @@ models/players/crash/redskin } { map models/players/crash/thruster_glow.tga - blendfunc gl_one gl_one + blendfunc add } } @@ -25,7 +25,7 @@ models/players/crash/blueskin nopicmip { map models/players/crash/blueskin.tga - blendfunc gl_src_alpha gl_one_minus_src_alpha + blendfunc blend alphaFunc GE128 depthwrite } @@ -36,7 +36,7 @@ models/players/crash/blueskin } { map models/players/crash/thruster_glow.tga - blendfunc gl_one gl_one + blendfunc add } } @@ -46,18 +46,18 @@ models/players/crash/skin1 nopicmip { map models/players/crash/skin1.tga - blendfunc gl_src_alpha gl_one_minus_src_alpha + blendfunc blend alphaFunc GE128 depthwrite } { map models/players/crash/skin1_scroll.tga - blendfunc gl_one gl_one + blendfunc add tcMod scroll -1.6 0 } { map models/players/crash/thruster_glow.tga - blendfunc gl_one gl_one + blendfunc add } } @@ -67,35 +67,17 @@ models/players/crash_color/skin_base //crash color shader nomipmaps { map models/players/crash_color/skin_base.tga - blendfunc gl_src_alpha gl_one_minus_src_alpha + blendfunc blend alphaFunc GE128 depthwrite } - - { - map models/players/crash_color/dm_character_skin_mask_a.tga - blendfunc blend - rgbGen lightingDiffuse - } - - { - map models/players/crash_color/dm_character_skin_mask_b.tga - blendfunc blend - rgbGen lightingDiffuse - } - - { - map models/players/crash_color/dm_character_skin_mask_c.tga - blendfunc blend - rgbGen lightingDiffuse - } { map models/players/crash/skin1_scroll.tga - blendfunc gl_one gl_one + blendfunc add tcMod scroll -1.6 0 } { map models/players/crash/thruster_glow.tga - blendfunc gl_one gl_one + blendfunc add } } diff --git a/assets_oa/scripts/common.shader b/assets_oa/scripts/common.shader new file mode 100644 index 00000000..ca17ca9e --- /dev/null +++ b/assets_oa/scripts/common.shader @@ -0,0 +1,6 @@ +textures/common/caulk +{ + surfaceparm nodraw + surfaceparm nomarks + surfaceparm nolightmap +} diff --git a/assets_oa/scripts/ctf.shader b/assets_oa/scripts/ctf.shader new file mode 100644 index 00000000..97c30af1 --- /dev/null +++ b/assets_oa/scripts/ctf.shader @@ -0,0 +1,50 @@ +sprites/friend +{ + nomipmaps + { + map sprites/friend1.tga + blendfunc blend + } +} + +sprites/foe +{ + nomipmaps + { + map sprites/foe2.tga + blendfunc blend + } +} + +models/flags/b_flag +{ + cull none + nopicmip + { + map models/flags/b_flag.tga + } +} + +models/flags/pole +{ + { + map textures/base_wall/chrome_env.tga + rgbGen lightingDiffuse + tcMod scale 0.5 0.5 + tcGen environment + } + { + map models/flags/pole.tga + blendfunc filter + rgbGen identity + } +} + +models/flags/r_flag +{ + cull none + nopicmip + { + map models/flags/r_flag.tga + } +} diff --git a/assets_oa/scripts/decals.shader b/assets_oa/scripts/decals.shader index b1ed5ba8..acb18bf6 100644 --- a/assets_oa/scripts/decals.shader +++ b/assets_oa/scripts/decals.shader @@ -30,6 +30,16 @@ gfx/damage/hole_lg_mrk //beam } } +gfx/damage/plasma_mrk //disc +{ + polygonOffset + { + map textures/model/circuit_decal.tga + blendfunc add + rgbgen const ( 0.00 0.58 1.00 ) //blue + } +} + //OTHER gfx/misc/tracer diff --git a/assets_oa/scripts/shaderlist.txt b/assets_oa/scripts/shaderlist.txt index 97956c0c..efc48f6d 100644 --- a/assets_oa/scripts/shaderlist.txt +++ b/assets_oa/scripts/shaderlist.txt @@ -1,6 +1,7 @@ // this file lists all the separate shader files ammo character +common decals iconsprites weaponhits diff --git a/deepmind/engine/BUILD b/deepmind/engine/BUILD index d4128020..8a798cf3 100644 --- a/deepmind/engine/BUILD +++ b/deepmind/engine/BUILD @@ -7,22 +7,136 @@ cc_library( name = "context", srcs = ["context.cc"], hdrs = ["context.h"], - visibility = ["//:__pkg__"], + visibility = ["//visibility:public"], deps = [ + ":context_entities", + ":context_events", + ":context_game", + ":context_observations", + ":context_pickups", + ":lua_image", ":lua_maze_generation", ":lua_random", ":lua_text_level_maker", - "//deepmind/include:context_headers", + "//:level_cache_types", + "//deepmind/include:context_hdrs", + "//deepmind/level_generation:compile_map", "//deepmind/lua", "//deepmind/lua:bind", "//deepmind/lua:call", "//deepmind/lua:class", "//deepmind/lua:n_results_or", + "//deepmind/lua:push", "//deepmind/lua:push_script", "//deepmind/lua:read", "//deepmind/lua:table_ref", "//deepmind/lua:vm", + "//deepmind/model_generation:lua_model", + "//deepmind/model_generation:lua_transform", + "//deepmind/model_generation:model", + "//deepmind/model_generation:model_getters", + "//deepmind/model_generation:model_lua", + "//deepmind/support:logging", "//deepmind/tensor:lua_tensor", + "//deepmind/tensor:tensor_view", + "//third_party/rl_api:env_c_api", + "@com_google_absl//absl/strings", + ], +) + +cc_library( + name = "context_events", + srcs = ["context_events.cc"], + hdrs = ["context_events.h"], + deps = [ + "//deepmind/lua", + "//deepmind/lua:class", + "//deepmind/lua:n_results_or", + "//deepmind/lua:read", + "//deepmind/tensor:lua_tensor", + "//deepmind/tensor:tensor_view", + "//third_party/rl_api:env_c_api", + ], +) + +cc_library( + name = "context_game", + srcs = ["context_game.cc"], + hdrs = ["context_game.h"], + deps = [ + "//deepmind/include:context_hdrs", + "//deepmind/lua", + "//deepmind/lua:class", + "//deepmind/lua:n_results_or", + "//deepmind/lua:push", + "//deepmind/lua:read", + "//deepmind/lua:table_ref", + "//deepmind/tensor:lua_tensor", + "//deepmind/tensor:tensor_view", + "//deepmind/util:files", + ], +) + +cc_library( + name = "context_entities", + srcs = ["context_entities.cc"], + hdrs = ["context_entities.h"], + deps = [ + "//deepmind/lua", + "//deepmind/lua:class", + "//deepmind/lua:n_results_or", + "//deepmind/lua:push", + "//deepmind/lua:read", + "//deepmind/lua:table_ref", + ], +) + +cc_test( + name = "context_entities_test", + srcs = ["context_entities_test.cc"], + deps = [ + ":context_entities", + "//deepmind/lua:bind", + "//deepmind/lua:call", + "//deepmind/lua:n_results_or_test_util", + "//deepmind/lua:push", + "//deepmind/lua:push_script", + "//deepmind/lua:read", + "//deepmind/lua:table_ref", + "//deepmind/lua:vm_test_util", + "@com_google_absl//absl/types:span", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "context_observations", + srcs = ["context_observations.cc"], + hdrs = ["context_observations.h"], + deps = [ + "//deepmind/lua", + "//deepmind/lua:call", + "//deepmind/lua:n_results_or", + "//deepmind/lua:push", + "//deepmind/lua:read", + "//deepmind/lua:table_ref", + "//deepmind/support:logging", + "//deepmind/tensor:lua_tensor", + "//deepmind/tensor:tensor_view", + "//third_party/rl_api:env_c_api", + ], +) + +cc_library( + name = "context_pickups", + srcs = ["context_pickups.cc"], + hdrs = ["context_pickups.h"], + deps = [ + "//deepmind/lua:call", + "//deepmind/lua:push", + "//deepmind/lua:read", + "//deepmind/lua:table_ref", + "//deepmind/support:logging", ], ) @@ -30,14 +144,16 @@ cc_library( name = "callbacks", srcs = ["callbacks.cc"], data = ["//:non_pk3_assets"], - visibility = ["//:__pkg__"], + visibility = ["//visibility:public"], deps = [ ":context", ":lua_maze_generation", ":lua_random", ":lua_text_level_maker", + "//deepmind/include:context_hdrs", "//deepmind/level_generation/text_level:lua_bindings", "//deepmind/lua:vm", + "//deepmind/model_generation:lua_model", "//deepmind/tensor:lua_tensor", ], ) @@ -48,9 +164,10 @@ cc_test( srcs = ["callbacks_test.cc"], deps = [ ":callbacks", - "//deepmind/include:context_headers", + "//deepmind/include:context_hdrs", + "//deepmind/support:logging", "//deepmind/support:test_srcdir", - "@googletest//:gtest_main", + "@com_google_googletest//:gtest_main", ], ) @@ -59,8 +176,10 @@ cc_library( srcs = ["lua_maze_generation.cc"], hdrs = ["lua_maze_generation.h"], deps = [ + ":lua_random", "//deepmind/level_generation/text_level:char_grid", "//deepmind/level_generation/text_maze_generation:algorithm", + "//deepmind/level_generation/text_maze_generation:flood_fill", "//deepmind/level_generation/text_maze_generation:text_maze", "//deepmind/lua", "//deepmind/lua:bind", @@ -79,11 +198,13 @@ cc_test( srcs = ["lua_maze_generation_test.cc"], deps = [ ":lua_maze_generation", + ":lua_random", + "//deepmind/lua:bind", "//deepmind/lua:call", "//deepmind/lua:n_results_or_test_util", "//deepmind/lua:push_script", "//deepmind/lua:vm_test_util", - "@googletest//:gtest_main", + "@com_google_googletest//:gtest_main", ], ) @@ -91,6 +212,10 @@ cc_library( name = "lua_random", srcs = ["lua_random.cc"], hdrs = ["lua_random.h"], + visibility = [ + "//deepmind/include:__pkg__", + "//deepmind/tensor:__pkg__", + ], deps = [ "//deepmind/lua", "//deepmind/lua:class", @@ -106,11 +231,12 @@ cc_test( srcs = ["lua_random_test.cc"], deps = [ ":lua_random", + "//deepmind/lua:bind", "//deepmind/lua:call", "//deepmind/lua:n_results_or_test_util", "//deepmind/lua:push_script", "//deepmind/lua:vm_test_util", - "@googletest//:gtest_main", + "@com_google_googletest//:gtest_main", ], ) @@ -121,13 +247,49 @@ cc_library( visibility = ["//deepmind:__subpackages__"], deps = [ ":lua_random", + "//:level_cache_types", "//deepmind/level_generation:compile_map", "//deepmind/level_generation/text_level:lua_bindings", "//deepmind/lua", + "//deepmind/lua:call", "//deepmind/lua:class", "//deepmind/lua:n_results_or", "//deepmind/lua:push", "//deepmind/lua:read", "//deepmind/lua:table_ref", + "//deepmind/util:files", + "@com_google_absl//absl/strings", + ], +) + +cc_library( + name = "lua_image", + srcs = ["lua_image.cc"], + hdrs = ["lua_image.h"], + deps = [ + "//deepmind/lua", + "//deepmind/lua:bind", + "//deepmind/lua:push", + "//deepmind/lua:read", + "//deepmind/lua:table_ref", + "//deepmind/tensor:lua_tensor", + "//deepmind/util:files", + "@com_google_absl//absl/strings", + "@png_archive//:png", + ], +) + +cc_test( + name = "lua_image_test", + size = "small", + srcs = ["lua_image_test.cc"], + deps = [ + ":lua_image", + "//deepmind/lua:call", + "//deepmind/lua:n_results_or_test_util", + "//deepmind/lua:push_script", + "//deepmind/lua:vm_test_util", + "//deepmind/tensor:lua_tensor", + "@com_google_googletest//:gtest_main", ], ) diff --git a/deepmind/engine/callbacks.cc b/deepmind/engine/callbacks.cc index 814505ca..4ecd5d4f 100644 --- a/deepmind/engine/callbacks.cc +++ b/deepmind/engine/callbacks.cc @@ -16,6 +16,8 @@ // //////////////////////////////////////////////////////////////////////////////// +#include + #include #include #include @@ -28,6 +30,7 @@ #include "deepmind/include/deepmind_context.h" #include "deepmind/level_generation/text_level/lua_bindings.h" #include "deepmind/lua/vm.h" +#include "deepmind/model_generation/lua_model.h" #include "deepmind/tensor/lua_tensor.h" namespace lua = deepmind::lab::lua; @@ -37,10 +40,15 @@ using deepmind::lab::LuaMazeGeneration; using deepmind::lab::LuaRandom; using deepmind::lab::LuaSnippetEmitter; using deepmind::lab::LuaTextLevelMaker; +using deepmind::lab::LuaModel; extern "C" { -int dmlab_create_context(const char* runfiles_path, DeepmindContext* ctx) { +int dmlab_create_context(const char* runfiles_path, DeepmindContext* ctx, + bool (*file_reader_override)(const char* file_name, + char** buff, + size_t* size), + const char* temp_folder) { lua::Vm lua_vm = lua::CreateVm(); lua_State* L = lua_vm.get(); tensor::LuaTensorRegister(L); @@ -48,9 +56,10 @@ int dmlab_create_context(const char* runfiles_path, DeepmindContext* ctx) { LuaRandom::Register(L); LuaTextLevelMaker::Register(L); LuaSnippetEmitter::Register(L); + LuaModel::Register(L); - ctx->userdata = - new Context(std::move(lua_vm), runfiles_path, &ctx->calls, &ctx->hooks); + ctx->userdata = new Context(std::move(lua_vm), runfiles_path, &ctx->calls, + &ctx->hooks, file_reader_override, temp_folder); return 0; } diff --git a/deepmind/engine/callbacks_test.cc b/deepmind/engine/callbacks_test.cc index 38ee0abd..f92514a0 100644 --- a/deepmind/engine/callbacks_test.cc +++ b/deepmind/engine/callbacks_test.cc @@ -30,8 +30,10 @@ using ::testing::ElementsAreArray; TEST(DeepmindCallbackTest, CreateAndDestroyContext) { DeepmindContext ctx{}; const char arg0[] = "dmlab"; - ASSERT_EQ(0, dmlab_create_context(TestSrcDir().c_str(), &ctx)); - ASSERT_EQ(0, ctx.hooks.set_script_name(ctx.userdata, "tests/callbacks_test")); + ASSERT_EQ(0, + dmlab_create_context(TestSrcDir().c_str(), &ctx, nullptr, nullptr)); + ASSERT_EQ( + 0, ctx.hooks.set_script_name(ctx.userdata, "test_levels/callbacks_test")); ctx.hooks.add_setting(ctx.userdata, "command", "hello"); ASSERT_EQ(0, ctx.hooks.init(ctx.userdata)); @@ -48,9 +50,10 @@ TEST(DeepmindCallbackTest, CreateAndDestroyContext) { TEST(DeepmindCallbackTest, CustomObservations) { DeepmindContext ctx{}; - const char callbacks_test[] = "tests/callbacks_test"; + const char callbacks_test[] = "test_levels/callbacks_test"; const char order[] = "Find Apples!"; - ASSERT_EQ(0, dmlab_create_context(TestSrcDir().c_str(), &ctx)); + ASSERT_EQ(0, + dmlab_create_context(TestSrcDir().c_str(), &ctx, nullptr, nullptr)); ctx.hooks.add_setting(ctx.userdata, "order", order); ASSERT_EQ(0, ctx.hooks.set_script_name(ctx.userdata, callbacks_test)); ASSERT_EQ(0, ctx.hooks.init(ctx.userdata)); @@ -100,4 +103,36 @@ TEST(DeepmindCallbackTest, CustomObservations) { dmlab_release_context(&ctx); } +TEST(DeepmindCallbackTest, CreateModel) { + DeepmindContext ctx{}; + ASSERT_EQ(0, + dmlab_create_context(TestSrcDir().c_str(), &ctx, nullptr, nullptr)); + ASSERT_EQ( + 0, ctx.hooks.set_script_name(ctx.userdata, "test_levels/callbacks_test")); + ASSERT_EQ(0, ctx.hooks.init(ctx.userdata)); + + ASSERT_TRUE(ctx.hooks.find_model(ctx.userdata, "cube")); + DeepmindModelGetters model; + void* model_data; + ctx.hooks.model_getters(ctx.userdata, &model, &model_data); + + ASSERT_EQ(model.get_surface_count(model_data), 1); + + ASSERT_EQ(model.get_surface_vertex_count(model_data, 0), 24); + ASSERT_EQ(model.get_surface_face_count(model_data, 0), 12); + + float vertex_normal[3]; + model.get_surface_vertex_normal(model_data, 0, 12, vertex_normal); + EXPECT_THAT(vertex_normal, ElementsAre(-1.0, 0.0, 0.0)); + model.get_surface_vertex_normal(model_data, 0, 23, vertex_normal); + EXPECT_THAT(vertex_normal, ElementsAre(0.0, -1.0, 0.0)); + + int face_indices[3]; + model.get_surface_face(model_data, 0, 11, face_indices); + EXPECT_THAT(face_indices, ElementsAre(20, 22, 23)); + + ctx.hooks.clear_model(ctx.userdata); + dmlab_release_context(&ctx); +} + } // namespace diff --git a/deepmind/engine/context.cc b/deepmind/engine/context.cc index 97e9ee8b..d2792161 100644 --- a/deepmind/engine/context.cc +++ b/deepmind/engine/context.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2016 Google Inc. +// Copyright (C) 2016-2017 Google Inc. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -20,21 +20,40 @@ #include #include +#include +#include +#include #include +#include #include #include #include +#include #include "deepmind/support/logging.h" -#include "deepmind/engine/lua_random.h" +#include "absl/strings/str_cat.h" +#include "deepmind/engine/lua_image.h" #include "deepmind/engine/lua_maze_generation.h" +#include "deepmind/engine/lua_random.h" #include "deepmind/engine/lua_text_level_maker.h" +#include "deepmind/include/deepmind_model_getters.h" +#include "deepmind/level_generation/compile_map.h" #include "deepmind/lua/bind.h" #include "deepmind/lua/call.h" #include "deepmind/lua/class.h" +#include "deepmind/lua/lua.h" +#include "deepmind/lua/push.h" #include "deepmind/lua/push_script.h" +#include "deepmind/lua/read.h" #include "deepmind/lua/table_ref.h" +#include "deepmind/model_generation/lua_model.h" +#include "deepmind/model_generation/lua_transform.h" +#include "deepmind/model_generation/model_getters.h" +#include "deepmind/model_generation/model_lua.h" #include "deepmind/tensor/lua_tensor.h" +#include "deepmind/tensor/tensor_view.h" +#include "public/level_cache_types.h" +#include "third_party/rl_api/env_c_api.h" using deepmind::lab::Context; @@ -43,6 +62,13 @@ static void add_setting(void* userdata, const char* key, const char* value) { return static_cast(userdata)->AddSetting(key, value); } +static void set_level_cache_settings( + void* userdata, bool local, bool global, + DeepMindLabLevelCacheParams level_cache_params) { + return static_cast(userdata)->SetLevelCacheSetting( + local, global, level_cache_params); +} + static int set_script_name(void* userdata, const char* script_name) { return static_cast(userdata)->SetScriptName(script_name); } @@ -55,26 +81,70 @@ static int start(void* userdata, int episode, int seed) { return static_cast(userdata)->Start(episode, seed); } +static const char* error_message(void* userdata) { + return static_cast(userdata)->ErrorMessage(); +} + +static void set_error_message(void* userdata, const char* error_message) { + static_cast(userdata)->SetErrorMessage(error_message); +} + +static int map_loaded(void* userdata) { + return static_cast(userdata)->MapLoaded(); +} + static const char* replace_command_line(void* userdata, const char* old_commandline) { return static_cast(userdata)->GetCommandLine(old_commandline); } +static const char* get_temporary_folder(void* userdata) { + return static_cast(userdata)->TempDirectory().c_str(); +} + static const char* next_map(void* userdata) { return static_cast(userdata)->NextMap(); } +void update_inventory(void* userdata, bool is_spawning, int player_id, + int gadget_count, int gadget_inventory[], int stat_count, + int stat_inventory[], int powerup_count, + int powerup_time[], int gadget_held, float height, + float position[3], float view_angles[3]) { + static_cast(userdata)->UpdateInventory( + is_spawning, player_id, gadget_count, gadget_inventory, stat_count, + stat_inventory, powerup_count, powerup_time, gadget_held, height, + position, view_angles); +} + +static int game_type(void* userdata) { + return static_cast(userdata)->GameType(); +} + +static char team_select(void* userdata, int player_id, + const char* player_name) { + return static_cast(userdata)->TeamSelect(player_id, player_name); +} + +static bool has_alt_cameras(void* userdata) { + return static_cast(userdata)->HasAltCameras(); +} + +static void set_has_alt_cameras(void* userdata, bool has_alt_camera) { + static_cast(userdata)->SetHasAltCameras(has_alt_camera); +} + static int run_lua_snippet(void* userdata, const char* command) { std::size_t command_len = std::strlen(command); return static_cast(userdata)->RunLuaSnippet(command, command_len); } -static void set_use_internal_controls(void* userdata, bool v) { - return static_cast(userdata)->SetUseInternalControls(v); +static void set_native_app(void* userdata, bool v) { + return static_cast(userdata)->SetNativeApp(v); } -static bool get_use_internal_controls(void* userdata) { - return static_cast(userdata)->UseInternalControls(); +static bool get_native_app(void* userdata) { + return static_cast(userdata)->NativeApp(); } static void set_actions(void* userdata, double look_down_up, @@ -98,16 +168,29 @@ static void get_actions(void* userdata, double* look_down_up, static int update_spawn_vars(void* userdata, char* spawn_var_chars, int* num_spawn_var_chars, int spawn_var_offsets[][2], int* num_spawn_vars) { - return static_cast(userdata)->UpdateSpawnVars( + return static_cast(userdata)->MutablePickups()->UpdateSpawnVars( spawn_var_chars, num_spawn_var_chars, spawn_var_offsets, num_spawn_vars); } +static int make_extra_entities(void* userdata) { + return static_cast(userdata)->MutablePickups()->MakeExtraEntities(); +} + +static void read_extra_entity(void* userdata, int entity_index, + char* spawn_var_chars, int* num_spawn_var_chars, + int spawn_var_offsets[][2], int* num_spawn_vars) { + return static_cast(userdata)->MutablePickups()->ReadExtraEntity( + entity_index, spawn_var_chars, num_spawn_var_chars, spawn_var_offsets, + num_spawn_vars); +} + static bool find_item(void* userdata, const char* class_name, int* index) { - return static_cast(userdata)->FindItem(class_name, index); + return static_cast(userdata)->MutablePickups()->FindItem(class_name, + index); } static int item_count(void* userdata) { - return static_cast(userdata)->ItemCount(); + return static_cast(userdata)->Pickups().ItemCount(); } static bool item(void* userdata, int index, @@ -115,33 +198,69 @@ static bool item(void* userdata, int index, char* class_name, int max_class_name, char* model_name, int max_model_name, int* quantity, int* type, int* tag) { - return static_cast(userdata)->GetItem( + return static_cast(userdata)->Pickups().GetItem( index, item_name, max_item_name, class_name, max_class_name, model_name, max_model_name, quantity, type, tag); } static void clear_items(void* userdata) { - static_cast(userdata)->ClearItems(); + static_cast(userdata)->MutablePickups()->ClearItems(); +} + +static bool find_model(void* userdata, const char* model_name) { + return static_cast(userdata)->FindModel(model_name); +} + +static void model_getters(void* userdata, DeepmindModelGetters* model_getters, + void** model_data) { + static_cast(userdata)->GetModelGetters(model_getters, model_data); +} + +static void clear_model(void* userdata) { + static_cast(userdata)->ClearModel(); } static bool map_finished(void* userdata) { - return static_cast(userdata)->MapFinished(); + return static_cast(userdata)->Game().MapFinished(); } static void set_map_finished(void* userdata, bool map_finished) { - static_cast(userdata)->SetMapFinished(map_finished); + static_cast(userdata)->MutableGame()->SetMapFinished(map_finished); } static bool can_pickup(void* userdata, int entity_id) { - return static_cast(userdata)->CanPickup(entity_id); + return static_cast(userdata)->MutablePickups()->CanPickup( + entity_id); } static bool override_pickup(void* userdata, int entity_id, int* respawn) { - return static_cast(userdata)->OverridePickup(entity_id, respawn); + return static_cast(userdata)->MutablePickups()->OverridePickup( + entity_id, respawn); } -static int external_reward(void* userdata, int player_id) { - return static_cast(userdata)->ExternalReward(player_id); +static bool can_trigger(void* userdata, int entity_id, + const char* target_name) { + return static_cast(userdata)->CanTrigger(entity_id, target_name); +} + +static bool override_trigger(void* userdata, int entity_id, + const char* target_name) { + return static_cast(userdata)->OverrideTrigger(entity_id, + target_name); +} + +static void trigger_lookat(void* userdata, int entity_id, bool looked_at, + const float position[3]) { + static_cast(userdata)->TriggerLookat(entity_id, looked_at, + position); +} + +static int reward_override(void* userdata, const char* reason_opt, + int player_id, int team, + const int* other_player_id_opt, + const float* origin_opt, int score) { + return static_cast(userdata)->RewardOverride( + reason_opt, player_id, team, other_player_id_opt, origin_opt, score); } static void add_score(void* userdata, int player_id, double reward) { @@ -162,211 +281,260 @@ static void add_bots(void* userdata) { return static_cast(userdata)->AddBots(); } -static void modify_rgba_texture(void* userdata, const char* name, +static bool modify_rgba_texture(void* userdata, const char* name, unsigned char* data, int width, int height) { - static_cast(userdata)->ModifyRgbaTexture(name, data, width, height); + return static_cast(userdata)->ModifyRgbaTexture(name, data, width, + height); +} + +static bool replace_model_name(void* userdata, const char* name, char* new_name, + int new_name_size, char* texture_prefix, + int texture_prefix_size) { + return static_cast(userdata)->ReplaceModelName( + name, new_name, new_name_size, texture_prefix, texture_prefix_size); +} + +static bool replace_texture_name(void* userdata, const char* name, + char* new_name, int max_size) { + return static_cast(userdata)->ReplaceTextureName(name, new_name, + max_size); +} + +static bool load_texture(void* userdata, const char* name, + unsigned char** pixels, int* width, int* height, + void* (*allocator)(int size)) { + return static_cast(userdata)->LoadTexture(name, pixels, width, + height, allocator); } static int custom_observation_count(void* userdata) { - return static_cast(userdata)->CustomObservationCount(); + return static_cast(userdata)->Observations().Count(); } static const char* custom_observation_name(void* userdata, int idx) { - return static_cast(userdata)->CustomObservationName(idx); + return static_cast(userdata)->Observations().Name(idx); } static void custom_observation_spec(void* userdata, int idx, EnvCApi_ObservationSpec* spec) { - static_cast(userdata)->CustomObservationSpec(idx, spec); + static_cast(userdata)->Observations().Spec(idx, spec); } static void custom_observation(void* userdata, int idx, EnvCApi_Observation* observation) { - return static_cast(userdata)->CustomObservation(idx, observation); + return static_cast(userdata)->MutableObservations()->Observation( + idx, observation); } -void predicted_player_state(void* userdata, const float origin[3], - const float velocity[3], const float viewangles[3], - float height, int timestamp_msec) { - return static_cast(userdata)->SetPredictPlayerState( - origin, velocity, viewangles, height, timestamp_msec); +static void player_state(void* userdata, const float origin[3], + const float velocity[3], const float viewangles[3], + float height, int team_score, int other_team_score, + int player_id, int timestamp_msec) { + return static_cast(userdata)->MutableGame()->SetPlayerState( + origin, velocity, viewangles, height, team_score, other_team_score, + player_id, timestamp_msec); } -int make_screen_messages(void* userdata, int screen_width, int screen_height, - int line_height, int string_buffer_size) { - return static_cast(userdata)->MakeScreenMesages( +static int make_screen_messages(void* userdata, int screen_width, + int screen_height, int line_height, + int string_buffer_size) { + return static_cast(userdata)->MakeScreenMessages( screen_width, screen_height, line_height, string_buffer_size); } -void get_screen_message(void* userdata, int message_id, char* buffer, int* x, - int* y, int* align_l0_r1_c2) { - static_cast(userdata)->GetScreenMessage(message_id, buffer, x, y, - align_l0_r1_c2); +static void get_screen_message(void* userdata, int message_id, char* buffer, + int* x, int* y, int* align_l0_r1_c2, + int* shadow, float rgba[4]) { + static_cast(userdata)->GetScreenMessage( + message_id, buffer, x, y, align_l0_r1_c2, shadow, rgba); } -} // extern "C" +static int make_filled_rectangles(void* userdata, int screen_width, + int screen_height) { + return static_cast(userdata)->MakeFilledRectangles(screen_width, + screen_height); +} -namespace deepmind { -namespace lab { -namespace { +static void get_filled_rectangle(void* userdata, int rectangle_id, int* x, + int* y, int* width, int* height, + float rgba[4]) { + static_cast(userdata)->GetFilledRectangle(rectangle_id, x, y, width, + height, rgba); +} -class LuaGameModule : public lua::Class { - friend class Class; - static const char* ClassName() { return "deepmind.lab.Game"; } - - public: - // '*ctx' owned by the caller and should out-live this object. - explicit LuaGameModule(Context* ctx) : ctx_(ctx) {} - - static void Register(lua_State* L) { - const Class::Reg methods[] = { - {"addScore", Member<&LuaGameModule::AddScore>}, - {"finishMap", Member<&LuaGameModule::FinishMap>}, - {"playerInfo", Member<&LuaGameModule::PlayerInfo>}, - }; - Class::Register(L, methods); - } +static void lua_mover(void* userdata, int entity_id, + const float entity_position[3], + const float player_position[3], + const float player_velocity[3], + float player_position_delta[3], + float player_velocity_delta[3]) { + static_cast(userdata)->CustomPlayerMovement( + entity_id, entity_position, player_position, player_velocity, + player_position_delta, player_velocity_delta); +} - private: - lua::NResultsOr AddScore(lua_State* L) { - int player_id = 0; - double score = 0; - if (lua::Read(L, 2, &player_id) && lua::Read(L, 3, &score) && - 0 <= player_id && player_id < 64) { - ctx_->Calls()->add_score(player_id, score); - return 0; - } - std::string error = "Invalid arguments player_id: "; - error += lua::ToString(L, 2); - error += " or reward: "; - error += lua::ToString(L, 3); - return std::move(error); - } +static void game_event(void* userdata, const char* event_name, int count, + const float* data) { + static_cast(userdata)->GameEvent(event_name, count, data); +} - lua::NResultsOr FinishMap(lua_State* L) { - ctx_->SetMapFinished(true); - return 0; - } +static void make_pk3_from_map(void* userdata, const char* map_path, + const char* map_name, bool gen_aas) { + static_cast(userdata)->MakePk3FromMap(map_path, map_name, gen_aas); +} - lua::NResultsOr PlayerInfo(lua_State* L) { - const auto& pv = ctx_->GetPredictPlayerView(); - auto table = lua::TableRef::Create(L); - table.Insert("pos", pv.pos); - table.Insert("vel", pv.vel); - table.Insert("angles", pv.angles); - table.Insert("anglesVel", pv.anglesVel); - table.Insert("height", pv.height); - lua::Push(L, table); - return 1; - } +static void events_clear(void* userdata) { + static_cast(userdata)->MutableEvents()->Clear(); +} - Context* ctx_; -}; +static int events_type_count(void* userdata) { + return static_cast(userdata)->Events().TypeCount(); +} -constexpr char kGameScriptPath[] = "/baselab/game_scripts"; -constexpr int kMaxSpawnVars = 64; -constexpr int kMaxSpawnVarChars = 4096; -constexpr double kDefaultEpisodeLengthSeconds = 5 * 30.0; +static const char* events_type_name(void* userdata, int event_type_idx) { + return static_cast(userdata)->Events().TypeName( + event_type_idx); +} -// If the string "arg" fits into the array pointed to by "dest" (including the -// null terminator), copies the string into the array and returns true; -// otherwise returns false. -bool StringCopy(const std::string& arg, char* dest, std::size_t max_size) { - auto len = arg.length() + 1; - if (len <= max_size) { - std::copy_n(arg.c_str(), len, dest); - return true; - } else { - return false; - } +static int events_count(void* userdata) { + return static_cast(userdata)->Events().Count(); } -lua::NResultsOr MapMakerModule(lua_State* L) { - if (auto* ctx = - static_cast(lua_touserdata(L, lua_upvalueindex(1)))) { - LuaTextLevelMaker::CreateObject(L, ctx->ExecutableRunfiles()); - return 1; - } else { - return "Missing context!"; - } +static void events_export(void* userdata, int event_idx, EnvCApi_Event* event) { + static_cast(userdata)->MutableEvents()->Export(event_idx, event); } -lua::NResultsOr GameModule(lua_State* L) { +static void entities_clear(void* userdata) { + static_cast(userdata)->MutableGameEntities()->Clear(); +} + +static void entities_add(void* userdata, int entity_id, int user_id, + int type, int flags, float position[3], + const char* classname) { + static_cast(userdata)->MutableGameEntities()->Add( + entity_id, user_id, type, flags, position, classname); +} + +} // extern "C" + +namespace deepmind { +namespace lab { +namespace { + +constexpr char kGameScriptPath[] = "/baselab/game_scripts"; +constexpr double kDefaultEpisodeLengthSeconds = 5 * 30.0; + +constexpr const char* const kTeamNames[] = { + "free", + "red", + "blue", + "spectator" +}; + +lua::NResultsOr MapMakerModule(lua_State* L) { if (auto* ctx = static_cast(lua_touserdata(L, lua_upvalueindex(1)))) { - LuaGameModule::Register(L); - LuaGameModule::CreateObject(L, ctx); + LuaTextLevelMaker::CreateObject( + L, ctx->ExecutableRunfiles(), ctx->TempDirectory(), + ctx->UseLocalLevelCache(), ctx->UseGlobalLevelCache(), + ctx->LevelCacheParams()); return 1; } else { return "Missing context!"; } } -lua::NResultsOr RandomModule(lua_State* L) { - if (auto* ctx = - static_cast(lua_touserdata(L, lua_upvalueindex(1)))) { - LuaRandom::CreateObject(L, ctx->UserPrbg()); +lua::NResultsOr ModelModule(lua_State* L) { + if (const auto* calls = static_cast( + lua_touserdata(L, lua_upvalueindex(1)))) { + LuaModel::CreateObject(L, calls); return 1; } else { return "Missing context!"; } } -// Returns the unique value in the range [-180, 180) that is equivalent to -// 'angle', where two values x and y are considered equivalent whenever x - y -// is an integral multiple of 360. (Note: the result may be meaningless if the -// magnitude of 'angle' is very large.) -double CanonicalAngle360(double angle) { - const double n = std::floor((angle + 180.0) / 360.0); - return angle - n * 360.0; -} - } // namespace Context::Context(lua::Vm lua_vm, const char* executable_runfiles, - const DeepmindCalls* calls, DeepmindHooks* hooks) + const DeepmindCalls* calls, DeepmindHooks* hooks, + bool (*file_reader_override)(const char* file_name, + char** buff, size_t* size), + const char* temp_folder) : lua_vm_(std::move(lua_vm)), - executable_runfiles_(executable_runfiles), - deepmind_calls_(calls), - use_internal_controls_(false), + native_app_(false), actions_{}, - map_finished_(false), - random_seed_(0), - predicted_player_view_{} { + level_cache_params_{}, + game_(executable_runfiles, calls, file_reader_override, + temp_folder != nullptr ? temp_folder : ""), + has_alt_cameras_(false) { CHECK(lua_vm_ != nullptr); hooks->add_setting = add_setting; + hooks->set_level_cache_settings = set_level_cache_settings; hooks->set_script_name = set_script_name; hooks->start = start; + hooks->map_loaded = map_loaded; hooks->init = init; + hooks->error_message = error_message; + hooks->set_error_message = set_error_message; hooks->replace_command_line = replace_command_line; hooks->next_map = next_map; + hooks->game_type = game_type; + hooks->team_select = team_select; hooks->run_lua_snippet = run_lua_snippet; - hooks->set_use_internal_controls = set_use_internal_controls; - hooks->get_use_internal_controls = get_use_internal_controls; + hooks->set_native_app = set_native_app; + hooks->get_native_app = get_native_app; hooks->set_actions = set_actions; hooks->get_actions = get_actions; hooks->update_spawn_vars = update_spawn_vars; + hooks->make_extra_entities = make_extra_entities; + hooks->read_extra_entity = read_extra_entity; hooks->find_item = find_item; hooks->item_count = item_count; hooks->item = item; hooks->clear_items = clear_items; + hooks->find_model = find_model; + hooks->model_getters = model_getters; + hooks->clear_model = clear_model; hooks->map_finished = map_finished; hooks->set_map_finished = set_map_finished; hooks->can_pickup = can_pickup; hooks->override_pickup = override_pickup; - hooks->external_reward = external_reward; + hooks->can_trigger = can_trigger; + hooks->override_trigger = override_trigger; + hooks->trigger_lookat = trigger_lookat; + hooks->reward_override = reward_override; hooks->add_score = add_score; hooks->make_random_seed = make_random_seed; hooks->has_episode_finished = has_episode_finished; hooks->add_bots = add_bots; + hooks->replace_model_name = replace_model_name; + hooks->replace_texture_name = replace_texture_name; + hooks->load_texture = load_texture; hooks->modify_rgba_texture = modify_rgba_texture; hooks->custom_observation_count = custom_observation_count; hooks->custom_observation_name = custom_observation_name; hooks->custom_observation_spec = custom_observation_spec; hooks->custom_observation = custom_observation; - hooks->predicted_player_state = predicted_player_state; + hooks->player_state = player_state; hooks->make_screen_messages = make_screen_messages; hooks->get_screen_message = get_screen_message; + hooks->make_filled_rectangles = make_filled_rectangles; + hooks->get_filled_rectangle = get_filled_rectangle; + hooks->get_temporary_folder = get_temporary_folder; + hooks->make_pk3_from_map = make_pk3_from_map; + hooks->lua_mover = lua_mover; + hooks->game_event = game_event; + hooks->events.clear = events_clear; + hooks->events.type_count = events_type_count; + hooks->events.type_name = events_type_name; + hooks->events.count = events_count; + hooks->events.export_event = events_export; + hooks->entities.clear = entities_clear; + hooks->entities.add = entities_add; + hooks->update_inventory = update_inventory; + hooks->set_has_alt_cameras = set_has_alt_cameras; + hooks->has_alt_cameras = has_alt_cameras; } void Context::AddSetting(const char* key, const char* value) { @@ -375,49 +543,60 @@ void Context::AddSetting(const char* key, const char* value) { lua::NResultsOr Context::PushScript() { lua_State* L = lua_vm_.get(); - if (script_name_.empty()) { + if (script_path_.empty()) { return "Must have level script!"; } - if (script_name_.length() > 4 && - // size_t pos, size_t len, const char* s, size_t n - script_name_.compare(script_name_.length() - 4, 4, ".lua", 4) == 0) { - return lua::PushScriptFile(L, script_name_); - } else { - std::string script_path = executable_runfiles_; - script_path += kGameScriptPath; - script_path += "/"; - script_path += script_name_; - script_path += ".lua"; - return lua::PushScriptFile(L, script_path); - } + return lua::PushScriptFile(L, script_path_); } -int Context::SetScriptName(const char* script_name) { - script_name_ = script_name; +int Context::SetScriptName(std::string script_name) { + if (script_name.length() > 4 && + // size_t pos, size_t len, const char* s, size_t n + script_name.compare(script_name.length() - 4, 4, ".lua", 4) == 0) { + script_path_ = std::move(script_name); + } else if (!script_name.empty()) { + script_path_ = absl::StrCat(ExecutableRunfiles(), kGameScriptPath, "/", + script_name, ".lua"); + } auto result = PushScript(); lua_State* L = lua_vm_.get(); if (result.ok()) { lua_pop(L, result.n_results()); return 0; } else { - std::cerr << "Level not found: " << result.error() << std::endl; + error_message_ = absl::StrCat("Level not found: ", result.error()); return 1; } } int Context::Init() { + // Clear texture handles from previous levels and initialise temp folder. + int init_code = MutableGame()->Init(); + if (init_code != 0) { + return init_code; + } + lua_State* L = lua_vm_.get(); auto result = PushScript(); if (!result.ok()) { - std::cerr << result.error() << std::endl; + error_message_ = result.error(); return 1; } - std::string scripts_folder = executable_runfiles_; - scripts_folder += kGameScriptPath; + std::string scripts_folder = + absl::StrCat(ExecutableRunfiles(), kGameScriptPath); + lua_vm_.AddPathToSearchers(scripts_folder); + // Add the current running script to the search path. + auto last_slash_pos = script_path_.find_last_of('/'); + if (last_slash_pos != std::string::npos) { + auto running_script_folder = script_path_.substr(0, last_slash_pos); + lua_vm_.AddPathToSearchers(running_script_folder); + } + + lua_vm_.AddCModuleToSearchers("dmlab.system.image", LuaImageRequire); lua_vm_.AddCModuleToSearchers( "dmlab.system.tensor", tensor::LuaTensorConstructors); lua_vm_.AddCModuleToSearchers( @@ -425,48 +604,76 @@ int Context::Init() { lua_vm_.AddCModuleToSearchers( "dmlab.system.map_maker", &lua::Bind, {this}); lua_vm_.AddCModuleToSearchers( - "dmlab.system.game", &lua::Bind, {this}); + "dmlab.system.game", &lua::Bind, {MutableGame()}); + lua_vm_.AddCModuleToSearchers( + "dmlab.system.events", &lua::Bind, + {MutableEvents()}); + lua_vm_.AddCModuleToSearchers("dmlab.system.game_entities", + &lua::Bind, + {MutableGameEntities()}); lua_vm_.AddCModuleToSearchers( - "dmlab.system.random", &lua::Bind, {this}); + "dmlab.system.random", &lua::Bind, {UserPrbg()}); + lua_vm_.AddCModuleToSearchers( + "dmlab.system.model", &lua::Bind, + {const_cast(Game().Calls())}); + lua_vm_.AddCModuleToSearchers( + "dmlab.system.transform", LuaTransform::Require); - lua::Push(L, script_name_); + lua::Push(L, script_path_); result = lua::Call(L, 1); if (!result.ok()) { - std::cerr << result.error() << std::endl; + error_message_ = result.error(); return 1; } if (result.n_results() != 1) { - std::cerr - << "Lua script must return only a table or userdata with metatable."; + error_message_ = + "Lua script must return only a table or userdata with metatable."; lua_pop(L, result.n_results()); return 1; } if (!lua::Read(L, -1, &script_table_ref_)) { - auto actual = lua::ToString(L, -1); - std::cerr << "Lua script must return a table or userdata with metatable. " - "Actually returned : '" - << actual << "'" << std::endl; + error_message_ = absl::StrCat( + "Lua script must return a table or userdata with metatable. Actually " + "returned : '", + lua::ToString(L, -1), "'"); lua_pop(L, result.n_results()); return 1; } lua_pop(L, result.n_results()); int err = CallInit(); if (err != 0) return err; - return CallObservationSpec(); + + MutablePickups()->SetScriptTableRef(script_table_ref_); + return MutableObservations()->ReadSpec(script_table_ref_); } int Context::Start(int episode, int seed) { - predicted_player_view_.timestamp_msec = 0; - random_seed_ = seed; EnginePrbg()->seed(seed); + MutableGame()->NextMap(); lua_State* L = lua_vm_.get(); script_table_ref_.PushMemberFunction("start"); if (!lua_isnil(L, -2)) { lua::Push(L, episode); - lua::Push(L, static_cast(seed)); + lua::Push(L, static_cast(MakeRandomSeed())); auto result = lua::Call(L, 3); if (!result.ok()) { - std::cerr << result.error() << std::endl; + error_message_ = result.error(); + return 1; + } + lua_pop(L, result.n_results()); + } else { + lua_pop(L, 2); + } + return 0; +} + +int Context::MapLoaded() { + lua_State* L = lua_vm_.get(); + script_table_ref_.PushMemberFunction("mapLoaded"); + if (!lua_isnil(L, -2)) { + auto result = lua::Call(L, 1); + if (!result.ok()) { + error_message_ = result.error(); return 1; } lua_pop(L, result.n_results()); @@ -540,11 +747,109 @@ const char* Context::NextMap() { CHECK_EQ(1, result.n_results()) << "'nextMap' must return one string."; CHECK(lua::Read(L, -1, &map_name_)) << "'nextMap' must return one string: Found " << lua::ToString(L, -1); - predicted_player_view_.timestamp_msec = 0; + MutableGame()->NextMap(); lua_pop(L, result.n_results()); return map_name_.c_str(); } +void Context::UpdateInventory(bool is_spawning, int player_id, int gadget_count, + int gadget_inventory[], int stat_count, + int stat_inventory[], int powerup_count, + int powerup_time[], int gadget_held, float height, + float position[3], float view_angles[3]) { + const char* update_type = is_spawning ? "spawnInventory" : "updateInventory"; + lua_State* L = lua_vm_.get(); + script_table_ref_.PushMemberFunction(update_type); + if (lua_isnil(L, -2)) { + lua_pop(L, 2); + return; + } + + auto table = lua::TableRef::Create(L); + table.Insert("playerId", player_id + 1); + table.Insert("amounts", absl::MakeConstSpan(gadget_inventory, gadget_count)); + table.Insert("stats", absl::MakeConstSpan(stat_inventory, stat_count)); + table.Insert("powerups", absl::MakeConstSpan(powerup_time, powerup_count)); + table.Insert("position", absl::MakeConstSpan(position, 3)); + table.Insert("angles", absl::MakeConstSpan(view_angles, 3)); + table.Insert("height", height); + table.Insert("gadget", gadget_held + 1); + lua::Push(L, table); + auto result = lua::Call(L, 2); + CHECK(result.ok()) << "[" << update_type << "] - " << result.error(); + if (result.n_results() > 0) { + CHECK_EQ(1, result.n_results()) + << "[" << update_type << "] - Must return table or nil!"; + if (!lua_isnil(L, -1)) { + CHECK(lua::Read(L, -1, &table)) + << "[" << update_type << "] - Must return table or nil!"; + CHECK(table.LookUp("amounts", + absl::MakeSpan(gadget_inventory, gadget_count))) + << "[" << update_type << "] - Table missing 'amounts'!"; + CHECK(table.LookUp("stats", absl::MakeSpan(stat_inventory, stat_count))) + << "[" << update_type << "] - Table missing 'stats'!"; + } + lua_pop(L, result.n_results()); + } +} + +int Context::GameType() { + lua_State* L = lua_vm_.get(); + script_table_ref_.PushMemberFunction("gameType"); + if (lua_isnil(L, -2)) { + lua_pop(L, 2); + return 0; // GT_FFA from bg_public. + } else { + auto result = lua::Call(L, 1); + CHECK(result.ok()) << "[gameType] - " << result.error(); + int game_type = 0; + CHECK(lua::Read(L, -1, &game_type)) + << "[gameType] - must return integer; actual \"" << lua::ToString(L, -1) + << "\""; + CHECK_LT(game_type, 8) + << "[gameType] - must return integer less than 8; actual \"" + << game_type << "\""; + return game_type; + } +} + +char Context::TeamSelect(int player_id, const char* player_name) { + lua_State* L = lua_vm_.get(); + script_table_ref_.PushMemberFunction("team"); + if (lua_isnil(L, -2)) { + lua_pop(L, 2); + return '\0'; + } + + lua::Push(L, player_id + 1); + lua::Push(L, player_name); + + auto result = lua::Call(L, 3); + CHECK(result.ok()) << result.error(); + if (result.n_results() == 0 || lua_isnil(L, -1)) { + lua_pop(L, result.n_results()); + return '\0'; + } + CHECK_EQ(1, result.n_results()) << "[team] - must return one string."; + std::string team; + CHECK(lua::Read(L, -1, &team)) << "[team] - must return one string: Found \"" + << lua::ToString(L, -1) << "\""; + + lua_pop(L, result.n_results()); + CHECK(!team.empty()) << "[team] - must return one character or nil: Found \"" + << lua::ToString(L, -1) << "\""; + + const char kTeam[] = "pbrs"; + auto ret = std::find_if(std::begin(kTeam), std::end(kTeam), + [&team](char t) { return team[0] == t; }); + if (ret != std::end(kTeam)) { + return *ret; + } + + LOG(FATAL) << "[team] - must return one of 'r', 'b', 'p' and 's'; actual" + << lua::ToString(L, -1); +} + void Context::SetActions(double look_down_up, double look_left_right, signed char move_back_forward, signed char strafe_left_right, signed char crouch_jump, @@ -561,6 +866,36 @@ void Context::GetActions(double* look_down_up, double* look_left_right, signed char* move_back_forward, signed char* strafe_left_right, signed char* crouch_jump, int* buttons_down) { + lua_State* L = lua_vm_.get(); + script_table_ref_.PushMemberFunction("modifyControl"); + // Check function exists. + if (!lua_isnil(L, -2)) { + auto table = lua::TableRef::Create(L); + table.Insert("lookDownUp", actions_.look_down_up); + table.Insert("lookLeftRight", actions_.look_left_right); + table.Insert("moveBackForward", actions_.move_back_forward); + table.Insert("strafeLeftRight", actions_.strafe_left_right); + table.Insert("crouchJump", actions_.crouch_jump); + table.Insert("buttonsDown", actions_.buttons_down); + lua::Push(L, table); + auto result = lua::Call(L, 2); + CHECK(result.ok()) << result.error(); + + if (result.n_results() >= 1) { + lua::Read(L, -1, &table); + CHECK(table.LookUp("lookDownUp", look_down_up)); + CHECK(table.LookUp("lookLeftRight", look_left_right)); + CHECK(table.LookUp("moveBackForward", move_back_forward)); + CHECK(table.LookUp("strafeLeftRight", strafe_left_right)); + CHECK(table.LookUp("crouchJump", crouch_jump)); + CHECK(table.LookUp("buttonsDown", buttons_down)); + lua_pop(L, result.n_results()); + return; + } + } else { + lua_pop(L, 2); + } + *look_down_up = actions_.look_down_up; *look_left_right = actions_.look_left_right; *move_back_forward = actions_.move_back_forward; @@ -574,182 +909,177 @@ int Context::MakeRandomSeed() { 1, std::numeric_limits::max())(*EnginePrbg()); } -bool Context::UpdateSpawnVars(char* spawn_var_chars, int* num_spawn_var_chars, - int spawn_var_offsets[][2], int* num_spawn_vars) { +bool Context::FindModel(const char* model_name) { lua_State* L = lua_vm_.get(); - script_table_ref_.PushMemberFunction("updateSpawnVars"); + script_table_ref_.PushMemberFunction("createModel"); + + // Check function exists. if (lua_isnil(L, -2)) { lua_pop(L, 2); - return true; + return false; } - auto table = lua::TableRef::Create(L); - for (int i = 0; i < *num_spawn_vars; ++i) { - table.Insert(std::string(spawn_var_chars + spawn_var_offsets[i][0]), - std::string(spawn_var_chars + spawn_var_offsets[i][1])); - } - lua::Push(L, table); + lua::Push(L, model_name); + auto result = lua::Call(L, 2); - CHECK(result.ok()) << result.error(); + CHECK(result.ok()) << "createModel: " << result.error(); - // Nil return so spawn is ignored. - if (lua_isnil(L, -1)) { + // If no description is returned or the description is nil, don't create + // model. + if (result.n_results() == 0 || lua_isnil(L, -1)) { lua_pop(L, result.n_results()); return false; } - std::unordered_map out_spawn_vars; - lua::Read(L, -1, &out_spawn_vars); - - *num_spawn_vars = out_spawn_vars.size(); - CHECK_NE(0, *num_spawn_vars) << "Must have spawn vars or return nil. (Make " - "sure all values are strings.)"; - CHECK_LT(*num_spawn_vars, kMaxSpawnVars) << "Too many spawn vars!"; - char* mem = spawn_var_chars; - - auto it = out_spawn_vars.begin(); - for (int i = 0; i < *num_spawn_vars; ++i) { - const auto& key = it->first; - const auto& value = it->second; - ++it; - std::size_t kl = key.length() + 1; - std::size_t vl = value.length() + 1; - *num_spawn_var_chars += kl + vl; - CHECK_LT(*num_spawn_var_chars, kMaxSpawnVarChars) << "Too large spawn vars"; - std::copy(key.c_str(), key.c_str() + kl, mem); - spawn_var_offsets[i][0] = std::distance(spawn_var_chars, mem); - mem += kl; - std::copy(value.c_str(), value.c_str() + vl, mem); - spawn_var_offsets[i][1] = std::distance(spawn_var_chars, mem); - mem += vl; - } + // Read model + std::unique_ptr model(new Model()); + CHECK(Read(L, -1, model.get())) + << "createModel: Failed to parse data for model " << model_name; + model_name_ = model_name; + model_ = std::move(model); + lua_pop(L, result.n_results()); return true; } -bool Context::FindItem(const char* class_name, int* index) { +void Context::GetModelGetters(DeepmindModelGetters* model_getters, + void** model_data) { + CHECK(model_) << "No model was selected for this context!"; + + *model_getters = ModelGetters(); + *model_data = model_.get(); +} + + +bool Context::CanTrigger(int entity_id, const char* target_name) { lua_State* L = lua_vm_.get(); - script_table_ref_.PushMemberFunction("createPickup"); + script_table_ref_.PushMemberFunction("canTrigger"); // Check function exists. if (lua_isnil(L, -2)) { lua_pop(L, 2); - return false; - } - - lua::Push(L, class_name); - - auto result = lua::Call(L, 2); - CHECK(result.ok()) << result.error(); - - // If nothing it returned or that it's nil, don't create item. - if (result.n_results() == 0 || lua_isnil(L, -1)) { - lua_pop(L, result.n_results()); - return false; + return true; } - lua::TableRef table; - CHECK(Read(L, -1, &table)) << "Failed to read pickup table!"; + lua::Push(L, entity_id); + lua::Push(L, target_name); - PickupItem item = {}; - CHECK(table.LookUp("name", &item.name)); - CHECK(table.LookUp("class_name", &item.class_name)); - CHECK(table.LookUp("model_name", &item.model_name)); - CHECK(table.LookUp("quantity", &item.quantity)); - CHECK(table.LookUp("type", &item.type)); + auto result = lua::Call(L, 3); + CHECK(result.ok()) << "[canTrigger] - " << result.error(); - // Optional tag field. - table.LookUp("tag", &item.tag); + CHECK(result.n_results() != 0 && !lua_isnil(L, -1)) + << "canTrigger: return value from lua canTrigger must be true or false."; - items_.push_back(item); - *index = ItemCount() - 1; + bool can_trigger; + CHECK(lua::Read(L, -1, &can_trigger)) + << "canTrigger: Failed to read the return value as a boolean." + << "Return true or false."; lua_pop(L, result.n_results()); - return true; -} - -bool Context::GetItem(int index, char* item_name, int max_item_name, // - char* class_name, int max_class_name, // - char* model_name, int max_model_name, // - int* quantity, int* type, int* tag) { - CHECK_GE(index, 0) << "Index out of range!"; - CHECK_LT(index, ItemCount()) << "Index out of range!"; - - const auto& item = items_[index]; - CHECK(StringCopy(item.name, item_name, max_item_name)); - CHECK(StringCopy(item.class_name, class_name, max_class_name)); - CHECK(StringCopy(item.model_name, model_name, max_model_name)); - *quantity = item.quantity; - *type = static_cast(item.type); - *tag = item.tag; - return true; + return can_trigger; } -bool Context::CanPickup(int entity_id) { +bool Context::OverrideTrigger(int entity_id, const char* target_name) { lua_State* L = lua_vm_.get(); - script_table_ref_.PushMemberFunction("canPickup"); + script_table_ref_.PushMemberFunction("trigger"); // Check function exists. if (lua_isnil(L, -2)) { lua_pop(L, 2); - return true; + return false; } lua::Push(L, entity_id); + lua::Push(L, target_name); - auto result = lua::Call(L, 2); - CHECK(result.ok()) << result.error(); + auto result = lua::Call(L, 3); + CHECK(result.ok()) << "[trigger] - " << result.error(); - // If nothing returned or the return is nil, the default. + // If nothing was returned or if the first return value is nil, the trigger + // behaviour was not overridden if (result.n_results() == 0 || lua_isnil(L, -1)) { lua_pop(L, result.n_results()); - return true; + return false; } - bool can_pickup = true; - CHECK(lua::Read(L, -1, &can_pickup)) - << "Failed to read canPickup return value"; + bool has_override; + CHECK(lua::Read(L, -1, &has_override)) + << "trigger: Failed to read the return value as a boolean." + << "Return true or false."; lua_pop(L, result.n_results()); - return can_pickup; + return has_override; } -bool Context::OverridePickup(int entity_id, int* respawn) { +void Context::TriggerLookat(int entity_id, bool looked_at, + const float position[3]) { lua_State* L = lua_vm_.get(); - script_table_ref_.PushMemberFunction("pickup"); + script_table_ref_.PushMemberFunction("lookat"); // Check function exists. if (lua_isnil(L, -2)) { lua_pop(L, 2); - return false; + return; } lua::Push(L, entity_id); + lua::Push(L, looked_at); + std::array float_array3; + std::copy_n(position, float_array3.size(), float_array3.data()); + lua::Push(L, float_array3); - auto result = lua::Call(L, 2); - CHECK(result.ok()) << result.error(); - - // If nothing returned or the return is nil, we're not overriding the - // pickup behaviour. - if (result.n_results() == 0 || lua_isnil(L, -1)) { - lua_pop(L, result.n_results()); - return false; - } - - CHECK(lua::Read(L, -1, &respawn)) << "Failed to read the respawn time"; + auto result = lua::Call(L, 4); + CHECK(result.ok()) << "[lookat] - " << result.error(); lua_pop(L, result.n_results()); - return true; +} + +int Context::RewardOverride(const char* optional_reason, int player_id, + int team, const int* optional_other_player_id, + const float* optional_origin, int score) { + if (optional_reason != nullptr) { + lua_State* L = script_table_ref_.LuaState(); + script_table_ref_.PushMemberFunction("rewardOverride"); + // Check function exists. + if (!lua_isnil(L, -2)) { + auto args = lua::TableRef::Create(L); + args.Insert("reason", optional_reason); + args.Insert("playerId", player_id); + if (team >= 0 && team + < std::distance(std::begin(kTeamNames), std::end(kTeamNames))) { + args.Insert("team", kTeamNames[team]); + } + if (optional_other_player_id != nullptr) { + args.Insert("otherPlayerId", *optional_other_player_id); + } + if (optional_origin != nullptr) { + std::array float_array3 = { + {optional_origin[0], optional_origin[1], optional_origin[2]}}; + args.Insert("location", float_array3); + } + args.Insert("score", score); + lua::Push(L, args); + auto result = lua::Call(L, 2); + CHECK(result.ok()) << "[scoreOverride] - " << result.error(); + CHECK(result.n_results() <= 1) + << "[scoreOverride] - Must return new score or nil"; + if (result.n_results() == 1 && !lua_isnil(L, -1)) { + CHECK(lua::Read(L, -1, &score)) + << "[scoreOverride] - Score must be an integer!"; + } + lua_pop(L, result.n_results()); + } else { + lua_pop(L, 2); + } + } + return ExternalReward(player_id) + score; } int Context::ExternalReward(int player_id) { CHECK_GE(player_id, 0) << "Invalid player Id!"; double reward = 0; if (static_cast(player_id) < player_rewards_.size()) { - if (player_rewards_[player_id] >= 1.0) { - player_rewards_[player_id] = - std::modf(player_rewards_[player_id], &reward); - } + player_rewards_[player_id] = std::modf(player_rewards_[player_id], &reward); } return reward; } @@ -791,19 +1121,122 @@ void Context::AddBots() { bot_table.LookUp("skill", &skill); std::string team = "free"; bot_table.LookUp("team", &team); - Calls()->add_bot(bot_name.c_str(), skill, team.c_str()); + Game().Calls()->add_bot(bot_name.c_str(), skill, team.c_str()); } lua_pop(L, result.n_results()); } -void Context::ModifyRgbaTexture(const char* name, unsigned char* data, +bool Context::ReplaceModelName(const char* name, char* new_name, + int new_name_size, char* texture_prefix, + int texture_prefix_size) { + lua_State* L = lua_vm_.get(); + script_table_ref_.PushMemberFunction("replaceModelName"); + // Check function exists. + if (lua_isnil(L, -2)) { + lua_pop(L, 2); + return false; + } + + lua::Push(L, name); + auto result = lua::Call(L, 2); + CHECK(result.ok()) << "[replaceModelName] - " << result.error(); + + if (lua_isnoneornil(L, 1)) { + CHECK(lua_isnoneornil(L, 2)) + << "[replaceModelName] - Return arg2 (texturePrefix) must be nil if " + "return arg1 (newModelName) is nil."; + lua_pop(L, result.n_results()); + return false; + } + + std::string replacement_name; + CHECK(lua::Read(L, 1, &replacement_name)) + << "[replaceModelName] - Return arg1 (newModelName) must be a string."; + CHECK_LT(replacement_name.size(), new_name_size) + << "[replaceModelName] - Return arg1 (newModelName) is too long."; + + std::string string_prefix; + if (result.n_results() == 2 && !lua_isnil(L, 2)) { + CHECK(lua::Read(L, 2, &string_prefix)) + << "[replaceModelName] - Return arg2 (texturePrefix) must be a string."; + CHECK_LT(string_prefix.size(), texture_prefix_size) + << "[replaceModelName] - Return arg2 (texturePrefix) is too long."; + } + + std::copy_n(replacement_name.c_str(), replacement_name.size() + 1, new_name); + std::copy_n(string_prefix.c_str(), string_prefix.size() + 1, texture_prefix); + lua_pop(L, result.n_results()); + return true; +} + +bool Context::ReplaceTextureName(const char* name, char* new_name, + int max_size) { + lua_State* L = lua_vm_.get(); + script_table_ref_.PushMemberFunction("replaceTextureName"); + // Check function exists. + if (lua_isnil(L, -2)) { + lua_pop(L, 2); + return false; + } + + lua::Push(L, name); + auto result = lua::Call(L, 2); + CHECK(result.ok()) << "[replaceTextureName] - " << result.error(); + if (result.n_results() == 0 || lua_isnil(L, -1)) { + lua_pop(L, result.n_results()); + return false; + } + std::string replacement_name; + CHECK(lua::Read(L, 1, &replacement_name)) + << "[replaceTextureName] - New name must be a string."; + CHECK_LT(replacement_name.size(), max_size) + << "[replaceTextureName] - New name is too long."; + std::copy_n(replacement_name.c_str(), replacement_name.size() + 1, new_name); + lua_pop(L, result.n_results()); + return true; +} + +bool Context::LoadTexture(const char* name, unsigned char** pixels, int* width, + int* height, void* (*allocator)(int size)) { + lua_State* L = lua_vm_.get(); + script_table_ref_.PushMemberFunction("loadTexture"); + // Check function exists. + if (lua_isnil(L, -2)) { + lua_pop(L, 2); + return false; + } + + lua::Push(L, name); + auto result = lua::Call(L, 2); + CHECK(result.ok()) << "[loadTexture] - " << result.error(); + if (result.n_results() == 0 || lua_isnil(L, -1)) { + lua_pop(L, result.n_results()); + return false; + } + auto* image_tensor = tensor::LuaTensor::ReadObject(L, -1); + CHECK(image_tensor) << "[loadTexture] - Must return ByteTensor."; + const auto& view = image_tensor->tensor_view(); + CHECK_EQ(3, view.shape().size()) + << "[loadTexture] - Must return ByteTensor shaped HxWx4"; + CHECK_EQ(4, view.shape()[2]) + << "[loadTexture] - Must return ByteTensor shaped HxWx4"; + *height = view.shape()[0]; + *width = view.shape()[1]; + *pixels = static_cast(allocator(view.num_elements())); + unsigned char* pixel_it = *pixels; + view.ForEach([&pixel_it](unsigned char value) { *pixel_it++ = value; }); + lua_pop(L, result.n_results()); + return true; +} + +bool Context::ModifyRgbaTexture(const char* name, unsigned char* data, int width, int height) { lua_State* L = lua_vm_.get(); script_table_ref_.PushMemberFunction("modifyTexture"); // Check function exists. if (lua_isnil(L, -2)) { lua_pop(L, 2); - return; + return false; } lua::Push(L, name); @@ -816,8 +1249,13 @@ void Context::ModifyRgbaTexture(const char* name, unsigned char* data, storage_validity); auto result = lua::Call(L, 3); CHECK(result.ok()) << "[modifyTexture] - " << result.error(); + + bool modified_texture; + CHECK(lua::Read(L, -1, &modified_texture)) + << "[modifyTexture] - must return true or false"; lua_pop(L, result.n_results()); storage_validity->Invalidate(); + return modified_texture; } int Context::CallInit() { @@ -829,151 +1267,32 @@ int Context::CallInit() { } lua::Push(L, settings_); auto result = lua::Call(L, 2); - lua_pop(L, result.n_results()); if (!result.ok()) { - std::cerr << result.error() << '\n'; + error_message_ = result.error(); return 1; } - return 0; -} -int Context::CallObservationSpec() { - lua_State* L = lua_vm_.get(); - script_table_ref_.PushMemberFunction("customObservationSpec"); - if (lua_isnil(L, -2)) { - lua_pop(L, 2); - return 0; - } - auto result = lua::Call(L, 1); - if (!result.ok()) { - std::cerr << result.error() << '\n'; - return 1; - } - lua::TableRef observations; - lua::Read(L, -1, &observations); - observation_infos_.clear(); - observation_infos_.reserve(observations.ArraySize()); - for (std::size_t i = 0, c = observations.ArraySize(); i != c; ++i) { - lua::TableRef observation_info; - observations.LookUp(i + 1, &observation_info); - ObservationSpecInfo info; - if (!observation_info.LookUp("name", &info.name)) { - std::cerr << "[customObservationSpec] - Missing 'name = '.\n"; - return 1; - } - std::string type = "Doubles"; - observation_info.LookUp("type", &type); - if (type.compare("Bytes") == 0) { - info.type = EnvCApi_ObservationBytes; - } else if (type.compare("Doubles") == 0) { - info.type = EnvCApi_ObservationDoubles; + int ret_val = 0; + bool correct_args = result.n_results() == 0 || + (result.n_results() == 1 && lua_isnil(L, 1)) || + (result.n_results() <= 2 && lua::Read(L, 1, &ret_val)); + if (ret_val != 0) { + if (result.n_results() == 2) { + error_message_ = lua::ToString(L, 2); } else { - std::cerr - << "[customObservationSpec] - Missing 'type = 'Bytes'|'Doubles''.\n"; - return 1; - } - if (!observation_info.LookUp("shape", &info.shape)) { - std::cerr - << "[customObservationSpec] - Missing 'shape = {, ...}'.\n"; - return 1; + error_message_ = "Error while calling 'init'."; } - observation_infos_.push_back(std::move(info)); } lua_pop(L, result.n_results()); - return 0; -} - -void Context::CustomObservation(int idx, EnvCApi_Observation* observation) { - lua_State* L = lua_vm_.get(); - script_table_ref_.PushMemberFunction("customObservation"); - // Function must exist. - CHECK(!lua_isnil(L, -2)) - << "Observations Spec set but no observation member function"; - const auto& info = observation_infos_[idx]; - lua::Push(L, info.name); - auto result = lua::Call(L, 2); - CHECK(result.ok()) << "[customObservation] - " << result.error(); - - CHECK_EQ(1, result.n_results()) - << "[customObservation] - Must return a " - << (info.type == EnvCApi_ObservationDoubles ? "DoubleTensor" - : "ByteTensor"); - - const tensor::Layout* layout = nullptr; - if (info.type == EnvCApi_ObservationDoubles) { - auto* double_tensor = tensor::LuaTensor::ReadObject(L, -1); - if (double_tensor != nullptr) { - const auto& view = double_tensor->tensor_view(); - CHECK(view.IsContiguous()) - << "[customObservation] - Must return a contiguous tensor!"; - layout = &view; - observation->spec.type = EnvCApi_ObservationDoubles; - observation->payload.doubles = view.storage() + view.start_offset(); - } - } else { - auto* byte_tensor = tensor::LuaTensor::ReadObject(L, -1); - if (byte_tensor != nullptr) { - const auto& view = byte_tensor->tensor_view(); - layout = &view; - CHECK(view.IsContiguous()) - << "[customObservation] - Must return a contiguous tensor!"; - observation->spec.type = EnvCApi_ObservationBytes; - observation->payload.bytes = view.storage() + view.start_offset(); - } - } - CHECK(layout != nullptr) - << "[customObservation] - Must return a contiguous tensor!\n:" - << "at idx" << idx << "\n" - << lua::ToString(L, -1); - - observation_tensor_shape_.resize(layout->shape().size()); - std::copy(layout->shape().begin(), layout->shape().end(), - observation_tensor_shape_.begin()); - observation->spec.dims = observation_tensor_shape_.size(); - observation->spec.shape = observation_tensor_shape_.data(); - - // Prevent observation->payload from being destroyed during pop. - lua::Read(L, -1, &observation_tensor_); - lua_pop(L, result.n_results()); -} - -void Context::CustomObservationSpec(int idx, - EnvCApi_ObservationSpec* spec) const { - const auto& info = observation_infos_[idx]; - spec->type = info.type; - spec->dims = info.shape.size(); - spec->shape = info.shape.data(); -} - -void Context::SetPredictPlayerState(const float pos[3], const float vel[3], - const float angles[3], float height, - int timestamp_msec) { - PlayerView before = predicted_player_view_; - std::copy_n(pos, 3, predicted_player_view_.pos.begin()); - std::copy_n(vel, 3, predicted_player_view_.vel.begin()); - std::copy_n(angles, 3, predicted_player_view_.angles.begin()); - predicted_player_view_.height = height; - predicted_player_view_.timestamp_msec = timestamp_msec; - - int delta_time_msec = - predicted_player_view_.timestamp_msec - before.timestamp_msec; - - // When delta_time_msec < 3 the velocities become inaccurate. - if (before.timestamp_msec > 0 && delta_time_msec > 0) { - double inv_delta_time = 1000.0 / delta_time_msec; - for (int i : {0, 1, 2}) { - predicted_player_view_.anglesVel[i] = - CanonicalAngle360(predicted_player_view_.angles[i] - - before.angles[i]) * - inv_delta_time; - } - } else { - predicted_player_view_.anglesVel.fill(0); + if (!correct_args) { + error_message_ = "[init] - Must return none, nil, or integer and message\n"; + return 1; } + return ret_val; } -int Context::MakeScreenMesages(int screen_width, int screen_height, - int line_height, int string_buffer_size) { +int Context::MakeScreenMessages(int screen_width, int screen_height, + int line_height, int string_buffer_size) { screen_messages_.clear(); lua_State* L = lua_vm_.get(); script_table_ref_.PushMemberFunction("screenMessages"); @@ -996,7 +1315,7 @@ int Context::MakeScreenMesages(int screen_width, int screen_height, << "[screenMessages] - Must return an array of messages"; lua::TableRef messages_array; lua::Read(L, -1, &messages_array); - for (std::size_t i = 0, e = messages_array.ArraySize(); i != e; ++i) { + for (std::size_t i = 0, size = messages_array.ArraySize(); i != size; ++i) { lua::TableRef message_table; CHECK(messages_array.LookUp(i + 1, &message_table)) << "[screenMessages] - Each message must be a table"; @@ -1009,6 +1328,10 @@ int Context::MakeScreenMesages(int screen_width, int screen_height, message_table.LookUp("x", &message.x); message_table.LookUp("y", &message.y); message_table.LookUp("alignment", &message.align_l0_r1_c2); + std::fill(message.rgba.begin(), message.rgba.end(), 1.0); + message_table.LookUp("rgba", &message.rgba); + message.shadow = true; + message_table.LookUp("shadow", &message.shadow); screen_messages_.push_back(std::move(message)); } @@ -1017,15 +1340,153 @@ int Context::MakeScreenMesages(int screen_width, int screen_height, } void Context::GetScreenMessage(int message_id, char* buffer, int* x, int* y, - int* align_l0_r1_c2) const { + int* align_l0_r1_c2, int* shadow, + float rgba[4]) const { const auto& screen_message = screen_messages_[message_id]; const std::string& msg_text = screen_message.text; - // MakeScreenMesages guarantees message is smaller than string_buffer_size. + // MakeScreenMessages guarantees message is smaller than string_buffer_size. std::copy_n(msg_text.c_str(), msg_text.size() + 1, buffer); *x = screen_message.x; *y = screen_message.y; *align_l0_r1_c2 = screen_message.align_l0_r1_c2; + *shadow = screen_message.shadow ? 1 : 0; + std::copy_n(screen_message.rgba.data(), screen_message.rgba.size(), rgba); +} + +int Context::MakeFilledRectangles(int screen_width, int screen_height) { + filled_rectangles_.clear(); + lua_State* L = lua_vm_.get(); + script_table_ref_.PushMemberFunction("filledRectangles"); + if (lua_isnil(L, -2)) { + lua_pop(L, 2); + return 0; + } + + auto args = lua::TableRef::Create(L); + args.Insert("width", screen_width); + args.Insert("height", screen_height); + lua::Push(L, args); + auto result = lua::Call(L, 2); + CHECK(result.ok()) << "[filledRectangles] - " << result.error(); + CHECK_EQ(1, result.n_results()) + << "[filledRectangles] - Must return an array of rectangles"; + lua::TableRef rectangles_array; + lua::Read(L, -1, &rectangles_array); + for (std::size_t i = 0, size = rectangles_array.ArraySize(); i != size; ++i) { + lua::TableRef rectangle_table; + CHECK(rectangles_array.LookUp(i + 1, &rectangle_table)) + << "[filledRectangles] - Each message must be a table"; + FilledRectangle filled_rectangle = {}; + CHECK(rectangle_table.LookUp("x", &filled_rectangle.x)) + << "[filledRectangles] - Must supply x"; + CHECK(rectangle_table.LookUp("y", &filled_rectangle.y)) + << "[filledRectangles] - Must supply y"; + CHECK(rectangle_table.LookUp("width", &filled_rectangle.width)) + << "[filledRectangles] - Must supply width"; + CHECK(rectangle_table.LookUp("height", &filled_rectangle.height)) + << "[filledRectangles] - Must supply height"; + CHECK(rectangle_table.LookUp("rgba", &filled_rectangle.rgba)) + << "[filledRectangles] - Must supply rgba"; + filled_rectangles_.push_back(filled_rectangle); + } + + lua_pop(L, result.n_results()); + return filled_rectangles_.size(); +} + +void Context::GetFilledRectangle(int rectangle_id, int* x, int* y, int* width, + int* height, float rgba[4]) const { + const auto& filled_rectangle = filled_rectangles_[rectangle_id]; + *x = filled_rectangle.x; + *y = filled_rectangle.y; + *width = filled_rectangle.width; + *height = filled_rectangle.height; + std::copy_n(filled_rectangle.rgba.data(), filled_rectangle.rgba.size(), rgba); +} + +void Context::MakePk3FromMap(const char* map_path, const char* map_name, + bool gen_aas) { + MapCompileSettings compile_settings; + compile_settings.generate_aas = gen_aas; + compile_settings.map_source_location = + absl::StrCat(ExecutableRunfiles(), "/", map_path); + compile_settings.use_local_level_cache = use_local_level_cache_; + compile_settings.use_global_level_cache = use_global_level_cache_; + compile_settings.level_cache_params = level_cache_params_; + std::string target = absl::StrCat(TempDirectory(), "/baselab/", map_name); + CHECK(RunMapCompileFor(ExecutableRunfiles(), target, compile_settings)); +} + +void Context::CustomPlayerMovement(int mover_id, const float mover_pos[3], + const float player_pos[3], + const float player_vel[3], + float player_pos_delta[3], + float player_vel_delta[3]) { + lua_State* L = lua_vm_.get(); + script_table_ref_.PushMemberFunction("playerMover"); + + // Check function exists. + if (lua_isnil(L, -2)) { + lua_pop(L, 2); + return; + } + + std::array float_array3; + + auto args = lua::TableRef::Create(L); + args.Insert("moverId", mover_id); + + std::copy_n(mover_pos, float_array3.size(), float_array3.data()); + args.Insert("moverPos", float_array3); + + std::copy_n(player_pos, float_array3.size(), float_array3.data()); + args.Insert("playerPos", float_array3); + + std::copy_n(player_vel, float_array3.size(), float_array3.data()); + args.Insert("playerVel", float_array3); + + lua::Push(L, args); + auto result = lua::Call(L, 2); + CHECK(result.ok()) << "[playerMover] - " << result.error(); + + std::array pos_delta = {{0.0f, 0.0f, 0.0f}}; + std::array vel_delta = {{0.0f, 0.0f, 0.0f}}; + + CHECK(lua_isnoneornil(L, 1) || lua::Read(L, 1, &pos_delta)) + << "[playerMover] - First return value must be a table containing" + "player position delta values."; + CHECK(lua_isnoneornil(L, 2) || lua::Read(L, 2, &vel_delta)) + << "[playerMover] - Second return value must be a table containing" + "player velocity delta values."; + + std::copy_n(pos_delta.data(), pos_delta.size(), player_pos_delta); + std::copy_n(vel_delta.data(), vel_delta.size(), player_vel_delta); + + lua_pop(L, result.n_results()); +} + +void Context::GameEvent(const char* event_name, int count, + const float* data) { + lua_State* L = script_table_ref_.LuaState(); + script_table_ref_.PushMemberFunction("engineEvent"); + // Check function exists. + if (lua_isnil(L, -2)) { + lua_pop(L, 2); + return; + } + + lua::Push(L, event_name); + lua_createtable(L, count, 0); + for (std::size_t i = 0; i < count; ++i) { + lua::Push(L, i + 1); + lua::Push(L, data[i]); + lua_settable(L, -3); + } + auto result = lua::Call(L, 3); + CHECK(result.ok()) << result.error() << '\n'; + lua_pop(L, result.n_results()); } } // namespace lab } // namespace deepmind + diff --git a/deepmind/engine/context.h b/deepmind/engine/context.h index e7fa5bd5..afdcfb83 100644 --- a/deepmind/engine/context.h +++ b/deepmind/engine/context.h @@ -1,4 +1,4 @@ -// Copyright (C) 2016 Google Inc. +// Copyright (C) 2016-2017 Google Inc. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -21,45 +21,35 @@ #ifndef DML_DEEPMIND_ENGINE_CONTEXT_H_ #define DML_DEEPMIND_ENGINE_CONTEXT_H_ +#include + #include #include +#include #include #include #include #include +#include "deepmind/engine/context_entities.h" +#include "deepmind/engine/context_events.h" +#include "deepmind/engine/context_game.h" +#include "deepmind/engine/context_observations.h" +#include "deepmind/engine/context_pickups.h" #include "deepmind/include/deepmind_calls.h" #include "deepmind/include/deepmind_hooks.h" +#include "deepmind/include/deepmind_model_getters.h" #include "deepmind/lua/n_results_or.h" #include "deepmind/lua/table_ref.h" #include "deepmind/lua/vm.h" +#include "deepmind/model_generation/model.h" +#include "deepmind/model_generation/model_getters.h" +#include "public/level_cache_types.h" +#include "third_party/rl_api/env_c_api.h" namespace deepmind { namespace lab { -// Parameters for a custom pickup item. -struct PickupItem { - std::string name; // Name that will show when picking up item. - std::string class_name; // Class name to spawn entity as. Must be unique. - std::string model_name; // Model for pickup item. - int quantity; // Amount to award on pickup. - int type; // Type of pickup. E.g. health, ammo, frags etc. - // Must match itemType_t in bg_public.h - int tag; // Tag used in conjunction with type. E.g. determine - // which weapon to award, or if a goal should bob and - // rotate. -}; - -// Represents a player's state in world units. -struct PlayerView { - std::array pos; // Position (forward, left, up). - std::array vel; // World velocity (forward, left, up). - std::array angles; // Orientation degrees (pitch, yaw, roll). - std::array anglesVel; // Angular velocity in degrees. - int timestamp_msec; // Engine time in msec of the view. - double height; // View height. -}; - // This is the userdata in DeepmindContext. It contains the Lua VM and // methods for handling callbacks from DeepMind Lab. class Context { @@ -68,18 +58,27 @@ class Context { // 'executable_runfiles' path to where DeepMind Lab assets are stored. // 'calls' allow the context to call into the engine. (Owned by engine.) // 'hooks' allow the engine to call into the context. + // 'file_reader_override' an optional function for reading from the file + // system. If set, a call returns whether the file 'file_name' was read + // successfully and if so 'buff' points to the content and 'size' contains the + // size of the file and after use 'buff' must be freed with 'free'. Otherwise + // returns false. + // 'temp_folder' optional folder to store temporary objects. Context(lua::Vm lua_vm, const char* executable_runfiles, - const DeepmindCalls* calls, DeepmindHooks* hooks); + const DeepmindCalls* calls, DeepmindHooks* hooks, + bool (*file_reader_override)(const char* file_name, char** buff, + size_t* size), + const char* temp_folder); // Inserts 'key' 'value' into settings_. // Must be called before Init. void AddSetting(const char* key, const char* value); - // 'script_name': name of a Lua file; this script is ran during first call to + // 'script_name': name of a Lua file; this script is run during first call to // Init. // Must be called before Init. // Returns zero if successful and non-zero on error. - int SetScriptName(const char* script_name); + int SetScriptName(std::string script_name); // Runs the script named script_name_ and stores the result in // script_table_ref_. @@ -92,6 +91,10 @@ class Context { // Returns zero if successful and non-zero on error. int Start(int episode, int seed); + // Calls "mapLoaded" member function on the script_table_ref_ . + // Returns zero if successful and non-zero on error. + int MapLoaded(); + // The return value is only valid until the next call to GetCommandLine(). // Must be called after Init. const char* GetCommandLine(const char* old_commandline); @@ -100,7 +103,19 @@ class Context { // Must be called after Init. const char* NextMap(); - // The script is called with the script_table_ref pushed on the the stack. + // Returns the game mode from script must match gametype_t. If not implemented + // in Lua it returns GT_FFA (free for all). + int GameType(); + + // Returns the team selection for a player. + // '\0' - No selection. + // 'p' - Free. + // 'r' - Red Team. + // 'b' - Blue Team. + // 's' - Spectator. + char TeamSelect(int player_id, const char* player_name); + + // The script is called with the script_table_ref pushed on the stack. // Runs the contents in the lua_vm_. If the script returns an integer this // function will return it too, else it returns 0. int RunLuaSnippet(const char* buf, std::size_t buf_len); @@ -115,6 +130,10 @@ class Context { int buttons_down); // Retrieves the current actions applied by the controller. + // The values of actions can be overridden by the lua callback named as + // modifyControl. The callback takes in a table param and returns a table. + // Both tables have 6 keys (look_down_up, look_left_right, ...) representing + // the six actions. void GetActions( // double* look_down_up, // double* look_left_right, // @@ -123,155 +142,245 @@ class Context { signed char* crouch_jump, // int* buttons_down); - // This returns whether the internal controller will call SetActions. - bool UseInternalControls() { return use_internal_controls_; } - - // Sets whether the internal controller will call SetActions. - void SetUseInternalControls(bool v) { use_internal_controls_ = v; } + // This returns whether we are running a native app and the internal + // controller will call SetActions. + bool NativeApp() { return native_app_; } - // Allows Lua to replace contents of this c-style dictionary. - // 'spawn_var_chars' is pointing at the memory holding the strings. - // '*num_spawn_var_chars' is the total length of all strings and nulls. - // 'spawn_var_offsets' is the key, value offsets in 'spawn_var_chars'. - // '*num_spawn_vars' is the number of valid spawn_var_offsets. - // - // So the dictionary { key0=value0, key1=value1, key2=value2 } would be - // represented as: - // spawn_var_chars[4096] = "key0\0value0\0key1\0value1\0key2\0value2"; - // *num_spawn_var_chars = 36 - // spawn_var_offsets[64][2] = {{0,5}, {12,17}, {24,29}}; - // *num_spawn_vars = 3 - // The update will not increase *num_spawn_var_chars to greater than 4096. - // and not increase *num_spawn_vars to greater than 64. - bool UpdateSpawnVars( // - char* spawn_var_chars, // - int* num_spawn_var_chars, // - int spawn_var_offsets[][2], // - int* num_spawn_vars); - - // Finds a pickup item by class_name, and registers this item with the - // Context's item array. This is so all the VMs can update their respective - // item lists by iterating through that array during update. - // Returns whether the item was found, and if so, writes the index at which - // the item now resides to *index. - bool FindItem(const char* class_name, int* index); + // Sets whether we are running a native app and the internal controller will + // call SetActions. + void SetNativeApp(bool v) { native_app_ = v; } // Adds all the bots specified in the script. Called on each map load. void AddBots(); - // Get the current number of registered items. - int ItemCount() const { return items_.size(); } - - // Get an item at a particular index and fill in the various buffers - // provided. - // Returns whether the operation succeeded. - bool GetItem( // - int index, // - char* item_name, // - int max_item_name, // - char* class_name, // - int max_class_name, // - char* model_name, // - int max_model_name, // - int* quantity, // - int* type, // - int* tag); - - // Clear the current list of registered items. Called just before loading a - // new map. - void ClearItems() { items_.clear(); } + // Finds a model by model_name, and registers this item with the Context's + // model array. + bool FindModel(const char* model_name); - // Returns whether we should finish the current map. - bool MapFinished() const { return map_finished_; } + // Return the accessor API for currently selected model. + void GetModelGetters( // + DeepmindModelGetters* model_accessors, // + void** model_data); - // Sets whether the current map should finish. - void SetMapFinished(bool map_finished) { map_finished_ = map_finished; } + // Clear the current list of registered models. Called just before loading a + // new map. + void ClearModel() { model_.reset(); } // Returns whether we should finish the episode. Called at the end of every // frame. bool HasEpisodeFinished(double elapsed_episode_time_seconds); - // Returns whether we can pickup the specified entity id. By default this + // Returns whether the specified entity id can trigger. By default this // returns true. - bool CanPickup(int entity_id); + bool CanTrigger(int entity_id, const char* target_name); - // Customization point for overriding the entity's pickup behaviour. Also - // allows for modifying the default respawn time for the entity. - // Returns true if the pickup behaviour has been overridden by the user, - // otherwise calls the default pickup behaviour based on the item type. - bool OverridePickup(int entity_id, int* respawn); + // Customization point for overriding the entity's trigger behaviour. + // Returns whether the trigger behaviour has been overridden by the user. + // If the trigger behaviour is not overridden, calls the default trigger + // behaviour based on the item type. + bool OverrideTrigger(int entity_id, const char* target_name); - // Subtracts the integral part from the stashed reward (see AddScore) and - // returns that integral part. The remaining stashed reward is smaller than - // one in magnitude. The returned (integral) value is suitable for the game - // server, which only deals in integral reward increments. - int ExternalReward(int player_id); + // Customization point for triggering a callback in response to a trigger + // lookat. + void TriggerLookat(int entity_id, bool looked_at, const float position[3]); + + // Customization point for overriding the value of a reward. + // + // * 'optional_reason' - The reason is either a nullptr or a string containing + // the reason this reward is being awarded. + // + // * 'player_id' Is the player the reward applies to. + // + // * 'team' is the team id the player belongs to. + // + // * 'optional_other_player_id' is a nullptr or the other player involved in + // the reward. + // + // * 'optional_origin' is either a nullptr or 3 floats containing the + // location of the reward. + // + // Returns the modified reward combined with the reward provided by + // 'ExternalReward'. + int RewardOverride(const char* optional_reason, int player_id, int team, + const int* optional_other_player_id, + const float* optional_origin, int score); // Adds the given reward for the specified player. The reward is accumulated // temporarily until it is harvested by ExternalReward. void AddScore(int player_id, double reward); // Path to where DeepMind Lab assets are stored. - const std::string& ExecutableRunfiles() const { return executable_runfiles_; } + const std::string& ExecutableRunfiles() const { + return Game().ExecutableRunfiles(); + } + + const std::string& TempDirectory() const { return Game().TempFolder(); } // Returns a new random seed on each call. Internally uses 'engine_prbg_' to // generate new positive integers. int MakeRandomSeed(); - const DeepmindCalls* Calls() const { return deepmind_calls_; } - - // Gets the seed this episode was launched with. - int GetEpisodeSeed() const { return random_seed_; } - std::mt19937_64* UserPrbg() { return &user_prbg_; } std::mt19937_64* EnginePrbg() { return &engine_prbg_; } + // Returns whether to replace the name of model being loaded with an + // alternative name and add a prefix to all the model's textures loaded with + // it. + // + // `name` - Name of the model being loaded. + // `new_name` - Pointer to a buffer to store the alternative name in. + // `new_name_size` - The size of the `new_name` buffer. + // `texture_prefix` - Pointer to a buffer to store the prefix in. + // `texture_prefix_size` - The size of the `texture_prefix` buffer. + bool ReplaceModelName(const char* name, char* new_name, int new_name_size, + char* texture_prefix, int texture_prefix_size); + + // Returns whether to replace the name of a texture being loaded with an + // alternative one. + // + // `name` - Name of the texture about to be loaded. + // `new_name` - A pointer to a buffer to store the alternative name in. + // `max_size` - The size of the `new_name` buffer. + bool ReplaceTextureName(const char* name, char* new_name, int max_size); + + // External texture loader. Returns whether a texture was loaded. If true, + // `pixels`, `width` and `height` will store the data about the texture. + // Otherwise the arguments are left unchanged and the built-in texture loaders + // are used. + // + // `name` - Name of the texture being loaded (without extension). + // `pixels` - A buffer sized to hold an rgba byte texture. + // `width` - Used to store the width of the created texture. + // `height` - Used to store the height of the created texture. + // `allocator` - Used to allocate the memory for `pixels`. The amount of + // memory allocated shall be *width * *height * 4 bytes. + bool LoadTexture(const char* name, unsigned char** pixels, int* width, + int* height, void* (*allocator)(int size)); + // Modify a texture after loading. - void ModifyRgbaTexture(const char* name, unsigned char* data, int width, + bool ModifyRgbaTexture(const char* name, unsigned char* data, int width, int height); - // Script observation count. - int CustomObservationCount() const { return observation_infos_.size(); } - - // Script observation name. - const char* CustomObservationName(int idx) const { - return observation_infos_[idx].name.c_str(); - } - - // Script observation spec. - void CustomObservationSpec(int idx, EnvCApi_ObservationSpec* spec) const; - - // Script observation. - void CustomObservation(int idx, EnvCApi_Observation* obs); - - // Set latest predicted player state. - void SetPredictPlayerState(const float pos[3], const float vel[3], - const float angles[3], float height, - int timestamp_msec); - - // Get latest predicted player view. (This is where the game renders from.) - const PlayerView GetPredictPlayerView() { - return predicted_player_view_; - } - // Calls script to retrieve a list of screen messages. The message returned // from the script shall be strictly smaller than buffer_size, since the // buffer needs space for the null padding. 'screen_width' and 'screen_height' // are the size of the screen. - int MakeScreenMesages(int screen_width, int screen_height, int line_height, - int string_buffer_size); + int MakeScreenMessages(int screen_width, int screen_height, int line_height, + int string_buffer_size); // Retrieve screen message. 'buffer' is filled with a null terminated string. // The room in the buffer is 'string_buffer_size' from the MakeScreenMessage // command. 'x' and 'y' are the screen coordinates in terms of the screen - // 'height' and 'width' also in from the MakeScreenMesages. - // 'message_id' shall be greater than or equal to zero and less then what + // 'height' and 'width' also from the MakeScreenMesages. + // 'message_id' shall be greater than or equal to zero and less than what // was returned by the last call of MakeScreenMesages. // 'align_l0_r1_c2' is how the text is horizontally aligned. '0' for left, - // '1' for right and '2' for center. + // '1' for right and '2' for center. 'shadow' is whether to render a black + // offset drop shadow. 'rgba' is the color and alpha of the text. void GetScreenMessage(int message_id, char* buffer, int* x, int* y, - int* align_l0_r1_c2) const; + int* align_l0_r1_c2, int* shadow, float rgba[4]) const; + + // Calls script to retrieve a list of filled rectangles. 'screen_width' and + // 'screen_height' are the size of the screen. + int MakeFilledRectangles(int screen_width, int screen_height); + + // Retrieve filled rectangle. + // 'rectangle_id' shall be greater than or equal to zero and less than what + // was returned by the last call of MakeFilledRectangles. + // 'x', 'y' is the position and 'width' and 'height' is the size of the + // rectangle in screen-coordinates. They shall be all greater or equal to + // zero. (Off-screen rendering is allowed.) + // 'rgba' is the color and alpha of the rendered rectangle. 'rgba' values + // shall be in the range [0, 1]. + // The parts of the rectangles that are out of bounds are not rendered. + void GetFilledRectangle(int rectangle_id, int* x, int* y, int* width, + int* height, float rgba[4]) const; + + // Retrieves player position and velocity deltas. + // 'mover_id' is the ID of the triggering entity. + // 'mover_pos' is the position of the triggering entity. + // 'player_pos' is the current position of the player. + // 'player_vel' is the current velocity of the player. + // 'player_pos_delta' retrieves the position delta for the player. + // 'player_vel_delta' retrieves the velocity delta for the player. + // If the Lua function 'playerMover' is unimplemented, player_pos_delta and + // player_vel_delta will remain unchanged. + void CustomPlayerMovement(int mover_id, const float mover_pos[3], + const float player_pos[3], + const float player_vel[3], + float player_pos_delta[3], + float player_vel_delta[3]); + + // Called on the spawning and updating of each player. Arrays are prefixed + // with their count: + // + // `is_spawning` whether the player player is spawning. + // `gadget_inventory` array and matches the contents of playerState_t::ammo, + // `stat_inventory` array and matches the contents of playerState_t::stats, + // `powerup_time` array and matches the contents of playerState_t::powerups. + // `gadget_held` player gadget held (See game_scripts/common/inventory.lua) + // `height` player eye height. + // `position` player location in world units. + // `view_angles` player look direction in Euler degrees. + void UpdateInventory(bool is_spawning, int player_id, int gadget_count, + int gadget_inventory[], int stat_count, + int stat_inventory[], int powerup_count, + int powerup_time[], int gadget_held, float height, + float position[3], float view_angles[3]); + + // Calls `gameEvent` with the event name and an array of data. + void GameEvent(const char* event_name, int count, const float* data); + + // Generates a pk3 from the map in `map_path` named `map_name`. + // `gen_aas` should be set if bots are used with level. + void MakePk3FromMap(const char* map_path, const char* map_name, bool gen_aas); + + // Sets which level caches to use. See MapCompileSettings in compile_map.h. + void SetLevelCacheSetting(bool local, bool global, + DeepMindLabLevelCacheParams level_cache_params) { + use_local_level_cache_ = local; + use_global_level_cache_ = global; + level_cache_params_ = level_cache_params; + } + + bool UseLocalLevelCache() const { return use_local_level_cache_; } + bool UseGlobalLevelCache() const { return use_global_level_cache_; } + DeepMindLabLevelCacheParams LevelCacheParams() const { + return level_cache_params_; + } + + const char* ErrorMessage() const { return error_message_.c_str(); } + + // Sets current error message. 'message' shall be a null terminated string. + void SetErrorMessage(const char* message) { + error_message_ = std::string(message); + } + + // Sets whether there are alternative cameras. This will make the server send + // all entities, so they are visible to all cameras. + void SetHasAltCameras(bool has_alt_cameras) { + has_alt_cameras_ = has_alt_cameras; + } + + // Returns whether the server sends all entities, so they are visible to all + // cameras. + bool HasAltCameras() const { return has_alt_cameras_; } + + const ContextGame& Game() const { return game_; } + ContextGame* MutableGame() { return &game_; } + + const ContextEvents& Events() const { return events_; } + ContextEvents* MutableEvents() { return &events_; } + + const ContextObservations& Observations() const { return observations_; } + ContextObservations* MutableObservations() { return &observations_; } + + const ContextPickups& Pickups() const { return pickups_; } + ContextPickups* MutablePickups() { return &pickups_; } + + const ContextEntities& GameEntities() const { return game_entities_; } + ContextEntities* MutableGameEntities() { return &game_entities_; } private: // Message to be placed on screen. @@ -280,6 +389,16 @@ class Context { int x; int y; int align_l0_r1_c2; + std::array rgba; + bool shadow; + }; + + struct FilledRectangle { + int x; + int y; + int width; + int height; + std::array rgba; }; // Current action state. @@ -292,17 +411,14 @@ class Context { int buttons_down; }; - // Entry for a custom observation spec. - struct ObservationSpecInfo { - std::string name; - EnvCApi_ObservationType type; - std::vector shape; - }; + // Subtracts the integral part from the stashed reward (see AddScore) and + // returns that integral part. The remaining stashed reward is smaller than + // one in magnitude. The returned (integral) value is suitable for the game + // server, which only deals in integral reward increments. + int ExternalReward(int player_id); int CallInit(); - int CallObservationSpec(); - Context(const Context&) = delete; Context& operator=(const Context&) = delete; @@ -322,8 +438,8 @@ class Context { // The settings to run the script with. std::unordered_map settings_; - // The name of the script ran on first Init. - std::string script_name_; + // The name of the script to run on first Init. + std::string script_path_; // The result of the script that was run when Init was first called. lua::TableRef script_table_ref_; @@ -337,47 +453,60 @@ class Context { // Cached map name to enable returning a pointer to its contents. std::string map_name_; - // Stores whether the internal controller will call SetActions. - bool use_internal_controls_; + // Stores whether we are running a native app and the internal controller will + // call SetActions. + bool native_app_; // Current actions to apply when lab is advanced. Actions actions_; - // Array of current custom pickup items. Reset each episode. - std::vector items_; - - // Flag that can be set from the game to finish the current map. - bool map_finished_; + // Current custom pickup model. Reset each episode. + std::string model_name_; + std::unique_ptr model_; // Transient reward stash for each player. Rewards are added with AddScore and // removed by ExternalReward. std::vector player_rewards_; - // Random seed used for this episode. - int random_seed_; - // A pseudo-random-bit generator for exclusive use by users. std::mt19937_64 user_prbg_; // A pseudo-random-bit generator for exclusive use of the engine. Seeded each - // episode with 'random_seed_'. + // episode with the episode start seed. std::mt19937_64 engine_prbg_; - // Storage of supplementary observation types from script. - std::vector observation_infos_; + // A list of screen messages to display this frame. + std::vector screen_messages_; - // Used to hold the EnvCApi_ObservationSpec::shape values until the next call - // of observation. - std::vector observation_tensor_shape_; + // A list of filled rectangles to display this frame. + std::vector filled_rectangles_; - // Used to hold a reference to the observation tensor until the next call of - // observation. - lua::TableRef observation_tensor_; + bool use_local_level_cache_; + bool use_global_level_cache_; - PlayerView predicted_player_view_; + // Callbacks for fetching/writing levels to cache. + DeepMindLabLevelCacheParams level_cache_params_; - // A list of screen messages to display this frame. - std::vector screen_messages_; + // Last error message. + std::string error_message_; + + // An object for storing and retrieving events. + ContextEvents events_; + + // An object for calling into the engine. + ContextGame game_; + + // An object for storing and retrieving custom observations. + ContextObservations observations_; + + // An object for interacting with pickups. + ContextPickups pickups_; + + // An object for retrieving information about in game entities. + ContextEntities game_entities_; + + // When enabled all entities are forced to be rendered. + bool has_alt_cameras_; }; } // namespace lab diff --git a/deepmind/engine/context_entities.cc b/deepmind/engine/context_entities.cc new file mode 100644 index 00000000..c81231cc --- /dev/null +++ b/deepmind/engine/context_entities.cc @@ -0,0 +1,107 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "deepmind/engine/context_entities.h" + +#include +#include + +#include "deepmind/lua/class.h" +#include "deepmind/lua/push.h" +#include "deepmind/lua/read.h" +#include "deepmind/lua/table_ref.h" + +namespace deepmind { +namespace lab { +namespace { + +class LuaEntitiesModule : public lua::Class { + friend class Class; + static const char* ClassName() { return "deepmind.lab.Entities"; } + + public: + // '*ctx' owned by the caller and should out-live this object. + explicit LuaEntitiesModule(ContextEntities* ctx) : ctx_(ctx) {} + + static void Register(lua_State* L) { + const Class::Reg methods[] = { + {"entities", Member<&LuaEntitiesModule::Entities>}, + }; + Class::Register(L, methods); + } + + private: + // Returns a list of entities to Lua. + // [0, 1, -] + lua::NResultsOr Entities(lua_State* L) { + constexpr int kEntityNotVisibilityFlag = 0x80; + lua::TableRef table = lua::TableRef::Create(L); + int row_idx = 0; + std::vector filter; + lua::Read(L, 2, &filter); + for (const auto& row : ctx_->Entities()) { + if (filter.empty() || std::find(filter.begin(), filter.end(), + row.class_name) != filter.end()) { + lua::TableRef entity = table.CreateSubTable(++row_idx); + entity.Insert("entityId", row.entity_id + 1); + entity.Insert("id", row.user_id); + entity.Insert("type", row.type); + entity.Insert("visible", (row.flags & kEntityNotVisibilityFlag) == 0); + entity.Insert("position", row.position); + entity.Insert("classname", row.class_name); + } + } + lua::Push(L, table); + return 1; + } + + ContextEntities* ctx_; +}; + +} // namespace + +lua::NResultsOr ContextEntities::Module(lua_State* L) { + if (auto* ctx = static_cast( + lua_touserdata(L, lua_upvalueindex(1)))) { + LuaEntitiesModule::Register(L); + LuaEntitiesModule::CreateObject(L, ctx); + return 1; + } else { + return "Missing context!"; + } +} + +void ContextEntities::Clear() { + entities_.clear(); +} + +void ContextEntities::Add(int entity_id, int user_id, int type, int flags, + const float position[3], const char* classname) { + entities_.emplace_back(); + Entity& entity = entities_.back(); + entity.entity_id = entity_id; + entity.user_id = user_id; + entity.type = type; + entity.flags = flags; + std::copy_n(position, 3, entity.position.begin()); + entity.class_name = classname; +} + +} // namespace lab +} // namespace deepmind + diff --git a/deepmind/engine/context_entities.h b/deepmind/engine/context_entities.h new file mode 100644 index 00000000..fee47739 --- /dev/null +++ b/deepmind/engine/context_entities.h @@ -0,0 +1,63 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef DML_DEEPMIND_ENGINE_CONTEXT_ENTITIES_H_ +#define DML_DEEPMIND_ENGINE_CONTEXT_ENTITIES_H_ + +#include +#include + +#include "deepmind/lua/lua.h" +#include "deepmind/lua/n_results_or.h" + +namespace deepmind { +namespace lab { + +// Receive calls from lua_script. +class ContextEntities { + public: + struct Entity { + int entity_id; + int user_id; + int type; + int flags; + std::array position; + std::string class_name; + }; + + // Returns an entity module. A pointer to ContextEntities must exist in the up + // value. [0, 1, e] + static lua::NResultsOr Module(lua_State* L); + + // Clears all entities and players. (Called at start of entity update.) + void Clear(); + + // Called on each entity every frame. + void Add(int entity_id, int user_id, int type, int flags, + const float position[3], const char* classname); + + const std::vector& Entities() const { return entities_; } + + private: + std::vector entities_; // Entities active this frame. +}; + +} // namespace lab +} // namespace deepmind + +#endif // DML_DEEPMIND_ENGINE_CONTEXT_ENTITIES_H_ diff --git a/deepmind/engine/context_entities_test.cc b/deepmind/engine/context_entities_test.cc new file mode 100644 index 00000000..236364ed --- /dev/null +++ b/deepmind/engine/context_entities_test.cc @@ -0,0 +1,68 @@ +#include "deepmind/engine/context_entities.h" + +#include "gtest/gtest.h" +#include "absl/types/span.h" +#include "deepmind/lua/bind.h" +#include "deepmind/lua/call.h" +#include "deepmind/lua/n_results_or_test_util.h" +#include "deepmind/lua/push.h" +#include "deepmind/lua/push_script.h" +#include "deepmind/lua/read.h" +#include "deepmind/lua/table_ref.h" +#include "deepmind/lua/vm_test_util.h" + +namespace deepmind { +namespace lab { +namespace { + +using ::deepmind::lab::lua::testing::IsOkAndHolds; + +class ContextEntitiesTest : public lua::testing::TestWithVm { + protected: + ContextEntitiesTest() { + vm()->AddCModuleToSearchers("dmlab.system.game_entities", + lua::Bind, {&ctx_}); + } + + ContextEntities ctx_; +}; + +constexpr char kGetEntity[] = R"( +local game_entities = require 'dmlab.system.game_entities' +local index = ... +return game_entities:entities()[index] +)"; + +TEST_F(ContextEntitiesTest, UpdateEntities) { + for (int i = 0; i < 10; ++i) { + int entity_id = i * 10; + int type = 10; + int flags = 0x80; + float position[3] = {i * 1.0f, i * 2.0f, i * 3.0f}; + ctx_.Add(entity_id, 0, type, flags, position, "classname"); + } + + const int index = 4; + + lua::PushScript(L, kGetEntity, sizeof(kGetEntity) - 1, "kGetEntity"); + lua::Push(L, index + 1); + ASSERT_THAT(lua::Call(L, 1), IsOkAndHolds(1)); + lua::TableRef table; + ASSERT_TRUE(lua::Read(L, 1, &table)); + + int val = 0; + EXPECT_TRUE(table.LookUp("entityId", &val)); + EXPECT_EQ(val, index * 10 + 1); + bool visible = true; + EXPECT_TRUE(table.LookUp("visible", &visible)); + EXPECT_FALSE(visible); + float new_position[3]; + EXPECT_TRUE(table.LookUp("position", absl::MakeSpan(new_position))); + float old_position[3] = {index * 1.0f, index * 2.0f, index * 3.0f}; + EXPECT_EQ(absl::MakeConstSpan(old_position), + absl::MakeConstSpan(new_position)); +} + +} // namespace +} // namespace lab +} // namespace deepmind diff --git a/deepmind/engine/context_events.cc b/deepmind/engine/context_events.cc new file mode 100644 index 00000000..02c62fd4 --- /dev/null +++ b/deepmind/engine/context_events.cc @@ -0,0 +1,184 @@ +#include "deepmind/engine/context_events.h" + +#include + +#include "deepmind/lua/class.h" +#include "deepmind/lua/lua.h" +#include "deepmind/lua/read.h" +#include "deepmind/tensor/lua_tensor.h" +#include "deepmind/tensor/tensor_view.h" + +namespace deepmind { +namespace lab { +namespace { + +class LuaEventsModule : public lua::Class { + friend class Class; + static const char* ClassName() { return "deepmind.lab.Events"; } + + public: + // '*ctx' owned by the caller and should out-live this object. + explicit LuaEventsModule(ContextEvents* ctx) : ctx_(ctx) {} + + // Registers classes metatable with Lua. + static void Register(lua_State* L) { + const Class::Reg methods[] = {{"add", Member<&LuaEventsModule::Add>}}; + Class::Register(L, methods); + } + + private: + template + void AddTensorObservation(int id, const tensor::TensorView& view) { + const auto& shape = view.shape(); + std::vector out_shape(shape.begin(), shape.end()); + + std::vector out_values; + out_values.reserve(view.num_elements()); + view.ForEach([&out_values](T v) { out_values.push_back(v); }); + ctx_->AddObservation(id, std::move(out_shape), std::move(out_values)); + } + + // Signature events:add(eventName, [obs1, [obs2 ...] ...]) + // Called with an event name and a list of observations. Each observation + // maybe one of string, ByteTensor or DoubleTensor. + // [-(2 + #observations), 0, e] + lua::NResultsOr Add(lua_State* L) { + int top = lua_gettop(L); + std::string name; + if (!lua::Read(L, 2, &name)) { + return "Event name must be a string"; + } + int id = ctx_->Add(std::move(name)); + for (int i = 3; i <= top; ++i) { + std::string string_arg; + if (lua::Read(L, i, &string_arg)) { + ctx_->AddObservation(id, std::move(string_arg)); + } else if (auto* double_tensor = + tensor::LuaTensor::ReadObject(L, i)) { + AddTensorObservation(id, double_tensor->tensor_view()); + } else if (auto* byte_tensor = + tensor::LuaTensor::ReadObject(L, i)) { + AddTensorObservation(id, byte_tensor->tensor_view()); + } else { + return "[event] - Observation type not supported. Must be one of " + "string|ByteTensor|DoubleTensor."; + } + } + return 0; + } + + ContextEvents* ctx_; +}; + +} // namespace + +lua::NResultsOr ContextEvents::Module(lua_State* L) { + if (auto* ctx = + static_cast(lua_touserdata(L, lua_upvalueindex(1)))) { + LuaEventsModule::Register(L); + LuaEventsModule::CreateObject(L, ctx); + return 1; + } else { + return "Missing event context!"; + } +} + +int ContextEvents::Add(std::string name) { + auto iter_inserted = name_to_id_.emplace(std::move(name), names_.size()); + if (iter_inserted.second) { + names_.push_back(iter_inserted.first->first.c_str()); + } + + int id = events_.size(); + events_.push_back(Event{iter_inserted.first->second}); + return id; +} + +void ContextEvents::AddObservation(int event_id, std::string string_value) { + Event& event = events_[event_id]; + event.observations.emplace_back(); + auto& observation = event.observations.back(); + observation.type = EnvCApi_ObservationString; + + observation.shape_id = shapes_.size(); + std::vector shape(1); + shape[0] = string_value.size(); + shapes_.emplace_back(std::move(shape)); + + observation.array_id = strings_.size(); + strings_.push_back(std::move(string_value)); +} + +void ContextEvents::AddObservation(int event_id, std::vector shape, + std::vector double_tensor) { + Event& event = events_[event_id]; + event.observations.emplace_back(); + auto& observation = event.observations.back(); + observation.type = EnvCApi_ObservationDoubles; + + observation.shape_id = shapes_.size(); + shapes_.push_back(std::move(shape)); + + observation.array_id = doubles_.size(); + doubles_.push_back(std::move(double_tensor)); +} + +void ContextEvents::AddObservation(int event_id, std::vector shape, + std::vector byte_tensor) { + Event& event = events_[event_id]; + event.observations.emplace_back(); + auto& observation = event.observations.back(); + observation.type = EnvCApi_ObservationBytes; + + observation.shape_id = shapes_.size(); + shapes_.push_back(std::move(shape)); + + observation.array_id = bytes_.size(); + bytes_.push_back(std::move(byte_tensor)); +} + +void ContextEvents::Clear() { + events_.clear(); + strings_.clear(); + shapes_.clear(); + doubles_.clear(); + bytes_.clear(); +} + +void ContextEvents::Export(int event_idx, EnvCApi_Event* event) { + const auto& internal_event = events_[event_idx]; + observations_.clear(); + observations_.reserve(internal_event.observations.size()); + for (const auto& observation : internal_event.observations) { + observations_.emplace_back(); + auto& observation_out = observations_.back(); + observation_out.spec.type = observation.type; + + const auto& shape = shapes_[observation.shape_id]; + observation_out.spec.dims = shape.size(); + observation_out.spec.shape = shape.data(); + + switch (observation.type) { + case EnvCApi_ObservationBytes: { + const auto& tensor = bytes_[observation.array_id]; + observation_out.payload.bytes = tensor.data(); + break; + } + case EnvCApi_ObservationDoubles: { + const auto& tensor = doubles_[observation.array_id]; + observation_out.payload.doubles = tensor.data(); + break; + } + case EnvCApi_ObservationString: + const auto& string_value = strings_[observation.array_id]; + observation_out.payload.string = string_value.c_str(); + break; + } + } + event->id = internal_event.type_id; + event->observations = observations_.data(); + event->observation_count = observations_.size(); +} + +} // namespace lab +} // namespace deepmind diff --git a/deepmind/engine/context_events.h b/deepmind/engine/context_events.h new file mode 100644 index 00000000..960d145f --- /dev/null +++ b/deepmind/engine/context_events.h @@ -0,0 +1,103 @@ +#ifndef DML_DEEPMIND_ENGINE_CONTEXT_EVENTS_H_ +#define DML_DEEPMIND_ENGINE_CONTEXT_EVENTS_H_ + +#include +#include +#include + +#include "deepmind/lua/lua.h" +#include "deepmind/lua/n_results_or.h" +#include "third_party/rl_api/env_c_api.h" + +namespace deepmind { +namespace lab { + +// Support class for storing events generated from Lua. These events can be read +// out of DM Lab using the events part of the EnvCApi. (See: env_c_api.h.) +// +// Each event contains a list of observations. Each observation type is one of +// EnvCApi_Observation{Doubles,Bytes,String}. +class ContextEvents { + public: + // Returns an event module. A pointer to ContextEvents must exist in the up + // value. [0, 1, -] + static lua::NResultsOr Module(lua_State* L); + + // Adds event returning its index. + int Add(std::string name); + + // Adds string observation to event at index 'event_id'. + void AddObservation(int event_id, std::string string_value); + + // Adds DoubleTensor observation to event at index 'event_id'. + void AddObservation(int event_id, std::vector shape, + std::vector double_tensor); + + // Adds ByteTensor observation to event at index 'event_id'. + void AddObservation(int event_id, std::vector shape, + std::vector byte_tensor); + + // Exports an event at 'event_idx', which must be in range [0, Count()), to an + // EnvCApi_Event structure. Observations within the `event` are invalidated by + // calls to non-const methods. + void Export(int event_idx, EnvCApi_Event* event); + + // Returns the number of events created since last call to ClearEvents(). + int Count() const { return events_.size(); } + + // Returns the number of event types. + int TypeCount() const { return names_.size(); } + + // Returns the name of the event associated with event_type_id, which must be + // in range [0, EventTypeCount()). New events types maybe added at any point + // but the event_type_ids remain stable. + const char* TypeName(int event_type_id) const { + return names_[event_type_id]; + } + + // Clears all the events and their observations. + void Clear(); + + private: + struct Event { + int type_id; // Event type id. + + struct Observation { + EnvCApi_ObservationType_enum type; + + int shape_id; // Index in shapes_ for shape of this observation. + + // Index of observation data. The array depends on type. + // If type == EnvCApi_ObservationDoubles then index in doubles_. + // If type == EnvCApi_ObservationBytes then index in bytes_. + // If type == EnvCApi_ObservationString then index in string_. + int array_id; + }; + + // List of observations associated with event. + std::vector observations; + }; + + // Events generated since construction or last call to Clear(). + std::vector events_; + + // Lookup type_id to event_name. + std::vector names_; + + // Reverse lookup of event_name to type_id. + std::unordered_map name_to_id_; + + // Event observation storage. + std::vector> shapes_; + std::vector> bytes_; + std::vector> doubles_; + std::vector strings_; + + // Temporary EnvCApi_Observation observation storage. + std::vector observations_; +}; + +} // namespace lab +} // namespace deepmind + +#endif // DML_DEEPMIND_ENGINE_CONTEXT_EVENTS_H_ diff --git a/deepmind/engine/context_game.cc b/deepmind/engine/context_game.cc new file mode 100644 index 00000000..6b218da5 --- /dev/null +++ b/deepmind/engine/context_game.cc @@ -0,0 +1,337 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "deepmind/engine/context_game.h" + +#include +#include +#include +#include +#include + +#include "deepmind/lua/class.h" +#include "deepmind/lua/push.h" +#include "deepmind/lua/read.h" +#include "deepmind/lua/table_ref.h" +#include "deepmind/tensor/lua_tensor.h" +#include "deepmind/tensor/tensor_view.h" +#include "deepmind/util/files.h" + +namespace deepmind { +namespace lab { +namespace { + +class StorageFreeChar : public tensor::StorageValidity { + public: + StorageFreeChar(char* data) : StorageValidity(kOwnsStorage), data_(data) {} + ~StorageFreeChar() { free(data_); } + unsigned char* data() { return reinterpret_cast(data_); } + + private: + char* data_; +}; + +class StorageString : public tensor::StorageValidity { + public: + StorageString(std::string data) + : StorageValidity(kOwnsStorage), data_(std::move(data)) {} + unsigned char* data() { return reinterpret_cast(&data_[0]); } + + private: + std::string data_; +}; + +class LuaGameModule : public lua::Class { + friend class Class; + static const char* ClassName() { return "deepmind.lab.Game"; } + + public: + // '*ctx' owned by the caller and should out-live this object. + explicit LuaGameModule(ContextGame* ctx) : ctx_(ctx) {} + + static void Register(lua_State* L) { + const Class::Reg methods[] = { + {"addScore", Member<&LuaGameModule::AddScore>}, + {"finishMap", Member<&LuaGameModule::FinishMap>}, + {"playerInfo", Member<&LuaGameModule::PlayerInfo>}, + {"updateTexture", Member<&LuaGameModule::UpdateTexture>}, + {"episodeTimeSeconds", Member<&LuaGameModule::EpisodeTimeSeconds>}, + {"tempFolder", Member<&LuaGameModule::TempFolder>}, + {"runFiles", Member<&LuaGameModule::ExecutableRunfiles>}, + {"raycast", Member<&LuaGameModule::Raycast>}, + {"loadFileToByteTensor", Member<&LuaGameModule::LoadFileToByteTensor>}, + {"loadFileToString", Member<&LuaGameModule::LoadFileToString>}, + {"copyFileToLocation", Member<&LuaGameModule::CopyFileToLocation>}, + }; + Class::Register(L, methods); + } + + private: + lua::NResultsOr AddScore(lua_State* L) { + int player_id = 0; + double score = 0; + if (lua::Read(L, 2, &player_id) && lua::Read(L, 3, &score) && + 0 <= player_id && player_id < 64) { + ctx_->Calls()->add_score(player_id, score); + return 0; + } + std::string error = "Invalid arguments player_id: "; + error += lua::ToString(L, 2); + error += " or reward: "; + error += lua::ToString(L, 3); + return std::move(error); + } + + lua::NResultsOr Raycast(lua_State* L) { + std::array start, end; + if (lua::Read(L, 2, &start) && lua::Read(L, 3, &end)) { + lua::Push(L, ctx_->Calls()->raycast(start.data(), end.data())); + return 1; + } + return "Must provide start and end coordinates"; + } + + lua::NResultsOr FinishMap(lua_State* L) { + ctx_->SetMapFinished(true); + return 0; + } + + lua::NResultsOr PlayerInfo(lua_State* L) { + const auto& pv = ctx_->GetPlayerView(); + auto table = lua::TableRef::Create(L); + table.Insert("pos", pv.pos); + table.Insert("vel", pv.vel); + table.Insert("angles", pv.angles); + table.Insert("anglesVel", pv.anglesVel); + table.Insert("height", pv.height); + table.Insert("playerId", pv.player_id + 1); + table.Insert("teamScore", pv.team_score); + table.Insert("otherTeamScore", pv.other_team_score); + lua::Push(L, table); + return 1; + } + + lua::NResultsOr UpdateTexture(lua_State* L) { + std::string name; + if (!lua::Read(L, 2, &name)) { + std::string error = "Invalid argument name: "; + error += lua::ToString(L, 2); + return std::move(error); + } + auto* data = tensor::LuaTensor::ReadObject(L, 3); + if (data == nullptr) { + std::string error = "Invalid argument data: "; + error += lua::ToString(L, 3); + return std::move(error); + } + const auto& tensor_view = data->tensor_view(); + const auto& shape = tensor_view.shape(); + if (shape.size() != 3 || shape[2] != 4) { + return "Invalid dimensions for argument data"; + } + bool success = ctx_->Calls()->update_rgba_texture( + name.c_str(), shape[1], shape[0], tensor_view.storage()); + if (!success) { + std::string error = "The texture named: '"; + error += name; + error += "' has not been updated"; + return error; + } + return 0; + } + + lua::NResultsOr EpisodeTimeSeconds(lua_State* L) { + lua::Push(L, ctx_->Calls()->total_time_seconds()); + return 1; + } + + lua::NResultsOr TempFolder(lua_State* L) { + lua::Push(L, ctx_->TempFolder()); + return 1; + } + + lua::NResultsOr ExecutableRunfiles(lua_State* L) { + lua::Push(L, ctx_->ExecutableRunfiles()); + return 1; + } + + lua::NResultsOr LoadFileToString(lua_State* L) { + std::string file_name; + if (!lua::Read(L, -1, &file_name)) { + return "Must supply file name."; + } + if (ctx_->FileReaderOverride()) { + size_t size = 0; + char* buff = nullptr; + if (!ctx_->FileReaderOverride()(file_name.c_str(), &buff, &size)) { + return "File not found!"; + } + lua_pushlstring(L, buff, size); + free(buff); + } else { + std::string contents; + if (!util::GetContents(file_name, &contents)) { + return "File not found!"; + } + lua::Push(L, contents); + } + return 1; + } + + lua::NResultsOr LoadFileToByteTensor(lua_State* L) { + std::string file_name; + if (!lua::Read(L, 2, &file_name)) { + return "Must supply file name."; + } + + if (ctx_->FileReaderOverride()) { + size_t size = 0; + char* buff = nullptr; + if (!ctx_->FileReaderOverride()(file_name.c_str(), &buff, &size)) { + return "File not found!"; + } + + auto storage = std::make_shared(buff); + tensor::TensorView tensor_view(tensor::Layout({size}), + storage->data()); + tensor::LuaTensor::CreateObject(L, std::move(tensor_view), + std::move(storage)); + } else { + std::string data; + if (!util::GetContents(file_name, &data)) { + return "File not found!"; + } + auto size = data.size(); + auto storage = std::make_shared(std::move(data)); + tensor::TensorView tensor_view(tensor::Layout({size}), + storage->data()); + + tensor::LuaTensor::CreateObject(L, std::move(tensor_view), + std::move(storage)); + } + return 1; + } + + lua::NResultsOr CopyFileToLocation(lua_State* L) { + std::string file_name_from, file_name_to; + if (!lua::Read(L, 2, &file_name_from)) { + return "Must supply from file name."; + } + if (!lua::Read(L, 3, &file_name_to)) { + return "Must supply from file name to."; + } + + if (ctx_->FileReaderOverride()) { + size_t size = 0; + char* buff = nullptr; + if (!ctx_->FileReaderOverride()(file_name_from.c_str(), &buff, &size)) { + return "File not found!"; + } + bool success = + util::SetContents(file_name_to, absl::string_view(buff, size)); + free(buff); + if (!success) { + return "Failed to write file"; + } + } else { + std::string contents; + if (!util::GetContents(file_name_from, &contents)) { + return "File not found!"; + } + bool success = util::SetContents(file_name_to, contents); + if (!success) { + return "Failed to write file"; + } + } + return 1; + } + + ContextGame* ctx_; +}; + +// Returns the unique value in the range [-180, 180) that is equivalent to +// 'angle', where two values x and y are considered equivalent whenever x - y +// is an integral multiple of 360. (Note: the result may be meaningless if the +// magnitude of 'angle' is very large.) +double CanonicalAngle360(double angle) { + const double n = std::floor((angle + 180.0) * (1.0 / 360.0)); + return angle - n * 360.0; +} + +} // namespace + +int ContextGame::Init() { + temp_folder_owned_ = temp_folder_.empty(); + if (temp_folder_owned_) { + temp_folder_ = util::GetTempDirectory() + "/dmlab_temp_folder_XXXXXX"; + char* temp_folder_result = mkdtemp(&temp_folder_[0]); + if (temp_folder_result == nullptr) { + std::cerr << "Failed to create temp folder\n"; + return 1; + } + } + return 0; +} + +ContextGame::~ContextGame() { + if (!temp_folder_.empty() && temp_folder_owned_) { + util::RemoveDirectory(temp_folder_); + } +} + +lua::NResultsOr ContextGame::Module(lua_State* L) { + if (auto* ctx = + static_cast(lua_touserdata(L, lua_upvalueindex(1)))) { + LuaGameModule::Register(L); + LuaGameModule::CreateObject(L, ctx); + return 1; + } else { + return "Missing context!"; + } +} + +void ContextGame::SetPlayerState(const float pos[3], const float vel[3], + const float angles[3], float height, + int team_score, int other_team_score, + int player_id, int timestamp_msec) { + PlayerView before = player_view_; + std::copy_n(pos, 3, player_view_.pos.begin()); + std::copy_n(vel, 3, player_view_.vel.begin()); + std::copy_n(angles, 3, player_view_.angles.begin()); + player_view_.height = height; + player_view_.timestamp_msec = timestamp_msec; + player_view_.player_id = player_id; + player_view_.team_score = team_score; + player_view_.other_team_score = other_team_score; + int delta_time_msec = player_view_.timestamp_msec - before.timestamp_msec; + + // When delta_time_msec < 3 the velocities become inaccurate. + if (before.timestamp_msec > 0 && delta_time_msec > 0) { + double inv_delta_time = 1000.0 / delta_time_msec; + for (int i : {0, 1, 2}) { + player_view_.anglesVel[i] = + CanonicalAngle360(player_view_.angles[i] - before.angles[i]) * + inv_delta_time; + } + } else { + player_view_.anglesVel.fill(0); + } +} + +} // namespace lab +} // namespace deepmind diff --git a/deepmind/engine/context_game.h b/deepmind/engine/context_game.h new file mode 100644 index 00000000..31aac5b7 --- /dev/null +++ b/deepmind/engine/context_game.h @@ -0,0 +1,128 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef DML_DEEPMIND_ENGINE_CONTEXT_GAME_H_ +#define DML_DEEPMIND_ENGINE_CONTEXT_GAME_H_ + +#include + +#include +#include +#include + +#include "deepmind/include/deepmind_calls.h" +#include "deepmind/lua/lua.h" +#include "deepmind/lua/n_results_or.h" + +namespace deepmind { +namespace lab { + +// Represents a player's state in world units. +struct PlayerView { + std::array pos; // Position (forward, left, up). + std::array vel; // World velocity (forward, left, up). + std::array angles; // Orientation degrees (pitch, yaw, roll). + std::array anglesVel; // Angular velocity in degrees. + int team_score; // Number of times we captured a flag. + int other_team_score; // Number of times others captured a flag. + int player_id; + int timestamp_msec; // Engine time in msec of the view. + double height; // View height. +}; + +// Receive calls from lua_script. +class ContextGame { + public: + // Optional override for reading contents of a file. + using Reader = bool (*)(const char* file_name, char** buff, size_t* size); + + ContextGame(const char* executable_runfiles, + const DeepmindCalls* deepmind_calls, Reader file_reader_override, + std::string temp_folder) + : deepmind_calls_(deepmind_calls), + map_finished_(false), + player_view_{}, + executable_runfiles_(executable_runfiles), + file_reader_override_(file_reader_override), + temp_folder_(std::move(temp_folder)) {} + + ~ContextGame(); + + // Returns an event module. A pointer to ContextGame must exist in the up + // value. [0, 1, -] + static lua::NResultsOr Module(lua_State* L); + + int Init(); + void NextMap() { player_view_.timestamp_msec = 0; } + + // Returns whether we should finish the current map. + bool MapFinished() const { return map_finished_; } + + // Sets whether the current map should finish. + void SetMapFinished(bool map_finished) { map_finished_ = map_finished; } + + // The path level scripts should use for temporary files. + const std::string& TempFolder() const { return temp_folder_; } + + const std::string& ExecutableRunfiles() const { return executable_runfiles_; } + + const DeepmindCalls* Calls() const { return deepmind_calls_; } + + void AddTextureHandle(std::string name, int handle); + + // Retrieves the handle for a named texture marked for update. + // Returns whether a matching handle was found. + bool TextureHandle(const std::string& name, int* handle) const; + + // Set latest player state. + void SetPlayerState(const float pos[3], const float vel[3], + const float angles[3], float height, + int team_score, int other_team_score, + int player_id, int timestamp_msec); + + // Get latest predicted player view. (This is where the game renders from.) + const PlayerView& GetPlayerView() { + return player_view_; + } + + Reader FileReaderOverride() { return file_reader_override_; } + + private: + // Calls into the engine. + const DeepmindCalls* deepmind_calls_; + + // Flag that can be set from the game to finish the current map. + bool map_finished_; + + PlayerView player_view_; + + // Path to executables runfiles. + std::string executable_runfiles_; + + // Optional override for reading contents of a file. + Reader file_reader_override_; + + // The path level scripts should use for temporary files. + std::string temp_folder_; + bool temp_folder_owned_; +}; + +} // namespace lab +} // namespace deepmind + +#endif // DML_DEEPMIND_ENGINE_CONTEXT_GAME_H_ diff --git a/deepmind/engine/context_observations.cc b/deepmind/engine/context_observations.cc new file mode 100644 index 00000000..2452fe2d --- /dev/null +++ b/deepmind/engine/context_observations.cc @@ -0,0 +1,158 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "deepmind/engine/context_observations.h" + +#include +#include +#include +#include +#include + +#include "deepmind/support/logging.h" +#include "deepmind/lua/call.h" +#include "deepmind/lua/push.h" +#include "deepmind/lua/read.h" +#include "deepmind/tensor/lua_tensor.h" +#include "deepmind/tensor/tensor_view.h" + +namespace deepmind { +namespace lab { + +int ContextObservations::ReadSpec(lua::TableRef script_table_ref) { + script_table_ref_ = std::move(script_table_ref); + script_table_ref_.PushMemberFunction("customObservationSpec"); + lua_State* L = script_table_ref_.LuaState(); + if (lua_isnil(L, -2)) { + lua_pop(L, 2); + return 0; + } + auto result = lua::Call(L, 1); + if (!result.ok()) { + std::cerr << result.error() << '\n'; + return 1; + } + lua::TableRef observations; + lua::Read(L, -1, &observations); + auto spec_count = observations.ArraySize(); + infos_.clear(); + infos_.reserve(spec_count); + for (std::size_t i = 0, c = spec_count; i != c; ++i) { + lua::TableRef info; + observations.LookUp(i + 1, &info); + SpecInfo spec_info; + if (!info.LookUp("name", &spec_info.name)) { + std::cerr << "[customObservationSpec] - Missing 'name = '.\n"; + return 1; + } + std::string type = "Doubles"; + info.LookUp("type", &type); + if (type.compare("Bytes") == 0) { + spec_info.type = EnvCApi_ObservationBytes; + } else if (type.compare("Doubles") == 0) { + spec_info.type = EnvCApi_ObservationDoubles; + } else if (type.compare("String") == 0) { + spec_info.type = EnvCApi_ObservationString; + } else { + std::cerr << "[customObservationSpec] - Missing 'type = " + "'Bytes'|'Doubles'|'String''.\n"; + return 1; + } + if (!info.LookUp("shape", &spec_info.shape)) { + std::cerr + << "[customObservationSpec] - Missing 'shape = {, ...}'.\n"; + return 1; + } + infos_.push_back(std::move(spec_info)); + } + lua_pop(L, result.n_results()); + return 0; +} + +void ContextObservations::Observation(int idx, + EnvCApi_Observation* observation) { + lua_State* L = script_table_ref_.LuaState(); + script_table_ref_.PushMemberFunction("customObservation"); + // Function must exist. + CHECK(!lua_isnil(L, -2)) + << "Observations Spec set but no observation member function"; + const auto& info = infos_[idx]; + lua::Push(L, info.name); + auto result = lua::Call(L, 2); + CHECK(result.ok()) << "[customObservation] - " << result.error(); + + const tensor::Layout* layout = nullptr; + observation->spec.type = info.type; + switch (info.type) { + case EnvCApi_ObservationDoubles: { + const char error_message[] = + "[customObservation] - Must return a contiguous DoubleTensor"; + CHECK_EQ(1, result.n_results()) << error_message; + auto* double_tensor = tensor::LuaTensor::ReadObject(L, -1); + CHECK(double_tensor != nullptr) << error_message; + const auto& view = double_tensor->tensor_view(); + CHECK(view.IsContiguous()) << error_message; + layout = &view; + observation->payload.doubles = view.storage() + view.start_offset(); + break; + } + case EnvCApi_ObservationBytes: { + const char error_message[] = + "[customObservation] - Must return a contiguous ByteTensor"; + CHECK_EQ(1, result.n_results()) << error_message; + auto* byte_tensor = tensor::LuaTensor::ReadObject(L, -1); + CHECK(byte_tensor != nullptr) << error_message; + const auto& view = byte_tensor->tensor_view(); + layout = &view; + CHECK(view.IsContiguous()) << error_message; + observation->payload.bytes = view.storage() + view.start_offset(); + break; + } + case EnvCApi_ObservationString: { + const char error_message[] = "[customObservation] - Must return a string"; + CHECK_EQ(1, result.n_results()) << error_message; + CHECK(lua::Read(L, -1, &string_)) << error_message; + observation->payload.string = string_.data(); + tensor_shape_.assign(1, string_.length()); + observation->spec.dims = tensor_shape_.size(); + observation->spec.shape = tensor_shape_.data(); + break; + } + } + + if (layout != nullptr) { + tensor_shape_.resize(layout->shape().size()); + std::copy(layout->shape().begin(), layout->shape().end(), + tensor_shape_.begin()); + observation->spec.dims = tensor_shape_.size(); + observation->spec.shape = tensor_shape_.data(); + // Prevent observation->payload from being destroyed during pop. + lua::Read(L, -1, &tensor_); + } + lua_pop(L, result.n_results()); +} + +void ContextObservations::Spec(int idx, EnvCApi_ObservationSpec* spec) const { + const auto& info = infos_[idx]; + spec->type = info.type; + spec->dims = info.shape.size(); + spec->shape = info.shape.data(); +} + +} // namespace lab +} // namespace deepmind diff --git a/deepmind/engine/context_observations.h b/deepmind/engine/context_observations.h new file mode 100644 index 00000000..e7fc34fc --- /dev/null +++ b/deepmind/engine/context_observations.h @@ -0,0 +1,83 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef DML_DEEPMIND_ENGINE_CONTEXT_OBSERVATIONS_H_ +#define DML_DEEPMIND_ENGINE_CONTEXT_OBSERVATIONS_H_ + +#include +#include +#include + +#include "deepmind/lua/lua.h" +#include "deepmind/lua/n_results_or.h" +#include "deepmind/lua/table_ref.h" +#include "third_party/rl_api/env_c_api.h" + +namespace deepmind { +namespace lab { + +class ContextObservations { + public: + // Reads the custom observation spec from the table passed in. + // Keeps a reference to the table for further calls. Returns 0 on success + // and non-zero on failure. + int ReadSpec(lua::TableRef script_table_ref); + + // Script observation count. + int Count() const { return infos_.size(); } + + // Script observation name. `idx` shall be in [0, Count()). + const char* Name(int idx) const { + return infos_[idx].name.c_str(); + } + + // Script observation spec. `idx` shall be in [0, Count()). + void Spec(int idx, EnvCApi_ObservationSpec* spec) const; + + // Script observation. `idx` shall be in [0, Count()). + void Observation(int idx, EnvCApi_Observation* observation); + + private: + // Entry for a custom observation spec. + struct SpecInfo { + std::string name; + EnvCApi_ObservationType type; + std::vector shape; + }; + + lua::TableRef script_table_ref_; + + // Storage of supplementary observation types from script. + std::vector infos_; + + // Used to hold the EnvCApi_ObservationSpec::shape values until the next call + // of observation. + std::vector tensor_shape_; + + // Used to hold a reference to the observation tensor until the next call of + // observation. + lua::TableRef tensor_; + + // Used to store the observation string until the next call of observation. + std::string string_; +}; + +} // namespace lab +} // namespace deepmind + +#endif // DML_DEEPMIND_ENGINE_CONTEXT_OBSERVATIONS_H_ diff --git a/deepmind/engine/context_pickups.cc b/deepmind/engine/context_pickups.cc new file mode 100644 index 00000000..73937a22 --- /dev/null +++ b/deepmind/engine/context_pickups.cc @@ -0,0 +1,265 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "deepmind/engine/context_pickups.h" + +#include +#include +#include + +#include "deepmind/support/logging.h" +#include "deepmind/lua/call.h" +#include "deepmind/lua/push.h" +#include "deepmind/lua/read.h" + +namespace deepmind { +namespace lab { +namespace { + +constexpr int kMaxSpawnVars = 64; +constexpr int kMaxSpawnVarChars = 4096; + +// If the string "arg" fits into the array pointed to by "dest" (including the +// null terminator), copies the string into the array and returns true; +// otherwise returns false. +bool StringCopy(const std::string& arg, char* dest, std::size_t max_size) { + auto len = arg.length() + 1; + if (len <= max_size) { + std::copy_n(arg.c_str(), len, dest); + return true; + } else { + return false; + } +} + +void ReadSpawnVars(const ContextPickups::EntityInstance& spawn_vars, + char* spawn_var_chars, int* num_spawn_var_chars, + int spawn_var_offsets[][2], int* num_spawn_vars) { + *num_spawn_var_chars = 0; + *num_spawn_vars = spawn_vars.size(); + CHECK_NE(0, *num_spawn_vars) << "Must have spawn vars or return nil. (Make " + "sure all values are strings.)"; + CHECK_LT(*num_spawn_vars, kMaxSpawnVars) << "Too many spawn vars!"; + char* mem = spawn_var_chars; + auto it = spawn_vars.begin(); + for (int i = 0; i < *num_spawn_vars; ++i) { + const auto& key = it->first; + const auto& value = it->second; + ++it; + std::size_t kl = key.length() + 1; + std::size_t vl = value.length() + 1; + *num_spawn_var_chars += kl + vl; + CHECK_LT(*num_spawn_var_chars, kMaxSpawnVarChars) << "Too large spawn vars"; + std::copy(key.c_str(), key.c_str() + kl, mem); + spawn_var_offsets[i][0] = std::distance(spawn_var_chars, mem); + mem += kl; + std::copy(value.c_str(), value.c_str() + vl, mem); + spawn_var_offsets[i][1] = std::distance(spawn_var_chars, mem); + mem += vl; + } +} + +} // namespace + +bool ContextPickups::UpdateSpawnVars(char* spawn_var_chars, + int* num_spawn_var_chars, + int spawn_var_offsets[][2], + int* num_spawn_vars) { + lua_State* L = script_table_ref_.LuaState(); + script_table_ref_.PushMemberFunction("updateSpawnVars"); + if (lua_isnil(L, -2)) { + lua_pop(L, 2); + return true; + } + + auto table = lua::TableRef::Create(L); + for (int i = 0; i < *num_spawn_vars; ++i) { + table.Insert(std::string(spawn_var_chars + spawn_var_offsets[i][0]), + std::string(spawn_var_chars + spawn_var_offsets[i][1])); + } + lua::Push(L, table); + auto result = lua::Call(L, 2); + CHECK(result.ok()) << result.error(); + + // Nil return so spawn is ignored. + if (lua_isnil(L, -1)) { + lua_pop(L, result.n_results()); + return false; + } + + EntityInstance entity; + lua::Read(L, -1, &entity); + lua_pop(L, result.n_results()); + ReadSpawnVars(entity, spawn_var_chars, num_spawn_var_chars, + spawn_var_offsets, num_spawn_vars); + return true; +} + +// Clears extra_spawn_vars_ and reads new entities from Lua. +// Returns number of entities created. +int ContextPickups::MakeExtraEntities() { + lua_State* L = script_table_ref_.LuaState(); + script_table_ref_.PushMemberFunction("extraEntities"); + if (lua_isnil(L, -2)) { + lua_pop(L, 2); + return 0; + } + auto result = lua::Call(L, 1); + CHECK(result.ok()) << result.error(); + // Nil return so no spawns returned. + if (lua_isnil(L, -1)) { + lua_pop(L, result.n_results()); + return 0; + } + extra_entities_.clear(); + CHECK(lua::Read(L, -1, &extra_entities_)) + << "[extraEntities] - Invalid return value"; + lua_pop(L, result.n_results()); + return extra_entities_.size(); +} + +// Read specific spawn var from extra_spawn_vars_. Shall be called after +// with spawn_var_index in range [0, MakeExtraSpawnVars()). +void ContextPickups::ReadExtraEntity( // + int entity_index, // + char* spawn_var_chars, // + int* num_spawn_var_chars, // + int spawn_var_offsets[][2], // + int* num_spawn_vars) { + CHECK(0 <= entity_index && entity_index < extra_entities_.size()); + ReadSpawnVars(extra_entities_[entity_index], spawn_var_chars, + num_spawn_var_chars, spawn_var_offsets, num_spawn_vars); +} + +bool ContextPickups::FindItem(const char* class_name, int* index) { + lua_State* L = script_table_ref_.LuaState(); + script_table_ref_.PushMemberFunction("createPickup"); + + // Check function exists. + if (lua_isnil(L, -2)) { + lua_pop(L, 2); + return false; + } + + lua::Push(L, class_name); + + auto result = lua::Call(L, 2); + CHECK(result.ok()) << result.error(); + + // If no description is returned or the description is nil, don't create item. + if (result.n_results() == 0 || lua_isnil(L, -1)) { + lua_pop(L, result.n_results()); + return false; + } + + lua::TableRef table; + CHECK(Read(L, -1, &table)) << "Failed to read pickup table!"; + + PickupItem item = {}; + CHECK(table.LookUp("name", &item.name)); + CHECK(table.LookUp("classname", &item.class_name)); + CHECK(table.LookUp("model", &item.model_name)); + CHECK(table.LookUp("quantity", &item.quantity)); + CHECK(table.LookUp("type", &item.type)); + + // Optional tag field. + table.LookUp("tag", &item.tag); + + items_.push_back(item); + *index = ItemCount() - 1; + + lua_pop(L, result.n_results()); + return true; +} + +bool ContextPickups::GetItem(int index, char* item_name, int max_item_name, // + char* class_name, int max_class_name, // + char* model_name, int max_model_name, // + int* quantity, int* type, int* tag) const { + CHECK_GE(index, 0) << "Index out of range!"; + CHECK_LT(index, ItemCount()) << "Index out of range!"; + + const auto& item = items_[index]; + CHECK(StringCopy(item.name, item_name, max_item_name)); + CHECK(StringCopy(item.class_name, class_name, max_class_name)); + CHECK(StringCopy(item.model_name, model_name, max_model_name)); + *quantity = item.quantity; + *type = static_cast(item.type); + *tag = item.tag; + return true; +} + +bool ContextPickups::CanPickup(int entity_id) { + lua_State* L = script_table_ref_.LuaState(); + script_table_ref_.PushMemberFunction("canPickup"); + + // Check function exists. + if (lua_isnil(L, -2)) { + lua_pop(L, 2); + return true; + } + + lua::Push(L, entity_id); + + auto result = lua::Call(L, 2); + CHECK(result.ok()) << result.error(); + + // If nothing returned or the return is nil, the default. + if (result.n_results() == 0 || lua_isnil(L, -1)) { + lua_pop(L, result.n_results()); + return true; + } + + bool can_pickup = true; + CHECK(lua::Read(L, -1, &can_pickup)) + << "Failed to read canPickup return value"; + + lua_pop(L, result.n_results()); + return can_pickup; +} + +bool ContextPickups::OverridePickup(int entity_id, int* respawn) { + lua_State* L = script_table_ref_.LuaState(); + script_table_ref_.PushMemberFunction("pickup"); + + // Check function exists. + if (lua_isnil(L, -2)) { + lua_pop(L, 2); + return false; + } + + lua::Push(L, entity_id); + + auto result = lua::Call(L, 2); + CHECK(result.ok()) << result.error(); + + // If nothing returned or the return is nil, we're not overriding the + // pickup behaviour. + if (result.n_results() == 0 || lua_isnil(L, -1)) { + lua_pop(L, result.n_results()); + return false; + } + + CHECK(lua::Read(L, -1, respawn)) << "Failed to read the respawn time"; + + lua_pop(L, result.n_results()); + return true; +} + +} // namespace lab +} // namespace deepmind diff --git a/deepmind/engine/context_pickups.h b/deepmind/engine/context_pickups.h new file mode 100644 index 00000000..555eff8c --- /dev/null +++ b/deepmind/engine/context_pickups.h @@ -0,0 +1,138 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef DML_DEEPMIND_ENGINE_CONTEXT_PICKUPS_H_ +#define DML_DEEPMIND_ENGINE_CONTEXT_PICKUPS_H_ + +#include +#include +#include + +#include "deepmind/lua/table_ref.h" + +namespace deepmind { +namespace lab { + +class ContextPickups { + public: + // Parameters for a custom pickup item. + using EntityInstance = std::unordered_map; + + void SetScriptTableRef(lua::TableRef script_table_ref) { + script_table_ref_ = std::move(script_table_ref); + } + + // Allows Lua to replace contents of this c-style dictionary. + // 'spawn_var_chars' is pointing at the memory holding the strings. + // '*num_spawn_var_chars' is the total length of all strings and nulls. + // 'spawn_var_offsets' is the key, value offsets in 'spawn_var_chars'. + // '*num_spawn_vars' is the number of valid spawn_var_offsets. + // + // So the dictionary { key0=value0, key1=value1, key2=value2 } would be + // represented as: + // spawn_var_chars[4096] = "key0\0value0\0key1\0value1\0key2\0value2"; + // *num_spawn_var_chars = 36 + // spawn_var_offsets[64][2] = {{0,5}, {12,17}, {24,29}}; + // *num_spawn_vars = 3 + // The update will not increase *num_spawn_var_chars to greater than 4096. + // and not increase *num_spawn_vars to greater than 64. + bool UpdateSpawnVars( // + char* spawn_var_chars, // + int* num_spawn_var_chars, // + int spawn_var_offsets[][2], // + int* num_spawn_vars); + + // Clears extra_spawn_vars_ and reads new spawn vars from lua. + // Returns number of spawn vars created. + int MakeExtraEntities(); + + // Read specific spawn var from extra_spawn_vars_. Shall be called after + // with entity_index in range [0, MakeExtraEntities()). + void ReadExtraEntity( // + int entity_index, // + char* spawn_var_chars, // + int* num_spawn_var_chars, // + int spawn_var_offsets[][2], // + int* num_spawn_vars); + + // Finds a pickup item by class_name, and registers this item with the + // Context's item array. This is so all the VMs can update their respective + // item lists by iterating through that array during update. + // Returns whether the item was found, and if so, writes the index at which + // the item now resides to *index. + bool FindItem(const char* class_name, int* index); + + // Get the current number of registered items. + int ItemCount() const { return items_.size(); } + + // Get an item at a particular index and fill in the various buffers + // provided. + // Returns whether the operation succeeded. + bool GetItem( // + int index, // + char* item_name, // + int max_item_name, // + char* class_name, // + int max_class_name, // + char* model_name, // + int max_model_name, // + int* quantity, // + int* type, // + int* tag) const; + + // Clear the current list of registered items. Called just before loading a + // new map. + void ClearItems() { items_.clear(); } + + // Returns whether we can pickup the specified entity id. By default this + // returns true. + bool CanPickup(int entity_id); + + // Customization point for overriding the entity's pickup behaviour. Also + // allows for modifying the default respawn time for the entity. + // Returns true if the pickup behaviour has been overridden by the user, + // otherwise calls the default pickup behaviour based on the item type. + bool OverridePickup(int entity_id, int* respawn); + + private: + // Parameters for a custom pickup item. + struct PickupItem { + std::string name; // Name that will show when picking up item. + std::string class_name; // Class name to spawn entity as. Must be unique. + std::string model_name; // Model for pickup item. + int quantity; // Amount to award on pickup. + int type; // Type of pickup. E.g. health, ammo, frags etc. + // Must match itemType_t in bg_public.h + int tag; // Tag used in conjunction with type. E.g. + // determine which weapon to award, or if a goal + // should bob and rotate. + }; + + lua::TableRef script_table_ref_; + + // Array of current custom pickup items. Reset each episode. + std::vector items_; + + // Array of extra spawn vars for this level. + std::vector extra_entities_; +}; + +} // namespace lab +} // namespace deepmind + +#endif // DML_DEEPMIND_ENGINE_CONTEXT_PICKUPS_H_ diff --git a/deepmind/engine/lua_image.cc b/deepmind/engine/lua_image.cc new file mode 100644 index 00000000..22547cb9 --- /dev/null +++ b/deepmind/engine/lua_image.cc @@ -0,0 +1,481 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "deepmind/engine/lua_image.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "absl/strings/str_cat.h" +#include "deepmind/lua/bind.h" +#include "deepmind/lua/push.h" +#include "deepmind/lua/read.h" +#include "deepmind/lua/table_ref.h" +#include "deepmind/tensor/lua_tensor.h" +#include "deepmind/util/files.h" +#include "png.h" + +namespace deepmind { +namespace lab { +namespace { + +std::vector PngParsePixels(png_structp png_ptr, + png_infop info_ptr, + const tensor::ShapeVector& shape) { + std::vector bytes; + bytes.reserve(shape[0] * shape[1] * shape[2]); + const png_uint_32 bytesPerRow = png_get_rowbytes(png_ptr, info_ptr); + std::unique_ptr row_data(new unsigned char[bytesPerRow]); + auto bytes_inserter = std::back_inserter(bytes); + for (std::size_t rowIdx = 0; rowIdx < shape[0]; ++rowIdx) { + png_read_row(png_ptr, row_data.get(), nullptr); + std::copy_n(row_data.get(), shape[1] * shape[2], bytes_inserter); + } + return bytes; +} + +struct Reader { + std::string contents; + std::size_t location; +}; + +extern "C" { +static void PngReadContents(png_structp png_ptr, png_bytep out_bytes, + png_size_t count) { + Reader* reader = static_cast(png_get_io_ptr(png_ptr)); + if (count + reader->location <= reader->contents.size()) { + std::copy_n(reader->contents.begin() + reader->location, count, out_bytes); + } + reader->location += count; +} +} // extern "C" + +lua::NResultsOr LoadPng(lua_State* L, std::string contents) { + Reader reader{std::move(contents), 0}; + constexpr std::size_t png_header_size = 8; + if (reader.contents.size() < png_header_size) { + return "Invalid format. Contents too short."; + } + + if (!png_check_sig(reinterpret_cast(&reader.contents[0]), + png_header_size)) + return "Invalid format. Unrecognised signature."; + + reader.location += png_header_size; + + struct Png { + png_structp ptr; + png_infop info_ptr; + ~Png() { + png_destroy_read_struct(ptr ? &ptr : nullptr, + info_ptr ? &info_ptr : nullptr, nullptr); + } + }; + + Png png = {}; + + png.ptr = + png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png.ptr) return "Internal error."; + + png.info_ptr = png_create_info_struct(png.ptr); + if (!png.info_ptr) return "Internal error."; + + png_set_read_fn(png.ptr, &reader, &PngReadContents); + + png_set_sig_bytes(png.ptr, png_header_size); + png_read_info(png.ptr, png.info_ptr); + png_uint_32 width = 0; + png_uint_32 height = 0; + int bitDepth = 0; + int colorType = -1; + png_uint_32 retval = + png_get_IHDR(png.ptr, png.info_ptr, &width, &height, &bitDepth, + &colorType, nullptr, nullptr, nullptr); + + if (retval != 1) return "Invalid format. Corrupted header."; + if (bitDepth != 8) + return "Unsupported format. Image must have 8-bit channels."; + + tensor::ShapeVector shape = {height, width, 0}; + + switch (colorType) { + case PNG_COLOR_TYPE_GRAY: + shape[2] = 1; + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: + shape[2] = 2; + break; + case PNG_COLOR_TYPE_RGB: + shape[2] = 3; + break; + case PNG_COLOR_TYPE_RGB_ALPHA: + shape[2] = 4; + break; + default: + return "Unsupported format. Image must not be paletted."; + } + + auto bytes = PngParsePixels(png.ptr, png.info_ptr, shape); + if (reader.location > reader.contents.size()) + return "Invalid format. Contents too short."; + tensor::LuaTensor::CreateObject(L, std::move(shape), + std::move(bytes)); + return 1; +} + +lua::NResultsOr Load(lua_State* L) { + std::string file_name; + if (!lua::Read(L, 1, &file_name) || file_name.size() < 4) { + std::string error = absl::StrCat("[image.load] - \"", lua::ToString(L, 1), + "\" - Invalid name"); + return error; + } + + std::string contents; + if (!util::GetContents(file_name, &contents)) { + std::string error = + absl::StrCat("[image.load] - \"", file_name, "\" could not be read."); + return error; + } + + if (file_name.compare(file_name.length() - 4, 4, ".png") == 0) { + auto status = LoadPng(L, std::move(contents)); + if (!status.ok()) { + std::string error = absl::StrCat("[image.load] (PNG) - \"", file_name, + "\" - ", status.error()); + return error; + } + return status; + } + std::string error = absl::StrCat("[image.load] - \"", file_name, + "\" - Unsupported file type."); + return error; +} + +class LinearMagnifier { + public: + // Given an image with 'num_channels' channels, scales up the + // scanline pointed to by 'source' from 'source_len' elements to 'target_len', + // storing the resulting pixels in 'target'. Such scaling is performed using + // linear interpolation. + // Returns the iterator position past the last computed pixel. + template + Iter2 operator()( // + std::size_t num_channels, // + std::size_t source_len, // + Iter1 source, // + std::size_t target_len, // + Iter2 target) const { + assert(source_len > 1); + assert(source_len < target_len); + // Supersampling: linearly interpolate source pixels. + std::array low, top; + double stride = (target_len - 1) / static_cast(source_len - 1); + double top_idx_f = 0.0; + std::size_t top_idx_i = 0; + std::copy_n(source, num_channels, top.begin()); + for (std::size_t i = 1; i < source_len; ++i) { + std::size_t low_idx_i = top_idx_i; + std::swap(low, top); + top_idx_f += stride; + top_idx_i = top_idx_f + 0.5; + std::size_t k = i * num_channels; + std::copy_n(source + k, num_channels, top.begin()); + double rcp_stride = 1.0 / (top_idx_i - low_idx_i); + for (std::size_t j = low_idx_i; j < top_idx_i; ++j) { + double low_wgt = (top_idx_i - j) * rcp_stride; + double top_wgt = 1.0 - low_wgt; + for (std::size_t c = 0; c < num_channels; ++c) { + *target = low_wgt * low[c] + top_wgt * top[c]; + ++target; + } + } + } + return std::copy_n(top.begin(), num_channels, target); + } +}; + +class NearestMagnifier { + public: + // Given an image with 'num_channels' channels, scales up the scanline pointed + // to by 'source' from 'source_len' elements to 'target_len', storing the + // resulting pixels in 'target'. Such scaling is performed by sampling the + // nearest source pixel. + // Returns the iterator position past the last computed pixel. + template + Iter2 operator()( // + std::size_t num_channels, // + std::size_t source_len, // + Iter1 source, // + std::size_t target_len, // + Iter2 target) const { + assert(source_len > 1); + assert(source_len < target_len); + // Supersampling: sample nearest source pixel. + double stride = target_len / static_cast(source_len); + double top_idx_f = 0.0; + std::size_t top_idx_i = 0; + for (std::size_t i = 0; i < source_len; ++i) { + std::size_t low_idx_i = top_idx_i; + top_idx_f += stride; + top_idx_i = top_idx_f + 0.5; + std::size_t k = i * num_channels; + for (std::size_t j = low_idx_i; j < top_idx_i; ++j) { + target = std::copy_n(source + k, num_channels, target); + } + } + return target; + } +}; + +// Given an image with 'num_channels' channels, scales down the scanline +// pointed to by 'source' from 'source_len' elements to 'target_len', storing +// the resulting pixels in 'target'. Such pixels are computed as the average of +// all the 'source' pixels mapped onto a single 'target' pixel, accounting for +// 'source' pixels which are split between adjacent 'target' pixels. Returns the +// iterator position past the last computed pixel. +template +Iter2 averagingMinify( // + std::size_t num_channels, // + std::size_t source_len, // + Iter1 source, // + std::size_t target_len, // + Iter2 target) { + std::array acc; + double stride = source_len / static_cast(target_len); + double top_idx_f = 0.0; + std::size_t top_idx_i = 0; + for (std::size_t i = 0; i < target_len; ++i) { + double low_idx_f = top_idx_f; + std::size_t low_idx_i = top_idx_i; + top_idx_f += stride; + top_idx_i = static_cast(top_idx_f); + double low_wgt = 1.0 - (low_idx_f - low_idx_i); + std::size_t k = low_idx_i * num_channels; + for (std::size_t c = 0; c < num_channels; ++c) { + acc[c] = low_wgt * source[k + c]; + } + for (std::size_t j = low_idx_i + 1; j < top_idx_i; j++) { + std::size_t k = j * num_channels; + for (std::size_t c = 0; c < num_channels; ++c) { + acc[c] += source[k + c]; + } + } + if (top_idx_f > top_idx_i) { + double top_wgt = top_idx_f - top_idx_i; + std::size_t k = top_idx_i * num_channels; + for (std::size_t c = 0; c < num_channels; ++c) { + acc[c] += top_wgt * source[k + c]; + } + } + for (std::size_t c = 0; c < num_channels; ++c) { + *target = acc[c] / stride; + ++target; + } + } + return target; +} + +// Given an single-pixel scanline using 'num_channels' channels, repeats that +// pixel 'target_len' times and stores the result in 'target' +// Returns the iterator position past the last computed pixel. +template +Iter2 replicate( // + std::size_t num_channels, // + Iter1 source, // + std::size_t target_len, // + Iter2 target) { + for (std::size_t i = 0; i < target_len; ++i) { + target = std::copy_n(source, num_channels, target); + } + return target; +} + +// Given an image with 'num_channels' channels, scales the scanline pointed to +// by 'source' from 'source_len' elements to 'target_len', using functor +// 'Magnifier' for supersampling and averagingMinify for subsampling. +// The resulting pixels are stored in 'target'. +// Returns the iterator position past the last computed pixel. +template +Iter2 scaleRow( // + std::size_t num_channels, // + std::size_t source_len, // + Iter1 source, // + const Magnifier& magnifier, // + std::size_t target_len, // + Iter2 target) { + assert(num_channels <= 4); + if (source_len > 1) { + if (source_len < target_len) { + target = magnifier(num_channels, source_len, source, target_len, target); + } else if (target_len > 0) { + target = + averagingMinify(num_channels, source_len, source, target_len, target); + } + } else { + target = replicate(num_channels, source, target_len, target); + } + return target; +} + +// Scales all 'source_rows' rows of input image 'source' to the width given by +// 'target_cols', by repeatedly calling 'scaleRows' on each of them. +// Returns the iterator position past the last computed pixel. +template +Iter2 scaleRows( // + std::size_t num_channels, // + std::size_t source_rows, // + std::size_t source_cols, // + Iter1 source, // + const Magnifier& magnifier, // + std::size_t target_cols, // + Iter2 target) { + for (std::size_t r = 0; r < source_rows; ++r) { + target = scaleRow(num_channels, source_cols, source, magnifier, target_cols, + target); + source += source_cols * num_channels; + } + return target; +} + +// Variant of linearScaleRows which stores the results in a transposed layout. +// Returns the iterator position past the last computed pixel. +template +Iter2 scaleRowsTransposed( // + std::size_t num_channels, // + std::size_t source_rows, // + std::size_t source_cols, // + Iter1 source, // + const Magnifier& magnifier, // + std::size_t target_cols, // + Iter2 target) { + using T = typename std::iterator_traits::value_type; + // TODO: remove the explicit transpose and copy below by storing the results + // of scaleRows in transposed fashion, using a strided output iterator. + std::vector target_trp; + target_trp.reserve(source_rows * target_cols * num_channels); + scaleRows(num_channels, source_rows, source_cols, source, magnifier, + target_cols, std::back_inserter(target_trp)); + if (target_trp.empty()) { + return target; + } + tensor::TensorView target_trp_mat( + tensor::Layout({source_rows, target_cols, num_channels}), &target_trp[0]); + target_trp_mat.Transpose(0, 1); + target_trp_mat.ForEach([&target](T val) { *target++ = val; }); + return target; +} + +// Compute an scaled image by sucessively scaling rows and columns. +// Returns the iterator position past the last computed pixel. +template +Iter2 scaleImage( // + std::size_t num_channels, // + std::size_t source_rows, // + std::size_t source_cols, // + Iter1 source, // + const Magnifier& magnifier, // + std::size_t target_rows, // + std::size_t target_cols, // + Iter2 target) { + // Scale rows and transpose. + std::vector tmp(source_rows * target_cols * num_channels); + if (tmp.begin() == scaleRowsTransposed(num_channels, source_rows, source_cols, + source, magnifier, target_cols, + tmp.begin())) { + return target; + } + // Scale columns and transpose. + return scaleRowsTransposed(num_channels, target_cols, source_rows, + tmp.begin(), magnifier, target_rows, target); +} + +lua::NResultsOr Scale(lua_State* L) { + // Validate input parameters. + auto* source = tensor::LuaTensor::ReadObject(L, 1); + if (source == nullptr || !source->tensor_view().IsContiguous() || + source->tensor_view().shape().size() != 3 || + source->tensor_view().shape()[2] > 4) { + std::string error = absl::StrCat("[image.scale] - \"", lua::ToString(L, 1), + "\" - Invalid source image"); + return error; + } + std::size_t target_rows; + if (!lua::Read(L, 2, &target_rows)) { + std::string error = absl::StrCat("[image.scale] - \"", lua::ToString(L, 2), + "\" - Invalid number of output rows"); + return error; + } + std::size_t target_cols; + if (!lua::Read(L, 3, &target_cols)) { + std::string error = absl::StrCat("[image.scale] - \"", lua::ToString(L, 3), + "\" - Invalid number of output columns"); + return error; + } + std::string mode = "bilinear"; + lua::Read(L, 4, &mode); + std::size_t source_rows = source->tensor_view().shape()[0]; + std::size_t source_cols = source->tensor_view().shape()[1]; + std::size_t num_channels = source->tensor_view().shape()[2]; + + // Compute the scaled image. + std::vector res(target_cols * target_rows * num_channels); + if (mode == "bilinear") { + if (res.begin() == scaleImage(num_channels, source_rows, source_cols, + source->tensor_view().storage(), + LinearMagnifier(), target_rows, target_cols, + res.begin())) { + return 0; + } + } else if (mode == "nearest") { + if (res.begin() == scaleImage(num_channels, source_rows, source_cols, + source->tensor_view().storage(), + NearestMagnifier(), target_rows, target_cols, + res.begin())) { + return 0; + } + } else { + std::string error = absl::StrCat("[image.scale] - \"", lua::ToString(L, 4), + "\" - Unsupported scaling mode"); + } + + // Construct contiguous tensor and return it on the stack. + tensor::ShapeVector res_shape = {target_rows, target_cols, num_channels}; + tensor::LuaTensor::CreateObject(L, std::move(res_shape), + std::move(res)); + return 1; +} + +} // namespace + +int LuaImageRequire(lua_State* L) { + auto table = lua::TableRef::Create(L); + table.Insert("load", &lua::Bind); + table.Insert("scale", &lua::Bind); + lua::Push(L, table); + return 1; +} + +} // namespace lab +} // namespace deepmind diff --git a/deepmind/engine/lua_image.h b/deepmind/engine/lua_image.h new file mode 100644 index 00000000..a3ee68e7 --- /dev/null +++ b/deepmind/engine/lua_image.h @@ -0,0 +1,49 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// +// +// Expose a function 'load' that accepts a path to an image file and returns a +// tensor containing the pixel data. Currently supports a sub set of the PNG +// format. + +#ifndef DML_DEEPMIND_ENGINE_LUA_IMAGE_H_ +#define DML_DEEPMIND_ENGINE_LUA_IMAGE_H_ + +#include "deepmind/lua/lua.h" + +namespace deepmind { +namespace lab { + +// Returns a table of image related functions: +// * load(path): [-1, +1, e] +// Loads supported PNG format with the suffix '.png' and returns a byte tensor +// with a shape {H, W, ChannelCount}. ChannelCount depends on the type of the +// PNG being loaded. +// Supports only non-paletted images with 8 bits per channel. +// * scale(src, tgt_height, tgt_width): [-3, +1, e] +// Scales the image contained in byte tensor 'src' to shape {tgt_height, +// tgt_width, ChannelCount}, where ChannelCount is the number of channels used +// by 'src'. Returns the scaled image. +// Supports only contiguous tensors as input. + +// [0, +1, -] +int LuaImageRequire(lua_State* L); + +} // namespace lab +} // namespace deepmind + +#endif // DML_DEEPMIND_ENGINE_LUA_IMAGE_H_ diff --git a/deepmind/engine/lua_image_test.cc b/deepmind/engine/lua_image_test.cc new file mode 100644 index 00000000..49e92f85 --- /dev/null +++ b/deepmind/engine/lua_image_test.cc @@ -0,0 +1,375 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "deepmind/engine/lua_image.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "deepmind/lua/call.h" +#include "deepmind/lua/n_results_or_test_util.h" +#include "deepmind/lua/push_script.h" +#include "deepmind/lua/vm_test_util.h" +#include "deepmind/tensor/lua_tensor.h" + +namespace deepmind { +namespace lab { + +using ::deepmind::lab::lua::testing::IsOkAndHolds; + +class LuaImageTest : public ::testing::Test { + protected: + LuaImageTest() : lua_vm_(lua::CreateVm()) { + auto* L = lua_vm_.get(); + lua_vm_.AddCModuleToSearchers("dmlab.system.image", LuaImageRequire); + tensor::LuaTensorRegister(L); + lua_vm_.AddCModuleToSearchers("dmlab.system.tensor", + tensor::LuaTensorConstructors); + } + lua::Vm lua_vm_; +}; + +constexpr char kLuaImageLinearScaleXMagYMag[] = R"( +local image = require 'dmlab.system.image' +local tensor = require 'dmlab.system.tensor' + +local src = tensor.ByteTensor{ + {{255, 0, 0}, {0, 255, 0}}, + {{0, 0, 255}, {255, 255, 255}} +} +local tgt = image.scale(src, 4, 4) +assert(tgt == tensor.ByteTensor{ + {{255, 0, 0}, {170, 85, 0}, {85, 170, 0}, {0, 255, 0}}, + {{170, 0, 85}, {141, 85, 85}, {113, 170, 85}, {85, 255, 85}}, + {{85, 0, 170}, {113, 85, 170}, {141, 170, 170}, {170, 255, 170}}, + {{0, 0, 255}, {85, 85, 255}, {170, 170, 255}, {255, 255, 255}} +}) +)"; + +TEST_F(LuaImageTest, LinearScaleXMagYMag) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kLuaImageLinearScaleXMagYMag, + sizeof(kLuaImageLinearScaleXMagYMag) - 1, + "kLuaImageLinearScaleXMagYMag"), + IsOkAndHolds(1)); + EXPECT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); +} + +constexpr char kLuaImageLinearScaleXMaxYMin[] = R"( +local image = require 'dmlab.system.image' +local tensor = require 'dmlab.system.tensor' + +local src = tensor.ByteTensor{ + {{255, 0, 0}, {170, 85, 0}, {85, 170, 0}, {0, 255, 0}}, + {{170, 0, 85}, {141, 85, 85}, {113, 170, 85}, {85, 255, 85}}, + {{85, 0, 170}, {113, 85, 170}, {141, 170, 170}, {170, 255, 170}}, + {{0, 0, 255}, {85, 85, 255}, {170, 170, 255}, {255, 255, 255}} +} +local tgt = image.scale(src, 2, 6) +assert(tgt == tensor.ByteTensor{ + {{212, 0, 42}, {184, 42, 42}, {155, 85, 42}, {99, 170, 42}, {70, 212, 42}, {42, 255, 42}}, + {{42, 0, 212}, {70, 42, 212}, {99, 85, 212}, {155, 170, 212}, {184, 212, 212}, {212, 255, 212}} +}) +)"; + +TEST_F(LuaImageTest, LinearScaleXMaxYMin) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kLuaImageLinearScaleXMaxYMin, + sizeof(kLuaImageLinearScaleXMaxYMin) - 1, + "kLuaImageLinearScaleXMaxYMin"), + IsOkAndHolds(1)); + EXPECT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); +} + +constexpr char kLuaImageLinearScaleXMinYMax[] = R"( +local image = require 'dmlab.system.image' +local tensor = require 'dmlab.system.tensor' + +local src = tensor.ByteTensor{ + {{255, 0, 0}, {170, 85, 0}, {85, 170, 0}, {0, 255, 0}}, + {{170, 0, 85}, {141, 85, 85}, {113, 170, 85}, {85, 255, 85}}, + {{85, 0, 170}, {113, 85, 170}, {141, 170, 170}, {170, 255, 170}}, + {{0, 0, 255}, {85, 85, 255}, {170, 170, 255}, {255, 255, 255}} +} +local tgt = image.scale(src, 6, 2) +assert(tgt == tensor.ByteTensor{ + {{212, 42, 0}, {42, 212, 0}}, + {{184, 42, 42}, {70, 212, 42}}, + {{155, 42, 85}, {99, 212, 85}}, + {{99, 42, 170}, {155, 212, 170}}, + {{70, 42, 212}, {184, 212, 212}}, + {{42, 42, 255}, {212, 212, 255}} +}) +)"; + +TEST_F(LuaImageTest, LinearScaleXMinYMax) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kLuaImageLinearScaleXMinYMax, + sizeof(kLuaImageLinearScaleXMinYMax) - 1, + "kLuaImageLinearScaleXMinYMax"), + IsOkAndHolds(1)); + EXPECT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); +} + +constexpr char kLuaImageLinearScaleXMinYMin[] = R"( +local image = require 'dmlab.system.image' +local tensor = require 'dmlab.system.tensor' + +local src = tensor.ByteTensor{ + {{255, 0, 0}, {170, 85, 0}, {85, 170, 0}, {0, 255, 0}}, + {{170, 0, 85}, {141, 85, 85}, {113, 170, 85}, {85, 255, 85}}, + {{85, 0, 170}, {113, 85, 170}, {141, 170, 170}, {170, 255, 170}}, + {{0, 0, 255}, {85, 85, 255}, {170, 170, 255}, {255, 255, 255}} +} +local tgt = image.scale(src, 2, 3) +assert(tgt == tensor.ByteTensor{ + {{198, 21, 42}, {127, 127, 42}, {56, 233, 42}}, + {{56, 21, 212}, {127, 127, 212}, {198, 233, 212}} +}) +)"; + +TEST_F(LuaImageTest, LinearScaleXMinYMin) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kLuaImageLinearScaleXMinYMin, + sizeof(kLuaImageLinearScaleXMinYMin) - 1, + "kLuaImageLinearScaleXMinYMin"), + IsOkAndHolds(1)); + EXPECT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); +} + +constexpr char kLuaImageLinearScaleLineXMaxYMax[] = R"( +local image = require 'dmlab.system.image' +local tensor = require 'dmlab.system.tensor' + +local src = tensor.ByteTensor{ + {{255, 0, 0}, {0, 255, 0}}, +} +local tgt = image.scale(src, 4, 3) +assert(tgt == tensor.ByteTensor{ + {{255, 0, 0}, {127, 127, 0}, {0, 255, 0}}, + {{255, 0, 0}, {127, 127, 0}, {0, 255, 0}}, + {{255, 0, 0}, {127, 127, 0}, {0, 255, 0}}, + {{255, 0, 0}, {127, 127, 0}, {0, 255, 0}} +}) +)"; + +TEST_F(LuaImageTest, LinearScaleLineXMaxYMax) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kLuaImageLinearScaleLineXMaxYMax, + sizeof(kLuaImageLinearScaleLineXMaxYMax) - 1, + "kLuaImageLinearScaleLineXMaxYMax"), + IsOkAndHolds(1)); + EXPECT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); +} + +constexpr char kLuaImageLinearScaleEmpty[] = R"( +local image = require 'dmlab.system.image' +local tensor = require 'dmlab.system.tensor' + +local src = tensor.ByteTensor(4, 4, 3):narrow(1, 1, 0) +local tgt = image.scale(src, 4, 3) +assert(tgt == nil) +)"; + +TEST_F(LuaImageTest, LinearScaleEmpty) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kLuaImageLinearScaleEmpty, + sizeof(kLuaImageLinearScaleEmpty) - 1, + "kLuaImageLinearScaleEmpty"), + IsOkAndHolds(1)); + EXPECT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); +} + +constexpr char kLuaImageLinearScaleZeroRows[] = R"( +local image = require 'dmlab.system.image' +local tensor = require 'dmlab.system.tensor' + +local src = tensor.ByteTensor{ + {{255, 0, 0}, {0, 255, 0}}, + {{0, 0, 255}, {255, 255, 255}} +} +local tgt = image.scale(src, 0, 3) +assert(tgt == nil) +)"; + +TEST_F(LuaImageTest, LinearScaleZeroRows) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kLuaImageLinearScaleZeroRows, + sizeof(kLuaImageLinearScaleZeroRows) - 1, + "kLuaImageLinearScaleZeroRows"), + IsOkAndHolds(1)); + EXPECT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); +} + +constexpr char kLuaImageLinearScaleZeroCols[] = R"( +local image = require 'dmlab.system.image' +local tensor = require 'dmlab.system.tensor' + +local src = tensor.ByteTensor{ + {{255, 0, 0}, {0, 255, 0}}, + {{0, 0, 255}, {255, 255, 255}} +} +local tgt = image.scale(src, 1, 0) +assert(tgt == nil) +)"; + +TEST_F(LuaImageTest, LinearScaleZeroCols) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kLuaImageLinearScaleZeroCols, + sizeof(kLuaImageLinearScaleZeroCols) - 1, + "kLuaImageLinearScaleZeroCols"), + IsOkAndHolds(1)); + EXPECT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); +} + +constexpr char kLuaImageNearestScaleXMagYMag[] = R"( +local image = require 'dmlab.system.image' +local tensor = require 'dmlab.system.tensor' + +local src = tensor.ByteTensor{ + {{255, 0, 0}, {0, 255, 0}}, + {{0, 0, 255}, {255, 255, 255}} +} +local tgt = image.scale(src, 4, 4, 'nearest') +assert(tgt == tensor.ByteTensor{ + {{255, 0, 0}, {255, 0, 0}, {0, 255, 0}, {0, 255, 0}}, + {{255, 0, 0}, {255, 0, 0}, {0, 255, 0}, {0, 255, 0}}, + {{0, 0, 255}, {0, 0, 255}, {255, 255, 255}, {255, 255, 255}}, + {{0, 0, 255}, {0, 0, 255}, {255, 255, 255}, {255, 255, 255}} +}) +)"; + +TEST_F(LuaImageTest, NearestScaleXMagYMag) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kLuaImageNearestScaleXMagYMag, + sizeof(kLuaImageNearestScaleXMagYMag) - 1, + "kLuaImageNearestScaleXMagYMag"), + IsOkAndHolds(1)); + EXPECT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); +} + +constexpr char kLuaImageNearestScaleXMaxYMin[] = R"( +local image = require 'dmlab.system.image' +local tensor = require 'dmlab.system.tensor' + +local src = tensor.ByteTensor{ + {{255, 0, 0}, {170, 85, 0}, {85, 170, 0}, {0, 255, 0}}, + {{170, 0, 85}, {141, 85, 85}, {113, 170, 85}, {85, 255, 85}}, + {{85, 0, 170}, {113, 85, 170}, {141, 170, 170}, {170, 255, 170}}, + {{0, 0, 255}, {85, 85, 255}, {170, 170, 255}, {255, 255, 255}} +} +local tgt = image.scale(src, 2, 6, 'nearest') +assert(tgt == tensor.ByteTensor{ + {{212, 0, 42}, {212, 0, 42}, {155, 85, 42}, {99, 170, 42}, {99, 170, 42}, {42, 255, 42}}, + {{42, 0, 212}, {42, 0, 212}, {99, 85, 212}, {155, 170, 212}, {155, 170, 212}, {212, 255, 212}} +}) +)"; + +TEST_F(LuaImageTest, NearestScaleXMaxYMin) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kLuaImageNearestScaleXMaxYMin, + sizeof(kLuaImageNearestScaleXMaxYMin) - 1, + "kLuaImageNearestScaleXMaxYMin"), + IsOkAndHolds(1)); + EXPECT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); +} + +constexpr char kLuaImageNearestScaleXMinYMax[] = R"( +local image = require 'dmlab.system.image' +local tensor = require 'dmlab.system.tensor' + +local src = tensor.ByteTensor{ + {{255, 0, 0}, {170, 85, 0}, {85, 170, 0}, {0, 255, 0}}, + {{170, 0, 85}, {141, 85, 85}, {113, 170, 85}, {85, 255, 85}}, + {{85, 0, 170}, {113, 85, 170}, {141, 170, 170}, {170, 255, 170}}, + {{0, 0, 255}, {85, 85, 255}, {170, 170, 255}, {255, 255, 255}} +} +local tgt = image.scale(src, 6, 2, 'nearest') +assert(tgt == tensor.ByteTensor{ + {{212, 42, 0}, {42, 212, 0}}, + {{212, 42, 0}, {42, 212, 0}}, + {{155, 42, 85}, {99, 212, 85}}, + {{99, 42, 170}, {155, 212, 170}}, + {{99, 42, 170}, {155, 212, 170}}, + {{42, 42, 255}, {212, 212, 255}} +}) +)"; + +TEST_F(LuaImageTest, NearestScaleXMinYMax) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kLuaImageNearestScaleXMinYMax, + sizeof(kLuaImageNearestScaleXMinYMax) - 1, + "kLuaImageNearestScaleXMinYMax"), + IsOkAndHolds(1)); + EXPECT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); +} + +constexpr char kLuaImageNearestScaleXMinYMin[] = R"( +local image = require 'dmlab.system.image' +local tensor = require 'dmlab.system.tensor' + +local src = tensor.ByteTensor{ + {{255, 0, 0}, {170, 85, 0}, {85, 170, 0}, {0, 255, 0}}, + {{170, 0, 85}, {141, 85, 85}, {113, 170, 85}, {85, 255, 85}}, + {{85, 0, 170}, {113, 85, 170}, {141, 170, 170}, {170, 255, 170}}, + {{0, 0, 255}, {85, 85, 255}, {170, 170, 255}, {255, 255, 255}} +} +local tgt = image.scale(src, 2, 3, 'nearest') +assert(tgt == tensor.ByteTensor{ + {{198, 21, 42}, {127, 127, 42}, {56, 233, 42}}, + {{56, 21, 212}, {127, 127, 212}, {198, 233, 212}} +}) +)"; + +TEST_F(LuaImageTest, NearestScaleXMinYMin) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kLuaImageNearestScaleXMinYMin, + sizeof(kLuaImageNearestScaleXMinYMin) - 1, + "kLuaImageNearestScaleXMinYMin"), + IsOkAndHolds(1)); + EXPECT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); +} + +constexpr char kLuaImageNearestScaleLineXMaxYMax[] = R"( +local image = require 'dmlab.system.image' +local tensor = require 'dmlab.system.tensor' + +local src = tensor.ByteTensor{ + {{255, 0, 0}, {0, 255, 0}}, +} +local tgt = image.scale(src, 4, 3, 'nearest') +assert(tgt == tensor.ByteTensor{ + {{255, 0, 0}, {255, 0, 0}, {0, 255, 0}}, + {{255, 0, 0}, {255, 0, 0}, {0, 255, 0}}, + {{255, 0, 0}, {255, 0, 0}, {0, 255, 0}}, + {{255, 0, 0}, {255, 0, 0}, {0, 255, 0}} +}) +)"; + +TEST_F(LuaImageTest, NearestScaleLineXMaxYMax) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kLuaImageNearestScaleLineXMaxYMax, + sizeof(kLuaImageNearestScaleLineXMaxYMax) - 1, + "kLuaImageNearestScaleLineXMaxYMax"), + IsOkAndHolds(1)); + EXPECT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); +} + +} // namespace lab +} // namespace deepmind diff --git a/deepmind/engine/lua_maze_generation.cc b/deepmind/engine/lua_maze_generation.cc index 171e388c..411074ec 100644 --- a/deepmind/engine/lua_maze_generation.cc +++ b/deepmind/engine/lua_maze_generation.cc @@ -22,6 +22,7 @@ #include #include +#include "deepmind/engine/lua_random.h" #include "deepmind/level_generation/text_level/char_grid.h" #include "deepmind/level_generation/text_maze_generation/algorithm.h" #include "deepmind/level_generation/text_maze_generation/flood_fill.h" @@ -33,6 +34,36 @@ namespace deepmind { namespace lab { +namespace { + +std::mt19937_64* GetRandomNumberGenerator(lua::TableRef* table, + std::mt19937_64* seeded_rng) { + std::mt19937_64* prng = nullptr; + lua_State* L = table->LuaState(); + table->LookUpToStack("random"); + if (!lua_isnil(L, -1)) { + LuaRandom* random = LuaRandom::ReadObject(L, -1); + if (random != nullptr) { + prng = random->GetPrbg(); + } + } + lua_pop(L, 1); + if (prng == nullptr) { + int seed = 0; + if (table->LookUp("seed", &seed)) { + seeded_rng->seed(seed); + prng = seeded_rng; + } + } + return prng; +} + +} // namespace + +// Default maximum number of attempts to lay out rooms in +// LuaMazeGeneration::CreateRandom. +constexpr int kDefaultRetryCount = 1000; +constexpr int kGridWidth = 100; class LuaRoom : public lua::Class { public: @@ -87,7 +118,9 @@ const char* LuaMazeGeneration::ClassName() { int LuaMazeGeneration::Require(lua_State* L) { auto table = lua::TableRef::Create(L); - table.Insert("MazeGeneration", &lua::Bind); + table.Insert("mazeGeneration", &lua::Bind); + table.Insert("randomMazeGeneration", + &lua::Bind); lua::Push(L, table); return 1; } @@ -98,7 +131,7 @@ lua::NResultsOr LuaMazeGeneration::Create(lua_State* L) { if (table.Contains("entity")) { std::string entity_layer; if (!table.LookUp("entity", &entity_layer) || entity_layer.empty()) { - return "[MazeGeneration] - Must construct with non empty entity_layer"; + return "[mazeGeneration] - Must construct with non empty entity_layer"; } std::string variations_layer; if (table.LookUp("variations", &variations_layer) && @@ -114,16 +147,154 @@ lua::NResultsOr LuaMazeGeneration::Create(lua_State* L) { int height = 0; int width = 0; if (!table.LookUp("height", &height) || height <= 0) { - return "[MazeGeneration] - Must construct with positive width and height"; + return "[mazeGeneration] - Must construct with positive width and height"; } if (!table.LookUp("width", &width) || width <= 0) { - return "[MazeGeneration] - Must construct with positive width and height"; + return "[mazeGeneration] - Must construct with positive width and height"; } CreateObject(L, maze_generation::Size{height, width}); } return 1; } +lua::NResultsOr LuaMazeGeneration::CreateRandom(lua_State* L) { + lua::TableRef table; + lua::Read(L, -1, &table); + + std::mt19937_64 seeded_rng; + std::mt19937_64* prng = GetRandomNumberGenerator(&table, &seeded_rng); + if (prng == nullptr) { + return "[randomMazeGeneration] - Must construct with 'random' a random " + "number generator. ('seed' is deprecated.)"; + } + + int height = 0; + int width = 0; + if (!table.LookUp("height", &height) || height <= 0 || height % 2 == 0) { + return "[randomMazeGeneration] - Must construct with positive odd height"; + } + if (!table.LookUp("width", &width) || width <= 0 || width % 2 == 0) { + return "[randomMazeGeneration] - Must construct with positive odd width"; + } + int max_rooms = 0; + if (!table.LookUp("maxRooms", &max_rooms) || max_rooms < 0) { + return "[randomMazeGeneration] - Must construct with non-negative " + "maxRooms"; + } + int max_variations = 26; + table.LookUp("maxVariations", &max_variations); + if (max_variations <= 0 || max_variations > 26) { + return "[randomMazeGeneration] - Must construct with maxVariations in " + "[1,26]"; + } + int room_min_size = 3; + table.LookUp("roomMinSize", &room_min_size); + if (room_min_size <= 0 || room_min_size % 2 == 0) { + return "[randomMazeGeneration] - Must construct with positive odd " + "roomMinSize"; + } + int room_max_size = 7; + table.LookUp("roomMaxSize", &room_max_size); + if (room_max_size <= 0 || room_max_size % 2 == 0) { + return "[randomMazeGeneration] - Must construct with positive odd " + "roomMaxSize"; + } + int retry_count = kDefaultRetryCount; + table.LookUp("retryCount", &retry_count); + if (retry_count <= 0) { + return "[randomMazeGeneration] - Must construct with positive retryCount"; + } + double extra_connection_probability = 0.05; + table.LookUp("extraConnectionProbability", &extra_connection_probability); + bool simplify = true; + table.LookUp("simplify", &simplify); + int room_spawn_count = 0; + std::string spawn = "P"; + table.LookUp("roomSpawnCount", &room_spawn_count); + table.LookUp("spawn", &spawn); + if (spawn.length() != 1) { + return "[randomMazeGeneration] - Must construct with single character as " + "spawn entity"; + } + int room_object_count = 0; + std::string object = "G"; + table.LookUp("roomObjectCount", &room_object_count); + table.LookUp("object", &object); + if (object.length() != 1) { + return "[randomMazeGeneration] - Must construct with single character as " + "object entity"; + } + bool has_doors = false; + table.LookUp("hasDoors", &has_doors); + + maze_generation::TextMaze maze(maze_generation::Size{height, width}); + + // Create random rooms. + maze_generation::SeparateRectangleParams params{}; + params.min_size = maze_generation::Size{room_min_size, room_min_size}; + params.max_size = maze_generation::Size{room_max_size, room_max_size}; + params.retry_count = retry_count; + params.max_rects = max_rooms; + params.density = 1.0; + const auto rects = MakeSeparateRectangles(maze.Area(), params, prng); + const auto num_rooms = rects.size(); + for (unsigned int r = 0; r < num_rooms; ++r) { + maze.VisitMutableIntersection(maze_generation::TextMaze::kEntityLayer, + rects[r], + [&maze, r](int i, int j, char* cell) { + *cell = ' '; + maze.SetCellId({i, j}, r + 1); + }); + } + + // Fill the vacant space with corridors. + FillSpaceWithMaze(num_rooms + 1, 0, &maze, prng); + + // Connect adjacent regions at least once. + auto conns = + RandomConnectRegions(-1, extra_connection_probability, &maze, prng); + + // Add variations. + maze.VisitMutable( + maze_generation::TextMaze::kVariationsLayer, + [max_variations, &maze, num_rooms](int i, int j, char* cell) { + auto id = maze.GetCellId({i, j}); + if (id > 0 && id <= num_rooms) { + *cell = 'A' + (id - 1) % max_variations; + } + }); + + + // Simplify the maze if requested. + if (simplify) { + RemoveDeadEnds(' ', '*', {}, &maze); + RemoveAllHorseshoeBends('*', {}, &maze); + } + + // Add entities and spawn points. + AddNEntitiesToEachRoom(rects, room_spawn_count, spawn[0], ' ', &maze, prng); + AddNEntitiesToEachRoom(rects, room_object_count, object[0], ' ', &maze, prng); + + // Set each connection cell connection type. + for (const auto& conn : conns) { + char connection_type; + // Set to wall if connected to nowhere. + if (maze.GetCell(maze_generation::TextMaze::kEntityLayer, + conn.first + conn.second) == '*') { + connection_type = '*'; + } else if (has_doors) { + connection_type = (conn.second.d_col == 0) ? 'H' : 'I'; + } else { + connection_type = ' '; + } + maze.SetCell(maze_generation::TextMaze::kEntityLayer, conn.first, + connection_type); + } + + CreateObject(L, maze); + return 1; +} + lua::NResultsOr LuaMazeGeneration::EntityLayer(lua_State* L) { lua::Push(L, text_maze_.Text(maze_generation::TextMaze::kEntityLayer)); return 1; @@ -218,6 +389,41 @@ lua::NResultsOr LuaMazeGeneration::Size(lua_State* L) { return 2; } +lua::NResultsOr LuaMazeGeneration::ToWorldPos(lua_State* L) { + int row = 0; + int col = 0; + if (lua_gettop(L) != 3 || !lua::Read(L, 2, &row) || !lua::Read(L, 3, &col)) { + return "[toWorldPos] - Must provide row, col"; + } + lua::Push(L, (col - 1) * kGridWidth + kGridWidth / 2); + lua::Push( + L, (text_maze_.Area().size.height - row) * kGridWidth + kGridWidth / 2); + return 2; +} + +lua::NResultsOr LuaMazeGeneration::FromWorldPos(lua_State* L) { + double x = 0.0; + double y = 0.0; + if (lua_gettop(L) != 3 || !lua::Read(L, 2, &x) || !lua::Read(L, 3, &y)) { + return "[fromWorldPos] - Must provide x, y"; + } + + // y = (text_maze_.Area().size.height - row) * kGridWidth + kGridWidth/2 + // ==> row = text_maze_.Area().size.height - (y - kGridWidth/2) / kGridWidth + // = text_maze_.Area().size.height - y/kGridWidth - 1/2 + // = text_maze_.Area().size.height - floor(y/kGridWidth) + lua::Push(L, text_maze_.Area().size.height - + static_cast(std::floor(y / kGridWidth))); + + // x = (col - 1) * kGridWidth + kGridWidth/2 + // ==> col = (x - kGridWidth/2) / kGridWidth + 1 + // = x / kGridWidth - 1/2 + 1 + // = x / kGridWidth + 1/2 + // = floor(x / kGridWidth + 1) + lua::Push(L, static_cast(std::floor(x / kGridWidth + 1.0))); + return 2; +} + lua::NResultsOr LuaMazeGeneration::VisitFill(lua_State* L) { lua::TableRef table; if (lua_gettop(L) < 2 || !lua::Read(L, 2, &table)) { @@ -260,8 +466,58 @@ lua::NResultsOr LuaMazeGeneration::VisitFill(lua_State* L) { return first_error; } +lua::NResultsOr LuaMazeGeneration::VisitRandomPath(lua_State* L) { + lua::TableRef table; + if (lua_gettop(L) < 2 || !lua::Read(L, 2, &table)) { + return "[visitRandomPath] - must supply table"; + } + std::mt19937_64 seeded_rng; + std::mt19937_64* prng = GetRandomNumberGenerator(&table, &seeded_rng); + if (prng == nullptr) { + return "[visitRandomPath] - must supply 'random' with random number " + "generator. ('seed' is deprecated.)"; + } + std::array from; + if (!table.LookUp("from", &from)) { + return "[visitRandomPath] - must supply 'from' with an array of two " + "integers."; + } + std::array to; + if (!table.LookUp("to", &to)) { + return "[visitRandomPath] - must supply 'to' with an array of two " + "integers."; + } + std::vector wall_chars = {'*'}; + if (table.Contains("wall")) { + std::string wall_string; + if (!table.LookUp("wall", &wall_string)) { + return "[visitRandomPath] - must supply 'wall' with a string of wall " + "characters."; + } + wall_chars.assign(wall_string.begin(), wall_string.end()); + } + if (!table.Contains("func")) { + return "[visitRandomPath] - must supply callback 'func' with a string of " + "wall characters."; + } + + std::vector path = maze_generation::FindRandomPath( + {from[0] - 1, from[1] - 1}, {to[0] - 1, to[1] - 1}, wall_chars, + &text_maze_, prng); + for (const auto& pos : path) { + table.LookUpToStack("func"); + lua::Push(L, pos.row + 1); + lua::Push(L, pos.col + 1); + lua::NResultsOr error = lua::Call(L, 2); + lua_pop(L, error.n_results()); + if (!error.ok()) return error; + } + lua::Push(L, !path.empty()); + return 1; +} + // Registers classes metatable with Lua. -// [0 0 -] +// [0, 0, -] void LuaMazeGeneration::Register(lua_State* L) { Class::Reg regs[] = { {"entityLayer", Class::Member<&LuaMazeGeneration::EntityLayer>}, @@ -274,7 +530,10 @@ void LuaMazeGeneration::Register(lua_State* L) { Class::Member<&LuaMazeGeneration::SetVariationsCell>}, {"findRooms", Class::Member<&LuaMazeGeneration::FindRooms>}, {"size", Class::Member<&LuaMazeGeneration::Size>}, + {"toWorldPos", Class::Member<&LuaMazeGeneration::ToWorldPos>}, + {"fromWorldPos", Class::Member<&LuaMazeGeneration::FromWorldPos>}, {"visitFill", Class::Member<&LuaMazeGeneration::VisitFill>}, + {"visitRandomPath", Class::Member<&LuaMazeGeneration::VisitRandomPath>}, }; Class::Register(L, regs); LuaRoom::Register(L); diff --git a/deepmind/engine/lua_maze_generation.h b/deepmind/engine/lua_maze_generation.h index 2aff520b..bf1592e3 100644 --- a/deepmind/engine/lua_maze_generation.h +++ b/deepmind/engine/lua_maze_generation.h @@ -46,7 +46,7 @@ class LuaMazeGeneration : public lua::Class { static void Register(lua_State* L); // Returns table of constructors and standalone functions. - // [0 1 -] + // [0, 1, -] static int Require(lua_State* L); private: @@ -64,6 +64,32 @@ class LuaMazeGeneration : public lua::Class { // [1, 1, e] static lua::NResultsOr Create(lua_State* L); + // Constructs a LuaMazeGeneration with a random layout of rooms and corridors. + // Keyword Arguments: + // 'seed' (number, required) - Seed value for the random number generator. + // 'height' (number, required) - Height of maze. + // 'width' (number, required) - Width of maze. + // 'maxRooms' (number, default=0) - Maximum number of rooms. + // 'maxVariations' (number, default=26) - Maximum number of variations. + // 'roomMinSize' (number, default=3) - Minimum room width or height. + // 'roomMaxSize' (number, default=7) - Maximum room width or height. + // 'retryCount' (number, default=1000) - Maximum number of attempts to lay + // out rooms. + // 'simplify' (bool, default=true) - Remove dead ends and horseshoe bends. + // 'extraConnectionProbability' (number, default=0.05) - Probability of + // additional connections between adjacent rooms (1 is always added). + // 'roomSpawnCount' (number, default=0) - The number of spawn locations + // per room. + // 'spawn' (character, default='P') - The character that specifies spawn + // points. + // 'roomObjectCount' (number, default=0) - The number of objects per room. + // 'object' (character, default='G') - The character that specifies object + // positions. + // 'hasDoors' (bool, default=false) - Whether to add doors to corridors + // between rooms. + // [1, 1, e] + static lua::NResultsOr CreateRandom(lua_State* L); + // Returns the entity layer of the text_maze_ on the Lua stack. // [0, 1, -] lua::NResultsOr EntityLayer(lua_State* L); @@ -100,11 +126,25 @@ class LuaMazeGeneration : public lua::Class { // [0, 2, -] lua::NResultsOr Size(lua_State* L); + // Implements toWorldPos(row, column), returning X, Y. + // [2, 2, e] + lua::NResultsOr ToWorldPos(lua_State* L); + + // Implements fromWorldPos(x, y), returning row, column. + // [2, 2, e] + lua::NResultsOr FromWorldPos(lua_State* L); + // Implements visitFill(goal, f), where f will be called with the row, column, // distance for each cell attached to the goal. // [1, 0, e] lua::NResultsOr VisitFill(lua_State* L); + // Implements visitRandomPath(start, goal, f), where a random path will be + // computed from start to goal and f will be called for each cell traversed. + // Returns whether a path was found. + // [1, 1, e] + lua::NResultsOr VisitRandomPath(lua_State* L); + maze_generation::TextMaze text_maze_; }; diff --git a/deepmind/engine/lua_maze_generation_test.cc b/deepmind/engine/lua_maze_generation_test.cc index eb35c09e..500c21d9 100644 --- a/deepmind/engine/lua_maze_generation_test.cc +++ b/deepmind/engine/lua_maze_generation_test.cc @@ -18,10 +18,14 @@ #include "deepmind/engine/lua_maze_generation.h" +#include +#include #include #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "deepmind/engine/lua_random.h" +#include "deepmind/lua/bind.h" #include "deepmind/lua/call.h" #include "deepmind/lua/n_results_or_test_util.h" #include "deepmind/lua/push_script.h" @@ -40,12 +44,17 @@ class LuaMazeGenerationTest : public lua::testing::TestWithVm { LuaMazeGeneration::Register(L); vm()->AddCModuleToSearchers("dmlab.system.maze_generation", LuaMazeGeneration::Require); + LuaRandom::Register(L); + vm()->AddCModuleToSearchers("dmlab.system.sys_random", + &lua::Bind, {&prbg_}); } + + std::mt19937_64 prbg_; }; constexpr char kCreateMaze[] = R"( -local maze_gen = require 'dmlab.system.maze_generation' -local maze = maze_gen.MazeGeneration{width = 5, height = 3} +local maze_generation = require 'dmlab.system.maze_generation' +local maze = maze_generation.mazeGeneration{width = 5, height = 3} return maze:entityLayer(), maze:variationsLayer() )"; @@ -68,7 +77,7 @@ TEST_F(LuaMazeGenerationTest, ConstructDefaultMaze) { } constexpr char kCreateMazeFromString[] = R"( -local maze_gen = require 'dmlab.system.maze_generation' +local maze_generation = require 'dmlab.system.maze_generation' local entityLayer = [[ ******* * *G* @@ -85,7 +94,7 @@ local variationsLayer = [[ ....... ]] -local maze = maze_gen.MazeGeneration{ +local maze = maze_generation.mazeGeneration{ entity = entityLayer, variations = variationsLayer } @@ -117,8 +126,8 @@ TEST_F(LuaMazeGenerationTest, ConstructMazeFromString) { } constexpr char kReadWriteMaze[] = R"( -local maze_gen = require 'dmlab.system.maze_generation' -local maze = maze_gen.MazeGeneration{width = 5, height = 3} +local maze_generation = require 'dmlab.system.maze_generation' +local maze = maze_generation.mazeGeneration{width = 5, height = 3} assert('*' == maze:getEntityCell(3, 3)) assert('.' == maze:getVariationsCell(3, 3)) @@ -158,7 +167,7 @@ TEST_F(LuaMazeGenerationTest, ConstructReadWrite) { } constexpr char kFindRooms[] = R"( -local maze_gen = require 'dmlab.system.maze_generation' +local maze_generation = require 'dmlab.system.maze_generation' local entityLayer = [[ ********* * * * @@ -173,7 +182,7 @@ local entityLayer = [[ ********* ]] -local maze = maze_gen.MazeGeneration{ +local maze = maze_generation.mazeGeneration{ entity = entityLayer, variations = "", } @@ -209,7 +218,7 @@ TEST_F(LuaMazeGenerationTest, FindRooms) { } constexpr char kVisitFill[] = R"( -local maze_gen = require 'dmlab.system.maze_generation' +local maze_generation = require 'dmlab.system.maze_generation' local entityLayer = [[ ********* * * * @@ -219,7 +228,7 @@ local entityLayer = [[ * * * ********* ]] -local maze = maze_gen.MazeGeneration{entity = entityLayer} +local maze = maze_generation.mazeGeneration{entity = entityLayer} local height, width = maze:size() maze:visitFill{cell={5, 7}, wall="*", func=function(row, col, distance) -- Start must be zero @@ -240,6 +249,298 @@ TEST_F(LuaMazeGenerationTest, kVisitFill) { ASSERT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); } +constexpr char kCreateRandomMaze0[] = R"( +local sys_random = require 'dmlab.system.sys_random' +local maze_generation = require 'dmlab.system.maze_generation' +sys_random:seed(0) +local maze = maze_generation.randomMazeGeneration{ + random = sys_random, + width = 15, + height = 13, + maxRooms = 3, + extraConnectionProbability = 0.0, + simplify = false, +} + +return maze:entityLayer(), maze:variationsLayer() +)"; + +TEST_F(LuaMazeGenerationTest, CreateRandomMaze0) { + lua::PushScript(L, kCreateRandomMaze0, sizeof(kCreateRandomMaze0) - 1, + "kCreateRandomMaze0"); + ASSERT_THAT(lua::Call(L, 0), IsOkAndHolds(2)); + std::string ent_layer, var_layer; + ASSERT_TRUE(lua::Read(L, 1, &ent_layer)); + ASSERT_TRUE(lua::Read(L, 2, &var_layer)); + EXPECT_EQ( + "***************\n" + "* *\n" + "***** ***** * *\n" + "* * * *\n" + "***** * * *\n" + "* * * *\n" + "* * * *\n" + "* * *\n" + "* * *** * *\n" + "* * * *\n" + "******* * * *\n" + "* *\n" + "***************\n", + ent_layer); + EXPECT_EQ( + "...............\n" + "...............\n" + "...............\n" + ".......AAAAA...\n" + ".......AAAAA...\n" + ".BBBBB.AAAAA...\n" + ".BBBBB.AAAAA...\n" + ".BBBBB.AAAAA...\n" + ".BBBBB.........\n" + ".BBBBB.....CCC.\n" + "...........CCC.\n" + "...........CCC.\n" + "...............\n", + var_layer); +} + +constexpr char kCreateRandomMaze1[] = R"( +local sys_random = require 'dmlab.system.sys_random' +local maze_generation = require 'dmlab.system.maze_generation' +sys_random:seed(0) +local maze = maze_generation.randomMazeGeneration{ + random = sys_random, + width = 15, + height = 13, + maxRooms = 3, + extraConnectionProbability = 0.0, + object = 'A', + spawn = '2', + hasDoors = true, + roomSpawnCount = 1, + roomObjectCount = 1, +} + +return maze:entityLayer(), maze:variationsLayer() +)"; + +TEST_F(LuaMazeGenerationTest, CreateRandomMaze1) { + lua::PushScript(L, kCreateRandomMaze1, sizeof(kCreateRandomMaze1) - 1, + "kCreateRandomMaze1"); + ASSERT_THAT(lua::Call(L, 0), IsOkAndHolds(2)); + std::string ent_layer, var_layer; + ASSERT_TRUE(lua::Read(L, 1, &ent_layer)); + ASSERT_TRUE(lua::Read(L, 2, &var_layer)); + EXPECT_EQ( + "***************\n" + "***** *\n" + "***** *****H* *\n" + "***** * 2 * *\n" + "*****H* * *\n" + "* 2* A * *\n" + "* * * *\n" + "* A I * *\n" + "* *H***H*H*\n" + "* I *** *\n" + "******* *** *\n" + "******* I 2A*\n" + "***************\n", + ent_layer); + EXPECT_EQ( + "...............\n" + "...............\n" + "...............\n" + ".......AAAAA...\n" + ".......AAAAA...\n" + ".BBBBB.AAAAA...\n" + ".BBBBB.AAAAA...\n" + ".BBBBB.AAAAA...\n" + ".BBBBB.........\n" + ".BBBBB.....CCC.\n" + "...........CCC.\n" + "...........CCC.\n" + "...............\n", + var_layer); +} + +constexpr char kCreateRandomMaze2[] = R"( +local sys_random = require 'dmlab.system.sys_random' +local maze_generation = require 'dmlab.system.maze_generation' +sys_random:seed(123) +local maze = maze_generation.randomMazeGeneration{ + random = sys_random, + width = 11, + height = 7, + roomMinSize = 5, + maxRooms = 1, + extraConnectionProbability = 0.0, + roomSpawnCount = 0, + roomObjectCount = 0, +} + +return maze:entityLayer(), maze:variationsLayer() +)"; + +TEST_F(LuaMazeGenerationTest, CreateRandomMaze2) { + lua::PushScript(L, kCreateRandomMaze2, sizeof(kCreateRandomMaze2) - 1, + "kCreateRandomMaze2"); + ASSERT_THAT(lua::Call(L, 0), IsOkAndHolds(2)); + std::string ent_layer, var_layer; + ASSERT_TRUE(lua::Read(L, 1, &ent_layer)); + ASSERT_TRUE(lua::Read(L, 2, &var_layer)); + EXPECT_EQ( + "***********\n" + "*** *\n" + "*** *\n" + "*** *\n" + "*** *\n" + "*** *\n" + "***********\n", + ent_layer); + EXPECT_EQ( + "...........\n" + "...AAAAAAA.\n" + "...AAAAAAA.\n" + "...AAAAAAA.\n" + "...AAAAAAA.\n" + "...AAAAAAA.\n" + "...........\n", + var_layer); +} + +constexpr char kCreateRandomMazeEvenSize[] = R"( +local sys_random = require 'dmlab.system.sys_random' +local maze_generation = require 'dmlab.system.maze_generation' +sys_random:seed(0) +local maze = maze_generation.randomMazeGeneration{ + random = sys_random, + roomMinSize = 4 +} + +return maze:entityLayer(), maze:variationsLayer() +)"; + +TEST_F(LuaMazeGenerationTest, CreateRandomMazeEvenSize) { + lua::PushScript(L, kCreateRandomMazeEvenSize, + sizeof(kCreateRandomMazeEvenSize) - 1, + "kCreateRandomMazeEvenSize"); + ASSERT_FALSE(lua::Call(L, 0).ok()); +} + +constexpr char kCreateRandomMazeMaxVariations[] = R"( +local sys_random = require 'dmlab.system.sys_random' +local maze_generation = require 'dmlab.system.maze_generation' +sys_random:seed(0) +local maze = maze_generation.randomMazeGeneration{ + random = sys_random, + roomMinSize = 27 +} + +return maze:entityLayer(), maze:variationsLayer() +)"; + +TEST_F(LuaMazeGenerationTest, CreateRandomMazeMaxVariations) { + lua::PushScript(L, kCreateRandomMazeMaxVariations, + sizeof(kCreateRandomMazeMaxVariations) - 1, + "kCreateRandomMazeMaxVariations"); + ASSERT_FALSE(lua::Call(L, 0).ok()); +} + +constexpr char kCreateRandomMazeNoRandom[] = R"( +local maze_generation = require 'dmlab.system.maze_generation' +local maze = maze_generation.randomMazeGeneration{ + width = 15, + height = 13, + maxRooms = 3, + extraConnectionProbability = 0.0, + simplify = false, +} + +return maze:entityLayer(), maze:variationsLayer() +)"; + +TEST_F(LuaMazeGenerationTest, CreateRandomMazeNoRandom) { + lua::PushScript(L, kCreateRandomMazeNoRandom, + sizeof(kCreateRandomMazeNoRandom) - 1, + "kCreateRandomMazeNoRandom"); + ASSERT_FALSE(lua::Call(L, 0).ok()); +} + +constexpr char kVisitRandomPath[] = R"( +local sys_random = require 'dmlab.system.sys_random' +local maze_generation = require 'dmlab.system.maze_generation' +local seed = ... +local entityLayer = [[ +*************** +*P * +***** ***** * * +* * * * +***** * * * +* * * * +* * * * +* * * +* * *** * * +* *G* * +******* * * * +* * +*************** +]] + +local maze = maze_generation.mazeGeneration{ + entity = entityLayer +} + +local oi = 2 +local oj = 2 +local startVisited = false +local goalVisited = false + +assert(maze:getEntityCell(oi, oj) == 'P', 'Misplaced start') +assert(maze:getEntityCell(10, 10) == 'G', 'Misplaced goal') +sys_random:seed(seed) +maze:visitRandomPath{ + random = sys_random, + from = {2, 2}, + to = {10, 10}, + func = function(i, j) + local c = maze:getEntityCell(i, j) + if c == 'P' then + assert(not startVisited, 'Start already visited') + startVisited = true + elseif c == 'G' then + assert(not goalVisited, 'Goal already visited') + goalVisited = true + elseif c == '*' then + error('Hit wall!') + elseif c == ' ' then + assert(startVisited, 'Start not visited yet') + assert(not goalVisited, 'Goal already visited') + assert(oi ~= i or oj ~= j, 'Movement not continuous') + else + error('Unknown error!') + end + assert(oi == i or oj == j, 'Movement not continuous') + assert(oi + 1 == i or oi == i or oi == i + 1, 'Movement not continuous') + assert(oj + 1 == j or oj == j or oj == j + 1, 'Movement not continuous') + oi, oj = i, j + end +} +assert(startVisited, 'Start not visited') +assert(goalVisited, 'Goal not visited') +)"; + + +TEST_F(LuaMazeGenerationTest, kVisitRandomPath) { + lua::PushScript(L, kVisitRandomPath, std::strlen(kVisitRandomPath), + "kVisitRandomPath"); + for (int seed = 1; seed < 100; ++seed) { + lua_pushvalue(L, -1); + lua::Push(L, seed); + EXPECT_THAT(lua::Call(L, 1), IsOkAndHolds(0)) << " with seed " << seed; + } + lua_pop(L, 1); +} + } // namespace } // namespace lab diff --git a/deepmind/engine/lua_random.cc b/deepmind/engine/lua_random.cc index 47f14770..7cc269f5 100644 --- a/deepmind/engine/lua_random.cc +++ b/deepmind/engine/lua_random.cc @@ -23,6 +23,7 @@ #include #include #include +#include #include "deepmind/lua/push.h" #include "deepmind/lua/read.h" @@ -56,12 +57,24 @@ bool ReadLargeNumber(lua_State* L, int idx, RbgNumType* num) { } // namespace +lua::NResultsOr LuaRandom::Require(lua_State* L) { + if (auto* prbg = static_cast( + lua_touserdata(L, lua_upvalueindex(1)))) { + LuaRandom::CreateObject(L, prbg); + return 1; + } else { + return "Missing std::mt19937_64 pointer in up value!"; + } +} + const char* LuaRandom::ClassName() { return "deepmind.lab.RandomView"; } void LuaRandom::Register(lua_State* L) { const Class::Reg methods[] = { + {"discreteDistribution", &Class::Member<&LuaRandom::DiscreteDistribution>}, + {"normal", &Class::Member<&LuaRandom::Normal>}, {"seed", &Class::Member<&LuaRandom::Seed>}, {"uniformInt", &Class::Member<&LuaRandom::UniformInt>}, {"uniformReal", &Class::Member<&LuaRandom::UniformReal>}, @@ -112,5 +125,25 @@ lua::NResultsOr LuaRandom::UniformReal(lua_State* L) { return 1; } +lua::NResultsOr LuaRandom::Normal(lua_State* L) { + lua_Number mean, stddev; + if (!lua::Read(L, -2, &mean) || !lua::Read(L, -1, &stddev)) { + return "Invalid arguments - 2 numbers expected.."; + } + lua::Push(L, std::normal_distribution(mean, stddev)(*prbg_)); + return 1; +} + +lua::NResultsOr LuaRandom::DiscreteDistribution(lua_State* L) { + std::vector weights; + + if (!lua::Read(L, -1, &weights) || weights.empty()) { + return "Invalid arguments - non empty list of numeric weights expected."; + } + lua::Push(L, std::discrete_distribution( + weights.begin(), weights.end())(*prbg_) + 1); + return 1; +} + } // namespace lab } // namespace deepmind diff --git a/deepmind/engine/lua_random.h b/deepmind/engine/lua_random.h index 49beaff9..d5a64b37 100644 --- a/deepmind/engine/lua_random.h +++ b/deepmind/engine/lua_random.h @@ -78,8 +78,30 @@ class LuaRandom : public lua::Class { // is not true, or if "b - a" exceeds the largest representable number. // (This disallows either value being "not-a-number" or "infinity".) // + // * normal(mean, stddev): [-2, 0, -] + // + // Returns a real number sampled from a normal distribution centered around + // mean with standard deviation stddev. It is an error if the two + // arguments are not both numbers. + // + // + // * discreteDistribution(listOfWeights): [-1, 0, -] + // + // Returns an integer in the range [1, n] where the probability of each + // integer i is the value of the ith weight divided by the sum of all n + // weights. It is an error if the argument is not a list of 1 or more + // numbers. + // static void Register(lua_State* L); + // Returns a constructed LuaRandom object on the Lua stack. Lua's upvalue + // shall be a pointer to an std::mt19937_64 object that shall outlast the + // Lua-VM. + // + // LuaRandom must be registered first. + // [-0, +1, e] + static lua::NResultsOr Require(lua_State* L); + // Sets a new seed for the PRBG. Results in an error if the first argument of // the call is not a number that can be represented by the seed type, which is // an unsigned 64-bit integer. The argument may be given either as a number or @@ -94,6 +116,11 @@ class LuaRandom : public lua::Class { // [-2, 0, e] lua::NResultsOr UniformInt(lua_State* L); lua::NResultsOr UniformReal(lua_State* L); + lua::NResultsOr Normal(lua_State* L); + lua::NResultsOr DiscreteDistribution(lua_State* L); + + // Returns the PRBG instance used by this class. + std::mt19937_64* GetPrbg() { return prbg_; } private: std::mt19937_64* prbg_; diff --git a/deepmind/engine/lua_random_test.cc b/deepmind/engine/lua_random_test.cc index 4742b82b..bccc6e9f 100644 --- a/deepmind/engine/lua_random_test.cc +++ b/deepmind/engine/lua_random_test.cc @@ -18,11 +18,13 @@ #include "deepmind/engine/lua_random.h" +#include #include #include #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "deepmind/lua/bind.h" #include "deepmind/lua/call.h" #include "deepmind/lua/push_script.h" #include "deepmind/lua/n_results_or_test_util.h" @@ -37,6 +39,8 @@ class LuaRandomTest : public lua::testing::TestWithVm { protected: LuaRandomTest() { LuaRandom::Register(L); + vm()->AddCModuleToSearchers("dmlab.system.sys_random", + &lua::Bind, {&prbg_}); } std::mt19937_64 prbg_; @@ -51,13 +55,12 @@ TEST_F(LuaRandomTest, SeedWithNumber) { prbg_.seed(0); const char kSeed[] = R"( - r = ... - r:seed(999) + local sys_random = require 'dmlab.system.sys_random' + sys_random:seed(999) )"; ASSERT_THAT(lua::PushScript(L, kSeed, "kSeed"), IsOkAndHolds(1)); - LuaRandom::CreateObject(L, &prbg_); - ASSERT_THAT(lua::Call(L, 1), IsOkAndHolds(0)); + ASSERT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); actual_state << prbg_; EXPECT_EQ(expected_state.str(), actual_state.str()); @@ -72,13 +75,12 @@ TEST_F(LuaRandomTest, SeedWithString) { prbg_.seed(0); const char kSeed[] = R"( - r = ... - r:seed("888") + local sys_random = require 'dmlab.system.sys_random' + sys_random:seed("888") )"; ASSERT_THAT(lua::PushScript(L, kSeed, "kSeed"), IsOkAndHolds(1)); - LuaRandom::CreateObject(L, &prbg_); - ASSERT_THAT(lua::Call(L, 1), IsOkAndHolds(0)); + ASSERT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); actual_state << prbg_; EXPECT_EQ(expected_state.str(), actual_state.str()); diff --git a/deepmind/engine/lua_text_level_maker.cc b/deepmind/engine/lua_text_level_maker.cc index 21e97c8f..ae2078bd 100644 --- a/deepmind/engine/lua_text_level_maker.cc +++ b/deepmind/engine/lua_text_level_maker.cc @@ -21,17 +21,288 @@ #include #include +#include "absl/strings/str_cat.h" +#include "absl/strings/str_join.h" #include "deepmind/engine/lua_random.h" #include "deepmind/level_generation/compile_map.h" #include "deepmind/level_generation/text_level/lua_bindings.h" +#include "deepmind/lua/call.h" #include "deepmind/lua/push.h" #include "deepmind/lua/read.h" #include "deepmind/lua/table_ref.h" +#include "deepmind/util/files.h" namespace deepmind { namespace lab { namespace { +// Theme based on a table of functions returned from Lua. +// A texture is defined by a table of the form: +// { +// tex = 'map/poltergeist' -- Relative to assets directory. +// width = '1024' -- Source width of texture. +// height = '1024' -- Source height of texture. +// scale = '1.0' -- Amount to scale texture. +// angle = '0.0' -- Amount to rotate texture in degrees. +// } +// Returns a table of textures for the variation. +// It shall return a complete table for variation 'default'. +// If no specific texture is specifed for a variation the 'default' is used. +// function theme:mazeVariation(variation) +// return { +// floor = textureFloor, -- Floor texture. +// ceiling = textureCeiling, -- Ceiling texture when there is no sky box. +// wallN = textureWallN, -- North wall texture. (Top of text map.) +// wallE = textureWallE, -- East wall texture. (Right of text map.) +// wallS = textureWallS, -- South wall texture. (Bottom of text map.) +// wallW = textureWallW, -- West wall texture. (Right of text map.) +// riser = textureRiser, -- Wall texture surounding platforms. +// tread = textureTread, -- Texture between riser and floor texture. +// } +// end + +// 'args' is an array of tables of the form: +// { +// index = 1, -- Index of table in args. +// i = 1, -- Cell row. +// j = 1, -- Cell col, +// variation = 'default' -- Character of underlying variation or 'default'. +// direction -- One of N, E, S, W wall side. +// } +// +// function theme:placeWallDecals(args) +// +// return { +// { +// location = 1 -- Index location in args. +// texture = tex -- Texture as defined above. +// }, ... +// } +// end +// A model is defined by a table of the form: +// { +// mod = '' -- Name of model (md3) relative to assets directory. +// scale = 1.0 -- Amount to scale the model. +// angle = 0.0 -- Amount to rotate the model in degrees. +// } +// +// 'args' is an array of tables of the form: +// { +// index = 1, -- Index of table in args. +// i = 1, -- Cell row. +// j = 1, -- Cell col, +// variation = 'default' -- Character of underlying variation or 'default'. +// } +// +// function theme:placeFloorModels(args) +// return { +// { +// index = 1 -- Index of location in args. +// model = model -- Model as defined above. +// }, ... +// } +// end +class LuaTheme : public Theme { + public: + LuaTheme(lua_State* state, lua::TableRef theme) + : lua_state_(state), theme_(std::move(theme)) {} + + Texture floor(int variation) override { + return ReadThemeTexture("floor", variation); + } + + Texture wall(int variation, Direction direction) override { + switch (direction) { + case Direction::North: + return ReadThemeTexture("wallN", variation); + case Direction::East: + return ReadThemeTexture("wallE", variation); + case Direction::South: + return ReadThemeTexture("wallS", variation); + case Direction::West: + return ReadThemeTexture("wallW", variation); + } + } + + Texture ceiling(int variation) override { + return ReadThemeTexture("ceiling", variation); + } + + Texture platform_riser() override { return ReadThemeTexture("riser", 0); } + + Texture platform_tread() override { return ReadThemeTexture("tread", 0); } + + std::vector WallDecorations( + const std::vector& wall_locations) override { + theme_.PushMemberFunction("placeWallDecals"); + if (lua_isnil(lua_state_, -2)) { + lua_pop(lua_state_, 2); + return {}; + } + + lua::TableRef wall_locs = lua::TableRef::Create(lua_state_); + for (std::size_t i = 0, e = wall_locations.size(); i < e; ++i) { + const auto& wall_loc = wall_locations[i]; + lua::TableRef wall_loc_table = lua::TableRef::Create(lua_state_); + wall_loc_table.Insert("index", i + 1); + wall_loc_table.Insert("i", wall_loc.cell.x()); + wall_loc_table.Insert("j", wall_loc.cell.y()); + wall_loc_table.Insert("variation", wall_loc.variation); + switch (wall_loc.direction) { + case Direction::North: + wall_loc_table.Insert("direction", "N"); + break; + case Direction::East: + wall_loc_table.Insert("direction", "E"); + break; + case Direction::South: + wall_loc_table.Insert("direction", "S"); + break; + case Direction::West: + wall_loc_table.Insert("direction", "W"); + break; + } + wall_locs.Insert(i + 1, wall_loc_table); + } + lua::Push(lua_state_, wall_locs); + auto result = lua::Call(lua_state_, 2); + CHECK(result.ok()) << "[Theme.placeWallDecals] - " << result.error(); + CHECK(result.n_results() == 0 || result.n_results() == 1) + << "[Theme.placeWallDecals] - Must return a table of locations"; + std::vector decs; + if (result.n_results() == 1) { + lua::TableRef wall_decs; + lua::Read(lua_state_, -1, &wall_decs); + decs.reserve(wall_decs.ArraySize()); + for (std::size_t i = 0, e = wall_decs.ArraySize(); i < e; ++i) { + std::size_t index = 1; + lua::TableRef wall_dec; + CHECK(wall_decs.LookUp(i + 1, &wall_dec)) + << "[Theme.placeWallDecals] - Must return array of tables."; + CHECK(wall_dec.LookUp("index", &index)) + << "[Theme.placeWallDecals] - Missing number 'index'!"; + CHECK(1 <= index && index <= wall_locations.size()) + << "[Theme.placeWallDecals] - 'index' is out of bounds!"; + lua::TableRef decal; + CHECK(wall_dec.LookUp("decal", &decal)) + << "[Theme.placeWallDecals] - Missing texture 'decal'!"; + decs.push_back({wall_locations[index - 1], ReadTexture(decal)}); + } + } + lua_pop(lua_state_, result.n_results()); + + return decs; + } + + std::vector FloorDecorations( + const std::vector& floor_locations) override { + std::vector decs; + theme_.PushMemberFunction("placeFloorModels"); + if (lua_isnil(lua_state_, -2)) { + lua_pop(lua_state_, 2); + return decs; + } + lua::TableRef floor_locs = lua::TableRef::Create(lua_state_); + for (std::size_t i = 0, e = floor_locations.size(); i < e; ++i) { + lua::TableRef floor_loc_table = lua::TableRef::Create(lua_state_); + floor_loc_table.Insert("i", floor_locations[i].cell.x()); + floor_loc_table.Insert("j", floor_locations[i].cell.y()); + floor_loc_table.Insert("variation", floor_locations[i].variation); + floor_locs.Insert(i + 1, floor_loc_table); + } + lua::Push(lua_state_, floor_locs); + auto result = lua::Call(lua_state_, 2); + CHECK(result.ok()) << "[Theme.placeFloorModels] - " << result.error(); + CHECK(result.n_results() == 0 || result.n_results() == 1) + << "[Theme.placeFloorModels] - Must return an array of tables"; + + if (result.n_results() == 1) { + lua::TableRef floor_decs; + lua::Read(lua_state_, -1, &floor_decs); + decs.reserve(floor_decs.ArraySize()); + for (std::size_t i = 0, e = floor_decs.ArraySize(); i < e; ++i) { + std::size_t index = 1; + lua::TableRef floor_dec; + CHECK(floor_decs.LookUp(i + 1, &floor_dec)) + << "[Theme.placeFloorModels] - Must return an array of tables."; + CHECK(floor_dec.LookUp("index", &index)) + << "[Theme.placeFloorModels] - 'index' is missing!"; + CHECK(1 <= index && index <= floor_locations.size()) + << "[Theme.placeFloorModels] - 'index' is out of bounds!"; + lua::TableRef model; + CHECK(floor_dec.LookUp("model", &model)) + << "[Theme.placeFloorModels] - 'model' table must be supplied."; + decs.push_back({floor_locations[index - 1], ReadModel(model)}); + } + } + lua_pop(lua_state_, result.n_results()); + return decs; + } + + private: + Texture ReadThemeTexture(const std::string& name, int variation) { + auto pair = theme_cache_.emplace(variation, lua::TableRef()); + if (pair.second) { + theme_.PushMemberFunction("mazeVariation"); + const char variation_char = variation; + if (variation == 0) { + lua::Push(lua_state_, "default"); + } else { + lua_pushlstring(lua_state_, &variation_char, 1); + } + auto result = lua::Call(lua_state_, 2); + CHECK(result.ok()) << "[mazeVariation] - " << result.error(); + bool table_exists = lua::Read(lua_state_, -1, &pair.first->second); + CHECK (table_exists || variation != 0) + << "[mazeVariation] - Must return a table for variation 'default'"; + lua_pop(lua_state_, result.n_results()); + if (!table_exists) { + return ReadThemeTexture(name, 0); + } + } + lua::TableRef texture; + if (pair.first->second.LookUp(name, &texture)) { + return ReadTexture(texture); + } else { + if (variation == 0) { + return {"map/poltergeist", 64, 64, 1.0}; + } else { + return ReadThemeTexture(name, 0); + } + } + } + + Texture ReadTexture(const lua::TableRef& texture) { + std::string name; + int sizeWidth = 1024; + int sizeHeight = 1024; + double scale = 1.0; + double angle = 0.0; + if (!texture.LookUp("tex", &name)) { + name = "map/poltergeist"; + } + texture.LookUp("width", &sizeWidth); + texture.LookUp("height", &sizeHeight); + texture.LookUp("scale", &scale); + texture.LookUp("angle", &angle); + return Texture{std::move(name), sizeWidth, sizeHeight, scale, angle}; + } + + Model ReadModel(const lua::TableRef& model) { + std::string name; + double scale = 1.0; + double angle = 0.0; + model.LookUp("mod", &name); + model.LookUp("scale", &scale); + model.LookUp("angle", &angle); + return Model{std::move(name), scale, angle}; + } + + std::unordered_map theme_cache_; + lua_State* lua_state_; + lua::TableRef theme_; +}; + // An entity callback that handles nothing. bool NoOp(std::size_t, std::size_t, char, const MapSnippetEmitter&, std::vector*) { @@ -40,9 +311,15 @@ bool NoOp(std::size_t, std::size_t, char, } // namespace -LuaTextLevelMaker::LuaTextLevelMaker(const std::string& self) - : prng_(0), - rundir_(self) {} +LuaTextLevelMaker::LuaTextLevelMaker( + const std::string& self, const std::string& output_folder, + bool use_local_level_cache, bool use_global_level_cache, + DeepMindLabLevelCacheParams level_cache_params) + : prng_(0), rundir_(self), output_folder_(output_folder) { + settings_.use_local_level_cache = use_local_level_cache; + settings_.use_global_level_cache = use_global_level_cache; + settings_.level_cache_params = level_cache_params; +} const char* LuaTextLevelMaker::ClassName() { return "deepmind.lab.TextLevelMaker"; @@ -57,42 +334,64 @@ void LuaTextLevelMaker::Register(lua_State* L) { } lua::NResultsOr LuaTextLevelMaker::MapFromTextLevel(lua_State* L) { + MapCompileSettings settings = settings_; lua::TableRef args; if (lua_gettop(L) != 2 || !lua::Read(L, 2, &args)) { return "Bad invocation, need exactly one (table) argument."; } - std::string ents, vars, odir, mapstr, map_name; + TextLevelSettings level_settings; + std::string ents, vars, mapstr, map_name, theme; if (!args.LookUp("entityLayer", &ents)) return "Bad or missing arg 'entityLayer'"; - if (!args.LookUp("outputDir", &odir)) - return "Bad or missing arg 'outputDir'"; args.LookUp("variationsLayer", &vars); // optional, no error if missing if (!args.LookUp("mapName", &map_name)) map_name = "luamap"; + // optional, no error if missing + args.LookUp("skyboxTextureName", &level_settings.skybox_texture_name); + args.LookUp("ceilingHeight", &level_settings.ceiling_height); + args.LookUp("drawDefaultLayout", &level_settings.draw_default_layout); + lua::TableRef custom_theme; + + if (args.LookUp("theme", &custom_theme)) { + level_settings.theme.reset(new LuaTheme(L, std::move(custom_theme))); + } + + std::string game_dir = output_folder_ + "/baselab"; + if (!util::MakeDirectory(game_dir)) { + return "Failed to create output directory: " + game_dir; + } - const std::string base = odir + "/" + map_name; + const std::string base = game_dir + "/" + map_name; args.LookUpToStack("callback"); if (lua_isnil(L, -1)) { - LOG(INFO) << "No custom entity handler provided, using defaults."; - mapstr = TranslateTextLevel(ents, vars, &prng_, NoOp); + mapstr = TranslateTextLevel(ents, vars, &prng_, NoOp, &level_settings); } else { - LOG(INFO) << "Using custom entity handler."; using namespace std::placeholders; auto user_cb = std::bind(LuaCustomEntityCallback, L, -1, _1, _2, _3, _4, _5); - mapstr = TranslateTextLevel(ents, vars, &prng_, user_cb); + mapstr = TranslateTextLevel(ents, vars, &prng_, user_cb, &level_settings); } if (!(std::ofstream(base + ".map") << mapstr)) { return "Failed to create output file."; } - if (!RunMapCompileFor(rundir_, base)) { - return "Failed to compile map."; + settings.generate_aas = false; + args.LookUp("allowBots", &settings.generate_aas); + + if (!RunMapCompileFor(rundir_, base, settings)) { + std::string error_message = "Failed to compile map.\n"; + error_message += "Map Name:\n"; + error_message += map_name; + error_message += "Entity Layer:\n"; + error_message += ents; + error_message += "Variations Layer:\n"; + error_message += vars; + return error_message; } lua::Push(L, base + ".pk3"); diff --git a/deepmind/engine/lua_text_level_maker.h b/deepmind/engine/lua_text_level_maker.h index 4474cf44..aaf3f0d1 100644 --- a/deepmind/engine/lua_text_level_maker.h +++ b/deepmind/engine/lua_text_level_maker.h @@ -25,9 +25,11 @@ #include #include +#include "deepmind/level_generation/compile_map.h" #include "deepmind/lua/class.h" #include "deepmind/lua/lua.h" #include "deepmind/lua/n_results_or.h" +#include "public/level_cache_types.h" namespace deepmind { namespace lab { @@ -38,7 +40,14 @@ class LuaTextLevelMaker : public lua::Class { public: // Constructed with path to where DeepMind Lab assets are stored. - explicit LuaTextLevelMaker(const std::string& self); + // And the `output_folder` contains the name of an existing, writable + // directory into which files can be stored. This directory will also contain + // the final .pk3 file. + explicit LuaTextLevelMaker(const std::string& self, + const std::string& output_folder, + bool use_local_level_cache, + bool use_global_level_cache, + DeepMindLabLevelCacheParams level_cache_params); // Registers MapFromTextLevel as "mapFromTextLevel". static void Register(lua_State* L); @@ -57,9 +66,7 @@ class LuaTextLevelMaker : public lua::Class { // * callback [optional] // // The fields entityLayer and variationsLayer contain the text map strings as - // described in the text level generation documentation. The outputDir field - // contains the name of an existing, writable directory into which temporary - // files can be stored. This directory will also contain the final .pk3 file. + // described in the text level generation documentation. // The name of the generated map file will either be the value of mapName, if // that field was provided, or otherwise 'luamap'. // @@ -78,7 +85,9 @@ class LuaTextLevelMaker : public lua::Class { private: std::mt19937_64 prng_; + MapCompileSettings settings_; const std::string rundir_; + const std::string output_folder_; }; } // namespace lab diff --git a/deepmind/include/BUILD b/deepmind/include/BUILD index f11664ac..fc6806cb 100644 --- a/deepmind/include/BUILD +++ b/deepmind/include/BUILD @@ -4,15 +4,17 @@ licenses(["restricted"]) # GPLv2 cc_library( - name = "context_headers", + name = "context_hdrs", hdrs = [ "deepmind_calls.h", "deepmind_context.h", "deepmind_hooks.h", + "deepmind_model_getters.h", + "deepmind_model_setters.h", ], - visibility = [ - "//:__pkg__", - "//deepmind/engine:__pkg__", + visibility = ["//visibility:public"], + deps = [ + "//:level_cache_types", + "//third_party/rl_api:env_c_api", ], - deps = ["//third_party/rl_api:env_c_api"], ) diff --git a/deepmind/include/deepmind_calls.h b/deepmind/include/deepmind_calls.h index 3e5da5b1..51b44a44 100644 --- a/deepmind/include/deepmind_calls.h +++ b/deepmind/include/deepmind_calls.h @@ -1,4 +1,4 @@ -// Copyright (C) 2016 Google Inc. +// Copyright (C) 2016-2017 Google Inc. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -22,6 +22,11 @@ #ifndef DML_DEEPMIND_INCLUDE_DEEPMIND_CALLS_H_ #define DML_DEEPMIND_INCLUDE_DEEPMIND_CALLS_H_ +#include + +#include "deepmind/include/deepmind_model_getters.h" +#include "deepmind/include/deepmind_model_setters.h" + #ifdef __cplusplus extern "C" { #endif @@ -39,10 +44,13 @@ struct DeepmindCalls_s { void (*screen_shape)(void* context, int* width, int* height); // Engine time between the start of two consecutive frames, in milliseconds. - int (*engine_frame_period_msec)(void* context); + int (*engine_frame_period_msec)(void); // Get time stamp for current episode. - int (*total_engine_time_msec)(void* context); + int (*total_engine_time_msec)(void); + + // Get time in seconds for current episode. + double (*total_time_seconds)(void); // Adds a bot to game. // 'name' - Name of bot. Must be one named in bots.txt. @@ -52,6 +60,55 @@ struct DeepmindCalls_s { // Get the player score. int (*player_score)(void* context); + + // Attempt to load a serialised model from the MD3 data in 'buffer', invoking + // the relevant callbacks in 'model_mutators' as the data is traversed. + // Returns whether valid model data was found in the buffer. + bool (*deserialise_model)( // + const void* buffer, // + const DeepmindModelSetters* model_mutators, // + void* model_data); + + // Attempt to load a model from a MD3 file at the given path, invoking the + // relevant callbacks in 'model_mutators' as the file is traversed. + // Returns whether a valid model file was found at the given path. + bool (*load_model)( // + const char* model_path, // + const DeepmindModelSetters* model_mutators, // + void* model_data); + + // Returns the buffer size required to serialise the model described by + // 'model_getters' in MD3 format. + size_t (*serialised_model_size)( // + const DeepmindModelGetters* model_getters, // + void* model_data); + + // Attempt to serialise the model described by 'model_getters' in MD3 format + // onto 'buffer'. + void (*serialise_model)( // + const DeepmindModelGetters* model_getters, // + void* model_data, // + void* buffer); + + // Attempt to save the model described by 'model_getters' as a MD3 file onto + // the given path. + // Returns whether the save operation succeeded. + bool (*save_model)( // + const DeepmindModelGetters* model_getters, // + void* model_data, // + const char* model_path); + + // Update the texture 'name' with the raw RGBA stream in 'data', using the + // specified 'width' and 'height'. + bool (*update_rgba_texture)( // + const char* name, // + int width, // + int height, // + const unsigned char* data); + + float (*raycast)(const float start[3], const float end[3]); + + bool (*is_map_loading)(); }; #ifdef __cplusplus diff --git a/deepmind/include/deepmind_context.h b/deepmind/include/deepmind_context.h index 2a43bb7c..8fc542d4 100644 --- a/deepmind/include/deepmind_context.h +++ b/deepmind/include/deepmind_context.h @@ -22,6 +22,8 @@ #ifndef DML_DEEPMIND_INCLUDE_DEEPMIND_CONTEXT_H_ #define DML_DEEPMIND_INCLUDE_DEEPMIND_CONTEXT_H_ +#include + #include "deepmind/include/deepmind_calls.h" #include "deepmind/include/deepmind_hooks.h" @@ -47,7 +49,16 @@ struct DeepmindContext_s { // 'runfiles_path' must be the path to where DeepMind Lab assets are stored. // 'ctx': pointer to the new context that is to be initialized. // 'ctx->calls': are methods provided by the engine to call into it. -int dmlab_create_context(const char* runfiles_path, DeepmindContext* ctx); +// 'file_reader_override': an optional function for reading from the file +// system. If set, a call returns whether the file 'file_name' was read +// successfully and if so 'buff' points to the content and 'size' contains the +// size of the file and after use 'buff' must be freed with 'free'. Otherwise +// returns false. +int dmlab_create_context(const char* runfiles_path, DeepmindContext* ctx, + bool (*file_reader_override)(const char* file_name, + char** buff, + size_t* size), + const char* temp_folder); // Release the resources associated with *ctx. The context shall have been // obtained by a successful call of the previous function. diff --git a/deepmind/include/deepmind_hooks.h b/deepmind/include/deepmind_hooks.h index f297727c..46396362 100644 --- a/deepmind/include/deepmind_hooks.h +++ b/deepmind/include/deepmind_hooks.h @@ -1,4 +1,4 @@ -// Copyright (C) 2016 Google Inc. +// Copyright (C) 2016-2017 Google Inc. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -24,6 +24,8 @@ #include +#include "deepmind/include/deepmind_model_getters.h" +#include "public/level_cache_types.h" #include "third_party/rl_api/env_c_api.h" #ifdef __cplusplus @@ -32,11 +34,47 @@ extern "C" { typedef struct DeepmindHooks_s DeepmindHooks; +typedef struct DeepmindEventsHooks_s DeepmindEventsHooks; + +typedef struct DeepmindEntitiesHooks_s DeepmindEntitiesHooks; + +struct DeepmindEventsHooks_s { + // Returns number of event types. + int (*type_count)(void* userdata); + + // Returns name of an event. 'type_idx' shall be on range [0, 'type_count'). + const char* (*type_name)(void* userdata, int type_idx); + + // Clears all events. + void (*clear)(void* userdata); + + // Returns the number of events generated since last call to 'clear'. + int (*count)(void* userdata); + + // Exports an event at 'idx', which must be in range [0, count()), to an + // EnvCApi_Event structure. + void (*export_event)(void* userdata, int idx, EnvCApi_Event* event); +}; + +struct DeepmindEntitiesHooks_s { + // Clears all entities at start of entity update. + void (*clear)(void* userdata); + + // Called with each active entity during entity update. + void (*add)(void* userdata, int entity_id, int user_id, int type, int flags, + float position[3], const char* classname); +}; + struct DeepmindHooks_s { // A script will be passed these settings via params when the script is ran. // Calls after first call of init will be ignored. void (*add_setting)(void* userdata, const char* key, const char* value); + // Sets which level caches to use when building levels. + void (*set_level_cache_settings)( + void* userdata, bool local, bool global, + DeepMindLabLevelCacheParams level_cache_params); + // Sets the name of the script that is ran during the first init. // Must be called before the first init. // Returns zero if successful and non-zero on error. @@ -46,6 +84,9 @@ struct DeepmindHooks_s { void (*set_executable_runfiles)(void* userdata, const char* executable_runfiles); + // Gets the path to the temporary folder where dynamic maps are generated. + const char* (*get_temporary_folder)(void* userdata); + // Called after all settings have been applied. // Returns zero if successful and non-zero on error. int (*init)(void* userdata); @@ -54,6 +95,10 @@ struct DeepmindHooks_s { // Returns zero if successful and non-zero on error. int (*start)(void* userdata, int episode, int random_seed); + // Called when the map has finished loading. + // Returns zero if successful and non-zero on error. + int (*map_loaded)(void* userdata); + // The Quake3 engine makes heavy use of the command line. This function allows // us to intercept and replace it. // Must be called after first init. @@ -64,16 +109,22 @@ struct DeepmindHooks_s { // Must return name of map discoverable by the engine. const char* (*next_map)(void* userdata); + // Called when game starts to establish game type. See gametype_t from + // bg_public.h + int (*game_type)(void* userdata); + // Runs a Lua snippet, using the Lua VM of the userdata. // If the script returns an integer, then this function // will return that value; otherwise it returns 0. int (*run_lua_snippet)(void* userdata, const char* command); - // This is set if internal controller is allowed to set actions. - void (*set_use_internal_controls)(void* userdata, bool v); + // This is set if we are running a native app and the internal controller is + // allowed to set actions. + void (*set_native_app)(void* userdata, bool v); - // Returns whether internal controller is allowed to set actions. - bool (*get_use_internal_controls)(void* userdata); + // Returns whether we are running a native app and the internal controller is + // allowed to set actions. + bool (*get_native_app)(void* userdata); // Sets the actions of the player. void (*set_actions)(void* userdata, // @@ -101,15 +152,29 @@ struct DeepmindHooks_s { int spawn_vars_offsets[][2], // int* num_spawn_vars); - // Find item with the given class_name. + // Hook to create a number of entities externally. Returns the number of + // entities created. + int (*make_extra_entities)(void* userdata); + + // Hook to read an entity at a particular 'entity_id'. 'entity_id' shall be in + // the range [0, dmlab_make_extra_entities()) and the remaining arguments + // match 'dmlab_update_spawn_vars'. + void (*read_extra_entity)(void* userdata, // + int entity_id, // + char* spawn_var_chars, // + int* num_spawn_var_chars, // + int spawn_var_offsets[][2], // + int* num_spawn_vars); // + + // Finds item with the given class_name. // Returns whether the item was found, and if so, writes the index of the // found item to *index. bool (*find_item)(void* userdata, const char* class_name, int* index); - // Get the current number of registered items. + // Gets the current number of registered items. int (*item_count)(void* userdata); - // Gets an item at a partiucular index, and fills in the various buffers. + // Gets an item at a particular index, and fills in the various buffers. // Returns whether the operation succeeded. bool (*item)(void* userdata, int index, char* item_name, int max_item_name, char* class_name, int max_class_name, @@ -120,6 +185,17 @@ struct DeepmindHooks_s { // initialization. void (*clear_items)(void* userdata); + // Retrieves model with the given model_name. + // Returns whether the model was found and loads it within the context. + bool (*find_model)(void* userdata, const char* model_name); + + // Return the accessor API for currently selected model. + void (*model_getters)(void* userdata, DeepmindModelGetters* model, + void** model_data); + + // Clears the currently selected model. This is called on each initialization. + void (*clear_model)(void* userdata); + // Returns whether we should finish the current map. bool (*map_finished)(void* userdata); @@ -133,8 +209,22 @@ struct DeepmindHooks_s { // behaviour. Optionally sets the default respawn time. bool (*override_pickup)(void* userdata, int entity_id, int* respawn); - // Get external reward added to the player since last call. - int (*external_reward)(void* userdata, int player_id); + // Returns if the specified entity has a trigger. + bool (*can_trigger)(void* userdata, int entity_id, const char* target_name); + + // Hook to determine if we're overriding the entity's default trigger + // behaviour. + bool (*override_trigger)(void* userdata, int entity_id, + const char* target_name); + + // Hook which is invoked on reply to a trigger lookat. + void (*trigger_lookat)(void* userdata, int entity_id, bool looked_at, + const float position[3]); + + // Get reward override for player. + int (*reward_override)(void* userdata, const char* reason_opt, int player_id, + int team, const int* other_player_id_opt, + const float* origin_opt, int score); // Add score to a player. (This is picked up by the server on the next // update.) @@ -148,10 +238,31 @@ struct DeepmindHooks_s { // Calls script when bots should be loaded. void (*add_bots)(void* userdata); - // Enables game to modify a texture after load. - void (*modify_rgba_texture)(void* userdata, const char* name, + // Returns whether to replace texture named 'name' with new_name. + // 'new_name' shall be a null terminated with length in [0, max_size). + bool (*replace_texture_name)(void* userdata, const char* name, char* new_name, + int max_size); + + // Returns whether the texture named 'name' has been loaded externally. + // If texture is loaded externally: 'pixels' will be a buffer sized *width * + // *height * 4 and allocated using 'allocator'. + // Otherwise 'pixels' 'width' and 'height' are left unchanged. + bool (*load_texture)(void* userdata, const char* name, unsigned char** pixels, + int* width, int* height, void* (*allocator)(int size)); + + // Enables game to modify a texture on load. + bool (*modify_rgba_texture)(void* userdata, const char* name, unsigned char* data, int width, int height); + // Returns whether to replace model named 'name' with 'new_name' and prefix + // the textures used with the model with 'texture_prefix'. The buffers pointed + // to by new_name and texture_prefix shall have space for at least + // new_name_size and texture_prefix_size, respectively, and on success they + // are filled with null-terminated strings. + bool (*replace_model_name)(void* userdata, const char* name, char* new_name, + int new_name_size, char* texture_prefix, + int texture_prefix_size); + // Level-specific observations /////////////// @@ -180,17 +291,66 @@ struct DeepmindHooks_s { void (*custom_observation)(void* userdata, int idx, EnvCApi_Observation* observation); - // Called from client game with current players predicted state. - void (*predicted_player_state)(void* userdata, const float origin[3], - const float velocity[3], - const float viewangles[3], float height, - int timestamp_msec); + // Called from client game with current players state. + void (*player_state)(void* userdata, const float origin[3], + const float velocity[3], const float viewangles[3], + float height, int team_score, int other_team_score, + int player_id, int timestamp_msec); + // See MakeScreenMessages in deepmind/engine/context.h. int (*make_screen_messages)(void* userdata, int width, int height, int line_height, int string_buffer_size); + // See GetScreenMessage in deepmind/engine/context.h. void (*get_screen_message)(void* userdata, int message_id, char* buffer, - int* x, int* y, int* align_l0_r1_c2); + int* x, int* y, int* shadow, int* align_l0_r1_c2, + float rgba[4]); + + // See MakeFilledRectangles in deepmind/engine/context.h. + int (*make_filled_rectangles)(void* userdata, int screen_width, + int screen_height); + + // See GetFilledRectangle in deepmind/engine/context.h. + void (*get_filled_rectangle)(void* userdata, int rectangle_id, int* x, int* y, + int* width, int* height, float rgba[4]); + + // See MakePk3FromMap in deepmind/engine/context.h. + void (*make_pk3_from_map)(void* userdata, const char* map_path, + const char* map_name, bool gen_aas); + + // See CustomPlayerMovement in deepmind/engine/context.h. + void (*lua_mover)(void* userdata, int entity_id, const float entity_pos[3], + const float player_pos[3], const float player_vel[3], + float player_pos_delta[3], float player_vel_delta[3]); + + // See GameEvent in deepmind/engine/context.h. + void (*game_event)(void* userdata, const char* event_name, int count, + const float* data); + + // See UpdateInventory in deepmind/engine/context.h. + void (*update_inventory)(void* userdata, bool is_spawning, int player_id, + int gadget_count, int gadget_inventory[], + int stat_count, int stats_inventory[], int powerups, + int powerups_time[], int gadget_held, float height, + float position[3], float view_angles[3]); + + // See TeamSelect in deepmind/engine/context.h. + char (*team_select)(void* userdata, int player_id, const char* player_name); + + // See SetHasAltCameras in deepmind/engine/context.h. + void (*set_has_alt_cameras)(void* userdata, bool has_alt_cameras); + + // See HasAltCameras in deepmind/engine/context.h. + bool (*has_alt_cameras)(void* userdata); + + // Set and retrieve error message. 'error_message' shall be a null terminated + // string. + void (*set_error_message)(void* userdata, const char* error_message); + const char* (*error_message)(void* userdata); + + DeepmindEventsHooks events; + + DeepmindEntitiesHooks entities; }; #ifdef __cplusplus diff --git a/deepmind/include/deepmind_model_getters.h b/deepmind/include/deepmind_model_getters.h new file mode 100644 index 00000000..ed02b927 --- /dev/null +++ b/deepmind/include/deepmind_model_getters.h @@ -0,0 +1,139 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// +// +// Functions to be used as callbacks within the engine for model saving. + +#ifndef DML_DEEPMIND_INCLUDE_DEEPMIND_MODEL_GETTERS_H_ +#define DML_DEEPMIND_INCLUDE_DEEPMIND_MODEL_GETTERS_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DeepmindModelGetters_s DeepmindModelGetters; + +struct DeepmindModelGetters_s { + // Copies this model's name onto 'name', up to 'max_length' characters. + void (*get_name)( // + const void* user_data, // + size_t max_length, // + char* name); + + // Returns the number of surfaces for the model. + size_t (*get_surface_count)( // + const void* user_data); + + // Copies the name for the surface with index 'surf_idx' onto 'name', up to + // 'max_length' characters. + void (*get_surface_name)( // + const void* user_data, // + size_t surf_idx, // + size_t max_length, // + char* name); + + // Returns the number of vertices for the surface with index 'surf_idx'. + size_t (*get_surface_vertex_count)( // + const void* user_data, // + size_t surf_idx); + + // Copies the position coordinates of the vertex with index 'vert_idx' within + // the surface with index 'surf_idx' onto 'location'. + void (*get_surface_vertex_location)( // + const void* user_data, // + size_t surf_idx, // + size_t vert_idx, // + float location[3]); + + // Copies the normal coordinates of the vertex with index 'vert_idx' within + // the surface with index 'surf_idx' onto 'normal'. + void (*get_surface_vertex_normal)( // + const void* user_data, // + size_t surf_idx, // + size_t vert_idx, // + float normal[3]); + + // Copies the texture coordinates of the vertex with index 'vert_idx' within + // the surface with index 'surf_idx' onto 'st'. + void (*get_surface_vertex_st)( // + const void* user_data, // + size_t surf_idx, // + size_t vert_idx, // + float st[2]); + + // Returns the number of faces for the surface with index 'surf_idx'. + size_t (*get_surface_face_count)( // + const void* user_data, // + size_t surf_idx); + + // Copies the triangular face with index 'face_idx' within the surface with + // index 'surf_idx' onto 'indices'. + void (*get_surface_face)( // + const void* user_data, // + size_t surf_idx, // + size_t face_idx, // + int indices[3]); + + // Returns the number of shaders for the surface with index 'surf_idx'. + size_t (*get_surface_shader_count)( // + const void* user_data, // + size_t surf_idx); + + // Copies the name of the shader with index 'shad_idx' within the surface + // with index 'surf_idx' onto 'name', up to 'max_length' characters. + void (*get_surface_shader)( // + const void* user_data, // + size_t surf_idx, // + size_t shad_idx, // + size_t max_length, // + char* name); + + // Returns the number of tags for the model. + size_t (*get_tag_count)( // + const void* user_data); + + // Copies the name for the tag with index 'tag_idx' onto 'name', up to + // 'max_length' characters. + void (*get_tag_name)( // + const void* user_data, // + size_t tag_idx, // + size_t max_length, // + char* name); + + // Copies the coordinates of the axis with index 'axis_idx' within the + // tag with index 'tag_idx' onto 'axis'. + void (*get_tag_axis)( // + const void* user_data, // + size_t tag_idx, // + size_t axis_idx, // + float axis[3]); + + // Copies the coordinates of the origin of the tag with index 'tag_idx' onto + // 'origin'. + void (*get_tag_origin)( // + const void* user_data, // + size_t tag_idx, // + float origin[3]); +}; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // DML_DEEPMIND_INCLUDE_DEEPMIND_MODEL_GETTERS_H_ diff --git a/deepmind/include/deepmind_model_setters.h b/deepmind/include/deepmind_model_setters.h new file mode 100644 index 00000000..50505c7c --- /dev/null +++ b/deepmind/include/deepmind_model_setters.h @@ -0,0 +1,143 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// +// +// Functions to be used as callbacks within the engine for model loading. + +#ifndef DML_DEEPMIND_INCLUDE_DEEPMIND_MODEL_SETTERS_H_ +#define DML_DEEPMIND_INCLUDE_DEEPMIND_MODEL_SETTERS_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DeepmindModelSetters_s DeepmindModelSetters; + +struct DeepmindModelSetters_s { + // Called with the name for this model. + void (*set_name)( // + void* user_data, // + const char* name); + + // Called with the number of surfaces for the model, prior to any call related + // to any of the them. + void (*set_surface_count)( // + void* user_data, // + size_t num_surfaces); + + // Called with the name for the surface with index 'surf_idx'. + void (*set_surface_name)( // + void* user_data, // + size_t surf_idx, // + const char* name); + + // Called with the number of vertices for the surface with index 'surf_idx', + // prior to any call related to vertex or face data. + void (*set_surface_vertex_count)( // + void* user_data, // + size_t surf_idx, // + size_t num_verts); + + // Called with the location coordinates of the vertex with index 'vert_idx' + // within the surface with index 'surf_idx'. + void (*set_surface_vertex_location)( // + void* user_data, // + size_t surf_idx, // + size_t vert_idx, // + float location[3]); + + // Called with the normal coordinates of the vertex with index 'vert_idx' + // within the surface with index 'surf_idx'. + void (*set_surface_vertex_normal)( // + void* user_data, // + size_t surf_idx, // + size_t vert_idx, // + float normal[3]); + + // Called with the texture coordinates of the vertex with index 'vert_idx' + // within the surface with index 'surf_idx'. + void (*set_surface_vertex_st)( // + void* user_data, // + size_t surf_idx, // + size_t vert_idx, // + float st[2]); + + // Called with the number of faces for the surface with index 'surf_idx', + // prior to any call related to face data. + void (*set_surface_face_count)( // + void* user_data, // + size_t surf_idx, // + size_t num_faces); + + // Called with the indices of the triangular face with index 'face_idx' within + // the surface with index 'surf_idx'. + void (*set_surface_face)( // + void* user_data, // + size_t surf_idx, // + size_t face_idx, // + int indices[3]); + + // Called with the number of shaders for the surface with index 'surf_idx', + // prior to any call related to shader data. + void (*set_surface_shader_count)( // + void* user_data, // + size_t surf_idx, // + size_t num_shaders); + + // Called with the name of the shader with index 'shad_idx' within the surface + // with index 'surf_idx'. + void (*set_surface_shader)( // + void* user_data, // + size_t surf_idx, // + size_t shad_idx, // + const char* name); + + // Called with the number of tags for the model, prior to any call related to + // any of the them. + void (*set_tag_count)( // + void* user_data, // + size_t num_tags); + + // Called with the name for the tag with index 'tag_idx', prior to any cal + // related to the tag data. + void (*set_tag_name)( // + void* user_data, // + size_t tag_idx, // + const char* name); + + // Called with the coordinates of the axis with index 'axis_idx' within the + // tag with index 'tag_idx'. + void (*set_tag_axis)( // + void* user_data, // + size_t tag_idx, // + size_t axis_idx, // + float axis[3]); + + // Called with the coordinates of the origin of the tag with index 'tag_idx'. + void (*set_tag_origin)( // + void* user_data, // + size_t tag_idx, // + float origin[3]); +}; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // DML_DEEPMIND_INCLUDE_DEEPMIND_MODEL_SETTERS_H_ diff --git a/deepmind/level_generation/BUILD b/deepmind/level_generation/BUILD index ecae3bde..71af401c 100644 --- a/deepmind/level_generation/BUILD +++ b/deepmind/level_generation/BUILD @@ -19,10 +19,20 @@ cc_library( name = "compile_map", srcs = ["compile_map.cc"], hdrs = ["compile_map.h"], + copts = [ + "-DMD5_DIGEST_LENGTH=16", + "-DMD5_Final=MD5Final", + "-DMD5_Init=MD5Init", + "-DMD5_Update=MD5Update", + ], data = [":compile_map_sh"], visibility = ["//deepmind:__subpackages__"], deps = [ + "//:level_cache_types", "//deepmind/support:logging", - "//deepmind/support:str_cat", + "//deepmind/util:files", + "//deepmind/util:run_executable", + "//third_party/md:md5", + "@com_google_absl//absl/strings", ], ) diff --git a/deepmind/level_generation/compile_map.cc b/deepmind/level_generation/compile_map.cc index 95931aeb..83038995 100644 --- a/deepmind/level_generation/compile_map.cc +++ b/deepmind/level_generation/compile_map.cc @@ -18,54 +18,117 @@ #include "deepmind/level_generation/compile_map.h" -#include -#include - +#include +#include +#include +#include +#include #include +#include #include "deepmind/support/logging.h" -#include "deepmind/support/str_cat.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_join.h" +#include "deepmind/util/files.h" +#include "deepmind/util/run_executable.h" +#include "third_party/md/md5.h" namespace deepmind { namespace lab { namespace { -// Produce a human-readable description of the platform-dependent result of -// system(). On our Linux, uses the semantics of wait(2). -bool ParseStatus(int s, std::string* msg) { - if (s == -1) { - LOG(QFATAL) << "Failed to call system()."; - } else if (WIFEXITED(s)) { - int retval = WEXITSTATUS(s); - if (retval == 0) { - *msg = "exited successfully (return value 0)"; - return true; - } else if (retval == 127) { - *msg = StrCat("system() failed to run /bin/sh.", retval); - return false; - } else { - *msg = StrCat("exited with failure, return value ", retval); - return false; - } - } else if (WIFSIGNALED(s)) { - int signum = WTERMSIG(s); - *msg = StrCat("exited with signal ", signum); - return false; - } else { - LOG(QFATAL) << "system() returned something implausible."; +constexpr char kScript[] = "deepmind/level_generation/compile_map.sh"; + +// Converts an md5 `digest` to a hex string. +std::string ToHex(const std::array& digest) { + std::string result; + result.reserve(digest.size() * 2); + static const char digits[] = "0123456789abcdef"; + for (unsigned char d : digest) { + result.push_back(digits[d / 16]); + result.push_back(digits[d % 16]); } + return result; } -constexpr char kScript[] = "deepmind/level_generation/compile_map.sh"; +std::string CalculateMd5(const std::string& file_name) { + std::array digest; + MD5_CTX md5_state; + MD5_Init(&md5_state); + std::ifstream stream(file_name); + do { + std::array read_buffer; + stream.read(read_buffer.data(), read_buffer.size()); + MD5_Update(&md5_state, reinterpret_cast(read_buffer.data()), + stream.gcount()); + } while (stream); + + MD5_Final(digest.data(), &md5_state); + return ToHex(digest); +} } // namespace -bool RunMapCompileFor(const std::string& rundir, const std::string& base) { - std::string msg, cmd = StrCat(rundir, "/", kScript, " \"", base, "\""); - LOG(INFO) << "Running map generation for map '" << base - << "', command:\n" << cmd << "\n"; - bool res = ParseStatus(std::system(cmd.c_str()), &msg); - LOG(INFO) << "Return status: " << msg << "\n"; +bool RunMapCompileFor(const std::string& rundir, const std::string& base, + const MapCompileSettings& compile_settings) { + std::vector paths; + if (compile_settings.use_local_level_cache) { + paths.emplace_back( + absl::StrCat(util::GetTempDirectory(), "/dmlab_level_cache")); + } + + + std::vector c_paths; + c_paths.reserve(paths.size()); + std::transform(paths.begin(), paths.end(), std::back_inserter(c_paths), + [](const std::string& s) { return s.c_str(); }); + + const std::string map_filename = compile_settings.map_source_location.empty() + ? std::string(absl::StrCat(base, ".map")) + : compile_settings.map_source_location; + const std::string md5 = CalculateMd5(map_filename); + + std::string base_cpy(base); + const std::string map_basename = basename(&base_cpy[0]); + const std::string key = absl::StrCat( + map_basename, compile_settings.generate_aas ? "_aas_" : "_noaas_", md5); + + const std::string pk3_filename = absl::StrCat(base, ".pk3"); + + if (compile_settings.level_cache_params.fetch_level_from_cache && + compile_settings.level_cache_params.fetch_level_from_cache( + compile_settings.level_cache_params.context, c_paths.data(), + c_paths.size(), key.c_str(), pk3_filename.c_str())) { + return true; + } + + // Level was not found in the cache; build it and write it to the cache (if + // enabled). + std::string cmd = absl::StrCat(rundir, "/", kScript); + + if (compile_settings.generate_aas) { + cmd += " -a"; + } + + if (!compile_settings.map_source_location.empty()) { + cmd += " -m " + compile_settings.map_source_location; + } + + cmd += " \"" + base + "\""; + std::string msg; + std::string output; + + bool res = util::RunExecutableWithOutput(cmd.c_str(), &msg, &output); + + if (res) { + if (compile_settings.level_cache_params.write_level_to_cache) { + compile_settings.level_cache_params.write_level_to_cache( + compile_settings.level_cache_params.context, c_paths.data(), + c_paths.size(), key.c_str(), pk3_filename.c_str()); + } + } else { + LOG(ERROR) << output; + } return res; } diff --git a/deepmind/level_generation/compile_map.h b/deepmind/level_generation/compile_map.h index 3d35ad1a..e6a86750 100644 --- a/deepmind/level_generation/compile_map.h +++ b/deepmind/level_generation/compile_map.h @@ -22,16 +22,37 @@ #define DML_DEEPMIND_LEVEL_GENERATION_COMPILE_MAP_H_ #include +#include "public/level_cache_types.h" namespace deepmind { namespace lab { +struct MapCompileSettings { + bool generate_aas = true; // Required if level needs to be traversed by bots. + std::string map_source_location; // Optional path to the map file. (If not + // empty the file at this location is copied + // to .map.) + + // Which level caches to use if any. A level is searched for in the + // local cache iff enabled and then the global cache iff enabled. + // If found in the local cache, it is made available. + // If found in the global cache, it is copied to the local cache iff enabled + // and made available. + // If not found, the level is generated and copied to enabled level caches. + bool use_local_level_cache = false; + bool use_global_level_cache = true; + + DeepMindLabLevelCacheParams level_cache_params; +}; + // Runs the map compiler for the map .map, producing .pk3. // The rundir parameter contains the name of base directory of the executable, -// with respect to which the compilation script is located. +// with respect to which the compilation script is located. The compile_settings +// parameter provides additional options for the compilation (see above). // // Returns whether the compilation succeeded. -bool RunMapCompileFor(const std::string& rundir, const std::string& base); +bool RunMapCompileFor(const std::string& rundir, const std::string& base, + const MapCompileSettings& compile_settings); } // namespace lab } // namespace deepmind diff --git a/deepmind/level_generation/compile_map.sh b/deepmind/level_generation/compile_map.sh index ff2ad633..13c6b43d 100755 --- a/deepmind/level_generation/compile_map.sh +++ b/deepmind/level_generation/compile_map.sh @@ -24,7 +24,7 @@ # You may replace this script with a different implementation as long as # it has the same interface, which is as follows: # -# compile_map.sh /path/to/mymap +# compile_map.sh [-a] /path/to/mymap # # This expects: # * A file /path/to/mymap.map. @@ -32,23 +32,49 @@ # # Effects: # * Creates a package file /path/to/mymap.pk3 which contains the files -# maps/mymap.bsp and maps/mymap.aas. +# maps/mymap.bsp and, if -a is specified, maps/mymap.aas. # # This implementation uses Bash and requires the bspc and q3map2 tools to exist # in fixed locations relative to this script, and the "zip" command must work. set -e +function usage() { + echo "Usage: ${0} [-a] [-m map_path] path/to/mymap" + echo " -a: Generate AAS data." + echo " -m: Path to map location." + exit 1 +} + +GENERATE_AAS=false +MAP_LOCATION= + +while getopts ":am:" opt; do + case $opt in + a ) GENERATE_AAS=true ;; + m ) MAP_LOCATION="${OPTARG}" ;; + \? ) usage ;; + esac +done + +shift $((OPTIND - 1)) + readonly MAPBASE="${1}" readonly BASE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" readonly Q3MP="${BASE}/../../q3map2/q3map2" readonly BSPC="${BASE}/../../bspc" -readonly DIR="$(dirname ${MAPBASE})" -readonly MPF="$(basename ${MAPBASE})" +readonly DIR="$(dirname "${MAPBASE}")" +readonly MPF="$(basename "${MAPBASE}")" readonly MPB="${MPF/.map/}" +if [[ -n $MAP_LOCATION ]]; then + mkdir --parents -- "${DIR}" + cp --force -- "${MAP_LOCATION}" "${MAPBASE}.map" + chmod 660 -- "${MAPBASE}.map" +fi + function die { echo "Error: ${1}" exit 1 @@ -56,7 +82,6 @@ function die { function check_exe { [[ -x "${1}" ]] || die "could not find ${2} tool" - echo "${2} tool found at '$(realpath -- "${1}")'." } ## Sanity checking @@ -68,9 +93,12 @@ check_exe "${BSPC}" "bspc" ## Main logic -# Step 1: q3map2 to generate the BSP +function clean_up { + rm --force -- "${DIR}/${MPB}."{bsp,map,prt,srf} +} -${Q3MP} -fs_basepath "${BASE}/../.." -fs_game baselab -meta -patchmeta -threads 8 "${MAPBASE}" +# Step 1: q3map2 to generate the BSP +${Q3MP} -fs_basepath "${BASE}/../.." -fs_game baselab -meta -patchmeta -threads 1 "${MAPBASE}" ${Q3MP} -fs_basepath "${BASE}/../.." -fs_game baselab -vis -threads 8 "${MAPBASE}" ${Q3MP} -fs_basepath "${BASE}/../.." -fs_game baselab -light -threads 8 -fast \ -patchshadows -samples 2 -bounce 3 -gamma 2 -compensate 4 -dirty \ @@ -78,15 +106,31 @@ ${Q3MP} -fs_basepath "${BASE}/../.." -fs_game baselab -light -threads 8 -fast \ # Step 2: bscp to generate the AAS -${BSPC} -optimize -forcesidesvisible -bsp2aas "${MAPBASE}.map" -output "${DIR}" +if [[ $GENERATE_AAS = true ]] +then + ${BSPC} -optimize -forcesidesvisible -bsp2aas "${MAPBASE}.map" -output "${DIR}" +fi # Step 3: Zip .bsp and .aas into a .pk3 archive. -mkdir -p -- "${DIR}/maps" -cp -t "${DIR}/maps" -- "${DIR}/${MPB}.bsp" "${DIR}/${MPB}.aas" +mkdir --parents -- "${DIR}/maps" +if [[ $GENERATE_AAS = true ]]; then + mv --target-directory="${DIR}/maps" -- "${DIR}/${MPB}.bsp" "${DIR}/${MPB}.aas" +else + mv --target-directory="${DIR}/maps" -- "${DIR}/${MPB}.bsp" +fi rm -f -- "${DIR}/${MPB}.pk3" -(cd -- "${DIR}" && zip "${MPB}.pk3" -- "maps/${MPB}.bsp" "maps/${MPB}.aas") +if [[ $GENERATE_AAS = true ]]; then + (cd -- "${DIR}" && zip "${MPB}.pk3" -- "maps/${MPB}.bsp" "maps/${MPB}.aas") + rm -- "${DIR}/maps/${MPB}.bsp" "${DIR}/maps/${MPB}.aas" +else + (cd -- "${DIR}" && zip "${MPB}.pk3" -- "maps/${MPB}.bsp") + rm -- "${DIR}/maps/${MPB}.bsp" +fi + +rmdir -- "${DIR}/maps" +clean_up # Done! diff --git a/deepmind/level_generation/map_builder/BUILD b/deepmind/level_generation/map_builder/BUILD index 288d0120..a104b116 100644 --- a/deepmind/level_generation/map_builder/BUILD +++ b/deepmind/level_generation/map_builder/BUILD @@ -17,11 +17,8 @@ cc_library( ], visibility = ["//visibility:public"], deps = [ - "//deepmind/support:str_cat", - "//deepmind/support:str_join", - "//deepmind/support:str_split", - "//deepmind/support:string_view", "//deepmind/support:stringprintf", + "@com_google_absl//absl/strings", "@eigen_archive//:eigen", ], ) @@ -32,7 +29,7 @@ cc_test( srcs = ["map_builder_test.cc"], deps = [ ":map_builder", - "//deepmind/support:stringprintf", - "@googletest//:gtest_main", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest_main", ], ) diff --git a/deepmind/level_generation/map_builder/brush.cc b/deepmind/level_generation/map_builder/brush.cc index d8a847c8..ec436db5 100644 --- a/deepmind/level_generation/map_builder/brush.cc +++ b/deepmind/level_generation/map_builder/brush.cc @@ -21,22 +21,21 @@ #include #include "deepmind/support/stringprintf.h" -#include "deepmind/support/str_cat.h" -#include "deepmind/support/str_split.h" -#include "deepmind/support/string_view.h" -#include "deepmind/support/string_view_utils.h" +#include "absl/strings/ascii.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_split.h" +#include "absl/strings/string_view.h" #include "Eigen/Geometry" namespace deepmind { namespace lab { namespace map_builder { - namespace { // Helper function to parse a single brush. -bool ParseSingleBrush(StringPiece brush_str, Brush* brush) { - for (StringPiece line : strings::Split(brush_str, '\n')) { - strings::RemoveLeadingWhitespace(&line); +bool ParseSingleBrush(absl::string_view brush_str, Brush* brush) { + for (absl::string_view line : absl::StrSplit(brush_str, '\n')) { + line = absl::StripLeadingAsciiWhitespace(line); Plane p; char path[256]; @@ -80,11 +79,11 @@ std::string Plane::ToString() const { } std::string Brush::ToString() const { - string brush = "{\n"; + std::string brush = "{\n"; for (const auto& plane : planes) { - StrAppend(&brush, " ", plane.ToString(), "\n"); + absl::StrAppend(&brush, " ", plane.ToString(), "\n"); } - StrAppend(&brush, " }"); + absl::StrAppend(&brush, " }"); return brush; } @@ -95,24 +94,24 @@ std::string PatchPoint::ToString() const { } std::string Patch::ToString() const { - string patch = - StrCat("{\n patchDef2\n {\n ", texture_.path, "\n ( ", - grid_size_.x(), " ", grid_size_.y(), " 0 0 0 )\n"); - StrAppend(&patch, " (\n"); + std::string patch = absl::StrCat("{\n patchDef2\n {\n ", + texture_.path, "\n ( ", grid_size_.x(), + " ", grid_size_.y(), " 0 0 0 )\n"); + absl::StrAppend(&patch, " (\n"); for (int x = 0; x < grid_size_.x(); x++) { - StrAppend(&patch, " ("); + absl::StrAppend(&patch, " ("); for (int y = 0; y < grid_size_.y(); y++) { - StrAppend(&patch, " ", point({x, y}).ToString()); + absl::StrAppend(&patch, " ", point({x, y}).ToString()); } - StrAppend(&patch, " )\n"); + absl::StrAppend(&patch, " )\n"); } - StrAppend(&patch, " )\n }\n }"); + absl::StrAppend(&patch, " )\n }\n }"); return patch; } namespace brush_util { -std::vector ParseBrushes(StringPiece brushes_str) { +std::vector ParseBrushes(absl::string_view brushes_str) { std::vector brushes; int brush_start = -1; @@ -123,8 +122,9 @@ std::vector ParseBrushes(StringPiece brushes_str) { } else if (brush_start != -1) { // End of current brush, parse its assets. Brush brush; - if (ParseSingleBrush( - brushes_str.substr(brush_start, cur - brush_start + 1), &brush)) { + if (ParseSingleBrush(absl::ClippedSubstr(brushes_str, brush_start, + cur - brush_start + 1), + &brush)) { brushes.push_back(std::move(brush)); } brush_start = -1; @@ -158,8 +158,8 @@ Brush CreateBoxBrush(const Eigen::Vector3d& a, const Eigen::Vector3d& b, Brush CreateFittedBoxBrush(const Eigen::Vector3d& a, const Eigen::Vector3d& b, const std::string& texture_name, Eigen::Vector2i texture_size) { - Eigen::Vector3d min_pos = a.cwiseMin(b); - Eigen::Vector3d max_pos = a.cwiseMax(b); + auto min_pos = a.cwiseMin(b); + auto max_pos = a.cwiseMax(b); Brush brush; @@ -233,29 +233,41 @@ std::vector CreateHollowBox(const Eigen::Vector3d& a, std::vector brushes; Eigen::Vector3d t(thickness, thickness, thickness); + auto min_pos = a.cwiseMin(b); + auto max_pos = a.cwiseMax(b); + // Top. brushes.emplace_back(CreateBoxBrush( - {a.x() - thickness, a.y() - thickness, b.z()}, b + t, texture)); + {min_pos.x() - thickness, min_pos.y() - thickness, max_pos.z()}, + max_pos + t, texture)); // Bottom. brushes.emplace_back(CreateBoxBrush( - a - t, {b.x() + thickness, b.y() + thickness, a.z()}, texture)); + min_pos - t, + {max_pos.x() + thickness, max_pos.y() + thickness, min_pos.z()}, + texture)); // Left. brushes.emplace_back(CreateBoxBrush( - a - t, {a.x(), b.y() + thickness, b.z() + thickness}, texture)); + min_pos - t, + {min_pos.x(), max_pos.y() + thickness, max_pos.z() + thickness}, + texture)); // Right. brushes.emplace_back(CreateBoxBrush( - {b.x(), a.y() - thickness, a.z() - thickness}, b + t, texture)); + {max_pos.x(), min_pos.y() - thickness, min_pos.z() - thickness}, + max_pos + t, texture)); // Front. brushes.emplace_back(CreateBoxBrush( - {a.x() - thickness, b.y(), a.z() - thickness}, b + t, texture)); + {min_pos.x() - thickness, max_pos.y(), min_pos.z() - thickness}, + max_pos + t, texture)); // Back. brushes.emplace_back(CreateBoxBrush( - a - t, {b.x() + thickness, a.y(), b.z() + thickness}, texture)); + min_pos - t, + {max_pos.x() + thickness, min_pos.y(), max_pos.z() + thickness}, + texture)); return brushes; } @@ -265,43 +277,38 @@ std::vector CreateSkybox(const Eigen::Vector3d& position, const std::string& texture_name, const Eigen::Vector2i& texture_size) { const auto half_size = size * 0.5; - auto a = position - half_size; - auto b = position + half_size; - const Eigen::Vector3d t = {thickness, thickness, thickness}; + auto min_pos = position - half_size; + auto max_pos = position + half_size; std::vector brushes; - // Up. - brushes.emplace_back(brush_util::CreateFittedBoxBrush( - {a.x() - thickness, a.y() - thickness, b.z()}, b + t, - StrCat(texture_name, "_up"), texture_size)); + brushes.emplace_back(CreateFittedBoxBrush( + max_pos, {min_pos.x(), min_pos.y(), max_pos.z() + thickness}, + absl::StrCat(texture_name, "_up"), texture_size)); - // Down. - brushes.emplace_back(brush_util::CreateFittedBoxBrush( - a - t, {b.x() + thickness, b.y() + thickness, a.z()}, - StrCat(texture_name, "_dn"), texture_size)); + brushes.emplace_back(CreateFittedBoxBrush( + min_pos, {max_pos.x(), max_pos.y(), min_pos.z() - thickness}, + absl::StrCat(texture_name, "_dn"), texture_size)); // Left. - brushes.emplace_back(brush_util::CreateFittedBoxBrush( - {a.x(), a.y() - thickness, a.z()}, - {a.x() - thickness, b.y() + thickness, b.z()}, - StrCat(texture_name, "_lf"), texture_size)); + brushes.emplace_back(CreateFittedBoxBrush( + min_pos, {min_pos.x() - thickness, max_pos.y(), max_pos.z()}, + absl::StrCat(texture_name, "_lf"), texture_size)); // Right. - brushes.emplace_back(brush_util::CreateFittedBoxBrush( - {b.x(), a.y() - thickness, a.z()}, - {b.x() + thickness, b.y() + thickness, b.z()}, - StrCat(texture_name, "_rt"), texture_size)); - - // Front. - brushes.emplace_back(brush_util::CreateFittedBoxBrush( - a, {b.x(), a.y() - thickness, b.z()}, StrCat(texture_name, "_ft"), - texture_size)); - - // Back. - brushes.emplace_back(brush_util::CreateFittedBoxBrush( - {a.x(), b.y() + thickness, a.z()}, b, StrCat(texture_name, "_bk"), - texture_size)); + brushes.emplace_back(CreateFittedBoxBrush( + max_pos, {max_pos.x() + thickness, min_pos.y(), min_pos.z()}, + absl::StrCat(texture_name, "_rt"), texture_size)); + + // Front + brushes.emplace_back(CreateFittedBoxBrush( + min_pos, {max_pos.x(), min_pos.y() - thickness, max_pos.z()}, + absl::StrCat(texture_name, "_ft"), texture_size)); + + // Back + brushes.emplace_back(CreateFittedBoxBrush( + max_pos, {min_pos.x(), max_pos.y() + thickness, min_pos.z()}, + absl::StrCat(texture_name, "_bk"), texture_size)); return brushes; } diff --git a/deepmind/level_generation/map_builder/brush.h b/deepmind/level_generation/map_builder/brush.h index 27fdc9b0..a13d7dd5 100644 --- a/deepmind/level_generation/map_builder/brush.h +++ b/deepmind/level_generation/map_builder/brush.h @@ -24,7 +24,7 @@ #include #include -#include "deepmind/support/string_view.h" +#include "absl/strings/string_view.h" #include "Eigen/Core" namespace deepmind { @@ -33,7 +33,7 @@ namespace map_builder { // Constant used to convert world units to game units. In DeepMind Lab there are // 32 units to the meter, so we multiply any plane or position by this number. -constexpr int kWorldToGameUnits = 32; +constexpr double kWorldToGameUnits = 32.0; // Textures are used in brushes and patches to define surface settings. struct Texture { @@ -161,7 +161,7 @@ namespace brush_util { // }" // // would create a vector of two brushes. -std::vector ParseBrushes(StringPiece str); +std::vector ParseBrushes(absl::string_view str); // Helper function for creating a box brush from two extents. // All planes use the provided texture. @@ -209,15 +209,15 @@ std::vector CreateHollowBox( // align correctly, we create the brushes so that they don't intersect with each // other, whilst still guaranteeing the box is airtight. The texture name is // appended with a suffix for each side (so _ft for front, etc). -// Here is a top-down representation, with the encompassing top brush removed: -// +--+------+--+ -// | | +// Here is a top-down representation: +// +------+ +// | | // +--+------+--+ // | | | | // | | | | // +--+------+--+ -// | | -// +--+------+--+ +// | | +// +------+ std::vector CreateSkybox( const Eigen::Vector3d& position, const Eigen::Vector3d& size, diff --git a/deepmind/level_generation/map_builder/builder.cc b/deepmind/level_generation/map_builder/builder.cc index b53e8f35..f87fc073 100644 --- a/deepmind/level_generation/map_builder/builder.cc +++ b/deepmind/level_generation/map_builder/builder.cc @@ -20,8 +20,8 @@ #include -#include "deepmind/support/str_cat.h" -#include "deepmind/support/str_join.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_join.h" namespace deepmind { namespace lab { @@ -36,10 +36,12 @@ constexpr double kThickness = 1.0; Builder::Builder() { entities_.emplace_back(Entity::CreateWorld()); } std::string Builder::ToString() const { - return strings::Join(entities_, "\n\n", - [](string* out, const Entity& entity) { - StrAppend(out, entity.ToString()); - }); + return absl::StrCat( + absl::StrJoin(entities_, "\n\n", + [](decltype(absl::StrCat())* out, const Entity& entity) { + absl::StrAppend(out, entity.ToString()); + }), + "\n"); } void Builder::AddSkybox( diff --git a/deepmind/level_generation/map_builder/entity.cc b/deepmind/level_generation/map_builder/entity.cc index 1bfcc62a..89733b3c 100644 --- a/deepmind/level_generation/map_builder/entity.cc +++ b/deepmind/level_generation/map_builder/entity.cc @@ -21,8 +21,8 @@ #include #include -#include "deepmind/support/str_cat.h" -#include "deepmind/support/str_join.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_join.h" namespace deepmind { namespace lab { @@ -35,7 +35,7 @@ Entity::Entity(std::string class_name, Eigen::Vector3d origin) Entity Entity::CreatePointLight(Eigen::Vector3d position, double intensity) { Entity point_light("light", position); - point_light.set_attribute("light", StringPrintf("%g", intensity)); + point_light.set_attribute("light", absl::StrCat(intensity)); point_light.set_attribute("style", "0"); point_light.set_attribute("spawnflags", "0"); return point_light; @@ -44,7 +44,7 @@ Entity Entity::CreatePointLight(Eigen::Vector3d position, double intensity) { Entity Entity::CreateSpawn(Eigen::Vector3d position, Angle angle) { Entity spawn_point("info_player_start", position); if (angle.radians()) { - spawn_point.set_attribute("angle", StringPrintf("%g", angle.degrees())); + spawn_point.set_attribute("angle", absl::StrCat(angle.degrees())); } return spawn_point; } @@ -52,10 +52,11 @@ Entity Entity::CreateSpawn(Eigen::Vector3d position, Angle angle) { std::pair Entity::CreateTeamSpawn(Eigen::Vector3d position, Angle angle, Team team) { const auto& team_string = team == Team::kRed ? "red" : "blue"; - Entity team_player(StrCat("team_CTF_", team_string, "player"), position); - Entity team_spawn(StrCat("team_CTF_", team_string, "spawn"), position); + Entity team_player(absl::StrCat("team_CTF_", team_string, "player"), + position); + Entity team_spawn(absl::StrCat("team_CTF_", team_string, "spawn"), position); if (angle.radians()) { - auto angle_string = StringPrintf("%g", angle.degrees()); + auto angle_string = absl::StrCat(angle.degrees()); team_spawn.set_attribute("angle", angle_string); team_player.set_attribute("angle", angle_string); } @@ -81,8 +82,9 @@ Entity Entity::CreateModel(const std::string& model_filename, rotation.roll.radians()) { model_entity.set_attribute( "angles", - StringPrintf("%g %g %g", rotation.pitch.degrees(), - rotation.yaw.degrees(), rotation.roll.degrees())); + absl::StrCat(rotation.pitch.degrees(), " ", + rotation.yaw.degrees(), " ", + rotation.roll.degrees())); } // Same for scale, only add if it's not 1. @@ -93,35 +95,36 @@ Entity Entity::CreateModel(const std::string& model_filename, } std::string Entity::ToString() const { - string result = StrCat("{\n \"classname\" \"", class_name(), "\""); + auto result = absl::StrCat("{\n \"classname\" \"", class_name(), "\""); + using abslstring = decltype(result); // Add attributes (if we have any). if (!attributes_.empty()) { - const auto quote_formatter = [](string* out, const string& in) { - StrAppend(out, "\"", in, "\""); + const auto quote_formatter = [](abslstring* out, const std::string& in) { + absl::StrAppend(out, "\"", in, "\""); }; - StrAppend(&result, "\n ", - strings::Join(attributes_, "\n ", - strings::PairFormatter(quote_formatter, " ", - quote_formatter))); + absl::StrAppend(&result, "\n ", + absl::StrJoin(attributes_, "\n ", + absl::PairFormatter(quote_formatter, " ", + quote_formatter))); } // Add brushes (if we have any). if (!brushes_.empty()) { - StrAppend( - &result, "\n ", - strings::Join(brushes_, "\n ", [](string* out, const Brush& brush) { - StrAppend(out, brush.ToString()); - })); + absl::StrAppend(&result, "\n ", + absl::StrJoin(brushes_, "\n ", + [](abslstring* out, const Brush& brush) { + absl::StrAppend(out, brush.ToString()); + })); } // Add patches (if we have any). if (!patches_.empty()) { - StrAppend( - &result, "\n ", - strings::Join(patches_, "\n ", [](string* out, const Patch& patch) { - StrAppend(out, patch.ToString()); - })); + absl::StrAppend(&result, "\n ", + absl::StrJoin(patches_, "\n ", + [](abslstring* out, const Patch& patch) { + absl::StrAppend(out, patch.ToString()); + })); } - StrAppend(&result, "\n}"); + absl::StrAppend(&result, "\n}"); return result; } diff --git a/deepmind/level_generation/map_builder/entity.h b/deepmind/level_generation/map_builder/entity.h index 861e8bf4..11597bb6 100644 --- a/deepmind/level_generation/map_builder/entity.h +++ b/deepmind/level_generation/map_builder/entity.h @@ -27,7 +27,7 @@ #include #include -#include "deepmind/support/stringprintf.h" +#include "absl/strings/str_cat.h" #include "Eigen/Core" #include "deepmind/level_generation/map_builder/brush.h" @@ -117,8 +117,7 @@ class Entity { } void set_attribute(const std::string& key, Eigen::Vector3d value) { - attributes_[key] = - StringPrintf("%g %g %g", value.x(), value.y(), value.z()); + attributes_[key] = absl::StrCat(value.x(), " ", value.y(), " ", value.z()); } template diff --git a/deepmind/level_generation/map_builder/map_builder_test.cc b/deepmind/level_generation/map_builder/map_builder_test.cc index 62d8d9f1..42fea9e4 100644 --- a/deepmind/level_generation/map_builder/map_builder_test.cc +++ b/deepmind/level_generation/map_builder/map_builder_test.cc @@ -16,20 +16,22 @@ // //////////////////////////////////////////////////////////////////////////////// -#include "deepmind/support/stringprintf.h" #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/strings/str_cat.h" #include "deepmind/level_generation/map_builder/builder.h" namespace deepmind { namespace lab { namespace map_builder { - namespace { +using ::testing::HasSubstr; + constexpr char kDefaultWorldMap[] = R"({ "classname" "worldspawn" -})"; +} +)"; TEST(MapBuilderTest, DefaultWorldEntity) { Builder builder; @@ -40,7 +42,8 @@ constexpr char kCustomWorldEntity[] = R"({ "classname" "worldspawn" "light" "100" "worldtype" "2" -})"; +} +)"; TEST(MapBuilderTest, CustomWorldEntity) { Builder builder; @@ -57,8 +60,7 @@ TEST(MapBuilderTest, AddBasicEntity) { Builder builder; builder.AddEntity(test_entity); - EXPECT_THAT(builder.ToString(), - testing::HasSubstr("\"classname\" \"test_entity\"")); + EXPECT_THAT(builder.ToString(), HasSubstr("\"classname\" \"test_entity\"")); } TEST(MapBuilderTest, CreatePointLight) { @@ -66,10 +68,10 @@ TEST(MapBuilderTest, CreatePointLight) { Entity light_entity = Entity::CreatePointLight(pos, 200.0); std::string light_str = light_entity.ToString(); - EXPECT_THAT(light_str, testing::HasSubstr("\"classname\" \"light\"")); - EXPECT_THAT(light_str, testing::HasSubstr("\"light\" \"200\"")); - EXPECT_THAT(light_str, testing::HasSubstr("\"style\" \"0\"")); - EXPECT_THAT(light_str, testing::HasSubstr("\"origin\" \"32 32 160\"")); + EXPECT_THAT(light_str, HasSubstr("\"classname\" \"light\"")); + EXPECT_THAT(light_str, HasSubstr("\"light\" \"200\"")); + EXPECT_THAT(light_str, HasSubstr("\"style\" \"0\"")); + EXPECT_THAT(light_str, HasSubstr("\"origin\" \"32 32 160\"")); } TEST(MapBuilderTest, CreateSpawn) { @@ -79,12 +81,11 @@ TEST(MapBuilderTest, CreateSpawn) { std::string spawn_str = spawn_entity.ToString(); - EXPECT_THAT(spawn_str, - testing::HasSubstr(StringPrintf("\"angle\" \"%g\"", angle))); - EXPECT_THAT(spawn_str, - testing::HasSubstr(StringPrintf( - "\"origin\" \"%g %g %g\"", pos.x() * kWorldToGameUnits, - pos.y() * kWorldToGameUnits, pos.z() * kWorldToGameUnits))); + EXPECT_THAT(spawn_str, HasSubstr(absl::StrCat("\"angle\" \"", angle, "\""))); + EXPECT_THAT(spawn_str, HasSubstr(absl::StrCat( + "\"origin\" \"", pos.x() * kWorldToGameUnits, " ", + pos.y() * kWorldToGameUnits, " ", + pos.z() * kWorldToGameUnits, "\""))); } TEST(MapBuilderTest, CreateTeamSpawn) { @@ -94,17 +95,17 @@ TEST(MapBuilderTest, CreateTeamSpawn) { Entity::CreateTeamSpawn(pos, Angle::Degrees(angle), Team::kRed); EXPECT_THAT(red_spawn_entities.first.ToString(), - testing::HasSubstr("\"classname\" \"team_CTF_redplayer\"")); + HasSubstr("\"classname\" \"team_CTF_redplayer\"")); EXPECT_THAT(red_spawn_entities.second.ToString(), - testing::HasSubstr("\"classname\" \"team_CTF_redspawn\"")); + HasSubstr("\"classname\" \"team_CTF_redspawn\"")); auto blue_spawn_entities = Entity::CreateTeamSpawn(pos, Angle::Degrees(angle), Team::kBlue); EXPECT_THAT(blue_spawn_entities.first.ToString(), - testing::HasSubstr("\"classname\" \"team_CTF_blueplayer\"")); + HasSubstr("\"classname\" \"team_CTF_blueplayer\"")); EXPECT_THAT(blue_spawn_entities.second.ToString(), - testing::HasSubstr("\"classname\" \"team_CTF_bluespawn\"")); + HasSubstr("\"classname\" \"team_CTF_bluespawn\"")); } TEST(MapBuilderTest, CreateFlag) { @@ -112,12 +113,12 @@ TEST(MapBuilderTest, CreateFlag) { auto red_flag_entity = Entity::CreateFlag(pos, Team::kRed); EXPECT_THAT(red_flag_entity.ToString(), - testing::HasSubstr("\"classname\" \"team_CTF_redflag\"")); + HasSubstr("\"classname\" \"team_CTF_redflag\"")); auto blue_flag_entity = Entity::CreateFlag(pos, Team::kBlue); EXPECT_THAT(blue_flag_entity.ToString(), - testing::HasSubstr("\"classname\" \"team_CTF_blueflag\"")); + HasSubstr("\"classname\" \"team_CTF_blueflag\"")); } constexpr char kDefaultModelEntity[] = R"({ @@ -135,23 +136,21 @@ TEST(MapBuilderTest, CreateModel) { std::string model_str = model_entity.ToString(); - EXPECT_THAT(model_str, - testing::HasSubstr(StringPrintf( - "\"origin\" \"%g %g %g\"", pos.x() * kWorldToGameUnits, - pos.y() * kWorldToGameUnits, pos.z() * kWorldToGameUnits))); - EXPECT_THAT(model_str, - testing::HasSubstr(StringPrintf( - "\"angles\" \"%g %g %g\"", rotation.pitch.degrees(), - rotation.yaw.degrees(), rotation.roll.degrees()))); - EXPECT_THAT(model_str, - testing::HasSubstr("\"modelscale_vec\" \"1 1 2\"")); + EXPECT_THAT(model_str, HasSubstr(absl::StrCat( + "\"origin\" \"", pos.x() * kWorldToGameUnits, " ", + pos.y() * kWorldToGameUnits, " ", + pos.z() * kWorldToGameUnits, "\""))); + EXPECT_THAT(model_str, HasSubstr(absl::StrCat( + "\"angles\" \"", rotation.pitch.degrees(), " ", + rotation.yaw.degrees(), " ", + rotation.roll.degrees(), "\""))); + EXPECT_THAT(model_str, HasSubstr("\"modelscale_vec\" \"1 1 2\"")); // Create model with default scale and rotation. Entity default_model = Entity::CreateModel("model/test.md3", pos, {}, {1, 1, 1}); - EXPECT_THAT(default_model.ToString(), - testing::HasSubstr(kDefaultModelEntity)); + EXPECT_THAT(default_model.ToString(), HasSubstr(kDefaultModelEntity)); } constexpr char kWorldBoxBrush[] = R"( @@ -172,7 +171,7 @@ TEST(MapBuilderTest, CreateBoxBrush) { auto brush = brush_util::CreateBoxBrush(a, b, {"test_texture"}); builder.mutable_world_entity()->add_brush(brush); - EXPECT_THAT(builder.ToString(), testing::HasSubstr(kWorldBoxBrush)); + EXPECT_THAT(builder.ToString(), HasSubstr(kWorldBoxBrush)); } // Only check one brush for each side. @@ -215,9 +214,9 @@ TEST(MapBuilderTest, CreateHollowBox) { auto brushes = brush_util::CreateHollowBox(a, b, 1.0, {"test_texture"}); builder.mutable_world_entity()->add_brushes(brushes); - EXPECT_THAT(builder.ToString(), testing::HasSubstr(kHollowBoxTop)); - EXPECT_THAT(builder.ToString(), testing::HasSubstr(kHollowBoxLeft)); - EXPECT_THAT(builder.ToString(), testing::HasSubstr(kHollowBoxFront)); + EXPECT_THAT(builder.ToString(), HasSubstr(kHollowBoxTop)); + EXPECT_THAT(builder.ToString(), HasSubstr(kHollowBoxLeft)); + EXPECT_THAT(builder.ToString(), HasSubstr(kHollowBoxFront)); } constexpr char kPatchString[] = R"({ @@ -252,7 +251,7 @@ TEST(MapBuilderTest, CreatePatch) { Builder builder; builder.mutable_world_entity()->add_patch(p); - EXPECT_THAT(builder.ToString(), testing::HasSubstr(kPatchString)); + EXPECT_THAT(builder.ToString(), HasSubstr(kPatchString)); } TEST(MapBuilderTest, CreateGridPatch) { @@ -329,8 +328,26 @@ TEST(MapBuilderTest, NestedBrushes) { EXPECT_EQ(2, brushes.size()); } -} // namespace +constexpr char kFittedBoxBrush[] = R"({ + ( -64 0 0 ) ( -64 32 0 ) ( -64 0 32 ) test 0 0 0 -0.3125 0.046875 0 0 0 + ( 0 0 0 ) ( 0 0 32 ) ( 0 32 0 ) test 0 0 0 -0.3125 0.046875 0 0 0 + ( 0 0 0 ) ( 0 0 32 ) ( 32 0 0 ) test 0 0 0 -0.0625 0.046875 0 0 0 + ( 0 320 0 ) ( 32 320 0 ) ( 0 320 32 ) test 0 0 0 -0.0625 0.046875 0 0 0 + ( 0 0 -48 ) ( 32 0 -48 ) ( 0 32 -48 ) test 0 0 0 -0.0625 0.3125 0 0 0 + ( 0 0 0 ) ( 0 32 0 ) ( 32 0 0 ) test 0 0 0 -0.0625 0.3125 0 0 0 + })"; + +TEST(MapBuilderTest, FittedBoxBrush) { + const Eigen::Vector3d a = {-2.0, 0.0, -1.5}; + const Eigen::Vector3d b = {0.0, 10.0, 0.0}; + const Eigen::Vector2i texture_size = {1024, 1024}; + + auto brush = brush_util::CreateFittedBoxBrush(a, b, "test", texture_size); + EXPECT_EQ(kFittedBoxBrush, brush.ToString()); +} + +} // namespace } // namespace map_builder } // namespace lab } // namespace deepmind diff --git a/deepmind/level_generation/text_level/BUILD b/deepmind/level_generation/text_level/BUILD index 508fd856..f11a06f2 100644 --- a/deepmind/level_generation/text_level/BUILD +++ b/deepmind/level_generation/text_level/BUILD @@ -1,6 +1,6 @@ # Description: -# A system for generating maps from "text levels". -# See docs/text_level.md for details. +# Translation from text levels into .map format. +# See deepmind/docs/developers/creating_levels/text_level.md. licenses(["restricted"]) # GPLv2 @@ -11,8 +11,7 @@ cc_library( visibility = ["//deepmind:__subpackages__"], deps = [ "//deepmind/support:logging", - "//deepmind/support:str_split", - "//deepmind/support:string_view", + "@com_google_absl//absl/strings", ], ) @@ -22,7 +21,7 @@ cc_test( srcs = ["char_grid_test.cc"], deps = [ ":char_grid", - "@googletest//:gtest_main", + "@com_google_googletest//:gtest_main", ], ) @@ -32,23 +31,26 @@ cc_library( ) cc_library( - name = "text_maze_exporter", - srcs = ["text_maze_exporter.cc"], - hdrs = ["text_maze_exporter.h"], + name = "text_level_exporter", + srcs = ["text_level_exporter.cc"], + hdrs = [ + "text_level_exporter.h", + "text_level_settings.h", + ], deps = [ "//deepmind/level_generation/map_builder", - "//deepmind/support:logging", + "@com_google_absl//absl/strings", "@eigen_archive//:eigen", ], ) cc_test( - name = "text_maze_exporter_test", + name = "text_level_exporter_test", size = "small", - srcs = ["text_maze_exporter_test.cc"], + srcs = ["text_level_exporter_test.cc"], deps = [ - ":text_maze_exporter", - "@googletest//:gtest_main", + ":text_level_exporter", + "@com_google_googletest//:gtest_main", ], ) @@ -68,20 +70,25 @@ cc_test( srcs = ["parse_text_level_test.cc"], deps = [ ":parse_text_level", - "@googletest//:gtest_main", + "@com_google_googletest//:gtest_main", ], ) cc_library( name = "translate_text_level", srcs = ["translate_text_level.cc"], - hdrs = ["translate_text_level.h"], + hdrs = [ + "text_level_settings.h", + "translate_text_level.h", + ], visibility = ["//visibility:public"], deps = [ ":grid_maze", ":parse_text_level", - ":text_maze_exporter", + ":text_level_exporter", "//deepmind/level_generation/map_builder", + "@com_google_absl//absl/strings", + "@eigen_archive//:eigen", ], ) @@ -98,6 +105,8 @@ cc_library( "//deepmind/lua:n_results_or", "//deepmind/lua:push", "//deepmind/lua:read", + "//deepmind/lua:table_ref", + "//deepmind/support:logging", ], ) @@ -111,6 +120,6 @@ cc_test( "//deepmind/lua:call", "//deepmind/lua:push_script", "//deepmind/lua:vm", - "@googletest//:gtest_main", + "@com_google_googletest//:gtest_main", ], ) diff --git a/deepmind/level_generation/text_level/char_grid.cc b/deepmind/level_generation/text_level/char_grid.cc index 15403368..ff6e08c8 100644 --- a/deepmind/level_generation/text_level/char_grid.cc +++ b/deepmind/level_generation/text_level/char_grid.cc @@ -22,17 +22,19 @@ #include #include "deepmind/support/logging.h" -#include "deepmind/support/str_split.h" +#include "absl/strings/str_split.h" +#include "absl/strings/string_view.h" namespace deepmind { namespace lab { CharGrid::CharGrid(std::string text) : raw_data_(std::move(text)), - rows_(strings::Split(raw_data_, '\n', strings::SkipEmpty())) { - auto it = std::max_element( - rows_.begin(), rows_.end(), - [](StringPiece lhs, StringPiece rhs) { return lhs.size() < rhs.size(); }); + rows_(absl::StrSplit(raw_data_, '\n', absl::SkipEmpty())) { + auto it = std::max_element(rows_.begin(), rows_.end(), + [](absl::string_view lhs, absl::string_view rhs) { + return lhs.size() < rhs.size(); + }); CHECK(it != rows_.end()); width_ = it->size(); } diff --git a/deepmind/level_generation/text_level/char_grid.h b/deepmind/level_generation/text_level/char_grid.h index 0012eee8..1e058af9 100644 --- a/deepmind/level_generation/text_level/char_grid.h +++ b/deepmind/level_generation/text_level/char_grid.h @@ -25,7 +25,7 @@ #include #include -#include "deepmind/support/string_view.h" +#include "absl/strings/string_view.h" namespace deepmind { namespace lab { @@ -54,7 +54,7 @@ class CharGrid { private: std::string raw_data_; - std::vector rows_; + std::vector rows_; std::size_t width_; }; diff --git a/deepmind/level_generation/text_level/lua_bindings.cc b/deepmind/level_generation/text_level/lua_bindings.cc index 4b6dc2ad..7bf69fca 100644 --- a/deepmind/level_generation/text_level/lua_bindings.cc +++ b/deepmind/level_generation/text_level/lua_bindings.cc @@ -32,6 +32,7 @@ #include "deepmind/lua/n_results_or.h" #include "deepmind/lua/push.h" #include "deepmind/lua/read.h" +#include "deepmind/lua/table_ref.h" namespace deepmind { namespace lab { @@ -72,8 +73,7 @@ bool LuaCustomEntityCallback( std::vector data(1); if (!res.ok()) { - LOG(ERROR) << "User callback invocation failed ('" + res.error() + "')"; - return false; + LOG(FATAL) << "User callback invocation failed ('" + res.error() + "')"; } else if ((res.n_results() == 0) || (res.n_results() == 1 && lua_isnil(L, -1))) { VLOG(1) << "User callback(" << i << ", " << j << ", '" << ent @@ -82,9 +82,7 @@ bool LuaCustomEntityCallback( return false; } else if ((res.n_results() > 1) || !(lua::Read(L, -1, &data) || lua::Read(L, -1, &data.front()))) { - LOG(ERROR) << "User callback returned invalid results."; - lua_pop(L, res.n_results()); - return false; + LOG(FATAL) << "User callback returned invalid results."; } else { auto is_empty = std::mem_fn(&std::string::empty); data.erase(std::remove_if(data.begin(), data.end(), is_empty), data.end()); @@ -99,56 +97,131 @@ bool LuaCustomEntityCallback( } lua::NResultsOr LuaSnippetEmitter::MakeEntity(lua_State* L) { - int i, j; - std::string class_name; + lua::TableRef table; + if (!lua::Read(L, 2, &table)) { + return "[makeEntity] - Invalid argument, it must be a table."; + } + + double i, j, height; + std::string classname; std::unordered_map attrs; - if (!lua::Read(L, 2, &i) || - !lua::Read(L, 3, &j) || - !lua::Read(L, 4, &class_name)) { - return "Bad arguments"; + if (!table.LookUp("i", &i) || !table.LookUp("j", &j) || + !table.LookUp("classname", &classname)) { + return "[makeEntity] - Invalid arguments"; } - if (lua_gettop(L) == 5 && !lua::Read(L, 5, &attrs)) { - LOG(ERROR) << "Malformed attribute table in user callback; ignoring."; + if (!table.LookUp("height", &height)) { + height = 0; } - lua::Push(L, emitter_.AddEntity( - i, j, std::move(class_name), - std::vector>( - attrs.begin(), attrs.end()))); + if (table.Contains("attributes")) { + if (!table.LookUp("attributes", &attrs)) { + LOG(ERROR) << "[makeEntity] - Malformed attribute table in user " + "callback; ignoring."; + } + } + + lua::Push(L, + emitter_.AddEntity(i, j, height, std::move(classname), + std::vector>( + attrs.begin(), attrs.end()))); return 1; } lua::NResultsOr LuaSnippetEmitter::MakeSpawnPoint(lua_State* L) { - int i, j; - double angle_rad; + lua::TableRef table; + if (!lua::Read(L, 2, &table)) { + return "[makeSpawnPoint] - Invalid argument, it must be a table."; + } - if (!lua::Read(L, 2, &i) || !lua::Read(L, 3, &j)) { - return "Bad arguments"; + double i, j, height, angle_rad; + + if (!table.LookUp("i", &i) || !table.LookUp("j", &j)) { + return "[makeSpawnPoint] - Invalid arguments"; } - if (!lua::Read(L, 4, &angle_rad)) { + if (!table.LookUp("height", &height)) { + height = 0; + } + + if (!table.LookUp("angleRad", &angle_rad)) { angle_rad = 0.0; } - lua::Push(L, emitter_.AddSpawn(i, j, angle_rad)); + lua::Push(L, emitter_.AddSpawn(i, j, height, angle_rad)); return 1; } lua::NResultsOr LuaSnippetEmitter::MakeDoor(lua_State* L) { - int i, j; + lua::TableRef table; + if (!lua::Read(L, 2, &table)) { + return "[makeDoor] - Invalid argument, it must be a table."; + } + + double i, j; bool is_east_west; - if (!lua::Read(L, 2, &i) || - !lua::Read(L, 3, &j) || - !lua::Read(L, 4, &is_east_west)) { - return "Bad arguments"; + if (!table.LookUp("i", &i) || !table.LookUp("j", &j) || + !table.LookUp("isEastWest", &is_east_west)) { + return "[makeDoor] - Invalid arguments"; } lua::Push(L, emitter_.AddDoor(i, j, is_east_west ? 'I' : 'H')); return 1; } +lua::NResultsOr LuaSnippetEmitter::MakeFenceDoor(lua_State* L) { + lua::TableRef table; + if (!lua::Read(L, 2, &table)) { + return "[makeFenceDoor] - Invalid argument, it must be a table."; + } + + double i, j; + bool is_east_west; + + if (!table.LookUp("i", &i) || !table.LookUp("j", &j) || + !table.LookUp("isEastWest", &is_east_west)) { + return "[makeFenceDoor] - Invalid arguments"; + } + + lua::Push(L, emitter_.AddFenceDoor(i, j, is_east_west ? 'I' : 'H')); + return 1; +} + +lua::NResultsOr LuaSnippetEmitter::AddPlatform(lua_State* L) { + lua::TableRef table; + if (!lua::Read(L, 2, &table)) { + return "[addPlatform] - Invalid argument, it must be a table."; + } + + double i, j, height; + + if (!table.LookUp("i", &i) || !table.LookUp("j", &j) || + !table.LookUp("height", &height)) { + return "[addPlatform] - Invalid arguments"; + } + + lua::Push(L, emitter_.AddPlatform(i, j, height)); + return 1; +} + +lua::NResultsOr LuaSnippetEmitter::AddGlassColumn(lua_State* L) { + lua::TableRef table; + if (!lua::Read(L, 2, &table)) { + return "[addGlassColumn] - Invalid argument, it must be a table."; + } + + double i, j, height; + + if (!table.LookUp("i", &i) || !table.LookUp("j", &j) || + !table.LookUp("height", &height)) { + return "[addGlassColumn] - Invalid arguments"; + } + + lua::Push(L, emitter_.AddGlassColumn(i, j, height)); + return 1; +} + } // namespace lab } // namespace deepmind diff --git a/deepmind/level_generation/text_level/lua_bindings.h b/deepmind/level_generation/text_level/lua_bindings.h index 02bd8190..88ca8d21 100644 --- a/deepmind/level_generation/text_level/lua_bindings.h +++ b/deepmind/level_generation/text_level/lua_bindings.h @@ -81,9 +81,12 @@ class LuaSnippetEmitter : public lua::Class { // strings that are suitable for returning from the custom entity callback. static void Register(lua_State* L) { const Class::Reg methods[] = { - {"makeEntity", Member<&LuaSnippetEmitter::MakeEntity>}, - {"makeSpawnPoint", Member<&LuaSnippetEmitter::MakeSpawnPoint>}, - {"makeDoor", Member<&LuaSnippetEmitter::MakeDoor>}, + {"makeEntity", Member<&LuaSnippetEmitter::MakeEntity>}, + {"makeSpawnPoint", Member<&LuaSnippetEmitter::MakeSpawnPoint>}, + {"makeDoor", Member<&LuaSnippetEmitter::MakeDoor>}, + {"makeFenceDoor", Member<&LuaSnippetEmitter::MakeFenceDoor>}, + {"addPlatform", Member<&LuaSnippetEmitter::AddPlatform>}, + {"addGlassColumn", Member<&LuaSnippetEmitter::AddGlassColumn>}, }; Class::Register(L, methods); } @@ -91,24 +94,44 @@ class LuaSnippetEmitter : public lua::Class { explicit LuaSnippetEmitter(const MapSnippetEmitter* emitter) : emitter_(*emitter) {} - // makeEntity(i, j, className[, attributes]) + // makeEntity({i, j[, height], classname[, attributes]}), + // use table call conventions. // // Creates a custom entity at the centre of cell (i, j) with the given class // name and list of attributes (which must be a string-to-string map). + // If the height is given, the entity will be created at lua::NResultsOr MakeEntity(lua_State* L); - // makeSpawnPoint(i, j[, angle_rad]) + // makeSpawnPoint({i, j[, height, angleRad]}), use table call conventions. // // Creates a spawn point at cell (i, j) facing in direction of angle_rad, // which is the angle (in radians) counter-clockwise from West. lua::NResultsOr MakeSpawnPoint(lua_State* L); - // makeDoor(i, j, isEastWest) + // makeDoor({i, j, isEastWest}), use table call conventions. // // Creates a door in cell (i, j). If isEastWest is true, the door is of type // 'I', otherwise it is of type 'H'. lua::NResultsOr MakeDoor(lua_State* L); + // makeFenceDoor({i, j, isEastWest}), use table call conventions. + // + // Creates a fence door in cell (i, j). If isEastWest is true, the door is of + // type 'I', otherwise it is of type 'H'. + lua::NResultsOr MakeFenceDoor(lua_State* L); + + // makePlatform({i, j, height}), use table call conventions. + // + // Adds a platform cell in cell (i, j) and place the platform at the given + // height. + lua::NResultsOr AddPlatform(lua_State* L); + + // addGlassColumn({i, j, height}), use table call conventions. + // + // Adds an invisible column all the way from the ground to the given height in + // cell (i, j). + lua::NResultsOr AddGlassColumn(lua_State* L); + private: const MapSnippetEmitter& emitter_; }; diff --git a/deepmind/level_generation/text_level/lua_bindings_test.cc b/deepmind/level_generation/text_level/lua_bindings_test.cc index 0ffe3600..41fd9332 100644 --- a/deepmind/level_generation/text_level/lua_bindings_test.cc +++ b/deepmind/level_generation/text_level/lua_bindings_test.cc @@ -24,6 +24,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "deepmind/level_generation/text_level/text_level_settings.h" #include "deepmind/level_generation/text_level/translate_text_level.h" #include "deepmind/lua/call.h" #include "deepmind/lua/push_script.h" @@ -40,16 +41,36 @@ constexpr char kLuaFn[] = R"( i, j, ent, emitter = ... if ent == "x" then - return {emitter:makeEntity(i, j, "xyzzy", {a = "goodattr"}), - emitter:makeEntity(i, j, "xyzzy", {a = "badattr", b = 10})} + return { + emitter:makeEntity{ + i = i, + j = j, + classname = "xyzzy", + attributes = {a = "goodattr"} + }, + emitter:makeEntity{ + i = i, + j = j, + classname = "xyzzy", + attributes = {a = "badattr", b = 10} + } + } end if ent == "y" then - return emitter:makeDoor(i, j, true) + return emitter:makeDoor{ + i = i, + j = j, + isEastWest = true + } end if ent == "z" then - return emitter:makeSpawnPoint(i, j, 1.25) + return emitter:makeSpawnPoint{ + i = i, + j = j, + angleRad = 1.25 + } end if ent == "w" then @@ -86,7 +107,8 @@ TEST(LuaBindings, Simple) { using namespace std::placeholders; auto cb = std::bind(LuaCustomEntityCallback, L, -1, _1, _2, _3, _4, _5); - std::string s = TranslateTextLevel("* xyzw abc *\n", "", &rng, cb); + TextLevelSettings settings; + std::string s = TranslateTextLevel("* xyzw *\n", "", &rng, cb, &settings); // From 'x': EXPECT_THAT(s, HasSubstr("\"classname\" \"xyzzy\"")); @@ -101,9 +123,26 @@ TEST(LuaBindings, Simple) { // From 'w': EXPECT_THAT(s, HasSubstr("x\n\ny\n\nz\n\n")); +} + +TEST(LuaBindings, CallbackErrorIsFatal) { + auto vm = lua::CreateVm(); + lua_State* L = vm.get(); + std::mt19937_64 rng(123); + + LuaSnippetEmitter::Register(L); + + { + auto res = lua::PushScript(L, std::string(kLuaFn), "lua_test_function"); + CHECK(res.ok()); + CHECK_EQ(1, res.n_results()); + } + using namespace std::placeholders; + auto cb = std::bind(LuaCustomEntityCallback, L, -1, _1, _2, _3, _4, _5); - // From 'a', 'b', 'c': - EXPECT_THAT(s, Not(HasSubstr("poison"))); + TextLevelSettings settings; + EXPECT_DEATH(TranslateTextLevel("* abc *\n", "", &rng, cb, &settings), + "User callback invocation failed"); } } // namespace diff --git a/deepmind/level_generation/text_level/text_level_exporter.cc b/deepmind/level_generation/text_level/text_level_exporter.cc new file mode 100644 index 00000000..c552996a --- /dev/null +++ b/deepmind/level_generation/text_level/text_level_exporter.cc @@ -0,0 +1,324 @@ +// Copyright (C) 2016 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "deepmind/level_generation/text_level/text_level_exporter.h" + +#include "absl/strings/str_cat.h" +#include "Eigen/Geometry" +#include "deepmind/level_generation/map_builder/entity.h" + +namespace deepmind { +namespace lab { +namespace { + +// Platform thickness, expressed in height units. +constexpr int kPlatformThickness = 2; +// Thickness of the border on the platforms edge (in maze cell units). +constexpr double kBorderThickness = 0.075; + +// Default size used for skybox. +const Eigen::Vector2i kSkyboxTextureSize = {1024, 1024}; + +struct TextureFile { + std::string name; + int width; + int height; +}; + +map_builder::Texture MapBuilderTexture(Theme::Texture texture) { + return {std::move(texture.name), + {0, 0}, + texture.angle, + {TextLevelExporter::kTexelSize / texture.width * texture.scale, + TextLevelExporter::kTexelSize / texture.height * texture.scale}}; +} + +} // namespace + +TextLevelExporter::TextLevelExporter(TextLevelSettings* settings) + : settings_(settings) { + builder_.mutable_world_entity()->set_attribute("light", absl::StrCat(100)); + builder_.mutable_world_entity()->set_attribute("worldtype", absl::StrCat(2)); +} + +void TextLevelExporter::SetBoundingBox(Eigen::Vector3d size) { + bounding_box_size_ = std::move(size); + bounding_box_size_.z() *= settings_->ceiling_scale; +} + +map_builder::Patch TextLevelExporter::GenerateWallDecal( + const Theme::Texture& texture, const Eigen::Vector3d& a, + const Eigen::Vector3d& b, const Eigen::Vector3d& interior_direction) { + const Eigen::Vector3d kUp = {0, 0, 1}; + Eigen::Vector3d pos = (a + b) * (0.5 * settings_->cell_size); + // Push decal slightly away from wall. + double scaleX = texture.scale; + double scaleY = texture.scale; + if (texture.width > texture.height) { + scaleY *= static_cast(texture.height) / + static_cast(texture.width); + } else if (texture.width < texture.height) { + scaleX *= static_cast(texture.width) / + static_cast(texture.height); + } + + pos += (2.0 / map_builder::kWorldToGameUnits) * interior_direction; + Eigen::Vector3d up = Eigen::AngleAxis( + map_builder::Angle::Degrees(texture.angle).radians(), + interior_direction) * + kUp; + return map_builder::brush_util::CreateGridPatch( + pos, -interior_direction, up, {scaleX, scaleY}, {3, 3}, {texture.name}); +} + +void TextLevelExporter::AddWall(const Eigen::Vector3d& a, + const Eigen::Vector3d& b, + const Eigen::Vector3d& interior_direction, + const Eigen::Vector2i& cell, + CellGroup group_number) { + Theme::Direction direction = Theme::Direction::South; + if (interior_direction.x() > 0) { + direction = Theme::Direction::West; + } else if (interior_direction.x() < 0) { + direction = Theme::Direction::East; + } else if (interior_direction.y() < 0) { + direction = Theme::Direction::North; + } + + auto& wall_textures = wall_textures_[static_cast(direction)]; + auto wall_tex_it = wall_textures.find(group_number); + if (wall_tex_it == wall_textures.end()) { + wall_textures[group_number] = + MapBuilderTexture(settings_->theme->wall(group_number, direction)); + } + + builder_.mutable_world_entity()->add_brush( + map_builder::brush_util::CreateBoxBrush( + a * settings_->cell_size + + Eigen::Vector3d::Zero().cwiseMin(interior_direction) / + map_builder::kWorldToGameUnits, + b * settings_->cell_size + + Eigen::Vector3d::Zero().cwiseMax(interior_direction) / + map_builder::kWorldToGameUnits, + wall_textures[group_number])); + + wall_art_locations_.push_back( + WallArtLocation{a, b, interior_direction, cell, group_number, direction}); +} + +void TextLevelExporter::AddFloor(const Eigen::Vector3d& a, + const Eigen::Vector3d& b, + const Eigen::Vector2i& cell, + CellGroup group_number) { + // Walls must have a thickness > 0 to be rendered. + Eigen::Vector3d thickness_offset(0, 0, + (1.0 / map_builder::kWorldToGameUnits)); + + if (floor_textures_.find(group_number) == floor_textures_.end()) { + floor_textures_[group_number] = + MapBuilderTexture(settings_->theme->floor(group_number)); + ceiling_textures_[group_number] = + MapBuilderTexture(settings_->theme->ceiling(group_number)); + } + + auto* world_entity = builder_.mutable_world_entity(); + + world_entity->add_brush(map_builder::brush_util::CreateBoxBrush( + a * settings_->cell_size, b * settings_->cell_size + thickness_offset, + floor_textures_[group_number])); + + // Don't add ceiling if we have a skybox set. + if (settings_->skybox_texture_name.empty()) { + Eigen::Vector3d ceiling_height(0, 0, bounding_box_size_.z()); + world_entity->add_brush(map_builder::brush_util::CreateBoxBrush( + (a + ceiling_height) * settings_->cell_size - thickness_offset, + (b + ceiling_height) * settings_->cell_size, + ceiling_textures_[group_number])); + } + floor_art_locations_.push_back({(a + b) / 2, cell, group_number}); +} + +map_builder::Entity TextLevelExporter::MakeEntity( + const Eigen::Vector3d& position, std::string class_name, + const std::vector>& attributes) { + map_builder::Entity entity(std::move(class_name), + position * settings_->cell_size); + entity.set_attributes(attributes); + return entity; +} + +map_builder::Entity TextLevelExporter::MakeEntityWithRealOffset( + const Eigen::Vector3d& position, const Eigen::Vector3d& offset, + std::string class_name, + const std::vector>& attributes) { + map_builder::Entity entity(std::move(class_name), + position * settings_->cell_size + offset); + entity.set_attributes(attributes); + return entity; +} + +map_builder::Entity TextLevelExporter::MakeBrushEntity( + const Eigen::Vector3d& min, const Eigen::Vector3d& max, + std::string class_name, std::string texture_name, + double texture_scale_width, double texture_scale_height, + const std::vector>& attributes) { + map_builder::Entity entity(std::move(class_name)); + map_builder::Texture texture = + texture_name.empty() + ? floor_textures_[0] + : map_builder::Texture{std::move(texture_name), {0, 0}, 0, + {kTexelSize * texture_scale_width, + kTexelSize * texture_scale_height}}; + entity.add_brush(map_builder::brush_util::CreateBoxBrush( + settings_->cell_size * min, settings_->cell_size * max, texture)); + entity.set_attributes(attributes); + return entity; +} + +map_builder::Entity TextLevelExporter::MakeFittedBrushEntity( + const Eigen::Vector3d& min, const Eigen::Vector3d& max, + std::string class_name, const std::string& texture_name, + double texture_width, double texture_height, + const std::vector>& attributes) { + map_builder::Entity entity(std::move(class_name)); + entity.add_brush(map_builder::brush_util::CreateFittedBoxBrush( + settings_->cell_size * min, settings_->cell_size * max, texture_name, + {texture_width, texture_height})); + entity.set_attributes(attributes); + return entity; +} + +map_builder::Entity TextLevelExporter::MakeBrushEntity( + const std::vector>& blocks, + std::string class_name, + std::string texture_name, + double texture_scale_width, + double texture_scale_height, + const std::vector>& attributes) { + map_builder::Entity entity(std::move(class_name)); + map_builder::Texture texture = + texture_name.empty() + ? floor_textures_[0] + : map_builder::Texture{std::move(texture_name), + {0, 0}, + 0, + {kTexelSize * texture_scale_width, + kTexelSize * texture_scale_height}}; + for (auto& block : blocks) { + entity.add_brush(map_builder::brush_util::CreateBoxBrush( + settings_->cell_size * block.first, settings_->cell_size * block.second, + texture)); + } + entity.set_attributes(attributes); + return entity; +} + +void TextLevelExporter::AddPlatform(double x, double y, int height) { + if (floor_textures_.find(height) == floor_textures_.end()) { + floor_textures_[height] = + MapBuilderTexture(settings_->theme->floor(height)); + } + if (riser_texture_.path.empty()) { + riser_texture_ = MapBuilderTexture(settings_->theme->platform_riser()); + } + + if (tread_texture_.path.empty()) { + tread_texture_ = MapBuilderTexture(settings_->theme->platform_tread()); + } + // Floor must have a thickness > 0 to be rendered. + double min_thickness = 1.0 / map_builder::kWorldToGameUnits; + double z = height * kHeightScale - min_thickness; + + auto* world_entity = builder_.mutable_world_entity(); + + // Add the platform brick base. + Eigen::Vector3d brick_min(x, y, z - kPlatformThickness * kHeightScale); + Eigen::Vector3d brick_max(x + 1.0, y + 1.0, z - kBorderThickness); + world_entity->add_brush(map_builder::brush_util::CreateBoxBrush( + brick_min * settings_->cell_size, brick_max * settings_->cell_size, + riser_texture_)); + + // Add a black border on top of the platform base. + Eigen::Vector3d base_min(x, y, z - kBorderThickness); + Eigen::Vector3d base_max(x + 1.0, y + 1.0, z); + world_entity->add_brush(map_builder::brush_util::CreateBoxBrush( + base_min * settings_->cell_size, base_max * settings_->cell_size, + tread_texture_)); + + // Add the flooring layer on top of the platform base and border. + Eigen::Vector3d floor_min(x, y, z); + Eigen::Vector3d floor_max(x + 1.0, y + 1.0, z + min_thickness); + world_entity->add_brush(map_builder::brush_util::CreateBoxBrush( + floor_min * settings_->cell_size, floor_max * settings_->cell_size, + floor_textures_[height])); +} + +void TextLevelExporter::AddGlassColumn(double x, double y, int height) { + Eigen::Vector3d a(x, y, 0); + Eigen::Vector3d b(x + 1.0, y + 1.0, height * kHeightScale); + builder_.mutable_world_entity()->add_brush( + map_builder::brush_util::CreateBoxBrush( + a * settings_->cell_size, b * settings_->cell_size, glass_texture_)); +} + +map_builder::Entity TextLevelExporter::MakeLight( + const Eigen::Vector3d& position, double intensity) { + return MakeEntity( + position, "light", + // Default light brightness is 200. + {{"light", absl::StrCat(intensity * 5 * settings_->light_intensity)}, + {"style", "0"}, + {"spawnflags", "0"}}); +} + +void TextLevelExporter::Finalize() { + auto floor_decorations = + settings_->theme->FloorDecorations(floor_art_locations_); + for (const auto& decoration : floor_decorations) { + if (!decoration.model.name.empty()) { + Add(MakeEntityWithRealOffset( + decoration.location.location, Eigen::Vector3d(0, 0, 1), "misc_model", + {{"model", decoration.model.name}, + {"angle", absl::StrCat(decoration.model.angle)}, + {"modelscale", absl::StrCat(decoration.model.scale)}})); + } + } + + auto wall_decorations = + settings_->theme->WallDecorations(wall_art_locations_); + for (const auto& decoration : wall_decorations) { + if (!decoration.texture.name.empty()) { + builder_.mutable_world_entity()->add_patch( + GenerateWallDecal(decoration.texture, decoration.location.top_left, + decoration.location.bottom_right, + decoration.location.interior_direction)); + } + } + + // Add skybox. + if (!settings_->skybox_texture_name.empty()) { + Eigen::Vector3d cell_size = Eigen::Vector3d::Constant(settings_->cell_size); + auto world_size = (bounding_box_size_ * settings_->cell_size) + cell_size; + auto pos = (world_size - cell_size) * 0.5; + builder_.AddSkybox(pos, world_size, settings_->skybox_texture_name, + kSkyboxTextureSize); + } +} + +} // namespace lab +} // namespace deepmind diff --git a/deepmind/level_generation/text_level/text_maze_exporter.h b/deepmind/level_generation/text_level/text_level_exporter.h similarity index 51% rename from deepmind/level_generation/text_level/text_maze_exporter.h rename to deepmind/level_generation/text_level/text_level_exporter.h index 7af31d7d..da8c8df2 100644 --- a/deepmind/level_generation/text_level/text_maze_exporter.h +++ b/deepmind/level_generation/text_level/text_level_exporter.h @@ -19,61 +19,41 @@ // TextMazeExporter: An internal data structure to manage the translation of an // absract "maze" entity into a .map format representation. -#ifndef DML_DEEPMIND_LEVEL_GENERATION_TEXT_LEVEL_TEXT_MAZE_EXPORTER_H_ -#define DML_DEEPMIND_LEVEL_GENERATION_TEXT_LEVEL_TEXT_MAZE_EXPORTER_H_ +#ifndef DML_DEEPMIND_LEVEL_GENERATION_TEXT_LEVEL_TEXT_LEVEL_EXPORTER_H_ +#define DML_DEEPMIND_LEVEL_GENERATION_TEXT_LEVEL_TEXT_LEVEL_EXPORTER_H_ +#include #include #include #include +#include #include #include "Eigen/Core" #include "deepmind/level_generation/map_builder/brush.h" #include "deepmind/level_generation/map_builder/builder.h" #include "deepmind/level_generation/map_builder/entity.h" +#include "deepmind/level_generation/text_level/text_level_settings.h" namespace deepmind { namespace lab { +constexpr char kGlassTexture[] = "map/poltergeist"; // Creates a Quake3 .map file from a given maze. Uses map_builder::Builder to // build up and generate the actual file. -class TextMazeExporter { +class TextLevelExporter { public: using CellGroup = int; - enum class Theme { - kDefault, - TRON, - MINESWEEPER, - TETRIS, - GO, - PACMAN, - MISHMASH, - }; + // Scale the textures to match the 100 units cell size. + static constexpr double kTexelSize = 100.0; - struct Settings { - Settings() - : theme(Theme::kDefault), - wall_decal_frequency(0.1), - floor_object_frequency(0.05), - cell_size(100.0 / map_builder::kWorldToGameUnits), - ceiling_scale(1.0), - light_intensity(1.0) {} - - Theme theme; - std::string skybox_texture_name; - double wall_decal_frequency; - double floor_object_frequency; - double cell_size; - double ceiling_scale; - double light_intensity; - }; + // A scaling factor for the platforms height. + static constexpr double kHeightScale = 0.2; - explicit TextMazeExporter( - std::mt19937_64* rng, - const Settings& settings = Settings()); + explicit TextLevelExporter(TextLevelSettings* settings); - const Settings& GetSettings() const { return settings_; } + const TextLevelSettings& GetSettings() const { return *settings_; } // Call Finalize() before calling ToString(). void Finalize(); @@ -84,21 +64,22 @@ class TextMazeExporter { void SetBoundingBox(Eigen::Vector3d size); void AddFloor( - Eigen::Vector3d a, - Eigen::Vector3d b, + const Eigen::Vector3d& a, + const Eigen::Vector3d& b, + const Eigen::Vector2i& cell, CellGroup group_number); void AddWall( - Eigen::Vector3d a, - Eigen::Vector3d b, - Eigen::Vector3d interior_direction, + const Eigen::Vector3d& a, + const Eigen::Vector3d& b, + const Eigen::Vector3d& interior_direction, + const Eigen::Vector2i& cell, CellGroup group_number); - void AddWall( - Eigen::Vector3d a, - Eigen::Vector3d b, - Eigen::Vector3d interior_direction, - const map_builder::Texture& texture); + + void AddPlatform(double x, double y, int height); + + void AddGlassColumn(double x, double y, int height); void Add(map_builder::Entity entity) { builder_.AddEntity(std::move(entity)); @@ -106,42 +87,62 @@ class TextMazeExporter { // Creates a light source. map_builder::Entity MakeLight( - Eigen::Vector3d position, + const Eigen::Vector3d& position, double intensity); // Creates a generic entity, scaling the position by the cell size. map_builder::Entity MakeEntity( - Eigen::Vector3d position, + const Eigen::Vector3d& position, std::string class_name, - std::vector> attributes); + const std::vector>& attributes); // Creates a generic entity, scaling the position by the cell size // and adding an offset. map_builder::Entity MakeEntityWithRealOffset( - Eigen::Vector3d position, - Eigen::Vector3d offset, + const Eigen::Vector3d& position, + const Eigen::Vector3d& offset, std::string class_name, - std::vector> attributes); + const std::vector>& attributes); // Creates a brush entity with a given texture. To make the texture fit the // brush, set texture_scale_{width, height} to be the size of the brush // divided by the size of the texture. If they are set to zero, the default // scale is used. map_builder::Entity MakeBrushEntity( - Eigen::Vector3d min, - Eigen::Vector3d max, + const Eigen::Vector3d& min, + const Eigen::Vector3d& max, std::string class_name, std::string texture_name, double texture_scale_width, double texture_scale_height, - std::vector> attributes); + const std::vector>& attributes); + + // Creates a brush entity with a given texture. The texture is fitted to the + // brush according to its size. + map_builder::Entity MakeFittedBrushEntity( + const Eigen::Vector3d& min, + const Eigen::Vector3d& max, + std::string class_name, + const std::string& texture_name, + double texture_width, + double texture_height, + const std::vector>& attributes); + + // Creates a brush entity with a series of blocks and a given texture. To make + // the texture fit the brush, set texture_scale_{width, height} to be the size + // of the brush divided by the size of the texture. If they are set to zero, + // the default scale is used. + map_builder::Entity MakeBrushEntity( + const std::vector>& blocks, + std::string class_name, + std::string texture_name, + double texture_scale_width, + double texture_scale_height, + const std::vector>& attributes); private: - struct ArtLocation { - Eigen::Vector3d a; - Eigen::Vector3d b; - Eigen::Vector3d interior_direction; - }; + using WallArtLocation = Theme::WallArtLocation; + using FloorArtLocation = Theme::FloorArtLocation; // Set of textures for a theme. struct TextureSet { @@ -154,52 +155,35 @@ class TextMazeExporter { std::vector ceiling; std::vector wall; std::vector wall_decals; + std::vector brick; std::vector floor_models; - std::vector wall_models; }; map_builder::Patch GenerateWallDecal( - const std::string& texture, - double size, - Eigen::Vector3d a, - Eigen::Vector3d b, - Eigen::Vector3d interior_direction); + const Theme::Texture& texture, + const Eigen::Vector3d& a, + const Eigen::Vector3d& b, + const Eigen::Vector3d& interior_direction); - map_builder::Patch GenerateWallDecal( - std::size_t texture_index, - Eigen::Vector3d a, - Eigen::Vector3d b, - Eigen::Vector3d interior_direction); - - map_builder::Entity GenerateWallModel( - std::size_t model_index, - Eigen::Vector3d a, - Eigen::Vector3d b, - Eigen::Vector3d interior_direction); - - std::size_t Unbiased(std::size_t n) { - return size_dist( - *rng_, - std::uniform_int_distribution::param_type(0, n - 1)); - } - - std::mt19937_64* rng_; - std::uniform_int_distribution size_dist; - std::uniform_real_distribution real_dist; - - const Settings settings_; + TextLevelSettings* settings_; std::map floor_textures_; std::map ceiling_textures_; - std::map wall_textures_; + map_builder::Texture riser_texture_; + map_builder::Texture tread_texture_; + std::array, 4> wall_textures_; map_builder::Builder builder_; TextureSet texture_set_; - std::vector art_locations_; + std::vector wall_art_locations_; + + std::vector floor_art_locations_; Eigen::Vector3d bounding_box_size_; + + map_builder::Texture glass_texture_{kGlassTexture, {0, 0}, 0, {0, 0}}; }; } // namespace lab } // namespace deepmind -#endif // DML_DEEPMIND_LEVEL_GENERATION_TEXT_LEVEL_TEXT_MAZE_EXPORTER_H_ +#endif // DML_DEEPMIND_LEVEL_GENERATION_TEXT_LEVEL_TEXT_LEVEL_EXPORTER_H_ diff --git a/deepmind/level_generation/text_level/text_maze_exporter_test.cc b/deepmind/level_generation/text_level/text_level_exporter_test.cc similarity index 50% rename from deepmind/level_generation/text_level/text_maze_exporter_test.cc rename to deepmind/level_generation/text_level/text_level_exporter_test.cc index 7cda839a..f8b555ac 100644 --- a/deepmind/level_generation/text_level/text_maze_exporter_test.cc +++ b/deepmind/level_generation/text_level/text_level_exporter_test.cc @@ -16,7 +16,7 @@ // //////////////////////////////////////////////////////////////////////////////// -#include "deepmind/level_generation/text_level/text_maze_exporter.h" +#include "deepmind/level_generation/text_level/text_level_exporter.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -27,15 +27,15 @@ namespace { using ::testing::Eq; -class TextMazeExporterTest : public ::testing::Test { +class TextLevelExporterTest : public ::testing::Test { protected: - TextMazeExporterTest() : rng_(123) {} - std::mt19937_64 rng_; + TextLevelExporterTest() {} + TextLevelSettings settings_; }; -TEST_F(TextMazeExporterTest, AddWall) { - TextMazeExporter exporter(&rng_); - exporter.AddWall({0.105, 0.2, 0.3}, {0.4, 0.5, 60000}, {0, 0, 0}, 0); +TEST_F(TextLevelExporterTest, AddWall) { + TextLevelExporter exporter(&settings_); + exporter.AddWall({0.105, 0.2, 0.3}, {0.4, 0.5, 60000}, {0, 0, 0}, {1, 1}, 0); const char kExpectedSubstr[] = R"({ "classname" "worldspawn" @@ -49,13 +49,14 @@ TEST_F(TextMazeExporterTest, AddWall) { ( 0 0 30 ) ( 32 0 30 ) ( 0 32 30 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 ( 0 0 6e+06 ) ( 0 32 6e+06 ) ( 32 0 6e+06 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } -})"; +} +)"; EXPECT_THAT(exporter.ToString(), Eq(kExpectedSubstr)); } -TEST_F(TextMazeExporterTest, AddLight) { - TextMazeExporter exporter(&rng_); +TEST_F(TextLevelExporterTest, AddLight) { + TextLevelExporter exporter(&settings_); exporter.Add(exporter.MakeLight({1, 2, 3}, 50)); const char kExpectedSubstr[] = R"( @@ -70,8 +71,68 @@ TEST_F(TextMazeExporterTest, AddLight) { ASSERT_THAT(exporter.ToString(), testing::HasSubstr(kExpectedSubstr)); } -TEST_F(TextMazeExporterTest, AddBrushEntity) { - TextMazeExporter exporter(&rng_); +TEST_F(TextLevelExporterTest, AddPlatform) { + TextLevelExporter exporter(&settings_); + exporter.AddPlatform(3, 4, 7); + + const char kExpectedSubstr[] = R"({ + "classname" "worldspawn" + "light" "100" + "worldtype" "2" + { + ( 300 0 0 ) ( 300 32 0 ) ( 300 0 32 ) map/lab_games/lg_style_02_wall_blue 0 0 0 0.0976562 0.0976562 0 0 0 + ( 400 0 0 ) ( 400 0 32 ) ( 400 32 0 ) map/lab_games/lg_style_02_wall_blue 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 400 0 ) ( 0 400 32 ) ( 32 400 0 ) map/lab_games/lg_style_02_wall_blue 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 500 0 ) ( 32 500 0 ) ( 0 500 32 ) map/lab_games/lg_style_02_wall_blue 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 96.875 ) ( 32 0 96.875 ) ( 0 32 96.875 ) map/lab_games/lg_style_02_wall_blue 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 129.375 ) ( 0 32 129.375 ) ( 32 0 129.375 ) map/lab_games/lg_style_02_wall_blue 0 0 0 0.0976562 0.0976562 0 0 0 + } + { + ( 300 0 0 ) ( 300 32 0 ) ( 300 0 32 ) map/black_d 0 0 0 1.5625 1.5625 0 0 0 + ( 400 0 0 ) ( 400 0 32 ) ( 400 32 0 ) map/black_d 0 0 0 1.5625 1.5625 0 0 0 + ( 0 400 0 ) ( 0 400 32 ) ( 32 400 0 ) map/black_d 0 0 0 1.5625 1.5625 0 0 0 + ( 0 500 0 ) ( 32 500 0 ) ( 0 500 32 ) map/black_d 0 0 0 1.5625 1.5625 0 0 0 + ( 0 0 129.375 ) ( 32 0 129.375 ) ( 0 32 129.375 ) map/black_d 0 0 0 1.5625 1.5625 0 0 0 + ( 0 0 136.875 ) ( 0 32 136.875 ) ( 32 0 136.875 ) map/black_d 0 0 0 1.5625 1.5625 0 0 0 + } + { + ( 300 0 0 ) ( 300 32 0 ) ( 300 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 400 0 0 ) ( 400 0 32 ) ( 400 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 400 0 ) ( 0 400 32 ) ( 32 400 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 500 0 ) ( 32 500 0 ) ( 0 500 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 136.875 ) ( 32 0 136.875 ) ( 0 32 136.875 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 140 ) ( 0 32 140 ) ( 32 0 140 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + } +} +)"; + + EXPECT_THAT(exporter.ToString(), Eq(kExpectedSubstr)); +} + +TEST_F(TextLevelExporterTest, AddGlassColumn) { + TextLevelExporter exporter(&settings_); + exporter.AddGlassColumn(2, 5, 8); + + const char kExpectedSubstr[] = R"({ + "classname" "worldspawn" + "light" "100" + "worldtype" "2" + { + ( 200 0 0 ) ( 200 32 0 ) ( 200 0 32 ) map/poltergeist 0 0 0 0 0 0 0 0 + ( 300 0 0 ) ( 300 0 32 ) ( 300 32 0 ) map/poltergeist 0 0 0 0 0 0 0 0 + ( 0 500 0 ) ( 0 500 32 ) ( 32 500 0 ) map/poltergeist 0 0 0 0 0 0 0 0 + ( 0 600 0 ) ( 32 600 0 ) ( 0 600 32 ) map/poltergeist 0 0 0 0 0 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/poltergeist 0 0 0 0 0 0 0 0 + ( 0 0 160 ) ( 0 32 160 ) ( 32 0 160 ) map/poltergeist 0 0 0 0 0 0 0 0 + } +} +)"; + + EXPECT_THAT(exporter.ToString(), Eq(kExpectedSubstr)); +} + +TEST_F(TextLevelExporterTest, AddBrushEntity) { + TextLevelExporter exporter(&settings_); exporter.Add(exporter.MakeBrushEntity( {150, 200, 10}, {170, 230, 50}, "func_door", "maps/brush_entity_placeholder", 0, 0, @@ -95,8 +156,8 @@ TEST_F(TextMazeExporterTest, AddBrushEntity) { ASSERT_THAT(exporter.ToString(), testing::HasSubstr(kExpectedSubstr)); } -TEST_F(TextMazeExporterTest, AddEntity) { - TextMazeExporter exporter(&rng_); +TEST_F(TextLevelExporterTest, AddEntity) { + TextLevelExporter exporter(&settings_); exporter.Add(exporter.MakeEntity( {1, 2, 3}, "custom_class", {{"key_00", "value_00"}, {"key_01", "value_01"}, {"key_02", "value_02"}})); diff --git a/deepmind/level_generation/text_level/text_level_settings.h b/deepmind/level_generation/text_level/text_level_settings.h new file mode 100644 index 00000000..17dda49e --- /dev/null +++ b/deepmind/level_generation/text_level/text_level_settings.h @@ -0,0 +1,209 @@ +#ifndef DML_DEEPMIND_LEVEL_GENERATION_TEXT_LEVEL_TEXT_LEVEL_SETTINGS_H_ +#define DML_DEEPMIND_LEVEL_GENERATION_TEXT_LEVEL_TEXT_LEVEL_SETTINGS_H_ + +#include +#include +#include + +#include "Eigen/Core" +#include "deepmind/level_generation/map_builder/builder.h" + +namespace deepmind { +namespace lab { + +class Theme { + public: + enum class Direction { + North, + East, + South, + West, + }; + + struct WallArtLocation { + // Corner placement of wall art. + Eigen::Vector3d top_left; + + // Corner placement of wall art. + Eigen::Vector3d bottom_right; + + // Normal of the wall the texture is applied to. + Eigen::Vector3d interior_direction; + + // Cell to place art. + Eigen::Vector2i cell; + + // Variation the wall pointing at. + int variation; + + // Closest direction the interior_direction is pointing to. + Direction direction; + }; + + struct FloorArtLocation { + // Floor position to place art. + Eigen::Vector3d location; + + // Floor cell to place art. + Eigen::Vector2i cell; + + // Variation of cell at location. + int variation; + }; + + struct Model { + // Path to an md3 model relative to the assets directory. + std::string name; + + // Scale of the model relative to design scale. + double scale; + + // Angle in degrees to place the model. + double angle; + }; + + struct FloorDecoration { + FloorArtLocation location; + Model model; + }; + + struct Texture { + // Path to texture relative to the assets directory. + std::string name; + + // Shape of the source texture in pixels. This ensures the texture matches + // the brush or patch is being applied to. + int width; + int height; + + // Uniformily scale the texture. Set to 1.0 for default scaling. + double scale; + + // Angle in degrees to place texture on brush or patches. + double angle; + }; + + struct WallDecoration { + WallArtLocation location; + Texture texture; + }; + + virtual ~Theme() = default; + + // Called when generating walls for a level. Returns a wall texture for that + // variation, direction combination. + virtual Texture wall(int variation, Direction direction) = 0; + + // Called when generating floors for a level. Returns a floor texture for that + // variation, direction combination. + virtual Texture floor(int variation) = 0; + + // Called when generating ceiling for a level. Returns a ceiling texture for + // that variation, direction combination. + virtual Texture ceiling(int variation) = 0; + + // Platforms + // floor ^ + // +-----------+ > floor. (One unit high. Thin) + // | | > platform_tread. + // +---------- + + // | | > platform_riser. + // | | + // | | + // | | + // +---------- + + // Called when generating platforms for a level. Returns a wall texture for + // that variation, direction combination. + virtual Texture platform_riser() = 0; + + // Called once for each variation used in the level being generated. Return an + // appropiate texture for that location. + virtual Texture platform_tread() = 0; + + virtual std::vector WallDecorations( + const std::vector& wall_locations) = 0; + virtual std::vector FloorDecorations( + const std::vector& floor_locations) = 0; +}; + +class NullTheme : public Theme { + public: + Texture floor(int variation) override { + return {"map/lab_games/lg_style_01_floor_orange", 1024, 1024, 1.0, 0.0}; + } + + Texture wall(int variation, Direction direction) override { + return {"map/lab_games/lg_style_01_wall_green", 1024, 1024, 1.0, 0.0}; + } + + Texture ceiling(int variation) override { + return {"map/lab_games/fake_sky", 1024, 1024, 1.0, 0.0}; + } + + Texture platform_riser() override { + return {"map/lab_games/lg_style_02_wall_blue", 1024, 1024, 1.0, 0.0}; + } + + Texture platform_tread() override { + return {"map/black_d", 64, 64, 1.0, 0.0}; + } + + std::vector WallDecorations( + const std::vector& wall_locations) override { + std::vector decs; + decs.reserve(wall_locations.size()); + for (const auto& location : wall_locations) { + WallDecoration dec; + dec.location = location; + dec.texture = + Texture{"decal/lab_games/dec_img_style01_001", 1024, 1024, 1.0, 90.0}; + decs.push_back(std::move(dec)); + } + return decs; + } + + std::vector FloorDecorations( + const std::vector& floor_locations) override { + std::vector decs; + decs.reserve(floor_locations.size()); + for (const auto& location : floor_locations) { + FloorDecoration dec; + dec.location = location; + dec.model = Model{"models/hr_tv.md3", 1.0, 0.0}; + decs.push_back(std::move(dec)); + } + decs.clear(); + return decs; + } +}; + +struct TextLevelSettings { + TextLevelSettings() + : theme(new NullTheme), + wall_decal_frequency(0.1), + floor_object_frequency(0.05), + cell_size(100.0 / map_builder::kWorldToGameUnits), + ceiling_scale(1.0), + light_intensity(1.0), + ceiling_height(1.0), + draw_default_layout(true) {} + + std::unique_ptr theme; + std::string skybox_texture_name; + double wall_decal_frequency; + double floor_object_frequency; + double cell_size; + double ceiling_scale; + double light_intensity; + // Decides the height of the maze bounding box, 1.0 by default, can be set + // from lua side for the multi-floor levels, e.g. platform levels. + double ceiling_height; + // Decides whether to draw the default walls and floors, true by default, need + // to be switch off for platform levels. + bool draw_default_layout; +}; + +} // namespace lab +} // namespace deepmind + +#endif // DML_DEEPMIND_LEVEL_GENERATION_TEXT_LEVEL_TEXT_LEVEL_SETTINGS_H_ diff --git a/deepmind/level_generation/text_level/text_maze_exporter.cc b/deepmind/level_generation/text_level/text_maze_exporter.cc deleted file mode 100644 index 172d31b6..00000000 --- a/deepmind/level_generation/text_level/text_maze_exporter.cc +++ /dev/null @@ -1,483 +0,0 @@ -// Copyright (C) 2016 Google Inc. -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 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., -// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// -//////////////////////////////////////////////////////////////////////////////// - -#include "deepmind/level_generation/text_level/text_maze_exporter.h" - -#include "deepmind/support/logging.h" -#include "deepmind/support/stringprintf.h" -#include "deepmind/support/str_cat.h" -#include "Eigen/Geometry" -#include "deepmind/level_generation/map_builder/entity.h" - -namespace deepmind { -namespace lab { -namespace { - -// Scale the textures to match the 100 units cell size. -constexpr double kTexelSize = 100.0; - -// Default size used for skybox. -const Eigen::Vector2i kSkyboxTextureSize = {1024, 1024}; - -struct TextureFile { - std::string name; - int width; - int height; -}; - -std::vector BuildDecalList( - const std::string& filename, - int num_decals, - int width, - int height) { - std::vector files; - for (int i = 0; i < num_decals; i++) { - files.push_back( - {StringPrintf("%s_%03d", filename.c_str(), i + 1), width, height}); - } - return files; -} - -std::vector JoinDecalLists( - const std::vector>& lists) { - std::vector files; - for (const auto& list : lists) { - for (const auto& file : list) { - files.push_back(file); - } - } - return files; -} - -std::vector BuildTextures( - const std::string& prefix, - const std::vector& files) { - std::vector textures; - for (const auto& file : files) { - textures.push_back(map_builder::Texture{ - prefix + "/" + file.name, - {0, 0}, - 0, - {kTexelSize / file.width, kTexelSize / file.height}}); - } - return textures; -} - -} // namespace - -TextMazeExporter::TextMazeExporter( - std::mt19937_64* rng, - const Settings& settings) - : rng_(rng), - settings_(settings) { - builder_.mutable_world_entity()->set_attribute("light", StrCat(100)); - builder_.mutable_world_entity()->set_attribute("worldtype", StrCat(2)); - - // Shared decal textures among all themes. - auto decal_textures = BuildTextures( - "decal/lab_games", - JoinDecalLists({BuildDecalList("dec_img_style01", 20, 1024, 1024), - BuildDecalList("dec_img_style02", 20, 1024, 1024), - BuildDecalList("dec_img_style03", 20, 1024, 1024), - BuildDecalList("dec_img_style04", 20, 1024, 1024)})); - - switch (settings_.theme) { - case Theme::kDefault: - case Theme::MISHMASH: - texture_set_ = TextureSet{ - BuildTextures("map/lab_games", - {{"lg_style_01_floor_orange", 1024, 1024}, - {"lg_style_01_floor_orange_bright", 1024, 1024}, - {"lg_style_01_floor_blue", 1024, 1024}, - {"lg_style_01_floor_blue_bright", 1024, 1024}, - {"lg_style_02_floor_blue", 1024, 1024}, - {"lg_style_02_floor_blue_bright", 1024, 1024}, - {"lg_style_02_floor_green", 1024, 1024}, - {"lg_style_02_floor_green_bright", 1024, 1024}, - {"lg_style_03_floor_green", 1024, 1024}, - {"lg_style_03_floor_green_bright", 1024, 1024}, - {"lg_style_03_floor_blue", 1024, 1024}, - {"lg_style_03_floor_blue_bright", 1024, 1024}, - {"lg_style_04_floor_blue", 1024, 1024}, - {"lg_style_04_floor_blue_bright", 1024, 1024}, - {"lg_style_04_floor_orange", 1024, 1024}, - {"lg_style_04_floor_orange_bright", 1024, 1024}, - {"lg_style_05_floor_blue", 1024, 1024}, - {"lg_style_05_floor_blue_bright", 1024, 1024}, - {"lg_style_05_floor_orange", 1024, 1024}, - {"lg_style_05_floor_orange_bright", 1024, 1024}}), - - BuildTextures("map/lab_games", {{"fake_sky", 1024, 1024}}), - BuildTextures("map/lab_games", - {{"lg_style_01_wall_green", 1024, 1024}, - {"lg_style_01_wall_green_bright", 1024, 1024}, - {"lg_style_01_wall_red", 1024, 1024}, - {"lg_style_01_wall_red_bright", 1024, 1024}, - {"lg_style_02_wall_yellow", 1024, 1024}, - {"lg_style_02_wall_yellow_bright", 1024, 1024}, - {"lg_style_02_wall_blue", 1024, 1024}, - {"lg_style_02_wall_blue_bright", 1024, 1024}, - {"lg_style_03_wall_orange", 1024, 1024}, - {"lg_style_03_wall_orange_bright", 1024, 1024}, - {"lg_style_03_wall_gray", 1024, 1024}, - {"lg_style_03_wall_gray_bright", 1024, 1024}, - {"lg_style_04_wall_green", 1024, 1024}, - {"lg_style_04_wall_green_bright", 1024, 1024}, - {"lg_style_04_wall_red", 1024, 1024}, - {"lg_style_04_wall_red_bright", 1024, 1024}, - {"lg_style_05_wall_red", 1024, 1024}, - {"lg_style_05_wall_red_bright", 1024, 1024}, - {"lg_style_05_wall_yellow", 1024, 1024}, - {"lg_style_05_wall_yellow_bright", 1024, 1024}}), - decal_textures, - }; - break; - case Theme::TRON: - texture_set_ = TextureSet{ - BuildTextures("map/lab_games", - {{"lg_style_01_floor_orange", 1024, 1024}, - {"lg_style_01_floor_orange_bright", 1024, 1024}, - {"lg_style_01_floor_blue", 1024, 1024}, - {"lg_style_01_floor_blue_bright", 1024, 1024}}), - BuildTextures("map/lab_games", {{"fake_sky", 1024, 1024}}), - BuildTextures("map/lab_games", - {{"lg_style_01_wall_green", 1024, 1024}, - {"lg_style_01_wall_green_bright", 1024, 1024}, - {"lg_style_01_wall_red", 1024, 1024}, - {"lg_style_01_wall_red_bright", 1024, 1024}}), - decal_textures, - }; - break; - case Theme::MINESWEEPER: - texture_set_ = TextureSet{ - BuildTextures("map/lab_games", - {{"lg_style_04_floor_blue", 1024, 1024}, - {"lg_style_04_floor_blue_bright", 1024, 1024}, - {"lg_style_04_floor_orange", 1024, 1024}, - {"lg_style_04_floor_orange_bright", 1024, 1024}}), - BuildTextures("map/lab_games", {{"fake_sky", 1024, 1024}}), - BuildTextures("map/lab_games", - {{"lg_style_04_wall_green", 1024, 1024}, - {"lg_style_04_wall_green_bright", 1024, 1024}, - {"lg_style_04_wall_red", 1024, 1024}, - {"lg_style_04_wall_red_bright", 1024, 1024}}), - decal_textures, - {{"fut_obj_barbell_01.md3", 1.0}, {"fut_obj_cylinder_01.md3", 1.0}}, - }; - break; - case Theme::TETRIS: - texture_set_ = TextureSet{ - BuildTextures("map/lab_games", - {{"lg_style_02_floor_blue", 1024, 1024}, - {"lg_style_02_floor_blue_bright", 1024, 1024}, - {"lg_style_02_floor_green", 1024, 1024}, - {"lg_style_02_floor_green_bright", 1024, 1024}}), - BuildTextures("map/lab_games", {{"fake_sky", 1024, 1024}}), - BuildTextures("map/lab_games", - {{"lg_style_02_wall_yellow", 1024, 1024}, - {"lg_style_02_wall_yellow_bright", 1024, 1024}, - {"lg_style_02_wall_blue", 1024, 1024}, - {"lg_style_02_wall_blue_bright", 1024, 1024}}), - decal_textures, - }; - break; - case Theme::GO: - texture_set_ = TextureSet{ - BuildTextures("map/lab_games", - {{"lg_style_03_floor_green", 1024, 1024}, - {"lg_style_03_floor_green_bright", 1024, 1024}, - {"lg_style_03_floor_blue", 1024, 1024}, - {"lg_style_03_floor_blue_bright", 1024, 1024}}), - BuildTextures("map/lab_games", {{"fake_sky", 1024, 1024}}), - BuildTextures("map/lab_games", - {{"lg_style_03_wall_orange", 1024, 1024}, - {"lg_style_03_wall_orange_bright", 1024, 1024}, - {"lg_style_03_wall_gray", 1024, 1024}, - {"lg_style_03_wall_gray_bright", 1024, 1024}}), - decal_textures, - {{"fut_obj_barbell_01.md3", 1.0}, - {"fut_obj_coil_01.md3", 1.0}, - {"fut_obj_cone_01.md3", 1.0}, - {"fut_obj_crossbar_01.md3", 1.0}, - {"fut_obj_cube_01.md3", 1.0}, - {"fut_obj_cylinder_01.md3", 1.0}, - {"fut_obj_doubleprism_01.md3", 1.0}, - {"fut_obj_glowball_01.md3", 1.0}}, - {{"fut_obj_toroid_01.md3", 1.0}, - {"fut_obj_toroid_02.md3", 1.0}, - {"fut_obj_toroid_03.md3", 1.0}}}; - break; - case Theme::PACMAN: - texture_set_ = TextureSet{ - BuildTextures("map/lab_games", - {{"lg_style_05_floor_blue", 1024, 1024}, - {"lg_style_05_floor_blue_bright", 1024, 1024}, - {"lg_style_05_floor_orange", 1024, 1024}, - {"lg_style_05_floor_orange_bright", 1024, 1024}}), - BuildTextures("map/lab_games", {{"fake_sky", 1024, 1024}}), - BuildTextures("map/lab_games", - {{"lg_style_05_wall_red", 1024, 1024}, - {"lg_style_05_wall_red_bright", 1024, 1024}, - {"lg_style_05_wall_yellow", 1024, 1024}, - {"lg_style_05_wall_yellow_bright", 1024, 1024}}), - decal_textures, - {{"fut_obj_toroid_01.md3", 1.0}, - {"fut_obj_cylinder_01.md3", 1.0}, - {"fut_obj_crossbar_01.md3", 1.0}, - {"fut_obj_cube_01.md3", 1.0}}, - }; - break; - } - - // Always use the first available texture for group 0, the group used for the - // corridors. This ensures that the overall appearance of the maze can be - // controlled manually, while still randomly sampling the decoration of - // smaller elements (e.g. rooms). - floor_textures_[0] = texture_set_.floor[0]; - ceiling_textures_[0] = texture_set_.ceiling[0]; - wall_textures_[0] = texture_set_.wall[0]; -} - -void TextMazeExporter::SetBoundingBox(Eigen::Vector3d size) { - bounding_box_size_ = size; - bounding_box_size_.z() *= settings_.ceiling_scale; -} - -map_builder::Patch TextMazeExporter::GenerateWallDecal( - const std::string& texture, - double size, - Eigen::Vector3d a, - Eigen::Vector3d b, - Eigen::Vector3d interior_direction) { - const Eigen::Vector3d kUp = {0, 0, 1}; - Eigen::Vector3d pos = (a + b) * (0.5 * settings_.cell_size); - // Push decal slightly away from wall. - pos += (2.0 / map_builder::kWorldToGameUnits) * interior_direction; - return map_builder::brush_util::CreateGridPatch( - pos, -interior_direction, kUp, {size, size}, {3, 3}, {texture}); -} - -map_builder::Patch TextMazeExporter::GenerateWallDecal( - std::size_t texture_index, - Eigen::Vector3d a, - Eigen::Vector3d b, - Eigen::Vector3d interior_direction) { - const auto& texture = texture_set_.wall_decals[texture_index]; - double size = settings_.cell_size * (0.25 + real_dist(*rng_) * 0.5); - return GenerateWallDecal(texture.path, size, a, b, interior_direction); -} - -map_builder::Entity TextMazeExporter::GenerateWallModel( - std::size_t model_index, - Eigen::Vector3d a, - Eigen::Vector3d b, - Eigen::Vector3d interior_direction) { - const auto& model = texture_set_.wall_models[model_index]; - - using map_builder::Angle; - - auto angle = [](const Eigen::Vector3d& x, const Eigen::Vector3d& y) -> Angle { - return Angle::Radians(std::atan2(x.cross(y).norm(), x.dot(y))); - }; - - return map_builder::Entity::CreateModel( - StrCat("models/", model.name), - ((a + b) / 2) * settings_.cell_size + interior_direction, - {Angle::Radians(0), Angle::Radians(0), angle(interior_direction, Eigen::Vector3d(0, 1, 0))}, - {model.scale, model.scale, model.scale}); -} - -void TextMazeExporter::AddWall( - Eigen::Vector3d a, - Eigen::Vector3d b, - Eigen::Vector3d interior_direction, - const map_builder::Texture& texture) { - builder_.mutable_world_entity()->add_brush( - map_builder::brush_util::CreateBoxBrush( - a * settings_.cell_size + Eigen::Vector3d::Zero().cwiseMin(interior_direction) / map_builder::kWorldToGameUnits, - b * settings_.cell_size + Eigen::Vector3d::Zero().cwiseMax(interior_direction) / map_builder::kWorldToGameUnits, - texture)); - - art_locations_.push_back(ArtLocation{a, b, interior_direction}); -} - -void TextMazeExporter::AddWall( - Eigen::Vector3d a, - Eigen::Vector3d b, - Eigen::Vector3d interior_direction, - CellGroup group_number) { - auto wall_tex_it = wall_textures_.find(group_number); - if (wall_tex_it == wall_textures_.end()) { - wall_tex_it = wall_textures_.emplace( - group_number, - texture_set_.wall[Unbiased(texture_set_.wall.size())]).first; - } - AddWall(a, b, interior_direction, wall_tex_it->second); -} - -void TextMazeExporter::AddFloor( - Eigen::Vector3d a, - Eigen::Vector3d b, - CellGroup group_number) { - // Walls must have a thickness > 0 to be rendered. - Eigen::Vector3d thickness_offset(0, 0, (1.0 / map_builder::kWorldToGameUnits)); - - if (floor_textures_.find(group_number) == floor_textures_.end()) { - floor_textures_[group_number] = - texture_set_.floor[Unbiased(texture_set_.floor.size())]; - ceiling_textures_[group_number] = - texture_set_.ceiling[Unbiased(texture_set_.ceiling.size())]; - } - - auto* world_entity = builder_.mutable_world_entity(); - - world_entity->add_brush(map_builder::brush_util::CreateBoxBrush( - a * settings_.cell_size, b * settings_.cell_size + thickness_offset, - floor_textures_[group_number])); - - // Don't add ceiling if we have a skybox set. - if (settings_.skybox_texture_name.empty()) { - Eigen::Vector3d ceiling_height(0, 0, bounding_box_size_.z()); - world_entity->add_brush(map_builder::brush_util::CreateBoxBrush( - (a + ceiling_height) * settings_.cell_size - thickness_offset, - (b + ceiling_height) * settings_.cell_size, - ceiling_textures_[group_number])); - } - - if (!texture_set_.floor_models.empty() && - real_dist(*rng_) < settings_.floor_object_frequency) { - auto model = - texture_set_.floor_models[Unbiased(texture_set_.floor_models.size())]; - Add(MakeEntityWithRealOffset( - (a + b) / 2, Eigen::Vector3d(0, 0, 1), "misc_model", - {{"model", "models/" + model.name}, - {"angle", StrCat(Unbiased(360))}, - {"modelscale", StrCat(model.scale)}})); - } -} - -map_builder::Entity TextMazeExporter::MakeEntity( - Eigen::Vector3d position, - std::string class_name, - std::vector> attributes) { - map_builder::Entity entity(std::move(class_name), - position * settings_.cell_size); - entity.set_attributes(attributes); - return entity; -} - -map_builder::Entity TextMazeExporter::MakeEntityWithRealOffset( - Eigen::Vector3d position, - Eigen::Vector3d offset, - std::string class_name, - std::vector> attributes) { - map_builder::Entity entity(std::move(class_name), - position * settings_.cell_size + offset); - entity.set_attributes(attributes); - return entity; -} - -map_builder::Entity TextMazeExporter::MakeBrushEntity( - Eigen::Vector3d min, - Eigen::Vector3d max, - std::string class_name, - std::string texture_name, - double texture_scale_width, - double texture_scale_height, - std::vector> attributes) { - map_builder::Entity entity(std::move(class_name)); - map_builder::Texture texture = - texture_name.empty() - ? floor_textures_[0] - : map_builder::Texture{std::move(texture_name), {0, 0}, 0, - {kTexelSize * texture_scale_width, - kTexelSize * texture_scale_height}}; - entity.add_brush(map_builder::brush_util::CreateBoxBrush( - settings_.cell_size * min, settings_.cell_size * max, texture)); - entity.set_attributes(attributes); - return entity; -} - -map_builder::Entity TextMazeExporter::MakeLight( - Eigen::Vector3d position, - double intensity) { - return MakeEntity( - position, "light", - // Default light brightness is 200. - {{"light", StrCat(intensity * 5 * settings_.light_intensity)}, - {"style", "0"}, - {"spawnflags", "0"}}); -} - -void TextMazeExporter::Finalize() { - int total_pieces = - texture_set_.wall_decals.size() + texture_set_.wall_models.size(); - - std::vector> pieces; - pieces.reserve(total_pieces); - for (std::size_t i = 0; i < texture_set_.wall_decals.size(); ++i) { - pieces.emplace_back(i, true); - } - for (std::size_t i = 0; i < texture_set_.wall_models.size(); ++i) { - pieces.emplace_back(i, false); - } - - // Uniform selection of pieces. - std::shuffle(pieces.begin(), pieces.end(), *rng_); - - // Uniform distribution of placement. - std::shuffle(art_locations_.begin(), art_locations_.end(), *rng_); - - std::size_t requested_pieces_count = - art_locations_.size() * settings_.wall_decal_frequency + 0.5; - - if (requested_pieces_count > pieces.size()) { - LOG(WARNING) << "Not enough paintings to match requested density:\n" - << settings_.wall_decal_frequency << "(" - << requested_pieces_count << ") requested, " - << static_cast(pieces.size()) / art_locations_.size() - << "(" << pieces.size() << ") achieved."; - requested_pieces_count = pieces.size(); - } else { - LOG(INFO) << "Requested wall hangings: " << requested_pieces_count; - } - - for (std::size_t i = 0; i < requested_pieces_count; ++i) { - const auto& piece = pieces[i]; - const auto& loc = art_locations_[i]; - if (piece.second) { - builder_.mutable_world_entity()->add_patch( - GenerateWallDecal(piece.first, loc.a, loc.b, loc.interior_direction)); - } else { - builder_.AddEntity( - GenerateWallModel(piece.first, loc.a, loc.b, loc.interior_direction)); - } - } - - // Add skybox. - if (!settings_.skybox_texture_name.empty()) { - Eigen::Vector3d cell_size = Eigen::Vector3d::Constant(settings_.cell_size); - auto world_size = (bounding_box_size_ * settings_.cell_size) + cell_size; - auto pos = (world_size - cell_size) * 0.5; - builder_.AddSkybox(pos, world_size, settings_.skybox_texture_name, - kSkyboxTextureSize); - } -} - -} // namespace lab -} // namespace deepmind diff --git a/deepmind/level_generation/text_level/translate_text_level.cc b/deepmind/level_generation/text_level/translate_text_level.cc index b5a41106..5280d543 100644 --- a/deepmind/level_generation/text_level/translate_text_level.cc +++ b/deepmind/level_generation/text_level/translate_text_level.cc @@ -23,17 +23,19 @@ #include #include -#include "deepmind/support/str_cat.h" -#include "deepmind/support/str_join.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_join.h" #include "deepmind/level_generation/map_builder/entity.h" #include "deepmind/level_generation/text_level/grid_maze.h" #include "deepmind/level_generation/text_level/parse_text_level.h" -#include "deepmind/level_generation/text_level/text_maze_exporter.h" +#include "deepmind/level_generation/text_level/text_level_exporter.h" namespace deepmind { namespace lab { namespace { +using abslstring = decltype(absl::StrCat()); + // Whether all bits of rhs are set in lhs. inline bool DirectionHas( GridMaze::Direction lhs, @@ -45,13 +47,16 @@ inline bool DirectionHas( // Spawn point elevation. constexpr double kStartPointZOffset = 0.3; -// Door dimensions +// Door dimensions. constexpr double kDoorThickness = 0.01; +constexpr double kFenceDoorThickness = 0.04; +constexpr double kFenceDoorPosts = 4; constexpr double kDoorSurround = 0.01; // Main translation function; creates the core level geometry. -void TranslateCoreLevel(const GridMaze& maze, std::vector* out, - TextMazeExporter* exporter, std::mt19937_64* rng) { +void TranslateCoreLevel(const GridMaze& maze, TextLevelExporter* exporter, + std::mt19937_64* rng, + TextLevelSettings* level_settings) { using Dir = GridMaze::Direction; // Light sources are added periodically with random strength. @@ -61,8 +66,11 @@ void TranslateCoreLevel(const GridMaze& maze, std::vector* out, // Note: This is what the original code did, not necessarily what we intended. std::uniform_int_distribution light_dist(10, 19); - exporter->SetBoundingBox({maze.width() * 1.0, maze.height() * 1.0, 1.0}); - maze.Visit([&](std::size_t i, std::size_t j, const GridMaze::Cell& cell) { + exporter->SetBoundingBox({maze.width() * 1.0, maze.height() * 1.0, + level_settings->ceiling_height}); + + if (level_settings->draw_default_layout) { + maze.Visit([&](std::size_t i, std::size_t j, const GridMaze::Cell& cell) { if (cell.value == cell.kInaccessible) return; // The .map format uses a bottom-left based coordinate system. @@ -87,55 +95,116 @@ void TranslateCoreLevel(const GridMaze& maze, std::vector* out, double x = j; double y = maze.height() - i - 1; + Eigen::Vector2i cell_location(i + 1, j + 1); if (!DirectionHas(cell.opening, Dir::kNorth)) { - exporter->AddWall({x + 0.0, y + 1.0, 0.0}, - {x + 1.0, y + 1.0, 1.0}, - { 0.0, -1.0, 0.0}, - cell.variation); + exporter->AddWall({x + 0.0, y + 1.0, 0.0}, {x + 1.0, y + 1.0, 1.0}, + {0.0, -1.0, 0.0}, cell_location, cell.variation); } if (!DirectionHas(cell.opening, Dir::kEast)) { - exporter->AddWall({x + 1.0, y + 0.0, 0.0}, - {x + 1.0, y + 1.0, 1.0}, - { -1.0, 0.0, 0.0}, - cell.variation); + exporter->AddWall({x + 1.0, y + 0.0, 0.0}, {x + 1.0, y + 1.0, 1.0}, + {-1.0, 0.0, 0.0}, cell_location, cell.variation); } if (!DirectionHas(cell.opening, Dir::kSouth)) { - exporter->AddWall({x + 0.0, y + 0.0, 0.0}, - {x + 1.0, y + 0.0, 1.0}, - { 0.0, 1.0, 0.0}, - cell.variation); + exporter->AddWall({x + 0.0, y + 0.0, 0.0}, {x + 1.0, y + 0.0, 1.0}, + {0.0, 1.0, 0.0}, cell_location, cell.variation); } if (!DirectionHas(cell.opening, Dir::kWest)) { - exporter->AddWall({x + 0.0, y + 0.0, 0.0}, - {x + 0.0, y + 1.0, 1.0}, - { 1.0, 0.0, 0.0}, - cell.variation); + exporter->AddWall({x + 0.0, y + 0.0, 0.0}, {x + 0.0, y + 1.0, 1.0}, + {1.0, 0.0, 0.0}, cell_location, cell.variation); } - exporter->AddFloor({x, y, 0.0}, {x + 1.0, y + 1.0, 0.0}, cell.variation); + exporter->AddFloor({x, y, 0.0}, {x + 1.0, y + 1.0, 0.0}, cell_location, + cell.variation); if ((maze.height() - i - 1 + j) % kLightSpacing == 0) { exporter->Add(exporter->MakeLight({(x + 0.5), (y + 0.5), kLightZOffset}, light_dist(*rng))); } }); + } +} + +// The following two methods create fence door blocks. Fence doors consist of +// top and bottom blocks connected by at least three posts: +// Example door shape: +// ----- - Top block +// | | | - Posts +// ----- - Bottom block + +// Returns blocks representing a "horizontal" (oriented along x axis) fence +// door. +std::vector> +CreateHorizontalFenceDoorBlocks(double x, double y) { + std::vector> blocks; + // Top block + blocks.push_back(std::pair( + {x + kDoorSurround, y + 0.5 - kFenceDoorThickness, + 1.0 - 2.0 * kFenceDoorThickness}, + {x + 1 + kDoorSurround, y + 0.5 + kFenceDoorThickness, 1.0})); + // Bottom block + blocks.push_back(std::pair( + {x + kDoorSurround, y + 0.5 - kFenceDoorThickness, 0}, + {x + 1 + kDoorSurround, y + 0.5 + kFenceDoorThickness, + 2.0 * kFenceDoorThickness})); + // Post blocks + const float post_step = + (1.0 - 2.0 * kFenceDoorThickness - 2.0 * kDoorSurround) / + (kFenceDoorPosts - 1); + for (float px = x + kFenceDoorThickness + kDoorSurround; px < x + 1; + px += post_step) { + blocks.push_back(std::pair( + {px - kFenceDoorThickness, y + 0.5 - kFenceDoorThickness, + 2.0 * kFenceDoorThickness}, + {px + kFenceDoorThickness, y + 0.5 + kFenceDoorThickness, + 1.0 - 2.0 * kFenceDoorThickness})); + } + return blocks; +} + +// Returns blocks representing a "vertical" (oriented along y axis) fence +// door. +std::vector> +CreateVerticalFenceDoorBlocks(double x, double y) { + std::vector> blocks; + // Top block + blocks.push_back(std::pair( + {x + 0.5 - kFenceDoorThickness, y + kDoorSurround, + 1.0 - 2.0 * kFenceDoorThickness}, + {x + 0.5 + kFenceDoorThickness, y + 1 + kDoorSurround, 1.0})); + // Bottom block + blocks.push_back(std::pair( + {x + 0.5 - kFenceDoorThickness, y + kDoorSurround, 0}, + {x + 0.5 + kFenceDoorThickness, y + 1 + kDoorSurround, + 2.0 * kFenceDoorThickness})); + // Post blocks + const float post_step = + (1.0 - 2.0 * kFenceDoorThickness - 2.0 * kDoorSurround) / + (kFenceDoorPosts - 1); + for (float py = y + kFenceDoorThickness + kDoorSurround; py < y + 1; + py += post_step) { + blocks.push_back(std::pair( + {x + 0.5 - kFenceDoorThickness, py - kFenceDoorThickness, + 2.0 * kFenceDoorThickness}, + {x + 0.5 + kFenceDoorThickness, py + kFenceDoorThickness, + 1.0 - 2.0 * kFenceDoorThickness})); + } + return blocks; } std::vector MakeDoor( - double x, double y, char dir, TextMazeExporter* exporter) { + double x, double y, char dir, TextLevelExporter* exporter) { std::vector door; - std::string target = StrCat("door_", x, "_", y); + std::string target = absl::StrCat("door_", x, "_", y); - const double texture_scale = (1.0 + 2.0 * kDoorSurround) / 1024.0; if (dir == 'H') { - door.push_back(exporter->MakeBrushEntity( + door.push_back(exporter->MakeFittedBrushEntity( {x + 0.0 + kDoorSurround, y + 0.5 - kDoorThickness, 0.0}, {x + 1.0 - kDoorSurround, y + 0.5 + kDoorThickness, 1.0}, - "func_door", "map/fut_door_d", texture_scale, texture_scale, + "func_door", "map/fut_door_d", 1024, 1024, {{"angle", "0"}, {"targetname", target}})); door.push_back(exporter->MakeBrushEntity( @@ -150,10 +219,55 @@ std::vector MakeDoor( "trigger_multiple", "", 0.0, 0.0, {{"wait", "1"}, {"target", target}})); } else if (dir == 'I') { - door.push_back(exporter->MakeBrushEntity( + door.push_back(exporter->MakeFittedBrushEntity( {x + 0.5 - kDoorThickness, y + 0.0 + kDoorSurround, 0.0}, {x + 0.5 + kDoorThickness, y + 1.0 - kDoorSurround, 1.0}, - "func_door", "map/fut_door_d", texture_scale, texture_scale, + "func_door", "map/fut_door_d", 1024, 1024, + {{"angle", "90"}, {"targetname", target}})); + + door.push_back(exporter->MakeBrushEntity( + {x + 0.0, y + 0.0, 0.0}, + {x + 0.5 - kDoorThickness, y + 1.0, 1.0}, + "trigger_multiple", "", 0.0, 0.0, + {{"wait", "1"}, {"target", target}})); + + door.push_back(exporter->MakeBrushEntity( + {x + 0.5 + kDoorThickness, y + 0.0 , 0.0}, + {x + 1.0, y + 1.0, 1.0}, + "trigger_multiple", "", 0.0, 0.0, + {{"wait", "1"}, {"target", target}})); + } + + return door; +} + +std::vector MakeFenceDoor( + double x, double y, char dir, TextLevelExporter* exporter) { + std::vector door; + std::string target = absl::StrCat("door_", x, "_", y); + + const double texture_scale = (1.0 + 2.0 * kDoorSurround) / 1024.0; + if (dir == 'H') { + door.push_back(exporter->MakeBrushEntity( + CreateHorizontalFenceDoorBlocks(x, y), "func_door", + "door_placeholder:" + target, texture_scale, texture_scale, + {{"angle", "0"}, {"targetname", target}})); + + door.push_back(exporter->MakeBrushEntity( + {x + 0.0, y + 0.0, 0.0}, + {x + 1.0, y + 0.5 - kDoorThickness, 1.0}, + "trigger_multiple", "", 0.0, 0.0, + {{"wait", "1"}, {"target", target}})); + + door.push_back(exporter->MakeBrushEntity( + {x + 0.0, y + 0.5 + kDoorThickness, 0.0}, + {x + 1.0, y + 1.0, 1.0}, + "trigger_multiple", "", 0.0, 0.0, + {{"wait", "1"}, {"target", target}})); + } else if (dir == 'I') { + door.push_back(exporter->MakeBrushEntity( + CreateVerticalFenceDoorBlocks(x, y), "func_door", + "door_placeholder:" + target, texture_scale, texture_scale, {{"angle", "90"}, {"targetname", target}})); door.push_back(exporter->MakeBrushEntity( @@ -174,14 +288,14 @@ std::vector MakeDoor( // Ready-made callback for adding a number of generally useful entities. bool DefaultHandler( - std::size_t i, std::size_t j, char val, + double i, double j, char val, const MapSnippetEmitter& emitter, std::vector* out) { switch (val) { default: return false; case 'P': - out->push_back(emitter.AddSpawn(i, j, 0)); + out->push_back(emitter.AddSpawn(i, j, 0, 0)); return true; case 'I': @@ -195,82 +309,119 @@ bool DefaultHandler( class MapSnippetEmitterImpl : public MapSnippetEmitter { public: - MapSnippetEmitterImpl(const GridMaze* maze, TextMazeExporter* exporter) + MapSnippetEmitterImpl(const GridMaze* maze, TextLevelExporter* exporter) : maze_(*maze), exporter_(exporter) {} - std::string AddEntity( - std::size_t i, std::size_t j, std::string class_name, - const std::vector>& - attributes) const { + std::string AddEntity(double i, double j, double height, + std::string class_name, + const std::vector>& + attributes) const { return exporter_ ->MakeEntity(Eigen::Vector3d({j + 0.5, (maze_.height() - i - 1) + 0.5, - kStartPointZOffset}), + height * TextLevelExporter::kHeightScale + + kStartPointZOffset}), std::move(class_name), attributes) .ToString(); } - std::string AddSpawn(std::size_t i, std::size_t j, double angle_rad) const { + std::string AddSpawn(double i, double j, double height, + double angle_rad) const { return map_builder::Entity::CreateSpawn( - Eigen::Vector3d( - {j + 0.5, (maze_.height() - i - 1) + 0.5, kStartPointZOffset}) - * exporter_->GetSettings().cell_size, - map_builder::Angle::Radians(angle_rad)).ToString(); + Eigen::Vector3d({j + 0.5, (maze_.height() - i - 1) + 0.5, + height * TextLevelExporter::kHeightScale + + kStartPointZOffset}) * + exporter_->GetSettings().cell_size, + map_builder::Angle::Radians(angle_rad)) + .ToString(); } - std::string AddDoor(std::size_t i, std::size_t j, char direction) const { - return strings::Join( + std::string AddDoor(double i, double j, char direction) const { + return absl::StrJoin( MakeDoor(j, maze_.height() - i - 1, direction, exporter_), "\n\n", - [](string* out, const map_builder::Entity& ent) { - StrAppend(out, ent.ToString()); + [](abslstring* out, const map_builder::Entity& ent) { + absl::StrAppend(out, ent.ToString()); + }); + } + + std::string AddFenceDoor(double i, double j, char direction) const { + return absl::StrJoin( + MakeFenceDoor(j, maze_.height() - i - 1, direction, exporter_), "\n\n", + [](abslstring* out, const map_builder::Entity& ent) { + absl::StrAppend(out, ent.ToString()); }); } + void AddPlatform(double i, double j, double height) const { + exporter_->AddPlatform(j, maze_.height() - i - 1, height); + } + + void AddGlassColumn(double i, double j, double height) const { + exporter_->AddGlassColumn(j, maze_.height() - i - 1, height); + } + private: friend class MapSnippetEmitter; const GridMaze& maze_; - TextMazeExporter* const exporter_; + TextLevelExporter* const exporter_; }; } // namespace std::string MapSnippetEmitter::AddEntity( - std::size_t i, - std::size_t j, - std::string class_name, + double i, double j, double height, std::string class_name, const std::vector>& attributes) const { return static_cast(this)->AddEntity( - i, j, std::move(class_name), attributes); + i, j, height, std::move(class_name), attributes); } -std::string MapSnippetEmitter::AddSpawn( - std::size_t i, std::size_t j, double angle_rad) const { - return static_cast(this)->AddSpawn( - i, j, angle_rad); +std::string MapSnippetEmitter::AddSpawn(double i, double j, + double height, double angle_rad) const { + return static_cast(this)->AddSpawn(i, j, height, + angle_rad); } std::string MapSnippetEmitter::AddDoor( - std::size_t i, std::size_t j, char direction) const { + double i, double j, char direction) const { return static_cast(this)->AddDoor( i, j, direction); } -std::string TranslateTextLevel( - std::string level_text, std::string variations_text, - std::mt19937_64* rng, - const TranslateTextLevelCallback& callback) { +std::string MapSnippetEmitter::AddFenceDoor( + double i, double j, char direction) const { + return static_cast(this)->AddFenceDoor( + i, j, direction); +} + +std::string MapSnippetEmitter::AddPlatform(double i, double j, + double height) const { + static_cast(this)->AddPlatform(i, j, height); + return ""; +} + +std::string MapSnippetEmitter::AddGlassColumn(double i, double j, + double height) const { + static_cast(this)->AddGlassColumn(i, j, height); + return ""; +} + +std::string TranslateTextLevel(std::string level_text, + std::string variations_text, + std::mt19937_64* rng, + const TranslateTextLevelCallback& callback, + TextLevelSettings* level_settings) { GridMaze maze = ParseTextLevel(std::move(level_text), std::move(variations_text)); - TextMazeExporter exporter(rng); + TextLevelExporter exporter(level_settings); MapSnippetEmitterImpl emitter(&maze, &exporter); - std::vector lines, extra_lines; - TranslateCoreLevel(maze, &lines, &exporter, rng); + TranslateCoreLevel(maze, &exporter, rng, level_settings); exporter.Finalize(); + std::vector lines; maze.Visit([&](std::size_t i, std::size_t j, const GridMaze::Cell& cell) { if (cell.value == cell.kInaccessible) return; @@ -278,15 +429,15 @@ std::string TranslateTextLevel( if (callback(i, j, cell.value, emitter, &v) || DefaultHandler(i, j, cell.value, emitter, &v)) { - std::move(v.begin(), v.end(), std::back_inserter(extra_lines)); + std::move(v.begin(), v.end(), std::back_inserter(lines)); } }); - lines.push_back(exporter.ToString()); - std::move(extra_lines.begin(), extra_lines.end(), std::back_inserter(lines)); + // The exporter string is already newline-terminated, so only add 1 newline + std::string acc = absl::StrCat(exporter.ToString(), "\n"); return std::accumulate( - lines.begin(), lines.end(), std::string(), + lines.begin(), lines.end(), acc, [](std::string& acc, const std::string& val) -> std::string { acc.append(val); acc.append(2, '\n'); diff --git a/deepmind/level_generation/text_level/translate_text_level.h b/deepmind/level_generation/text_level/translate_text_level.h index 989897b0..44f11848 100644 --- a/deepmind/level_generation/text_level/translate_text_level.h +++ b/deepmind/level_generation/text_level/translate_text_level.h @@ -28,6 +28,8 @@ #include #include +#include "deepmind/level_generation/text_level/text_level_settings.h" + namespace deepmind { namespace lab { @@ -36,15 +38,14 @@ namespace lab { class MapSnippetEmitter { public: // Emits an entity with the given class name and set of attributes located in - // the centre of the the (i, j) cell. + // the centre of the the (i, j) cell and at a given height. std::string AddEntity( - std::size_t i, - std::size_t j, - std::string class_name, + double i, double j, double height, std::string class_name, const std::vector>& attributes) const; - // Emits a spawn point in the (i, j) cell. - std::string AddSpawn(std::size_t i, std::size_t j, double angle_rad) const; + // Emits a spawn point in the (i, j) cell and at a given height. + std::string AddSpawn(double i, double j, double height, + double angle_rad) const; // Emits a door cell. There are two possible directions for a door, labeled // 'I' and 'H'. A door if type 'I' at cell (i, j) can be traversed in the @@ -58,7 +59,27 @@ class MapSnippetEmitter { // * * // ******* // - std::string AddDoor(std::size_t i, std::size_t j, char direction) const; + std::string AddDoor(double i, double j, char direction) const; + + // Emits a fence door cell. Fence door cells are very similar to door cells, + // except that the fence doors are composed of a series of blocks so that we + // can see through them. + std::string AddFenceDoor(double i, double j, char direction) const; + + // Emits a platform cell at specified position and height. The platform + // surface size is a whole maze cell. + // A platform is composed by several layers from top to bottom: + // - A top thin layer with a floor texture. + // - An intermediate layer with black texture, to enphasize the platform + // borders. + // - A plaform base, with a brick-like textures. + // - An invisible column from the bottom of the platform down to the ground + // which prevents the player from steping below other platforms. + std::string AddPlatform(double i, double j, double height) const; + + // Emits an invisible column all the way from the ground to the specified + // height. The column section will be a whole maze cell. + std::string AddGlassColumn(double i, double j, double height) const; protected: MapSnippetEmitter() = default; @@ -88,6 +109,9 @@ class MapSnippetEmitter { // A random number generator must be provided which is used to select variations // and decorations. // +// Additional parameters for level generation (e.g. use skybox) can be specified +// via level_settings. +// // See documentation for details. using TranslateTextLevelCallback = std::function* out) { if (ent != 'x') return false; - out->push_back(em.AddEntity(i, j, "XyzzyEntity", {{"a", "1"}, {"b", "2"}})); + out->push_back( + em.AddEntity(i, j, 0, "XyzzyEntity", {{"a", "1"}, {"b", "2"}})); return true; }; std::mt19937_64 rng(123); + + TextLevelSettings settings; std::string actual = TranslateTextLevel( - kEntities, kVariations, &rng, callback); + kEntities, kVariations, &rng, callback, &settings); EXPECT_THAT(actual, testing::HasSubstr( "{\n" @@ -119,6 +128,134 @@ TEST(TranslateTextLevel, Custom) { "}")); } +TEST(TranslateTextLevel, SkyBox) { + std::mt19937_64 rng(123); + TextLevelSettings exporter_settings; + exporter_settings.skybox_texture_name = "map/lab_games/sky/lg_sky_01"; + std::string actual = TranslateTextLevel(kEntities, kVariations, &rng, NoOp, + &exporter_settings); + + // Test skybox brushes + EXPECT_THAT(actual, testing::HasSubstr( + " {\n" + " ( -626 0 0 ) ( -626 32 0 ) ( -626 0 32 ) " + "map/lab_games/sky/lg_sky_01_up 378 0 0 -0.375 0.03125 0 0 0\n" + " ( -242 0 0 ) ( -242 0 32 ) ( -242 32 0 ) " + "map/lab_games/sky/lg_sky_01_up 378 0 0 -0.375 0.03125 0 0 0\n" + " ( 0 -626 0 ) ( 0 -626 32 ) ( 32 -626 0 ) " + "map/lab_games/sky/lg_sky_01_up 378 0 0 -0.375 0.03125 0 0 0\n" + " ( 0 -242 0 ) ( 32 -242 0 ) ( 0 -242 32 ) " + "map/lab_games/sky/lg_sky_01_up 378 0 0 -0.375 0.03125 0 0 0\n" + " ( 0 0 192 ) ( 32 0 192 ) ( 0 32 192 ) " + "map/lab_games/sky/lg_sky_01_up 378 378 0 -0.375 0.375 0 0 0\n" + " ( 0 0 224 ) ( 0 32 224 ) ( 32 0 224 ) " + "map/lab_games/sky/lg_sky_01_up 378 378 0 -0.375 0.375 0 0 0\n" + " }\n" + " {\n" + " ( -626 0 0 ) ( -626 32 0 ) ( -626 0 32 ) " + "map/lab_games/sky/lg_sky_01_dn 378 0 0 -0.375 0.03125 0 0 0\n" + " ( -242 0 0 ) ( -242 0 32 ) ( -242 32 0 ) " + "map/lab_games/sky/lg_sky_01_dn 378 0 0 -0.375 0.03125 0 0 0\n" + " ( 0 -626 0 ) ( 0 -626 32 ) ( 32 -626 0 ) " + "map/lab_games/sky/lg_sky_01_dn 378 0 0 -0.375 0.03125 0 0 0\n" + " ( 0 -242 0 ) ( 32 -242 0 ) ( 0 -242 32 ) " + "map/lab_games/sky/lg_sky_01_dn 378 0 0 -0.375 0.03125 0 0 0\n" + " ( 0 0 -224 ) ( 32 0 -224 ) ( 0 32 -224 ) " + "map/lab_games/sky/lg_sky_01_dn 378 378 0 -0.375 0.375 0 0 0\n" + " ( 0 0 -192 ) ( 0 32 -192 ) ( 32 0 -192 ) " + "map/lab_games/sky/lg_sky_01_dn 378 378 0 -0.375 0.375 0 0 0\n" + " }\n" + " {\n" + " ( -658 0 0 ) ( -658 32 0 ) ( -658 0 32 ) " + "map/lab_games/sky/lg_sky_01_lf 378 512 0 -0.375 0.375 0 0 0\n" + " ( -626 0 0 ) ( -626 0 32 ) ( -626 32 0 ) " + "map/lab_games/sky/lg_sky_01_lf 378 512 0 -0.375 0.375 0 0 0\n" + " ( 0 -626 0 ) ( 0 -626 32 ) ( 32 -626 0 ) " + "map/lab_games/sky/lg_sky_01_lf 448 512 0 -0.03125 0.375 0 0 0\n" + " ( 0 -242 0 ) ( 32 -242 0 ) ( 0 -242 32 ) " + "map/lab_games/sky/lg_sky_01_lf 448 512 0 -0.03125 0.375 0 0 0\n" + " ( 0 0 -192 ) ( 32 0 -192 ) ( 0 32 -192 ) " + "map/lab_games/sky/lg_sky_01_lf 448 378 0 -0.03125 0.375 0 0 0\n" + " ( 0 0 192 ) ( 0 32 192 ) ( 32 0 192 ) " + "map/lab_games/sky/lg_sky_01_lf 448 378 0 -0.03125 0.375 0 0 0\n" + " }\n" + " {\n" + " ( -242 0 0 ) ( -242 32 0 ) ( -242 0 32 ) " + "map/lab_games/sky/lg_sky_01_rt 378 512 0 -0.375 0.375 0 0 0\n" + " ( -210 0 0 ) ( -210 0 32 ) ( -210 32 0 ) " + "map/lab_games/sky/lg_sky_01_rt 378 512 0 -0.375 0.375 0 0 0\n" + " ( 0 -626 0 ) ( 0 -626 32 ) ( 32 -626 0 ) " + "map/lab_games/sky/lg_sky_01_rt 448 512 0 -0.03125 0.375 0 0 0\n" + " ( 0 -242 0 ) ( 32 -242 0 ) ( 0 -242 32 ) " + "map/lab_games/sky/lg_sky_01_rt 448 512 0 -0.03125 0.375 0 0 0\n" + " ( 0 0 -192 ) ( 32 0 -192 ) ( 0 32 -192 ) " + "map/lab_games/sky/lg_sky_01_rt 448 378 0 -0.03125 0.375 0 0 0\n" + " ( 0 0 192 ) ( 0 32 192 ) ( 32 0 192 ) " + "map/lab_games/sky/lg_sky_01_rt 448 378 0 -0.03125 0.375 0 0 0\n" + " }\n" + " {\n" + " ( -626 0 0 ) ( -626 32 0 ) ( -626 0 32 ) " + "map/lab_games/sky/lg_sky_01_ft 448 512 0 -0.03125 0.375 0 0 0\n" + " ( -242 0 0 ) ( -242 0 32 ) ( -242 32 0 ) " + "map/lab_games/sky/lg_sky_01_ft 448 512 0 -0.03125 0.375 0 0 0\n" + " ( 0 -658 0 ) ( 0 -658 32 ) ( 32 -658 0 ) " + "map/lab_games/sky/lg_sky_01_ft 378 512 0 -0.375 0.375 0 0 0\n" + " ( 0 -626 0 ) ( 32 -626 0 ) ( 0 -626 32 ) " + "map/lab_games/sky/lg_sky_01_ft 378 512 0 -0.375 0.375 0 0 0\n" + " ( 0 0 -192 ) ( 32 0 -192 ) ( 0 32 -192 ) " + "map/lab_games/sky/lg_sky_01_ft 378 448 0 -0.375 0.03125 0 0 0\n" + " ( 0 0 192 ) ( 0 32 192 ) ( 32 0 192 ) " + "map/lab_games/sky/lg_sky_01_ft 378 448 0 -0.375 0.03125 0 0 0\n" + " }\n" + " {\n" + " ( -626 0 0 ) ( -626 32 0 ) ( -626 0 32 ) " + "map/lab_games/sky/lg_sky_01_bk 448 512 0 -0.03125 0.375 0 0 0\n" + " ( -242 0 0 ) ( -242 0 32 ) ( -242 32 0 ) " + "map/lab_games/sky/lg_sky_01_bk 448 512 0 -0.03125 0.375 0 0 0\n" + " ( 0 -242 0 ) ( 0 -242 32 ) ( 32 -242 0 ) " + "map/lab_games/sky/lg_sky_01_bk 378 512 0 -0.375 0.375 0 0 0\n" + " ( 0 -210 0 ) ( 32 -210 0 ) ( 0 -210 32 ) " + "map/lab_games/sky/lg_sky_01_bk 378 512 0 -0.375 0.375 0 0 0\n" + " ( 0 0 -192 ) ( 32 0 -192 ) ( 0 32 -192 ) " + "map/lab_games/sky/lg_sky_01_bk 378 448 0 -0.375 0.03125 0 0 0\n" + " ( 0 0 192 ) ( 0 32 192 ) ( 32 0 192 ) " + "map/lab_games/sky/lg_sky_01_bk 378 448 0 -0.375 0.03125 0 0 0\n }")); + // Test skybox entity + EXPECT_THAT(actual, testing::HasSubstr( + "{\n" + " \"classname\" \"_skybox\"\n" + " \"origin\" \"-434 -434 0\"\n" + "}")); +} + +TEST(TranslateTextLevel, MakeFenceDoor) { + auto callback = [](std::size_t i, std::size_t j, char ent, + const MapSnippetEmitter& em, + std::vector* out) { + if (ent == 'I' || ent == 'H') { + out->push_back(em.AddFenceDoor(i, j, ent)); + return true; + } + return false; + }; + + std::mt19937_64 rng(123); + TextLevelSettings settings; + std::string actual = TranslateTextLevel( + kEntities, kVariations, &rng, callback, &settings); + + // Test the first vertical fence door. + EXPECT_THAT(actual, testing::HasSubstr( + "\"angle\" \"90\"\n \"targetname\" \"door_11_3\"\n")); + EXPECT_THAT(actual, testing::HasSubstr( + "( 1146 0 0 ) ( 1146 32 0 ) ( 1146 0 32 ) door_placeholder:door_11_3")); + // Test the first horizontal fence door. + EXPECT_THAT(actual, testing::HasSubstr( + "\"angle\" \"0\"\n \"targetname\" \"door_9_2\"\n")); + EXPECT_THAT(actual, testing::HasSubstr( + "( 901 0 0 ) ( 901 32 0 ) ( 901 0 32 ) door_placeholder:door_9_2")); +} + } // namespace } // namespace lab } // namespace deepmind diff --git a/deepmind/level_generation/text_level/translate_text_level_test.golden_output b/deepmind/level_generation/text_level/translate_text_level_test.golden_output index 05a70bb1..fe757837 100644 --- a/deepmind/level_generation/text_level/translate_text_level_test.golden_output +++ b/deepmind/level_generation/text_level/translate_text_level_test.golden_output @@ -139,28 +139,28 @@ ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 400 0 0 ) ( 400 32 0 ) ( 400 0 32 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 500 0 0 ) ( 500 0 32 ) ( 500 32 0 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 499 0 ) ( 0 499 32 ) ( 32 499 0 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 500 0 ) ( 32 500 0 ) ( 0 500 32 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 + ( 400 0 0 ) ( 400 32 0 ) ( 400 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 500 0 0 ) ( 500 0 32 ) ( 500 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 499 0 ) ( 0 499 32 ) ( 32 499 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 500 0 ) ( 32 500 0 ) ( 0 500 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 400 0 0 ) ( 400 32 0 ) ( 400 0 32 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 401 0 0 ) ( 401 0 32 ) ( 401 32 0 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 400 0 ) ( 0 400 32 ) ( 32 400 0 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 500 0 ) ( 32 500 0 ) ( 0 500 32 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 + ( 400 0 0 ) ( 400 32 0 ) ( 400 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 401 0 0 ) ( 401 0 32 ) ( 401 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 400 0 ) ( 0 400 32 ) ( 32 400 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 500 0 ) ( 32 500 0 ) ( 0 500 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 400 0 0 ) ( 400 32 0 ) ( 400 0 32 ) map/lab_games/lg_style_04_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 500 0 0 ) ( 500 0 32 ) ( 500 32 0 ) map/lab_games/lg_style_04_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 400 0 ) ( 0 400 32 ) ( 32 400 0 ) map/lab_games/lg_style_04_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 500 0 ) ( 32 500 0 ) ( 0 500 32 ) map/lab_games/lg_style_04_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_04_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_04_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 400 0 0 ) ( 400 32 0 ) ( 400 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 500 0 0 ) ( 500 0 32 ) ( 500 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 400 0 ) ( 0 400 32 ) ( 32 400 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 500 0 ) ( 32 500 0 ) ( 0 500 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 } { ( 400 0 0 ) ( 400 32 0 ) ( 400 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 @@ -171,20 +171,20 @@ ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 500 0 0 ) ( 500 32 0 ) ( 500 0 32 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 600 0 0 ) ( 600 0 32 ) ( 600 32 0 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 499 0 ) ( 0 499 32 ) ( 32 499 0 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 500 0 ) ( 32 500 0 ) ( 0 500 32 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 + ( 500 0 0 ) ( 500 32 0 ) ( 500 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 600 0 0 ) ( 600 0 32 ) ( 600 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 499 0 ) ( 0 499 32 ) ( 32 499 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 500 0 ) ( 32 500 0 ) ( 0 500 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 500 0 0 ) ( 500 32 0 ) ( 500 0 32 ) map/lab_games/lg_style_04_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 600 0 0 ) ( 600 0 32 ) ( 600 32 0 ) map/lab_games/lg_style_04_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 400 0 ) ( 0 400 32 ) ( 32 400 0 ) map/lab_games/lg_style_04_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 500 0 ) ( 32 500 0 ) ( 0 500 32 ) map/lab_games/lg_style_04_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_04_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_04_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 500 0 0 ) ( 500 32 0 ) ( 500 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 600 0 0 ) ( 600 0 32 ) ( 600 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 400 0 ) ( 0 400 32 ) ( 32 400 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 500 0 ) ( 32 500 0 ) ( 0 500 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 } { ( 500 0 0 ) ( 500 32 0 ) ( 500 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 @@ -195,20 +195,20 @@ ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 600 0 0 ) ( 600 32 0 ) ( 600 0 32 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 700 0 0 ) ( 700 0 32 ) ( 700 32 0 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 499 0 ) ( 0 499 32 ) ( 32 499 0 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 500 0 ) ( 32 500 0 ) ( 0 500 32 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 + ( 600 0 0 ) ( 600 32 0 ) ( 600 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 700 0 0 ) ( 700 0 32 ) ( 700 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 499 0 ) ( 0 499 32 ) ( 32 499 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 500 0 ) ( 32 500 0 ) ( 0 500 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 600 0 0 ) ( 600 32 0 ) ( 600 0 32 ) map/lab_games/lg_style_04_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 700 0 0 ) ( 700 0 32 ) ( 700 32 0 ) map/lab_games/lg_style_04_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 400 0 ) ( 0 400 32 ) ( 32 400 0 ) map/lab_games/lg_style_04_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 500 0 ) ( 32 500 0 ) ( 0 500 32 ) map/lab_games/lg_style_04_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_04_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_04_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 600 0 0 ) ( 600 32 0 ) ( 600 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 700 0 0 ) ( 700 0 32 ) ( 700 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 400 0 ) ( 0 400 32 ) ( 32 400 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 500 0 ) ( 32 500 0 ) ( 0 500 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 } { ( 600 0 0 ) ( 600 32 0 ) ( 600 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 @@ -219,28 +219,28 @@ ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 700 0 0 ) ( 700 32 0 ) ( 700 0 32 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 800 0 0 ) ( 800 0 32 ) ( 800 32 0 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 499 0 ) ( 0 499 32 ) ( 32 499 0 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 500 0 ) ( 32 500 0 ) ( 0 500 32 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 + ( 700 0 0 ) ( 700 32 0 ) ( 700 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 800 0 0 ) ( 800 0 32 ) ( 800 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 499 0 ) ( 0 499 32 ) ( 32 499 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 500 0 ) ( 32 500 0 ) ( 0 500 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 799 0 0 ) ( 799 32 0 ) ( 799 0 32 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 800 0 0 ) ( 800 0 32 ) ( 800 32 0 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 400 0 ) ( 0 400 32 ) ( 32 400 0 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 500 0 ) ( 32 500 0 ) ( 0 500 32 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow 0 0 0 0.0976562 0.0976562 0 0 0 + ( 799 0 0 ) ( 799 32 0 ) ( 799 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 800 0 0 ) ( 800 0 32 ) ( 800 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 400 0 ) ( 0 400 32 ) ( 32 400 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 500 0 ) ( 32 500 0 ) ( 0 500 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 700 0 0 ) ( 700 32 0 ) ( 700 0 32 ) map/lab_games/lg_style_04_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 800 0 0 ) ( 800 0 32 ) ( 800 32 0 ) map/lab_games/lg_style_04_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 400 0 ) ( 0 400 32 ) ( 32 400 0 ) map/lab_games/lg_style_04_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 500 0 ) ( 32 500 0 ) ( 0 500 32 ) map/lab_games/lg_style_04_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_04_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_04_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 700 0 0 ) ( 700 32 0 ) ( 700 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 800 0 0 ) ( 800 0 32 ) ( 800 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 400 0 ) ( 0 400 32 ) ( 32 400 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 500 0 ) ( 32 500 0 ) ( 0 500 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 } { ( 700 0 0 ) ( 700 32 0 ) ( 700 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 @@ -707,20 +707,20 @@ ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 300 0 0 ) ( 300 32 0 ) ( 300 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 301 0 0 ) ( 301 0 32 ) ( 301 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 0 200 32 ) ( 32 200 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 300 0 ) ( 32 300 0 ) ( 0 300 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 300 0 0 ) ( 300 32 0 ) ( 300 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 301 0 0 ) ( 301 0 32 ) ( 301 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 0 200 32 ) ( 32 200 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 300 0 ) ( 32 300 0 ) ( 0 300 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 300 0 0 ) ( 300 32 0 ) ( 300 0 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 400 0 0 ) ( 400 0 32 ) ( 400 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 0 200 32 ) ( 32 200 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 300 0 ) ( 32 300 0 ) ( 0 300 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 300 0 0 ) ( 300 32 0 ) ( 300 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 400 0 0 ) ( 400 0 32 ) ( 400 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 0 200 32 ) ( 32 200 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 300 0 ) ( 32 300 0 ) ( 0 300 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 } { ( 300 0 0 ) ( 300 32 0 ) ( 300 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 @@ -731,12 +731,12 @@ ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 400 0 0 ) ( 400 32 0 ) ( 400 0 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 500 0 0 ) ( 500 0 32 ) ( 500 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 0 200 32 ) ( 32 200 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 300 0 ) ( 32 300 0 ) ( 0 300 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 400 0 0 ) ( 400 32 0 ) ( 400 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 500 0 0 ) ( 500 0 32 ) ( 500 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 0 200 32 ) ( 32 200 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 300 0 ) ( 32 300 0 ) ( 0 300 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 } { ( 400 0 0 ) ( 400 32 0 ) ( 400 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 @@ -747,20 +747,20 @@ ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 599 0 0 ) ( 599 32 0 ) ( 599 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 600 0 0 ) ( 600 0 32 ) ( 600 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 0 200 32 ) ( 32 200 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 300 0 ) ( 32 300 0 ) ( 0 300 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 599 0 0 ) ( 599 32 0 ) ( 599 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 600 0 0 ) ( 600 0 32 ) ( 600 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 0 200 32 ) ( 32 200 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 300 0 ) ( 32 300 0 ) ( 0 300 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 500 0 0 ) ( 500 32 0 ) ( 500 0 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 600 0 0 ) ( 600 0 32 ) ( 600 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 0 200 32 ) ( 32 200 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 300 0 ) ( 32 300 0 ) ( 0 300 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 500 0 0 ) ( 500 32 0 ) ( 500 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 600 0 0 ) ( 600 0 32 ) ( 600 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 0 200 32 ) ( 32 200 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 300 0 ) ( 32 300 0 ) ( 0 300 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 } { ( 500 0 0 ) ( 500 32 0 ) ( 500 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 @@ -771,28 +771,28 @@ ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 999 0 0 ) ( 999 32 0 ) ( 999 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1000 0 0 ) ( 1000 0 32 ) ( 1000 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 0 200 32 ) ( 32 200 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 300 0 ) ( 32 300 0 ) ( 0 300 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 999 0 0 ) ( 999 32 0 ) ( 999 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1000 0 0 ) ( 1000 0 32 ) ( 1000 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 0 200 32 ) ( 32 200 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 300 0 ) ( 32 300 0 ) ( 0 300 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 900 0 0 ) ( 900 32 0 ) ( 900 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 901 0 0 ) ( 901 0 32 ) ( 901 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 0 200 32 ) ( 32 200 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 300 0 ) ( 32 300 0 ) ( 0 300 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 900 0 0 ) ( 900 32 0 ) ( 900 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 901 0 0 ) ( 901 0 32 ) ( 901 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 0 200 32 ) ( 32 200 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 300 0 ) ( 32 300 0 ) ( 0 300 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 900 0 0 ) ( 900 32 0 ) ( 900 0 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1000 0 0 ) ( 1000 0 32 ) ( 1000 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 0 200 32 ) ( 32 200 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 300 0 ) ( 32 300 0 ) ( 0 300 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 900 0 0 ) ( 900 32 0 ) ( 900 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1000 0 0 ) ( 1000 0 32 ) ( 1000 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 0 200 32 ) ( 32 200 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 300 0 ) ( 32 300 0 ) ( 0 300 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 } { ( 900 0 0 ) ( 900 32 0 ) ( 900 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 @@ -803,28 +803,28 @@ ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 1499 0 0 ) ( 1499 32 0 ) ( 1499 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1500 0 0 ) ( 1500 0 32 ) ( 1500 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 0 200 32 ) ( 32 200 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 300 0 ) ( 32 300 0 ) ( 0 300 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1499 0 0 ) ( 1499 32 0 ) ( 1499 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1500 0 0 ) ( 1500 0 32 ) ( 1500 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 0 200 32 ) ( 32 200 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 300 0 ) ( 32 300 0 ) ( 0 300 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 1400 0 0 ) ( 1400 32 0 ) ( 1400 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1401 0 0 ) ( 1401 0 32 ) ( 1401 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 0 200 32 ) ( 32 200 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 300 0 ) ( 32 300 0 ) ( 0 300 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1400 0 0 ) ( 1400 32 0 ) ( 1400 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1401 0 0 ) ( 1401 0 32 ) ( 1401 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 0 200 32 ) ( 32 200 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 300 0 ) ( 32 300 0 ) ( 0 300 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 1400 0 0 ) ( 1400 32 0 ) ( 1400 0 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1500 0 0 ) ( 1500 0 32 ) ( 1500 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 0 200 32 ) ( 32 200 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 300 0 ) ( 32 300 0 ) ( 0 300 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1400 0 0 ) ( 1400 32 0 ) ( 1400 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1500 0 0 ) ( 1500 0 32 ) ( 1500 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 0 200 32 ) ( 32 200 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 300 0 ) ( 32 300 0 ) ( 0 300 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 } { ( 1400 0 0 ) ( 1400 32 0 ) ( 1400 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 @@ -859,28 +859,28 @@ ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 100 0 0 ) ( 100 32 0 ) ( 100 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 200 0 0 ) ( 200 0 32 ) ( 200 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 199 0 ) ( 0 199 32 ) ( 32 199 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 100 0 0 ) ( 100 32 0 ) ( 100 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 200 0 0 ) ( 200 0 32 ) ( 200 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 199 0 ) ( 0 199 32 ) ( 32 199 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 199 0 0 ) ( 199 32 0 ) ( 199 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 200 0 0 ) ( 200 0 32 ) ( 200 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 199 0 0 ) ( 199 32 0 ) ( 199 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 200 0 0 ) ( 200 0 32 ) ( 200 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 100 0 0 ) ( 100 32 0 ) ( 100 0 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 200 0 0 ) ( 200 0 32 ) ( 200 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 100 0 0 ) ( 100 32 0 ) ( 100 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 200 0 0 ) ( 200 0 32 ) ( 200 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 } { ( 100 0 0 ) ( 100 32 0 ) ( 100 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 @@ -891,28 +891,28 @@ ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 300 0 0 ) ( 300 32 0 ) ( 300 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 400 0 0 ) ( 400 0 32 ) ( 400 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 101 0 ) ( 32 101 0 ) ( 0 101 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 300 0 0 ) ( 300 32 0 ) ( 300 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 400 0 0 ) ( 400 0 32 ) ( 400 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 101 0 ) ( 32 101 0 ) ( 0 101 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 300 0 0 ) ( 300 32 0 ) ( 300 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 301 0 0 ) ( 301 0 32 ) ( 301 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 300 0 0 ) ( 300 32 0 ) ( 300 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 301 0 0 ) ( 301 0 32 ) ( 301 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 300 0 0 ) ( 300 32 0 ) ( 300 0 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 400 0 0 ) ( 400 0 32 ) ( 400 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 300 0 0 ) ( 300 32 0 ) ( 300 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 400 0 0 ) ( 400 0 32 ) ( 400 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 } { ( 300 0 0 ) ( 300 32 0 ) ( 300 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 @@ -923,20 +923,20 @@ ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 400 0 0 ) ( 400 32 0 ) ( 400 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 500 0 0 ) ( 500 0 32 ) ( 500 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 101 0 ) ( 32 101 0 ) ( 0 101 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 400 0 0 ) ( 400 32 0 ) ( 400 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 500 0 0 ) ( 500 0 32 ) ( 500 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 101 0 ) ( 32 101 0 ) ( 0 101 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 400 0 0 ) ( 400 32 0 ) ( 400 0 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 500 0 0 ) ( 500 0 32 ) ( 500 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 400 0 0 ) ( 400 32 0 ) ( 400 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 500 0 0 ) ( 500 0 32 ) ( 500 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 } { ( 400 0 0 ) ( 400 32 0 ) ( 400 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 @@ -947,28 +947,28 @@ ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 599 0 0 ) ( 599 32 0 ) ( 599 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 600 0 0 ) ( 600 0 32 ) ( 600 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 599 0 0 ) ( 599 32 0 ) ( 599 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 600 0 0 ) ( 600 0 32 ) ( 600 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 500 0 0 ) ( 500 32 0 ) ( 500 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 600 0 0 ) ( 600 0 32 ) ( 600 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 101 0 ) ( 32 101 0 ) ( 0 101 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 500 0 0 ) ( 500 32 0 ) ( 500 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 600 0 0 ) ( 600 0 32 ) ( 600 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 101 0 ) ( 32 101 0 ) ( 0 101 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 500 0 0 ) ( 500 32 0 ) ( 500 0 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 600 0 0 ) ( 600 0 32 ) ( 600 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 500 0 0 ) ( 500 32 0 ) ( 500 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 600 0 0 ) ( 600 0 32 ) ( 600 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 } { ( 500 0 0 ) ( 500 32 0 ) ( 500 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 @@ -979,36 +979,36 @@ ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 700 0 0 ) ( 700 32 0 ) ( 700 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 800 0 0 ) ( 800 0 32 ) ( 800 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 199 0 ) ( 0 199 32 ) ( 32 199 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 700 0 0 ) ( 700 32 0 ) ( 700 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 800 0 0 ) ( 800 0 32 ) ( 800 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 199 0 ) ( 0 199 32 ) ( 32 199 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 700 0 0 ) ( 700 32 0 ) ( 700 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 800 0 0 ) ( 800 0 32 ) ( 800 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 101 0 ) ( 32 101 0 ) ( 0 101 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 700 0 0 ) ( 700 32 0 ) ( 700 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 800 0 0 ) ( 800 0 32 ) ( 800 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 101 0 ) ( 32 101 0 ) ( 0 101 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 700 0 0 ) ( 700 32 0 ) ( 700 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 701 0 0 ) ( 701 0 32 ) ( 701 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 700 0 0 ) ( 700 32 0 ) ( 700 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 701 0 0 ) ( 701 0 32 ) ( 701 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 700 0 0 ) ( 700 32 0 ) ( 700 0 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 800 0 0 ) ( 800 0 32 ) ( 800 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 700 0 0 ) ( 700 32 0 ) ( 700 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 800 0 0 ) ( 800 0 32 ) ( 800 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 } { ( 700 0 0 ) ( 700 32 0 ) ( 700 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 @@ -1019,28 +1019,28 @@ ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 800 0 0 ) ( 800 32 0 ) ( 800 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 900 0 0 ) ( 900 0 32 ) ( 900 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 199 0 ) ( 0 199 32 ) ( 32 199 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 800 0 0 ) ( 800 32 0 ) ( 800 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 900 0 0 ) ( 900 0 32 ) ( 900 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 199 0 ) ( 0 199 32 ) ( 32 199 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 800 0 0 ) ( 800 32 0 ) ( 800 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 900 0 0 ) ( 900 0 32 ) ( 900 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 101 0 ) ( 32 101 0 ) ( 0 101 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 800 0 0 ) ( 800 32 0 ) ( 800 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 900 0 0 ) ( 900 0 32 ) ( 900 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 101 0 ) ( 32 101 0 ) ( 0 101 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 800 0 0 ) ( 800 32 0 ) ( 800 0 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 900 0 0 ) ( 900 0 32 ) ( 900 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 800 0 0 ) ( 800 32 0 ) ( 800 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 900 0 0 ) ( 900 0 32 ) ( 900 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 } { ( 800 0 0 ) ( 800 32 0 ) ( 800 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 @@ -1051,20 +1051,20 @@ ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 900 0 0 ) ( 900 32 0 ) ( 900 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1000 0 0 ) ( 1000 0 32 ) ( 1000 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 101 0 ) ( 32 101 0 ) ( 0 101 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 900 0 0 ) ( 900 32 0 ) ( 900 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1000 0 0 ) ( 1000 0 32 ) ( 1000 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 101 0 ) ( 32 101 0 ) ( 0 101 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 900 0 0 ) ( 900 32 0 ) ( 900 0 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1000 0 0 ) ( 1000 0 32 ) ( 1000 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 900 0 0 ) ( 900 32 0 ) ( 900 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1000 0 0 ) ( 1000 0 32 ) ( 1000 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 } { ( 900 0 0 ) ( 900 32 0 ) ( 900 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 @@ -1075,28 +1075,28 @@ ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 1000 0 0 ) ( 1000 32 0 ) ( 1000 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1100 0 0 ) ( 1100 0 32 ) ( 1100 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 199 0 ) ( 0 199 32 ) ( 32 199 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1000 0 0 ) ( 1000 32 0 ) ( 1000 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1100 0 0 ) ( 1100 0 32 ) ( 1100 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 199 0 ) ( 0 199 32 ) ( 32 199 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 1000 0 0 ) ( 1000 32 0 ) ( 1000 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1100 0 0 ) ( 1100 0 32 ) ( 1100 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 101 0 ) ( 32 101 0 ) ( 0 101 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1000 0 0 ) ( 1000 32 0 ) ( 1000 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1100 0 0 ) ( 1100 0 32 ) ( 1100 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 101 0 ) ( 32 101 0 ) ( 0 101 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 1000 0 0 ) ( 1000 32 0 ) ( 1000 0 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1100 0 0 ) ( 1100 0 32 ) ( 1100 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1000 0 0 ) ( 1000 32 0 ) ( 1000 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1100 0 0 ) ( 1100 0 32 ) ( 1100 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 } { ( 1000 0 0 ) ( 1000 32 0 ) ( 1000 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 @@ -1107,36 +1107,36 @@ ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 1100 0 0 ) ( 1100 32 0 ) ( 1100 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1200 0 0 ) ( 1200 0 32 ) ( 1200 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 199 0 ) ( 0 199 32 ) ( 32 199 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1100 0 0 ) ( 1100 32 0 ) ( 1100 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1200 0 0 ) ( 1200 0 32 ) ( 1200 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 199 0 ) ( 0 199 32 ) ( 32 199 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 1199 0 0 ) ( 1199 32 0 ) ( 1199 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1200 0 0 ) ( 1200 0 32 ) ( 1200 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1199 0 0 ) ( 1199 32 0 ) ( 1199 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1200 0 0 ) ( 1200 0 32 ) ( 1200 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 1100 0 0 ) ( 1100 32 0 ) ( 1100 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1200 0 0 ) ( 1200 0 32 ) ( 1200 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 101 0 ) ( 32 101 0 ) ( 0 101 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1100 0 0 ) ( 1100 32 0 ) ( 1100 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1200 0 0 ) ( 1200 0 32 ) ( 1200 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 101 0 ) ( 32 101 0 ) ( 0 101 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 1100 0 0 ) ( 1100 32 0 ) ( 1100 0 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1200 0 0 ) ( 1200 0 32 ) ( 1200 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1100 0 0 ) ( 1100 32 0 ) ( 1100 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1200 0 0 ) ( 1200 0 32 ) ( 1200 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 } { ( 1100 0 0 ) ( 1100 32 0 ) ( 1100 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 @@ -1147,266 +1147,1082 @@ ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 1300 0 0 ) ( 1300 32 0 ) ( 1300 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1400 0 0 ) ( 1400 0 32 ) ( 1400 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 199 0 ) ( 0 199 32 ) ( 32 199 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - } - { - ( 1300 0 0 ) ( 1300 32 0 ) ( 1300 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1400 0 0 ) ( 1400 0 32 ) ( 1400 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 101 0 ) ( 32 101 0 ) ( 0 101 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1300 0 0 ) ( 1300 32 0 ) ( 1300 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1400 0 0 ) ( 1400 0 32 ) ( 1400 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 199 0 ) ( 0 199 32 ) ( 32 199 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 } { - ( 1300 0 0 ) ( 1300 32 0 ) ( 1300 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1301 0 0 ) ( 1301 0 32 ) ( 1301 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - } + ( 1300 0 0 ) ( 1300 32 0 ) ( 1300 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1400 0 0 ) ( 1400 0 32 ) ( 1400 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 101 0 ) ( 32 101 0 ) ( 0 101 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + } + { + ( 1300 0 0 ) ( 1300 32 0 ) ( 1300 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1301 0 0 ) ( 1301 0 32 ) ( 1301 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + } + { + ( 1300 0 0 ) ( 1300 32 0 ) ( 1300 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1400 0 0 ) ( 1400 0 32 ) ( 1400 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + } + { + ( 1300 0 0 ) ( 1300 32 0 ) ( 1300 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1400 0 0 ) ( 1400 0 32 ) ( 1400 32 0 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 99 ) ( 32 0 99 ) ( 0 32 99 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + } + { + ( 1400 0 0 ) ( 1400 32 0 ) ( 1400 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1500 0 0 ) ( 1500 0 32 ) ( 1500 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 101 0 ) ( 32 101 0 ) ( 0 101 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + } + { + ( 1400 0 0 ) ( 1400 32 0 ) ( 1400 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1500 0 0 ) ( 1500 0 32 ) ( 1500 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + } + { + ( 1400 0 0 ) ( 1400 32 0 ) ( 1400 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1500 0 0 ) ( 1500 0 32 ) ( 1500 32 0 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 99 ) ( 32 0 99 ) ( 0 32 99 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + } + { + ( 1500 0 0 ) ( 1500 32 0 ) ( 1500 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1600 0 0 ) ( 1600 0 32 ) ( 1600 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 199 0 ) ( 0 199 32 ) ( 32 199 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + } + { + ( 1599 0 0 ) ( 1599 32 0 ) ( 1599 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1600 0 0 ) ( 1600 0 32 ) ( 1600 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + } + { + ( 1500 0 0 ) ( 1500 32 0 ) ( 1500 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1600 0 0 ) ( 1600 0 32 ) ( 1600 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 101 0 ) ( 32 101 0 ) ( 0 101 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + } + { + ( 1500 0 0 ) ( 1500 32 0 ) ( 1500 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1600 0 0 ) ( 1600 0 32 ) ( 1600 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + } + { + ( 1500 0 0 ) ( 1500 32 0 ) ( 1500 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1600 0 0 ) ( 1600 0 32 ) ( 1600 32 0 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 99 ) ( 32 0 99 ) ( 0 32 99 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + } + { + ( 0 0 0 ) ( 0 32 0 ) ( 0 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 100 0 0 ) ( 100 0 32 ) ( 100 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 0 0 32 ) ( 32 0 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 1 0 ) ( 32 1 0 ) ( 0 1 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + } + { + ( 0 0 0 ) ( 0 32 0 ) ( 0 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 1 0 0 ) ( 1 0 32 ) ( 1 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 0 0 32 ) ( 32 0 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 32 100 0 ) ( 0 100 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + } + { + ( 0 0 0 ) ( 0 32 0 ) ( 0 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 100 0 0 ) ( 100 0 32 ) ( 100 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 0 0 32 ) ( 32 0 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 32 100 0 ) ( 0 100 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + } + { + ( 0 0 0 ) ( 0 32 0 ) ( 0 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 100 0 0 ) ( 100 0 32 ) ( 100 32 0 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 0 0 32 ) ( 32 0 0 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 32 100 0 ) ( 0 100 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 99 ) ( 32 0 99 ) ( 0 32 99 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + } + { + ( 199 0 0 ) ( 199 32 0 ) ( 199 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 200 0 0 ) ( 200 0 32 ) ( 200 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 0 0 32 ) ( 32 0 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 32 100 0 ) ( 0 100 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + } + { + ( 100 0 0 ) ( 100 32 0 ) ( 100 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 200 0 0 ) ( 200 0 32 ) ( 200 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 0 0 32 ) ( 32 0 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 1 0 ) ( 32 1 0 ) ( 0 1 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + } + { + ( 100 0 0 ) ( 100 32 0 ) ( 100 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 200 0 0 ) ( 200 0 32 ) ( 200 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 0 0 32 ) ( 32 0 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 32 100 0 ) ( 0 100 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + } + { + ( 100 0 0 ) ( 100 32 0 ) ( 100 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 200 0 0 ) ( 200 0 32 ) ( 200 32 0 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 0 ) ( 0 0 32 ) ( 32 0 0 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 100 0 ) ( 32 100 0 ) ( 0 100 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 99 ) ( 32 0 99 ) ( 0 32 99 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 66 598 34 0 1 ) ( 50 598 34 0 0.5 ) ( 34 598 34 0 0 ) ) + ( ( 66 598 50 0.5 1 ) ( 50 598 50 0.5 0.5 ) ( 34 598 50 0.5 0 ) ) + ( ( 66 598 66 1 1 ) ( 50 598 66 1 0.5 ) ( 34 598 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 2 566 34 0 1 ) ( 2 550 34 0 0.5 ) ( 2 534 34 0 0 ) ) + ( ( 2 566 50 0.5 1 ) ( 2 550 50 0.5 0.5 ) ( 2 534 50 0.5 0 ) ) + ( ( 2 566 66 1 1 ) ( 2 550 66 1 0.5 ) ( 2 534 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 166 598 34 0 1 ) ( 150 598 34 0 0.5 ) ( 134 598 34 0 0 ) ) + ( ( 166 598 50 0.5 1 ) ( 150 598 50 0.5 0.5 ) ( 134 598 50 0.5 0 ) ) + ( ( 166 598 66 1 1 ) ( 150 598 66 1 0.5 ) ( 134 598 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 134 502 34 0 1 ) ( 150 502 34 0 0.5 ) ( 166 502 34 0 0 ) ) + ( ( 134 502 50 0.5 1 ) ( 150 502 50 0.5 0.5 ) ( 166 502 50 0.5 0 ) ) + ( ( 134 502 66 1 1 ) ( 150 502 66 1 0.5 ) ( 166 502 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 266 598 34 0 1 ) ( 250 598 34 0 0.5 ) ( 234 598 34 0 0 ) ) + ( ( 266 598 50 0.5 1 ) ( 250 598 50 0.5 0.5 ) ( 234 598 50 0.5 0 ) ) + ( ( 266 598 66 1 1 ) ( 250 598 66 1 0.5 ) ( 234 598 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 298 534 34 0 1 ) ( 298 550 34 0 0.5 ) ( 298 566 34 0 0 ) ) + ( ( 298 534 50 0.5 1 ) ( 298 550 50 0.5 0.5 ) ( 298 566 50 0.5 0 ) ) + ( ( 298 534 66 1 1 ) ( 298 550 66 1 0.5 ) ( 298 566 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 234 502 34 0 1 ) ( 250 502 34 0 0.5 ) ( 266 502 34 0 0 ) ) + ( ( 234 502 50 0.5 1 ) ( 250 502 50 0.5 0.5 ) ( 266 502 50 0.5 0 ) ) + ( ( 234 502 66 1 1 ) ( 250 502 66 1 0.5 ) ( 266 502 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 98 434 34 0 1 ) ( 98 450 34 0 0.5 ) ( 98 466 34 0 0 ) ) + ( ( 98 434 50 0.5 1 ) ( 98 450 50 0.5 0.5 ) ( 98 466 50 0.5 0 ) ) + ( ( 98 434 66 1 1 ) ( 98 450 66 1 0.5 ) ( 98 466 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 2 466 34 0 1 ) ( 2 450 34 0 0.5 ) ( 2 434 34 0 0 ) ) + ( ( 2 466 50 0.5 1 ) ( 2 450 50 0.5 0.5 ) ( 2 434 50 0.5 0 ) ) + ( ( 2 466 66 1 1 ) ( 2 450 66 1 0.5 ) ( 2 434 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 466 498 34 0 1 ) ( 450 498 34 0 0.5 ) ( 434 498 34 0 0 ) ) + ( ( 466 498 50 0.5 1 ) ( 450 498 50 0.5 0.5 ) ( 434 498 50 0.5 0 ) ) + ( ( 466 498 66 1 1 ) ( 450 498 66 1 0.5 ) ( 434 498 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 402 466 34 0 1 ) ( 402 450 34 0 0.5 ) ( 402 434 34 0 0 ) ) + ( ( 402 466 50 0.5 1 ) ( 402 450 50 0.5 0.5 ) ( 402 434 50 0.5 0 ) ) + ( ( 402 466 66 1 1 ) ( 402 450 66 1 0.5 ) ( 402 434 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 566 498 34 0 1 ) ( 550 498 34 0 0.5 ) ( 534 498 34 0 0 ) ) + ( ( 566 498 50 0.5 1 ) ( 550 498 50 0.5 0.5 ) ( 534 498 50 0.5 0 ) ) + ( ( 566 498 66 1 1 ) ( 550 498 66 1 0.5 ) ( 534 498 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 666 498 34 0 1 ) ( 650 498 34 0 0.5 ) ( 634 498 34 0 0 ) ) + ( ( 666 498 50 0.5 1 ) ( 650 498 50 0.5 0.5 ) ( 634 498 50 0.5 0 ) ) + ( ( 666 498 66 1 1 ) ( 650 498 66 1 0.5 ) ( 634 498 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 766 498 34 0 1 ) ( 750 498 34 0 0.5 ) ( 734 498 34 0 0 ) ) + ( ( 766 498 50 0.5 1 ) ( 750 498 50 0.5 0.5 ) ( 734 498 50 0.5 0 ) ) + ( ( 766 498 66 1 1 ) ( 750 498 66 1 0.5 ) ( 734 498 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 798 434 34 0 1 ) ( 798 450 34 0 0.5 ) ( 798 466 34 0 0 ) ) + ( ( 798 434 50 0.5 1 ) ( 798 450 50 0.5 0.5 ) ( 798 466 50 0.5 0 ) ) + ( ( 798 434 66 1 1 ) ( 798 450 66 1 0.5 ) ( 798 466 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 98 334 34 0 1 ) ( 98 350 34 0 0.5 ) ( 98 366 34 0 0 ) ) + ( ( 98 334 50 0.5 1 ) ( 98 350 50 0.5 0.5 ) ( 98 366 50 0.5 0 ) ) + ( ( 98 334 66 1 1 ) ( 98 350 66 1 0.5 ) ( 98 366 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 2 366 34 0 1 ) ( 2 350 34 0 0.5 ) ( 2 334 34 0 0 ) ) + ( ( 2 366 50 0.5 1 ) ( 2 350 50 0.5 0.5 ) ( 2 334 50 0.5 0 ) ) + ( ( 2 366 66 1 1 ) ( 2 350 66 1 0.5 ) ( 2 334 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 266 398 34 0 1 ) ( 250 398 34 0 0.5 ) ( 234 398 34 0 0 ) ) + ( ( 266 398 50 0.5 1 ) ( 250 398 50 0.5 0.5 ) ( 234 398 50 0.5 0 ) ) + ( ( 266 398 66 1 1 ) ( 250 398 66 1 0.5 ) ( 234 398 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 234 302 34 0 1 ) ( 250 302 34 0 0.5 ) ( 266 302 34 0 0 ) ) + ( ( 234 302 50 0.5 1 ) ( 250 302 50 0.5 0.5 ) ( 266 302 50 0.5 0 ) ) + ( ( 234 302 66 1 1 ) ( 250 302 66 1 0.5 ) ( 266 302 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 202 366 34 0 1 ) ( 202 350 34 0 0.5 ) ( 202 334 34 0 0 ) ) + ( ( 202 366 50 0.5 1 ) ( 202 350 50 0.5 0.5 ) ( 202 334 50 0.5 0 ) ) + ( ( 202 366 66 1 1 ) ( 202 350 66 1 0.5 ) ( 202 334 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 366 398 34 0 1 ) ( 350 398 34 0 0.5 ) ( 334 398 34 0 0 ) ) + ( ( 366 398 50 0.5 1 ) ( 350 398 50 0.5 0.5 ) ( 334 398 50 0.5 0 ) ) + ( ( 366 398 66 1 1 ) ( 350 398 66 1 0.5 ) ( 334 398 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 634 302 34 0 1 ) ( 650 302 34 0 0.5 ) ( 666 302 34 0 0 ) ) + ( ( 634 302 50 0.5 1 ) ( 650 302 50 0.5 0.5 ) ( 666 302 50 0.5 0 ) ) + ( ( 634 302 66 1 1 ) ( 650 302 66 1 0.5 ) ( 666 302 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 734 302 34 0 1 ) ( 750 302 34 0 0.5 ) ( 766 302 34 0 0 ) ) + ( ( 734 302 50 0.5 1 ) ( 750 302 50 0.5 0.5 ) ( 766 302 50 0.5 0 ) ) + ( ( 734 302 66 1 1 ) ( 750 302 66 1 0.5 ) ( 766 302 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 866 398 34 0 1 ) ( 850 398 34 0 0.5 ) ( 834 398 34 0 0 ) ) + ( ( 866 398 50 0.5 1 ) ( 850 398 50 0.5 0.5 ) ( 834 398 50 0.5 0 ) ) + ( ( 866 398 66 1 1 ) ( 850 398 66 1 0.5 ) ( 834 398 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 834 302 34 0 1 ) ( 850 302 34 0 0.5 ) ( 866 302 34 0 0 ) ) + ( ( 834 302 50 0.5 1 ) ( 850 302 50 0.5 0.5 ) ( 866 302 50 0.5 0 ) ) + ( ( 834 302 66 1 1 ) ( 850 302 66 1 0.5 ) ( 866 302 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 966 398 34 0 1 ) ( 950 398 34 0 0.5 ) ( 934 398 34 0 0 ) ) + ( ( 966 398 50 0.5 1 ) ( 950 398 50 0.5 0.5 ) ( 934 398 50 0.5 0 ) ) + ( ( 966 398 66 1 1 ) ( 950 398 66 1 0.5 ) ( 934 398 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 1066 398 34 0 1 ) ( 1050 398 34 0 0.5 ) ( 1034 398 34 0 0 ) ) + ( ( 1066 398 50 0.5 1 ) ( 1050 398 50 0.5 0.5 ) ( 1034 398 50 0.5 0 ) ) + ( ( 1066 398 66 1 1 ) ( 1050 398 66 1 0.5 ) ( 1034 398 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 1034 302 34 0 1 ) ( 1050 302 34 0 0.5 ) ( 1066 302 34 0 0 ) ) + ( ( 1034 302 50 0.5 1 ) ( 1050 302 50 0.5 0.5 ) ( 1066 302 50 0.5 0 ) ) + ( ( 1034 302 66 1 1 ) ( 1050 302 66 1 0.5 ) ( 1066 302 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 1166 398 34 0 1 ) ( 1150 398 34 0 0.5 ) ( 1134 398 34 0 0 ) ) + ( ( 1166 398 50 0.5 1 ) ( 1150 398 50 0.5 0.5 ) ( 1134 398 50 0.5 0 ) ) + ( ( 1166 398 66 1 1 ) ( 1150 398 66 1 0.5 ) ( 1134 398 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 1134 302 34 0 1 ) ( 1150 302 34 0 0.5 ) ( 1166 302 34 0 0 ) ) + ( ( 1134 302 50 0.5 1 ) ( 1150 302 50 0.5 0.5 ) ( 1166 302 50 0.5 0 ) ) + ( ( 1134 302 66 1 1 ) ( 1150 302 66 1 0.5 ) ( 1166 302 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 1266 398 34 0 1 ) ( 1250 398 34 0 0.5 ) ( 1234 398 34 0 0 ) ) + ( ( 1266 398 50 0.5 1 ) ( 1250 398 50 0.5 0.5 ) ( 1234 398 50 0.5 0 ) ) + ( ( 1266 398 66 1 1 ) ( 1250 398 66 1 0.5 ) ( 1234 398 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 1234 302 34 0 1 ) ( 1250 302 34 0 0.5 ) ( 1266 302 34 0 0 ) ) + ( ( 1234 302 50 0.5 1 ) ( 1250 302 50 0.5 0.5 ) ( 1266 302 50 0.5 0 ) ) + ( ( 1234 302 66 1 1 ) ( 1250 302 66 1 0.5 ) ( 1266 302 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 1366 398 34 0 1 ) ( 1350 398 34 0 0.5 ) ( 1334 398 34 0 0 ) ) + ( ( 1366 398 50 0.5 1 ) ( 1350 398 50 0.5 0.5 ) ( 1334 398 50 0.5 0 ) ) + ( ( 1366 398 66 1 1 ) ( 1350 398 66 1 0.5 ) ( 1334 398 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 1334 302 34 0 1 ) ( 1350 302 34 0 0.5 ) ( 1366 302 34 0 0 ) ) + ( ( 1334 302 50 0.5 1 ) ( 1350 302 50 0.5 0.5 ) ( 1366 302 50 0.5 0 ) ) + ( ( 1334 302 66 1 1 ) ( 1350 302 66 1 0.5 ) ( 1366 302 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 1466 398 34 0 1 ) ( 1450 398 34 0 0.5 ) ( 1434 398 34 0 0 ) ) + ( ( 1466 398 50 0.5 1 ) ( 1450 398 50 0.5 0.5 ) ( 1434 398 50 0.5 0 ) ) + ( ( 1466 398 66 1 1 ) ( 1450 398 66 1 0.5 ) ( 1434 398 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 1566 398 34 0 1 ) ( 1550 398 34 0 0.5 ) ( 1534 398 34 0 0 ) ) + ( ( 1566 398 50 0.5 1 ) ( 1550 398 50 0.5 0.5 ) ( 1534 398 50 0.5 0 ) ) + ( ( 1566 398 66 1 1 ) ( 1550 398 66 1 0.5 ) ( 1534 398 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 1598 334 34 0 1 ) ( 1598 350 34 0 0.5 ) ( 1598 366 34 0 0 ) ) + ( ( 1598 334 50 0.5 1 ) ( 1598 350 50 0.5 0.5 ) ( 1598 366 50 0.5 0 ) ) + ( ( 1598 334 66 1 1 ) ( 1598 350 66 1 0.5 ) ( 1598 366 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 1534 302 34 0 1 ) ( 1550 302 34 0 0.5 ) ( 1566 302 34 0 0 ) ) + ( ( 1534 302 50 0.5 1 ) ( 1550 302 50 0.5 0.5 ) ( 1566 302 50 0.5 0 ) ) + ( ( 1534 302 66 1 1 ) ( 1550 302 66 1 0.5 ) ( 1566 302 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 98 234 34 0 1 ) ( 98 250 34 0 0.5 ) ( 98 266 34 0 0 ) ) + ( ( 98 234 50 0.5 1 ) ( 98 250 50 0.5 0.5 ) ( 98 266 50 0.5 0 ) ) + ( ( 98 234 66 1 1 ) ( 98 250 66 1 0.5 ) ( 98 266 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 2 266 34 0 1 ) ( 2 250 34 0 0.5 ) ( 2 234 34 0 0 ) ) + ( ( 2 266 50 0.5 1 ) ( 2 250 50 0.5 0.5 ) ( 2 234 50 0.5 0 ) ) + ( ( 2 266 66 1 1 ) ( 2 250 66 1 0.5 ) ( 2 234 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 302 266 34 0 1 ) ( 302 250 34 0 0.5 ) ( 302 234 34 0 0 ) ) + ( ( 302 266 50 0.5 1 ) ( 302 250 50 0.5 0.5 ) ( 302 234 50 0.5 0 ) ) + ( ( 302 266 66 1 1 ) ( 302 250 66 1 0.5 ) ( 302 234 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 598 234 34 0 1 ) ( 598 250 34 0 0.5 ) ( 598 266 34 0 0 ) ) + ( ( 598 234 50 0.5 1 ) ( 598 250 50 0.5 0.5 ) ( 598 266 50 0.5 0 ) ) + ( ( 598 234 66 1 1 ) ( 598 250 66 1 0.5 ) ( 598 266 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 998 234 34 0 1 ) ( 998 250 34 0 0.5 ) ( 998 266 34 0 0 ) ) + ( ( 998 234 50 0.5 1 ) ( 998 250 50 0.5 0.5 ) ( 998 266 50 0.5 0 ) ) + ( ( 998 234 66 1 1 ) ( 998 250 66 1 0.5 ) ( 998 266 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 902 266 34 0 1 ) ( 902 250 34 0 0.5 ) ( 902 234 34 0 0 ) ) + ( ( 902 266 50 0.5 1 ) ( 902 250 50 0.5 0.5 ) ( 902 234 50 0.5 0 ) ) + ( ( 902 266 66 1 1 ) ( 902 250 66 1 0.5 ) ( 902 234 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 1498 234 34 0 1 ) ( 1498 250 34 0 0.5 ) ( 1498 266 34 0 0 ) ) + ( ( 1498 234 50 0.5 1 ) ( 1498 250 50 0.5 0.5 ) ( 1498 266 50 0.5 0 ) ) + ( ( 1498 234 66 1 1 ) ( 1498 250 66 1 0.5 ) ( 1498 266 66 1 0 ) ) + ) + } + } { - ( 1300 0 0 ) ( 1300 32 0 ) ( 1300 0 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1400 0 0 ) ( 1400 0 32 ) ( 1400 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 1402 266 34 0 1 ) ( 1402 250 34 0 0.5 ) ( 1402 234 34 0 0 ) ) + ( ( 1402 266 50 0.5 1 ) ( 1402 250 50 0.5 0.5 ) ( 1402 234 50 0.5 0 ) ) + ( ( 1402 266 66 1 1 ) ( 1402 250 66 1 0.5 ) ( 1402 234 66 1 0 ) ) + ) + } } { - ( 1300 0 0 ) ( 1300 32 0 ) ( 1300 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1400 0 0 ) ( 1400 0 32 ) ( 1400 32 0 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 99 ) ( 32 0 99 ) ( 0 32 99 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 2 166 34 0 1 ) ( 2 150 34 0 0.5 ) ( 2 134 34 0 0 ) ) + ( ( 2 166 50 0.5 1 ) ( 2 150 50 0.5 0.5 ) ( 2 134 50 0.5 0 ) ) + ( ( 2 166 66 1 1 ) ( 2 150 66 1 0.5 ) ( 2 134 66 1 0 ) ) + ) + } } { - ( 1400 0 0 ) ( 1400 32 0 ) ( 1400 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1500 0 0 ) ( 1500 0 32 ) ( 1500 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 101 0 ) ( 32 101 0 ) ( 0 101 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 166 198 34 0 1 ) ( 150 198 34 0 0.5 ) ( 134 198 34 0 0 ) ) + ( ( 166 198 50 0.5 1 ) ( 150 198 50 0.5 0.5 ) ( 134 198 50 0.5 0 ) ) + ( ( 166 198 66 1 1 ) ( 150 198 66 1 0.5 ) ( 134 198 66 1 0 ) ) + ) + } } { - ( 1400 0 0 ) ( 1400 32 0 ) ( 1400 0 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1500 0 0 ) ( 1500 0 32 ) ( 1500 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 198 134 34 0 1 ) ( 198 150 34 0 0.5 ) ( 198 166 34 0 0 ) ) + ( ( 198 134 50 0.5 1 ) ( 198 150 50 0.5 0.5 ) ( 198 166 50 0.5 0 ) ) + ( ( 198 134 66 1 1 ) ( 198 150 66 1 0.5 ) ( 198 166 66 1 0 ) ) + ) + } } { - ( 1400 0 0 ) ( 1400 32 0 ) ( 1400 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1500 0 0 ) ( 1500 0 32 ) ( 1500 32 0 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 99 ) ( 32 0 99 ) ( 0 32 99 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 334 102 34 0 1 ) ( 350 102 34 0 0.5 ) ( 366 102 34 0 0 ) ) + ( ( 334 102 50 0.5 1 ) ( 350 102 50 0.5 0.5 ) ( 366 102 50 0.5 0 ) ) + ( ( 334 102 66 1 1 ) ( 350 102 66 1 0.5 ) ( 366 102 66 1 0 ) ) + ) + } } { - ( 1500 0 0 ) ( 1500 32 0 ) ( 1500 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1600 0 0 ) ( 1600 0 32 ) ( 1600 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 199 0 ) ( 0 199 32 ) ( 32 199 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 302 166 34 0 1 ) ( 302 150 34 0 0.5 ) ( 302 134 34 0 0 ) ) + ( ( 302 166 50 0.5 1 ) ( 302 150 50 0.5 0.5 ) ( 302 134 50 0.5 0 ) ) + ( ( 302 166 66 1 1 ) ( 302 150 66 1 0.5 ) ( 302 134 66 1 0 ) ) + ) + } } { - ( 1599 0 0 ) ( 1599 32 0 ) ( 1599 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1600 0 0 ) ( 1600 0 32 ) ( 1600 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 434 102 34 0 1 ) ( 450 102 34 0 0.5 ) ( 466 102 34 0 0 ) ) + ( ( 434 102 50 0.5 1 ) ( 450 102 50 0.5 0.5 ) ( 466 102 50 0.5 0 ) ) + ( ( 434 102 66 1 1 ) ( 450 102 66 1 0.5 ) ( 466 102 66 1 0 ) ) + ) + } } { - ( 1500 0 0 ) ( 1500 32 0 ) ( 1500 0 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1600 0 0 ) ( 1600 0 32 ) ( 1600 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 101 0 ) ( 32 101 0 ) ( 0 101 32 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_05_wall_yellow_bright 0 0 0 0.0976562 0.0976562 0 0 0 + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 598 134 34 0 1 ) ( 598 150 34 0 0.5 ) ( 598 166 34 0 0 ) ) + ( ( 598 134 50 0.5 1 ) ( 598 150 50 0.5 0.5 ) ( 598 166 50 0.5 0 ) ) + ( ( 598 134 66 1 1 ) ( 598 150 66 1 0.5 ) ( 598 166 66 1 0 ) ) + ) + } } { - ( 1500 0 0 ) ( 1500 32 0 ) ( 1500 0 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1600 0 0 ) ( 1600 0 32 ) ( 1600 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_05_floor_blue_bright 0 0 0 0.0976562 0.0976562 0 0 0 + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 534 102 34 0 1 ) ( 550 102 34 0 0.5 ) ( 566 102 34 0 0 ) ) + ( ( 534 102 50 0.5 1 ) ( 550 102 50 0.5 0.5 ) ( 566 102 50 0.5 0 ) ) + ( ( 534 102 66 1 1 ) ( 550 102 66 1 0.5 ) ( 566 102 66 1 0 ) ) + ) + } } { - ( 1500 0 0 ) ( 1500 32 0 ) ( 1500 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1600 0 0 ) ( 1600 0 32 ) ( 1600 32 0 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 0 100 32 ) ( 32 100 0 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 200 0 ) ( 32 200 0 ) ( 0 200 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 99 ) ( 32 0 99 ) ( 0 32 99 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 766 198 34 0 1 ) ( 750 198 34 0 0.5 ) ( 734 198 34 0 0 ) ) + ( ( 766 198 50 0.5 1 ) ( 750 198 50 0.5 0.5 ) ( 734 198 50 0.5 0 ) ) + ( ( 766 198 66 1 1 ) ( 750 198 66 1 0.5 ) ( 734 198 66 1 0 ) ) + ) + } } { - ( 0 0 0 ) ( 0 32 0 ) ( 0 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 - ( 100 0 0 ) ( 100 0 32 ) ( 100 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 0 0 32 ) ( 32 0 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 1 0 ) ( 32 1 0 ) ( 0 1 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 734 102 34 0 1 ) ( 750 102 34 0 0.5 ) ( 766 102 34 0 0 ) ) + ( ( 734 102 50 0.5 1 ) ( 750 102 50 0.5 0.5 ) ( 766 102 50 0.5 0 ) ) + ( ( 734 102 66 1 1 ) ( 750 102 66 1 0.5 ) ( 766 102 66 1 0 ) ) + ) + } } { - ( 0 0 0 ) ( 0 32 0 ) ( 0 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 - ( 1 0 0 ) ( 1 0 32 ) ( 1 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 0 0 32 ) ( 32 0 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 32 100 0 ) ( 0 100 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 702 166 34 0 1 ) ( 702 150 34 0 0.5 ) ( 702 134 34 0 0 ) ) + ( ( 702 166 50 0.5 1 ) ( 702 150 50 0.5 0.5 ) ( 702 134 50 0.5 0 ) ) + ( ( 702 166 66 1 1 ) ( 702 150 66 1 0.5 ) ( 702 134 66 1 0 ) ) + ) + } } { - ( 0 0 0 ) ( 0 32 0 ) ( 0 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 100 0 0 ) ( 100 0 32 ) ( 100 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 0 0 32 ) ( 32 0 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 32 100 0 ) ( 0 100 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 866 198 34 0 1 ) ( 850 198 34 0 0.5 ) ( 834 198 34 0 0 ) ) + ( ( 866 198 50 0.5 1 ) ( 850 198 50 0.5 0.5 ) ( 834 198 50 0.5 0 ) ) + ( ( 866 198 66 1 1 ) ( 850 198 66 1 0.5 ) ( 834 198 66 1 0 ) ) + ) + } } { - ( 0 0 0 ) ( 0 32 0 ) ( 0 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 100 0 0 ) ( 100 0 32 ) ( 100 32 0 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 0 0 32 ) ( 32 0 0 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 32 100 0 ) ( 0 100 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 99 ) ( 32 0 99 ) ( 0 32 99 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 834 102 34 0 1 ) ( 850 102 34 0 0.5 ) ( 866 102 34 0 0 ) ) + ( ( 834 102 50 0.5 1 ) ( 850 102 50 0.5 0.5 ) ( 866 102 50 0.5 0 ) ) + ( ( 834 102 66 1 1 ) ( 850 102 66 1 0.5 ) ( 866 102 66 1 0 ) ) + ) + } } { - ( 199 0 0 ) ( 199 32 0 ) ( 199 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 - ( 200 0 0 ) ( 200 0 32 ) ( 200 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 0 0 32 ) ( 32 0 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 32 100 0 ) ( 0 100 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 934 102 34 0 1 ) ( 950 102 34 0 0.5 ) ( 966 102 34 0 0 ) ) + ( ( 934 102 50 0.5 1 ) ( 950 102 50 0.5 0.5 ) ( 966 102 50 0.5 0 ) ) + ( ( 934 102 66 1 1 ) ( 950 102 66 1 0.5 ) ( 966 102 66 1 0 ) ) + ) + } } { - ( 100 0 0 ) ( 100 32 0 ) ( 100 0 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 - ( 200 0 0 ) ( 200 0 32 ) ( 200 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 0 0 32 ) ( 32 0 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 1 0 ) ( 32 1 0 ) ( 0 1 32 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/lg_style_01_wall_green 0 0 0 0.0976562 0.0976562 0 0 0 + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 1066 198 34 0 1 ) ( 1050 198 34 0 0.5 ) ( 1034 198 34 0 0 ) ) + ( ( 1066 198 50 0.5 1 ) ( 1050 198 50 0.5 0.5 ) ( 1034 198 50 0.5 0 ) ) + ( ( 1066 198 66 1 1 ) ( 1050 198 66 1 0.5 ) ( 1034 198 66 1 0 ) ) + ) + } } { - ( 100 0 0 ) ( 100 32 0 ) ( 100 0 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 200 0 0 ) ( 200 0 32 ) ( 200 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 0 0 32 ) ( 32 0 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 32 100 0 ) ( 0 100 32 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 1 ) ( 0 32 1 ) ( 32 0 1 ) map/lab_games/lg_style_01_floor_orange 0 0 0 0.0976562 0.0976562 0 0 0 + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 1034 102 34 0 1 ) ( 1050 102 34 0 0.5 ) ( 1066 102 34 0 0 ) ) + ( ( 1034 102 50 0.5 1 ) ( 1050 102 50 0.5 0.5 ) ( 1066 102 50 0.5 0 ) ) + ( ( 1034 102 66 1 1 ) ( 1050 102 66 1 0.5 ) ( 1066 102 66 1 0 ) ) + ) + } } { - ( 100 0 0 ) ( 100 32 0 ) ( 100 0 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 200 0 0 ) ( 200 0 32 ) ( 200 32 0 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 0 ) ( 0 0 32 ) ( 32 0 0 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 100 0 ) ( 32 100 0 ) ( 0 100 32 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 99 ) ( 32 0 99 ) ( 0 32 99 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/lab_games/fake_sky 0 0 0 0.0976562 0.0976562 0 0 0 + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 1166 198 34 0 1 ) ( 1150 198 34 0 0.5 ) ( 1134 198 34 0 0 ) ) + ( ( 1166 198 50 0.5 1 ) ( 1150 198 50 0.5 0.5 ) ( 1134 198 50 0.5 0 ) ) + ( ( 1166 198 66 1 1 ) ( 1150 198 66 1 0.5 ) ( 1134 198 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 1198 134 34 0 1 ) ( 1198 150 34 0 0.5 ) ( 1198 166 34 0 0 ) ) + ( ( 1198 134 50 0.5 1 ) ( 1198 150 50 0.5 0.5 ) ( 1198 166 50 0.5 0 ) ) + ( ( 1198 134 66 1 1 ) ( 1198 150 66 1 0.5 ) ( 1198 166 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 1134 102 34 0 1 ) ( 1150 102 34 0 0.5 ) ( 1166 102 34 0 0 ) ) + ( ( 1134 102 50 0.5 1 ) ( 1150 102 50 0.5 0.5 ) ( 1166 102 50 0.5 0 ) ) + ( ( 1134 102 66 1 1 ) ( 1150 102 66 1 0.5 ) ( 1166 102 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 1366 198 34 0 1 ) ( 1350 198 34 0 0.5 ) ( 1334 198 34 0 0 ) ) + ( ( 1366 198 50 0.5 1 ) ( 1350 198 50 0.5 0.5 ) ( 1334 198 50 0.5 0 ) ) + ( ( 1366 198 66 1 1 ) ( 1350 198 66 1 0.5 ) ( 1334 198 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 1334 102 34 0 1 ) ( 1350 102 34 0 0.5 ) ( 1366 102 34 0 0 ) ) + ( ( 1334 102 50 0.5 1 ) ( 1350 102 50 0.5 0.5 ) ( 1366 102 50 0.5 0 ) ) + ( ( 1334 102 66 1 1 ) ( 1350 102 66 1 0.5 ) ( 1366 102 66 1 0 ) ) + ) + } + } + { + patchDef2 + { + decal/lab_games/dec_img_style01_001 + ( 3 3 0 0 0 ) + ( + ( ( 1302 166 34 0 1 ) ( 1302 150 34 0 0.5 ) ( 1302 134 34 0 0 ) ) + ( ( 1302 166 50 0.5 1 ) ( 1302 150 50 0.5 0.5 ) ( 1302 134 50 0.5 0 ) ) + ( ( 1302 166 66 1 1 ) ( 1302 150 66 1 0.5 ) ( 1302 134 66 1 0 ) ) + ) + } } { patchDef2 { - decal/lab_games/dec_img_style02_006 + decal/lab_games/dec_img_style01_001 ( 3 3 0 0 0 ) ( - ( ( 598 173.708 26.2917 0 1 ) ( 598 173.708 50 0 0.5 ) ( 598 173.708 73.7083 0 0 ) ) - ( ( 598 150 26.2917 0.5 1 ) ( 598 150 50 0.5 0.5 ) ( 598 150 73.7083 0.5 0 ) ) - ( ( 598 126.292 26.2917 1 1 ) ( 598 126.292 50 1 0.5 ) ( 598 126.292 73.7083 1 0 ) ) + ( ( 1434 102 34 0 1 ) ( 1450 102 34 0 0.5 ) ( 1466 102 34 0 0 ) ) + ( ( 1434 102 50 0.5 1 ) ( 1450 102 50 0.5 0.5 ) ( 1466 102 50 0.5 0 ) ) + ( ( 1434 102 66 1 1 ) ( 1450 102 66 1 0.5 ) ( 1466 102 66 1 0 ) ) ) } } { patchDef2 { - decal/lab_games/dec_img_style01_003 + decal/lab_games/dec_img_style01_001 ( 3 3 0 0 0 ) ( - ( ( 2 336.048 36.0478 0 1 ) ( 2 336.048 50 0 0.5 ) ( 2 336.048 63.9522 0 0 ) ) - ( ( 2 350 36.0478 0.5 1 ) ( 2 350 50 0.5 0.5 ) ( 2 350 63.9522 0.5 0 ) ) - ( ( 2 363.952 36.0478 1 1 ) ( 2 363.952 50 1 0.5 ) ( 2 363.952 63.9522 1 0 ) ) + ( ( 1566 198 34 0 1 ) ( 1550 198 34 0 0.5 ) ( 1534 198 34 0 0 ) ) + ( ( 1566 198 50 0.5 1 ) ( 1550 198 50 0.5 0.5 ) ( 1534 198 50 0.5 0 ) ) + ( ( 1566 198 66 1 1 ) ( 1550 198 66 1 0.5 ) ( 1534 198 66 1 0 ) ) ) } } { patchDef2 { - decal/lab_games/dec_img_style01_020 + decal/lab_games/dec_img_style01_001 ( 3 3 0 0 0 ) ( - ( ( 713.854 198 13.8539 0 1 ) ( 713.854 198 50 0 0.5 ) ( 713.854 198 86.1461 0 0 ) ) - ( ( 750 198 13.8539 0.5 1 ) ( 750 198 50 0.5 0.5 ) ( 750 198 86.1461 0.5 0 ) ) - ( ( 786.146 198 13.8539 1 1 ) ( 786.146 198 50 1 0.5 ) ( 786.146 198 86.1461 1 0 ) ) + ( ( 1598 134 34 0 1 ) ( 1598 150 34 0 0.5 ) ( 1598 166 34 0 0 ) ) + ( ( 1598 134 50 0.5 1 ) ( 1598 150 50 0.5 0.5 ) ( 1598 166 50 0.5 0 ) ) + ( ( 1598 134 66 1 1 ) ( 1598 150 66 1 0.5 ) ( 1598 166 66 1 0 ) ) ) } } { patchDef2 { - decal/lab_games/dec_img_style01_013 + decal/lab_games/dec_img_style01_001 ( 3 3 0 0 0 ) ( - ( ( 98 366.2 33.7998 0 1 ) ( 98 366.2 50 0 0.5 ) ( 98 366.2 66.2002 0 0 ) ) - ( ( 98 350 33.7998 0.5 1 ) ( 98 350 50 0.5 0.5 ) ( 98 350 66.2002 0.5 0 ) ) - ( ( 98 333.8 33.7998 1 1 ) ( 98 333.8 50 1 0.5 ) ( 98 333.8 66.2002 1 0 ) ) + ( ( 1534 102 34 0 1 ) ( 1550 102 34 0 0.5 ) ( 1566 102 34 0 0 ) ) + ( ( 1534 102 50 0.5 1 ) ( 1550 102 50 0.5 0.5 ) ( 1566 102 50 0.5 0 ) ) + ( ( 1534 102 66 1 1 ) ( 1550 102 66 1 0.5 ) ( 1566 102 66 1 0 ) ) ) } } { patchDef2 { - decal/lab_games/dec_img_style03_020 + decal/lab_games/dec_img_style01_001 ( 3 3 0 0 0 ) ( - ( ( 298 579.469 20.5305 0 1 ) ( 298 579.469 50 0 0.5 ) ( 298 579.469 79.4695 0 0 ) ) - ( ( 298 550 20.5305 0.5 1 ) ( 298 550 50 0.5 0.5 ) ( 298 550 79.4695 0.5 0 ) ) - ( ( 298 520.531 20.5305 1 1 ) ( 298 520.531 50 1 0.5 ) ( 298 520.531 79.4695 1 0 ) ) + ( ( 34 2 34 0 1 ) ( 50 2 34 0 0.5 ) ( 66 2 34 0 0 ) ) + ( ( 34 2 50 0.5 1 ) ( 50 2 50 0.5 0.5 ) ( 66 2 50 0.5 0 ) ) + ( ( 34 2 66 1 1 ) ( 50 2 66 1 0.5 ) ( 66 2 66 1 0 ) ) ) } } { patchDef2 { - decal/lab_games/dec_img_style01_008 + decal/lab_games/dec_img_style01_001 ( 3 3 0 0 0 ) ( - ( ( 614.613 498 14.6129 0 1 ) ( 614.613 498 50 0 0.5 ) ( 614.613 498 85.3871 0 0 ) ) - ( ( 650 498 14.6129 0.5 1 ) ( 650 498 50 0.5 0.5 ) ( 650 498 85.3871 0.5 0 ) ) - ( ( 685.387 498 14.6129 1 1 ) ( 685.387 498 50 1 0.5 ) ( 685.387 498 85.3871 1 0 ) ) + ( ( 2 66 34 0 1 ) ( 2 50 34 0 0.5 ) ( 2 34 34 0 0 ) ) + ( ( 2 66 50 0.5 1 ) ( 2 50 50 0.5 0.5 ) ( 2 34 50 0.5 0 ) ) + ( ( 2 66 66 1 1 ) ( 2 50 66 1 0.5 ) ( 2 34 66 1 0 ) ) ) } } { patchDef2 { - decal/lab_games/dec_img_style03_006 + decal/lab_games/dec_img_style01_001 ( 3 3 0 0 0 ) ( - ( ( 181.675 502 18.3252 0 1 ) ( 181.675 502 50 0 0.5 ) ( 181.675 502 81.6748 0 0 ) ) - ( ( 150 502 18.3252 0.5 1 ) ( 150 502 50 0.5 0.5 ) ( 150 502 81.6748 0.5 0 ) ) - ( ( 118.325 502 18.3252 1 1 ) ( 118.325 502 50 1 0.5 ) ( 118.325 502 81.6748 1 0 ) ) + ( ( 198 34 34 0 1 ) ( 198 50 34 0 0.5 ) ( 198 66 34 0 0 ) ) + ( ( 198 34 50 0.5 1 ) ( 198 50 50 0.5 0.5 ) ( 198 66 50 0.5 0 ) ) + ( ( 198 34 66 1 1 ) ( 198 50 66 1 0.5 ) ( 198 66 66 1 0 ) ) ) } } { patchDef2 { - decal/lab_games/dec_img_style04_001 + decal/lab_games/dec_img_style01_001 ( 3 3 0 0 0 ) ( - ( ( 786.912 302 13.0883 0 1 ) ( 786.912 302 50 0 0.5 ) ( 786.912 302 86.9117 0 0 ) ) - ( ( 750 302 13.0883 0.5 1 ) ( 750 302 50 0.5 0.5 ) ( 750 302 86.9117 0.5 0 ) ) - ( ( 713.088 302 13.0883 1 1 ) ( 713.088 302 50 1 0.5 ) ( 713.088 302 86.9117 1 0 ) ) + ( ( 134 2 34 0 1 ) ( 150 2 34 0 0.5 ) ( 166 2 34 0 0 ) ) + ( ( 134 2 50 0.5 1 ) ( 150 2 50 0.5 0.5 ) ( 166 2 50 0.5 0 ) ) + ( ( 134 2 66 1 1 ) ( 150 2 66 1 0.5 ) ( 166 2 66 1 0 ) ) ) } } @@ -1430,7 +2246,7 @@ { "classname" "light" - "light" "55" + "light" "95" "origin" "450 450 70" "spawnflags" "0" "style" "0" @@ -1438,7 +2254,7 @@ { "classname" "light" - "light" "95" + "light" "85" "origin" "650 450 70" "spawnflags" "0" "style" "0" @@ -1446,7 +2262,7 @@ { "classname" "light" - "light" "60" + "light" "55" "origin" "350 350 70" "spawnflags" "0" "style" "0" @@ -1454,7 +2270,7 @@ { "classname" "light" - "light" "90" + "light" "55" "origin" "550 350 70" "spawnflags" "0" "style" "0" @@ -1462,7 +2278,7 @@ { "classname" "light" - "light" "70" + "light" "95" "origin" "750 350 70" "spawnflags" "0" "style" "0" @@ -1478,7 +2294,7 @@ { "classname" "light" - "light" "75" + "light" "90" "origin" "1150 350 70" "spawnflags" "0" "style" "0" @@ -1486,7 +2302,7 @@ { "classname" "light" - "light" "50" + "light" "70" "origin" "1350 350 70" "spawnflags" "0" "style" "0" @@ -1494,7 +2310,7 @@ { "classname" "light" - "light" "75" + "light" "60" "origin" "1550 350 70" "spawnflags" "0" "style" "0" @@ -1502,7 +2318,7 @@ { "classname" "light" - "light" "80" + "light" "75" "origin" "50 250 70" "spawnflags" "0" "style" "0" @@ -1510,7 +2326,7 @@ { "classname" "light" - "light" "65" + "light" "50" "origin" "450 250 70" "spawnflags" "0" "style" "0" @@ -1518,7 +2334,7 @@ { "classname" "light" - "light" "90" + "light" "75" "origin" "1450 250 70" "spawnflags" "0" "style" "0" @@ -1526,7 +2342,7 @@ { "classname" "light" - "light" "55" + "light" "80" "origin" "150 150 70" "spawnflags" "0" "style" "0" @@ -1534,7 +2350,7 @@ { "classname" "light" - "light" "90" + "light" "95" "origin" "350 150 70" "spawnflags" "0" "style" "0" @@ -1542,7 +2358,7 @@ { "classname" "light" - "light" "55" + "light" "90" "origin" "550 150 70" "spawnflags" "0" "style" "0" @@ -1550,7 +2366,7 @@ { "classname" "light" - "light" "50" + "light" "75" "origin" "750 150 70" "spawnflags" "0" "style" "0" @@ -1558,7 +2374,7 @@ { "classname" "light" - "light" "75" + "light" "65" "origin" "950 150 70" "spawnflags" "0" "style" "0" @@ -1574,7 +2390,7 @@ { "classname" "light" - "light" "85" + "light" "55" "origin" "1350 150 70" "spawnflags" "0" "style" "0" @@ -1582,7 +2398,7 @@ { "classname" "light" - "light" "60" + "light" "90" "origin" "1550 150 70" "spawnflags" "0" "style" "0" @@ -1590,7 +2406,7 @@ { "classname" "light" - "light" "50" + "light" "55" "origin" "50 50 70" "spawnflags" "0" "style" "0" @@ -1601,12 +2417,12 @@ "angle" "90" "targetname" "door_11_3" { - ( 1149 0 0 ) ( 1149 32 0 ) ( 1149 0 32 ) map/fut_door_d 0 0 0 0.0996094 0.0996094 0 0 0 - ( 1151 0 0 ) ( 1151 0 32 ) ( 1151 32 0 ) map/fut_door_d 0 0 0 0.0996094 0.0996094 0 0 0 - ( 0 301 0 ) ( 0 301 32 ) ( 32 301 0 ) map/fut_door_d 0 0 0 0.0996094 0.0996094 0 0 0 - ( 0 399 0 ) ( 32 399 0 ) ( 0 399 32 ) map/fut_door_d 0 0 0 0.0996094 0.0996094 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/fut_door_d 0 0 0 0.0996094 0.0996094 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/fut_door_d 0 0 0 0.0996094 0.0996094 0 0 0 + ( 1149 0 0 ) ( 1149 32 0 ) ( 1149 0 32 ) map/fut_door_d 73 0 0 -0.0957031 0.0976562 0 0 0 + ( 1151 0 0 ) ( 1151 0 32 ) ( 1151 32 0 ) map/fut_door_d 73 0 0 -0.0957031 0.0976562 0 0 0 + ( 0 301 0 ) ( 0 301 32 ) ( 32 301 0 ) map/fut_door_d 512 0 0 -0.00195312 0.0976562 0 0 0 + ( 0 399 0 ) ( 32 399 0 ) ( 0 399 32 ) map/fut_door_d 512 0 0 -0.00195312 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/fut_door_d 512 73 0 -0.00195312 0.0957031 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/fut_door_d 512 73 0 -0.00195312 0.0957031 0 0 0 } } @@ -1648,12 +2464,12 @@ "angle" "0" "targetname" "door_9_2" { - ( 901 0 0 ) ( 901 32 0 ) ( 901 0 32 ) map/fut_door_d 0 0 0 0.0996094 0.0996094 0 0 0 - ( 999 0 0 ) ( 999 0 32 ) ( 999 32 0 ) map/fut_door_d 0 0 0 0.0996094 0.0996094 0 0 0 - ( 0 249 0 ) ( 0 249 32 ) ( 32 249 0 ) map/fut_door_d 0 0 0 0.0996094 0.0996094 0 0 0 - ( 0 251 0 ) ( 32 251 0 ) ( 0 251 32 ) map/fut_door_d 0 0 0 0.0996094 0.0996094 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/fut_door_d 0 0 0 0.0996094 0.0996094 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/fut_door_d 0 0 0 0.0996094 0.0996094 0 0 0 + ( 901 0 0 ) ( 901 32 0 ) ( 901 0 32 ) map/fut_door_d 512 0 0 -0.00195312 0.0976562 0 0 0 + ( 999 0 0 ) ( 999 0 32 ) ( 999 32 0 ) map/fut_door_d 512 0 0 -0.00195312 0.0976562 0 0 0 + ( 0 249 0 ) ( 0 249 32 ) ( 32 249 0 ) map/fut_door_d 198 0 0 -0.0957031 0.0976562 0 0 0 + ( 0 251 0 ) ( 32 251 0 ) ( 0 251 32 ) map/fut_door_d 198 0 0 -0.0957031 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/fut_door_d 198 512 0 -0.0957031 0.00195312 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/fut_door_d 198 512 0 -0.0957031 0.00195312 0 0 0 } } @@ -1690,12 +2506,12 @@ "angle" "0" "targetname" "door_14_2" { - ( 1401 0 0 ) ( 1401 32 0 ) ( 1401 0 32 ) map/fut_door_d 0 0 0 0.0996094 0.0996094 0 0 0 - ( 1499 0 0 ) ( 1499 0 32 ) ( 1499 32 0 ) map/fut_door_d 0 0 0 0.0996094 0.0996094 0 0 0 - ( 0 249 0 ) ( 0 249 32 ) ( 32 249 0 ) map/fut_door_d 0 0 0 0.0996094 0.0996094 0 0 0 - ( 0 251 0 ) ( 32 251 0 ) ( 0 251 32 ) map/fut_door_d 0 0 0 0.0996094 0.0996094 0 0 0 - ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/fut_door_d 0 0 0 0.0996094 0.0996094 0 0 0 - ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/fut_door_d 0 0 0 0.0996094 0.0996094 0 0 0 + ( 1401 0 0 ) ( 1401 32 0 ) ( 1401 0 32 ) map/fut_door_d 512 0 0 -0.00195312 0.0976562 0 0 0 + ( 1499 0 0 ) ( 1499 0 32 ) ( 1499 32 0 ) map/fut_door_d 512 0 0 -0.00195312 0.0976562 0 0 0 + ( 0 249 0 ) ( 0 249 32 ) ( 32 249 0 ) map/fut_door_d 303 0 0 -0.0957031 0.0976562 0 0 0 + ( 0 251 0 ) ( 32 251 0 ) ( 0 251 32 ) map/fut_door_d 303 0 0 -0.0957031 0.0976562 0 0 0 + ( 0 0 0 ) ( 32 0 0 ) ( 0 32 0 ) map/fut_door_d 303 512 0 -0.0957031 0.00195312 0 0 0 + ( 0 0 100 ) ( 0 32 100 ) ( 32 0 100 ) map/fut_door_d 303 512 0 -0.0957031 0.00195312 0 0 0 } } diff --git a/deepmind/level_generation/text_maze_generation/BUILD b/deepmind/level_generation/text_maze_generation/BUILD index 61258324..03b1663b 100644 --- a/deepmind/level_generation/text_maze_generation/BUILD +++ b/deepmind/level_generation/text_maze_generation/BUILD @@ -16,7 +16,7 @@ cc_test( srcs = ["text_maze_test.cc"], deps = [ ":text_maze", - "@googletest//:gtest_main", + "@com_google_googletest//:gtest_main", ], ) @@ -39,7 +39,7 @@ cc_test( deps = [ ":algorithm", ":text_maze", - "@googletest//:gtest_main", + "@com_google_googletest//:gtest_main", ], ) @@ -50,3 +50,15 @@ cc_library( visibility = ["//deepmind/engine:__pkg__"], deps = [":text_maze"], ) + +cc_test( + name = "flood_fill_test", + size = "small", + srcs = ["flood_fill_test.cc"], + deps = [ + ":algorithm", + ":flood_fill", + ":text_maze", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/deepmind/level_generation/text_maze_generation/algorithm.cc b/deepmind/level_generation/text_maze_generation/algorithm.cc index 9a23ac2c..9540355f 100644 --- a/deepmind/level_generation/text_maze_generation/algorithm.cc +++ b/deepmind/level_generation/text_maze_generation/algorithm.cc @@ -18,6 +18,8 @@ #include "deepmind/level_generation/text_maze_generation/algorithm.h" +#include + #include "deepmind/level_generation/text_maze_generation/flood_fill.h" namespace deepmind { @@ -226,7 +228,7 @@ void RemoveDeadEnds(char empty, char wall, const std::vector& wall_chars, if (cell_value == empty) { last_pos = look_at_pos; ++empty_count; - } else if (is_wall_char[cell_value]) { + } else if (is_wall_char[static_cast(cell_value)]) { ++wall_count; } ++visit_count; @@ -243,6 +245,364 @@ void RemoveDeadEnds(char empty, char wall, const std::vector& wall_chars, }); } +namespace { + +// Contains Left, Right, Down, Up. +std::array PathDirections() { + return {{{1, 0}, {-1, 0}, {0, 1}, {0, -1}}}; +} + +// Returns a list of visitable directions containing fill_variation from a +// position step_size away. +std::vector PossibleDirections( // + const TextMaze& text_maze, // + const Pos& pos, // + unsigned int fill_id, // + int step_size) { + std::vector result; + auto rect = text_maze.Area(); + for (const auto& direction : PathDirections()) { + Pos two_step = pos + step_size * direction; + if (rect.InBounds(two_step) && text_maze.GetCellId(two_step) == fill_id) { + result.push_back(direction); + } + } + return result; +} + +// Visit the id layer at odd positions in a TextMaze +template +void VisitOddIds(const TextMaze& text_maze, F&& f) { + auto rect = text_maze.Area(); + auto next_odd = [](int v) -> int { return v | 1; }; + for (int i = next_odd(rect.pos.row); i < rect.pos.row + rect.size.height; + i += 2) { + for (int j = next_odd(rect.pos.col); j < rect.pos.col + rect.size.width; + j += 2) { + f(i, j, text_maze.GetCellId({i, j})); + } + } +} + +bool RemoveHorseshoeBendAtPos( // + Pos pos, // + int bend_size, // + char wall, // + const std::vector& wall_chars, // + TextMaze* text_maze) { + auto is_wall_char = internal::MakeCharBoolMap(wall_chars); + is_wall_char[static_cast(wall)] = true; + auto is_corridor = [text_maze](const Pos pos) { + return text_maze->GetCell(TextMaze::kEntityLayer, pos) == ' '; + }; + + auto is_wall = [text_maze, &is_wall_char](const Pos pos) { + return is_wall_char[static_cast( + text_maze->GetCell(TextMaze::kEntityLayer, pos))]; + }; + + bool bends_removed = false; + bool found = false; + do { + found = false; + if (!is_wall(pos)) { + break; + } + for (const auto& v_dir : PathDirections()) { + const Vec u_dir = {v_dir.d_col, -v_dir.d_row}; + // + // Input configuration: Output configuration: + // origin (0, 0) is wall origin (0, 0) is corridor + // + // v bend_size v + // ^ <-----> ^ + // 2| ?*********? 2| ?*********? + // 1| * * 1| *********** + // 0| ? ******* ? 0| ? ? + // m| ??*******?? m| ??*******?? + // ...---------->u ...---------->u + // nm0123...po nm0123...po + // + bool ok = true; + // pos_u0 and pos_um must be wall for u in [0, bend_size -1] + for (int u = 0; u < bend_size; ++u) { + Pos pos_u0 = pos + u * u_dir; + Pos pos_um = pos + u * u_dir - v_dir; + if (!is_wall(pos_u0) || !is_wall(pos_um)) { + ok = false; + break; + } + } + if (!ok) { + continue; + } + // pos_u1 must be corridor for u in [-1, bend_size] + for (int u = -1; u <= bend_size; ++u) { + Pos pos_u1 = pos + u * u_dir + v_dir; + if (!is_corridor(pos_u1)) { + ok = false; + break; + } + } + if (!ok) { + continue; + } + // pos_u2 must be wall for u in [-1, bend_size] + for (int u = -1; u <= bend_size; ++u) { + Pos pos_u2 = pos + u * u_dir + 2 * v_dir; + if (!is_wall(pos_u2)) { + ok = false; + break; + } + } + if (!ok) { + continue; + } + // pos_n1 and pos_o1 must be wall + Pos pos_n1 = pos - 2 * u_dir + v_dir; + Pos pos_o1 = pos + (bend_size + 1) * u_dir + v_dir; + if (!is_wall(pos_n1) || !is_wall(pos_o1)) { + continue; + } + + // pos_m0 and pos_p0 must be corridor + Pos pos_m0 = pos - u_dir; + Pos pos_p0 = pos + bend_size * u_dir; + if (!is_corridor(pos_m0) || !is_corridor(pos_p0)) { + continue; + } + + // pull the string + bends_removed = true; + for (int u = -1; u <= bend_size; ++u) { + Pos pos_u1 = pos + u * u_dir + v_dir; + if (u >= 0 && u < bend_size) { + Pos pos_u0 = pos + u * u_dir; + text_maze->SetCell( + TextMaze::kEntityLayer, pos_u0, + text_maze->GetCell(TextMaze::kEntityLayer, pos_u1)); + } + text_maze->SetCell(TextMaze::kEntityLayer, pos_u1, wall); + } + // move pos to pos_0m + pos = pos - v_dir; + found = true; + break; + } + } while (found); + return bends_removed; +} + +} // namespace + +void FillWithMaze( // + const Pos& pos, // + unsigned int maze_id, // + TextMaze* text_maze, // + std::mt19937_64* prbg) { + std::vector stack; + stack.push_back(pos); + unsigned int fill_id = text_maze->GetCellId(pos); + text_maze->SetCell(TextMaze::kEntityLayer, pos, ' '); + text_maze->SetCellId(pos, maze_id); + while (!stack.empty()) { + Pos current = stack.back(); + // Find the possible directions we can two step to. + auto possible_directions = + PossibleDirections(*text_maze, current, fill_id, 2 /*step_size*/); + if (possible_directions.empty()) { + stack.pop_back(); + continue; + } + int direction_id = std::uniform_int_distribution<>( + 0, possible_directions.size() - 1)(*prbg); + const auto& direction = possible_directions[direction_id]; + Pos one_step = current + direction; + text_maze->SetCell(TextMaze::kEntityLayer, one_step, ' '); + text_maze->SetCellId(one_step, maze_id); + Pos two_step = current + 2 * direction; + text_maze->SetCell(TextMaze::kEntityLayer, two_step, ' '); + text_maze->SetCellId(two_step, maze_id); + stack.push_back(two_step); + } +} + +void FillSpaceWithMaze( // + unsigned int start_id, // + unsigned int fill_id, // + TextMaze* text_maze, // + std::mt19937_64* prbg) { + auto visitor = [&start_id, fill_id, text_maze, prbg](int i, int j, + unsigned int id) { + if (id == fill_id) { + FillWithMaze({i, j}, start_id++, text_maze, prbg); + } + }; + VisitOddIds(*text_maze, visitor); +} + +std::vector> RandomConnectRegions( // + char connector, // + double extra_probability, // + TextMaze* text_maze, // + std::mt19937_64* prbg) { + // Find all connecting points between regions. + std::map, + std::vector>> + cell_ids_connectors; + auto visitor = [text_maze, &cell_ids_connectors](int i, int j, + unsigned int id_0) { + if (id_0 != 0) { + Pos pos = {i, j}; + for (const auto& direction : PathDirections()) { + Pos two_step = pos + 2 * direction; + if (!text_maze->Area().InBounds(two_step)) continue; + auto id_1 = text_maze->GetCellId(two_step); + if (id_1 == 0 || id_1 <= id_0) continue; + Pos one_step = pos + direction; + cell_ids_connectors[{id_0, id_1}].emplace_back(one_step, direction); + } + } + }; + VisitOddIds(*text_maze, visitor); + + // Connect each region with at least one connecting point. + // Then add extra connections with a probability of extra_probability. + std::vector> result; + for (const auto& cell_ids_connector : cell_ids_connectors) { + const auto& locations = cell_ids_connector.second; + if (locations.empty()) continue; + int door = std::uniform_int_distribution<>(0, locations.size() - 1)(*prbg); + result.push_back(locations[door]); + text_maze->SetCell(TextMaze::kEntityLayer, locations[door].first, + connector); + } + + for (const auto& cell_ids_connector : cell_ids_connectors) { + const auto& locations = cell_ids_connector.second; + if (locations.empty()) continue; + for (std::size_t loc_id = 0; loc_id != locations.size(); ++loc_id) { + if (std::uniform_real_distribution<>(0, 1)(*prbg) <= extra_probability) { + bool next_to_door = false; + for (auto direction : PathDirections()) { + Pos one_step = locations[loc_id].first + direction; + if (text_maze->GetCell(TextMaze::kEntityLayer, one_step) == + connector) { + next_to_door = true; + break; + } + } + if (!next_to_door) { + result.push_back(locations[loc_id]); + text_maze->SetCell(TextMaze::kEntityLayer, locations[loc_id].first, + connector); + } + } + } + } + return result; +} + +bool RemoveHorseshoeBends( // + int bend_size, // + char wall, // + const std::vector& wall_chars, // + TextMaze* text_maze) { + bool bends_removed = false; + auto visitor = [bend_size, wall, &wall_chars, text_maze, &bends_removed]( + int i, int j, char value) { + bends_removed = + bends_removed || RemoveHorseshoeBendAtPos({i, j}, bend_size, wall, + wall_chars, text_maze); + }; + text_maze->Visit(TextMaze::kEntityLayer, visitor); + return bends_removed; +} + +void RemoveAllHorseshoeBends( // + char wall, // + const std::vector& wall_chars, // + TextMaze* text_maze) { + for (int i = 1; i + 3 < text_maze->Area().size.width;) { + bool bends_removed = RemoveHorseshoeBends(i, wall, wall_chars, text_maze); + // Removing bends of bend_size > 1 may generate smaller loops. + // We must start again in this case. + if (bends_removed && i != 1) { + i = 1; + } else { + ++i; + } + } +} + +void AddNEntitiesToEachRoom( // + const std::vector& rooms, // + int n, // + char entity, // + char empty, // + TextMaze* text_maze, // + std::mt19937_64* prbg) { + for (const auto& room : rooms) { + std::vector samples; + text_maze->VisitIntersection(TextMaze::kEntityLayer, room, + [empty, &samples](int i, int j, char value) { + if (value == empty) { + samples.push_back({i, j}); + } + }); + std::shuffle(samples.begin(), samples.end(), *prbg); + for (std::size_t i = 0; + i < std::min(samples.size(), static_cast(n)); ++i) { + text_maze->SetCell(TextMaze::kEntityLayer, samples[i], entity); + } + } +} + +std::vector FindRandomPath( // + const Pos& from, // + const Pos& to, // + const std::vector& wall_chars, // + TextMaze* text_maze, // + std::mt19937_64* prbg) { + // Set the Id of all visitable locations to 1, 0 otherwise. + auto is_wall_char = internal::MakeCharBoolMap(wall_chars); + text_maze->Visit(TextMaze::kEntityLayer, [text_maze, is_wall_char]( + int i, int j, char cell) { + text_maze->SetCellId( + {i, j}, is_wall_char[static_cast(cell)] ? 0 : 1); + }); + std::vector path; + path.push_back(from); + text_maze->SetCellId(from, 0); + if (from == to) { + return path; + } + while (!path.empty()) { + std::vector candidates; + const auto& pos = path.back(); + for (const auto& direction : PathDirections()) { + auto candidate = pos + direction; + if (candidate == to) { + path.push_back(candidate); + return path; + } + if (text_maze->GetCellId(candidate) != 0) { + candidates.push_back(candidate); + } + } + if (candidates.empty()) { + path.pop_back(); + continue; + } + int candidate_id = + std::uniform_int_distribution<>(0, candidates.size() - 1)(*prbg); + Pos candidate = candidates[candidate_id]; + path.push_back(candidate); + text_maze->SetCellId(candidate, 0); + } + return path; +} + } // namespace maze_generation } // namespace lab } // namespace deepmind diff --git a/deepmind/level_generation/text_maze_generation/algorithm.h b/deepmind/level_generation/text_maze_generation/algorithm.h index c02bd272..5f976dab 100644 --- a/deepmind/level_generation/text_maze_generation/algorithm.h +++ b/deepmind/level_generation/text_maze_generation/algorithm.h @@ -78,6 +78,104 @@ std::vector MakeSeparateRectangles( void RemoveDeadEnds(char empty, char wall, const std::vector& wall_chars, TextMaze* text_maze); +// Implements the recursive backtracking maze generation algorithm, starting +// from 'pos' and spreading accross space with the same id value, and replacing +// it with 'maze_id'. +void FillWithMaze( // + const Pos& pos, // + unsigned int maze_id, // + TextMaze* text_maze, // + std::mt19937_64* prbg); + +// Iteratively invokes FillWithMaze for all positions within text_maze with +// id value 'fill_id', assigning sequential id values to each maze sequence +// starting from 'start_id'. +void FillSpaceWithMaze( // + unsigned int start_id, // + unsigned int fill_id, // + TextMaze* text_maze, // + std::mt19937_64* prbg); + +// Locates connections between adjacent regions in the id layer, placing +// value 'connector' in the relevant positions of the entity layer. At least one +// connection will be identified between each pair of adjacent regions, with +// additional connections created with probability 'extra_probability'. +// Returns a vector with the position and directions of the connections. +std::vector> RandomConnectRegions( // + char connector, // + double extra_probability, // + TextMaze* text_maze, // + std::mt19937_64* prbg); + +// Simplifies all corridors in 'text_maze' by removing horseshoe bends of a +// given size. Horseshoe bends are meandering sub-paths in a corridor where the +// adjacent segments are collinear and can be reduced to a single segment by +// connecting both ends of the sub-path, without crossing over any other +// corridors. +// For instance, in the case of a corridor linking rooms A and B within an +// example maze: +// +// horseshoe bend of size 1: corridor with the bend removed: +// +// ********* ********* +// *** *** ********* +// *** * *BB *******BB +// * * BB * BB +// AA******* AA******* +// AA******* AA******* +// +// The size of the bend is the number of maze cells used to shortcut it. +// Horseshoe bends only contain 4 turns. More complex meandering sub-paths +// can be removed by repeatedly invoking this function: +// +// complex sub-path: step 1: step 2: +// +// ********* ********* ********* +// *** *** *** *** ********* +// *** * * *** * *** ********* +// * *** * *** * *** ********* +// * *** * *** * *** ********* +// * * *** *** * *** ********* +// *** * *BB *** * *BB *******BB +// * * BB * * BB * BB +// AA******* AA******* AA******* +// AA******* AA******* AA******* +// +// Returns whether any bends were removed. +bool RemoveHorseshoeBends( // + int bend_size, // + char wall, // + const std::vector& wall_chars, // + TextMaze* text_maze); + +// Removes horseshoe bends of all sizes in all maze corridors. +void RemoveAllHorseshoeBends( // + char wall, // + const std::vector& wall_chars, // + TextMaze* text_maze); + +// For each region in 'rooms', attempts to set 'n' random cells to value +// 'entity' in the entity layer of 'text_maze'. This only operates on cells +// currently set to value 'empty'. +void AddNEntitiesToEachRoom( // + const std::vector& rooms, // + int n, // + char entity, // + char empty, // + TextMaze* text_maze, // + std::mt19937_64* prbg); + +// Attempts to find in 'text_maze' a random path between positions 'from' and +// 'to', while considering as walls the characters in 'wall_chars'. If +// successful, the function returns a vector of the path positions, in order of +// traversal. Otherwise it returns an empty vector. +std::vector FindRandomPath( // + const Pos& from, // + const Pos& to, // + const std::vector& wall_chars, // + TextMaze* text_maze, // + std::mt19937_64* prbg); + } // namespace maze_generation } // namespace lab } // namespace deepmind diff --git a/deepmind/level_generation/text_maze_generation/algorithm_test.cc b/deepmind/level_generation/text_maze_generation/algorithm_test.cc index 9f7bc013..16451509 100644 --- a/deepmind/level_generation/text_maze_generation/algorithm_test.cc +++ b/deepmind/level_generation/text_maze_generation/algorithm_test.cc @@ -285,6 +285,175 @@ TEST(AlgorithmTest, RemoveDeadEndsEdge) { maze.Text(TextMaze::kEntityLayer)); } +// The utility of the following tests: +// - FillSpaceWithMaze +// - RandomConnectRegions +// - RemoveAllHorseshoeBends +// - AddNEntitiesToEachRoom +// is somewhat limited: Their outputs depend on the specific implementation of +// various random algorithms, and thus they are really only reliable on a fixed + +// platform. When moving platforms, you will most likely need to generate new +// expected outputs. +TEST(AlgorithmTest, FillSpaceWithMaze) { + std::mt19937_64 prbg(0); + TextMaze maze({11, 11}); + FillSpaceWithMaze(1, 0, &maze, &prbg); + EXPECT_EQ( + "***********\n" + "* * *\n" + "* *** *** *\n" + "* * * * *\n" + "*** * * * *\n" + "* * * *\n" + "* ***** ***\n" + "* * * *\n" + "* * *** * *\n" + "* * *\n" + "***********\n", + maze.Text(TextMaze::kEntityLayer)); +} + +TEST(AlgorithmTest, RandomConnectRegions) { + std::mt19937_64 prbg(0); + TextMaze maze = + FromCharGrid(CharGrid("***********\n" + "* *****\n" + "* *****\n" + "* * *\n" + "******* *\n" + "* *** *\n" + "* *******\n" + "* * *\n" + "* * *\n" + "* * *\n" + "***********\n")); + auto rooms = FindRooms(maze, {'*'}); + EXPECT_EQ(4, rooms.size()); + for (unsigned int i = 0; i < rooms.size(); ++i) { + const auto& room = rooms[i]; + for (const auto& cell : room) { + maze.SetCellId(cell, i + 1); + } + } + RandomConnectRegions('#', 0.5, &maze, &prbg); + EXPECT_EQ( + "***********\n" + "* *****\n" + "* *****\n" + "* # *\n" + "*#*#*** *\n" + "* *** *\n" + "* ***#***\n" + "* * *\n" + "* * *\n" + "* # *\n" + "***********\n", + maze.Text(TextMaze::kEntityLayer)); +} + +TEST(AlgorithmTest, RemoveAllHorseshoeBends) { + std::mt19937_64 prbg(0); + TextMaze maze = + FromCharGrid(CharGrid("***********\n" + "* * *\n" + "* * * *\n" + "* * *\n" + "* ******* *\n" + "* * *\n" + "* * ***** *\n" + "* * *\n" + "* ***** *\n" + "* * *\n" + "***********\n")); + auto rooms = FindRooms(maze, {'*'}); + for (unsigned int i = 0; i < rooms.size(); ++i) { + const auto& room = rooms[i]; + for (const auto& cell : room) { + maze.SetCellId(cell, i + 1); + } + } + RemoveDeadEnds(' ', '*', {}, &maze); + RemoveAllHorseshoeBends('*', {}, &maze); + EXPECT_EQ( + "***********\n" + "* ***\n" + "* * ***\n" + "* * *\n" + "* ******* *\n" + "* ***** *\n" + "*** ***** *\n" + "*** *\n" + "******* *\n" + "******* *\n" + "***********\n", + maze.Text(TextMaze::kEntityLayer)); +} + +TEST(AlgorithmTest, RemoveAllHorseshoeBendsDoors) { + std::mt19937_64 prbg(0); + TextMaze maze = + FromCharGrid(CharGrid("**********\n" + "***** *\n" + "***** *\n" + "* # *\n" + "* ******#*\n" + "* *\n" + "**********\n")); + auto rooms = FindRooms(maze, {'*'}); + ASSERT_EQ(1, rooms.size()); + ASSERT_EQ(12, rooms.front().size()); + for (unsigned int i = 0; i < rooms.size(); ++i) { + const auto& room = rooms[i]; + for (const auto& cell : room) { + maze.SetCellId(cell, i + 1); + } + } + RemoveDeadEnds(' ', '*', {}, &maze); + RemoveAllHorseshoeBends('*', {}, &maze); + + EXPECT_EQ( + "**********\n" + "***** *\n" + "***** *\n" + "*** # *\n" + "*** ****#*\n" + "*** *\n" + "**********\n", + maze.Text(TextMaze::kEntityLayer)); +} + +TEST(AlgorithmTest, AddNEntitiesToEachRoom) { + std::mt19937_64 prbg(0); + TextMaze maze = + FromCharGrid(CharGrid("***********\n" + "* * *\n" + "* * * *\n" + "* * *\n" + "* ******* *\n" + "* * *\n" + "* * ***** *\n" + "* * *\n" + "* ***** *\n" + "* * *\n" + "***********\n")); + std::vector rooms = {{{1, 1}, {3, 5}}, {{7, 7}, {3, 3}}}; + AddNEntitiesToEachRoom(rooms, 3, 'A', ' ', &maze, &prbg); + EXPECT_EQ( + "***********\n" + "*A A * *\n" + "* A * * *\n" + "* * *\n" + "* ******* *\n" + "* * *\n" + "* * ***** *\n" + "* * A *\n" + "* ***** *\n" + "* *AA *\n" + "***********\n", + maze.Text(TextMaze::kEntityLayer)); +} + } // namespace } // namespace maze_generation } // namespace lab diff --git a/deepmind/level_generation/text_maze_generation/flood_fill.cc b/deepmind/level_generation/text_maze_generation/flood_fill.cc index 90d26461..b77eb42b 100644 --- a/deepmind/level_generation/text_maze_generation/flood_fill.cc +++ b/deepmind/level_generation/text_maze_generation/flood_fill.cc @@ -67,7 +67,7 @@ int FloodFill::DistanceFrom(Pos pos) const { } } -FloodFill::FloodFill(const TextMaze& maze, TextMaze::eLayer layer, Pos goal, +FloodFill::FloodFill(const TextMaze& maze, TextMaze::Layer layer, Pos goal, const std::vector& wall_chars) : area_(maze.Area()) { auto is_wall = internal::MakeCharBoolMap(wall_chars); diff --git a/deepmind/level_generation/text_maze_generation/flood_fill.h b/deepmind/level_generation/text_maze_generation/flood_fill.h index a89f5952..c4696fd8 100644 --- a/deepmind/level_generation/text_maze_generation/flood_fill.h +++ b/deepmind/level_generation/text_maze_generation/flood_fill.h @@ -68,7 +68,7 @@ class FloodFill { // Finds all points attached to goal. // 'goal' - Flood fill starts from goal. // 'wall_chars' are characters for the flood fill to avoid. - FloodFill(const TextMaze& maze, TextMaze::eLayer layer, Pos goal, + FloodFill(const TextMaze& maze, TextMaze::Layer layer, Pos goal, const std::vector& wall_chars); // If goal is reachable from start, returns the minimum distance between start diff --git a/deepmind/level_generation/text_maze_generation/text_maze.cc b/deepmind/level_generation/text_maze_generation/text_maze.cc index 37a1aa03..f778b8d7 100644 --- a/deepmind/level_generation/text_maze_generation/text_maze.cc +++ b/deepmind/level_generation/text_maze_generation/text_maze.cc @@ -32,6 +32,7 @@ TextMaze::TextMaze(Size extents) : area_{{0, 0}, extents} { } text_[kEntityLayer] = std::move(level_layer); text_[kVariationsLayer] = std::move(variations_layer); + ids_.assign(area_.size.height * area_.size.width, 0); } } // namespace maze_generation diff --git a/deepmind/level_generation/text_maze_generation/text_maze.h b/deepmind/level_generation/text_maze_generation/text_maze.h index 904dc973..0be99008 100644 --- a/deepmind/level_generation/text_maze_generation/text_maze.h +++ b/deepmind/level_generation/text_maze_generation/text_maze.h @@ -23,16 +23,38 @@ #include #include #include +#include namespace deepmind { namespace lab { namespace maze_generation { +struct Vec { + int d_row; + int d_col; +}; + +inline Vec operator*(int scl, const Vec& vec) { + return {scl * vec.d_row, scl * vec.d_col}; +} + struct Pos { int row; int col; }; +inline Pos operator+(const Pos& pos, const Vec& vec) { + return {pos.row + vec.d_row, pos.col + vec.d_col}; +} + +inline Pos operator-(const Pos& pos, const Vec& vec) { + return {pos.row - vec.d_row, pos.col - vec.d_col}; +} + +inline bool operator==(const Pos& op0, const Pos& op1) { + return op0.row == op1.row && op0.col == op1.col; +} + struct Size { int height; int width; @@ -103,7 +125,7 @@ inline bool IsSeparate(const Rectangle& lhs, const Rectangle& rhs) { class TextMaze { public: // Selects which layer to apply operations to. - enum eLayer { kEntityLayer, kVariationsLayer }; + enum Layer { kEntityLayer, kVariationsLayer }; // Creates an entity and variation layer of a text level with extents of // 'extents'. Each layer is constructed as a new-line separated block of text. @@ -114,7 +136,7 @@ class TextMaze { // Calls f(i, j, cell) for each cell (i, j) in the intersection of the maze // and rect. template - void VisitIntersection(eLayer layer, const Rectangle& rect, F&& f) const { + void VisitIntersection(Layer layer, const Rectangle& rect, F&& f) const { const auto& text = text_[layer]; Overlap(Area(), rect).Visit([this, &text, &f](int i, int j) { f(i, j, text[ToTextIdx(i, j)]); @@ -123,7 +145,7 @@ class TextMaze { // Mutable variant of VisitIntersection. template - void VisitMutableIntersection(eLayer layer, const Rectangle& rect, F&& f) { + void VisitMutableIntersection(Layer layer, const Rectangle& rect, F&& f) { auto& text = text_[layer]; Overlap(Area(), rect).Visit([this, &text, &f](int i, int j) { f(i, j, &text[ToTextIdx(i, j)]); @@ -132,19 +154,19 @@ class TextMaze { // Calls f(i, j, cell) for each cell (i, j). template - void Visit(eLayer layer, F&& f) const { + void Visit(Layer layer, F&& f) const { VisitIntersection(layer, Area(), std::forward(f)); } // Mutable variant of Visit. template - void VisitMutable(eLayer layer, F&& f) { + void VisitMutable(Layer layer, F&& f) { VisitMutableIntersection(layer, Area(), std::forward(f)); } // Returns the character at position pos in layer layer, or '\0' of pos is out // of bounds of the maze. - char GetCell(eLayer layer, Pos pos) const { + char GetCell(Layer layer, Pos pos) const { if (Area().InBounds(pos)) { return text_[layer][ToTextIdx(pos.row, pos.col)]; } else { @@ -154,14 +176,31 @@ class TextMaze { // Sets the character at position 'pos' in layer 'layer' to 'value' if pos is // within bounds of the maze; otherwise there is no effect. - void SetCell(eLayer layer, Pos pos, char value) { + void SetCell(Layer layer, Pos pos, char value) { if (Area().InBounds(pos)) { text_[layer][ToTextIdx(pos.row, pos.col)] = value; } } + // Returns the id at position pos, or 0 of pos is out of bounds of the maze. + unsigned int GetCellId(Pos pos) const { + if (Area().InBounds(pos)) { + return ids_[ToIdIdx(pos.row, pos.col)]; + } else { + return 0; + } + } + + // Sets the character at position 'pos' in layer 'layer' to 'value' if pos is + // within bounds of the maze; otherwise there is no effect. + void SetCellId(Pos pos, unsigned int id) { + if (Area().InBounds(pos)) { + ids_[ToIdIdx(pos.row, pos.col)] = id; + } + } + // Returns text associated with the 'layer'. - const std::string& Text(eLayer layer) const { return text_[layer]; } + const std::string& Text(Layer layer) const { return text_[layer]; } // Area representing mutable cells of the grid. const Rectangle& Area() const { return area_; } @@ -172,8 +211,13 @@ class TextMaze { // (j is allowed to be area_.size.width for setting new-lines.) int ToTextIdx(int i, int j) const { return i * (area_.size.width + 1) + j; } + // Translates grid coordinates to the linear id position. Use only when (i, j) + // is within bounds. + int ToIdIdx(int i, int j) const { return i * area_.size.width + j; } + const Rectangle area_; std::array text_; + std::vector ids_; }; } // namespace maze_generation diff --git a/deepmind/lua/BUILD b/deepmind/lua/BUILD index fa6fcd7c..c5901dd6 100644 --- a/deepmind/lua/BUILD +++ b/deepmind/lua/BUILD @@ -5,10 +5,12 @@ licenses(["restricted"]) # GPLv2 package(default_visibility = ["//visibility:public"]) +LUA_VERSION = "@lua_system//:lua" + cc_library( name = "lua", hdrs = ["lua.h"], - deps = ["@lua_system//:lua"], + deps = [LUA_VERSION], ) cc_library( @@ -25,7 +27,7 @@ cc_library( deps = [ ":vm", "//deepmind/support:logging", - "@googletest//:gtest", + "@com_google_googletest//:gtest", ], ) @@ -41,9 +43,8 @@ cc_test( ":read", ":table_ref", ":vm", - "//deepmind/support:logging", "//deepmind/support:test_srcdir", - "@googletest//:gtest_main", + "@com_google_googletest//:gtest_main", ], ) @@ -58,7 +59,7 @@ cc_library( hdrs = ["n_results_or_test_util.h"], deps = [ ":n_results_or", - "@googletest//:gtest", + "@com_google_googletest//:gtest", ], ) @@ -80,7 +81,7 @@ cc_test( ":push", ":read", ":vm_test_util", - "@googletest//:gtest_main", + "@com_google_googletest//:gtest_main", ], ) @@ -109,14 +110,18 @@ cc_test( ":push_script", ":read", ":vm_test_util", - "@googletest//:gtest_main", + "@com_google_googletest//:gtest_main", ], ) cc_library( name = "push", hdrs = ["push.h"], - deps = [":lua"], + deps = [ + ":lua", + "@com_google_absl//absl/container:inlined_vector", + "@com_google_absl//absl/types:span", + ], ) cc_test( @@ -126,7 +131,9 @@ cc_test( deps = [ ":push", ":vm_test_util", - "@googletest//:gtest_main", + "@com_google_absl//absl/container:inlined_vector", + "@com_google_absl//absl/types:span", + "@com_google_googletest//:gtest_main", ], ) @@ -144,7 +151,11 @@ cc_library( cc_library( name = "read", hdrs = ["read.h"], - deps = [":lua"], + deps = [ + ":lua", + "@com_google_absl//absl/container:inlined_vector", + "@com_google_absl//absl/types:span", + ], ) cc_test( @@ -155,7 +166,9 @@ cc_test( ":push", ":read", ":vm_test_util", - "@googletest//:gtest_main", + "@com_google_absl//absl/container:inlined_vector", + "@com_google_absl//absl/types:span", + "@com_google_googletest//:gtest_main", ], ) @@ -183,7 +196,7 @@ cc_test( ":read", ":table_ref", ":vm_test_util", - "@googletest//:gtest_main", + "@com_google_googletest//:gtest_main", ], ) @@ -211,6 +224,6 @@ cc_test( ":read", ":table_ref", ":vm_test_util", - "@googletest//:gtest_main", + "@com_google_googletest//:gtest_main", ], ) diff --git a/deepmind/lua/call_test.cc b/deepmind/lua/call_test.cc index 152dab80..53608a4b 100644 --- a/deepmind/lua/call_test.cc +++ b/deepmind/lua/call_test.cc @@ -33,6 +33,7 @@ namespace lua { namespace { using ::deepmind::lab::lua::testing::IsOkAndHolds; +using ::deepmind::lab::lua::testing::StatusIs; using ::testing::AllOf; using ::testing::HasSubstr; using ::testing::Not; @@ -72,12 +73,10 @@ TEST_F(CallTest, FunctionErrors) { Push(L, false); - n_or = Call(L, 1); - ASSERT_FALSE(n_or.ok()); - EXPECT_THAT(n_or.error(), AllOf( + EXPECT_THAT(Call(L, 1), StatusIs(AllOf( HasSubstr("Random Error Message!"), HasSubstr("TestFunction"), - HasSubstr("kTestAssert"))); + HasSubstr("kTestAssert")))); EXPECT_EQ(lua_gettop(L), top); } @@ -90,11 +89,9 @@ TEST_F(CallTest, FunctionErrorsNoStack) { Push(L, false); - NResultsOr call_result = Call(L, 1, false); - ASSERT_FALSE(call_result.ok()); - EXPECT_THAT(call_result.error(), AllOf( + EXPECT_THAT(Call(L, 1, false), StatusIs(AllOf( HasSubstr("Random Error Message!"), - Not(HasSubstr("TestFunction")))); + Not(HasSubstr("TestFunction"))))); EXPECT_EQ(lua_gettop(L), top); } @@ -116,9 +113,8 @@ TEST_F(CallTest, FunctionBindErrors) { EXPECT_EQ(lua_gettop(L), top + 1); Push(L, false); - NResultsOr call_result = Call(L, 1); - EXPECT_FALSE(call_result.ok()); - EXPECT_THAT(call_result.error(), HasSubstr("My error message!")); + auto call_result = Call(L, 1); + EXPECT_THAT(call_result, StatusIs(HasSubstr("My error message!"))); EXPECT_EQ(lua_gettop(L), top + call_result.n_results()); } diff --git a/deepmind/lua/n_results_or_test_util.h b/deepmind/lua/n_results_or_test_util.h index cce3e079..7abe3692 100644 --- a/deepmind/lua/n_results_or_test_util.h +++ b/deepmind/lua/n_results_or_test_util.h @@ -28,6 +28,7 @@ #define DML_DEEPMIND_LUA_N_RESULTS_OR_TEST_UTIL_H_ #include +#include #include "gmock/gmock.h" #include "deepmind/lua/n_results_or.h" @@ -79,9 +80,9 @@ class IsOkAndHoldsImpl : public ::testing::MatcherInterface { const std::string explanation = listener.str(); if (explanation != "") { - *result_listener << "which contains value " - << ::testing::PrintToString(actual_value.n_results()) - << ", " << explanation; + *result_listener << "which contains value " + << ::testing::PrintToString(actual_value.n_results()) + << ", " << explanation; } return matches; @@ -90,6 +91,49 @@ class IsOkAndHoldsImpl : public ::testing::MatcherInterface { const ::testing::Matcher matcher_; }; +class StatusIsImpl : public ::testing::MatcherInterface { + public: + template + explicit StatusIsImpl(M matcher) + : matcher_(::testing::SafeMatcherCast(matcher)) {} + + private: + void DescribeTo(std::ostream* os) const override { + *os << "is in error state with error value that "; + matcher_.DescribeTo(os); + } + + void DescribeNegationTo(std::ostream* os) const override { + *os << "isn't in error state or has an error value that "; + matcher_.DescribeNegationTo(os); + } + + bool MatchAndExplain( + const NResultsOr& actual_value, + ::testing::MatchResultListener* result_listener) const override { + if (actual_value.ok()) { + *result_listener << "which is OK (with " << actual_value.n_results() + << " arguments)"; + return false; + } + + ::testing::StringMatchResultListener listener; + + const bool matches = matcher_.MatchAndExplain(actual_value.error(), + &listener); + const std::string explanation = listener.str(); + + if (explanation != "") { + *result_listener << "which contains error value " + << actual_value.error() << ", " << explanation; + } + + return matches; + } + + const ::testing::Matcher matcher_; +}; + template class IsOkAndHoldsMatcher { public: @@ -103,6 +147,19 @@ class IsOkAndHoldsMatcher { M matcher_; }; +template +class StatusIsMatcher { + public: + explicit StatusIsMatcher(M matcher) : matcher_(matcher) {} + + operator ::testing::Matcher() const { + return ::testing::MakeMatcher(new StatusIsImpl(matcher_)); + } + + private: + M matcher_; +}; + } // namespace internal template @@ -110,6 +167,11 @@ internal::IsOkAndHoldsMatcher IsOkAndHolds(M matcher) { return internal::IsOkAndHoldsMatcher(matcher); } +template +internal::StatusIsMatcher StatusIs(M matcher) { + return internal::StatusIsMatcher(matcher); +} + } // namespace testing } // namespace lua } // namespace lab diff --git a/deepmind/lua/push.h b/deepmind/lua/push.h index ce4149a2..ee2b544d 100644 --- a/deepmind/lua/push.h +++ b/deepmind/lua/push.h @@ -27,6 +27,8 @@ #include #include +#include "absl/container/inlined_vector.h" +#include "absl/types/span.h" #include "deepmind/lua/lua.h" namespace deepmind { @@ -88,29 +90,39 @@ void Push(lua_State* L, const std::array& values); template void Push(lua_State* L, const std::unordered_map& values); +// [0, +1, -] +template +void Push(lua_State* L, const absl::InlinedVector& values); + +// [0, +1, -] +template +void Push(lua_State* L, absl::Span values); // End of public header, implementation details follow. -namespace internal { -template -void PushRange(lua_State* L, const C& c) { - lua_createtable(L, c.size(), 0); - for (std::size_t i = 0; i < c.size(); ++i) { +template +void Push(lua_State* L, absl::Span values) { + lua_createtable(L, values.size(), 0); + for (std::size_t i = 0; i < values.size(); ++i) { Push(L, i + 1); - Push(L, c[i]); + Push(L, values[i]); lua_settable(L, -3); } } -} // namespace internal template void Push(lua_State* L, const std::vector& values) { - internal::PushRange(L, values); + Push(L, absl::MakeConstSpan(values)); } template void Push(lua_State* L, const std::array& values) { - internal::PushRange(L, values); + Push(L, absl::MakeConstSpan(values)); +} + +template +void Push(lua_State* L, const absl::InlinedVector& values) { + Push(L, absl::MakeConstSpan(values)); } template diff --git a/deepmind/lua/push_test.cc b/deepmind/lua/push_test.cc index 1dc81129..eb19ee13 100644 --- a/deepmind/lua/push_test.cc +++ b/deepmind/lua/push_test.cc @@ -21,6 +21,8 @@ #include #include "gtest/gtest.h" +#include "absl/container/inlined_vector.h" +#include "absl/types/span.h" #include "deepmind/lua/vm_test_util.h" namespace deepmind { @@ -105,6 +107,38 @@ TEST_F(PushTest, PushVector) { } } +TEST_F(PushTest, PushInlinedVector) { + absl::InlinedVector test = {1, 2, 3, 4, 5}; + Push(L, test); + ASSERT_EQ(LUA_TTABLE, lua_type(L, 1)); + + std::size_t count = ArrayLength(L, 1); + ASSERT_EQ(test.size(), count); + for (std::size_t i = 0; i < count; ++i) { + lua_rawgeti(L, 1, i + 1); + ASSERT_EQ(LUA_TNUMBER, lua_type(L, -1)); + double value = lua_tonumber(L, -1); + EXPECT_EQ(test[i], value); + lua_pop(L, 1); + } +} + +TEST_F(PushTest, PushSpan) { + const double data[] = {1.0, 2.0, 3.0, 4.0}; + Push(L, absl::MakeConstSpan(data)); + ASSERT_EQ(LUA_TTABLE, lua_type(L, 1)); + const std::size_t count = ArrayLength(L, 1); + ASSERT_EQ(count, 4); + + for (std::size_t i = 0; i < count; ++i) { + lua_rawgeti(L, 1, i + 1); + ASSERT_EQ(LUA_TNUMBER, lua_type(L, -1)); + const double value = lua_tonumber(L, -1); + EXPECT_EQ(data[i], value); + lua_pop(L, 1); + } +} + TEST_F(PushTest, PushFixedSizeArray) { std::array test_d{{1.0, 2.0, 3.0}}; std::array test_sz{{1, 2, 3}}; diff --git a/deepmind/lua/read.h b/deepmind/lua/read.h index 89e0d034..603337b3 100644 --- a/deepmind/lua/read.h +++ b/deepmind/lua/read.h @@ -27,6 +27,8 @@ #include #include +#include "absl/container/inlined_vector.h" +#include "absl/types/span.h" #include "deepmind/lua/lua.h" namespace deepmind { @@ -56,7 +58,6 @@ Type* ReadUDT(lua_State* L, int idx, const char* tname) { // In all Read overloads, '*result' is filled in if value at the stack // location 'idx' is valid for the given type. The return value indicates // whether the read was valid. If the read fails, '*result' is unmodified. - inline bool Read(lua_State* L, int idx, std::string* result) { if (lua_type(L, idx) == LUA_TSTRING) { std::size_t length = 0; @@ -160,12 +161,24 @@ bool Read(lua_State* L, int idx, T** result) { template bool Read(lua_State* L, int idx, std::vector* result); -// Reads a Lua array into '*result'. The failure conditions are the same as in -// the previous function, but '*result' may be modified even if this function +// Reads a Lua array into 'values'. The failure conditions are the same as in +// the previous function, but 'values' may be modified even if this function +// fails. +template +bool Read(lua_State* L, int idx, absl::Span values); + +// Reads a Lua array into '*values'. The failure conditions are the same as in +// the previous function, but '*values' may be modified even if this function // fails. template bool Read(lua_State* L, int idx, std::array* values); +// Reads a Lua array into '*values'. The failure conditions are the same as in +// the previous function and '*values' may be modified even if this function +// fails. +template +bool Read(lua_State* L, int idx, absl::InlinedVector* values); + // Reads a table from the Lua stack. On success, the table is stored in // '*result'; on failure, '*result' is unmodified. Returns whether the function // succeeds. @@ -176,14 +189,14 @@ bool Read(lua_State* L, int idx, std::array* values); template bool Read(lua_State* L, int idx, std::unordered_map* result); -template -bool Read(lua_State* L, int idx, std::array* values) { +template +bool Read(lua_State* L, int idx, absl::Span values) { if (lua_type(L, idx) == LUA_TTABLE) { - std::size_t count = ArrayLength(L, idx); - if (count >= values->size()) { - for (std::size_t i = 0; i < values->size(); ++i) { + const std::size_t count = ArrayLength(L, idx); + if (count >= values.size()) { + for (std::size_t i = 0; i < values.size(); ++i) { lua_rawgeti(L, idx, i + 1); - if (!Read(L, -1, &(*values)[i])) { + if (!Read(L, -1, &values[i])) { lua_pop(L, 1); return false; } @@ -195,6 +208,33 @@ bool Read(lua_State* L, int idx, std::array* values) { return false; } +template +bool Read(lua_State* L, int idx, std::array* values) { + return Read(L, idx, absl::MakeSpan(*values)); +} + +template +bool Read(lua_State* L, int idx, absl::InlinedVector* values) { + values->clear(); + if (lua_type(L, idx) == LUA_TTABLE) { + std::size_t count = ArrayLength(L, idx); + values->reserve(count); + for (std::size_t i = 0; i < count; ++i) { + lua_rawgeti(L, idx, i + 1); + T value; + if (Read(L, -1, &value)) { + values->push_back(std::move(value)); + lua_pop(L, 1); + } else { + lua_pop(L, 1); + return false; + } + } + return true; + } + return false; +} + template bool Read(lua_State* L, int idx, std::vector* result) { std::vector local_result; diff --git a/deepmind/lua/read_test.cc b/deepmind/lua/read_test.cc index 73988a50..c4c4da84 100644 --- a/deepmind/lua/read_test.cc +++ b/deepmind/lua/read_test.cc @@ -23,6 +23,8 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/container/inlined_vector.h" +#include "absl/types/span.h" #include "deepmind/lua/push.h" #include "deepmind/lua/vm_test_util.h" @@ -122,6 +124,19 @@ TEST_F(ReadTest, ReadArray) { EXPECT_EQ(3, lua_gettop(L)); } +TEST_F(ReadTest, ReadInlinedVector) { + absl::InlinedVector test{{1, 2, 3, 4, 5}}; + Push(L, "Junk"); + Push(L, test); + Push(L, "Junk"); + absl::InlinedVector result; + ASSERT_TRUE(Read(L, 2, &result)); + EXPECT_EQ(result, test); + ASSERT_TRUE(Read(L, -2, &result)); + EXPECT_EQ(result, test); + EXPECT_EQ(3, lua_gettop(L)); +} + TEST_F(ReadTest, ReadArrayFloat) { std::array test{{1, 2, 3, 4, 5}}; Push(L, "Junk"); @@ -135,6 +150,15 @@ TEST_F(ReadTest, ReadArrayFloat) { EXPECT_EQ(3, lua_gettop(L)); } +TEST_F(ReadTest, ReadSpan) { + const std::array test{{1, 2, 3, 4, 5}}; + Push(L, test); + float result[] = {0, 0, 0, 0, 0}; + ASSERT_TRUE(Read(L, 1, absl::MakeSpan(result))); + EXPECT_EQ(absl::MakeSpan(result), test); + EXPECT_EQ(1, lua_gettop(L)); +} + TEST_F(ReadTest, ReadTable) { std::unordered_map test = { {"one", 1}, // diff --git a/deepmind/lua/table_ref.h b/deepmind/lua/table_ref.h index 19dbd90e..0ea27834 100644 --- a/deepmind/lua/table_ref.h +++ b/deepmind/lua/table_ref.h @@ -129,12 +129,12 @@ class TableRef final { return result; } - // Sets value to table[key] only if the table contains the key and the value - // at that location is of type T. - // Returns true on success. + // If the table contains an entry of type T for index key, sets *value to + // table[key] and returns true; otherwise returns false and does not access + // *value. // [0, 0, -] - Precondition: !this->is_unbound(). template - bool LookUp(const K& key, T* value) const { + bool LookUp(const K& key, T value) const { PushTable(); Push(lua_state_, key); lua_gettable(lua_state_, -2); @@ -185,6 +185,17 @@ class TableRef final { lua_pop(lua_state_, 1); } + // Pops the value on top of the stack and stores it into table[key]. + // [0, -1, -] - Precondition: !this->is_unbound(). + template + void InsertFromStackTop(const K& key) const { + PushTable(); + Push(lua_state_, key); + lua_pushvalue(lua_state_, -3); + lua_settable(lua_state_, -3); + lua_pop(lua_state_, 2); + } + // [0, 0, -] - Precondition: !this->is_unbound(). template TableRef CreateSubTable(const K& key) { @@ -193,6 +204,9 @@ class TableRef final { return subtable; } + // Gets the internal Lua state. + lua_State* LuaState() { return lua_state_; } + private: friend bool Read(lua_State* L, int idx, TableRef* table); friend void Push(lua_State* L, const TableRef& table); diff --git a/deepmind/lua/table_ref_test.cc b/deepmind/lua/table_ref_test.cc index c05f0b3f..0029a1f4 100644 --- a/deepmind/lua/table_ref_test.cc +++ b/deepmind/lua/table_ref_test.cc @@ -96,10 +96,12 @@ TEST_F(TableRefTest, TestPushMemberFunction) { Foo::Register(L); Push(L, "Hello"); ASSERT_FALSE(Read(L, -1, &table)); + ASSERT_NE(L, table.LuaState()); Foo::CreateFoo(L); ASSERT_TRUE(Read(L, -1, &table)); + ASSERT_EQ(L, table.LuaState()); // Also has keys "__gc" and "__index" but may have more in the future. EXPECT_GE(table.KeyCount(), 4); @@ -162,7 +164,7 @@ TEST_F(TableRefTest, TestCreateTable) { for (int i = 0; i < 10; ++i) { EXPECT_EQ(i + 1, result[i]); int out = 0; - table.LookUp(i + 1, &out); + EXPECT_TRUE(table.LookUp(i + 1, &out)); EXPECT_EQ(i + 1, out); } } @@ -183,7 +185,7 @@ TEST_F(TableRefTest, TestCreateSubTable) { for (int i = 0; i < 10; ++i) { EXPECT_THAT(result[i].Keys(), ElementsAre(i + 1)); int out = 0; - result[i].LookUp(i + 1, &out); + EXPECT_TRUE(result[i].LookUp(i + 1, &out)); EXPECT_EQ(i + 1, out); } } @@ -212,6 +214,38 @@ TEST_F(TableRefTest, TestCopyMoveAssignTable) { EXPECT_TRUE(table == table2); } +TEST_F(TableRefTest, TestCreateInsertFromStack) { + TableRef table = TableRef::Create(L); + for (int i = 0; i < 10; ++i) { + TableRef subtable = TableRef::Create(L); + subtable.Insert(i + 1, i + 1); + Push(L, subtable); + table.InsertFromStackTop(i + 1); + } + + std::vector result; + Push(L, table); + Read(L, -1, &result); + lua_pop(L, 1); + ASSERT_EQ(10, result.size()); + + for (int i = 0; i < 10; ++i) { + EXPECT_THAT(result[i].Keys(), ElementsAre(i + 1)); + int out = 0; + EXPECT_TRUE(result[i].LookUp(i + 1, &out)); + EXPECT_EQ(i + 1, out); + } +} + +TEST_F(TableRefTest, TestInsertAndReadSpan) { + TableRef table = TableRef::Create(L); + const int data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + table.Insert("array", absl::MakeConstSpan(data)); + int result[10] = {}; + table.LookUp("array", absl::MakeSpan(result)); + EXPECT_EQ(absl::MakeConstSpan(data), absl::MakeConstSpan(result)); +} + } // namespace } // namespace lua } // namespace lab diff --git a/deepmind/lua/vm_test.cc b/deepmind/lua/vm_test.cc index 9fead107..0bd36585 100644 --- a/deepmind/lua/vm_test.cc +++ b/deepmind/lua/vm_test.cc @@ -99,15 +99,13 @@ TEST(VmTest, TestEmbedCClosure) { int val1 = 55; int val2 = 5; vm.AddCModuleToSearchers("test.module", CModuleUpValue, {&val1, &val2}); + auto* L = vm.get(); - auto script_result = PushScript(L, kUseModule, "kUseModule"); - ASSERT_TRUE(script_result.ok()) << script_result.error(); - ASSERT_EQ(1, script_result.n_results()) << "Missing script"; - auto call_result = lua::Call(L, 0); - ASSERT_TRUE(call_result.ok()) << call_result.error(); - ASSERT_EQ(1, call_result.n_results()) << "Missing result"; - int val = 0; - EXPECT_TRUE(lua::Read(L, -1, &val)); + ASSERT_THAT(PushScript(L, kUseModule, "kUseModule"), IsOkAndHolds(1)); + ASSERT_THAT(lua::Call(L, 0), IsOkAndHolds(1)); + + int val; + ASSERT_TRUE(lua::Read(L, -1, &val)); EXPECT_EQ(11, val); } diff --git a/deepmind/lua/vm_test_module.lua b/deepmind/lua/vm_test_module.lua index a836f3c3..ca52f3f0 100644 --- a/deepmind/lua/vm_test_module.lua +++ b/deepmind/lua/vm_test_module.lua @@ -1,2 +1,2 @@ -local mod = { hello = 11 } +local mod = {hello = 11} return mod diff --git a/deepmind/model_generation/BUILD b/deepmind/model_generation/BUILD new file mode 100644 index 00000000..4f6f6252 --- /dev/null +++ b/deepmind/model_generation/BUILD @@ -0,0 +1,281 @@ +# Description: +# Procedural modelling library. + +licenses(["restricted"]) # GPLv2 + +cc_library( + name = "geometry_util", + srcs = ["geometry_util.cc"], + hdrs = ["geometry_util.h"], + deps = [ + ":transform", + "//deepmind/support:logging", + "@com_google_absl//absl/strings", + "@eigen_archive//:eigen", + ], +) + +cc_library( + name = "geometry_cone", + srcs = ["geometry_cone.cc"], + hdrs = ["geometry_cone.h"], + deps = [ + ":geometry_util", + ":model", + ":transform", + "//deepmind/support:logging", + "@eigen_archive//:eigen", + ], +) + +cc_library( + name = "geometry_cube", + srcs = ["geometry_cube.cc"], + hdrs = ["geometry_cube.h"], + deps = [ + ":geometry_util", + ":model", + ":transform", + "//deepmind/support:logging", + "@eigen_archive//:eigen", + ], +) + +cc_library( + name = "geometry_cylinder", + srcs = ["geometry_cylinder.cc"], + hdrs = ["geometry_cylinder.h"], + deps = [ + ":geometry_util", + ":model", + ":transform", + "//deepmind/support:logging", + "@eigen_archive//:eigen", + ], +) + +cc_library( + name = "geometry_sphere", + srcs = ["geometry_sphere.cc"], + hdrs = ["geometry_sphere.h"], + deps = [ + ":geometry_util", + ":model", + ":transform", + "//deepmind/support:logging", + "@eigen_archive//:eigen", + ], +) + +cc_test( + name = "geometry_test", + size = "small", + srcs = ["geometry_test.cc"], + deps = [ + ":geometry_cone", + ":geometry_cube", + ":geometry_cylinder", + ":geometry_sphere", + ":geometry_util", + "//deepmind/support:logging", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "model", + hdrs = ["model.h"], + visibility = ["//visibility:public"], + deps = [":transform"], +) + +cc_library( + name = "model_lua", + srcs = ["model_lua.cc"], + hdrs = ["model_lua.h"], + visibility = ["//visibility:public"], + deps = [ + ":model", + ":transform_lua", + "//deepmind/lua", + "//deepmind/lua:push", + "//deepmind/lua:read", + "//deepmind/lua:table_ref", + "//deepmind/support:logging", + "//deepmind/tensor:lua_tensor", + ], +) + +cc_test( + name = "model_lua_test", + size = "small", + srcs = ["model_lua_test.cc"], + deps = [ + ":geometry_util", + ":model_lua", + "//deepmind/lua:call", + "//deepmind/lua:n_results_or_test_util", + "//deepmind/lua:push_script", + "//deepmind/lua:vm", + "//deepmind/support:test_srcdir", + "//deepmind/tensor:lua_tensor", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "model_getters", + srcs = ["model_getters.cc"], + hdrs = ["model_getters.h"], + visibility = ["//visibility:public"], + deps = [ + ":model", + "//deepmind/include:context_hdrs", + "//deepmind/support:logging", + ], +) + +cc_library( + name = "model_setters", + srcs = ["model_setters.cc"], + hdrs = ["model_setters.h"], + visibility = ["//visibility:public"], + deps = [ + ":model", + "//deepmind/include:context_hdrs", + "//deepmind/support:logging", + ], +) + +cc_library( + name = "model_util", + srcs = ["model_util.cc"], + hdrs = ["model_util.h"], + visibility = ["//visibility:public"], + deps = [ + ":model", + "@eigen_archive//:eigen", + ], +) + +cc_library( + name = "transform", + hdrs = ["transform.h"], + deps = ["@eigen_archive//:eigen"], +) + +cc_library( + name = "transform_lua", + srcs = ["transform_lua.cc"], + hdrs = ["transform_lua.h"], + deps = [ + ":transform", + "//deepmind/lua", + "//deepmind/support:logging", + "//deepmind/tensor:lua_tensor", + "//deepmind/tensor:tensor_view", + ], +) + +cc_test( + name = "transform_lua_test", + size = "small", + srcs = ["transform_lua_test.cc"], + deps = [ + ":geometry_util", + ":transform_lua", + "//deepmind/lua:call", + "//deepmind/lua:n_results_or_test_util", + "//deepmind/lua:push_script", + "//deepmind/lua:vm", + "//deepmind/support:test_srcdir", + "//deepmind/tensor:lua_tensor", + "@com_google_googletest//:gtest_main", + "@eigen_archive//:eigen", + ], +) + +cc_library( + name = "lua_model", + srcs = ["lua_model.cc"], + hdrs = ["lua_model.h"], + visibility = ["//visibility:public"], + deps = [ + ":geometry_cone", + ":geometry_cube", + ":geometry_cylinder", + ":geometry_sphere", + ":geometry_util", + ":model_getters", + ":model_lua", + ":model_setters", + ":model_util", + ":transform_lua", + "//deepmind/include:context_hdrs", + "//deepmind/lua", + "//deepmind/lua:class", + "//deepmind/lua:push", + "//deepmind/lua:read", + "//deepmind/lua:table_ref", + "//deepmind/support:logging", + "@com_google_absl//absl/strings", + ], +) + +cc_test( + name = "lua_model_test", + size = "small", + srcs = ["lua_model_test.cc"], + deps = [ + ":geometry_util", + ":lua_model", + ":model_lua", + "//deepmind/engine:callbacks", + "//deepmind/include:context_hdrs", + "//deepmind/lua:bind", + "//deepmind/lua:call", + "//deepmind/lua:n_results_or_test_util", + "//deepmind/lua:push_script", + "//deepmind/lua:vm", + "//deepmind/support:test_srcdir", + "//deepmind/tensor:lua_tensor", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "lua_transform", + srcs = ["lua_transform.cc"], + hdrs = ["lua_transform.h"], + visibility = ["//visibility:public"], + deps = [ + ":geometry_util", + ":transform_lua", + "//deepmind/lua", + "//deepmind/lua:bind", + "//deepmind/lua:n_results_or", + "//deepmind/lua:push", + "//deepmind/lua:read", + "//deepmind/lua:table_ref", + ], +) + +cc_test( + name = "lua_transform_test", + size = "small", + srcs = ["lua_transform_test.cc"], + deps = [ + ":geometry_util", + ":lua_transform", + ":transform_lua", + "//deepmind/lua:bind", + "//deepmind/lua:call", + "//deepmind/lua:n_results_or_test_util", + "//deepmind/lua:push_script", + "//deepmind/lua:vm", + "//deepmind/support:test_srcdir", + "//deepmind/tensor:lua_tensor", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/deepmind/model_generation/geometry_cone.cc b/deepmind/model_generation/geometry_cone.cc new file mode 100644 index 00000000..6148071d --- /dev/null +++ b/deepmind/model_generation/geometry_cone.cc @@ -0,0 +1,116 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "deepmind/model_generation/geometry_cone.h" + +#include "deepmind/support/logging.h" +#include "Eigen/Geometry" +#include "deepmind/model_generation/geometry_util.h" +#include "deepmind/model_generation/transform.h" + +namespace deepmind { +namespace lab { +namespace geometry { + +Model::Surface CreateSurface(const Cone& cone) { + Model::Surface surf; + // Run argument checks. + CHECK_GT(cone.width_radius, kEpsilon); + CHECK_GT(cone.depth_radius, kEpsilon); + CHECK_GT(cone.height, kEpsilon); + CHECK_GT(cone.num_phi_segments, 0); + CHECK_GT(cone.num_radius_segments, 0); + CHECK_GT(cone.num_height_segments, 0); + // Reserve storage for conical sheet and caps. + const std::size_t sections = cone.num_phi_segments * 4; + std::size_t sheet_num_vertices, sheet_num_triangles; + ComputeDiskMeshSize(sections, cone.num_height_segments, &sheet_num_vertices, + &sheet_num_triangles); + std::size_t cap_num_vertices, cap_num_triangles; + ComputeDiskMeshSize(sections, cone.num_radius_segments, &cap_num_vertices, + &cap_num_triangles); + surf.vertices.reserve((sheet_num_vertices + cap_num_vertices) * 8); + surf.indices.reserve((sheet_num_triangles + cap_num_triangles) * 3); + // Build cone sheet. + const float rx = 1.0f / cone.width_radius; + const float ry = 1.0f / cone.depth_radius; + const float rz = 1.0f / cone.height; + const float nz = 0.70710678118f; // 0.5f * kPi - std::atan(1.0f) + const float nxy = 0.70710678118f; // sqrt(1.0f - nz * nz) + BuildDiskMesh(sections, cone.num_height_segments, surf.vertices.size() / 8, + [rx, ry, rz, nz, nxy, &cone](float u, + float v) -> std::array { + const float phi = 2.0f * kPi * u; + const float px = std::cos(phi) * v * cone.width_radius; + const float py = -std::sin(phi) * v * cone.depth_radius; + const float pz = (0.5f - v) * cone.height; + Eigen::Vector3f nrm = {std::cos(phi) * nxy * rx, + -std::sin(phi) * nxy * ry, nz * rz}; + nrm.normalize(); + return {{px, py, pz, nrm[0], nrm[1], nrm[2], u, v}}; + }, + &surf.vertices, &surf.indices); + // Build cone cap. + BuildDiskMesh(sections, cone.num_radius_segments, surf.vertices.size() / 8, + [&cone](float u, float v) -> std::array { + const float phi = 2.0f * kPi * u; + const float x = std::cos(phi) * v * cone.width_radius; + const float y = std::sin(phi) * v * cone.depth_radius; + const float z = -0.5f * cone.height; + return {{x, y, z, 0.0f, 0.0f, -1.0f, u, v}}; + }, + &surf.vertices, &surf.indices); + surf.name = "cone_surface"; + surf.shader_name = cone.shader_name; + return surf; +} + +Model::LocatorMap CreateLocators(const Cone& cone, + const Eigen::Vector3f& offset) { + Model::LocatorMap locators; + const float rx = 1.0f / cone.width_radius; + const float ry = 1.0f / cone.depth_radius; + const float rz = 1.0f / cone.height; + BuildDefaultLocators( + [rx, ry, rz, &cone, &offset](float u, float v, float w) -> Transform { + auto z_dir = ComputeDefaultZDir(u, v, w, rx, ry, rz); + auto y_vector = ComputeDefaultYVector(u, v, w); + Eigen::Vector3f pos(u, v, w); + pos.head<2>().normalize(); + pos[0] *= 0.5f * (1.0f - w) * cone.width_radius; + pos[1] *= 0.5f * (1.0f - w) * cone.depth_radius; + pos[2] *= 0.5f * cone.height; + return CreateZAlignedFrame(pos + offset, -z_dir, -y_vector); + }, + [rx, ry, rz, &cone, &offset](float u, float v, float w) -> Transform { + auto z_dir = ComputeDefaultZDir(u, v, w, rx, ry, rz); + auto y_vector = ComputeDefaultYVector(u, v, w); + Eigen::Vector3f pos(u, v, w); + pos.head<2>().normalize(); + pos[0] *= 0.5f * (1.0f - w) * cone.width_radius; + pos[1] *= 0.5f * (1.0f - w) * cone.depth_radius; + pos[2] *= 0.5f * cone.height; + return CreateZAlignedFrame(pos + offset, z_dir, y_vector); + }, + &locators); + return locators; +} + +} // namespace geometry +} // namespace lab +} // namespace deepmind diff --git a/deepmind/model_generation/geometry_cone.h b/deepmind/model_generation/geometry_cone.h new file mode 100644 index 00000000..3a320c1d --- /dev/null +++ b/deepmind/model_generation/geometry_cone.h @@ -0,0 +1,61 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef DML_DEEPMIND_MODEL_GENERATION_GEOMETRY_CONE_H_ +#define DML_DEEPMIND_MODEL_GENERATION_GEOMETRY_CONE_H_ + +#include +#include + +#include "Eigen/Geometry" +#include "deepmind/model_generation/model.h" + +namespace deepmind { +namespace lab { +namespace geometry { + +// Parameters used to construct surfaces in the shape of an elliptic cone +// (defaulting to regular cone with height 1 and base diameter 1). +struct Cone { + float width_radius = 0.5f; + float depth_radius = 0.5f; + float height = 1.0f; + // How many angular segments in a quadrant of the cone's base. + std::size_t num_phi_segments = 4; + // How many radial segments in the cone's base. + std::size_t num_radius_segments = 1; + // How many horizontal segments along the cone's body. + std::size_t num_height_segments = 1; + // Shader used for the cone's surfaces. + std::string shader_name; +}; + +// Create a surface in the shape of a Cone with the parameters provided. +Model::Surface CreateSurface(const Cone& cone_params); + +// Create the locator set for a cone model with the parameters provided. All +// locators will be translated by the offset position. +Model::LocatorMap CreateLocators( + const Cone& cone_params, + const Eigen::Vector3f& offset = Eigen::Vector3f::Zero()); + +} // namespace geometry +} // namespace lab +} // namespace deepmind + +#endif // DML_DEEPMIND_MODEL_GENERATION_GEOMETRY_CONE_H_ diff --git a/deepmind/model_generation/geometry_cube.cc b/deepmind/model_generation/geometry_cube.cc new file mode 100644 index 00000000..6ffe8823 --- /dev/null +++ b/deepmind/model_generation/geometry_cube.cc @@ -0,0 +1,143 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "deepmind/model_generation/geometry_cube.h" + +#include "deepmind/support/logging.h" +#include "deepmind/model_generation/geometry_util.h" +#include "deepmind/model_generation/transform.h" + +namespace deepmind { +namespace lab { +namespace geometry { + +Model::Surface CreateSurface(const Cube& cube) { + Model::Surface surf; + // Run argument checks. + CHECK_GT(cube.width, kEpsilon); + CHECK_GT(cube.depth, kEpsilon); + CHECK_GT(cube.height, kEpsilon); + CHECK_GT(cube.num_width_segments, 0); + CHECK_GT(cube.num_depth_segments, 0); + CHECK_GT(cube.num_height_segments, 0); + // Reserve storage for 3 pairs of face sheets. + std::size_t zy_num_vertices, zy_num_triangles; + ComputeRectMeshSize(cube.num_depth_segments, cube.num_height_segments, + &zy_num_vertices, &zy_num_triangles); + std::size_t xz_num_vertices, xz_num_triangles; + ComputeRectMeshSize(cube.num_height_segments, cube.num_width_segments, + &xz_num_vertices, &xz_num_triangles); + std::size_t xy_num_vertices, xy_num_triangles; + ComputeRectMeshSize(cube.num_depth_segments, cube.num_width_segments, + &xy_num_vertices, &xy_num_triangles); + surf.vertices.reserve((zy_num_vertices + xz_num_vertices + xy_num_vertices) * + 2 * 8); + surf.indices.reserve( + (zy_num_triangles + xz_num_triangles + xy_num_triangles) * 2 * 3); + // Build zy faces. + BuildRectMesh(cube.num_depth_segments, cube.num_height_segments, + surf.vertices.size() / 8, + [&cube](float u, float v) -> std::array { + const float x = 0.5f * cube.width; + const float y = (0.5f - u) * cube.depth; + const float z = (v - 0.5f) * cube.height; + return {{x, y, z, 1.0f, 0.0f, 0.0f, u, v}}; + }, + &surf.vertices, &surf.indices); + BuildRectMesh(cube.num_depth_segments, cube.num_height_segments, + surf.vertices.size() / 8, + [&cube](float u, float v) -> std::array { + const float x = -0.5f * cube.width; + const float y = (u - 0.5f) * cube.depth; + const float z = (v - 0.5f) * cube.height; + return {{x, y, z, -1.0f, 0.0f, 0.0f, u, v}}; + }, + &surf.vertices, &surf.indices); + // Build xz faces. + BuildRectMesh(cube.num_height_segments, cube.num_width_segments, + surf.vertices.size() / 8, + [&cube](float u, float v) -> std::array { + const float x = (u - 0.5f) * cube.width; + const float y = 0.5f * cube.depth; + const float z = (v - 0.5f) * cube.height; + return {{x, y, z, 0.0f, 1.0f, 0.0f, u, v}}; + }, + &surf.vertices, &surf.indices); + BuildRectMesh(cube.num_height_segments, cube.num_width_segments, + surf.vertices.size() / 8, + [&cube](float u, float v) -> std::array { + const float x = (0.5f - u) * cube.width; + const float y = -0.5f * cube.depth; + const float z = (v - 0.5f) * cube.height; + return {{x, y, z, 0.0f, -1.0f, 0.0f, u, v}}; + }, + &surf.vertices, &surf.indices); + // Build xy faces. + BuildRectMesh(cube.num_depth_segments, cube.num_width_segments, + surf.vertices.size() / 8, + [&cube](float u, float v) -> std::array { + const float x = (u - 0.5f) * cube.width; + const float y = (0.5f - v) * cube.depth; + const float z = 0.5f * cube.height; + return {{x, y, z, 0.0f, 0.0f, 1.0f, u, v}}; + }, + &surf.vertices, &surf.indices); + BuildRectMesh(cube.num_depth_segments, cube.num_width_segments, + surf.vertices.size() / 8, + [&cube](float u, float v) -> std::array { + const float x = (u - 0.5f) * cube.width; + const float y = (v - 0.5f) * cube.depth; + const float z = -0.5f * cube.height; + return {{x, y, z, 0.0f, 0.0f, -1.0f, u, v}}; + }, + &surf.vertices, &surf.indices); + surf.name = "cube_surface"; + surf.shader_name = cube.shader_name; + return surf; +} + +Model::LocatorMap CreateLocators(const Cube& cube, + const Eigen::Vector3f& offset) { + Model::LocatorMap locators; + const float rx = 1.0f / cube.width; + const float ry = 1.0f / cube.depth; + const float rz = 1.0f / cube.height; + BuildDefaultLocators( + [rx, ry, rz, &cube, &offset](float u, float v, float w) -> Transform { + auto z_dir = ComputeDefaultZDir(u, v, w, rx, ry, rz); + auto y_vector = ComputeDefaultYVector(u, v, w); + const Eigen::Vector3f pos(u * 0.5f * cube.width, // + v * 0.5f * cube.depth, // + w * 0.5f * cube.height); + return CreateZAlignedFrame(pos + offset, -z_dir, -y_vector); + }, + [rx, ry, rz, &cube, &offset](float u, float v, float w) -> Transform { + auto z_dir = ComputeDefaultZDir(u, v, w, rx, ry, rz); + auto y_vector = ComputeDefaultYVector(u, v, w); + const Eigen::Vector3f pos(u * 0.5f * cube.width, // + v * 0.5f * cube.depth, // + w * 0.5f * cube.height); + return CreateZAlignedFrame(pos + offset, z_dir, y_vector); + }, + &locators); + return locators; +} + +} // namespace geometry +} // namespace lab +} // namespace deepmind diff --git a/deepmind/model_generation/geometry_cube.h b/deepmind/model_generation/geometry_cube.h new file mode 100644 index 00000000..feef0e27 --- /dev/null +++ b/deepmind/model_generation/geometry_cube.h @@ -0,0 +1,58 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef DML_DEEPMIND_MODEL_GENERATION_GEOMETRY_CUBE_H_ +#define DML_DEEPMIND_MODEL_GENERATION_GEOMETRY_CUBE_H_ + +#include +#include + +#include "Eigen/Geometry" +#include "deepmind/model_generation/model.h" + +// Parameters used to construct surfaces in the shape of a cuboids (defaulting +// to a cube with edge length 1). +namespace deepmind { +namespace lab { +namespace geometry { + +struct Cube { + float width = 1.0f; + float depth = 1.0f; + float height = 1.0f; + std::size_t num_width_segments = 1; + std::size_t num_depth_segments = 1; + std::size_t num_height_segments = 1; + // Shader used for the cube's surfaces. + std::string shader_name; +}; + +// Create a surface in the shape of a cube with the parameters provided. +Model::Surface CreateSurface(const Cube& cube_params); + +// Create the locator set for a cube model with the parameters provided. All +// locators will be translated by the offset position. +Model::LocatorMap CreateLocators( + const Cube& cube_params, + const Eigen::Vector3f& offset = Eigen::Vector3f::Zero()); + +} // namespace geometry +} // namespace lab +} // namespace deepmind + +#endif // DML_DEEPMIND_MODEL_GENERATION_GEOMETRY_CUBE_H_ diff --git a/deepmind/model_generation/geometry_cylinder.cc b/deepmind/model_generation/geometry_cylinder.cc new file mode 100644 index 00000000..444a2c55 --- /dev/null +++ b/deepmind/model_generation/geometry_cylinder.cc @@ -0,0 +1,125 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "deepmind/model_generation/geometry_cylinder.h" + +#include "deepmind/support/logging.h" +#include "Eigen/Geometry" +#include "deepmind/model_generation/geometry_util.h" +#include "deepmind/model_generation/transform.h" + +namespace deepmind { +namespace lab { +namespace geometry { + +Model::Surface CreateSurface(const Cylinder& cylinder) { + Model::Surface surf; + // Run argument checks. + CHECK_GT(cylinder.width_radius, kEpsilon); + CHECK_GT(cylinder.depth_radius, kEpsilon); + CHECK_GT(cylinder.height, kEpsilon); + CHECK_GT(cylinder.num_phi_segments, 0); + CHECK_GT(cylinder.num_radius_segments, 0); + CHECK_GT(cylinder.num_height_segments, 0); + // Reserve storage for cylinder sheet and caps. + const std::size_t sections = cylinder.num_phi_segments * 4; + std::size_t sheet_num_vertices, sheet_num_triangles; + ComputeRectMeshSize(cylinder.num_height_segments, sections, + &sheet_num_vertices, &sheet_num_triangles); + std::size_t cap_num_vertices, cap_num_triangles; + ComputeDiskMeshSize(sections, cylinder.num_radius_segments, &cap_num_vertices, + &cap_num_triangles); + surf.vertices.reserve((sheet_num_vertices + cap_num_vertices * 2) * 8); + surf.indices.reserve((sheet_num_triangles + cap_num_triangles * 2) * 3); + // Build cylinder sheet. + const float rx = 1.0f / cylinder.width_radius; + const float ry = 1.0f / cylinder.depth_radius; + BuildRectMesh(cylinder.num_height_segments, sections, + surf.vertices.size() / 8, + [rx, ry, &cylinder](float u, float v) -> std::array { + const float phi = 2.0f * kPi * u; + const float x = std::cos(phi); + const float y = -std::sin(phi); + const float z = v - 0.5f; + Eigen::Vector3f nrm = {rx * x, ry * y, 0.0f}; + nrm.normalize(); + return {{x * cylinder.width_radius, y * cylinder.depth_radius, + z * cylinder.height, nrm[0], nrm[1], nrm[2], u, v}}; + }, + &surf.vertices, &surf.indices); + // Build top cap. + BuildDiskMesh(sections, cylinder.num_radius_segments, + surf.vertices.size() / 8, + [&cylinder](float u, float v) -> std::array { + const float phi = 2.0f * kPi * u; + const float px = std::cos(phi) * v * cylinder.width_radius; + const float py = -std::sin(phi) * v * cylinder.depth_radius; + const float pz = 0.5f * cylinder.height; + return {{px, py, pz, 0.0f, 0.0f, 1.0f, u, v}}; + }, + &surf.vertices, &surf.indices); + // Build bottom cap. + BuildDiskMesh(sections, cylinder.num_radius_segments, + surf.vertices.size() / 8, + [&cylinder](float u, float v) -> std::array { + const float phi = 2.0f * kPi * u; + const float px = std::cos(phi) * v * cylinder.width_radius; + const float py = std::sin(phi) * v * cylinder.depth_radius; + const float pz = -0.5f * cylinder.height; + return {{px, py, pz, 0.0f, 0.0f, -1.0f, u, v}}; + }, + &surf.vertices, &surf.indices); + surf.name = "cylinder_surface"; + surf.shader_name = cylinder.shader_name; + return surf; +} + +Model::LocatorMap CreateLocators(const Cylinder& cylinder, + const Eigen::Vector3f& offset) { + Model::LocatorMap locators; + const float rx = 1.0f / cylinder.width_radius; + const float ry = 1.0f / cylinder.depth_radius; + const float rz = 2.0f / cylinder.height; + BuildDefaultLocators( + [rx, ry, rz, &cylinder, &offset](float u, float v, float w) -> Transform { + auto z_dir = ComputeDefaultZDir(u, v, w, rx, ry, rz); + auto y_vector = ComputeDefaultYVector(u, v, w); + Eigen::Vector3f pos(u, v, w); + pos.head<2>().normalize(); + pos[0] *= cylinder.width_radius; + pos[1] *= cylinder.depth_radius; + pos[2] *= 0.5 * cylinder.height; + return CreateZAlignedFrame(pos + offset, -z_dir, -y_vector); + }, + [rx, ry, rz, &cylinder, &offset](float u, float v, float w) -> Transform { + auto z_dir = ComputeDefaultZDir(u, v, w, rx, ry, rz); + auto y_vector = ComputeDefaultYVector(u, v, w); + Eigen::Vector3f pos(u, v, w); + pos.head<2>().normalize(); + pos[0] *= cylinder.width_radius; + pos[1] *= cylinder.depth_radius; + pos[2] *= 0.5 * cylinder.height; + return CreateZAlignedFrame(pos + offset, z_dir, y_vector); + }, + &locators); + return locators; +} + +} // namespace geometry +} // namespace lab +} // namespace deepmind diff --git a/deepmind/model_generation/geometry_cylinder.h b/deepmind/model_generation/geometry_cylinder.h new file mode 100644 index 00000000..bbdb276d --- /dev/null +++ b/deepmind/model_generation/geometry_cylinder.h @@ -0,0 +1,61 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef DML_DEEPMIND_MODEL_GENERATION_GEOMETRY_CYLINDER_H_ +#define DML_DEEPMIND_MODEL_GENERATION_GEOMETRY_CYLINDER_H_ + +#include +#include + +#include "Eigen/Geometry" +#include "deepmind/model_generation/model.h" + +namespace deepmind { +namespace lab { +namespace geometry { + +// Parameters used to construct surfaces in the shape of a elliptic cylinder +// (defaulting to a regular cylinder width diameter 1 and height 1). +struct Cylinder { + float width_radius = 0.5f; + float depth_radius = 0.5f; + float height = 1.0f; + // How many angular segments in a quadrant of the cylinder's caps. + std::size_t num_phi_segments = 4; + // How many radial segments in the cylinder's caps. + std::size_t num_radius_segments = 1; + // How many horizontal segments along the cylinder's body. + std::size_t num_height_segments = 1; + // Shader used for the cylinder's surfaces. + std::string shader_name; +}; + +// Create a surface in the shape of a cylinder with the parameters provided. +Model::Surface CreateSurface(const Cylinder& cylinder_params); + +// Create the locator set for a Cone model with the parameters provided. All +// locators will be translated by the offset position. +Model::LocatorMap CreateLocators( + const Cylinder& cylinder_params, + const Eigen::Vector3f& offset = Eigen::Vector3f::Zero()); + +} // namespace geometry +} // namespace lab +} // namespace deepmind + +#endif // DML_DEEPMIND_MODEL_GENERATION_GEOMETRY_CYLINDER_H_ diff --git a/deepmind/model_generation/geometry_sphere.cc b/deepmind/model_generation/geometry_sphere.cc new file mode 100644 index 00000000..c2b23088 --- /dev/null +++ b/deepmind/model_generation/geometry_sphere.cc @@ -0,0 +1,115 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "deepmind/model_generation/geometry_sphere.h" + +#include "deepmind/support/logging.h" +#include "Eigen/Geometry" +#include "deepmind/model_generation/geometry_util.h" +#include "deepmind/model_generation/transform.h" + +namespace deepmind { +namespace lab { +namespace geometry { + +Model::Surface CreateSurface(const Sphere& sphere) { + Model::Surface surf; + // Run argument checks. + CHECK_GT(sphere.width_radius, kEpsilon); + CHECK_GT(sphere.depth_radius, kEpsilon); + CHECK_GT(sphere.height_radius, kEpsilon); + CHECK_GT(sphere.num_phi_segments, 0); + CHECK_GT(sphere.num_theta_segments, 0); + // Reserve storage for two hemispheres. + const std::size_t sections = sphere.num_phi_segments * 4; + std::size_t hemi_num_vertices, hemi_num_triangles; + ComputeDiskMeshSize(sections, sphere.num_theta_segments, &hemi_num_vertices, + &hemi_num_triangles); + surf.vertices.reserve(hemi_num_vertices * 2 * 8); + surf.indices.reserve(hemi_num_triangles * 2 * 3); + // Build two hemispheres. + const float rx = 1.0f / sphere.width_radius; + const float ry = 1.0f / sphere.depth_radius; + const float rz = 1.0f / sphere.height_radius; + BuildDiskMesh( + sections, sphere.num_theta_segments, surf.vertices.size() / 8, + [rx, ry, rz, &sphere](float u, float v) -> std::array { + const float phi = 2.0f * kPi * u; + const float theta = 0.5f * kPi * v; + const float x = std::cos(phi) * std::sin(theta); + const float y = -std::sin(phi) * std::sin(theta); + const float z = std::cos(theta); + Eigen::Vector3f nrm = {x * rx, y * ry, z * rz}; + nrm.normalize(); + return {{x * sphere.width_radius, y * sphere.depth_radius, + z * sphere.height_radius, nrm[0], nrm[1], nrm[2], u, v}}; + }, + &surf.vertices, &surf.indices); + BuildDiskMesh( + sections, sphere.num_theta_segments, surf.vertices.size() / 8, + [rx, ry, rz, &sphere](float u, float v) -> std::array { + const float phi = 2.0f * kPi * u; + const float theta = 0.5f * kPi * v; + const float x = std::cos(phi) * std::sin(theta); + const float y = std::sin(phi) * std::sin(theta); + const float z = -std::cos(theta); + Eigen::Vector3f nrm = {x * rx, y * ry, z * rz}; + nrm.normalize(); + return {{x * sphere.width_radius, y * sphere.depth_radius, + z * sphere.height_radius, nrm[0], nrm[1], nrm[2], u, v}}; + }, + &surf.vertices, &surf.indices); + surf.name = "sphere_surface"; + surf.shader_name = sphere.shader_name; + return surf; +} + +Model::LocatorMap CreateLocators(const Sphere& sphere, + const Eigen::Vector3f& offset) { + Model::LocatorMap locators; + const float rx = 1.0f / sphere.width_radius; + const float ry = 1.0f / sphere.depth_radius; + const float rz = 1.0f / sphere.height_radius; + BuildDefaultLocators( + [rx, ry, rz, &sphere, &offset](float u, float v, float w) -> Transform { + auto z_dir = ComputeDefaultZDir(u, v, w, rx, ry, rz); + auto y_vector = ComputeDefaultYVector(u, v, w); + Eigen::Vector3f pos(u, v, w); + pos.normalize(); + pos[0] *= sphere.width_radius; + pos[1] *= sphere.depth_radius; + pos[2] *= sphere.height_radius; + return CreateZAlignedFrame(pos + offset, -z_dir, -y_vector); + }, + [rx, ry, rz, &sphere, &offset](float u, float v, float w) -> Transform { + auto z_dir = ComputeDefaultZDir(u, v, w, rx, ry, rz); + auto y_vector = ComputeDefaultYVector(u, v, w); + Eigen::Vector3f pos(u, v, w); + pos.normalize(); + pos[0] *= sphere.width_radius; + pos[1] *= sphere.depth_radius; + pos[2] *= sphere.height_radius; + return CreateZAlignedFrame(pos + offset, z_dir, y_vector); + }, + &locators); + return locators; +} + +} // namespace geometry +} // namespace lab +} // namespace deepmind diff --git a/deepmind/model_generation/geometry_sphere.h b/deepmind/model_generation/geometry_sphere.h new file mode 100644 index 00000000..e33b6293 --- /dev/null +++ b/deepmind/model_generation/geometry_sphere.h @@ -0,0 +1,59 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef DML_DEEPMIND_MODEL_GENERATION_GEOMETRY_SPHERE_H_ +#define DML_DEEPMIND_MODEL_GENERATION_GEOMETRY_SPHERE_H_ + +#include +#include + +#include "Eigen/Geometry" +#include "deepmind/model_generation/model.h" + +namespace deepmind { +namespace lab { +namespace geometry { + +// Parameters used to construct surfaces in the shape of a ellipsoid (defaulting +// to a sphere with diameter 1). +struct Sphere { + float width_radius = 0.5f; + float depth_radius = 0.5f; + float height_radius = 0.5f; + // How many segments along the azimuth range of an octant of the sphere. + std::size_t num_phi_segments = 4; + // How many segments along the elevation range of an octant of the sphere. + std::size_t num_theta_segments = 4; + // Shader used for the cylinder's surfaces. + std::string shader_name; +}; + +// Create a surface in the shape of a sphere with the parameters provided. +Model::Surface CreateSurface(const Sphere& sphere_params); + +// Create the locator set for a sphere model with the parameters provided. All +// locators will be translated by the offset position. +Model::LocatorMap CreateLocators( + const Sphere& sphere_params, + const Eigen::Vector3f& offset = Eigen::Vector3f::Zero()); + +} // namespace geometry +} // namespace lab +} // namespace deepmind + +#endif // DML_DEEPMIND_MODEL_GENERATION_GEOMETRY_SPHERE_H_ diff --git a/deepmind/model_generation/geometry_test.cc b/deepmind/model_generation/geometry_test.cc new file mode 100644 index 00000000..e1546d3d --- /dev/null +++ b/deepmind/model_generation/geometry_test.cc @@ -0,0 +1,426 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "deepmind/model_generation/geometry_cone.h" +#include "deepmind/model_generation/geometry_cube.h" +#include "deepmind/model_generation/geometry_cylinder.h" +#include "deepmind/model_generation/geometry_sphere.h" +#include "deepmind/model_generation/geometry_util.h" + +namespace deepmind { +namespace lab { +namespace geometry { +namespace { + +#define VERIFY_EQ(a, b) \ + if ((a) != (b)) \ + return ::testing::AssertionFailure() \ + << "\n Value of: " #b "\n Actual: " << (b) << "\n" \ + << " Expected: " #a "\n Which is: " << (a) << "\n "; + +testing::AssertionResult TestDiskTopology( // + int num_sectors, // + int num_tracks, // + int offset, // + std::vector::const_iterator it) { + for (int j = 0; j < num_sectors; ++j) { + VERIFY_EQ(*it++, offset + (j + 1) * (num_tracks + 1)); + VERIFY_EQ(*it++, offset + (j + 1) * (num_tracks + 1) - 1); + VERIFY_EQ(*it++, offset + j * (num_tracks + 1)); + for (int i = 1; i < num_tracks; ++i) { + VERIFY_EQ(*it++, offset + (j + 1) * (num_tracks + 1) + i); + VERIFY_EQ(*it++, offset + (j + 1) * (num_tracks + 1) + i - 1); + VERIFY_EQ(*it++, offset + j * (num_tracks + 1) + i - 1); + VERIFY_EQ(*it++, offset + j * (num_tracks + 1) + i); + VERIFY_EQ(*it++, offset + (j + 1) * (num_tracks + 1) + i); + VERIFY_EQ(*it++, offset + j * (num_tracks + 1) + i - 1); + } + } + return testing::AssertionSuccess(); +} + +testing::AssertionResult TestRectTopology( // + int num_rows, // + int num_cols, // + int offset, // + std::vector::const_iterator it) { + for (int j = 0; j < num_cols; ++j) { + for (int i = 0; i < num_rows; ++i) { + VERIFY_EQ(*it++, offset + j * (num_rows + 1) + i); + VERIFY_EQ(*it++, offset + (j + 1) * (num_rows + 1) + i); + VERIFY_EQ(*it++, offset + (j + 1) * (num_rows + 1) + i + 1); + VERIFY_EQ(*it++, offset + j * (num_rows + 1) + i); + VERIFY_EQ(*it++, offset + (j + 1) * (num_rows + 1) + i + 1); + VERIFY_EQ(*it++, offset + j * (num_rows + 1) + i + 1); + } + } + return testing::AssertionSuccess(); +} + +#undef VERIFY_EQ + +TEST(DeepmindGeometryTest, DiskMesh) { + std::size_t num_vertices, num_triangles; + ComputeDiskMeshSize(16, 4, &num_vertices, &num_triangles); + EXPECT_EQ(num_vertices, 84); + EXPECT_EQ(num_triangles, 112); + + struct MockEvaluator { + MOCK_METHOD2(eval, std::array(float u, float v)); + } mockEvaluator; + + { + testing::InSequence s; + float u, v; + u = 0.0f; + for (int i = 1; i <= 4; ++i) { + v = i / 4.0f; + EXPECT_CALL(mockEvaluator, eval(u, v)); + } + for (int j = 1; j <= 16; ++j) { + u = (j - 0.5f) / 16.0f; + v = 0.0f; + EXPECT_CALL(mockEvaluator, eval(u, v)); + u = j / 16.0f; + for (int i = 1; i < 4; ++i) { + v = i / 4.0f; + EXPECT_CALL(mockEvaluator, eval(u, v)); + } + v = 1.0f; + EXPECT_CALL(mockEvaluator, eval(u, v)); + } + } + + std::vector vertices; + std::vector indices; + BuildDiskMesh(16, 4, 0, + [&mockEvaluator](float u, float v) -> std::array { + return mockEvaluator.eval(u, v); + }, + &vertices, &indices); + EXPECT_TRUE(TestDiskTopology(16, 4, 0, indices.begin())); +} + +TEST(DeepmindGeometryTest, RectMesh) { + std::size_t num_vertices, num_triangles; + ComputeRectMeshSize(4, 5, &num_vertices, &num_triangles); + EXPECT_EQ(num_vertices, 30); + EXPECT_EQ(num_triangles, 40); + + struct MockEvaluator { + MOCK_METHOD2(eval, std::array(float u, float v)); + } mockEvaluator; + + { + testing::InSequence s; + for (int j = 0; j <= 5; ++j) { + float u = j / 5.0f; + for (int i = 0; i <= 4; ++i) { + float v = i / 4.0f; + EXPECT_CALL(mockEvaluator, eval(u, v)); + } + } + } + + std::vector vertices; + std::vector indices; + BuildRectMesh(4, 5, 0, + [&mockEvaluator](float u, float v) -> std::array { + return mockEvaluator.eval(u, v); + }, + &vertices, &indices); + EXPECT_TRUE(TestRectTopology(4, 5, 0, indices.begin())); +} + +TEST(DeepmindGeometryTest, ConeSurface) { + Cone cone; + cone.num_phi_segments = 4; + cone.num_radius_segments = 4; + cone.num_height_segments = 4; + Model::Surface surf = CreateSurface(cone); + ASSERT_EQ(surf.vertices.size(), 168 * 8); + ASSERT_EQ(surf.indices.size(), 224 * 3); + const float nz = 1.0f / std::sqrt(5.0f); + EXPECT_NEAR(surf.vertices[3 * 8 + 5], nz, kEpsilon); + EXPECT_NEAR(surf.vertices[4 * 8 + 5], nz, kEpsilon); + EXPECT_NEAR(surf.vertices[83 * 8 + 5], nz, kEpsilon); + EXPECT_NEAR(surf.vertices[88 * 8 + 3], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[88 * 8 + 4], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[88 * 8 + 5], -1.0f, kEpsilon); + EXPECT_TRUE(TestDiskTopology(16, 4, 0, surf.indices.begin())); + EXPECT_TRUE(TestDiskTopology(16, 4, 84, surf.indices.begin() + 112 * 3)); +} + +TEST(DeepmindGeometryTest, ConeLocators) { + Cone cone; + cone.num_phi_segments = 4; + cone.num_radius_segments = 4; + cone.num_height_segments = 4; + Model::LocatorMap locators = CreateLocators(cone); + EXPECT_EQ(locators.size(), 27 * 2); + auto it = locators.find("centre_top_left_p"); + ASSERT_NE(it, locators.end()); + const float nz = 1.0f / std::sqrt(5.0f); + EXPECT_NEAR(it->second(0, 2), -2.0f * nz, kEpsilon); + EXPECT_NEAR(it->second(1, 2), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(2, 2), nz, kEpsilon); + EXPECT_NEAR(it->second(0, 3), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(1, 3), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(2, 3), 0.5f, kEpsilon); + it = locators.find("front_bottom_centre_p"); + ASSERT_NE(it, locators.end()); + EXPECT_NEAR(it->second(0, 2), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(1, 2), 2.0f * nz, kEpsilon); + EXPECT_NEAR(it->second(2, 2), -nz, kEpsilon); + EXPECT_NEAR(it->second(0, 3), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(1, 3), 0.5f, kEpsilon); + EXPECT_NEAR(it->second(2, 3), -0.5f, kEpsilon); + it = locators.find("centre_bottom_centre_s"); + ASSERT_NE(it, locators.end()); + EXPECT_NEAR(it->second(0, 2), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(1, 2), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(2, 2), 1.0f, kEpsilon); + EXPECT_NEAR(it->second(0, 3), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(1, 3), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(2, 3), -0.5f, kEpsilon); +} + +TEST(DeepmindGeometryTest, CubeSurface) { + Cube cube; + cube.num_width_segments = 4; + cube.num_depth_segments = 4; + cube.num_height_segments = 4; + Model::Surface surf = CreateSurface(cube); + ASSERT_EQ(surf.vertices.size(), 150 * 8); + ASSERT_EQ(surf.indices.size(), 192 * 3); + EXPECT_NEAR(surf.vertices[3], 1.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[4], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[5], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[25 * 8 + 3], -1.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[25 * 8 + 4], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[25 * 8 + 5], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[50 * 8 + 3], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[50 * 8 + 4], 1.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[50 * 8 + 5], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[75 * 8 + 3], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[75 * 8 + 4], -1.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[75 * 8 + 5], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[100 * 8 + 3], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[100 * 8 + 4], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[100 * 8 + 5], 1.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[125 * 8 + 3], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[125 * 8 + 4], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[125 * 8 + 5], -1.0f, kEpsilon); + EXPECT_TRUE(TestRectTopology(4, 4, 0, surf.indices.begin())); + EXPECT_TRUE(TestRectTopology(4, 4, 25, surf.indices.begin() + 32 * 3)); + EXPECT_TRUE(TestRectTopology(4, 4, 50, surf.indices.begin() + 64 * 3)); + EXPECT_TRUE(TestRectTopology(4, 4, 75, surf.indices.begin() + 96 * 3)); + EXPECT_TRUE(TestRectTopology(4, 4, 100, surf.indices.begin() + 128 * 3)); + EXPECT_TRUE(TestRectTopology(4, 4, 125, surf.indices.begin() + 160 * 3)); +} + +TEST(DeepmindGeometryTest, CubeLocators) { + Cube cube; + cube.num_width_segments = 4; + cube.num_depth_segments = 4; + cube.num_height_segments = 4; + Model::LocatorMap locators = CreateLocators(cube); + EXPECT_EQ(locators.size(), 27 * 2); + auto it = locators.find("centre_top_left_p"); + ASSERT_NE(it, locators.end()); + const float nz = 1.0f / std::sqrt(2.0f); + EXPECT_NEAR(it->second(0, 2), -nz, kEpsilon); + EXPECT_NEAR(it->second(1, 2), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(2, 2), nz, kEpsilon); + EXPECT_NEAR(it->second(0, 3), -0.5f, kEpsilon); + EXPECT_NEAR(it->second(1, 3), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(2, 3), 0.5f, kEpsilon); + it = locators.find("front_top_centre_p"); + ASSERT_NE(it, locators.end()); + EXPECT_NEAR(it->second(0, 2), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(1, 2), nz, kEpsilon); + EXPECT_NEAR(it->second(2, 2), nz, kEpsilon); + EXPECT_NEAR(it->second(0, 3), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(1, 3), 0.5f, kEpsilon); + EXPECT_NEAR(it->second(2, 3), 0.5f, kEpsilon); + it = locators.find("centre_bottom_centre_s"); + ASSERT_NE(it, locators.end()); + EXPECT_NEAR(it->second(0, 2), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(1, 2), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(2, 2), 1.0f, kEpsilon); + EXPECT_NEAR(it->second(0, 3), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(1, 3), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(2, 3), -0.5f, kEpsilon); +} + +TEST(DeepmindGeometryTest, CylinderSurface) { + Cylinder cylinder; + cylinder.num_phi_segments = 4; + cylinder.num_radius_segments = 4; + cylinder.num_height_segments = 4; + Model::Surface surf = CreateSurface(cylinder); + ASSERT_EQ(surf.vertices.size(), 253 * 8); + ASSERT_EQ(surf.indices.size(), 352 * 3); + EXPECT_NEAR(surf.vertices[3], 1.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[4], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[5], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[80 * 8 + 3], 1.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[80 * 8 + 4], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[80 * 8 + 5], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[88 * 8 + 3], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[88 * 8 + 4], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[88 * 8 + 5], 1.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[172 * 8 + 3], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[172 * 8 + 4], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[172 * 8 + 5], -1.0f, kEpsilon); + EXPECT_TRUE(TestRectTopology(4, 16, 0, surf.indices.begin())); + EXPECT_TRUE(TestDiskTopology(16, 4, 85, surf.indices.begin() + 128 * 3)); + EXPECT_TRUE(TestDiskTopology(16, 4, 169, surf.indices.begin() + 240 * 3)); +} + +TEST(DeepmindGeometryTest, CylinderLocators) { + Cylinder cylinder; + cylinder.num_phi_segments = 4; + cylinder.num_radius_segments = 4; + cylinder.num_height_segments = 4; + Model::LocatorMap locators = CreateLocators(cylinder); + EXPECT_EQ(locators.size(), 27 * 2); + auto it = locators.find("centre_top_left_p"); + ASSERT_NE(it, locators.end()); + const float nz = 1.0f / std::sqrt(2.0f); + EXPECT_NEAR(it->second(0, 2), -nz, kEpsilon); + EXPECT_NEAR(it->second(1, 2), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(2, 2), nz, kEpsilon); + EXPECT_NEAR(it->second(0, 3), -0.5f, kEpsilon); + EXPECT_NEAR(it->second(1, 3), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(2, 3), 0.5f, kEpsilon); + it = locators.find("front_top_centre_p"); + ASSERT_NE(it, locators.end()); + EXPECT_NEAR(it->second(0, 2), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(1, 2), nz, kEpsilon); + EXPECT_NEAR(it->second(2, 2), nz, kEpsilon); + EXPECT_NEAR(it->second(0, 3), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(1, 3), 0.5f, kEpsilon); + EXPECT_NEAR(it->second(2, 3), 0.5f, kEpsilon); + it = locators.find("centre_bottom_centre_s"); + ASSERT_NE(it, locators.end()); + EXPECT_NEAR(it->second(0, 2), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(1, 2), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(2, 2), 1.0f, kEpsilon); + EXPECT_NEAR(it->second(0, 3), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(1, 3), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(2, 3), -0.5f, kEpsilon); +} + +TEST(DeepmindGeometryTest, SphereSurface) { + Sphere sphere; + sphere.num_phi_segments = 4; + sphere.num_theta_segments = 4; + Model::Surface surf = CreateSurface(sphere); + ASSERT_EQ(surf.vertices.size(), 168 * 8); + ASSERT_EQ(surf.indices.size(), 224 * 3); + EXPECT_NEAR(surf.vertices[3 * 8 + 3], 1.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[3 * 8 + 4], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[3 * 8 + 5], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[4 * 8 + 3], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[4 * 8 + 4], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[4 * 8 + 5], 1.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[83 * 8 + 3], 1.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[83 * 8 + 4], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[83 * 8 + 5], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[88 * 8 + 3], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[88 * 8 + 4], 0.0f, kEpsilon); + EXPECT_NEAR(surf.vertices[88 * 8 + 5], -1.0f, kEpsilon); + EXPECT_TRUE(TestDiskTopology(16, 4, 0, surf.indices.begin())); + EXPECT_TRUE(TestDiskTopology(16, 4, 84, surf.indices.begin() + 112 * 3)); +} + +TEST(DeepmindGeometryTest, SphereLocators) { + Sphere sphere; + sphere.num_phi_segments = 4; + sphere.num_theta_segments = 4; + Model::LocatorMap locators = CreateLocators(sphere); + EXPECT_EQ(locators.size(), 27 * 2); + auto it = locators.find("back_top_left_p"); + ASSERT_NE(it, locators.end()); + const float nz = 1.0f / std::sqrt(3.0f); + EXPECT_NEAR(it->second(0, 2), -nz, kEpsilon); + EXPECT_NEAR(it->second(1, 2), -nz, kEpsilon); + EXPECT_NEAR(it->second(2, 2), nz, kEpsilon); + EXPECT_NEAR(it->second(0, 3), -0.5f * nz, kEpsilon); + EXPECT_NEAR(it->second(1, 3), -0.5f * nz, kEpsilon); + EXPECT_NEAR(it->second(2, 3), 0.5f * nz, kEpsilon); + it = locators.find("front_top_right_p"); + ASSERT_NE(it, locators.end()); + EXPECT_NEAR(it->second(0, 2), nz, kEpsilon); + EXPECT_NEAR(it->second(1, 2), nz, kEpsilon); + EXPECT_NEAR(it->second(2, 2), nz, kEpsilon); + EXPECT_NEAR(it->second(0, 3), 0.5f * nz, kEpsilon); + EXPECT_NEAR(it->second(1, 3), 0.5f * nz, kEpsilon); + EXPECT_NEAR(it->second(2, 3), 0.5f * nz, kEpsilon); + it = locators.find("centre_bottom_centre_s"); + ASSERT_NE(it, locators.end()); + EXPECT_NEAR(it->second(0, 2), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(1, 2), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(2, 2), 1.0f, kEpsilon); + EXPECT_NEAR(it->second(0, 3), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(1, 3), 0.0f, kEpsilon); + EXPECT_NEAR(it->second(2, 3), -0.5f, kEpsilon); +} + +TEST(DeepmindGeometryTest, ZAlignedFrame) { + Eigen::Matrix4f ref_data; + const float kRcpSqrt2 = 1.0f / std::sqrt(2.0f); + const float kRcpSqrt3 = 1.0f / std::sqrt(3.0f); + const float kRcpSqrt6 = 1.0f / std::sqrt(6.0f); + ref_data << kRcpSqrt2, -kRcpSqrt6, kRcpSqrt3, 1.0f, // + 0.0f, 2.0f * kRcpSqrt6, kRcpSqrt3, 2.0f, // + -kRcpSqrt2, -kRcpSqrt6, kRcpSqrt3, 3.0f, // + 0.0f, 0.0f, 0.0f, 1.0f; + auto tst_xfrm = CreateZAlignedFrame( + Eigen::Vector3f(1.0f, 2.0f, 3.0f), + Eigen::Vector3f(1.0f, 1.0f, 1.0f).normalized(), Eigen::Vector3f::UnitY()); + EXPECT_NEAR((ref_data - tst_xfrm.matrix()).norm(), 0.0f, kEpsilon); +} + +TEST(DeepmindGeometryTest, DefaultZDir) { + auto tst0_zdir = ComputeDefaultZDir(0.0f, 0.0f, 0.0f, 1.0f, 2.0f, 3.0f); + EXPECT_NEAR((tst0_zdir - Eigen::Vector3f::UnitZ()).norm(), 0.0f, kEpsilon); + auto tst1_zdir = ComputeDefaultZDir(1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 3.0f); + const float kRcpSqrt14 = 1.0f / std::sqrt(14.0f); // 1 / norm([1, 2, 3]) + Eigen::Vector3f ref1_zdir(kRcpSqrt14, 2.0f * kRcpSqrt14, 3.0f * kRcpSqrt14); + EXPECT_NEAR((tst1_zdir - ref1_zdir).norm(), 0.0f, kEpsilon); +} + +TEST(DeepmindGeometryTest, DefaultYVector) { + auto tst0_yvec = ComputeDefaultYVector(0.0f, 0.0f, 1.0f); + EXPECT_NEAR((tst0_yvec - Eigen::Vector3f::UnitY()).norm(), 0.0f, kEpsilon); + auto tst1_yvec = ComputeDefaultYVector(0.0f, 0.0f, -1.0f); + EXPECT_NEAR((tst1_yvec + Eigen::Vector3f::UnitY()).norm(), 0.0f, kEpsilon); + auto tst2_yvec = ComputeDefaultYVector(1.0f, 1.0f, 1.0f); + EXPECT_NEAR((tst2_yvec + Eigen::Vector3f::UnitZ()).norm(), 0.0f, kEpsilon); +} + +} // namespace +} // namespace geometry +} // namespace lab +} // namespace deepmind diff --git a/deepmind/model_generation/geometry_util.cc b/deepmind/model_generation/geometry_util.cc new file mode 100644 index 00000000..b65200a5 --- /dev/null +++ b/deepmind/model_generation/geometry_util.cc @@ -0,0 +1,198 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "deepmind/model_generation/geometry_util.h" + +#include "deepmind/support/logging.h" +#include "absl/strings/str_cat.h" + +namespace deepmind { +namespace lab { +namespace geometry { + +Eigen::Affine3f CreateZAlignedFrame( // + const Eigen::Vector3f& trans, // + const Eigen::Vector3f& z_dir, // + const Eigen::Vector3f& y_vector) { + Eigen::Vector3f z_axis = z_dir.normalized(); + Eigen::Vector3f x_axis = y_vector.cross(z_axis); + float cross_norm = x_axis.norm(); + CHECK_GT(cross_norm, kEpsilon) + << "z_dir " << z_dir << " is nearly parallel to y_vector " << y_vector; + x_axis /= cross_norm; + Eigen::Vector3f y_axis = z_axis.cross(x_axis); + Eigen::Matrix4f mat; + mat << x_axis, y_axis, z_axis, trans, 0.0f, 0.0f, 0.0f, 1.0f; + return Eigen::Affine3f(mat); +} + +void ComputeDiskMeshSize( // + std::size_t num_sectors, // + std::size_t num_tracks, // + std::size_t* num_vertices, // + std::size_t* num_triangles) { + *num_vertices = (num_tracks + 1) * (num_sectors + 1) - 1; + *num_triangles = (num_tracks * 2 - 1) * num_sectors; +} + +void BuildDiskMesh( // + std::size_t num_sectors, // + std::size_t num_tracks, // + std::size_t offset, // + const std::function(float, float)>& eval, // + std::vector* vertices, // + std::vector* indices) { + std::array vertex; + for (std::size_t i = 1; i <= num_tracks; ++i) { + float v = i / static_cast(num_tracks); + vertex = eval(0.0f, v); + vertices->insert(vertices->end(), vertex.begin(), vertex.end()); + ++offset; + } + for (std::size_t j = 1; j <= num_sectors; ++j) { + // Add apex vertex. + vertex = eval((j - 0.5f) / num_sectors, 0.0f); + vertices->insert(vertices->end(), vertex.begin(), vertex.end()); + ++offset; + // Add apical face. + indices->push_back(offset); + indices->push_back(offset - 1); + indices->push_back(offset - num_tracks - 1); + // Trace the face strip from the apex. + float u = j / static_cast(num_sectors); + for (std::size_t i = 1; i < num_tracks; ++i) { + float v = i / static_cast(num_tracks); + vertex = eval(u, v); + vertices->insert(vertices->end(), vertex.begin(), vertex.end()); + ++offset; + // Triangulate strip faces; + indices->push_back(offset); + indices->push_back(offset - 1); + indices->push_back(offset - num_tracks - 2); + indices->push_back(offset - num_tracks - 1); + indices->push_back(offset); + indices->push_back(offset - num_tracks - 2); + } + // Add rim vertex. + vertex = eval(u, 1.0f); + vertices->insert(vertices->end(), vertex.begin(), vertex.end()); + ++offset; + } +} + +void ComputeRectMeshSize( // + std::size_t num_rows, // + std::size_t num_cols, // + std::size_t* num_vertices, // + std::size_t* num_triangles) { + *num_vertices = (num_rows + 1) * (num_cols + 1); + *num_triangles = num_rows * num_cols * 2; +} + +void BuildRectMesh( // + std::size_t num_rows, // + std::size_t num_cols, // + std::size_t offset, // + const std::function(float, float)>& eval, // + std::vector* vertices, // + std::vector* indices) { + std::array vertex; + for (std::size_t i = 0; i <= num_rows; ++i) { + float v = i / static_cast(num_rows); + vertex = eval(0.0f, v); + vertices->insert(vertices->end(), vertex.begin(), vertex.end()); + ++offset; + } + for (std::size_t j = 1; j <= num_cols; ++j) { + float u = j / static_cast(num_cols); + for (std::size_t i = 0; i < num_rows; ++i) { + float v = i / static_cast(num_rows); + vertex = eval(u, v); + vertices->insert(vertices->end(), vertex.begin(), vertex.end()); + ++offset; + // Triangulate strip faces; + indices->push_back(offset - num_rows - 2); + indices->push_back(offset - 1); + indices->push_back(offset); + indices->push_back(offset - num_rows - 2); + indices->push_back(offset); + indices->push_back(offset - num_rows - 1); + } + vertex = eval(u, 1.0f); + vertices->insert(vertices->end(), vertex.begin(), vertex.end()); + ++offset; + } +} + +void BuildDefaultLocators( // + const std::function& eval_socket, // + const std::function& eval_plug, // + std::unordered_map* locators) { + static const char* const kHeightPrefix[] = {"bottom_", "centre_", "top_"}; + for (int k = 0; k < 3; ++k) { + const float w = k - 1.0f; + static const char* const kDepthPrefix[] = {"back_", "centre_", "front_"}; + for (int j = 0; j < 3; ++j) { + const float v = j - 1.0f; + static const char* const kWidthPrefix[] = {"left_", "centre_", "right_"}; + for (int i = 0; i < 3; ++i) { + const float u = i - 1.0f; + const auto kPrefix = + absl::StrCat(kDepthPrefix[j], kHeightPrefix[k], kWidthPrefix[i]); + const auto kSocketName = absl::StrCat(kPrefix, "s"); + (*locators)[kSocketName] = eval_socket(u, v, w); + const auto kPlugName = absl::StrCat(kPrefix, "p"); + (*locators)[kPlugName] = eval_plug(u, v, w); + } + } + } +} + +Eigen::Vector3f ComputeDefaultZDir( // + float u, float v, float w, // + float rx, float ry, float rz) { + // Compute z_dir as the normal vector of the ellipsoid inscribed in the + // primitive's bounding box, uniformly scaled so that it is tangent to the + // locator's position. + Eigen::Vector3f z_dir(u * rx, v * ry, w * rz); + float norm = z_dir.norm(); + if (norm > kEpsilon) { + return z_dir / norm; + } else { + return Eigen::Vector3f::UnitZ(); + } +} + +Eigen::Vector3f ComputeDefaultYVector( // + float u, float v, float w) { + // Deterministic choice of y_vector which ensures no-parallelism with the + // z_dir vector computed by the function above. + if (u * u + v * v <= kEpsilon) { + if (w >= 0.0f) { + return Eigen::Vector3f::UnitY(); + } else { + return -Eigen::Vector3f::UnitY(); + } + } else { + return -Eigen::Vector3f::UnitZ(); + } +} + +} // namespace geometry +} // namespace lab +} // namespace deepmind diff --git a/deepmind/model_generation/geometry_util.h b/deepmind/model_generation/geometry_util.h new file mode 100644 index 00000000..13c8093d --- /dev/null +++ b/deepmind/model_generation/geometry_util.h @@ -0,0 +1,130 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef DML_DEEPMIND_MODEL_GENERATION_GEOMETRY_UTIL_H_ +#define DML_DEEPMIND_MODEL_GENERATION_GEOMETRY_UTIL_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "Eigen/Geometry" +#include "deepmind/model_generation/transform.h" + +namespace deepmind { +namespace lab { +namespace geometry { + +// Shared constants. +constexpr float kEpsilon = 1.0e-6f; +constexpr float kPi = 3.14159265358979323846f; + +// Build a reference frame centered on position `trans` where the z axis is a +// unit vector in the direction of `z_dir`, the x axis is computed as the +// normalised cross product of `y_vector` and the z axis, and the y axis is +// computed as the cross product of the z axis and the x axis. +Eigen::Affine3f CreateZAlignedFrame( // + const Eigen::Vector3f& trans, // + const Eigen::Vector3f& z_dir, // + const Eigen::Vector3f& y_vector); + +// Computes the number of vertices and triangles required to discretise a disk +// with the given amount of sectors (angular segments) and tracks (radial +// segments). See https://en.wikipedia.org/wiki/Disk_sector for an illustration. +// Both `num_sectors` and `num_tracks` must be greater than 0. +void ComputeDiskMeshSize( // + std::size_t num_sectors, // + std::size_t num_tracks, // + std::size_t* num_vertices, // + std::size_t* num_triangles); + +// Samples the given evaluator (eval) at the vertices of a discretised disk and +// builds a polygonal mesh with the results. The sampled vertices and face +// indices are appended to the vector parameters of the same name. The `offset` +// value is added to the face indices. Both `num_sectors` and `num_tracks` must +// be greater than 0. +void BuildDiskMesh( // + std::size_t num_sectors, // + std::size_t num_tracks, // + std::size_t offset, // + const std::function(float, float)>& eval, // + std::vector* vertices, // + std::vector* indices); + +// Computes the number of vertices and triangles required to discretise a +// rectangular grid with the given amount of polygonal rows and columns. Both +// `num_rows` and `num_cols` must be greater than 0. +void ComputeRectMeshSize( // + std::size_t num_rows, // + std::size_t num_cols, // + std::size_t* num_vertices, // + std::size_t* num_triangles); + +// Samples the given evaluator (eval) at the vertices of a discretised rectangle +// and builds a polygonal mesh with the results. The sampled vertices and +// face indices are appended to the vector parameters of the same name. The +// `offset` value is added to the face indices. Both `num_rows` and `num_cols` +// must be greater than 0. +void BuildRectMesh( // + std::size_t num_rows, // + std::size_t num_cols, // + std::size_t offset, // + const std::function(float, float)>& eval, // + std::vector* vertices, // + std::vector* indices); + +// Build the default locator set. Default locators are evaluated using functions +// `eval_socket` and `eval_plug`. These functions are called at 27 positions +// evenly spaced within a normalised bounding box of the primitive, and the +// resulting locators are named according to the following regex: +// +// [back|centre|front]_[bottom|centre|top]_[left|centre|right]_[s|p] +// +// where the s and p suffixes stand for socket and plug, respectively. +// Socket and plug locators are meant to be placed the same position but have +// opposing orientations (sockets are inbound, plugs are outbound). +void BuildDefaultLocators( // + const std::function& eval_socket, // + const std::function& eval_plug, // + std::unordered_map* locators); + +// Computes the z_dir vector used to construct the default locator set for most +// primitives. Coefficients [u, v, w] are the normalised coordinates (range [-1, +// 1]) of the locator's position within an axis-aligned bounding box +// encompassing the primitive. Coefficients [rx, ry, rz] are the reciprocals of +// the extents of such bounding box along each axis. +Eigen::Vector3f ComputeDefaultZDir( // + float u, float v, float w, // + float rx, float ry, float rz); + +// Computes the y_vector used to construct the default locator set for most +// primitives. Coefficients [u, v, w] are the normalised coordinates (range [-1, +// 1]) of the locator's position within an axis-aligned bounding box +// encompassing the primitive. +Eigen::Vector3f ComputeDefaultYVector( // + float u, float v, float w); + +} // namespace geometry +} // namespace lab +} // namespace deepmind + +#endif // DML_DEEPMIND_MODEL_GENERATION_GEOMETRY_UTIL_H_ diff --git a/deepmind/model_generation/lua_model.cc b/deepmind/model_generation/lua_model.cc new file mode 100644 index 00000000..da99066b --- /dev/null +++ b/deepmind/model_generation/lua_model.cc @@ -0,0 +1,353 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "deepmind/model_generation/lua_model.h" + +#include +#include + +#include "deepmind/support/logging.h" +#include "absl/strings/str_cat.h" +#include "deepmind/include/deepmind_model_getters.h" +#include "deepmind/include/deepmind_model_setters.h" +#include "deepmind/lua/lua.h" +#include "deepmind/lua/push.h" +#include "deepmind/lua/read.h" +#include "deepmind/lua/table_ref.h" +#include "deepmind/model_generation/geometry_cone.h" +#include "deepmind/model_generation/geometry_cube.h" +#include "deepmind/model_generation/geometry_cylinder.h" +#include "deepmind/model_generation/geometry_sphere.h" +#include "deepmind/model_generation/geometry_util.h" +#include "deepmind/model_generation/model_getters.h" +#include "deepmind/model_generation/model_lua.h" +#include "deepmind/model_generation/model_setters.h" +#include "deepmind/model_generation/model_util.h" +#include "deepmind/model_generation/transform_lua.h" + +namespace deepmind { +namespace lab { + +const char* LuaModel::ClassName() { return "deepmind.lab.Model"; } + +void LuaModel::Register(lua_State* L) { + const Class::Reg methods[] = { + {"cone", &Class::Member<&LuaModel::CreateCone>}, + {"cube", &Class::Member<&LuaModel::CreateCube>}, + {"cylinder", &Class::Member<&LuaModel::CreateCylinder>}, + {"sphere", &Class::Member<&LuaModel::CreateSphere>}, + {"hierarchy", &Class::Member<&LuaModel::CreateHierarchy>}, + {"circularLayout", &Class::Member<&LuaModel::CreateCircularLayout>}, + {"linearLayout", &Class::Member<&LuaModel::CreateLinearLayout>}, + {"loadMD3", &Class::Member<&LuaModel::LoadMD3>}, + {"saveMD3", &Class::Member<&LuaModel::SaveMD3>}, + }; + Class::Register(L, methods); +} + +lua::NResultsOr LuaModel::CreateCone(lua_State* L) { + lua::TableRef table; + if (lua::Read(L, -1, &table)) { + geometry::Cone cone; + // Handle both regular and elliptic cones. + if (table.LookUp("radius", &cone.width_radius)) { + cone.depth_radius = cone.width_radius; + } else { + table.LookUp("widthRadius", &cone.width_radius); + table.LookUp("depthRadius", &cone.depth_radius); + } + table.LookUp("height", &cone.height); + table.LookUp("phiSegments", &cone.num_phi_segments); + table.LookUp("radiusSegments", &cone.num_radius_segments); + table.LookUp("heightSegments", &cone.num_height_segments); + table.LookUp("shaderName", &cone.shader_name); + Model model = {"cone", {CreateSurface(cone)}, CreateLocators(cone)}; + Push(L, model); + return 1; + } + return "[model.cone] Must call with argument table."; +} + +lua::NResultsOr LuaModel::CreateCube(lua_State* L) { + lua::TableRef table; + if (lua::Read(L, -1, &table)) { + geometry::Cube cube; + // Handle both cubes and arbitrary cuboids. + if (table.LookUp("size", &cube.width)) { + cube.depth = cube.height = cube.width; + } else { + table.LookUp("width", &cube.width); + table.LookUp("height", &cube.height); + table.LookUp("depth", &cube.depth); + } + if (table.LookUp("segments", &cube.num_width_segments)) { + cube.num_height_segments = cube.num_depth_segments = + cube.num_width_segments; + } else { + table.LookUp("widthSegments", &cube.num_width_segments); + table.LookUp("heightSegments", &cube.num_height_segments); + table.LookUp("depthSegments", &cube.num_depth_segments); + } + table.LookUp("shaderName", &cube.shader_name); + Model model = {"cube", {CreateSurface(cube)}, CreateLocators(cube)}; + Push(L, model); + return 1; + } + return "[model.cube] Must call with argument table."; +} + +lua::NResultsOr LuaModel::CreateCylinder(lua_State* L) { + lua::TableRef table; + if (lua::Read(L, -1, &table)) { + geometry::Cylinder cylinder; + // Handle both regular and elliptic cylinders. + if (table.LookUp("radius", &cylinder.width_radius)) { + cylinder.depth_radius = cylinder.width_radius; + } else { + table.LookUp("widthRadius", &cylinder.width_radius); + table.LookUp("depthRadius", &cylinder.depth_radius); + } + table.LookUp("height", &cylinder.height); + table.LookUp("phiSegments", &cylinder.num_phi_segments); + table.LookUp("radiusSegments", &cylinder.num_radius_segments); + table.LookUp("heightSegments", &cylinder.num_height_segments); + table.LookUp("shaderName", &cylinder.shader_name); + Model model = { + "cylinder", {CreateSurface(cylinder)}, CreateLocators(cylinder)}; + Push(L, model); + return 1; + } + return "[model.cylinder] Must call with argument table."; +} + +lua::NResultsOr LuaModel::CreateSphere(lua_State* L) { + lua::TableRef table; + if (lua::Read(L, -1, &table)) { + geometry::Sphere sphere; + // Handle both spheres and arbitrary ellipsoids. + if (table.LookUp("radius", &sphere.width_radius)) { + sphere.height_radius = sphere.depth_radius = sphere.width_radius; + } else { + table.LookUp("widthRadius", &sphere.width_radius); + table.LookUp("heightRadius", &sphere.height_radius); + table.LookUp("depthRadius", &sphere.depth_radius); + } + table.LookUp("phiSegments", &sphere.num_phi_segments); + table.LookUp("thetaSegments", &sphere.num_theta_segments); + table.LookUp("shaderName", &sphere.shader_name); + Model model = {"sphere", {CreateSurface(sphere)}, CreateLocators(sphere)}; + Push(L, model); + return 1; + } + return "[model.sphere] Must call with argument table."; +} + +namespace { + +// Flatten a hierarchy of models by depth-first recursion from a given node, +// transforming all traversed surfaces and moving them onto the output model. +bool RecurseHierarchy( // + const lua::TableRef& node, // + const std::string& prefix, // + Transform xfrm, // + Model* model, // + Eigen::AlignedBox3f* bbox, // + std::string* error) { + Model node_model; + if (!node.LookUp("model", &node_model)) { + *error = absl::StrCat("Failed to load model at ", prefix); + return false; + } + // Retrieve node transform, otherwise treat as identity. + Transform node_xfrm; + node.LookUpToStack("transform"); + if (node.LookUp("transform", &node_xfrm)) { + xfrm = xfrm * node_xfrm; + } + // Retrieve inbound locator, otherwise treat as identity. + std::string node_locator_name = "identity"; + if (node.LookUp("locator", &node_locator_name)) { + auto iit = node_model.locators.find(node_locator_name); + if (iit == node_model.locators.end()) { + *error = absl::StrCat("Failed to find ", node_locator_name, + " amongst model locators at ", prefix); + return false; + } + const Transform& node_locator_xfrm = iit->second; + xfrm = xfrm * node_locator_xfrm.inverse(); + } + // Transform the node surfaces and append them to the output model. + Eigen::Matrix3f xfrm_i = xfrm.linear().inverse(); + for (auto& surface : node_model.surfaces) { + TransformSurface(xfrm, xfrm_i, &surface, bbox); + surface.name = absl::StrCat(prefix, surface.name); + model->surfaces.emplace_back(std::move(surface)); + } + // Recurse over children. + lua::TableRef node_children; + if (node.LookUp("children", &node_children)) { + auto keys = node_children.Keys(); + for (std::size_t i = 0; i < keys.size(); ++i) { + const auto& locator_name = keys[i]; + // Compute child transform. + auto iit = node_model.locators.find(locator_name); + if (iit == node_model.locators.end()) { + *error = absl::StrCat("Failed to find ", locator_name, + " amongst model locators at ", prefix); + return false; + } + Transform child_xfrm = xfrm * iit->second; + // Construct the child prefix. + std::string child_prefix = absl::StrCat(prefix, "c", i, ":"); + // Tail recursion. + lua::TableRef child_node; + if (!node_children.LookUp(locator_name, &child_node)) { + *error = absl::StrCat("Failed to read child node connected to ", + locator_name, " at ", prefix); + return false; + } + if (!RecurseHierarchy(child_node, child_prefix, child_xfrm, model, bbox, + error)) { + return false; + } + } + } + return true; +} + +} // namespace + +lua::NResultsOr LuaModel::CreateHierarchy(lua_State* L) { + lua::TableRef table; + if (lua::Read(L, -1, &table)) { + Model model; + model.name = "hierarchy"; + Transform xfrm; + xfrm = Eigen::Matrix4f::Identity(); + Eigen::AlignedBox3f bbox; + std::string rec_error; + if (!RecurseHierarchy(table, "root_", xfrm, &model, &bbox, &rec_error)) { + std::string error = absl::StrCat("[model.hierarchy] ", rec_error); + return error; + } + // Construct locators + geometry::Cube cube; + cube.width = bbox.max()[0] - bbox.min()[0]; + cube.depth = bbox.max()[1] - bbox.min()[1]; + cube.height = bbox.max()[2] - bbox.min()[2]; + model.locators = CreateLocators(cube, (bbox.max() + bbox.min()) * 0.5f); + Push(L, model); + return 1; + } + return "[model.hierarchy] Must call with argument table."; +} + +lua::NResultsOr LuaModel::CreateCircularLayout(lua_State* L) { + float radius; + int num_samples; + if (lua::Read(L, -2, &radius) && lua::Read(L, -1, &num_samples)) { + if (num_samples < 1) { + std::string error = absl::StrCat( + "[model.circularLayout] number of samples must be greater than 0, " + "received: ", + lua::ToString(L, -1)); + return error; + } + Model::LocatorMap locators; + for (int i = 0; i < num_samples; ++i) { + const float phi = 2.0f * geometry::kPi * i / num_samples; + const Eigen::Vector3f y_dir(std::cos(phi), -std::sin(phi), 0.0f); + locators[absl::StrCat("layout_", i)] = geometry::CreateZAlignedFrame( + y_dir * radius, Eigen::Vector3f::UnitZ(), y_dir); + } + Model res = {"circular_layout", {}, std::move(locators)}; + Push(L, res); + return 1; + } + std::string error = absl::StrCat( + "[model.circularLayout] Must contain layout radius and number of " + "samples, received: ", + lua::ToString(L, -2), ", ", lua::ToString(L, -1)); + return error; +} + +lua::NResultsOr LuaModel::CreateLinearLayout(lua_State* L) { + float length; + int num_samples; + if (lua::Read(L, -2, &length) && lua::Read(L, -1, &num_samples)) { + if (num_samples < 1) { + std::string error = absl::StrCat( + "[model.linearLayout] number of samples must be greater than 0, " + "received: ", + lua::ToString(L, -1)); + return error; + } + Model::LocatorMap locators; + if (num_samples == 1) { + Transform xfrm; + xfrm = Eigen::Matrix4f::Identity(); + locators = {{"layout_0", xfrm}}; + } else { + for (int i = 0; i < num_samples; ++i) { + const float x = i / (num_samples - 1.0f) - 0.5f; + locators[absl::StrCat("layout_", i)] = + Eigen::Translation3f(x * length, 0.0f, 0.0f); + } + } + Model res = {"linear_layout", {}, std::move(locators)}; + Push(L, res); + return 1; + } + std::string error = absl::StrCat( + "[model.linearLayout] Must contain layout length and number of samples, " + "received: ", + lua::ToString(L, -2), ", ", lua::ToString(L, -1)); + return error; +} + +lua::NResultsOr LuaModel::LoadMD3(lua_State* L) { + std::string model_path; + if (lua::Read(L, -1, &model_path)) { + ModelSettersData model_data; + DeepmindModelSetters model_setters = ModelSetters(); + if (!calls_->load_model(model_path.c_str(), &model_setters, &model_data)) { + return "[model.loadMD3] Unable to open model file: " + model_path; + } + Push(L, model_data.model); + return 1; + } + return "[model.loadMD3] Must call with model path, received: " + + lua::ToString(L, -1); +} + +lua::NResultsOr LuaModel::SaveMD3(lua_State* L) { + Model model; + std::string model_path; + if (Read(L, -2, &model) && lua::Read(L, -1, &model_path)) { + DeepmindModelGetters model_getters = ModelGetters(); + if (!calls_->save_model(&model_getters, &model, model_path.c_str())) { + return "[model.saveMD3] Unable to save model file: " + model_path; + } + return 0; + } + return "[model.saveMD3] Must call with model and model_path, received: " + + lua::ToString(L, -2) + ", " + lua::ToString(L, -1); +} + +} // namespace lab +} // namespace deepmind diff --git a/deepmind/model_generation/lua_model.h b/deepmind/model_generation/lua_model.h new file mode 100644 index 00000000..02e8ba5e --- /dev/null +++ b/deepmind/model_generation/lua_model.h @@ -0,0 +1,57 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef DML_DEEPMIND_MODEL_GENERATION_LUA_MODEL_H_ +#define DML_DEEPMIND_MODEL_GENERATION_LUA_MODEL_H_ + +#include "deepmind/include/deepmind_calls.h" +#include "deepmind/lua/class.h" +#include "deepmind/lua/lua.h" + +namespace deepmind { +namespace lab { + +class LuaModel : public lua::Class { + friend class Class; + static const char* ClassName(); + + public: + // '*calls' owned by the caller and should out-live this object. + explicit LuaModel(const DeepmindCalls* calls) : calls_(calls) {} + + // Registers the class as well as member functions. + static void Register(lua_State* L); + + lua::NResultsOr CreateCone(lua_State* L); + lua::NResultsOr CreateCube(lua_State* L); + lua::NResultsOr CreateCylinder(lua_State* L); + lua::NResultsOr CreateSphere(lua_State* L); + lua::NResultsOr CreateHierarchy(lua_State* L); + lua::NResultsOr CreateCircularLayout(lua_State* L); + lua::NResultsOr CreateLinearLayout(lua_State* L); + lua::NResultsOr LoadMD3(lua_State* L); + lua::NResultsOr SaveMD3(lua_State* L); + + private: + const DeepmindCalls* calls_; +}; + +} // namespace lab +} // namespace deepmind + +#endif // DML_DEEPMIND_MODEL_GENERATION_LUA_MODEL_H_ diff --git a/deepmind/model_generation/lua_model_test.cc b/deepmind/model_generation/lua_model_test.cc new file mode 100644 index 00000000..6d2f4967 --- /dev/null +++ b/deepmind/model_generation/lua_model_test.cc @@ -0,0 +1,301 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "deepmind/model_generation/lua_model.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/strings/str_cat.h" +#include "deepmind/include/deepmind_context.h" +#include "deepmind/lua/bind.h" +#include "deepmind/lua/call.h" +#include "deepmind/lua/n_results_or_test_util.h" +#include "deepmind/lua/push_script.h" +#include "deepmind/lua/vm.h" +#include "deepmind/model_generation/geometry_util.h" +#include "deepmind/model_generation/model_lua.h" +#include "deepmind/support/test_srcdir.h" +#include "deepmind/tensor/lua_tensor.h" + +namespace deepmind { +namespace lab { +namespace { + +using geometry::kEpsilon; +using ::testing::ElementsAre; + +TEST(DeepmindModelLibTest, CreateCone) { + DeepmindContext ctx{}; + ASSERT_EQ(0, dmlab_create_context(TestSrcDir().c_str(), &ctx, nullptr, + nullptr)); + ASSERT_EQ(0, + ctx.hooks.set_script_name(ctx.userdata, "test_levels/model_test")); + ASSERT_EQ(0, ctx.hooks.init(ctx.userdata)); + + ASSERT_TRUE(ctx.hooks.find_model(ctx.userdata, "cone")); + DeepmindModelGetters model; + void* model_data; + ctx.hooks.model_getters(ctx.userdata, &model, &model_data); + + ASSERT_EQ(model.get_surface_count(model_data), 1); + + ASSERT_EQ(model.get_surface_vertex_count(model_data, 0), 168); + ASSERT_EQ(model.get_surface_face_count(model_data, 0), 224); + + int face_indices[3]; + model.get_surface_face(model_data, 0, 0, face_indices); + EXPECT_THAT(face_indices, ElementsAre(5, 4, 0)); + model.get_surface_face(model_data, 0, 112, face_indices); + EXPECT_THAT(face_indices, ElementsAre(89, 88, 84)); + + ctx.hooks.clear_model(ctx.userdata); + dmlab_release_context(&ctx); +} + +TEST(DeepmindModelLibTest, CreateCube) { + DeepmindContext ctx{}; + ASSERT_EQ(0, + dmlab_create_context(TestSrcDir().c_str(), &ctx, nullptr, nullptr)); + ASSERT_EQ(0, + ctx.hooks.set_script_name(ctx.userdata, "test_levels/model_test")); + ASSERT_EQ(0, ctx.hooks.init(ctx.userdata)); + + ASSERT_TRUE(ctx.hooks.find_model(ctx.userdata, "cube")); + DeepmindModelGetters model; + void* model_data; + ctx.hooks.model_getters(ctx.userdata, &model, &model_data); + + ASSERT_EQ(model.get_surface_count(model_data), 1); + + ASSERT_EQ(model.get_surface_vertex_count(model_data, 0), 150); + ASSERT_EQ(model.get_surface_face_count(model_data, 0), 192); + + int face_indices[3]; + model.get_surface_face(model_data, 0, 0, face_indices); + EXPECT_THAT(face_indices, ElementsAre(0, 5, 6)); + model.get_surface_face(model_data, 0, 1, face_indices); + EXPECT_THAT(face_indices, ElementsAre(0, 6, 1)); + + ctx.hooks.clear_model(ctx.userdata); + dmlab_release_context(&ctx); +} + +TEST(DeepmindModelLibTest, CreateCylinder) { + DeepmindContext ctx{}; + ASSERT_EQ(0, + dmlab_create_context(TestSrcDir().c_str(), &ctx, nullptr, nullptr)); + ASSERT_EQ(0, + ctx.hooks.set_script_name(ctx.userdata, "test_levels/model_test")); + ASSERT_EQ(0, ctx.hooks.init(ctx.userdata)); + + ASSERT_TRUE(ctx.hooks.find_model(ctx.userdata, "cylinder")); + DeepmindModelGetters model; + void* model_data; + ctx.hooks.model_getters(ctx.userdata, &model, &model_data); + + ASSERT_EQ(model.get_surface_count(model_data), 1); + + ASSERT_EQ(model.get_surface_vertex_count(model_data, 0), 253); + ASSERT_EQ(model.get_surface_face_count(model_data, 0), 352); + + ctx.hooks.clear_model(ctx.userdata); + dmlab_release_context(&ctx); +} + +TEST(DeepmindModelLibTest, CreateSphere) { + DeepmindContext ctx{}; + ASSERT_EQ(0, + dmlab_create_context(TestSrcDir().c_str(), &ctx, nullptr, nullptr)); + ASSERT_EQ(0, + ctx.hooks.set_script_name(ctx.userdata, "test_levels/model_test")); + ASSERT_EQ(0, ctx.hooks.init(ctx.userdata)); + + ASSERT_TRUE(ctx.hooks.find_model(ctx.userdata, "sphere")); + DeepmindModelGetters model; + void* model_data; + ctx.hooks.model_getters(ctx.userdata, &model, &model_data); + + ASSERT_EQ(model.get_surface_count(model_data), 1); + + ASSERT_EQ(model.get_surface_vertex_count(model_data, 0), 168); + ASSERT_EQ(model.get_surface_face_count(model_data, 0), 224); + + int face_indices[3]; + model.get_surface_face(model_data, 0, 0, face_indices); + EXPECT_THAT(face_indices, ElementsAre(5, 4, 0)); + model.get_surface_face(model_data, 0, 112, face_indices); + EXPECT_THAT(face_indices, ElementsAre(89, 88, 84)); + + ctx.hooks.clear_model(ctx.userdata); + dmlab_release_context(&ctx); +} + +TEST(DeepmindModelLibTest, CreateHierarchy) { + DeepmindContext ctx{}; + ASSERT_EQ(0, + dmlab_create_context(TestSrcDir().c_str(), &ctx, nullptr, nullptr)); + ASSERT_EQ(0, + ctx.hooks.set_script_name(ctx.userdata, "test_levels/model_test")); + ASSERT_EQ(0, ctx.hooks.init(ctx.userdata)); + + ASSERT_TRUE(ctx.hooks.find_model(ctx.userdata, "hierarchy")); + DeepmindModelGetters model; + void* model_data; + ctx.hooks.model_getters(ctx.userdata, &model, &model_data); + + constexpr int kSurfaceCount = 4; + ASSERT_EQ(model.get_surface_count(model_data), kSurfaceCount); + + constexpr std::array kSurfaceVertCounts = { + {168, 100, 66, 66}}; + constexpr std::array kSurfaceTriCounts = { + {224, 64, 32, 32}}; + for (size_t i = 0; i < kSurfaceCount; ++i) { + ASSERT_EQ(model.get_surface_vertex_count(model_data, i), + kSurfaceVertCounts[i]); + ASSERT_EQ(model.get_surface_face_count(model_data, i), + kSurfaceTriCounts[i]); + } + + ctx.hooks.clear_model(ctx.userdata); + dmlab_release_context(&ctx); +} + +lua::NResultsOr ModelModule(lua_State* L) { + if (auto* ctx = static_cast( + lua_touserdata(L, lua_upvalueindex(1)))) { + LuaModel::CreateObject(L, ctx); + return 1; + } else { + return "Missing context!"; + } +} + +constexpr char kLuaModelCircularLayout[] = R"( +local model = require 'dmlab.system.model' +return model:circularLayout(10, 4) +)"; + +TEST(DeepmindModelLibTest, LayoutCircular) { + DeepmindContext ctx{}; + ASSERT_EQ(0, + dmlab_create_context(TestSrcDir().c_str(), &ctx, nullptr, nullptr)); + + auto lua_vm = lua::CreateVm(); + lua_vm.AddPathToSearchers(TestSrcDir()); + lua_vm.AddCModuleToSearchers("dmlab.system.tensor", + tensor::LuaTensorConstructors); + lua_vm.AddCModuleToSearchers("dmlab.system.model", &lua::Bind, + {const_cast(&ctx.calls)}); + auto* L = lua_vm.get(); + deepmind::lab::tensor::LuaTensorRegister(L); + deepmind::lab::LuaModel::Register(L); + + ASSERT_THAT( + lua::PushScript(L, kLuaModelCircularLayout, "kLuaModelCircularLayout"), + lua::testing::IsOkAndHolds(1)) + << "Missing script"; + ASSERT_THAT(lua::Call(L, 0), lua::testing::IsOkAndHolds(1)) + << "Missing result"; + + Model test_model; + ASSERT_TRUE(Read(L, -1, &test_model)); + const auto& test_locators = test_model.locators; + ASSERT_EQ(test_locators.size(), 4); + const Eigen::Vector3f kRefPos[4] = {{10.0f, 0.0f, 0.0f}, + {0.0f, -10.0f, 0.0f}, + {-10.0f, 0.0f, 0.0f}, + {0.0f, 10.0f, 0.0f}}; + for (int i = 0; i < 4; ++i) { + const auto locator_name = absl::StrCat("layout_", i); + const auto lit = test_locators.find(locator_name); + ASSERT_NE(lit, test_locators.end()); + const auto& test_xfrm = lit->second; + const auto& ref_pos = kRefPos[i]; + const auto test_pos = test_xfrm.matrix().col(3); + const auto ref_y_dir = ref_pos.normalized(); + const auto test_y_dir = test_xfrm.matrix().col(1); + const auto test_z_dir = test_xfrm.matrix().col(2); + const auto ref_z_dir = Eigen::Vector3f::UnitZ(); + for (int j = 0; j < 3; ++j) { + EXPECT_NEAR(test_pos[j], ref_pos[j], kEpsilon); + EXPECT_NEAR(test_y_dir[j], ref_y_dir[j], kEpsilon); + EXPECT_NEAR(test_z_dir[j], ref_z_dir[j], kEpsilon); + } + }; + + dmlab_release_context(&ctx); +} + +constexpr char kLuaModelLinearLayout[] = R"( +local model = require 'dmlab.system.model' +return model:linearLayout(10, 5) +)"; + +TEST(DeepmindModelLibTest, LayoutLinear) { + DeepmindContext ctx{}; + ASSERT_EQ(0, + dmlab_create_context(TestSrcDir().c_str(), &ctx, nullptr, nullptr)); + + auto lua_vm = lua::CreateVm(); + lua_vm.AddPathToSearchers(TestSrcDir()); + lua_vm.AddCModuleToSearchers("dmlab.system.tensor", + tensor::LuaTensorConstructors); + lua_vm.AddCModuleToSearchers("dmlab.system.model", &lua::Bind, + {const_cast(&ctx.calls)}); + auto* L = lua_vm.get(); + deepmind::lab::tensor::LuaTensorRegister(L); + deepmind::lab::LuaModel::Register(L); + + ASSERT_THAT( + lua::PushScript(L, kLuaModelLinearLayout, "kLuaModelLinearLayout"), + lua::testing::IsOkAndHolds(1)) + << "Missing script"; + ASSERT_THAT(lua::Call(L, 0), lua::testing::IsOkAndHolds(1)) + << "Missing result"; + + Model test_model; + ASSERT_TRUE(Read(L, -1, &test_model)); + const auto& test_locators = test_model.locators; + ASSERT_EQ(test_locators.size(), 5); + const Eigen::Vector3f kRefPos[5] = {{-5.0f, 0.0f, 0.0f}, + {-2.5f, 0.0f, 0.0f}, + {0.0f, 0.0f, 0.0f}, + {2.5f, 0.0f, 0.0f}, + {5.0f, 0.0f, 0.0f}}; + const Eigen::Matrix3f kRefFrame = Eigen::Matrix3f::Identity(); + for (int i = 0; i < 5; ++i) { + const auto locator_name = absl::StrCat("layout_", i); + const auto lit = test_locators.find(locator_name); + ASSERT_NE(lit, test_locators.end()); + const auto& test_xfrm = lit->second; + const auto& ref_pos = kRefPos[i]; + const auto test_pos = test_xfrm.matrix().col(3); + for (int j = 0; j < 3; ++j) { + EXPECT_NEAR(test_pos[j], ref_pos[j], kEpsilon); + } + const auto test_frame = test_xfrm.linear(); + EXPECT_NEAR((test_frame - kRefFrame).norm(), 0.0f, kEpsilon); + } + + dmlab_release_context(&ctx); +} + +} // namespace +} // namespace lab +} // namespace deepmind diff --git a/deepmind/model_generation/lua_transform.cc b/deepmind/model_generation/lua_transform.cc new file mode 100644 index 00000000..4aafd2fd --- /dev/null +++ b/deepmind/model_generation/lua_transform.cc @@ -0,0 +1,85 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "deepmind/model_generation/lua_transform.h" + +#include "deepmind/lua/bind.h" +#include "deepmind/lua/push.h" +#include "deepmind/lua/read.h" +#include "deepmind/lua/table_ref.h" +#include "deepmind/model_generation/geometry_util.h" +#include "deepmind/model_generation/transform_lua.h" + +namespace deepmind { +namespace lab { + +int LuaTransform::Require(lua_State* L) { + auto table = lua::TableRef::Create(L); + table.Insert("translate", &lua::Bind); + table.Insert("rotate", &lua::Bind); + table.Insert("scale", &lua::Bind); + lua::Push(L, table); + return 1; +} + +lua::NResultsOr LuaTransform::CreateTranslation(lua_State* L) { + std::array t; + if (lua::Read(L, -1, &t)) { + Transform xfrm; + xfrm = Eigen::Translation3f(t[0], t[1], t[2]); + Push(L, xfrm); + return 1; + } + return "[transform.translate] Must call with offset vector, " + "recieved: " + + lua::ToString(L, -1); +} + +using geometry::kPi; + +lua::NResultsOr LuaTransform::CreateRotation(lua_State* L) { + float angle; + std::array a; + if (lua::Read(L, -2, &angle), lua::Read(L, -1, &a)) { + Transform xfrm; + xfrm = Eigen::AngleAxis(angle * kPi / 180.0f, // Convert to radians. + Eigen::Vector3f(a[0], a[1], a[2])); + Push(L, xfrm); + return 1; + } + return "[transform.translate] Must call with angle (in degrees) and rotation " + "axis, " + "recieved: " + + lua::ToString(L, -2) + ", " + lua::ToString(L, -1); +} + +lua::NResultsOr LuaTransform::CreateScaling(lua_State* L) { + std::array s; + if (lua::Read(L, -1, &s)) { + Transform xfrm; + xfrm = Eigen::Scaling(s[0], s[1], s[2]); + Push(L, xfrm); + return 1; + } + return "[transform.rotate] Must call with scaling factor vector, " + "recieved: " + + lua::ToString(L, -1); +} + +} // namespace lab +} // namespace deepmind diff --git a/deepmind/model_generation/lua_transform.h b/deepmind/model_generation/lua_transform.h new file mode 100644 index 00000000..ec2b0499 --- /dev/null +++ b/deepmind/model_generation/lua_transform.h @@ -0,0 +1,42 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef DML_DEEPMIND_MODEL_GENERATION_LUA_TRANSFORM_H_ +#define DML_DEEPMIND_MODEL_GENERATION_LUA_TRANSFORM_H_ + +#include "deepmind/lua/lua.h" +#include "deepmind/lua/n_results_or.h" + +namespace deepmind { +namespace lab { + +class LuaTransform { + public: + // Returns table of constructors and standalone functions. + // [0 1 -] + static int Require(lua_State* L); + + static lua::NResultsOr CreateTranslation(lua_State* L); + static lua::NResultsOr CreateRotation(lua_State* L); + static lua::NResultsOr CreateScaling(lua_State* L); +}; + +} // namespace lab +} // namespace deepmind + +#endif // DML_DEEPMIND_MODEL_GENERATION_LUA_TRANSFORM_H_ diff --git a/deepmind/model_generation/lua_transform_test.cc b/deepmind/model_generation/lua_transform_test.cc new file mode 100644 index 00000000..82ec725a --- /dev/null +++ b/deepmind/model_generation/lua_transform_test.cc @@ -0,0 +1,123 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "deepmind/model_generation/lua_transform.h" + +#include "gtest/gtest.h" +#include "deepmind/lua/bind.h" +#include "deepmind/lua/call.h" +#include "deepmind/lua/n_results_or_test_util.h" +#include "deepmind/lua/push_script.h" +#include "deepmind/lua/vm.h" +#include "deepmind/model_generation/geometry_util.h" +#include "deepmind/model_generation/transform_lua.h" +#include "deepmind/support/test_srcdir.h" +#include "deepmind/tensor/lua_tensor.h" + +namespace deepmind { +namespace lab { +namespace { + +using ::deepmind::lab::TestSrcDir; + +constexpr char kTransformTranslate[] = R"( +local transform = require 'dmlab.system.transform' +return transform.translate{1.0, 2.0, 3.0} +)"; + +TEST(DeepmindTransformTest, Translate) { + auto lua_vm = lua::CreateVm(); + lua_vm.AddPathToSearchers(TestSrcDir()); + lua_vm.AddCModuleToSearchers("dmlab.system.tensor", + tensor::LuaTensorConstructors); + lua_vm.AddCModuleToSearchers("dmlab.system.transform", LuaTransform::Require); + auto* L = lua_vm.get(); + tensor::LuaTensorRegister(L); + ASSERT_THAT(lua::PushScript(L, kTransformTranslate, "kTransformTranslate"), + lua::testing::IsOkAndHolds(1)) + << "Missing script"; + ASSERT_THAT(lua::Call(L, 0), lua::testing::IsOkAndHolds(1)) + << "Missing result"; + Eigen::Matrix4f ref_data; + ref_data << 1.0f, 0.0, 0.0, 1.0f, // + 0.0f, 1.0f, 0.0f, 2.0f, // + 0.0f, 0.0f, 1.0f, 3.0f, // + 0.0f, 0.0f, 0.0f, 1.0f; + Transform tst_xfrm; + EXPECT_TRUE(Read(L, -1, &tst_xfrm)); + EXPECT_NEAR((tst_xfrm.matrix() - ref_data).norm(), 0.0f, geometry::kEpsilon); +} + +constexpr char kTransformRotate[] = R"( +local transform = require 'dmlab.system.transform' +return transform.rotate(30.0, {0.57735026919, 0.57735026919, 0.57735026919}) +)"; + +TEST(DeepmindTransformTest, Rotate) { + auto lua_vm = lua::CreateVm(); + lua_vm.AddPathToSearchers(TestSrcDir()); + lua_vm.AddCModuleToSearchers("dmlab.system.tensor", + tensor::LuaTensorConstructors); + lua_vm.AddCModuleToSearchers("dmlab.system.transform", LuaTransform::Require); + auto* L = lua_vm.get(); + tensor::LuaTensorRegister(L); + ASSERT_THAT(lua::PushScript(L, kTransformRotate, "kTransformRotate"), + lua::testing::IsOkAndHolds(1)) + << "Missing script"; + ASSERT_THAT(lua::Call(L, 0), lua::testing::IsOkAndHolds(1)) + << "Missing result"; + Eigen::Matrix4f ref_data; + ref_data << 0.910684f, -0.244017f, 0.333333f, 0.0f, // + 0.333333f, 0.910684f, -0.244017f, 0.0f, // + -0.244017f, 0.333333f, 0.910684f, 0.0f, // + 0.0f, 0.0f, 0.0f, 1.0f; + Transform tst_xfrm; + EXPECT_TRUE(Read(L, -1, &tst_xfrm)); + EXPECT_NEAR((tst_xfrm.matrix() - ref_data).norm(), 0.0f, geometry::kEpsilon); +} +constexpr char kTransformScale[] = R"( +local transform = require 'dmlab.system.transform' +return transform.scale{1.0, 2.0, 3.0} +)"; + +TEST(DeepmindTransformTest, Scale) { + auto lua_vm = lua::CreateVm(); + lua_vm.AddPathToSearchers(TestSrcDir()); + lua_vm.AddCModuleToSearchers("dmlab.system.tensor", + tensor::LuaTensorConstructors); + lua_vm.AddCModuleToSearchers("dmlab.system.transform", LuaTransform::Require); + auto* L = lua_vm.get(); + tensor::LuaTensorRegister(L); + ASSERT_THAT(lua::PushScript(L, kTransformScale, "kTransformScale"), + lua::testing::IsOkAndHolds(1)) + << "Missing script"; + ASSERT_THAT(lua::Call(L, 0), lua::testing::IsOkAndHolds(1)) + << "Missing result"; + Eigen::Matrix4f ref_data; + ref_data << 1.0f, 0.0, 0.0, 0.0f, // + 0.0f, 2.0f, 0.0f, 0.0f, // + 0.0f, 0.0f, 3.0f, 0.0f, // + 0.0f, 0.0f, 0.0f, 1.0f; + Transform tst_xfrm; + EXPECT_TRUE(Read(L, -1, &tst_xfrm)); + EXPECT_NEAR((tst_xfrm.matrix() - ref_data).norm(), 0.0f, geometry::kEpsilon); +} + +} // namespace +} // namespace lab +} // namespace deepmind diff --git a/deepmind/model_generation/model.h b/deepmind/model_generation/model.h new file mode 100644 index 00000000..0fc28315 --- /dev/null +++ b/deepmind/model_generation/model.h @@ -0,0 +1,61 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef DML_DEEPMIND_MODEL_GENERATION_MODEL_H_ +#define DML_DEEPMIND_MODEL_GENERATION_MODEL_H_ + +#include +#include +#include + +#include "deepmind/model_generation/transform.h" + +namespace deepmind { +namespace lab { + +// Encapsulates data retrieved from custom models, either defined by the level +// API, or created for the primitives implemented in the model library. +struct Model { + // Model name. + std::string name; + + // Custom models are composed of polygonal surfaces, rendered with a given + // shader. + struct Surface { + std::string name; + // Vertex data, encoded as [pos_x, pos_y, pos_z, nrm_x, nrm_y, nrm_z, tex_s, + // tex_t] + std::vector vertices; + // Triples of vertex indices representing each triangular face. + std::vector indices; + std::string shader_name; + }; + + std::vector surfaces; + + // Custom models also include named reference frames which serve as locators + // where to attach child models. + using LocatorMap = std::unordered_map; + + LocatorMap locators; +}; + +} // namespace lab +} // namespace deepmind + +#endif // DML_DEEPMIND_MODEL_GENERATION_MODEL_H_ diff --git a/deepmind/model_generation/model_getters.cc b/deepmind/model_generation/model_getters.cc new file mode 100644 index 00000000..e054ae5d --- /dev/null +++ b/deepmind/model_generation/model_getters.cc @@ -0,0 +1,193 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "deepmind/model_generation/model_getters.h" + +#include + +#include "deepmind/support/logging.h" +#include "deepmind/model_generation/model.h" + +namespace deepmind { +namespace lab { +namespace { + +const Model* CastModel(const void* model_data) { + CHECK(model_data != nullptr); + return static_cast(model_data); +} + +void GetName(const void* model_data, std::size_t max_length, char* name) { + name[CastModel(model_data)->name.copy(name, max_length - 1)] = '\0'; +} + +std::size_t GetSurfaceCount(const void* model_data) { + return CastModel(model_data)->surfaces.size(); +} + +void GetSurfaceName(const void* model_data, std::size_t surf_idx, + std::size_t max_length, char* name) { + const Model& model = *CastModel(model_data); + CHECK_LT(surf_idx, model.surfaces.size()) << "Incorrect surface index."; + CHECK_GT(max_length, 0) << "name buffer must have positive length."; + name[model.surfaces[surf_idx].name.copy(name, max_length - 1)] = '\0'; +} + +std::size_t GetSurfaceVertexCount(const void* model_data, + std::size_t surf_idx) { + const Model& model = *CastModel(model_data); + CHECK_LT(surf_idx, model.surfaces.size()) << "Incorrect surface index."; + return model.surfaces[surf_idx].vertices.size() / 8; +} + +void GetSurfaceVertexLocation(const void* model_data, std::size_t surf_idx, + std::size_t vert_idx, float location[3]) { + const Model& model = *CastModel(model_data); + CHECK_LT(surf_idx, model.surfaces.size()) << "Incorrect surface index."; + const Model::Surface& surf = model.surfaces[surf_idx]; + CHECK_LT(vert_idx, surf.vertices.size() / 8) << "Incorrect vertex index."; + const float* vertex = &surf.vertices[8 * vert_idx]; + for (int i = 0; i < 3; ++i) { + location[i] = vertex[i]; + } +} + +void GetSurfaceVertexNormal(const void* model_data, std::size_t surf_idx, + std::size_t vert_idx, float normal[3]) { + const Model& model = *CastModel(model_data); + CHECK_LT(surf_idx, model.surfaces.size()) << "Incorrect surface index."; + const Model::Surface& surf = model.surfaces[surf_idx]; + CHECK_LT(vert_idx, surf.vertices.size() / 8) << "Incorrect vertex index."; + const float* vertex = &surf.vertices[8 * vert_idx]; + for (int i = 0; i < 3; ++i) { + normal[i] = vertex[3 + i]; + } +} + +void GetSurfaceVertexST(const void* model_data, std::size_t surf_idx, + std::size_t vert_idx, float st[2]) { + const Model& model = *CastModel(model_data); + CHECK_LT(surf_idx, model.surfaces.size()) << "Incorrect surface index."; + const Model::Surface& surf = model.surfaces[surf_idx]; + CHECK_LT(vert_idx, surf.vertices.size() / 8) << "Incorrect vertex index."; + const float* vertex = &surf.vertices[8 * vert_idx]; + for (int i = 0; i < 2; ++i) { + st[i] = vertex[6 + i]; + } +} + +std::size_t GetSurfaceFaceCount(const void* model_data, std::size_t surf_idx) { + const Model& model = *CastModel(model_data); + CHECK_LT(surf_idx, model.surfaces.size()) << "Incorrect surface index."; + return model.surfaces[surf_idx].indices.size() / 3; +} + +void GetSurfaceFace(const void* model_data, std::size_t surf_idx, + std::size_t face_idx, int indices[3]) { + const Model& model = *CastModel(model_data); + CHECK_LT(surf_idx, model.surfaces.size()) << "Incorrect surface index."; + const Model::Surface& surf = model.surfaces[surf_idx]; + CHECK_LT(face_idx, surf.indices.size() / 3) << "Incorrect face index."; + const int* face = &surf.indices[3 * face_idx]; + for (int i = 0; i < 3; ++i) { + indices[i] = face[i]; + } +} + +std::size_t GetSurfaceShaderCount(const void* model_data, + std::size_t surf_idx) { + return 1; +} + +void GetSurfaceShader(const void* model_data, std::size_t surf_idx, + std::size_t shad_idx, std::size_t max_length, + char* name) { + const Model& model = *CastModel(model_data); + CHECK_LT(surf_idx, model.surfaces.size()) << "Incorrect surface index."; + CHECK_GT(max_length, 0) << "name buffer must have positive length."; + name[model.surfaces[surf_idx].shader_name.copy(name, max_length - 1)] = '\0'; +} + +std::size_t GetTagCount(const void* model_data) { + return CastModel(model_data)->locators.size(); +} + +void GetTagName(const void* model_data, std::size_t tag_idx, + std::size_t max_length, char* name) { + const Model& model = *CastModel(model_data); + CHECK_LT(tag_idx, model.locators.size()) << "Incorrect tag index."; + Model::LocatorMap::const_iterator it = model.locators.cbegin(); + for (std::size_t i = 0; i < tag_idx; ++i) { + ++it; + } + CHECK_GT(max_length, 0) << "name buffer must have positive length."; + name[it->first.copy(name, max_length - 1)] = '\0'; +} + +void GetTagAxis(const void* model_data, std::size_t tag_idx, + std::size_t axis_idx, float axis[3]) { + const Model& model = *CastModel(model_data); + CHECK_LT(tag_idx, model.locators.size()) << "Incorrect tag index."; + Model::LocatorMap::const_iterator it = model.locators.cbegin(); + for (std::size_t i = 0; i < tag_idx; ++i) { + ++it; + } + const Transform& xfrm = it->second; + for (int i = 0; i < 3; ++i) { + axis[i] = xfrm(i, axis_idx); + } +} + +void GetTagOrigin(const void* model_data, std::size_t tag_idx, + float origin[3]) { + const Model& model = *CastModel(model_data); + CHECK_LT(tag_idx, model.locators.size()) << "Incorrect tag index."; + Model::LocatorMap::const_iterator it = model.locators.cbegin(); + for (std::size_t i = 0; i < tag_idx; ++i) { + ++it; + } + const Transform& xfrm = it->second; + for (int i = 0; i < 3; ++i) { + origin[i] = xfrm(i, 3); + } +} + +} // namespace + +DeepmindModelGetters ModelGetters() { + return { + GetName, // + GetSurfaceCount, // + GetSurfaceName, // + GetSurfaceVertexCount, // + GetSurfaceVertexLocation, // + GetSurfaceVertexNormal, // + GetSurfaceVertexST, // + GetSurfaceFaceCount, // + GetSurfaceFace, // + GetSurfaceShaderCount, // + GetSurfaceShader, // + GetTagCount, // + GetTagName, // + GetTagAxis, // + GetTagOrigin // + }; +} + +} // namespace lab +} // namespace deepmind diff --git a/deepmind/support/string_view_utils.h b/deepmind/model_generation/model_getters.h similarity index 61% rename from deepmind/support/string_view_utils.h rename to deepmind/model_generation/model_getters.h index 0e4405e0..c729f801 100644 --- a/deepmind/support/string_view_utils.h +++ b/deepmind/model_generation/model_getters.h @@ -1,4 +1,4 @@ -// Copyright (C) 2016 Google Inc. +// Copyright (C) 2017 Google Inc. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -16,24 +16,19 @@ // //////////////////////////////////////////////////////////////////////////////// -#ifndef DEEPMIND_SUPPORT_STRING_VIEW_UTILS_H -#define DEEPMIND_SUPPORT_STRING_VIEW_UTILS_H +#ifndef DML_DEEPMIND_MODEL_GENERATION_MODEL_ACCESSORS_H_ +#define DML_DEEPMIND_MODEL_GENERATION_MODEL_ACCESSORS_H_ -#include +#include "deepmind/include/deepmind_model_getters.h" -#include "deepmind/support/string_view.h" +namespace deepmind { +namespace lab { -namespace strings { +// Returns the accessor API for custom models. This is used to query model data +// from the ioq3 engine. +DeepmindModelGetters ModelGetters(); -inline StringPiece::size_type RemoveLeadingWhitespace(StringPiece* text) { - StringPiece::size_type n = 0; - for (auto* p = text->begin(); p != text->end() && std::isspace(*p); ++p) { - ++n; - } - text->remove_prefix(n); - return n; -} +} // namespace lab +} // namespace deepmind -} // namespace strings - -#endif // DEEPMIND_SUPPORT_STRING_VIEW_UTILS_H +#endif // DML_DEEPMIND_MODEL_GENERATION_MODEL_ACCESSORS_H_ diff --git a/deepmind/model_generation/model_lua.cc b/deepmind/model_generation/model_lua.cc new file mode 100644 index 00000000..66ca9aef --- /dev/null +++ b/deepmind/model_generation/model_lua.cc @@ -0,0 +1,138 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "deepmind/model_generation/model_lua.h" + +#include + +#include "deepmind/support/logging.h" +#include "deepmind/lua/push.h" +#include "deepmind/lua/read.h" +#include "deepmind/lua/table_ref.h" +#include "deepmind/model_generation/transform_lua.h" +#include "deepmind/tensor/lua_tensor.h" + +namespace deepmind { +namespace lab { + +void Push(lua_State* L, const Model& model) { + lua::TableRef res = lua::TableRef::Create(L); + res.Insert("name", model.name); + lua::TableRef surfaces = res.CreateSubTable("surfaces"); + for (const Model::Surface& surf : model.surfaces) { + // Save each surface. + lua::TableRef surface = surfaces.CreateSubTable(surf.name); + surface.Insert("name", surf.name); + tensor::ShapeVector vertices_shape = {surf.vertices.size() / 8, 8}; + tensor::LuaTensor::CreateObject(L, std::move(vertices_shape), + surf.vertices); + surface.InsertFromStackTop("vertices"); + tensor::ShapeVector indices_shape = {surf.indices.size() / 3, 3}; + auto* indices = tensor::LuaTensor::CreateObject( + L, std::move(indices_shape), surf.indices); + // Translate C++ indices (base 0) to Lua (base 1). + indices->mutable_tensor_view()->ForEachMutable([](int* val) { ++*val; }); + surface.InsertFromStackTop("indices"); + surface.Insert("shaderName", surf.shader_name); + } + res.Insert("locators", model.locators); + lua::Push(L, res); +} + +bool Read(lua_State* L, int idx, Model* model) { + lua::TableRef table; + if (!lua::Read(L, idx, &table)) { + LOG(ERROR) << "Failed to read model table"; + return false; + } + table.LookUp("name", &model->name); + lua::TableRef surfaces; + table.LookUp("surfaces", &surfaces); + for (const auto& surface_name : surfaces.Keys()) { + // Read each surface. + lua::TableRef surface; + if (!surfaces.LookUp(surface_name, &surface)) { + LOG(ERROR) << "Failed to read surface table"; + return false; + } + Model::Surface model_surface; + model_surface.name = surface_name; + if (!surface.LookUp("shaderName", &model_surface.shader_name)) { + LOG(ERROR) << "Bad or missing arg 'surfaces.shader_name'"; + return false; + } + // Read vertices. + surface.LookUpToStack("vertices"); + auto* vertices = tensor::LuaTensor::ReadObject(L, -1); + if (vertices == nullptr) { + LOG(ERROR) << "Bad or missing arg 'surfaces.vertices'"; + lua_pop(L, 1); + return false; + } + if (vertices->tensor_view().shape().size() != 2 || + vertices->tensor_view().shape()[1] != 8) { + LOG(ERROR) << "Incorrect dimensions for arg 'surfaces.vertices'"; + lua_pop(L, 1); + return false; + } + model_surface.vertices.reserve(vertices->tensor_view().shape()[0] * 8); + auto& surface_vertices = model_surface.vertices; + vertices->tensor_view().ForEach( + [&surface_vertices](float val) { surface_vertices.push_back(val); }); + lua_pop(L, 1); + // Read indices. + surface.LookUpToStack("indices"); + auto* indices = tensor::LuaTensor::ReadObject(L, -1); + if (indices == nullptr) { + LOG(ERROR) << "Bad or missing arg 'surfaces.indices'"; + lua_pop(L, 1); + return false; + } + if (indices->tensor_view().shape().size() != 2 || + indices->tensor_view().shape()[1] != 3) { + LOG(ERROR) << "Incorrect dimensions for arg 'surfaces.indices'"; + lua_pop(L, 1); + return false; + } + model_surface.indices.reserve(indices->tensor_view().shape()[0] * 3); + auto& surface_indices = model_surface.indices; + int min_idx = std::numeric_limits::max(); + int max_idx = std::numeric_limits::min(); + indices->tensor_view().ForEach( + [&surface_indices, &min_idx, &max_idx](int val) { + min_idx = std::min(min_idx, val); + max_idx = std::max(max_idx, val); + // Translate Lua indices (base 1) to C++ indices (base 0). + surface_indices.push_back(val - 1); + }); + if (min_idx < 1 || max_idx > model_surface.vertices.size() / 8) { + LOG(ERROR) << "Found vertex index in 'surfaces.indices' outside the " + "expected range [1, " + << model_surface.vertices.size() << "]"; + lua_pop(L, 1); + return false; + } + model->surfaces.emplace_back(std::move(model_surface)); + lua_pop(L, 1); + } + table.LookUp("locators", &model->locators); + return true; +} + +} // namespace lab +} // namespace deepmind diff --git a/deepmind/model_generation/model_lua.h b/deepmind/model_generation/model_lua.h new file mode 100644 index 00000000..794f9710 --- /dev/null +++ b/deepmind/model_generation/model_lua.h @@ -0,0 +1,40 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef DML_DEEPMIND_MODEL_GENERATION_GEOMETRY_MODEL_LUA_H_ +#define DML_DEEPMIND_MODEL_GENERATION_GEOMETRY_MODEL_LUA_H_ + +#include "deepmind/lua/lua.h" +#include "deepmind/model_generation/model.h" + +namespace deepmind { +namespace lab { + +// Construct a table representation of model and push it onto the stack. +// [1, 0, -] +void Push(lua_State* L, const Model& model); + +// Read a model from the position in the stack given by idx. +// Returns whether the model was successfully read. +// [0, 0, -] +bool Read(lua_State* L, int idx, Model* model); + +} // namespace lab +} // namespace deepmind + +#endif // DML_DEEPMIND_MODEL_GENERATION_GEOMETRY_MODEL_LUA_H_ diff --git a/deepmind/model_generation/model_lua_test.cc b/deepmind/model_generation/model_lua_test.cc new file mode 100644 index 00000000..5e84a876 --- /dev/null +++ b/deepmind/model_generation/model_lua_test.cc @@ -0,0 +1,339 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "deepmind/model_generation/model_lua.h" + +#include "gtest/gtest.h" +#include "deepmind/lua/call.h" +#include "deepmind/lua/n_results_or_test_util.h" +#include "deepmind/lua/push_script.h" +#include "deepmind/lua/vm.h" +#include "deepmind/model_generation/geometry_util.h" +#include "deepmind/support/test_srcdir.h" +#include "deepmind/tensor/lua_tensor.h" + +namespace deepmind { +namespace lab { +namespace { + +using geometry::kEpsilon; + +TEST(DeepmindModelTest, PushRead) { + Transform xfrm; + xfrm = Eigen::Matrix4f::Identity(); + const Model ref_model = { + "ref_model", // + { + { + "cube_surface", // + { + -0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 0.0, 0.0, // + 0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 1.0, 0.0, // + 0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 1.0, 1.0, // + -0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 0.0, 1.0, // + 0.5, -0.5, -0.5, 1.0, 0.0, 0.0, 0.0, 0.0, // + 0.5, -0.5, 0.5, 1.0, 0.0, 0.0, 1.0, 0.0, // + 0.5, 0.5, 0.5, 1.0, 0.0, 0.0, 1.0, 1.0, // + 0.5, 0.5, -0.5, 1.0, 0.0, 0.0, 0.0, 1.0, // + 0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 0.0, 0.0, // + -0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 0.0, // + -0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 1.0, // + 0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 0.0, 1.0, // + -0.5, -0.5, 0.5, -1.0, 0.0, 0.0, 0.0, 0.0, // + -0.5, -0.5, -0.5, -1.0, 0.0, 0.0, 1.0, 0.0, // + -0.5, 0.5, -0.5, -1.0, 0.0, 0.0, 1.0, 1.0, // + -0.5, 0.5, 0.5, -1.0, 0.0, 0.0, 0.0, 1.0, // + -0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 0.0, 0.0, // + 0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 1.0, 0.0, // + 0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 1.0, 1.0, // + -0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 1.0, // + 0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 0.0, 0.0, // + -0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 1.0, 0.0, // + -0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 1.0, 1.0, // + 0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 0.0, 1.0 // + }, // + { + 0, 1, 2, // + 0, 2, 3, // + 4, 5, 6, // + 4, 6, 7, // + 8, 9, 10, // + 8, 10, 11, // + 12, 13, 14, // + 12, 14, 15, // + 16, 17, 18, // + 16, 18, 19, // + 20, 21, 22, // + 20, 22, 23 // + }, // + "textures/model/beam" // + } // + }, // + { + { + "centre_locator", // + xfrm // + } // + } // + }; + Model test_model; + + auto lua_vm = lua::CreateVm(); + auto* L = lua_vm.get(); + tensor::LuaTensorRegister(L); + + int top = lua_gettop(L); + Push(L, ref_model); + Read(L, -1, &test_model); + lua_pop(L, 1); + ASSERT_EQ(top, lua_gettop(L)); + + ASSERT_EQ(ref_model.surfaces.size(), test_model.surfaces.size()); + for (std::size_t i = 0; i < ref_model.surfaces.size(); ++i) { + const Model::Surface& ref_surface = ref_model.surfaces[i]; + const Model::Surface& test_surface = test_model.surfaces[i]; + EXPECT_EQ(ref_surface.name, test_surface.name); + EXPECT_EQ(ref_surface.vertices, test_surface.vertices); + EXPECT_EQ(ref_surface.indices, test_surface.indices); + EXPECT_EQ(ref_surface.shader_name, test_surface.shader_name); + } + + for (const auto& ref_locator : ref_model.locators) { + auto lit = test_model.locators.find(ref_locator.first); + ASSERT_NE(lit, test_model.locators.end()); + const auto ref_matrix = ref_locator.second.matrix(); + const auto test_matrix = lit->second.matrix(); + EXPECT_NEAR((test_matrix - ref_matrix).norm(), 0.0f, kEpsilon); + } +} + +constexpr char kModelIndicesOutOfRange[] = R"( +local tensor = require 'dmlab.system.tensor' +local cube = { + surfaces = { + cube_surface = { + vertices = tensor.FloatTensor{ + { -0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 0.0, 0.0 } + }, + indices = tensor.Int32Tensor{ + { 1, 2, 3 } + }, + shader_name = 'textures/model/beam' + } + } +} +return cube +)"; + +TEST(DeepmindModelTest, ReadIndicesOutOfRange) { + auto lua_vm = lua::CreateVm(); + lua_vm.AddPathToSearchers(TestSrcDir()); + lua_vm.AddCModuleToSearchers("dmlab.system.tensor", + tensor::LuaTensorConstructors); + auto* L = lua_vm.get(); + tensor::LuaTensorRegister(L); + + ASSERT_THAT( + lua::PushScript(L, kModelIndicesOutOfRange, "kModelIndicesOutOfRange"), + lua::testing::IsOkAndHolds(1)) + << "Missing script"; + ASSERT_THAT(lua::Call(L, 0), lua::testing::IsOkAndHolds(1)) + << "Missing result"; + + Model test_model; + EXPECT_FALSE(Read(L, -1, &test_model)); +} + +constexpr char kModelNoVertices[] = R"( +local tensor = require 'dmlab.system.tensor' +local cube = { + surfaces = { + cube_surface = { + indices = tensor.Int32Tensor{ + { 1, 2, 3 } + }, + shader_name = 'textures/model/beam' + } + } +} +return cube +)"; + +TEST(DeepmindModelTest, ReadNoVertices) { + auto lua_vm = lua::CreateVm(); + lua_vm.AddPathToSearchers(TestSrcDir()); + lua_vm.AddCModuleToSearchers("dmlab.system.tensor", + tensor::LuaTensorConstructors); + auto* L = lua_vm.get(); + tensor::LuaTensorRegister(L); + + ASSERT_THAT(lua::PushScript(L, kModelNoVertices, "kModelNoVertices"), + lua::testing::IsOkAndHolds(1)) + << "Missing script"; + ASSERT_THAT(lua::Call(L, 0), lua::testing::IsOkAndHolds(1)) + << "Missing result"; + + Model test_model; + EXPECT_FALSE(Read(L, -1, &test_model)); +} + +constexpr char kModelVerticesWrongSize[] = R"( +local tensor = require 'dmlab.system.tensor' +local cube = { + surfaces = { + cube_surface = { + vertices = tensor.FloatTensor{ + { -0.5, -0.5, -0.5, 0.0, 0.0, -1.0 } + }, + indices = tensor.Int32Tensor{ + { 1, 1, 1 } + }, + shader_name = 'textures/model/beam' + } + } +} +return cube +)"; + +TEST(DeepmindModelTest, ReadVerticesWrongSize) { + auto lua_vm = lua::CreateVm(); + lua_vm.AddPathToSearchers(TestSrcDir()); + lua_vm.AddCModuleToSearchers("dmlab.system.tensor", + tensor::LuaTensorConstructors); + auto* L = lua_vm.get(); + tensor::LuaTensorRegister(L); + + ASSERT_THAT( + lua::PushScript(L, kModelVerticesWrongSize, "kModelVerticesWrongSize"), + lua::testing::IsOkAndHolds(1)) + << "Missing script"; + ASSERT_THAT(lua::Call(L, 0), lua::testing::IsOkAndHolds(1)) + << "Missing result"; + + Model test_model; + EXPECT_FALSE(Read(L, -1, &test_model)); +} + +constexpr char kModelNoIndices[] = R"( +local tensor = require 'dmlab.system.tensor' +local cube = { + surfaces = { + cube_surface = { + vertices = tensor.FloatTensor{ + { -0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 0.0, 0.0 } + }, + shader_name = 'textures/model/beam' + } + } +} +return cube +)"; + +TEST(DeepmindModelTest, ReadNoIndices) { + auto lua_vm = lua::CreateVm(); + lua_vm.AddPathToSearchers(TestSrcDir()); + lua_vm.AddCModuleToSearchers("dmlab.system.tensor", + tensor::LuaTensorConstructors); + auto* L = lua_vm.get(); + tensor::LuaTensorRegister(L); + + ASSERT_THAT(lua::PushScript(L, kModelNoVertices, "kModelNoIndices"), + lua::testing::IsOkAndHolds(1)) + << "Missing script"; + ASSERT_THAT(lua::Call(L, 0), lua::testing::IsOkAndHolds(1)) + << "Missing result"; + + Model test_model; + EXPECT_FALSE(Read(L, -1, &test_model)); +} + +constexpr char kModelIndicesWrongSize[] = R"( +local tensor = require 'dmlab.system.tensor' +local cube = { + surfaces = { + cube_surface = { + vertices = tensor.FloatTensor{ + { -0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 0.0, 0.0 } + }, + indices = tensor.Int32Tensor{ + { 1, 1, 1, 1 } + }, + shader_name = 'textures/model/beam' + } + } +} +return cube +)"; + +TEST(DeepmindModelTest, ReadIndicesWrongSize) { + auto lua_vm = lua::CreateVm(); + lua_vm.AddPathToSearchers(TestSrcDir()); + lua_vm.AddCModuleToSearchers("dmlab.system.tensor", + tensor::LuaTensorConstructors); + auto* L = lua_vm.get(); + tensor::LuaTensorRegister(L); + + ASSERT_THAT( + lua::PushScript(L, kModelIndicesWrongSize, "kModelIndicesWrongSize"), + lua::testing::IsOkAndHolds(1)) + << "Missing script"; + ASSERT_THAT(lua::Call(L, 0), lua::testing::IsOkAndHolds(1)) + << "Missing result"; + + Model test_model; + EXPECT_FALSE(Read(L, -1, &test_model)); +} + +constexpr char kModelNoShader[] = R"( +local tensor = require 'dmlab.system.tensor' +local cube = { + surfaces = { + cube_surface = { + vertices = tensor.FloatTensor{ + { -0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 0.0, 0.0 } + }, + indices = tensor.Int32Tensor{ + { 1, 1, 1 } + } + } + } +} +return cube +)"; + +TEST(DeepmindModelTest, ReadNoShader) { + auto lua_vm = lua::CreateVm(); + lua_vm.AddPathToSearchers(TestSrcDir()); + lua_vm.AddCModuleToSearchers("dmlab.system.tensor", + tensor::LuaTensorConstructors); + auto* L = lua_vm.get(); + tensor::LuaTensorRegister(L); + + ASSERT_THAT(lua::PushScript(L, kModelNoShader, "kModelNoShader"), + lua::testing::IsOkAndHolds(1)) + << "Missing script"; + ASSERT_THAT(lua::Call(L, 0), lua::testing::IsOkAndHolds(1)) + << "Missing result"; + + Model test_model; + EXPECT_FALSE(Read(L, -1, &test_model)); +} + +} // namespace +} // namespace lab +} // namespace deepmind diff --git a/deepmind/model_generation/model_setters.cc b/deepmind/model_generation/model_setters.cc new file mode 100644 index 00000000..b4b18c4a --- /dev/null +++ b/deepmind/model_generation/model_setters.cc @@ -0,0 +1,186 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "deepmind/model_generation/model_setters.h" + +#include + +#include "deepmind/support/logging.h" + +namespace deepmind { +namespace lab { +namespace { + +ModelSettersData* CastModelData(void* model_data) { + CHECK(model_data != nullptr); + return static_cast(model_data); +} + +void SetName(void* model_data, const char* name) { + CastModelData(model_data)->model.name = name; +} + +void SetSurfaceCount(void* model_data, std::size_t num_surfaces) { + CastModelData(model_data)->model.surfaces.resize(num_surfaces); +} + +void SetSurfaceName(void* model_data, std::size_t surf_idx, const char* name) { + Model& model = CastModelData(model_data)->model; + CHECK_LT(surf_idx, model.surfaces.size()) << "Incorrect surface index."; + model.surfaces[surf_idx].name = name; +} + +void SetSurfaceVertexCount(void* model_data, std::size_t surf_idx, + std::size_t num_verts) { + Model& model = CastModelData(model_data)->model; + CHECK_LT(surf_idx, model.surfaces.size()) << "Incorrect surface index."; + model.surfaces[surf_idx].vertices.resize(num_verts * 8); +} + +void SetSurfaceVertexLocation(void* model_data, std::size_t surf_idx, + std::size_t vert_idx, float location[3]) { + Model& model = CastModelData(model_data)->model; + CHECK_LT(surf_idx, model.surfaces.size()) << "Incorrect surface index."; + Model::Surface& surf = model.surfaces[surf_idx]; + CHECK_LT(vert_idx, surf.vertices.size() / 8) << "Incorrect vertex index."; + float* vertex = &surf.vertices[vert_idx * 8]; + for (int i = 0; i < 3; ++i) { + vertex[i] = location[i]; + } +} + +void SetSurfaceVertexNormal(void* model_data, std::size_t surf_idx, + std::size_t vert_idx, float normal[3]) { + Model& model = CastModelData(model_data)->model; + CHECK_LT(surf_idx, model.surfaces.size()) << "Incorrect surface index."; + Model::Surface& surf = model.surfaces[surf_idx]; + CHECK_LT(vert_idx, surf.vertices.size() / 8) << "Incorrect vertex index."; + float* vertex = &surf.vertices[vert_idx * 8]; + for (int i = 0; i < 3; ++i) { + vertex[3 + i] = normal[i]; + } +} + +void SetSurfaceVertexST(void* model_data, std::size_t surf_idx, + std::size_t vert_idx, float st[2]) { + Model& model = CastModelData(model_data)->model; + CHECK_LT(surf_idx, model.surfaces.size()) << "Incorrect surface index."; + Model::Surface& surf = model.surfaces[surf_idx]; + CHECK_LT(vert_idx, surf.vertices.size() / 8) << "Incorrect vertex index."; + float* vertex = &surf.vertices[vert_idx * 8]; + for (int i = 0; i < 2; ++i) { + vertex[6 + i] = st[i]; + } +} + +void SetSurfaceFaceCount(void* model_data, std::size_t surf_idx, + std::size_t num_faces) { + Model& model = CastModelData(model_data)->model; + CHECK_LT(surf_idx, model.surfaces.size()) << "Incorrect surface index."; + model.surfaces[surf_idx].indices.resize(num_faces * 3); +} + +void SetSurfaceFace(void* model_data, std::size_t surf_idx, + std::size_t face_idx, int indices[3]) { + Model& model = CastModelData(model_data)->model; + CHECK_LT(surf_idx, model.surfaces.size()) << "Incorrect surface index."; + Model::Surface& surf = model.surfaces[surf_idx]; + CHECK_LT(face_idx, surf.indices.size() / 3) << "Incorrect face index."; + int* face = &surf.indices[face_idx * 3]; + for (int i = 0; i < 3; ++i) { + face[i] = indices[i]; + } +} + +void SetSurfaceShaderCount(void* model_data, std::size_t surf_idx, + std::size_t num_shaders) { + Model& model = CastModelData(model_data)->model; + CHECK_LT(surf_idx, model.surfaces.size()) << "Incorrect surface index."; + if (num_shaders > 0) { + LOG_IF(WARNING, num_shaders > 1) + << "Surface specifies " << num_shaders + << "shaders, only the 1st one will be used."; + } else { + model.surfaces[surf_idx].shader_name = "default"; + } +} + +void SetSurfaceShader(void* model_data, std::size_t surf_idx, + std::size_t shad_idx, const char* name) { + Model& model = CastModelData(model_data)->model; + CHECK_LT(surf_idx, model.surfaces.size()) << "Incorrect surface index."; + if (shad_idx == 0) { + model.surfaces[surf_idx].shader_name = name; + } +} + +void SetTagCount(void* model_data, std::size_t num_tags) { + CastModelData(model_data)->locatorNames.reserve(num_tags); +} + +void SetTagName(void* model_data, std::size_t tag_idx, const char* name) { + ModelSettersData& data = *CastModelData(model_data); + CHECK_EQ(tag_idx, data.locatorNames.size()) << "Incorrect tag index."; + data.locatorNames.push_back(name); +} + +void SetTagAxis(void* model_data, std::size_t tag_idx, std::size_t axis_idx, + float axis[3]) { + ModelSettersData& data = *CastModelData(model_data); + CHECK_LT(tag_idx, data.locatorNames.size()) << "Incorrect tag index."; + Transform& xfrm = data.model.locators[data.locatorNames[tag_idx]]; + for (int i = 0; i < 3; ++i) { + xfrm(i, axis_idx) = axis[i]; + } + xfrm(3, axis_idx) = 0.0f; +} + +void SetTagOrigin(void* model_data, std::size_t tag_idx, float origin[3]) { + ModelSettersData& data = *CastModelData(model_data); + CHECK_LT(tag_idx, data.locatorNames.size()) << "Incorrect tag index."; + Transform& xfrm = data.model.locators[data.locatorNames[tag_idx]]; + for (int i = 0; i < 3; ++i) { + xfrm(i, 3) = origin[i]; + } + xfrm(3, 3) = 1.0f; +} + +} // namespace + +DeepmindModelSetters ModelSetters() { + return { + SetName, // + SetSurfaceCount, // + SetSurfaceName, // + SetSurfaceVertexCount, // + SetSurfaceVertexLocation, // + SetSurfaceVertexNormal, // + SetSurfaceVertexST, // + SetSurfaceFaceCount, // + SetSurfaceFace, // + SetSurfaceShaderCount, // + SetSurfaceShader, // + SetTagCount, // + SetTagName, // + SetTagAxis, // + SetTagOrigin // + }; +} + +} // namespace lab +} // namespace deepmind diff --git a/deepmind/support/str_split.h b/deepmind/model_generation/model_setters.h similarity index 54% rename from deepmind/support/str_split.h rename to deepmind/model_generation/model_setters.h index aa568eb9..68443478 100644 --- a/deepmind/support/str_split.h +++ b/deepmind/model_generation/model_setters.h @@ -1,4 +1,4 @@ -// Copyright (C) 2016 Google Inc. +// Copyright (C) 2017 Google Inc. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -16,32 +16,29 @@ // //////////////////////////////////////////////////////////////////////////////// -#ifndef DEEPMIND_SUPPORT_STR_SPLIT_H -#define DEEPMIND_SUPPORT_STR_SPLIT_H +#ifndef DML_DEEPMIND_MODEL_GENERATION_MODEL_SETTERS_H_ +#define DML_DEEPMIND_MODEL_GENERATION_MODEL_SETTERS_H_ -#include +#include #include -#include "deepmind/support/string_view.h" +#include "deepmind/include/deepmind_model_setters.h" +#include "deepmind/model_generation/model.h" -namespace strings { +namespace deepmind { +namespace lab { -struct SkipEmpty {}; +// Structure to be passed to the mutator API as 'model_data'. +struct ModelSettersData { + Model model; + std::vector locatorNames; +}; -inline std::vector Split(StringPiece s, char sep, SkipEmpty = {}) { - std::vector result; - for (const char* first = s.ptr_, * last = first + s.size_; ; ) { - while (first != last && *first == sep) ++first; - if (first == last) break; +// Returns the mutator API for custom models. This is used to modify model data +// from the ioq3 engine. +DeepmindModelSetters ModelSetters(); - const char* middle = std::find(first, last, sep); - result.emplace_back(first, middle); +} // namespace lab +} // namespace deepmind - first = middle; - } - return result; -} - -} // namespace strings - -#endif // DEEPMIND_SUPPORT_STR_SPLIT_H +#endif // DML_DEEPMIND_MODEL_GENERATION_MODEL_SETTERS_H_ diff --git a/deepmind/model_generation/model_util.cc b/deepmind/model_generation/model_util.cc new file mode 100644 index 00000000..9a86e7b7 --- /dev/null +++ b/deepmind/model_generation/model_util.cc @@ -0,0 +1,45 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "deepmind/model_generation/model_util.h" + +namespace deepmind { +namespace lab { + +void TransformSurface( // + const Eigen::Affine3f& xfrm, // + const Eigen::Matrix3f& xfrm_i, // + Model::Surface* surface, // + Eigen::AlignedBox3f* bbox) { + auto& vertices = surface->vertices; + for (std::size_t i = 0; i < vertices.size(); i += 8) { + Eigen::Vector4f pos(vertices[i], vertices[i + 1], vertices[i + 2], 1.0f); + Eigen::RowVector3f nrm(vertices[i + 3], vertices[i + 4], vertices[i + 5]); + pos = xfrm * pos; + nrm = nrm * xfrm_i; + nrm.normalize(); + for (std::size_t j = 0; j < 3; ++j) { + vertices[i + j] = pos[j]; + vertices[i + 3 + j] = nrm[j]; + } + bbox->extend(pos.head<3>()); + } +} + +} // namespace lab +} // namespace deepmind diff --git a/deepmind/model_generation/model_util.h b/deepmind/model_generation/model_util.h new file mode 100644 index 00000000..a0d7cef2 --- /dev/null +++ b/deepmind/model_generation/model_util.h @@ -0,0 +1,41 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef DML_DEEPMIND_MODEL_GENERATION_MODEL_UTIL_H_ +#define DML_DEEPMIND_MODEL_GENERATION_MODEL_UTIL_H_ + +#include "Eigen/Geometry" +#include "deepmind/model_generation/model.h" + +namespace deepmind { +namespace lab { + +// Transforms the geometry of the given surface in-place, using matrix 'xfrm' +// for vertex positions and the inverse of its linear part 'xfrm_i' for vertex +// normals. The bounding box 'bbox' is updated to encompass the transformed +// vertices. +void TransformSurface( // + const Eigen::Affine3f& xfrm, // + const Eigen::Matrix3f& xfrm_i, // + Model::Surface* surface, // + Eigen::AlignedBox3f* bbox); + +} // namespace lab +} // namespace deepmind + +#endif // DML_DEEPMIND_MODEL_GENERATION_MODEL_UTIL_H_ diff --git a/deepmind/support/str_join.h b/deepmind/model_generation/transform.h similarity index 58% rename from deepmind/support/str_join.h rename to deepmind/model_generation/transform.h index 5b643426..d740e32a 100644 --- a/deepmind/support/str_join.h +++ b/deepmind/model_generation/transform.h @@ -1,4 +1,4 @@ -// Copyright (C) 2016 Google Inc. +// Copyright (C) 2017 Google Inc. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -16,36 +16,24 @@ // //////////////////////////////////////////////////////////////////////////////// -#ifndef DEEPMIND_SUPPORT_STR_JOIN_H -#define DEEPMIND_SUPPORT_STR_JOIN_H +#ifndef DML_DEEPMIND_MODEL_GENERATION_TRANSFORM_H_ +#define DML_DEEPMIND_MODEL_GENERATION_TRANSFORM_H_ -#include -#include +#include "Eigen/Geometry" -namespace strings { +namespace deepmind { +namespace lab { -template -inline std::string Join(const T& t, const std::string& sep, F f) { - using std::begin; - using std::end; +class Transform : public Eigen::Affine3f { + public: + using Eigen::Affine3f::Affine3f; + using Eigen::Affine3f::operator=; - std::string result; + inline Transform() = default; + inline Transform(const Eigen::Affine3f& alt) : Eigen::Affine3f(alt) {} +}; - auto it = begin(t), eit = end(t); +} // namespace lab +} // namespace deepmind - if (it != eit) { - f(&result, *it); - ++it; - } - - for (; it != eit; ++it) { - result.append(sep); - f(&result, *it); - } - - return result; -} - -} // namespace strings - -#endif // DEEPMIND_SUPPORT_STR_JOIN_H +#endif // DML_DEEPMIND_MODEL_GENERATION_TRANSFORM_H_ diff --git a/deepmind/model_generation/transform_lua.cc b/deepmind/model_generation/transform_lua.cc new file mode 100644 index 00000000..926a0227 --- /dev/null +++ b/deepmind/model_generation/transform_lua.cc @@ -0,0 +1,56 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "deepmind/model_generation/transform_lua.h" + +#include "deepmind/support/logging.h" +#include "deepmind/tensor/lua_tensor.h" +#include "deepmind/tensor/tensor_view.h" + +namespace deepmind { +namespace lab { + +void Push(lua_State* L, const Transform& xfrm) { + auto storage = std::make_shared>(1, xfrm); + tensor::LuaTensor::CreateObject( + L, + tensor::TensorView(tensor::Layout({4, 4}), + storage->mutable_data()->front().data()), + storage); +} + +bool Read(lua_State* L, int idx, Transform* xfrm) { + auto* tensor = tensor::LuaTensor::ReadObject(L, idx); + if (tensor == nullptr) { + return false; + } + const auto& view = tensor->tensor_view(); + const auto& shape = view.shape(); + if (shape.size() != 2 || shape[0] != 4 || shape[1] != 4 || + !view.IsContiguous()) { + LOG(ERROR) << "Incorrect dimensions for arg 'xfrm'"; + return false; + } + const float* storage = view.storage(); + view.ForEachOffset( + [=](std::size_t offset) { xfrm->data()[offset] = storage[offset]; }); + return true; +} + +} // namespace lab +} // namespace deepmind diff --git a/deepmind/model_generation/transform_lua.h b/deepmind/model_generation/transform_lua.h new file mode 100644 index 00000000..6301c79b --- /dev/null +++ b/deepmind/model_generation/transform_lua.h @@ -0,0 +1,40 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef DML_DEEPMIND_MODEL_GENERATION_TRANSFORM_LUA_H_ +#define DML_DEEPMIND_MODEL_GENERATION_TRANSFORM_LUA_H_ + +#include "deepmind/lua/lua.h" +#include "deepmind/model_generation/transform.h" + +namespace deepmind { +namespace lab { + +// Construct a table representation of transform and push it onto the stack. +// [1, 0, -] +void Push(lua_State* L, const Transform& transform); + +// Read a transform from the given position in the stack and load it onto xfrm. +// Returns whether the transform was successfully read. +// [0, 0, -] +bool Read(lua_State* L, int idx, Transform* transform); + +} // namespace lab +} // namespace deepmind + +#endif // DML_DEEPMIND_MODEL_GENERATION_TRANSFORM_LUA_H_ diff --git a/deepmind/model_generation/transform_lua_test.cc b/deepmind/model_generation/transform_lua_test.cc new file mode 100644 index 00000000..67a26c34 --- /dev/null +++ b/deepmind/model_generation/transform_lua_test.cc @@ -0,0 +1,80 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "deepmind/model_generation/transform_lua.h" + +#include "gtest/gtest.h" +#include "Eigen/Geometry" +#include "deepmind/lua/call.h" +#include "deepmind/lua/n_results_or_test_util.h" +#include "deepmind/lua/push_script.h" +#include "deepmind/lua/vm.h" +#include "deepmind/model_generation/geometry_util.h" +#include "deepmind/support/test_srcdir.h" +#include "deepmind/tensor/lua_tensor.h" + +namespace deepmind { +namespace lab { +namespace { + +using geometry::kEpsilon; + +TEST(DeepmindTransformTest, PushRead) { + Eigen::Matrix4f ref_data; + ref_data << 0.0f, 1.0f, 2.0f, 3.0f, // + 4.0f, 5.0f, 6.0f, 7.0f, // + 8.0f, 9.0f, 10.0f, 11.0f, // + 12.0f, 13.0f, 14.0f, 15.0f; + auto lua_vm = lua::CreateVm(); + tensor::LuaTensorRegister(lua_vm.get()); + Transform ref_xfrm; + ref_xfrm = ref_data; + Push(lua_vm.get(), ref_xfrm); + Transform tst_xfrm; + Read(lua_vm.get(), -1, &tst_xfrm); + EXPECT_NEAR((ref_data - tst_xfrm.matrix()).norm(), 0.0f, kEpsilon); +} + +constexpr char kTransformWrongDims[] = R"( +local tensor = require 'dmlab.system.tensor' +local matrix = tensor.FloatTensor{ + { 0.0, 1.0, 2.0, 3.0 }, + { 4.0, 5.0, 6.0, 7.0 } +} +return matrix +)"; + +TEST(DeepmindTransformTest, ReadWrongDims) { + auto lua_vm = lua::CreateVm(); + lua_vm.AddPathToSearchers(TestSrcDir()); + lua_vm.AddCModuleToSearchers("dmlab.system.tensor", + tensor::LuaTensorConstructors); + auto* L = lua_vm.get(); + deepmind::lab::tensor::LuaTensorRegister(L); + ASSERT_THAT(lua::PushScript(L, kTransformWrongDims, "kTransformWrongDims"), + lua::testing::IsOkAndHolds(1)) + << "Missing script"; + ASSERT_THAT(lua::Call(L, 0), lua::testing::IsOkAndHolds(1)) + << "Missing result"; + Transform tst_matrix; + EXPECT_FALSE(Read(L, -1, &tst_matrix)); +} + +} // namespace +} // namespace lab +} // namespace deepmind diff --git a/deepmind/support/BUILD b/deepmind/support/BUILD index d89bd9f7..36676217 100644 --- a/deepmind/support/BUILD +++ b/deepmind/support/BUILD @@ -10,30 +10,6 @@ cc_library( hdrs = ["logging.h"], ) -cc_library( - name = "str_join", - hdrs = ["str_join.h"], -) - -cc_library( - name = "str_split", - hdrs = ["str_split.h"], -) - -cc_library( - name = "str_cat", - hdrs = ["str_cat.h"], - deps = [":string_view"], -) - -cc_library( - name = "string_view", - hdrs = [ - "string_view.h", - "string_view_utils.h", - ], -) - cc_library( name = "stringprintf", srcs = ["stringprintf.cc"], diff --git a/deepmind/support/logging.h b/deepmind/support/logging.h index bf00d448..0367f427 100644 --- a/deepmind/support/logging.h +++ b/deepmind/support/logging.h @@ -118,6 +118,10 @@ enum class LogLevel { LogMessage LogStream(std::integral_constant); LogMessageFatal LogStream(std::integral_constant); +struct Voidify { + void operator&(std::ostream&) {} +}; + } // namespace internal } // namespace deepmind @@ -165,4 +169,12 @@ LogMessageFatal LogStream(std::integral_constant); #define VLOG(level) ::deepmind::internal::NullStream() +#define LOG_IF(level, condition) \ + !(condition) \ + ? static_cast(0) \ + : ::deepmind::internal::Voidify() & \ + decltype(::deepmind::internal::LogStream( \ + std::integral_constant<::deepmind::internal::LogLevel, level>())) \ + (__FILE__, __LINE__).stream() + #endif // DEEPMIND_SUPPORT_LOGGING_H diff --git a/deepmind/support/str_cat.h b/deepmind/support/str_cat.h deleted file mode 100644 index 59a6c32b..00000000 --- a/deepmind/support/str_cat.h +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (C) 2016 Google Inc. -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 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., -// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef DEEPMIND_SUPPORT_STR_CAT_H -#define DEEPMIND_SUPPORT_STR_CAT_H - -#include -#include -#include - -using std::string; - -template -inline std::string StrCat(const Args&... args) { - std::ostringstream oss; - int dummy[] = { 1, (oss << args, 0)... }; - static_cast(dummy); - return oss.str(); -} - -template -inline void StrAppend(std::string* out, const Args&... args) { - std::ostringstream oss; - int dummy[] = { 1, (oss << args, 0)... }; - static_cast(dummy); - out->append(oss.str()); -} - -namespace strings { -namespace internal { - -template -class PairFormatterImpl { - public: - PairFormatterImpl(F1 f1, F2 f2, std::string sep) - : f1_(std::move(f1)), - f2_(std::move(f2)), - sep_(std::move(sep)) {} - - template - void operator()(std::string* out, const T& p) { - f1_(out, p.first); - out->append(sep_); - f2_(out, p.second); - } - - private: - F1 f1_; - F2 f2_; - std::string sep_; -}; - -} // namespace internal - -template -internal::PairFormatterImpl PairFormatter( - F1 f1, std::string sep, F2 f2) { - return internal::PairFormatterImpl( - std::move(f1), std::move(f2), std::move(sep)); -} - -} // namespace strings - -#endif // DEEPMIND_SUPPORT_STR_CAT_H diff --git a/deepmind/support/string_view.h b/deepmind/support/string_view.h deleted file mode 100644 index 3598d1b4..00000000 --- a/deepmind/support/string_view.h +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (C) 2016 Google Inc. -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 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., -// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef DEEPMIND_SUPPORT_STRING_VIEW_H -#define DEEPMIND_SUPPORT_STRING_VIEW_H - -#include -#include -#include -#include -#include - -struct StringPiece { - std::size_t size() const { - return size_; - } - - const char* data() const { return ptr_; } - - char operator[](std::size_t i) const { - return ptr_[i]; - } - - const char* begin() const { return data(); } - const char* end() const { return data() + size(); } - - using size_type = std::size_t; - - static constexpr size_type npos = static_cast(-1); - - StringPiece() - : ptr_(nullptr), size_(0) {} - - StringPiece(const char* ntbs) - : ptr_(ntbs), size_(std::strlen(ntbs)) {} - - StringPiece(const std::string& s) - : ptr_(s.data()), size_(s.size()) {} - - StringPiece(const char* first, const char* last) - : ptr_(first), size_(last - first) {} - - size_type find(char c, size_type pos = 0) const { - if (pos >= size_) return npos; - auto it = std::find(ptr_ + pos, ptr_ + size_, c); - return it == ptr_ + size_ ? npos : it - ptr_; - } - - StringPiece substr(size_type pos, size_type len = npos) { - if (pos > size_) pos = size_; - if (len > size_ - pos) len = size_ - pos; - return StringPiece(ptr_ + pos, ptr_ + pos + len); - } - - size_type find_first_of(StringPiece chars, size_type pos = 0) { - if (pos >= size_) return npos; - const char* it = std::find_first_of( - begin() + pos, end(), chars.begin(), chars.end()); - return it == end() ? npos : it - begin(); - } - - void remove_prefix(size_type n) { - assert(n <= size_); - ptr_ += n; - size_ -= n; - } - - const char* ptr_; - std::size_t size_; -}; - -#endif // DEEPMIND_SUPPORT_STRING_VIEW_H diff --git a/deepmind/tensor/BUILD b/deepmind/tensor/BUILD index 8d009b16..927ea4fe 100644 --- a/deepmind/tensor/BUILD +++ b/deepmind/tensor/BUILD @@ -8,6 +8,7 @@ cc_library( srcs = ["tensor_view.cc"], hdrs = ["tensor_view.h"], visibility = ["//visibility:public"], + deps = ["@eigen_archive//:eigen"], ) cc_test( @@ -16,7 +17,7 @@ cc_test( srcs = ["tensor_view_test.cc"], deps = [ ":tensor_view", - "@googletest//:gtest_main", + "@com_google_googletest//:gtest_main", ], ) @@ -27,6 +28,7 @@ cc_library( visibility = ["//visibility:public"], deps = [ ":tensor_view", + "//deepmind/engine:lua_random", "//deepmind/lua", "//deepmind/lua:bind", "//deepmind/lua:call", @@ -43,12 +45,13 @@ cc_test( srcs = ["lua_tensor_test.cc"], deps = [ ":lua_tensor", + "//deepmind/lua:bind", "//deepmind/lua:call", "//deepmind/lua:n_results_or_test_util", "//deepmind/lua:push_script", "//deepmind/lua:read", "//deepmind/lua:table_ref", "//deepmind/lua:vm", - "@googletest//:gtest_main", + "@com_google_googletest//:gtest_main", ], ) diff --git a/deepmind/tensor/lua_tensor.h b/deepmind/tensor/lua_tensor.h index 0eb655bc..946bc41a 100644 --- a/deepmind/tensor/lua_tensor.h +++ b/deepmind/tensor/lua_tensor.h @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include #include #include @@ -29,6 +31,7 @@ #include #include +#include "deepmind/engine/lua_random.h" #include "deepmind/lua/call.h" #include "deepmind/lua/class.h" #include "deepmind/lua/lua.h" @@ -54,9 +57,9 @@ int LuaTensorConstructors(lua_State* L); class StorageValidity { public: enum Tag { - kInvalid, // The tensor_view_ is valid now but may become invalid in the + kInvalid, // The tensor_view_ is invalid. + kValid, // The tensor_view_ is valid now but may become invalid in the // future. - kValid, // The tensor_view_ is invalid. kOwnsStorage // The tensor_view_ is valid and always will be. }; explicit StorageValidity(Tag tag = kValid) : tag_(tag) {} @@ -110,7 +113,7 @@ class LuaTensor : public lua::Class> { storage_validity_(std::move(storage_validity)) {} // Create an owning LuaTensor from shape and storage. - LuaTensor(std::vector shape, std::vector storage) + LuaTensor(ShapeVector shape, std::vector storage) : tensor_view_(Layout(std::move(shape)), storage.data()), storage_validity_( std::make_shared>(std::move(storage))) {} @@ -123,6 +126,7 @@ class LuaTensor : public lua::Class> { {"__call", &Class::template Member<&LuaTensor::Index>}, {"__eq", &Class::template Member<&LuaTensor::Equal>}, {"shape", &Class::template Member<&LuaTensor::Shape>}, + {"reshape", &Class::template Member<&LuaTensor::Reshape>}, {"clone", &Class::template Member<&LuaTensor::Clone>}, {"val", &Class::template Member<&LuaTensor::Val>}, {"transpose", &Class::template Member<&LuaTensor::Transpose>}, @@ -142,6 +146,13 @@ class LuaTensor : public lua::Class> { {"cadd", &Class::template Member<&LuaTensor::ViewOp<&View::CAdd>>}, {"cdiv", &Class::template Member<&LuaTensor::ViewOp<&View::CDiv>>}, {"csub", &Class::template Member<&LuaTensor::ViewOp<&View::CSub>>}, + {"mmul", &Class::template Member<&LuaTensor::MMul>}, + {"floor", + &Class::template Member<&LuaTensor::UnaryOp<&View::Floor>>}, + {"ceil", &Class::template Member<&LuaTensor::UnaryOp<&View::Ceil>>}, + {"round", + &Class::template Member<&LuaTensor::UnaryOp<&View::Round>>}, + {"shuffle", &Class::template Member<&LuaTensor::Shuffle>}, {"byte", &Class::template Member<&LuaTensor::Convert>}, {"char", &Class::template Member<&LuaTensor::Convert>}, {"int16", &Class::template Member<&LuaTensor::Convert>}, @@ -157,9 +168,9 @@ class LuaTensor : public lua::Class> { // Used by Create to read the values from a table into *values. // Returns whether all values are the correct type and match the given shape. // ['shape_begin', 'shape_end') must form a valid range. - static bool ReadTable(lua::TableRef table, - std::vector::const_iterator shape_begin, - std::vector::const_iterator shape_end, + static bool ReadTable(const lua::TableRef& table, + ShapeVector::const_iterator shape_begin, + ShapeVector::const_iterator shape_end, std::vector* values) { if (shape_begin == shape_end) return false; if (shape_begin + 1 == shape_end) { @@ -174,8 +185,7 @@ class LuaTensor : public lua::Class> { lua::TableRef subtable; for (std::size_t i = 0; i < *shape_begin; ++i) { if (!table.LookUp(i + 1, &subtable) || - !ReadTable(std::move(subtable), shape_begin + 1, shape_end, - values)) { + !ReadTable(subtable, shape_begin + 1, shape_end, values)) { return false; } } @@ -185,8 +195,7 @@ class LuaTensor : public lua::Class> { // Used by 'Create' to find the shape of a given Lua table. // Returns whether shape could be implied from the table. - static bool ReadTableShape(const lua::TableRef& table, - std::vector* shape) { + static bool ReadTableShape(const lua::TableRef& table, ShapeVector* shape) { auto table_size = table.ArraySize(); if (shape->size() == 20 || table_size == 0) { shape->clear(); @@ -200,45 +209,233 @@ class LuaTensor : public lua::Class> { return true; } + // Used by 'CreateFromRange' to construct a range from a given Lua table, in + // one of these 3 forms: + // - '{lower_bound, upper_bound, stride}' + // - '{lower_bound, upper_bound}', where the stride is assumed to be 1 + // - '{upper_bound}', where the lower bound is also assumed to be 1 + // The range bounds and stride are output through the corresponing pointer + // parameters. + // Returns whether range could be implied from the table. + static bool ReadTableRange(const lua::TableRef& table, T* lower_bound, + T* upper_bound, T* stride) { + std::size_t i = 1; + *lower_bound = 1; + *stride = 1; + switch (table.ArraySize()) { + default: + return false; + case 3: + if (!table.LookUp(3, stride)) return false; + // fallthrough + case 2: + if (!table.LookUp(i++, lower_bound)) return false; + // fallthrough + case 1: + if (!table.LookUp(i, upper_bound)) return false; + } + return true; + } + + // Creates a tensor from the given hierarchy of tables and values. The shape + // is implied. + static lua::NResultsOr CreateFromTableValues(lua_State* L, + const lua::TableRef& table) { + ShapeVector shape; + std::vector storage; + if (ReadTableShape(table, &shape)) { + storage.reserve(Layout::num_elements(shape)); + if (ReadTable(table, shape.begin(), shape.end(), &storage)) { + LuaTensor::CreateObject(L, std::move(shape), std::move(storage)); + return 1; + } + } + return "[Tensor.CreateFromTableValues] Failed to read table in to Tensor."; + } + + // Creates a rank-1 tensor. Ranges can be declared three ways: + // + // * {'from', 'to', 'step'} + // * {'from', 'to'} -- Equivalent to { from, to, 1} + // * {'to'} -- Equivalent to {1, to, 1} + // + // The number of elements will be floor(('to' - 'from') / 'step') + 1. + // Elements are generated by starting with 'from' and advancing by 'step' for + // each subsequent value. If 'step' is zero or the number of elements + // generated is not positive then an error is thrown. + // + // Examples: + // {5} -> generates {1, 2, 3, 4, 5} + // {3, 7} -> generates {3, 4, 5, 6, 7} + // {1, 2, 0.5} -> generates {1, 1.5, 2} + // {7, 3, -1} -> generates {7, 6, 5, 4, 3} + // {3, 9, 3} -> generates {3, 6, 9} + // {3, 14, 3} -> generates {3, 6, 9, 12} + static lua::NResultsOr CreateFromRange(lua_State* L, + const lua::TableRef& range) { + ShapeVector shape; + std::vector storage; + T lower_bound, upper_bound, stride; + if (!ReadTableRange(range, &lower_bound, &upper_bound, &stride)) { + return "[Tensor.CreateFromRange] Failed to read Tensor range."; + } + if (stride == 0) { + return "[Tensor.CreateFromRange] Step size must not be zero."; + } + std::ptrdiff_t n = std::floor((upper_bound - lower_bound) / stride); + if (n < 0) { + return "[Tensor.CreateFromRange] Invalid Tensor range."; + } + shape.push_back(n + 1); + storage.reserve(n + 1); + std::generate_n(std::back_inserter(storage), n + 1, + [&lower_bound, stride]() { + auto prev = lower_bound; + lower_bound += stride; + return prev; + }); + LuaTensor::CreateObject(L, std::move(shape), std::move(storage)); + return 1; + } + + // Creates a rank-N tensor with extents dim1, dim2, ..., dimN. We refer to the + // tuple (dim1, dim2, ..., dimN) as the shape of the tensor. N is the number + // of integer arguments passed to the tensor. + static lua::NResultsOr CreateFromArgs(lua_State* L) { + int top = lua_gettop(L); + ShapeVector shape; + shape.reserve(top); + for (int i = 0; i < top; ++i) { + int dim; + if (lua::Read(L, i + 1, &dim) && dim > 0) { + shape.push_back(dim); + } else { + return "[Tensor.CreateFromArgs] Failed to read Tensor shape."; + } + } + std::vector storage(Layout::num_elements(shape)); + LuaTensor::CreateObject(L, std::move(shape), std::move(storage)); + return 1; + } + + // Creates a rank-1 tensor by reading bytes within a file. The file is read in + // system local endian order. + // + // Keyword Args: + // + // * 'name': Name of the file to read. Must be a valid filename. + // * 'byteOffset': Offset from the beginning of the file at which reading + // starts. Optional, default 0. + // * 'numElements': Number of elements to read starting at the offset. + // Optional, defaults to largest number of elements that is available from + // the given offset. + // + // If the offset is outside of the range [0, file size] or if reading count + // values plus the offset would exceed the size of the file an error is + // thrown. + static lua::NResultsOr CreateFromFile(lua_State* L, lua::TableRef file_args) { + ShapeVector shape; + std::vector storage; + std::ptrdiff_t offset = 0; + std::string name; + if (!file_args.LookUp("name", &name)) { + return "[Tensor.CreateFromFile] Field 'name' must exist and be a string."; + } + + file_args.LookUp("byteOffset", &offset); + if (offset < 0) { + return "[Tensor.CreateFromFile] 'byteOffset' must be >= 0."; + } + + std::ifstream ifs(name.c_str(), std::ifstream::binary); + + if (!ifs) { + return "[Tensor.CreateFromFile] Failed to open file, name: " + name; + } + + if (!ifs.seekg(0, std::ios::end)) { + return "[Tensor.CreateFromFile] Failed to read file, name: " + name; + } + + std::ptrdiff_t file_size = ifs.tellg(); + if (offset > file_size) { + std::string error = + "[Tensor.CreateFromFile] Must supply 'byteOffset' within file size"; + error += ", name: " + name; + error += ", offset: " + std::to_string(offset); + error += ", file size: " + std::to_string(file_size); + return std::move(error); + } + std::ptrdiff_t count = (file_size - offset) / sizeof(T); + file_args.LookUp("numElements", &count); + if (!(0 <= count && count * sizeof(T) + offset <= file_size)) { + std::string error = "[Tensor.CreateFromFile] Must supply in range count."; + error += ", name: " + name; + error += ", numElements: " + std::to_string(count); + error += ", max numElements: " + + std::to_string((file_size - offset) / sizeof(T)); + error += ", offset: " + std::to_string(offset); + error += ", file size: " + std::to_string(file_size); + return std::move(error); + } + + storage.resize(count); + if (!ifs.seekg(offset, std::ios::beg) || + !ifs.read(reinterpret_cast(storage.data()), sizeof(T) * count)) { + return "[Tensor.CreateFromFile] Failed to read file, name: " + name; + } + shape.push_back(count); + LuaTensor::CreateObject(L, std::move(shape), std::move(storage)); + return 1; + } + // Creates a LuaTensor and returns it on the stack. // If called with value arguments, Tensor(s1, s2, s3, ...), // it will create a zeroed tensor of shape (s1, s2, s3, ...). // If called with a Lua array, Tensor{{v1, v2}, {v3, v4}, ...}, // it will create a tensor matching the shape of the tables passed in. + // If called with a range parameter, Tensor{range = {s1, ...}} + // it will create a rank-1 tensor with the values in the range. + // If called with a file parameter, Tensor{file = {name=, ...}} + // it will create a rank-1 tensor with the values read from the file. // Fails if the shape is inconsistent or contains values that cannot be read. // [1, (n|1), e] static lua::NResultsOr Create(lua_State* L) { - int top = lua_gettop(L); lua::TableRef table; if (lua::Read(L, 1, &table)) { - std::vector shape; - std::vector storage; - if (table.ArraySize() == 0) { - LuaTensor::CreateObject(L, std::move(shape), std::move(storage)); - return 1; + if (lua_gettop(L) != 1) { + return "[Tensor.Create] 'Must only pass one argument for table " + "construction."; } - if (ReadTableShape(table, &shape)) { - storage.reserve(Layout::num_elements(shape)); - if (ReadTable(std::move(table), shape.begin(), shape.end(), &storage)) { - LuaTensor::CreateObject(L, std::move(shape), std::move(storage)); + auto keys = table.Keys(); + if (keys.empty()) { + if (table.ArraySize() == 0) { + LuaTensor::CreateObject(L, ShapeVector{}, std::vector{}); return 1; - } - } - return "[Tensor.CreateFromTable] Failed to read table in to Tensor."; - } else { - std::vector shape; - shape.reserve(top); - for (int i = 0; i < top; ++i) { - int dim; - if (lua::Read(L, i + 1, &dim) && dim > 0) { - shape.push_back(dim); } else { - return "[Tensor.CreateFromShape] Failed to read Tensor shape."; + return CreateFromTableValues(L, table); + } + } else if (keys.size() == 1) { + if (keys.front() == "range") { + lua::TableRef range; + if (table.LookUp("range", &range)) { + return CreateFromRange(L, range); + } else { + return "[Tensor.Create] 'range' must contain a table."; + } + } else if (keys.front() == "file") { + lua::TableRef file_args; + if (table.LookUp("file", &file_args)) { + return CreateFromFile(L, file_args); + } else { + return "[Tensor.Create] 'file' must contain a table."; + } } + } else { + return "[Tensor.Create] Must supply only one named contructor."; } - std::vector storage(Layout::num_elements(shape)); - LuaTensor::CreateObject(L, std::move(shape), std::move(storage)); - return 1; + } else { + return CreateFromArgs(L); } } @@ -257,8 +454,34 @@ class LuaTensor : public lua::Class> { return 1; } + static void ToLuaTable(lua_State* L, const tensor::TensorView& view) { + const auto& shape = view.shape(); + if (shape.empty()) { + lua_createtable(L, 0, 0); + return; + } + lua_createtable(L, shape.front(), 0); + if (shape.size() == 1) { + std::size_t i = 0; + view.ForEach([&i, L](T value) { + lua::Push(L, ++i); + lua::Push(L, value); + lua_settable(L, -3); + }); + } else { + for (std::size_t i = 0; i < shape[0]; ++i) { + lua::Push(L, i + 1); + tensor::TensorView new_view = view; + new_view.Select(0, i); + ToLuaTable(L, new_view); + lua_settable(L, -3); + } + } + } + lua::NResultsOr Val(lua_State* L) { - if (tensor_view_.shape().size() == 1 && tensor_view_.shape().front() == 1) { + const auto& shape = tensor_view_.shape(); + if (shape.size() == 1 && shape.front() == 1) { T& val = tensor_view_.mutable_storage()[tensor_view_.start_offset()]; if (lua_gettop(L) == 2) { if (!lua::Read(L, 2, &val)) { @@ -268,7 +491,33 @@ class LuaTensor : public lua::Class> { lua::Push(L, val); return 1; } else { - return "[Tensor.Val] 'val' can only be called on an element"; + if (lua_gettop(L) == 2) { + lua::TableRef table; + if (!lua::Read(L, 2, &table)) { + return "[Tensor.Val] failed read table shape."; + } + ShapeVector table_shape; + if (!ReadTableShape(table, &table_shape)) { + return "[Tensor.Val] failed read table shape."; + } + if (shape.size() != table_shape.size() || + !std::equal(table_shape.begin(), table_shape.end(), + shape.begin())) { + return "[Tensor.Val] shape must match tensor shape."; + } + std::vector values; + if (!ReadTable(std::move(table), table_shape.begin(), table_shape.end(), + &values)) { + return "[Tensor.Val] failed to read values from tables"; + } + int i = 0; + tensor_view_.ForEachMutable([&values, &i](T* value) { + *value = values[i]; + ++i; + }); + } + ToLuaTable(L, tensor_view_); + return 1; } } @@ -298,7 +547,7 @@ class LuaTensor : public lua::Class> { return 1; } return "[Tensor.Narrow] Must contain 1 based dim, index, size " - "recieved: " + + "received: " + lua::ToString(L, 2) + ", " + lua::ToString(L, 3) + ", " + lua::ToString(L, 4); } @@ -307,7 +556,7 @@ class LuaTensor : public lua::Class> { lua::NResultsOr ApplyIndexed(lua_State* L) { lua::NResultsOr err = 0; tensor_view_.ForEachIndexedMutable( - [L, &err](const std::vector& index, T* value) { + [L, &err](const ShapeVector& index, T* value) { lua_pushvalue(L, 2); lua::Push(L, *value); // Convert index to 1 based. @@ -396,6 +645,22 @@ class LuaTensor : public lua::Class> { return 1; } + // Call with an array of integers. The tensor must be contiguous and the + // number of elements in the new shape must match that of the original + // otherwise an error is raised. + // [1, 1, e] + lua::NResultsOr Reshape(lua_State* L) { + auto result = tensor_view_; + ShapeVector new_shape; + if (lua::Read(L, -1, &new_shape) && result.Reshape(std::move(new_shape))) { + LuaTensor::CreateObject(L, std::move(result), storage_validity_); + return 1; + } else { + return "Must be called on a contiguous tensor with a matching element " + "count."; + } + } + // [1, 0, -] lua::NResultsOr Clone(lua_State* L) { std::vector storage; @@ -425,21 +690,42 @@ class LuaTensor : public lua::Class> { return 1; } - // Returns self on to the stack. + // Returns self on to the stack, after the operation is applied in-place. + // [0, 1, -] + template + lua::NResultsOr UnaryOp(lua_State* L) { + (tensor_view_.*Op)(); + return 1; + } + + // Returns self on to the stack, after the operation is applied in-place. // [1, 1, e] template lua::NResultsOr ScalarOp(lua_State* L) { + std::vector values; double value; if (lua::Read(L, 2, &value)) { (tensor_view_.*Op)(value); lua_pop(L, lua_gettop(L) - 1); return 1; + } else if (lua::Read(L, 2, &values)) { + const auto& shape = tensor_view_.shape(); + if (!shape.empty() && values.size() == shape.back()) { + for (std::size_t i = 0; i < values.size(); ++i) { + auto new_view = tensor_view_; + new_view.Select(shape.size() - 1, i); + (new_view.*Op)(values[i]); + } + lua_pop(L, lua_gettop(L) - 1); + return 1; + } } - return "[Tensor.ScalerOp] Must call with number, recieved: " + + return "[Tensor.ScalerOp] Must call with number or an array that matches " + "last dimension received: " + lua::ToString(L, 2); } - // Returns self on to the stack. + // Returns self on to the stack, after the operation is applied in place. // [1, 1, e] template lua::NResultsOr ViewOp(lua_State* L) { @@ -449,7 +735,7 @@ class LuaTensor : public lua::Class> { return 1; } } - return "[Tensor.ViewOp] Must call with same sized tensor, recieved: " + + return "[Tensor.ViewOp] Must call with same sized tensor, received: " + lua::ToString(L, 2); } @@ -464,10 +750,57 @@ class LuaTensor : public lua::Class> { LuaTensor::CreateObject(L, std::move(result), storage_validity_); return 1; } - return "[Tensor.Transpose] Must contain 1 based indexes, recieved: " + + return "[Tensor.Transpose] Must contain 1 based indexes, received: " + lua::ToString(L, 2) + ", " + lua::ToString(L, 3); } + // Retrieves a tensor operand 'rhs' from the top of the stack and computes + // the matrix product self * rhs, returning the result on to the stack. + // Fails if any of the operands is not a rank-2 tensor, or their respective + // dimensions are not product-compatible (#colums(self) != #rows(rhs)). + // [1, 1, e] + lua::NResultsOr MMul(lua_State* L) { + if (LuaTensor* rhs = LuaTensor::ReadObject(L, 2)) { + const auto& lhs_shape = tensor_view().shape(); + const auto& rhs_shape = rhs->tensor_view().shape(); + if (lhs_shape.size() != 2) { + return "[Tensor.MMul] LHS is not a matrix"; + } + if (rhs_shape.size() != 2) { + return "[Tensor.MMul] RHS is not a matrix"; + } + ShapeVector shape = {lhs_shape[0], rhs_shape[1]}; + std::vector storage(Layout::num_elements(shape)); + auto* prod = + LuaTensor::CreateObject(L, std::move(shape), std::move(storage)); + if (!prod->mutable_tensor_view()->MMul(tensor_view(), + rhs->tensor_view())) { + return "[Tensor.MMul] incorrect matrix dimensions"; + } + return 1; + } + return std::string("[Tensor.MMul] Must contain 1 RHS tensor of type ") + + ClassName() + ", received: " + lua::ToString(L, 2); + } + + // Retrieves a random bit generator (random) from the top of the stack and + // shuffles the elements of self (in-place) using a permutation computed with + // such generator. + // Fails if self is not a rank-1 tensor or if the parameter provided is not + // a generator. + // Returns self on to the stack. + // [1, 1, e] + lua::NResultsOr Shuffle(lua_State* L) { + LuaRandom* random = LuaRandom::ReadObject(L, 2); + if (random && tensor_view_.Shuffle(random->GetPrbg())) { + lua_pop(L, lua_gettop(L) - 1); + return 1; + } + return "[Tensor.Shuffle] Must call on a rank-1 Tensor with random number " + "generator, received: " + + lua::ToString(L, 2); + } + lua::NResultsOr ToString(lua_State* L) { std::ostringstream ss; ss << "[" << ClassName() << "]\n"; diff --git a/deepmind/tensor/lua_tensor_benchmark.cc b/deepmind/tensor/lua_tensor_benchmark.cc new file mode 100644 index 00000000..9df54959 --- /dev/null +++ b/deepmind/tensor/lua_tensor_benchmark.cc @@ -0,0 +1,140 @@ +#include "deepmind/tensor/lua_tensor.h" + +#include "testing/base/public/benchmark.h" +#include "deepmind/lua/call.h" +#include "deepmind/lua/lua.h" +#include "deepmind/lua/n_results_or_test_util.h" +#include "deepmind/lua/push_script.h" +#include "deepmind/lua/vm.h" + +namespace deepmind { +namespace lab { +namespace { + +void PerformOperation(benchmark::State& state, const char* ctor, + const char* operation) { + using lua::testing::IsOkAndHolds; + auto lua_vm = lua::CreateVm(); + auto* L = lua_vm.get(); + LuaRandom::Register(L); + tensor::LuaTensorRegister(L); + lua_vm.AddCModuleToSearchers("dmlab.system.tensor", + tensor::LuaTensorConstructors); + + ASSERT_THAT(lua::PushScript(L, ctor, "Construction"), IsOkAndHolds(1)); + ASSERT_EQ(1, lua_gettop(L)); + + ASSERT_THAT(lua::Call(L, 0), IsOkAndHolds(2)); + ASSERT_EQ(2, lua_gettop(L)); + + ASSERT_THAT(lua::PushScript(L, operation, "Operation"), IsOkAndHolds(1)); + + ASSERT_EQ(3, lua_gettop(L)); + + // Lua stack: bt1, bt2, function + for (auto _ : state) { + lua_pushvalue(L, -1); + // Lua stack: bt1, bt2, function, function, + lua_pushvalue(L, -4); + // Lua stack: bt1, bt2, function, function, bt1 + lua_pushvalue(L, -4); + // Lua stack: bt1, bt2, function, function, bt1, bt2 + ASSERT_THAT(lua::Call(L, 2), IsOkAndHolds(0)); + // Lua stack: bt1, bt2, function + } + lua_pop(L, 3); +} + +// Constructors: + +constexpr char kTwoContigTensors[] = R"( +local tensor = require 'dmlab.system.tensor' +return tensor.ByteTensor(1000, 1000, 2), tensor.ByteTensor(1000, 1000, 2) +)"; + +constexpr char kContigNonContigTensors[] = R"( +local tensor = require 'dmlab.system.tensor' +local contiguous = tensor.ByteTensor(1000, 1000, 2) +local nonContiguous = tensor.ByteTensor(1000, 1000, 3):narrow(3, 1, 2) +return nonContiguous, contiguous +)"; + +constexpr char kTwoNonContigTensors[] = R"( +local tensor = require 'dmlab.system.tensor' +local contiguous = tensor.ByteTensor(1000, 1000, 3):narrow(3, 1, 2) +local nonContiguous = tensor.ByteTensor(1000, 1000, 3):narrow(3, 1, 2) +return nonContiguous, contiguous +)"; + +// Operations: + +constexpr char kCopyTensors[] = R"( +local bt1, bt2 = ... +bt1:copy(bt2) +)"; + +constexpr char kCopyTensorsBySlice[] = R"( +local bt1, bt2 = ... +bt1:select(3, 1):copy(bt2:select(3, 1)) +bt1:select(3, 2):copy(bt2:select(3, 2)) +)"; + +constexpr char kDoubleTensors[] = R"( +local bt1, bt2 = ... +bt1:mul(2) +bt2:mul(2) +)"; + +constexpr char kDoubleBySlice[] = R"( +local bt1, bt2 = ... +bt1:select(3,1):mul(2) +bt1:select(3,2):mul(2) +bt2:select(3,1):mul(2) +bt2:select(3,2):mul(2) +)"; + +void BM_ContiguousTensorCopy(benchmark::State& state) { + PerformOperation(state, kTwoContigTensors, kCopyTensors); +} + +BENCHMARK(BM_ContiguousTensorCopy); + +void BM_ContiguousToNonContiguousCopy(benchmark::State& state) { + PerformOperation(state, kContigNonContigTensors, kCopyTensors); +} + +BENCHMARK(BM_ContiguousToNonContiguousCopy); + +void BM_TwoNonContiguousCopy(benchmark::State& state) { + PerformOperation(state, kTwoNonContigTensors, kCopyTensors); +} + +BENCHMARK(BM_TwoNonContiguousCopy); + +void BM_TwoNonContiguousCopyBySlice(benchmark::State& state) { + PerformOperation(state, kTwoNonContigTensors, kCopyTensorsBySlice); +} + +BENCHMARK(BM_TwoNonContiguousCopyBySlice); + +void BM_ContiguousTensorDouble(benchmark::State& state) { + PerformOperation(state, kTwoContigTensors, kDoubleTensors); +} + +BENCHMARK(BM_ContiguousTensorDouble); + +void BM_NonContiguousTensorDouble(benchmark::State& state) { + PerformOperation(state, kTwoNonContigTensors, kDoubleTensors); +} + +BENCHMARK(BM_NonContiguousTensorDouble); + +void BM_NonContiguousTensorDoubleBySlice(benchmark::State& state) { + PerformOperation(state, kTwoNonContigTensors, kDoubleBySlice); +} + +BENCHMARK(BM_NonContiguousTensorDoubleBySlice); + +} // namespace +} // namespace lab +} // namespace deepmind diff --git a/deepmind/tensor/lua_tensor_test.cc b/deepmind/tensor/lua_tensor_test.cc index 83d6d68b..1f29b06e 100644 --- a/deepmind/tensor/lua_tensor_test.cc +++ b/deepmind/tensor/lua_tensor_test.cc @@ -19,10 +19,12 @@ #include "deepmind/tensor/lua_tensor.h" #include +#include #include #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "deepmind/lua/bind.h" #include "deepmind/lua/call.h" #include "deepmind/lua/n_results_or_test_util.h" #include "deepmind/lua/push_script.h" @@ -35,16 +37,23 @@ namespace lab { namespace { using ::deepmind::lab::lua::testing::IsOkAndHolds; -using ::testing::HasSubstr; +using ::deepmind::lab::lua::testing::StatusIs; +using ::testing::AllOf; using ::testing::ElementsAre; +using ::testing::HasSubstr; class LuaTensorTest : public ::testing::Test { protected: LuaTensorTest() : lua_vm_(lua::CreateVm()) { - tensor::LuaTensorRegister(lua_vm_.get()); + auto* L = lua_vm_.get(); + LuaRandom::Register(L); + lua_vm_.AddCModuleToSearchers("dmlab.system.sys_random", + &lua::Bind, {&prbg_}); + tensor::LuaTensorRegister(L); lua_vm_.AddCModuleToSearchers("dmlab.system.tensor", tensor::LuaTensorConstructors); } + std::mt19937_64 prbg_; lua::Vm lua_vm_; }; @@ -97,6 +106,47 @@ TEST_F(LuaTensorTest, CreateTensor) { }); } +constexpr char kLuaTensorRange[] = R"( +local tensor = require 'dmlab.system.tensor' + +local v0, v1, v2, v3, v4 +v0 = tensor.DoubleTensor{range = {5}} +v1 = tensor.DoubleTensor{range = {2, 5}} +v2 = tensor.DoubleTensor{range = {2, 5, 0.5}} +v3 = tensor.DoubleTensor{range = {2, 4.9, 0.5}} +v4 = tensor.DoubleTensor{range = {2, -1, -1}} +assert(v0 == tensor.DoubleTensor{1, 2, 3, 4, 5}) +assert(v1 == tensor.DoubleTensor{2, 3, 4, 5}) +assert(v2 == tensor.DoubleTensor{2, 2.5, 3, 3.5, 4, 4.5, 5}) +assert(v3 == tensor.DoubleTensor{2, 2.5, 3, 3.5, 4, 4.5}) +assert(v4 == tensor.DoubleTensor{2, 1, 0, -1}) +)"; + +TEST_F(LuaTensorTest, TensorRange) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kLuaTensorRange, sizeof(kLuaTensorRange) - 1, + "kLuaTensorRange"), + IsOkAndHolds(1)); + ASSERT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); +} + +constexpr char kInvalidLuaTensorRange[] = R"( +local tensor = require 'dmlab.system.tensor' + +local v0 = tensor.DoubleTensor{range={1, -5}} +)"; + +TEST_F(LuaTensorTest, InvalidLuaTensorRange) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kInvalidLuaTensorRange, + sizeof(kInvalidLuaTensorRange) - 1, + "kInvalidLuaTensorRange"), + IsOkAndHolds(1)); + auto result = lua::Call(L, 0); + EXPECT_FALSE(result.ok()); + EXPECT_THAT(result.error(), HasSubstr("Invalid Tensor range.")); +} + constexpr char kValueOps[] = R"( local tensor = require 'dmlab.system.tensor' @@ -230,9 +280,9 @@ TEST_F(LuaTensorTest, Narrow) { constexpr char kApply[] = R"( local tensor = require 'dmlab.system.tensor' local bt = tensor.ByteTensor{{1, 2}, {3, 4}, {5, 6}} -local bt_apply = bt:apply(function(val) return val + 3 end) +local btApply = bt:apply(function(val) return val + 3 end) local apply = tensor.ByteTensor{{4, 5}, {6, 7}, {8, 9}} -assert (bt_apply == apply) +assert (btApply == apply) )"; TEST_F(LuaTensorTest, Apply) { @@ -245,11 +295,11 @@ TEST_F(LuaTensorTest, Apply) { constexpr char kApplyIndexed[] = R"( local tensor = require 'dmlab.system.tensor' local bt = tensor.ByteTensor{{1, 2}, {3, 4}, {5, 6}} -local bt_apply = bt:applyIndexed(function(val, index) +local btApply = bt:applyIndexed(function(val, index) return index[1] * index[2] + val end) local apply = tensor.ByteTensor{{2, 4}, {5, 8}, {8, 12}} -assert (bt_apply == apply) +assert (btApply == apply) )"; TEST_F(LuaTensorTest, ApplyIndexed) { @@ -264,9 +314,9 @@ TEST_F(LuaTensorTest, ApplyIndexed) { constexpr char kFill[] = R"( local tensor = require 'dmlab.system.tensor' local bt = tensor.ByteTensor{{1, 2}, {3, 4}, {5, 6}} -local bt_apply = bt:fill(10) +local btApply = bt:fill(10) local apply = tensor.ByteTensor{{10, 10}, {10, 10}, {10, 10}} -assert (bt_apply == apply) +assert (btApply == apply) )"; TEST_F(LuaTensorTest, Fill) { @@ -276,6 +326,22 @@ TEST_F(LuaTensorTest, Fill) { ASSERT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); } +constexpr char kFillTable[] = R"( +local tensor = require 'dmlab.system.tensor' +local bt = tensor.ByteTensor{{1, 2}, {3, 4}, {5, 6}} +local btApply = bt:fill{10, 12} +local apply = tensor.ByteTensor{{10, 12}, {10, 12}, {10, 12}} +assert (btApply == apply) +)"; + +TEST_F(LuaTensorTest, FillTable) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT( + lua::PushScript(L, kFillTable, sizeof(kFillTable) - 1, "kFillTable"), + IsOkAndHolds(1)); + ASSERT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); +} + constexpr char kVal[] = R"( local tensor = require 'dmlab.system.tensor' local bt = tensor.ByteTensor(3, 3) @@ -307,6 +373,23 @@ TEST_F(LuaTensorTest, kScalarOp) { ASSERT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); } +constexpr char kScalarOpTable[] = R"( +local tensor = require 'dmlab.system.tensor' +local bt = tensor.ByteTensor{{1, 2}, {3, 4}, {5, 6}} +assert (bt:mul{2, 4} == tensor.ByteTensor{{2, 8}, {6, 16}, {10, 24}}, "1") +assert (bt:add{2, 4} == tensor.ByteTensor{{4, 12}, {8, 20}, {12, 28}}, "2") +assert (bt:div{2, 4} == tensor.ByteTensor{{2, 3}, {4, 5}, {6, 7}}, "3") +assert (bt:sub{2, 3} == tensor.ByteTensor{{0, 0}, {2, 2}, {4, 4}}, "4") +)"; + +TEST_F(LuaTensorTest, kScalarOpTable) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kScalarOpTable, sizeof(kScalarOpTable) - 1, + "kScalarOpTable"), + IsOkAndHolds(1)); + ASSERT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); +} + constexpr char kComponentOp[] = R"( local tensor = require 'dmlab.system.tensor' local bt = tensor.ByteTensor{{1, 2}, {3, 4}, {5, 6}} @@ -423,6 +506,399 @@ TEST_F(LuaTensorTest, kTestToString) { EXPECT_EQ(0, bt_alt->tensor_view().num_elements()); } +constexpr char kMMulOpLHSNonMatrix[] = R"( +local tensor = require 'dmlab.system.tensor' +local at = tensor.FloatTensor(2, 2, 2) +local bt = tensor.FloatTensor(2, 3) +return at:mmul(bt) +)"; + +TEST_F(LuaTensorTest, kMMulOpLHSNonMatrix) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT( + lua::PushScript(L, kMMulOpLHSNonMatrix, sizeof(kMMulOpLHSNonMatrix) - 1, + "kMMulOpLHSNonMatrix"), + IsOkAndHolds(1)); + auto result = lua::Call(L, 0); + EXPECT_FALSE(result.ok()); + EXPECT_THAT(result.error(), HasSubstr("LHS is not a matrix")); +} + +constexpr char kMMulOpRHSNonMatrix[] = R"( +local tensor = require 'dmlab.system.tensor' +local at = tensor.FloatTensor(2, 2, 2) +local bt = tensor.FloatTensor(2, 3) +return bt:mmul(at) +)"; + +TEST_F(LuaTensorTest, kMMulOpRHSNonMatrix) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT( + lua::PushScript(L, kMMulOpRHSNonMatrix, sizeof(kMMulOpRHSNonMatrix) - 1, + "kMMulOpRHSNonMatrix"), + IsOkAndHolds(1)); + auto result = lua::Call(L, 0); + EXPECT_FALSE(result.ok()); + EXPECT_THAT(result.error(), HasSubstr("RHS is not a matrix")); +} + +constexpr char kMMulOpIncompatibleDims[] = R"( +local tensor = require 'dmlab.system.tensor' +local at = tensor.FloatTensor(2, 3) +local bt = tensor.FloatTensor(2, 2) +return at:mmul(bt) +)"; + +TEST_F(LuaTensorTest, kMMulOpIncompatibleDims) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kMMulOpIncompatibleDims, + sizeof(kMMulOpIncompatibleDims) - 1, + "kMMulOpIncompatibleDims"), + IsOkAndHolds(1)); + auto result = lua::Call(L, 0); + EXPECT_FALSE(result.ok()); + EXPECT_THAT(result.error(), HasSubstr("incorrect matrix dimensions")); +} + +constexpr char kMMulOpIncompatibleType[] = R"( +local tensor = require 'dmlab.system.tensor' +local at = tensor.FloatTensor(2, 2) +local bt = tensor.ByteTensor(2, 2) +return at:mmul(bt) +)"; + +TEST_F(LuaTensorTest, kMMulOpIncompatibleType) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kMMulOpIncompatibleType, + sizeof(kMMulOpIncompatibleType) - 1, + "kMMulOpIncompatibleType"), + IsOkAndHolds(1)); + auto result = lua::Call(L, 0); + EXPECT_FALSE(result.ok()); + EXPECT_THAT( + result.error(), + HasSubstr( + "Must contain 1 RHS tensor of type deepmind.lab.tensor.FloatTensor")); +} + +constexpr char kRoundingOps[] = R"( +local tensor = require 'dmlab.system.tensor' +local bt = tensor.DoubleTensor{{-2.25, -1.75}, {0.5, 1.0}} +assert (bt:clone():floor() == tensor.DoubleTensor{{-3.0, -2.0}, {0.0, 1.0}}) +assert (bt:clone():ceil() == tensor.DoubleTensor{{-2.0, -1.0}, {1.0, 1.0}}) +assert (bt:clone():round() == tensor.DoubleTensor{{-2.0, -2.0}, {1.0, 1.0}}) +)"; + +TEST_F(LuaTensorTest, kRoundingOps) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kRoundingOps, sizeof(kRoundingOps) - 1, + "kRoundingOps"), + IsOkAndHolds(1)); + ASSERT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); +} + +constexpr char kShuffle[] = R"( +local sys_random = require 'dmlab.system.sys_random' +local tensor = require 'dmlab.system.tensor' + +sys_random:seed(123) +local at = tensor.Int64Tensor{range={5}}:shuffle(sys_random) +local bt = tensor.Tensor{0, 0, 0, 0, 0} +for i = 1,5 do + local j = at(i):val() + bt(j):val(1) +end +assert (bt == tensor.Tensor{1, 1, 1, 1, 1}) +local ct = tensor.Tensor{0}:narrow(1, 1, 0):shuffle(sys_random) +assert (ct:shape()[1] == 0) +)"; + +TEST_F(LuaTensorTest, kShuffle) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kShuffle, sizeof(kShuffle) - 1, "kShuffle"), + IsOkAndHolds(1)); + ASSERT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); +} + +constexpr char kReshape[] = R"( +local tensor = require 'dmlab.system.tensor' +local bt = tensor.ByteTensor{{1, 2}, {3, 4}, {5, 6}} +assert(bt:reshape{6} == tensor.ByteTensor{1, 2, 3, 4, 5, 6}) +assert(bt:reshape{2, 3} == tensor.ByteTensor{{1, 2, 3}, {4, 5, 6}}) +assert(bt:reshape{6, 1} == tensor.ByteTensor{{1}, {2}, {3}, {4}, {5}, {6}}) +assert(bt:reshape{1, 6} == tensor.ByteTensor{{1, 2, 3, 4, 5, 6}}) +)"; + +TEST_F(LuaTensorTest, Reshape) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kReshape, sizeof(kReshape) - 1, "kReshape"), + IsOkAndHolds(1)); + ASSERT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); +} + +constexpr char kValTableRead[] = R"( +local tensor = require 'dmlab.system.tensor' +local bt = tensor.ByteTensor{{1, 2}, {3, 4}, {5, 6}} +local asTable = bt:val() +for i = 1, 3 do + for j = 1, 2 do + assert(asTable[i][j] == (i - 1) * 2 + j) + end +end +)"; + +TEST_F(LuaTensorTest, kValTableRead) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kValTableRead, sizeof(kValTableRead) - 1, + "kValTableRead"), + IsOkAndHolds(1)); + ASSERT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); +} + +constexpr char kValTableWrite[] = R"( +local tensor = require 'dmlab.system.tensor' +local bt = tensor.ByteTensor{{1, 2}, {3, 4}, {5, 6}} +bt(1):val{1, 1} +bt(2):val{2, 2} +bt(3):val{3, 3} +for i = 1, 3 do + for j = 1, 2 do + assert(bt(i, j):val() == i) + end +end +)"; + +TEST_F(LuaTensorTest, kValTableWrite) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kValTableWrite, sizeof(kValTableWrite) - 1, + "kValTableWrite"), + IsOkAndHolds(1)); + ASSERT_THAT(lua::Call(L, 0), IsOkAndHolds(0)); +} + +template +void CreateFileWith64IncrementingValues(const std::string& filename) { + std::ofstream ofs(filename, std::ios::binary); + for (T val = 0; val < 64; ++val) { + ofs.write(reinterpret_cast(&val), sizeof val); + } +} + +std::string CreateFourRawFiles() { + std::string temp_dir = testing::TempDir() + "/"; + CreateFileWith64IncrementingValues(temp_dir + "bytes.bin"); + CreateFileWith64IncrementingValues(temp_dir + "doubles.bin"); + CreateFileWith64IncrementingValues(temp_dir + "int64s.bin"); + CreateFileWith64IncrementingValues(temp_dir + "floats.bin"); + return temp_dir; +} + +std::string CreateBytesRawFile() { + std::string temp_dir = testing::TempDir() + "/"; + CreateFileWith64IncrementingValues(temp_dir + "bytes.bin"); + return temp_dir; +} + +constexpr char kLoadWholeFile[] = R"( +local tensor = require 'dmlab.system.tensor' +local path = ... +assert(tensor.ByteTensor{file = {name = path .. 'bytes.bin'}} + == tensor.ByteTensor{range = {0, 63}}) +assert(tensor.DoubleTensor{file = {name = path .. 'doubles.bin'}} + == tensor.DoubleTensor{range = {0, 63}}) +assert(tensor.Int64Tensor{file = {name = path .. 'int64s.bin'}} + == tensor.Int64Tensor{range = {0, 63}}) +assert(tensor.FloatTensor{file = {name = path .. 'floats.bin'}} + == tensor.FloatTensor{range = {0, 63}}) +)"; + +TEST_F(LuaTensorTest, kLoadWholeFile) { + lua_State* L = lua_vm_.get(); + + ASSERT_THAT(lua::PushScript(L, kLoadWholeFile, sizeof(kLoadWholeFile) - 1, + "kLoadWholeFile"), + IsOkAndHolds(1)); + lua::Push(L, CreateFourRawFiles()); + ASSERT_THAT(lua::Call(L, 1), IsOkAndHolds(0)); +} + +constexpr char kLoadStartFile[] = R"( +local tensor = require 'dmlab.system.tensor' +local path = ... +assert(tensor.ByteTensor{file = + {name = path .. 'bytes.bin', numElements = 10} + } == tensor.ByteTensor{range = {0, 9}}) +assert(tensor.DoubleTensor{file = + {name = path .. 'doubles.bin', numElements = 10} + } == tensor.DoubleTensor{range = {0, 9}}) +assert(tensor.Int64Tensor{file = + {name = path .. 'int64s.bin', numElements = 10} + } == tensor.Int64Tensor{range = {0, 9}}) +assert(tensor.FloatTensor{file = + {name = path .. 'floats.bin', numElements = 10} + } == tensor.FloatTensor{range = {0, 9}}) +)"; + +TEST_F(LuaTensorTest, kLoadStartFile) { + lua_State* L = lua_vm_.get(); + + ASSERT_THAT(lua::PushScript(L, kLoadStartFile, sizeof(kLoadStartFile) - 1, + "kLoadStartFile"), + IsOkAndHolds(1)); + lua::Push(L, CreateFourRawFiles()); + ASSERT_THAT(lua::Call(L, 1), IsOkAndHolds(0)); +} + +constexpr char kLoadEndFile[] = R"( +local tensor = require 'dmlab.system.tensor' +local path = ... +assert(tensor.ByteTensor{file = + {name = path .. 'bytes.bin', byteOffset = 40 * 1} + } == tensor.ByteTensor{range = {40, 63}}) +assert(tensor.DoubleTensor{file = + {name = path .. 'doubles.bin', byteOffset = 40 * 8} + } == tensor.DoubleTensor{range = {40, 63}}) +assert(tensor.Int64Tensor{file = + {name = path .. 'int64s.bin', byteOffset = 40 * 8} + } == tensor.Int64Tensor{range = {40, 63}}) +assert(tensor.FloatTensor{file = + {name = path .. 'floats.bin', byteOffset = 40 * 4} + } == tensor.FloatTensor{range = {40, 63}}) +)"; + +TEST_F(LuaTensorTest, kLoadEndFile) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kLoadEndFile, sizeof(kLoadEndFile) - 1, + "kLoadEndFile"), + IsOkAndHolds(1)); + lua::Push(L, CreateFourRawFiles()); + ASSERT_THAT(lua::Call(L, 1), IsOkAndHolds(0)); +} + +constexpr char kLoadMiddleFile[] = R"( +local tensor = require 'dmlab.system.tensor' +local path = ... +assert(tensor.ByteTensor{file = + {name = path .. 'bytes.bin', byteOffset = 40 * 1, numElements = 6} + } == tensor.ByteTensor{range = {40, 45}}) +assert(tensor.DoubleTensor{file = + {name = path .. 'doubles.bin', byteOffset = 40 * 8, numElements = 6} + } == tensor.DoubleTensor{range = {40, 45}}) +assert(tensor.Int64Tensor{file = + {name = path .. 'int64s.bin', byteOffset = 40 * 8, numElements = 6} + } == tensor.Int64Tensor{range = {40, 45}}) +assert(tensor.FloatTensor{file = + {name = path .. 'floats.bin', byteOffset = 40 * 4, numElements = 6} + } == tensor.FloatTensor{range = {40, 45}}) +)"; + +TEST_F(LuaTensorTest, kLoadMiddleFile) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kLoadMiddleFile, sizeof(kLoadMiddleFile) - 1, + "kLoadMiddleFile"), + IsOkAndHolds(1)); + lua::Push(L, CreateFourRawFiles()); + ASSERT_THAT(lua::Call(L, 1), IsOkAndHolds(0)); +} + +constexpr char kBadFileName[] = R"( +local tensor = require 'dmlab.system.tensor' +local path = ... +return tensor.ByteTensor{file = {name = path .. 'bad_file.bin'}} +)"; + +TEST_F(LuaTensorTest, kBadFileName) { + std::string temp_dir = testing::TempDir() + "/"; + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kBadFileName, sizeof(kBadFileName) - 1, + "kBadFileName"), + IsOkAndHolds(1)); + lua::Push(L, CreateBytesRawFile()); + ASSERT_THAT(lua::Call(L, 1), StatusIs(HasSubstr("bad_file.bin"))); +} + +constexpr char kBadNumElements[] = R"( +local tensor = require 'dmlab.system.tensor' +local path = ... +return tensor.ByteTensor{file = {name = path .. 'bytes.bin', numElements = 65}} +)"; + +TEST_F(LuaTensorTest, kBadNumElements) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kBadNumElements, sizeof(kBadNumElements) - 1, + "kBadNumElements"), + IsOkAndHolds(1)); + lua::Push(L, CreateBytesRawFile()); + ASSERT_THAT(lua::Call(L, 1), + StatusIs(AllOf(HasSubstr("numElements"), HasSubstr("65")))); +} + +constexpr char kBadNumElementsOffset[] = R"( +local tensor = require 'dmlab.system.tensor' +local path = ... +return tensor.ByteTensor{ + file = {name = path .. 'bytes.bin', byteOffset = 1, numElements = 64} +} +)"; + +TEST_F(LuaTensorTest, kBadNumElementsOffset) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kBadNumElementsOffset, + sizeof(kBadNumElementsOffset) - 1, + "kBadNumElementsOffset"), + IsOkAndHolds(1)); + lua::Push(L, CreateBytesRawFile()); + ASSERT_THAT(lua::Call(L, 1), + StatusIs(AllOf(HasSubstr("numElements"), HasSubstr("63")))); +} + +constexpr char kBadNumElementsNegative[] = R"( +local tensor = require 'dmlab.system.tensor' +local path = ... +return tensor.ByteTensor{file = {name = path .. 'bytes.bin', numElements = -1}} +)"; + +TEST_F(LuaTensorTest, kBadNumElementsNegative) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kBadNumElementsNegative, + sizeof(kBadNumElementsNegative) - 1, + "kBadNumElementsNegative"), + IsOkAndHolds(1)); + lua::Push(L, CreateBytesRawFile()); + ASSERT_THAT(lua::Call(L, 1), + StatusIs(AllOf(HasSubstr("numElements"), HasSubstr("-1")))); +} + +constexpr char kBadByteOffset[] = R"( +local tensor = require 'dmlab.system.tensor' +local path = ... +return tensor.ByteTensor{file = {name = path .. 'bytes.bin', byteOffset = 65}} +)"; + +TEST_F(LuaTensorTest, kBadByteOffset) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kBadByteOffset, sizeof(kBadByteOffset) - 1, + "kBadByteOffset"), + IsOkAndHolds(1)); + lua::Push(L, CreateBytesRawFile()); + ASSERT_THAT(lua::Call(L, 1), StatusIs(HasSubstr("byteOffset"))); +} + +constexpr char kBadByteOffsetNegative[] = R"( +local tensor = require 'dmlab.system.tensor' +local path = ... +return tensor.ByteTensor{file = {name = path .. 'bytes.bin', byteOffset = -1}} +)"; + +TEST_F(LuaTensorTest, kBadByteOffsetNegative) { + lua_State* L = lua_vm_.get(); + ASSERT_THAT(lua::PushScript(L, kBadByteOffsetNegative, + sizeof(kBadByteOffsetNegative) - 1, + "kBadByteOffsetNegative"), + IsOkAndHolds(1)); + lua::Push(L, CreateBytesRawFile()); + ASSERT_THAT(lua::Call(L, 1), StatusIs(HasSubstr("byteOffset"))); +} } // namespace } // namespace lab } // namespace deepmind diff --git a/deepmind/tensor/tensor_view.cc b/deepmind/tensor/tensor_view.cc index 54449a1d..d172dbf8 100644 --- a/deepmind/tensor/tensor_view.cc +++ b/deepmind/tensor/tensor_view.cc @@ -61,7 +61,7 @@ void Layout::PrintToStream( }); ForEachIndexedOffset([os, &s, &printer, max_width]( - const std::vector& index, std::size_t offset) { + const ShapeVector& index, std::size_t offset) { int open_brackets = std::distance( index.rbegin(), std::find_if(index.rbegin(), index.rend(), [](std::size_t val) { return val != 0; })); diff --git a/deepmind/tensor/tensor_view.h b/deepmind/tensor/tensor_view.h index 6756dc2a..55ac6051 100644 --- a/deepmind/tensor/tensor_view.h +++ b/deepmind/tensor/tensor_view.h @@ -20,18 +20,25 @@ #define DML_DEEPMIND_TENSOR_TENSOR_VIEW_H_ #include +#include #include #include #include #include #include +#include #include #include +#include "Eigen/Dense" + namespace deepmind { namespace lab { namespace tensor { +using ShapeVector = std::vector; +using StrideVector = std::vector; + // Class for calculating offsets into storage for a tensor. // Supports functions which do not require manipulation of the storage data. // Can have any stride but the default is to have strides in row-major @@ -41,7 +48,7 @@ class Layout { // Constructs a layout with the shape and stride such that: // ..., shape[n - 2] * shape[n - 1], shape[n - 1], 1 // So if shape is {3, 4, 5} - stride is {20, 5, 1}. - explicit Layout(std::vector shape) + explicit Layout(ShapeVector shape) : shape_(std::move(shape)), offset_(0) { if (!shape_.empty()) { stride_.reserve(shape_.size()); @@ -116,15 +123,15 @@ class Layout { } } - const std::vector& shape() const { return shape_; } - const std::vector& stride() const { return stride_; } + const ShapeVector& shape() const { return shape_; } + const StrideVector& stride() const { return stride_; } const std::size_t start_offset() const { return offset_; } // Returns the product of the shape. std::size_t num_elements() const { return num_elements(shape()); } // Returns how many elements the Layout requires for a given shape. - static std::size_t num_elements(const std::vector& shape) { + static std::size_t num_elements(const ShapeVector& shape) { return shape.empty() ? 0 : std::accumulate(shape.begin(), shape.end(), 1, std::multiplies()); } @@ -145,8 +152,9 @@ class Layout { // Visit all indexes and offsets in Layout order. template void ForEachIndexedOffset(F&& f) const { - auto it = MakeIterator(true /*require_index*/); - for (std::size_t i = 0; i < it.num; ++i) { + std::size_t num = num_elements(); + auto it = MakeIterator(); + for (std::size_t i = 0; i < num; ++i) { f(it.index, it.offset); Next(&it); } @@ -157,13 +165,38 @@ class Layout { // order, otherwise there is no effect. template bool PairwiseForEachOffset(const Layout& rhs, F&& f) const { - auto l_iter = MakeIterator(false /*require_index*/); - auto r_iter = rhs.MakeIterator(false /*require_index*/); - if (l_iter.num != r_iter.num) { return false; } - for (std::size_t i = 0; i < l_iter.num; ++i) { - f(l_iter.offset, r_iter.offset); - Next(&l_iter); - rhs.Next(&r_iter); + std::size_t l_num_elements = num_elements(); + std::size_t r_num_elements = rhs.num_elements(); + if (l_num_elements != r_num_elements) { + return false; + } + std::ptrdiff_t l_contig = ContiguousStride(); + std::ptrdiff_t r_contig = rhs.ContiguousStride(); + if (l_contig != 0 && r_contig != 0) { + std::ptrdiff_t l_offset = offset_; + std::ptrdiff_t r_offset = rhs.offset_; + for (std::size_t i = 0; i < l_num_elements; ++i) { + f(i * l_contig + l_offset, i * r_contig + r_offset); + } + } else if (l_contig != 0) { + std::ptrdiff_t l_offset = offset_; + auto r_it = rhs.MakeIterator(); + for (std::size_t i = 0; i < l_num_elements; ++i, rhs.Next(&r_it)) { + f(i * l_contig + l_offset, r_it.offset); + } + } else if (r_contig != 0) { + auto l_it = MakeIterator(); + std::ptrdiff_t r_offset = rhs.offset_; + for (std::size_t i = 0; i < l_num_elements; ++i, Next(&l_it)) { + f(l_it.offset, i * r_contig + r_offset); + } + } else { + auto l_it = MakeIterator(); + auto r_it = rhs.MakeIterator(); + for (std::size_t i = 0; i < l_num_elements; + ++i, Next(&r_it), rhs.Next(&r_it)) { + f(l_it.offset, r_it.offset); + } } return true; } @@ -174,17 +207,46 @@ class Layout { // If a call to 'p' returns false, no further calls to 'p' are made. template bool AllOf(const Layout& rhs, F&& f) const { - auto l_iter = MakeIterator(false /*require_index*/); - auto r_iter = rhs.MakeIterator(false /*require_index*/); - if (l_iter.num != r_iter.num) { + std::size_t l_num_elements = num_elements(); + std::size_t r_num_elements = rhs.num_elements(); + if (l_num_elements != r_num_elements) { return false; } - for (std::size_t i = 0; i < l_iter.num; ++i) { - if (!f(l_iter.offset, r_iter.offset)) { - return false; + std::ptrdiff_t l_contig = ContiguousStride(); + std::ptrdiff_t r_contig = rhs.ContiguousStride(); + if (l_contig != 0 && r_contig != 0) { + std::ptrdiff_t l_offset = offset_; + std::ptrdiff_t r_offset = rhs.offset_; + for (std::size_t i = 0; i < l_num_elements; ++i) { + if (!f(i * l_contig + l_offset, i * r_contig + r_offset)) { + return false; + } + } + } else if (l_contig != 0) { + std::ptrdiff_t l_offset = offset_; + auto r_it = rhs.MakeIterator(); + for (std::size_t i = 0; i < l_num_elements; ++i, rhs.Next(&r_it)) { + if (!f(i * l_contig + l_offset, r_it.offset)) { + return false; + } + } + } else if (r_contig != 0) { + auto l_it = MakeIterator(); + std::ptrdiff_t r_offset = rhs.offset_; + for (std::size_t i = 0; i < l_num_elements; ++i, Next(&l_it)) { + if (!f(l_it.offset, i * r_contig + r_offset)) { + return false; + } + } + } else { + auto l_it = MakeIterator(); + auto r_it = rhs.MakeIterator(); + for (std::size_t i = 0; i < l_num_elements; + ++i, Next(&r_it), rhs.Next(&r_it)) { + if (!f(l_it.offset, r_it.offset)) { + return false; + } } - Next(&l_iter); - rhs.Next(&r_iter); } return true; } @@ -192,16 +254,24 @@ class Layout { // Visit all offsets in Layout order. template void ForEachOffset(F&& f) const { - auto it = MakeIterator(false /*require_index*/); - for (std::size_t i = 0; i < it.num; ++i) { - f(it.offset); - Next(&it); + std::size_t num = num_elements(); + std::ptrdiff_t contig = ContiguousStride(); + if (contig != 0) { + std::ptrdiff_t offset = offset_; + for (std::size_t i = 0; i < num; ++i) { + f(i * contig + offset); + } + } else { + auto it = MakeIterator(); + for (std::size_t i = 0; i < num; ++i, Next(&it)) { + f(it.offset); + } } } // Returns whether an index is valid. // If it is valid offset is set to the position that index represents. - bool GetOffset(const std::vector& index, + bool GetOffset(const ShapeVector& index, std::size_t* offset) const { if (index.size() != stride_.size()) { return false; } std::size_t local_offset = offset_; @@ -216,7 +286,7 @@ class Layout { // Returns whether the current Layout has a constant stride and number of // elements in new_shape matches num_elements(). If true the new_shape is set // and stride calculated. Otherwise this call has no effect. - bool Reshape(std::vector new_shape) { + bool Reshape(ShapeVector new_shape) { std::size_t new_size = new_shape.empty() ? 0 : std::accumulate(new_shape.begin(), new_shape.end(), @@ -260,58 +330,46 @@ class Layout { struct OffsetIterator { std::size_t offset; // 'index' cannot be used if is_contiguous is true. - std::vector index; + ShapeVector index; std::size_t num; std::size_t pos; std::ptrdiff_t stride; bool is_contiguous; }; + struct WindowIterator { + std::size_t offset; + std::size_t pos; + ShapeVector index; + const std::size_t back_idx; + }; + // Creates an iterator for visiting all offsets. - // The code has runs faster if we don't require an index and the layout has a - // contiguous stride. - OffsetIterator MakeIterator(bool require_index) const { - std::ptrdiff_t contigous_stride = ContiguousStride(); - bool is_contiguous = contigous_stride != 0 && !require_index; - return {offset_, - std::vector(is_contiguous ? 0 : shape_.size()), - num_elements(), - 0, - contigous_stride, - is_contiguous}; - } - - // If there is a next element this updates the offset. - // If the iterator requires an index that is updated to the current position - // too. - void Next(OffsetIterator* iterator) const { - if (iterator->pos + 1 == iterator->num) { - return; - } - if (iterator->is_contiguous) { - ++iterator->pos; - iterator->offset += iterator->stride; - } else { - ++iterator->pos; - std::size_t back_idx = shape_.size() - 1; - ++iterator->index[back_idx]; - iterator->offset += stride_[back_idx]; - for (; back_idx != 0 && iterator->index[back_idx] == shape_[back_idx]; - --back_idx) { - iterator->offset -= stride_[back_idx] * shape_[back_idx]; - iterator->index[back_idx] = 0; - iterator->offset += stride_[back_idx - 1]; - ++iterator->index[back_idx - 1]; - } + WindowIterator MakeIterator() const { + return {offset_, 0, ShapeVector(shape_.size()), + shape_.empty() ? 0 : shape_.size() - 1}; + } + + // Updates the iterator. Must not be called more than num_elements times. + void Next(WindowIterator* iterator) const { + ++iterator->pos; + std::size_t back_idx = iterator->back_idx; + ++iterator->index[back_idx]; + iterator->offset += stride_[back_idx]; + for (; back_idx != 0 && iterator->index[back_idx] == shape_[back_idx]; + --back_idx) { + iterator->offset -= stride_[back_idx] * shape_[back_idx]; + iterator->index[back_idx] = 0; + iterator->offset += stride_[back_idx - 1]; + ++iterator->index[back_idx - 1]; } - return; } // Stores the shape in row major order. - std::vector shape_; + ShapeVector shape_; // Stores the stride in row major order. - std::vector stride_; + StrideVector stride_; // Stores the start offset of this layout. std::size_t offset_; @@ -334,7 +392,7 @@ class TensorView : public Layout { void ForEachIndexedMutable(F&& f) { T* storage = storage_; ForEachIndexedOffset( - [&f, storage](const std::vector& iterator, + [&f, storage](const ShapeVector& iterator, std::size_t offset) { f(iterator, &storage[offset]); }); } @@ -344,7 +402,7 @@ class TensorView : public Layout { void ForEachIndexed(F&& f) const { const T* storage = storage_; ForEachIndexedOffset( - [&f, storage](const std::vector& iterator, + [&f, storage](const ShapeVector& iterator, std::size_t offset) { f(iterator, storage[offset]); }); } @@ -454,11 +512,85 @@ class TensorView : public Layout { return ComponentOpMutable(rhs, [](T* v_lhs, U v_rhs) { *v_lhs -= v_rhs; }); } + // Compute the matrix product of 'lhs' and 'rhs' and store it in '*this'. + template + bool MMul(const TensorView& lhs, const TensorView& rhs) { + const auto& lhs_shape = lhs.shape(); + const auto& rhs_shape = rhs.shape(); + if (lhs_shape.size() != 2 || rhs_shape.size() != 2 || + lhs_shape[1] != rhs_shape[0] || shape().size() != 2 || + shape()[0] != lhs_shape[0] || shape()[1] != rhs_shape[1]) { + return false; + } + // Map the storage used by the input tensors as Eigen matrices. + using MatrixXU = + Eigen::Matrix; + using MatrixXUStride = Eigen::Stride; + using MatrixXUInMap = Eigen::Map; + const auto& lhs_stride = lhs.stride(); + MatrixXUInMap lhs_map(lhs.storage() + lhs.start_offset(), // + lhs_shape[0], lhs_shape[1], // + MatrixXUStride(lhs_stride[0], lhs_stride[1])); + const auto& rhs_stride = rhs.stride(); + MatrixXUInMap rhs_map(rhs.storage() + rhs.start_offset(), // + rhs_shape[0], rhs_shape[1], // + MatrixXUStride(rhs_stride[0], rhs_stride[1])); + // Map the storage used by the output vector as an Eigen matrix. + using MatrixXUOutMap = Eigen::Map; + const auto& prod_stride = stride(); + MatrixXUOutMap prod_map(storage_ + start_offset(), // + lhs_shape[0], rhs_shape[1], // + MatrixXUStride(prod_stride[0], prod_stride[1])); + if (lhs.storage() == storage_ || rhs.storage() == storage_) { + // If there is potential aliasing between any of the operands and the + // output, use eval() to compute the product on temporary storage before + // writing it to the output tensor. + prod_map = (lhs_map * rhs_map).eval(); + } else { + // No aliasing is possible, compute the product directly on to the output + // tensor. + prod_map = lhs_map * rhs_map; + } + return true; + } + + // Assigns '*this' component-wise to 'floor(rhs)'. + void Floor() { + ForEachMutable([](T* val) { *val = std::floor(*val); }); + } + + // Assigns '*this' component-wise to 'ceil(rhs)'. + void Ceil() { + ForEachMutable([](T* val) { *val = std::ceil(*val); }); + } + + // Assigns '*this' component-wise to 'round(rhs)'. + void Round() { + ForEachMutable([](T* val) { *val = std::round(*val); }); + } + + // Shuffles a rank-1 tensor. + // If the tensor is rank-1, it shuffles its elements using the provided random + // bit generator and returns true, otherwise it returns false. + template + bool Shuffle(R* prbg) { + if (shape().size() == 1) { + for (std::size_t i = 1, e = shape()[0]; i < e; ++i) { + std::uniform_int_distribution d(0, e - i); + std::swap(storage_[start_offset() + stride()[0] * (e - i)], + storage_[start_offset() + stride()[0] * d(*prbg)]); + } + return true; + } else { + return false; + } + } + // All the following getters and setters return whether the index is valid // If the index is invalid it results in a no-op. // // Assigns 'value' in location 'index'. - bool Set(const std::vector& index, T value) { + bool Set(const ShapeVector& index, T value) { std::size_t offset; if (GetOffset(index, &offset)) { storage_[offset] = value; @@ -470,7 +602,7 @@ class TensorView : public Layout { // Iff the index is valid, retrieve the value at 'index' into 'value'. // Returns whether the index is valid. - bool Get(const std::vector& index, T* value) const { + bool Get(const ShapeVector& index, T* value) const { std::size_t offset; if (GetOffset(index, &offset)) { *value = storage_[offset]; @@ -480,7 +612,7 @@ class TensorView : public Layout { } } - // 1D overload of Set(const std::vector& index, T value). + // 1D overload of Set(const ShapeVector& index, T value). // Iff the index is valid, set the value at 'index' to 'value'. // Returns whether the index is valid. bool Set(std::size_t index, T value) { @@ -492,7 +624,7 @@ class TensorView : public Layout { } } - // 1D overload of Get(const std::vector& index, T* value). + // 1D overload of Get(const ShapeVector& index, T* value). // Iff the index is valid, retrieve the value at 'index' into 'value'. // Returns whether the index is valid. bool Get(std::size_t index, T* value) const { diff --git a/deepmind/tensor/tensor_view_test.cc b/deepmind/tensor/tensor_view_test.cc index a89b6f4a..40b5b2c0 100644 --- a/deepmind/tensor/tensor_view_test.cc +++ b/deepmind/tensor/tensor_view_test.cc @@ -18,6 +18,8 @@ #include "deepmind/tensor/tensor_view.h" +#include + #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -26,6 +28,7 @@ namespace lab { namespace tensor { namespace { +using ::testing::UnorderedElementsAre; using ::testing::ElementsAre; using ::testing::HasSubstr; @@ -40,7 +43,7 @@ std::vector MakeSequence(std::size_t num_elements) { } TEST(TensorViewTest, Layout) { - std::vector shape = {4, 3}; + ShapeVector shape = {4, 3}; std::vector storage(Layout::num_elements(shape)); TensorView byte_tensor_view(Layout(shape), storage.data()); EXPECT_THAT(byte_tensor_view.stride(), ElementsAre(3, 1)); @@ -62,7 +65,7 @@ TEST(TensorViewTest, Layout) { } TEST(TensorViewTest, NonContiguous) { - std::vector shape = {7, 5, 2}; + ShapeVector shape = {7, 5, 2}; std::vector storage = MakeSequence(Layout::num_elements(shape)); TensorView int_tensor_view(Layout(shape), storage.data()); ASSERT_TRUE(int_tensor_view.Narrow(0, 1, 5)); @@ -81,7 +84,7 @@ TEST(TensorViewTest, NonContiguous) { } TEST(TensorViewTest, Reshape) { - std::vector shape = {3, 8}; + ShapeVector shape = {3, 8}; std::vector storage = MakeSequence(Layout::num_elements(shape)); TensorView view(Layout(std::move(shape)), storage.data()); EXPECT_TRUE(view.Reshape({4, 6})); @@ -93,7 +96,7 @@ TEST(TensorViewTest, Reshape) { } TEST(TensorViewTest, ForEach) { - std::vector shape = {5, 2}; + ShapeVector shape = {5, 2}; std::vector storage = MakeSequence(Layout::num_elements(shape)); TensorView view(Layout(std::move(shape)), storage.data()); EXPECT_EQ(10U, view.num_elements()); @@ -115,10 +118,10 @@ TEST(TensorViewTest, ForEach) { } TEST(TensorViewTest, ForEachIndexed) { - std::vector shape = {5, 2}; + ShapeVector shape = {5, 2}; std::vector storage = MakeSequence(Layout::num_elements(shape)); TensorView view(Layout(std::move(shape)), storage.data()); - view.ForEachIndexed([](const std::vector& index, std::size_t v) { + view.ForEachIndexed([](const ShapeVector& index, std::size_t v) { EXPECT_EQ(2U, index.size()); EXPECT_EQ(v / 2, index[0]); EXPECT_EQ(v % 2, index[1]); @@ -126,10 +129,10 @@ TEST(TensorViewTest, ForEachIndexed) { } TEST(TensorViewTest, ForEachIndexedMutable) { - std::vector shape = {5, 2}; + ShapeVector shape = {5, 2}; std::vector storage = MakeSequence(Layout::num_elements(shape)); TensorView view(Layout(std::move(shape)), storage.data()); - view.ForEachIndexedMutable([](const std::vector& index, int* v) { + view.ForEachIndexedMutable([](const ShapeVector& index, int* v) { EXPECT_THAT(index, ElementsAre(*v / 2, *v % 2)); *v = index[1]; }); @@ -137,12 +140,12 @@ TEST(TensorViewTest, ForEachIndexedMutable) { } TEST(TensorViewTest, TestAssign) { - std::vector shape1 = {3, 4}; + ShapeVector shape1 = {3, 4}; std::vector storage1(Layout::num_elements(shape1)); TensorView view1(Layout(std::move(shape1)), storage1.data()); view1.Assign(2.0); - std::vector shape2 = {2, 6}; + ShapeVector shape2 = {2, 6}; std::vector storage2 = MakeSequence(Layout::num_elements(shape2)); TensorView view2(Layout(std::move(shape2)), storage2.data()); @@ -151,7 +154,7 @@ TEST(TensorViewTest, TestAssign) { } TEST(TensorViewTest, TestAdd) { - std::vector shape = {2, 2}; + ShapeVector shape = {2, 2}; std::vector storage = MakeSequence(Layout::num_elements(shape)); TensorView view(Layout(std::move(shape)), storage.data()); view.Add(1); @@ -161,7 +164,7 @@ TEST(TensorViewTest, TestAdd) { } TEST(TensorViewTest, TestMul) { - std::vector shape = {2, 2}; + ShapeVector shape = {2, 2}; std::vector storage = MakeSequence(Layout::num_elements(shape)); TensorView view(Layout(std::move(shape)), storage.data()); view.Add(1); @@ -172,7 +175,7 @@ TEST(TensorViewTest, TestMul) { } TEST(TensorViewTest, TestDiv) { - std::vector shape = {2, 2}; + ShapeVector shape = {2, 2}; std::vector storage = MakeSequence(Layout::num_elements(shape)); TensorView view(Layout(std::move(shape)), storage.data()); view.Add(1); @@ -183,7 +186,7 @@ TEST(TensorViewTest, TestDiv) { } TEST(TensorViewTest, TestSub) { - std::vector shape = {2, 2}; + ShapeVector shape = {2, 2}; std::vector storage = MakeSequence(Layout::num_elements(shape)); TensorView view(Layout(std::move(shape)), storage.data()); view.Sub(2); @@ -193,7 +196,7 @@ TEST(TensorViewTest, TestSub) { } TEST(TensorViewTest, TestGet) { - std::vector shape = {5, 5}; + ShapeVector shape = {5, 5}; std::vector storage = MakeSequence(Layout::num_elements(shape)); TensorView view(Layout(std::move(shape)), storage.data()); float value; @@ -208,8 +211,111 @@ TEST(TensorViewTest, TestGet) { ASSERT_FALSE(view.Get(5, &value)); } +TEST(TensorViewTest, TestMMulFloat) { + ShapeVector lhs_shape = {2, 2}; + std::vector lhs_storage = + MakeSequence(Layout::num_elements(lhs_shape)); + TensorView lhs_view(Layout(std::move(lhs_shape)), lhs_storage.data()); + ShapeVector rhs_shape = {2, 3}; + std::vector rhs_storage = + MakeSequence(Layout::num_elements(rhs_shape)); + TensorView rhs_view(Layout(std::move(rhs_shape)), rhs_storage.data()); + ShapeVector shape = {2, 3}; + std::vector storage = MakeSequence(Layout::num_elements(shape)); + TensorView view(Layout(std::move(shape)), storage.data()); + ASSERT_TRUE(view.MMul(lhs_view, rhs_view)); + EXPECT_THAT(storage, ElementsAre(3.0f, 4.0f, 5.0f, 9.0f, 14.0f, 19.0f)); +} + +TEST(TensorViewTest, TestMMulByte) { + ShapeVector lhs_shape = {2, 2}; + std::vector lhs_storage = + MakeSequence(Layout::num_elements(lhs_shape)); + TensorView lhs_view(Layout(std::move(lhs_shape)), lhs_storage.data()); + ShapeVector rhs_shape = {2, 3}; + std::vector rhs_storage = + MakeSequence(Layout::num_elements(rhs_shape)); + TensorView rhs_view(Layout(std::move(rhs_shape)), rhs_storage.data()); + ShapeVector shape = {2, 3}; + std::vector storage = MakeSequence(Layout::num_elements(shape)); + TensorView view(Layout(std::move(shape)), storage.data()); + ASSERT_TRUE(view.MMul(lhs_view, rhs_view)); + EXPECT_THAT(storage, ElementsAre(3, 4, 5, 9, 14, 19)); +} + +TEST(TensorViewTest, TestMMulOverlap) { + ShapeVector lhs_shape = {2, 2}; + std::vector lhs_storage = + MakeSequence(Layout::num_elements(lhs_shape)); + TensorView lhs_view(Layout(std::move(lhs_shape)), lhs_storage.data()); + ShapeVector rhs_shape = {2, 2}; + std::vector rhs_storage = + MakeSequence(Layout::num_elements(rhs_shape)); + TensorView rhs_view(Layout(std::move(rhs_shape)), rhs_storage.data()); + ASSERT_TRUE(lhs_view.MMul(lhs_view, rhs_view)); + EXPECT_THAT(lhs_storage, ElementsAre(2, 3, 6, 11)); +} + +TEST(TensorViewTest, TestMMulOverlapNarrow) { + ShapeVector lhs_shape = {2, 3}; + std::vector lhs_storage = + MakeSequence(Layout::num_elements(lhs_shape)); + TensorView lhs_view(Layout(std::move(lhs_shape)), lhs_storage.data()); + ASSERT_TRUE(lhs_view.Narrow(1, 1, 2)); + ASSERT_EQ(lhs_view.shape()[0], 2); + ASSERT_EQ(lhs_view.shape()[1], 2); + ShapeVector rhs_shape = {2, 2}; + std::vector rhs_storage = + MakeSequence(Layout::num_elements(rhs_shape)); + TensorView rhs_view(Layout(std::move(rhs_shape)), rhs_storage.data()); + ASSERT_TRUE(lhs_view.MMul(lhs_view, rhs_view)); + EXPECT_THAT(lhs_storage, ElementsAre(0, 4, 7, 3, 10, 19)); +} + +TEST(TensorViewTest, TestFloor) { + ShapeVector shape = {2, 2}; + std::vector storage = {-2.25, -1.75, 0.5, 1.0}; + TensorView view(Layout(std::move(shape)), storage.data()); + view.Floor(); + EXPECT_THAT(storage, ElementsAre(-3.0, -2.0, 0.0, 1.0)); +} + +TEST(TensorViewTest, TestCeil) { + ShapeVector shape = {2, 2}; + std::vector storage = {-2.25, -1.75, 0.5, 1.0}; + TensorView view(Layout(std::move(shape)), storage.data()); + view.Ceil(); + EXPECT_THAT(storage, ElementsAre(-2.0, -1.0, 1.0, 1.0)); +} + +TEST(TensorViewTest, TestRound) { + ShapeVector shape = {2, 2}; + std::vector storage = {-2.25, -1.75, 0.5, 1.0}; + TensorView view(Layout(std::move(shape)), storage.data()); + view.Round(); + EXPECT_THAT(storage, ElementsAre(-2.0, -2.0, 1.0, 1.0)); +} + +TEST(TensorViewTest, TestShuffle) { + ShapeVector shape = {5}; + std::vector storage = {1.0, 2.0, 3.0, 4.0, 5.0}; + TensorView view(Layout(std::move(shape)), storage.data()); + std::mt19937_64 prbg(123); + EXPECT_TRUE(view.Shuffle(&prbg)); + EXPECT_THAT(storage, UnorderedElementsAre(1.0, 2.0, 3.0, 4.0, 5.0)); +} + +TEST(TensorViewTest, TestShuffleEmpty) { + ShapeVector shape = {0}; + std::vector storage = {}; + TensorView view(Layout(std::move(shape)), storage.data()); + std::mt19937_64 prbg(123); + EXPECT_TRUE(view.Shuffle(&prbg)); + EXPECT_TRUE(storage.empty()); +} + TEST(TensorViewTest, TestSet) { - std::vector shape = {3, 3}; + ShapeVector shape = {3, 3}; std::vector storage = MakeSequence(Layout::num_elements(shape)); TensorView view(Layout(std::move(shape)), storage.data()); ASSERT_TRUE(view.Set({1, 1}, 555.0f)); @@ -225,7 +331,7 @@ TEST(TensorViewTest, TestSet) { } TEST(TensorViewTest, TestEqual) { - std::vector shape = {2, 8}; + ShapeVector shape = {2, 8}; std::vector storage1 = MakeSequence(Layout::num_elements(shape)); TensorView view1(Layout(shape), storage1.data()); @@ -243,7 +349,7 @@ TEST(TensorViewTest, TestEqual) { } TEST(TensorViewTest, TestStream) { - std::vector shape = {4, 2}; + ShapeVector shape = {4, 2}; std::vector storage = MakeSequence(Layout::num_elements(shape)); TensorView view(Layout(std::move(shape)), storage.data()); { @@ -284,7 +390,7 @@ TEST(TensorViewTest, TestStream) { } { std::stringstream ss; - std::vector shape_large = {32, 32}; + ShapeVector shape_large = {32, 32}; std::vector storage_large = MakeSequence(Layout::num_elements(shape_large)); TensorView view_large(Layout(std::move(shape_large)), diff --git a/deepmind/util/BUILD b/deepmind/util/BUILD new file mode 100644 index 00000000..953b0eae --- /dev/null +++ b/deepmind/util/BUILD @@ -0,0 +1,43 @@ +# Description: +# General utilities. + +licenses(["restricted"]) # GPLv2 + +# Library for running external executables. +cc_library( + name = "run_executable", + srcs = ["run_executable.cc"], + hdrs = ["run_executable.h"], + visibility = ["//deepmind:__subpackages__"], + deps = [ + "//deepmind/support:logging", + "@com_google_absl//absl/strings", + ], +) + +cc_test( + name = "run_executable_test", + srcs = ["run_executable_test.cc"], + deps = [ + ":run_executable", + "@com_google_googletest//:gtest_main", + ], +) + +# Library for platform-specific file operations. +cc_library( + name = "files", + srcs = ["files.cc"], + hdrs = ["files.h"], + visibility = ["//visibility:public"], + deps = ["@com_google_absl//absl/strings"], +) + +cc_test( + name = "files_test", + srcs = ["files_test.cc"], + deps = [ + ":files", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/deepmind/util/files.cc b/deepmind/util/files.cc new file mode 100644 index 00000000..3758f834 --- /dev/null +++ b/deepmind/util/files.cc @@ -0,0 +1,103 @@ +#include "deepmind/util/files.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace deepmind { +namespace lab { +namespace util { + +extern "C" { +static int delete_entry(const char* fpath, const struct stat* sb, int typeflag, + struct FTW* ftwbuf) { + return (typeflag == FTW_DP ? rmdir : unlink)(fpath); +} +} // extern "C" + +void RemoveDirectory(const std::string& path) { + nftw(path.c_str(), delete_entry, 10, FTW_DEPTH | FTW_PHYS); +} + +// Recursively build the directories for the folder `path`. +bool MakeDirectory(const std::string& path) { + struct stat st = {0}; + if (stat(path.c_str(), &st) == 0) { + return S_ISDIR(st.st_mode); + } + + auto pos = path.find_last_of('/'); + if (pos == std::string::npos || MakeDirectory(path.substr(0, pos))) { + mkdir(path.c_str(), 0777); + } + return stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode); +} + +std::string GetTempDirectory() { + // In unit tests, "TEST_TMPDIR" is the preferred temporary directory. + const char* tempdir = getenv("TEST_TMPDIR"); + if (tempdir == nullptr) { + tempdir = getenv("TMPDIR"); + if (tempdir == nullptr) { + tempdir = "/tmp"; + } + } + return tempdir; +} + +bool SetContents(const std::string& file_name, absl::string_view contents, + const char* scratch_directory) { + std::string temp_file = + (scratch_directory != nullptr && scratch_directory[0] != '\0') + ? std::string(scratch_directory) + : util::GetTempDirectory(); + temp_file += "/dmlab_temp_file_XXXXXX"; + // Thread safe temporary file generation. + { + std::unique_ptr file( + fdopen(mkstemp(&temp_file.front()), "w"), std::fclose); + if (!file) { + std::cerr << "Failed to make temp file! " << errno << " - " + << std::strerror(errno) << "\n"; + return false; + } + if (std::fwrite(contents.data(), 1, contents.size(), file.get()) != + contents.size()) { + std::cerr << "Failed to write to temp file! " << errno << " - " + << std::strerror(errno) << "\n"; + return false; + } + } + + if (std::rename(temp_file.c_str(), file_name.c_str()) != 0) { + std::cerr << "Failed to rename temp file to: " << file_name << " " << errno + << " - " << std::strerror(errno) << "\n"; + std::remove(temp_file.c_str()); + return false; + } + return true; +} + +bool GetContents(const std::string& file_name, std::string* contents) { + std::filebuf fb; + if (fb.open(file_name, std::ios::in | std::ios::binary) == nullptr) { + return false; + } + contents->reserve(fb.pubseekoff(0, std::ios::end)); + fb.pubseekpos(0); + contents->assign(std::istreambuf_iterator(&fb), {}); + return true; +} + +} // namespace util +} // namespace lab +} // namespace deepmind diff --git a/deepmind/util/files.h b/deepmind/util/files.h new file mode 100644 index 00000000..79ab78e3 --- /dev/null +++ b/deepmind/util/files.h @@ -0,0 +1,39 @@ +#ifndef DML_DEEPMIND_UTIL_FILES_H_ +#define DML_DEEPMIND_UTIL_FILES_H_ + +#include + +#include "absl/strings/string_view.h" + +namespace deepmind { +namespace lab { +namespace util { + +// Recursively builds the directory for the `path` specified. +// `path` shall be in canonicalised form. +// Returns whether the folder was successfully made. +bool MakeDirectory(const std::string& path); + +// Recursively removes the directory for the `path` specified. +void RemoveDirectory(const std::string& path); + +// Returns an existing temporary directory. +std::string GetTempDirectory(); + +// A file is written with `contents` into a temporary file first, then renamed +// to `file_name`. The directory name of `file_name` must be an existing +// directory. The temporary file is created in the `scratch_directory` if it is +// not null or empty, otherwise it is created in the system temporary directory. +// Returns whether the file was successfully created with contents. +bool SetContents(const std::string& file_name, absl::string_view contents, + const char* scratch_directory = nullptr); + +// File at `file_name` is read into `contents`. +// Returns whether `file_name` was successfully read. +bool GetContents(const std::string& file_name, std::string* contents); + +} // namespace util +} // namespace lab +} // namespace deepmind + +#endif // DML_DEEPMIND_UTIL_FILES_H_ diff --git a/deepmind/util/files_test.cc b/deepmind/util/files_test.cc new file mode 100644 index 00000000..a5080e72 --- /dev/null +++ b/deepmind/util/files_test.cc @@ -0,0 +1,80 @@ +#include +#include +#include +#include + +#include + +#include "gtest/gtest.h" +#include "deepmind/util/files.h" + +namespace deepmind { +namespace lab { +namespace util { +namespace { + +std::string TestName() { + return ::testing::UnitTest::GetInstance()->current_test_info()->name(); +} + +TEST(FilesTest, RemovesDirectory) { + // Ensures RemoveDirectory removes directories. + std::string root_path(GetTempDirectory() + "/" + TestName()); + mkdir(root_path.c_str(), 0666); + std::string new_path(root_path + "/b"); + mkdir(new_path.c_str(), 0666); + new_path += "/c"; + creat(new_path.c_str(), 0666); + + EXPECT_EQ(access(root_path.c_str(), F_OK), 0); + + RemoveDirectory(root_path); + + EXPECT_EQ(access(root_path.c_str(), F_OK), -1); +} + +TEST(FilesTest, RemovesDirectoryNoSymlink) { + // Ensures RemoveDirectory does not follow symlinks. + std::string temp_directory = GetTempDirectory(); + std::string root_path(GetTempDirectory() + "/" + TestName()); + mkdir(root_path.c_str(), 0666); + std::string new_path(root_path + "/b"); + mkdir(new_path.c_str(), 0666); + new_path += "/c"; + creat(new_path.c_str(), 0666); + std::string link_path(temp_directory + "/l"); + symlink(root_path.c_str(), link_path.c_str()); + RemoveDirectory(link_path); + EXPECT_EQ(access(root_path.c_str(), F_OK), 0); + RemoveDirectory(root_path); +} + +TEST(FilesTest, CreateAndRemoveDirectory) { + // Ensures RemoveDirectory does not follow symlinks. + std::string root_path(GetTempDirectory() + "/" + TestName()); + std::string new_path(root_path + "/b/b/b"); + EXPECT_TRUE(MakeDirectory(new_path)); + RemoveDirectory(root_path); +} + +TEST(FilesTest, CreateFileWithContents) { + std::string temp_dir = GetTempDirectory(); + std::string root_path(temp_dir + "/dmlab_files_test"); + ASSERT_TRUE(MakeDirectory(root_path)); + + std::string contents(1000000, '@'); + SetContents(root_path + "/content", contents, temp_dir.c_str()); + + std::string result; + GetContents(root_path + "/content", &result); + + // Don't print large file. + EXPECT_TRUE(result == contents); + + RemoveDirectory(root_path); +} + +} // namespace +} // namespace util +} // namespace lab +} // namespace deepmind diff --git a/deepmind/util/run_executable.cc b/deepmind/util/run_executable.cc new file mode 100644 index 00000000..ad7e727d --- /dev/null +++ b/deepmind/util/run_executable.cc @@ -0,0 +1,79 @@ +#include "deepmind/util/run_executable.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "deepmind/support/logging.h" +#include "absl/strings/str_cat.h" + +namespace { + +// Produce a human-readable description of the platform-dependent result of +// running the command line on the system. On our Linux, uses the semantics of +// wait(2). +bool ParseStatus(int s, std::string* msg) { + if (s == -1) { + LOG(QFATAL) << "Failed to call the system. " << std::strerror(errno); + } else if (WIFEXITED(s)) { + int retval = WEXITSTATUS(s); + if (retval == 0) { + *msg = "exited successfully (return value 0)"; + return true; + } else if (retval == 127) { + *msg = absl::StrCat("system() failed to run command. ", retval); + return false; + } else { + *msg = absl::StrCat("exited with failure, return value ", retval); + return false; + } + } else if (WIFSIGNALED(s)) { + int signum = WTERMSIG(s); + *msg = absl::StrCat("exited with signal ", signum); + return false; + } else { + LOG(QFATAL) << "The system returned something implausible."; + } +} + +} // namespace + +namespace deepmind { +namespace lab { +namespace util { + +bool RunExecutable(const char* command_line, std::string* message) { + CHECK(command_line != nullptr) << "Must provide command_line!"; + LOG(INFO) << "Running command:\n" << command_line << "\n"; + return ParseStatus(std::system(command_line), message); +} + +bool RunExecutableWithOutput(const char* command_line, std::string* message, + std::string* output) { + CHECK(command_line != nullptr) << "Must provide command_line!"; + int& err = errno; + err = 0; + if (FILE* pipe = popen(command_line, "r")) { + std::array buffer; + while (size_t read = std::fread(buffer.data(), 1, buffer.size(), pipe)) { + output->append(buffer.data(), read); + } + return ParseStatus(pclose(pipe), message); + } else { + *message = "Failed to run command!\n"; + if (err != 0) { + *message += std::strerror(err); + } + return false; + } +} + +} // namespace util +} // namespace lab +} // namespace deepmind diff --git a/deepmind/util/run_executable.h b/deepmind/util/run_executable.h new file mode 100644 index 00000000..fe52fe8a --- /dev/null +++ b/deepmind/util/run_executable.h @@ -0,0 +1,29 @@ +#ifndef DML_DEEPMIND_UTIL_RUN_EXECUTABLE_H_ +#define DML_DEEPMIND_UTIL_RUN_EXECUTABLE_H_ + +#include + +namespace deepmind { +namespace lab { +namespace util { + +// Runs the provided 'command_line' on the system. +// 'message' will contain a human-readable description of the platform-dependent +// result of running the command. On our Linux, uses the semantics of +// wait(2). +// Returns whether the command ran successfully. +bool RunExecutable(const char* command_line, std::string* message); + +// Runs the provided 'command_line' on the system. +// 'output' will be appended with the output of stdout. +// 'message' will contain a human-readable description of the platform-dependent +// result of running the command. On our Linux, uses the semantics of +// wait(2). +// Returns whether the command ran successfully. +bool RunExecutableWithOutput(const char* command_line, std::string* message, + std::string* output); + +} // namespace util +} // namespace lab +} // namespace deepmind +#endif // DML_DEEPMIND_UTIL_RUN_EXECUTABLE_H_ diff --git a/deepmind/util/run_executable_test.cc b/deepmind/util/run_executable_test.cc new file mode 100644 index 00000000..6e6147d3 --- /dev/null +++ b/deepmind/util/run_executable_test.cc @@ -0,0 +1,52 @@ +#include "deepmind/util/run_executable.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace deepmind { +namespace lab { +namespace { + +using ::testing::HasSubstr; + +TEST(RunExecutableTest, Success) { + std::string message; + EXPECT_TRUE(util::RunExecutable("exit 0", &message)); + EXPECT_THAT(message, HasSubstr("success")); +} + +TEST(RunExecutableTest, ReturnError) { + std::string message; + EXPECT_FALSE(util::RunExecutable("exit 13", &message)); + EXPECT_THAT(message, HasSubstr("fail")); + EXPECT_THAT(message, HasSubstr("13")); +} + +TEST(RunExecutableTest, CannotFindCommand) { + std::string message; + EXPECT_FALSE(util::RunExecutable("./invalid_command", &message)); + EXPECT_THAT(message, HasSubstr("fail")); + EXPECT_THAT(message, HasSubstr("127")); +} + +TEST(RunExecutableTest, WithOutput) { + std::string message; + std::string output; + EXPECT_TRUE(util::RunExecutableWithOutput("echo Hello", &message, &output)); + EXPECT_EQ(output, "Hello\n"); + EXPECT_THAT(message, HasSubstr("success")); +} + +TEST(RunExecutableTest, WithOutputAndError) { + std::string message; + std::string output; + EXPECT_FALSE(util::RunExecutableWithOutput("(echo Hello; exit 17)", &message, + &output)); + EXPECT_EQ(output, "Hello\n"); + EXPECT_THAT(message, HasSubstr("fail")); + EXPECT_THAT(message, HasSubstr("17")); +} + +} // namespace +} // namespace lab +} // namespace deepmind diff --git a/docs/developers/creating_levels.md b/docs/developers/creating_levels.md new file mode 100644 index 00000000..bbc3898a --- /dev/null +++ b/docs/developers/creating_levels.md @@ -0,0 +1,29 @@ +# Creating Levels + +Levels are Lua scripts loaded from the `assets/game_scripts` directory. + +## Design Pattern + +All logic is put into a `factory` in `assets/game_scripts/factories`. +That factory is then instantiated with a small, data-only script. + +For example, `assets/game_scripts/platforms_easy.lua` instantiates the +logic in `assets/games_scripts/factories/platform_factory.lua` with +`difficulty = 0.1`. + +```lua +local factory = require 'factories.platform_factory' + +return factory.createLevelApi{ + difficulty = 0.1, +} +``` + +## Details + +* [Text Levels](/docs/developers/creating_levels/text_level.md) + for how to convert ASCII text maps into levels. +* [Model Generation](/docs/developers/creating_levels/model_generation.md) + for how models and their variants are placed in levels. +* [Level Generation](/docs/developers/creating_levels/level_generation.md) + for details on the levels generation pipeline. diff --git a/docs/level_generation.md b/docs/developers/creating_levels/level_generation.md similarity index 87% rename from docs/level_generation.md rename to docs/developers/creating_levels/level_generation.md index 81cb2cbb..f9189e64 100644 --- a/docs/level_generation.md +++ b/docs/developers/creating_levels/level_generation.md @@ -1,9 +1,3 @@ -(Switch to: [Lua](lua_api.md) · [Python](python_api.md) · - Level Generation · - [Tensor](tensor.md) · [Text Levels](text_level.md) · - [Build](build.md) · - [Known Issues](issues.md)) - # Level generation Levels for DeepMind Lab are _Quake III Arena_ levels. They are packaged into `.pk3` diff --git a/docs/developers/creating_levels/model_generation.md b/docs/developers/creating_levels/model_generation.md new file mode 100644 index 00000000..fadcaf96 --- /dev/null +++ b/docs/developers/creating_levels/model_generation.md @@ -0,0 +1,518 @@ +# Model generation {#model_generation} + +The 3D models used to represent reward objects in DeepMind Lab are specified via +the `model` member in the table returned by the _createPickup_ method of the +level API: + +```Lua +local api = {} + +function api:createPickup(classname) + return { + name = 'Apple', + classname = 'apple_reward', + model = 'models/apple.md3', + quantity = 1, + type = pickups.type.kReward + } +end + +return api +``` + +In most cases, the `model` string references the file containing the 3D model +data in a native _Quake III Arena_ file format, such as MD3. These models must +be created in a Digital Content Creator tool such as Blender, and the exported +model files must be included in any of the `.pk3` files deployed alongside +DeepMind Lab. + +The asset pipeline just described precludes programmatic generation of 3D +models. This functionality would be desirable in order to create environments +where agents are presented with randomly generated reward objects. For instance, +an environment where agents are expected to recognise objects in the shape of a +chair with varying properties such as the shape of the backrest or the number of +legs. + +Whenever the runtime cannot resolve the asset referenced by `model`, it will +invoke the _createModel_ method of the level API with the contents of that +string. This allows defining 3D Models dynamically from Lua, using a low level +description consisting of a collection of polygonal surfaces. These polygonal +surfaces are Lua tables with the following members: + +* `vertices`: a 2D FloatTensor with rows of vertex data, each with 8 + components - 3 position coordinates, 3 normal coordinates and 2 texture + coordinates +* `indices`: a 2D Int32Tensor with rows of index data, each with 3 + components - the vertex indices for each triangular face +* `shaderName`: the shader used to render the surface + +In addition, a model can also define a set of 3D frames of reference, called +'locators', which identify notable points and are used to combine several models +together. These frames are expressed as homogeneous 3D transformation matrices +(4x4), stored in column-major order. + +In the example below, the level API defines a unit cube for a custom reward +object, with a single locator pointing upwards from the centre of its top face: + +```Lua +local api = {} + +function api:createPickup(classname) + return { + name = 'Custom cube', + classname = 'custom_cube', + model = 'customCubeModel', + quantity = 1, + type = pickups.type.kReward + } +end + +local kCustomModels = { + customCubeModel = { + surfaces = { + cubeSurface = { + vertices = tensor.FloatTensor{ + { -0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 0.0, 0.0 }, + { 0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 1.0, 0.0 }, + { 0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 1.0, 1.0 }, + { -0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 0.0, 1.0 }, + { 0.5, -0.5, -0.5, 1.0, 0.0, 0.0, 0.0, 0.0 }, + { 0.5, -0.5, 0.5, 1.0, 0.0, 0.0, 1.0, 0.0 }, + { 0.5, 0.5, 0.5, 1.0, 0.0, 0.0, 1.0, 1.0 }, + { 0.5, 0.5, -0.5, 1.0, 0.0, 0.0, 0.0, 1.0 }, + { 0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 0.0, 0.0 }, + { -0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 0.0 }, + { -0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 1.0 }, + { 0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 0.0, 1.0 }, + { -0.5, -0.5, 0.5, -1.0, 0.0, 0.0, 0.0, 0.0 }, + { -0.5, -0.5, -0.5, -1.0, 0.0, 0.0, 1.0, 0.0 }, + { -0.5, 0.5, -0.5, -1.0, 0.0, 0.0, 1.0, 1.0 }, + { -0.5, 0.5, 0.5, -1.0, 0.0, 0.0, 0.0, 1.0 }, + { -0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 0.0, 0.0 }, + { 0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 1.0, 0.0 }, + { 0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 1.0, 1.0 }, + { -0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 1.0 }, + { 0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 0.0, 0.0 }, + { -0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 1.0, 0.0 }, + { -0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 1.0, 1.0 }, + { 0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 0.0, 1.0 } + }, + indices = tensor.Int32Tensor{ + { 1, 2, 3 }, + { 1, 3, 4 }, + { 5, 6, 7 }, + { 5, 7, 8 }, + { 9, 10, 11 }, + { 9, 11, 12 }, + { 13, 14, 15 }, + { 13, 15, 16 }, + { 17, 18, 19 }, + { 17, 19, 20 }, + { 21, 22, 23 }, + { 21, 23, 24 } + }, + shaderName = 'textures/model/beam_d' + } + }, + locators = { + topFaceOutwards = tensor.FloatTensor{ + { 1.0, 0.0, 0.0, 0.0 }, + { 0.0, 1.0, 0.0, 0.0 }, + { 0.0, 0.0, 1.0, 0.5 }, + { 0.0, 0.0, 0.0, 1.0 } + } + } + } +} + +function api:createModel(modelName) + return kCustomModels[modelName] +end + +return api +``` + +## Model hierarchies + +The low level 3D model representation expected from `api:createModel` is not +practical for sampling random models. Instead, the `dmlab.systems.model` Lua +library provides a higher level description which can be more easily randomised. +This description defines a model as a collection of parts attached to each other +at specific locators, just like the plugs and sockets of a construction toy set. + +Parts are either 3D models [loaded from a MD3 file](#md3loading) or +parameterised solids from a [set of primitives](#primitives). + +Parts can also be translated and rotated with respect to the locators they are +attached to (any affine transformation is possible). + +Model graphs are constructed hierarchically, starting from the root part and +descending down to the leaves, for instance, in the case of a chair: + +![chair model graph](model_generation_chair_graph.png) + +Parts can be grouped by defining local hierarchies (henceforth referred to as +'groups'). This allows constructing model graphs with more complex topologies +than plain trees (e.g. branch merging). In the example above, this is showcased +by the 'columns' group supporting the 'backrest' part. + +[Layout nodes](#layouts) allow distributing parts in regular arrangements such +as circular of linear patterns. They do this by creating placeholder locators +named 'locator{index}'. Not all placeholders need to be used. + +Primitives and groups define a default set of locators, placed on 27 points +evenly distributed across the bounding volume of the part. They are named +according to the following regular expression: + +``` +{back,centre,front}_{bottom,centre,top}_{left,centre,right}_{s,p} +``` + +where the '_s' and '_p' suffixes stand for socket and plug, respectively. Socket +and plug locators are meant to be placed the same position but have opposing +orientations (sockets are inbound, plugs are outbound). + +In Lua, model hierarchies are constructed using nested tables. The table +corresponding to each node has the following entries: + +* `locator`: name of the socket locator used to attach the model in this node + to its parent. If absent the model is attached using a identity frame placed + at the origin of coordinates. +* `transform`: rank-2 tensor with the coefficients of a 4x4 homogeneous + transformation matrix (using column vector conventions) to be applied to the + node's geometry with respect to the socket locator. If absent no + transformation is applied. +* `model`: model data for the part used in this node, as described in the + [previous section](#model_generation). +* `children`: table which contains the child nodes parented by this node in + the hierarchy, labelled according to the plug locator they attach to. + +Transformation matrices can be easily constructed using the utility functions in +[`common.transform`](#transforms). + +Tables shared across multiple nodes can be declared separately and referenced in +the parent table. + +The graph above can be generated through the following function: + +```Lua +local model = require 'dmlab.system.model' +local transform = require 'common.transform' + +function chairModel() + -- Leg submodel, defined separately for reuse. + local legModel = { + locator = 'centre_bottom_centre_s', + model = model:cylinder{ + radius = 1, + height = 15, + shaderName = 'textures/model/blue_d' + } + } + -- Column submodel, defined separately for reuse. + local colModel = { + locator = 'centre_bottom_centre_s', + model = model:cylinder{ + radius = 1, + height = 10, + shaderName = 'textures/model/yellow_d' + } + } + -- Chair model. + return model:hierarchy{ + -- Use a squashed cylinder for the seat. + model = model:cylinder{ + radius = 10, + height = 2, + shaderName = 'textures/model/red_d' + }, + children = { + -- Seat legs, attached to a circular layout placed at the bottom of the seat. + centre_bottom_centre_p = { + transform = transform.rotateZ(45), + model = model:circularLayout(8, 4), + children = { + layout_0 = legModel, + layout_1 = legModel, + layout_2 = legModel, + layout_3 = legModel + } + }, + -- Seat back. + centre_top_centre_p = { + -- Back columns, grouped together so that they can both parent the back rest. + model = model:hierarchy{ + model = model:circularLayout(8, 12), + children = { + layout_8 = colModel, + layout_10 = colModel + } + }, + children = { + -- Back rest model, attached to the top of the column group. + centre_top_centre_p = { + locator = 'centre_bottom_centre_s', + model = model:cube{ + width = 15, + height = 5, + depth = 2, + shaderName = 'textures/model/blue_d' + } + } + } + } + } + } +end +``` + +The results are as follow: + +![results of chairModel()](model_generation_chair.png) + +In order to produce a wider variety of models, random variables can be easily +introduced in the function above: + +```Lua +function randomChairModel() + local height = random.uniformReal(30.0, 45.0) + local split = random.uniformReal(0.4, 0.7) + local legLength = height * split + local backLength = height * (1.0 - split) + local backSplit = random.uniformReal(0.2, 0.8) + local legBend = random.uniformReal(-10.0, 5.0) + local backBend = random.uniformReal(-20.0, 5.0) + local columnBend = random.uniformReal(-5.0, 5.0) + -- Leg submodel, defined separately for reuse. + local legModel = { + locator = 'centre_bottom_centre_s', + transform = transform.rotateX(legBend), + model = model:cylinder{ + radius = 1, + height = legLength, + shaderName = 'textures/model/blue_d' + } + } + -- Column geometry, defined separately for reuse. + local colGeometry = model:cylinder{ + radius = 1, + height = backSplit * backLength, + shaderName = 'textures/model/yellow_d' + } + -- Chair model. + return model:hierarchy{ + transform = transform.translate{0, 0, 20}, + -- Use a squashed cylinder for the seat. + model = model:cylinder{ + radius = 10, + height = 2, + shaderName = 'textures/model/red_d' + }, + children = { + -- Seat legs, attached to a circular layout placed at the bottom of the seat. + centre_bottom_centre_p = { + transform = transform.rotateZ(45), + model = model:circularLayout(8, 4), + children = { + layout_0 = legModel, + layout_1 = legModel, + layout_2 = legModel, + layout_3 = legModel + } + }, + -- Seat back. + centre_top_centre_p = { + transform = transform.rotateX(backBend), + -- Back columns, grouped together so that they can both parent the back rest. + model = model:hierarchy{ + model = model:circularLayout(8, 12), + children = { + layout_8 = { + transform = transform.rotateY(-columnBend), + locator = 'centre_bottom_centre_s', + model = colGeometry + }, + layout_10 = { + transform = transform.rotateY(columnBend), + locator = 'centre_bottom_centre_s', + model = colGeometry + }, + } + }, + children = { + -- Back rest model, attached to the top of the group model. + centre_top_centre_p = { + locator = 'centre_bottom_centre_s', + model = model:cube{ + width = 15, + height = (1.0 - backSplit) * backLength, + depth = 2, + shaderName = 'textures/model/blue_d' + } + } + } + } + } + } +end +``` + +Which produces various samples like these: + +![results of randomChairModel()](model_generation_chair_random.png) + +In addition to continuous variables, discrete choices such as the number of legs +or the presence of armrests can be used to create an even wider variety. + +## Primitive library {#primitives} + +The `dmlab.systems.model` Lua library provides the following primitives: + +* Cones / elliptical cones: + +```Lua + dmlab.systems.model:cone{ + radius = 5, + height = 10, + shaderName = 'textures/model/yellow_d' + } + + dmlab.systems.model:cone{ + widthRadius = 2, + depthRadius = 2, + height = 15, + shaderName = 'textures/model/yellow_d' + } +``` + +* Cubes / cuboids: + +```Lua + dmlab.systems.model:cube{ + size = 10, + shaderName = 'textures/model/red_d' + } + + dmlab.systems.model:cube{ + width = 5, + depth = 10, + height = 7, + shaderName = 'textures/model/red_d' + } +``` + +* Cylinders / epllictical cylinders: + +```Lua + dmlab.systems.model:cylinder{ + radius = 5, + height = 10, + shaderName = 'textures/model/blue_d' + } + + dmlab.systems.model:cylinder{ + widthRadius = 2, + depthRadius = 2, + height = 15, + shaderName = 'textures/model/blue_d' + } +``` + +* Spheres / ellipsoids: + +```Lua + dmlab.systems.model:sphere{ + radius = 5, + shaderName = 'textures/model/yellow_d' + } + + dmlab.systems.model:sphere{ + widthRadius = 2, + depthRadius = 6, + heightRadius = 4, + shaderName = 'textures/model/yellow_d' +} + +``` + +Additional parameters (`widthSegments`, `depthSegments`, `heightSegments`, +`phiSegments`, `thetaSegments`, `radiusSegments`) allow controlling the density +of polygonisation. The defaults provided are valid for most purposes. + +## Transformation utilities {#transform} + +The `common.transform` Lua library provides a series of utility functions to +create common transformation matrices: + +* Translation: + +```Lua + common.transform.translate{offsetX, offsetY, offsetZ} +``` + +* Rotation around an arbitrary axis: + +```Lua + common.transform.rotate(angleInDegrees, {axisX, axisY, axisZ}) +``` + +* Rotation around the X axis: + +```Lua + common.transform.rotateX(angleInDegrees) +``` + +* Rotation around the Y axis: + +```Lua + common.transform.rotateY(angleInDegrees) +``` + +* Rotation around the Z axis: + +```Lua + common.transform.rotateZ(angleInDegrees) +``` + +* Scaling: + +```Lua + common.transform.scale{scaleFactorX, scaleFactorY, scaleFactorZ} +``` + +Concatenating transformations is possible using the matrix multiplication +operator for tensors: + +```Lua + common.transform.rotateX(180):mmul(common.transform.translate{0, 10, 0}), +``` + +## Layout nodes {#layouts} + +The `dmlab.systems.model` Lua library provides the following layout nodes: + +* Circular: + +```Lua + dmlab.systems.model:circularLayout(radius, numberOfSamples) +``` + +* Linear: + +```Lua + dmlab.systems.model:linearLayout(length, numberOfSamples) +``` + +## MD3 loading {#md3loading} + +Lua model data can be loaded from MD3 files using the +`dmlab.system.model:loadMD3` function, e.g.: + +```Lua + dmlab.system.model:loadMD3('models/apple.md3') +``` + +the requested MD3 files must be present in the PK3 files build with DeepMind +Lab, and the engine filesystem must be initialised before this function can be +used. Avoid declaring models using MD3 parts as Lua globals to avoid this latter +caveat. diff --git a/docs/developers/creating_levels/model_generation_chair.png b/docs/developers/creating_levels/model_generation_chair.png new file mode 100644 index 00000000..dd4bd72e Binary files /dev/null and b/docs/developers/creating_levels/model_generation_chair.png differ diff --git a/docs/developers/creating_levels/model_generation_chair_graph.png b/docs/developers/creating_levels/model_generation_chair_graph.png new file mode 100644 index 00000000..6d6ff17a Binary files /dev/null and b/docs/developers/creating_levels/model_generation_chair_graph.png differ diff --git a/docs/developers/creating_levels/model_generation_chair_random.png b/docs/developers/creating_levels/model_generation_chair_random.png new file mode 100644 index 00000000..1db66d7e Binary files /dev/null and b/docs/developers/creating_levels/model_generation_chair_random.png differ diff --git a/docs/text_level.md b/docs/developers/creating_levels/text_level.md similarity index 93% rename from docs/text_level.md rename to docs/developers/creating_levels/text_level.md index fec9a9fa..3581eab8 100644 --- a/docs/text_level.md +++ b/docs/developers/creating_levels/text_level.md @@ -1,9 +1,3 @@ -(Switch to: [Lua](lua_api.md) · [Python](python_api.md) · - [Level Generation](level_generation.md) · - [Tensor](tensor.md) · Text Levels · - [Build](build.md) · - [Known Issues](issues.md)) - # Text Levels Text levels are DeepMind Lab levels that are compiled from a simple text format, @@ -94,6 +88,21 @@ be configurable or extensible. * A *theme*, a collection of textures used to decorate the map. * Decal frequencies: Random decals are added to adorn the map at a given rate. + * A *skybox*, a textured cube which is always rendered as if its faces were + at an infinite distance from the agent. Whenever the skybox is present the + ceiling of the cells is open. + +### Themes + +These are the themes currently supported: + + * MISHMASH (default) + * TRON + * MINESWEEPER + * TETRIS + * GO + * PACMAN + * INVISIBLE_WALLS ## Randomness @@ -139,15 +148,15 @@ The following default cell values are recognized: **Variation layer:** ``` - - - AAA - AAA - AAA - - - - CCCCCCCC BBB + + + AAA + AAA + AAA + + + + CCCCCCCC BBB ``` This example shows a small map with four rooms separated by doors. The top-left diff --git a/docs/developers/reference.md b/docs/developers/reference.md new file mode 100644 index 00000000..cd91e9ef --- /dev/null +++ b/docs/developers/reference.md @@ -0,0 +1,22 @@ +# Reference + +## Lua + +* [Callbacks](/docs/developers/reference/lua_api.md#lua-callbacks), + functions that hook into various game events. +* [Common helper objects](/docs/developers/reference/lua_api.md#lua-common-helper-objects), + sundry helper functions. +* [The game module](/docs/developers/reference/lua_api.md#the-game-module), + functions to interact with the current game. +* [The events module](/docs/developers/reference/lua_api.md#the-events-module), + functions that report lists of observations. +* [Factory functions](/docs/developers/reference/lua_api.md#factory-functions), + objects used to create levels. +* [Maps](/docs/developers/reference/lua_api.md#maps), + functions to convert a text level to a Quake III Arena map. +* [Rendering](/docs/developers/reference/lua_api.md#rendering), + sundry functions for drawing to the screen. +* [Image](/docs/developers/reference/image.md), + functions for loading and scaling image files into tensors. +* [Tensor](/docs/developers/reference/tensor.md), + a small tensor library for manipulating numerical data in DeepMind Lab. diff --git a/docs/developers/reference/image.md b/docs/developers/reference/image.md new file mode 100644 index 00000000..23f1c568 --- /dev/null +++ b/docs/developers/reference/image.md @@ -0,0 +1,56 @@ +# Image + +Functions for interacting with PNG images. + +Underlying C++ code is in `deepmind/engine/lua_image.cc` + +## `load`(*path*) + +Loads a PNG image into a tensor. + +The shape of the tensor will be either {height, width, 3} or {height, width, 4} +depending whether the png has 3 or 4 channels. It doesn't support paletted PNGs. + +```lua +local image = require 'dmlab.system.image' +local helpers = require 'common.helpers' + +local FILE_NAME = ... + +local image = image.load(helpers.dirname(FILE_NAME) .. "path/to/image.png") +``` + +## `scale`(*src*, *tgt_height*, *tgt_width*) + +Scales an image tensor, using bilinear interpolation for upsampling and the +average of the minified pixels when downsampling. + +The shape of the scaled tensor will be {tgt_rows, tgt_cols, ChannelCount}, where +ChannelCount is the number of channels used by src. + +```lua +local image = require 'dmlab.system.image' +local tensor = require 'dmlab.system.tensor' + +> src = tensor.ByteTensor{ + {{255, 0, 0}, {170, 85, 0}, {85, 170, 0}, {0, 255, 0}}, + {{170, 0, 85}, {141, 85, 85}, {113, 170, 85}, {85, 255, 85}}, + {{85, 0, 170}, {113, 85, 170}, {141, 170, 170}, {170, 255, 170}}, + {{0, 0, 255}, {85, 85, 255}, {170, 170, 255}, {255, 255, 255}} +} +> tgt = image.scale(src, 2, 6) +> tgt +Shape: [2, 6, 3] +[[[212, 0, 42] + [178, 51, 42] + [143, 102, 42] + [110, 153, 42] + [ 76, 204, 42] + [ 42, 255, 42]] + [[ 42, 0, 212] + [ 76, 51, 212] + [110, 102, 212] + [144, 153, 212] + [178, 204, 212] + [212, 255, 212]]] +``` diff --git a/docs/developers/reference/images/lua_api_flowchart.png b/docs/developers/reference/images/lua_api_flowchart.png new file mode 100644 index 00000000..ad589acb Binary files /dev/null and b/docs/developers/reference/images/lua_api_flowchart.png differ diff --git a/docs/developers/reference/lua_api.md b/docs/developers/reference/lua_api.md new file mode 100644 index 00000000..6a4daa20 --- /dev/null +++ b/docs/developers/reference/lua_api.md @@ -0,0 +1,829 @@ +# DeepMind Lab environment documentation: Lua + +## Lua callbacks + +*DeepMind Lab* provides a number of ways for Lua callback functions to hook into +various game events. + +On construction of the environment, the `levelName` setting specifies a game +script file name. Game script files live in the `game_scripts` directory and end +in `.lua`. They consist of Lua code that should return an object implementing +some of the API functions below. + +The environment will periodically attempt to call these functions in order to +determine its behaviour. However, in most cases there is some appropriate +default behaviour that takes place if the corresponding API function is not +defined. + +In order to successfully load a map, at least `nextMap` should be provided by +the game script. + +The calling code that calls into these API functions can be found in: + +* [deepmind/engine/context.cc](../../../deepmind/engine/context.cc). +* [deepmind/engine/context_entities.cc](../../../deepmind/engine/context_entities.cc). +* [deepmind/engine/context_events.cc](../../../deepmind/engine/context_events.cc). +* [deepmind/engine/context_game.cc](../../../deepmind/engine/context_game.cc). +* [deepmind/engine/context_observations.cc](../../../deepmind/engine/context_observations.cc). +* [deepmind/engine/context_pickups.cc](../../../deepmind/engine/context_pickups.cc). + +## Lua api callbacks order + +All callback functions are invoked by the environment in a specific order: +![lua_api_callbacks_flowchart](images/lua_api_flowchart.png "Callbacks order") + + +### `addBots`() → array + +Called at beginning of the level to populate the level with in-game bots. +Returns an array of tables, each of which has a `name` and `skill` entry. Each +`skill` should be a number between `1` and `5`. + +### `gameType`() → int + +Called before loading a map and sets the game type being played these can be +accessed from `common.game_types`. If not implemented FREE_FOR_ALL is returned. + +```lua +local game_types = require 'common.game_types' + +function api:gameType() + return game_types.CAPTURE_THE_FLAG +end +``` + +If the game type is team based the team a player belongs to can be set via +`team`(). + +### `team`(*playerId*, *playerName*) → string + +Called for each player joining the game. The team can be selected by returning +one of the following characters. A team must be selected if game mode is a team +game. + +Character | Team +:-------: | ------------------ +'p' | Any Team (Default) +'r' | Red Team +'b' | Blue Team +'s' | Spectator + +```lua +local TEAMS = {'r', 'b'} + +function api:team(playerId, playerName) + return TEAMS[playerId % 2 + 1] +end +``` + +### `canPickup`(*entity_id*) → boolean + +The environment calls this function to decide whether the item with ID +`entity_id` is allowed to be picked up. Return `false` to disallow pickup. +Defaults to `true` if the callback function isn't implemented. + +Will not be called if *entity_id* is not a positive integer; this is normally +ensured by `updateSpawnVars()`. + +### `commandLine`(*old_commandline*) → string + +The environment calls this function at the very beginning to determine the +initial engine console commands. The `old_commandline` parameter is the default +value, which you should return as-is if you do not want to customize the command +sequence. + +Example: + +```lua +function api:commandLine(old_command_line) + return '+set r_fullscreen "1"' +end +``` + +Lists of available commands in *Quake III Arena* can be found online. + +### `createPickup`(*class_name*) → table + +Returns a table with keys `name`, `classname`, `model`, `quantity`, `type`, and +an optional `tag`. + +Pickups are rendered floating and spinning in the air. If it is required for +them to be static use pickups.move_type.kStatic as `tag`. + +DMLab defines two item types not found in *Quake III Arena*: + +* pickups.type.REWARD: an item which when picked up changes the agent's score, + positive or negative. +* pickups.type.GOAL: an item which when picked up changes the agent's score + *and* causes the current level to restart. + +See assets/game_scripts/common/pickups.lua for examples. + +### `customObservation`(*name*) → tensor.ByteTensor or tensor.DoubleTensor. + +(For information on *DeepMind Lab*'s tensor library, see [Tensor](tensor.md).) + +When called it must return a tensor of a matching shape and type as specified in +`customObservationSpec`. + +Example: + +```lua +local order = 'Find Apples!' +local observationTable = { + LOOK_PITCH = tensor.Tensor{0}, + ORDER = tensor.ByteTensor(order:byte(1, -1)), + LOCATION = tensor.Tensor{0, 0, 0}, +} + +function api:customObservation(name) + return observationTable[name] +end +``` + +### `customObservationSpec`() → array + +Called after `init`, it returns an additional array of extra observation types +supplied by the script. + +Each entry must contain a `name`, `type` and `shape`. + +* `name` Name of the observation reported by the environment. +* `type` Type of tensor returned by the environment. May only be "bytes" or + "doubles". +* `shape` An array of integers denoting the shape of the tensor. If the size + of any dimension can change between calls, it must be set to zero here. + +```lua +-- See customObservation how to implement these. +function api:customObservationSpec() + return { + {name = 'LOCATION', type = 'doubles', shape = {3}}, + {name = 'ORDER', type = 'bytes', shape = {0}}, + {name = 'LOOK_PITCH', type = 'doubles', shape = {1}}, + } +end +``` + +When this function is specified the environment may call `observation` with any +`name` specified in the spec. + +### `init`(*settings*) → nil + +Called at game startup with the table *settings* representing a key-value store +for all settings passed at the start of the environment which weren't consumed +by the engine itself. See [python_api.md](../../users/python_api.md#python-environment-api) +for settings consumed by the engine. + +Example: + +```lua +function api:init(settings) + print('Script Settings') + for k, v in pairs(settings) do + print(k .. ' = ' .. v) + end + io.flush() +end +``` + +### `hasEpisodeFinished`(*timeInSeconds*) → boolean + +Called at the end of every frame with the elapsed time as an argument to +determine if the episode has finished and the game evaluation should continue +with the next episode. + +The default implementation returns `false` until 2 minutes 30 seconds have +passed. + +### `nextMap`() → string + +Called whenever a map needs to be loaded. Must return the name of a map +discoverable by the engine. + +For more information on maps, see [Maps](#maps) below. + +### `gameEvent` (*eventName*, *eventData*) + +Called when a game event occurs in engine. The event data is the data associated +with an event and is event specific. May be called multiple times within a +frame. + +Events generated by the engine: + +> None + +### `pickup`(*spawnId*) → number + +Event handler when the item with id *spawnId* is picked up. Returns the respawn +time, although this value is ignored if the item has a non-nil `wait` spawnVar. + +Will not be called if *entity_id* is not an integer; this is normally ensured by +`updateSpawnVars()`. + +### `start`(*episode*, *seed*) → nil + +The environment calls this function at the start of each episode, with: + +* A number *episode*, starting from 0. +* A number *seed*, the random seed of the game engine. Supplying the same seed + is intended to result in reproducible behaviour. + +Example: + +```lua +function api:start(episode, seed) + print('Entering episode no. ' .. episode .. ' with seed ' .. seed) + io.flush() +end +``` + +### `updateSpawnVars`(*spawnVars*) → table + +Called once per `spawnVars` entry when a new map is loaded. The `spawnVars` +argument is a Lua table with the internal Quake III Arena `spawnVars` key-value +setting. Quake III Arena [maps](../creating_levels/level_generation.md) define a +list of entities (light sources, player start points, items, ...). Implementing +this function allows overriding the settings for each map entity. + +Returning `nil` will have the effect of deleting the map entity. + +The default implementation returns `spawnVars` unchanged. + +#### spawnVars: common keys and values + +* angle - angle (in degrees) that the entity is facing. A value of 0 + corresponds to facing along the +X axis. +* classname - Required. Determines the functionality and type of the entity. +* id - an arbitrary identifier. If it is not a (string-encoded) integer then + `canPikcup()` and `pickup()` will not be called. +* model - The geometry and texture of the entity, specified by an MD3 file. +* origin - space-delimited string with XYZ coordinate values. (+Z is up) +* spawnflags - For most entities, setting this to `1` indicates that the + object is free-floating, and will not attempt to place the entity on the + ground, but respect the given Z-axis value for height. +* wait - Delay before the entity is respawned after being picked up; -1 means + never respawn. Leave zero (unspecified) to allow `pickup()` to specify + respawn time. + +### `extraEntities`() → table + +Called once after all built-in `updateSpawnVars` are processed. Must return an +array of new entities that will be added to a scene. Each entity must be a +string-string table containing the key 'classname'. These should match internal +Quake III Arena `spawnVars` key-value settings. Note `updateSpawnVars` is not +called for the entities created explicitly. + +This example will add two apples to the scene at the locations specified. + +```lua +function api:extraEntities() + local vars = { + { + classname = 'apple_reward', + model = 'models/apple.md3', + origin = '550 450 0', + }, + { + classname = 'apple_reward', + model = 'models/apple.md3', + origin = '600 450 0', + }, + } +end +``` + +### `replaceModelName`(*modelName*) → optional string, optional string + +Called once per `modelName` when a new model is loaded. If a replacement name is +required return a new name. Otherwise return nil. The model can have it's +textures modified with a prefix with an additional return value. This allows us +to load the same model multiple times with different textures. Returned strings +have a maximum size and the operation will fail if set to be too long. + +### `replaceTextureName`(*textureName*) → optional string + +Called once per `textureName` when a new map is loaded. If a replacement name is +required return a new name. Otherwise return nil. + +### `loadTexture`(*textureName*) → optional ByteTensor(H, W, 4) + +Called once per `textureName` when a new map is loaded. If a replacement name +was specified then that name is used here. If we want to override the built-in +texture loading return a tensor.ByteTensor(H, W, 4) of the texture. Otherwise +return nil. + +### `modifyTexture`(*textureName*, *image*) + +Called once per `textureName` when a new map is loaded. `image` is a ByteTensor +of the texture loaded. The script has an opportunity to modify the contents of +the texture in-place. `image` will be invalidated after the call is complete so +a copy of image must be made if to be used outside of the callback. + +The callback must return whether the texture was modified. + +### `mapLoaded`() + +Called when the map has finished loading. + +### `spawnInventory`(*loadOut*) -> table `updateInventory`(*loadOut*) -> table + +`spawnInventory` is called on each avatar when spawned. `updateInventory` is +called on each avatar every frame. Must return a same table as passed in if +fields are edited or nil. The data in the table can be updated before returning. +There is a helper table used to interact with the `loadOut`: + + local inventory = require 'common.inventory' + +If the function is defined `loadOut` must be returned, which can be modified +with `inventory.View`. See [Inventory View](#inventory_view) + +```Lua +local inventory = require 'common.inventory' + +function api:spawnInventory(loadOut) + -- This is actually the default load out for a player. + -- These can be adjusted per player and per respawn. + local view = inventory.View(loadOut) + view:setGadgets{ + inventory.GADGETS.IMPULSE, + inventory.GADGETS.RAPID, + -- inventory.GADGETS.ORB, + -- inventory.GADGETS.BEAM, + -- inventory.GADGETS.DISC, + } + + view:setGadgetAmount(inventory.GADGETS.IMPULSE, inventory.UNLIMITED) + view:setGadgetAmount(inventory.GADGETS.RAPID, 100) + + -- Max health of the player. If health is greater than this the health counts + -- down to this value. + view:setMaxHealth(100) + + -- Health counts down to loadOut.stats[inventory.STATS.MAX_HEALTH] + view:setHealth(100) + + -- Initial armor for player to start with. + view:setArmor(0) + + -- Must return original table. + return view:loadOut() +end +``` + +#### Inventory View + +Valid Gadgets: + + inventory.GADGETS.IMPULSE -- Contact gadget. + inventory.GADGETS.RAPID, -- Rapid fire gadget. + inventory.GADGETS.ORB, -- Area damage gadget. (Knocks players) + inventory.GADGETS.BEAM, -- Accurate and very rapid fire beam. + inventory.GADGETS.DISC, -- Powerful but lond period between firing. + +Valid amount are in range `[0, 999)` or `inventory.UNLIMITED` + +```Lua +-- Returns/sets list of gadgets. +function View:gadgets() +function View:setGadgets(gadgets) + +-- Returns/sets gadget's amount. +function View:gadgetAmount(gadget) +function View:setGadgetAmount(gadget, amount) + +-- Adds gadget with optional amount. +function View:addGadget(gadget, amount) +-- Removes gadget. +function View:removeGadget(gadget) + +-- Returns whether powerup is active. +function View:hasPowerUp(powerUp) + +-- Returns/sets player's armor +function View:armor() +function View:setArmor(amount) + +-- Returns/sets player's health. If health() > maxHealth() health will reduce +-- until it matches maxHealth(). +function View:health() +function View:setHealth(amount) + +-- Returns/sets player's max health. +function View:maxHealth() +function View:setMaxHealth(amount) + +-- Returns player's eye position. +function View:eyePos() + +-- Returns players view direction in Euler angles degrees. +function View:eyeAngles() + +-- Returns players gadget. +function View:gadget() + +-- Returns players id. +function View:playerId() +``` + +### `modifyControl`(*actions*) + +Called once per frame. `actions` is a lua table containing six keys: +`look_down_up`, `look_left_right`, `move_back_forward`, `strafe_left_right`, +`crouch_jump` and `buttons_down`, denoting the actions retrieved from the +controller. The script has an opportunity to modify the actions and return it to +override the actions to be applied. If nothing is returned, the method makes no +effect. + +### `playerMover`(*kwargs*) + +Called by a func_lua_mover entity. `kwargs` provides the ID and position of the +mover entity, as well as the triggering player's current position and velocity. +Returns a player position delta table and a player velocity delta table. + +### `lookat`(*entityId*, *lookedAt*, *position*) + +Called once per frame when the player focuses on a lookat trigger with id +`entityId`. Once the player looks away a final call is made with `lookedAt` set +to `false`. + +The lookat position relative to the trigger's bounding box is given as +`position`. + +### `rewardOverride`(*kwargs*) -> Int or nil + +Called whenever there is a reward event. This allows the level script to adjust +the reward received by the engine and to propagate events externally. The +`kwargs` are: + +* score - Integer default reward associated with in-game event. +* reason - String associated with the event (see [reasons](#reasons)). +* playerId - Player receiving the reward. +* otherPlayerId (optional) - Other player associated with event if there is + one. +* location (optional) - Location of the event of there exists one. +* team - One of "free", "red", "blue". + +Return either score or nil for default behaviour. + +#### Reasons + +See common/rewards.lua for tools for logging these events. + +* `PICKUP_REWARD` - `playerId` touched reward pickup. +* `PICKUP_GOAL` - `playerId` touched goal pickup. +* `TARGET_SCORE` - Level triggered a reward at `playerId`. +* `TAG_SELF` - `playerId` tagged self. +* `TAG_PLAYER` - `playerId` tagged `otherPlayerId` +* `CTF_FLAG_BONUS` - `playerId` picked up enemy flag. +* `CTF_CAPTURE_BONUS` - `playerId` has captured the opposing team's flag. +* `CTF_TEAM_BONUS` - `playerId` is part of a team that has captured the + opposing team's flag. +* `CTF_FRAG_CARRIER_BONUS` - `playerId` tagged opponent(`otherPlayerId`) flag + carrier. +* `CTF_RECOVERY_BONUS` - `playerId` has returned their team flag to the team's + base. +* `CTF_CARRIER_DANGER_PROTECT_BONUS` - `playerId` is on the same team as the + flag carrier and tagged opponent(`otherPlayerId`) who dammaged our flag + carrier. +* `CTF_FLAG_DEFENSE_BONUS` - `playerId` tagged opponent(`otherPlayerId`) while + `playerId` or `otherPlayerId` is near `playerId`'s flag. +* `CTF_CARRIER_PROTECT_BONUS` - `playerId` tagged opponent(`otherPlayerId`) + while `playerId` or `otherPlayerId` is near `playerId`'s flag carrier. +* `CTF_RETURN_FLAG_ASSIST_BONUS` - `playerId` returned the team flag just + before a payerId's team captured opponent team's flag. +* `CTF_FRAG_CARRIER_ASSIST_BONUS` - `playerId` tagged opponent team's flag + carrier just before a capturing event occurred. + +## Lua common helper objects + +A number of helper functions can be found in `game_scripts/common`. They can be +used in game scripts via Lua's `require` statement, for example: + +```lua +local make_map = require 'common.make_map' + +function api:nextMap() + return make_map.makeMap('G I A P', 'my_map') +end +``` + +### `common.make_map` + +Returns a Lua object offering a [`makeMap`](#makemapmap_text-map_name) and a +[`commandLine`](#commandlineold_commandline--string) function. + +### `common.pickups` + +Returns a Lua object offering a `type` and `defaults` table for defining pickup +objects. + +## The game module + +Game scripts can interact with *DeepMind Lab* using the `dmlab.system.game` +module, which can be loaded using `local game = require 'dmlab.system.game'`. +The module provides the following functions: + +### `addScore`(*playerId*, *score*) + +Adds a *score* to the total score of the player with *playerId*. + +### `finishMap`() + +Finishes the current map. + +### `updateTexture`(*name*, *tensorData*) + +Allows replacing the contents of a previously-loaded texture with the image data +in `tensorData`. Raises an error if the texture cannot be found. + +### `episodeTimeSeconds`() + +Returns the game time spent in the current episode in seconds. + +### `playerInfo`() + +Returns the state of your player in a table, + +```lua +{ + -- Player position in world units. + -- In our text mazes, one grid square is 100 world units. + pos = {forward, left, up}, + + -- Player velocity in world units per second. + vel = {forward, left, up}, + + -- Player orientation in degrees. + -- In Euler angles (see https://en.wikipedia.org/wiki/Euler_angles). + -- The relationship between the `pos` and `vel` and `angles` is, + -- + -- yaw = 90 degrees + -- +left axis + -- ^ + -- | + -- | + -- yaw = 180 degrees <----+----> yaw = 0 degrees + -- -forward axis | +forward axis + -- | + -- v + -- yaw = -90 degrees + -- -left axis + -- + angles = {pitch, yaw, roll}, + + -- Player angular velocity in degrees per second. + anglesVel = {pitch, yaw, roll}, + + -- Height of the camera above player position. + -- A single value in world units. + height = h, +} +``` + +### `tempFolder`() + +Returns the temporary folder where temporary assets are built and removed when +the context is released. This folder can also be used for script temporary +storage. + +### `runFiles`() + +Returns the folder where assets are stored. Most assets are placed in a +sub-folder called 'baselab'. + +### `raycast`(startPos, endPos) -> fraction (float in range \[0, 1\]) + +Returns 1 if 'endPos' is visible from startPos and less than 1 otherwise. + +If the fraction is less than one the first point of obstruction will be: +`(endPos - startPos) * fraction + startPos`. + +### `copyFileToLocation`(fromFileName, toFileName) -> string + +Sets the contents of `toFileName` with the contents of `fromFileName`. An error +is produced if the file the operation fails for any reason. (It will use +file_reader_override if provided.) + +### `loadFileToString`(fileName) -> string + +Returns contents of fileName in a string. An error is produced if the file +doesn't exist. (It will use file_reader_override if provided.) + +### `loadFileToByteTensor`(fileName) -> tensor.ByteTensor + +Returns contents of fileName in a tensor.ByteTensor. An error is produced if +the file doesn't exist. (It will use file_reader_override if provided.) + +## The game_entities module + +Game scripts read information about entities with *DeepMind Lab* using the +`dmlab.system.game_entities` module, which can be loaded using `local +game_entities = require 'dmlab.system.game_entities'`. The module provides the +following functions. + +### `entities`\(\[\{*classname1*, *classname2*, ...\}\]\) -> *array*. + +Returns a list of entities that are active that frame. The optional argument is +a list of classnames. If provided, the result is filtered such that only +entities that match one of the classnames provided are returned. + +Each entity in the list returned in a table: + +```Lua +entity = { + entityId = 0, -- Internal id. + id = 0, -- Id provided via spawnVars. + type = 0, -- Type matching 'pickups.type' in common.pickiups. + visible = true, -- Whether the entity is visible. (Placed pickups + -- are marked invisible when picked up.) + position = {x, y, z}, -- Location the pickup is this frame. + classname = "classname", -- The classname of the pickup. +} +``` + +Example usage: + +```Lua +local game_entities = require 'dmlab.system.game_entities' + + +local entities = game_entities:entities{'apple_reward', 'lemon_reward'} + +-- Print all apple_reward and lemon_reward locations. +for _, v in ipairs(entities) do + print(unpack(v.position)) +end +``` + +## The events module + +Game scripts can interact with *DeepMind Lab* using the `dmlab.system.events` +module, which can be loaded using `local event = require 'dmlab.system.events'`. +The module provides the following functions. + +### `add`(*name*, \[observation1, \[observation2 ... \]\]) + +Adds events to be read by the user. Each event has a list of observations. Each +observation may be one of string, ByteTensor or DoubleTensor. + +Example script: + +```Lua +local events = require 'dmlab.system.events' +local tensor = require 'dmlab.system.tensor' + +local api = {} +function api:start(episode, seed) + events:add('Event Name', 'Text', tensor.ByteTensor{3}, tensor.DoubleTensor{7}) +end +``` + +## Factory functions + +*DeepMind Lab* provides a number of Lua files with auxiliary helper functions in +the `game_scripts/helpers` directory. Among them are *factories*, which are of +the form + +```lua +local factory = {} + +function factory.createLevelApi(kwargs) + local api = {} + -- ... + return api +end + +return factory +``` + +For examples, see any `game_scripts/helpers/*_factory.lua` file. Factory +functions can be used as a higher-level API for creating game scripts. As an +example, the `lt_chasm.lua` game script is implemented via + +```lua +local factory = require 'helpers.lt_factory' +return factory.createLevelApi{mapName = 'lt_chasm'} +``` + +## Maps + +*DeepMind Lab* maps are identified by strings representing a file name. This +file must contain a Quake III Arena map, see [Level +Generation](level_generation.md) for a description of those. *DeepMind Lab* also +defines a [text level](../creating_levels/text_level.md) format which provides a +simple text format for creating levels. The Lua module +`game_scripts/common/make_map.lua` provides a convenient interface for making +maps from ASCII text level strings on the fly, namely + +### `makeMap`(*args*) + +Generates a map with a given name from a text level description. + +`args` is a table containing: + +* `mapName` - Name of the map. Acts as a file name and should be unique. +* `mapEntityLayer` - Text level description. For details on the format, see + [Text Levels](../creating_levels/text_level.md). +* `mapVariationsLayer` - Optional part of the text level description, which + allows changing the appearance of specific map locations. See [Text + Levels](../creating_levels/text_level.md). +* `useSkybox` - Whether to use a skybox. Ceilings are open when a skybox is + used and closed otherwise. Default: true. +* `theme` - Name of the texture set to use in the generated level. For a list + of available themes see [Text Levels](../creating_levels/text_level.md). +* `allowBots` - Whether to generate Area Awareness System (AAS) for bots to + navigate the map. Default is false. + +An example usage of `makeMap` is + +```lua +local make_map = require 'common.make_map' +... +function api:nextMap() + mapText = 'G I A P' + api._count = api._count + 1 + return make_map.makeMap{ + mapName = 'luaMap' .. api._count, + mapEntityLayer = mapText + } +end +``` + +where `api._count` would have been set up in `start`. The +`test_levels/empty_room_test.lua` game script file provides a self-contained +example. + +## Rendering + +### `screenMessages`(*args*) + +Called to enable the script to render text on the screen at arbitrary locations. + +`args` is a table containing: + +* `width` - Virtual screen width. (Always 640.) +* `height` - Virtual screen height. (Always 480.) +* `line_height` - Distance to move vertically between lines. (Always 20.) +* `max_string_length` - Maximum length of string per message (Always 79.) + +The user must return an array of messages. Each message shall contain: + +* `message` - String for the screen to render. +* `x` - X location to render the string. (0 is left edge, 640 is right.) +* `y` - Y location to render the string. (0 is top edge, 480 is bottom.) +* `alignment` - 0 left aligned, 1 right aligned, 2 center aligned. +* `shadow` - (default true). - Whether to add black drop shadow. +* `rgba` - (default {1, 1, 1, 1} ). - Text color and transparency. + +Helpers for rendering some message types are in `common.screen_message`: + +```lua +local screen_message = require 'common.screen_message' + +function api:screenMessages(args) + local message_order = { + message = 'Find an apple!', + x = args.width / 2, + y = (args.height - args.line_height) / 2, + alignment = screen_message.ALIGN_CENTER, + } + return { message_order } +end +``` + +For an example, see: +[demo_levels/screen_decoration/text.lua](../../../assets/game_scripts/demo_levels/screen_decoration/text.lua). + +### `filledRectangles`(*args*) + +Called to enable the script to render a filled rectangle at arbitrary locations. + +`args` is a table containing: + +* `width` - Virtual screen width. (Always 640.) +* `height` - Virtual screen height. (Always 480.) + +The user must return an array of tables. Each table shall contain: + +* `x` - X location to render the rectangle. (0 is left edge, 640 is right.) +* `y` - Y location to render the rectangle. (0 is top edge, 480 is bottom.) +* `width` - width to render the rectangle. (0 is left edge, 640 is right.) +* `height` - height to render the rectangle. (0 is top edge, 480 is bottom.) +* `rgba` - colour {R, G, B, A} to render rectangle. + +```lua +function api:filledRectangles(args) + local redCenterRect = { + x = args.width / 2 - 30, + y = args.height / 2 - 30, + width = 60, + height = 60, + rgba = {1, 0, 0, 1}, + } + return { redCenterRect } +end +``` + +For an example, see: +[demo_levels/screen_decoration/rectangles.lua](../../../assets/game_scripts/demo_levels/screen_decoration/rectangles.lua). diff --git a/docs/tensor.md b/docs/developers/reference/tensor.md similarity index 60% rename from docs/tensor.md rename to docs/developers/reference/tensor.md index 7458c22d..18ea0482 100644 --- a/docs/tensor.md +++ b/docs/developers/reference/tensor.md @@ -1,9 +1,3 @@ -(Switch to: [Lua](lua_api.md) · [Python](python_api.md) · - [Level Generation](level_generation.md) · - Tensor · [Text Levels](text_level.md) · - [Build](build.md) · - [Known Issues](issues.md)) - # Tensor This is a small tensor library for manipulating numerical data in DeepMind Lab. @@ -63,6 +57,71 @@ By default use `DoubleTensor`. Other data types may be difficult to use correctly, because numeric conversions between Lua's number representation and the representations used by the underlying tensor implementation may be invalid. +## Reading/Writing + +Reading and writing to a tensor can be done via the function `val()`. + +### val() -> value|table + +When called on a single element tensor val() returns the value of that element. +When called on a tensor with more elements it returns a Lua table of the same +shape. + +Value form: + +```Lua +> myTensor = tensor.Int64Tensor{{1,2}, {2,3}} +> assert(myTensor(1, 1):val() == 1) +> assert(myTensor(1, 2):val() == 2) +> assert(myTensor(2, 1):val() == 3) +> assert(myTensor(2, 2):val() == 4) +``` + +Table form: + +```Lua +> myTensor = tensor.Int64Tensor{{1,2}, {2,3}} +> t = myTensor:val() +> assert(t[1][1] == 1) +> assert(t[1][2] == 2) +> assert(t[2][1] == 3) +> assert(t[2][2] == 4) +``` + +### val(value|table) + +When called on a single element tensor it sets the value of that element to +`value`. When called on a tensor with more elements the value must be a table +that matches the shape of the tensor. The tensor is then assigned to the values +in the table. + +Value form: + +```Lua +> myTensor = tensor.Int64Tensor{{0, 0}, {0, 0}} +> myTensor(1, 1):val(1) +> myTensor(1, 2):val(2) +> myTensor(2, 1):val(3) +> myTensor(2, 2):val(4) +> myTensor +[dmlab.system.tensor.Int64Tensor] +Shape: [2, 2] +[[1, 2], + [3, 4]] +``` + +Table form: + +```Lua +> myTensor = tensor.Int64Tensor{{0,0}, {0,0}} +> myTensor:val{{1,2}, {2,3}} +> myTensor +[dmlab.system.tensor.Int64Tensor] +Shape: [2, 2] +[[1, 2], + [3, 4]] +``` + ## Creation For each type of tensor there is a corresponding construction function and @@ -101,7 +160,7 @@ Shape: [3, 2] [0, 0]] ``` -### `tensor.DoubleTensor`{{*val11*, ...}, {*val21*, ...}, ...} +### `tensor.DoubleTensor`\{\{*val11*, ...\}, \{*val21*, ...\}, ...\} Creates a tensor from the given hierarchy of tables and values. The shape is implied. @@ -116,6 +175,101 @@ Shape: [2, 2, 2] [7, 8]]] ``` +### `tensor.DoubleTensor`\{file=\*kwargs*\} + +Creates a rank-1 tensor by reading bytes within a file. The file is read in +system local endian order. 'kwargs' must be a table with the following keyword +arguments: + +* 'name': Name of the file to read. Must be a valid filename. +* 'byteOffset': Offset from the beginning of the file at which reading starts. + Optional, default 0. +* 'numElements': Number of elements to read starting at the offset. Optional, + defaults to largest number of elements that is available from the given + offset. + +If the offset is outside of the range \[0, file size\] or if reading count +values plus the offset would exceed the size of the file an error is thrown. + +Assuming the file 'data.bin' is 64 * 8 bytes long, generated by writing the list +of doubles from 0 to 63 inclusive in local endian format: + +```Lua +-- Read whole file. +assert( + tensor.DoubleTensor{ + file = {name = 'data.bin'} + } == tensor.DoubleTensor{range = {0, 63}} +) + +-- Read count elements. +assert( + tensor.DoubleTensor{ + file = {name = 'data.bin', numElements = 10} + } == tensor.DoubleTensor{range = {0, 9}} +) + +-- Read end of file. +assert( + tensor.DoubleTensor{ + file = {name = 'data.bin', byteOffset = 40 * 8} + } == tensor.DoubleTensor{range = {40, 63}} +) + +-- Read middle of file. +assert( + tensor.DoubleTensor{ + file = {name = 'data.bin', byteOffset = 40 * 8, numElements = 6} + } == tensor.DoubleTensor{range = {40, 45}} +) +``` + +### `tensor.DoubleTensor`\{range=\{*from*, *to*, *step*\}\} + +Creates a rank-1 tensor with values in the closed interval spanned by endpoints +*from* and *to*, starting from *from* and advancing by the given *step*. Ranges +can be also defined as range=\{*from*, *to*\}, where the implicit *step* value +is 1, and range=\{*to*\}, where the implicit *from* value is also 1. + +```Lua +> tensor.Int64Tensor{range = {5}} +[dmlab.system.tensor.DoubleTensor] +Shape: [5] +[1, 2, 3, 4, 5] +> tensor.Int64Tensor{range = {3, 5}} +[dmlab.system.tensor.DoubleTensor] +Shape: [3] +[3, 4, 5] +> tensor.DoubleTensor{range = {1, 2, 0.5}} +[dmlab.system.tensor.DoubleTensor] +Shape: [3] +[1.0, 1.5, 2.0] +``` + +Please note that the upper bound is included in the range if and only if the +interval width is divisible by the step size: + +```Lua +> tensor.DoubleTensor{range = {1, 3, 1}} +[dmlab.system.tensor.DoubleTensor] +Shape: [3] +[1.0, 2.0, 3.0] +> tensor.DoubleTensor{range = {1, 2.75, 1}} +[dmlab.system.tensor.DoubleTensor] +Shape: [2] +[1.0, 2.0] +``` + +Also, the extents of the range are subject to the numerical precision of the +element type: + +```Lua +> tensor.FloatTensor{range = {300000000, 300000001, 0.5}} +[dmlab.system.tensor.DoubleTensor] +Shape: [1] +[3e+8] +``` + ### Conversion A new tensor is returned with new storage and of the specified type. The new @@ -147,6 +301,65 @@ z1 = tensor.DoubleTensor{...} z2 = z1:clone() -- same as z1:double() ``` +## Rounding + +Rounding operations can be applied to nonintegral types. + +### `floor`() + +Rounds the tensor elements to the greatest preceding integers. + +```Lua +> z = tensor.DoubleTensor{{-2.25, -1.75}, {0.5, 1.0}} +> z +[dmlab.system.tensor.DoubleTensor] +Shape: [2, 2] +[[-2.25, -1.75], + [0.5, 1]] +> z:floor() +[dmlab.system.tensor.DoubleTensor] +Shape: [2, 2] +[[-3, -2], + [0.0, 1.0]] +``` + +### `ceil`() + +Rounds the tensor elements to the least succeeding integers. + +```Lua +> z = tensor.DoubleTensor{{-2.25, -1.75}, {0.5, 1.0}} +> z +[dmlab.system.tensor.DoubleTensor] +Shape: [2, 2] +[[-2.25, -1.75], + [0.5, 1]] +> z:ceil() +[dmlab.system.tensor.DoubleTensor] +Shape: [2, 2] +[[-2, -1], + [1.0, 1.0]] +``` + +### `round`() + +Rounds the tensor elements to the closest integers, defaulting to the away from +zero in case of the value being equidistant. + +```Lua +> z = tensor.DoubleTensor{{-2.25, -1.75}, {0.5, 1.0}} +> z +[dmlab.system.tensor.DoubleTensor] +Shape: [2, 2] +[[-2.25, -1.75], + [0.5, 1]] +> z:round() +[dmlab.system.tensor.DoubleTensor] +Shape: [2, 2] +[[-2, -2], + [1.0, 1.0]] +``` + ## Layout Operations There are operations that do not affect the underlying storage of the tensor, @@ -247,7 +460,19 @@ Shape: [2, 4] Scalar operations work on the tensor object and a single scalar argument value. The respective element-wise binary operation is applied to pairs of each tensor -element and the scalar value. +element and the scalar value. If an array is passed then it becomes equivalent +to: + +```Lua +fillValue = {1, 2} +myTensor:(fillValue) +-- Equivalent to: +for i, value in ipairs(fillValue) do + myTensor:Select(#myTensor:shape(), i):(value) +end +``` + +Where is any of the functions defined below. ### `fill`(*value*) @@ -265,6 +490,20 @@ Shape: [4, 4] [7, 7, 7, 7]] ``` +Table form: + +```Lua +> z = tensor.DoubleTensor(4, 4) +> z:fill{1, 2, 3, 4} +> z +[dmlab.system.tensor.DoubleTensor] +Shape: [4, 4] +[[1, 2, 3, 4], + [1, 2, 3, 4], + [1, 2, 3, 4], + [1, 2, 3, 4]] +``` + ### `add`(*value*) ```Lua @@ -280,6 +519,20 @@ Shape: [4, 4] [6, 6, 6, 6]] ``` +Table form: + +```Lua +> z = tensor.DoubleTensor(4, 4) +> z:fill(5) +> z:add{1, 2, 3, 4} +> z +[dmlab.system.tensor.DoubleTensor] +Shape: [4, 4] +[[6, 7, 8, 9], + [6, 7, 8, 9], + [6, 7, 8, 9], + [6, 7, 8, 9]] +``` ### `mul`(*value*) ```Lua @@ -295,6 +548,21 @@ Shape: [4, 4] [2, 2, 2, 2]] ``` +Table form: + +```Lua +> z = tensor.DoubleTensor(4, 4) +> z:fill(0.5) +> z:mul{4, 6, 8, 10} +> z +[dmlab.system.tensor.DoubleTensor] +Shape: [4, 4] +[[2, 3, 4, 5], + [2, 3, 4, 5], + [2, 3, 4, 5], + [2, 3, 4, 5]] +``` + ### `sub`(*value*) ```Lua @@ -310,6 +578,21 @@ Shape: [4, 4] [-2, -2, -2, -2]] ``` +Table form: + +```Lua +> z = tensor.DoubleTensor(4, 4) +> z:fill(2) +> z:sub{4, 5, 6, 7} +> z +[dmlab.system.tensor.DoubleTensor] +Shape: [4, 4] +[[-2, -3, -4, -5], + [-2, -3, -4, -5], + [-2, -3, -4, -5], + [-2, -3, -4, -5]] +``` + ### `div`(*value*) ```Lua @@ -325,6 +608,21 @@ Shape: [4, 4] [-1, -1, -1, -1]] ``` +Table form: + +```Lua +> z = tensor.DoubleTensor(4, 4) +> z:fill(-12) +> z:div{1, 2, 3, 4} +> z +[dmlab.system.tensor.DoubleTensor] +Shape: [4, 4] +[[-12, -6, -4, -3], + [-12, -6, -4, -3], + [-12, -6, -4, -3], + [-12, -6, -4, -3]] +``` + ## Component Operations Component operations work on two tensors (the object and the argument) that have @@ -427,6 +725,39 @@ Shape: [3, 2] [5, 6]] ``` +## Matrix operations + +Matrix operations only apply to rank-2 tensors. + +### `mmul`(*value*) + +Matrix multiplication. + +```Lua +> at = tensor.FloatTensor{{1, 2, 3}, {4, 5, 6}} +> bt = tensor.FloatTensor{{1, 0}, {0, 2}} +> bt:mmul(at) +[dmlab.system.tensor.FloatTensor] +Shape: [2, 3] +[[1, 2, 3], + [8, 10, 12]] +``` + +## Random operations + +### `shuffle`(*gen*) + +Shuffles the elements of a rank-1 tensor, using the permuation computed by +random bit generator 'gen'. + +```Lua +> random = require 'dmlab.system.random' +> tensor.Int64Tensor{range={5}}:shuffle(random) +[dmlab.system.tensor.Int64Tensor] +Shape: [5] +[2, 3, 5, 1, 4] +``` + ## Comparison Two tensors are equal iff the type, shape and values are the same. diff --git a/docs/logo.png b/docs/logo.png deleted file mode 100644 index 36d71023..00000000 Binary files a/docs/logo.png and /dev/null differ diff --git a/docs/lua_api.md b/docs/lua_api.md deleted file mode 100644 index 82244290..00000000 --- a/docs/lua_api.md +++ /dev/null @@ -1,307 +0,0 @@ -(Switch to: Lua · [Python](python_api.md) · - [Level Generation](level_generation.md) · - [Tensor](tensor.md) · [Text Levels](text_level.md) · - [Build](build.md) · - [Known Issues](issues.md)) - -# DeepMind Lab environment documentation: Lua - -## Lua callbacks - -*DeepMind Lab* provides a number of ways for Lua callback functions to hook into -various game events. - -On construction of the environment, the `levelName` setting specifies a game -script file name. Game script files live in the `game_scripts` directory -and end in `.lua`. They consist of Lua code that should return an object -implementing some of the API functions below. - -The environment will periodically attempt to call these functions in order to -determine its behaviour. However, in most cases there is some appropriate -default behaviour that takes place if the corresponding API function is not -defined. - -In order to successfully load a map, at least `nextMap` should be provided by -the game script. - -The calling code that calls into these API functions can be found in -[deepmind/engine/context.cc](deepmind/engine/context.cc). - -### `addBots`() → array - -Called at beginning of the level to populate the level with in-game -bots. Returns an array of tables, each of which has a `name` and `skill` -entry. Each `skill` should be a number between `1` and `5`. - -### `canPickup`(*entity_id*) → boolean - -The environment calls this function to decide whether the item with ID -`entity_id` is allowed to be picked up. Return `false` to disallow -pickup. Defaults to `true` if the callback function isn't implemented. - -### `commandLine`(*old_commandline*) → string - -The environment calls this function at the very beginning to determine the -initial engine console commands. The `old_commandline` parameter is the default -value, which you should return as-is if you do not want to customize the command -sequence. - -Example: - -```lua -function api:commandLine(old_command_line) - return "+set r_fullscreen \"1\"" -end -``` - -Lists of available commands in *Quake III Arena* can be found online. - -### `createPickup`(*class_name*) → table - -Returns a table with keys `name`, `class_name`, `model_name`, `quantity`, -`type`, and an optional `tag`. - -## `customObservation`(*name*) → tensor.ByteTensor or tensor.DoubleTensor. - -(For information on *DeepMind Lab*'s tensor library, see [Tensor](tensor.md).) - -When called it must return a tensor of a matching shape and type as specified in -`customObservationSpec`. - -Example: - -```lua -local order = 'Find Apples!' -local observationTable = { - LOOK_PITCH = tensor.Tensor{0}, - ORDER = tensor.ByteTensor(order:byte(1, -1)), - LOCATION = tensor.Tensor{0, 0, 0}, -} - -function api:customObservation(name) - return observationTable[name] -end -``` - -## `customObservationSpec`() → array - -Called after `init`, it returns an additional array of extra -observation types supplied by the script. - -Each entry must contain a `name`, `type` and `shape`. - -* `name` Name of the observation reported by the environment. -* `type` Type of tensor returned by the environment. May only be "bytes" or - "doubles". -* `shape` An array of integers denoting the shape of the tensor. If the size - of any dimension can change between calls, it must be set to zero here. - -```lua --- See customObservation how to implement these. -function api:customObservationSpec() - return { - {name = 'LOCATION', type = 'doubles', shape = {3}}, - {name = 'ORDER', type = 'bytes', shape = {0}}, - {name = 'LOOK_PITCH', type = 'doubles', shape = {1}}, - } -end -``` - -When this function is specified the environment may call `observation` with any -`name` specified in the spec. - -### `init`(*settings*) → nil - -Called at game startup with the table *settings* representing a key-value store -for all settings passed at the start of the environment which weren't consumed -by the engine itself. See [python_api.md](python_api.md#python-environment-api) for -settings consumed by the engine. - -Example: - -```lua -function api:init(settings) - print("Script Settings") - for k, v in pairs(settings) do - print(k .. " = " .. v) - end - io.flush() -end -``` - -### `hasEpisodeFinished`(*time_in_seconds*) → boolean - -Called at the end of every frame with the elapsed time as an argument -to determine if the episode has finished and the game evaluation -should continue with the next episode. - -The default implementation returns `false` until 5 minutes have passed. - -### `nextMap`() → string - -Called whenever a map needs to be loaded. Must return the name of a map -discoverable by the engine. - -For more information on maps, see [Maps](#maps) below. - -### `pickup`(*spawn_id*) → number - -Event handler when the item with id *spawn_id* is picked up. Returns the respawn -time. - -### `start`(*episode*, *seed*) → nil - -The environment calls this function at the start of each episode, with: - -* A number *episode*, starting from 0. -* A number *seed*, the random seed of the game engine. Supplying the same seed - is intended to result in reproducible behaviour. - -Example: - -```lua -function api:start(episode, seed) - print("Entering episode no. " .. episode .. " with seed " .. seed) - io.flush() -end -``` -### `updateSpawnVars`(*spawn_vars*) → table - -Called once per `spawn_vars` entry when a new map is loaded. The `spawn_vars` -argument is a Lua table with the internal Quake III Arena `spawnVars` key-value -setting. Quake III Arena [maps](level_generation.md) define a list of entities -(light sources, player start points, items, ...). Implementing this function -allows overriding the settings for each map entity. - -The default implementation returns `spawn_vars` unchanged. - -## Lua common helper objects - -A number of helper functions can be found in `game_scripts/common`. They -can be used in game scripts via Lua's `require` statement, for example: - -```lua -local make_map = require 'common.make_map' - -function api:nextMap() - return make_map.makeMap("G I A P", "my_map") -end -``` - -### `common.make_map` - -Returns a Lua object offering a [`makeMap`](#makemapmap_text-map_name) -and a [`commandLine`](#commandlineold_commandline--string) function. - -### `common.pickups` - -Returns a Lua object offering a `type` and `defaults` table for -defining pickup objects. - -## The game module - -Game scripts can interact with *DeepMind Lab* using the `dmlab.system.game` -module, which can be loaded using `local game = require 'dmlab.system.game'`. -The module provides the following functions. - -### `addScore`(*player_id*, *score*) - -Adds a *score* to the total score of the player with *player_id*. - -### `finishMap`() - -Finishes the current map. - -## Factory functions - -*DeepMind Lab* provides a number of Lua files with auxiliary helper functions in -the `game_scripts/helpers` directory. Among them are *factories*, which are of -the form - -``` lua -local factory = {} - -function factory.createLevelApi(kwargs) - local api = {} - -- ... - return api -end - -return factory -``` - -For examples, see any `game_scripts/helpers/*_factory.lua` file. Factory -functions can be used as a higher-level API for creating game scripts. As an -example, the `lt_chasm.lua` game script is implemented via - -``` lua -local factory = require 'helpers.lt_factory' -return factory.createLevelApi{mapName = "lt_chasm"} -``` - -## Maps - -*DeepMind Lab* maps are identified by strings representing a file name. This -file must contain a Quake III Arena map, see -[Level Generation](level_generation.md) for a description of -those. *DeepMind Lab* also defines a [text level](text_level.md) -format which provides a simple text format for creating levels. The Lua module -`game_scripts/common/make_map.lua` provides a convenient interface for making -maps from ASCII text level strings on the fly, namely - -### `makeMap`(*map_text*, *map_name*) - -Generates a map called *map_name* from *map_text*. The map name acts as a file -name and should be unique. For a description of the text level format that -*map_text* uses, see [Text Levels](text_level.md). - -An example usage of `makeMap` is - -``` lua -local make_map = require 'common.make_map' -... -function api:nextMap() - map_text = "G I A P" - api._count = api._count + 1 - return make_map.makeMap(map_text, "luaMap" .. api._count) -end -``` - -where `api._count` would have been set up in `start`. The `tests/demo_map.lua` -game script file provides a self-contained example. - -## Rendering - -### `screenMessages`(*args*) - -Called to enable the script to render text on the screen at arbitrary locations. - -`args` is a table containing: - -* `width` - Virtual screen width. (Always 640.) -* `height` - Virtual screen height. (Always 480.) -* `line_height` - Distance to move vertically between lines. (Always 20.) -* `max_string_length` - Maximum length of string per message (Always 79.) - -The user must return an array of messages. Each message shall contain: - -* `message` - String for the screen to render. -* `x` - X location to render the string. (0 is left edge, 640 is right.) -* `y` - Y location to render the string. (0 is top edge, 480 is bottom.) -* `alignment` - 0 left aligned, 1 right aligned, 2 center aligned. - -Helpers for rendering some message types are in `common.screen_message`: - -```lua -local screen_message = require 'common.screen_message' - -function api:screenMessages(args) - local message_order = { - message = 'Find an apple!', - x = args.width / 2, - y = (args.height - args.line_height) / 2, - alignment = screen_message.ALIGN_CENTER, - } - return { message_order } -end -``` diff --git a/docs/build.md b/docs/users/build.md similarity index 57% rename from docs/build.md rename to docs/users/build.md index c8f5a3e5..f0f661ef 100644 --- a/docs/build.md +++ b/docs/users/build.md @@ -1,12 +1,9 @@ -(Switch to: [Lua](lua_api.md) · [Python](python_api.md) · - [Level Generation](level_generation.md) · - [Tensor](tensor.md) · [Text Levels](text_level.md) · - Build · - [Known Issues](issues.md)) - # How to build *DeepMind Lab* -*DeepMind Lab* uses [Bazel](https://www.bazel.io/) as its build system. Its main + + + +*DeepMind Lab* uses [Bazel](https://bazel.build/) as its build system. Its main `BUILD` file defines a number of *build targets* and their dependencies. The build rules should work out of the box on Debian (Jessie or newer) and Ubuntu (version 14.04 or newer), provided the required packages are installed. @@ -16,20 +13,20 @@ files might be required, see below. *DeepMind Lab* is written in C99 and C++11, and you will need a sufficiently modern compiler. GCC 4.8 should suffice. +Instructions for installing Bazel can be found in the [Bazel install +guide](https://docs.bazel.build/versions/master/install.html). + ### Step-by-step instructions for Debian or Ubuntu Tested on Debian 8.6 (Jessie) and Ubuntu 14.04 (Trusty) and newer. -1. Install Bazel by adding a custom APT repository, as described - [on the Bazel homepage](http://bazel.io/docs/install.html#ubuntu) or using - an [installer](https://github.com/bazelbuild/bazel/releases). - This should also install GCC and zip. +1. Install Bazel (see above). 2. Install *DeepMind Lab*'s dependencies: ```shell $ sudo apt-get install lua5.1 liblua5.1-0-dev libffi-dev gettext \ - freeglut3-dev libsdl2-dev libosmesa6-dev python-dev python-numpy realpath + freeglut3-dev libsdl2-dev libosmesa6-dev python-dev python-numpy ``` 3. [Clone or download *DeepMind Lab*](https://github.com/deepmind/lab). @@ -38,34 +35,29 @@ Tested on Debian 8.6 (Jessie) and Ubuntu 14.04 (Trusty) and newer. ```shell $ cd lab - # Build the Python interface to DeepMind Lab with OpenGL - lab$ bazel build :deepmind_lab.so --define headless=glx + + # Build the Python interface to DeepMind Lab + lab$ bazel build :deepmind_lab.so + # Build and run the tests for it - lab$ bazel run :python_module_test --define headless=glx - # Rebuild the Python interface in non-headless mode and run a random agent - lab$ bazel run :random_agent --define headless=false + lab$ bazel run :python_module_test + + # Run a random agent + lab$ bazel run :python_random_agent ``` The Bazel target `:deepmind_lab.so` builds the Python module that interfaces -*DeepMind Lab*. It can be build in headless hardware rendering mode (`--define -headless=glx`), headless software rendering mode (`--define headless=osmesa`) or -non-headless mode (`--define headless=false`). - -The random agent target `:random_agent` has a number of optional command line -arguments. Run +with *DeepMind Lab*. -``` shell -lab$ bazel run :random_agent -- --help -``` - -to see those. +The random agent target `:python_random_agent` has a number of optional command line +arguments. Run `bazel run :random_agent -- --help` to see those. ### Building on Red Hat Enterprise Linux Server Tested on release 7.2 (Maipo). -1. Add the Extra Packages as described on - [fedoraproject.org](http://fedoraproject.org/wiki/EPEL#How_can_I_use_these_extra_packages.3F) +1. Install Bazel (see above). + 2. Install Bazel's and DeepMind Lab's dependencies ```shell @@ -73,17 +65,10 @@ Tested on release 7.2 (Maipo). java-1.8.0-openjdk-devel gcc gcc-c++ freeglut-devel SDL2 SDL2-devel \ mesa-libOSMesa-devel python-devel numpy ``` -3. Download and run - a [Bazel binary installer](https://github.com/bazelbuild/bazel/releases), - e.g. - ```shell - sudo yum -y install wget - wget https://github.com/bazelbuild/bazel/releases/download/0.3.2/bazel-0.3.2-installer-linux-x86_64.sh - sh bazel-0.3.2-installer-linux-x86_64.sh - ``` -4. [Clone or download *DeepMind Lab*](https://github.com/deepmind/lab). -5. Edit `lua.BUILD` to reflect how Lua is installed on your system: +3. [Clone or download *DeepMind Lab*](https://github.com/deepmind/lab). + +4. Edit `lua.BUILD` to reflect how Lua is installed on your system: ```python cc_library( @@ -94,29 +79,25 @@ Tested on release 7.2 (Maipo). ``` The output of `pkg-config lua --libs --cflags` might be helpful to find the right include folders and linker options. -6. Build *DeepMind Lab* using Bazel as above. + +5. Build *DeepMind Lab* using Bazel as above. ### Building on SUSE Linux Tested on SUSE Linux Enterprise Server 12. -1. Install Bazel's and DeepMind Lab's dependencies +1. Install Bazel (see above). + +2. Install Bazel's and DeepMind Lab's dependencies ```shell sudo zypper --non-interactive install java-1_8_0-openjdk \ java-1_8_0-openjdk-devel gcc gcc-c++ lua lua-devel python-devel \ python-numpy-devel libSDL-devel libOSMesa-devel freeglut-devel ``` -2. Download and run - a [Bazel binary installer](https://github.com/bazelbuild/bazel/releases), - e.g. - ```shell - sudo yum -y install wget - wget https://github.com/bazelbuild/bazel/releases/download/0.3.2/bazel-0.3.2-installer-linux-x86_64.sh - sh bazel-0.3.2-installer-linux-x86_64.sh - ``` 3. [Clone or download *DeepMind Lab*](https://github.com/deepmind/lab). + 4. Edit `lua.BUILD` to reflect how Lua is installed on your system: ```python @@ -128,6 +109,7 @@ Tested on SUSE Linux Enterprise Server 12. ``` The output of `pkg-config lua --libs --cflags` might be helpful to find the right include folders and linker options. + 5. Edit `python.BUILD` to reflect how Python is installed on your system: ```python @@ -146,4 +128,7 @@ Tested on SUSE Linux Enterprise Server 12. ``` The outputs of `rpm -ql python` and `rpm -ql python-numpy-devel` might be helpful to find the rihgt include folders. + 6. Build *DeepMind Lab* using Bazel as above. + + diff --git a/docs/issues.md b/docs/users/issues.md similarity index 90% rename from docs/issues.md rename to docs/users/issues.md index ba7b5a1f..41332933 100644 --- a/docs/issues.md +++ b/docs/users/issues.md @@ -1,9 +1,3 @@ -(Switch to: [Lua](lua_api.md) · [Python](python_api.md) · - [Level Generation](level_generation.md) · - [Tensor](tensor.md) · [Text Levels](text_level.md) · - [Build](build.md) · - Known Issues) - # Known Issues Please take note of the following subtleties when modifying *DeepMind Lab* or diff --git a/docs/python_api.md b/docs/users/python_api.md similarity index 59% rename from docs/python_api.md rename to docs/users/python_api.md index 32d1ca0c..ceef3006 100644 --- a/docs/python_api.md +++ b/docs/users/python_api.md @@ -1,64 +1,12 @@ -(Switch to: [Lua](lua_api.md) · Python · - [Level Generation](level_generation.md) · - [Tensor](tensor.md) · [Text Levels](text_level.md) · - [Build](build.md) · - [Known Issues](issues.md)) +# Python environment API -# DeepMind Lab environment documentation: Python -## Environment usage in Python - -Constructing the environment, doing one step and retrieving one observation: - -```python -import deepmind_lab - -# Construct and start the environment. -lab = deepmind_lab.Lab('seekavoid_arena_01', ['RGB_INTERLACED']) -lab.reset() - -# Create all-zeros vector for actions. -action = np.zeros([7], dtype=np.intc) - -# Advance the environment 4 frames while executing the all-zeros action. -reward = env.step(action, num_steps=4) - -# Retrieve the observations of the environment in its new state. -obs = env.observations() # dict of Numpy arrays -rgb_i = obs['RGB_INTERLACED'] -assert rgb_i.shape == (240, 320, 3) -``` - -For an example of doing the same thing with the C API, take a look at -[examples/game_main.c](../examples/game_main.c). - -Initialised environments can be asked for the list of available observations: - -```python -import pprint -lab = deepmind_lab.Lab('seekavoid_arena_01', []) -observation_spec = lab.observation_spec() -pprint.pprint(observation_spec) -# Outputs: -# [{'dtype': , -# 'name': 'RGB_INTERLACED', -# 'shape': (240, 320, 3)}, -# {'dtype': , -# 'name': 'RGBD_INTERLACED', -# 'shape': (240, 320, 4)}, -# {'dtype': , 'name': 'RGB', 'shape': (3, 240, 320)}, -# {'dtype': , 'name': 'RGBD', 'shape': (4, 240, 320)}, -# {'dtype': , 'name': 'VEL.TRANS', 'shape': (3,)}, -# {'dtype': , 'name': 'VEL.ROT', 'shape': (3,)}] -``` - -For full documentation of the Python environment API, see below. - -## Python environment API The Python module `deepmind_lab` defines the `Lab` class. For example -usage, there is [python/dmlab_module_test.py](../python/dmlab_module_test.py) -and [python/random_agent.py](../python/random_agent.py). +usage, there is +[python/tests/dmlab_module_test.py](../../python/tests/dmlab_module_test.py), +[python/random_agent.py](../../python/random_agent.py), and +[python/random_agent_simple.py](../../python/random_agent_simple.py). ### class `deepmind_lab.Lab`(*level*, *observations*, *config={}*) @@ -74,7 +22,27 @@ Option | Description | Default value `width` | horizontal resolution of the observation frames | `'320'` `height` | vertical resolution of the observation frames | `'240'` `fps` | frames per second | `'60'` -`appendCommand` | commands for the internal Quake console, see also the [Lua map API](lua_api.md#commandlineold-commandline-string) | `''` +`appendCommand` | commands for the internal Quake console, see also the [Lua map API](/docs/developers/reference/lua_api.md#commandlineold-commandline-string) | `''` + +Unrecognized options are passed down to the level's init function. In Lua, +this is `kwargs.opts` in `api:init`. + +For example, you can run a modified version of the rat_object_eat level with +these calls, + +```python +import deepmind_lab + +observations = ['RGBD'] +env = deepmind_lab.Lab('rat_object_eat', observations, + config={'width': '640', # screen size, in pixels + 'height': '480', # screen size, in pixels + 'categoryCount': '8', # rat_object_eat option + 'mazeHeight': '23', # common rat option + 'mazeWidth': '23', # common rat option + 'objectCount': '15'}) # common rat option +env.reset() +``` DeepMind Lab environment objects have the following methods: @@ -113,6 +81,11 @@ specification given in `action_spec`(), otherwise the behaviour is undefined. Returns a list specifying the available observations *DeepMind Lab* supports. See above for an example. +### `events`() + +Returns a list of events that has occurred since the last call to `reset`() or +`step`(). Each event is a tuple of a name, and a list of observations. + ### `fps`() An advisory metric that correlates discrete environment steps @@ -123,8 +96,8 @@ An advisory metric that correlates discrete environment steps Returns a dict specifying the shape of the actions expected by `step`(): ```python -lab = deepmind_lab.Lab('tests/demo_map', []) -action_spec = lab.action_spec() +env = deepmind_lab.Lab('test_levels/empty_room_test', []) +action_spec = env.action_spec() pprint.pprint(action_spec) # Outputs: # [{'max': 512, 'min': -512, 'name': 'LOOK_LEFT_RIGHT_PIXELS_PER_FRAME'}, @@ -142,8 +115,8 @@ Returns a dict, with every observation type passed at initialization as a Numpy array: ```python -lab = deepmind_lab.Lab('tests/demo_map', ['RGBD']) -lab.reset() +env = deepmind_lab.Lab('test_levels/empty_room_test', ['RGBD']) +env.reset() obs = env.observations() obs['RGBD'].dtype # => dtype('int64') @@ -154,3 +127,5 @@ obs['RGBD'].dtype Closes the environment and releases the underlying Quake III Arena instance. The only method call allowed for closed environments is `is_running`(). + + diff --git a/docs/users/run_as_human.md b/docs/users/run_as_human.md new file mode 100644 index 00000000..40893bb1 --- /dev/null +++ b/docs/users/run_as_human.md @@ -0,0 +1,33 @@ +# How to Run *DeepMind Lab* as a Human + +*DeepMind Lab* can be run from the command line via the stand-alone `:game` +target. + + + +## Running with Bazel + + + +Build and run *DeepMind Lab* using Bazel. +Specify the level name (without `.lua`) with the `-l` or `--level_script` +flag. For example, to run `assets/game_scripts/lt_chasm.lua`, + +```shell +lab$ bazel run :game -- -l lt_chasm +``` + +Once the binary is built, you can also run it directly without Bazel: +```shell +lab$ bazel-bin/game -l lt_chasm +``` +(Running through `bazel` ensures that the binary is rebuilt if necessary.) + + +Specify setting overrides with the `-s` or `--level_setting` flag: + +```shell +lab$ bazel run :game -- -l lt_chasm \ + -s categoryCount=8 -s mazeHeight=23 -s mazeWidth=23 -s objectCount=15 +``` + diff --git a/docs/users/run_from_script.md b/docs/users/run_from_script.md new file mode 100644 index 00000000..3fa8d468 --- /dev/null +++ b/docs/users/run_from_script.md @@ -0,0 +1,70 @@ +# How to Run *DeepMind Lab* from a Script + +*DeepMind Lab* can be run, + + * from a [Python script](#running-with-python) + +It can also be invoked, + +* from a [C program](#running-with-c) + + +## Running with Python + + + + +For a complete example script, please see, +[examples/game_main.py](../../examples/game_main.py). + +Construct the environment, step once, and retrieve an observation, + +```python +import deepmind_lab + +# Construct and start the environment. +env = deepmind_lab.Lab('seekavoid_arena_01', ['RGB_INTERLACED']) +env.reset() + +# Create all-zeros vector for actions. +action = np.zeros([7], dtype=np.intc) + +# Advance the environment 4 frames while executing the all-zeros action. +reward = env.step(action, num_steps=4) + +# Retrieve the observations of the environment in its new state. +obs = env.observations() # dict of Numpy arrays +rgb_i = obs['RGB_INTERLACED'] +assert rgb_i.shape == (240, 320, 3) +``` + +Initialized environments can be asked for the list of available observations, + +```python +import pprint +env = deepmind_lab.Lab('seekavoid_arena_01', []) +observation_spec = env.observation_spec() +pprint.pprint(observation_spec) +# Outputs: +# [{'dtype': , +# 'name': 'RGB_INTERLACED', +# 'shape': (240, 320, 3)}, +# {'dtype': , +# 'name': 'RGBD_INTERLACED', +# 'shape': (240, 320, 4)}, +# {'dtype': , 'name': 'RGB', 'shape': (3, 240, 320)}, +# {'dtype': , 'name': 'RGBD', 'shape': (4, 240, 320)}, +# {'dtype': , 'name': 'VEL.TRANS', 'shape': (3,)}, +# {'dtype': , 'name': 'VEL.ROT', 'shape': (3,)}] +``` + +Please see the +[Python Reference](/docs/users/python_api.md) +for complete listing of all environment functions. + + +## Running with C + +For an example of doing the same thing with the C API, please see, +[examples/game_main.c](../../examples/game_main.c). + diff --git a/engine/BUGS b/engine/BUGS deleted file mode 100644 index 081c55dc..00000000 --- a/engine/BUGS +++ /dev/null @@ -1,4 +0,0 @@ -- On Solaris/SPARC gcc optimizations higher than -O0 currently lead - to a segfault - -https://bugzilla.icculus.org/ for more. diff --git a/engine/NOTTODO b/engine/NOTTODO deleted file mode 100644 index 0db15474..00000000 --- a/engine/NOTTODO +++ /dev/null @@ -1 +0,0 @@ -http://wiki.ioquake3.org/NotToDo diff --git a/engine/README.md b/engine/README.md index f7594120..3dd355ce 100644 --- a/engine/README.md +++ b/engine/README.md @@ -106,13 +106,11 @@ Makefile.local: USE_CODEC_OPUS - enable Ogg Opus support USE_MUMBLE - enable Mumble support USE_VOIP - enable built-in VoIP support + USE_FREETYPE - enable FreeType support for rendering fonts USE_INTERNAL_LIBS - build internal libraries instead of dynamically linking against system libraries; this just sets - the default for USE_INTERNAL_SPEEX etc. + the default for USE_INTERNAL_ZLIB etc. and USE_LOCAL_HEADERS - USE_INTERNAL_SPEEX - build internal speex library instead of dynamically - linking against system libspeex - USE_FREETYPE - enable FreeType support for rendering fonts USE_INTERNAL_ZLIB - build and link against internal zlib USE_INTERNAL_JPEG - build and link against internal JPEG library USE_INTERNAL_OGG - build and link against internal ogg library @@ -142,6 +140,12 @@ The defaults for these variables differ depending on the target platform. behaviour, 0 for standard q3 cl_mouseAccelOffset - Tuning the acceleration curve, see below + con_autochat - Set to 0 to disable sending console input + text as chat when there is not a slash + at the beginning + con_autoclear - Set to 0 to disable clearing console + input text when console is closed + in_joystickUseAnalog - Do not translate joystick axis events to keyboard commands @@ -329,6 +333,8 @@ The defaults for these variables differ depending on the target platform. cvar_modified [filter] - list modified cvars, can filter results (such as "r*" for renderer cvars) like cvarlist which lists all cvars + + addbot random - the bot name "random" now selects a random bot ``` diff --git a/engine/code/botlib/be_aas_debug.c b/engine/code/botlib/be_aas_debug.c index ab44bc0b..b9a1465b 100644 --- a/engine/code/botlib/be_aas_debug.c +++ b/engine/code/botlib/be_aas_debug.c @@ -774,4 +774,5 @@ void AAS_FloodAreas(vec3_t origin) areanum = AAS_PointAreaNum(origin); cluster = AAS_AreaCluster(areanum); AAS_FloodAreas_r(areanum, cluster, done); + FreeMemory(done); } diff --git a/engine/code/botlib/be_aas_file.c b/engine/code/botlib/be_aas_file.c index f74f5318..f4d91720 100644 --- a/engine/code/botlib/be_aas_file.c +++ b/engine/code/botlib/be_aas_file.c @@ -61,8 +61,8 @@ void AAS_SwapAASData(void) aasworld.bboxes[i].flags = LittleLong(aasworld.bboxes[i].flags); for (j = 0; j < 3; j++) { - aasworld.bboxes[i].mins[j] = LittleLong(aasworld.bboxes[i].mins[j]); - aasworld.bboxes[i].maxs[j] = LittleLong(aasworld.bboxes[i].maxs[j]); + aasworld.bboxes[i].mins[j] = LittleFloat(aasworld.bboxes[i].mins[j]); + aasworld.bboxes[i].maxs[j] = LittleFloat(aasworld.bboxes[i].maxs[j]); } //end for } //end for //vertexes diff --git a/engine/code/botlib/be_aas_main.c b/engine/code/botlib/be_aas_main.c index 08f82135..bd55fda1 100644 --- a/engine/code/botlib/be_aas_main.c +++ b/engine/code/botlib/be_aas_main.c @@ -220,10 +220,9 @@ void AAS_ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_ int AAS_LoadFiles(const char *mapname) { int errnum; - char aasfile[MAX_PATH]; -// char bspfile[MAX_PATH]; + char aasfile[MAX_QPATH]; - strcpy(aasworld.mapname, mapname); + Q_strncpyz(aasworld.mapname, mapname, sizeof(aasworld.mapname)); //NOTE: first reset the entity links into the AAS areas and BSP leaves // the AAS link heap and BSP link heap are reset after respectively the // AAS file and BSP file are loaded @@ -232,13 +231,13 @@ int AAS_LoadFiles(const char *mapname) AAS_LoadBSPFile(); //load the aas file - Com_sprintf(aasfile, MAX_PATH, "maps/%s.aas", mapname); + Com_sprintf(aasfile, sizeof(aasfile), "maps/%s.aas", mapname); errnum = AAS_LoadAASFile(aasfile); if (errnum != BLERR_NOERROR) return errnum; botimport.Print(PRT_MESSAGE, "loaded %s\n", aasfile); - strncpy(aasworld.filename, aasfile, MAX_PATH); + Q_strncpyz(aasworld.filename, aasfile, sizeof(aasworld.filename)); return BLERR_NOERROR; } //end of the function AAS_LoadFiles //=========================================================================== diff --git a/engine/code/botlib/be_aas_move.c b/engine/code/botlib/be_aas_move.c index 9ce95c6c..8ff56aa9 100644 --- a/engine/code/botlib/be_aas_move.c +++ b/engine/code/botlib/be_aas_move.c @@ -553,7 +553,7 @@ int AAS_ClientMovementPrediction(struct aas_clientmove_s *move, //if on the ground or swimming if (onground || swimming) { - friction = swimming ? phys_friction : phys_waterfriction; + friction = swimming ? phys_waterfriction : phys_friction; //apply friction VectorScale(frame_test_vel, 1/frametime, frame_test_vel); AAS_ApplyFriction(frame_test_vel, friction, phys_stopspeed, frametime); diff --git a/engine/code/botlib/be_ai_chat.c b/engine/code/botlib/be_ai_chat.c index e90aaddc..2413706b 100644 --- a/engine/code/botlib/be_ai_chat.c +++ b/engine/code/botlib/be_ai_chat.c @@ -342,7 +342,7 @@ void BotQueueConsoleMessage(int chatstate, int type, char *message) m->handle = cs->handle; m->time = AAS_Time(); m->type = type; - strncpy(m->message, message, MAX_MESSAGE_SIZE); + Q_strncpyz(m->message, message, MAX_MESSAGE_SIZE); m->next = NULL; if (cs->lastmessage) { @@ -1456,7 +1456,7 @@ int BotFindMatch(char *str, bot_match_t *match, unsigned long int context) int i; bot_matchtemplate_t *ms; - strncpy(match->string, str, MAX_MESSAGE_SIZE); + Q_strncpyz(match->string, str, MAX_MESSAGE_SIZE); //remove any trailing enters while(strlen(match->string) && match->string[strlen(match->string)-1] == '\n') @@ -2114,7 +2114,7 @@ bot_chat_t *BotLoadInitialChat(char *chatfile, char *chatname) if (pass && ptr) { chattype = (bot_chattype_t *) ptr; - strncpy(chattype->name, token.string, MAX_CHATTYPE_NAME); + Q_strncpyz(chattype->name, token.string, MAX_CHATTYPE_NAME); chattype->firstchatmessage = NULL; //add the chat type to the chat chattype->next = chat->types; @@ -2884,7 +2884,7 @@ void BotSetChatName(int chatstate, char *name, int client) if (!cs) return; cs->client = client; Com_Memset(cs->name, 0, sizeof(cs->name)); - strncpy(cs->name, name, sizeof(cs->name)); + strncpy(cs->name, name, sizeof(cs->name)-1); cs->name[sizeof(cs->name)-1] = '\0'; } //end of the function BotSetChatName //=========================================================================== diff --git a/engine/code/botlib/be_ai_goal.c b/engine/code/botlib/be_ai_goal.c index 3a5d01ab..0bbc6a5e 100644 --- a/engine/code/botlib/be_ai_goal.c +++ b/engine/code/botlib/be_ai_goal.c @@ -268,7 +268,7 @@ itemconfig_t *LoadItemConfig(char *filename) { int max_iteminfo; token_t token; - char path[MAX_PATH]; + char path[MAX_QPATH]; source_t *source; itemconfig_t *ic; iteminfo_t *ii; @@ -281,7 +281,7 @@ itemconfig_t *LoadItemConfig(char *filename) LibVarSet( "max_iteminfo", "256" ); } - strncpy( path, filename, MAX_PATH ); + Q_strncpyz(path, filename, sizeof(path)); PC_SetBaseFolder(BOTFILESBASEFOLDER); source = LoadSourceFile( path ); if( !source ) { @@ -314,7 +314,7 @@ itemconfig_t *LoadItemConfig(char *filename) return NULL; } //end if StripDoubleQuotes(token.string); - strncpy(ii->classname, token.string, sizeof(ii->classname)-1); + Q_strncpyz(ii->classname, token.string, sizeof(ii->classname)); if (!ReadStructure(source, &iteminfo_struct, (char *) ii)) { FreeMemory(ic); @@ -685,8 +685,7 @@ void BotGoalName(int number, char *name, int size) { if (li->number == number) { - strncpy(name, itemconfig->iteminfo[li->iteminfo].name, size-1); - name[size-1] = '\0'; + Q_strncpyz(name, itemconfig->iteminfo[li->iteminfo].name, size); return; } //end for } //end for diff --git a/engine/code/botlib/be_ai_weap.c b/engine/code/botlib/be_ai_weap.c index 8fab4d79..df685114 100644 --- a/engine/code/botlib/be_ai_weap.c +++ b/engine/code/botlib/be_ai_weap.c @@ -199,7 +199,7 @@ weaponconfig_t *LoadWeaponConfig(char *filename) { int max_weaponinfo, max_projectileinfo; token_t token; - char path[MAX_PATH]; + char path[MAX_QPATH]; int i, j; source_t *source; weaponconfig_t *wc; @@ -219,7 +219,7 @@ weaponconfig_t *LoadWeaponConfig(char *filename) max_projectileinfo = 32; LibVarSet("max_projectileinfo", "32"); } //end if - strncpy(path, filename, MAX_PATH); + Q_strncpyz(path, filename, sizeof(path)); PC_SetBaseFolder(BOTFILESBASEFOLDER); source = LoadSourceFile(path); if (!source) diff --git a/engine/code/botlib/be_ea.c b/engine/code/botlib/be_ea.c index 41653fda..efecd28d 100644 --- a/engine/code/botlib/be_ea.c +++ b/engine/code/botlib/be_ea.c @@ -39,7 +39,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "be_ea.h" #define MAX_USERMOVE 400 -#define MAX_COMMANDARGUMENTS 10 bot_input_t *botinputs; diff --git a/engine/code/botlib/be_interface.c b/engine/code/botlib/be_interface.c index 6f599753..415c1b2b 100644 --- a/engine/code/botlib/be_interface.c +++ b/engine/code/botlib/be_interface.c @@ -238,7 +238,7 @@ int Export_BotLibShutdown(void) // Returns: - // Changes Globals: - //=========================================================================== -int Export_BotLibVarSet(char *var_name, char *value) +int Export_BotLibVarSet(const char *var_name, const char *value) { LibVarSet(var_name, value); return BLERR_NOERROR; @@ -249,7 +249,7 @@ int Export_BotLibVarSet(char *var_name, char *value) // Returns: - // Changes Globals: - //=========================================================================== -int Export_BotLibVarGet(char *var_name, char *value, int size) +int Export_BotLibVarGet(const char *var_name, char *value, int size) { char *varvalue; diff --git a/engine/code/botlib/botlib.h b/engine/code/botlib/botlib.h index abdaa688..830a5eaa 100644 --- a/engine/code/botlib/botlib.h +++ b/engine/code/botlib/botlib.h @@ -409,9 +409,9 @@ typedef struct botlib_export_s //shutdown the bot library, returns BLERR_ int (*BotLibShutdown)(void); //sets a library variable returns BLERR_ - int (*BotLibVarSet)(char *var_name, char *value); + int (*BotLibVarSet)(const char *var_name, const char *value); //gets a library variable returns BLERR_ - int (*BotLibVarGet)(char *var_name, char *value, int size); + int (*BotLibVarGet)(const char *var_name, char *value, int size); //sets a C-like define returns BLERR_ int (*PC_AddGlobalDefine)(char *string); diff --git a/engine/code/botlib/l_libvar.c b/engine/code/botlib/l_libvar.c index 0270781f..4020d0fe 100644 --- a/engine/code/botlib/l_libvar.c +++ b/engine/code/botlib/l_libvar.c @@ -42,7 +42,7 @@ libvar_t *libvarlist = NULL; // Returns: - // Changes Globals: - //=========================================================================== -float LibVarStringValue(char *string) +float LibVarStringValue(const char *string) { int dotfound = 0; float value = 0; @@ -80,7 +80,7 @@ float LibVarStringValue(char *string) // Returns: - // Changes Globals: - //=========================================================================== -libvar_t *LibVarAlloc(char *var_name) +libvar_t *LibVarAlloc(const char *var_name) { libvar_t *v; @@ -128,7 +128,7 @@ void LibVarDeAllocAll(void) // Returns: - // Changes Globals: - //=========================================================================== -libvar_t *LibVarGet(char *var_name) +libvar_t *LibVarGet(const char *var_name) { libvar_t *v; @@ -147,7 +147,7 @@ libvar_t *LibVarGet(char *var_name) // Returns: - // Changes Globals: - //=========================================================================== -char *LibVarGetString(char *var_name) +char *LibVarGetString(const char *var_name) { libvar_t *v; @@ -167,7 +167,7 @@ char *LibVarGetString(char *var_name) // Returns: - // Changes Globals: - //=========================================================================== -float LibVarGetValue(char *var_name) +float LibVarGetValue(const char *var_name) { libvar_t *v; @@ -187,7 +187,7 @@ float LibVarGetValue(char *var_name) // Returns: - // Changes Globals: - //=========================================================================== -libvar_t *LibVar(char *var_name, char *value) +libvar_t *LibVar(const char *var_name, const char *value) { libvar_t *v; v = LibVarGet(var_name); @@ -210,7 +210,7 @@ libvar_t *LibVar(char *var_name, char *value) // Returns: - // Changes Globals: - //=========================================================================== -char *LibVarString(char *var_name, char *value) +char *LibVarString(const char *var_name, const char *value) { libvar_t *v; @@ -223,7 +223,7 @@ char *LibVarString(char *var_name, char *value) // Returns: - // Changes Globals: - //=========================================================================== -float LibVarValue(char *var_name, char *value) +float LibVarValue(const char *var_name, const char *value) { libvar_t *v; @@ -236,7 +236,7 @@ float LibVarValue(char *var_name, char *value) // Returns: - // Changes Globals: - //=========================================================================== -void LibVarSet(char *var_name, char *value) +void LibVarSet(const char *var_name, const char *value) { libvar_t *v; @@ -263,7 +263,7 @@ void LibVarSet(char *var_name, char *value) // Returns: - // Changes Globals: - //=========================================================================== -qboolean LibVarChanged(char *var_name) +qboolean LibVarChanged(const char *var_name) { libvar_t *v; @@ -283,7 +283,7 @@ qboolean LibVarChanged(char *var_name) // Returns: - // Changes Globals: - //=========================================================================== -void LibVarSetNotModified(char *var_name) +void LibVarSetNotModified(const char *var_name) { libvar_t *v; diff --git a/engine/code/botlib/l_libvar.h b/engine/code/botlib/l_libvar.h index d96685f4..531da04b 100644 --- a/engine/code/botlib/l_libvar.h +++ b/engine/code/botlib/l_libvar.h @@ -43,21 +43,21 @@ typedef struct libvar_s //removes all library variables void LibVarDeAllocAll(void); //gets the library variable with the given name -libvar_t *LibVarGet(char *var_name); +libvar_t *LibVarGet(const char *var_name); //gets the string of the library variable with the given name -char *LibVarGetString(char *var_name); +char *LibVarGetString(const char *var_name); //gets the value of the library variable with the given name -float LibVarGetValue(char *var_name); +float LibVarGetValue(const char *var_name); //creates the library variable if not existing already and returns it -libvar_t *LibVar(char *var_name, char *value); +libvar_t *LibVar(const char *var_name, const char *value); //creates the library variable if not existing already and returns the value -float LibVarValue(char *var_name, char *value); +float LibVarValue(const char *var_name, const char *value); //creates the library variable if not existing already and returns the value string -char *LibVarString(char *var_name, char *value); +char *LibVarString(const char *var_name, const char *value); //sets the library variable -void LibVarSet(char *var_name, char *value); +void LibVarSet(const char *var_name, const char *value); //returns true if the library variable has been modified -qboolean LibVarChanged(char *var_name); +qboolean LibVarChanged(const char *var_name); //sets the library variable to unmodified -void LibVarSetNotModified(char *var_name); +void LibVarSetNotModified(const char *var_name); diff --git a/engine/code/botlib/l_log.c b/engine/code/botlib/l_log.c index ee25604e..ba51e008 100644 --- a/engine/code/botlib/l_log.c +++ b/engine/code/botlib/l_log.c @@ -75,7 +75,7 @@ void Log_Open(char *filename) botimport.Print(PRT_ERROR, "can't open the log file %s\n", filename); return; } //end if - strncpy(logfile.filename, filename, MAX_LOGFILENAMESIZE); + Q_strncpyz(logfile.filename, filename, MAX_LOGFILENAMESIZE); botimport.Print(PRT_MESSAGE, "Opened log %s\n", logfile.filename); } //end of the function Log_Create //=========================================================================== diff --git a/engine/code/botlib/l_precomp.c b/engine/code/botlib/l_precomp.c index fd4d4f3d..282ac8a8 100644 --- a/engine/code/botlib/l_precomp.c +++ b/engine/code/botlib/l_precomp.c @@ -556,7 +556,7 @@ void PC_PrintDefineHashTable(define_t **definehash) int PC_NameHash(char *name) { - int register hash, i; + int hash, i; hash = 0; for (i = 0; name[i] != '\0'; i++) @@ -979,7 +979,11 @@ int PC_Directive_include(source_t *source) { script_t *script; token_t token; - char path[MAX_PATH]; +#ifdef BSPC + char path[2 * MAX_PATH]; +#else + char path[MAX_QPATH]; +#endif #ifdef QUAKE foundfile_t file; #endif //QUAKE @@ -1052,7 +1056,7 @@ int PC_Directive_include(source_t *source) { Com_Memset(&file, 0, sizeof(foundfile_t)); script = LoadScriptFile(path); - if (script) strncpy(script->filename, path, MAX_PATH); + if (script) Q_strncpyz(script->filename, path, sizeof(script->filename)); } //end if #endif //QUAKE if (!script) @@ -1340,7 +1344,7 @@ define_t *PC_DefineFromString(char *string) script = LoadScriptMemory(string, strlen(string), "*extern"); //create a new source Com_Memset(&src, 0, sizeof(source_t)); - strncpy(src.filename, "*extern", sizeof(src.filename) - 1); + Q_strncpyz(src.filename, "*extern", sizeof(src.filename)); src.scriptstack = script; #if DEFINEHASHING src.definehash = GetClearedMemory(DEFINEHASHSIZE * sizeof(define_t *)); @@ -2977,7 +2981,7 @@ void PC_SetIncludePath(source_t *source, char *path) { size_t len; - Q_strncpyz(source->includepath, path, MAX_PATH-1); + Q_strncpyz(source->includepath, path, sizeof(source->includepath)-1); len = strlen(source->includepath); //add trailing path seperator @@ -3018,7 +3022,7 @@ source_t *LoadSourceFile(const char *filename) source = (source_t *) GetMemory(sizeof(source_t)); Com_Memset(source, 0, sizeof(source_t)); - strncpy(source->filename, filename, MAX_PATH); + Q_strncpyz(source->filename, filename, sizeof(source->filename)); source->scriptstack = script; source->tokens = NULL; source->defines = NULL; @@ -3051,7 +3055,7 @@ source_t *LoadSourceMemory(char *ptr, int length, char *name) source = (source_t *) GetMemory(sizeof(source_t)); Com_Memset(source, 0, sizeof(source_t)); - strncpy(source->filename, name, MAX_PATH); + Q_strncpyz(source->filename, name, sizeof(source->filename)); source->scriptstack = script; source->tokens = NULL; source->defines = NULL; diff --git a/engine/code/botlib/l_script.c b/engine/code/botlib/l_script.c index bc068bc1..e07416fa 100644 --- a/engine/code/botlib/l_script.c +++ b/engine/code/botlib/l_script.c @@ -812,7 +812,7 @@ int PS_ReadPunctuation(script_t *script, token_t *token) //if the script contains the punctuation if (!strncmp(script->script_p, p, len)) { - strncpy(token->string, p, MAX_TOKEN); + Q_strncpyz(token->string, p, MAX_TOKEN); script->script_p += len; token->type = TT_PUNCTUATION; //sub type is the number of the punctuation diff --git a/engine/code/botlib/l_struct.c b/engine/code/botlib/l_struct.c index ee77c01b..1373e079 100644 --- a/engine/code/botlib/l_struct.c +++ b/engine/code/botlib/l_struct.c @@ -221,7 +221,7 @@ int ReadString(source_t *source, fielddef_t *fd, void *p) //remove the double quotes StripDoubleQuotes(token.string); //copy the string - strncpy((char *) p, token.string, MAX_STRINGFIELD); + strncpy((char *) p, token.string, MAX_STRINGFIELD-1); //make sure the string is closed with a zero ((char *)p)[MAX_STRINGFIELD-1] = '\0'; // diff --git a/engine/code/botlib/l_utils.h b/engine/code/botlib/l_utils.h index 6944d06f..0c7e6fa4 100644 --- a/engine/code/botlib/l_utils.h +++ b/engine/code/botlib/l_utils.h @@ -30,8 +30,5 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************/ #define Vector2Angles(v,a) vectoangles(v,a) -#ifndef MAX_PATH -#define MAX_PATH MAX_QPATH -#endif #define Maximum(x,y) (x > y ? x : y) #define Minimum(x,y) (x < y ? x : y) diff --git a/engine/code/bspc/aas_store.c b/engine/code/bspc/aas_store.c index 2079e346..d79143e0 100644 --- a/engine/code/bspc/aas_store.c +++ b/engine/code/bspc/aas_store.c @@ -684,8 +684,13 @@ qboolean AAS_GetPlane(vec3_t normal, vec_t dist, int *planenum) { aas_plane_t *plane, temp; - //if (AAS_FindPlane(normal, dist, planenum)) return true; - if (AAS_FindHashedPlane(normal, dist, planenum)) return true; + // NOTE: DeepMind change: re-enabled linear search over hashed search. + // Rationale: we found that the hashed search is broken in some + // circumstances, and that instead of offering any speedup, it is + // actually slightly slowing us down. It's possible that our grid-like + // maps cause numerous collisions in the hash table. + if (AAS_FindPlane(normal, dist, planenum)) return true; + // if (AAS_FindHashedPlane(normal, dist, planenum)) return true; if (aasworld.numplanes >= max_aas.max_planes-1) { diff --git a/engine/code/bspc/bspc.c b/engine/code/bspc/bspc.c index ca2f1a57..2fbfbd6c 100644 --- a/engine/code/bspc/bspc.c +++ b/engine/code/bspc/bspc.c @@ -280,10 +280,15 @@ int main (int argc, char **argv) myargv = argv; start_time = I_FloatTime(); - - Log_Open("bspc.log"); //open a log file - Log_Print("BSPC version "BSPC_VERSION", %s %s\n", __DATE__, __TIME__); - + for (i = 1; i < argc; i++) + { + if (!stricmp(argv[i], "-logFile")) + { + if (i + 1 >= argc) { break; } + Log_Open(argv[i + 1]); //open a log file + Log_Print("BSPC version "BSPC_VERSION", %s %s\n", __DATE__, __TIME__); + } //end if + } DefaultCfg(); for (i = 1; i < argc; i++) { @@ -293,6 +298,11 @@ int main (int argc, char **argv) numthreads = atoi(argv[++i]); Log_Print("threads = %d\n", numthreads); } //end if + else if (!stricmp(argv[i], "-logFile")) + { + if (i + 1 >= argc) {i = 0; break;} + ++i; // Already processed. + } //end if else if (!stricmp(argv[i], "-noverbose")) { Log_Print("verbose = false\n"); diff --git a/engine/code/cgame/cg_draw.c b/engine/code/cgame/cg_draw.c index c7606e99..2704dc64 100644 --- a/engine/code/cgame/cg_draw.c +++ b/engine/code/cgame/cg_draw.c @@ -730,26 +730,44 @@ static float CG_DrawSnapshot( float y ) { return y + BIGCHAR_HEIGHT + 4; } +static void CG_DrawScriptFilledRectangles( void ) { + int i, x, y, width, height; + vec4_t rgba; + int c = dmlab_make_filled_rectangles( SCREEN_WIDTH, SCREEN_HEIGHT ); + for (i = 0; i < c; ++i) { + dmlab_get_filled_rectangle( i, &x, &y, &width, &height, rgba ); + CG_FillRect( x, y, width, height, rgba ); + } +} + static void CG_DrawScriptMessage( void ) { char s[80]; - int c = dmlab_make_screen_messages( - 640, 480, BIGCHAR_HEIGHT + 4, 80 ); - int i, w, x = 0, y = 0, align_l0_r1_c2 = 0; + int c = 0; + int i, x = 0, y = 0, align_l0_r1_c2 = 0; + int shadow = 1; + vec4_t rgba = {1.0f, 1.0f, 1.0f, 1.0f}; + c = dmlab_make_screen_messages( + SCREEN_WIDTH, SCREEN_HEIGHT, BIGCHAR_HEIGHT + 4, 80 ); for (i = 0; i < c; ++i) { - dmlab_get_screen_message( i, s, &x, &y, &align_l0_r1_c2 ); + dmlab_get_screen_message( i, s, &x, &y, &align_l0_r1_c2, &shadow, rgba ); + y = y + 2; switch (align_l0_r1_c2) { case 0: // Left - CG_DrawBigString( x, y + 2, s, 1.0F ); break; case 1: // Right - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; - CG_DrawBigString( x - w, y + 2, s, 1.0F ); + x -= CG_DrawStrlen( s ) * BIGCHAR_WIDTH; break; case 2: // Center default: - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; - CG_DrawBigString( x - w / 2, y + 2, s, 1.0F ); break; + x -= CG_DrawStrlen( s ) * BIGCHAR_WIDTH / 2; + break; } + CG_DrawStringExt( + x, y, s, rgba, + /*forceColor=*/qfalse, + /*shadow=*/shadow != 0, + BIGCHAR_WIDTH, BIGCHAR_HEIGHT, + /*maxChars=*/0 ); } } @@ -1046,16 +1064,95 @@ Draw the small two score display */ #ifndef MISSIONPACK static float CG_DrawScores( float y ) { - int score = dmlab_player_score(); - const char* s = va( "%2i", score ); - vec4_t color = { 0.0f, 0.0f, 1.0f, 0.33f }; - int x = 640; - int w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; - x -= w; - y -= BIGCHAR_HEIGHT + 8; - CG_DrawPic( x, y - 4, w, BIGCHAR_HEIGHT + 8, cgs.media.selectShader ); - CG_FillRect( x, y - 4, w, BIGCHAR_HEIGHT + 8, color ); - CG_DrawBigString( x + 4, y, s, 1.0f ); + const char *s; + int s1, s2; + int x, w; + int v; + vec4_t color; + float y1; + gitem_t *item; + + s1 = cgs.scores1; + s2 = cgs.scores2; + + // draw from the right side to left + if ( cgs.gametype >= GT_TEAM ) { + y -= BIGCHAR_HEIGHT + 8; + y1 = y; + x = 640; + color[0] = 0.0f; + color[1] = 0.0f; + color[2] = 1.0f; + color[3] = 0.33f; + s = va( "%2i", s2 ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; + x -= w; + CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); + } + CG_DrawBigString( x + 4, y, s, 1.0F); + + if ( cgs.gametype == GT_CTF ) { + // Display flag status + item = BG_FindItemForPowerup( PW_BLUEFLAG ); + + if (item) { + y1 = y - BIGCHAR_HEIGHT - 8; + if( cgs.blueflag >= 0 && cgs.blueflag <= 2 ) { + CG_DrawPic( x, y1-4, w, BIGCHAR_HEIGHT+8, cgs.media.blueFlagShader[cgs.blueflag] ); + } + } + } + color[0] = 1.0f; + color[1] = 0.0f; + color[2] = 0.0f; + color[3] = 0.33f; + s = va( "%2i", s1 ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; + x -= w; + CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { + CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); + } + CG_DrawBigString( x + 4, y, s, 1.0F); + + if ( cgs.gametype == GT_CTF ) { + // Display flag status + item = BG_FindItemForPowerup( PW_REDFLAG ); + + if (item) { + y1 = y - BIGCHAR_HEIGHT - 8; + if( cgs.redflag >= 0 && cgs.redflag <= 2 ) { + CG_DrawPic( x, y1-4, w, BIGCHAR_HEIGHT+8, cgs.media.redFlagShader[cgs.redflag] ); + } + } + } + + if ( cgs.gametype >= GT_CTF ) { + v = cgs.capturelimit; + } else { + v = cgs.fraglimit; + } + if ( v ) { + s = va( "%2i", v ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; + x -= w; + CG_DrawBigString( x + 4, y, s, 1.0F); + } + y = y1; + } else { + int score = dmlab_player_score(); + const char* s = va( "%2i", score ); + vec4_t color = { 0.0f, 0.0f, 1.0f, 0.33f }; + int x = 640; + int w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; + x -= w; + y -= BIGCHAR_HEIGHT + 8; + CG_DrawPic( x, y - 4, w, BIGCHAR_HEIGHT + 8, cgs.media.selectShader ); + CG_FillRect( x, y - 4, w, BIGCHAR_HEIGHT + 8, color ); + CG_DrawBigString( x + 4, y, s, 1.0f ); + } return y - 8; } @@ -1096,10 +1193,15 @@ static float CG_DrawPowerups( float y ) { if ( !ps->powerups[ i ] ) { continue; } - t = ps->powerups[ i ] - cg.time; - // ZOID--don't draw if the power up has unlimited time (999 seconds) + + // ZOID--don't draw if the power up has unlimited time // This is true of the CTF flags - if ( t < 0 || t > 999000) { + if ( ps->powerups[ i ] == INT_MAX ) { + continue; + } + + t = ps->powerups[ i ] - cg.time; + if ( t <= 0 ) { continue; } @@ -1174,10 +1276,13 @@ static void CG_DrawLowerRight( void ) { y = 480 - ICON_SIZE; - if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 2 ) { - y = CG_DrawTeamOverlay( y, qtrue, qfalse ); - } - + if ( cgs.gametype >= GT_TEAM ) { + if ( cg_drawTeamOverlay.integer == 2 ) { + y = CG_DrawTeamOverlay( y, qtrue, qfalse ); + } else { + y -= TINYCHAR_HEIGHT; + } + } y = CG_DrawScores( y ); CG_DrawPowerups( y ); } @@ -1268,12 +1373,12 @@ static void CG_DrawTeamInfo( void ) { h = (cgs.teamChatPos - cgs.teamLastChatPos) * TINYCHAR_HEIGHT; - if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { + if ( cgs.clientinfo[cg.clientNum].team == TEAM_RED ) { hcolor[0] = 1.0f; hcolor[1] = 0.0f; hcolor[2] = 0.0f; hcolor[3] = 0.33f; - } else if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + } else if ( cgs.clientinfo[cg.clientNum].team == TEAM_BLUE ) { hcolor[0] = 0.0f; hcolor[1] = 0.0f; hcolor[2] = 1.0f; @@ -1505,8 +1610,13 @@ static void CG_DrawDisconnect( void ) { return; } +#ifdef MISSIONPACK + x = 640 - 48; + y = 480 - 144; +#else x = 640 - 48; y = 480 - 48; +#endif CG_DrawPic( x, y, 48, 48, trap_R_RegisterShader("gfx/2d/net.tga" ) ); } @@ -2471,15 +2581,16 @@ static void CG_Draw2D(stereoFrame_t stereoFrame) #endif CG_DrawReward(); } - - if ( cgs.gametype >= GT_TEAM ) { + } + + if ( cgs.gametype >= GT_TEAM ) { #ifndef MISSIONPACK - CG_DrawTeamInfo(); + CG_DrawTeamInfo(); #endif - } } CG_DrawScriptMessage(); + CG_DrawScriptFilledRectangles(); CG_DrawVote(); CG_DrawTeamVote(); @@ -2510,13 +2621,6 @@ static void CG_Draw2D(stereoFrame_t stereoFrame) } -static void CG_DrawTourneyScoreboard( void ) { -#ifdef MISSIONPACK -#else - CG_DrawOldTourneyScoreboard(); -#endif -} - /* ===================== CG_DrawActive diff --git a/engine/code/cgame/cg_drawtools.c b/engine/code/cgame/cg_drawtools.c index c0ce1ef8..fb9e29a4 100644 --- a/engine/code/cgame/cg_drawtools.c +++ b/engine/code/cgame/cg_drawtools.c @@ -91,7 +91,7 @@ void CG_DrawRect( float x, float y, float width, float height, float size, const trap_R_SetColor( color ); CG_DrawTopBottom(x, y, width, height, size); - CG_DrawSides(x, y, width, height, size); + CG_DrawSides(x, y + size, width, height - size * 2, size); trap_R_SetColor( NULL ); } diff --git a/engine/code/cgame/cg_ents.c b/engine/code/cgame/cg_ents.c index 7629f0b8..9433f513 100644 --- a/engine/code/cgame/cg_ents.c +++ b/engine/code/cgame/cg_ents.c @@ -232,6 +232,7 @@ static void CG_Item( centity_t *cent ) { float frac; float scale; weaponInfo_t *wi; + qboolean movementFlags; es = ¢->currentState; if ( es->modelindex >= bg_numItems ) { @@ -259,18 +260,25 @@ static void CG_Item( centity_t *cent ) { } // items bob up and down continuously - scale = 0.005 + cent->currentState.number * 0.00001; - cent->lerpOrigin[2] += 4 + cos( ( cg.time + 1000 ) * scale ) * 4; - memset (&ent, 0, sizeof(ent)); + movementFlags = item->giType == IT_REWARD && cent->currentState.generic1 != REWARD_MV_BOB; + + memset( &ent, 0, sizeof( ent ) ); - // autorotate at one of two speeds - if ( item->giType == IT_HEALTH ) { - VectorCopy( cg.autoAnglesFast, cent->lerpAngles ); - AxisCopy( cg.autoAxisFast, ent.axis ); + if ( movementFlags ) { + cent->lerpOrigin[2] += 4; + AnglesToAxis( es->angles, ent.axis ); } else { - VectorCopy( cg.autoAngles, cent->lerpAngles ); - AxisCopy( cg.autoAxis, ent.axis ); + scale = 0.005 + cent->currentState.number * 0.00001; + cent->lerpOrigin[2] += 4 + cos( ( cg.time + 1000 ) * scale ) * 4; + // autorotate at one of two speeds + if ( item->giType == IT_HEALTH ) { + VectorCopy( cg.autoAnglesFast, cent->lerpAngles ); + AxisCopy( cg.autoAxisFast, ent.axis ); + } else { + VectorCopy( cg.autoAngles, cent->lerpAngles ); + AxisCopy( cg.autoAxis, ent.axis ); + } } wi = NULL; diff --git a/engine/code/cgame/cg_local.h b/engine/code/cgame/cg_local.h index 92a506d5..c12069d3 100644 --- a/engine/code/cgame/cg_local.h +++ b/engine/code/cgame/cg_local.h @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 1999-2005 Id Software, Inc., 2016 Google Inc. +Copyright (C) 1999-2005 Id Software, Inc., 2016-2017 Google Inc. This file is part of Quake III Arena source code. @@ -1173,9 +1173,11 @@ extern vmCvar_t cg_timescaleFadeEnd; extern vmCvar_t cg_timescaleFadeSpeed; extern vmCvar_t cg_timescale; extern vmCvar_t cg_cameraMode; +#ifdef MISSIONPACK extern vmCvar_t cg_smallFont; extern vmCvar_t cg_bigFont; extern vmCvar_t cg_noTaunt; +#endif extern vmCvar_t cg_noProjectileTrail; extern vmCvar_t cg_oldRail; extern vmCvar_t cg_oldRocket; @@ -1233,7 +1235,7 @@ void CG_ZoomDown_f( void ); void CG_ZoomUp_f( void ); void CG_AddBufferedSound( sfxHandle_t sfx); -void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); +void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback, qboolean skipRendering ); // @@ -1446,7 +1448,7 @@ void CG_DrawInformation( void ); // cg_scoreboard.c // qboolean CG_DrawOldScoreboard( void ); -void CG_DrawOldTourneyScoreboard( void ); +void CG_DrawTourneyScoreboard( void ); // // cg_consolecmds.c diff --git a/engine/code/cgame/cg_main.c b/engine/code/cgame/cg_main.c index 4c67975a..0eff9538 100644 --- a/engine/code/cgame/cg_main.c +++ b/engine/code/cgame/cg_main.c @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 1999-2005 Id Software, Inc., 2016 Google Inc. +Copyright (C) 1999-2005 Id Software, Inc., 2016-2017 Google Inc. This file is part of Quake III Arena source code. @@ -33,7 +33,7 @@ int forceModelModificationCount = -1; void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ); void CG_Shutdown( void ); - +void CG_UpdateCustomItems( void ); /* ================ @@ -55,7 +55,7 @@ Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, i case CG_CONSOLE_COMMAND: return CG_ConsoleCommand(); case CG_DRAW_ACTIVE_FRAME: - CG_DrawActiveFrame( arg0, arg1, arg2 ); + CG_DrawActiveFrame( arg0, arg1, arg2, arg3 ); return 0; case CG_CROSSHAIR_PLAYER: return CG_CrosshairPlayer(); @@ -74,6 +74,9 @@ Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, i case CG_EVENT_HANDLING: CG_EventHandling(arg0); return 0; + case CG_UPDATE_CUSTOM_ITEMS: + CG_UpdateCustomItems(); + return 0; default: CG_Error( "vmMain: unknown command %i", command ); break; @@ -180,9 +183,11 @@ vmCvar_t cg_cameraOrbitDelay; vmCvar_t cg_timescaleFadeEnd; vmCvar_t cg_timescaleFadeSpeed; vmCvar_t cg_timescale; +#ifdef MISSIONPACK vmCvar_t cg_smallFont; vmCvar_t cg_bigFont; vmCvar_t cg_noTaunt; +#endif vmCvar_t cg_noProjectileTrail; vmCvar_t cg_oldRail; vmCvar_t cg_oldRocket; @@ -319,10 +324,12 @@ static cvarTable_t cvarTable[] = { { &pmove_fixed, "pmove_fixed", "0", CVAR_SYSTEMINFO}, { &pmove_msec, "pmove_msec", "8", CVAR_SYSTEMINFO}, - { &cg_noTaunt, "cg_noTaunt", "0", CVAR_ARCHIVE}, - { &cg_noProjectileTrail, "cg_noProjectileTrail", "0", CVAR_ARCHIVE}, +#ifdef MISSIONPACK { &cg_smallFont, "ui_smallFont", "0.25", CVAR_ARCHIVE}, { &cg_bigFont, "ui_bigFont", "0.4", CVAR_ARCHIVE}, + { &cg_noTaunt, "cg_noTaunt", "0", CVAR_ARCHIVE}, +#endif + { &cg_noProjectileTrail, "cg_noProjectileTrail", "0", CVAR_ARCHIVE}, { &cg_oldRail, "cg_oldRail", "1", CVAR_ARCHIVE}, { &cg_oldRocket, "cg_oldRocket", "1", CVAR_ARCHIVE}, { &cg_oldPlasma, "cg_oldPlasma", "1", CVAR_ARCHIVE}, @@ -952,7 +959,7 @@ static void CG_RegisterGraphics( void ) { if ( cgs.gametype >= GT_TEAM || cg_buildScript.integer ) { cgs.media.friendShader = trap_R_RegisterShader( "sprites/foe" ); cgs.media.redQuadShader = trap_R_RegisterShader("powerups/blueflag" ); - cgs.media.teamStatusBar = trap_R_RegisterShader( "gfx/2d/colorbar.tga" ); + cgs.media.teamStatusBar = trap_R_RegisterShader( "white" ); #ifdef MISSIONPACK cgs.media.blueKamikazeShader = trap_R_RegisterShader( "models/weaphits/kamikblu" ); #endif @@ -1427,7 +1434,7 @@ qboolean CG_Load_Menu(char **p) { return qtrue; } - if ( !token || token[0] == 0 ) { + if (!token[0]) { return qfalse; } @@ -1474,7 +1481,7 @@ void CG_LoadMenus(const char *menuFile) { while ( 1 ) { token = COM_ParseExt( &p, qtrue ); - if( !token || token[0] == 0 || token[0] == '}') { + if (!token[0]) { break; } @@ -1692,18 +1699,14 @@ static void CG_FeederSelection(float feederID, int index) { cg.selectedScore = index; } } -#endif -#ifdef MISSIONPACK static float CG_Cvar_Get(const char *cvar) { char buff[128]; memset(buff, 0, sizeof(buff)); trap_Cvar_VariableStringBuffer(cvar, buff, sizeof(buff)); return atof(buff); } -#endif -#ifdef MISSIONPACK void CG_Text_PaintWithCursor(float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style) { CG_Text_Paint(x, y, scale, color, text, 0, limit, style); } @@ -1972,6 +1975,30 @@ void CG_Shutdown( void ) { } +/* +================= +CG_UpdateCustomItems + +Called on restart to get custom items from gameplay (bg_itemlist) in sync with +custom items in rendering (cg_items). +================= +*/ +void CG_UpdateCustomItems( void ) { + int i; + + // Pull all the custom items back down. + BG_InitItemList(); + BG_UpdateItems(); + + // Associate each custom item with its rendering data. + for ( i = bg_defaultNumItems ; i < bg_numItems ; i++ ) { + itemInfo_t *itemInfo = &cg_items[ i ]; + itemInfo->registered = qfalse; + + CG_RegisterItemVisuals( i ); + } +} + /* ================== CG_EventHandling diff --git a/engine/code/cgame/cg_newdraw.c b/engine/code/cgame/cg_newdraw.c index 8a443d9e..04295cfd 100644 --- a/engine/code/cgame/cg_newdraw.c +++ b/engine/code/cgame/cg_newdraw.c @@ -199,15 +199,6 @@ static void CG_DrawPlayerArmorValue(rectDef_t *rect, float scale, vec4_t color, } } -#ifndef MISSIONPACK -static float healthColors[4][4] = { -// { 0.2, 1.0, 0.2, 1.0 } , { 1.0, 0.2, 0.2, 1.0 }, {0.5, 0.5, 0.5, 1} }; - { 1.0f, 0.69f, 0.0f, 1.0f } , // normal - { 1.0f, 0.2f, 0.2f, 1.0f }, // low health - { 0.5f, 0.5f, 0.5f, 1.0f}, // weapon firing - { 1.0f, 1.0f, 1.0f, 1.0f } }; // health > 100 -#endif - static void CG_DrawPlayerAmmoIcon( rectDef_t *rect, qboolean draw2D ) { centity_t *cent; vec3_t angles; @@ -841,10 +832,15 @@ static void CG_DrawAreaPowerUp(rectDef_t *rect, int align, float special, float if ( !ps->powerups[ i ] ) { continue; } - t = ps->powerups[ i ] - cg.time; - // ZOID--don't draw if the power up has unlimited time (999 seconds) + + // ZOID--don't draw if the power up has unlimited time // This is true of the CTF flags - if ( t <= 0 || t >= 999000) { + if ( ps->powerups[ i ] == INT_MAX ) { + continue; + } + + t = ps->powerups[ i ] - cg.time; + if ( t <= 0 ) { continue; } diff --git a/engine/code/cgame/cg_particles.c b/engine/code/cgame/cg_particles.c index 53507ad5..2c70281f 100644 --- a/engine/code/cgame/cg_particles.c +++ b/engine/code/cgame/cg_particles.c @@ -1027,10 +1027,6 @@ void CG_ParticleSnowFlurry (qhandle_t pshader, centity_t *cent) VectorCopy(cent->currentState.origin, p->org); - p->org[0] = p->org[0]; - p->org[1] = p->org[1]; - p->org[2] = p->org[2]; - p->vel[0] = p->vel[1] = 0; p->accel[0] = p->accel[1] = p->accel[2] = 0; diff --git a/engine/code/cgame/cg_players.c b/engine/code/cgame/cg_players.c index 9c76aac3..500c63be 100644 --- a/engine/code/cgame/cg_players.c +++ b/engine/code/cgame/cg_players.c @@ -128,12 +128,12 @@ static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) while ( 1 ) { prev = text_p; // so we can unget token = COM_Parse( &text_p ); - if ( !token ) { + if ( !token[0] ) { break; } if ( !Q_stricmp( token, "footsteps" ) ) { token = COM_Parse( &text_p ); - if ( !token ) { + if ( !token[0] ) { break; } if ( !Q_stricmp( token, "default" ) || !Q_stricmp( token, "normal" ) ) { @@ -153,7 +153,7 @@ static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) } else if ( !Q_stricmp( token, "headoffset" ) ) { for ( i = 0 ; i < 3 ; i++ ) { token = COM_Parse( &text_p ); - if ( !token ) { + if ( !token[0] ) { break; } ci->headOffset[i] = atof( token ); @@ -161,7 +161,7 @@ static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) continue; } else if ( !Q_stricmp( token, "sex" ) ) { token = COM_Parse( &text_p ); - if ( !token ) { + if ( !token[0] ) { break; } if ( token[0] == 'f' || token[0] == 'F' ) { @@ -192,7 +192,7 @@ static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) for ( i = 0 ; i < MAX_ANIMATIONS ; i++ ) { token = COM_Parse( &text_p ); - if ( !*token ) { + if ( !token[0] ) { if( i >= TORSO_GETFLAG && i <= TORSO_NEGATIVE ) { animations[i].firstFrame = animations[TORSO_GESTURE].firstFrame; animations[i].frameLerp = animations[TORSO_GESTURE].frameLerp; @@ -215,7 +215,7 @@ static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) } token = COM_Parse( &text_p ); - if ( !*token ) { + if ( !token[0] ) { break; } animations[i].numFrames = atoi( token ); @@ -229,13 +229,13 @@ static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) } token = COM_Parse( &text_p ); - if ( !*token ) { + if ( !token[0] ) { break; } animations[i].loopFrames = atoi( token ); token = COM_Parse( &text_p ); - if ( !*token ) { + if ( !token[0] ) { break; } fps = atof( token ); diff --git a/engine/code/cgame/cg_predict.c b/engine/code/cgame/cg_predict.c index de33e4c9..6ec837c8 100644 --- a/engine/code/cgame/cg_predict.c +++ b/engine/code/cgame/cg_predict.c @@ -489,9 +489,11 @@ void CG_PredictPlayerState( void ) { if ( pmove_msec.integer < 8 ) { trap_Cvar_Set("pmove_msec", "8"); + trap_Cvar_Update(&pmove_msec); } else if (pmove_msec.integer > 33) { trap_Cvar_Set("pmove_msec", "33"); + trap_Cvar_Update(&pmove_msec); } cg_pmove.pmove_fixed = pmove_fixed.integer;// | cg_pmove_fixed.integer; diff --git a/engine/code/cgame/cg_public.h b/engine/code/cgame/cg_public.h index e3dafb64..6d4a9078 100644 --- a/engine/code/cgame/cg_public.h +++ b/engine/code/cgame/cg_public.h @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 1999-2005 Id Software, Inc., 2016 Google Inc. +Copyright (C) 1999-2005 Id Software, Inc., 2016-2017 Google Inc. This file is part of Quake III Arena source code. @@ -218,7 +218,8 @@ typedef enum { // command is not known to the game CG_DRAW_ACTIVE_FRAME, -// void (*CG_DrawActiveFrame)( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); +// void (*CG_DrawActiveFrame)( int serverTime, stereoFrame_t stereoView, +// qboolean demoPlayback, qboolean skipRendering ); // Generates and draws a game scene and status information at the given time. // If demoPlayback is set, local movement prediction will not be enabled @@ -233,8 +234,11 @@ typedef enum { CG_MOUSE_EVENT, // void (*CG_MouseEvent)( int dx, int dy ); - CG_EVENT_HANDLING + CG_EVENT_HANDLING, // void (*CG_EventHandling)(int type); + + CG_UPDATE_CUSTOM_ITEMS, +// void (*CG_UpdateCustomItems)( void ); } cgameExport_t; //---------------------------------------------- diff --git a/engine/code/cgame/cg_scoreboard.c b/engine/code/cgame/cg_scoreboard.c index 364c9dd4..4e8f8590 100644 --- a/engine/code/cgame/cg_scoreboard.c +++ b/engine/code/cgame/cg_scoreboard.c @@ -453,7 +453,7 @@ CG_DrawTourneyScoreboard Draw the oversize scoreboard for tournements ================= */ -void CG_DrawOldTourneyScoreboard( void ) { +void CG_DrawTourneyScoreboard( void ) { const char *s; vec4_t color; int min, tens, ones; diff --git a/engine/code/cgame/cg_servercmds.c b/engine/code/cgame/cg_servercmds.c index dcca84f8..b2c2f8ab 100644 --- a/engine/code/cgame/cg_servercmds.c +++ b/engine/code/cgame/cg_servercmds.c @@ -561,7 +561,7 @@ int CG_ParseVoiceChats( const char *filename, voiceChatList_t *voiceChatList, in voiceChats[i].id[0] = 0; } token = COM_ParseExt(p, qtrue); - if (!token || token[0] == 0) { + if (!token[0]) { return qtrue; } if (!Q_stricmp(token, "female")) { @@ -581,7 +581,7 @@ int CG_ParseVoiceChats( const char *filename, voiceChatList_t *voiceChatList, in voiceChatList->numVoiceChats = 0; while ( 1 ) { token = COM_ParseExt(p, qtrue); - if (!token || token[0] == 0) { + if (!token[0]) { return qtrue; } Com_sprintf(voiceChats[voiceChatList->numVoiceChats].id, sizeof( voiceChats[voiceChatList->numVoiceChats].id ), "%s", token); @@ -593,7 +593,7 @@ int CG_ParseVoiceChats( const char *filename, voiceChatList_t *voiceChatList, in voiceChats[voiceChatList->numVoiceChats].numSounds = 0; while(1) { token = COM_ParseExt(p, qtrue); - if (!token || token[0] == 0) { + if (!token[0]) { return qtrue; } if (!Q_stricmp(token, "}")) @@ -601,7 +601,7 @@ int CG_ParseVoiceChats( const char *filename, voiceChatList_t *voiceChatList, in sound = trap_S_RegisterSound( token, compress ); voiceChats[voiceChatList->numVoiceChats].sounds[voiceChats[voiceChatList->numVoiceChats].numSounds] = sound; token = COM_ParseExt(p, qtrue); - if (!token || token[0] == 0) { + if (!token[0]) { return qtrue; } Com_sprintf(voiceChats[voiceChatList->numVoiceChats].chats[ @@ -669,7 +669,7 @@ int CG_HeadModelVoiceChats( char *filename ) { p = &ptr; token = COM_ParseExt(p, qtrue); - if (!token || token[0] == 0) { + if ( !token[0] ) { return -1; } @@ -894,6 +894,10 @@ void CG_VoiceChatLocal( int mode, qboolean voiceOnly, int clientNum, int color, return; } + if ( mode == SAY_ALL && cgs.gametype >= GT_TEAM && cg_teamChatsOnly.integer ) { + return; + } + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { clientNum = 0; } @@ -904,23 +908,20 @@ void CG_VoiceChatLocal( int mode, qboolean voiceOnly, int clientNum, int color, voiceChatList = CG_VoiceChatListForClient( clientNum ); if ( CG_GetVoiceChat( voiceChatList, cmd, &snd, &chat ) ) { - // - if ( mode == SAY_TEAM || !cg_teamChatsOnly.integer ) { - vchat.clientNum = clientNum; - vchat.snd = snd; - vchat.voiceOnly = voiceOnly; - Q_strncpyz(vchat.cmd, cmd, sizeof(vchat.cmd)); - if ( mode == SAY_TELL ) { - Com_sprintf(vchat.message, sizeof(vchat.message), "[%s]: %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); - } - else if ( mode == SAY_TEAM ) { - Com_sprintf(vchat.message, sizeof(vchat.message), "(%s): %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); - } - else { - Com_sprintf(vchat.message, sizeof(vchat.message), "%s: %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); - } - CG_AddBufferedVoiceChat(&vchat); + vchat.clientNum = clientNum; + vchat.snd = snd; + vchat.voiceOnly = voiceOnly; + Q_strncpyz(vchat.cmd, cmd, sizeof(vchat.cmd)); + if ( mode == SAY_TELL ) { + Com_sprintf(vchat.message, sizeof(vchat.message), "[%s]: %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); + } + else if ( mode == SAY_TEAM ) { + Com_sprintf(vchat.message, sizeof(vchat.message), "(%s): %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); } + else { + Com_sprintf(vchat.message, sizeof(vchat.message), "%s: %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); + } + CG_AddBufferedVoiceChat(&vchat); } } @@ -1012,12 +1013,14 @@ static void CG_ServerCommand( void ) { } if ( !strcmp( cmd, "chat" ) ) { - if ( !cg_teamChatsOnly.integer ) { - trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); - Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT ); - CG_RemoveChatEscapeChar( text ); - CG_Printf( "%s\n", text ); + if ( cgs.gametype >= GT_TEAM && cg_teamChatsOnly.integer ) { + return; } + + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT ); + CG_RemoveChatEscapeChar( text ); + CG_Printf( "%s\n", text ); return; } diff --git a/engine/code/cgame/cg_snapshot.c b/engine/code/cgame/cg_snapshot.c index a2e018db..3a705590 100644 --- a/engine/code/cgame/cg_snapshot.c +++ b/engine/code/cgame/cg_snapshot.c @@ -76,11 +76,8 @@ static void CG_TransitionEntity( centity_t *cent ) { ================== CG_SetInitialSnapshot -This will only happen on the very first snapshot, or -on tourney restarts. All other times will use -CG_TransitionSnapshot instead. - -FIXME: Also called by map_restart? +This will only happen on the very first snapshot. +All other times will use CG_TransitionSnapshot instead. ================== */ void CG_SetInitialSnapshot( snapshot_t *snap ) { @@ -141,8 +138,7 @@ static void CG_TransitionSnapshot( void ) { CG_ExecuteNewServerCommands( cg.nextSnap->serverCommandSequence ); // if we had a map_restart, set everthing with initial - if ( !cg.snap ) { - return; + if ( cg.mapRestart ) { } // clear the currentValid flag for all entities in the existing snapshot diff --git a/engine/code/cgame/cg_view.c b/engine/code/cgame/cg_view.c index be026556..3c30b249 100644 --- a/engine/code/cgame/cg_view.c +++ b/engine/code/cgame/cg_view.c @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 1999-2005 Id Software, Inc., 2016 Google Inc. +Copyright (C) 1999-2005 Id Software, Inc., 2016-2017 Google Inc. This file is part of Quake III Arena source code. @@ -753,8 +753,9 @@ CG_DrawActiveFrame Generates and draws a game scene and status information at the given time. ================= */ -void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ) { +void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback, qboolean skipRendering ) { int inwater; + int team = cg.snap->ps.persistant[PERS_TEAM]; cg.time = serverTime; cg.demoPlayback = demoPlayback; @@ -767,7 +768,9 @@ void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demo // if we are only updating the screen as a loading // pacifier, don't even try to read snapshots if ( cg.infoScreenText[0] != 0 ) { - CG_DrawInformation(); + if ( !skipRendering ) { + CG_DrawInformation(); + } return; } @@ -784,7 +787,9 @@ void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demo // if we haven't received any snapshots yet, all // we can draw is the information screen if ( !cg.snap || ( cg.snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) { - CG_DrawInformation(); + if ( !skipRendering ) { + CG_DrawInformation(); + } return; } @@ -797,15 +802,24 @@ void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demo // update cg.predictedPlayerState CG_PredictPlayerState(); - // Inform context of latest player state prediction. - dmlab_predicted_player_state( &cg.predictedPlayerState ); + // Inform context of latest player state. + if ( team == TEAM_RED ) { + dmlab_player_state( &cg.predictedPlayerState, cgs.scores1, cgs.scores2 ); + } else if ( team == TEAM_BLUE ) { + dmlab_player_state( &cg.predictedPlayerState, cgs.scores2, cgs.scores1 ); + } else { + dmlab_player_state( &cg.predictedPlayerState, 0, 0 ); + } // decide on third person view - cg.renderingThirdPerson = cg_thirdPerson.integer || (cg.snap->ps.stats[STAT_HEALTH] <= 0); + cg.renderingThirdPerson = team != TEAM_SPECTATOR + && (cg_thirdPerson.integer || (cg.snap->ps.stats[STAT_HEALTH] <= 0)); // build cg.refdef inwater = CG_CalcViewValues(); + if ( skipRendering ) return; + // first person blend blobs, done after AnglesToAxis if ( !cg.renderingThirdPerson ) { CG_DamageBlendBlob(); diff --git a/engine/code/client/cl_cgame.c b/engine/code/client/cl_cgame.c index 4cb177b3..c14e5b51 100644 --- a/engine/code/client/cl_cgame.c +++ b/engine/code/client/cl_cgame.c @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 1999-2005 Id Software, Inc., 2016 Google Inc. +Copyright (C) 1999-2005 Id Software, Inc., 2016-2017 Google Inc. This file is part of Quake III Arena source code. @@ -24,6 +24,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "client.h" #include "../botlib/botlib.h" +#include "../deepmind/context.h" #include "../qcommon/deepmind_hooks.h" #ifdef USE_MUMBLE @@ -441,7 +442,7 @@ intptr_t CL_CgameSystemCalls( intptr_t *args ) { case CG_FS_FOPENFILE: return FS_FOpenFileByMode( VMA(1), VMA(2), args[3] ); case CG_FS_READ: - FS_Read2( VMA(1), args[2], args[3] ); + FS_Read( VMA(1), args[2], args[3] ); return 0; case CG_FS_WRITE: FS_Write( VMA(1), args[2], args[3] ); @@ -796,8 +797,8 @@ qboolean CL_GameCommand( void ) { CL_CGameRendering ===================== */ -void CL_CGameRendering( stereoFrame_t stereo ) { - VM_Call( cgvm, CG_DRAW_ACTIVE_FRAME, cl.serverTime, stereo, clc.demoplaying ); +void CL_CGameRendering( stereoFrame_t stereo, qboolean skipRendering ) { + VM_Call( cgvm, CG_DRAW_ACTIVE_FRAME, cl.serverTime, stereo, clc.demoplaying, skipRendering ); VM_Debug( 0 ); } @@ -1076,6 +1077,7 @@ void CL_SetCGameTime( void ) { // the contents of cl.snap CL_ReadDemoMessage(); if ( clc.state != CA_ACTIVE ) { + dmlab_context()->hooks.set_map_finished(dmlab_context()->userdata, qtrue); return; // end of demo } } diff --git a/engine/code/client/cl_cin.c b/engine/code/client/cl_cin.c index 327e5c37..73eecd46 100644 --- a/engine/code/client/cl_cin.c +++ b/engine/code/client/cl_cin.c @@ -577,8 +577,12 @@ static unsigned short yuv_to_rgb( long y, long u, long v ) g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 8; b = (YY + ROQ_UB_tab[u]) >> 9; - if (r<0) r = 0; if (g<0) g = 0; if (b<0) b = 0; - if (r > 31) r = 31; if (g > 63) g = 63; if (b > 31) b = 31; + if (r<0) r = 0; + if (g<0) g = 0; + if (b<0) b = 0; + if (r > 31) r = 31; + if (g > 63) g = 63; + if (b > 31) b = 31; return (unsigned short)((r<<11)+(g<<5)+(b)); } @@ -598,8 +602,12 @@ static unsigned int yuv_to_rgb24( long y, long u, long v ) g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 6; b = (YY + ROQ_UB_tab[u]) >> 6; - if (r<0) r = 0; if (g<0) g = 0; if (b<0) b = 0; - if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; + if (r<0) r = 0; + if (g<0) g = 0; + if (b<0) b = 0; + if (r > 255) r = 255; + if (g > 255) g = 255; + if (b > 255) b = 255; return LittleLong ((r)|(g<<8)|(b<<16)|(255<<24)); } diff --git a/engine/code/client/cl_console.c b/engine/code/client/cl_console.c index 7d806e94..6a65bb79 100644 --- a/engine/code/client/cl_console.c +++ b/engine/code/client/cl_console.c @@ -56,6 +56,7 @@ typedef struct { console_t con; cvar_t *con_conspeed; +cvar_t *con_autoclear; cvar_t *con_notifytime; #define DEFAULT_CONSOLE_WIDTH 78 @@ -72,7 +73,10 @@ void Con_ToggleConsole_f (void) { return; } - Field_Clear( &g_consoleField ); + if ( con_autoclear->integer ) { + Field_Clear( &g_consoleField ); + } + g_consoleField.widthInChars = g_console_field_width; Con_ClearNotify (); @@ -191,6 +195,12 @@ void Con_Dump_f (void) Q_strncpyz( filename, Cmd_Argv( 1 ), sizeof( filename ) ); COM_DefaultExtension( filename, sizeof( filename ), ".txt" ); + if (!COM_CompareExtension(filename, ".txt")) + { + Com_Printf("Con_Dump_f: Only the \".txt\" extension is supported by this command!\n"); + return; + } + f = FS_FOpenFileWrite( filename ); if (!f) { @@ -348,6 +358,7 @@ void Con_Init (void) { con_notifytime = Cvar_Get ("con_notifytime", "3", 0); con_conspeed = Cvar_Get ("scr_conspeed", "3", 0); + con_autoclear = Cvar_Get("con_autoclear", "1", CVAR_ARCHIVE); Field_Clear( &g_consoleField ); g_consoleField.widthInChars = g_console_field_width; diff --git a/engine/code/client/cl_curl.c b/engine/code/client/cl_curl.c index 3ff5a3d8..5384390e 100644 --- a/engine/code/client/cl_curl.c +++ b/engine/code/client/cl_curl.c @@ -299,6 +299,8 @@ void CL_cURL_BeginDownload( const char *localName, const char *remoteURL ) qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_FAILONERROR, 1); qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_FOLLOWLOCATION, 1); qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_MAXREDIRS, 5); + qcurl_easy_setopt_warn(clc.downloadCURL, CURLOPT_PROTOCOLS, + CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP | CURLPROTO_FTPS); clc.downloadCURLM = qcurl_multi_init(); if(!clc.downloadCURLM) { qcurl_easy_cleanup(clc.downloadCURL); diff --git a/engine/code/client/cl_curl.h b/engine/code/client/cl_curl.h index 1c0be55a..147afc21 100644 --- a/engine/code/client/cl_curl.h +++ b/engine/code/client/cl_curl.h @@ -28,7 +28,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "../qcommon/qcommon.h" #ifdef USE_LOCAL_HEADERS - #include "../libcurl-7.35.0/curl/curl.h" + #include "../curl-7.54.0/include/curl/curl.h" #else #include #endif diff --git a/engine/code/client/cl_input.c b/engine/code/client/cl_input.c index 89573164..35534437 100644 --- a/engine/code/client/cl_input.c +++ b/engine/code/client/cl_input.c @@ -558,7 +558,7 @@ CL_FinishMove void CL_FinishMove( vec3_t old_angles, usercmd_t *cmd ) { DeepmindContext* ctx = dmlab_context(); int i; - int eng_frame_msec = ctx->calls.engine_frame_period_msec( ctx->context ); + int eng_frame_msec = ctx->calls.engine_frame_period_msec(); if ( eng_frame_msec == 0 ) { eng_frame_msec = frame_msec; } @@ -569,26 +569,32 @@ void CL_FinishMove( vec3_t old_angles, usercmd_t *cmd ) { // can be determined without allowing cheating cmd->serverTime = cl.serverTime; - if (ctx->hooks.get_use_internal_controls( ctx->userdata ) != 0) { + if (ctx->hooks.get_native_app( ctx->userdata ) != 0) { ctx->hooks.set_actions( ctx->userdata, AngleDelta( cl.viewangles[PITCH], old_angles[PITCH] ) / eng_frame_msec, AngleDelta( cl.viewangles[YAW], old_angles[YAW] ) / eng_frame_msec, cmd->forwardmove, cmd->rightmove, cmd->upmove, cmd->buttons ); - cl.viewangles[YAW] = old_angles[YAW]; } double pitch = 0; double yaw = 0; - ctx->hooks.get_actions( - ctx->userdata, - &pitch, &yaw, - &cmd->forwardmove, &cmd->rightmove, &cmd->upmove, - &cmd->buttons); - cl.viewangles[PITCH] += pitch * eng_frame_msec; - cl.viewangles[PITCH] = AngleNormalize360( cl.viewangles[PITCH] ); - cl.viewangles[YAW] += yaw * eng_frame_msec; - cl.viewangles[YAW] = AngleNormalize360( cl.viewangles[YAW] ); + if (!ctx->calls.is_map_loading()) { + ctx->hooks.get_actions( + ctx->userdata, + &pitch, &yaw, + &cmd->forwardmove, &cmd->rightmove, &cmd->upmove, + &cmd->buttons); + } else { + cmd->forwardmove = 0; + cmd->rightmove = 0; + cmd->upmove = 0; + cmd->buttons = 0; + } + + cl.viewangles[ROLL] = 0; + cl.viewangles[PITCH] = AngleNormalize360( old_angles[PITCH] + pitch * eng_frame_msec ); + cl.viewangles[YAW] = AngleNormalize360( old_angles[YAW] + yaw * eng_frame_msec ); for (i=0 ; i<3 ; i++) { cmd->angles[i] = ANGLE2SHORT(cl.viewangles[i]); @@ -623,13 +629,6 @@ usercmd_t CL_CreateCmd( void ) { // get basic movement from joystick CL_JoystickMove( &cmd ); - // check to make sure the angles haven't wrapped - if ( cl.viewangles[PITCH] - oldAngles[PITCH] > 90 ) { - cl.viewangles[PITCH] = oldAngles[PITCH] + 90; - } else if ( oldAngles[PITCH] - cl.viewangles[PITCH] > 90 ) { - cl.viewangles[PITCH] = oldAngles[PITCH] - 90; - } - // store out the final values CL_FinishMove( oldAngles, &cmd ); diff --git a/engine/code/client/cl_keys.c b/engine/code/client/cl_keys.c index f73083a5..7ddea0c7 100644 --- a/engine/code/client/cl_keys.c +++ b/engine/code/client/cl_keys.c @@ -613,7 +613,7 @@ void Console_Key (int key) { // enter finishes the line if ( key == K_ENTER || key == K_KP_ENTER ) { // if not in the game explicitly prepend a slash if needed - if ( clc.state != CA_ACTIVE && + if ( clc.state != CA_ACTIVE && con_autochat->integer && g_consoleField.buffer[0] && g_consoleField.buffer[0] != '\\' && g_consoleField.buffer[0] != '/' ) { @@ -635,7 +635,10 @@ void Console_Key (int key) { if ( !g_consoleField.buffer[0] ) { return; // empty lines just scroll the console without adding to history } else { - Cbuf_AddText ("cmd say "); + if ( con_autochat->integer ) { + Cbuf_AddText ("cmd say "); + } + Cbuf_AddText( g_consoleField.buffer ); Cbuf_AddText ("\n"); } @@ -1239,6 +1242,11 @@ void CL_KeyDownEvent( int key, unsigned time ) if( keys[K_ALT].down && key == K_ENTER ) { + // don't repeat fullscreen toggle when keys are held down + if ( keys[K_ENTER].repeats > 1 ) { + return; + } + Cvar_SetValue( "r_fullscreen", !Cvar_VariableIntegerValue( "r_fullscreen" ) ); return; diff --git a/engine/code/client/cl_main.c b/engine/code/client/cl_main.c index b68d4f99..45c4df74 100644 --- a/engine/code/client/cl_main.c +++ b/engine/code/client/cl_main.c @@ -1069,7 +1069,8 @@ demo */ void CL_PlayDemo_f( void ) { char name[MAX_OSPATH]; - char *arg, *ext_test; + char arg[MAX_OSPATH]; + char *ext_test; int protocol, i; char retry[MAX_OSPATH]; @@ -1083,7 +1084,7 @@ void CL_PlayDemo_f( void ) { Cvar_Set( "sv_killserver", "2" ); // open the demo file - arg = Cmd_Argv(1); + Q_strncpyz( arg, Cmd_Argv(1), sizeof( arg ) ); CL_Disconnect( qtrue ); @@ -1690,7 +1691,7 @@ CL_Connect_f ================ */ void CL_Connect_f( void ) { - char *server; + char server[MAX_OSPATH]; const char *serverString; int argc = Cmd_Argc(); netadrtype_t family = NA_UNSPEC; @@ -1701,7 +1702,7 @@ void CL_Connect_f( void ) { } if(argc == 2) - server = Cmd_Argv(1); + Q_strncpyz( server, Cmd_Argv(1), sizeof( server ) ); else { if(!strcmp(Cmd_Argv(1), "-4")) @@ -1711,7 +1712,7 @@ void CL_Connect_f( void ) { else Com_Printf( "warning: only -4 or -6 as address type understood.\n"); - server = Cmd_Argv(2); + Q_strncpyz( server, Cmd_Argv(2), sizeof( server ) ); } // save arguments for reconnect @@ -1890,6 +1891,7 @@ void CL_Rcon_f( void ) { } NET_SendPacket (NS_CLIENT, strlen(message)+1, message, to); + cls.rconAddress = to; } /* @@ -2469,7 +2471,7 @@ void CL_ServersResponsePacket( const netadr_t* from, msg_t *msg, qboolean extend byte* buffptr; byte* buffend; - Com_Printf("CL_ServersResponsePacket\n"); + Com_Printf("CL_ServersResponsePacket from %s\n", NET_AdrToStringwPort(*from)); if (cls.numglobalservers == -1) { // state to detect lack of servers or lack of response @@ -2748,7 +2750,10 @@ void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { // echo request from server if ( !Q_stricmp(c, "echo") ) { - NET_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv(1) ); + // NOTE: we may have to add exceptions for auth and update servers + if ( NET_CompareAdr( from, clc.serverAddress ) || NET_CompareAdr( from, cls.rconAddress ) ) { + NET_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv(1) ); + } return; } @@ -2765,12 +2770,14 @@ void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { } // echo request from server - if(!Q_stricmp(c, "print")){ - s = MSG_ReadString( msg ); - - Q_strncpyz( clc.serverMessage, s, sizeof( clc.serverMessage ) ); - Com_Printf( "%s", s ); + if ( !Q_stricmp(c, "print") ) { + // NOTE: we may have to add exceptions for auth and update servers + if ( NET_CompareAdr( from, clc.serverAddress ) || NET_CompareAdr( from, cls.rconAddress ) ) { + s = MSG_ReadString( msg ); + Q_strncpyz( clc.serverMessage, s, sizeof( clc.serverMessage ) ); + Com_Printf( "%s", s ); + } return; } @@ -3479,6 +3486,17 @@ void CL_Sayto_f( void ) { CL_AddReliableCommand(va("tell %i \"%s\"", clientNum, p ), qfalse); } +/* +================= +CL_UpdateCustomItems_f + +Ensures the rendering state is in sync with the gameplay state. +================= +*/ +void CL_UpdateCustomItems_f( void ) { + VM_Call( cgvm, CG_UPDATE_CUSTOM_ITEMS ); +} + /* ==================== CL_Init @@ -3551,7 +3569,7 @@ void CL_Init( void ) { cl_allowDownload = Cvar_Get ("cl_allowDownload", "0", CVAR_ARCHIVE); #ifdef USE_CURL_DLOPEN - cl_cURLLib = Cvar_Get("cl_cURLLib", DEFAULT_CURL_LIB, CVAR_ARCHIVE); + cl_cURLLib = Cvar_Get("cl_cURLLib", DEFAULT_CURL_LIB, CVAR_ARCHIVE | CVAR_PROTECTED); #endif cl_conXOffset = Cvar_Get ("cl_conXOffset", "0", 0); @@ -3681,6 +3699,7 @@ void CL_Init( void ) { Cmd_AddCommand ("model", CL_SetModel_f ); Cmd_AddCommand ("video", CL_Video_f ); Cmd_AddCommand ("stopvideo", CL_StopVideo_f ); + Cmd_AddCommand ("updatecustomitems", CL_UpdateCustomItems_f ); if( !com_dedicated->integer ) { Cmd_AddCommand ("sayto", CL_Sayto_f ); Cmd_SetCommandCompletionFunc( "sayto", CL_CompletePlayerName ); @@ -4158,6 +4177,10 @@ void CL_LocalServers_f( void ) { /* ================== CL_GlobalServers_f + +Originally master 0 was Internet and master 1 was MPlayer. +ioquake3 2008; added support for requesting five separate master servers using 0-4. +ioquake3 2017; made master 0 fetch all master servers and 1-5 request a single master server. ================== */ void CL_GlobalServers_f( void ) { @@ -4165,13 +4188,36 @@ void CL_GlobalServers_f( void ) { int count, i, masterNum; char command[1024], *masteraddress; - if ((count = Cmd_Argc()) < 3 || (masterNum = atoi(Cmd_Argv(1))) < 0 || masterNum > MAX_MASTER_SERVERS - 1) + if ((count = Cmd_Argc()) < 3 || (masterNum = atoi(Cmd_Argv(1))) < 0 || masterNum > MAX_MASTER_SERVERS) { - Com_Printf("usage: globalservers [keywords]\n", MAX_MASTER_SERVERS - 1); + Com_Printf("usage: globalservers [keywords]\n", MAX_MASTER_SERVERS); return; } - sprintf(command, "sv_master%d", masterNum + 1); + // request from all master servers + if ( masterNum == 0 ) { + int numAddress = 0; + + for ( i = 1; i <= MAX_MASTER_SERVERS; i++ ) { + sprintf(command, "sv_master%d", i); + masteraddress = Cvar_VariableString(command); + + if(!*masteraddress) + continue; + + numAddress++; + + Com_sprintf(command, sizeof(command), "globalservers %d %s %s\n", i, Cmd_Argv(2), Cmd_ArgsFrom(3)); + Cbuf_AddText(command); + } + + if ( !numAddress ) { + Com_Printf( "CL_GlobalServers_f: Error: No master server addresses.\n"); + } + return; + } + + sprintf(command, "sv_master%d", masterNum); masteraddress = Cvar_VariableString(command); if(!*masteraddress) @@ -4193,7 +4239,7 @@ void CL_GlobalServers_f( void ) { else if(i == 2) to.port = BigShort(PORT_MASTER); - Com_Printf("Requesting servers from master %s...\n", masteraddress); + Com_Printf("Requesting servers from %s (%s)...\n", masteraddress, NET_AdrToStringwPort(to)); cls.numglobalservers = -1; cls.pingUpdateSource = AS_GLOBAL; diff --git a/engine/code/client/cl_parse.c b/engine/code/client/cl_parse.c index 39682507..318c8d88 100644 --- a/engine/code/client/cl_parse.c +++ b/engine/code/client/cl_parse.c @@ -804,10 +804,10 @@ void CL_ParseVoip ( msg_t *msg, qboolean ignoreData ) { #if 0 static FILE *encio = NULL; if (encio == NULL) encio = fopen("voip-incoming-encoded.bin", "wb"); - if (encio != NULL) { fwrite(encoded, len, 1, encio); fflush(encio); } + if (encio != NULL) { fwrite(encoded, packetsize, 1, encio); fflush(encio); } static FILE *decio = NULL; if (decio == NULL) decio = fopen("voip-incoming-decoded.bin", "wb"); - if (decio != NULL) { fwrite(decoded+written, clc.speexFrameSize*2, 1, decio); fflush(decio); } + if (decio != NULL) { fwrite(decoded+written, numSamples*2, 1, decio); fflush(decio); } #endif written += numSamples; diff --git a/engine/code/client/cl_scrn.c b/engine/code/client/cl_scrn.c index 831d5ef7..a352e6ea 100644 --- a/engine/code/client/cl_scrn.c +++ b/engine/code/client/cl_scrn.c @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 1999-2005 Id Software, Inc., 2017 Google Inc. This file is part of Quake III Arena source code. @@ -472,10 +472,15 @@ SCR_DrawScreenField This will be called twice if rendering in stereo mode ================== */ -void SCR_DrawScreenField( stereoFrame_t stereoFrame ) { +void SCR_DrawScreenField( stereoFrame_t stereoFrame, qboolean skipRendering ) { qboolean uiFullscreen; - re.BeginFrame( stereoFrame ); + // Many skip rendering calls below are not needed for performance + // but to match any BeginFrame skips + if ( !skipRendering ) { + re.BeginFrame( stereoFrame ); + } + uiFullscreen = (uivm && VM_Call( uivm, UI_IS_FULLSCREEN )); @@ -497,43 +502,55 @@ void SCR_DrawScreenField( stereoFrame_t stereoFrame ) { Com_Error( ERR_FATAL, "SCR_DrawScreenField: bad clc.state" ); break; case CA_CINEMATIC: - SCR_DrawCinematic(); + if ( !skipRendering ) { + SCR_DrawCinematic(); + } break; case CA_DISCONNECTED: - // force menu up - S_StopAllSounds(); - VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); + if ( !skipRendering ) { + // force menu up + S_StopAllSounds(); + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); + } break; case CA_CONNECTING: case CA_CHALLENGING: case CA_CONNECTED: - // connecting clients will only show the connection dialog - // refresh to update the time - VM_Call( uivm, UI_REFRESH, cls.realtime ); - VM_Call( uivm, UI_DRAW_CONNECT_SCREEN, qfalse ); + if ( !skipRendering ) { + // connecting clients will only show the connection dialog + // refresh to update the time + VM_Call( uivm, UI_REFRESH, cls.realtime ); + VM_Call( uivm, UI_DRAW_CONNECT_SCREEN, qfalse ); + } break; case CA_LOADING: case CA_PRIMED: // draw the game information screen and loading progress - CL_CGameRendering(stereoFrame); - - // also draw the connection information, so it doesn't - // flash away too briefly on local or lan games - // refresh to update the time - VM_Call( uivm, UI_REFRESH, cls.realtime ); - VM_Call( uivm, UI_DRAW_CONNECT_SCREEN, qtrue ); + CL_CGameRendering(stereoFrame, skipRendering); + + if ( !skipRendering ) { + // also draw the connection information, so it doesn't + // flash away too briefly on local or lan games + // refresh to update the time + VM_Call( uivm, UI_REFRESH, cls.realtime ); + VM_Call( uivm, UI_DRAW_CONNECT_SCREEN, qtrue ); + } break; case CA_ACTIVE: // always supply STEREO_CENTER as vieworg offset is now done by the engine. - CL_CGameRendering(stereoFrame); - SCR_DrawDemoRecording(); + CL_CGameRendering(stereoFrame, skipRendering); + if ( !skipRendering ) { + SCR_DrawDemoRecording(); #ifdef USE_VOIP - SCR_DrawVoipMeter(); + SCR_DrawVoipMeter(); #endif + } break; } } + if ( skipRendering ) return; + // the menu draws next if ( Key_GetCatcher( ) & KEYCATCH_UI && uivm ) { VM_Call( uivm, UI_REFRESH, cls.realtime ); @@ -548,6 +565,19 @@ void SCR_DrawScreenField( stereoFrame_t stereoFrame ) { } } +static qboolean skipRendering = qfalse; + +/* +================== +SCR_SkipRendering + +Skip rendering for subsequent calls to SCR_UpdateScreen +================== +*/ +void SCR_SkipRendering( qboolean value ) { + skipRendering = value; +} + /* ================== SCR_UpdateScreen @@ -576,19 +606,21 @@ void SCR_UpdateScreen( void ) { int in_anaglyphMode = Cvar_VariableIntegerValue("r_anaglyphMode"); // if running in stereo, we need to draw the frame twice if ( cls.glconfig.stereoEnabled || in_anaglyphMode) { - SCR_DrawScreenField( STEREO_LEFT ); - SCR_DrawScreenField( STEREO_RIGHT ); + SCR_DrawScreenField( STEREO_LEFT, skipRendering ); + SCR_DrawScreenField( STEREO_RIGHT, skipRendering ); } else { - SCR_DrawScreenField( STEREO_CENTER ); + SCR_DrawScreenField( STEREO_CENTER, skipRendering ); } - if ( com_speeds->integer ) { - re.EndFrame( &time_frontend, &time_backend ); - } else { - re.EndFrame( NULL, NULL ); + if ( !skipRendering ) { + if ( com_speeds->integer ) { + re.EndFrame( &time_frontend, &time_backend ); + } else { + re.EndFrame( NULL, NULL ); + } } } - + recursive = 0; } diff --git a/engine/code/client/cl_ui.c b/engine/code/client/cl_ui.c index 9117d337..e0e45d03 100644 --- a/engine/code/client/cl_ui.c +++ b/engine/code/client/cl_ui.c @@ -375,6 +375,7 @@ LAN_CompareServers static int LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int s2 ) { int res; serverInfo_t *server1, *server2; + int clients1, clients2; server1 = LAN_GetServerPtr(source, s1); server2 = LAN_GetServerPtr(source, s2); @@ -392,10 +393,19 @@ static int LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int res = Q_stricmp( server1->mapName, server2->mapName ); break; case SORT_CLIENTS: - if (server1->clients < server2->clients) { + // sub sort by max clients + if ( server1->clients == server2->clients ) { + clients1 = server1->maxClients; + clients2 = server2->maxClients; + } else { + clients1 = server1->clients; + clients2 = server2->clients; + } + + if (clients1 < clients2) { res = -1; } - else if (server1->clients > server2->clients) { + else if (clients1 > clients2) { res = 1; } else { @@ -781,7 +791,7 @@ intptr_t CL_UISystemCalls( intptr_t *args ) { return FS_FOpenFileByMode( VMA(1), VMA(2), args[3] ); case UI_FS_READ: - FS_Read2( VMA(1), args[2], args[3] ); + FS_Read( VMA(1), args[2], args[3] ); return 0; case UI_FS_WRITE: diff --git a/engine/code/client/client.h b/engine/code/client/client.h index 0fe3889c..6bf8755e 100644 --- a/engine/code/client/client.h +++ b/engine/code/client/client.h @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 1999-2005 Id Software, Inc., 2017 Google Inc. This file is part of Quake III Arena source code. @@ -344,6 +344,8 @@ typedef struct { netadr_t authorizeServer; + netadr_t rconAddress; + // rendering info glconfig_t glconfig; qhandle_t charSetShader; @@ -480,6 +482,7 @@ int CL_GetPingQueueCount( void ); void CL_ShutdownRef( void ); void CL_InitRef( void ); +void CL_UpdateCustomItems ( void ); qboolean CL_CDKeyValidate( const char *key, const char *checksum ); int CL_ServerStatus( char *serverAddress, char *serverStatusString, int maxLen ); @@ -561,6 +564,7 @@ void CL_SaveConsoleHistory( void ); // cl_scrn.c // void SCR_Init (void); +void SCR_SkipRendering (qboolean value); void SCR_UpdateScreen (void); void SCR_DebugGraph (float value); @@ -602,7 +606,7 @@ void CIN_CloseAllVideos(void); void CL_InitCGame( void ); void CL_ShutdownCGame( void ); qboolean CL_GameCommand( void ); -void CL_CGameRendering( stereoFrame_t stereo ); +void CL_CGameRendering(stereoFrame_t stereo, qboolean skipRendering); void CL_SetCGameTime( void ); void CL_FirstSnapshot( void ); void CL_ShaderStateChanged(void); diff --git a/engine/code/client/snd_mem.c b/engine/code/client/snd_mem.c index 655ae420..0ef3bd6b 100644 --- a/engine/code/client/snd_mem.c +++ b/engine/code/client/snd_mem.c @@ -126,13 +126,15 @@ static int ResampleSfx( sfx_t *sfx, int channels, int inrate, int inwidth, int s outcount = samples / stepscale; + srcsample = 0; samplefrac = 0; fracstep = stepscale * 256 * channels; chunk = sfx->soundData; for (i=0 ; i> 8; + srcsample += samplefrac >> 8; + samplefrac &= 255; samplefrac += fracstep; for (j=0 ; j> 8; + srcsample += samplefrac >> 8; + samplefrac &= 255; samplefrac += fracstep; for (j=0 ; j #include #include +#include +#include "dmlab_load_model.h" +#include "dmlab_recording.h" +#include "dmlab_save_model.h" #include "../../../deepmind/include/deepmind_context.h" #include "../../../public/dmlab.h" #include "../client/client.h" @@ -32,6 +36,8 @@ #include "../renderercommon/qgl.h" #include "../sys/sys_local.h" +static const int kMaxAspectRatio[2] = {16, 9}; + static const double kPixelsPerFrameToDegreesPerMilliseconds = 0.11 * 60 / 1000; // We define a notion of "external time" for convenience and to avoid rounding @@ -77,6 +83,7 @@ enum ObservationsEnum { kObservations_RgbdInterlaced, kObservations_RgbPlanar, kObservations_RgbdPlanar, + kObservations_MapFrameNumber, }; const char* const kObservationNames[] = { @@ -84,16 +91,35 @@ const char* const kObservationNames[] = { "RGBD_INTERLACED", // "RGB", // "RGBD", // + "MAP_FRAME_NUMBER", // }; +typedef enum PixelBufferTypeEnum_e { + kPixelBufferTypeEnum_Rgb, + kPixelBufferTypeEnum_Depth, +} PixelBufferTypeEnum; + +typedef struct PboData_s { + GLuint id; // PBO buffer id allocated by glGenBuffers. + int size; // Current size in bytes of assigned data buffer. +} PboData; + +typedef struct GamePixelBufferObjects_s { + PboData rgb, depth; // Structs to store PBO information. + bool supported; // If PBO's are supported with the current GL backend. + bool enabled; // If PBO rendering is enabled. +} GamePixelBufferObjects; + typedef struct GameContext_s { DeepmindContext* dm_ctx; int width; int height; + int alt_camera_width; + int alt_camera_height; int image_shape[3]; unsigned char* image_buffer; unsigned char* temp_buffer; // Holds result from glReadPixels. - char script_name[MAX_STRING_CHARS]; + GamePixelBufferObjects pbos; char command_line[MAX_STRING_CHARS]; char runfiles_path[MAX_STRING_CHARS]; bool first_start; @@ -102,13 +128,149 @@ typedef struct GameContext_s { // advance the engine each frame. If set to // zero the wall clock is used. int step; + int map_start_frame; // First frame after warm-up. double total_engine_time_msec; // This is step * engine_frame_period_msec. double score; + dmlabRecordingContext *recording_ctx; + vmInterpret_t vm_mode; // Selected VM mode for the game, ui, and client. + bool is_server; // Whether this environment acts as a server. + bool is_client_only; // Whether this environment is attached to an external + // server. + bool is_connecting; // Whether the environment is connecting to a client + // or server. + int server_port; // The port a client will attach to on the server. + int port; // The port that this environment advertises on. + + bool use_local_level_cache; + bool use_global_level_cache; + + DeepMindLabLevelCacheParams level_cache_params; + + int map_frame_number_shape[1]; + double map_frame_number_observation; + bool is_map_loading; + bool current_screen_rendered; } GameContext; // **** Local helper functions and data **** // +static void* realloc_or_die(void* ptr, size_t n) { + void* result = realloc(ptr, n); + if (n > 0 && result == NULL) { + fputs("Reallocation failure, aborting.\n", stderr); + abort(); + } + return result; +} + +static bool use_pbo_rendering(GameContext* gc) { + return gc->pbos.supported && gc->pbos.enabled; +} + +// Helper function for creating or updating PBO's based on the provided game +// context. Aborts on error. +static void create_update_pbo_or_die(GameContext* gc) { + if (!gc->pbos.rgb.id) { + qglGenBuffers(1, &gc->pbos.rgb.id); + } + if (!gc->pbos.depth.id) { + qglGenBuffers(1, &gc->pbos.depth.id); + } + + // Check that we successfully created RGB and Depth buffers. + if (qglGetError() != GL_NO_ERROR) { + fputs("GL Error creating PBO buffers.\n", stderr); + abort(); + } + + int rgb_pbo_size = gc->width * gc->height * 3; + if (gc->pbos.rgb.size < rgb_pbo_size) { + qglBindBuffer(GL_PIXEL_PACK_BUFFER, gc->pbos.rgb.id); + qglBufferData(GL_PIXEL_PACK_BUFFER, rgb_pbo_size, NULL, + GL_STREAM_READ); + if (qglGetError() != GL_NO_ERROR) { + fputs("Failed to generate PBO data buffer.\n", stderr); + abort(); + } + gc->pbos.rgb.size = rgb_pbo_size; + qglBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + } + + int depth_pbo_size = gc->width * gc->height; + if (gc->pbos.depth.size < depth_pbo_size) { + qglBindBuffer(GL_PIXEL_PACK_BUFFER, gc->pbos.depth.id); + qglBufferData(GL_PIXEL_PACK_BUFFER, depth_pbo_size, NULL, GL_STREAM_READ); + + if (qglGetError() != GL_NO_ERROR) { + fputs("Failed to generate PBO data buffer.\n", stderr); + abort(); + } + gc->pbos.depth.size = depth_pbo_size; + qglBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + } +} + +// Begins request for provided pixel observation. For PBO rendering, this sends +// async GL command to begin reading pixels off card. +static void request_pixel_observations(GameContext* gc, + PixelBufferTypeEnum type) { + if (use_pbo_rendering(gc)) { + create_update_pbo_or_die(gc); + switch (type) { + case kPixelBufferTypeEnum_Rgb: + qglBindBuffer(GL_PIXEL_PACK_BUFFER, gc->pbos.rgb.id); + qglReadPixels(0, 0, gc->width, gc->height, GL_RGB, GL_UNSIGNED_BYTE, 0); + break; + case kPixelBufferTypeEnum_Depth: + qglBindBuffer(GL_PIXEL_PACK_BUFFER, gc->pbos.depth.id); + qglReadPixels(0, 0, gc->width, gc->height, GL_DEPTH_COMPONENT, + GL_UNSIGNED_BYTE, 0); + break; + } + qglBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + } +} + +// Returns a pointer to the requested pixel observations. +static void* bind_pixel_observation(GameContext* gc, PixelBufferTypeEnum type) { + if (use_pbo_rendering(gc)) { + switch (type) { + case kPixelBufferTypeEnum_Rgb: + qglBindBuffer(GL_PIXEL_PACK_BUFFER, gc->pbos.rgb.id); + break; + case kPixelBufferTypeEnum_Depth: + qglBindBuffer(GL_PIXEL_PACK_BUFFER, gc->pbos.depth.id); + break; + } + void* pixel_buffer = qglMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY); + + return pixel_buffer; + } else { + gc->temp_buffer = + realloc_or_die(gc->temp_buffer, gc->width * gc->height * 3); + switch (type) { + case kPixelBufferTypeEnum_Rgb: + qglReadPixels(0, 0, gc->width, gc->height, GL_RGB, GL_UNSIGNED_BYTE, + gc->temp_buffer); + break; + case kPixelBufferTypeEnum_Depth: + qglReadPixels(0, 0, gc->width, gc->height, GL_DEPTH_COMPONENT, + GL_UNSIGNED_BYTE, gc->temp_buffer); + break; + } + + return gc->temp_buffer; + } +} + +static void unbind_pixel_observation(GameContext* gc) { + if (use_pbo_rendering(gc)) { + qglUnmapBuffer(GL_PIXEL_PACK_BUFFER); + qglBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + } +} + static int first_start(GameContext* gc) { DeepmindContext* ctx = gc->dm_ctx; @@ -117,6 +279,15 @@ static int first_start(GameContext* gc) { Sys_PlatformInit(); Sys_Milliseconds(); + const char* dynamic_path = ctx->hooks.get_temporary_folder(ctx->userdata); + Q_strcat(gc->command_line, sizeof(gc->command_line), + va(" +set fs_temporarypath \"%s\"", dynamic_path)); + Q_strcat(gc->command_line, sizeof(gc->command_line), + va(" +set fs_homepath \"%s\"", dynamic_path)); + + Q_strcat(gc->command_line, sizeof(gc->command_line), + va(" +set g_gametype \"%d\"", ctx->hooks.game_type(ctx->userdata))); + const char* modifiedCommandLine = ctx->hooks.replace_command_line(ctx->userdata, gc->command_line); @@ -125,36 +296,113 @@ static int first_start(GameContext* gc) { NET_Init(); CON_Init(); + // We assume PBO's are supported iff we can load the glGenBuffers function. + gc->pbos.supported = qglGenBuffers; + return 0; } -static void load_map(GameContext* gc) { +static bool make_map(GameContext* gc, const char* next_map) { + DeepmindContext* ctx = gc->dm_ctx; + char fullPath[MAX_QPATH]; + fileHandle_t f; + int len; + // See if already exists. + FS_Restart(0); + Com_sprintf(fullPath, sizeof(fullPath), "maps/%s.bsp", next_map); + len = FS_FOpenFileRead(fullPath, &f, qfalse); + FS_FCloseFile(f); + if (len > 0) { + return true; + } + + // Look for a source map for generating bsp. + bool gen_aas = true; + Com_sprintf(fullPath, sizeof(fullPath), BASEGAME "/maps/%s.map", next_map); + len = FS_SV_FOpenFileRead(fullPath, &f); + + if (len <= 0) { + // Try maps_no_ai instead. + gen_aas = false; + FS_FCloseFile(f); + Com_sprintf(fullPath, sizeof(fullPath), BASEGAME "/maps_no_ai/%s.map", + next_map); + len = FS_SV_FOpenFileRead(fullPath, &f); + } + FS_FCloseFile(f); + + if (len <= 0) { + // Not found! + return false; + } + + // Successfully found a map file. + + // Generate a BSP (and AAS file if requested) and wrap in a PK3. + ctx->hooks.make_pk3_from_map(ctx->userdata, fullPath, next_map, gen_aas); + + // File sytem needs to know about the new map file. + FS_Restart(0); + return true; +} + +static void dev_map(GameContext* gc) { DeepmindContext* ctx = gc->dm_ctx; - const char* next_map = ctx->hooks.next_map(ctx->userdata); - Cmd_ExecuteString(va("devmap %s", next_map)); Cvar_Set("fixedtime", va("%d", gc->engine_frame_period_msec)); - Com_Frame(); - while (clc.state < CA_ACTIVE) { - IN_Frame(); + const char* next_map = ctx->hooks.next_map(ctx->userdata); + if (next_map[0] == '\0') { + Cmd_ExecuteString("map_restart 0"); + Cmd_ExecuteString("updatecustomitems"); + Com_Frame(); + } else { + if (!make_map(gc, next_map)) { + perror(va("Didn't find map '%s'\n", next_map)); + exit(1); + } + Cmd_ExecuteString(va("devmap \"%s\"", next_map)); Com_Frame(); + ctx->hooks.add_bots(ctx->userdata); } - ctx->hooks.add_bots(ctx->userdata); - printf("Map loaded: '%s'\n", next_map); - fflush(stdout); } -static int engine_frame_period_msec(void* context) { - GameContext* gc = context; - return gc->engine_frame_period_msec; +static int connecting(GameContext* gc) { + int err = !gc->is_server && clc.state < CA_ACTIVE ? EAGAIN : 0; + IN_Frame(); + Com_Frame(); + return err; } -static int total_engine_time_msec(void* context) { - GameContext* gc = context; - return gc->total_engine_time_msec; +static bool load_map(GameContext* gc) { + gc->is_map_loading = true; + dev_map(gc); + if (!gc->recording_ctx->is_demo) { + while (connecting(gc) == EAGAIN) { + } + // Players join team games in spectator mode. Leave 3 frames for player to + // join the correct team. + for (int i = 0; i < 3; ++i) { + IN_Frame(); + Com_Frame(); + } + } + gc->map_start_frame = cls.framecount; + + bool demo_ok = true; + if (gc->recording_ctx->is_recording) { + demo_ok &= dmlab_start_recording(gc->recording_ctx); + } + if (gc->recording_ctx->is_demo) { + demo_ok &= dmlab_start_demo(gc->recording_ctx); + } + if (gc->recording_ctx->is_video) { + demo_ok &= dmlab_start_video(gc->recording_ctx); + } + fflush(stdout); + return demo_ok; } // Return 0 iff successful. -static int parse_int(const char* s, long int* out) { +static int parse_int(const char* s, long int* out, DeepmindContext* ctx) { errno = 0; char* e; long int val = strtol(s, &e, 0); @@ -162,12 +410,14 @@ static int parse_int(const char* s, long int* out) { *out = val; return 0; } else { + ctx->hooks.set_error_message(ctx->userdata, + va("Invalid int setting %s\n", s)); return -1; } } // Return 0 iff successful. -static int parse_double(const char* s, double* out) { +static int parse_double(const char* s, double* out, DeepmindContext* ctx) { errno = 0; char* e; long int val = strtod(s, &e); @@ -175,6 +425,25 @@ static int parse_double(const char* s, double* out) { *out = val; return 0; } else { + ctx->hooks.set_error_message(ctx->userdata, + va("Invalid double arg %s\n", s)); + return -1; + } +} + +// Return 0 iff successful. +static int parse_bool(const char* s, bool* out, DeepmindContext* ctx) { + if (strcmp(s, "true") == 0) { + *out = true; + return 0; + } else if (strcmp(s, "false") == 0) { + *out = false; + return 0; + } else { + ctx->hooks.set_error_message(ctx->userdata, + va("Invalid boolean arg must be either " + "\"true\" or \"false\"; actual \"%s\"\n", + s)); return -1; } } @@ -191,82 +460,155 @@ static DeepmindContext* get_context_once(void) { } } -static void* realloc_or_die(void* ptr, size_t n) { - void* result = realloc(ptr, n); - if (n > 0 && result == NULL) { - fputs("Reallocation failure, aborting.\n", stderr); - abort(); - } - return result; -} - // **** DeepmindContext **** // DeepmindContext* dmlab_context(void) { return &dmlab_context_impl; } - // **** RL Environment implementation **** // +static const char* dmlab_error_message(void* context) { + GameContext* gc = context; + DeepmindContext* ctx = gc->dm_ctx; + return ctx->hooks.error_message(ctx->userdata); +} static int dmlab_setting(void* context, const char* key, const char* value) { GameContext* gc = context; + DeepmindContext* ctx = gc->dm_ctx; if (gc->init_called) { - fputs( - "'init' has already been called. No further settings can be applied.\n", - stderr); + ctx->hooks.set_error_message(ctx->userdata, + "'init' has already been called. No further " + "settings can be applied.\n"); + return 1; } - DeepmindContext* ctx = gc->dm_ctx; long int v; double v_double; + bool v_bool; if (strcmp(key, "levelName") == 0) { return ctx->hooks.set_script_name(ctx->userdata, value); } else if (strcmp(key, "width") == 0) { - int res = parse_int(value, &v); + int res = parse_int(value, &v, ctx); if (res != 0) return res; gc->width = v; } else if (strcmp(key, "height") == 0) { - int res = parse_int(value, &v); + int res = parse_int(value, &v, ctx); if (res != 0) return res; gc->height = v; + } else if (strcmp(key, "server") == 0) { + int res = parse_bool(value, &v_bool, ctx); + if (res != 0) return res; + gc->is_server = v_bool; + } else if (strcmp(key, "client") == 0) { + int res = parse_bool(value, &v_bool, ctx); + if (res != 0) return res; + gc->is_client_only = v_bool; + } else if (strcmp(key, "hasAltCameras") == 0) { + int res = parse_bool(value, &v_bool, ctx); + if (res != 0) return res; + ctx->hooks.set_has_alt_cameras(ctx->userdata, v_bool); + return 0; + } else if (strcmp(key, "maxAltCameraWidth") == 0) { + int res = parse_int(value, &v, ctx); + if (res != 0) return res; + if (v >= 0) gc->alt_camera_width = v; + return 0; + } else if (strcmp(key, "maxAltCameraHeight") == 0) { + int res = parse_int(value, &v, ctx); + if (res != 0) return res; + if (v >= 0) gc->alt_camera_height = v; + return 0; + } else if (strcmp(key, "localLevelCache") == 0) { + int res = parse_bool(value, &v_bool, ctx); + if (res != 0) return res; + gc->use_local_level_cache = v_bool; + } else if (strcmp(key, "globalLevelCache") == 0) { + int res = parse_bool(value, &v_bool, ctx); + if (res != 0) return res; + gc->use_global_level_cache = v_bool; + } else if (strcmp(key, "serverPort") == 0) { + int res = parse_int(value, &v, ctx); + if (res != 0) return res; + gc->server_port = v; + return 0; + } else if (strcmp(key, "port") == 0) { + int res = parse_int(value, &v, ctx); + if (res != 0) return res; + gc->port = v; + Q_strcat(gc->command_line, sizeof(gc->command_line), + va(" +set net_port %ld", v)); + return 0; + } else if (strcmp(key, "vmMode") == 0) { + if (strcmp(value, "interpreted") == 0) { + gc->vm_mode = VMI_BYTECODE; + } else if (strcmp(value, "compiled") == 0) { + gc->vm_mode = VMI_COMPILED; + } else if (strcmp(value, "native") == 0) { + gc->vm_mode = VMI_NATIVE; + } else { + ctx->hooks.set_error_message( + ctx->userdata, + va("vmMode must be either: " + "\"interpreted\", \"compiled\", or \"native\"; actual: \"%s\"\n", + value)); + return 1; + } } else if (strcmp(key, "fps") == 0) { - int res = parse_double(value, &v_double); + int res = parse_double(value, &v_double, ctx); if (res != 0) return res; if (v_double > 0) { gc->engine_frame_period_msec = (int)((kEngineTimePerExternalTime * 1000.0 / v_double) + 0.5); } } else if(strcmp(key, "logToStdErr") == 0) { - if (strcmp(value, "true") == 0) { + int res = parse_bool(value, &v_bool, ctx); + if (res != 0) return res; + if (v_bool) { fputs("logToStdErr: \"true\"\n", stderr); Q_strcat(gc->command_line, sizeof(gc->command_line), " +set com_logToStdErr 1"); - } else if (strcmp(value, "false") == 0) { + } else { Q_strcat(gc->command_line, sizeof(gc->command_line), " +set com_logToStdErr 0"); - } else { - fputs("logToStdErr must be either \"true\" or \"false\"\n", stderr); - return 1; - } - } else if (strcmp(key, "controls") == 0) { - if (strcmp(value, "internal") == 0) { - ctx->hooks.set_use_internal_controls(ctx->userdata, true); - } else if (strcmp(value, "external") == 0) { - ctx->hooks.set_use_internal_controls(ctx->userdata, false); } + } else if (strcmp(key, "nativeApp") == 0) { + int res = parse_bool(value, &v_bool, ctx); + if (res != 0) return res; + ctx->hooks.set_native_app(ctx->userdata, v_bool); } else if (strcmp(key, "appendCommand") == 0) { - bool quote = strchr(value, '0') != NULL; - if (quote) { - Q_strcat(gc->command_line, sizeof(gc->command_line), "\""); - } + Q_strcat(gc->command_line, sizeof(gc->command_line), " "); Q_strcat(gc->command_line, sizeof(gc->command_line), value); - if (quote) { - Q_strcat(gc->command_line, sizeof(gc->command_line), "\""); + } else if (strcmp(key, "record") == 0) { + dmlab_set_recording_name(gc->recording_ctx, value); + } else if (strcmp(key, "demo") == 0) { + dmlab_set_demo_name(gc->recording_ctx, value); + } else if (strcmp(key, "video") == 0) { + dmlab_set_video_name(gc->recording_ctx, value); + } else if (strcmp(key, "demofiles") == 0) { + dmlab_set_demofiles_path(gc->recording_ctx, value); + } else if (strcmp(key, "use_pbos") == 0) { + int res = parse_bool(value, &v_bool, ctx); + if (res != 0) return res; + gc->pbos.enabled = v_bool; + } else if (strcmp(key, "gpuDeviceIndex") == 0) { + int res = parse_int(value, &v, ctx); + if (res != 0) return res; + Q_strcat(gc->command_line, sizeof(gc->command_line), + va(" +set r_gpuDeviceIndex %ld", v)); + } else if (strcmp(key, "playerName") == 0) { + if (strlen(value) >= MAX_CVAR_VALUE_STRING) { + ctx->hooks.set_error_message(ctx->userdata, + va("Invalid playerName is must be shorter " + "than, '%d' characters.", + MAX_CVAR_VALUE_STRING)); + return 1; } + Q_strcat(gc->command_line, sizeof(gc->command_line), + va(" +set name \"%s\"", value)); } else { ctx->hooks.add_setting(ctx->userdata, key, value); } @@ -277,19 +619,73 @@ static int dmlab_setting(void* context, const char* key, const char* value) { static int dmlab_init(void* context) { GameContext* gc = context; DeepmindContext* ctx = gc->dm_ctx; + SCR_SkipRendering(!ctx->hooks.get_native_app(ctx->userdata)); + + if (gc->width * kMaxAspectRatio[1] > gc->height * kMaxAspectRatio[0]) { + int max_width = (gc->height * kMaxAspectRatio[0]) / kMaxAspectRatio[1]; + ctx->hooks.set_error_message( + ctx->userdata, + va("Screen width too wide for screen height. Width %d, Height %d." + " Max width for height: %d\n", + gc->width, gc->height, max_width)); + return 1; + } + + if (gc->vm_mode != VMI_NATIVE) { + Q_strcat(gc->command_line, sizeof(gc->command_line), + va(" +set vm_cgame \"%d\"" + " +set vm_game \"%d\"" + " +set vm_ui \"%d\"", + gc->vm_mode, gc->vm_mode, gc->vm_mode)); + } + if (gc->is_server) { + Q_strcat(gc->command_line, sizeof(gc->command_line), + " +set sv_hostname \"server\"" + " +set sv_fps 20" + " +set dedicated 1" + " +set sv_host server" + " +set sv_allowDownload 1"); + } if (gc->init_called) { - fputs("'init' has already been called previously.\n", stderr); + ctx->hooks.set_error_message( + ctx->userdata, "'init' has already been called previously.\n"); return 1; } gc->init_called = true; + ctx->hooks.set_level_cache_settings(ctx->userdata, gc->use_local_level_cache, + gc->use_global_level_cache, + gc->level_cache_params); return ctx->hooks.init(ctx->userdata); } +static void connect_client(GameContext* gc) { + Cmd_ExecuteString(va("connect 127.0.0.1:%d\n", gc->server_port)); + Cvar_Set("fixedtime", va("%d", gc->engine_frame_period_msec)); + Com_Frame(); + gc->is_connecting = true; +} + +static void start_server(GameContext* gc) { + dev_map(gc); + if (gc->recording_ctx->is_recording && + !dmlab_start_recording(gc->recording_ctx)) { + fprintf(stderr, "Recording failed: '%s' already exists.\n", + gc->recording_ctx->recording_name); + } + gc->is_connecting = true; +} + static int dmlab_start(void* context, int episode_id, int seed) { // Make seed a non-negative integer. seed = (seed < 0) ? seed + 1 + INT_MAX : seed; GameContext* gc = context; DeepmindContext* ctx = gc->dm_ctx; + gc->current_screen_rendered = false; + if (gc->is_connecting) { + re.MakeCurrent(); + return connecting(gc); + } + ctx->hooks.events.clear(ctx->userdata); gc->step = 0; gc->total_engine_time_msec = 0.0; gc->score = 0.0; @@ -307,7 +703,22 @@ static int dmlab_start(void* context, int episode_id, int seed) { gc->first_start = true; } - load_map(gc); + re.MakeCurrent(); + if (gc->is_client_only) { + connect_client(gc); + } else if (gc->is_server) { + start_server(gc); + } else { + load_map(gc); + if (ctx->hooks.map_loaded(ctx->userdata) != 0) { + return 1; + } + } + + gc->is_map_loading = false; + if (gc->is_client_only) { + return clc.state < CA_ACTIVE ? EAGAIN : 0; + } return 0; } @@ -316,7 +727,7 @@ static const char* dmlab_environment_name(void* context) { } static int dmlab_action_discrete_count(void* context) { - return ARRAY_LEN(kActionNames); + return ((GameContext*)context)->is_server ? 0 : ARRAY_LEN(kActionNames); } static const char* dmlab_action_discrete_name(void* context, int discrete_idx) { @@ -373,7 +784,11 @@ static void dmlab_observation_spec( void* context, int observation_idx, EnvCApi_ObservationSpec* spec) { GameContext* gc = context; - if (observation_idx < ARRAY_LEN(kObservationNames)) { + if (observation_idx == kObservations_MapFrameNumber) { + spec->type = EnvCApi_ObservationDoubles; + spec->dims = 1; + spec->shape = gc->map_frame_number_shape; + } else if (observation_idx < ARRAY_LEN(kObservationNames)) { spec->type = EnvCApi_ObservationBytes; spec->dims = 3; spec->shape = gc->image_shape; @@ -407,14 +822,6 @@ static void dmlab_observation_spec( } } -static int dmlab_event_type_count(void* context) { - return 0; -} - -const char* dmlab_event_type_name(void* context, int event_type) { - return 0; -} - static int dmlab_fps(void* context) { GameContext* gc = context; if (gc->engine_frame_period_msec > 0) { @@ -428,80 +835,111 @@ static void dmlab_observation( void* context, int observation_idx, EnvCApi_Observation* obs) { GameContext* gc = context; if (observation_idx < ARRAY_LEN(kObservationNames)) { - int window_size = gc->height * gc->width; - dmlab_observation_spec(context, observation_idx, &obs->spec); - gc->temp_buffer = realloc_or_die(gc->temp_buffer, window_size * 3); + + if (observation_idx == kObservations_MapFrameNumber) { + gc->map_frame_number_observation = cls.framecount - gc->map_start_frame; + obs->payload.doubles = &gc->map_frame_number_observation; + return; + } + re.MakeCurrent(); - qglReadPixels(0, 0, gc->width, gc->height, GL_RGB, GL_UNSIGNED_BYTE, - gc->temp_buffer); + + if (!gc->current_screen_rendered) { + SCR_SkipRendering(false); + SCR_UpdateScreen(); + gc->current_screen_rendered = true; + } + + bool render_depth = observation_idx == kObservations_RgbdInterlaced || + observation_idx == kObservations_RgbdPlanar; + + const int width = gc->width; + const int height = gc->height; + const int window_size = height * width; + + request_pixel_observations(gc, kPixelBufferTypeEnum_Rgb); + if (render_depth) { + request_pixel_observations(gc, kPixelBufferTypeEnum_Depth); + } + + byte* temp_buffer = bind_pixel_observation(gc, kPixelBufferTypeEnum_Rgb); switch (observation_idx) { - case kObservations_RgbInterlaced: + case kObservations_RgbInterlaced: { gc->image_buffer = realloc_or_die(gc->image_buffer, window_size * 3); - for (int i = 0; i < gc->height; ++i) { - for (int j = 0; j < gc->width; ++j) { - int loc = (i * gc->width + j) * 3; - int invy = (gc->height - i - 1) * gc->width + j; - gc->image_buffer[invy * 3 + 0] = gc->temp_buffer[loc + 0]; - gc->image_buffer[invy * 3 + 1] = gc->temp_buffer[loc + 1]; - gc->image_buffer[invy * 3 + 2] = gc->temp_buffer[loc + 2]; + unsigned char* const image_buffer = gc->image_buffer; + for (int i = 0; i < height; ++i) { + for (int j = 0; j < width; ++j) { + int loc = (i * width + j) * 3; + int invy = (height - i - 1) * width + j; + image_buffer[invy * 3 + 0] = temp_buffer[loc + 0]; + image_buffer[invy * 3 + 1] = temp_buffer[loc + 1]; + image_buffer[invy * 3 + 2] = temp_buffer[loc + 2]; } } break; - case kObservations_RgbdInterlaced: + } + case kObservations_RgbdInterlaced: { gc->image_buffer = realloc_or_die(gc->image_buffer, window_size * 4); - for (int i = 0; i < gc->height; ++i) { - for (int j = 0; j < gc->width; ++j) { - int loc = (i * gc->width + j) * 3; - int invy = (gc->height - i - 1) * gc->width + j; - gc->image_buffer[invy * 4 + 0] = gc->temp_buffer[loc + 0]; - gc->image_buffer[invy * 4 + 1] = gc->temp_buffer[loc + 1]; - gc->image_buffer[invy * 4 + 2] = gc->temp_buffer[loc + 2]; + unsigned char* const image_buffer = gc->image_buffer; + for (int i = 0; i < height; ++i) { + for (int j = 0; j < width; ++j) { + int loc = (i * width + j) * 3; + int invy = (height - i - 1) * width + j; + image_buffer[invy * 4 + 0] = temp_buffer[loc + 0]; + image_buffer[invy * 4 + 1] = temp_buffer[loc + 1]; + image_buffer[invy * 4 + 2] = temp_buffer[loc + 2]; } } break; - case kObservations_RgbPlanar: + } + case kObservations_RgbPlanar: { gc->image_buffer = realloc_or_die(gc->image_buffer, window_size * 3); - for (int i = 0; i < gc->height; ++i) { - for (int j = 0; j < gc->width; ++j) { - int loc = (i * gc->width + j) * 3; - int invy = (gc->height - i - 1) * gc->width + j; - gc->image_buffer[invy + window_size * 0] = gc->temp_buffer[loc + 0]; - gc->image_buffer[invy + window_size * 1] = gc->temp_buffer[loc + 1]; - gc->image_buffer[invy + window_size * 2] = gc->temp_buffer[loc + 2]; + unsigned char* const image_buffer = gc->image_buffer; + for (int i = 0; i < height; ++i) { + for (int j = 0; j < width; ++j) { + int loc = (i * width + j) * 3; + int invy = (height - i - 1) * width + j; + image_buffer[invy + window_size * 0] = temp_buffer[loc + 0]; + image_buffer[invy + window_size * 1] = temp_buffer[loc + 1]; + image_buffer[invy + window_size * 2] = temp_buffer[loc + 2]; } } break; - case kObservations_RgbdPlanar: + } + case kObservations_RgbdPlanar: { gc->image_buffer = realloc_or_die(gc->image_buffer, window_size * 4); - for (int i = 0; i < gc->height; ++i) { - for (int j = 0; j < gc->width; ++j) { - int loc = (i * gc->width + j) * 3; - int invy = (gc->height - i - 1) * gc->width + j; - gc->image_buffer[invy + window_size * 0] = gc->temp_buffer[loc + 0]; - gc->image_buffer[invy + window_size * 1] = gc->temp_buffer[loc + 1]; - gc->image_buffer[invy + window_size * 2] = gc->temp_buffer[loc + 2]; + unsigned char* const image_buffer = gc->image_buffer; + for (int i = 0; i < height; ++i) { + for (int j = 0; j < width; ++j) { + int loc = (i * width + j) * 3; + int invy = (height - i - 1) * width + j; + image_buffer[invy + window_size * 0] = temp_buffer[loc + 0]; + image_buffer[invy + window_size * 1] = temp_buffer[loc + 1]; + image_buffer[invy + window_size * 2] = temp_buffer[loc + 2]; } } break; + } } - if (observation_idx == kObservations_RgbdInterlaced || - observation_idx == kObservations_RgbdPlanar) { - qglReadPixels(0, 0, gc->width, gc->height, GL_DEPTH_COMPONENT, - GL_UNSIGNED_BYTE, gc->temp_buffer); - - for (int i = 0; i < gc->height; ++i) { - for (int j = 0; j < gc->width; ++j) { - int loc = i * gc->width + j; - int invy = (gc->height - i - 1) * gc->width + j; + unbind_pixel_observation(gc); + + if (render_depth) { + unsigned char* const image_buffer = gc->image_buffer; + temp_buffer = bind_pixel_observation(gc, kPixelBufferTypeEnum_Depth); + for (int i = 0; i < height; ++i) { + for (int j = 0; j < width; ++j) { + int loc = i * width + j; + int invy = (height - i - 1) * width + j; if (observation_idx == kObservations_RgbdInterlaced) { - gc->image_buffer[invy * 4 + 3] = gc->temp_buffer[loc]; + image_buffer[invy * 4 + 3] = temp_buffer[loc]; } else { - gc->image_buffer[invy + window_size * 3] = gc->temp_buffer[loc]; + image_buffer[invy + window_size * 3] = temp_buffer[loc]; } } } + unbind_pixel_observation(gc); } obs->payload.bytes = gc->image_buffer; } else { @@ -511,14 +949,34 @@ static void dmlab_observation( } } +static int dmlab_event_type_count(void* context) { + GameContext* gc = context; + DeepmindContext* ctx = gc->dm_ctx; + return ctx->hooks.events.type_count(ctx->userdata); +} + +static const char* dmlab_event_type_name(void* context, int event_type) { + GameContext* gc = context; + DeepmindContext* ctx = gc->dm_ctx; + return ctx->hooks.events.type_name(ctx->userdata, event_type); +} + static int dmlab_event_count(void* context) { - return 0; + GameContext* gc = context; + DeepmindContext* ctx = gc->dm_ctx; + return ctx->hooks.events.count(ctx->userdata); } -static void dmlab_event(void* context, int event_idx, EnvCApi_Event* event) {} +static void dmlab_event(void* context, int event_idx, EnvCApi_Event* event) { + GameContext* gc = context; + DeepmindContext* ctx = gc->dm_ctx; + return ctx->hooks.events.export_event(ctx->userdata, event_idx, event); +} static void dmlab_act(void* context, const int act_d[], const double act_c[]) { GameContext* gc = context; + gc->is_connecting = false; + if (gc->is_server) return; DeepmindContext* ctx = gc->dm_ctx; int rightmove = act_d[kActions_StrafeLeftRight] * 127; int forwardmove = act_d[kActions_MoveBackForward] * 127; @@ -547,6 +1005,9 @@ static EnvCApi_EnvironmentStatus dmlab_advance( re.MakeCurrent(); GameContext* gc = context; DeepmindContext* ctx = gc->dm_ctx; + SCR_SkipRendering(!ctx->hooks.get_native_app(ctx->userdata)); + gc->current_screen_rendered = false; + ctx->hooks.events.clear(ctx->userdata); *reward = 0; bool episode_ended = false; for (int i = 0; i < num_steps && !episode_ended; ++i) { @@ -554,7 +1015,13 @@ static EnvCApi_EnvironmentStatus dmlab_advance( if (ctx->hooks.map_finished(ctx->userdata)) { // Capture any rewards given during map_finished(). double final_reward_score = get_engine_score(); - load_map(gc); + + if (!load_map(gc)) { + return EnvCApi_EnvironmentStatus_Terminated; + } + if (ctx->hooks.map_loaded(ctx->userdata) != 0) { + return EnvCApi_EnvironmentStatus_Error; + } ctx->hooks.set_map_finished(ctx->userdata, false); // TODO: Update player score to keep from previous map. double start_reward = get_engine_score(); @@ -577,10 +1044,18 @@ static EnvCApi_EnvironmentStatus dmlab_advance( episode_ended = ctx->hooks.has_episode_finished( ctx->userdata, gc->total_engine_time_msec / (kEngineTimePerExternalTime * 1000.0)); - double reward_after = get_engine_score(); - double delta_score = reward_after - reward_before; - gc->score += delta_score; - *reward += delta_score; + // The last frame of demos wipe the game state, effectively erasing the + // score. By checking the state for active we only accumulate the score if + // it has not been wiped. This is a workaround for an issue where server + // game script methods are not invoked during demos (i.e. set_map_finished + // is not triggered.) + if (clc.state == CA_ACTIVE) { + double reward_after = get_engine_score(); + double delta_score = reward_after - reward_before; + gc->score += delta_score; + *reward += delta_score; + } + gc->is_map_loading = false; } return episode_ended ? EnvCApi_EnvironmentStatus_Terminated @@ -591,7 +1066,26 @@ static void dmlab_destroy_context(void* context) { GameContext* gc = context; DeepmindContext* ctx = gc->dm_ctx; + if (gc->recording_ctx->is_recording) { + dmlab_stop_recording(gc->recording_ctx); + } + if (gc->recording_ctx->is_video) { + dmlab_stop_video(gc->recording_ctx); + } + + if (gc->pbos.rgb.id || gc->pbos.depth.id) { + re.MakeCurrent(); + if (gc->pbos.rgb.id) { + qglDeleteBuffers(1, &gc->pbos.rgb.id); + } + + if (gc->pbos.depth.id) { + qglDeleteBuffers(1, &gc->pbos.depth.id); + } + } + dmlab_release_context(ctx); + free(gc->recording_ctx); free(gc->temp_buffer); free(gc->image_buffer); free(gc); @@ -612,6 +1106,35 @@ static void add_bot(const char* name, double skill, const char* team) { Cbuf_AddText(va("addbot %s %f %s\n", name, skill, team)); } +static int engine_frame_period_msec() { + DeepmindContext* ctx = dmlab_context(); + GameContext* gc = ctx->context; + return gc->engine_frame_period_msec; +} + +static int total_engine_time_msec() { + DeepmindContext* ctx = dmlab_context(); + GameContext* gc = ctx->context; + return gc->total_engine_time_msec; +} + +static double total_time_seconds() { + DeepmindContext* ctx = dmlab_context(); + GameContext* gc = ctx->context; + return gc->total_engine_time_msec / (kEngineTimePerExternalTime * 1000.0); +} + +static bool dmlab_is_map_loading(void* context) { + DeepmindContext* ctx = dmlab_context(); + GameContext* gc = ctx->context; + return gc->is_map_loading; +} + +float dmlab_raycast(const float start[3], const float end[3]); + +bool dmlab_update_rgba_texture(const char* name, int width, int height, + const unsigned char* data); + int dmlab_connect(const DeepMindLabLaunchParams* params, EnvCApi* env_c_api, void** context) { DeepmindContext* dm_ctx = get_context_once(); @@ -632,19 +1155,34 @@ int dmlab_connect(const DeepMindLabLaunchParams* params, EnvCApi* env_c_api, return 4; } + dmlabRecordingContext* rcxt = calloc(1, sizeof(dmlabRecordingContext)); + if (rcxt == NULL) { + return 1; + } + *context = gc; Q_strncpyz(gc->runfiles_path, params->runfiles_path, sizeof(gc->runfiles_path)); + + // Disable local level cache by default. + gc->use_local_level_cache = false; + gc->use_global_level_cache = true; + gc->level_cache_params = params->level_cache_params; gc->width = 320; - gc->height = 240; + gc->height = 180; gc->dm_ctx = dm_ctx; + gc->recording_ctx = rcxt; + gc->map_frame_number_shape[0] = 1; + gc->map_frame_number_observation = 0; + gc->pbos.enabled = true; memset(env_c_api, 0, sizeof(EnvCApi)); env_c_api->setting = dmlab_setting; env_c_api->init = dmlab_init; env_c_api->start = dmlab_start; + env_c_api->error_message = dmlab_error_message; env_c_api->environment_name = dmlab_environment_name; env_c_api->action_discrete_count = dmlab_action_discrete_count; env_c_api->action_discrete_name = dmlab_action_discrete_name; @@ -671,7 +1209,17 @@ int dmlab_connect(const DeepMindLabLaunchParams* params, EnvCApi* env_c_api, gc->dm_ctx->calls.add_bot = add_bot; gc->dm_ctx->calls.engine_frame_period_msec = engine_frame_period_msec; gc->dm_ctx->calls.total_engine_time_msec = total_engine_time_msec; + gc->dm_ctx->calls.total_time_seconds = total_time_seconds; + gc->dm_ctx->calls.deserialise_model = dmlab_deserialise_model; + gc->dm_ctx->calls.load_model = dmlab_load_model; + gc->dm_ctx->calls.serialised_model_size = dmlab_serialised_model_size; + gc->dm_ctx->calls.serialise_model = dmlab_serialise_model; + gc->dm_ctx->calls.save_model = dmlab_save_model; + gc->dm_ctx->calls.update_rgba_texture = dmlab_update_rgba_texture; + gc->dm_ctx->calls.raycast = dmlab_raycast; + gc->dm_ctx->calls.is_map_loading = dmlab_is_map_loading; gc->dm_ctx->context = gc; - - return dmlab_create_context(gc->runfiles_path, gc->dm_ctx); + return dmlab_create_context(gc->runfiles_path, gc->dm_ctx, + params->file_reader_override, + params->optional_temp_folder); } diff --git a/engine/code/deepmind/dmlab_load_model.c b/engine/code/deepmind/dmlab_load_model.c new file mode 100644 index 00000000..341a8f39 --- /dev/null +++ b/engine/code/deepmind/dmlab_load_model.c @@ -0,0 +1,210 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "dmlab_load_model.h" + +#include + +#include "../qcommon/q_platform.h" +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" + +static const float kPi = 3.14159265358979323846f; + +// Decode a normal vector from the runtime format used by ioq3 renderers. +static void LatLongToNormal(byte normal[2], vec3_t out) { + float phi = normal[0] * 2.0f * kPi / 255.0f; + float theta = normal[1] * 2.0f * kPi / 255.0f; + out[0] = cosf(theta) * sinf(phi); + out[1] = sinf(theta) * sinf(phi); + out[2] = cosf(phi); +} + +// Traverse the contents of md3Surface data structure 'md3_surf', invoking the +// relevant callbacks in `model_setters`. +static bool DeserialiseSurface( // + const md3Surface_t* md3_surf, // + size_t surf_idx, // + const DeepmindModelSetters* model_setters, // + void* model_data) { + // Check MD3 surface signature. + if (md3_surf->ident != LittleLong(MD3_IDENT)) { + fputs("Invalid surface data.\n", stderr); + return false; + } + + // Set surface name. + model_setters->set_surface_name(model_data, surf_idx, md3_surf->name); + + // Set component counts. + size_t num_verts = LittleLong(md3_surf->numVerts); + model_setters->set_surface_vertex_count(model_data, surf_idx, num_verts); + size_t num_triangles = LittleLong(md3_surf->numTriangles); + model_setters->set_surface_face_count(model_data, surf_idx, num_triangles); + size_t num_shaders = LittleLong(md3_surf->numShaders); + model_setters->set_surface_shader_count(model_data, surf_idx, num_shaders); + + // Set vertex data. + size_t ofs_xyznormals = LittleLong(md3_surf->ofsXyzNormals); + const md3XyzNormal_t* md3_verts = + (const md3XyzNormal_t*)((const byte*)md3_surf + ofs_xyznormals); + size_t ofs_st = LittleLong(md3_surf->ofsSt); + const md3St_t* md3_st = + (const md3St_t*)((const byte*)md3_surf + ofs_st); + for (size_t i = 0; i < num_verts; ++i) { + float location[3]; + float normal[3]; + float st[2]; + short md3_normal; + + // Set vertex location. + for (size_t j = 0; j < 3; ++j) { + location[j] = LittleShort(md3_verts[i].xyz[j]) * MD3_XYZ_SCALE; + } + model_setters->set_surface_vertex_location(model_data, surf_idx, i, + location); + + // Set vertex normal. + md3_normal = LittleShort(md3_verts[i].normal); + LatLongToNormal((byte *)&md3_normal, normal); + model_setters->set_surface_vertex_normal(model_data, surf_idx, i, normal); + + // Set vertex texture coordinates. + for (size_t j = 0; j < 2; ++j) { + st[j] = LittleFloat(md3_st[i].st[j]); + } + model_setters->set_surface_vertex_st(model_data, surf_idx, i, st); + } + + // Set face indices. + size_t ofs_triangles = LittleLong(md3_surf->ofsTriangles); + const md3Triangle_t* md3_triangles = + (const md3Triangle_t*)((const byte*)md3_surf + ofs_triangles); + for (size_t i = 0; i < num_triangles; ++i) { + int triangle[3]; + for (size_t j = 0; j < 3; ++j) { + triangle[j] = LittleLong(md3_triangles[i].indexes[j]); + } + model_setters->set_surface_face(model_data, surf_idx, i, triangle); + } + + // Set shaders. + size_t ofs_shaders = LittleLong(md3_surf->ofsShaders); + const md3Shader_t* md3_shaders = + (const md3Shader_t*)((const byte*)md3_surf + ofs_shaders); + for (size_t i = 0; i < num_shaders; ++i) { + model_setters->set_surface_shader(model_data, surf_idx, i, + md3_shaders[i].name); + } + + return true; +} + +// Traverse the contents of md3Tag data structure 'tag', invoking the relevant +// callbacks in `model_setters`. +static void DeserialiseTag( // + const md3Tag_t* md3_tag, // + size_t tag_idx, // + const DeepmindModelSetters* model_setters, // + void* model_data) { + model_setters->set_tag_name(model_data, tag_idx, md3_tag->name); + for (size_t i = 0; i < 3; ++i) { + float axis[3]; + for (size_t j = 0; j < 3; ++j) { + axis[j] = LittleFloat(md3_tag->axis[i][j]); + } + model_setters->set_tag_axis(model_data, tag_idx, i, axis); + } + float origin[3]; + for (size_t j = 0; j < 3; ++j) { + origin[j] = LittleFloat(md3_tag->origin[j]); + } + model_setters->set_tag_origin(model_data, tag_idx, origin); +} + +bool dmlab_deserialise_model( // + const void* buffer, // + const DeepmindModelSetters* model_setters, // + void* model_data) { + const md3Header_t* md3_model = (const md3Header_t*)buffer; + + // Check MD3 model_setters signature. + if (md3_model->ident != LittleLong(MD3_IDENT)) { + fputs("Invalid model_setters data.\n", stderr); + return false; + } + + // Check MD3 version. + if (md3_model->version != LittleLong(15)) { + fprintf(stderr, "Unsupported version model_setters: %d\n", + LittleLong(md3_model->version)); + return false; + } + + // Set model_setters name. + model_setters->set_name(model_data, md3_model->name); + + // Set model_setters component counts. + size_t num_surfs = LittleLong(md3_model->numSurfaces); + model_setters->set_surface_count(model_data, num_surfs); + size_t num_tags = LittleLong(md3_model->numTags); + model_setters->set_tag_count(model_data, num_tags); + + // Set model_setters surfaces. + size_t ofs_surfs = LittleLong(md3_model->ofsSurfaces); + const md3Surface_t* md3_surf = + (const md3Surface_t*)((const byte*)buffer + ofs_surfs); + for (size_t i = 0; i < num_surfs; ++i) { + if (!DeserialiseSurface(md3_surf, i, model_setters, model_data)) { + return false; + } + md3_surf = (const md3Surface_t*)((const byte*)md3_surf + + LittleLong(md3_surf->ofsEnd)); + } + + // Set model_setters tags. + size_t ofs_tags = LittleLong(md3_model->ofsTags); + const md3Tag_t* md3_tags = + (const md3Tag_t*)((const byte*)buffer + ofs_tags); + for (size_t i = 0; i < num_tags; ++i) { + DeserialiseTag(md3_tags + i, i, model_setters, model_data); + } + + return true; +} + +bool dmlab_load_model( // + const char* model_path, // + const DeepmindModelSetters* model_setters, // + void* model_data) { + if (!FS_Initialized()) { + fputs("File system not initialized, cannot load models.\n", stderr); + return false; + } + void* buffer; + FS_ReadFile(model_path, &buffer); + if (buffer == NULL) { + fprintf(stderr, "Unable to open model_setters file: %s\n", model_path); + return false; + } + if (!dmlab_deserialise_model(buffer, model_setters, model_data)) { + return false; + } + FS_FreeFile(buffer); + return true; +} diff --git a/engine/code/deepmind/dmlab_load_model.h b/engine/code/deepmind/dmlab_load_model.h new file mode 100644 index 00000000..0778d018 --- /dev/null +++ b/engine/code/deepmind/dmlab_load_model.h @@ -0,0 +1,42 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef DML_ENGINE_CODE_DEEPMIND_DMLAB_LOAD_MODEL_H_ +#define DML_ENGINE_CODE_DEEPMIND_DMLAB_LOAD_MODEL_H_ + +#include + +#include "../../../deepmind/include/deepmind_model_setters.h" + +// Attempts to load a serialised model from the MD3 data in 'buffer', invoking +// the relevant callbacks in 'model_setters' as the data is traversed. +// Returns whether valid model data was found in the buffer. +bool dmlab_deserialise_model( // + const void* buffer, // + const DeepmindModelSetters* model_setters, // + void* model_data); + +// Attempts to load a model from a MD3 file at the given path, invoking the +// relevant callbacks in 'model_setters' as the file is traversed. +// Returns whether a valid model file was found at the given path. +bool dmlab_load_model( // + const char* model_path, // + const DeepmindModelSetters* model_setters, // + void* model_data); + +#endif // DML_ENGINE_CODE_DEEPMIND_DMLAB_LOAD_MODEL_H_ diff --git a/engine/code/deepmind/dmlab_recording.c b/engine/code/deepmind/dmlab_recording.c new file mode 100644 index 00000000..28e9d497 --- /dev/null +++ b/engine/code/deepmind/dmlab_recording.c @@ -0,0 +1,281 @@ +// Copyright (C) 2016 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "dmlab_recording.h" + +#include "../qcommon/qcommon.h" + +#include +#include +#include +#include + +static const char* homefiles_file(const char* file_name) { + static char path[MAX_STRING_CHARS]; + const char* homepath = Cvar_VariableString("fs_homepath"); + const char* gamedir = Cvar_VariableString("fs_game"); + + Q_strncpyz(path, FS_BuildOSPath(homepath, gamedir, file_name), sizeof(path)); + return path; +} + +static const char* homefiles_demo_path(const char* demo_name, int demo_number) { + const char* demoext = "dm_"; // DEMOEXT, from qshared.h + const char* protocol = Cvar_VariableString("com_protocol"); + + char* file = va("demos/%s/%05d.%s%s", demo_name, demo_number, + demoext, protocol); + return homefiles_file(file); +} + +static const char* homefiles_video_path( + const char* video_name, int demo_number) { + const char* videoext = "avi"; + + char* file = va("videos/%s/%05d.%s", video_name, demo_number, videoext); + return homefiles_file(file); +} + +static const char* demofiles_demo_path( + const char* demofiles_path, const char* demo_name, int demo_number) { + static char path[MAX_STRING_CHARS]; + const char* demoext = "dm_"; // DEMOEXT, from qshared.h + const char* protocol = Cvar_VariableString("com_protocol"); + + char* file = va("demos/%s/%05d.%s%s", + demo_name, demo_number, demoext, protocol); + Q_strncpyz(path, FS_BuildOSPath(demofiles_path, ".", file), + sizeof(path)); + return path; +} + +static const char* demofiles_video_path( + const char* demofiles_path, const char* video_name, int demo_number) { + static char path[MAX_STRING_CHARS]; + + const char* videoext = "avi"; + char* file = va("videos/%s/%05d.%s", video_name, demo_number, videoext); + + Q_strncpyz(path, FS_BuildOSPath(demofiles_path, ".", file), sizeof(path)); + return path; +} + +static bool file_exists(const char *path) { + struct stat path_stat; + return stat(path, &path_stat) == 0 && S_ISREG(path_stat.st_mode); +} + +// Copies the file contents from one file pointer to another. +// Returns 0 on success, 1 otherwise. +static int copy_fp(FILE* src_file, FILE* dest_file) { + static char buf[MAX_STRING_CHARS]; + size_t len; + + while ((len = fread(buf, 1, MAX_STRING_CHARS, src_file))) { + if (len != fwrite(buf, 1, len, dest_file)) { + return 1; // errno might be set. + } + } + + return ferror(src_file) != 0; +} + +// Moves a file from the src location to the dest location potentially across +// storage devices. +static int move_file(const char* src, const char* dest) { + FILE* src_file; + FILE* dest_file; + + int rename_code = rename(src, dest); + if (rename_code == 0) { + return 0; + } + + // Continue only if it's an invalid cross-device link error. + if (errno != EXDEV) { + return rename_code; + } + + // Rename failed, copy the file and delete the original. + if (!(src_file = fopen(src, "r"))) { + return 1; // 'fopen' sets errno. + } + + if (!(dest_file = fopen(dest, "w"))) { + fclose(src_file); + return 1; // 'fopen' sets errno. + } + + int copy_fp_code = copy_fp(src_file, dest_file); + fclose(src_file); + fclose(dest_file); + + if (copy_fp_code == 0) { + return unlink(src); + } else { + unlink(dest); + return copy_fp_code; + } +} + +void dmlab_set_recording_name(dmlabRecordingContext* ctx, const char* name) { + if (name == NULL || name[0] == '\0') { + ctx->is_recording = false; + } else { + Q_strncpyz(ctx->recording_name, name, sizeof(ctx->recording_name)); + ctx->is_recording = true; + ctx->demo_number = 0; + } +} + +void dmlab_set_demo_name(dmlabRecordingContext* ctx, const char* name) { + if (name == NULL || name[0] == '\0') { + ctx->is_recording = false; + } else { + Q_strncpyz(ctx->demo_name, name, sizeof(ctx->demo_name)); + ctx->is_demo = true; + ctx->demo_number = 0; + } +} + +void dmlab_set_video_name(dmlabRecordingContext *ctx, const char* name) { + if (name == NULL || name[0] == '\0') { + ctx->is_video = false; + } else { + Q_strncpyz(ctx->video_name, name, sizeof(ctx->video_name)); + ctx->is_video = true; + } +} + +void dmlab_set_demofiles_path( + dmlabRecordingContext* context, const char* path) { + Q_strncpyz(context->demofiles_path, path, sizeof(context->demofiles_path)); +} + +bool dmlab_start_recording(dmlabRecordingContext* ctx) { + if (ctx->demofiles_path[0] == '\0') { + fprintf(stderr, "Recording failed: demofiles_path not specified.\n"); + return false; + } + ctx->demo_number++; + if (file_exists(demofiles_demo_path(ctx->demofiles_path, ctx->recording_name, + ctx->demo_number)) || + file_exists(homefiles_demo_path(ctx->recording_name, ctx->demo_number))) { + fprintf(stderr, "Recording failed: '%s' already exists.\n", + ctx->recording_name); + return false; + } else { + Cvar_Set("ui_recordSPDemo", "1"); + Cvar_Set("g_synchronousClients", "1"); + Cbuf_AddText(va("record \"%s/%05d\"\n", ctx->recording_name, + ctx->demo_number)); + return true; + } +} + +bool dmlab_stop_recording(dmlabRecordingContext* ctx) { + // Make sure recordings are completely written out at shutdown. + if (fflush(NULL) != 0) { + fprintf(stderr, "Error flushing output streams: %s\n", strerror(errno)); + } + + FS_CreatePath( + (char *)demofiles_demo_path(ctx->demofiles_path, ctx->recording_name, 0)); + + for (int demo_number = 1; demo_number <= ctx->demo_number; demo_number++) { + const char* demofiles_path = demofiles_demo_path( + ctx->demofiles_path, ctx->recording_name, demo_number); + const char* homefiles_path = homefiles_demo_path( + ctx->recording_name, demo_number); + if (move_file(homefiles_path, demofiles_path)) { + fprintf(stderr, "Moving demo file failed: %s %s %s\n", + strerror(errno), homefiles_path, demofiles_path); + return false; + } + } + return true; +} + +bool dmlab_start_demo(dmlabRecordingContext* ctx) { + if (ctx->demofiles_path[0] == '\0') { + fprintf(stderr, "Demo playback failed: demofiles_path not specified.\n"); + return false; + } + if (ctx->demo_number == 0) { + FS_CreatePath((char *)homefiles_demo_path(ctx->demo_name, 0)); + + int demo_number = 1; + while (file_exists(demofiles_demo_path( + ctx->demofiles_path, ctx->demo_name, demo_number))) { + const char* demofiles_path = demofiles_demo_path( + ctx->demofiles_path, ctx->demo_name, demo_number); + const char* homefiles_path = homefiles_demo_path( + ctx->demo_name, demo_number); + if (move_file(demofiles_path, homefiles_path)) { + fprintf(stderr, "Moving demo file failed: %s %s %s\n", + strerror(errno), homefiles_path, demofiles_path); + return false; + } + demo_number++; + } + } + + ctx->demo_number++; + const char* homefiles_path = homefiles_demo_path( + ctx->demo_name, ctx->demo_number); + if (file_exists(homefiles_path)) { + Cbuf_AddText(va("demo \"%s/%05d\"\n", ctx->demo_name, ctx->demo_number)); + return true; + } else { + return false; + } +} + +bool dmlab_start_video(dmlabRecordingContext* ctx) { + if (ctx->demofiles_path[0] == '\0') { + fprintf(stderr, "Video recording failed: demofiles_path not specified.\n"); + return false; + } + if (file_exists(homefiles_video_path(ctx->video_name, ctx->demo_number))) { + fprintf(stderr, "Video recording failed: '%s' already exists.\n", + ctx->video_name); + return false; + } else { + // Demo number is incremented by dmlab_start_demo (since video must be + // invoked with demo). + Cbuf_AddText(va("video \"%s/%05d\"\n", ctx->video_name, ctx->demo_number)); + return true; + } +} + +bool dmlab_stop_video(dmlabRecordingContext* ctx) { + FS_CreatePath((char *)demofiles_video_path( + ctx->demofiles_path, ctx->video_name, 0)); + for (int demo_number = 1; demo_number <= ctx->demo_number; demo_number++) { + const char* demofiles_path = demofiles_video_path( + ctx->demofiles_path, ctx->video_name, demo_number); + const char* homefiles_path = homefiles_video_path( + ctx->video_name, demo_number); + if (move_file(homefiles_path, demofiles_path)) { + fprintf(stderr, "Moving video file failed: %s %s %s\n", + strerror(errno), homefiles_path, demofiles_path); + return false; + } + } + return true; +} diff --git a/engine/code/deepmind/dmlab_recording.h b/engine/code/deepmind/dmlab_recording.h new file mode 100644 index 00000000..ec5ace76 --- /dev/null +++ b/engine/code/deepmind/dmlab_recording.h @@ -0,0 +1,91 @@ +// Copyright (C) 2016 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// +#ifndef DML_ENGINE_CODE_DEEPMIND_DMLAB_RECORDING_H_ +#define DML_ENGINE_CODE_DEEPMIND_DMLAB_RECORDING_H_ + +#include + +#include "../qcommon/q_shared.h" + +typedef struct dmlabRecordingContext_s { + // The name of the recording to be stored in the demos directory in runfiles. + // [runfiles_dir]/demos/[recording_name]/[recording_number].dm_71 + char recording_name[MAX_STRING_CHARS]; + + // The name of the demo to be played back, same as the structure of + // recording_name. + char demo_name[MAX_STRING_CHARS]; + + // The name of the video directory to be created. + // [runfiles_dir]/videos/[video_name]/[recording_number].avi + char video_name[MAX_STRING_CHARS]; + + // True when configured to create a recording, otherwise false. + bool is_recording; + + // True when configured to play back a demo, otherwise false. + bool is_demo; + + // True when configured to generate a video, otherwise false. + bool is_video; + + // The numerical index of the recording, increments each time a new map is + // loaded in the episode. + int demo_number; + + // The path where the demo files are located when is_demo or the path to + // where the files should be moved when is_recording. + char demofiles_path[MAX_STRING_CHARS]; +} dmlabRecordingContext; + +// Copies |name| into the context as the recording name. +void dmlab_set_recording_name(dmlabRecordingContext* context, const char* name); + +// Copies |name| into the context as the video name. +void dmlab_set_video_name(dmlabRecordingContext* context, const char* name); + +// Copies |name| into the context as the demo name. +void dmlab_set_demo_name(dmlabRecordingContext* context, const char* name); + +// Copies |path| into the context as the demofiles_path. +void dmlab_set_demofiles_path(dmlabRecordingContext* context, const char* path); + +// Starts recording to the demo directory. If the demo with the specified name +// already exists, recording does not start and false is returned. Otherwise, +// true is returned. +bool dmlab_start_recording(dmlabRecordingContext* context); + +// Ends the recording and moves any demo files from the home directory to +// the demo path. +bool dmlab_stop_recording(dmlabRecordingContext* context); + +// Starts recording to the video directory. If the video with the specified name +// already exists, recording does not start and false is returned. Otherwise, +// true is returned. +bool dmlab_start_video(dmlabRecordingContext* context); + +// Ends the video recording and moves any video files from the home directory to +// the demo path. +bool dmlab_stop_video(dmlabRecordingContext* context); + +// Starts playback of the recorded demo. Moves files from the demo path to the +// home path if any exist. If there are no more demos to be played, returns +// false. Otherwise returns true. +bool dmlab_start_demo(dmlabRecordingContext* context); + +#endif // DML_ENGINE_CODE_DEEPMIND_DMLAB_RECORDING_H_ diff --git a/engine/code/deepmind/dmlab_save_model.c b/engine/code/deepmind/dmlab_save_model.c new file mode 100644 index 00000000..90f34782 --- /dev/null +++ b/engine/code/deepmind/dmlab_save_model.c @@ -0,0 +1,294 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "dmlab_save_model.h" + +#include + +#include "../qcommon/q_platform.h" +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" + +// The AABB functions below have been duplicated from q3map2/libs/mathlib. +typedef struct aabb_s { + vec3_t origin; + vec3_t extents; + vec_t radius; +} aabb_t; + +static void AABBUpdateRadius(aabb_t *aabb) { + aabb->radius = VectorLength(aabb->extents); +} + +static void AABBClear(aabb_t *aabb) { + aabb->origin[0] = aabb->origin[1] = aabb->origin[2] = 0; + aabb->extents[0] = aabb->extents[1] = aabb->extents[2] = -1; +} + +static void AABBExtendByPoint(aabb_t *aabb, const vec3_t point) { + vec_t min, max, displacement; + for (int i = 0; i < 3; ++i) { + displacement = point[i] - aabb->origin[i]; + if (fabs(displacement) > aabb->extents[i]) { + if (aabb->extents[i] < 0) { // degenerate + min = max = point[i]; + } else if (displacement > 0) { + min = aabb->origin[i] - aabb->extents[i]; + max = aabb->origin[i] + displacement; + } else { + max = aabb->origin[i] + aabb->extents[i]; + min = aabb->origin[i] + displacement; + } + aabb->origin[i] = (min + max) * 0.5f; + aabb->extents[i] = max - aabb->origin[i]; + } + } +} + +// Encode a normal vector in the runtime format used by ioq3 renderers, +// expressing them in spherical coordinates (longitude, latitude) and storing +// such coordinates in unsigned byte range. +static void NormalToLatLong(const vec3_t normal, byte out[2]) { + // check for singularities + if (normal[0] == 0 && normal[1] == 0) { + if (normal[2] > 0) { + out[0] = 0; + out[1] = 0; // lat = 0, long = 0 + } else { + out[0] = 128; + out[1] = 0; // lat = 0, long = 128 + } + } else { + int a = RAD2DEG(atan2(normal[1], normal[0])) * (255.0f / 360.0f); + int b = RAD2DEG(acos(normal[2])) * (255.0f / 360.0f); + out[0] = b & 0xff; // longitude + out[1] = a & 0xff; // latitude + } +} + +// Compute the size required to store the surface with index 'surf_idx', using +// the surface data returned by the callbacks in 'model'. +static size_t SurfaceSize( // + size_t surf_idx, // + const DeepmindModelGetters* model, // + void* model_data) { + size_t res; + res = sizeof(md3Surface_t); + res += model->get_surface_shader_count(model_data, surf_idx) * + sizeof(md3Shader_t); + res += model->get_surface_face_count(model_data, surf_idx) * + sizeof(md3Triangle_t); + res += model->get_surface_vertex_count(model_data, surf_idx) * + (sizeof(md3St_t) + sizeof(md3XyzNormal_t)); + return res; +} + +// Returns the size required to store the serialised form of 'model'. +size_t dmlab_serialised_model_size( // + const DeepmindModelGetters* model, // + void* model_data) { + size_t res = sizeof(md3Header_t); + res += sizeof(md3Frame_t); + res += model->get_tag_count(model_data) * sizeof(md3Tag_t); + size_t num_surfs = model->get_surface_count(model_data); + for (size_t i = 0; i < num_surfs; ++i) { + res += SurfaceSize(i, model, model_data); + } + return res; +} + +// Fill buffer 'surf' with the surface data for index 'surf_idx' returned by the +// callbacks in 'model'. +static void SerialiseSurface( // + size_t surf_idx, // + const DeepmindModelGetters* model, // + void* model_data, // + md3Surface_t* md3_surf, // + aabb_t* bbox) { + // Fill surface header. + md3_surf->ident = LittleLong(MD3_IDENT); + model->get_surface_name(model_data, surf_idx, MAX_QPATH, md3_surf->name); + md3_surf->flags = 0; + md3_surf->numFrames = LittleLong(1); + size_t num_shaders = model->get_surface_shader_count(model_data, surf_idx); + md3_surf->numShaders = LittleLong(num_shaders); + size_t num_verts = model->get_surface_vertex_count(model_data, surf_idx); + md3_surf->numVerts = LittleLong(num_verts); + size_t num_triangles = model->get_surface_face_count(model_data, surf_idx); + md3_surf->numTriangles = LittleLong(num_triangles); + size_t ofs_shaders = sizeof(md3Surface_t); + md3_surf->ofsShaders = LittleLong(ofs_shaders); + size_t ofs_triangles = ofs_shaders + num_shaders * sizeof(md3Shader_t); + md3_surf->ofsTriangles = LittleLong(ofs_triangles); + size_t ofs_st = ofs_triangles + num_triangles * sizeof(md3Triangle_t); + md3_surf->ofsSt = LittleLong(ofs_st); + size_t ofs_xyznormals = ofs_st + num_verts * sizeof(md3St_t); + md3_surf->ofsXyzNormals = LittleLong(ofs_xyznormals); + size_t ofs_end = ofs_xyznormals + num_verts * sizeof(md3XyzNormal_t); + md3_surf->ofsEnd = LittleLong(ofs_end); + + // Fill shader data. + md3Shader_t* md3_shaders = (md3Shader_t*)((byte*)md3_surf + ofs_shaders); + for (size_t i = 0; i < num_shaders; ++i) { + model->get_surface_shader(model_data, surf_idx, i, MAX_QPATH, + md3_shaders[i].name); + md3_shaders[i].shaderIndex = 0; + } + + // Fill triangle data. + md3Triangle_t* md3_triangles = + (md3Triangle_t*)((byte*)md3_surf + ofs_triangles); + for (size_t i = 0; i < num_triangles; ++i) { + int triangle[3]; + model->get_surface_face(model_data, surf_idx, i, triangle); + for (size_t j = 0; j < 3; ++j) { + md3_triangles[i].indexes[j] = LittleLong(triangle[j]); + } + } + + // Fill texture coordinate data. + md3St_t* md3_st = (md3St_t*)((byte*)md3_surf + ofs_st); + for (size_t i = 0; i < num_verts; ++i) { + float st[2]; + model->get_surface_vertex_st(model_data, surf_idx, i, st); + for (size_t j = 0; j < 2; ++j) { + md3_st[i].st[j] = LittleFloat(st[j]); + } + } + + // Fill vertex data. + md3XyzNormal_t* md3_verts = + (md3XyzNormal_t*)((byte*)md3_surf + ofs_xyznormals); + for (size_t i = 0; i < num_verts; ++i) { + float location[3]; + float normal[3]; + short md3_normal; + + // Fill vertex location. + model->get_surface_vertex_location(model_data, surf_idx, i, location); + for (size_t j = 0; j < 3; ++j) { + md3_verts[i].xyz[j] = LittleShort(location[j] / MD3_XYZ_SCALE); + } + + // Fill vertex normal. + model->get_surface_vertex_normal(model_data, surf_idx, i, normal); + NormalToLatLong(normal, (byte *)&md3_normal); + md3_verts[i].normal = LittleShort(md3_normal); + + // Update bounding box. + AABBExtendByPoint(bbox, location); + } +} + +// Fill buffer 'tag' with the tag data for index 'tag_idx' returned by the +// callbacks in 'model'. +static void SerialiseTag( // + size_t tag_idx, // + const DeepmindModelGetters* model, // + void* model_data, // + md3Tag_t* tag) { + model->get_tag_name(model_data, tag_idx, MAX_QPATH, tag->name); + for (size_t i = 0; i < 3; ++i) { + float axis[3]; + model->get_tag_axis(model_data, tag_idx, i, axis); + for (size_t j = 0; j < 3; ++j) { + tag->axis[i][j] = LittleFloat(axis[j]); + } + } + float origin[3]; + model->get_tag_origin(model_data, tag_idx, origin); + for (size_t j = 0; j < 3; ++j) { + tag->origin[j] = LittleFloat(origin[j]); + } +} + +void dmlab_serialise_model( // + const DeepmindModelGetters* model, // + void* model_data, // + void* buffer) { + // Fill model data. + md3Header_t* md3_model = (md3Header_t*)buffer; + md3_model->ident = LittleLong(MD3_IDENT); + md3_model->version =LittleLong(MD3_VERSION); + model->get_name(model_data, MAX_QPATH, md3_model->name); + md3_model->flags = 0; + md3_model->numFrames = LittleLong(1); + size_t num_tags = model->get_tag_count(model_data); + md3_model->numTags = LittleLong(num_tags); + size_t num_surfs = model->get_surface_count(model_data); + md3_model->numSurfaces = LittleLong(num_surfs); + md3_model->numSkins = 0; + size_t ofs_frames = sizeof(md3Header_t); + md3_model->ofsFrames = LittleLong(ofs_frames); + size_t ofs_tags = ofs_frames + sizeof(md3Frame_t); + md3_model->ofsTags = LittleLong(ofs_tags); + size_t ofs_surfs = ofs_tags + num_tags * sizeof(md3Tag_t); + md3_model->ofsSurfaces = LittleLong(ofs_surfs); + + // Fill tags. + md3Tag_t* md3_tags = (md3Tag_t*)(buffer + ofs_tags); + for (size_t i = 0; i < num_tags; ++i) { + SerialiseTag(i, model, model_data, md3_tags + i); + } + + // Fill surfaces. + aabb_t bbox; + AABBClear(&bbox); + md3Surface_t* md3_surf = (md3Surface_t*)(buffer + ofs_surfs); + for (size_t i = 0; i < num_surfs; ++i) { + SerialiseSurface(i, model, model_data, md3_surf, &bbox); + md3_surf = (md3Surface_t*)((byte*)md3_surf + LittleLong(md3_surf->ofsEnd)); + } + + // Fill frame data. + AABBUpdateRadius(&bbox); + md3Frame_t* md3_frame = (md3Frame_t*)(buffer + ofs_frames); + for (size_t j = 0; j < 3; ++j) { + md3_frame->bounds[0][j] = LittleFloat(bbox.origin[j] - bbox.extents[j]); + md3_frame->bounds[1][j] = LittleFloat(bbox.origin[j] + bbox.extents[j]); + md3_frame->localOrigin[j] = LittleFloat(bbox.origin[j]); + } + md3_frame->radius = LittleFloat(bbox.radius); + Q_strncpyz(md3_frame->name, md3_model->name, sizeof(md3_frame->name)); + + // Complete model data. + size_t ofs_end = (byte*)md3_surf - (byte*)buffer; + md3_model->ofsEnd = LittleLong(ofs_end); +} + +bool dmlab_save_model( // + const DeepmindModelGetters* model, // + void* model_data, // + const char* model_path) { + if (!FS_Initialized()) { + fputs("File system not initialized, cannot save models.\n", stderr); + return false; + } + size_t len = dmlab_serialised_model_size(model, model_data); + byte* buffer = Hunk_AllocateTempMemory(len); + if (buffer == NULL) { + fprintf(stderr, + "Unable to allocate intermediate storage to serialize model: %s\n", + model_path); + return false; + } + dmlab_serialise_model(model, model_data, buffer); + FS_WriteFile(model_path, buffer, len); + Hunk_FreeTempMemory(buffer); + return true; +} diff --git a/engine/code/deepmind/dmlab_save_model.h b/engine/code/deepmind/dmlab_save_model.h new file mode 100644 index 00000000..8146743e --- /dev/null +++ b/engine/code/deepmind/dmlab_save_model.h @@ -0,0 +1,48 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef DML_ENGINE_CODE_DEEPMIND_DMLAB_SAVE_MODEL_H_ +#define DML_ENGINE_CODE_DEEPMIND_DMLAB_SAVE_MODEL_H_ + +#include +#include + +#include "../../../deepmind/include/deepmind_model_getters.h" + +// Returns the buffer size required to serialise the model described by +// 'model_getters' in MD3 format. +size_t dmlab_serialised_model_size( // + const DeepmindModelGetters* model_getters, // + void* model_data); + +// Attempts to serialise the model described by 'model_getters' in MD3 format +// onto 'buffer'. +void dmlab_serialise_model( // + const DeepmindModelGetters* model_getters, // + void* model_data, // + void* buffer); + +// Attempts to save the model described by 'model_getters' as a MD3 file onto +// the given path. +// Returns whether the save operation succeeded. +bool dmlab_save_model( // + const DeepmindModelGetters* model_getters, // + void* model_data, // + const char* model_path); + +#endif // DML_ENGINE_CODE_DEEPMIND_DMLAB_SAVE_MODEL_H_ diff --git a/engine/code/deepmind/glimp_common.c b/engine/code/deepmind/glimp_common.c index febd3386..5d97bb24 100644 --- a/engine/code/deepmind/glimp_common.c +++ b/engine/code/deepmind/glimp_common.c @@ -1,4 +1,4 @@ -// Copyright (C) 2016 Google Inc. +// Copyright (C) 2016-2017 Google Inc. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -17,6 +17,8 @@ //////////////////////////////////////////////////////////////////////////////// #include +#include + #include "../renderercommon/tr_common.h" #include "../sys/sys_local.h" @@ -34,6 +36,14 @@ void (*qglClientActiveTextureARB)(GLenum texture); void (*qglLockArraysEXT)(int, int); void (*qglUnlockArraysEXT)(void); +#define GLE(ret, name, ...) name##proc *qgl##name; +QGL_1_1_PROCS; +QGL_DESKTOP_1_1_PROCS; +QGL_3_0_PROCS; +#undef GLE + +int qglMajorVersion, qglMinorVersion; + void GLimp_EndFrame(void) {} void GLimp_CommonPreInit(void) { @@ -53,10 +63,55 @@ void GLimp_CommonPreInit(void) { } void GLimp_CommonPostInit(void) { +#define GLE(ret, name, ...) \ + qgl##name = (name##proc *)GLimp_GetProcAddress("gl" #name); + GLimp_MakeCurrent(); + + // OpenGL 1.0 + GLE(const GLubyte *, GetString, GLenum name) + + const char *version = (const char *)qglGetString(GL_VERSION); + + if (!version) { + Com_Error(ERR_FATAL, "Failed to get GL_VERSION string.\n"); + } else if (sscanf(version, "%d.%d", &qglMajorVersion, &qglMinorVersion) < + 2) { + Com_Error(ERR_FATAL, "Failed to read GL Version: %s\n", version); + } + + if (QGL_VERSION_ATLEAST(1, 1)) { + QGL_1_1_PROCS; + QGL_DESKTOP_1_1_PROCS; + } else { + Com_Error(ERR_FATAL, "Unsupported OpenGL Version: %s\n", version); + } + + if (QGL_VERSION_ATLEAST(3, 0)) { + QGL_3_0_PROCS; + } + glConfig.colorBits = r_colorbits->value; - glGetIntegerv(GL_DEPTH_BITS, &glConfig.depthBits); - glGetIntegerv(GL_STENCIL_BITS, &glConfig.stencilBits); + qglGetIntegerv(GL_DEPTH_BITS, &glConfig.depthBits); + qglGetIntegerv(GL_STENCIL_BITS, &glConfig.stencilBits); + + Q_strncpyz( + glConfig.vendor_string, (const char *)qglGetString(GL_VENDOR), + sizeof(glConfig.vendor_string)); + Q_strncpyz( + glConfig.version_string, version, sizeof(glConfig.version_string)); + Q_strncpyz( + glConfig.renderer_string, (const char *)qglGetString(GL_RENDERER), + sizeof(glConfig.renderer_string)); + + if (glConfig.renderer_string[0] != '\0') { + size_t n = strlen(glConfig.renderer_string); + if (glConfig.renderer_string[n - 1] == '\n') { + glConfig.renderer_string[n - 1] = 0; + } + } + +#undef GLE } void GLimp_Minimize(void) {} diff --git a/engine/code/deepmind/headless_egl_glimp.c b/engine/code/deepmind/headless_egl_glimp.c new file mode 100644 index 00000000..dc6db2ea --- /dev/null +++ b/engine/code/deepmind/headless_egl_glimp.c @@ -0,0 +1,102 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include "../renderercommon/tr_common.h" +// Must include tr_common.h before opengl. +#include + +#include "../sys/sys_local.h" +#include "glimp_common.h" +#include "third_party/GL/util/egl_util.h" + +#define CHECK_EGL_SUCCESS(egl_expr) \ + do { \ + (egl_expr); \ + EGLint egl_error = eglGetError(); \ + if (egl_error != EGL_SUCCESS) { \ + Sys_Error("EGL ERROR: 0x%x file:%s, line:%d\n", egl_error, __FILE__, \ + __LINE__); \ + } \ + } while (0) + +static const EGLint kConfigAttribs[] = { + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 8, + EGL_STENCIL_SIZE, 8, + EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, + EGL_NONE +}; + +static EGLDisplay egl_display; +static EGLSurface egl_surface; +static EGLContext egl_context; + +void GLimp_MakeCurrent(void) { + CHECK_EGL_SUCCESS(eglMakeCurrent(egl_display, egl_surface, egl_surface, + egl_context)); +} + +void GLimp_Init(qboolean coreContext) { + GLimp_CommonPreInit(); + + cvar_t* r_gpu_device_index = + ri.Cvar_Get("r_gpuDeviceIndex", "0", CVAR_ARCHIVE | CVAR_LATCH); + + egl_display = CreateInitializedEGLDisplayAtIndex(r_gpu_device_index->integer); + if (egl_display == EGL_NO_DISPLAY) { + Sys_Error("Failed to create EGL display for device index %d!\n", + r_gpu_device_index->integer); + return; + } + + EGLint num_configs; + EGLConfig egl_config; + CHECK_EGL_SUCCESS(eglChooseConfig(egl_display, kConfigAttribs, &egl_config, 1, + &num_configs)); + + EGLint pbuffer_attribs[] = { + EGL_WIDTH, glConfig.vidWidth, // + EGL_HEIGHT, glConfig.vidHeight, // + EGL_NONE, + }; + + CHECK_EGL_SUCCESS(egl_surface = eglCreatePbufferSurface( + egl_display, egl_config, pbuffer_attribs)); + + CHECK_EGL_SUCCESS(eglBindAPI(EGL_OPENGL_API)); + + CHECK_EGL_SUCCESS(egl_context = eglCreateContext(egl_display, egl_config, + EGL_NO_CONTEXT, NULL)); + + GLimp_CommonPostInit(); +} + +void* GLimp_GetProcAddress(const char* func) { return eglGetProcAddress(func); } + +void GLimp_Shutdown(void) { + CHECK_EGL_SUCCESS(eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT)); + CHECK_EGL_SUCCESS(eglDestroySurface(egl_display, egl_surface)); + CHECK_EGL_SUCCESS(eglDestroyContext(egl_display, egl_context)); + CHECK_EGL_SUCCESS(TerminateInitializedEGLDisplay(egl_display)); +} diff --git a/engine/code/deepmind/headless_native_glimp.c b/engine/code/deepmind/headless_native_glimp.c index 79d9095c..33d2efae 100644 --- a/engine/code/deepmind/headless_native_glimp.c +++ b/engine/code/deepmind/headless_native_glimp.c @@ -20,7 +20,6 @@ #include "../renderercommon/tr_common.h" // Must include tr_common.h before opengl. #include -#include #include #include "../sys/sys_local.h" #include "glimp_common.h" @@ -34,11 +33,11 @@ static Display* glx_display; void GLimp_MakeCurrent(void) { if (!glXMakeCurrent(glx_display, glx_pbuffer, glx_context)) { - Sys_Error( "GLimp_MakeCurrent - Failed!"); + Sys_Error("GLimp_MakeCurrent - Failed!"); } } -void GLimp_Init(void) { +void GLimp_Init(qboolean coreContext) { static const int visual_attribs[] = { GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_ALPHA_SIZE, 8, GLX_DOUBLEBUFFER, True, None}; @@ -70,6 +69,10 @@ void GLimp_Init(void) { glXChooseFBConfig(glx_display, DefaultScreen(glx_display), visual_attribs, &number_of_fb_config); + if (fb_configs == NULL || number_of_fb_config == 0) { + Sys_Error("glXChooseFBConfig failed!"); + } + glx_context = glXCreateContextAttribsARB(glx_display, fb_configs[0], 0, True, context_attribs); @@ -91,6 +94,8 @@ void GLimp_Init(void) { GLimp_CommonPostInit(); } +void* GLimp_GetProcAddress(const char* func) { return glXGetProcAddress(func); } + void GLimp_Shutdown(void) { glXMakeCurrent(glx_display, 0, NULL); glXDestroyPbuffer(glx_display, glx_pbuffer); diff --git a/engine/code/deepmind/headless_osmesa_glimp.c b/engine/code/deepmind/headless_osmesa_glimp.c index 030dc904..69025a8b 100644 --- a/engine/code/deepmind/headless_osmesa_glimp.c +++ b/engine/code/deepmind/headless_osmesa_glimp.c @@ -24,16 +24,16 @@ #include "glimp_common.h" static OSMesaContext osmesa_ctx; -static GLubyte *osmesa_frame_buffer; +static GLubyte* osmesa_frame_buffer; -void GLimp_MakeCurrent( void ) { +void GLimp_MakeCurrent(void) { if (!OSMesaMakeCurrent(osmesa_ctx, osmesa_frame_buffer, GL_UNSIGNED_BYTE, glConfig.vidWidth, glConfig.vidHeight)) { Sys_Error("GLimp_MakeCurrent - Failed!"); } } -void GLimp_Init(void) { +void GLimp_Init(qboolean coreContext) { r_colorbits->value = 16; GLimp_CommonPreInit(); /* Create an RGBA-mode context */ @@ -45,14 +45,22 @@ void GLimp_Init(void) { /* Allocate the image buffer */ osmesa_frame_buffer = - malloc(glConfig.vidWidth * glConfig.vidHeight * 4 * sizeof(GLubyte)); + calloc(glConfig.vidWidth * glConfig.vidHeight * 4, sizeof(GLubyte)); if (!osmesa_frame_buffer) { Sys_Error("Alloc image buffer failed!"); } + // Force draw buffer to GL_FRONT, as OSMesa doesn't have a back buffer to + // render to. + ri.Cvar_Set("r_drawBuffer", "GL_FRONT"); + GLimp_CommonPostInit(); } +void* GLimp_GetProcAddress(const char* func) { + return OSMesaGetProcAddress(func); +} + void GLimp_Shutdown(void) { OSMesaDestroyContext(osmesa_ctx); free(osmesa_frame_buffer); diff --git a/engine/code/game/ai_chat.c b/engine/code/game/ai_chat.c index 57166482..a65875ff 100644 --- a/engine/code/game/ai_chat.c +++ b/engine/code/game/ai_chat.c @@ -68,13 +68,9 @@ BotNumActivePlayers int BotNumActivePlayers(void) { int i, num; char buf[MAX_INFO_STRING]; - static int maxclients; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); num = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; @@ -94,22 +90,17 @@ BotIsFirstInRankings int BotIsFirstInRankings(bot_state_t *bs) { int i, score; char buf[MAX_INFO_STRING]; - static int maxclients; playerState_t ps; - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - score = bs->cur_ps.persistant[PERS_SCORE]; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; // - BotAI_GetClientState(i, &ps); - if (score < ps.persistant[PERS_SCORE]) return qfalse; + if (BotAI_GetClientState(i, &ps) && score < ps.persistant[PERS_SCORE]) return qfalse; } return qtrue; } @@ -122,22 +113,17 @@ BotIsLastInRankings int BotIsLastInRankings(bot_state_t *bs) { int i, score; char buf[MAX_INFO_STRING]; - static int maxclients; playerState_t ps; - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - score = bs->cur_ps.persistant[PERS_SCORE]; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; // - BotAI_GetClientState(i, &ps); - if (score > ps.persistant[PERS_SCORE]) return qfalse; + if (BotAI_GetClientState(i, &ps) && score > ps.persistant[PERS_SCORE]) return qfalse; } return qtrue; } @@ -151,23 +137,18 @@ char *BotFirstClientInRankings(void) { int i, bestscore, bestclient; char buf[MAX_INFO_STRING]; static char name[32]; - static int maxclients; playerState_t ps; - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - bestscore = -999999; bestclient = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; // - BotAI_GetClientState(i, &ps); - if (ps.persistant[PERS_SCORE] > bestscore) { + if (BotAI_GetClientState(i, &ps) && ps.persistant[PERS_SCORE] > bestscore) { bestscore = ps.persistant[PERS_SCORE]; bestclient = i; } @@ -185,23 +166,18 @@ char *BotLastClientInRankings(void) { int i, worstscore, bestclient; char buf[MAX_INFO_STRING]; static char name[32]; - static int maxclients; playerState_t ps; - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - worstscore = 999999; bestclient = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; // - BotAI_GetClientState(i, &ps); - if (ps.persistant[PERS_SCORE] < worstscore) { + if (BotAI_GetClientState(i, &ps) && ps.persistant[PERS_SCORE] < worstscore) { worstscore = ps.persistant[PERS_SCORE]; bestclient = i; } @@ -219,15 +195,11 @@ char *BotRandomOpponentName(bot_state_t *bs) { int i, count; char buf[MAX_INFO_STRING]; int opponents[MAX_CLIENTS], numopponents; - static int maxclients; static char name[32]; - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - numopponents = 0; opponents[0] = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { if (i == bs->client) continue; // trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); diff --git a/engine/code/game/ai_cmd.c b/engine/code/game/ai_cmd.c index db954858..2944fb06 100644 --- a/engine/code/game/ai_cmd.c +++ b/engine/code/game/ai_cmd.c @@ -237,15 +237,12 @@ FindClientByName int FindClientByName(char *name) { int i; char buf[MAX_INFO_STRING]; - static int maxclients; - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { ClientName(i, buf, sizeof(buf)); if (!Q_stricmp(buf, name)) return i; } - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { ClientName(i, buf, sizeof(buf)); if (stristr(buf, name)) return i; } @@ -260,16 +257,13 @@ FindEnemyByName int FindEnemyByName(bot_state_t *bs, char *name) { int i; char buf[MAX_INFO_STRING]; - static int maxclients; - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { if (BotSameTeam(bs, i)) continue; ClientName(i, buf, sizeof(buf)); if (!Q_stricmp(buf, name)) return i; } - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { if (BotSameTeam(bs, i)) continue; ClientName(i, buf, sizeof(buf)); if (stristr(buf, name)) return i; @@ -285,13 +279,9 @@ NumPlayersOnSameTeam int NumPlayersOnSameTeam(bot_state_t *bs) { int i, num; char buf[MAX_INFO_STRING]; - static int maxclients; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); num = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { trap_GetConfigstring(CS_PLAYERS+i, buf, MAX_INFO_STRING); if (strlen(buf)) { if (BotSameTeam(bs, i+1)) num++; diff --git a/engine/code/game/ai_dmq3.c b/engine/code/game/ai_dmq3.c index 75749229..3e330733 100644 --- a/engine/code/game/ai_dmq3.c +++ b/engine/code/game/ai_dmq3.c @@ -72,7 +72,6 @@ bot_waypoint_t *botai_freewaypoints; //NOTE: not using a cvars which can be updated because the game should be reloaded anyway int gametype; //game type -int maxclients; //maximum number of clients vmCvar_t bot_grapple; vmCvar_t bot_rocketjump; @@ -207,7 +206,10 @@ qboolean EntityIsDead(aas_entityinfo_t *entinfo) { if (entinfo->number >= 0 && entinfo->number < MAX_CLIENTS) { //retrieve the current client state - BotAI_GetClientState( entinfo->number, &ps ); + if (!BotAI_GetClientState(entinfo->number, &ps)) { + return qfalse; + } + if (ps.pm_type != PM_NORMAL) return qtrue; } return qfalse; @@ -1427,11 +1429,8 @@ ClientFromName int ClientFromName(char *name) { int i; char buf[MAX_INFO_STRING]; - static int maxclients; - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); Q_CleanStr( buf ); if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i; @@ -1447,11 +1446,8 @@ ClientOnSameTeamFromName int ClientOnSameTeamFromName(bot_state_t *bs, char *name) { int i; char buf[MAX_INFO_STRING]; - static int maxclients; - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { if (!BotSameTeam(bs, i)) continue; trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); @@ -1740,18 +1736,18 @@ void BotUpdateInventory(bot_state_t *bs) { bs->inventory[INVENTORY_CHAINGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_CHAINGUN)) != 0;; #endif //ammo - bs->inventory[INVENTORY_SHELLS] = bs->cur_ps.ammo[WP_SHOTGUN]; - bs->inventory[INVENTORY_BULLETS] = bs->cur_ps.ammo[WP_MACHINEGUN]; - bs->inventory[INVENTORY_GRENADES] = bs->cur_ps.ammo[WP_GRENADE_LAUNCHER]; - bs->inventory[INVENTORY_CELLS] = bs->cur_ps.ammo[WP_PLASMAGUN]; - bs->inventory[INVENTORY_LIGHTNINGAMMO] = bs->cur_ps.ammo[WP_LIGHTNING]; - bs->inventory[INVENTORY_ROCKETS] = bs->cur_ps.ammo[WP_ROCKET_LAUNCHER]; - bs->inventory[INVENTORY_SLUGS] = bs->cur_ps.ammo[WP_RAILGUN]; - bs->inventory[INVENTORY_BFGAMMO] = bs->cur_ps.ammo[WP_BFG]; + bs->inventory[INVENTORY_SHELLS] = bs->cur_ps.ammo[WP_SHOTGUN] < 0 ? 999 : bs->cur_ps.ammo[WP_SHOTGUN]; + bs->inventory[INVENTORY_BULLETS] = bs->cur_ps.ammo[WP_MACHINEGUN] < 0 ? 999 : bs->cur_ps.ammo[WP_MACHINEGUN]; + bs->inventory[INVENTORY_GRENADES] = bs->cur_ps.ammo[WP_GRENADE_LAUNCHER] < 0 ? 999 : bs->cur_ps.ammo[WP_GRENADE_LAUNCHER]; + bs->inventory[INVENTORY_CELLS] = bs->cur_ps.ammo[WP_PLASMAGUN] < 0 ? 999 : bs->cur_ps.ammo[WP_PLASMAGUN]; + bs->inventory[INVENTORY_LIGHTNINGAMMO] = bs->cur_ps.ammo[WP_LIGHTNING] < 0 ? 999 : bs->cur_ps.ammo[WP_LIGHTNING]; + bs->inventory[INVENTORY_ROCKETS] = bs->cur_ps.ammo[WP_ROCKET_LAUNCHER] < 0 ? 999 : bs->cur_ps.ammo[WP_ROCKET_LAUNCHER]; + bs->inventory[INVENTORY_SLUGS] = bs->cur_ps.ammo[WP_RAILGUN] < 0 ? 999 : bs->cur_ps.ammo[WP_RAILGUN]; + bs->inventory[INVENTORY_BFGAMMO] = bs->cur_ps.ammo[WP_BFG] < 0 ? 999 : bs->cur_ps.ammo[WP_BFG]; #ifdef MISSIONPACK - bs->inventory[INVENTORY_NAILS] = bs->cur_ps.ammo[WP_NAILGUN]; - bs->inventory[INVENTORY_MINES] = bs->cur_ps.ammo[WP_PROX_LAUNCHER]; - bs->inventory[INVENTORY_BELT] = bs->cur_ps.ammo[WP_CHAINGUN]; + bs->inventory[INVENTORY_NAILS] = bs->cur_ps.ammo[WP_NAILGUN] < 0 ? 999 : bs->cur_ps.ammo[WP_NAILGUN]; + bs->inventory[INVENTORY_MINES] = bs->cur_ps.ammo[WP_PROX_LAUNCHER] < 0 ? 999 : bs->cur_ps.ammo[WP_PROX_LAUNCHER]; + bs->inventory[INVENTORY_BELT] = bs->cur_ps.ammo[WP_CHAINGUN] < 0 ? 999 : bs->cur_ps.ammo[WP_CHAINGUN]; #endif //powerups bs->inventory[INVENTORY_HEALTH] = bs->cur_ps.stats[STAT_HEALTH]; @@ -2834,8 +2830,12 @@ float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int aas_entityinfo_t entinfo; vec3_t dir, entangles, start, end, middle; - //calculate middle of bounding box BotEntityInfo(ent, &entinfo); + if (!entinfo.valid) { + return 0; + } + + //calculate middle of bounding box VectorAdd(entinfo.mins, entinfo.maxs, middle); VectorScale(middle, 0.5, middle); VectorAdd(entinfo.origin, middle, middle); @@ -2983,11 +2983,15 @@ int BotFindEnemy(bot_state_t *bs, int curenemy) { } #endif // - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { if (i == bs->client) continue; //if it's the current enemy if (i == curenemy) continue; + //if the enemy has targeting disabled + if (g_entities[i].flags & FL_NOTARGET) { + continue; + } // BotEntityInfo(i, &entinfo); // @@ -3062,7 +3066,7 @@ int BotTeamFlagCarrierVisible(bot_state_t *bs) { float vis; aas_entityinfo_t entinfo; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { if (i == bs->client) continue; // @@ -3095,7 +3099,7 @@ int BotTeamFlagCarrier(bot_state_t *bs) { int i; aas_entityinfo_t entinfo; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { if (i == bs->client) continue; // @@ -3125,7 +3129,7 @@ int BotEnemyFlagCarrierVisible(bot_state_t *bs) { float vis; aas_entityinfo_t entinfo; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { if (i == bs->client) continue; // @@ -3164,7 +3168,7 @@ void BotVisibleTeamMatesAndEnemies(bot_state_t *bs, int *teammates, int *enemies *teammates = 0; if (enemies) *enemies = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { if (i == bs->client) continue; // @@ -3206,7 +3210,7 @@ int BotTeamCubeCarrierVisible(bot_state_t *bs) { float vis; aas_entityinfo_t entinfo; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { if (i == bs->client) continue; // BotEntityInfo(i, &entinfo); @@ -3235,7 +3239,7 @@ int BotEnemyCubeCarrierVisible(bot_state_t *bs) { float vis; aas_entityinfo_t entinfo; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { if (i == bs->client) continue; // @@ -3691,22 +3695,27 @@ void BotMapScripts(bot_state_t *bs) { strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1); mapname[sizeof(mapname)-1] = '\0'; - if (!Q_stricmp(mapname, "q3tourney6")) { - vec3_t mins = {700, 204, 672}, maxs = {964, 468, 680}; + if (!Q_stricmp(mapname, "q3tourney6") || !Q_stricmp(mapname, "q3tourney6_ctf") || !Q_stricmp(mapname, "mpq3tourney6")) { + vec3_t mins = {694, 200, 480}, maxs = {968, 472, 680}; vec3_t buttonorg = {304, 352, 920}; //NOTE: NEVER use the func_bobbing in q3tourney6 bs->tfl &= ~TFL_FUNCBOB; - //if the bot is below the bounding box + //crush area is higher in mpq3tourney6 + if (!Q_stricmp(mapname, "mpq3tourney6")) { + mins[2] += 64; + maxs[2] += 64; + } + //if the bot is in the bounding box of the crush area if (bs->origin[0] > mins[0] && bs->origin[0] < maxs[0]) { if (bs->origin[1] > mins[1] && bs->origin[1] < maxs[1]) { - if (bs->origin[2] < mins[2]) { + if (bs->origin[2] > mins[2] && bs->origin[2] < maxs[2]) { return; } } } shootbutton = qfalse; - //if an enemy is below this bounding box then shoot the button - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + //if an enemy is in the bounding box then shoot the button + for (i = 0; i < level.maxclients; i++) { if (i == bs->client) continue; // @@ -3718,13 +3727,13 @@ void BotMapScripts(bot_state_t *bs) { // if (entinfo.origin[0] > mins[0] && entinfo.origin[0] < maxs[0]) { if (entinfo.origin[1] > mins[1] && entinfo.origin[1] < maxs[1]) { - if (entinfo.origin[2] < mins[2]) { + if (entinfo.origin[2] > mins[2] && entinfo.origin[2] < maxs[2]) { //if there's a team mate below the crusher if (BotSameTeam(bs, i)) { shootbutton = qfalse; break; } - else { + else if (bs->enemy == i) { shootbutton = qtrue; } } @@ -3746,10 +3755,6 @@ void BotMapScripts(bot_state_t *bs) { } } } - else if (!Q_stricmp(mapname, "mpq3tourney6")) { - //NOTE: NEVER use the func_bobbing in mpq3tourney6 - bs->tfl &= ~TFL_FUNCBOB; - } } /* @@ -3834,7 +3839,6 @@ int BotFuncButtonActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *a modelindex = atoi(model+1); if (!modelindex) return qfalse; - VectorClear(angles); entitynum = BotModelMinsMaxs(modelindex, ET_MOVER, 0, mins, maxs); //get the lip of the button trap_AAS_FloatForBSPEpairKey(bspent, "lip", &lip); @@ -3972,7 +3976,7 @@ BotFuncDoorGoal int BotFuncDoorActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { int modelindex, entitynum; char model[MAX_INFO_STRING]; - vec3_t mins, maxs, origin, angles; + vec3_t mins, maxs, origin; //shoot at the shootable door trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); @@ -3981,7 +3985,6 @@ int BotFuncDoorActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *act modelindex = atoi(model+1); if (!modelindex) return qfalse; - VectorClear(angles); entitynum = BotModelMinsMaxs(modelindex, ET_MOVER, 0, mins, maxs); //door origin VectorAdd(mins, maxs, origin); @@ -4007,7 +4010,7 @@ BotTriggerMultipleGoal int BotTriggerMultipleActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { int i, areas[10], numareas, modelindex, entitynum; char model[128]; - vec3_t start, end, mins, maxs, angles; + vec3_t start, end, mins, maxs; vec3_t origin, goalorigin; activategoal->shoot = qfalse; @@ -4019,7 +4022,6 @@ int BotTriggerMultipleActivateGoal(bot_state_t *bs, int bspent, bot_activategoal modelindex = atoi(model+1); if (!modelindex) return qfalse; - VectorClear(angles); entitynum = BotModelMinsMaxs(modelindex, 0, CONTENTS_TRIGGER, mins, maxs); //trigger origin VectorAdd(mins, maxs, origin); @@ -4167,7 +4169,7 @@ int BotGetActivateGoal(bot_state_t *bs, int entitynum, bot_activategoal_t *activ char targetname[10][128]; aas_entityinfo_t entinfo; aas_areainfo_t areainfo; - vec3_t origin, angles, absmins, absmaxs; + vec3_t origin, absmins, absmaxs; memset(activategoal, 0, sizeof(bot_activategoal_t)); BotEntityInfo(entitynum, &entinfo); @@ -4211,7 +4213,6 @@ int BotGetActivateGoal(bot_state_t *bs, int entitynum, bot_activategoal_t *activ if (*model) { modelindex = atoi(model+1); if (modelindex) { - VectorClear(angles); BotModelMinsMaxs(modelindex, ET_MOVER, 0, absmins, absmaxs); // numareas = trap_AAS_BBoxAreas(absmins, absmaxs, areas, MAX_ACTIVATEAREAS*2); @@ -4560,7 +4561,7 @@ int BotAIPredictObstacles(bot_state_t *bs, bot_goal_t *goal) { bs->predictobstacles_goalareanum = goal->areanum; bs->predictobstacles_time = FloatTime(); - // predict at most 100 areas or 10 seconds ahead + // predict at most 100 areas or 1 second ahead trap_AAS_PredictRoute(&route, bs->areanum, bs->origin, goal->areanum, bs->tfl, 100, 1000, RSE_USETRAVELTYPE|RSE_ENTERCONTENTS, @@ -5218,7 +5219,7 @@ BotDeathmatchAI ================== */ void BotDeathmatchAI(bot_state_t *bs, float thinktime) { - char gender[144], name[144], buf[144]; + char gender[144], name[144]; char userinfo[MAX_INFO_STRING]; int i; @@ -5232,11 +5233,6 @@ void BotDeathmatchAI(bot_state_t *bs, float thinktime) { trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo)); Info_SetValueForKey(userinfo, "sex", gender); trap_SetUserinfo(bs->client, userinfo); - //set the team - if ( !bs->map_restart && g_gametype.integer != GT_TOURNAMENT ) { - Com_sprintf(buf, sizeof(buf), "team %s", bs->settings.team); - trap_EA_Command(bs->client, buf); - } //set the chat gender if (gender[0] == 'm') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE); else if (gender[0] == 'f') trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE); @@ -5403,7 +5399,6 @@ void BotSetupDeathmatchAI(void) { char model[128]; gametype = trap_Cvar_VariableIntegerValue("g_gametype"); - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); trap_Cvar_Register(&bot_rocketjump, "bot_rocketjump", "1", 0); trap_Cvar_Register(&bot_grapple, "bot_grapple", "0", 0); diff --git a/engine/code/game/ai_main.c b/engine/code/game/ai_main.c index c6958318..4b350d0f 100644 --- a/engine/code/game/ai_main.c +++ b/engine/code/game/ai_main.c @@ -55,10 +55,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "inv.h" #include "syn.h" -#ifndef MAX_PATH -#define MAX_PATH 144 -#endif - //bot states bot_state_t *botstates[MAX_CLIENTS]; @@ -392,7 +388,7 @@ void BotTeamplayReport(void) { char buf[MAX_INFO_STRING]; BotAI_Print(PRT_MESSAGE, S_COLOR_RED"RED\n"); - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { // if ( !botstates[i] || !botstates[i]->inuse ) continue; // @@ -405,7 +401,7 @@ void BotTeamplayReport(void) { } } BotAI_Print(PRT_MESSAGE, S_COLOR_BLUE"BLUE\n"); - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { // if ( !botstates[i] || !botstates[i]->inuse ) continue; // @@ -546,7 +542,7 @@ void BotUpdateInfoConfigStrings(void) { int i; char buf[MAX_INFO_STRING]; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { // if ( !botstates[i] || !botstates[i]->inuse ) continue; @@ -990,8 +986,10 @@ int BotAI(int client, float thinktime) { } //retrieve the current client state - BotAI_GetClientState( client, &bs->cur_ps ); - + if (!BotAI_GetClientState(client, &bs->cur_ps)) { + BotAI_Print(PRT_FATAL, "BotAI: failed to get player state for player %d\n", client); + return qfalse; + } //retrieve any waiting server commands while( trap_BotGetServerCommand(client, buf, sizeof(buf)) ) { //have buf point to the command and args to the command arguments @@ -1172,7 +1170,7 @@ BotAISetupClient ============== */ int BotAISetupClient(int client, struct bot_settings_s *settings, qboolean restart) { - char filename[MAX_PATH], name[MAX_PATH], gender[MAX_PATH]; + char filename[144], name[144], gender[144]; bot_state_t *bs; int errnum; @@ -1204,7 +1202,7 @@ int BotAISetupClient(int client, struct bot_settings_s *settings, qboolean resta //allocate a goal state bs->gs = trap_BotAllocGoalState(client); //load the item weights - trap_Characteristic_String(bs->character, CHARACTERISTIC_ITEMWEIGHTS, filename, MAX_PATH); + trap_Characteristic_String(bs->character, CHARACTERISTIC_ITEMWEIGHTS, filename, sizeof(filename)); errnum = trap_BotLoadItemWeights(bs->gs, filename); if (errnum != BLERR_NOERROR) { trap_BotFreeGoalState(bs->gs); @@ -1213,7 +1211,7 @@ int BotAISetupClient(int client, struct bot_settings_s *settings, qboolean resta //allocate a weapon state bs->ws = trap_BotAllocWeaponState(); //load the weapon weights - trap_Characteristic_String(bs->character, CHARACTERISTIC_WEAPONWEIGHTS, filename, MAX_PATH); + trap_Characteristic_String(bs->character, CHARACTERISTIC_WEAPONWEIGHTS, filename, sizeof(filename)); errnum = trap_BotLoadWeaponWeights(bs->ws, filename); if (errnum != BLERR_NOERROR) { trap_BotFreeGoalState(bs->gs); @@ -1223,8 +1221,8 @@ int BotAISetupClient(int client, struct bot_settings_s *settings, qboolean resta //allocate a chat state bs->cs = trap_BotAllocChatState(); //load the chat file - trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_FILE, filename, MAX_PATH); - trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_NAME, name, MAX_PATH); + trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_FILE, filename, sizeof(filename)); + trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_NAME, name, sizeof(name)); errnum = trap_BotLoadChatFile(bs->cs, filename, name); if (errnum != BLERR_NOERROR) { trap_BotFreeChatState(bs->cs); @@ -1233,7 +1231,7 @@ int BotAISetupClient(int client, struct bot_settings_s *settings, qboolean resta return qfalse; } //get the gender characteristic - trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, MAX_PATH); + trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, sizeof(gender)); //set the chat gender if (*gender == 'f' || *gender == 'F') trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE); else if (*gender == 'm' || *gender == 'M') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE); @@ -1597,8 +1595,7 @@ int BotInitLibrary(void) { char buf[144]; //set the maxclients and maxentities library variables before calling BotSetupLibrary - trap_Cvar_VariableStringBuffer("sv_maxclients", buf, sizeof(buf)); - if (!strlen(buf)) strcpy(buf, "8"); + Com_sprintf(buf, sizeof(buf), "%d", level.maxclients); trap_BotLibVarSet("maxclients", buf); Com_sprintf(buf, sizeof(buf), "%d", MAX_GENTITIES); trap_BotLibVarSet("maxentities", buf); diff --git a/engine/code/game/ai_team.c b/engine/code/game/ai_team.c index 925a94d6..95b445cd 100644 --- a/engine/code/game/ai_team.c +++ b/engine/code/game/ai_team.c @@ -83,13 +83,9 @@ BotNumTeamMates int BotNumTeamMates(bot_state_t *bs) { int i, numplayers; char buf[MAX_INFO_STRING]; - static int maxclients; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); numplayers = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; @@ -112,8 +108,12 @@ int BotClientTravelTimeToGoal(int client, bot_goal_t *goal) { playerState_t ps; int areanum; - BotAI_GetClientState(client, &ps); - areanum = BotPointAreaNum(ps.origin); + if (BotAI_GetClientState(client, &ps)) { + areanum = BotPointAreaNum(ps.origin); + } else { + areanum = 0; + } + if (!areanum) return 1; return trap_AAS_AreaTravelTimeToGoalArea(areanum, ps.origin, goal->areanum, TFL_DEFAULT); } @@ -127,7 +127,6 @@ int BotSortTeamMatesByBaseTravelTime(bot_state_t *bs, int *teammates, int maxtea int i, j, k, numteammates, traveltime; char buf[MAX_INFO_STRING]; - static int maxclients; int traveltimes[MAX_CLIENTS]; bot_goal_t *goal = NULL; @@ -150,11 +149,8 @@ int BotSortTeamMatesByBaseTravelTime(bot_state_t *bs, int *teammates, int maxtea goal = &blueobelisk; } #endif - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - numteammates = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; @@ -885,13 +881,9 @@ void BotTeamOrders(bot_state_t *bs) { int teammates[MAX_CLIENTS]; int numteammates, i; char buf[MAX_INFO_STRING]; - static int maxclients; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); numteammates = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + for (i = 0; i < level.maxclients; i++) { trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; diff --git a/engine/code/game/bg_lib.c b/engine/code/game/bg_lib.c index ff023219..f893f442 100644 --- a/engine/code/game/bg_lib.c +++ b/engine/code/game/bg_lib.c @@ -53,10 +53,10 @@ static void swapfunc(char *, char *, int, int); */ #define swapcode(TYPE, parmi, parmj, n) { \ long i = (n) / sizeof (TYPE); \ - register TYPE *pi = (TYPE *) (parmi); \ - register TYPE *pj = (TYPE *) (parmj); \ + TYPE *pi = (TYPE *) (parmi); \ + TYPE *pj = (TYPE *) (parmj); \ do { \ - register TYPE t = *pi; \ + TYPE t = *pi; \ *pi++ = *pj; \ *pj++ = t; \ } while (--i > 0); \ diff --git a/engine/code/game/bg_public.h b/engine/code/game/bg_public.h index c25a4e6b..841961c9 100644 --- a/engine/code/game/bg_public.h +++ b/engine/code/game/bg_public.h @@ -623,6 +623,11 @@ typedef enum { IT_GOAL } itemType_t; +typedef enum { + REWARD_MV_BOB, // bob 4 units above ground + REWARD_MV_STATIC // stay still +} it_reward_mv_t; + #define MAX_ITEM_MODELS 4 typedef struct gitem_s { @@ -645,6 +650,7 @@ typedef struct gitem_s { // included in both the game dll and the client extern gitem_t bg_itemlist[]; extern int bg_numItems; +extern int bg_defaultNumItems; void BG_InitItemList( void ); void BG_UpdateItems( void ); diff --git a/engine/code/game/bg_slidemove.c b/engine/code/game/bg_slidemove.c index 0806b2fa..9228ada4 100644 --- a/engine/code/game/bg_slidemove.c +++ b/engine/code/game/bg_slidemove.c @@ -158,8 +158,10 @@ qboolean PM_SlideMove( qboolean gravity ) { // slide along the plane PM_ClipVelocity (pm->ps->velocity, planes[i], clipVelocity, OVERCLIP ); - // slide along the plane - PM_ClipVelocity (endVelocity, planes[i], endClipVelocity, OVERCLIP ); + if ( gravity ) { + // slide along the plane + PM_ClipVelocity (endVelocity, planes[i], endClipVelocity, OVERCLIP ); + } // see if there is a second plane that the new move enters for ( j = 0 ; j < numplanes ; j++ ) { @@ -172,7 +174,10 @@ qboolean PM_SlideMove( qboolean gravity ) { // try clipping the move to the plane PM_ClipVelocity( clipVelocity, planes[j], clipVelocity, OVERCLIP ); - PM_ClipVelocity( endClipVelocity, planes[j], endClipVelocity, OVERCLIP ); + + if ( gravity ) { + PM_ClipVelocity( endClipVelocity, planes[j], endClipVelocity, OVERCLIP ); + } // see if it goes back into the first clip plane if ( DotProduct( clipVelocity, planes[i] ) >= 0 ) { @@ -185,10 +190,12 @@ qboolean PM_SlideMove( qboolean gravity ) { d = DotProduct( dir, pm->ps->velocity ); VectorScale( dir, d, clipVelocity ); - CrossProduct (planes[i], planes[j], dir); - VectorNormalize( dir ); - d = DotProduct( dir, endVelocity ); - VectorScale( dir, d, endClipVelocity ); + if ( gravity ) { + CrossProduct (planes[i], planes[j], dir); + VectorNormalize( dir ); + d = DotProduct( dir, endVelocity ); + VectorScale( dir, d, endClipVelocity ); + } // see if there is a third plane the the new move enters for ( k = 0 ; k < numplanes ; k++ ) { @@ -207,7 +214,11 @@ qboolean PM_SlideMove( qboolean gravity ) { // if we have fixed all interactions, try another move VectorCopy( clipVelocity, pm->ps->velocity ); - VectorCopy( endClipVelocity, endVelocity ); + + if ( gravity ) { + VectorCopy( endClipVelocity, endVelocity ); + } + break; } } diff --git a/engine/code/game/g_active.c b/engine/code/game/g_active.c index 96427f0a..9d39f510 100644 --- a/engine/code/game/g_active.c +++ b/engine/code/game/g_active.c @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 1999-2005 Id Software, Inc., 2016 Google Inc. +Copyright (C) 1999-2005 Id Software, Inc., 2016-2017 Google Inc. This file is part of Quake III Arena source code. @@ -309,6 +309,70 @@ void G_TouchTriggers( gentity_t *ent ) { } } +/* +============ +ClientLookAtTriggers + +Find all lookat trigger entities that the entity is looking at. +We use the muzzle position & orientation for the ray. +============ +*/ +void ClientLookAtTriggers( gentity_t *ent ) { + trace_t trace; + vec3_t start, end; + vec3_t forward, right, up; + gentity_t* trigger; + gentity_t* last_trigger; + + // Double check that we're definitely a player. + if ( !ent->client ) { + return; + } + + // Ignore dead clients. + if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) { + // Clear the last lookat entity when dead. + if ( ent->client->lastLookAt ) { + last_trigger = &g_entities[ ent->client->lastLookAt ]; + if( last_trigger->look ) + last_trigger->look( last_trigger, ent, NULL ); + ent->client->lastLookAt = 0; + } + return; + } + + // Calculate the start and end of the ray. + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + CalcMuzzlePoint( ent, forward, right, up, start ); + VectorMA( start, 131072, forward, end ); + + trap_Trace( &trace, start, vec3_origin, vec3_origin, end, + ent->client->ps.clientNum, CONTENTS_SOLID|CONTENTS_LOOKAT ); + + // If the new trigger is not what the client was last looking at, invoke the + // last trigger with 'trace' set to NULL & reset. This allows entites to + // reset any internal state they may have. + if ( ent->client->lastLookAt && ent->client->lastLookAt != trace.entityNum ) { + last_trigger = &g_entities[ ent->client->lastLookAt ]; + if( last_trigger->look ) + last_trigger->look( last_trigger, ent, NULL ); + ent->client->lastLookAt = 0; + } + + trigger = &g_entities[ trace.entityNum ]; + + // Check ray hit a lookat trigger and not a wall. + if ( !trace.entityNum || (trigger->r.contents & CONTENTS_LOOKAT) == 0 ) + return; + + // Store the trigger for the next frame, and call the look function (if it + // exists). + ent->client->lastLookAt = trace.entityNum; + if (trigger->look) { + trigger->look(trigger, ent, &trace); + } +} + /* ================= SpectatorThink @@ -321,7 +385,12 @@ void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) { client = ent->client; if ( client->sess.spectatorState != SPECTATOR_FOLLOW ) { - client->ps.pm_type = PM_SPECTATOR; + if ( client->noclip ) { + client->ps.pm_type = PM_NOCLIP; + } else { + client->ps.pm_type = PM_SPECTATOR; + } + client->ps.speed = 400; // faster than normal // set up for pmove @@ -747,6 +816,7 @@ void ClientThink_real( gentity_t *ent ) { pmove_t pm; int oldEventSequence; int msec; + int health; usercmd_t *ucmd; client = ent->client; @@ -780,9 +850,11 @@ void ClientThink_real( gentity_t *ent ) { if ( pmove_msec.integer < 8 ) { trap_Cvar_Set("pmove_msec", "8"); + trap_Cvar_Update(&pmove_msec); } else if (pmove_msec.integer > 33) { trap_Cvar_Set("pmove_msec", "33"); + trap_Cvar_Update(&pmove_msec); } if ( pmove_fixed.integer || client->pers.pmoveFixed ) { @@ -964,6 +1036,9 @@ void ClientThink_real( gentity_t *ent ) { G_TouchTriggers( ent ); } + // Determine what lookat triggers are being looked at by this client. + ClientLookAtTriggers( ent ); + // NOTE: now copy the exact origin over otherwise clients can be snapped into solid VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); @@ -984,7 +1059,18 @@ void ClientThink_real( gentity_t *ent ) { client->latched_buttons |= client->buttons & ~client->oldbuttons; // Add External Score - AddScore( ent, NULL, 0 ); + AddScore( ent, NULL, 0, NULL, NULL ); + health = client->ps.stats[STAT_HEALTH]; + dmlab_update_inventory(&client->ps); + + if (health != client->ps.stats[STAT_HEALTH]) { + // Don't revive the dead. + if (health <= 0) { + client->ps.stats[STAT_HEALTH] = health; + } else { + ent->health = client->ps.stats[STAT_HEALTH]; + } + } // check for respawning if ( client->ps.stats[STAT_HEALTH] <= 0 ) { // wait for the attack button to be pressed diff --git a/engine/code/game/g_bot.c b/engine/code/game/g_bot.c index 6cc39178..b5731a67 100644 --- a/engine/code/game/g_bot.c +++ b/engine/code/game/g_bot.c @@ -230,71 +230,101 @@ static void PlayerIntroSound( const char *modelAndSkin ) { /* =============== -G_AddRandomBot +G_CountBotPlayersByName + +Check connected and connecting (delay join) bots. + +Returns number of bots with name on specified team or whole server if team is -1. =============== */ -void G_AddRandomBot( int team ) { - int i, n, num; - float skill; - char *value, netname[36], *teamstr; +int G_CountBotPlayersByName( const char *name, int team ) { + int i, num; gclient_t *cl; num = 0; - for ( n = 0; n < g_numBots ; n++ ) { - value = Info_ValueForKey( g_botInfos[n], "name" ); - // - for ( i=0 ; i< g_maxclients.integer ; i++ ) { - cl = level.clients + i; - if ( cl->pers.connected != CON_CONNECTED ) { - continue; - } - if ( !(g_entities[i].r.svFlags & SVF_BOT) ) { - continue; - } - if ( team >= 0 && cl->sess.sessionTeam != team ) { - continue; - } - if ( !Q_stricmp( value, cl->pers.netname ) ) { - break; - } + for ( i=0 ; i< g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected == CON_DISCONNECTED ) { + continue; + } + if ( !(g_entities[i].r.svFlags & SVF_BOT) ) { + continue; + } + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; } - if (i >= g_maxclients.integer) { - num++; + if ( name && Q_stricmp( name, cl->pers.netname ) ) { + continue; } + num++; } - num = random() * num; + return num; +} + +/* +=============== +G_SelectRandomBotInfo + +Get random least used bot info on team or whole server if team is -1. +=============== +*/ +int G_SelectRandomBotInfo( int team ) { + int selection[MAX_BOTS]; + int n, num; + int count, bestCount; + char *value; + + // don't add duplicate bots to the server if there are less bots than bot types + if ( team != -1 && G_CountBotPlayersByName( NULL, -1 ) < g_numBots ) { + team = -1; + } + + num = 0; + bestCount = MAX_CLIENTS; for ( n = 0; n < g_numBots ; n++ ) { - value = Info_ValueForKey( g_botInfos[n], "name" ); + value = Info_ValueForKey( g_botInfos[n], "funname" ); + if ( !value[0] ) { + value = Info_ValueForKey( g_botInfos[n], "name" ); + } // - for ( i=0 ; i< g_maxclients.integer ; i++ ) { - cl = level.clients + i; - if ( cl->pers.connected != CON_CONNECTED ) { - continue; - } - if ( !(g_entities[i].r.svFlags & SVF_BOT) ) { - continue; - } - if ( team >= 0 && cl->sess.sessionTeam != team ) { - continue; - } - if ( !Q_stricmp( value, cl->pers.netname ) ) { - break; - } + count = G_CountBotPlayersByName( value, team ); + + if ( count < bestCount ) { + bestCount = count; + num = 0; } - if (i >= g_maxclients.integer) { - num--; - if (num <= 0) { - skill = trap_Cvar_VariableValue( "g_spSkill" ); - if (team == TEAM_RED) teamstr = "red"; - else if (team == TEAM_BLUE) teamstr = "blue"; - else teamstr = ""; - Q_strncpyz(netname, value, sizeof(netname)); - Q_CleanStr(netname); - trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s %f %s %i\n", netname, skill, teamstr, 0) ); - return; + + if ( count == bestCount ) { + selection[num++] = n; + + if ( num == MAX_BOTS ) { + break; } } } + + if ( num > 0 ) { + num = random() * ( num - 1 ); + return selection[num]; + } + + return -1; +} + +/* +=============== +G_AddRandomBot +=============== +*/ +void G_AddRandomBot( int team ) { + char *teamstr; + float skill; + + skill = trap_Cvar_VariableValue( "g_spSkill" ); + if (team == TEAM_RED) teamstr = "red"; + else if (team == TEAM_BLUE) teamstr = "blue"; + else teamstr = "free"; + trap_SendConsoleCommand( EXEC_INSERT, va("addbot random %f %s %i\n", skill, teamstr, 0) ); } /* @@ -352,16 +382,18 @@ int G_CountHumanPlayers( int team ) { /* =============== G_CountBotPlayers + +Check connected and connecting (delay join) bots. =============== */ int G_CountBotPlayers( int team ) { - int i, n, num; + int i, num; gclient_t *cl; num = 0; for ( i=0 ; i< g_maxclients.integer ; i++ ) { cl = level.clients + i; - if ( cl->pers.connected != CON_CONNECTED ) { + if ( cl->pers.connected == CON_DISCONNECTED ) { continue; } if ( !(g_entities[i].r.svFlags & SVF_BOT) ) { @@ -372,15 +404,6 @@ int G_CountBotPlayers( int team ) { } num++; } - for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { - if( !botSpawnQueue[n].spawnTime ) { - continue; - } - if ( botSpawnQueue[n].spawnTime > level.time ) { - continue; - } - num++; - } return num; } @@ -542,7 +565,6 @@ qboolean G_BotConnect( int clientNum, qboolean restart ) { Q_strncpyz( settings.characterfile, Info_ValueForKey( userinfo, "characterfile" ), sizeof(settings.characterfile) ); settings.skill = atof( Info_ValueForKey( userinfo, "skill" ) ); - Q_strncpyz( settings.team, Info_ValueForKey( userinfo, "team" ), sizeof(settings.team) ); if (!BotAISetupClient( clientNum, &settings, restart )) { trap_DropClient( clientNum, "BotAISetupClient failed" ); @@ -560,6 +582,8 @@ G_AddBot */ static void G_AddBot( const char *name, float skill, const char *team, int delay, char *altname) { int clientNum; + int teamNum; + int botinfoNum; char *botinfo; char *key; char *s; @@ -576,8 +600,50 @@ static void G_AddBot( const char *name, float skill, const char *team, int delay return; } + // set default team + if( !team || !*team ) { + if( g_gametype.integer >= GT_TEAM ) { + if( PickTeam(clientNum) == TEAM_RED) { + team = "red"; + } + else { + team = "blue"; + } + } + else { + team = "free"; + } + } + // get the botinfo from bots.txt - botinfo = G_GetBotInfoByName( name ); + if ( Q_stricmp( name, "random" ) == 0 ) { + if ( Q_stricmp( team, "red" ) == 0 || Q_stricmp( team, "r" ) == 0 ) { + teamNum = TEAM_RED; + } + else if ( Q_stricmp( team, "blue" ) == 0 || Q_stricmp( team, "b" ) == 0 ) { + teamNum = TEAM_BLUE; + } + else if ( !Q_stricmp( team, "spectator" ) || !Q_stricmp( team, "s" ) ) { + teamNum = TEAM_SPECTATOR; + } + else { + teamNum = TEAM_FREE; + } + + botinfoNum = G_SelectRandomBotInfo( teamNum ); + + if ( botinfoNum < 0 ) { + G_Printf( S_COLOR_RED "Error: Cannot add random bot, no bot info available.\n" ); + trap_BotFreeClient( clientNum ); + return; + } + + botinfo = G_GetBotInfoByNumber( botinfoNum ); + } + else { + botinfo = G_GetBotInfoByName( name ); + } + if ( !botinfo ) { G_Printf( S_COLOR_RED "Error: Bot '%s' not defined\n", name ); trap_BotFreeClient( clientNum ); @@ -599,6 +665,7 @@ static void G_AddBot( const char *name, float skill, const char *team, int delay Info_SetValueForKey( userinfo, "rate", "25000" ); Info_SetValueForKey( userinfo, "snaps", "20" ); Info_SetValueForKey( userinfo, "skill", va("%.2f", skill) ); + Info_SetValueForKey( userinfo, "teampref", team ); if ( skill >= 1 && skill < 2 ) { Info_SetValueForKey( userinfo, "handicap", "50" ); @@ -657,20 +724,8 @@ static void G_AddBot( const char *name, float skill, const char *team, int delay } Info_SetValueForKey( userinfo, "characterfile", s ); - if( !team || !*team ) { - if( g_gametype.integer >= GT_TEAM ) { - if( PickTeam(clientNum) == TEAM_RED) { - team = "red"; - } - else { - team = "blue"; - } - } - else { - team = "red"; - } - } - Info_SetValueForKey( userinfo, "team", team ); + // don't send tinfo to bots, they don't parse it + Info_SetValueForKey( userinfo, "teamoverlay", "0" ); // register the userinfo trap_SetUserinfo( clientNum, userinfo ); @@ -720,7 +775,7 @@ void Svcmd_AddBot_f( void ) { skill = 4; } else { - skill = atof( string ); + skill = Com_Clamp( 1, 5, atof( string ) ); } // team diff --git a/engine/code/game/g_client.c b/engine/code/game/g_client.c index 68456f63..0e0bfbe8 100644 --- a/engine/code/game/g_client.c +++ b/engine/code/game/g_client.c @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 1999-2005 Id Software, Inc., 2016 Google Inc. +Copyright (C) 1999-2005 Id Software, Inc., 2016-2017 Google Inc. This file is part of Quake III Arena source code. @@ -741,7 +741,7 @@ if desired. */ void ClientUserinfoChanged( int clientNum ) { gentity_t *ent; - int teamTask, teamLeader, team, health; + int teamTask, teamLeader, health; char *s; char model[MAX_QPATH]; char headModel[MAX_QPATH]; @@ -765,12 +765,6 @@ void ClientUserinfoChanged( int clientNum ) { trap_DropClient(clientNum, "Invalid userinfo"); } - // check for local client - s = Info_ValueForKey( userinfo, "ip" ); - if ( !strcmp( s, "localhost" ) ) { - client->pers.localClient = qtrue; - } - // check the item prediction s = Info_ValueForKey( userinfo, "cg_predictItems" ); if ( !atoi( s ) ) { @@ -826,22 +820,6 @@ void ClientUserinfoChanged( int clientNum ) { Q_strncpyz( headModel, Info_ValueForKey (userinfo, "headmodel"), sizeof( headModel ) ); } - // bots set their team a few frames later - if (g_gametype.integer >= GT_TEAM && g_entities[clientNum].r.svFlags & SVF_BOT) { - s = Info_ValueForKey( userinfo, "team" ); - if ( !Q_stricmp( s, "red" ) || !Q_stricmp( s, "r" ) ) { - team = TEAM_RED; - } else if ( !Q_stricmp( s, "blue" ) || !Q_stricmp( s, "b" ) ) { - team = TEAM_BLUE; - } else { - // pick the team with the least number of players - team = PickTeam( clientNum ); - } - } - else { - team = client->sess.sessionTeam; - } - /* NOTE: all client side now // team @@ -864,7 +842,7 @@ void ClientUserinfoChanged( int clientNum ) { */ #ifdef MISSIONPACK - if (g_gametype.integer >= GT_TEAM) { + if (g_gametype.integer >= GT_TEAM && !(ent->r.svFlags & SVF_BOT)) { client->pers.teamInfo = qtrue; } else { s = Info_ValueForKey( userinfo, "teamoverlay" ); @@ -910,7 +888,7 @@ void ClientUserinfoChanged( int clientNum ) { if (ent->r.svFlags & SVF_BOT) { s = va("n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\skill\\%s\\tt\\%d\\tl\\%d", - client->pers.netname, team, model, headModel, c1, c2, + client->pers.netname, client->sess.sessionTeam, model, headModel, c1, c2, client->pers.maxHealth, client->sess.wins, client->sess.losses, Info_ValueForKey( userinfo, "skill" ), teamTask, teamLeader ); } @@ -995,11 +973,11 @@ char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) { client->pers.connected = CON_CONNECTING; - // read or initialize the session data - if ( firstTime || level.newSession ) { - G_InitSessionData( client, userinfo ); + // check for local client + value = Info_ValueForKey( userinfo, "ip" ); + if ( !strcmp( value, "localhost" ) ) { + client->pers.localClient = qtrue; } - G_ReadSessionData( client ); if( isBot ) { ent->r.svFlags |= SVF_BOT; @@ -1009,6 +987,12 @@ char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) { } } + // read or initialize the session data + if ( firstTime || level.newSession ) { + G_InitSessionData( client, userinfo ); + } + G_ReadSessionData( client ); + // get and distribute relevent paramters G_LogPrintf( "ClientConnect: %i\n", clientNum ); ClientUserinfoChanged( clientNum ); @@ -1233,11 +1217,14 @@ void ClientSpawn(gentity_t *ent) { client->ps.ammo[WP_GRAPPLING_HOOK] = -1; // health will count down towards max_health - ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] + 25; + client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] + 25; G_SetOrigin( ent, spawn_origin ); VectorCopy( spawn_origin, client->ps.origin ); + dmlab_spawn_inventory(&client->ps); + ent->health = client->ps.stats[STAT_HEALTH]; + // the respawned flag will be cleared after the attack and jump keys come up client->ps.pm_flags |= PMF_RESPAWNED; diff --git a/engine/code/game/g_cmds.c b/engine/code/game/g_cmds.c index f01df847..4f73c1d9 100644 --- a/engine/code/game/g_cmds.c +++ b/engine/code/game/g_cmds.c @@ -34,12 +34,17 @@ DeathmatchScoreboardMessage */ void DeathmatchScoreboardMessage( gentity_t *ent ) { char entry[1024]; - char string[1400]; + char string[1000]; int stringlength; int i, j; gclient_t *cl; int numSorted, scoreFlags, accuracy, perfect; + // don't send scores to bots, they don't parse it + if ( ent->r.svFlags & SVF_BOT ) { + return; + } + // send the latest information on all clients string[0] = 0; stringlength = 0; @@ -187,31 +192,35 @@ Returns a player number for either a number or name string Returns -1 if invalid ================== */ -int ClientNumberFromString( gentity_t *to, char *s ) { +int ClientNumberFromString( gentity_t *to, char *s, qboolean checkNums, qboolean checkNames ) { gclient_t *cl; int idnum; char cleanName[MAX_STRING_CHARS]; - // numeric values could be slot numbers - if ( StringIsInteger( s ) ) { - idnum = atoi( s ); - if ( idnum >= 0 && idnum < level.maxclients ) { - cl = &level.clients[idnum]; - if ( cl->pers.connected == CON_CONNECTED ) { - return idnum; + if ( checkNums ) { + // numeric values could be slot numbers + if ( StringIsInteger( s ) ) { + idnum = atoi( s ); + if ( idnum >= 0 && idnum < level.maxclients ) { + cl = &level.clients[idnum]; + if ( cl->pers.connected == CON_CONNECTED ) { + return idnum; + } } } } - // check for a name match - for ( idnum=0,cl=level.clients ; idnum < level.maxclients ; idnum++,cl++ ) { - if ( cl->pers.connected != CON_CONNECTED ) { - continue; - } - Q_strncpyz(cleanName, cl->pers.netname, sizeof(cleanName)); - Q_CleanStr(cleanName); - if ( !Q_stricmp( cleanName, s ) ) { - return idnum; + if ( checkNames ) { + // check for a name match + for ( idnum=0,cl=level.clients ; idnum < level.maxclients ; idnum++,cl++ ) { + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + Q_strncpyz(cleanName, cl->pers.netname, sizeof(cleanName)); + Q_CleanStr(cleanName); + if ( !Q_stricmp( cleanName, s ) ) { + return idnum; + } } } @@ -503,7 +512,7 @@ void BroadcastTeamChange( gclient_t *client, int oldTeam ) SetTeam ================= */ -void SetTeam( gentity_t *ent, char *s ) { +void SetTeam( gentity_t *ent, const char *s ) { int team, oldTeam; gclient_t *client; int clientNum; @@ -545,7 +554,7 @@ void SetTeam( gentity_t *ent, char *s ) { team = PickTeam( clientNum ); } - if ( g_teamForceBalance.integer ) { + if ( g_teamForceBalance.integer && !client->pers.localClient && !( ent->r.svFlags & SVF_BOT ) ) { int counts[TEAM_NUM_TEAMS]; counts[TEAM_BLUE] = TeamCount( clientNum, TEAM_BLUE ); @@ -592,8 +601,8 @@ void SetTeam( gentity_t *ent, char *s ) { // execute the team change // - // if the player was dead leave the body - if ( client->ps.stats[STAT_HEALTH] <= 0 ) { + // if the player was dead leave the body, but only if they're actually in game + if ( client->ps.stats[STAT_HEALTH] <= 0 && client->pers.connected == CON_CONNECTED ) { CopyToBodyQue(ent); } @@ -633,6 +642,11 @@ void SetTeam( gentity_t *ent, char *s ) { // get and distribute relevent paramters ClientUserinfoChanged( clientNum ); + // client hasn't spawned yet, they sent an early team command, teampref userinfo, or g_teamAutoJoin is enabled + if ( client->pers.connected != CON_CONNECTED ) { + return; + } + ClientBegin( clientNum ); } @@ -724,7 +738,7 @@ void Cmd_Follow_f( gentity_t *ent ) { } trap_Argv( 1, arg, sizeof( arg ) ); - i = ClientNumberFromString( ent, arg ); + i = ClientNumberFromString( ent, arg, qtrue, qtrue ); if ( i == -1 ) { return; } @@ -914,6 +928,16 @@ void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText ) } } +static void SanitizeChatText( char *text ) { + int i; + + for ( i = 0; text[i]; i++ ) { + if ( text[i] == '\n' || text[i] == '\r' ) { + text[i] = ' '; + } + } +} + /* ================== @@ -936,6 +960,8 @@ static void Cmd_Say_f( gentity_t *ent, int mode, qboolean arg0 ) { p = ConcatArgs( 1 ); } + SanitizeChatText( p ); + G_Say( ent, NULL, mode, p ); } @@ -956,7 +982,7 @@ static void Cmd_Tell_f( gentity_t *ent ) { } trap_Argv( 1, arg, sizeof( arg ) ); - targetNum = ClientNumberFromString( ent, arg ); + targetNum = ClientNumberFromString( ent, arg, qtrue, qtrue ); if ( targetNum == -1 ) { return; } @@ -968,6 +994,8 @@ static void Cmd_Tell_f( gentity_t *ent ) { p = ConcatArgs( 2 ); + SanitizeChatText( p ); + G_LogPrintf( "tell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, p ); G_Say( ent, target, SAY_TELL, p ); // don't tell to the player self if it was already directed to this player @@ -1062,6 +1090,8 @@ static void Cmd_Voice_f( gentity_t *ent, int mode, qboolean arg0, qboolean voice p = ConcatArgs( 1 ); } + SanitizeChatText( p ); + G_Voice( ent, NULL, mode, p, voiceonly ); } @@ -1082,7 +1112,7 @@ static void Cmd_VoiceTell_f( gentity_t *ent, qboolean voiceonly ) { } trap_Argv( 1, arg, sizeof( arg ) ); - targetNum = ClientNumberFromString( ent, arg ); + targetNum = ClientNumberFromString( ent, arg, qtrue, qtrue ); if ( targetNum == -1 ) { return; } @@ -1094,6 +1124,8 @@ static void Cmd_VoiceTell_f( gentity_t *ent, qboolean voiceonly ) { id = ConcatArgs( 2 ); + SanitizeChatText( id ); + G_LogPrintf( "vtell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, id ); G_Voice( ent, target, SAY_TELL, id, voiceonly ); // don't tell to the player self if it was already directed to this player @@ -1211,7 +1243,7 @@ void Cmd_GameCommand_f( gentity_t *ent ) { } trap_Argv( 1, arg, sizeof( arg ) ); - targetNum = ClientNumberFromString( ent, arg ); + targetNum = ClientNumberFromString( ent, arg, qtrue, qtrue ); if ( targetNum == -1 ) { return; } @@ -1312,6 +1344,13 @@ void Cmd_CallVote_f( gentity_t *ent ) { // if there is still a vote to be executed if ( level.voteExecuteTime ) { + // don't start a vote when map change or restart is in progress + if ( !Q_stricmpn( level.voteString, "map", 3 ) + || !Q_stricmpn( level.voteString, "nextmap", 7 ) ) { + trap_SendServerCommand( ent-g_entities, "print \"Vote after map change.\n\"" ); + return; + } + level.voteExecuteTime = 0; trap_SendConsoleCommand( EXEC_APPEND, va("%s\n", level.voteString ) ); } @@ -1348,6 +1387,19 @@ void Cmd_CallVote_f( gentity_t *ent ) { } Com_sprintf( level.voteString, sizeof( level.voteString ), "vstr nextmap"); Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", level.voteString ); + } else if ( !Q_stricmp( arg1, "clientkick" ) || !Q_stricmp( arg1, "kick" ) ) { + i = ClientNumberFromString( ent, arg2, !Q_stricmp( arg1, "clientkick" ), !Q_stricmp( arg1, "kick" ) ); + if ( i == -1 ) { + return; + } + + if ( level.clients[i].pers.localClient ) { + trap_SendServerCommand( ent - g_entities, "print \"Cannot kick host player.\n\"" ); + return; + } + + Com_sprintf( level.voteString, sizeof( level.voteString ), "clientkick %d", i ); + Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "kick %s", level.clients[i].pers.netname ); } else { Com_sprintf( level.voteString, sizeof( level.voteString ), "%s \"%s\"", arg1, arg2 ); Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", level.voteString ); @@ -1416,6 +1468,7 @@ Cmd_CallTeamVote_f ================== */ void Cmd_CallTeamVote_f( gentity_t *ent ) { + char* c; int i, team, cs_offset; char arg1[MAX_STRING_TOKENS]; char arg2[MAX_STRING_TOKENS]; @@ -1455,9 +1508,16 @@ void Cmd_CallTeamVote_f( gentity_t *ent ) { trap_Argv( i, &arg2[strlen(arg2)], sizeof( arg2 ) - strlen(arg2) ); } - if( strchr( arg1, ';' ) || strchr( arg2, ';' ) ) { - trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" ); - return; + // check for command separators in arg2 + for( c = arg2; *c; ++c) { + switch(*c) { + case '\n': + case '\r': + case ';': + trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" ); + return; + break; + } } if ( !Q_stricmp( arg1, "leader" ) ) { @@ -1652,6 +1712,14 @@ void ClientCommand( int clientNum ) { ent = g_entities + clientNum; if (!ent->client || ent->client->pers.connected != CON_CONNECTED) { + if (ent->client && ent->client->pers.localClient) { + // Handle early team command sent by UI when starting a local + // team play game. + trap_Argv( 0, cmd, sizeof( cmd ) ); + if (Q_stricmp (cmd, "team") == 0) { + Cmd_Team_f (ent); + } + } return; // not fully in game yet } diff --git a/engine/code/game/g_combat.c b/engine/code/game/g_combat.c index 25f41229..386185b8 100644 --- a/engine/code/game/g_combat.c +++ b/engine/code/game/g_combat.c @@ -49,7 +49,8 @@ AddScore Adds score to both the client and his team ============ */ -void AddScore( gentity_t *ent, vec3_t origin, int score ) { +void AddScore( gentity_t *ent, vec3_t origin, int score, const char* reason, gentity_t *other ) { + const int* other_client_num = NULL; if ( !ent->client ) { return; } @@ -57,7 +58,10 @@ void AddScore( gentity_t *ent, vec3_t origin, int score ) { if ( level.warmupTime ) { return; } - score += dmlab_external_reward( ent - g_entities ); + if (other != NULL && other->client) { + other_client_num = &other->client->ps.clientNum; + } + score = dmlab_reward_override(reason, ent->client->ps.clientNum, ent->client->sess.sessionTeam, other_client_num, origin, score); if ( score == 0 ) { return; } @@ -512,9 +516,9 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int attacker->client->lastkilled_client = self->s.number; if ( attacker == self || OnSameTeam (self, attacker ) ) { - AddScore( attacker, self->r.currentOrigin, -1 ); + AddScore( attacker, self->r.currentOrigin, -1, "TAG_SELF", self ); } else { - AddScore( attacker, self->r.currentOrigin, 1 ); + AddScore( attacker, self->r.currentOrigin, 1, "TAG_PLAYER", self ); if( meansOfDeath == MOD_GAUNTLET ) { @@ -545,7 +549,7 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int } } else { - AddScore( self, self->r.currentOrigin, -1 ); + AddScore( self, self->r.currentOrigin, -1, "TAG_SELF", self ); } // Add team bonuses @@ -830,7 +834,7 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, return; } - // the intermission has allready been qualified for, so don't + // the intermission has already been qualified for, so don't // allow any extra scoring if ( level.intermissionQueued ) { return; diff --git a/engine/code/game/g_items.c b/engine/code/game/g_items.c index 8b870c6a..9074fdf7 100644 --- a/engine/code/game/g_items.c +++ b/engine/code/game/g_items.c @@ -206,14 +206,14 @@ int Pickup_Holdable( gentity_t *ent, gentity_t *other ) { //====================================================================== int Pickup_Reward( gentity_t *ent, gentity_t *other ) { - AddScore( other, NULL, ent->count ? ent->count : ent->item->quantity ); + AddScore( other, NULL, ent->count ? ent->count : ent->item->quantity, "PICKUP_REWARD", ent ); return -1; } //====================================================================== int Pickup_Goal( gentity_t *ent, gentity_t *other ) { - AddScore( other, NULL, ent->count ? ent->count : ent->item->quantity ); + AddScore( other, NULL, ent->count ? ent->count : ent->item->quantity, "PICKUP_GOAL", ent ); dmlab_set_map_finished( qtrue ); return -1; } @@ -938,6 +938,10 @@ void G_SpawnItem (gentity_t *ent, gitem_t *item) { ent->s.generic1 = ent->spawnflags; } #endif + + if ( item->giType == IT_REWARD ) { + ent->s.generic1 = item->giTag; + } } diff --git a/engine/code/game/g_local.h b/engine/code/game/g_local.h index a3c1dc28..eed03427 100644 --- a/engine/code/game/g_local.h +++ b/engine/code/game/g_local.h @@ -137,6 +137,7 @@ struct gentity_s { void (*use)(gentity_t *self, gentity_t *other, gentity_t *activator); void (*pain)(gentity_t *self, gentity_t *attacker, int damage); void (*die)(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod); + void (*look)(gentity_t *self, gentity_t *other, const trace_t *trace); int pain_debounce_time; int fly_sound_debounce_time; // wind tunnel @@ -325,6 +326,7 @@ struct gclient_s { #endif char *areabits; + int lastLookAt; // Stores the last look at trigger for a client. }; @@ -435,7 +437,7 @@ char *G_NewString( const char *string ); void Cmd_Score_f (gentity_t *ent); void StopFollowing( gentity_t *ent ); void BroadcastTeamChange( gclient_t *client, int oldTeam ); -void SetTeam( gentity_t *ent, char *s ); +void SetTeam( gentity_t *ent, const char *s ); void Cmd_FollowCycle_f( gentity_t *ent, int dir ); // @@ -579,7 +581,7 @@ void BeginIntermission (void); void InitBodyQue (void); void ClientSpawn( gentity_t *ent ); void player_die (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod); -void AddScore( gentity_t *ent, vec3_t origin, int score ); +void AddScore( gentity_t *ent, vec3_t origin, int score, const char* reason, gentity_t *other ); void CalculateRanks( void ); qboolean SpotWouldTelefrag( gentity_t *spot ); @@ -684,7 +686,6 @@ typedef struct bot_settings_s { char characterfile[MAX_FILEPATH]; float skill; - char team[MAX_FILEPATH]; } bot_settings_t; int BotAISetup( int restart ); @@ -753,6 +754,7 @@ extern vmCvar_t g_enableDust; extern vmCvar_t g_enableBreath; extern vmCvar_t g_singlePlayer; extern vmCvar_t g_proxMineTimeout; +extern vmCvar_t g_localTeamPref; extern vmCvar_t g_teleportBody; extern vmCvar_t g_unlimitedAmmo; diff --git a/engine/code/game/g_main.c b/engine/code/game/g_main.c index 1291756a..a9852eb2 100644 --- a/engine/code/game/g_main.c +++ b/engine/code/game/g_main.c @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 1999-2005 Id Software, Inc., 2016 Google Inc. +Copyright (C) 1999-2005 Id Software, Inc., 2016-2017 Google Inc. This file is part of Quake III Arena source code. @@ -81,6 +81,7 @@ vmCvar_t pmove_fixed; vmCvar_t pmove_msec; vmCvar_t g_rankings; vmCvar_t g_listEntity; +vmCvar_t g_localTeamPref; vmCvar_t g_teleportBody; vmCvar_t g_unlimitedAmmo; #ifdef MISSIONPACK @@ -180,7 +181,8 @@ static cvarTable_t gameCvarTable[] = { { &pmove_fixed, "pmove_fixed", "0", CVAR_SYSTEMINFO, 0, qfalse}, { &pmove_msec, "pmove_msec", "8", CVAR_SYSTEMINFO, 0, qfalse}, - { &g_rankings, "g_rankings", "0", 0, 0, qfalse} + { &g_rankings, "g_rankings", "0", 0, 0, qfalse}, + { &g_localTeamPref, "g_localTeamPref", "", 0, 0, qfalse } }; @@ -279,7 +281,7 @@ void G_FindTeams( void ) { c = 0; c2 = 0; - for ( i=1, e=g_entities+i ; i < level.num_entities ; i++,e++ ){ + for ( i=MAX_CLIENTS, e=g_entities+i ; i < level.num_entities ; i++,e++ ) { if (!e->inuse) continue; if (!e->team) @@ -1154,6 +1156,7 @@ void LogExit( const char *string ) { gclient_t *cl; #ifdef MISSIONPACK qboolean won = qtrue; + team_t team = TEAM_RED; #endif G_LogPrintf( "Exit: %s\n", string ); @@ -1190,7 +1193,10 @@ void LogExit( const char *string ) { G_LogPrintf( "score: %i ping: %i client: %i %s\n", cl->ps.persistant[PERS_SCORE], ping, level.sortedClients[i], cl->pers.netname ); #ifdef MISSIONPACK - if (g_singlePlayer.integer && g_gametype.integer == GT_TOURNAMENT) { + if (g_singlePlayer.integer && !(g_entities[cl - level.clients].r.svFlags & SVF_BOT)) { + team = cl->sess.sessionTeam; + } + if (g_singlePlayer.integer && g_gametype.integer < GT_TEAM) { if (g_entities[cl - level.clients].r.svFlags & SVF_BOT && cl->ps.persistant[PERS_RANK] == 0) { won = qfalse; } @@ -1201,8 +1207,12 @@ void LogExit( const char *string ) { #ifdef MISSIONPACK if (g_singlePlayer.integer) { - if (g_gametype.integer >= GT_CTF) { - won = level.teamScores[TEAM_RED] > level.teamScores[TEAM_BLUE]; + if (g_gametype.integer >= GT_TEAM) { + if (team == TEAM_BLUE) { + won = level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED]; + } else { + won = level.teamScores[TEAM_RED] > level.teamScores[TEAM_BLUE]; + } } trap_SendConsoleCommand( EXEC_APPEND, (won) ? "spWin\n" : "spLose\n" ); } @@ -1499,7 +1509,7 @@ void CheckTournament( void ) { int counts[TEAM_NUM_TEAMS]; qboolean notEnough = qfalse; - if ( g_gametype.integer > GT_TEAM ) { + if ( g_gametype.integer >= GT_TEAM ) { counts[TEAM_BLUE] = TeamCount( -1, TEAM_BLUE ); counts[TEAM_RED] = TeamCount( -1, TEAM_RED ); @@ -1780,7 +1790,7 @@ void G_RunFrame( int levelTime ) { G_UpdateCvars(); BG_UpdateItems(); - + dmlab_entities_clear(); // // go through all allocated objects // @@ -1812,6 +1822,9 @@ void G_RunFrame( int levelTime ) { } } + if (ent->item) { + dmlab_entities_add( ent->s.number, ent->id, ent->item->giType, ent->s.eFlags, ent->r.currentOrigin, ent->item->classname ); + } // temporary entities don't think if ( ent->freeAfterEvent ) { continue; diff --git a/engine/code/game/g_mover.c b/engine/code/game/g_mover.c index 756c0dd0..f6f16a05 100644 --- a/engine/code/game/g_mover.c +++ b/engine/code/game/g_mover.c @@ -24,7 +24,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "g_local.h" - /* =============================================================================== @@ -1158,6 +1157,46 @@ void SP_func_plat (gentity_t *ent) { } } +/* +=============================================================================== + +LUA MOVER + +=============================================================================== +*/ + +void Think_LuaMover( gentity_t *ent ) { + vec3_t player_position_delta; + vec3_t player_velocity_delta; + playerState_t* ps = &(ent->activator->client->ps); + + VectorSet(player_position_delta, 0.0f, 0.0f, 0.0f); + VectorSet(player_velocity_delta, 0.0f, 0.0f, 0.0f); + + dmlab_lua_mover(ent->id, ent->s.origin, ps->origin, ps->velocity, player_position_delta, player_velocity_delta); + + VectorAdd(ps->origin, player_position_delta, ps->origin); + VectorAdd(ps->velocity, player_velocity_delta, ps->velocity); + + ent->nextthink = level.time + FRAMETIME; + ent->think = Think_LuaMover; +} + +void Use_LuaMover( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + if (activator->client) { + ent->activator = activator; + ent->nextthink = level.time; + ent->think = Think_LuaMover; + } +} + +void SP_func_lua_mover( gentity_t *ent ) { + if (ent->targetname) { + ent->use = Use_LuaMover; + } else { + Use_LuaMover(ent, ent, ent); + } +} /* =============================================================================== diff --git a/engine/code/game/g_session.c b/engine/code/game/g_session.c index eaac1a71..a33f5615 100644 --- a/engine/code/game/g_session.c +++ b/engine/code/game/g_session.c @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 1999-2005 Id Software, Inc., 2017 Google Inc. This file is part of Quake III Arena source code. @@ -102,20 +102,35 @@ Called on a first-time connect void G_InitSessionData( gclient_t *client, char *userinfo ) { clientSession_t *sess; const char *value; - + char team[2] = {'\0', '\0'}; sess = &client->sess; + // check for team preference, mainly for bots + value = Info_ValueForKey( userinfo, "teampref" ); + + // check for human's team preference set by start server menu + if ( !value[0] && g_localTeamPref.string[0] && client->pers.localClient ) { + value = g_localTeamPref.string; + + // clear team so it's only used once + trap_Cvar_Set( "g_localTeamPref", "" ); + } + + team[0] = dmlab_select_team( client - level.clients, Info_ValueForKey (userinfo, "name") ); + if ( team[0] ){ + value = team; + } + // initial team determination if ( g_gametype.integer >= GT_TEAM ) { - if ( g_teamAutoJoin.integer && !(g_entities[ client - level.clients ].r.svFlags & SVF_BOT) ) { - sess->sessionTeam = PickTeam( -1 ); - BroadcastTeamChange( client, -1 ); - } else { - // always spawn as spectator in team games - sess->sessionTeam = TEAM_SPECTATOR; + // always spawn as spectator in team games + sess->sessionTeam = TEAM_SPECTATOR; + sess->spectatorState = SPECTATOR_FREE; + + if ( value[0] || g_teamAutoJoin.integer ) { + SetTeam( &g_entities[client - level.clients], value ); } } else { - value = Info_ValueForKey( userinfo, "team" ); if ( value[0] == 's' ) { // a willing spectator, not a waiting-in-line sess->sessionTeam = TEAM_SPECTATOR; @@ -141,9 +156,10 @@ void G_InitSessionData( gclient_t *client, char *userinfo ) { break; } } + + sess->spectatorState = SPECTATOR_FREE; } - sess->spectatorState = SPECTATOR_FREE; AddTournamentQueue(client); G_WriteClientSessionData( client ); diff --git a/engine/code/game/g_spawn.c b/engine/code/game/g_spawn.c index 7a89d225..88420cea 100644 --- a/engine/code/game/g_spawn.c +++ b/engine/code/game/g_spawn.c @@ -133,12 +133,14 @@ void SP_func_button (gentity_t *ent); void SP_func_door (gentity_t *ent); void SP_func_train (gentity_t *ent); void SP_func_timer (gentity_t *self); +void SP_func_lua_mover (gentity_t *self); void SP_trigger_always (gentity_t *ent); void SP_trigger_multiple (gentity_t *ent); void SP_trigger_push (gentity_t *ent); void SP_trigger_teleport (gentity_t *ent); void SP_trigger_hurt (gentity_t *ent); +void SP_trigger_lookat (gentity_t *ent); void SP_target_remove_powerups( gentity_t *ent ); void SP_target_give (gentity_t *ent); @@ -202,6 +204,7 @@ spawn_t spawns[] = { {"func_train", SP_func_train}, {"func_group", SP_info_null}, {"func_timer", SP_func_timer}, // rename trigger_timer? + {"func_lua_mover", SP_func_lua_mover}, // Triggers are brush objects that cause an effect when contacted // by a living player, usually involving firing targets. @@ -213,6 +216,7 @@ spawn_t spawns[] = { {"trigger_push", SP_trigger_push}, {"trigger_teleport", SP_trigger_teleport}, {"trigger_hurt", SP_trigger_hurt}, + {"trigger_lookat", SP_trigger_lookat}, // targets perform no action by themselves, but must be triggered // by another entity @@ -399,28 +403,6 @@ void G_SpawnGEntityFromSpawnVars( void ) { gentity_t *ent; char *s, *value, *gametypeName; static char *gametypeNames[] = {"ffa", "tournament", "single", "team", "ctf", "oneflag", "obelisk", "harvester"}; - int spawnVarsOffset[MAX_SPAWN_VARS][2]; // key / value pairs offsets - - // Convert to offsets. - for (i = 0; i < level.numSpawnVars; ++i) { - spawnVarsOffset[i][0] = level.spawnVars[i][0] - level.spawnVarChars; - spawnVarsOffset[i][1] = level.spawnVars[i][1] - level.spawnVarChars; - } - - // Early out if spawn is not required. - if (!dmlab_update_spawn_vars( - level.spawnVarChars, - &level.numSpawnVarChars, - spawnVarsOffset, - &level.numSpawnVars)) { - return; - } - - // Convert from offsets. - for (i = 0; i < level.numSpawnVars; ++i) { - level.spawnVars[i][0] = level.spawnVarChars + spawnVarsOffset[i][0]; - level.spawnVars[i][1] = level.spawnVarChars + spawnVarsOffset[i][1]; - } // get the next free entity ent = G_Spawn(); @@ -644,6 +626,8 @@ Parses textual entity definitions out of an entstring and spawns gentities. */ void G_SpawnEntitiesFromString( void ) { // allow calls to G_Spawn*() + int i, ent_id, extra_spawn_count; + int spawnVarsOffset[MAX_SPAWN_VARS][2]; // key / value pairs offsets level.spawning = qtrue; level.numSpawnVars = 0; @@ -657,8 +641,46 @@ void G_SpawnEntitiesFromString( void ) { // parse ents while( G_ParseSpawnVars() ) { + // Convert to offsets. + for (i = 0; i < level.numSpawnVars; ++i) { + spawnVarsOffset[i][0] = level.spawnVars[i][0] - level.spawnVarChars; + spawnVarsOffset[i][1] = level.spawnVars[i][1] - level.spawnVarChars; + } + + // Skip if spawn is not required. + if (!dmlab_update_spawn_vars( + level.spawnVarChars, + &level.numSpawnVarChars, + spawnVarsOffset, + &level.numSpawnVars)) { + continue; + } + // Convert from offsets. + for (i = 0; i < level.numSpawnVars; ++i) { + level.spawnVars[i][0] = level.spawnVarChars + spawnVarsOffset[i][0]; + level.spawnVars[i][1] = level.spawnVarChars + spawnVarsOffset[i][1]; + } G_SpawnGEntityFromSpawnVars(); - } + } + extra_spawn_count = dmlab_make_extra_entities(); + for (ent_id = 0; ent_id < extra_spawn_count; ++ent_id) { + level.numSpawnVars = 0; + level.numSpawnVarChars = 0; + // Early out if spawn is not required. + dmlab_read_extra_entity( + ent_id, + level.spawnVarChars, + &level.numSpawnVarChars, + spawnVarsOffset, + &level.numSpawnVars); + + // Convert from offsets. + for (i = 0; i < level.numSpawnVars; ++i) { + level.spawnVars[i][0] = level.spawnVarChars + spawnVarsOffset[i][0]; + level.spawnVars[i][1] = level.spawnVarChars + spawnVarsOffset[i][1]; + } + G_SpawnGEntityFromSpawnVars(); + } level.spawning = qfalse; // any future calls to G_Spawn*() will be errors } diff --git a/engine/code/game/g_svcmds.c b/engine/code/game/g_svcmds.c index 755a7da3..3d377156 100644 --- a/engine/code/game/g_svcmds.c +++ b/engine/code/game/g_svcmds.c @@ -320,8 +320,8 @@ void Svcmd_EntityList_f (void) { int e; gentity_t *check; - check = g_entities+1; - for (e = 1; e < level.num_entities ; e++, check++) { + check = g_entities; + for (e = 0; e < level.num_entities ; e++, check++) { if ( !check->inuse ) { continue; } diff --git a/engine/code/game/g_target.c b/engine/code/game/g_target.c index 5205990d..1b191ce0 100644 --- a/engine/code/game/g_target.c +++ b/engine/code/game/g_target.c @@ -122,7 +122,7 @@ void SP_target_delay( gentity_t *ent ) { The activator is given this many points. */ void Use_Target_Score (gentity_t *ent, gentity_t *other, gentity_t *activator) { - AddScore( activator, ent->r.currentOrigin, ent->count ); + AddScore( activator, ent->r.currentOrigin, ent->count, "TARGET_SCORE", ent ); } void SP_target_score( gentity_t *ent ) { diff --git a/engine/code/game/g_team.c b/engine/code/game/g_team.c index bde9377b..6aa20972 100644 --- a/engine/code/game/g_team.c +++ b/engine/code/game/g_team.c @@ -300,6 +300,7 @@ void Team_FragBonuses(gentity_t *targ, gentity_t *inflictor, gentity_t *attacker #ifdef MISSIONPACK if (g_gametype.integer == GT_1FCTF) { + flag_pw = PW_NEUTRALFLAG; enemy_flag_pw = PW_NEUTRALFLAG; } #endif @@ -313,7 +314,7 @@ void Team_FragBonuses(gentity_t *targ, gentity_t *inflictor, gentity_t *attacker #endif if (targ->client->ps.powerups[enemy_flag_pw]) { attacker->client->pers.teamState.lastfraggedcarrier = level.time; - AddScore(attacker, targ->r.currentOrigin, CTF_FRAG_CARRIER_BONUS); + AddScore(attacker, targ->r.currentOrigin, CTF_FRAG_CARRIER_BONUS, "CTF_FRAG_CARRIER_BONUS", targ); attacker->client->pers.teamState.fragcarrier++; PrintMsg(NULL, "%s" S_COLOR_WHITE " tagged %s's flag carrier!\n", attacker->client->pers.netname, TeamName(team)); @@ -331,7 +332,7 @@ void Team_FragBonuses(gentity_t *targ, gentity_t *inflictor, gentity_t *attacker // did the attacker frag a head carrier? other->client->ps.generic1 if (tokens) { attacker->client->pers.teamState.lastfraggedcarrier = level.time; - AddScore(attacker, targ->r.currentOrigin, CTF_FRAG_CARRIER_BONUS * tokens * tokens); + AddScore(attacker, targ->r.currentOrigin, CTF_FRAG_CARRIER_BONUS * tokens * tokens, "CTF_FRAG_CARRIER_BONUS", targ); attacker->client->pers.teamState.fragcarrier++; PrintMsg(NULL, "%s" S_COLOR_WHITE " tagged %s's skull carrier!\n", attacker->client->pers.netname, TeamName(team)); @@ -351,24 +352,7 @@ void Team_FragBonuses(gentity_t *targ, gentity_t *inflictor, gentity_t *attacker !attacker->client->ps.powerups[flag_pw]) { // attacker is on the same team as the flag carrier and // fragged a guy who hurt our flag carrier - AddScore(attacker, targ->r.currentOrigin, CTF_CARRIER_DANGER_PROTECT_BONUS); - - attacker->client->pers.teamState.carrierdefense++; - targ->client->pers.teamState.lasthurtcarrier = 0; - - attacker->client->ps.persistant[PERS_DEFEND_COUNT]++; - // add the sprite over the player's head - attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP ); - attacker->client->ps.eFlags |= EF_AWARD_DEFEND; - attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME; - - return; - } - - if (targ->client->pers.teamState.lasthurtcarrier && - level.time - targ->client->pers.teamState.lasthurtcarrier < CTF_CARRIER_DANGER_PROTECT_TIMEOUT) { - // attacker is on the same team as the skull carrier and - AddScore(attacker, targ->r.currentOrigin, CTF_CARRIER_DANGER_PROTECT_BONUS); + AddScore(attacker, targ->r.currentOrigin, CTF_CARRIER_DANGER_PROTECT_BONUS, "CTF_CARRIER_DANGER_PROTECT_BONUS", targ); attacker->client->pers.teamState.carrierdefense++; targ->client->pers.teamState.lasthurtcarrier = 0; @@ -448,7 +432,7 @@ void Team_FragBonuses(gentity_t *targ, gentity_t *inflictor, gentity_t *attacker attacker->client->sess.sessionTeam != targ->client->sess.sessionTeam) { // we defended the base flag - AddScore(attacker, targ->r.currentOrigin, CTF_FLAG_DEFENSE_BONUS); + AddScore(attacker, targ->r.currentOrigin, CTF_FLAG_DEFENSE_BONUS, "CTF_FLAG_DEFENSE_BONUS", targ); attacker->client->pers.teamState.basedefense++; attacker->client->ps.persistant[PERS_DEFEND_COUNT]++; @@ -462,14 +446,14 @@ void Team_FragBonuses(gentity_t *targ, gentity_t *inflictor, gentity_t *attacker if (carrier && carrier != attacker) { VectorSubtract(targ->r.currentOrigin, carrier->r.currentOrigin, v1); - VectorSubtract(attacker->r.currentOrigin, carrier->r.currentOrigin, v1); + VectorSubtract(attacker->r.currentOrigin, carrier->r.currentOrigin, v2); if ( ( ( VectorLength(v1) < CTF_ATTACKER_PROTECT_RADIUS && trap_InPVS(carrier->r.currentOrigin, targ->r.currentOrigin ) ) || ( VectorLength(v2) < CTF_ATTACKER_PROTECT_RADIUS && trap_InPVS(carrier->r.currentOrigin, attacker->r.currentOrigin ) ) ) && attacker->client->sess.sessionTeam != targ->client->sess.sessionTeam) { - AddScore(attacker, targ->r.currentOrigin, CTF_CARRIER_PROTECT_BONUS); + AddScore(attacker, targ->r.currentOrigin, CTF_CARRIER_PROTECT_BONUS, "CTF_CARRIER_PROTECT_BONUS", targ); attacker->client->pers.teamState.carrierdefense++; attacker->client->ps.persistant[PERS_DEFEND_COUNT]++; @@ -503,6 +487,12 @@ void Team_CheckHurtCarrier(gentity_t *targ, gentity_t *attacker) else flag_pw = PW_REDFLAG; +#ifdef MISSIONPACK + if (g_gametype.integer == GT_1FCTF) { + flag_pw = PW_NEUTRALFLAG; + } +#endif + // flags if (targ->client->ps.powerups[flag_pw] && targ->client->sess.sessionTeam != attacker->client->sess.sessionTeam) @@ -710,7 +700,7 @@ int Team_TouchOurFlag( gentity_t *ent, gentity_t *other, int team ) { // hey, it's not home. return it by teleporting it back PrintMsg( NULL, "%s" S_COLOR_WHITE " returned the %s flag!\n", cl->pers.netname, TeamName(team)); - AddScore(other, ent->r.currentOrigin, CTF_RECOVERY_BONUS); + AddScore(other, ent->r.currentOrigin, CTF_RECOVERY_BONUS, "CTF_RECOVERY_BONUS", ent); other->client->pers.teamState.flagrecovery++; other->client->pers.teamState.lastreturnedflag = level.time; //ResetFlag will remove this entity! We must return zero @@ -753,7 +743,7 @@ int Team_TouchOurFlag( gentity_t *ent, gentity_t *other, int team ) { other->client->ps.persistant[PERS_CAPTURES]++; // other gets another 10 frag bonus - AddScore(other, ent->r.currentOrigin, CTF_CAPTURE_BONUS); + AddScore(other, ent->r.currentOrigin, CTF_CAPTURE_BONUS, "CTF_CAPTURE_BONUS", ent); Team_CaptureFlagSound( ent, team ); @@ -770,13 +760,11 @@ int Team_TouchOurFlag( gentity_t *ent, gentity_t *other, int team ) { player->client->pers.teamState.lasthurtcarrier = -5; } else if (player->client->sess.sessionTeam == cl->sess.sessionTeam) { -#ifdef MISSIONPACK - AddScore(player, ent->r.currentOrigin, CTF_TEAM_BONUS); -#endif + AddScore(player, ent->r.currentOrigin, CTF_TEAM_BONUS, "CTF_TEAM_BONUS", ent); // award extra points for capture assists if (player->client->pers.teamState.lastreturnedflag + CTF_RETURN_FLAG_ASSIST_TIMEOUT > level.time) { - AddScore (player, ent->r.currentOrigin, CTF_RETURN_FLAG_ASSIST_BONUS); + AddScore(player, ent->r.currentOrigin, CTF_RETURN_FLAG_ASSIST_BONUS, "CTF_RETURN_FLAG_ASSIST_BONUS", ent); other->client->pers.teamState.assists++; player->client->ps.persistant[PERS_ASSIST_COUNT]++; @@ -788,7 +776,7 @@ int Team_TouchOurFlag( gentity_t *ent, gentity_t *other, int team ) { } if (player->client->pers.teamState.lastfraggedcarrier + CTF_FRAG_CARRIER_ASSIST_TIMEOUT > level.time) { - AddScore(player, ent->r.currentOrigin, CTF_FRAG_CARRIER_ASSIST_BONUS); + AddScore(player, ent->r.currentOrigin, CTF_FRAG_CARRIER_ASSIST_BONUS, "CTF_FRAG_CARRIER_ASSIST_BONUS", ent); other->client->pers.teamState.assists++; player->client->ps.persistant[PERS_ASSIST_COUNT]++; // add the sprite over the player's head @@ -835,8 +823,8 @@ int Team_TouchEnemyFlag( gentity_t *ent, gentity_t *other, int team ) { #ifdef MISSIONPACK } - AddScore(other, ent->r.currentOrigin, CTF_FLAG_BONUS); #endif + AddScore(other, ent->r.currentOrigin, CTF_FLAG_BONUS, "CTF_FLAG_BONUS", ent); cl->pers.teamState.flagsince = level.time; Team_TakeFlagSound( ent, team ); @@ -1251,7 +1239,7 @@ static void ObeliskDie( gentity_t *self, gentity_t *inflictor, gentity_t *attack G_AddEvent( self->activator, EV_OBELISKEXPLODE, 0 ); - AddScore(attacker, self->r.currentOrigin, CTF_CAPTURE_BONUS); + AddScore(attacker, self->r.currentOrigin, CTF_CAPTURE_BONUS, "CTF_CAPTURE_BONUS"); // add the sprite over the player's head attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP ); @@ -1280,13 +1268,13 @@ static void ObeliskTouch( gentity_t *self, gentity_t *other, trace_t *trace ) { return; } - PrintMsg(NULL, "%s" S_COLOR_WHITE " brought in %i skull%s.\n", - other->client->pers.netname, tokens, tokens ? "s" : "" ); + PrintMsg(NULL, "%s" S_COLOR_WHITE " brought in %i %s.\n", + other->client->pers.netname, tokens, ( tokens == 1 ) ? "skull" : "skulls" ); AddTeamScore(self->s.pos.trBase, other->client->sess.sessionTeam, tokens); Team_ForceGesture(other->client->sess.sessionTeam); - AddScore(other, self->r.currentOrigin, CTF_CAPTURE_BONUS*tokens); + AddScore(other, self->r.currentOrigin, CTF_CAPTURE_BONUS*tokens, "CTF_CAPTURE_BONUS"); // add the sprite over the player's head other->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP ); @@ -1310,12 +1298,11 @@ static void ObeliskPain( gentity_t *self, gentity_t *attacker, int damage ) { G_AddEvent(self, EV_OBELISKPAIN, 0); } self->activator->s.frame = 1; - AddScore(attacker, self->r.currentOrigin, actualDamage); + AddScore(attacker, self->r.currentOrigin, actualDamage, "OBELISK"); } -gentity_t *SpawnObelisk( vec3_t origin, int team, int spawnflags) { - trace_t tr; - vec3_t dest; +// spawn invisible damagable obelisk entity / harvester base trigger. +gentity_t *SpawnObelisk( vec3_t origin, vec3_t mins, vec3_t maxs, int team ) { gentity_t *ent; ent = G_Spawn(); @@ -1324,8 +1311,8 @@ gentity_t *SpawnObelisk( vec3_t origin, int team, int spawnflags) { VectorCopy( origin, ent->s.pos.trBase ); VectorCopy( origin, ent->r.currentOrigin ); - VectorSet( ent->r.mins, -15, -15, 0 ); - VectorSet( ent->r.maxs, 15, 15, 87 ); + VectorCopy( mins, ent->r.mins ); + VectorCopy( maxs, ent->r.maxs ); ent->s.eType = ET_GENERAL; ent->flags = FL_NO_KNOCKBACK; @@ -1344,7 +1331,26 @@ gentity_t *SpawnObelisk( vec3_t origin, int team, int spawnflags) { ent->touch = ObeliskTouch; } - if ( spawnflags & 1 ) { + G_SetOrigin( ent, ent->s.origin ); + + ent->spawnflags = team; + + trap_LinkEntity( ent ); + + return ent; +} + +// setup entity for team base model / obelisk model. +void ObeliskInit( gentity_t *ent ) { + trace_t tr; + vec3_t dest; + + ent->s.eType = ET_TEAM; + + VectorSet( ent->r.mins, -15, -15, 0 ); + VectorSet( ent->r.maxs, 15, 15, 87 ); + + if ( ent->spawnflags & 1 ) { // suspended G_SetOrigin( ent, ent->s.origin ); } else { @@ -1368,12 +1374,6 @@ gentity_t *SpawnObelisk( vec3_t origin, int team, int spawnflags) { G_SetOrigin( ent, tr.endpos ); } } - - ent->spawnflags = team; - - trap_LinkEntity( ent ); - - return ent; } /*QUAKED team_redobelisk (1 0 0) (-16 -16 0) (16 16 8) @@ -1385,16 +1385,16 @@ void SP_team_redobelisk( gentity_t *ent ) { G_FreeEntity(ent); return; } - ent->s.eType = ET_TEAM; + ObeliskInit( ent ); if ( g_gametype.integer == GT_OBELISK ) { - obelisk = SpawnObelisk( ent->s.origin, TEAM_RED, ent->spawnflags ); + obelisk = SpawnObelisk( ent->s.origin, ent->r.mins, ent->r.maxs, TEAM_RED ); obelisk->activator = ent; // initial obelisk health value ent->s.modelindex2 = 0xff; ent->s.frame = 0; } if ( g_gametype.integer == GT_HARVESTER ) { - obelisk = SpawnObelisk( ent->s.origin, TEAM_RED, ent->spawnflags ); + obelisk = SpawnObelisk( ent->s.origin, ent->r.mins, ent->r.maxs, TEAM_RED ); obelisk->activator = ent; } ent->s.modelindex = TEAM_RED; @@ -1410,16 +1410,16 @@ void SP_team_blueobelisk( gentity_t *ent ) { G_FreeEntity(ent); return; } - ent->s.eType = ET_TEAM; + ObeliskInit( ent ); if ( g_gametype.integer == GT_OBELISK ) { - obelisk = SpawnObelisk( ent->s.origin, TEAM_BLUE, ent->spawnflags ); + obelisk = SpawnObelisk( ent->s.origin, ent->r.mins, ent->r.maxs, TEAM_BLUE ); obelisk->activator = ent; // initial obelisk health value ent->s.modelindex2 = 0xff; ent->s.frame = 0; } if ( g_gametype.integer == GT_HARVESTER ) { - obelisk = SpawnObelisk( ent->s.origin, TEAM_BLUE, ent->spawnflags ); + obelisk = SpawnObelisk( ent->s.origin, ent->r.mins, ent->r.maxs, TEAM_BLUE ); obelisk->activator = ent; } ent->s.modelindex = TEAM_BLUE; @@ -1433,10 +1433,10 @@ void SP_team_neutralobelisk( gentity_t *ent ) { G_FreeEntity(ent); return; } - ent->s.eType = ET_TEAM; + ObeliskInit( ent ); if ( g_gametype.integer == GT_HARVESTER) { - neutralObelisk = SpawnObelisk( ent->s.origin, TEAM_FREE, ent->spawnflags); - neutralObelisk->spawnflags = TEAM_FREE; + neutralObelisk = SpawnObelisk( ent->s.origin, ent->r.mins, ent->r.maxs, TEAM_FREE ); + neutralObelisk->activator = ent; } ent->s.modelindex = TEAM_FREE; trap_LinkEntity(ent); diff --git a/engine/code/game/g_trigger.c b/engine/code/game/g_trigger.c index 74613918..2b29ceff 100644 --- a/engine/code/game/g_trigger.c +++ b/engine/code/game/g_trigger.c @@ -84,7 +84,7 @@ void Touch_Multi( gentity_t *self, gentity_t *other, trace_t *trace ) { multi_trigger( self, other ); } -/*QUAKED trigger_multiple (.5 .5 .5) ? +/*QUAKED trigger_multiple (.5 .5 .5) ? RED_ONLY BLUE_ONLY "wait" : Seconds between triggerings, 0.5 default, -1 = one time only. "random" wait variance, default is 0 Variable sized repeatable trigger. Must be targeted at one or more entities. @@ -283,13 +283,19 @@ void trigger_teleporter_touch (gentity_t *self, gentity_t *other, trace_t *trace return; } - dest = G_PickTarget( self->target ); if (!dest) { G_Printf ("Couldn't find teleporter destination\n"); return; } + if ( !dmlab_can_trigger(self->id, self->target) ) { + return; + } + if ( dmlab_override_trigger(self->id, self->target) ) { + return; + } + TeleportPlayer( other, dest->s.origin, dest->s.angles ); } @@ -401,6 +407,77 @@ void SP_trigger_hurt( gentity_t *self ) { } } +static void SwapElements(vec3_t matrix[3], int i, int j) { + float temp = matrix[i][j]; + matrix[i][j] = matrix[j][i]; + matrix[j][i] = temp; +} + +static void InverseRotation(const vec3_t angles, vec3_t point) { + vec3_t matrix[3]; + vec3_t tvec; + AngleVectors(angles, matrix[0], matrix[1], matrix[2]); + VectorInverse(matrix[1]); + // Transpose matrix. + SwapElements(matrix, 0, 1); + SwapElements(matrix, 0, 2); + SwapElements(matrix, 1, 2); + // Copy point because VectorRotate requires for the input and output vectors + // not to be aliases. + VectorCopy(point, tvec); + VectorRotate(tvec, matrix, point); +} + +static void BoxRelative(const vec3_t point, const vec3_t box_min, + const vec3_t box_max, vec3_t result) { + result[0] = (point[0] - box_min[0])/(box_max[0] - box_min[0]); + result[1] = (point[1] - box_min[1])/(box_max[1] - box_min[1]); + result[2] = (point[2] - box_min[2])/(box_max[2] - box_min[2]); +} + +static void MoveToEntitySpace(const gentity_t* entity, const vec3_t world_point, + vec3_t result) { + vec3_t local_point; + // Compute the displacement vector of 'world_point' with respect to the + // entity's origin. + VectorSubtract(world_point, entity->r.currentOrigin, local_point); + // Express such displacement vector in the coordinate frame spanned by the + // entity's bounding box. + InverseRotation(entity->r.currentAngles, local_point); + BoxRelative(local_point, entity->r.mins, entity->r.maxs, result); +} + +/* +============================================================================== + +trigger_lookat + +============================================================================== +*/ + +void func_lookat_look(gentity_t *self, gentity_t *other, const trace_t *trace) { + vec3_t local_point = {0.0, 0.0, 0.0}; + + if (!other->client) return; + if (other->client->ps.pm_type == PM_DEAD) return; + + if (trace) { + MoveToEntitySpace(self, trace->endpos, local_point); + } + dmlab_trigger_lookat( self->id, trace != NULL, local_point ); +} + +/*QUAKED trigger_lookat +Activation caused while looking at the entity. Can be overriden by the level +API. +A final call is issued (with looked_at = false) when the player looks away from +the trigger. +*/ +void SP_trigger_lookat(gentity_t *self) { + InitTrigger(self); + self->r.contents |= CONTENTS_LOOKAT; + self->look = func_lookat_look; +} /* ============================================================================== diff --git a/engine/code/game/g_utils.c b/engine/code/game/g_utils.c index 41781556..c178ca45 100644 --- a/engine/code/game/g_utils.c +++ b/engine/code/game/g_utils.c @@ -249,6 +249,14 @@ void G_UseTargets( gentity_t *ent, gentity_t *activator ) { return; } + if ( !dmlab_can_trigger(ent->id, ent->target) ) { + return; + } + + if ( dmlab_override_trigger(ent->id, ent->target) ) { + return; + } + t = NULL; while ( (t = G_Find (t, FOFS(targetname), ent->target)) != NULL ) { if ( t == ent ) { @@ -391,7 +399,6 @@ gentity_t *G_Spawn( void ) { gentity_t *e; e = NULL; // shut up warning - i = 0; // shut up warning for ( force = 0 ; force < 2 ; force++ ) { // if we go through all entities and can't find one to free, // override the normal minimum times before use @@ -411,11 +418,11 @@ gentity_t *G_Spawn( void ) { G_InitGentity( e ); return e; } - if ( i != MAX_GENTITIES ) { + if ( level.num_entities < ENTITYNUM_MAX_NORMAL ) { break; } } - if ( i == ENTITYNUM_MAX_NORMAL ) { + if ( level.num_entities == ENTITYNUM_MAX_NORMAL ) { for (i = 0; i < MAX_GENTITIES; i++) { G_Printf("%4i: %s\n", i, g_entities[i].classname); } @@ -442,6 +449,11 @@ qboolean G_EntitiesFree( void ) { int i; gentity_t *e; + if ( level.num_entities < ENTITYNUM_MAX_NORMAL ) { + // can open a new slot if needed + return qtrue; + } + e = &g_entities[MAX_CLIENTS]; for ( i = MAX_CLIENTS; i < level.num_entities; i++, e++) { if ( e->inuse ) { diff --git a/engine/code/game/g_weapon.c b/engine/code/game/g_weapon.c index ecf65057..e4d32f9c 100644 --- a/engine/code/game/g_weapon.c +++ b/engine/code/game/g_weapon.c @@ -275,6 +275,7 @@ qboolean ShotgunPellet( vec3_t start, vec3_t end, gentity_t *ent ) { vec3_t impactpoint, bouncedir; #endif vec3_t tr_start, tr_end; + qboolean hitClient = qfalse; passent = ent->s.number; VectorCopy( start, tr_start ); @@ -304,19 +305,12 @@ qboolean ShotgunPellet( vec3_t start, vec3_t end, gentity_t *ent ) { } continue; } - else { - G_Damage( traceEnt, ent, ent, forward, tr.endpos, - damage, 0, MOD_SHOTGUN); - if( LogAccuracyHit( traceEnt, ent ) ) { - return qtrue; - } - } -#else - G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD_SHOTGUN); - if( LogAccuracyHit( traceEnt, ent ) ) { - return qtrue; - } #endif + if( LogAccuracyHit( traceEnt, ent ) ) { + hitClient = qtrue; + } + G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD_SHOTGUN); + return hitClient; } return qfalse; } @@ -663,14 +657,11 @@ void Weapon_LightningFire( gentity_t *ent ) { } continue; } - else { - G_Damage( traceEnt, ent, ent, forward, tr.endpos, - damage, 0, MOD_LIGHTNING); - } -#else - G_Damage( traceEnt, ent, ent, forward, tr.endpos, - damage, 0, MOD_LIGHTNING); #endif + if( LogAccuracyHit( traceEnt, ent ) ) { + ent->client->accuracy_hits++; + } + G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD_LIGHTNING); } if ( traceEnt->takedamage && traceEnt->client ) { @@ -678,9 +669,6 @@ void Weapon_LightningFire( gentity_t *ent ) { tent->s.otherEntityNum = traceEnt->s.number; tent->s.eventParm = DirToByte( tr.plane.normal ); tent->s.weapon = ent->s.weapon; - if( LogAccuracyHit( traceEnt, ent ) ) { - ent->client->accuracy_hits++; - } } else if ( !( tr.surfaceFlags & SURF_NOIMPACT ) ) { tent = G_TempEntity( tr.endpos, EV_MISSILE_MISS ); tent->s.eventParm = DirToByte( tr.plane.normal ); diff --git a/engine/code/q3_ui/ui_atoms.c b/engine/code/q3_ui/ui_atoms.c index 3412dbac..3a3c3dcd 100644 --- a/engine/code/q3_ui/ui_atoms.c +++ b/engine/code/q3_ui/ui_atoms.c @@ -872,17 +872,21 @@ UI_MouseEvent void UI_MouseEvent( int dx, int dy ) { int i; + int bias; menucommon_s* m; if (!uis.activemenu) return; + // convert X bias to 640 coords + bias = uis.bias / uis.xscale; + // update mouse screen position uis.cursorx += dx; - if (uis.cursorx < -uis.bias) - uis.cursorx = -uis.bias; - else if (uis.cursorx > SCREEN_WIDTH+uis.bias) - uis.cursorx = SCREEN_WIDTH+uis.bias; + if (uis.cursorx < -bias) + uis.cursorx = -bias; + else if (uis.cursorx > SCREEN_WIDTH+bias) + uis.cursorx = SCREEN_WIDTH+bias; uis.cursory += dy; if (uis.cursory < 0) diff --git a/engine/code/q3_ui/ui_local.h b/engine/code/q3_ui/ui_local.h index 72979a9e..6bb0fb31 100644 --- a/engine/code/q3_ui/ui_local.h +++ b/engine/code/q3_ui/ui_local.h @@ -486,6 +486,9 @@ typedef struct { animation_t animations[MAX_ANIMATIONS]; + qboolean fixedlegs; // true if legs yaw is always the same as torso yaw + qboolean fixedtorso; // true if torso never changes yaw + qhandle_t weaponModel; qhandle_t barrelModel; qhandle_t flashModel; diff --git a/engine/code/q3_ui/ui_main.c b/engine/code/q3_ui/ui_main.c index 1dd7e89f..216269cf 100644 --- a/engine/code/q3_ui/ui_main.c +++ b/engine/code/q3_ui/ui_main.c @@ -185,7 +185,7 @@ static cvarTable_t cvarTable[] = { { &ui_spSelection, "ui_spSelection", "", CVAR_ROM }, - { &ui_browserMaster, "ui_browserMaster", "0", CVAR_ARCHIVE }, + { &ui_browserMaster, "ui_browserMaster", "1", CVAR_ARCHIVE }, { &ui_browserGameType, "ui_browserGameType", "0", CVAR_ARCHIVE }, { &ui_browserSortKey, "ui_browserSortKey", "4", CVAR_ARCHIVE }, { &ui_browserShowFull, "ui_browserShowFull", "1", CVAR_ARCHIVE }, @@ -214,7 +214,8 @@ static cvarTable_t cvarTable[] = { { &ui_server16, "server16", "", CVAR_ARCHIVE }, { &ui_cdkeychecked, "ui_cdkeychecked", "0", CVAR_ROM }, - { &ui_ioq3, "ui_ioq3", "1", CVAR_ROM } + { &ui_ioq3, "ui_ioq3", "1", CVAR_ROM }, + { NULL, "g_localTeamPref", "", 0 } }; static int cvarTableSize = ARRAY_LEN( cvarTable ); @@ -244,6 +245,10 @@ void UI_UpdateCvars( void ) { cvarTable_t *cv; for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) { + if ( !cv->vmCvar ) { + continue; + } + trap_Cvar_Update( cv->vmCvar ); } } diff --git a/engine/code/q3_ui/ui_players.c b/engine/code/q3_ui/ui_players.c index 5c75042e..e8347db8 100644 --- a/engine/code/q3_ui/ui_players.c +++ b/engine/code/q3_ui/ui_players.c @@ -363,7 +363,7 @@ UI_RunLerpFrame =============== */ static void UI_RunLerpFrame( playerInfo_t *ci, lerpFrame_t *lf, int newAnimation ) { - int f; + int f, numFrames; animation_t *anim; // see if the animation sequence is switching @@ -379,25 +379,41 @@ static void UI_RunLerpFrame( playerInfo_t *ci, lerpFrame_t *lf, int newAnimation // get the next frame based on the animation anim = lf->animation; + if ( !anim->frameLerp ) { + return; // shouldn't happen + } if ( dp_realtime < lf->animationTime ) { lf->frameTime = lf->animationTime; // initial lerp } else { lf->frameTime = lf->oldFrameTime + anim->frameLerp; } f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; - if ( f >= anim->numFrames ) { - f -= anim->numFrames; + + numFrames = anim->numFrames; + if (anim->flipflop) { + numFrames *= 2; + } + if ( f >= numFrames ) { + f -= numFrames; if ( anim->loopFrames ) { f %= anim->loopFrames; f += anim->numFrames - anim->loopFrames; } else { - f = anim->numFrames - 1; + f = numFrames - 1; // the animation is stuck at the end, so it // can immediately transition to another sequence lf->frameTime = dp_realtime; } } - lf->frame = anim->firstFrame + f; + if ( anim->reversed ) { + lf->frame = anim->firstFrame + anim->numFrames - 1 - f; + } + else if (anim->flipflop && f>=anim->numFrames) { + lf->frame = anim->firstFrame + anim->numFrames - 1 - (f%anim->numFrames); + } + else { + lf->frame = anim->firstFrame + f; + } if ( dp_realtime > lf->frameTime ) { lf->frameTime = dp_realtime; } @@ -615,6 +631,16 @@ static void UI_PlayerAngles( playerInfo_t *pi, vec3_t legs[3], vec3_t torso[3], UI_SwingAngles( dest, 15, 30, 0.1f, &pi->torso.pitchAngle, &pi->torso.pitching ); torsoAngles[PITCH] = pi->torso.pitchAngle; + if ( pi->fixedtorso ) { + torsoAngles[PITCH] = 0.0f; + } + + if ( pi->fixedlegs ) { + legsAngles[YAW] = torsoAngles[YAW]; + legsAngles[PITCH] = 0.0f; + legsAngles[ROLL] = 0.0f; + } + // pull the angles back out of the hierarchial chain AnglesSubtract( headAngles, torsoAngles, headAngles ); AnglesSubtract( torsoAngles, legsAngles, torsoAngles ); @@ -930,7 +956,7 @@ static qboolean UI_RegisterClientSkin( playerInfo_t *pi, const char *modelName, UI_ParseAnimationFile ====================== */ -static qboolean UI_ParseAnimationFile( const char *filename, animation_t *animations ) { +static qboolean UI_ParseAnimationFile( const char *filename, playerInfo_t *pi ) { char *text_p, *prev; int len; int i; @@ -939,9 +965,15 @@ static qboolean UI_ParseAnimationFile( const char *filename, animation_t *animat int skip; char text[20000]; fileHandle_t f; + animation_t *animations; + + animations = pi->animations; memset( animations, 0, sizeof( animation_t ) * MAX_ANIMATIONS ); + pi->fixedlegs = qfalse; + pi->fixedtorso = qfalse; + // load the file len = trap_FS_FOpenFile( filename, &f, FS_READ ); if ( len <= 0 ) { @@ -964,29 +996,35 @@ static qboolean UI_ParseAnimationFile( const char *filename, animation_t *animat while ( 1 ) { prev = text_p; // so we can unget token = COM_Parse( &text_p ); - if ( !token ) { + if ( !token[0] ) { break; } if ( !Q_stricmp( token, "footsteps" ) ) { token = COM_Parse( &text_p ); - if ( !token ) { + if ( !token[0] ) { break; } continue; } else if ( !Q_stricmp( token, "headoffset" ) ) { for ( i = 0 ; i < 3 ; i++ ) { token = COM_Parse( &text_p ); - if ( !token ) { + if ( !token[0] ) { break; } } continue; } else if ( !Q_stricmp( token, "sex" ) ) { token = COM_Parse( &text_p ); - if ( !token ) { + if ( !token[0] ) { break; } continue; + } else if ( !Q_stricmp( token, "fixedlegs" ) ) { + pi->fixedlegs = qtrue; + continue; + } else if ( !Q_stricmp( token, "fixedtorso" ) ) { + pi->fixedtorso = qtrue; + continue; } // if it is a number, start parsing animations @@ -1002,7 +1040,17 @@ static qboolean UI_ParseAnimationFile( const char *filename, animation_t *animat for ( i = 0 ; i < MAX_ANIMATIONS ; i++ ) { token = COM_Parse( &text_p ); - if ( !token ) { + if ( !token[0] ) { + if( i >= TORSO_GETFLAG && i <= TORSO_NEGATIVE ) { + animations[i].firstFrame = animations[TORSO_GESTURE].firstFrame; + animations[i].frameLerp = animations[TORSO_GESTURE].frameLerp; + animations[i].initialLerp = animations[TORSO_GESTURE].initialLerp; + animations[i].loopFrames = animations[TORSO_GESTURE].loopFrames; + animations[i].numFrames = animations[TORSO_GESTURE].numFrames; + animations[i].reversed = qfalse; + animations[i].flipflop = qfalse; + continue; + } break; } animations[i].firstFrame = atoi( token ); @@ -1010,24 +1058,32 @@ static qboolean UI_ParseAnimationFile( const char *filename, animation_t *animat if ( i == LEGS_WALKCR ) { skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame; } - if ( i >= LEGS_WALKCR ) { + if ( i >= LEGS_WALKCR && ianimations ) ) { + if ( !UI_ParseAnimationFile( filename, pi ) ) { Com_Printf( "Failed to load animation file %s\n", filename ); return qfalse; } diff --git a/engine/code/q3_ui/ui_qmenu.c b/engine/code/q3_ui/ui_qmenu.c index 95066bf5..fafea9b6 100644 --- a/engine/code/q3_ui/ui_qmenu.c +++ b/engine/code/q3_ui/ui_qmenu.c @@ -1035,6 +1035,50 @@ sfxHandle_t ScrollList_Key( menulist_s *l, int key ) } return (menu_buzz_sound); + case K_MWHEELUP: + if( l->columns > 1 ) { + return menu_null_sound; + } + + if (l->top > 0) + { + // if scrolling 3 lines would replace over half of the + // displayed items, only scroll 1 item at a time. + int scroll = l->height < 6 ? 1 : 3; + l->top -= scroll; + if (l->top < 0) + l->top = 0; + + if (l->generic.callback) + l->generic.callback( l, QM_GOTFOCUS ); + + // make scrolling silent + return (menu_null_sound); + } + return (menu_buzz_sound); + + case K_MWHEELDOWN: + if( l->columns > 1 ) { + return menu_null_sound; + } + + if (l->top < l->numitems-l->height) + { + // if scrolling 3 items would replace over half of the + // displayed items, only scroll 1 item at a time. + int scroll = l->height < 6 ? 1 : 3; + l->top += scroll; + if (l->top > l->numitems-l->height) + l->top = l->numitems-l->height; + + if (l->generic.callback) + l->generic.callback( l, QM_GOTFOCUS ); + + // make scrolling silent + return (menu_null_sound); + } + return (menu_buzz_sound); + case K_KP_UPARROW: case K_UPARROW: if( l->curvalue == 0 ) { diff --git a/engine/code/q3_ui/ui_servers2.c b/engine/code/q3_ui/ui_servers2.c index c5c4c743..e709925e 100644 --- a/engine/code/q3_ui/ui_servers2.c +++ b/engine/code/q3_ui/ui_servers2.c @@ -81,14 +81,15 @@ MULTIPLAYER MENU (SERVER BROWSER) #define GR_LETTERS 31 #define UIAS_LOCAL 0 -#define UIAS_GLOBAL1 1 -#define UIAS_GLOBAL2 2 -#define UIAS_GLOBAL3 3 -#define UIAS_GLOBAL4 4 -#define UIAS_GLOBAL5 5 -#define UIAS_FAVORITES 6 +#define UIAS_GLOBAL0 1 +#define UIAS_GLOBAL1 2 +#define UIAS_GLOBAL2 3 +#define UIAS_GLOBAL3 4 +#define UIAS_GLOBAL4 5 +#define UIAS_GLOBAL5 6 +#define UIAS_FAVORITES 7 -#define UI_MAX_MASTER_SERVERS 5 +#define UI_MAX_MASTER_SERVERS 6 #define SORT_HOST 0 #define SORT_MAP 1 @@ -104,11 +105,12 @@ MULTIPLAYER MENU (SERVER BROWSER) static const char *master_items[] = { "Local", - "Internet1", - "Internet2", - "Internet3", - "Internet4", - "Internet5", + "Internet", + "Master1", + "Master2", + "Master3", + "Master4", + "Master5", "Favorites", NULL }; @@ -347,6 +349,7 @@ int ArenaServers_SourceForLAN(void) { default: case UIAS_LOCAL: return AS_LOCAL; + case UIAS_GLOBAL0: case UIAS_GLOBAL1: case UIAS_GLOBAL2: case UIAS_GLOBAL3: @@ -421,7 +424,6 @@ static void ArenaServers_UpdateMenu( void ) { } else { // all servers pinged - enable controls - g_arenaservers.master.generic.flags &= ~QMF_GRAYED; g_arenaservers.gametype.generic.flags &= ~QMF_GRAYED; g_arenaservers.sortkey.generic.flags &= ~QMF_GRAYED; g_arenaservers.showempty.generic.flags &= ~QMF_GRAYED; @@ -432,7 +434,7 @@ static void ArenaServers_UpdateMenu( void ) { g_arenaservers.punkbuster.generic.flags &= ~QMF_GRAYED; // update status bar - if( g_servertype >= UIAS_GLOBAL1 && g_servertype <= UIAS_GLOBAL5 ) { + if( g_servertype >= UIAS_GLOBAL0 && g_servertype <= UIAS_GLOBAL5 ) { g_arenaservers.statusbar.string = quake3worldMessage; } else { @@ -448,7 +450,6 @@ static void ArenaServers_UpdateMenu( void ) { g_arenaservers.statusbar.string = "Press SPACE to stop"; // disable controls during refresh - g_arenaservers.master.generic.flags |= QMF_GRAYED; g_arenaservers.gametype.generic.flags |= QMF_GRAYED; g_arenaservers.sortkey.generic.flags |= QMF_GRAYED; g_arenaservers.showempty.generic.flags |= QMF_GRAYED; @@ -467,7 +468,7 @@ static void ArenaServers_UpdateMenu( void ) { } // update status bar - if( g_servertype >= UIAS_GLOBAL1 && g_servertype <= UIAS_GLOBAL5 ) { + if( g_servertype >= UIAS_GLOBAL0 && g_servertype <= UIAS_GLOBAL5 ) { g_arenaservers.statusbar.string = quake3worldMessage; } else { @@ -719,38 +720,6 @@ static void ArenaServers_Insert( char* adrstr, char* info, int pingtime ) } -/* -================= -ArenaServers_InsertFavorites - -Insert nonresponsive address book entries into display lists. -================= -*/ -void ArenaServers_InsertFavorites( void ) -{ - int i; - int j; - char info[MAX_INFO_STRING]; - - // resync existing results with new or deleted cvars - info[0] = '\0'; - Info_SetValueForKey( info, "hostname", "No Response" ); - for (i=0; i= g_numfavoriteservers) - { - // not in list, add it - ArenaServers_Insert( g_arenaservers.favoriteaddresses[i], info, ArenaServers_MaxPing() ); - } - } -} - - /* ================= ArenaServers_LoadFavorites @@ -834,12 +803,6 @@ static void ArenaServers_StopRefresh( void ) g_arenaservers.refreshservers = qfalse; - if (g_servertype == UIAS_FAVORITES) - { - // nonresponsive favorites must be shown - ArenaServers_InsertFavorites(); - } - // final tally if (g_arenaservers.numqueriedservers >= 0) { @@ -881,6 +844,13 @@ static void ArenaServers_DoRefresh( void ) return; } } + } else if (g_servertype == UIAS_LOCAL) { + if (!trap_LAN_GetServerCount(AS_LOCAL)) { + // no local servers found, check again + trap_Cmd_ExecuteText( EXEC_APPEND, "localservers\n" ); + g_arenaservers.refreshtime = uis.realtime + 5000; + return; + } } if (uis.realtime < g_arenaservers.nextpingtime) @@ -926,6 +896,12 @@ static void ArenaServers_DoRefresh( void ) // stale it out info[0] = '\0'; time = maxPing; + + // set hostname for nonresponsive favorite server + if (g_servertype == UIAS_FAVORITES) { + Info_SetValueForKey( info, "hostname", adrstr ); + Info_SetValueForKey( info, "game", "???" ); + } } else { @@ -1037,7 +1013,7 @@ static void ArenaServers_StartRefresh( void ) return; } - if( g_servertype >= UIAS_GLOBAL1 && g_servertype <= UIAS_GLOBAL5 ) { + if( g_servertype >= UIAS_GLOBAL0 && g_servertype <= UIAS_GLOBAL5 ) { switch( g_arenaservers.gametype.curvalue ) { default: case GAMES_ALL: @@ -1073,10 +1049,10 @@ static void ArenaServers_StartRefresh( void ) protocol[0] = '\0'; trap_Cvar_VariableStringBuffer( "debug_protocol", protocol, sizeof(protocol) ); if (strlen(protocol)) { - trap_Cmd_ExecuteText( EXEC_APPEND, va( "globalservers %d %s%s\n", g_servertype - 1, protocol, myargs )); + trap_Cmd_ExecuteText( EXEC_APPEND, va( "globalservers %d %s%s\n", g_servertype - UIAS_GLOBAL0, protocol, myargs )); } else { - trap_Cmd_ExecuteText( EXEC_APPEND, va( "globalservers %d %d%s\n", g_servertype - 1, (int)trap_Cvar_VariableValue( "protocol" ), myargs ) ); + trap_Cmd_ExecuteText( EXEC_APPEND, va( "globalservers %d %d%s\n", g_servertype - UIAS_GLOBAL0, (int)trap_Cvar_VariableValue( "protocol" ), myargs ) ); } } } @@ -1121,18 +1097,27 @@ ArenaServers_SetType */ int ArenaServers_SetType( int type ) { + ArenaServers_StopRefresh(); + if(type >= UIAS_GLOBAL1 && type <= UIAS_GLOBAL5) { char masterstr[2], cvarname[sizeof("sv_master1")]; + int direction; - while(type <= UIAS_GLOBAL5) + if (type == g_servertype || type == ((g_servertype+1) % (ARRAY_LEN(master_items)-1))) { + direction = 1; + } else { + direction = -1; + } + + while(type >= UIAS_GLOBAL1 && type <= UIAS_GLOBAL5) { - Com_sprintf(cvarname, sizeof(cvarname), "sv_master%d", type); + Com_sprintf(cvarname, sizeof(cvarname), "sv_master%d", type - UIAS_GLOBAL0); trap_Cvar_VariableStringBuffer(cvarname, masterstr, sizeof(masterstr)); if(*masterstr) break; - type++; + type += direction; } } @@ -1147,14 +1132,15 @@ int ArenaServers_SetType( int type ) g_arenaservers.maxservers = MAX_LOCALSERVERS; break; + case UIAS_GLOBAL0: case UIAS_GLOBAL1: case UIAS_GLOBAL2: case UIAS_GLOBAL3: case UIAS_GLOBAL4: case UIAS_GLOBAL5: g_arenaservers.remove.generic.flags |= (QMF_INACTIVE|QMF_HIDDEN); - g_arenaservers.serverlist = g_globalserverlist[type-UIAS_GLOBAL1]; - g_arenaservers.numservers = &g_numglobalservers[type-UIAS_GLOBAL1]; + g_arenaservers.serverlist = g_globalserverlist[type-UIAS_GLOBAL0]; + g_arenaservers.numservers = &g_numglobalservers[type-UIAS_GLOBAL0]; g_arenaservers.maxservers = MAX_GLOBALSERVERS; break; diff --git a/engine/code/q3_ui/ui_startserver.c b/engine/code/q3_ui/ui_startserver.c index 6de0df2f..fdbfd415 100644 --- a/engine/code/q3_ui/ui_startserver.c +++ b/engine/code/q3_ui/ui_startserver.c @@ -119,7 +119,7 @@ static int GametypeBits( char *string ) { p = string; while( 1 ) { token = COM_ParseExt( &p, qfalse ); - if( token[0] == 0 ) { + if ( !token[0] ) { break; } @@ -331,6 +331,7 @@ static void StartServer_LevelshotDraw( void *self ) { int h; int n; const char *info; + char mapname[ MAX_NAMELENGTH ]; b = (menubitmap_s *)self; @@ -366,7 +367,9 @@ static void StartServer_LevelshotDraw( void *self ) { n = s_startserver.page * MAX_MAPSPERPAGE + b->generic.id - ID_PICTURES; info = UI_GetArenaInfoByNumber( s_startserver.maplist[ n ]); - UI_DrawString( x, y, Info_ValueForKey( info, "map" ), UI_CENTER|UI_SMALLFONT, color_blue ); + Q_strncpyz( mapname, Info_ValueForKey( info, "map"), MAX_NAMELENGTH ); + Q_strupr( mapname ); + UI_DrawString( x, y, mapname, UI_CENTER|UI_SMALLFONT, color_blue ); x = b->generic.x; y = b->generic.y; @@ -815,7 +818,11 @@ static void ServerOptions_Start( void ) { // set player's team if( dedicated == 0 && s_serveroptions.gametype >= GT_TEAM ) { + // send team command for vanilla q3 game qvm trap_Cmd_ExecuteText( EXEC_APPEND, va( "wait 5; team %s\n", playerTeam_list[s_serveroptions.playerTeam[0].curvalue] ) ); + + // set g_localTeamPref for ioq3 game qvm + trap_Cvar_Set( "g_localTeamPref", playerTeam_list[s_serveroptions.playerTeam[0].curvalue] ); } } diff --git a/engine/code/qcommon/cm_patch.c b/engine/code/qcommon/cm_patch.c index e4b90002..c284e4d5 100644 --- a/engine/code/qcommon/cm_patch.c +++ b/engine/code/qcommon/cm_patch.c @@ -826,7 +826,7 @@ CM_AddFacetBevels void CM_AddFacetBevels( facet_t *facet ) { int i, j, k, l; - int axis, dir, order, flipped; + int axis, dir, flipped; float plane[4], d, newplane[4]; winding_t *w, *w2; vec3_t mins, maxs, vec, vec2; @@ -852,10 +852,9 @@ void CM_AddFacetBevels( facet_t *facet ) { WindingBounds(w, mins, maxs); // add the axial planes - order = 0; for ( axis = 0 ; axis < 3 ; axis++ ) { - for ( dir = -1 ; dir <= 1 ; dir += 2, order++ ) + for ( dir = -1 ; dir <= 1 ; dir += 2 ) { VectorClear(plane); plane[axis] = dir; @@ -869,7 +868,7 @@ void CM_AddFacetBevels( facet_t *facet ) { if (CM_PlaneEqual(&planes[facet->surfacePlane], plane, &flipped)) { continue; } - // see if the plane is allready present + // see if the plane is already present for ( i = 0 ; i < facet->numBorders ; i++ ) { if (CM_PlaneEqual(&planes[facet->borderPlanes[i]], plane, &flipped)) break; @@ -933,7 +932,7 @@ void CM_AddFacetBevels( facet_t *facet ) { if (CM_PlaneEqual(&planes[facet->surfacePlane], plane, &flipped)) { continue; } - // see if the plane is allready present + // see if the plane is already present for ( i = 0 ; i < facet->numBorders ; i++ ) { if (CM_PlaneEqual(&planes[facet->borderPlanes[i]], plane, &flipped)) { break; diff --git a/engine/code/qcommon/common.c b/engine/code/qcommon/common.c index 83affe5c..c63392ec 100644 --- a/engine/code/qcommon/common.c +++ b/engine/code/qcommon/common.c @@ -32,6 +32,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include #endif +// #define OVERWRITE_FREED_MEMORY + int demo_protocols[] = { 67, 66, 0 }; @@ -97,6 +99,9 @@ cvar_t *com_legacyprotocol; cvar_t *com_basegame; cvar_t *com_homepath; cvar_t *com_busyWait; +#ifndef DEDICATED +cvar_t *con_autochat; +#endif cvar_t *com_writeConfig; #if idx64 @@ -494,9 +499,9 @@ void Com_StartupVariable( const char *match ) { if(!match || !strcmp(s, match)) { if(Cvar_Flags(s) == CVAR_NONEXISTENT) - Cvar_Get(s, Cmd_Argv(2), CVAR_USER_CREATED); + Cvar_Get(s, Cmd_ArgsFrom(2), CVAR_USER_CREATED); else - Cvar_Set2(s, Cmd_Argv(2), qfalse); + Cvar_Set2(s, Cmd_ArgsFrom(2), qfalse); } } } @@ -525,7 +530,7 @@ qboolean Com_AddStartupCommands( void ) { } // set commands already added with Com_StartupVariable - if ( !Q_stricmpn( com_consoleLines[i], "set", 3 ) ) { + if ( !Q_stricmpn( com_consoleLines[i], "set ", 4 ) ) { continue; } @@ -882,9 +887,11 @@ void Z_Free( void *ptr ) { } zone->used -= block->size; +#ifdef OVERWRITE_FREED_MEMORY // set the block to something that should cause problems // if it is referenced... Com_Memset( ptr, 0xaa, block->size - sizeof( *block ) ); +#endif block->tag = 0; // mark as free @@ -918,7 +925,6 @@ Z_FreeTags ================ */ void Z_FreeTags( int tag ) { - int count; memzone_t *zone; if ( tag == TAG_SMALL ) { @@ -927,13 +933,11 @@ void Z_FreeTags( int tag ) { else { zone = mainzone; } - count = 0; // use the rover as our pointer, because // Z_Free automatically adjusts it zone->rover = zone->blocklist.next; do { if ( zone->rover->tag == tag ) { - count++; Z_Free( (void *)(zone->rover + 1) ); continue; } @@ -1287,7 +1291,7 @@ Com_Meminfo_f void Com_Meminfo_f( void ) { memblock_t *block; int zoneBytes, zoneBlocks; - int smallZoneBytes, smallZoneBlocks; + int smallZoneBytes; int botlibBytes, rendererBytes; int unused; @@ -1325,11 +1329,9 @@ void Com_Meminfo_f( void ) { } smallZoneBytes = 0; - smallZoneBlocks = 0; for (block = smallzone->blocklist.next ; ; block = block->next) { if ( block->tag ) { smallZoneBytes += block->size; - smallZoneBlocks++; } if (block->next == &smallzone->blocklist) { @@ -1381,7 +1383,7 @@ Touch all known used data to make sure it is paged in void Com_TouchMemory( void ) { int start, end; int i, j; - int sum; + unsigned sum; memblock_t *block; Z_CheckHeap(); @@ -1966,6 +1968,19 @@ void Com_QueueEvent( int time, sysEventType_t type, int value, int value2, int p { sysEvent_t *ev; + // combine mouse movement with previous mouse event + if ( type == SE_MOUSE && eventHead != eventTail ) + { + ev = &eventQueue[ ( eventHead + MAX_QUEUED_EVENTS - 1 ) & MASK_QUEUED_EVENTS ]; + + if ( ev->evType == SE_MOUSE ) + { + ev->evValue += value; + ev->evValue2 += value2; + return; + } + } + ev = &eventQueue[ eventHead & MASK_QUEUED_EVENTS ]; if ( eventHead - eventTail >= MAX_QUEUED_EVENTS ) @@ -2612,7 +2627,7 @@ static void Com_DetectSSE(void) #endif Q_VMftol = qvmftolsse; - Com_Printf("Have SSE support\n"); + Com_Printf("SSE instruction set enabled\n"); #if !idx64 } else @@ -2621,7 +2636,7 @@ static void Com_DetectSSE(void) Q_VMftol = qvmftolx87; Q_SnapVector = qsnapvectorx87; - Com_Printf("No SSE support on this machine\n"); + Com_Printf("SSE instruction set not available\n"); } #endif } @@ -2796,6 +2811,10 @@ void Com_Init( char *commandLine ) { #endif Cvar_Get("protocol", com_protocol->string, CVAR_ROM); +#ifndef DEDICATED + con_autochat = Cvar_Get("con_autochat", "1", CVAR_ARCHIVE); +#endif + Sys_Init(); Sys_InitPIDFile( FS_GetCurrentGameDir() ); @@ -2992,6 +3011,13 @@ void Com_WriteConfig_f( void ) { Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) ); COM_DefaultExtension( filename, sizeof( filename ), ".cfg" ); + + if (!COM_CompareExtension(filename, ".cfg")) + { + Com_Printf("Com_WriteConfig_f: Only the \".cfg\" extension is supported by this command!\n"); + return; + } + Com_Printf( "Writing %s.\n", filename ); Com_WriteConfigToFile( filename ); } @@ -3153,8 +3179,10 @@ void Com_Frame( void ) { NET_Sleep(0); else NET_Sleep(timeVal - 1); - } while(Com_TimeVal(minMsec)); + } while(com_maxfps->integer && Com_TimeVal(minMsec)); + IN_Frame(); + lastTime = com_frameTime; com_frameTime = Com_EventLoop(); @@ -3484,8 +3512,8 @@ void Field_CompleteCommand( char *cmd, completionString = Cmd_Argv( completionArgument - 1 ); #ifndef DEDICATED - // Unconditionally add a '\' to the start of the buffer - if( completionField->buffer[ 0 ] && + // add a '\' to the start of the buffer if it might be sent as chat otherwise + if( con_autochat->integer && completionField->buffer[ 0 ] && completionField->buffer[ 0 ] != '\\' ) { if( completionField->buffer[ 0 ] != '/' ) diff --git a/engine/code/qcommon/cvar.c b/engine/code/qcommon/cvar.c index f07705db..4220869a 100644 --- a/engine/code/qcommon/cvar.c +++ b/engine/code/qcommon/cvar.c @@ -21,6 +21,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ // cvar.c -- dynamic variable tracking +#include #include "q_shared.h" #include "qcommon.h" @@ -503,6 +504,7 @@ Cvar_Set2 */ cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force ) { cvar_t *var; + double value_d; // Com_DPrintf( "Cvar_Set2: %s %s\n", var_name, value ); @@ -615,7 +617,13 @@ cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force ) { Z_Free (var->string); // free the old value string var->string = CopyString(value); - var->value = atof (var->string); + value_d = atof(var->string); + + // Ignore result of atof if var->string is not representable as a float. + if (-FLT_MAX <= value_d && value_d <= FLT_MAX) { + var->value = value_d; + } + var->integer = atoi (var->string); return var; diff --git a/engine/code/qcommon/deepmind_hooks.c b/engine/code/qcommon/deepmind_hooks.c index c7c5e396..70d0ea80 100644 --- a/engine/code/qcommon/deepmind_hooks.c +++ b/engine/code/qcommon/deepmind_hooks.c @@ -1,4 +1,4 @@ -// Copyright (C) 2016 Google Inc. +// Copyright (C) 2016-2017 Google Inc. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -28,46 +28,147 @@ int dmlab_callback( DeepmindContext* ctx = dmlab_context(); switch (dm_callnum) { case DEEPMIND_UPDATE_SPAWN_VARS: - return ctx->hooks.update_spawn_vars(ctx->userdata, VM_ArgPtr(a1), - VM_ArgPtr(a2), VM_ArgPtr(a3), - VM_ArgPtr(a4)); + return ctx->hooks.update_spawn_vars(ctx->userdata, + /*spawn_var_chars=*/VM_ArgPtr(a1), + /*num_spawn_var_chars=*/VM_ArgPtr(a2), + /*spawn_vars_offsets=*/VM_ArgPtr(a3), + /*num_spawn_vars=*/VM_ArgPtr(a4)); + + case DEEPMIND_MAKE_EXTRA_ENTITIES: + return ctx->hooks.make_extra_entities(ctx->userdata); + case DEEPMIND_READ_EXTRA_ENTITY: + ctx->hooks.read_extra_entity(ctx->userdata, + /*entity_index=*/a1, + /*spawn_var_chars=*/VM_ArgPtr(a2), + /*num_spawn_var_chars=*/VM_ArgPtr(a3), + /*spawn_vars_offsets=*/VM_ArgPtr(a4), + /*num_spawn_vars=*/VM_ArgPtr(a5)); + return 0; case DEEPMIND_FIND_ITEM: - return ctx->hooks.find_item(ctx->userdata, VM_ArgPtr(a1), VM_ArgPtr(a2)); + return ctx->hooks.find_item(ctx->userdata, /*class_name=*/VM_ArgPtr(a1), + /*index=*/VM_ArgPtr(a2)); case DEEPMIND_ITEM_COUNT: return ctx->hooks.item_count(ctx->userdata); case DEEPMIND_ITEM: - return ctx->hooks.item(ctx->userdata, a1, VM_ArgPtr(a2), a3, - VM_ArgPtr(a4), a5, VM_ArgPtr(a6), a7, - VM_ArgPtr(a8), VM_ArgPtr(a9), VM_ArgPtr(a10)); + return ctx->hooks.item( + ctx->userdata, /*index=*/a1, /*item_name=*/VM_ArgPtr(a2), + /*max_item_name=*/a3, + /*class_name=*/VM_ArgPtr(a4), /*max_class_name=*/a5, + /*model_name=*/VM_ArgPtr(a6), /*max_model_name=*/a7, + /*quantity=*/VM_ArgPtr(a8), /*type=*/VM_ArgPtr(a9), + /*tag=*/VM_ArgPtr(a10)); case DEEPMIND_CLEAR_ITEMS: ctx->hooks.clear_items(ctx->userdata); return 1; case DEEPMIND_FINISH_MAP: - ctx->hooks.set_map_finished(ctx->userdata, a1); + ctx->hooks.set_map_finished(ctx->userdata, /*map_finished=*/a1); return 1; case DEEPMIND_CAN_PICKUP: - return ctx->hooks.can_pickup(ctx->userdata, a1); + return ctx->hooks.can_pickup(ctx->userdata, /*entity_id=*/a1); case DEEPMIND_OVERRIDE_PICKUP: - return ctx->hooks.override_pickup(ctx->userdata, a1, VM_ArgPtr(a2)); - case DEEPMIND_EXTERNAL_REWARD: - return ctx->hooks.external_reward(ctx->userdata, a1); - case DEEPMIND_SET_PREDICTED_PLAYER_STATE: { + return ctx->hooks.override_pickup(ctx->userdata, /*entity_id=*/a1, + /*respawn=*/VM_ArgPtr(a2)); + case DEEPMIND_CAN_TRIGGER: + return ctx->hooks.can_trigger(ctx->userdata, /*entity_id=*/a1, + /*target_name=*/VM_ArgPtr(a2)); + case DEEPMIND_OVERRIDE_TRIGGER: + return ctx->hooks.override_trigger(ctx->userdata, /*entity_id=*/a1, + /*target_name=*/VM_ArgPtr(a2)); + case DEEPMIND_OVERRIDE_LOOKAT: + ctx->hooks.trigger_lookat(ctx->userdata, /*entity_id=*/a1, + /*looked_at=*/a2, + /*position=*/VM_ArgPtr(a3)); + break; + case DEEPMIND_REWARD_OVERRIDE: + return ctx->hooks.reward_override(ctx->userdata, + /*reason=*/VM_ArgPtr(a1), + /*player_id=*/a2, + /*team=*/a3, + /*other_player_id_opt=*/VM_ArgPtr(a4), + /*origin=*/VM_ArgPtr(a5), + /*score=*/a6); + case DEEPMIND_SET_PLAYER_STATE: { const playerState_t* ps = VM_ArgPtr(a1); - int timestamp_msec = ctx->calls.total_engine_time_msec(ctx->context); - ctx->hooks.predicted_player_state(ctx->userdata, ps->origin, ps->velocity, - ps->viewangles, ps->viewheight, - timestamp_msec); + int timestamp_msec = ctx->calls.total_engine_time_msec(); + ctx->hooks.player_state(ctx->userdata, ps->origin, ps->velocity, + ps->viewangles, ps->viewheight, + /*team_score=*/a2, + /*other_team_score=*/a3, ps->clientNum, + timestamp_msec); break; } case DEEPMIND_MAKE_SCREEN_MESSAGES: - return ctx->hooks.make_screen_messages(ctx->userdata, a1, a2, a3, a4); + return ctx->hooks.make_screen_messages(ctx->userdata, /*width=*/a1, + /*height=*/a2, /*line_height=*/a3, + /*string_buffer_size=*/a4); case DEEPMIND_GET_SCREEN_MESSAGE: - ctx->hooks.get_screen_message(ctx->userdata, a1, VM_ArgPtr(a2), - VM_ArgPtr(a3), VM_ArgPtr(a4), - VM_ArgPtr(a5)); + ctx->hooks.get_screen_message( + ctx->userdata, /*message_id=*/a1, /*buffer=*/VM_ArgPtr(a2), + /*x=*/VM_ArgPtr(a3), + /*y=*/VM_ArgPtr(a4), /*shadow=*/VM_ArgPtr(a5), + /*align_l0_r1_c2=*/VM_ArgPtr(a6), + /*rgba=*/VM_ArgPtr(a7)); + break; + case DEEPMIND_MAKE_FILLED_RECTANGLES: + return ctx->hooks.make_filled_rectangles(ctx->userdata, + /*screen_width=*/a1, + /*screen_height=*/a2); + case DEEPMIND_GET_FILLED_RECTANGLE: + ctx->hooks.get_filled_rectangle( + ctx->userdata, /*rectangle_id=*/a1, /*x=*/VM_ArgPtr(a2), + /*y=*/VM_ArgPtr(a3), + /*width=*/VM_ArgPtr(a4), /*height=*/VM_ArgPtr(a5), + /*rgba=*/VM_ArgPtr(a6)); break; case DEEPMIND_PLAYER_SCORE: return ctx->calls.player_score(ctx->context); + case DEEPMIND_LUA_MOVER: + ctx->hooks.lua_mover( + ctx->userdata, /*entity_id=*/a1, /*entity_pos=*/VM_ArgPtr(a2), + /*player_pos=*/VM_ArgPtr(a3), /*player_vel=*/VM_ArgPtr(a4), + /*player_pos_delta=*/VM_ArgPtr(a5), + /*player_vel_delta=*/VM_ArgPtr(a6)); + break; + case DEEPMIND_GAME_EVENT: + ctx->hooks.game_event(ctx->userdata, /*event_name=*/VM_ArgPtr(a1), + /*count=*/a2, + /*data=*/VM_ArgPtr(a3)); + break; + case DEEPMIND_SPAWN_INVENTORY: + case DEEPMIND_UPDATE_INVENTORY: { + playerState_t* ps = VM_ArgPtr(a1); + ctx->hooks.update_inventory( + ctx->userdata, + /*is_spawning=*/dm_callnum == DEEPMIND_SPAWN_INVENTORY, + /*player_id=*/ps->clientNum, + /*gadget_count=*/MAX_WEAPONS, + /*gadget_inventory=*/ps->ammo, + /*stat_count=*/MAX_STATS, + /*stats_inventory=*/ps->stats, + /*powerup_count=*/MAX_POWERUPS, + /*powerup_time=*/ps->powerups, + /*gadget_held=*/ps->weapon, + /*height=*/ps->viewheight, + /*position=*/ps->origin, + /*view_angles=*/ps->viewangles); + break; + } + case DEEPMIND_TEAM_SELECT: + return ctx->hooks.team_select(ctx->userdata, + /*player_id=*/a1, + /*player_name=*/VM_ArgPtr(a2)); + break; + case DEEPMIND_ENTITIES_CLEAR: + ctx->hooks.entities.clear(ctx->userdata); + break; + case DEEPMIND_ENTITIES_ADD: + ctx->hooks.entities.add(ctx->userdata, /*player_id=*/a1, + /*user_id=*/a2, + /*type=*/a3, + /*flags=*/a4, + /*position=*/VM_ArgPtr(a5), + /*classname=*/VM_ArgPtr(a6)); + break; default: Com_Error(ERR_DROP, "DeepMind system call %d not implemented\n", dm_callnum); diff --git a/engine/code/qcommon/files.c b/engine/code/qcommon/files.c index fd18a49a..37abf92e 100644 --- a/engine/code/qcommon/files.c +++ b/engine/code/qcommon/files.c @@ -174,6 +174,7 @@ or configs will never get loaded from disk! // every time a new demo pk3 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 +#ifndef STANDALONE #define DEMO_PAK0_CHECKSUM 2985612116u static const unsigned int pak_checksums[] = { 1566731103u, @@ -194,6 +195,7 @@ static const unsigned int missionpak_checksums[] = 2662638993u, 1438664554u }; +#endif // if this is defined, the executable positively won't work with any paks other // than the demo pak, even if productid is present. This is only used for our @@ -251,6 +253,8 @@ static cvar_t *fs_homepath; static cvar_t *fs_apppath; #endif static cvar_t *fs_steampath; +static cvar_t *fs_gogpath; +static cvar_t *fs_temporarypath; static cvar_t *fs_basepath; static cvar_t *fs_basegame; @@ -280,7 +284,6 @@ typedef struct { int zipFilePos; int zipFileLen; qboolean zipFile; - qboolean streamed; char name[MAX_ZPATH]; } fileHandleData_t; @@ -566,7 +569,7 @@ static void FS_CheckFilenameIsMutable( const char *filename, const char *function ) { // Check if the filename ends with the library, QVM, or pk3 extension - if( COM_CompareExtension( filename, DLL_EXT ) + if( Sys_DllExtension( filename ) || COM_CompareExtension( filename, ".qvm" ) || COM_CompareExtension( filename, ".pk3" ) ) { @@ -751,6 +754,21 @@ long FS_SV_FOpenFileRead(const char *filename, fileHandle_t *fp) fsh[f].handleSync = qfalse; } + // Check fs_temporarypath too + if (!fsh[f].handleFiles.file.o && fs_temporarypath->string[0]) + { + ospath = FS_BuildOSPath( fs_temporarypath->string, filename, "" ); + ospath[strlen(ospath)-1] = '\0'; + + if ( fs_debug->integer ) + { + Com_Printf( "FS_SV_FOpenFileRead (fs_temporarypath): %s\n", ospath ); + } + + fsh[f].handleFiles.file.o = Sys_FOpen( ospath, "rb" ); + fsh[f].handleSync = qfalse; + } + // Check fs_steampath too if (!fsh[f].handleFiles.file.o && fs_steampath->string[0]) { @@ -766,6 +784,21 @@ long FS_SV_FOpenFileRead(const char *filename, fileHandle_t *fp) fsh[f].handleSync = qfalse; } + // Check fs_gogpath + if (!fsh[f].handleFiles.file.o && fs_gogpath->string[0]) + { + ospath = FS_BuildOSPath( fs_gogpath->string, filename, "" ); + ospath[strlen(ospath)-1] = '\0'; + + if ( fs_debug->integer ) + { + Com_Printf( "FS_SV_FOpenFileRead (fs_gogpath): %s\n", ospath ); + } + + fsh[f].handleFiles.file.o = Sys_FOpen( ospath, "rb" ); + fsh[f].handleSync = qfalse; + } + if ( !fsh[f].handleFiles.file.o ) { f = 0; @@ -1364,12 +1397,18 @@ long FS_FOpenFileRead(const char *filename, fileHandle_t *file, qboolean uniqueF { searchpath_t *search; long len; + qboolean isLocalConfig; if(!fs_searchpaths) Com_Error(ERR_FATAL, "Filesystem call made without initialization"); + isLocalConfig = !strcmp(filename, "autoexec.cfg") || !strcmp(filename, Q3CONFIG_CFG); for(search = fs_searchpaths; search; search = search->next) { + // autoexec.cfg and q3config.cfg can only be loaded outside of pk3 files. + if (isLocalConfig && search->pack) + continue; + len = FS_FOpenFileReadDir(filename, search, file, uniqueFILE, qfalse); if(file == NULL) @@ -1504,25 +1543,6 @@ FS_Read Properly handles partial reads ================= */ -int FS_Read2( void *buffer, int len, fileHandle_t f ) { - if ( !fs_searchpaths ) { - Com_Error( ERR_FATAL, "Filesystem call made without initialization" ); - } - - if ( !f ) { - return 0; - } - if (fsh[f].streamed) { - int r; - fsh[f].streamed = qfalse; - r = FS_Read( buffer, len, f ); - fsh[f].streamed = qtrue; - return r; - } else { - return FS_Read( buffer, len, f); - } -} - int FS_Read( void *buffer, int len, fileHandle_t f ) { int block, remaining; int read; @@ -1649,14 +1669,6 @@ int FS_Seek( fileHandle_t f, long offset, int origin ) { return -1; } - if (fsh[f].streamed) { - int r; - fsh[f].streamed = qfalse; - r = FS_Seek( f, offset, origin ); - fsh[f].streamed = qtrue; - return r; - } - if (fsh[f].zipFile == qtrue) { //FIXME: this is really, really crappy //(but better than what was here before) @@ -2210,7 +2222,7 @@ static int FS_AddFileToList( char *name, char *list[MAX_FOUND_FILES], int nfiles } for ( i = 0 ; i < nfiles ; i++ ) { if ( !Q_stricmp( name, list[i] ) ) { - return nfiles; // allready in list + return nfiles; // already in list } } list[nfiles] = CopyString( name ); @@ -2516,103 +2528,94 @@ void FS_GetModDescription( const char *modDir, char *description, int descriptio FS_GetModList Returns a list of mod directory names -A mod directory is a peer to baseq3 with a pk3 in it -The directories are searched in base path, cd path and home path +A mod directory is a peer to baseq3 with a pk3 or pk3dir in it ================ */ int FS_GetModList( char *listbuf, int bufsize ) { - int nMods, i, j, nTotal, nLen, nPaks, nPotential, nDescLen; + int nMods, i, j, k, nTotal, nLen, nPaks, nDirs, nPakDirs, nPotential, nDescLen; char **pFiles = NULL; char **pPaks = NULL; + char **pDirs = NULL; char *name, *path; char description[MAX_INSTALL_PATH]; int dummy; char **pFiles0 = NULL; - char **pFiles1 = NULL; - char **pFiles2 = NULL; - char **pFiles3 = NULL; qboolean bDrop = qfalse; + // paths to search for mods + const char * const paths[] = { fs_basepath->string, fs_homepath->string, fs_steampath->string, fs_gogpath->string, fs_temporarypath->string }; + *listbuf = 0; nMods = nTotal = 0; - pFiles0 = Sys_ListFiles( fs_homepath->string, NULL, NULL, &dummy, qtrue ); - pFiles1 = Sys_ListFiles( fs_basepath->string, NULL, NULL, &dummy, qtrue ); - pFiles2 = Sys_ListFiles( fs_steampath->string, NULL, NULL, &dummy, qtrue ); - // we searched for mods in the three paths - // it is likely that we have duplicate names now, which we will cleanup below - pFiles3 = Sys_ConcatenateFileLists( pFiles0, pFiles1 ); - pFiles = Sys_ConcatenateFileLists( pFiles2, pFiles3 ); + // iterate through paths and get list of potential mods + for (i = 0; i < ARRAY_LEN(paths); i++) { + pFiles0 = Sys_ListFiles(paths[i], NULL, NULL, &dummy, qtrue); + // Sys_ConcatenateFileLists frees the lists so Sys_FreeFileList isn't required + pFiles = Sys_ConcatenateFileLists(pFiles, pFiles0); + } nPotential = Sys_CountFileList(pFiles); - for ( i = 0 ; i < nPotential ; i++ ) { + for (i = 0; i < nPotential; i++) { name = pFiles[i]; // NOTE: cleaner would involve more changes // ignore duplicate mod directories - if (i!=0) { + if (i != 0) { bDrop = qfalse; - for(j=0; jstring) == 0 || Q_stricmpn(name, ".", 1) == 0) { continue; } - // we drop "baseq3" "." and ".." - if (Q_stricmp(name, com_basegame->string) && Q_stricmpn(name, ".", 1)) { - // now we need to find some .pk3 files to validate the mod - // NOTE TTimo: (actually I'm not sure why .. what if it's a mod under developement with no .pk3?) - // we didn't keep the information when we merged the directory names, as to what OS Path it was found under - // so it could be in base path, cd path or home path - // we will try each three of them here (yes, it's a bit messy) - path = FS_BuildOSPath( fs_basepath->string, name, "" ); - nPaks = 0; - pPaks = Sys_ListFiles(path, ".pk3", NULL, &nPaks, qfalse); - Sys_FreeFileList( pPaks ); // we only use Sys_ListFiles to check wether .pk3 files are present - - /* try on home path */ - if ( nPaks <= 0 ) - { - path = FS_BuildOSPath( fs_homepath->string, name, "" ); - nPaks = 0; - pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse ); - Sys_FreeFileList( pPaks ); + + // in order to be a valid mod the directory must contain at least one .pk3 or .pk3dir + // we didn't keep the information when we merged the directory names, as to what OS Path it was found under + // so we will try each of them here + for (j = 0; j < ARRAY_LEN(paths); j++) { + path = FS_BuildOSPath(paths[j], name, ""); + nPaks = nDirs = nPakDirs = 0; + pPaks = Sys_ListFiles(path, ".pk3", NULL, &nPaks, qfalse); + pDirs = Sys_ListFiles(path, "/", NULL, &nDirs, qfalse); + for (k = 0; k < nDirs; k++) { + // we only want to count directories ending with ".pk3dir" + if (FS_IsExt(pDirs[k], ".pk3dir", strlen(pDirs[k]))) { + nPakDirs++; + } } + // we only use Sys_ListFiles to check whether files are present + Sys_FreeFileList(pPaks); + Sys_FreeFileList(pDirs); - /* try on steam path */ - if ( nPaks <= 0 ) - { - path = FS_BuildOSPath( fs_steampath->string, name, "" ); - nPaks = 0; - pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse ); - Sys_FreeFileList( pPaks ); + if (nPaks > 0 || nPakDirs > 0) { + break; } + } - if (nPaks > 0) { - nLen = strlen(name) + 1; - // nLen is the length of the mod path - // we need to see if there is a description available - FS_GetModDescription( name, description, sizeof( description ) ); - nDescLen = strlen(description) + 1; - - if (nTotal + nLen + 1 + nDescLen + 1 < bufsize) { - strcpy(listbuf, name); - listbuf += nLen; - strcpy(listbuf, description); - listbuf += nDescLen; - nTotal += nLen + nDescLen; - nMods++; - } - else { - break; - } + if (nPaks > 0 || nPakDirs > 0) { + nLen = strlen(name) + 1; + // nLen is the length of the mod path + // we need to see if there is a description available + FS_GetModDescription(name, description, sizeof(description)); + nDescLen = strlen(description) + 1; + + if (nTotal + nLen + 1 + nDescLen + 1 < bufsize) { + strcpy(listbuf, name); + listbuf += nLen; + strcpy(listbuf, description); + listbuf += nDescLen; + nTotal += nLen + nDescLen; + nMods++; + } else { + break; } } } @@ -3322,6 +3325,16 @@ static void FS_Startup( const char *gameName ) fs_homepath = Cvar_Get ("fs_homepath", homePath, CVAR_INIT|CVAR_PROTECTED ); fs_gamedirvar = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO ); + // add search path elements in reverse priority order + fs_gogpath = Cvar_Get ("fs_gogpath", Sys_GogPath(), CVAR_INIT|CVAR_PROTECTED ); + if (fs_gogpath->string[0]) { + FS_AddGameDirectory( fs_gogpath->string, gameName ); + } + fs_temporarypath = Cvar_Get ("fs_temporarypath", fs_homepath->string, CVAR_INIT|CVAR_PROTECTED ); + if (fs_temporarypath->string[0]) { + FS_AddGameDirectory( fs_temporarypath->string, gameName ); + } + // add search path elements in reverse priority order fs_steampath = Cvar_Get ("fs_steampath", Sys_SteamPath(), CVAR_INIT|CVAR_PROTECTED ); if (fs_steampath->string[0]) { @@ -3347,6 +3360,9 @@ static void FS_Startup( const char *gameName ) // check for additional base game so mods can be based upon other mods if ( fs_basegame->string[0] && Q_stricmp( fs_basegame->string, gameName ) ) { + if (fs_gogpath->string[0]) { + FS_AddGameDirectory(fs_gogpath->string, fs_basegame->string); + } if (fs_steampath->string[0]) { FS_AddGameDirectory(fs_steampath->string, fs_basegame->string); } @@ -3360,6 +3376,9 @@ static void FS_Startup( const char *gameName ) // check for additional game folder for mods if ( fs_gamedirvar->string[0] && Q_stricmp( fs_gamedirvar->string, gameName ) ) { + if (fs_gogpath->string[0]) { + FS_AddGameDirectory(fs_gogpath->string, fs_gamedirvar->string); + } if (fs_steampath->string[0]) { FS_AddGameDirectory(fs_steampath->string, fs_gamedirvar->string); } @@ -4113,11 +4132,6 @@ int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ) { if ( *f ) { fsh[*f].fileSize = r; - fsh[*f].streamed = qfalse; - - if (mode == FS_READ) { - fsh[*f].streamed = qtrue; - } } fsh[*f].handleSync = sync; diff --git a/engine/code/qcommon/huffman.c b/engine/code/qcommon/huffman.c index c1b9f242..6190726a 100644 --- a/engine/code/qcommon/huffman.c +++ b/engine/code/qcommon/huffman.c @@ -279,9 +279,14 @@ int Huff_Receive (node_t *node, int *ch, byte *fin) { } /* Get a symbol */ -void Huff_offsetReceive (node_t *node, int *ch, byte *fin, int *offset) { +void Huff_offsetReceive (node_t *node, int *ch, byte *fin, int *offset, int maxoffset) { bloc = *offset; while (node && node->symbol == INTERNAL_NODE) { + if (bloc >= maxoffset) { + *ch = 0; + *offset = maxoffset + 1; + return; + } if (get_bit(fin)) { node = node->right; } else { @@ -298,11 +303,15 @@ void Huff_offsetReceive (node_t *node, int *ch, byte *fin, int *offset) { } /* Send the prefix code for this node */ -static void send(node_t *node, node_t *child, byte *fout) { +static void send(node_t *node, node_t *child, byte *fout, int maxoffset) { if (node->parent) { - send(node->parent, node, fout); + send(node->parent, node, fout, maxoffset); } if (child) { + if (bloc >= maxoffset) { + bloc = maxoffset + 1; + return; + } if (node->right == child) { add_bit(1, fout); } else { @@ -312,22 +321,22 @@ static void send(node_t *node, node_t *child, byte *fout) { } /* Send a symbol */ -void Huff_transmit (huff_t *huff, int ch, byte *fout) { +void Huff_transmit (huff_t *huff, int ch, byte *fout, int maxoffset) { int i; if (huff->loc[ch] == NULL) { /* node_t hasn't been transmitted, send a NYT, then the symbol */ - Huff_transmit(huff, NYT, fout); + Huff_transmit(huff, NYT, fout, maxoffset); for (i = 7; i >= 0; i--) { add_bit((char)((ch >> i) & 0x1), fout); } } else { - send(huff->loc[ch], NULL, fout); + send(huff->loc[ch], NULL, fout, maxoffset); } } -void Huff_offsetTransmit (huff_t *huff, int ch, byte *fout, int *offset) { +void Huff_offsetTransmit (huff_t *huff, int ch, byte *fout, int *offset, int maxoffset) { bloc = *offset; - send(huff->loc[ch], NULL, fout); + send(huff->loc[ch], NULL, fout, maxoffset); *offset = bloc; } @@ -413,10 +422,17 @@ void Huff_Compress(msg_t *mbuf, int offset) { for (i=0; icursize = (bloc>>3) + offset; diff --git a/engine/code/qcommon/md4.c b/engine/code/qcommon/md4.c index 0eb23a50..41e10907 100644 --- a/engine/code/qcommon/md4.c +++ b/engine/code/qcommon/md4.c @@ -106,8 +106,13 @@ static void copy64(uint32_t *M, byte *in) int i; for (i=0;i<16;i++) - M[i] = (in[i*4+3]<<24) | (in[i*4+2]<<16) | - (in[i*4+1]<<8) | (in[i*4+0]<<0); + { + M[i] = + ((uint32_t)in[i*4+3] << 24) | + ((uint32_t)in[i*4+2] << 16) | + ((uint32_t)in[i*4+1] << 8) | + ((uint32_t)in[i*4+0] << 0) ; + } } static void copy4(byte *out,uint32_t x) diff --git a/engine/code/qcommon/md5.c b/engine/code/qcommon/md5.c index 994083fc..cee93a32 100644 --- a/engine/code/qcommon/md5.c +++ b/engine/code/qcommon/md5.c @@ -78,7 +78,7 @@ static void MD5Init(struct MD5Context *ctx) static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) { - register uint32_t a, b, c, d; + uint32_t a, b, c, d; a = buf[0]; b = buf[1]; @@ -290,7 +290,7 @@ char *Com_MD5File( const char *fn, int length, const char *prefix, int prefix_le MD5Update(&md5 , (unsigned char *)prefix, prefix_len); for(;;) { - r = FS_Read2(buffer, sizeof(buffer), f); + r = FS_Read(buffer, sizeof(buffer), f); if(r < 1) break; if(r + total > length) diff --git a/engine/code/qcommon/msg.c b/engine/code/qcommon/msg.c index de21032d..b9ae3c29 100644 --- a/engine/code/qcommon/msg.c +++ b/engine/code/qcommon/msg.c @@ -107,9 +107,7 @@ void MSG_WriteBits( msg_t *msg, int value, int bits ) { oldsize += bits; - // this isn't an exact overflow check, but close enough - if ( msg->maxsize - msg->cursize < 4 ) { - msg->overflowed = qtrue; + if ( msg->overflowed ) { return; } @@ -122,6 +120,11 @@ void MSG_WriteBits( msg_t *msg, int value, int bits ) { } if ( msg->oob ) { + if ( msg->cursize + ( bits >> 3 ) > msg->maxsize ) { + msg->overflowed = qtrue; + return; + } + if ( bits == 8 ) { msg->data[msg->cursize] = value; msg->cursize += 1; @@ -144,6 +147,10 @@ void MSG_WriteBits( msg_t *msg, int value, int bits ) { if ( bits&7 ) { int nbits; nbits = bits&7; + if ( msg->bit + nbits > msg->maxsize << 3 ) { + msg->overflowed = qtrue; + return; + } for( i = 0; i < nbits; i++ ) { Huff_putBit( (value & 1), msg->data, &msg->bit ); value = (value >> 1); @@ -152,11 +159,19 @@ void MSG_WriteBits( msg_t *msg, int value, int bits ) { } if ( bits ) { for( i = 0; i < bits; i += 8 ) { - Huff_offsetTransmit( &msgHuff.compressor, (value & 0xff), msg->data, &msg->bit ); + Huff_offsetTransmit( &msgHuff.compressor, (value & 0xff), msg->data, &msg->bit, msg->maxsize << 3 ); value = (value >> 8); + + if ( msg->bit > msg->maxsize << 3 ) { + msg->overflowed = qtrue; + return; + } } } msg->cursize = (msg->bit >> 3) + 1; + if (msg->bit % 8 == 0) { + msg->data[msg->bit / 8] = 0; + } } } @@ -167,6 +182,10 @@ int MSG_ReadBits( msg_t *msg, int bits ) { int i, nbits; // FILE* fp; + if ( msg->readcount > msg->cursize ) { + return 0; + } + value = 0; if ( bits < 0 ) { @@ -177,6 +196,11 @@ int MSG_ReadBits( msg_t *msg, int bits ) { } if (msg->oob) { + if (msg->readcount + (bits>>3) > msg->cursize) { + msg->readcount = msg->cursize + 1; + return 0; + } + if(bits==8) { value = msg->data[msg->readcount]; @@ -204,6 +228,10 @@ int MSG_ReadBits( msg_t *msg, int bits ) { nbits = 0; if (bits&7) { nbits = bits&7; + if (msg->bit + nbits > msg->cursize << 3) { + msg->readcount = msg->cursize + 1; + return 0; + } for(i=0;idata, &msg->bit)<data, &msg->bit); + Huff_offsetReceive (msgHuff.decompressor.tree, &get, msg->data, &msg->bit, msg->cursize<<3); // fwrite(&get, 1, 1, fp); value |= (get<<(i+nbits)); + + if (msg->bit > msg->cursize<<3) { + msg->readcount = msg->cursize + 1; + return 0; + } } // fclose(fp); } @@ -1244,7 +1277,7 @@ void MSG_WriteDeltaPlayerstate( msg_t *msg, struct playerState_s *from, struct p MSG_WriteBits( msg, persistantbits, MAX_PERSISTANT ); for (i=0 ; ipersistant[i]); + MSG_WriteLong (msg, to->persistant[i]); } else { MSG_WriteBits( msg, 0, 1 ); // no change } @@ -1379,7 +1412,7 @@ void MSG_ReadDeltaPlayerstate (msg_t *msg, playerState_t *from, playerState_t *t bits = MSG_ReadBits (msg, MAX_PERSISTANT); for (i=0 ; ipersistant[i] = MSG_ReadShort(msg); + to->persistant[i] = MSG_ReadLong(msg); } } } diff --git a/engine/code/qcommon/q_math.c b/engine/code/qcommon/q_math.c index ce47317a..cc06493d 100644 --- a/engine/code/qcommon/q_math.c +++ b/engine/code/qcommon/q_math.c @@ -148,7 +148,7 @@ vec3_t bytedirs[NUMVERTEXNORMALS] = //============================================================== int Q_rand( int *seed ) { - *seed = (69069 * *seed + 1); + *seed = (69069U * *seed + 1U); return *seed; } @@ -554,16 +554,7 @@ Always returns a value from -180 to 180 ================= */ float AngleSubtract( float a1, float a2 ) { - float a; - - a = a1 - a2; - while ( a > 180 ) { - a -= 360; - } - while ( a < -180 ) { - a += 360; - } - return a; + return AngleNormalize180(a1 - a2); } @@ -574,9 +565,8 @@ void AnglesSubtract( vec3_t v1, vec3_t v2, vec3_t v3 ) { } -float AngleMod(float a) { - a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); - return a; +float AngleMod(float angle) { + return angle - 360.0f * (int)(angle * (1.0f / 360.0f)); } @@ -588,7 +578,7 @@ returns angle normalized to the range [0 <= angle < 360] ================= */ float AngleNormalize360 ( float angle ) { - return (360.0 / 65536) * ((int)(angle * (65536 / 360.0)) & 65535); + return angle - 360.0f * (int)(angle * (1.0f / 360.0f)); } @@ -596,15 +586,11 @@ float AngleNormalize360 ( float angle ) { ================= AngleNormalize180 -returns angle normalized to the range [-180 < angle <= 180] +returns angle normalized to the range [-180 <= angle < 180] ================= */ float AngleNormalize180 ( float angle ) { - angle = AngleNormalize360( angle ); - if ( angle > 180.0 ) { - angle -= 360.0; - } - return angle; + return angle - 360.0f * (int)((angle + 180.0f) * (1.0f / 360.0f)); } diff --git a/engine/code/qcommon/q_shared.h b/engine/code/qcommon/q_shared.h index 6ad65f13..c4b1b7be 100644 --- a/engine/code/qcommon/q_shared.h +++ b/engine/code/qcommon/q_shared.h @@ -50,6 +50,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define HOMEPATH_NAME_MACOSX HOMEPATH_NAME_WIN #define STEAMPATH_NAME "Quake 3 Arena" #define STEAMPATH_APPID "2200" + #define GOGPATH_ID "1441704920" #define GAMENAME_FOR_MASTER "Quake3Arena" #define CINEMATICS_LOGO "idlogo.RoQ" #define CINEMATICS_INTRO "intro.RoQ" @@ -1075,8 +1076,8 @@ typedef enum { ======================================================================== */ -#define ANGLE2SHORT(x) ((int)((x)*65536/360) & 65535) -#define SHORT2ANGLE(x) ((x)*(360.0/65536)) +#define ANGLE2SHORT(x) ((int)((x)*(65536.0f/360.0f)+0.5f) & 65535) +#define SHORT2ANGLE(x) ((x)*(360.0f/65536.0f)) #define SNAPFLAG_RATE_DELAYED 1 #define SNAPFLAG_NOT_ACTIVE 2 // snapshot used during connection and for zombies diff --git a/engine/code/qcommon/qcommon.h b/engine/code/qcommon/qcommon.h index a74a86b6..0b10562b 100644 --- a/engine/code/qcommon/qcommon.h +++ b/engine/code/qcommon/qcommon.h @@ -661,7 +661,6 @@ int FS_FileIsInPAK(const char *filename, int *pChecksum ); int FS_Write( const void *buffer, int len, fileHandle_t f ); -int FS_Read2( void *buffer, int len, fileHandle_t f ); int FS_Read( void *buffer, int len, fileHandle_t f ); // properly handles partial reads and reads from other dlls @@ -885,6 +884,9 @@ extern cvar_t *com_protocol; #ifdef LEGACY_PROTOCOL extern cvar_t *com_legacyprotocol; #endif +#ifndef DEDICATED +extern cvar_t *con_autochat; +#endif // com_speeds times extern int time_game; @@ -1059,6 +1061,14 @@ int SV_SendQueuedPackets(void); qboolean UI_GameCommand( void ); qboolean UI_usesUniqueCDKey(void); +// +// input interface +// +void IN_Init( void *windowData ); +void IN_Frame( void ); +void IN_Shutdown( void ); +void IN_Restart( void ); + /* ============================================================== @@ -1076,6 +1086,8 @@ void * QDECL Sys_LoadGameDll( const char *name, intptr_t (QDECL **entryPoint)(in intptr_t (QDECL *systemcalls)(intptr_t, ...) ); void Sys_UnloadDll( void *dllHandle ); +qboolean Sys_DllExtension( const char *name ); + char *Sys_GetCurrentUser( void ); void QDECL Sys_Error( const char *error, ...) __attribute__ ((noreturn, format (printf, 1, 2))); @@ -1112,6 +1124,7 @@ char *Sys_Cwd( void ); void Sys_SetDefaultInstallPath(const char *path); char *Sys_DefaultInstallPath(void); char *Sys_SteamPath(void); +char *Sys_GogPath(void); #ifdef __APPLE__ char *Sys_DefaultAppPath(void); @@ -1194,9 +1207,9 @@ void Huff_Decompress(msg_t *buf, int offset); void Huff_Init(huffman_t *huff); void Huff_addRef(huff_t* huff, byte ch); int Huff_Receive (node_t *node, int *ch, byte *fin); -void Huff_transmit (huff_t *huff, int ch, byte *fout); -void Huff_offsetReceive (node_t *node, int *ch, byte *fin, int *offset); -void Huff_offsetTransmit (huff_t *huff, int ch, byte *fout, int *offset); +void Huff_transmit (huff_t *huff, int ch, byte *fout, int maxoffset); +void Huff_offsetReceive (node_t *node, int *ch, byte *fin, int *offset, int maxoffset); +void Huff_offsetTransmit (huff_t *huff, int ch, byte *fout, int *offset, int maxoffset); void Huff_putBit( int bit, byte *fout, int *offset); int Huff_getBit( byte *fout, int *offset); diff --git a/engine/code/qcommon/surfaceflags.h b/engine/code/qcommon/surfaceflags.h index b7c10a17..874dafc5 100644 --- a/engine/code/qcommon/surfaceflags.h +++ b/engine/code/qcommon/surfaceflags.h @@ -37,6 +37,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define CONTENTS_NOTTEAM2 0x0100 #define CONTENTS_NOBOTCLIP 0x0200 +#define CONTENTS_LOOKAT 0x0400 + #define CONTENTS_AREAPORTAL 0x8000 #define CONTENTS_PLAYERCLIP 0x10000 diff --git a/engine/code/qcommon/unzip.c b/engine/code/qcommon/unzip.c index 413279c7..77f85497 100644 --- a/engine/code/qcommon/unzip.c +++ b/engine/code/qcommon/unzip.c @@ -76,10 +76,11 @@ woven in by Terry Thorsen 1/2003. #define SIZECENTRALDIRITEM (0x2e) #define SIZEZIPLOCALHEADER (0x1e) +/* The callers never check for CRC errors, so the crc32 doesn't need to be calculated: */ +// #define CHECK_CRC32_HASH - -const char unz_copyright[] = +static const char unz_copyright[] = " unzip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll"; @@ -1272,9 +1273,12 @@ extern int ZEXPORT unzReadCurrentFile (file, buf, len) *(pfile_in_zip_read_info->stream.next_out+i) = *(pfile_in_zip_read_info->stream.next_in+i); +#ifdef CHECK_CRC32_HASH pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32, pfile_in_zip_read_info->stream.next_out, uDoCopy); +#endif + 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; @@ -1307,9 +1311,11 @@ extern int ZEXPORT unzReadCurrentFile (file, buf, len) uTotalOutAfter = pfile_in_zip_read_info->stream.total_out; uOutThis = uTotalOutAfter-uTotalOutBefore; +#ifdef CHECK_CRC32_HASH pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32,bufBefore, (uInt)(uOutThis)); +#endif pfile_in_zip_read_info->rest_read_uncompressed -= uOutThis; @@ -1451,14 +1457,14 @@ extern int ZEXPORT unzCloseCurrentFile (file) if (pfile_in_zip_read_info==NULL) return UNZ_PARAMERROR; - +#ifdef CHECK_CRC32_HASH if ((pfile_in_zip_read_info->rest_read_uncompressed == 0) && (!pfile_in_zip_read_info->raw)) { if (pfile_in_zip_read_info->crc32 != pfile_in_zip_read_info->crc32_wait) err=UNZ_CRCERROR; } - +#endif TRYFREE(pfile_in_zip_read_info->read_buffer); pfile_in_zip_read_info->read_buffer = NULL; diff --git a/engine/code/qcommon/vm.c b/engine/code/qcommon/vm.c index 32915f3a..39d7ea3d 100644 --- a/engine/code/qcommon/vm.c +++ b/engine/code/qcommon/vm.c @@ -451,13 +451,15 @@ vmHeader_t *VM_LoadQVM( vm_t *vm, qboolean alloc, qboolean unpure) if(alloc) { // allocate zero filled space for initialized and uninitialized data - vm->dataBase = Hunk_Alloc(dataLength, h_high); + // leave some space beyond data mask so we can secure all mask operations + vm->dataAlloc = dataLength + 4; + vm->dataBase = Hunk_Alloc(vm->dataAlloc, h_high); vm->dataMask = dataLength - 1; } else { // clear the data, but make sure we're not clearing more than allocated - if(vm->dataMask + 1 != dataLength) + if(vm->dataAlloc != dataLength + 4) { VM_Free(vm); FS_FreeFile(header.v); @@ -467,7 +469,7 @@ vmHeader_t *VM_LoadQVM( vm_t *vm, qboolean alloc, qboolean unpure) return NULL; } - Com_Memset(vm->dataBase, 0, dataLength); + Com_Memset(vm->dataBase, 0, vm->dataAlloc); } // copy the intialized data diff --git a/engine/code/qcommon/vm_armv7l.c b/engine/code/qcommon/vm_armv7l.c new file mode 100644 index 00000000..8f2bd8ce --- /dev/null +++ b/engine/code/qcommon/vm_armv7l.c @@ -0,0 +1,1222 @@ +/* +=========================================================================== +Copyright (C) 2009 David S. Miller +Copyright (C) 2013,2014 SUSE Linux Products GmbH + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can 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. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== + +ARMv7l VM by Ludwig Nussel + +TODO: optimization + +Docu: +http://www.coranac.com/tonc/text/asm.htm +http://www.heyrick.co.uk/armwiki/Category:Opcodes +ARMv7-A_ARMv7-R_DDI0406_2007.pdf +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "vm_local.h" +#define R0 0 +#define R1 1 +#define R2 2 +#define R3 3 +#define R4 4 + +#define R12 12 + +#define FP 11 +#define SP 13 +#define LR 14 +#define PC 15 + +#define APSR_nzcv 15 + +#define S14 14 +#define S15 15 + +#define rOPSTACK 5 +#define rOPSTACKBASE 6 +#define rCODEBASE 7 +#define rPSTACK 8 +#define rDATABASE 9 +#define rDATAMASK 10 + +#define bit(x) (1<compiled = qfalse; return; } while(0) +#endif + +static void VM_Destroy_Compiled(vm_t *vm) +{ + if (vm->codeBase) { + if (munmap(vm->codeBase, vm->codeLength)) + Com_Printf(S_COLOR_RED "Memory unmap failed, possible memory leak\n"); + } + vm->codeBase = NULL; +} + +/* +================= +ErrJump +Error handler for jump/call to invalid instruction number +================= +*/ + +static void __attribute__((__noreturn__)) ErrJump(unsigned num) +{ + Com_Error(ERR_DROP, "program tried to execute code outside VM (%x)", num); +} + +static int asmcall(int call, int pstack) +{ + // save currentVM so as to allow for recursive VM entry + vm_t *savedVM = currentVM; + int i, ret; + + // modify VM stack pointer for recursive VM entry + currentVM->programStack = pstack - 4; + + if (sizeof(intptr_t) == sizeof(int)) { + intptr_t *argPosition = (intptr_t *)((byte *)currentVM->dataBase + pstack + 4); + argPosition[0] = -1 - call; + ret = currentVM->systemCall(argPosition); + } else { + intptr_t args[MAX_VMSYSCALL_ARGS]; + + args[0] = -1 - call; + int *argPosition = (int *)((byte *)currentVM->dataBase + pstack + 4); + for( i = 1; i < ARRAY_LEN(args); i++ ) + args[i] = argPosition[i]; + + ret = currentVM->systemCall(args); + } + + currentVM = savedVM; + + return ret; +} + +void _emit(vm_t *vm, unsigned isn, int pass) +{ +#if 0 + static int fd = -2; + if (fd == -2) + fd = open("code.bin", O_TRUNC|O_WRONLY|O_CREAT, 0644); + if (fd > 0) + write(fd, &isn, 4); +#endif + + if (pass) + memcpy(vm->codeBase+vm->codeLength, &isn, 4); + vm->codeLength+=4; +} + +#define emit(isn) _emit(vm, isn, pass) + +static unsigned char off8(unsigned val) +{ + if (val&3) + DIE("offset must be multiple of four"); + if (val > 1020) + DIE("offset too large"); + return val>>2; +} + +// ARM is really crazy ... +static unsigned short rimm(unsigned val) +{ + unsigned shift = 0; + if (val < 256) + return val; + // rotate the value until it fits + while (shift < 16 && (val>255 || !(val&3))) { + val = (val&3)<<30 | val>>2; + ++shift; + } + if (shift > 15 || val > 255) { + DIE("immediate cannot be encoded (%d, %d)\n", shift, val); + } + return (16-shift)<<8 | val; +} + +// same as rimm but doesn't die, returns 0 if not encodable so don't call with zero as argument! +static unsigned short can_encode(unsigned val) +{ + unsigned shift = 0; + if (!val) + DIE("can_encode: invalid argument"); + if (val < 256) + return val; + // rotate the value until it fits + while (shift < 16 && (val>255 || !(val&3))) { + val = (val&3)<<30 | val>>2; + ++shift; + } + if (shift > 15 || val > 255) { + return 0; + } + return (16-shift)<<8 | val; +} + +#define PREINDEX (1<<24) + +#define rASR(i, reg) (0b10<<5 | ((i&31)<<7) | reg) +#define rLSL(i, reg) (0b00<<5 | ((i&31)<<7) | reg) +#define rLSR(i, reg) (0b01<<5 | ((i&31)<<7) | reg) +#define rROR(i, reg) (0b11<<5 | ((i&31)<<7) | reg) + +// conditions +#define EQ (0b0000<<28) +#define NE (0b0001<<28) +#define CS (0b0010<<28) +#define HS CS +#define CC (0b0011<<28) +#define LO CC +#define MI (0b0100<<28) +#define PL (0b0101<<28) +#define VS (0b0110<<28) +#define VC (0b0111<<28) +#define HI (0b1000<<28) +#define LS (0b1001<<28) +#define GE (0b1010<<28) +#define LT (0b1011<<28) +#define GT (0b1100<<28) +#define LE (0b1101<<28) +#define AL (0b1110<<28) +#define cond(what, op) (what | (op&~AL)) + +// XXX: v not correctly computed +#define BKPT(v) (AL | 0b10010<<20 | ((v&~0xF)<<4) | 0b0111<<4 | (v&0xF)) + +#define YIELD (0b110010<<20 | 0b1111<<12 | 1) +#define NOP cond(AL, YIELD) + +// immediate value must fit in 0xFF! +#define ANDi(dst, src, i) (AL | (0b001<<25) | (0b00000<<20) | (src<<16) | (dst<<12) | rimm(i)) +#define EORi(dst, src, i) (AL | (0b001<<25) | (0b00010<<20) | (src<<16) | (dst<<12) | rimm(i)) +#define SUBi(dst, src, i) (AL | (0b001<<25) | (0b00100<<20) | (src<<16) | (dst<<12) | rimm(i)) +#define RSBi(dst, src, i) (AL | (0b001<<25) | (0b00110<<20) | (src<<16) | (dst<<12) | rimm(i)) +#define ADDi(dst, src, i) (AL | (0b001<<25) | (0b01000<<20) | (src<<16) | (dst<<12) | rimm(i)) +#define ADCi(dst, src, i) (AL | (0b001<<25) | (0b01010<<20) | (src<<16) | (dst<<12) | rimm(i)) +#define SBCi(dst, src, i) (AL | (0b001<<25) | (0b01100<<20) | (src<<16) | (dst<<12) | rimm(i)) +#define RSCi(dst, src, i) (AL | (0b001<<25) | (0b01110<<20) | (src<<16) | (dst<<12) | rimm(i)) + +#define ORRi(dst, src, i) (AL | (0b001<<25) | (0b11000<<20) | (src<<16) | (dst<<12) | rimm(i)) +#define MOVi(dst, i) (AL | (0b001<<25) | (0b11010<<20) | (dst<<12) | rimm(i)) +#define BICi(dst, src, i) (AL | (0b001<<25) | (0b11100<<20) | (src<<16) | (dst<<12) | rimm(i)) +#define MVNi(dst, i) (AL | (0b001<<25) | (0b11110<<20) | (dst<<12) | rimm(i)) + +#define MOVW(dst, i) (AL | (0b11<<24) | ((((i)>>12)&0xF)<<16) | (dst<<12) | ((i)&((1<<12)-1))) +#define MOVT(dst, i) (AL | (0b11<<24) | (0b0100<<20) | ((((i)>>12)&0xF)<<16) | (dst<<12) | ((i)&((1<<12)-1))) + +#define TSTi( src, i) (AL | (0b001<<25) | (0b10001<<20) | (src<<16) | rimm(i)) +#define TEQi( src, i) (AL | (0b001<<25) | (0b10011<<20) | (src<<16) | rimm(i)) +#define CMPi( src, i) (AL | (0b001<<25) | (0b10101<<20) | (src<<16) | rimm(i)) +#define CMNi( src, i) (AL | (0b001<<25) | (0b10111<<20) | (src<<16) | rimm(i)) + +#define ANDSi(dst, src, i) (ANDi(dst, src, i) | (1<<20)) +#define EORSi(dst, src, i) (EORi(dst, src, i) | (1<<20)) +#define SUBSi(dst, src, i) (SUBi(dst, src, i) | (1<<20)) +#define RSBSi(dst, src, i) (RSBi(dst, src, i) | (1<<20)) +#define ADDSi(dst, src, i) (ADDi(dst, src, i) | (1<<20)) +#define ADCSi(dst, src, i) (ADCi(dst, src, i) | (1<<20)) +#define SBCSi(dst, src, i) (SBCi(dst, src, i) | (1<<20)) +#define RSCSi(dst, src, i) (RSCi(dst, src, i) | (1<<20)) + +#define ORRSi(dst, src, i) (ORRi(dst, src, i) | (1<<20)) +#define MOVSi(dst, i) (MOVi(dst, i) | (1<<20)) +#define BICSi(dst, src, i) (BICi(dst, src, i) | (1<<20)) +#define MVNSi(dst, i) (MVNi(dst, src, i) | (1<<20)) + +#define AND(dst, src, reg) (AL | (0b000<<25) | (0b00000<<20) | (src<<16) | (dst<<12) | reg) +#define EOR(dst, src, reg) (AL | (0b000<<25) | (0b00010<<20) | (src<<16) | (dst<<12) | reg) +#define SUB(dst, src, reg) (AL | (0b000<<25) | (0b00100<<20) | (src<<16) | (dst<<12) | reg) +#define RSB(dst, src, reg) (AL | (0b000<<25) | (0b00110<<20) | (src<<16) | (dst<<12) | reg) +#define ADD(dst, src, reg) (AL | (0b000<<25) | (0b01000<<20) | (src<<16) | (dst<<12) | reg) +#define ADC(dst, src, reg) (AL | (0b000<<25) | (0b01010<<20) | (src<<16) | (dst<<12) | reg) +#define SBC(dst, src, reg) (AL | (0b000<<25) | (0b01100<<20) | (src<<16) | (dst<<12) | reg) +#define RSC(dst, src, reg) (AL | (0b000<<25) | (0b01110<<20) | (src<<16) | (dst<<12) | reg) + +#define ORR(dst, src, reg) (AL | (0b000<<25) | (0b11000<<20) | (src<<16) | (dst<<12) | reg) +#define MOV(dst, src) (AL | (0b000<<25) | (0b11010<<20) | (dst<<12) | src) + +#define LSL(dst, src, reg) (AL | (0b000<<25) | (0b1101<<21) | (0<<20) | (dst<<12) | (reg<<8) | (0b0001<<4) | src) +#define LSR(dst, src, reg) (AL | (0b000<<25) | (0b1101<<21) | (0<<20) | (dst<<12) | (reg<<8) | (0b0011<<4) | src) +#define ASR(dst, src, reg) (AL | (0b000<<25) | (0b1101<<21) | (0<<20) | (dst<<12) | (reg<<8) | (0b0101<<4) | src) +#define ROR(dst, src, reg) (AL | (0b000<<25) | (0b1101<<21) | (0<<20) | (dst<<12) | (reg<<8) | (0b0111<<4) | src) + +#define LSLi(dst, src, i) (AL | (0b000<<25) | (0b1101<<21) | (0<<20) | (dst<<12) | ((i&0x1F)<<7) | (0b000<<4) | src) +#define LSRi(dst, src, i) (AL | (0b000<<25) | (0b1101<<21) | (0<<20) | (dst<<12) | ((i&0x1F)<<7) | (0b010<<4) | src) +#define ASRi(dst, src, i) (AL | (0b000<<25) | (0b1101<<21) | (0<<20) | (dst<<12) | ((i&0x1F)<<7) | (0b100<<4) | src) +#define RORi(dst, src, i) (AL | (0b000<<25) | (0b1101<<21) | (0<<20) | (dst<<12) | ((i&0x1F)<<7) | (0b110<<4) | src) +#define RRX(dst, src) (AL | (0b000<<25) | (0b1101<<21) | (0<<20) | (dst<<12) | (0b110<<4) | src) + +#define BIC(dst, src, reg) (AL | (0b000<<25) | (0b11100<<20) | (src<<16) | (dst<<12) | reg) +#define MVN(dst, reg) (AL | (0b000<<25) | (0b11110<<20) | (dst<<12) | reg) + +#define TST( src, reg) (AL | (0b000<<25) | (0b10001<<20) | (src<<16) | reg) +#define TEQ( src, reg) (AL | (0b000<<25) | (0b10011<<20) | (src<<16) | reg) +#define CMP( src, reg) (AL | (0b000<<25) | (0b10101<<20) | (src<<16) | reg) +#define CMN( src, reg) (AL | (0b000<<25) | (0b10111<<20) | (src<<16) | reg) + +#define LDRa(dst, base, off) (AL | (0b011<<25) | (0b1100<<21) | (1<<20) | base<<16 | dst<<12 | off) +#define LDRx(dst, base, off) (AL | (0b011<<25) | (0b1000<<21) | (1<<20) | base<<16 | dst<<12 | off) + +#define LDRai(dst, base, off) (AL | (0b010<<25) | (0b1100<<21) | (1<<20) | base<<16 | dst<<12 | rimm(off)) +#define LDRxi(dst, base, off) (AL | (0b010<<25) | (0b1000<<21) | (1<<20) | base<<16 | dst<<12 | rimm(off)) +#define LDRxiw(dst, base, off) (AL | (0b010<<25) | (0b1001<<21) | (1<<20) | base<<16 | dst<<12 | rimm(off)) + +#define LDRTa(dst, base, off) (AL | (0b011<<25) | (0b0101<<21) | (1<<20) | base<<16 | dst<<12 | off) +#define LDRTx(dst, base, off) (AL | (0b011<<25) | (0b0001<<21) | (1<<20) | base<<16 | dst<<12 | off) +#define LDRTai(dst, base, off) (AL | (0b010<<25) | (0b0101<<21) | (1<<20) | base<<16 | dst<<12 | rimm(off)) +#define LDRTxi(dst, base, off) (AL | (0b010<<25) | (0b0001<<21) | (1<<20) | base<<16 | dst<<12 | rimm(off)) + +#define LDRBa(dst, base, off) (AL | (0b011<<25) | (0b1110<<21) | (1<<20) | base<<16 | dst<<12 | off) +#define LDRSBai(dst, base, off) (AL | (0b000<<25) | (0b0110<<21) | (1<<20) | base<<16 | dst<<12 | ((off&0xF0)<<4)|0b1101<<4|(off&0x0F)) +#define STRBa(dst, base, off) (AL | (0b011<<25) | (0b1110<<21) | (0<<20) | base<<16 | dst<<12 | off) + +#define LDRHa(dst, base, off) (AL | (0b000<<25) | (0b1100<<21) | (1<<20) | base<<16 | dst<<12 | (0b1011<<4) | off) +#define LDRSHai(dst, base, off) (AL | (0b000<<25) | (0b1110<<21) | (1<<20) | base<<16 | dst<<12 | ((off&0xF0)<<4)|0b1111<<4|(off&0x0F)) +#define STRHa(dst, base, off) (AL | (0b000<<25) | (0b1100<<21) | (0<<20) | base<<16 | dst<<12 | (0b1011<<4) | off) + +#define STRa(dst, base, off) (AL | (0b011<<25) | (0b1100<<21) | (0<<20) | base<<16 | dst<<12 | off) +#define STRx(dst, base, off) (AL | (0b011<<25) | (0b1000<<21) | (0<<20) | base<<16 | dst<<12 | off) +#define STRai(dst, base, off) (AL | (0b010<<25) | (0b1100<<21) | (0<<20) | base<<16 | dst<<12 | rimm(off)) +#define STRxi(dst, base, off) (AL | (0b010<<25) | (0b1000<<21) | (0<<20) | base<<16 | dst<<12 | rimm(off)) +#define STRaiw(dst, base, off) (AL | (0b010<<25) | (0b1101<<21) | (0<<20) | base<<16 | dst<<12 | rimm(off)) +#define STRxiw(dst, base, off) (AL | (0b010<<25) | (0b1001<<21) | (0<<20) | base<<16 | dst<<12 | rimm(off)) + +// load with post-increment +#define POP1(reg) (AL | (0b010<<25) | (0b0100<<21) | (1<<20) | SP<<16 | reg<<12 | reg) +// store with post-increment +#define PUSH1(reg) (AL | (0b010<<25) | (0b1001<<21) | (0<<20) | SP<<16 | reg<<12 | 4) + +// branch to target address (for small jumps) +#define Bi(i) \ + (AL | (0b10)<<26 | (1<<25) /*I*/ | (0<<24) /*L*/ | (i)) +// call subroutine +#define BLi(i) \ + (AL | (0b10)<<26 | (1<<25) /*I*/ | (1<<24) /*L*/ | (i)) +// branch and exchange (register) +#define BX(reg) \ + (AL | 0b00010010<<20 | 0b1111<<16 | 0b1111<<12 | 0b1111<<8| 0b0001<<4 | reg) +// call subroutine (register) +#define BLX(reg) \ + (AL | 0b00010010<<20 | 0b1111<<16 | 0b1111<<12 | 0b1111<<8| 0b0011<<4 | reg) + +#define PUSH(mask) (AL | (0b100100<<22) | (0b10<<20) | (0b1101<<16) | mask) +#define PUSH2(r1, r2) (AL | (0b100100<<22) | (0b10<<20) | (0b1101<<16) | 1< 0xFFFF) \ + emit(MOVT(reg, (((arg>>16)&0xFFFF)))); \ + } while(0) + +// puts integer arg in register reg. adds nop if only one instr is needed to +// make size constant +#define emit_MOVRxi_or_NOP(reg, arg) do { \ + emit(MOVW(reg, (arg&0xFFFF))); \ + if (arg > 0xFFFF) \ + emit(MOVT(reg, (((arg>>16)&0xFFFF)))); \ + else \ + emit(NOP); \ + } while(0) + +// arm core register -> singe precision register +#define VMOVass(Vn, Rt) (AL|(0b1110<<24)|(0b000<<21)|(0<<20)| ((Vn>>1)<<16) | (Rt<<12) | (0b1010<<8) | ((Vn&1)<<7) | (1<<4)) +// singe precision register -> arm core register +#define VMOVssa(Rt, Vn) (AL|(0b1110<<24)|(0b000<<21)|(1<<20)| ((Vn>>1)<<16) | (Rt<<12) | (0b1010<<8) | ((Vn&1)<<7) | (1<<4)) + +#define _VCVT_F(Vd, Vm, opc2, op) \ + (AL|(0b11101<<23)|((Vd&1)<<22)|(0b111<<19)|(opc2<<16)|((Vd>>1)<<12)|(0b101<<9)|(0<<8)|(op<<7)|(1<<6)|((Vm&1)<<5)|(Vm>>1)) +#define VCVT_F32_U32(Sd, Sm) _VCVT_F(Sd, Sm, 0b000, 0 /* unsigned */) +#define VCVT_U32_F32(Sd, Sm) _VCVT_F(Sd, Sm, 0b100, 1 /* round zero */) +#define VCVT_F32_S32(Sd, Sm) _VCVT_F(Sd, Sm, 0b000, 1 /* unsigned */) +#define VCVT_S32_F32(Sd, Sm) _VCVT_F(Sd, Sm, 0b101, 1 /* round zero */) + +#define VLDRa(Vd, Rn, i) (AL|(0b1101<<24)|1<<23|((Vd&1)<<22)|1<<20|(Rn<<16)|((Vd>>1)<<12)|(0b1010<<8)|off8(i)) +#define VSTRa(Vd, Rn, i) (AL|(0b1101<<24)|1<<23|((Vd&1)<<22)|0<<20|(Rn<<16)|((Vd>>1)<<12)|(0b1010<<8)|off8(i)) + +#define VNEG_F32(Vd, Vm) \ + (AL|(0b11101<<23)|((Vd&1)<<22)|(0b11<<20)|(1<<16)|((Vd>>1)<<12)|(0b101<<9)|(0<<8)|(1<<6)|((Vm&1)<<5)|(Vm>>1)) + +#define VADD_F32(Vd, Vn, Vm) \ + (AL|(0b11100<<23)|((Vd&1)<<22)|(0b11<<20)|((Vn>>1)<<16)|((Vd>>1)<<12)|(0b101<<9)|(0<<8)|((Vn&1)<<7)|(0<<6)|((Vm&1)<<5)|(Vm>>1)) +#define VSUB_F32(Vd, Vn, Vm) \ + (AL|(0b11100<<23)|((Vd&1)<<22)|(0b11<<20)|((Vn>>1)<<16)|((Vd>>1)<<12)|(0b101<<9)|(0<<8)|((Vn&1)<<7)|(1<<6)|((Vm&1)<<5)|(Vm>>1)) +#define VMUL_F32(Vd, Vn, Vm) \ + (AL|(0b11100<<23)|((Vd&1)<<22)|(0b10<<20)|((Vn>>1)<<16)|((Vd>>1)<<12)|(0b101)<<9|(0<<8)|((Vn&1)<<7)|(0<<6)|((Vm&1)<<5)|(Vm>>1)) +#define VDIV_F32(Vd, Vn, Vm) \ + (AL|(0b11101<<23)|((Vd&1)<<22)|(0b00<<20)|((Vn>>1)<<16)|((Vd>>1)<<12)|(0b101<<9)|(0<<8)|((Vn&1)<<7)|(0<<6)|((Vm&1)<<5)|(Vm>>1)) + +#define _VCMP_F32(Vd, Vm, E) \ + (AL|(0b11101<<23)|((Vd&1)<<22)|(0b11<<20)|((0b0100)<<16)|((Vd>>1)<<12)|(0b101<<9)|(0<<8)|(E<<7)|(1<<6)|((Vm&1)<<5)|(Vm>>1)) +#define VCMP_F32(Vd, Vm) _VCMP_F32(Vd, Vm, 0) + +#define VMRS(Rt) \ + (AL|(0b11101111<<20)|(0b0001<<16)|(Rt<<12)|(0b1010<<8)|(1<<4)) + +// check if instruction in R0 is within range. Clobbers R1, R12 +#define CHECK_JUMP do { \ + static int bytes_to_skip = -1; \ + static unsigned branch = -1; \ + emit_MOVRxi(R1, (unsigned)vm->instructionCount); \ + emit(CMP(R0, R1)); \ + if (branch == -1) \ + branch = vm->codeLength; \ + emit(cond(LT, Bi(j_rel(bytes_to_skip)))); \ + emit_MOVRxi_or_NOP(R12, (unsigned)ErrJump); \ + emit(BLX(R12)); \ + if (bytes_to_skip == -1) \ + bytes_to_skip = vm->codeLength - branch; \ +} while(0) + +//#define CONST_OPTIMIZE +#ifdef CONST_OPTIMIZE +#define MAYBE_EMIT_CONST() \ + if (got_const) \ + { \ + got_const = 0; \ + vm->instructionPointers[instruction-1] = assembler_get_code_size(); \ + STACK_PUSH(4); \ + emit("movl $%d, (%%r9, %%rbx, 4)", const_value); \ + } +#else +#define MAYBE_EMIT_CONST() +#endif + +// optimize: use load multiple +#define IJ(comparator) do { \ + MAYBE_EMIT_CONST(); \ + emit_MOVRxi(R0, arg.i); \ + CHECK_JUMP; \ + emit(LDRTxi(R0, rOPSTACK, 4)); \ + emit(LDRTxi(R1, rOPSTACK, 4)); \ + emit(CMP(R1, R0)); \ + emit(cond(comparator, Bi(j_rel(vm->instructionPointers[arg.i]-vm->codeLength)))); \ +} while (0) + +#define FJ(comparator) do { \ + emit_MOVRxi(R0, arg.i); \ + CHECK_JUMP; \ + emit(SUBi(rOPSTACK, rOPSTACK, 8)); \ + emit(VLDRa(S15, rOPSTACK, 4)); \ + emit(VLDRa(S14, rOPSTACK, 8)); \ + emit(VCMP_F32(S15, S14)); \ + emit(VMRS(APSR_nzcv)); \ + emit(cond(comparator, Bi(j_rel(vm->instructionPointers[arg.i]-vm->codeLength)))); \ +} while (0) + +#define printreg(reg) emit(PUSH1(R3)); emit(BLX(reg)); emit(POP1(R3)); + +static inline unsigned _j_rel(int x, int pc) +{ + if (x&3) goto err; + x = (x>>2)-2; + if (x < 0) + { + if ((x&(0xFF<<24)) != 0xFF<<24) + goto err; + x &= ~(0xFF<<24); + } + else if (x&(0xFF<<24)) + goto err; + return x; +err: + DIE("jump %d out of range at %d", x, pc); +} + +void VM_Compile(vm_t *vm, vmHeader_t *header) +{ + unsigned char *code; + int i_count, pc = 0; + int pass; + int codeoffsets[2]; // was 1024 but it's only used for OFF_CODE and OFF_IMMEDIATES + +#define j_rel(x) (pass?_j_rel(x, pc):0xBAD) +#define OFFSET(i) (pass?(j_rel(codeoffsets[i]-vm->codeLength)):(0xF000000F)) +//#define new_offset() (offsidx++) +#define get_offset(i) (codeoffsets[i]) +#define save_offset(i) (codeoffsets[i] = vm->codeLength) +#define OFF_CODE 0 +#define OFF_IMMEDIATES 1 + + vm->compiled = qfalse; + + vm->codeBase = NULL; + vm->codeLength = 0; + + for (pass = 0; pass < 2; ++pass) { + +// int offsidx = 0; + +#ifdef CONST_OPTIMIZE + // const optimization + unsigned got_const = 0, const_value = 0; +#endif + + if(pass) + { + vm->codeBase = mmap(NULL, vm->codeLength, PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); + if(vm->codeBase == MAP_FAILED) + Com_Error(ERR_FATAL, "VM_CompileARM: can't mmap memory"); + vm->codeLength = 0; + } + + //int (*entry)(vm_t*, int*, int*); + emit(PUSH((((1<<8)-1)<<4)|(1<<14))); // push R4-R11, LR + emit(SUBi(SP, SP, 12)); // align stack! + emit(LDRai(rCODEBASE, R0, offsetof(vm_t, codeBase))); + emit(LDRai(rDATABASE, R0, offsetof(vm_t, dataBase))); + emit(LDRai(rDATAMASK, R0, offsetof(vm_t, dataMask))); + emit(LDRai(rPSTACK, R1, 0)); + emit(MOV(rOPSTACK, R2)); // TODO: reverse opstack to avoid writing to return address + emit(MOV(rOPSTACKBASE, rOPSTACK)); + + emit(BLi(OFFSET(OFF_CODE))); + + // save return value in r0 + emit(LDRTxi(R0, rOPSTACK, 4)); // r0 = *opstack; rOPSTACK -= 4 + + emit(ADDi(SP, SP, 12)); // align stack! + emit(POP((((1<<8)-1)<<4)|(1<<15))); // pop R4-R11, LR -> PC + + /* save some immediates here */ + emit(BKPT(0)); + emit(BKPT(0)); + save_offset(OFF_IMMEDIATES); +// emit((unsigned)whatever); + emit(BKPT(0)); + emit(BKPT(0)); + + save_offset(OFF_CODE); +// offsidx = OFF_IMMEDIATES+1; + + code = (unsigned char *) header + header->codeOffset; + pc = 0; + + for (i_count = 0; i_count < header->instructionCount; i_count++) { + union { + unsigned char b[4]; + unsigned int i; + } arg; + unsigned char op = code[pc++]; + + vm->instructionPointers[i_count] = vm->codeLength; + + if (vm_opInfo[op] & opImm4) + { + memcpy(arg.b, &code[pc], 4); + pc += 4; +#ifdef EXCESSIVE_DEBUG + Com_Printf("%d: instruction %d (%s %d), offset %d\n", pass, i_count, opnames[op], arg.i, vm->codeLength); +#endif + } + else if (vm_opInfo[op] & opImm1) + { + arg.b[0] = code[pc]; + ++pc; +#ifdef EXCESSIVE_DEBUG + Com_Printf("%d: instruction %d (%s %hhd), offset %d\n", pass, i_count, opnames[op], arg.i, vm->codeLength); +#endif + } + else + { +#ifdef EXCESSIVE_DEBUG + Com_Printf("%d: instruction %d (%s), offset %d\n", pass, i_count, opnames[op], vm->codeLength); +#endif + } + + // TODO: for debug only + //emit_MOVRxi(R4, i_count); + + switch ( op ) + { + case OP_UNDEF: + break; + + case OP_IGNORE: + NOTIMPL(op); + break; + + case OP_BREAK: + emit(BKPT(0)); + break; + + case OP_ENTER: + MAYBE_EMIT_CONST(); + emit(PUSH1(LR)); + emit(SUBi(SP, SP, 12)); // align stack + if (arg.i == 0 || can_encode(arg.i)) + { + emit(SUBi(rPSTACK, rPSTACK, arg.i)); // pstack -= arg + } + else + { + emit_MOVR0i(arg.i); + emit(SUB(rPSTACK, rPSTACK, R0)); // pstack -= arg + } + break; + + case OP_LEAVE: + if (arg.i == 0 || can_encode(arg.i)) + { + emit(ADDi(rPSTACK, rPSTACK, arg.i)); // pstack += arg + } + else + { + emit_MOVR0i(arg.i); + emit(ADD(rPSTACK, rPSTACK, R0)); // pstack += arg + } + emit(ADDi(SP, SP, 12)); + emit(0xe49df004); // pop pc + break; + + case OP_CALL: +#if 0 + // save next instruction + emit_MOVR0i(i_count); + emit(STRa(R0, rDATABASE, rPSTACK)); // dataBase[pstack] = r0 +#endif +#ifdef CONST_OPTIMIZE + if (got_const) + { + NOTIMPL(op); + } + else +#endif + { + static int bytes_to_skip = -1; + static unsigned start_block = -1; + MAYBE_EMIT_CONST(); + // get instruction nr from stack + emit(LDRTxi(R0, rOPSTACK, 4)); // r0 = *opstack; rOPSTACK -= 4 + emit(CMPi(R0, 0)); // check if syscall + if (start_block == -1) + start_block = vm->codeLength; + emit(cond(LT, Bi(j_rel(bytes_to_skip)))); + CHECK_JUMP; + emit_MOVRxi_or_NOP(R1, (unsigned)vm->instructionPointers); + emit(LDRa(R0, R1, rLSL(2, R0))); // r0 = ((int*)r1)[r0] + emit(ADD(R0, rCODEBASE, R0)); // r0 = codeBase+r0 + emit(BLX(R0)); + emit(Bi(j_rel(vm->instructionPointers[i_count+1]-vm->codeLength))); + if (bytes_to_skip == -1) + bytes_to_skip = vm->codeLength - start_block; + emit(MOV(R1, rPSTACK)); + emit_MOVRxi(R12, (unsigned)asmcall); + emit(BLX(R12)); + // store return value + emit(STRaiw(R0, rOPSTACK, 4)); // opstack+=4; *opstack = r0 + } + break; + + case OP_PUSH: + MAYBE_EMIT_CONST(); + emit(ADDi(rOPSTACK, rOPSTACK, 4)); + break; + + case OP_POP: + MAYBE_EMIT_CONST(); + emit(SUBi(rOPSTACK, rOPSTACK, 4)); + break; + + case OP_CONST: + MAYBE_EMIT_CONST(); + emit_MOVR0i(arg.i); + emit(STRaiw(R0, rOPSTACK, 4)); // opstack+=4; *opstack = r0 + break; + + case OP_LOCAL: + MAYBE_EMIT_CONST(); + if (arg.i == 0 || can_encode(arg.i)) + { + emit(ADDi(R0, rPSTACK, arg.i)); // r0 = pstack+arg + } + else + { + emit_MOVR0i(arg.i); + emit(ADD(R0, rPSTACK, R0)); // r0 = pstack+arg + } + emit(STRaiw(R0, rOPSTACK, 4)); // opstack+=4; *opstack = r0 + break; + + case OP_JUMP: +#ifdef CONST_OPTIMIZE + if (got_const) + { + NOTIMPL(op); + } + else +#endif + { + emit(LDRTxi(R0, rOPSTACK, 4)); // r0 = *opstack; rOPSTACK -= 4 + CHECK_JUMP; + emit_MOVRxi(R1, (unsigned)vm->instructionPointers); + emit(LDRa(R0, R1, rLSL(2, R0))); // r0 = ((int*)r1)[r0] + emit(ADD(R0, rCODEBASE, R0)); // r0 = codeBase+r0 + emit(BLX(R0)); + } + break; + + case OP_EQ: + IJ(EQ); + break; + + case OP_NE: + IJ(NE); + break; + + case OP_LTI: + IJ(LT); + break; + + case OP_LEI: + IJ(LE); + break; + + case OP_GTI: + IJ(GT); + break; + + case OP_GEI: + IJ(GE); + break; + + case OP_LTU: + IJ(LO); + break; + + case OP_LEU: + IJ(LS); + break; + + case OP_GTU: + IJ(HI); + break; + + case OP_GEU: + IJ(HS); + break; + + case OP_EQF: + FJ(EQ); + break; + + case OP_NEF: + FJ(NE); + break; + + case OP_LTF: + FJ(LT); + break; + + case OP_LEF: + FJ(LE); + break; + + case OP_GTF: + FJ(GT); + break; + + case OP_GEF: + FJ(GE); + break; + + case OP_LOAD1: + MAYBE_EMIT_CONST(); + emit(LDRai(R0, rOPSTACK, 0)); // r0 = *opstack + emit(AND(R0, rDATAMASK, R0)); // r0 = r0 & rDATAMASK + emit(LDRBa(R0, rDATABASE, R0)); // r0 = (unsigned char)dataBase[r0] + emit(STRai(R0, rOPSTACK, 0)); // *opstack = r0 + break; + + case OP_LOAD2: + MAYBE_EMIT_CONST(); + emit(LDRai(R0, rOPSTACK, 0)); // r0 = *opstack + emit(AND(R0, rDATAMASK, R0)); // r0 = r0 & rDATAMASK + emit(LDRHa(R0, rDATABASE, R0)); // r0 = (unsigned short)dataBase[r0] + emit(STRai(R0, rOPSTACK, 0)); // *opstack = r0 + break; + + case OP_LOAD4: + MAYBE_EMIT_CONST(); + emit(LDRai(R0, rOPSTACK, 0)); // r0 = *opstack + emit(AND(R0, rDATAMASK, R0)); // r0 = r0 & rDATAMASK + emit(LDRa(R0, rDATABASE, R0)); // r0 = dataBase[r0] + emit(STRai(R0, rOPSTACK, 0)); // *opstack = r0 + break; + + case OP_STORE1: + MAYBE_EMIT_CONST(); + emit(LDRTxi(R0, rOPSTACK, 4)); // r0 = *opstack; rOPSTACK -= 4 + emit(LDRTxi(R1, rOPSTACK, 4)); // r1 = *opstack; rOPSTACK -= 4 + emit(AND(R1, rDATAMASK, R1)); // r1 = r1 & rDATAMASK + emit(STRBa(R0, rDATABASE, R1)); // database[r1] = r0 + break; + + case OP_STORE2: + MAYBE_EMIT_CONST(); + emit(LDRTxi(R0, rOPSTACK, 4)); // r0 = *opstack; rOPSTACK -= 4 + emit(LDRTxi(R1, rOPSTACK, 4)); // r1 = *opstack; rOPSTACK -= 4 + emit(AND(R1, rDATAMASK, R1)); // r1 = r1 & rDATAMASK + emit(STRHa(R0, rDATABASE, R1)); // database[r1] = r0 + break; + + case OP_STORE4: + MAYBE_EMIT_CONST(); + // optimize: use load multiple + // value + emit(LDRTxi(R0, rOPSTACK, 4)); // r0 = *opstack; rOPSTACK -= 4 + // pointer + emit(LDRTxi(R1, rOPSTACK, 4)); // r1 = *opstack; rOPSTACK -= 4 + emit(AND(R1, rDATAMASK, R1)); // r1 = r1 & rDATAMASK + // store value at pointer + emit(STRa(R0, rDATABASE, R1)); // database[r1] = r0 + break; + + case OP_ARG: + MAYBE_EMIT_CONST(); + emit(LDRTxi(R0, rOPSTACK, 4)); // r0 = *opstack; rOPSTACK -= 4 + emit(ADDi(R1, rPSTACK, arg.b[0])); // r1 = programStack+arg + emit(AND(R1, rDATAMASK, R1)); // r1 = r1 & rDATAMASK + emit(STRa(R0, rDATABASE, R1)); // dataBase[r1] = r0 + break; + + case OP_BLOCK_COPY: + MAYBE_EMIT_CONST(); + emit(LDRTxi(R1, rOPSTACK, 4)); // r0 = *opstack; rOPSTACK -= 4 + emit(LDRTxi(R0, rOPSTACK, 4)); + emit_MOVRxi(R2, arg.i); + emit_MOVRxi(R12, (unsigned)VM_BlockCopy); + emit(BLX(R12)); + break; + + case OP_SEX8: + MAYBE_EMIT_CONST(); + emit(LDRSBai(R0, rOPSTACK, 0)); // sign extend *opstack + emit(STRai(R0, rOPSTACK, 0)); // *opstack = r0 + break; + + case OP_SEX16: + MAYBE_EMIT_CONST(); + emit(LDRSHai(R0, rOPSTACK, 0)); // sign extend *opstack + emit(STRai(R0, rOPSTACK, 0)); // *opstack = r0 + break; + + case OP_NEGI: + MAYBE_EMIT_CONST(); + emit(LDRai(R0, rOPSTACK, 0)); // r0 = *opstack + emit(RSBi(R0, R0, 0)); // r0 = -r0 + emit(STRai(R0, rOPSTACK, 0)); // *opstack = r0 + break; + + case OP_ADD: + MAYBE_EMIT_CONST(); + emit(LDRai(R0, rOPSTACK, 0)); // r0 = *opstack + emit(LDRxiw(R1, rOPSTACK, 4)); // opstack-=4; r1 = *opstack + emit(ADD(R0, R1, R0)); // r0 = r1 + r0 + emit(STRai(R0, rOPSTACK, 0)); // *opstack = r0 + break; + + case OP_SUB: + MAYBE_EMIT_CONST(); + emit(LDRai(R0, rOPSTACK, 0)); // r0 = *opstack + emit(LDRxiw(R1, rOPSTACK, 4)); // opstack-=4; r1 = *opstack + emit(SUB(R0, R1, R0)); // r0 = r1 - r0 + emit(STRai(R0, rOPSTACK, 0)); // *opstack = r0 + break; + + case OP_DIVI: + case OP_DIVU: + MAYBE_EMIT_CONST(); + emit(LDRai(R1, rOPSTACK, 0)); // r1 = *opstack + emit(LDRxiw(R0, rOPSTACK, 4)); // opstack-=4; r0 = *opstack + if ( op == OP_DIVI ) + emit_MOVRxi(R12, (unsigned)__aeabi_idiv); + else + emit_MOVRxi(R12, (unsigned)__aeabi_uidiv); + emit(BLX(R12)); + emit(STRai(R0, rOPSTACK, 0)); // *opstack = r0 + break; + + case OP_MODI: + case OP_MODU: + MAYBE_EMIT_CONST(); + emit(LDRai(R1, rOPSTACK, 0)); // r1 = *opstack + emit(LDRxiw(R0, rOPSTACK, 4)); // opstack-=4; r0 = *opstack + if ( op == OP_MODI ) + emit_MOVRxi(R12, (unsigned)__aeabi_idivmod); + else + emit_MOVRxi(R12, (unsigned)__aeabi_uidivmod); + emit(BLX(R12)); + emit(STRai(R1, rOPSTACK, 0)); // *opstack = r1 + break; + + case OP_MULI: + case OP_MULU: + MAYBE_EMIT_CONST(); + emit(LDRai(R0, rOPSTACK, 0)); // r0 = *opstack + emit(LDRxiw(R1, rOPSTACK, 4)); // opstack-=4; r1 = *opstack + emit(MUL(R0, R1, R0)); // r0 = r1 * r0 + emit(STRai(R0, rOPSTACK, 0)); // *opstack = r0 + break; + + case OP_BAND: + MAYBE_EMIT_CONST(); + emit(LDRai(R0, rOPSTACK, 0)); // r0 = *opstack + emit(LDRxiw(R1, rOPSTACK, 4)); // opstack-=4; r1 = *opstack + emit(AND(R0, R1, R0)); // r0 = r1 & r0 + emit(STRai(R0, rOPSTACK, 0)); // *opstack = r0 + break; + + case OP_BOR: + MAYBE_EMIT_CONST(); + emit(LDRai(R0, rOPSTACK, 0)); // r0 = *opstack + emit(LDRxiw(R1, rOPSTACK, 4)); // opstack-=4; r1 = *opstack + emit(ORR(R0, R1, R0)); // r0 = r1 | r0 + emit(STRai(R0, rOPSTACK, 0)); // *opstack = r0 + break; + + case OP_BXOR: + MAYBE_EMIT_CONST(); + emit(LDRai(R0, rOPSTACK, 0)); // r0 = *opstack + emit(LDRxiw(R1, rOPSTACK, 4)); // opstack-=4; r1 = *opstack + emit(EOR(R0, R1, R0)); // r0 = r1 ^ r0 + emit(STRai(R0, rOPSTACK, 0)); // *opstack = r0 + break; + + case OP_BCOM: + MAYBE_EMIT_CONST(); + emit(LDRai(R0, rOPSTACK, 0)); // r0 = *opstack + emit(MVN(R0, R0)); // r0 = ~r0 + emit(STRai(R0, rOPSTACK, 0)); // *opstack = r0 + break; + + case OP_LSH: + MAYBE_EMIT_CONST(); + emit(LDRai(R0, rOPSTACK, 0)); // r0 = *opstack + emit(LDRxiw(R1, rOPSTACK, 4)); // opstack-=4; r1 = *opstack + emit(LSL(R0, R1, R0)); // r0 = r1 << r0 + emit(STRai(R0, rOPSTACK, 0)); // *opstack = r0 + break; + + case OP_RSHI: + MAYBE_EMIT_CONST(); + emit(LDRai(R0, rOPSTACK, 0)); // r0 = *opstack + emit(LDRxiw(R1, rOPSTACK, 4)); // opstack-=4; r1 = *opstack + emit(ASR(R0, R1, R0)); // r0 = r1 >> r0 + emit(STRai(R0, rOPSTACK, 0)); // *opstack = r0 + break; + + case OP_RSHU: + MAYBE_EMIT_CONST(); + emit(LDRai(R0, rOPSTACK, 0)); // r0 = *opstack + emit(LDRxiw(R1, rOPSTACK, 4)); // opstack-=4; r1 = *opstack + emit(LSR(R0, R1, R0)); // r0 = (unsigned)r1 >> r0 + emit(STRai(R0, rOPSTACK, 0)); // *opstack = r0 + break; + + case OP_NEGF: + MAYBE_EMIT_CONST(); + emit(VLDRa(S14, rOPSTACK, 0)); // s14 = *((float*)opstack) + emit(VNEG_F32(S14, S14)); // s15 = -s14 + emit(VSTRa(S14, rOPSTACK, 0)); // *((float*)opstack) = s15 + break; + + case OP_ADDF: + MAYBE_EMIT_CONST(); + emit(VLDRa(S14, rOPSTACK, 0)); // s14 = *((float*)opstack) + // vldr can't modify rOPSTACK so + // we'd either need to change it + // with sub or use regular ldr+vmov + emit(LDRxiw(R0, rOPSTACK, 4)); // opstack-=4; r1 = *opstack + emit(VMOVass(S15,R0)); // s15 = r0 + emit(VADD_F32(S14, S15, S14)); // s14 = s14 + s15 + emit(VSTRa(S14, rOPSTACK, 0)); // *((float*)opstack) = s15 + break; + + case OP_SUBF: + emit(VLDRa(S14, rOPSTACK, 0)); // s14 = *((float*)opstack) + // see OP_ADDF + emit(LDRxiw(R0, rOPSTACK, 4)); // opstack-=4; r1 = *opstack + emit(VMOVass(S15,R0)); // s15 = r0 + emit(VSUB_F32(S14, S15, S14)); // s14 = s14 - s15 + emit(VSTRa(S14, rOPSTACK, 0)); // *((float*)opstack) = s15 + break; + + case OP_DIVF: + emit(VLDRa(S14, rOPSTACK, 0)); // s14 = *((float*)opstack) + // see OP_ADDF + emit(LDRxiw(R0, rOPSTACK, 4)); // opstack-=4; r1 = *opstack + emit(VMOVass(S15,R0)); // s15 = r0 + emit(VDIV_F32(S14, S15, S14)); // s14 = s14 / s15 + emit(VSTRa(S14, rOPSTACK, 0)); // *((float*)opstack) = s15 + break; + + case OP_MULF: + emit(VLDRa(S14, rOPSTACK, 0)); // s14 = *((float*)opstack) + // see OP_ADDF + emit(LDRxiw(R0, rOPSTACK, 4)); // opstack-=4; r1 = *opstack + emit(VMOVass(S15,R0)); // s15 = r0 + emit(VMUL_F32(S14, S15, S14)); // s14 = s14 * s15 + emit(VSTRa(S14, rOPSTACK, 0)); // *((float*)opstack) = s15 + break; + + case OP_CVIF: + MAYBE_EMIT_CONST(); + emit(LDRai(R0, rOPSTACK, 0)); // r0 = *opstack + emit(VMOVass(S14,R0)); // s14 = r0 + emit(VCVT_F32_S32(S14, S14)); // s15 = (float)s14 + emit(VSTRa(S14, rOPSTACK, 0)); // *((float*)opstack) = s15 + break; + + case OP_CVFI: + MAYBE_EMIT_CONST(); + emit(VLDRa(S14, rOPSTACK, 0)); // s14 = *((float*)opstack) + emit(VCVT_S32_F32(S14, S14)); // s15 = (int)s14 + emit(VMOVssa(R0,S14)); // s14 = r0 + emit(STRai(R0, rOPSTACK, 0)); // *opstack = r0 + break; + } + } + + // never reached + emit(BKPT(0)); + } // pass + + if (mprotect(vm->codeBase, vm->codeLength, PROT_READ|PROT_EXEC/* |PROT_WRITE */)) { + VM_Destroy_Compiled(vm); + DIE("mprotect failed"); + } + + // clear icache, http://blogs.arm.com/software-enablement/141-caches-and-self-modifying-code/ + __clear_cache(vm->codeBase, vm->codeBase+vm->codeLength); + + vm->destroy = VM_Destroy_Compiled; + vm->compiled = qtrue; +} + +int VM_CallCompiled(vm_t *vm, int *args) +{ + byte stack[OPSTACK_SIZE + 15]; + int *opStack; + int programStack = vm->programStack; + int stackOnEntry = programStack; + byte *image = vm->dataBase; + int *argPointer; + int retVal; + + currentVM = vm; + + vm->currentlyInterpreting = qtrue; + + programStack -= ( 8 + 4 * MAX_VMMAIN_ARGS ); + argPointer = (int *)&image[ programStack + 8 ]; + memcpy( argPointer, args, 4 * MAX_VMMAIN_ARGS ); + argPointer[-1] = 0; + argPointer[-2] = -1; + + + opStack = PADP(stack, 16); + *opStack = 0xDEADBEEF; + +#if 0 + Com_Printf("r5 opStack:\t\t%p\n", opStack); + Com_Printf("r7 codeBase:\t\t%p\n", vm->codeBase); + Com_Printf("r8 programStack:\t0x%x\n", programStack); + Com_Printf("r9 dataBase:\t\t%p\n", vm->dataBase); +#endif + + /* call generated code */ + { + //int (*entry)(void *, int, void *, int); + int (*entry)(vm_t*, int*, int*); + + entry = (void *)(vm->codeBase); + //__asm__ volatile("bkpt"); + //retVal = entry(vm->codeBase, programStack, vm->dataBase, vm->dataMask); + retVal = entry(vm, &programStack, opStack); + } + + if(*opStack != 0xDEADBEEF) + { + Com_Error(ERR_DROP, "opStack corrupted in compiled code"); + } + + if(programStack != stackOnEntry - (8 + 4 * MAX_VMMAIN_ARGS)) + Com_Error(ERR_DROP, "programStack corrupted in compiled code"); + + vm->programStack = stackOnEntry; + vm->currentlyInterpreting = qfalse; + + return retVal; +} diff --git a/engine/code/qcommon/vm_interpreted.c b/engine/code/qcommon/vm_interpreted.c index aa45fde6..cb86a08d 100644 --- a/engine/code/qcommon/vm_interpreted.c +++ b/engine/code/qcommon/vm_interpreted.c @@ -317,8 +317,8 @@ locals from sp int VM_CallInterpreted( vm_t *vm, int *args ) { byte stack[OPSTACK_SIZE + 15]; - register int *opStack; - register uint8_t opStackOfs; + int *opStack; + uint8_t opStackOfs; int programCounter; int programStack; int stackOnEntry; @@ -436,31 +436,31 @@ int VM_CallInterpreted( vm_t *vm, int *args ) { return 0; } #endif - r0 = opStack[opStackOfs] = *(int *) &image[r0 & dataMask & ~3 ]; + r0 = opStack[opStackOfs] = *(int *) &image[ r0 & dataMask ]; goto nextInstruction2; case OP_LOAD2: - r0 = opStack[opStackOfs] = *(unsigned short *)&image[ r0&dataMask&~1 ]; + r0 = opStack[opStackOfs] = *(unsigned short *)&image[ r0 & dataMask ]; goto nextInstruction2; case OP_LOAD1: - r0 = opStack[opStackOfs] = image[ r0&dataMask ]; + r0 = opStack[opStackOfs] = image[ r0 & dataMask ]; goto nextInstruction2; case OP_STORE4: - *(int *)&image[ r1&(dataMask & ~3) ] = r0; + *(int *)&image[ r1 & dataMask ] = r0; opStackOfs -= 2; goto nextInstruction; case OP_STORE2: - *(short *)&image[ r1&(dataMask & ~1) ] = r0; + *(short *)&image[ r1 & dataMask ] = r0; opStackOfs -= 2; goto nextInstruction; case OP_STORE1: - image[ r1&dataMask ] = r0; + image[ r1 & dataMask ] = r0; opStackOfs -= 2; goto nextInstruction; case OP_ARG: // single byte offset from programStack - *(int *)&image[ (codeImage[programCounter] + programStack)&dataMask&~3 ] = r0; + *(int *)&image[ (codeImage[programCounter] + programStack) & dataMask ] = r0; opStackOfs--; programCounter += 1; goto nextInstruction; diff --git a/engine/code/qcommon/vm_local.h b/engine/code/qcommon/vm_local.h index 76b1a4b1..07e89675 100644 --- a/engine/code/qcommon/vm_local.h +++ b/engine/code/qcommon/vm_local.h @@ -170,6 +170,7 @@ struct vm_s { byte *dataBase; int dataMask; + int dataAlloc; // actually allocated int stackBottom; // if programStack < stackBottom, error diff --git a/engine/code/qcommon/vm_x86.c b/engine/code/qcommon/vm_x86.c index 0081982b..38c25a9a 100644 --- a/engine/code/qcommon/vm_x86.c +++ b/engine/code/qcommon/vm_x86.c @@ -800,7 +800,7 @@ qboolean ConstOptimize(vm_t *vm, int callProcOfsSyscall) return qtrue; case OP_STORE4: - EmitMovEAXStack(vm, (vm->dataMask & ~3)); + EmitMovEAXStack(vm, vm->dataMask); #if idx64 EmitRexString(0x41, "C7 04 01"); // mov dword ptr [r9 + eax], 0x12345678 Emit4(Constant4()); @@ -815,7 +815,7 @@ qboolean ConstOptimize(vm_t *vm, int callProcOfsSyscall) return qtrue; case OP_STORE2: - EmitMovEAXStack(vm, (vm->dataMask & ~1)); + EmitMovEAXStack(vm, vm->dataMask); #if idx64 Emit1(0x66); // mov word ptr [r9 + eax], 0x1234 EmitRexString(0x41, "C7 04 01"); @@ -1387,7 +1387,7 @@ void VM_Compile(vm_t *vm, vmHeader_t *header) case OP_STORE4: EmitMovEAXStack(vm, 0); EmitString("8B 54 9F FC"); // mov edx, dword ptr -4[edi + ebx * 4] - MASK_REG("E2", vm->dataMask & ~3); // and edx, 0x12345678 + MASK_REG("E2", vm->dataMask); // and edx, 0x12345678 #if idx64 EmitRexString(0x41, "89 04 11"); // mov dword ptr [r9 + edx], eax #else @@ -1399,7 +1399,7 @@ void VM_Compile(vm_t *vm, vmHeader_t *header) case OP_STORE2: EmitMovEAXStack(vm, 0); EmitString("8B 54 9F FC"); // mov edx, dword ptr -4[edi + ebx * 4] - MASK_REG("E2", vm->dataMask & ~1); // and edx, 0x12345678 + MASK_REG("E2", vm->dataMask); // and edx, 0x12345678 #if idx64 Emit1(0x66); // mov word ptr [r9 + edx], eax EmitRexString(0x41, "89 04 11"); diff --git a/engine/code/renderercommon/qgl.h b/engine/code/renderercommon/qgl.h index 8b7367af..eae0d90a 100644 --- a/engine/code/renderercommon/qgl.h +++ b/engine/code/renderercommon/qgl.h @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 1999-2005 Id Software, Inc., 2017 Google Inc. This file is part of Quake III Arena source code. @@ -42,356 +42,96 @@ extern void (APIENTRYP qglUnlockArraysEXT) (void); //=========================================================================== -#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 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 - // GL function loader, based on https://gist.github.com/rygorous/16796a0c876cf8a5f542caddb55bce8a - -// OpenGL 1.2, was GL_EXT_draw_range_elements -#define QGL_1_2_PROCS \ - GLE(void, DrawRangeElements, GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices) \ +// get missing functions from code/SDL2/include/SDL_opengl.h + +// OpenGL 1.0/1.1 and OpenGL ES 1.0 +#define QGL_1_1_PROCS \ + GLE(void, AlphaFunc, GLenum func, GLclampf ref) \ + GLE(void, BindTexture, GLenum target, GLuint texture) \ + GLE(void, BlendFunc, GLenum sfactor, GLenum dfactor) \ + GLE(void, ClearColor, GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha) \ + GLE(void, Clear, GLbitfield mask) \ + GLE(void, ClearStencil, GLint s) \ + GLE(void, Color4f, GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) \ + GLE(void, ColorMask, GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) \ + GLE(void, ColorPointer, GLint size, GLenum type, GLsizei stride, const GLvoid *ptr) \ + GLE(void, CopyTexSubImage2D, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) \ + GLE(void, CullFace, GLenum mode) \ + GLE(void, DeleteTextures, GLsizei n, const GLuint *textures) \ + GLE(void, DepthFunc, GLenum func) \ + GLE(void, DepthMask, GLboolean flag) \ + GLE(void, DisableClientState, GLenum cap) \ + GLE(void, Disable, GLenum cap) \ + GLE(void, DrawArrays, GLenum mode, GLint first, GLsizei count) \ + GLE(void, DrawElements, GLenum mode, GLsizei count, GLenum type, const GLvoid *indices) \ + GLE(void, EnableClientState, GLenum cap) \ + GLE(void, Enable, GLenum cap) \ + GLE(void, Finish, void) \ + GLE(void, Flush, void) \ + GLE(void, GenTextures, GLsizei n, GLuint *textures ) \ + GLE(void, GetBooleanv, GLenum pname, GLboolean *params) \ + GLE(GLenum, GetError, void) \ + GLE(void, GetFloatv, GLenum pname, GLfloat *params) \ + GLE(void, GetIntegerv, GLenum pname, GLint *params) \ + GLE(const GLubyte *, GetString, GLenum name) \ + GLE(void, LineWidth, GLfloat width) \ + GLE(void, LoadIdentity, void) \ + GLE(void, LoadMatrixf, const GLfloat *m) \ + GLE(void, MatrixMode, GLenum mode) \ + GLE(void, PolygonOffset, GLfloat factor, GLfloat units) \ + GLE(void, PopMatrix, void) \ + GLE(void, PushMatrix, void) \ + GLE(void, ReadPixels, GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels) \ + GLE(void, Scissor, GLint x, GLint y, GLsizei width, GLsizei height) \ + GLE(void, ShadeModel, GLenum mode) \ + GLE(void, StencilFunc, GLenum func, GLint ref, GLuint mask) \ + GLE(void, StencilMask, GLuint mask) \ + GLE(void, StencilOp, GLenum fail, GLenum zfail, GLenum zpass) \ + GLE(void, TexCoordPointer, GLint size, GLenum type, GLsizei stride, const GLvoid *ptr) \ + GLE(void, TexEnvf, GLenum target, GLenum pname, GLfloat param) \ + GLE(void, TexImage2D, GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels) \ + GLE(void, TexParameterf, GLenum target, GLenum pname, GLfloat param) \ + GLE(void, TexParameteri, GLenum target, GLenum pname, GLint param) \ + GLE(void, TexSubImage2D, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels) \ + GLE(void, Translatef, GLfloat x, GLfloat y, GLfloat z) \ + GLE(void, VertexPointer, GLint size, GLenum type, GLsizei stride, const GLvoid *ptr) \ + GLE(void, Viewport, GLint x, GLint y, GLsizei width, GLsizei height) \ + +// OpenGL 1.0/1.1 but not OpenGL ES 1.x +#define QGL_DESKTOP_1_1_PROCS \ + GLE(void, ArrayElement, GLint i) \ + GLE(void, Begin, GLenum mode) \ + GLE(void, ClearDepth, GLclampd depth) \ + GLE(void, ClipPlane, GLenum plane, const GLdouble *equation) \ + GLE(void, Color3f, GLfloat red, GLfloat green, GLfloat blue) \ + GLE(void, Color4ubv, const GLubyte *v) \ + GLE(void, DepthRange, GLclampd near_val, GLclampd far_val) \ + GLE(void, DrawBuffer, GLenum mode) \ + GLE(void, End, void) \ + GLE(void, Frustum, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val) \ + GLE(void, Ortho, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val) \ + GLE(void, PolygonMode, GLenum face, GLenum mode) \ + GLE(void, TexCoord2f, GLfloat s, GLfloat t) \ + GLE(void, TexCoord2fv, const GLfloat *v) \ + GLE(void, Vertex2f, GLfloat x, GLfloat y) \ + GLE(void, Vertex3f, GLfloat x, GLfloat y, GLfloat z) \ + GLE(void, Vertex3fv, const GLfloat *v) \ + +// OpenGL ES 1.1 but not desktop OpenGL 1.x +#define QGL_ES_1_1_PROCS \ + GLE(void, ClearDepthf, GLclampf depth) \ + GLE(void, ClipPlanef, GLenum plane, const GLfloat *equation) \ + GLE(void, DepthRangef, GLclampf near_val, GLclampf far_val) \ + GLE(void, Frustumf, GLfloat left, GLfloat right, GLfloat bottom, GLfloat top, GLfloat near_val, GLfloat far_val) \ + GLE(void, Orthof, GLfloat left, GLfloat right, GLfloat bottom, GLfloat top, GLfloat near_val, GLfloat far_val) \ // OpenGL 1.3, was GL_ARB_texture_compression #define QGL_1_3_PROCS \ + GLE(void, ActiveTexture, GLenum texture) \ GLE(void, CompressedTexImage2D, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data) \ GLE(void, CompressedTexSubImage2D, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data) \ -// OpenGL 1.4, was GL_EXT_multi_draw_arrays -#define QGL_1_4_PROCS \ - GLE(void, MultiDrawElements, GLenum mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount) \ - // OpenGL 1.5, was GL_ARB_vertex_buffer_object and GL_ARB_occlusion_query #define QGL_1_5_PROCS \ GLE(void, GenQueries, GLsizei n, GLuint *ids) \ @@ -405,6 +145,8 @@ extern void (APIENTRYP qglUnlockArraysEXT) (void); GLE(void, GenBuffers, GLsizei n, GLuint *buffers) \ GLE(void, BufferData, GLenum target, GLsizeiptr size, const void *data, GLenum usage) \ GLE(void, BufferSubData, GLenum target, GLintptr offset, GLsizeiptr size, const void *data) \ + GLE(void*, MapBuffer, GLenum target, GLenum access) \ + GLE(GLboolean, UnmapBuffer, GLenum target) \ // OpenGL 2.0, was GL_ARB_shading_language_100, GL_ARB_vertex_program, GL_ARB_shader_objects, and GL_ARB_vertex_shader #define QGL_2_0_PROCS \ @@ -486,97 +228,31 @@ extern void (APIENTRYP qglUnlockArraysEXT) (void); #define GL_HALF_FLOAT_ARB 0x140B #endif -// GL_EXT_framebuffer_object -#define QGL_EXT_framebuffer_object_PROCS \ - GLE(void, BindRenderbufferEXT, GLenum target, GLuint renderbuffer) \ - GLE(void, DeleteRenderbuffersEXT, GLsizei n, const GLuint *renderbuffers) \ - GLE(void, GenRenderbuffersEXT, GLsizei n, GLuint *renderbuffers) \ - GLE(void, RenderbufferStorageEXT, GLenum target, GLenum internalformat, GLsizei width, GLsizei height) \ - GLE(void, BindFramebufferEXT, GLenum target, GLuint framebuffer) \ - GLE(void, DeleteFramebuffersEXT, GLsizei n, const GLuint *framebuffers) \ - GLE(void, GenFramebuffersEXT, GLsizei n, GLuint *framebuffers) \ - GLE(GLenum, CheckFramebufferStatusEXT, GLenum target) \ - GLE(void, FramebufferTexture2DEXT, GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) \ - GLE(void, FramebufferRenderbufferEXT, GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) \ - GLE(void, GenerateMipmapEXT, GLenum target) \ - -#ifndef GL_EXT_framebuffer_object -#define GL_EXT_framebuffer_object -#define GL_FRAMEBUFFER_EXT 0x8D40 -#define GL_RENDERBUFFER_EXT 0x8D41 -#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_WIDTH_EXT 0x8D42 -#define GL_RENDERBUFFER_HEIGHT_EXT 0x8D43 -#define GL_RENDERBUFFER_INTERNAL_FORMAT_EXT 0x8D44 -#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 -#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_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_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_FRAMEBUFFER_BINDING_EXT 0x8CA6 -#define GL_RENDERBUFFER_BINDING_EXT 0x8CA7 -#define GL_MAX_COLOR_ATTACHMENTS_EXT 0x8CDF -#define GL_MAX_RENDERBUFFER_SIZE_EXT 0x84E8 -#define GL_INVALID_FRAMEBUFFER_OPERATION_EXT 0x0506 -#endif - -// GL_EXT_framebuffer_blit -#define QGL_EXT_framebuffer_blit_PROCS \ - GLE(void, BlitFramebufferEXT, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) \ - -#ifndef GL_EXT_framebuffer_blit -#define GL_EXT_framebuffer_blit -#define GL_READ_FRAMEBUFFER_EXT 0x8CA8 -#define GL_DRAW_FRAMEBUFFER_EXT 0x8CA9 -#define GL_DRAW_FRAMEBUFFER_BINDING_EXT 0x8CA6 -#define GL_READ_FRAMEBUFFER_BINDING_EXT 0x8CAA -#endif - -// GL_EXT_framebuffer_multisample -#define QGL_EXT_framebuffer_multisample_PROCS \ - GLE(void, RenderbufferStorageMultisampleEXT, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height) \ - -#ifndef GL_EXT_framebuffer_multisample -#define GL_EXT_framebuffer_multisample -#define GL_RENDERBUFFER_SAMPLES_EXT 0x8CAB -#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT 0x8D56 -#define GL_MAX_SAMPLES_EXT 0x8D57 -#endif +// OpenGL 3.0 specific +#define QGL_3_0_PROCS \ + GLE(const GLubyte *, GetStringi, GLenum name, GLuint index) \ + +// GL_ARB_framebuffer_object, built-in to OpenGL 3.0 +#define QGL_ARB_framebuffer_object_PROCS \ + GLE(void, BindRenderbuffer, GLenum target, GLuint renderbuffer) \ + GLE(void, DeleteRenderbuffers, GLsizei n, const GLuint *renderbuffers) \ + GLE(void, GenRenderbuffers, GLsizei n, GLuint *renderbuffers) \ + GLE(void, RenderbufferStorage, GLenum target, GLenum internalformat, GLsizei width, GLsizei height) \ + GLE(void, BindFramebuffer, GLenum target, GLuint framebuffer) \ + GLE(void, DeleteFramebuffers, GLsizei n, const GLuint *framebuffers) \ + GLE(void, GenFramebuffers, GLsizei n, GLuint *framebuffers) \ + GLE(GLenum, CheckFramebufferStatus, GLenum target) \ + GLE(void, FramebufferTexture2D, GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) \ + GLE(void, FramebufferRenderbuffer, GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) \ + GLE(void, GenerateMipmap, GLenum target) \ + GLE(void, BlitFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) \ + GLE(void, RenderbufferStorageMultisample, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height) \ + +// GL_ARB_vertex_array_object, built-in to OpenGL 3.0 +#define QGL_ARB_vertex_array_object_PROCS \ + GLE(void, BindVertexArray, GLuint array) \ + GLE(void, DeleteVertexArrays, GLsizei n, const GLuint *arrays) \ + GLE(void, GenVertexArrays, GLsizei n, GLuint *arrays) \ #ifndef GL_ARB_texture_compression_rgtc #define GL_ARB_texture_compression_rgtc @@ -604,17 +280,6 @@ extern void (APIENTRYP qglUnlockArraysEXT) (void); #define GL_TEXTURE_CUBE_MAP_SEAMLESS 0x884F #endif -// GL_ARB_vertex_array_object -#define QGL_ARB_vertex_array_object_PROCS \ - GLE(void, BindVertexArray, GLuint array) \ - GLE(void, DeleteVertexArrays, GLsizei n, const GLuint *arrays) \ - GLE(void, GenVertexArrays, GLsizei n, GLuint *arrays) \ - -#ifndef GL_ARB_vertex_array_object -#define GL_ARB_vertex_array_object -#define GL_VERTEX_ARRAY_BINDING_ARB 0x85B5 -#endif - // GL_EXT_direct_state_access #define QGL_EXT_direct_state_access_PROCS \ GLE(GLvoid, BindMultiTextureEXT, GLenum texunit, GLenum target, GLuint texture) \ @@ -640,16 +305,21 @@ extern void (APIENTRYP qglUnlockArraysEXT) (void); GLE(GLvoid, NamedFramebufferRenderbufferEXT, GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) \ #define GLE(ret, name, ...) typedef ret APIENTRY name##proc(__VA_ARGS__); extern name##proc * qgl##name; -QGL_1_2_PROCS; +QGL_1_1_PROCS; +QGL_DESKTOP_1_1_PROCS; +QGL_ES_1_1_PROCS; QGL_1_3_PROCS; -QGL_1_4_PROCS; QGL_1_5_PROCS; QGL_2_0_PROCS; -QGL_EXT_framebuffer_object_PROCS; -QGL_EXT_framebuffer_blit_PROCS; -QGL_EXT_framebuffer_multisample_PROCS; +QGL_3_0_PROCS; +QGL_ARB_framebuffer_object_PROCS; QGL_ARB_vertex_array_object_PROCS; QGL_EXT_direct_state_access_PROCS; #undef GLE +extern int qglMajorVersion, qglMinorVersion; +extern int qglesMajorVersion, qglesMinorVersion; +#define QGL_VERSION_ATLEAST( major, minor ) ( qglMajorVersion > major || ( qglMajorVersion == major && qglMinorVersion >= minor ) ) +#define QGLES_VERSION_ATLEAST( major, minor ) ( qglesMajorVersion > major || ( qglesMajorVersion == major && qglesMinorVersion >= minor ) ) + #endif diff --git a/engine/code/renderercommon/tr_common.h b/engine/code/renderercommon/tr_common.h index dd11519d..3fd4473d 100644 --- a/engine/code/renderercommon/tr_common.h +++ b/engine/code/renderercommon/tr_common.h @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 1999-2005 Id Software, Inc., 2017 Google Inc. This file is part of Quake III Arena source code. @@ -23,8 +23,10 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define TR_COMMON_H #include "../qcommon/q_shared.h" -#include "../renderercommon/tr_public.h" +#include "../qcommon/qcommon.h" +#include "../qcommon/qfiles.h" #include "qgl.h" +#include "tr_public.h" typedef enum { @@ -43,14 +45,14 @@ typedef enum IMGFLAG_NO_COMPRESSION = 0x0010, IMGFLAG_NOLIGHTSCALE = 0x0020, IMGFLAG_CLAMPTOEDGE = 0x0040, - IMGFLAG_SRGB = 0x0080, - IMGFLAG_GENNORMALMAP = 0x0100, + IMGFLAG_GENNORMALMAP = 0x0080, } imgFlags_t; typedef struct image_s { char imgName[MAX_QPATH]; // game path, including extension int width, height; // source image int uploadWidth, uploadHeight; // after power of two and picmip but not including clamp to MAX_TEXTURE_SIZE + int uploadMips; GLuint texnum; // gl texture binding int frameUsed; // for texture usage in frame statistics @@ -117,7 +119,7 @@ extern cvar_t *r_saveFontData; qboolean R_GetModeInfo( int *width, int *height, float *windowAspect, int mode ); -float R_NoiseGet4f( float x, float y, float z, float t ); +float R_NoiseGet4f( float x, float y, float z, double t ); void R_NoiseInit( void ); image_t *R_FindImageFile( const char *name, imgType_t type, imgFlags_t flags ); @@ -147,6 +149,17 @@ void R_LoadJPG( const char *name, byte **pic, int *width, int *height ); void R_LoadPCX( const char *name, byte **pic, int *width, int *height ); void R_LoadPNG( const char *name, byte **pic, int *width, int *height ); void R_LoadTGA( const char *name, byte **pic, int *width, int *height ); +void R_LoadDDS( const char *filename, byte **pic, int *width, int *height, GLenum *picFormat, int *numMips ); + +/* +==================================================================== + +MODEL HELPERS + +==================================================================== +*/ + +qboolean R_DMLabToMD3( const char *mod_name, md3Header_t **mod_md3 ); /* ==================================================================== @@ -156,9 +169,9 @@ IMPLEMENTATION SPECIFIC FUNCTIONS ==================================================================== */ -void GLimp_Init( void ); -void GLimp_MakeCurrent( void ); +void GLimp_Init( qboolean ); void GLimp_Shutdown( void ); +void GLimp_MakeCurrent( void ); void GLimp_EndFrame( void ); void GLimp_LogComment( char *comment ); @@ -168,5 +181,6 @@ void GLimp_SetGamma( unsigned char red[256], unsigned char green[256], unsigned char blue[256] ); +void* GLimp_GetProcAddress( const char *func ); #endif diff --git a/engine/code/renderercommon/tr_model.c b/engine/code/renderercommon/tr_model.c new file mode 100644 index 00000000..b9f45215 --- /dev/null +++ b/engine/code/renderercommon/tr_model.c @@ -0,0 +1,61 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include "../deepmind/context.h" +#include "tr_common.h" + +// Load a custom model as a little endian MD3 data structure, stored in +// *mod_md3. +// Custom models are returned by the level API in response to a call to +// api:createModel with a given model name (mod_name). This function uses the +// DMLab context to query for such models. +// When the custom model is retrieved successfully from the level API, this +// function dynamically allocates the returned MD3 data structure through the +// file-loading system allocator. Ownership is transferred to the caller, which +// must release such memory via Hunk_FreeTempMemory. +// Returns whether the custom model was loaded successfully. +qboolean R_DMLabToMD3(const char *mod_name, md3Header_t **mod_md3) { + DeepmindContext *ctx = dmlab_context(); + + // Load the model in the context. + if (!ctx->hooks.find_model(ctx->userdata, mod_name)) { + return qfalse; + } + + // Retrieve the model's accessor API. + DeepmindModelGetters model_getters; + void* model_data; + ctx->hooks.model_getters(ctx->userdata, &model_getters, &model_data); + + // Compute the required buffer size. + size_t buffer_len = + ctx->calls.serialised_model_size(&model_getters, model_data); + + // Allocate the data. + *mod_md3 = ri.Malloc(buffer_len); + + // Fill up the buffer with MD3 model data. + ctx->calls.serialise_model(&model_getters, model_data, *mod_md3); + + // Clear the loaded model. + ctx->hooks.clear_model(ctx->userdata); + + return qtrue; +} diff --git a/engine/code/renderercommon/tr_noise.c b/engine/code/renderercommon/tr_noise.c index 445ef827..abd27cbd 100644 --- a/engine/code/renderercommon/tr_noise.c +++ b/engine/code/renderercommon/tr_noise.c @@ -49,7 +49,7 @@ void R_NoiseInit( void ) } } -float R_NoiseGet4f( float x, float y, float z, float t ) +float R_NoiseGet4f( float x, float y, float z, double t ) { int i; int ix, iy, iz, it; diff --git a/engine/code/renderergl1/tr_animation.c b/engine/code/renderergl1/tr_animation.c index b01cf813..2979ab9f 100644 --- a/engine/code/renderergl1/tr_animation.c +++ b/engine/code/renderergl1/tr_animation.c @@ -263,9 +263,9 @@ void R_MDRAddAnimSurfaces( trRefEntity_t *ent ) { for(j = 0; j < skin->numSurfaces; j++) { - if (!strcmp(skin->surfaces[j]->name, surface->name)) + if (!strcmp(skin->surfaces[j].name, surface->name)) { - shader = skin->surfaces[j]->shader; + shader = skin->surfaces[j].shader; break; } } diff --git a/engine/code/renderergl1/tr_backend.c b/engine/code/renderergl1/tr_backend.c index 41fe47ac..5f5ec82e 100644 --- a/engine/code/renderergl1/tr_backend.c +++ b/engine/code/renderergl1/tr_backend.c @@ -509,7 +509,7 @@ void RB_RenderDrawSurfList( drawSurf_t *drawSurfs, int numDrawSurfs ) { int i; drawSurf_t *drawSurf; int oldSort; - float originalTime; + double originalTime; // save original time for entity shader offsets originalTime = backEnd.refdef.floatTime; @@ -562,7 +562,10 @@ void RB_RenderDrawSurfList( drawSurf_t *drawSurfs, int numDrawSurfs ) { if ( entityNum != REFENTITYNUM_WORLD ) { backEnd.currentEntity = &backEnd.refdef.entities[entityNum]; - backEnd.refdef.floatTime = originalTime - backEnd.currentEntity->e.shaderTime; + + // FIXME: e.shaderTime must be passed as int to avoid fp-precision loss issues + backEnd.refdef.floatTime = originalTime - (double)backEnd.currentEntity->e.shaderTime; + // we have to reset the shaderTime as well otherwise image animations start // from the wrong frame tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; @@ -713,7 +716,7 @@ void RB_SetGL2D (void) { // set time for 2D shaders backEnd.refdef.time = ri.Milliseconds(); - backEnd.refdef.floatTime = backEnd.refdef.time * 0.001f; + backEnd.refdef.floatTime = backEnd.refdef.time * 0.001; } @@ -1098,7 +1101,7 @@ const void *RB_SwapBuffers( const void *data ) { } - if ( !glState.finishCalled ) { + if ( r_finish->integer == 1 && !glState.finishCalled ) { qglFinish(); } diff --git a/engine/code/renderergl1/tr_extensions.c b/engine/code/renderergl1/tr_extensions.c new file mode 100644 index 00000000..80915bec --- /dev/null +++ b/engine/code/renderergl1/tr_extensions.c @@ -0,0 +1,33 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "tr_local.h" + +#define GLE(ret, name, ...) name##proc *qgl##name; +QGL_1_3_PROCS; +QGL_1_5_PROCS; +#undef GLE + +void GLimp_InitExtraExtensions() { +#define GLE(ret, name, ...) \ + qgl##name = (name##proc *)GLimp_GetProcAddress("gl" #name); + QGL_1_3_PROCS; + QGL_1_5_PROCS; +#undef GLE + +} diff --git a/engine/code/renderergl1/tr_image.c b/engine/code/renderergl1/tr_image.c index 302414a7..e15708a4 100644 --- a/engine/code/renderergl1/tr_image.c +++ b/engine/code/renderergl1/tr_image.c @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 1999-2005 Id Software, Inc., 2016 Google Inc. +Copyright (C) 1999-2005 Id Software, Inc., 2016-2017 Google Inc. This file is part of Quake III Arena source code. @@ -32,6 +32,8 @@ int gl_filter_max = GL_LINEAR; #define FILE_HASH_SIZE 1024 static image_t* hashTable[FILE_HASH_SIZE]; +static int textureMaxSize; + /* ** R_GammaCorrect */ @@ -554,13 +556,14 @@ Upload32 =============== */ static void Upload32( unsigned *data, - int width, int height, + int width, int height, int numMips, qboolean mipmap, qboolean picmip, qboolean lightMap, qboolean allowCompression, + qboolean capMaxSize, int *format, - int *pUploadWidth, int *pUploadHeight ) + int *pUploadWidth, int *pUploadHeight, int *pUploadMips ) { int samples; unsigned *scaledBuffer = NULL; @@ -570,6 +573,8 @@ static void Upload32( unsigned *data, byte *scan; GLenum internalFormat = GL_RGB; float rMax = 0, gMax = 0, bMax = 0; + int texsizex, texsizey; + int miplevel = 0; // // convert to exact power of 2 sizes @@ -591,6 +596,13 @@ static void Upload32( unsigned *data, height = scaled_height; } + if (capMaxSize) { + texsizex = ( textureMaxSize <= 0 || textureMaxSize > glConfig.maxTextureSize ) ? glConfig.maxTextureSize : textureMaxSize; + texsizey = texsizex; + } else { + texsizex = width; + texsizey = height; + } // // perform optional picmip operation // @@ -614,8 +626,8 @@ static void Upload32( unsigned *data, // scale both axis down equally so we don't have to // deal with a half mip resampling // - while ( scaled_width > glConfig.maxTextureSize - || scaled_height > glConfig.maxTextureSize ) { + while ( scaled_width > texsizex + || scaled_height > texsizey ) { scaled_width >>= 1; scaled_height >>= 1; } @@ -630,7 +642,7 @@ static void Upload32( unsigned *data, scan = ((byte *)data); samples = 3; - if( r_greyscale->integer ) + if( r_greyscale->integer || ( lightMap && r_monolightmaps->integer ) ) { for ( i = 0; i < c; i++ ) { @@ -640,20 +652,21 @@ static void Upload32( unsigned *data, scan[i*4 + 2] = luma; } } - else if( r_greyscale->value ) + else if( r_greyscale->value || ( lightMap && r_monolightmaps->value ) ) { + float scl = r_greyscale->value ? r_greyscale->value : r_monolightmaps->value; for ( i = 0; i < c; i++ ) { float luma = LUMA(scan[i*4], scan[i*4 + 1], scan[i*4 + 2]); - scan[i*4] = LERP(scan[i*4], luma, r_greyscale->value); - scan[i*4 + 1] = LERP(scan[i*4 + 1], luma, r_greyscale->value); - scan[i*4 + 2] = LERP(scan[i*4 + 2], luma, r_greyscale->value); + scan[i*4] = LERP(scan[i*4], luma, scl); + scan[i*4 + 1] = LERP(scan[i*4 + 1], luma, scl); + scan[i*4 + 2] = LERP(scan[i*4 + 2], luma, scl); } } if(lightMap) { - if(r_greyscale->integer) + if(r_greyscale->integer || r_monolightmaps->integer) internalFormat = GL_LUMINANCE; else internalFormat = GL_RGB; @@ -745,19 +758,37 @@ static void Upload32( unsigned *data, } } + *pUploadWidth = scaled_width; + *pUploadHeight = scaled_height; + *format = internalFormat; + *pUploadMips = 1; + // copy or resample data as appropriate for first MIP level if ( ( scaled_width == width ) && ( scaled_height == height ) ) { if (!mipmap) { qglTexImage2D (GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); - *pUploadWidth = scaled_width; - *pUploadHeight = scaled_height; - *format = internalFormat; - goto done; } - Com_Memcpy (scaledBuffer, data, width*height*4); + else + { + while ( scaled_width > 1 || scaled_height > 1 ) { + if ( miplevel + 1 >= numMips ) break; // stop at the last available mip + qglTexImage2D (GL_TEXTURE_2D, miplevel, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + data += scaled_width * scaled_height; + scaled_width /= 2; + scaled_height /= 2; + if ( scaled_width < 1 ) { + scaled_width = 1; + } + if ( scaled_height < 1 ) { + scaled_height = 1; + } + ++miplevel; + } + } + Com_Memcpy (scaledBuffer, data, scaled_width*scaled_height*4); } else { @@ -778,17 +809,10 @@ static void Upload32( unsigned *data, R_LightScaleTexture (scaledBuffer, scaled_width, scaled_height, !mipmap ); - *pUploadWidth = scaled_width; - *pUploadHeight = scaled_height; - *format = internalFormat; - - qglTexImage2D (GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer ); + qglTexImage2D (GL_TEXTURE_2D, miplevel, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer ); if (mipmap) { - int miplevel; - - miplevel = 0; while (scaled_width > 1 || scaled_height > 1) { R_MipMap( (byte *)scaledBuffer, scaled_width, scaled_height ); @@ -806,6 +830,7 @@ static void Upload32( unsigned *data, qglTexImage2D (GL_TEXTURE_2D, miplevel, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer ); } + *pUploadMips = miplevel + 1; } done: @@ -838,13 +863,13 @@ static void Upload32( unsigned *data, /* ================ -R_CreateImage +R_CreateImageInternal This is the only way any image_t are created ================ */ -image_t *R_CreateImage( const char *name, byte *pic, int width, int height, - imgType_t type, imgFlags_t flags, int internalFormat ) { +image_t *R_CreateImageInternal( const char *name, byte *pic, int width, int height, int numMips, + imgType_t type, imgFlags_t flags, int internalFormat, qboolean capMaxSize ) { image_t *image; qboolean isLightmap = qfalse; long hash; @@ -890,14 +915,16 @@ image_t *R_CreateImage( const char *name, byte *pic, int width, int height, GL_Bind(image); - Upload32( (unsigned *)pic, image->width, image->height, + Upload32( (unsigned *)pic, image->width, image->height, numMips, image->flags & IMGFLAG_MIPMAP, image->flags & IMGFLAG_PICMIP, isLightmap, !(image->flags & IMGFLAG_NO_COMPRESSION), + capMaxSize, &image->internalFormat, &image->uploadWidth, - &image->uploadHeight ); + &image->uploadHeight, + &image->uploadMips ); qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, glWrapClampMode ); qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, glWrapClampMode ); @@ -916,6 +943,18 @@ image_t *R_CreateImage( const char *name, byte *pic, int width, int height, return image; } +/* +================ +R_CreateImage + +Wrapper of R_CreateImageInternal using 0 mips +================ +*/ +image_t *R_CreateImage( const char *name, byte *pic, int width, int height, + imgType_t type, imgFlags_t flags, int internalFormat ) { + return R_CreateImageInternal( name, pic, width, height, 0, type, flags, internalFormat, qtrue ); +} + //=================================================================== typedef struct @@ -1032,10 +1071,15 @@ Returns NULL if it fails, not a default image. image_t *R_FindImageFile( const char *name, imgType_t type, imgFlags_t flags ) { DeepmindContext *ctx = dmlab_context(); + char new_name[MAX_QPATH]; + char dds_name[MAX_QPATH]; + const char *file_name = name; image_t *image; - int width, height; - byte *pic; + int width = 0, height = 0, numMips = 0; + byte *pic = NULL; long hash; + GLenum picFormat; + qboolean capMaxSize = qtrue; if (!name) { return NULL; @@ -1058,18 +1102,45 @@ image_t *R_FindImageFile( const char *name, imgType_t type, imgFlags_t flags ) } } + if ( ctx->hooks.replace_texture_name( ctx->userdata, name, new_name, sizeof( new_name ) ) ) { + file_name = new_name; + } // - // load the pic from disk + // Load the pic from disk or from hooks. // - R_LoadImage( name, &pic, &width, &height ); + if ( !ctx->hooks.load_texture( ctx->userdata, file_name, &pic, &width, &height, ri.Malloc ) ) { + // Attempt to load a DDS version of this requested texture. + COM_StripExtension( file_name, dds_name, MAX_QPATH ); + Q_strcat( dds_name, MAX_QPATH, ".dds" ); + + R_LoadDDS( dds_name, &pic, &width, &height, &picFormat, &numMips ); + + // If a DDS texture was loaded, but it is any other format than the expected + // RGBA8, discard it and carry on attempting to load the orginal texture. + if ( pic != NULL && picFormat != GL_RGBA8 ) { + ri.Free( pic ); + pic = NULL; + numMips = 0; + } + + if ( pic == NULL ) { + R_LoadImage( file_name, &pic, &width, &height ); + } + } else { + capMaxSize = qfalse; + } + if ( pic == NULL ) { return NULL; } // Allow modification of a loaded texture. - ctx->hooks.modify_rgba_texture( ctx->userdata, name, pic, width, height ); + if ( ctx->hooks.modify_rgba_texture( ctx->userdata, name, pic, width, height ) ) { + numMips = 1; + capMaxSize = qfalse; + } - image = R_CreateImage( ( char * ) name, pic, width, height, type, flags, 0 ); + image = R_CreateImageInternal( ( char * ) name, pic, width, height, numMips, type, flags, 0, capMaxSize ); ri.Free( pic ); return image; } @@ -1359,6 +1430,9 @@ R_InitImages */ void R_InitImages( void ) { Com_Memset(hashTable, 0, sizeof(hashTable)); + + textureMaxSize = r_textureMaxSize->integer; + // build brightness translation tables R_SetColorMappings(); @@ -1513,6 +1587,7 @@ RE_RegisterSkin =============== */ qhandle_t RE_RegisterSkin( const char *name ) { + skinSurface_t parseSurfaces[MAX_SKIN_SURFACES]; qhandle_t hSkin; skin_t *skin; skinSurface_t *surf; @@ -1523,6 +1598,7 @@ qhandle_t RE_RegisterSkin( const char *name ) { char *text_p; char *token; char surfName[MAX_QPATH]; + int totalSurfaces; if ( !name || !name[0] ) { ri.Printf( PRINT_DEVELOPER, "Empty name passed to RE_RegisterSkin\n" ); @@ -1562,8 +1638,8 @@ qhandle_t RE_RegisterSkin( const char *name ) { // If not a .skin file, load as a single shader if ( strcmp( name + strlen( name ) - 5, ".skin" ) ) { skin->numSurfaces = 1; - skin->surfaces[0] = ri.Hunk_Alloc( sizeof( *skin->surfaces[0] ), h_low ); - skin->surfaces[0]->shader = R_FindShader( name, LIGHTMAP_NONE, qtrue ); + skin->surfaces = ri.Hunk_Alloc( sizeof( skinSurface_t ), h_low ); + skin->surfaces[0].shader = R_FindShader( name, LIGHTMAP_NONE, qtrue ); return hSkin; } @@ -1573,6 +1649,7 @@ qhandle_t RE_RegisterSkin( const char *name ) { return 0; } + totalSurfaces = 0; text_p = text.c; while ( text_p && *text_p ) { // get surface name @@ -1596,25 +1673,32 @@ qhandle_t RE_RegisterSkin( const char *name ) { // parse the shader name token = CommaParse( &text_p ); - if ( skin->numSurfaces >= MD3_MAX_SURFACES ) { - ri.Printf( PRINT_WARNING, "WARNING: Ignoring surfaces in '%s', the max is %d surfaces!\n", name, MD3_MAX_SURFACES ); - break; + if ( skin->numSurfaces < MAX_SKIN_SURFACES ) { + surf = &parseSurfaces[skin->numSurfaces]; + Q_strncpyz( surf->name, surfName, sizeof( surf->name ) ); + surf->shader = R_FindShader( token, LIGHTMAP_NONE, qtrue ); + skin->numSurfaces++; } - surf = skin->surfaces[ skin->numSurfaces ] = ri.Hunk_Alloc( sizeof( *skin->surfaces[0] ), h_low ); - Q_strncpyz( surf->name, surfName, sizeof( surf->name ) ); - surf->shader = R_FindShader( token, LIGHTMAP_NONE, qtrue ); - skin->numSurfaces++; + totalSurfaces++; } ri.FS_FreeFile( text.v ); + if ( totalSurfaces > MAX_SKIN_SURFACES ) { + ri.Printf( PRINT_WARNING, "WARNING: Ignoring excess surfaces (found %d, max is %d) in skin '%s'!\n", + totalSurfaces, MAX_SKIN_SURFACES, name ); + } // never let a skin have 0 shaders if ( skin->numSurfaces == 0 ) { return 0; // use default skin } + // copy surfaces to skin + skin->surfaces = ri.Hunk_Alloc( skin->numSurfaces * sizeof( skinSurface_t ), h_low ); + memcpy( skin->surfaces, parseSurfaces, skin->numSurfaces * sizeof( skinSurface_t ) ); + return hSkin; } @@ -1633,8 +1717,8 @@ void R_InitSkins( void ) { skin = tr.skins[0] = ri.Hunk_Alloc( sizeof( skin_t ), h_low ); Q_strncpyz( skin->name, "", sizeof( skin->name ) ); skin->numSurfaces = 1; - skin->surfaces[0] = ri.Hunk_Alloc( sizeof( *skin->surfaces[0] ), h_low ); - skin->surfaces[0]->shader = tr.defaultShader; + skin->surfaces = ri.Hunk_Alloc( sizeof( skinSurface_t ), h_low ); + skin->surfaces[0].shader = tr.defaultShader; } /* @@ -1663,12 +1747,37 @@ void R_SkinList_f( void ) { for ( i = 0 ; i < tr.numSkins ; i++ ) { skin = tr.skins[i]; - ri.Printf( PRINT_ALL, "%3i:%s\n", i, skin->name ); + ri.Printf( PRINT_ALL, "%3i:%s (%d surfaces)\n", i, skin->name, skin->numSurfaces ); for ( j = 0 ; j < skin->numSurfaces ; j++ ) { ri.Printf( PRINT_ALL, " %s = %s\n", - skin->surfaces[j]->name, skin->surfaces[j]->shader->name ); + skin->surfaces[j].name, skin->surfaces[j].shader->name ); } } ri.Printf (PRINT_ALL, "------------------\n"); } +/* +=============== +Api_UpdateTexture +=============== +*/ +bool dmlab_update_rgba_texture( const char* name, int width, int height, const unsigned char* data ) { + image_t* image; + long hash = generateHashValue(name); + for (image=hashTable[hash]; image; image=image->next) { + if ( !strcmp( name, image->imgName ) ) { + qboolean picmip = image->flags & IMGFLAG_PICMIP; + qboolean mipmap = image->flags & IMGFLAG_MIPMAP; + qboolean capMaxSize = width != image->uploadWidth || height !=image-> uploadHeight; + glState.currenttextures[glState.currenttmu] = image->texnum; + qglBindTexture( GL_TEXTURE_2D, image->texnum ); + Upload32( (unsigned *)data, width, height, 0, mipmap, picmip, qfalse, qfalse, capMaxSize, + &image->internalFormat, + &image->uploadWidth, + &image->uploadHeight, + &image->uploadMips ); + return true; + } + } + return false; +} diff --git a/engine/code/renderergl1/tr_init.c b/engine/code/renderergl1/tr_init.c index ef620a99..7268c40d 100644 --- a/engine/code/renderergl1/tr_init.c +++ b/engine/code/renderergl1/tr_init.c @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 1999-2005 Id Software, Inc., 2017 Google Inc. This file is part of Quake III Arena source code. @@ -22,6 +22,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // tr_init.c -- functions that are not called every frame #include "tr_local.h" +#include glconfig_t glConfig; qboolean textureFilterAnisotropic = qfalse; @@ -63,6 +64,7 @@ cvar_t *r_stereoEnabled; cvar_t *r_anaglyphMode; cvar_t *r_greyscale; +cvar_t *r_monolightmaps; cvar_t *r_ignorehwgamma; cvar_t *r_measureOverdraw; @@ -168,6 +170,8 @@ int max_polys; cvar_t *r_maxpolyverts; int max_polyverts; +cvar_t *r_textureMaxSize; + /* ** InitOpenGL ** @@ -195,7 +199,8 @@ static void InitOpenGL( void ) { GLint temp; - GLimp_Init(); + GLimp_Init( qfalse ); + GLimp_InitExtraExtensions(); strcpy( renderer_buffer, glConfig.renderer_string ); Q_strlwr( renderer_buffer ); @@ -836,11 +841,19 @@ const void *RB_TakeVideoFrameCmd( const void *data ) */ void GL_SetDefaultState( void ) { + static const GLfloat initial_color[4] = {1, 1, 1, 1}; + GLfloat current_color[4]; + qglClearDepth( 1.0f ); qglCullFace(GL_FRONT); - qglColor4f (1,1,1,1); + qglGetFloatv(GL_CURRENT_COLOR, current_color); + + // Because of a bug in NVIDIA's driver, we should only change glColor if it is needed. + if (memcmp(current_color, initial_color, sizeof(current_color)) != 0) { + qglColor4f (1,1,1,1); + } // initialize downstream texture unit if we're running // in a multitexture environment @@ -920,7 +933,43 @@ void GfxInfo_f( void ) ri.Printf( PRINT_ALL, "GL_RENDERER: %s\n", glConfig.renderer_string ); ri.Printf( PRINT_ALL, "GL_VERSION: %s\n", glConfig.version_string ); ri.Printf( PRINT_ALL, "GL_EXTENSIONS: " ); - R_PrintLongString( glConfig.extensions_string ); + if ( qglGetStringi ) + { + GLint numExtensions; + GLenum error_code; + int i; + + // Flush existing errors first. (There shouldn't be any.) + while ( (error_code = qglGetError()) != GL_NO_ERROR ) + { + ri.Printf( PRINT_ALL, "Unexpected previous error %d.\n", error_code ); + } + + qglGetIntegerv( GL_NUM_EXTENSIONS, &numExtensions ); + if ( (error_code = qglGetError()) != GL_NO_ERROR ) + { + ri.Printf( PRINT_ALL, "Failed to enumerate GL Extensions, %d\n", error_code ); + } + else + { + for ( i = 0; i < numExtensions; i++ ) + { + const GLubyte *const extension_name = qglGetStringi( GL_EXTENSIONS, i ); + if ( (error_code = qglGetError()) != GL_NO_ERROR ) + { + ri.Printf( PRINT_ALL, "Failed to get GL Extension name for extension %d (error: %d)\n", i, error_code ); + } + else + { + ri.Printf( PRINT_ALL, "%s ", extension_name ); + } + } + } + } + else + { + R_PrintLongString( glConfig.extensions_string ); + } ri.Printf( PRINT_ALL, "\n" ); ri.Printf( PRINT_ALL, "GL_MAX_TEXTURE_SIZE: %d\n", glConfig.maxTextureSize ); ri.Printf( PRINT_ALL, "GL_MAX_TEXTURE_UNITS_ARB: %d\n", glConfig.numTextureUnits ); @@ -1141,6 +1190,10 @@ void R_Register( void ) r_maxpolys = ri.Cvar_Get( "r_maxpolys", va("%d", MAX_POLYS), 0); r_maxpolyverts = ri.Cvar_Get( "r_maxpolyverts", va("%d", MAX_POLYVERTS), 0); + r_monolightmaps = ri.Cvar_Get("r_monolightmaps", "0", CVAR_ARCHIVE | CVAR_LATCH); + + r_textureMaxSize = ri.Cvar_Get( "r_textureMaxSize", "0", CVAR_ARCHIVE | CVAR_LATCH ); + // make sure all the commands added here are also // removed in R_Shutdown ri.Cmd_AddCommand( "imagelist", R_ImageList_f ); @@ -1259,16 +1312,15 @@ void RE_Shutdown( qboolean destroyWindow ) { ri.Printf( PRINT_ALL, "RE_Shutdown( %i )\n", destroyWindow ); - ri.Cmd_RemoveCommand ("modellist"); - ri.Cmd_RemoveCommand ("screenshotJPEG"); - ri.Cmd_RemoveCommand ("screenshot"); - ri.Cmd_RemoveCommand ("imagelist"); - ri.Cmd_RemoveCommand ("shaderlist"); - ri.Cmd_RemoveCommand ("skinlist"); - ri.Cmd_RemoveCommand ("gfxinfo"); - ri.Cmd_RemoveCommand("minimize"); + ri.Cmd_RemoveCommand( "imagelist" ); + ri.Cmd_RemoveCommand( "shaderlist" ); + ri.Cmd_RemoveCommand( "skinlist" ); + ri.Cmd_RemoveCommand( "modellist" ); ri.Cmd_RemoveCommand( "modelist" ); - ri.Cmd_RemoveCommand( "shaderstate" ); + ri.Cmd_RemoveCommand( "screenshot" ); + ri.Cmd_RemoveCommand( "screenshotJPEG" ); + ri.Cmd_RemoveCommand( "gfxinfo" ); + ri.Cmd_RemoveCommand( "minimize" ); if ( tr.registered ) { diff --git a/engine/code/renderergl1/tr_local.h b/engine/code/renderergl1/tr_local.h index aedf1a4f..279564bf 100644 --- a/engine/code/renderergl1/tr_local.h +++ b/engine/code/renderergl1/tr_local.h @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 1999-2005 Id Software, Inc., 2017 Google Inc. This file is part of Quake III Arena source code. @@ -362,8 +362,8 @@ typedef struct shader_s { void (*optimalStageIteratorFunc)( void ); - float clampTime; // time this shader is clamped to - float timeOffset; // current time offset for this shader + double clampTime; // time this shader is clamped to + double timeOffset; // current time offset for this shader struct shader_s *remappedShader; // current shader this one is remapped too @@ -388,7 +388,7 @@ typedef struct { byte areamask[MAX_MAP_AREA_BYTES]; qboolean areamaskModified; // qtrue if areamask changed since last scene - float floatTime; // tr.refdef.time / 1000.0 + double floatTime; // tr.refdef.time / 1000.0 // text messages for deform text shaders char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH]; @@ -411,6 +411,12 @@ typedef struct { //================================================================================= +// max surfaces per-skin +// This is an arbitry limit. Vanilla Q3 only supported 32 surfaces in skins but failed to +// enforce the maximum limit when reading skin files. It was possile to use more than 32 +// surfaces which accessed out of bounds memory past end of skin->surfaces hunk block. +#define MAX_SKIN_SURFACES 256 + // skins allow models to be retextured without modifying the model file typedef struct { char name[MAX_QPATH]; @@ -420,7 +426,7 @@ typedef struct { typedef struct skin_s { char name[MAX_QPATH]; // game path, including extension int numSurfaces; - skinSurface_t *surfaces[MD3_MAX_SURFACES]; + skinSurface_t *surfaces; // dynamically allocated array of surfaces } skin_t; @@ -1053,6 +1059,10 @@ extern cvar_t *r_printShaders; extern cvar_t *r_marksOnTriangleMeshes; +extern cvar_t *r_monolightmaps; + +extern cvar_t *r_textureMaxSize; + //==================================================================== void R_SwapBuffers( int ); @@ -1184,6 +1194,16 @@ void R_RemapShader(const char *oldShader, const char *newShader, const char * /* ==================================================================== +IMPLEMENTATION SPECIFIC FUNCTIONS + +==================================================================== +*/ + +void GLimp_InitExtraExtensions( void ); + +/* +==================================================================== + TESSELATOR/SHADER DECLARATIONS ==================================================================== @@ -1211,7 +1231,7 @@ typedef struct shaderCommands_s color4ub_t constantColor255[SHADER_MAX_VERTEXES] QALIGN(16); shader_t *shader; - float shaderTime; + double shaderTime; int fogNum; int dlightBits; // or together of all vertexDlightBits diff --git a/engine/code/renderergl1/tr_main.c b/engine/code/renderergl1/tr_main.c index 0e388314..09559a2e 100644 --- a/engine/code/renderergl1/tr_main.c +++ b/engine/code/renderergl1/tr_main.c @@ -1332,6 +1332,9 @@ Visualization aid for movement clipping debugging ==================== */ void R_DebugGraphics( void ) { + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return; + } if ( !r_debugSurface->integer ) { return; } diff --git a/engine/code/renderergl1/tr_mesh.c b/engine/code/renderergl1/tr_mesh.c index d0be29bc..4cdd9fc2 100644 --- a/engine/code/renderergl1/tr_mesh.c +++ b/engine/code/renderergl1/tr_mesh.c @@ -361,8 +361,8 @@ void R_AddMD3Surfaces( trRefEntity_t *ent ) { shader = tr.defaultShader; for ( j = 0 ; j < skin->numSurfaces ; j++ ) { // the names have both been lowercased - if ( !strcmp( skin->surfaces[j]->name, surface->name ) ) { - shader = skin->surfaces[j]->shader; + if ( !strcmp( skin->surfaces[j].name, surface->name ) ) { + shader = skin->surfaces[j].shader; break; } } diff --git a/engine/code/renderergl1/tr_model.c b/engine/code/renderergl1/tr_model.c index 51269a51..128d1b58 100644 --- a/engine/code/renderergl1/tr_model.c +++ b/engine/code/renderergl1/tr_model.c @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 1999-2005 Id Software, Inc., 2016 Google Inc. This file is part of Quake III Arena source code. @@ -21,12 +21,13 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ // tr_models.c -- model loading and caching +#include "../deepmind/context.h" #include "tr_local.h" #define LL(x) x=LittleLong(x) -static qboolean R_LoadMD3(model_t *mod, int lod, void *buffer, const char *name ); -static qboolean R_LoadMDR(model_t *mod, void *buffer, int filesize, const char *name ); +static qboolean R_LoadMD3(model_t *mod, int lod, void *buffer, const char *mod_name, const char *shaderPrefix); +static qboolean R_LoadMDR(model_t *mod, void *buffer, int filesize, const char *name, const char *shaderPrefix); /* ==================== @@ -43,12 +44,15 @@ qhandle_t R_RegisterMD3(const char *name, model_t *mod) int ident; qboolean loaded = qfalse; int numLoaded; - char filename[MAX_QPATH], namebuf[MAX_QPATH+20]; + char filename[MAX_QPATH], namebuf[MAX_QPATH+20], shaderPrefix[MAX_QPATH] = ""; char *fext, defex[] = "md3"; + DeepmindContext *ctx = dmlab_context(); numLoaded = 0; - strcpy(filename, name); + if ( !ctx->hooks.replace_model_name( ctx->userdata, name, filename, sizeof(filename), shaderPrefix, sizeof(shaderPrefix) ) ) { + strcpy(filename, name); + } fext = strchr(filename, '.'); if(!fext) @@ -72,7 +76,7 @@ qhandle_t R_RegisterMD3(const char *name, model_t *mod) ident = LittleLong(* (unsigned *) buf.u); if (ident == MD3_IDENT) - loaded = R_LoadMD3(mod, lod, buf.u, name); + loaded = R_LoadMD3(mod, lod, buf.u, name, shaderPrefix); else ri.Printf(PRINT_WARNING,"R_RegisterMD3: unknown fileid for %s\n", name); @@ -123,7 +127,13 @@ qhandle_t R_RegisterMDR(const char *name, model_t *mod) qboolean loaded = qfalse; int filesize; - filesize = ri.FS_ReadFile(name, (void **) &buf.v); + char filename[MAX_QPATH], shaderPrefix[MAX_QPATH] = ""; + DeepmindContext *ctx = dmlab_context(); + if ( !ctx->hooks.replace_model_name( ctx->userdata, name, filename, sizeof(filename), shaderPrefix, sizeof(shaderPrefix) ) ) { + strcpy(filename, name); + } + + filesize = ri.FS_ReadFile(filename, (void **) &buf.v); if(!buf.u) { mod->type = MOD_BAD; @@ -132,7 +142,7 @@ qhandle_t R_RegisterMDR(const char *name, model_t *mod) ident = LittleLong(*(unsigned *)buf.u); if(ident == MDR_IDENT) - loaded = R_LoadMDR(mod, buf.u, filesize, name); + loaded = R_LoadMDR(mod, buf.u, filesize, name, shaderPrefix); ri.FS_FreeFile (buf.v); @@ -181,6 +191,61 @@ qhandle_t R_RegisterIQM(const char *name, model_t *mod) return mod->index; } +/* +==================== +R_FindShaderIndex +==================== +*/ +int R_FindShaderIndex(const char *shader_name) +{ + shader_t *sh = R_FindShader( shader_name, LIGHTMAP_NONE, qtrue ); + return sh->defaultShader ? 0 : sh->index; +} + +/* +==================== +R_RegisterDMLab +==================== +*/ +qhandle_t R_RegisterDMLab(const char *name, model_t *mod) +{ + int lod; + char namebuf[MAX_QPATH + 20]; + md3Header_t *mod_md3; + qboolean loaded = qfalse; + + for ( lod = 0 ; lod < MD3_MAX_LODS ; ++lod ) + { + if ( lod != 0 ) + Com_sprintf(namebuf, sizeof(namebuf), "%s_%d", name, lod); + else + Com_sprintf(namebuf, sizeof(namebuf), "%s", name); + + if ( R_DMLabToMD3( name, &mod_md3 ) ) + { + loaded = R_LoadMD3( mod, lod, (byte *)mod_md3, name, "" ); + ri.Free( mod_md3 ); + } + + if ( loaded ) + mod->numLods++; + else + break; + } + + if ( lod > 0 ) + { + mod->type = MOD_MESH; + return mod->index; + } + +#ifdef _DEBUG + ri.Printf(PRINT_WARNING, "R_RegisterDMLab: couldn't load %s\n", name); +#endif + + mod->type = MOD_BAD; + return 0; +} typedef struct { @@ -361,6 +426,12 @@ qhandle_t RE_RegisterModel( const char *name ) { } } + // As a fallback, attempt to load a DeepMind Lab custom model. + if( !hModel ) + { + hModel = R_RegisterDMLab( name, mod ); + } + return hModel; } @@ -369,7 +440,7 @@ qhandle_t RE_RegisterModel( const char *name ) { R_LoadMD3 ================= */ -static qboolean R_LoadMD3 (model_t *mod, int lod, void *buffer, const char *mod_name ) { +static qboolean R_LoadMD3 (model_t *mod, int lod, void *buffer, const char *mod_name, const char *shaderPrefix ) { int i, j; md3Header_t *pinmodel; md3Frame_t *frame; @@ -381,6 +452,9 @@ static qboolean R_LoadMD3 (model_t *mod, int lod, void *buffer, const char *mod_ md3Tag_t *tag; int version; int size; + char shaderName[MAX_QPATH + 64]; + int shaderPrefixLength = strlen(shaderPrefix); + Com_Memcpy(shaderName, shaderPrefix, shaderPrefixLength + 1); pinmodel = (md3Header_t *)buffer; @@ -477,18 +551,18 @@ static qboolean R_LoadMD3 (model_t *mod, int lod, void *buffer, const char *mod_ surf->name[j-2] = 0; } - // register the shaders - shader = (md3Shader_t *) ( (byte *)surf + surf->ofsShaders ); - for ( j = 0 ; j < surf->numShaders ; j++, shader++ ) { - shader_t *sh; - - sh = R_FindShader( shader->name, LIGHTMAP_NONE, qtrue ); + // register the shaders + shader = (md3Shader_t *) ( (byte *)surf + surf->ofsShaders ); + for ( j = 0 ; j < surf->numShaders ; j++, shader++ ) { + shader_t *sh; + Com_Memcpy( shaderName + shaderPrefixLength, shader->name, sizeof(shader->name) ); + sh = R_FindShader( shaderName, LIGHTMAP_NONE, qtrue ); if ( sh->defaultShader ) { shader->shaderIndex = 0; } else { shader->shaderIndex = sh->index; } - } + } // swap all the triangles tri = (md3Triangle_t *) ( (byte *)surf + surf->ofsTriangles ); @@ -531,7 +605,7 @@ static qboolean R_LoadMD3 (model_t *mod, int lod, void *buffer, const char *mod_ R_LoadMDR ================= */ -static qboolean R_LoadMDR( model_t *mod, void *buffer, int filesize, const char *mod_name ) +static qboolean R_LoadMDR( model_t *mod, void *buffer, int filesize, const char *mod_name, const char *shaderPrefix ) { int i, j, k, l; mdrHeader_t *pinmodel, *mdr; @@ -544,6 +618,9 @@ static qboolean R_LoadMDR( model_t *mod, void *buffer, int filesize, const char mdrTag_t *tag, *curtag; int size; shader_t *sh; + char shaderName[MAX_QPATH + 64]; + int shaderPrefixLength = strlen(shaderPrefix); + Com_Memcpy( shaderName, shaderPrefix, shaderPrefixLength + 1 ); pinmodel = (mdrHeader_t *)buffer; @@ -747,7 +824,8 @@ static qboolean R_LoadMDR( model_t *mod, void *buffer, int filesize, const char Q_strlwr( surf->name ); // register the shaders - sh = R_FindShader(surf->shader, LIGHTMAP_NONE, qtrue); + Com_Memcpy( shaderName + shaderPrefixLength, surf->name, sizeof(surf->name) ); + sh = R_FindShader(shaderName, LIGHTMAP_NONE, qtrue); if ( sh->defaultShader ) { surf->shaderIndex = 0; } else { diff --git a/engine/code/renderergl1/tr_model_iqm.c b/engine/code/renderergl1/tr_model_iqm.c index 6c47ed07..2cb5cd4f 100644 --- a/engine/code/renderergl1/tr_model_iqm.c +++ b/engine/code/renderergl1/tr_model_iqm.c @@ -903,9 +903,9 @@ void R_AddIQMSurfaces( trRefEntity_t *ent ) { for(j = 0; j < skin->numSurfaces; j++) { - if (!strcmp(skin->surfaces[j]->name, surface->name)) + if (!strcmp(skin->surfaces[j].name, surface->name)) { - shader = skin->surfaces[j]->shader; + shader = skin->surfaces[j].shader; break; } } diff --git a/engine/code/renderergl1/tr_scene.c b/engine/code/renderergl1/tr_scene.c index 5d7a3dd1..639f4e2d 100644 --- a/engine/code/renderergl1/tr_scene.c +++ b/engine/code/renderergl1/tr_scene.c @@ -344,7 +344,7 @@ void RE_RenderScene( const refdef_t *fd ) { // derived info - tr.refdef.floatTime = tr.refdef.time * 0.001f; + tr.refdef.floatTime = tr.refdef.time * 0.001; tr.refdef.numDrawSurfs = r_firstSceneDrawSurf; tr.refdef.drawSurfs = backEndData->drawSurfs; diff --git a/engine/code/renderergl1/tr_shade.c b/engine/code/renderergl1/tr_shade.c index d26cb1ac..57a43c5a 100644 --- a/engine/code/renderergl1/tr_shade.c +++ b/engine/code/renderergl1/tr_shade.c @@ -218,7 +218,7 @@ R_BindAnimatedImage ================= */ static void R_BindAnimatedImage( textureBundle_t *bundle ) { - int index; + int64_t index; if ( bundle->isVideoMap ) { ri.CIN_RunCinematic(bundle->videoMapHandle); @@ -233,13 +233,18 @@ static void R_BindAnimatedImage( textureBundle_t *bundle ) { // it is necessary to do this messy calc to make sure animations line up // exactly with waveforms of the same frequency - index = ri.ftol(tess.shaderTime * bundle->imageAnimationSpeed * FUNCTABLE_SIZE); + index = tess.shaderTime * bundle->imageAnimationSpeed * FUNCTABLE_SIZE; index >>= FUNCTABLE_SIZE2; if ( index < 0 ) { index = 0; // may happen with shader time offsets } - index %= bundle->numImageAnimations; + + // Windows x86 doesn't load renderer DLL with 64 bit modulus + //index %= bundle->numImageAnimations; + while ( index >= bundle->numImageAnimations ) { + index -= bundle->numImageAnimations; + } GL_Bind( bundle->image[ index ] ); } diff --git a/engine/code/renderergl1/tr_shade_calc.c b/engine/code/renderergl1/tr_shade_calc.c index 82f7c0cc..58b54270 100644 --- a/engine/code/renderergl1/tr_shade_calc.c +++ b/engine/code/renderergl1/tr_shade_calc.c @@ -27,7 +27,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #endif -#define WAVEVALUE( table, base, amplitude, phase, freq ) ((base) + table[ ri.ftol( ( ( (phase) + tess.shaderTime * (freq) ) * FUNCTABLE_SIZE ) ) & FUNCTABLE_MASK ] * (amplitude)) +#define WAVEVALUE( table, base, amplitude, phase, freq ) ((base) + table[ ( (int64_t) ( ( (phase) + tess.shaderTime * (freq) ) * FUNCTABLE_SIZE ) ) & FUNCTABLE_MASK ] * (amplitude)) static float *TableForFunc( genFunc_t func ) { @@ -206,12 +206,12 @@ void RB_CalcBulgeVertexes( deformStage_t *ds ) { const float *st = ( const float * ) tess.texCoords[0]; float *xyz = ( float * ) tess.xyz; float *normal = ( float * ) tess.normal; - float now; + double now; - now = backEnd.refdef.time * ds->bulgeSpeed * 0.001f; + now = backEnd.refdef.time * 0.001 * ds->bulgeSpeed; for ( i = 0; i < tess.numVertexes; i++, xyz += 4, st += 4, normal += 4 ) { - int off; + int64_t off; float scale; off = (float)( FUNCTABLE_SIZE / (M_PI*2) ) * ( st[0] * ds->bulgeWidth + now ); @@ -920,7 +920,7 @@ void RB_CalcEnvironmentTexCoords( float *st ) void RB_CalcTurbulentTexCoords( const waveForm_t *wf, float *st ) { int i; - float now; + double now; now = ( wf->phase + tess.shaderTime * wf->frequency ); @@ -929,8 +929,8 @@ void RB_CalcTurbulentTexCoords( const waveForm_t *wf, float *st ) float s = st[0]; float t = st[1]; - st[0] = s + tr.sinTable[ ( ( int ) ( ( ( tess.xyz[i][0] + tess.xyz[i][2] )* 1.0/128 * 0.125 + now ) * FUNCTABLE_SIZE ) ) & ( FUNCTABLE_MASK ) ] * wf->amplitude; - st[1] = t + tr.sinTable[ ( ( int ) ( ( tess.xyz[i][1] * 1.0/128 * 0.125 + now ) * FUNCTABLE_SIZE ) ) & ( FUNCTABLE_MASK ) ] * wf->amplitude; + st[0] = s + tr.sinTable[ ( ( int64_t ) ( ( ( tess.xyz[i][0] + tess.xyz[i][2] )* 1.0/128 * 0.125 + now ) * FUNCTABLE_SIZE ) ) & ( FUNCTABLE_MASK ) ] * wf->amplitude; + st[1] = t + tr.sinTable[ ( ( int64_t ) ( ( tess.xyz[i][1] * 1.0/128 * 0.125 + now ) * FUNCTABLE_SIZE ) ) & ( FUNCTABLE_MASK ) ] * wf->amplitude; } } @@ -954,8 +954,8 @@ void RB_CalcScaleTexCoords( const float scale[2], float *st ) void RB_CalcScrollTexCoords( const float scrollSpeed[2], float *st ) { int i; - float timeScale = tess.shaderTime; - float adjustedScrollS, adjustedScrollT; + double timeScale = tess.shaderTime; + double adjustedScrollS, adjustedScrollT; adjustedScrollS = scrollSpeed[0] * timeScale; adjustedScrollT = scrollSpeed[1] * timeScale; @@ -994,9 +994,9 @@ void RB_CalcTransformTexCoords( const texModInfo_t *tmi, float *st ) */ void RB_CalcRotateTexCoords( float degsPerSecond, float *st ) { - float timeScale = tess.shaderTime; - float degs; - int index; + double timeScale = tess.shaderTime; + double degs; + int64_t index; float sinValue, cosValue; texModInfo_t tmi; diff --git a/engine/code/renderergl1/tr_shader.c b/engine/code/renderergl1/tr_shader.c index c931326f..aef1c267 100644 --- a/engine/code/renderergl1/tr_shader.c +++ b/engine/code/renderergl1/tr_shader.c @@ -687,6 +687,8 @@ static qboolean ParseStage( shaderStage_t *stage, char **text ) // else if ( !Q_stricmp( token, "animMap" ) ) { + int totalImages = 0; + token = COM_ParseExt( text, qfalse ); if ( !token[0] ) { @@ -721,6 +723,12 @@ static qboolean ParseStage( shaderStage_t *stage, char **text ) } stage->bundle[0].numImageAnimations++; } + totalImages++; + } + + if ( totalImages > MAX_IMAGE_ANIMATIONS ) { + ri.Printf( PRINT_WARNING, "WARNING: ignoring excess images for 'animMap' (found %d, max is %d) in shader '%s'\n", + totalImages, MAX_IMAGE_ANIMATIONS, shader.name ); } } else if ( !Q_stricmp( token, "videoMap" ) ) @@ -735,6 +743,8 @@ static qboolean ParseStage( shaderStage_t *stage, char **text ) if (stage->bundle[0].videoMapHandle != -1) { stage->bundle[0].isVideoMap = qtrue; stage->bundle[0].image[0] = tr.scratchImage[stage->bundle[0].videoMapHandle]; + } else { + ri.Printf( PRINT_WARNING, "WARNING: could not load '%s' for 'videoMap' keyword in shader '%s'\n", token, shader.name ); } } // @@ -2352,7 +2362,6 @@ static shader_t *FinishShader( void ) { ri.Printf( PRINT_DEVELOPER, "WARNING: shader '%s' has VERTEX forced lightmap!\n", shader.name ); } else { ri.Printf( PRINT_DEVELOPER, "WARNING: shader '%s' has lightmap but no lightmap stage!\n", shader.name ); - shader.lightmapIndex = LIGHTMAP_NONE; } } diff --git a/engine/code/renderergl1/tr_surface.c b/engine/code/renderergl1/tr_surface.c index c4a1b2d9..9aadf95f 100644 --- a/engine/code/renderergl1/tr_surface.c +++ b/engine/code/renderergl1/tr_surface.c @@ -563,8 +563,8 @@ static void VectorArrayNormalize(vec4_t *normals, unsigned int count) #if idppc { - register float half = 0.5; - register float one = 1.0; + float half = 0.5; + float one = 1.0; float *components = (float *)normals; // Vanilla PPC code, but since PPC has a reciprocal square root estimate instruction, diff --git a/engine/code/renderergl2/glsl/bokeh_fp.glsl b/engine/code/renderergl2/glsl/bokeh_fp.glsl index d08816ae..d2ec5b4b 100644 --- a/engine/code/renderergl2/glsl/bokeh_fp.glsl +++ b/engine/code/renderergl2/glsl/bokeh_fp.glsl @@ -11,7 +11,15 @@ void main() vec2 tc; #if 0 - float c[7] = float[7](1.0, 0.9659258263, 0.8660254038, 0.7071067812, 0.5, 0.2588190451, 0.0); + float c[7]; + + c[0] = 1.0; + c[1] = 0.9659258263; + c[2] = 0.8660254038; + c[3] = 0.7071067812; + c[4] = 0.5; + c[5] = 0.2588190451; + c[6] = 0.0; tc = var_TexCoords + u_InvTexRes * vec2( c[0], c[6]); color = texture2D(u_TextureMap, tc); tc = var_TexCoords + u_InvTexRes * vec2( c[1], c[5]); color += texture2D(u_TextureMap, tc); @@ -44,7 +52,13 @@ void main() gl_FragColor = color * 0.04166667 * u_Color; #endif - float c[5] = float[5](1.0, 0.9238795325, 0.7071067812, 0.3826834324, 0.0); + float c[5]; + + c[0] = 1.0; + c[1] = 0.9238795325; + c[2] = 0.7071067812; + c[3] = 0.3826834324; + c[4] = 0.0; tc = var_TexCoords + u_InvTexRes * vec2( c[0], c[4]); color = texture2D(u_TextureMap, tc); tc = var_TexCoords + u_InvTexRes * vec2( c[1], c[3]); color += texture2D(u_TextureMap, tc); diff --git a/engine/code/renderergl2/glsl/depthblur_fp.glsl b/engine/code/renderergl2/glsl/depthblur_fp.glsl index d71b3487..d63df88a 100644 --- a/engine/code/renderergl2/glsl/depthblur_fp.glsl +++ b/engine/code/renderergl2/glsl/depthblur_fp.glsl @@ -6,7 +6,7 @@ varying vec2 var_ScreenTex; //float gauss[8] = float[8](0.17, 0.17, 0.16, 0.14, 0.12, 0.1, 0.08, 0.06); //float gauss[5] = float[5](0.30, 0.23, 0.097, 0.024, 0.0033); -float gauss[4] = float[4](0.40, 0.24, 0.054, 0.0044); +//float gauss[4] = float[4](0.40, 0.24, 0.054, 0.0044); //float gauss[3] = float[3](0.60, 0.19, 0.0066); #define BLUR_SIZE 4 @@ -22,6 +22,12 @@ float getLinearDepth(sampler2D depthMap, const vec2 tex, const float zFarDivZNea vec4 depthGaussian1D(sampler2D imageMap, sampler2D depthMap, vec2 tex, float zFarDivZNear, float zFar, vec2 scale) { + float gauss[4]; + + gauss[0] = 0.40; + gauss[1] = 0.24; + gauss[2] = 0.054; + gauss[3] = 0.0044; #if defined(USE_DEPTH) float depthCenter = getLinearDepth(depthMap, tex, zFarDivZNear); diff --git a/engine/code/renderergl2/glsl/dlight_fp.glsl b/engine/code/renderergl2/glsl/dlight_fp.glsl index 8ffca5b9..41be0494 100644 --- a/engine/code/renderergl2/glsl/dlight_fp.glsl +++ b/engine/code/renderergl2/glsl/dlight_fp.glsl @@ -1,5 +1,7 @@ uniform sampler2D u_DiffuseMap; +uniform int u_AlphaTest; + varying vec2 var_Tex1; varying vec4 var_Color; @@ -8,5 +10,23 @@ void main() { vec4 color = texture2D(u_DiffuseMap, var_Tex1); - gl_FragColor = color * var_Color; + float alpha = color.a * var_Color.a; + if (u_AlphaTest == 1) + { + if (alpha == 0.0) + discard; + } + else if (u_AlphaTest == 2) + { + if (alpha >= 0.5) + discard; + } + else if (u_AlphaTest == 3) + { + if (alpha < 0.5) + discard; + } + + gl_FragColor.rgb = color.rgb * var_Color.rgb; + gl_FragColor.a = alpha; } diff --git a/engine/code/renderergl2/glsl/generic_fp.glsl b/engine/code/renderergl2/glsl/generic_fp.glsl index 50db0785..c0a49407 100644 --- a/engine/code/renderergl2/glsl/generic_fp.glsl +++ b/engine/code/renderergl2/glsl/generic_fp.glsl @@ -1,5 +1,7 @@ uniform sampler2D u_DiffuseMap; +uniform int u_AlphaTest; + varying vec2 var_DiffuseTex; varying vec4 var_Color; @@ -8,5 +10,24 @@ varying vec4 var_Color; void main() { vec4 color = texture2D(u_DiffuseMap, var_DiffuseTex); - gl_FragColor = color * var_Color; + + float alpha = color.a * var_Color.a; + if (u_AlphaTest == 1) + { + if (alpha == 0.0) + discard; + } + else if (u_AlphaTest == 2) + { + if (alpha >= 0.5) + discard; + } + else if (u_AlphaTest == 3) + { + if (alpha < 0.5) + discard; + } + + gl_FragColor.rgb = color.rgb * var_Color.rgb; + gl_FragColor.a = alpha; } diff --git a/engine/code/renderergl2/glsl/lightall_fp.glsl b/engine/code/renderergl2/glsl/lightall_fp.glsl index 5cb8233d..8e7c9b4a 100644 --- a/engine/code/renderergl2/glsl/lightall_fp.glsl +++ b/engine/code/renderergl2/glsl/lightall_fp.glsl @@ -45,6 +45,8 @@ uniform vec4 u_CubeMapInfo; #endif #endif +uniform int u_AlphaTest; + varying vec4 var_TexCoords; varying vec4 var_Color; @@ -228,6 +230,23 @@ void main() #endif vec4 diffuse = texture2D(u_DiffuseMap, texCoords); + + float alpha = diffuse.a * var_Color.a; + if (u_AlphaTest == 1) + { + if (alpha == 0.0) + discard; + } + else if (u_AlphaTest == 2) + { + if (alpha >= 0.5) + discard; + } + else if (u_AlphaTest == 3) + { + if (alpha < 0.5) + discard; + } #if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT) L = var_LightDir.xyz; @@ -406,5 +425,5 @@ void main() #endif - gl_FragColor.a = diffuse.a * var_Color.a; + gl_FragColor.a = alpha; } diff --git a/engine/code/renderergl2/glsl/pshadow_fp.glsl b/engine/code/renderergl2/glsl/pshadow_fp.glsl index b152971a..c196f488 100644 --- a/engine/code/renderergl2/glsl/pshadow_fp.glsl +++ b/engine/code/renderergl2/glsl/pshadow_fp.glsl @@ -8,12 +8,6 @@ uniform float u_LightRadius; varying vec3 var_Position; varying vec3 var_Normal; -float sampleDistMap(sampler2D texMap, vec2 uv, float scale) -{ - vec3 distv = texture2D(texMap, uv).xyz; - return dot(distv, vec3(1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0)) * scale; -} - void main() { vec3 lightToPos = var_Position - u_LightOrigin.xyz; @@ -57,42 +51,28 @@ void main() #endif intensity *= fade; -#if defined(USE_PCF) - float part; - - dist = sampleDistMap(u_ShadowMap, st + vec2(-1.0/512.0, -1.0/512.0), u_LightRadius); - part = max(sign(lightDist - dist), 0.0); - - dist = sampleDistMap(u_ShadowMap, st + vec2( 1.0/512.0, -1.0/512.0), u_LightRadius); - part += max(sign(lightDist - dist), 0.0); - dist = sampleDistMap(u_ShadowMap, st + vec2(-1.0/512.0, 1.0/512.0), u_LightRadius); - part += max(sign(lightDist - dist), 0.0); - - dist = sampleDistMap(u_ShadowMap, st + vec2( 1.0/512.0, 1.0/512.0), u_LightRadius); - part += max(sign(lightDist - dist), 0.0); + float part; +#if defined(USE_PCF) + part = float(texture2D(u_ShadowMap, st + vec2(-1.0/512.0, -1.0/512.0)).r != 1.0); + part += float(texture2D(u_ShadowMap, st + vec2( 1.0/512.0, -1.0/512.0)).r != 1.0); + part += float(texture2D(u_ShadowMap, st + vec2(-1.0/512.0, 1.0/512.0)).r != 1.0); + part += float(texture2D(u_ShadowMap, st + vec2( 1.0/512.0, 1.0/512.0)).r != 1.0); +#else + part = float(texture2D(u_ShadowMap, st).r != 1.0); +#endif - #if defined(USE_DISCARD) if (part <= 0.0) { discard; } - #endif +#if defined(USE_PCF) intensity *= part * 0.25; #else - dist = sampleDistMap(u_ShadowMap, st, u_LightRadius); - - #if defined(USE_DISCARD) - if (lightDist - dist <= 0.0) - { - discard; - } - #endif - - intensity *= max(sign(lightDist - dist), 0.0); + intensity *= part; #endif - + gl_FragColor.rgb = vec3(0); gl_FragColor.a = clamp(intensity, 0.0, 0.75); } diff --git a/engine/code/renderergl2/glsl/shadowmask_fp.glsl b/engine/code/renderergl2/glsl/shadowmask_fp.glsl index 053907cf..2b57e3ba 100644 --- a/engine/code/renderergl2/glsl/shadowmask_fp.glsl +++ b/engine/code/renderergl2/glsl/shadowmask_fp.glsl @@ -52,10 +52,10 @@ float PCF(const sampler2DShadow shadowmap, const vec2 st, const float dist) offset.y += offset.x; if (offset.y > 1.1) offset.y = 0.0; - mult = shadow2D(shadowmap, vec3(st + (offset + vec2(-1.5, 0.5)) * scale, dist)).r - + shadow2D(shadowmap, vec3(st + (offset + vec2( 0.5, 0.5)) * scale, dist)).r - + shadow2D(shadowmap, vec3(st + (offset + vec2(-1.5, -1.5)) * scale, dist)).r - + shadow2D(shadowmap, vec3(st + (offset + vec2( 0.5, -1.5)) * scale, dist)).r; + mult = shadow2D(shadowmap, vec3(st + (offset + vec2(-1.5, 0.5)) * scale, dist)) + + shadow2D(shadowmap, vec3(st + (offset + vec2( 0.5, 0.5)) * scale, dist)) + + shadow2D(shadowmap, vec3(st + (offset + vec2(-1.5, -1.5)) * scale, dist)) + + shadow2D(shadowmap, vec3(st + (offset + vec2( 0.5, -1.5)) * scale, dist)); mult *= 0.25; #endif @@ -66,23 +66,23 @@ float PCF(const sampler2DShadow shadowmap, const vec2 st, const float dist) float cosr = cos(r) * scale; mat2 rmat = mat2(cosr, sinr, -sinr, cosr); - mult = shadow2D(shadowmap, vec3(st + rmat * vec2(-0.7055767, 0.196515), dist)).r; - mult += shadow2D(shadowmap, vec3(st + rmat * vec2(0.3524343, -0.7791386), dist)).r; - mult += shadow2D(shadowmap, vec3(st + rmat * vec2(0.2391056, 0.9189604), dist)).r; + mult = shadow2D(shadowmap, vec3(st + rmat * vec2(-0.7055767, 0.196515), dist)); + mult += shadow2D(shadowmap, vec3(st + rmat * vec2(0.3524343, -0.7791386), dist)); + mult += shadow2D(shadowmap, vec3(st + rmat * vec2(0.2391056, 0.9189604), dist)); #if defined(USE_SHADOW_FILTER2) - mult += shadow2D(shadowmap, vec3(st + rmat * vec2(-0.07580382, -0.09224417), dist)).r; - mult += shadow2D(shadowmap, vec3(st + rmat * vec2(0.5784913, -0.002528916), dist)).r; - mult += shadow2D(shadowmap, vec3(st + rmat * vec2(0.192888, 0.4064181), dist)).r; - mult += shadow2D(shadowmap, vec3(st + rmat * vec2(-0.6335801, -0.5247476), dist)).r; - mult += shadow2D(shadowmap, vec3(st + rmat * vec2(-0.5579782, 0.7491854), dist)).r; - mult += shadow2D(shadowmap, vec3(st + rmat * vec2(0.7320465, 0.6317794), dist)).r; + mult += shadow2D(shadowmap, vec3(st + rmat * vec2(-0.07580382, -0.09224417), dist)); + mult += shadow2D(shadowmap, vec3(st + rmat * vec2(0.5784913, -0.002528916), dist)); + mult += shadow2D(shadowmap, vec3(st + rmat * vec2(0.192888, 0.4064181), dist)); + mult += shadow2D(shadowmap, vec3(st + rmat * vec2(-0.6335801, -0.5247476), dist)); + mult += shadow2D(shadowmap, vec3(st + rmat * vec2(-0.5579782, 0.7491854), dist)); + mult += shadow2D(shadowmap, vec3(st + rmat * vec2(0.7320465, 0.6317794), dist)); mult *= 0.11111; #else mult *= 0.33333; #endif #else - mult = shadow2D(shadowmap, vec3(st, dist)).r; + mult = shadow2D(shadowmap, vec3(st, dist)); #endif return mult; diff --git a/engine/code/renderergl2/glsl/ssao_fp.glsl b/engine/code/renderergl2/glsl/ssao_fp.glsl index 93f61859..d4c43063 100644 --- a/engine/code/renderergl2/glsl/ssao_fp.glsl +++ b/engine/code/renderergl2/glsl/ssao_fp.glsl @@ -4,6 +4,7 @@ uniform vec4 u_ViewInfo; // zfar / znear, zfar, 1/width, 1/height varying vec2 var_ScreenTex; +#if 0 vec2 poissonDisc[9] = vec2[9]( vec2(-0.7055767, 0.196515), vec2(0.3524343, -0.7791386), vec2(0.2391056, 0.9189604), vec2(-0.07580382, -0.09224417), @@ -11,6 +12,8 @@ vec2(0.5784913, -0.002528916), vec2(0.192888, 0.4064181), vec2(-0.6335801, -0.5247476), vec2(-0.5579782, 0.7491854), vec2(0.7320465, 0.6317794) ); +#endif + #define NUM_SAMPLES 3 // Input: It uses texture coords as the random number seed. @@ -46,6 +49,18 @@ float getLinearDepth(sampler2D depthMap, const vec2 tex, const float zFarDivZNea float ambientOcclusion(sampler2D depthMap, const vec2 tex, const float zFarDivZNear, const float zFar, const vec2 scale) { + vec2 poissonDisc[9]; + + poissonDisc[0] = vec2(-0.7055767, 0.196515); + poissonDisc[1] = vec2(0.3524343, -0.7791386); + poissonDisc[2] = vec2(0.2391056, 0.9189604); + poissonDisc[3] = vec2(-0.07580382, -0.09224417); + poissonDisc[4] = vec2(0.5784913, -0.002528916); + poissonDisc[5] = vec2(0.192888, 0.4064181); + poissonDisc[6] = vec2(-0.6335801, -0.5247476); + poissonDisc[7] = vec2(-0.5579782, 0.7491854); + poissonDisc[8] = vec2(0.7320465, 0.6317794); + float result = 0; float sampleZ = getLinearDepth(depthMap, tex, zFarDivZNear); diff --git a/engine/code/renderergl2/tr_animation.c b/engine/code/renderergl2/tr_animation.c index 9acd6fc7..38fffc64 100644 --- a/engine/code/renderergl2/tr_animation.c +++ b/engine/code/renderergl2/tr_animation.c @@ -267,9 +267,9 @@ void R_MDRAddAnimSurfaces( trRefEntity_t *ent ) { for(j = 0; j < skin->numSurfaces; j++) { - if (!strcmp(skin->surfaces[j]->name, surface->name)) + if (!strcmp(skin->surfaces[j].name, surface->name)) { - shader = skin->surfaces[j]->shader; + shader = skin->surfaces[j].shader; break; } } diff --git a/engine/code/renderergl2/tr_backend.c b/engine/code/renderergl2/tr_backend.c index bc0d5e74..d7c01ff2 100644 --- a/engine/code/renderergl2/tr_backend.c +++ b/engine/code/renderergl2/tr_backend.c @@ -263,44 +263,6 @@ void GL_State( unsigned long stateBits ) } } - // - // alpha test - // - if ( diff & GLS_ATEST_BITS ) - { - uint32_t oldState = glState.glStateBits & GLS_ATEST_BITS; - uint32_t newState = stateBits & GLS_ATEST_BITS; - uint32_t storedState = glState.storedGlState & GLS_ATEST_BITS; - - if (oldState == 0) - { - qglEnable(GL_ALPHA_TEST); - } - else if (newState == 0) - { - qglDisable(GL_ALPHA_TEST); - } - - if (newState != 0 && storedState != newState) - { - glState.storedGlState &= ~GLS_ATEST_BITS; - glState.storedGlState |= newState; - - switch ( newState ) - { - case GLS_ATEST_GT_0: - qglAlphaFunc( GL_GREATER, 0.0f ); - break; - case GLS_ATEST_LT_80: - qglAlphaFunc( GL_LESS, 0.5f ); - break; - case GLS_ATEST_GE_80: - qglAlphaFunc( GL_GEQUAL, 0.5f ); - break; - } - } - } - glState.glStateBits = stateBits; } @@ -471,13 +433,10 @@ void RB_RenderDrawSurfList( drawSurf_t *drawSurfs, int numDrawSurfs ) { int i; drawSurf_t *drawSurf; int oldSort; - float originalTime; + double originalTime; FBO_t* fbo = NULL; qboolean inQuery = qfalse; - float depth[2]; - - // save original time for entity shader offsets originalTime = backEnd.refdef.floatTime; @@ -495,9 +454,6 @@ void RB_RenderDrawSurfList( drawSurf_t *drawSurfs, int numDrawSurfs ) { oldCubemapIndex = -1; oldSort = -1; - depth[0] = 0.f; - depth[1] = 1.f; - backEnd.pc.c_surfaces += numDrawSurfs; for (i = 0, drawSurf = drawSurfs ; i < numDrawSurfs ; i++, drawSurf++) { @@ -538,12 +494,14 @@ void RB_RenderDrawSurfList( drawSurf_t *drawSurfs, int numDrawSurfs ) { // change the modelview matrix if needed // if ( entityNum != oldEntityNum ) { - qboolean sunflare = qfalse; depthRange = isCrosshair = qfalse; if ( entityNum != REFENTITYNUM_WORLD ) { backEnd.currentEntity = &backEnd.refdef.entities[entityNum]; - backEnd.refdef.floatTime = originalTime - backEnd.currentEntity->e.shaderTime; + + // FIXME: e.shaderTime must be passed as int to avoid fp-precision loss issues + backEnd.refdef.floatTime = originalTime - (double)backEnd.currentEntity->e.shaderTime; + // we have to reset the shaderTime as well otherwise image animations start // from the wrong frame tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; @@ -604,12 +562,8 @@ void RB_RenderDrawSurfList( drawSurf_t *drawSurfs, int numDrawSurfs ) { } } - if(!oldDepthRange) - { - depth[0] = 0; - depth[1] = 0.3f; - qglDepthRange (depth[0], depth[1]); - } + if(!oldDepthRange) + qglDepthRange (0, 0.3); } else { @@ -618,11 +572,7 @@ void RB_RenderDrawSurfList( drawSurf_t *drawSurfs, int numDrawSurfs ) { GL_SetProjectionMatrix( backEnd.viewParms.projectionMatrix ); } - if (!sunflare) - qglDepthRange (0, 1); - - depth[0] = 0; - depth[1] = 1; + qglDepthRange (0, 1); } oldDepthRange = depthRange; @@ -711,7 +661,7 @@ void RB_SetGL2D (void) { // set time for 2D shaders backEnd.refdef.time = ri.Milliseconds(); - backEnd.refdef.floatTime = backEnd.refdef.time * 0.001f; + backEnd.refdef.floatTime = backEnd.refdef.time * 0.001; } @@ -931,6 +881,7 @@ RB_DrawSurfs */ const void *RB_DrawSurfs( const void *data ) { const drawSurfsCommand_t *cmd; + qboolean isShadowView; // finish any 2D drawing if needed if ( tess.numIndexes ) { @@ -942,6 +893,8 @@ const void *RB_DrawSurfs( const void *data ) { backEnd.refdef = cmd->refdef; backEnd.viewParms = cmd->viewParms; + isShadowView = !!(backEnd.viewParms.flags & VPF_DEPTHSHADOW); + // clear the z buffer, set the modelview, etc RB_BeginDrawingView (); @@ -950,7 +903,7 @@ const void *RB_DrawSurfs( const void *data ) { qglEnable(GL_DEPTH_CLAMP); } - if (glRefConfig.framebufferObject && !(backEnd.refdef.rdflags & RDF_NOWORLDMODEL) && (r_depthPrepass->integer || (backEnd.viewParms.flags & VPF_DEPTHSHADOW))) + if (glRefConfig.framebufferObject && !(backEnd.refdef.rdflags & RDF_NOWORLDMODEL) && (r_depthPrepass->integer || isShadowView)) { FBO_t *oldFbo = glState.currentFBO; vec4_t viewInfo; @@ -963,205 +916,208 @@ const void *RB_DrawSurfs( const void *data ) { qglColorMask(!backEnd.colorMask[0], !backEnd.colorMask[1], !backEnd.colorMask[2], !backEnd.colorMask[3]); backEnd.depthFill = qfalse; - if (tr.msaaResolveFbo) + if (!isShadowView) { - // If we're using multisampling, resolve the depth first - FBO_FastBlit(tr.renderFbo, NULL, tr.msaaResolveFbo, NULL, GL_DEPTH_BUFFER_BIT, GL_NEAREST); - } - else if (tr.renderFbo == NULL && tr.renderDepthImage) - { - // If we're rendering directly to the screen, copy the depth to a texture - // This is incredibly slow on Intel Graphics, so just skip it on there - if (!glRefConfig.intelGraphics) - qglCopyTextureSubImage2DEXT(tr.renderDepthImage->texnum, GL_TEXTURE_2D, 0, 0, 0, 0, 0, glConfig.vidWidth, glConfig.vidHeight); - } + if (tr.msaaResolveFbo) + { + // If we're using multisampling, resolve the depth first + FBO_FastBlit(tr.renderFbo, NULL, tr.msaaResolveFbo, NULL, GL_DEPTH_BUFFER_BIT, GL_NEAREST); + } + else if (tr.renderFbo == NULL && tr.renderDepthImage) + { + // If we're rendering directly to the screen, copy the depth to a texture + // This is incredibly slow on Intel Graphics, so just skip it on there + if (!glRefConfig.intelGraphics) + qglCopyTextureSubImage2DEXT(tr.renderDepthImage->texnum, GL_TEXTURE_2D, 0, 0, 0, 0, 0, glConfig.vidWidth, glConfig.vidHeight); + } - if (tr.hdrDepthFbo) - { - // need the depth in a texture we can do GL_LINEAR sampling on, so copy it to an HDR image - vec4_t srcTexCoords; + if (tr.hdrDepthFbo) + { + // need the depth in a texture we can do GL_LINEAR sampling on, so copy it to an HDR image + vec4_t srcTexCoords; - VectorSet4(srcTexCoords, 0.0f, 0.0f, 1.0f, 1.0f); + VectorSet4(srcTexCoords, 0.0f, 0.0f, 1.0f, 1.0f); - FBO_BlitFromTexture(tr.renderDepthImage, srcTexCoords, NULL, tr.hdrDepthFbo, NULL, NULL, NULL, 0); - } + FBO_BlitFromTexture(tr.renderDepthImage, srcTexCoords, NULL, tr.hdrDepthFbo, NULL, NULL, NULL, 0); + } - if (r_sunlightMode->integer && backEnd.viewParms.flags & VPF_USESUNLIGHT) - { - vec4_t quadVerts[4]; - vec2_t texCoords[4]; - vec4_t box; + if (r_sunlightMode->integer && backEnd.viewParms.flags & VPF_USESUNLIGHT) + { + vec4_t quadVerts[4]; + vec2_t texCoords[4]; + vec4_t box; - FBO_Bind(tr.screenShadowFbo); + FBO_Bind(tr.screenShadowFbo); - box[0] = backEnd.viewParms.viewportX * tr.screenShadowFbo->width / (float)glConfig.vidWidth; - box[1] = backEnd.viewParms.viewportY * tr.screenShadowFbo->height / (float)glConfig.vidHeight; - box[2] = backEnd.viewParms.viewportWidth * tr.screenShadowFbo->width / (float)glConfig.vidWidth; - box[3] = backEnd.viewParms.viewportHeight * tr.screenShadowFbo->height / (float)glConfig.vidHeight; + box[0] = backEnd.viewParms.viewportX * tr.screenShadowFbo->width / (float)glConfig.vidWidth; + box[1] = backEnd.viewParms.viewportY * tr.screenShadowFbo->height / (float)glConfig.vidHeight; + box[2] = backEnd.viewParms.viewportWidth * tr.screenShadowFbo->width / (float)glConfig.vidWidth; + box[3] = backEnd.viewParms.viewportHeight * tr.screenShadowFbo->height / (float)glConfig.vidHeight; - qglViewport(box[0], box[1], box[2], box[3]); - qglScissor(box[0], box[1], box[2], box[3]); + qglViewport(box[0], box[1], box[2], box[3]); + qglScissor(box[0], box[1], box[2], box[3]); - box[0] = backEnd.viewParms.viewportX / (float)glConfig.vidWidth; - box[1] = backEnd.viewParms.viewportY / (float)glConfig.vidHeight; - box[2] = box[0] + backEnd.viewParms.viewportWidth / (float)glConfig.vidWidth; - box[3] = box[1] + backEnd.viewParms.viewportHeight / (float)glConfig.vidHeight; + box[0] = backEnd.viewParms.viewportX / (float)glConfig.vidWidth; + box[1] = backEnd.viewParms.viewportY / (float)glConfig.vidHeight; + box[2] = box[0] + backEnd.viewParms.viewportWidth / (float)glConfig.vidWidth; + box[3] = box[1] + backEnd.viewParms.viewportHeight / (float)glConfig.vidHeight; - texCoords[0][0] = box[0]; texCoords[0][1] = box[3]; - texCoords[1][0] = box[2]; texCoords[1][1] = box[3]; - texCoords[2][0] = box[2]; texCoords[2][1] = box[1]; - texCoords[3][0] = box[0]; texCoords[3][1] = box[1]; + texCoords[0][0] = box[0]; texCoords[0][1] = box[3]; + texCoords[1][0] = box[2]; texCoords[1][1] = box[3]; + texCoords[2][0] = box[2]; texCoords[2][1] = box[1]; + texCoords[3][0] = box[0]; texCoords[3][1] = box[1]; - box[0] = -1.0f; - box[1] = -1.0f; - box[2] = 1.0f; - box[3] = 1.0f; + box[0] = -1.0f; + box[1] = -1.0f; + box[2] = 1.0f; + box[3] = 1.0f; - VectorSet4(quadVerts[0], box[0], box[3], 0, 1); - VectorSet4(quadVerts[1], box[2], box[3], 0, 1); - VectorSet4(quadVerts[2], box[2], box[1], 0, 1); - VectorSet4(quadVerts[3], box[0], box[1], 0, 1); + VectorSet4(quadVerts[0], box[0], box[3], 0, 1); + VectorSet4(quadVerts[1], box[2], box[3], 0, 1); + VectorSet4(quadVerts[2], box[2], box[1], 0, 1); + VectorSet4(quadVerts[3], box[0], box[1], 0, 1); - GL_State( GLS_DEPTHTEST_DISABLE ); + GL_State(GLS_DEPTHTEST_DISABLE); - GLSL_BindProgram(&tr.shadowmaskShader); + GLSL_BindProgram(&tr.shadowmaskShader); - GL_BindToTMU(tr.renderDepthImage, TB_COLORMAP); - - if (r_shadowCascadeZFar->integer != 0) - { - GL_BindToTMU(tr.sunShadowDepthImage[0], TB_SHADOWMAP); - GL_BindToTMU(tr.sunShadowDepthImage[1], TB_SHADOWMAP2); - GL_BindToTMU(tr.sunShadowDepthImage[2], TB_SHADOWMAP3); - GL_BindToTMU(tr.sunShadowDepthImage[3], TB_SHADOWMAP4); - - GLSL_SetUniformMat4(&tr.shadowmaskShader, UNIFORM_SHADOWMVP, backEnd.refdef.sunShadowMvp[0]); - GLSL_SetUniformMat4(&tr.shadowmaskShader, UNIFORM_SHADOWMVP2, backEnd.refdef.sunShadowMvp[1]); - GLSL_SetUniformMat4(&tr.shadowmaskShader, UNIFORM_SHADOWMVP3, backEnd.refdef.sunShadowMvp[2]); - GLSL_SetUniformMat4(&tr.shadowmaskShader, UNIFORM_SHADOWMVP4, backEnd.refdef.sunShadowMvp[3]); - } - else - { - GL_BindToTMU(tr.sunShadowDepthImage[3], TB_SHADOWMAP); - GLSL_SetUniformMat4(&tr.shadowmaskShader, UNIFORM_SHADOWMVP, backEnd.refdef.sunShadowMvp[3]); - } - - GLSL_SetUniformVec3(&tr.shadowmaskShader, UNIFORM_VIEWORIGIN, backEnd.refdef.vieworg); - { - vec3_t viewVector; + GL_BindToTMU(tr.renderDepthImage, TB_COLORMAP); - float zmax = backEnd.viewParms.zFar; - float ymax = zmax * tan(backEnd.viewParms.fovY * M_PI / 360.0f); - float xmax = zmax * tan(backEnd.viewParms.fovX * M_PI / 360.0f); + if (r_shadowCascadeZFar->integer != 0) + { + GL_BindToTMU(tr.sunShadowDepthImage[0], TB_SHADOWMAP); + GL_BindToTMU(tr.sunShadowDepthImage[1], TB_SHADOWMAP2); + GL_BindToTMU(tr.sunShadowDepthImage[2], TB_SHADOWMAP3); + GL_BindToTMU(tr.sunShadowDepthImage[3], TB_SHADOWMAP4); + + GLSL_SetUniformMat4(&tr.shadowmaskShader, UNIFORM_SHADOWMVP, backEnd.refdef.sunShadowMvp[0]); + GLSL_SetUniformMat4(&tr.shadowmaskShader, UNIFORM_SHADOWMVP2, backEnd.refdef.sunShadowMvp[1]); + GLSL_SetUniformMat4(&tr.shadowmaskShader, UNIFORM_SHADOWMVP3, backEnd.refdef.sunShadowMvp[2]); + GLSL_SetUniformMat4(&tr.shadowmaskShader, UNIFORM_SHADOWMVP4, backEnd.refdef.sunShadowMvp[3]); + } + else + { + GL_BindToTMU(tr.sunShadowDepthImage[3], TB_SHADOWMAP); + GLSL_SetUniformMat4(&tr.shadowmaskShader, UNIFORM_SHADOWMVP, backEnd.refdef.sunShadowMvp[3]); + } - VectorScale(backEnd.refdef.viewaxis[0], zmax, viewVector); - GLSL_SetUniformVec3(&tr.shadowmaskShader, UNIFORM_VIEWFORWARD, viewVector); - VectorScale(backEnd.refdef.viewaxis[1], xmax, viewVector); - GLSL_SetUniformVec3(&tr.shadowmaskShader, UNIFORM_VIEWLEFT, viewVector); - VectorScale(backEnd.refdef.viewaxis[2], ymax, viewVector); - GLSL_SetUniformVec3(&tr.shadowmaskShader, UNIFORM_VIEWUP, viewVector); + GLSL_SetUniformVec3(&tr.shadowmaskShader, UNIFORM_VIEWORIGIN, backEnd.refdef.vieworg); + { + vec3_t viewVector; - GLSL_SetUniformVec4(&tr.shadowmaskShader, UNIFORM_VIEWINFO, viewInfo); - } + float zmax = backEnd.viewParms.zFar; + float ymax = zmax * tan(backEnd.viewParms.fovY * M_PI / 360.0f); + float xmax = zmax * tan(backEnd.viewParms.fovX * M_PI / 360.0f); - RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes); + VectorScale(backEnd.refdef.viewaxis[0], zmax, viewVector); + GLSL_SetUniformVec3(&tr.shadowmaskShader, UNIFORM_VIEWFORWARD, viewVector); + VectorScale(backEnd.refdef.viewaxis[1], xmax, viewVector); + GLSL_SetUniformVec3(&tr.shadowmaskShader, UNIFORM_VIEWLEFT, viewVector); + VectorScale(backEnd.refdef.viewaxis[2], ymax, viewVector); + GLSL_SetUniformVec3(&tr.shadowmaskShader, UNIFORM_VIEWUP, viewVector); - if (r_shadowBlur->integer) - { - viewInfo[2] = 1.0f / (float)(tr.screenScratchFbo->width); - viewInfo[3] = 1.0f / (float)(tr.screenScratchFbo->height); + GLSL_SetUniformVec4(&tr.shadowmaskShader, UNIFORM_VIEWINFO, viewInfo); + } - FBO_Bind(tr.screenScratchFbo); + RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes); - GLSL_BindProgram(&tr.depthBlurShader[0]); + if (r_shadowBlur->integer) + { + viewInfo[2] = 1.0f / (float)(tr.screenScratchFbo->width); + viewInfo[3] = 1.0f / (float)(tr.screenScratchFbo->height); - GL_BindToTMU(tr.screenShadowImage, TB_COLORMAP); - GL_BindToTMU(tr.hdrDepthImage, TB_LIGHTMAP); + FBO_Bind(tr.screenScratchFbo); - GLSL_SetUniformVec4(&tr.depthBlurShader[0], UNIFORM_VIEWINFO, viewInfo); + GLSL_BindProgram(&tr.depthBlurShader[0]); - RB_InstantQuad2(quadVerts, texCoords); + GL_BindToTMU(tr.screenShadowImage, TB_COLORMAP); + GL_BindToTMU(tr.hdrDepthImage, TB_LIGHTMAP); - FBO_Bind(tr.screenShadowFbo); + GLSL_SetUniformVec4(&tr.depthBlurShader[0], UNIFORM_VIEWINFO, viewInfo); - GLSL_BindProgram(&tr.depthBlurShader[1]); + RB_InstantQuad2(quadVerts, texCoords); - GL_BindToTMU(tr.screenScratchImage, TB_COLORMAP); - GL_BindToTMU(tr.hdrDepthImage, TB_LIGHTMAP); + FBO_Bind(tr.screenShadowFbo); - GLSL_SetUniformVec4(&tr.depthBlurShader[1], UNIFORM_VIEWINFO, viewInfo); + GLSL_BindProgram(&tr.depthBlurShader[1]); - RB_InstantQuad2(quadVerts, texCoords); + GL_BindToTMU(tr.screenScratchImage, TB_COLORMAP); + GL_BindToTMU(tr.hdrDepthImage, TB_LIGHTMAP); + + GLSL_SetUniformVec4(&tr.depthBlurShader[1], UNIFORM_VIEWINFO, viewInfo); + + RB_InstantQuad2(quadVerts, texCoords); + } } - } - if (r_ssao->integer) - { - vec4_t quadVerts[4]; - vec2_t texCoords[4]; + if (r_ssao->integer) + { + vec4_t quadVerts[4]; + vec2_t texCoords[4]; - viewInfo[2] = 1.0f / ((float)(tr.quarterImage[0]->width) * tan(backEnd.viewParms.fovX * M_PI / 360.0f) * 2.0f); - viewInfo[3] = 1.0f / ((float)(tr.quarterImage[0]->height) * tan(backEnd.viewParms.fovY * M_PI / 360.0f) * 2.0f); - viewInfo[3] *= (float)backEnd.viewParms.viewportHeight / (float)backEnd.viewParms.viewportWidth; + viewInfo[2] = 1.0f / ((float)(tr.quarterImage[0]->width) * tan(backEnd.viewParms.fovX * M_PI / 360.0f) * 2.0f); + viewInfo[3] = 1.0f / ((float)(tr.quarterImage[0]->height) * tan(backEnd.viewParms.fovY * M_PI / 360.0f) * 2.0f); + viewInfo[3] *= (float)backEnd.viewParms.viewportHeight / (float)backEnd.viewParms.viewportWidth; - FBO_Bind(tr.quarterFbo[0]); + FBO_Bind(tr.quarterFbo[0]); - qglViewport(0, 0, tr.quarterFbo[0]->width, tr.quarterFbo[0]->height); - qglScissor(0, 0, tr.quarterFbo[0]->width, tr.quarterFbo[0]->height); + qglViewport(0, 0, tr.quarterFbo[0]->width, tr.quarterFbo[0]->height); + qglScissor(0, 0, tr.quarterFbo[0]->width, tr.quarterFbo[0]->height); - VectorSet4(quadVerts[0], -1, 1, 0, 1); - VectorSet4(quadVerts[1], 1, 1, 0, 1); - VectorSet4(quadVerts[2], 1, -1, 0, 1); - VectorSet4(quadVerts[3], -1, -1, 0, 1); + VectorSet4(quadVerts[0], -1, 1, 0, 1); + VectorSet4(quadVerts[1], 1, 1, 0, 1); + VectorSet4(quadVerts[2], 1, -1, 0, 1); + VectorSet4(quadVerts[3], -1, -1, 0, 1); - texCoords[0][0] = 0; texCoords[0][1] = 1; - texCoords[1][0] = 1; texCoords[1][1] = 1; - texCoords[2][0] = 1; texCoords[2][1] = 0; - texCoords[3][0] = 0; texCoords[3][1] = 0; + texCoords[0][0] = 0; texCoords[0][1] = 1; + texCoords[1][0] = 1; texCoords[1][1] = 1; + texCoords[2][0] = 1; texCoords[2][1] = 0; + texCoords[3][0] = 0; texCoords[3][1] = 0; - GL_State( GLS_DEPTHTEST_DISABLE ); + GL_State( GLS_DEPTHTEST_DISABLE ); - GLSL_BindProgram(&tr.ssaoShader); + GLSL_BindProgram(&tr.ssaoShader); - GL_BindToTMU(tr.hdrDepthImage, TB_COLORMAP); + GL_BindToTMU(tr.hdrDepthImage, TB_COLORMAP); - GLSL_SetUniformVec4(&tr.ssaoShader, UNIFORM_VIEWINFO, viewInfo); + GLSL_SetUniformVec4(&tr.ssaoShader, UNIFORM_VIEWINFO, viewInfo); - RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes); + RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes); - viewInfo[2] = 1.0f / (float)(tr.quarterImage[0]->width); - viewInfo[3] = 1.0f / (float)(tr.quarterImage[0]->height); + viewInfo[2] = 1.0f / (float)(tr.quarterImage[0]->width); + viewInfo[3] = 1.0f / (float)(tr.quarterImage[0]->height); - FBO_Bind(tr.quarterFbo[1]); + FBO_Bind(tr.quarterFbo[1]); - qglViewport(0, 0, tr.quarterFbo[1]->width, tr.quarterFbo[1]->height); - qglScissor(0, 0, tr.quarterFbo[1]->width, tr.quarterFbo[1]->height); + qglViewport(0, 0, tr.quarterFbo[1]->width, tr.quarterFbo[1]->height); + qglScissor(0, 0, tr.quarterFbo[1]->width, tr.quarterFbo[1]->height); - GLSL_BindProgram(&tr.depthBlurShader[0]); + GLSL_BindProgram(&tr.depthBlurShader[0]); - GL_BindToTMU(tr.quarterImage[0], TB_COLORMAP); - GL_BindToTMU(tr.hdrDepthImage, TB_LIGHTMAP); + GL_BindToTMU(tr.quarterImage[0], TB_COLORMAP); + GL_BindToTMU(tr.hdrDepthImage, TB_LIGHTMAP); - GLSL_SetUniformVec4(&tr.depthBlurShader[0], UNIFORM_VIEWINFO, viewInfo); + GLSL_SetUniformVec4(&tr.depthBlurShader[0], UNIFORM_VIEWINFO, viewInfo); - RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes); + RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes); - FBO_Bind(tr.screenSsaoFbo); + FBO_Bind(tr.screenSsaoFbo); - qglViewport(0, 0, tr.screenSsaoFbo->width, tr.screenSsaoFbo->height); - qglScissor(0, 0, tr.screenSsaoFbo->width, tr.screenSsaoFbo->height); + qglViewport(0, 0, tr.screenSsaoFbo->width, tr.screenSsaoFbo->height); + qglScissor(0, 0, tr.screenSsaoFbo->width, tr.screenSsaoFbo->height); - GLSL_BindProgram(&tr.depthBlurShader[1]); + GLSL_BindProgram(&tr.depthBlurShader[1]); - GL_BindToTMU(tr.quarterImage[1], TB_COLORMAP); - GL_BindToTMU(tr.hdrDepthImage, TB_LIGHTMAP); + GL_BindToTMU(tr.quarterImage[1], TB_COLORMAP); + GL_BindToTMU(tr.hdrDepthImage, TB_LIGHTMAP); - GLSL_SetUniformVec4(&tr.depthBlurShader[1], UNIFORM_VIEWINFO, viewInfo); + GLSL_SetUniformVec4(&tr.depthBlurShader[1], UNIFORM_VIEWINFO, viewInfo); - RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes); + RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes); + } } // reset viewport and scissor @@ -1174,7 +1130,7 @@ const void *RB_DrawSurfs( const void *data ) { qglDisable(GL_DEPTH_CLAMP); } - if (!(backEnd.viewParms.flags & VPF_DEPTHSHADOW)) + if (!isShadowView) { RB_RenderDrawSurfList( cmd->drawSurfs, cmd->numDrawSurfs ); @@ -1443,7 +1399,7 @@ const void *RB_SwapBuffers( const void *data ) { } } - if ( !glState.finishCalled ) { + if ( r_finish->integer == 1 && !glState.finishCalled ) { qglFinish(); } @@ -1651,16 +1607,29 @@ const void *RB_PostProcess(const void *data) if (0 && r_sunlightMode->integer) { ivec4_t dstBox; - VectorSet4(dstBox, 0, 0, 128, 128); + VectorSet4(dstBox, 0, glConfig.vidHeight - 128, 128, 128); FBO_BlitFromTexture(tr.sunShadowDepthImage[0], NULL, NULL, NULL, dstBox, NULL, NULL, 0); - VectorSet4(dstBox, 128, 0, 128, 128); + VectorSet4(dstBox, 128, glConfig.vidHeight - 128, 128, 128); FBO_BlitFromTexture(tr.sunShadowDepthImage[1], NULL, NULL, NULL, dstBox, NULL, NULL, 0); - VectorSet4(dstBox, 256, 0, 128, 128); + VectorSet4(dstBox, 256, glConfig.vidHeight - 128, 128, 128); FBO_BlitFromTexture(tr.sunShadowDepthImage[2], NULL, NULL, NULL, dstBox, NULL, NULL, 0); - VectorSet4(dstBox, 384, 0, 128, 128); + VectorSet4(dstBox, 384, glConfig.vidHeight - 128, 128, 128); FBO_BlitFromTexture(tr.sunShadowDepthImage[3], NULL, NULL, NULL, dstBox, NULL, NULL, 0); } + if (0 && r_shadows->integer == 4) + { + ivec4_t dstBox; + VectorSet4(dstBox, 512 + 0, glConfig.vidHeight - 128, 128, 128); + FBO_BlitFromTexture(tr.pshadowMaps[0], NULL, NULL, NULL, dstBox, NULL, NULL, 0); + VectorSet4(dstBox, 512 + 128, glConfig.vidHeight - 128, 128, 128); + FBO_BlitFromTexture(tr.pshadowMaps[1], NULL, NULL, NULL, dstBox, NULL, NULL, 0); + VectorSet4(dstBox, 512 + 256, glConfig.vidHeight - 128, 128, 128); + FBO_BlitFromTexture(tr.pshadowMaps[2], NULL, NULL, NULL, dstBox, NULL, NULL, 0); + VectorSet4(dstBox, 512 + 384, glConfig.vidHeight - 128, 128, 128); + FBO_BlitFromTexture(tr.pshadowMaps[3], NULL, NULL, NULL, dstBox, NULL, NULL, 0); + } + if (0) { ivec4_t dstBox; diff --git a/engine/code/renderergl2/tr_bsp.c b/engine/code/renderergl2/tr_bsp.c index cf6b5734..0480f91e 100644 --- a/engine/code/renderergl2/tr_bsp.c +++ b/engine/code/renderergl2/tr_bsp.c @@ -1680,428 +1680,6 @@ void R_MovePatchSurfacesToHunk(void) { } -/* -================= -BSPSurfaceCompare -compare function for qsort() -================= -*/ -static int BSPSurfaceCompare(const void *a, const void *b) -{ - msurface_t *aa, *bb; - - aa = *(msurface_t **) a; - bb = *(msurface_t **) b; - - // shader first - if(aa->shader->sortedIndex < bb->shader->sortedIndex) - return -1; - - else if(aa->shader->sortedIndex > bb->shader->sortedIndex) - return 1; - - // by fogIndex - if(aa->fogIndex < bb->fogIndex) - return -1; - - else if(aa->fogIndex > bb->fogIndex) - return 1; - - // by cubemapIndex - if(aa->cubemapIndex < bb->cubemapIndex) - return -1; - - else if(aa->cubemapIndex > bb->cubemapIndex) - return 1; - - // by leaf - if (s_worldData.surfacesViewCount[aa - s_worldData.surfaces] < s_worldData.surfacesViewCount[bb - s_worldData.surfaces]) - return -1; - - else if (s_worldData.surfacesViewCount[aa - s_worldData.surfaces] > s_worldData.surfacesViewCount[bb - s_worldData.surfaces]) - return 1; - - // by surface number - if (aa < bb) - return -1; - - else if (aa > bb) - return 1; - - return 0; -} - - -static void CopyVert(const srfVert_t * in, srfVert_t * out) -{ - VectorCopy(in->xyz, out->xyz); - VectorCopy4(in->tangent, out->tangent); - VectorCopy4(in->normal, out->normal); - VectorCopy4(in->lightdir, out->lightdir); - - VectorCopy2(in->st, out->st); - VectorCopy2(in->lightmap, out->lightmap); - - VectorCopy4(in->color, out->color); -} - - -/* -=============== -R_CreateWorldVaos -=============== -*/ -static void R_CreateWorldVaos(void) -{ - int i, j, k; - - int numVerts; - srfVert_t *verts; - - int numIndexes; - glIndex_t *indexes; - - int numSortedSurfaces, numSurfaces; - msurface_t *surface, **firstSurf, **lastSurf, **currSurf; - msurface_t **surfacesSorted; - - vao_t *vao; - - int maxVboSize = 4 * 1024 * 1024; - - int startTime, endTime; - - startTime = ri.Milliseconds(); - - // mark surfaces with best matching leaf, using overlapping bounds - // using surfaceViewCount[] as leaf number, and surfacesDlightBits[] as coverage * 256 - for (i = 0; i < s_worldData.numWorldSurfaces; i++) - { - s_worldData.surfacesViewCount[i] = -1; - } - - for (i = 0; i < s_worldData.numWorldSurfaces; i++) - { - s_worldData.surfacesDlightBits[i] = 0; - } - - for (i = s_worldData.numDecisionNodes; i < s_worldData.numnodes; i++) - { - mnode_t *leaf = s_worldData.nodes + i; - - for (j = leaf->firstmarksurface; j < leaf->firstmarksurface + leaf->nummarksurfaces; j++) - { - int surfaceNum = s_worldData.marksurfaces[j]; - msurface_t *surface = s_worldData.surfaces + surfaceNum; - float coverage = 1.0f; - int iCoverage; - - for (k = 0; k < 3; k++) - { - float left, right; - - if (leaf->mins[k] > surface->cullinfo.bounds[1][k] || surface->cullinfo.bounds[0][k] > leaf->maxs[k]) - { - coverage = 0.0f; - break; - } - - left = MAX(leaf->mins[k], surface->cullinfo.bounds[0][k]); - right = MIN(leaf->maxs[k], surface->cullinfo.bounds[1][k]); - - // nudge a bit in case this is an axis aligned wall - coverage *= right - left + 1.0f/256.0f; - } - - iCoverage = coverage * 256; - - if (iCoverage > s_worldData.surfacesDlightBits[surfaceNum]) - { - s_worldData.surfacesDlightBits[surfaceNum] = iCoverage; - s_worldData.surfacesViewCount[surfaceNum] = i - s_worldData.numDecisionNodes; - } - } - } - - for (i = 0; i < s_worldData.numWorldSurfaces; i++) - { - s_worldData.surfacesDlightBits[i] = 0; - } - - // count surfaces - numSortedSurfaces = 0; - for(surface = s_worldData.surfaces; surface < s_worldData.surfaces + s_worldData.numWorldSurfaces; surface++) - { - srfBspSurface_t *bspSurf; - shader_t *shader = surface->shader; - - if (shader->isPortal || shader->isSky || ShaderRequiresCPUDeforms(shader)) - continue; - - // check for this now so we can use srfBspSurface_t* universally in the rest of the function - if (!(*surface->data == SF_FACE || *surface->data == SF_GRID || *surface->data == SF_TRIANGLES)) - continue; - - bspSurf = (srfBspSurface_t *) surface->data; - - if (!bspSurf->numIndexes || !bspSurf->numVerts) - continue; - - numSortedSurfaces++; - } - - // presort surfaces - surfacesSorted = ri.Malloc(numSortedSurfaces * sizeof(*surfacesSorted)); - - j = 0; - for(surface = s_worldData.surfaces; surface < s_worldData.surfaces + s_worldData.numWorldSurfaces; surface++) - { - srfBspSurface_t *bspSurf; - shader_t *shader = surface->shader; - - if (shader->isPortal || shader->isSky || ShaderRequiresCPUDeforms(shader)) - continue; - - // check for this now so we can use srfBspSurface_t* universally in the rest of the function - if (!(*surface->data == SF_FACE || *surface->data == SF_GRID || *surface->data == SF_TRIANGLES)) - continue; - - bspSurf = (srfBspSurface_t *) surface->data; - - if (!bspSurf->numIndexes || !bspSurf->numVerts) - continue; - - surfacesSorted[j++] = surface; - } - - qsort(surfacesSorted, numSortedSurfaces, sizeof(*surfacesSorted), BSPSurfaceCompare); - - k = 0; - for(firstSurf = lastSurf = surfacesSorted; firstSurf < surfacesSorted + numSortedSurfaces; firstSurf = lastSurf) - { - int currVboSize; - - // Find range of surfaces to place in a VAO by: - // - Collecting a number of surfaces which fit under maxVboSize, or - // - All the surfaces with a single shader which go over maxVboSize - currVboSize = 0; - while (currVboSize < maxVboSize && lastSurf < surfacesSorted + numSortedSurfaces) - { - int addVboSize, currShaderIndex; - - addVboSize = 0; - currShaderIndex = (*lastSurf)->shader->sortedIndex; - - for(currSurf = lastSurf; currSurf < surfacesSorted + numSortedSurfaces && (*currSurf)->shader->sortedIndex == currShaderIndex; currSurf++) - { - srfBspSurface_t *bspSurf = (srfBspSurface_t *) (*currSurf)->data; - - addVboSize += bspSurf->numVerts * sizeof(srfVert_t); - } - - if (currVboSize != 0 && addVboSize + currVboSize > maxVboSize) - break; - - lastSurf = currSurf; - - currVboSize += addVboSize; - } - - // count verts/indexes/surfaces - numVerts = 0; - numIndexes = 0; - numSurfaces = 0; - for (currSurf = firstSurf; currSurf < lastSurf; currSurf++) - { - srfBspSurface_t *bspSurf = (srfBspSurface_t *) (*currSurf)->data; - - numVerts += bspSurf->numVerts; - numIndexes += bspSurf->numIndexes; - numSurfaces++; - } - - ri.Printf(PRINT_ALL, "...calculating world VAO %d ( %i verts %i tris )\n", k, numVerts, numIndexes / 3); - - // create arrays - verts = ri.Hunk_AllocateTempMemory(numVerts * sizeof(srfVert_t)); - indexes = ri.Hunk_AllocateTempMemory(numIndexes * sizeof(glIndex_t)); - - // set up indices and copy vertices - numVerts = 0; - numIndexes = 0; - for (currSurf = firstSurf; currSurf < lastSurf; currSurf++) - { - srfBspSurface_t *bspSurf = (srfBspSurface_t *) (*currSurf)->data; - glIndex_t *surfIndex; - - bspSurf->firstIndex = numIndexes; - bspSurf->minIndex = numVerts + bspSurf->indexes[0]; - bspSurf->maxIndex = numVerts + bspSurf->indexes[0]; - - for(i = 0, surfIndex = bspSurf->indexes; i < bspSurf->numIndexes; i++, surfIndex++) - { - indexes[numIndexes++] = numVerts + *surfIndex; - bspSurf->minIndex = MIN(bspSurf->minIndex, numVerts + *surfIndex); - bspSurf->maxIndex = MAX(bspSurf->maxIndex, numVerts + *surfIndex); - } - - bspSurf->firstVert = numVerts; - - for(i = 0; i < bspSurf->numVerts; i++) - { - CopyVert(&bspSurf->verts[i], &verts[numVerts++]); - } - } - - vao = R_CreateVao2(va("staticBspModel%i_VAO", k), numVerts, verts, numIndexes, indexes); - - // point bsp surfaces to VAO - for (currSurf = firstSurf; currSurf < lastSurf; currSurf++) - { - srfBspSurface_t *bspSurf = (srfBspSurface_t *) (*currSurf)->data; - - bspSurf->vao = vao; - } - - ri.Hunk_FreeTempMemory(indexes); - ri.Hunk_FreeTempMemory(verts); - - k++; - } - - if (r_mergeLeafSurfaces->integer) - { - msurface_t *mergedSurf; - - // count merged surfaces - int numMergedSurfaces = 0, numUnmergedSurfaces = 0; - for(firstSurf = lastSurf = surfacesSorted; firstSurf < surfacesSorted + numSortedSurfaces; firstSurf = lastSurf) - { - for (lastSurf++ ; lastSurf < surfacesSorted + numSortedSurfaces; lastSurf++) - { - int lastSurfLeafIndex, firstSurfLeafIndex; - - if ((*lastSurf)->shader != (*firstSurf)->shader - || (*lastSurf)->fogIndex != (*firstSurf)->fogIndex - || (*lastSurf)->cubemapIndex != (*firstSurf)->cubemapIndex) - break; - - lastSurfLeafIndex = s_worldData.surfacesViewCount[*lastSurf - s_worldData.surfaces]; - firstSurfLeafIndex = s_worldData.surfacesViewCount[*firstSurf - s_worldData.surfaces]; - - if (lastSurfLeafIndex != firstSurfLeafIndex) - break; - } - - // don't merge single surfaces - if (firstSurf + 1 == lastSurf) - { - numUnmergedSurfaces++; - continue; - } - - numMergedSurfaces++; - } - - // Allocate merged surfaces - s_worldData.mergedSurfaces = ri.Hunk_Alloc(sizeof(*s_worldData.mergedSurfaces) * numMergedSurfaces, h_low); - s_worldData.mergedSurfacesViewCount = ri.Hunk_Alloc(sizeof(*s_worldData.mergedSurfacesViewCount) * numMergedSurfaces, h_low); - s_worldData.mergedSurfacesDlightBits = ri.Hunk_Alloc(sizeof(*s_worldData.mergedSurfacesDlightBits) * numMergedSurfaces, h_low); - s_worldData.mergedSurfacesPshadowBits = ri.Hunk_Alloc(sizeof(*s_worldData.mergedSurfacesPshadowBits) * numMergedSurfaces, h_low); - s_worldData.numMergedSurfaces = numMergedSurfaces; - - // view surfaces are like mark surfaces, except negative ones represent merged surfaces - // -1 represents 0, -2 represents 1, and so on - s_worldData.viewSurfaces = ri.Hunk_Alloc(sizeof(*s_worldData.viewSurfaces) * s_worldData.nummarksurfaces, h_low); - - // actually merge surfaces - mergedSurf = s_worldData.mergedSurfaces; - for(firstSurf = lastSurf = surfacesSorted; firstSurf < surfacesSorted + numSortedSurfaces; firstSurf = lastSurf) - { - srfBspSurface_t *bspSurf, *vaoSurf; - - for ( lastSurf++ ; lastSurf < surfacesSorted + numSortedSurfaces; lastSurf++) - { - int lastSurfLeafIndex, firstSurfLeafIndex; - - if ((*lastSurf)->shader != (*firstSurf)->shader - || (*lastSurf)->fogIndex != (*firstSurf)->fogIndex - || (*lastSurf)->cubemapIndex != (*firstSurf)->cubemapIndex) - break; - - lastSurfLeafIndex = s_worldData.surfacesViewCount[*lastSurf - s_worldData.surfaces]; - firstSurfLeafIndex = s_worldData.surfacesViewCount[*firstSurf - s_worldData.surfaces]; - - if (lastSurfLeafIndex != firstSurfLeafIndex) - break; - } - - // don't merge single surfaces - if (firstSurf + 1 == lastSurf) - continue; - - bspSurf = (srfBspSurface_t *)(*firstSurf)->data; - - vaoSurf = ri.Hunk_Alloc(sizeof(*vaoSurf), h_low); - memset(vaoSurf, 0, sizeof(*vaoSurf)); - vaoSurf->surfaceType = SF_VAO_MESH; - - vaoSurf->vao = bspSurf->vao; - - vaoSurf->firstIndex = bspSurf->firstIndex; - vaoSurf->minIndex = bspSurf->minIndex; - vaoSurf->maxIndex = bspSurf->maxIndex; - - ClearBounds(vaoSurf->cullBounds[0], vaoSurf->cullBounds[1]); - for (currSurf = firstSurf; currSurf < lastSurf; currSurf++) - { - srfBspSurface_t *currBspSurf = (srfBspSurface_t *)(*currSurf)->data; - - vaoSurf->numVerts += currBspSurf->numVerts; - vaoSurf->numIndexes += currBspSurf->numIndexes; - vaoSurf->minIndex = MIN(vaoSurf->minIndex, currBspSurf->minIndex); - vaoSurf->maxIndex = MAX(vaoSurf->maxIndex, currBspSurf->maxIndex); - AddPointToBounds((*currSurf)->cullinfo.bounds[0], vaoSurf->cullBounds[0], vaoSurf->cullBounds[1]); - AddPointToBounds((*currSurf)->cullinfo.bounds[1], vaoSurf->cullBounds[0], vaoSurf->cullBounds[1]); - } - - VectorCopy(vaoSurf->cullBounds[0], mergedSurf->cullinfo.bounds[0]); - VectorCopy(vaoSurf->cullBounds[1], mergedSurf->cullinfo.bounds[1]); - - mergedSurf->cullinfo.type = CULLINFO_BOX; - mergedSurf->data = (surfaceType_t *)vaoSurf; - mergedSurf->fogIndex = (*firstSurf)->fogIndex; - mergedSurf->cubemapIndex = (*firstSurf)->cubemapIndex; - mergedSurf->shader = (*firstSurf)->shader; - - // change surfacesViewCount[] from leaf index to viewSurface index - 1 so we can redirect later - // subtracting 2 (viewSurface index - 1) to avoid collision with -1 (no leaf) - for (currSurf = firstSurf; currSurf < lastSurf; currSurf++) - s_worldData.surfacesViewCount[*currSurf - s_worldData.surfaces] = -((int)(mergedSurf - s_worldData.mergedSurfaces)) - 2; - - mergedSurf++; - } - - // direct viewSurfaces to merged and unmerged surfaces - for (i = 0; i < s_worldData.nummarksurfaces; i++) - { - int viewSurfaceIndex = s_worldData.surfacesViewCount[s_worldData.marksurfaces[i]] + 1; - s_worldData.viewSurfaces[i] = (viewSurfaceIndex < 0) ? viewSurfaceIndex : s_worldData.marksurfaces[i]; - } - - ri.Printf(PRINT_ALL, "Processed %d mergeable surfaces into %d merged, %d unmerged\n", - numSortedSurfaces, numMergedSurfaces, numUnmergedSurfaces); - } - - for (i = 0; i < s_worldData.numWorldSurfaces; i++) - s_worldData.surfacesViewCount[i] = -1; - - ri.Free(surfacesSorted); - - endTime = ri.Milliseconds(); - ri.Printf(PRINT_ALL, "world VAOs calculation time = %5.2f seconds\n", (endTime - startTime) / 1000.0); -} - /* =============== R_LoadSurfaces @@ -3420,9 +2998,6 @@ void RE_LoadWorldMap( const char *name ) { } } - // create static VAOS from the world - R_CreateWorldVaos(); - s_worldData.dataSize = (byte *)ri.Hunk_Alloc(0, h_low) - startMarker; // only set tr.world now that we know the entire level has loaded properly diff --git a/engine/code/renderergl2/tr_cmds.c b/engine/code/renderergl2/tr_cmds.c index 254bb0d7..2aefb4c9 100644 --- a/engine/code/renderergl2/tr_cmds.c +++ b/engine/code/renderergl2/tr_cmds.c @@ -66,8 +66,8 @@ void R_PerformanceCounters( void ) { } else if (r_speeds->integer == 7 ) { - ri.Printf( PRINT_ALL, "VAO draws: static %i dynamic %i\nMultidraws: %i merged %i\n", - backEnd.pc.c_staticVaoDraws, backEnd.pc.c_dynamicVaoDraws, backEnd.pc.c_multidraws, backEnd.pc.c_multidrawsMerged ); + ri.Printf( PRINT_ALL, "VAO draws: static %i dynamic %i\n", + backEnd.pc.c_staticVaoDraws, backEnd.pc.c_dynamicVaoDraws); ri.Printf( PRINT_ALL, "GLSL binds: %i draws: gen %i light %i fog %i dlight %i\n", backEnd.pc.c_glslShaderBinds, backEnd.pc.c_genericDraws, backEnd.pc.c_lightallDraws, backEnd.pc.c_fogDraws, backEnd.pc.c_dlightDraws); } diff --git a/engine/code/renderergl2/tr_dsa.c b/engine/code/renderergl2/tr_dsa.c index 8fde8419..a9d07568 100644 --- a/engine/code/renderergl2/tr_dsa.c +++ b/engine/code/renderergl2/tr_dsa.c @@ -43,7 +43,7 @@ void GL_BindNullTextures() { for (i = 0; i < NUM_TEXTURE_BUNDLES; i++) { - qglBindMultiTextureEXT(GL_TEXTURE0_ARB + i, GL_TEXTURE_2D, 0); + qglBindMultiTextureEXT(GL_TEXTURE0 + i, GL_TEXTURE_2D, 0); glDsaState.textures[i] = 0; } } @@ -51,19 +51,19 @@ void GL_BindNullTextures() { for (i = 0; i < NUM_TEXTURE_BUNDLES; i++) { - qglActiveTextureARB(GL_TEXTURE0_ARB + i); + qglActiveTexture(GL_TEXTURE0 + i); qglBindTexture(GL_TEXTURE_2D, 0); glDsaState.textures[i] = 0; } - qglActiveTextureARB(GL_TEXTURE0_ARB); - glDsaState.texunit = GL_TEXTURE0_ARB; + qglActiveTexture(GL_TEXTURE0); + glDsaState.texunit = GL_TEXTURE0; } } int GL_BindMultiTexture(GLenum texunit, GLenum target, GLuint texture) { - GLuint tmu = texunit - GL_TEXTURE0_ARB; + GLuint tmu = texunit - GL_TEXTURE0; if (glDsaState.textures[tmu] == texture) return 0; @@ -80,7 +80,7 @@ GLvoid APIENTRY GLDSA_BindMultiTextureEXT(GLenum texunit, GLenum target, GLuint { if (glDsaState.texunit != texunit) { - qglActiveTextureARB(texunit); + qglActiveTexture(texunit); glDsaState.texunit = texunit; } @@ -138,7 +138,7 @@ GLvoid APIENTRY GLDSA_CompressedTextureSubImage2DEXT(GLuint texture, GLenum targ GLvoid APIENTRY GLDSA_GenerateTextureMipmapEXT(GLuint texture, GLenum target) { GL_BindMultiTexture(glDsaState.texunit, target, texture); - qglGenerateMipmapEXT(target); + qglGenerateMipmap(target); } void GL_BindNullProgram() @@ -207,9 +207,9 @@ GLvoid APIENTRY GLDSA_ProgramUniformMatrix4fvEXT(GLuint program, GLint location, void GL_BindNullFramebuffers() { - qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + qglBindFramebuffer(GL_FRAMEBUFFER, 0); glDsaState.drawFramebuffer = glDsaState.readFramebuffer = 0; - qglBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0); + qglBindRenderbuffer(GL_RENDERBUFFER, 0); glDsaState.renderbuffer = 0; } @@ -217,26 +217,26 @@ void GL_BindFramebuffer(GLenum target, GLuint framebuffer) { switch (target) { - case GL_FRAMEBUFFER_EXT: + case GL_FRAMEBUFFER: if (framebuffer != glDsaState.drawFramebuffer || framebuffer != glDsaState.readFramebuffer) { - qglBindFramebufferEXT(target, framebuffer); + qglBindFramebuffer(target, framebuffer); glDsaState.drawFramebuffer = glDsaState.readFramebuffer = framebuffer; } break; - case GL_DRAW_FRAMEBUFFER_EXT: + case GL_DRAW_FRAMEBUFFER: if (framebuffer != glDsaState.drawFramebuffer) { - qglBindFramebufferEXT(target, framebuffer); + qglBindFramebuffer(target, framebuffer); glDsaState.drawFramebuffer = framebuffer; } break; - case GL_READ_FRAMEBUFFER_EXT: + case GL_READ_FRAMEBUFFER: if (framebuffer != glDsaState.readFramebuffer) { - qglBindFramebufferEXT(target, framebuffer); + qglBindFramebuffer(target, framebuffer); glDsaState.readFramebuffer = framebuffer; } break; @@ -247,7 +247,7 @@ void GL_BindRenderbuffer(GLuint renderbuffer) { if (renderbuffer != glDsaState.renderbuffer) { - qglBindRenderbufferEXT(GL_RENDERBUFFER_EXT, renderbuffer); + qglBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); glDsaState.renderbuffer = renderbuffer; } } @@ -256,32 +256,32 @@ GLvoid APIENTRY GLDSA_NamedRenderbufferStorageEXT(GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height) { GL_BindRenderbuffer(renderbuffer); - qglRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, internalformat, width, height); + qglRenderbufferStorage(GL_RENDERBUFFER, internalformat, width, height); } GLvoid APIENTRY GLDSA_NamedRenderbufferStorageMultisampleEXT(GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height) { GL_BindRenderbuffer(renderbuffer); - qglRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, samples, internalformat, width, height); + qglRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, internalformat, width, height); } GLenum APIENTRY GLDSA_CheckNamedFramebufferStatusEXT(GLuint framebuffer, GLenum target) { GL_BindFramebuffer(target, framebuffer); - return qglCheckFramebufferStatusEXT(target); + return qglCheckFramebufferStatus(target); } GLvoid APIENTRY GLDSA_NamedFramebufferTexture2DEXT(GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level) { - GL_BindFramebuffer(GL_FRAMEBUFFER_EXT, framebuffer); - qglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, attachment, textarget, texture, level); + GL_BindFramebuffer(GL_FRAMEBUFFER, framebuffer); + qglFramebufferTexture2D(GL_FRAMEBUFFER, attachment, textarget, texture, level); } GLvoid APIENTRY GLDSA_NamedFramebufferRenderbufferEXT(GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) { - GL_BindFramebuffer(GL_FRAMEBUFFER_EXT, framebuffer); - qglFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, attachment, renderbuffertarget, renderbuffer); + GL_BindFramebuffer(GL_FRAMEBUFFER, framebuffer); + qglFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, renderbuffertarget, renderbuffer); } diff --git a/engine/code/renderergl2/tr_extensions.c b/engine/code/renderergl2/tr_extensions.c index 10ae3b85..fdfa63b9 100644 --- a/engine/code/renderergl2/tr_extensions.c +++ b/engine/code/renderergl2/tr_extensions.c @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 2011 James Canete (use.less01@gmail.com) +Copyright (C) 2011 James Canete (use.less01@gmail.com), 2017 Google Inc. This file is part of Quake III Arena source code. @@ -31,38 +31,29 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "tr_dsa.h" #define GLE(ret, name, ...) name##proc * qgl##name; -QGL_1_2_PROCS; QGL_1_3_PROCS; -QGL_1_4_PROCS; QGL_1_5_PROCS; QGL_2_0_PROCS; -QGL_EXT_framebuffer_object_PROCS; -QGL_EXT_framebuffer_blit_PROCS; -QGL_EXT_framebuffer_multisample_PROCS; +QGL_ARB_framebuffer_object_PROCS; QGL_ARB_vertex_array_object_PROCS; QGL_EXT_direct_state_access_PROCS; #undef GLE -static qboolean GLimp_HaveExtension(const char *ext) -{ - const char *ptr = Q_stristr( glConfig.extensions_string, ext ); - if (ptr == NULL) - return qfalse; - ptr += strlen(ext); - return ((*ptr == ' ') || (*ptr == '\0')); // verify it's complete string. -} - void GLimp_InitExtraExtensions() { char *extension; const char* result[3] = { "...ignoring %s\n", "...using %s\n", "...%s not found\n" }; + qboolean q_gl_version_at_least_3_0; + qboolean q_gl_version_at_least_3_2; // Check OpenGL version - sscanf(glConfig.version_string, "%d.%d", &glRefConfig.openglMajorVersion, &glRefConfig.openglMinorVersion); - if (glRefConfig.openglMajorVersion < 2) + if ( !QGL_VERSION_ATLEAST( 2, 0 ) ) ri.Error(ERR_FATAL, "OpenGL 2.0 required!"); ri.Printf(PRINT_ALL, "...using OpenGL %s\n", glConfig.version_string); + q_gl_version_at_least_3_0 = QGL_VERSION_ATLEAST( 3, 0 ); + q_gl_version_at_least_3_2 = QGL_VERSION_ATLEAST( 3, 2 ); + // Check if we need Intel graphics specific fixes. glRefConfig.intelGraphics = qfalse; if (strstr((char *)qglGetString(GL_RENDERER), "Intel")) @@ -74,21 +65,11 @@ void GLimp_InitExtraExtensions() #undef GLE // GL function loader, based on https://gist.github.com/rygorous/16796a0c876cf8a5f542caddb55bce8a -#define GLE(ret, name, ...) qgl##name = (name##proc *) SDL_GL_GetProcAddress("gl" #name); - - // OpenGL 1.2, was GL_EXT_draw_range_elements - QGL_1_2_PROCS; - glRefConfig.drawRangeElements = !!r_ext_draw_range_elements->integer; - ri.Printf(PRINT_ALL, result[glRefConfig.drawRangeElements], "glDrawRangeElements()"); +#define GLE(ret, name, ...) qgl##name = (name##proc *) GLimp_GetProcAddress("gl" #name); // OpenGL 1.3, was GL_ARB_texture_compression QGL_1_3_PROCS; - // OpenGL 1.4, was GL_EXT_multi_draw_arrays - QGL_1_4_PROCS; - glRefConfig.multiDrawArrays = !!r_ext_multi_draw_arrays->integer; - ri.Printf(PRINT_ALL, result[glRefConfig.multiDrawArrays], "glMultiDrawElements()"); - // OpenGL 1.5, was GL_ARB_vertex_buffer_object and GL_ARB_occlusion_query QGL_1_5_PROCS; glRefConfig.occlusionQuery = qtrue; @@ -96,57 +77,57 @@ void GLimp_InitExtraExtensions() // OpenGL 2.0, was GL_ARB_shading_language_100, GL_ARB_vertex_program, GL_ARB_shader_objects, and GL_ARB_vertex_shader QGL_2_0_PROCS; - // Determine GLSL version - if (1) + // OpenGL 3.0 - GL_ARB_framebuffer_object + extension = "GL_ARB_framebuffer_object"; + glRefConfig.framebufferObject = qfalse; + glRefConfig.framebufferBlit = qfalse; + glRefConfig.framebufferMultisample = qfalse; + if (q_gl_version_at_least_3_0 || SDL_GL_ExtensionSupported(extension)) { - char version[256]; - - Q_strncpyz(version, (char *)qglGetString(GL_SHADING_LANGUAGE_VERSION), sizeof(version)); - - sscanf(version, "%d.%d", &glRefConfig.glslMajorVersion, &glRefConfig.glslMinorVersion); - - ri.Printf(PRINT_ALL, "...using GLSL version %s\n", version); - } + glRefConfig.framebufferObject = !!r_ext_framebuffer_object->integer; + glRefConfig.framebufferBlit = qtrue; + glRefConfig.framebufferMultisample = qtrue; - glRefConfig.memInfo = MI_NONE; + qglGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &glRefConfig.maxRenderbufferSize); + qglGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &glRefConfig.maxColorAttachments); - // GL_NVX_gpu_memory_info - extension = "GL_NVX_gpu_memory_info"; - if( GLimp_HaveExtension( extension ) ) - { - glRefConfig.memInfo = MI_NVX; + QGL_ARB_framebuffer_object_PROCS; - ri.Printf(PRINT_ALL, result[1], extension); + ri.Printf(PRINT_ALL, result[glRefConfig.framebufferObject], extension); } else { ri.Printf(PRINT_ALL, result[2], extension); } - // GL_ATI_meminfo - extension = "GL_ATI_meminfo"; - if( GLimp_HaveExtension( extension ) ) + // OpenGL 3.0 - GL_ARB_vertex_array_object + extension = "GL_ARB_vertex_array_object"; + glRefConfig.vertexArrayObject = qfalse; + if (q_gl_version_at_least_3_0 || SDL_GL_ExtensionSupported(extension)) { - if (glRefConfig.memInfo == MI_NONE) + if (q_gl_version_at_least_3_0) { - glRefConfig.memInfo = MI_ATI; - - ri.Printf(PRINT_ALL, result[1], extension); + // force VAO, core context requires it + glRefConfig.vertexArrayObject = qtrue; } else { - ri.Printf(PRINT_ALL, result[0], extension); + glRefConfig.vertexArrayObject = !!r_arb_vertex_array_object->integer; } + + QGL_ARB_vertex_array_object_PROCS; + + ri.Printf(PRINT_ALL, result[glRefConfig.vertexArrayObject], extension); } else { ri.Printf(PRINT_ALL, result[2], extension); } - // GL_ARB_texture_float + // OpenGL 3.0 - GL_ARB_texture_float extension = "GL_ARB_texture_float"; glRefConfig.textureFloat = qfalse; - if( GLimp_HaveExtension( extension ) ) + if (q_gl_version_at_least_3_0 || SDL_GL_ExtensionSupported(extension)) { glRefConfig.textureFloat = !!r_ext_texture_float->integer; @@ -157,51 +138,75 @@ void GLimp_InitExtraExtensions() ri.Printf(PRINT_ALL, result[2], extension); } - // GL_EXT_framebuffer_object - extension = "GL_EXT_framebuffer_object"; - glRefConfig.framebufferObject = qfalse; - if( GLimp_HaveExtension( extension ) ) + // OpenGL 3.2 - GL_ARB_depth_clamp + extension = "GL_ARB_depth_clamp"; + glRefConfig.depthClamp = qfalse; + if (q_gl_version_at_least_3_2 || SDL_GL_ExtensionSupported(extension)) { - glRefConfig.framebufferObject = !!r_ext_framebuffer_object->integer; + glRefConfig.depthClamp = qtrue; - qglGetIntegerv(GL_MAX_RENDERBUFFER_SIZE_EXT, &glRefConfig.maxRenderbufferSize); - qglGetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, &glRefConfig.maxColorAttachments); + ri.Printf(PRINT_ALL, result[glRefConfig.depthClamp], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } - QGL_EXT_framebuffer_object_PROCS; + // OpenGL 3.2 - GL_ARB_seamless_cube_map + extension = "GL_ARB_seamless_cube_map"; + glRefConfig.seamlessCubeMap = qfalse; + if (q_gl_version_at_least_3_2 || SDL_GL_ExtensionSupported(extension)) + { + glRefConfig.seamlessCubeMap = !!r_arb_seamless_cube_map->integer; - ri.Printf(PRINT_ALL, result[glRefConfig.framebufferObject], extension); + ri.Printf(PRINT_ALL, result[glRefConfig.seamlessCubeMap], extension); } else { ri.Printf(PRINT_ALL, result[2], extension); } - // GL_EXT_framebuffer_blit - extension = "GL_EXT_framebuffer_blit"; - glRefConfig.framebufferBlit = qfalse; - if (GLimp_HaveExtension(extension)) + // Determine GLSL version + if (1) { - glRefConfig.framebufferBlit = qtrue; + char version[256]; - QGL_EXT_framebuffer_blit_PROCS; + Q_strncpyz(version, (char *)qglGetString(GL_SHADING_LANGUAGE_VERSION), sizeof(version)); - ri.Printf(PRINT_ALL, result[glRefConfig.framebufferBlit], extension); + sscanf(version, "%d.%d", &glRefConfig.glslMajorVersion, &glRefConfig.glslMinorVersion); + + ri.Printf(PRINT_ALL, "...using GLSL version %s\n", version); + } + + glRefConfig.memInfo = MI_NONE; + + // GL_NVX_gpu_memory_info + extension = "GL_NVX_gpu_memory_info"; + if( SDL_GL_ExtensionSupported( extension ) ) + { + glRefConfig.memInfo = MI_NVX; + + ri.Printf(PRINT_ALL, result[1], extension); } else { ri.Printf(PRINT_ALL, result[2], extension); } - // GL_EXT_framebuffer_multisample - extension = "GL_EXT_framebuffer_multisample"; - glRefConfig.framebufferMultisample = qfalse; - if (GLimp_HaveExtension(extension)) + // GL_ATI_meminfo + extension = "GL_ATI_meminfo"; + if( SDL_GL_ExtensionSupported( extension ) ) { - glRefConfig.framebufferMultisample = qtrue; - - QGL_EXT_framebuffer_multisample_PROCS; + if (glRefConfig.memInfo == MI_NONE) + { + glRefConfig.memInfo = MI_ATI; - ri.Printf(PRINT_ALL, result[glRefConfig.framebufferMultisample], extension); + ri.Printf(PRINT_ALL, result[1], extension); + } + else + { + ri.Printf(PRINT_ALL, result[0], extension); + } } else { @@ -212,7 +217,7 @@ void GLimp_InitExtraExtensions() // GL_ARB_texture_compression_rgtc extension = "GL_ARB_texture_compression_rgtc"; - if (GLimp_HaveExtension(extension)) + if (SDL_GL_ExtensionSupported(extension)) { qboolean useRgtc = r_ext_compressed_textures->integer >= 1; @@ -230,7 +235,7 @@ void GLimp_InitExtraExtensions() // GL_ARB_texture_compression_bptc extension = "GL_ARB_texture_compression_bptc"; - if (GLimp_HaveExtension(extension)) + if (SDL_GL_ExtensionSupported(extension)) { qboolean useBptc = r_ext_compressed_textures->integer >= 2; @@ -244,54 +249,10 @@ void GLimp_InitExtraExtensions() ri.Printf(PRINT_ALL, result[2], extension); } - // GL_ARB_depth_clamp - extension = "GL_ARB_depth_clamp"; - glRefConfig.depthClamp = qfalse; - if( GLimp_HaveExtension( extension ) ) - { - glRefConfig.depthClamp = qtrue; - - ri.Printf(PRINT_ALL, result[glRefConfig.depthClamp], extension); - } - else - { - ri.Printf(PRINT_ALL, result[2], extension); - } - - // GL_ARB_seamless_cube_map - extension = "GL_ARB_seamless_cube_map"; - glRefConfig.seamlessCubeMap = qfalse; - if( GLimp_HaveExtension( extension ) ) - { - glRefConfig.seamlessCubeMap = !!r_arb_seamless_cube_map->integer; - - ri.Printf(PRINT_ALL, result[glRefConfig.seamlessCubeMap], extension); - } - else - { - ri.Printf(PRINT_ALL, result[2], extension); - } - - // GL_ARB_vertex_array_object - extension = "GL_ARB_vertex_array_object"; - glRefConfig.vertexArrayObject = qfalse; - if( GLimp_HaveExtension( extension ) ) - { - glRefConfig.vertexArrayObject = !!r_arb_vertex_array_object->integer; - - QGL_ARB_vertex_array_object_PROCS; - - ri.Printf(PRINT_ALL, result[glRefConfig.vertexArrayObject], extension); - } - else - { - ri.Printf(PRINT_ALL, result[2], extension); - } - // GL_EXT_direct_state_access extension = "GL_EXT_direct_state_access"; glRefConfig.directStateAccess = qfalse; - if (GLimp_HaveExtension(extension)) + if (SDL_GL_ExtensionSupported(extension)) { glRefConfig.directStateAccess = !!r_ext_direct_state_access->integer; diff --git a/engine/code/renderergl2/tr_fbo.c b/engine/code/renderergl2/tr_fbo.c index 6b7ab01a..98c5b503 100644 --- a/engine/code/renderergl2/tr_fbo.c +++ b/engine/code/renderergl2/tr_fbo.c @@ -32,48 +32,38 @@ R_CheckFBO */ qboolean R_CheckFBO(const FBO_t * fbo) { - GLenum code = qglCheckNamedFramebufferStatusEXT(fbo->frameBuffer, GL_FRAMEBUFFER_EXT); + GLenum code = qglCheckNamedFramebufferStatusEXT(fbo->frameBuffer, GL_FRAMEBUFFER); - if(code == GL_FRAMEBUFFER_COMPLETE_EXT) + if(code == GL_FRAMEBUFFER_COMPLETE) return qtrue; // an error occured switch (code) { - case GL_FRAMEBUFFER_UNSUPPORTED_EXT: + case GL_FRAMEBUFFER_UNSUPPORTED: ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Unsupported framebuffer format\n", fbo->name); break; - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete attachment\n", fbo->name); break; - case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete, missing attachment\n", fbo->name); break; - //case GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT_EXT: - // ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete, duplicate attachment\n", fbo->name); - // break; - - case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: - ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete, attached images must have same dimensions\n", - fbo->name); - break; - - case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: - ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete, attached images must have same format\n", - fbo->name); - break; - - case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete, missing draw buffer\n", fbo->name); break; - case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete, missing read buffer\n", fbo->name); break; + case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: + ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete multisample\n", fbo->name); + break; + default: ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) unknown error 0x%X\n", fbo->name, code); break; @@ -117,7 +107,7 @@ FBO_t *FBO_Create(const char *name, int width, int height) fbo->width = width; fbo->height = height; - qglGenFramebuffersEXT(1, &fbo->frameBuffer); + qglGenFramebuffers(1, &fbo->frameBuffer); return fbo; } @@ -145,7 +135,7 @@ void FBO_CreateBuffer(FBO_t *fbo, int format, int index, int multisample) case GL_RGBA32F_ARB: fbo->colorFormat = format; pRenderBuffer = &fbo->colorBuffers[index]; - attachment = GL_COLOR_ATTACHMENT0_EXT + index; + attachment = GL_COLOR_ATTACHMENT0 + index; break; case GL_DEPTH_COMPONENT: @@ -154,21 +144,21 @@ void FBO_CreateBuffer(FBO_t *fbo, int format, int index, int multisample) case GL_DEPTH_COMPONENT32_ARB: fbo->depthFormat = format; pRenderBuffer = &fbo->depthBuffer; - attachment = GL_DEPTH_ATTACHMENT_EXT; + attachment = GL_DEPTH_ATTACHMENT; break; case GL_STENCIL_INDEX: - case GL_STENCIL_INDEX1_EXT: - case GL_STENCIL_INDEX4_EXT: - case GL_STENCIL_INDEX8_EXT: - case GL_STENCIL_INDEX16_EXT: + case GL_STENCIL_INDEX1: + case GL_STENCIL_INDEX4: + case GL_STENCIL_INDEX8: + case GL_STENCIL_INDEX16: fbo->stencilFormat = format; pRenderBuffer = &fbo->stencilBuffer; - attachment = GL_STENCIL_ATTACHMENT_EXT; + attachment = GL_STENCIL_ATTACHMENT; break; - case GL_DEPTH_STENCIL_EXT: - case GL_DEPTH24_STENCIL8_EXT: + case GL_DEPTH_STENCIL: + case GL_DEPTH24_STENCIL8: fbo->packedDepthStencilFormat = format; pRenderBuffer = &fbo->packedDepthStencilBuffer; attachment = 0; // special for stencil and depth @@ -181,7 +171,7 @@ void FBO_CreateBuffer(FBO_t *fbo, int format, int index, int multisample) absent = *pRenderBuffer == 0; if (absent) - qglGenRenderbuffersEXT(1, pRenderBuffer); + qglGenRenderbuffers(1, pRenderBuffer); if (multisample && glRefConfig.framebufferMultisample) qglNamedRenderbufferStorageMultisampleEXT(*pRenderBuffer, multisample, format, fbo->width, fbo->height); @@ -192,12 +182,12 @@ void FBO_CreateBuffer(FBO_t *fbo, int format, int index, int multisample) { if (attachment == 0) { - qglNamedFramebufferRenderbufferEXT(fbo->frameBuffer, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, *pRenderBuffer); - qglNamedFramebufferRenderbufferEXT(fbo->frameBuffer, GL_STENCIL_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, *pRenderBuffer); + qglNamedFramebufferRenderbufferEXT(fbo->frameBuffer, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, *pRenderBuffer); + qglNamedFramebufferRenderbufferEXT(fbo->frameBuffer, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, *pRenderBuffer); } else { - qglNamedFramebufferRenderbufferEXT(fbo->frameBuffer, attachment, GL_RENDERBUFFER_EXT, *pRenderBuffer); + qglNamedFramebufferRenderbufferEXT(fbo->frameBuffer, attachment, GL_RENDERBUFFER, *pRenderBuffer); } } } @@ -217,7 +207,7 @@ void FBO_AttachImage(FBO_t *fbo, image_t *image, GLenum attachment, GLuint cubem target = GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + cubemapside; qglNamedFramebufferTexture2DEXT(fbo->frameBuffer, attachment, target, image->texnum, 0); - index = attachment - GL_COLOR_ATTACHMENT0_EXT; + index = attachment - GL_COLOR_ATTACHMENT0; if (index >= 0 && index <= 15) fbo->colorImage[index] = image; } @@ -245,7 +235,7 @@ void FBO_Bind(FBO_t * fbo) GLimp_LogComment(va("--- FBO_Bind( %s ) ---\n", fbo ? fbo->name : "NULL")); } - GL_BindFramebuffer(GL_FRAMEBUFFER_EXT, fbo ? fbo->frameBuffer : 0); + GL_BindFramebuffer(GL_FRAMEBUFFER, fbo ? fbo->frameBuffer : 0); glState.currentFBO = fbo; } @@ -275,7 +265,7 @@ void FBO_Init(void) hdrFormat = GL_RGBA16F_ARB; if (glRefConfig.framebufferMultisample) - qglGetIntegerv(GL_MAX_SAMPLES_EXT, &multisample); + qglGetIntegerv(GL_MAX_SAMPLES, &multisample); if (r_ext_framebuffer_multisample->integer < multisample) multisample = r_ext_framebuffer_multisample->integer; @@ -292,19 +282,19 @@ void FBO_Init(void) { tr.renderFbo = FBO_Create("_render", tr.renderDepthImage->width, tr.renderDepthImage->height); FBO_CreateBuffer(tr.renderFbo, hdrFormat, 0, multisample); - FBO_CreateBuffer(tr.renderFbo, GL_DEPTH_COMPONENT24_ARB, 0, multisample); + FBO_CreateBuffer(tr.renderFbo, GL_DEPTH_COMPONENT24, 0, multisample); R_CheckFBO(tr.renderFbo); tr.msaaResolveFbo = FBO_Create("_msaaResolve", tr.renderDepthImage->width, tr.renderDepthImage->height); - FBO_AttachImage(tr.msaaResolveFbo, tr.renderImage, GL_COLOR_ATTACHMENT0_EXT, 0); - FBO_AttachImage(tr.msaaResolveFbo, tr.renderDepthImage, GL_DEPTH_ATTACHMENT_EXT, 0); + FBO_AttachImage(tr.msaaResolveFbo, tr.renderImage, GL_COLOR_ATTACHMENT0, 0); + FBO_AttachImage(tr.msaaResolveFbo, tr.renderDepthImage, GL_DEPTH_ATTACHMENT, 0); R_CheckFBO(tr.msaaResolveFbo); } else if (r_hdr->integer) { tr.renderFbo = FBO_Create("_render", tr.renderDepthImage->width, tr.renderDepthImage->height); - FBO_AttachImage(tr.renderFbo, tr.renderImage, GL_COLOR_ATTACHMENT0_EXT, 0); - FBO_AttachImage(tr.renderFbo, tr.renderDepthImage, GL_DEPTH_ATTACHMENT_EXT, 0); + FBO_AttachImage(tr.renderFbo, tr.renderImage, GL_COLOR_ATTACHMENT0, 0); + FBO_AttachImage(tr.renderFbo, tr.renderDepthImage, GL_DEPTH_ATTACHMENT, 0); R_CheckFBO(tr.renderFbo); } @@ -312,34 +302,34 @@ void FBO_Init(void) // this fixes the corrupt screen bug with r_hdr 1 on older hardware if (tr.renderFbo) { - GL_BindFramebuffer(GL_FRAMEBUFFER_EXT, tr.renderFbo->frameBuffer); + GL_BindFramebuffer(GL_FRAMEBUFFER, tr.renderFbo->frameBuffer); qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); } if (tr.screenScratchImage) { tr.screenScratchFbo = FBO_Create("screenScratch", tr.screenScratchImage->width, tr.screenScratchImage->height); - FBO_AttachImage(tr.screenScratchFbo, tr.screenScratchImage, GL_COLOR_ATTACHMENT0_EXT, 0); - FBO_AttachImage(tr.screenScratchFbo, tr.renderDepthImage, GL_DEPTH_ATTACHMENT_EXT, 0); + FBO_AttachImage(tr.screenScratchFbo, tr.screenScratchImage, GL_COLOR_ATTACHMENT0, 0); + FBO_AttachImage(tr.screenScratchFbo, tr.renderDepthImage, GL_DEPTH_ATTACHMENT, 0); R_CheckFBO(tr.screenScratchFbo); } if (tr.sunRaysImage) { tr.sunRaysFbo = FBO_Create("_sunRays", tr.renderDepthImage->width, tr.renderDepthImage->height); - FBO_AttachImage(tr.sunRaysFbo, tr.sunRaysImage, GL_COLOR_ATTACHMENT0_EXT, 0); - FBO_AttachImage(tr.sunRaysFbo, tr.renderDepthImage, GL_DEPTH_ATTACHMENT_EXT, 0); + FBO_AttachImage(tr.sunRaysFbo, tr.sunRaysImage, GL_COLOR_ATTACHMENT0, 0); + FBO_AttachImage(tr.sunRaysFbo, tr.renderDepthImage, GL_DEPTH_ATTACHMENT, 0); R_CheckFBO(tr.sunRaysFbo); } - // FIXME: Don't use separate color/depth buffers for a shadow buffer if (MAX_DRAWN_PSHADOWS && tr.pshadowMaps[0]) { for( i = 0; i < MAX_DRAWN_PSHADOWS; i++) { tr.pshadowFbos[i] = FBO_Create(va("_shadowmap%d", i), tr.pshadowMaps[i]->width, tr.pshadowMaps[i]->height); - FBO_AttachImage(tr.pshadowFbos[i], tr.pshadowMaps[i], GL_COLOR_ATTACHMENT0_EXT, 0); - FBO_CreateBuffer(tr.pshadowFbos[i], GL_DEPTH_COMPONENT24_ARB, 0, 0); + // FIXME: this next line wastes 16mb with 16x512x512 sun shadow maps, skip if OpenGL 4.3+ or ARB_framebuffer_no_attachments + FBO_CreateBuffer(tr.pshadowFbos[i], GL_RGBA8, 0, 0); + FBO_AttachImage(tr.pshadowFbos[i], tr.pshadowMaps[i], GL_DEPTH_ATTACHMENT, 0); R_CheckFBO(tr.pshadowFbos[i]); } } @@ -352,7 +342,7 @@ void FBO_Init(void) // FIXME: this next line wastes 16mb with 4x1024x1024 sun shadow maps, skip if OpenGL 4.3+ or ARB_framebuffer_no_attachments // This at least gets sun shadows working on older GPUs (Intel) FBO_CreateBuffer(tr.sunShadowFbo[i], GL_RGBA8, 0, 0); - FBO_AttachImage(tr.sunShadowFbo[i], tr.sunShadowDepthImage[i], GL_DEPTH_ATTACHMENT_EXT, 0); + FBO_AttachImage(tr.sunShadowFbo[i], tr.sunShadowDepthImage[i], GL_DEPTH_ATTACHMENT, 0); R_CheckFBO(tr.sunShadowFbo[i]); } } @@ -360,7 +350,7 @@ void FBO_Init(void) if (tr.screenShadowImage) { tr.screenShadowFbo = FBO_Create("_screenshadow", tr.screenShadowImage->width, tr.screenShadowImage->height); - FBO_AttachImage(tr.screenShadowFbo, tr.screenShadowImage, GL_COLOR_ATTACHMENT0_EXT, 0); + FBO_AttachImage(tr.screenShadowFbo, tr.screenShadowImage, GL_COLOR_ATTACHMENT0, 0); R_CheckFBO(tr.screenShadowFbo); } @@ -369,7 +359,7 @@ void FBO_Init(void) for (i = 0; i < 2; i++) { tr.textureScratchFbo[i] = FBO_Create(va("_texturescratch%d", i), tr.textureScratchImage[i]->width, tr.textureScratchImage[i]->height); - FBO_AttachImage(tr.textureScratchFbo[i], tr.textureScratchImage[i], GL_COLOR_ATTACHMENT0_EXT, 0); + FBO_AttachImage(tr.textureScratchFbo[i], tr.textureScratchImage[i], GL_COLOR_ATTACHMENT0, 0); R_CheckFBO(tr.textureScratchFbo[i]); } } @@ -377,14 +367,14 @@ void FBO_Init(void) if (tr.calcLevelsImage) { tr.calcLevelsFbo = FBO_Create("_calclevels", tr.calcLevelsImage->width, tr.calcLevelsImage->height); - FBO_AttachImage(tr.calcLevelsFbo, tr.calcLevelsImage, GL_COLOR_ATTACHMENT0_EXT, 0); + FBO_AttachImage(tr.calcLevelsFbo, tr.calcLevelsImage, GL_COLOR_ATTACHMENT0, 0); R_CheckFBO(tr.calcLevelsFbo); } if (tr.targetLevelsImage) { tr.targetLevelsFbo = FBO_Create("_targetlevels", tr.targetLevelsImage->width, tr.targetLevelsImage->height); - FBO_AttachImage(tr.targetLevelsFbo, tr.targetLevelsImage, GL_COLOR_ATTACHMENT0_EXT, 0); + FBO_AttachImage(tr.targetLevelsFbo, tr.targetLevelsImage, GL_COLOR_ATTACHMENT0, 0); R_CheckFBO(tr.targetLevelsFbo); } @@ -393,7 +383,7 @@ void FBO_Init(void) for (i = 0; i < 2; i++) { tr.quarterFbo[i] = FBO_Create(va("_quarter%d", i), tr.quarterImage[i]->width, tr.quarterImage[i]->height); - FBO_AttachImage(tr.quarterFbo[i], tr.quarterImage[i], GL_COLOR_ATTACHMENT0_EXT, 0); + FBO_AttachImage(tr.quarterFbo[i], tr.quarterImage[i], GL_COLOR_ATTACHMENT0, 0); R_CheckFBO(tr.quarterFbo[i]); } } @@ -401,28 +391,28 @@ void FBO_Init(void) if (tr.hdrDepthImage) { tr.hdrDepthFbo = FBO_Create("_hdrDepth", tr.hdrDepthImage->width, tr.hdrDepthImage->height); - FBO_AttachImage(tr.hdrDepthFbo, tr.hdrDepthImage, GL_COLOR_ATTACHMENT0_EXT, 0); + FBO_AttachImage(tr.hdrDepthFbo, tr.hdrDepthImage, GL_COLOR_ATTACHMENT0, 0); R_CheckFBO(tr.hdrDepthFbo); } if (tr.screenSsaoImage) { tr.screenSsaoFbo = FBO_Create("_screenssao", tr.screenSsaoImage->width, tr.screenSsaoImage->height); - FBO_AttachImage(tr.screenSsaoFbo, tr.screenSsaoImage, GL_COLOR_ATTACHMENT0_EXT, 0); + FBO_AttachImage(tr.screenSsaoFbo, tr.screenSsaoImage, GL_COLOR_ATTACHMENT0, 0); R_CheckFBO(tr.screenSsaoFbo); } if (tr.renderCubeImage) { tr.renderCubeFbo = FBO_Create("_renderCubeFbo", tr.renderCubeImage->width, tr.renderCubeImage->height); - FBO_AttachImage(tr.renderCubeFbo, tr.renderCubeImage, GL_COLOR_ATTACHMENT0_EXT, 0); + FBO_AttachImage(tr.renderCubeFbo, tr.renderCubeImage, GL_COLOR_ATTACHMENT0, 0); FBO_CreateBuffer(tr.renderCubeFbo, GL_DEPTH_COMPONENT24_ARB, 0, 0); R_CheckFBO(tr.renderCubeFbo); } GL_CheckErrors(); - GL_BindFramebuffer(GL_FRAMEBUFFER_EXT, 0); + GL_BindFramebuffer(GL_FRAMEBUFFER, 0); glState.currentFBO = NULL; } @@ -450,17 +440,17 @@ void FBO_Shutdown(void) for(j = 0; j < glRefConfig.maxColorAttachments; j++) { if(fbo->colorBuffers[j]) - qglDeleteRenderbuffersEXT(1, &fbo->colorBuffers[j]); + qglDeleteRenderbuffers(1, &fbo->colorBuffers[j]); } if(fbo->depthBuffer) - qglDeleteRenderbuffersEXT(1, &fbo->depthBuffer); + qglDeleteRenderbuffers(1, &fbo->depthBuffer); if(fbo->stencilBuffer) - qglDeleteRenderbuffersEXT(1, &fbo->stencilBuffer); + qglDeleteRenderbuffers(1, &fbo->stencilBuffer); if(fbo->frameBuffer) - qglDeleteFramebuffersEXT(1, &fbo->frameBuffer); + qglDeleteFramebuffers(1, &fbo->frameBuffer); } } @@ -661,12 +651,12 @@ void FBO_FastBlit(FBO_t *src, ivec4_t srcBox, FBO_t *dst, ivec4_t dstBox, int bu VectorSet4(dstBoxFinal, dstBox[0], dstBox[1], dstBox[0] + dstBox[2], dstBox[1] + dstBox[3]); } - GL_BindFramebuffer(GL_READ_FRAMEBUFFER_EXT, srcFb); - GL_BindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, dstFb); - qglBlitFramebufferEXT(srcBoxFinal[0], srcBoxFinal[1], srcBoxFinal[2], srcBoxFinal[3], + GL_BindFramebuffer(GL_READ_FRAMEBUFFER, srcFb); + GL_BindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFb); + qglBlitFramebuffer(srcBoxFinal[0], srcBoxFinal[1], srcBoxFinal[2], srcBoxFinal[3], dstBoxFinal[0], dstBoxFinal[1], dstBoxFinal[2], dstBoxFinal[3], buffers, filter); - GL_BindFramebuffer(GL_FRAMEBUFFER_EXT, 0); + GL_BindFramebuffer(GL_FRAMEBUFFER, 0); glState.currentFBO = NULL; } diff --git a/engine/code/renderergl2/tr_glsl.c b/engine/code/renderergl2/tr_glsl.c index 84f36ec1..70ad8165 100644 --- a/engine/code/renderergl2/tr_glsl.c +++ b/engine/code/renderergl2/tr_glsl.c @@ -146,6 +146,8 @@ static uniformInfo_t uniformsInfo[] = { "u_PrimaryLightRadius", GLSL_FLOAT }, { "u_CubeMapInfo", GLSL_VEC4 }, + + { "u_AlphaTest", GLSL_INT }, }; typedef enum @@ -239,7 +241,10 @@ static void GLSL_GetShaderHeader( GLenum shaderType, const GLchar *extra, char * // HACK: abuse the GLSL preprocessor to turn GLSL 1.20 shaders into 1.30 ones if(glRefConfig.glslMajorVersion > 1 || (glRefConfig.glslMajorVersion == 1 && glRefConfig.glslMinorVersion >= 30)) { - Q_strcat(dest, size, "#version 130\n"); + if (glRefConfig.glslMajorVersion > 1 || (glRefConfig.glslMajorVersion == 1 && glRefConfig.glslMinorVersion >= 50)) + Q_strcat(dest, size, "#version 150\n"); + else + Q_strcat(dest, size, "#version 130\n"); if(shaderType == GL_VERTEX_SHADER) { @@ -252,11 +257,15 @@ static void GLSL_GetShaderHeader( GLenum shaderType, const GLchar *extra, char * Q_strcat(dest, size, "out vec4 out_Color;\n"); Q_strcat(dest, size, "#define gl_FragColor out_Color\n"); + Q_strcat(dest, size, "#define texture2D texture\n"); + Q_strcat(dest, size, "#define textureCubeLod textureLod\n"); + Q_strcat(dest, size, "#define shadow2D texture\n"); } } else { Q_strcat(dest, size, "#version 120\n"); + Q_strcat(dest, size, "#define shadow2D(a,b) shadow2D(a,b).r \n"); } // HACK: add some macros to avoid extra uniforms and save speed and code maintenance @@ -478,20 +487,6 @@ static void GLSL_LinkProgram(GLuint program) } } -static void GLSL_ValidateProgram(GLuint program) -{ - GLint validated; - - qglValidateProgram(program); - - qglGetProgramiv(program, GL_VALIDATE_STATUS, &validated); - if(!validated) - { - GLSL_PrintLog(program, GLSL_PRINTLOG_PROGRAM_INFO, qfalse); - ri.Error(ERR_DROP, "shaders failed to validate"); - } -} - static void GLSL_ShowProgramUniforms(GLuint program) { int i, count, size; @@ -686,7 +681,6 @@ void GLSL_InitUniforms(shaderProgram_t *program) void GLSL_FinishGPUShader(shaderProgram_t *program) { - GLSL_ValidateProgram(program->program); GLSL_ShowProgramUniforms(program->program); GL_CheckErrors(); } @@ -945,7 +939,7 @@ void GLSL_InitGPUShaders(void) attribs = ATTR_POSITION | ATTR_TEXCOORD; - if (!GLSL_InitGPUShader(&tr.textureColorShader, "texturecolor", attribs, qtrue, NULL, qfalse, fallbackShader_texturecolor_vp, fallbackShader_texturecolor_fp)) + if (!GLSL_InitGPUShader(&tr.textureColorShader, "texturecolor", attribs, qtrue, extradefines, qtrue, fallbackShader_texturecolor_vp, fallbackShader_texturecolor_fp)) { ri.Error(ERR_FATAL, "Could not load texturecolor shader!"); } @@ -1015,7 +1009,7 @@ void GLSL_InitGPUShaders(void) if ((i & LIGHTDEF_USE_PARALLAXMAP) && !r_parallaxMapping->integer) continue; - if (!lightType && (i & LIGHTDEF_USE_SHADOWMAP)) + if ((i & LIGHTDEF_USE_SHADOWMAP) && (!lightType || !r_sunlightMode->integer)) continue; attribs = ATTR_POSITION | ATTR_TEXCOORD | ATTR_COLOR | ATTR_NORMAL; diff --git a/engine/code/renderergl2/tr_image.c b/engine/code/renderergl2/tr_image.c index 135616a0..ff32bee9 100644 --- a/engine/code/renderergl2/tr_image.c +++ b/engine/code/renderergl2/tr_image.c @@ -503,11 +503,11 @@ static void RGBAtoNormal(const byte *in, byte *out, int width, int height, qbool if (clampToEdge) { - src_x = CLAMP(src_x, 0, height - 1); + src_x = CLAMP(src_x, 0, width - 1); } else { - src_x = (src_x + height) % height; + src_x = (src_x + width) % width; } s[i++] = *(out + (src_y * width + src_x) * 4 + 3); @@ -1479,7 +1479,7 @@ RawImage_ScaleToPower2 =============== */ -static qboolean RawImage_ScaleToPower2( byte **data, int *inout_width, int *inout_height, imgType_t type, imgFlags_t flags, byte **resampledBuffer) +static qboolean RawImage_ScaleToPower2( byte **data, int *inout_width, int *inout_height, imgType_t type, imgFlags_t flags, byte **resampledBuffer, qboolean capMaxSize ) { int width = *inout_width; int height = *inout_height; @@ -1526,8 +1526,8 @@ static qboolean RawImage_ScaleToPower2( byte **data, int *inout_width, int *inou finalheight >>= 1; } - while ( finalwidth > glConfig.maxTextureSize - || finalheight > glConfig.maxTextureSize ) { + while ( capMaxSize && ( finalwidth > glConfig.maxTextureSize + || finalheight > glConfig.maxTextureSize ) ) { finalwidth >>= 1; finalheight >>= 1; } @@ -1708,7 +1708,7 @@ static GLenum RawImage_GetFormat(const byte *data, int numPixels, GLenum picForm } else if(lightMap) { - if(r_greyscale->integer) + if(r_greyscale->integer || r_monolightmaps->integer) internalFormat = GL_LUMINANCE; else internalFormat = GL_RGBA; @@ -2021,16 +2021,16 @@ Upload32 =============== */ -static void Upload32(byte *data, int x, int y, int width, int height, GLenum picFormat, int numMips, image_t *image, qboolean scaled) +static void Upload32(byte *data, int x, int y, int width, int height, GLenum picFormat, int numMips, image_t *image, qboolean scaled, qboolean lightMap) { int i, c; byte *scan; - imgType_t type = image->type; imgFlags_t flags = image->flags; GLenum internalFormat = image->internalFormat; qboolean rgba8 = picFormat == GL_RGBA8 || picFormat == GL_SRGB8_ALPHA8_EXT; qboolean mipmap = !!(flags & IMGFLAG_MIPMAP) && (rgba8 || numMips > 1); + image->uploadMips = mipmap ? numMips : 1; qboolean cubemap = !!(flags & IMGFLAG_CUBEMAP); // These operations cannot be performed on non-rgba8 images. @@ -2041,7 +2041,7 @@ static void Upload32(byte *data, int x, int y, int width, int height, GLenum pic if (type == IMGTYPE_COLORALPHA) { - if( r_greyscale->integer ) + if( r_greyscale->integer || ( lightMap && r_monolightmaps->integer ) ) { for ( i = 0; i < c; i++ ) { @@ -2051,14 +2051,15 @@ static void Upload32(byte *data, int x, int y, int width, int height, GLenum pic scan[i*4 + 2] = luma; } } - else if( r_greyscale->value ) + else if( r_greyscale->value || ( lightMap && r_monolightmaps->value ) ) { + float scl = r_greyscale->value ? r_greyscale->value : r_monolightmaps->value; for ( i = 0; i < c; i++ ) { float luma = LUMA(scan[i*4], scan[i*4 + 1], scan[i*4 + 2]); - scan[i*4] = LERP(scan[i*4], luma, r_greyscale->value); - scan[i*4 + 1] = LERP(scan[i*4 + 1], luma, r_greyscale->value); - scan[i*4 + 2] = LERP(scan[i*4 + 2], luma, r_greyscale->value); + scan[i*4] = LERP(scan[i*4], luma, scl); + scan[i*4 + 1] = LERP(scan[i*4 + 1], luma, scl); + scan[i*4 + 2] = LERP(scan[i*4 + 2], luma, scl); } } @@ -2094,14 +2095,7 @@ static void Upload32(byte *data, int x, int y, int width, int height, GLenum pic } -/* -================ -R_CreateImage2 - -This is the only way any image_t are created -================ -*/ -image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLenum picFormat, int numMips, imgType_t type, imgFlags_t flags, int internalFormat ) { +static image_t *R_CreateImage2Internal( const char *name, byte *pic, int width, int height, GLenum picFormat, int numMips, imgType_t type, imgFlags_t flags, int internalFormat, qboolean capMaxSize ) { byte *resampledBuffer = NULL; image_t *image; qboolean isLightmap = qfalse, scaled = qfalse; @@ -2127,7 +2121,7 @@ image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLe } image = tr.images[tr.numImages] = ri.Hunk_Alloc( sizeof( image_t ), h_low ); - image->texnum = 1024 + tr.numImages; + qglGenTextures(1, &image->texnum); tr.numImages++; image->type = type; @@ -2152,7 +2146,7 @@ image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLe if (!cubemap) { if (rgba8) - scaled = RawImage_ScaleToPower2(&pic, &width, &height, type, flags, &resampledBuffer); + scaled = RawImage_ScaleToPower2(&pic, &width, &height, type, flags, &resampledBuffer, capMaxSize); else if (pic && picmip) { for (miplevel = r_picmip->integer; miplevel > 0 && numMips > 1; miplevel--, numMips--) @@ -2196,7 +2190,7 @@ image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLe // Upload data. if (pic) - Upload32(pic, 0, 0, width, height, picFormat, numMips, image, scaled); + Upload32(pic, 0, 0, width, height, picFormat, numMips, image, scaled, isLightmap); if (resampledBuffer != NULL) ri.Hunk_FreeTempMemory(resampledBuffer); @@ -2240,6 +2234,18 @@ image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLe } +/* +================ +R_CreateImage2 + +This is the only way any image_t are created +================ +*/ +image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLenum picFormat, int numMips, imgType_t type, imgFlags_t flags, int internalFormat ) { + return R_CreateImage2Internal( name, pic, width, height, picFormat, numMips, type, flags, internalFormat, qtrue ); +} + + /* ================ R_CreateImage @@ -2255,7 +2261,7 @@ image_t *R_CreateImage(const char *name, byte *pic, int width, int height, imgTy void R_UpdateSubImage( image_t *image, byte *pic, int x, int y, int width, int height, GLenum picFormat ) { - Upload32(pic, x, y, width, height, picFormat, 0, image, qfalse); + Upload32(pic, x, y, width, height, picFormat, 0, image, qfalse, qfalse); } //=================================================================== @@ -2394,13 +2400,16 @@ Returns NULL if it fails, not a default image. image_t *R_FindImageFile( const char *name, imgType_t type, imgFlags_t flags ) { DeepmindContext* ctx = dmlab_context(); + char new_name[MAX_QPATH]; + const char *file_name = name; image_t *image; - int width, height; - byte *pic; + int width = 0, height = 0; + byte *pic = NULL; GLenum picFormat; int picNumMips; long hash; imgFlags_t checkFlagsTrue, checkFlagsFalse; + qboolean capMaxSize = qtrue; if (!name) { return NULL; @@ -2423,16 +2432,27 @@ image_t *R_FindImageFile( const char *name, imgType_t type, imgFlags_t flags ) } } + if ( ctx->hooks.replace_texture_name( ctx->userdata, name, new_name, sizeof( new_name ) ) ) { + file_name = new_name; + } // - // load the pic from disk + // Load the pic from disk or from hooks. // - R_LoadImage( name, &pic, &width, &height, &picFormat, &picNumMips ); + if ( !ctx->hooks.load_texture( ctx->userdata, file_name, &pic, &width, &height, ri.Malloc ) ) { + R_LoadImage( file_name, &pic, &width, &height, &picFormat, &picNumMips ); + } else { + capMaxSize = qfalse; + } + if ( pic == NULL ) { return NULL; } // Allow modification of a loaded texture. - ctx->hooks.modify_rgba_texture( ctx->userdata, name, pic, width, height ); + if ( ctx->hooks.modify_rgba_texture( ctx->userdata, name, pic, width, height ) ) { + picNumMips = 1; + capMaxSize = qfalse; + } checkFlagsTrue = IMGFLAG_PICMIP | IMGFLAG_MIPMAP | IMGFLAG_GENNORMALMAP; checkFlagsFalse = IMGFLAG_CUBEMAP; @@ -2535,8 +2555,8 @@ image_t *R_FindImageFile( const char *name, imgType_t type, imgFlags_t flags ) } #endif - R_CreateImage( normalName, normalPic, normalWidth, normalHeight, IMGTYPE_NORMAL, normalFlags, 0 ); - ri.Free( normalPic ); + R_CreateImage2Internal( normalName, normalPic, normalWidth, normalHeight, GL_RGBA8, 0, IMGTYPE_NORMAL, normalFlags, 0, capMaxSize ); + ri.Free( normalPic ); } } @@ -2554,7 +2574,7 @@ image_t *R_FindImageFile( const char *name, imgType_t type, imgFlags_t flags ) flags &= ~IMGFLAG_MIPMAP; } - image = R_CreateImage2( ( char * ) name, pic, width, height, picFormat, picNumMips, type, flags, 0 ); + image = R_CreateImage2Internal( ( char * ) name, pic, width, height, picFormat, picNumMips, type, flags, 0, capMaxSize ); ri.Free( pic ); return image; } @@ -2777,13 +2797,13 @@ void R_CreateBuiltinImages( void ) { tr.screenScratchImage = R_CreateImage("screenScratch", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, rgbFormat); if (r_shadowBlur->integer || r_ssao->integer) - tr.hdrDepthImage = R_CreateImage("*hdrDepth", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_INTENSITY32F_ARB); + tr.hdrDepthImage = R_CreateImage("*hdrDepth", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_R32F); if (r_drawSunRays->integer) tr.sunRaysImage = R_CreateImage("*sunRays", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, rgbFormat); - tr.renderDepthImage = R_CreateImage("*renderdepth", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_DEPTH_COMPONENT24_ARB); - tr.textureDepthImage = R_CreateImage("*texturedepth", NULL, PSHADOW_MAP_SIZE, PSHADOW_MAP_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_DEPTH_COMPONENT24_ARB); + tr.renderDepthImage = R_CreateImage("*renderdepth", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_DEPTH_COMPONENT24); + tr.textureDepthImage = R_CreateImage("*texturedepth", NULL, PSHADOW_MAP_SIZE, PSHADOW_MAP_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_DEPTH_COMPONENT24); { void *p; @@ -2813,19 +2833,18 @@ void R_CreateBuiltinImages( void ) { tr.screenSsaoImage = R_CreateImage("*screenSsao", NULL, width / 2, height / 2, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8); } - if (r_shadows->integer == 4) + for( x = 0; x < MAX_DRAWN_PSHADOWS; x++) { - for( x = 0; x < MAX_DRAWN_PSHADOWS; x++) - { - tr.pshadowMaps[x] = R_CreateImage(va("*shadowmap%i", x), NULL, PSHADOW_MAP_SIZE, PSHADOW_MAP_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8); - } + tr.pshadowMaps[x] = R_CreateImage(va("*shadowmap%i", x), NULL, PSHADOW_MAP_SIZE, PSHADOW_MAP_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_DEPTH_COMPONENT24); + //qglTextureParameterfEXT(tr.pshadowMaps[x]->texnum, GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); + //qglTextureParameterfEXT(tr.pshadowMaps[x]->texnum, GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); } if (r_sunlightMode->integer) { for ( x = 0; x < 4; x++) { - tr.sunShadowDepthImage[x] = R_CreateImage(va("*sunshadowdepth%i", x), NULL, r_shadowMapSize->integer, r_shadowMapSize->integer, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_DEPTH_COMPONENT24_ARB); + tr.sunShadowDepthImage[x] = R_CreateImage(va("*sunshadowdepth%i", x), NULL, r_shadowMapSize->integer, r_shadowMapSize->integer, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_DEPTH_COMPONENT24); qglTextureParameterfEXT(tr.sunShadowDepthImage[x]->texnum, GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); qglTextureParameterfEXT(tr.sunShadowDepthImage[x]->texnum, GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); } @@ -3065,6 +3084,7 @@ RE_RegisterSkin =============== */ qhandle_t RE_RegisterSkin( const char *name ) { + skinSurface_t parseSurfaces[MAX_SKIN_SURFACES]; qhandle_t hSkin; skin_t *skin; skinSurface_t *surf; @@ -3075,6 +3095,7 @@ qhandle_t RE_RegisterSkin( const char *name ) { char *text_p; char *token; char surfName[MAX_QPATH]; + int totalSurfaces; if ( !name || !name[0] ) { ri.Printf( PRINT_DEVELOPER, "Empty name passed to RE_RegisterSkin\n" ); @@ -3114,8 +3135,8 @@ qhandle_t RE_RegisterSkin( const char *name ) { // If not a .skin file, load as a single shader if ( strcmp( name + strlen( name ) - 5, ".skin" ) ) { skin->numSurfaces = 1; - skin->surfaces[0] = ri.Hunk_Alloc( sizeof( *skin->surfaces[0] ), h_low ); - skin->surfaces[0]->shader = R_FindShader( name, LIGHTMAP_NONE, qtrue ); + skin->surfaces = ri.Hunk_Alloc( sizeof( skinSurface_t ), h_low ); + skin->surfaces[0].shader = R_FindShader( name, LIGHTMAP_NONE, qtrue ); return hSkin; } @@ -3125,6 +3146,7 @@ qhandle_t RE_RegisterSkin( const char *name ) { return 0; } + totalSurfaces = 0; text_p = text.c; while ( text_p && *text_p ) { // get surface name @@ -3148,25 +3170,32 @@ qhandle_t RE_RegisterSkin( const char *name ) { // parse the shader name token = CommaParse( &text_p ); - if ( skin->numSurfaces >= MD3_MAX_SURFACES ) { - ri.Printf( PRINT_WARNING, "WARNING: Ignoring surfaces in '%s', the max is %d surfaces!\n", name, MD3_MAX_SURFACES ); - break; + if ( skin->numSurfaces < MAX_SKIN_SURFACES ) { + surf = &parseSurfaces[skin->numSurfaces]; + Q_strncpyz( surf->name, surfName, sizeof( surf->name ) ); + surf->shader = R_FindShader( token, LIGHTMAP_NONE, qtrue ); + skin->numSurfaces++; } - surf = skin->surfaces[ skin->numSurfaces ] = ri.Hunk_Alloc( sizeof( *skin->surfaces[0] ), h_low ); - Q_strncpyz( surf->name, surfName, sizeof( surf->name ) ); - surf->shader = R_FindShader( token, LIGHTMAP_NONE, qtrue ); - skin->numSurfaces++; + totalSurfaces++; } ri.FS_FreeFile( text.v ); + if ( totalSurfaces > MAX_SKIN_SURFACES ) { + ri.Printf( PRINT_WARNING, "WARNING: Ignoring excess surfaces (found %d, max is %d) in skin '%s'!\n", + totalSurfaces, MAX_SKIN_SURFACES, name ); + } // never let a skin have 0 shaders if ( skin->numSurfaces == 0 ) { return 0; // use default skin } + // copy surfaces to skin + skin->surfaces = ri.Hunk_Alloc( skin->numSurfaces * sizeof( skinSurface_t ), h_low ); + memcpy( skin->surfaces, parseSurfaces, skin->numSurfaces * sizeof( skinSurface_t ) ); + return hSkin; } @@ -3185,8 +3214,8 @@ void R_InitSkins( void ) { skin = tr.skins[0] = ri.Hunk_Alloc( sizeof( skin_t ), h_low ); Q_strncpyz( skin->name, "", sizeof( skin->name ) ); skin->numSurfaces = 1; - skin->surfaces[0] = ri.Hunk_Alloc( sizeof( *skin->surfaces[0] ), h_low ); - skin->surfaces[0]->shader = tr.defaultShader; + skin->surfaces = ri.Hunk_Alloc( sizeof( skinSurface_t ), h_low ); + skin->surfaces[0].shader = tr.defaultShader; } /* @@ -3215,13 +3244,35 @@ void R_SkinList_f( void ) { for ( i = 0 ; i < tr.numSkins ; i++ ) { skin = tr.skins[i]; - ri.Printf( PRINT_ALL, "%3i:%s\n", i, skin->name ); + ri.Printf( PRINT_ALL, "%3i:%s (%d surfaces)\n", i, skin->name, skin->numSurfaces ); for ( j = 0 ; j < skin->numSurfaces ; j++ ) { ri.Printf( PRINT_ALL, " %s = %s\n", - skin->surfaces[j]->name, skin->surfaces[j]->shader->name ); + skin->surfaces[j].name, skin->surfaces[j].shader->name ); } } ri.Printf (PRINT_ALL, "------------------\n"); } +/* +=============== +Api_UpdateTexture +=============== +*/ +bool dmlab_update_rgba_texture( const char* name, int width, int height, const unsigned char* data ) { + image_t* image; + long hash = generateHashValue(name); + for (image=hashTable[hash]; image; image=image->next) { + if ( !strcmp( name, image->imgName ) ) { + qglBindTexture( GL_TEXTURE_2D, image->texnum ); + Upload32( (unsigned char*)data, 0, 0, width, height, + image->internalFormat, + image->uploadMips, + image, + qfalse, + qfalse ); + return true; + } + } + return false; +} diff --git a/engine/code/renderergl2/tr_init.c b/engine/code/renderergl2/tr_init.c index 7e41302c..85f336f5 100644 --- a/engine/code/renderergl2/tr_init.c +++ b/engine/code/renderergl2/tr_init.c @@ -65,6 +65,7 @@ cvar_t *r_stereoEnabled; cvar_t *r_anaglyphMode; cvar_t *r_greyscale; +cvar_t *r_monolightmaps; cvar_t *r_ignorehwgamma; cvar_t *r_measureOverdraw; @@ -98,8 +99,6 @@ cvar_t *r_ext_texture_env_add; cvar_t *r_ext_texture_filter_anisotropic; cvar_t *r_ext_max_anisotropy; -cvar_t *r_ext_draw_range_elements; -cvar_t *r_ext_multi_draw_arrays; cvar_t *r_ext_framebuffer_object; cvar_t *r_ext_texture_float; cvar_t *r_ext_framebuffer_multisample; @@ -107,9 +106,6 @@ cvar_t *r_arb_seamless_cube_map; cvar_t *r_arb_vertex_array_object; cvar_t *r_ext_direct_state_access; -cvar_t *r_mergeMultidraws; -cvar_t *r_mergeLeafSurfaces; - cvar_t *r_cameraExposure; cvar_t *r_externalGLSL; @@ -264,7 +260,7 @@ static void InitOpenGL( void ) { GLint temp; - GLimp_Init(); + GLimp_Init( qtrue ); GLimp_InitExtraExtensions(); strcpy( renderer_buffer, glConfig.renderer_string ); @@ -948,14 +944,11 @@ void GL_SetDefaultState( void ) qglCullFace(GL_FRONT); - qglColor4f (1,1,1,1); - GL_BindNullTextures(); if (glRefConfig.framebufferObject) GL_BindNullFramebuffers(); - qglEnable(GL_TEXTURE_2D); GL_TextureMode( r_textureMode->string ); //qglShadeModel( GL_SMOOTH ); @@ -1039,7 +1032,21 @@ void GfxInfo_f( void ) ri.Printf( PRINT_ALL, "GL_RENDERER: %s\n", glConfig.renderer_string ); ri.Printf( PRINT_ALL, "GL_VERSION: %s\n", glConfig.version_string ); ri.Printf( PRINT_ALL, "GL_EXTENSIONS: " ); - R_PrintLongString( glConfig.extensions_string ); + if ( qglGetStringi ) + { + GLint numExtensions; + int i; + + qglGetIntegerv( GL_NUM_EXTENSIONS, &numExtensions ); + for ( i = 0; i < numExtensions; i++ ) + { + ri.Printf( PRINT_ALL, "%s ", qglGetStringi( GL_EXTENSIONS, i ) ); + } + } + else + { + R_PrintLongString( glConfig.extensions_string ); + } ri.Printf( PRINT_ALL, "\n" ); ri.Printf( PRINT_ALL, "GL_MAX_TEXTURE_SIZE: %d\n", glConfig.maxTextureSize ); ri.Printf( PRINT_ALL, "GL_MAX_TEXTURE_UNITS_ARB: %d\n", glConfig.numTextureUnits ); @@ -1065,7 +1072,6 @@ void GfxInfo_f( void ) ri.Printf( PRINT_ALL, "texturemode: %s\n", r_textureMode->string ); ri.Printf( PRINT_ALL, "picmip: %d\n", r_picmip->integer ); ri.Printf( PRINT_ALL, "texture bits: %d\n", r_texturebits->integer ); - ri.Printf( PRINT_ALL, "multitexture: %s\n", enablestrings[qglActiveTextureARB != 0] ); ri.Printf( PRINT_ALL, "compiled vertex arrays: %s\n", enablestrings[qglLockArraysEXT != 0 ] ); ri.Printf( PRINT_ALL, "texenv add: %s\n", enablestrings[glConfig.textureEnvAddAvailable != 0] ); ri.Printf( PRINT_ALL, "compressed textures: %s\n", enablestrings[glConfig.textureCompression!=TC_NONE] ); @@ -1158,8 +1164,6 @@ void R_Register( void ) r_ext_compiled_vertex_array = ri.Cvar_Get( "r_ext_compiled_vertex_array", "1", CVAR_ARCHIVE | CVAR_LATCH); r_ext_texture_env_add = ri.Cvar_Get( "r_ext_texture_env_add", "1", CVAR_ARCHIVE | CVAR_LATCH); - r_ext_draw_range_elements = ri.Cvar_Get( "r_ext_draw_range_elements", "1", CVAR_ARCHIVE | CVAR_LATCH); - r_ext_multi_draw_arrays = ri.Cvar_Get( "r_ext_multi_draw_arrays", "1", CVAR_ARCHIVE | CVAR_LATCH); r_ext_framebuffer_object = ri.Cvar_Get( "r_ext_framebuffer_object", "1", CVAR_ARCHIVE | CVAR_LATCH); r_ext_texture_float = ri.Cvar_Get( "r_ext_texture_float", "1", CVAR_ARCHIVE | CVAR_LATCH); r_ext_framebuffer_multisample = ri.Cvar_Get( "r_ext_framebuffer_multisample", "0", CVAR_ARCHIVE | CVAR_LATCH); @@ -1215,7 +1219,7 @@ void R_Register( void ) r_forceAutoExposureMin = ri.Cvar_Get( "r_forceAutoExposureMin", "-2.0", CVAR_CHEAT ); r_forceAutoExposureMax = ri.Cvar_Get( "r_forceAutoExposureMax", "2.0", CVAR_CHEAT ); - r_cameraExposure = ri.Cvar_Get( "r_cameraExposure", "0", CVAR_CHEAT ); + r_cameraExposure = ri.Cvar_Get( "r_cameraExposure", "1", CVAR_CHEAT ); r_depthPrepass = ri.Cvar_Get( "r_depthPrepass", "1", CVAR_ARCHIVE ); r_ssao = ri.Cvar_Get( "r_ssao", "0", CVAR_LATCH | CVAR_ARCHIVE ); @@ -1297,8 +1301,6 @@ void R_Register( void ) r_directedScale = ri.Cvar_Get( "r_directedScale", "1", CVAR_CHEAT ); r_anaglyphMode = ri.Cvar_Get("r_anaglyphMode", "0", CVAR_ARCHIVE); - r_mergeMultidraws = ri.Cvar_Get("r_mergeMultidraws", "1", CVAR_ARCHIVE); - r_mergeLeafSurfaces = ri.Cvar_Get("r_mergeLeafSurfaces", "1", CVAR_ARCHIVE); // // temporary variables that can change at any time @@ -1353,6 +1355,8 @@ void R_Register( void ) r_maxpolys = ri.Cvar_Get( "r_maxpolys", va("%d", MAX_POLYS), 0); r_maxpolyverts = ri.Cvar_Get( "r_maxpolyverts", va("%d", MAX_POLYVERTS), 0); + r_monolightmaps = ri.Cvar_Get("r_monolightmaps", "0", CVAR_ARCHIVE | CVAR_LATCH); + // make sure all the commands added here are also // removed in R_Shutdown ri.Cmd_AddCommand( "imagelist", R_ImageList_f ); @@ -1500,16 +1504,15 @@ void RE_Shutdown( qboolean destroyWindow ) { ri.Printf( PRINT_ALL, "RE_Shutdown( %i )\n", destroyWindow ); - ri.Cmd_RemoveCommand ("modellist"); - ri.Cmd_RemoveCommand ("screenshotJPEG"); - ri.Cmd_RemoveCommand ("screenshot"); - ri.Cmd_RemoveCommand ("imagelist"); - ri.Cmd_RemoveCommand ("shaderlist"); - ri.Cmd_RemoveCommand ("skinlist"); - ri.Cmd_RemoveCommand ("gfxinfo"); - ri.Cmd_RemoveCommand("minimize"); + ri.Cmd_RemoveCommand( "imagelist" ); + ri.Cmd_RemoveCommand( "shaderlist" ); + ri.Cmd_RemoveCommand( "skinlist" ); + ri.Cmd_RemoveCommand( "modellist" ); ri.Cmd_RemoveCommand( "modelist" ); - ri.Cmd_RemoveCommand( "shaderstate" ); + ri.Cmd_RemoveCommand( "screenshot" ); + ri.Cmd_RemoveCommand( "screenshotJPEG" ); + ri.Cmd_RemoveCommand( "gfxinfo" ); + ri.Cmd_RemoveCommand( "minimize" ); ri.Cmd_RemoveCommand( "gfxmeminfo" ); ri.Cmd_RemoveCommand( "exportCubemaps" ); diff --git a/engine/code/renderergl2/tr_light.c b/engine/code/renderergl2/tr_light.c index cf4c0b5b..f1886268 100644 --- a/engine/code/renderergl2/tr_light.c +++ b/engine/code/renderergl2/tr_light.c @@ -99,7 +99,6 @@ void R_DlightBmodel( bmodel_t *bmodel ) { case SF_FACE: case SF_GRID: case SF_TRIANGLES: - case SF_VAO_MESH: ((srfBspSurface_t *)surf->data)->dlightBits = mask; break; diff --git a/engine/code/renderergl2/tr_local.h b/engine/code/renderergl2/tr_local.h index e6781b73..ec5675b5 100644 --- a/engine/code/renderergl2/tr_local.h +++ b/engine/code/renderergl2/tr_local.h @@ -471,37 +471,14 @@ typedef struct shader_s { void (*optimalStageIteratorFunc)( void ); - float clampTime; // time this shader is clamped to - float timeOffset; // current time offset for this shader + double clampTime; // time this shader is clamped to + double timeOffset; // current time offset for this shader struct shader_s *remappedShader; // current shader this one is remapped too struct shader_s *next; } shader_t; -static ID_INLINE qboolean ShaderRequiresCPUDeforms(const shader_t * shader) -{ - if(shader->numDeforms) - { - const deformStage_t *ds = &shader->deforms[0]; - - if (shader->numDeforms > 1) - return qtrue; - - switch (ds->deformation) - { - case DEFORM_WAVE: - case DEFORM_BULGE: - return qfalse; - - default: - return qtrue; - } - } - - return qfalse; -} - enum { ATTR_INDEX_POSITION = 0, @@ -694,6 +671,8 @@ typedef enum UNIFORM_CUBEMAPINFO, + UNIFORM_ALPHATEST, + UNIFORM_COUNT } uniform_t; @@ -731,7 +710,7 @@ typedef struct { byte areamask[MAX_MAP_AREA_BYTES]; qboolean areamaskModified; // qtrue if areamask changed since last scene - float floatTime; // tr.refdef.time / 1000.0 + double floatTime; // tr.refdef.time / 1000.0 float blurFactor; @@ -766,6 +745,12 @@ typedef struct { //================================================================================= +// max surfaces per-skin +// This is an arbitry limit. Vanilla Q3 only supported 32 surfaces in skins but failed to +// enforce the maximum limit when reading skin files. It was possile to use more than 32 +// surfaces which accessed out of bounds memory past end of skin->surfaces hunk block. +#define MAX_SKIN_SURFACES 256 + // skins allow models to be retextured without modifying the model file typedef struct { char name[MAX_QPATH]; @@ -775,7 +760,7 @@ typedef struct { typedef struct skin_s { char name[MAX_QPATH]; // game path, including extension int numSurfaces; - skinSurface_t *surfaces[MD3_MAX_SURFACES]; + skinSurface_t *surfaces; // dynamically allocated array of surfaces } skin_t; @@ -850,7 +835,6 @@ typedef enum { SF_IQM, SF_FLARE, SF_ENTITY, // beams, rails, lightning, etc that can be determined by entity - SF_VAO_MESH, SF_VAO_MDVMESH, SF_NUM_SURFACE_TYPES, @@ -903,7 +887,7 @@ typedef struct #define srfVert_t_cleared(x) srfVert_t (x) = {{0, 0, 0}, {0, 0}, {0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}} -// srfBspSurface_t covers SF_GRID, SF_TRIANGLES, SF_POLY, and SF_VAO_MESH +// srfBspSurface_t covers SF_GRID, SF_TRIANGLES, and SF_POLY typedef struct srfBspSurface_s { surfaceType_t surfaceType; @@ -925,15 +909,6 @@ typedef struct srfBspSurface_s // vertexes int numVerts; srfVert_t *verts; - - // BSP VBO offsets - int firstVert; - int firstIndex; - glIndex_t minIndex; - glIndex_t maxIndex; - - // static render data - vao_t *vao; // SF_GRID specific variables after here @@ -1005,8 +980,6 @@ typedef struct srfVaoMdvMesh_s // backEnd stats int numIndexes; int numVerts; - glIndex_t minIndex; - glIndex_t maxIndex; // static render data vao_t *vao; @@ -1135,15 +1108,8 @@ typedef struct { int *surfacesDlightBits; int *surfacesPshadowBits; - int numMergedSurfaces; - msurface_t *mergedSurfaces; - int *mergedSurfacesViewCount; - int *mergedSurfacesDlightBits; - int *mergedSurfacesPshadowBits; - int nummarksurfaces; int *marksurfaces; - int *viewSurfaces; int numfogs; fog_t *fogs; @@ -1370,13 +1336,8 @@ typedef enum { // We can't change glConfig_t without breaking DLL/vms compatibility, so // store extensions we have here. typedef struct { - int openglMajorVersion; - int openglMinorVersion; - qboolean intelGraphics; - qboolean drawRangeElements; - qboolean multiDrawArrays; qboolean occlusionQuery; int glslMajorVersion; @@ -1415,9 +1376,6 @@ typedef struct { int c_staticVaoDraws; int c_dynamicVaoDraws; - int c_multidraws; - int c_multidrawsMerged; - int c_dlightVertexes; int c_dlightIndexes; @@ -1689,8 +1647,6 @@ extern cvar_t *r_showcluster; extern cvar_t *r_gamma; extern cvar_t *r_displayRefresh; // optional display refresh option -extern cvar_t *r_ext_draw_range_elements; -extern cvar_t *r_ext_multi_draw_arrays; extern cvar_t *r_ext_framebuffer_object; extern cvar_t *r_ext_texture_float; extern cvar_t *r_ext_framebuffer_multisample; @@ -1734,9 +1690,6 @@ extern cvar_t *r_skipBackEnd; extern cvar_t *r_anaglyphMode; -extern cvar_t *r_mergeMultidraws; -extern cvar_t *r_mergeLeafSurfaces; - extern cvar_t *r_externalGLSL; extern cvar_t *r_hdr; @@ -1810,6 +1763,34 @@ extern cvar_t *r_printShaders; extern cvar_t *r_marksOnTriangleMeshes; +extern cvar_t *r_monolightmaps; + +//==================================================================== + +static ID_INLINE qboolean ShaderRequiresCPUDeforms(const shader_t * shader) +{ + if(shader->numDeforms) + { + const deformStage_t *ds = &shader->deforms[0]; + + if (shader->numDeforms > 1) + return qtrue; + + switch (ds->deformation) + { + case DEFORM_WAVE: + case DEFORM_BULGE: + // need CPU deforms at high level-times to avoid floating point percision loss + return ( backEnd.refdef.floatTime != (float)backEnd.refdef.floatTime ); + + default: + return qtrue; + } + } + + return qfalse; +} + //==================================================================== void R_SwapBuffers( int ); @@ -1977,8 +1958,6 @@ typedef struct stageVars vec2_t texcoords[NUM_TEXTURE_BUNDLES][SHADER_MAX_VERTEXES]; } stageVars_t; -#define MAX_MULTIDRAW_PRIMITIVES 256 - typedef struct shaderCommands_s { glIndex_t indexes[SHADER_MAX_INDEXES] QALIGN(16); @@ -1994,13 +1973,14 @@ typedef struct shaderCommands_s void *attribPointers[ATTR_INDEX_COUNT]; vao_t *vao; qboolean useInternalVao; + qboolean useCacheVao; stageVars_t svars QALIGN(16); //color4ub_t constantColor255[SHADER_MAX_VERTEXES] QALIGN(16); shader_t *shader; - float shaderTime; + double shaderTime; int fogNum; int cubemapIndex; @@ -2010,14 +1990,6 @@ typedef struct shaderCommands_s int firstIndex; int numIndexes; int numVertexes; - glIndex_t minIndex; - glIndex_t maxIndex; - - int multiDrawPrimitives; - GLsizei multiDrawNumIndexes[MAX_MULTIDRAW_PRIMITIVES]; - glIndex_t *multiDrawFirstIndex[MAX_MULTIDRAW_PRIMITIVES]; - glIndex_t multiDrawMinIndex[MAX_MULTIDRAW_PRIMITIVES]; - glIndex_t multiDrawMaxIndex[MAX_MULTIDRAW_PRIMITIVES]; // info extracted from current shader int numPasses; @@ -2032,7 +2004,7 @@ void RB_EndSurface(void); void RB_CheckOverflow( int verts, int indexes ); #define RB_CHECKOVERFLOW(v,i) if (tess.numVertexes + (v) >= SHADER_MAX_VERTEXES || tess.numIndexes + (i) >= SHADER_MAX_INDEXES ) {RB_CheckOverflow(v,i);} -void R_DrawElementsVao( int numIndexes, glIndex_t firstIndex, glIndex_t minIndex, glIndex_t maxIndex ); +void R_DrawElements( int numIndexes, glIndex_t firstIndex ); void RB_StageIteratorGeneric( void ); void RB_StageIteratorSky( void ); void RB_StageIteratorVertexLitTexture( void ); @@ -2171,6 +2143,14 @@ void R_VaoList_f(void); void RB_UpdateTessVao(unsigned int attribBits); +void VaoCache_Commit(void); +void VaoCache_Init(void); +void VaoCache_BindVao(void); +void VaoCache_CheckAdd(qboolean *endSurface, qboolean *recycleVertexBuffer, qboolean *recycleIndexBuffer, int numVerts, int numIndexes); +void VaoCache_RecycleVertexBuffer(void); +void VaoCache_RecycleIndexBuffer(void); +void VaoCache_InitQueue(void); +void VaoCache_AddSurface(srfVert_t *verts, int numVerts, glIndex_t *indexes, int numIndexes); /* ============================================================ diff --git a/engine/code/renderergl2/tr_main.c b/engine/code/renderergl2/tr_main.c index cc33c719..02f7d489 100644 --- a/engine/code/renderergl2/tr_main.c +++ b/engine/code/renderergl2/tr_main.c @@ -1715,6 +1715,9 @@ Visualization aid for movement clipping debugging ==================== */ void R_DebugGraphics( void ) { + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return; + } if ( !r_debugSurface->integer ) { return; } @@ -2098,7 +2101,7 @@ void R_RenderPshadowMaps(const refdef_t *fd) if (glRefConfig.framebufferObject) shadowParms.targetFbo = tr.pshadowFbos[i]; - shadowParms.flags = VPF_SHADOWMAP | VPF_DEPTHSHADOW | VPF_NOVIEWMODEL; + shadowParms.flags = VPF_DEPTHSHADOW | VPF_NOVIEWMODEL; shadowParms.zFar = shadow->lightRadius; VectorCopy(shadow->lightOrigin, shadowParms.or.origin); diff --git a/engine/code/renderergl2/tr_mesh.c b/engine/code/renderergl2/tr_mesh.c index 0d6844a4..4f2c6724 100644 --- a/engine/code/renderergl2/tr_mesh.c +++ b/engine/code/renderergl2/tr_mesh.c @@ -365,8 +365,8 @@ void R_AddMD3Surfaces( trRefEntity_t *ent ) { shader = tr.defaultShader; for ( j = 0 ; j < skin->numSurfaces ; j++ ) { // the names have both been lowercased - if ( !strcmp( skin->surfaces[j]->name, surface->name ) ) { - shader = skin->surfaces[j]->shader; + if ( !strcmp( skin->surfaces[j].name, surface->name ) ) { + shader = skin->surfaces[j].shader; break; } } @@ -376,21 +376,34 @@ void R_AddMD3Surfaces( trRefEntity_t *ent ) { else if (shader->defaultShader) { ri.Printf( PRINT_DEVELOPER, "WARNING: shader %s in skin %s not found\n", shader->name, skin->name); } - //} else if ( surface->numShaders <= 0 ) { - //shader = tr.defaultShader; + } else if ( surface->numShaderIndexes <= 0 ) { + shader = tr.defaultShader; } else { - //md3Shader = (md3Shader_t *) ( (byte *)surface + surface->ofsShaders ); - //md3Shader += ent->e.skinNum % surface->numShaders; - //shader = tr.shaders[ md3Shader->shaderIndex ]; shader = tr.shaders[ surface->shaderIndexes[ ent->e.skinNum % surface->numShaderIndexes ] ]; } - // don't add third_person objects if not viewing through a portal - if(!personalModel) - { - srfVaoMdvMesh_t *vaoSurface = &model->vaoSurfaces[i]; + // we will add shadows even if the main object isn't visible in the view + + // stencil shadows can't do personal models unless I polyhedron clip + if ( !personalModel + && r_shadows->integer == 2 + && fogNum == 0 + && !(ent->e.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) ) + && shader->sort == SS_OPAQUE ) { + R_AddDrawSurf( (void *)&model->vaoSurfaces[i], tr.shadowShader, 0, qfalse, qfalse, 0 ); + } + + // projection shadows work fine with personal models + if ( r_shadows->integer == 3 + && fogNum == 0 + && (ent->e.renderfx & RF_SHADOW_PLANE ) + && shader->sort == SS_OPAQUE ) { + R_AddDrawSurf( (void *)&model->vaoSurfaces[i], tr.projectionShadowShader, 0, qfalse, qfalse, 0 ); + } - R_AddDrawSurf((void *)vaoSurface, shader, fogNum, qfalse, qfalse, cubemapIndex ); + // don't add third_person objects if not viewing through a portal + if ( !personalModel ) { + R_AddDrawSurf((void *)&model->vaoSurfaces[i], shader, fogNum, qfalse, qfalse, cubemapIndex ); } surface++; diff --git a/engine/code/renderergl2/tr_model.c b/engine/code/renderergl2/tr_model.c index 1fa907a9..014a5f6d 100644 --- a/engine/code/renderergl2/tr_model.c +++ b/engine/code/renderergl2/tr_model.c @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 1999-2005 Id Software, Inc., 2016 Google Inc. This file is part of Quake III Arena source code. @@ -21,12 +21,13 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ // tr_models.c -- model loading and caching +#include "../deepmind/context.h" #include "tr_local.h" #define LL(x) x=LittleLong(x) -static qboolean R_LoadMD3(model_t *mod, int lod, void *buffer, int bufferSize, const char *modName); -static qboolean R_LoadMDR(model_t *mod, void *buffer, int filesize, const char *name ); +static qboolean R_LoadMD3(model_t *mod, int lod, void *buffer, int bufferSize, const char *mod_name, const char *shaderPrefix); +static qboolean R_LoadMDR(model_t *mod, void *buffer, int filesize, const char *name, const char *shaderPrefix); /* ==================== @@ -44,12 +45,16 @@ qhandle_t R_RegisterMD3(const char *name, model_t *mod) int ident; qboolean loaded = qfalse; int numLoaded; - char filename[MAX_QPATH], namebuf[MAX_QPATH+20]; + char filename[MAX_QPATH], namebuf[MAX_QPATH+20], shaderPrefix[MAX_QPATH] = ""; char *fext, defex[] = "md3"; + DeepmindContext *ctx = dmlab_context(); numLoaded = 0; - strcpy(filename, name); + if ( !ctx->hooks.replace_model_name( ctx->userdata, name, filename, sizeof(filename), shaderPrefix, sizeof(shaderPrefix) ) ) { + strcpy(filename, name); + } + fext = strchr(filename, '.'); if(!fext) @@ -73,7 +78,7 @@ qhandle_t R_RegisterMD3(const char *name, model_t *mod) ident = LittleLong(* (unsigned *) buf.u); if (ident == MD3_IDENT) - loaded = R_LoadMD3(mod, lod, buf.u, size, name); + loaded = R_LoadMD3(mod, lod, buf.u, size, name, shaderPrefix); else ri.Printf(PRINT_WARNING,"R_RegisterMD3: unknown fileid for %s\n", name); @@ -124,7 +129,14 @@ qhandle_t R_RegisterMDR(const char *name, model_t *mod) qboolean loaded = qfalse; int filesize; - filesize = ri.FS_ReadFile(name, (void **) &buf.v); + char filename[MAX_QPATH], shaderPrefix[MAX_QPATH] = ""; + DeepmindContext *ctx = dmlab_context(); + + if ( !ctx->hooks.replace_model_name( ctx->userdata, name, filename, sizeof(filename), shaderPrefix, sizeof(shaderPrefix) ) ) { + strcpy(filename, name); + } + + filesize = ri.FS_ReadFile(filename, (void **) &buf.v); if(!buf.u) { mod->type = MOD_BAD; @@ -133,7 +145,7 @@ qhandle_t R_RegisterMDR(const char *name, model_t *mod) ident = LittleLong(*(unsigned *)buf.u); if(ident == MDR_IDENT) - loaded = R_LoadMDR(mod, buf.u, filesize, name); + loaded = R_LoadMDR(mod, buf.u, filesize, name, shaderPrefix); ri.FS_FreeFile (buf.v); @@ -182,6 +194,50 @@ qhandle_t R_RegisterIQM(const char *name, model_t *mod) return mod->index; } +/* +==================== +R_RegisterDMLab +==================== +*/ +qhandle_t R_RegisterDMLab(const char *name, model_t *mod) +{ + int lod; + char namebuf[MAX_QPATH + 20]; + md3Header_t *mod_md3; + qboolean loaded = qfalse; + + for ( lod = 0 ; lod < MD3_MAX_LODS ; ++lod ) + { + if ( lod != 0 ) + Com_sprintf(namebuf, sizeof(namebuf), "%s_%d", name, lod); + else + Com_sprintf(namebuf, sizeof(namebuf), "%s", name); + + if ( R_DMLabToMD3( name, &mod_md3 ) ) + { + loaded = R_LoadMD3( mod, lod, (byte *)mod_md3, mod_md3->ofsEnd, name, "" ); + ri.Free( mod_md3 ); + } + + if ( loaded ) + mod->numLods++; + else + break; + } + + if ( lod > 0 ) + { + mod->type = MOD_MESH; + return mod->index; + } + +#ifdef _DEBUG + ri.Printf(PRINT_WARNING, "R_RegisterDMLab: couldn't load %s\n", name); +#endif + + mod->type = MOD_BAD; + return 0; +} typedef struct { @@ -362,6 +418,12 @@ qhandle_t RE_RegisterModel( const char *name ) { } } + // As a fallback, attempt to load a DeepMind Lab custom model. + if( !hModel ) + { + hModel = R_RegisterDMLab( name, mod ); + } + return hModel; } @@ -370,7 +432,7 @@ qhandle_t RE_RegisterModel( const char *name ) { R_LoadMD3 ================= */ -static qboolean R_LoadMD3(model_t * mod, int lod, void *buffer, int bufferSize, const char *modName) +static qboolean R_LoadMD3(model_t * mod, int lod, void *buffer, int bufferSize, const char *modName, const char *shaderPrefix) { int f, i, j; @@ -393,8 +455,11 @@ static qboolean R_LoadMD3(model_t * mod, int lod, void *buffer, int bufferSize, mdvTag_t *tag; mdvTagName_t *tagName; - int version; - int size; + int version; + int size; + char shader_name[MAX_QPATH + 64]; + int shaderPrefix_length = strlen(shaderPrefix); + Com_Memcpy(shader_name, shaderPrefix, shaderPrefix_length + 1); md3Model = (md3Header_t *) buffer; @@ -531,8 +596,8 @@ static qboolean R_LoadMD3(model_t * mod, int lod, void *buffer, int bufferSize, for(j = 0; j < md3Surf->numShaders; j++, shaderIndex++, md3Shader++) { shader_t *sh; - - sh = R_FindShader(md3Shader->name, LIGHTMAP_NONE, qtrue); + Com_Memcpy(shader_name + shaderPrefix_length, md3Shader->name, sizeof(md3Shader->name)); + sh = R_FindShader(shader_name, LIGHTMAP_NONE, qtrue); if(sh->defaultShader) { *shaderIndex = 0; @@ -764,9 +829,6 @@ static qboolean R_LoadMD3(model_t * mod, int lod, void *buffer, int bufferSize, vaoSurf->numIndexes = surf->numIndexes; vaoSurf->numVerts = surf->numVerts; - vaoSurf->minIndex = 0; - vaoSurf->maxIndex = surf->numVerts - 1; - vaoSurf->vao = R_CreateVao(va("staticMD3Mesh_VAO '%s'", surf->name), data, dataSize, (byte *)surf->indexes, surf->numIndexes * sizeof(*surf->indexes), VAO_USAGE_STATIC); vaoSurf->vao->attribs[ATTR_INDEX_POSITION].enabled = 1; @@ -824,7 +886,7 @@ static qboolean R_LoadMD3(model_t * mod, int lod, void *buffer, int bufferSize, R_LoadMDR ================= */ -static qboolean R_LoadMDR( model_t *mod, void *buffer, int filesize, const char *mod_name ) +static qboolean R_LoadMDR( model_t *mod, void *buffer, int filesize, const char *mod_name, const char *shaderPrefix) { int i, j, k, l; mdrHeader_t *pinmodel, *mdr; @@ -837,6 +899,9 @@ static qboolean R_LoadMDR( model_t *mod, void *buffer, int filesize, const char mdrTag_t *tag, *curtag; int size; shader_t *sh; + char shader_name[MAX_QPATH + 64]; + int shaderPrefix_length = strlen(shaderPrefix); + Com_Memcpy(shader_name, shaderPrefix, shaderPrefix_length + 1); pinmodel = (mdrHeader_t *)buffer; @@ -1040,7 +1105,9 @@ static qboolean R_LoadMDR( model_t *mod, void *buffer, int filesize, const char Q_strlwr( surf->name ); // register the shaders - sh = R_FindShader(surf->shader, LIGHTMAP_NONE, qtrue); + Com_Memcpy( shader_name + shaderPrefix_length, surf->shader, sizeof(surf->shader) ); + sh = R_FindShader(shader_name, LIGHTMAP_NONE, qtrue); + if ( sh->defaultShader ) { surf->shaderIndex = 0; } else { diff --git a/engine/code/renderergl2/tr_model_iqm.c b/engine/code/renderergl2/tr_model_iqm.c index ce6d3610..caa4308b 100644 --- a/engine/code/renderergl2/tr_model_iqm.c +++ b/engine/code/renderergl2/tr_model_iqm.c @@ -849,7 +849,8 @@ void R_AddIQMSurfaces( trRefEntity_t *ent ) { surface = data->surfaces; // don't add third_person objects if not in a portal - personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal; + personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !(tr.viewParms.isPortal + || (tr.viewParms.flags & (VPF_SHADOWMAP | VPF_DEPTHSHADOW))); if ( ent->e.renderfx & RF_WRAP_FRAMES ) { ent->e.frame %= data->num_frames; @@ -906,9 +907,9 @@ void R_AddIQMSurfaces( trRefEntity_t *ent ) { for(j = 0; j < skin->numSurfaces; j++) { - if (!strcmp(skin->surfaces[j]->name, surface->name)) + if (!strcmp(skin->surfaces[j].name, surface->name)) { - shader = skin->surfaces[j]->shader; + shader = skin->surfaces[j].shader; break; } } diff --git a/engine/code/renderergl2/tr_postprocess.c b/engine/code/renderergl2/tr_postprocess.c index ade9ebf3..9931757b 100644 --- a/engine/code/renderergl2/tr_postprocess.c +++ b/engine/code/renderergl2/tr_postprocess.c @@ -82,7 +82,7 @@ void RB_ToneMap(FBO_t *hdrFbo, ivec4_t hdrBox, FBO_t *ldrFbo, ivec4_t ldrBox, in // tonemap color[0] = color[1] = - color[2] = pow(2, r_cameraExposure->value); //exp2(r_cameraExposure->value); + color[2] = pow(2, r_cameraExposure->value - autoExposure); //exp2(r_cameraExposure->value); color[3] = 1.0f; if (autoExposure) diff --git a/engine/code/renderergl2/tr_scene.c b/engine/code/renderergl2/tr_scene.c index 2d039e7c..ff0a22e2 100644 --- a/engine/code/renderergl2/tr_scene.c +++ b/engine/code/renderergl2/tr_scene.c @@ -400,7 +400,7 @@ void RE_BeginScene(const refdef_t *fd) // derived info - tr.refdef.floatTime = tr.refdef.time * 0.001f; + tr.refdef.floatTime = tr.refdef.time * 0.001; tr.refdef.numDrawSurfs = r_firstSceneDrawSurf; tr.refdef.drawSurfs = backEndData->drawSurfs; @@ -491,7 +491,7 @@ void RE_RenderScene( const refdef_t *fd ) { // playing with even more shadows if(glRefConfig.framebufferObject && r_sunlightMode->integer && !( fd->rdflags & RDF_NOWORLDMODEL ) && (r_forceSun->integer || tr.sunShadows)) { - if (r_shadowCascadeZFar != 0) + if (r_shadowCascadeZFar->integer != 0) { R_RenderSunShadowMaps(fd, 0); R_RenderSunShadowMaps(fd, 1); diff --git a/engine/code/renderergl2/tr_shade.c b/engine/code/renderergl2/tr_shade.c index fd88191e..03662472 100644 --- a/engine/code/renderergl2/tr_shade.c +++ b/engine/code/renderergl2/tr_shade.c @@ -41,42 +41,9 @@ R_DrawElements ================== */ -void R_DrawElementsVao( int numIndexes, glIndex_t firstIndex, glIndex_t minIndex, glIndex_t maxIndex ) +void R_DrawElements( int numIndexes, glIndex_t firstIndex) { - if (glRefConfig.drawRangeElements) - qglDrawRangeElements(GL_TRIANGLES, minIndex, maxIndex, numIndexes, GL_INDEX_TYPE, BUFFER_OFFSET(firstIndex * sizeof(glIndex_t))); - else - qglDrawElements(GL_TRIANGLES, numIndexes, GL_INDEX_TYPE, BUFFER_OFFSET(firstIndex * sizeof(glIndex_t))); - -} - - -static void R_DrawMultiElementsVao( int multiDrawPrimitives, glIndex_t *multiDrawMinIndex, glIndex_t *multiDrawMaxIndex, - GLsizei *multiDrawNumIndexes, glIndex_t **multiDrawFirstIndex) -{ - if (glRefConfig.multiDrawArrays && multiDrawPrimitives > 1) - { - qglMultiDrawElements(GL_TRIANGLES, multiDrawNumIndexes, GL_INDEX_TYPE, (const GLvoid **)multiDrawFirstIndex, multiDrawPrimitives); - } - else - { - int i; - - if (glRefConfig.drawRangeElements) - { - for (i = 0; i < multiDrawPrimitives; i++) - { - qglDrawRangeElements(GL_TRIANGLES, multiDrawMinIndex[i], multiDrawMaxIndex[i], multiDrawNumIndexes[i], GL_INDEX_TYPE, multiDrawFirstIndex[i]); - } - } - else - { - for (i = 0; i < multiDrawPrimitives; i++) - { - qglDrawElements(GL_TRIANGLES, multiDrawNumIndexes[i], GL_INDEX_TYPE, multiDrawFirstIndex[i]); - } - } - } + qglDrawElements(GL_TRIANGLES, numIndexes, GL_INDEX_TYPE, BUFFER_OFFSET(firstIndex * sizeof(glIndex_t))); } @@ -98,7 +65,7 @@ R_BindAnimatedImageToTMU ================= */ static void R_BindAnimatedImageToTMU( textureBundle_t *bundle, int tmu ) { - int index; + int64_t index; if ( bundle->isVideoMap ) { ri.CIN_RunCinematic(bundle->videoMapHandle); @@ -114,13 +81,18 @@ static void R_BindAnimatedImageToTMU( textureBundle_t *bundle, int tmu ) { // it is necessary to do this messy calc to make sure animations line up // exactly with waveforms of the same frequency - index = ri.ftol(tess.shaderTime * bundle->imageAnimationSpeed * FUNCTABLE_SIZE); + index = tess.shaderTime * bundle->imageAnimationSpeed * FUNCTABLE_SIZE; index >>= FUNCTABLE_SIZE2; if ( index < 0 ) { index = 0; // may happen with shader time offsets } - index %= bundle->numImageAnimations; + + // Windows x86 doesn't load renderer DLL with 64 bit modulus + //index %= bundle->numImageAnimations; + while ( index >= bundle->numImageAnimations ) { + index -= bundle->numImageAnimations; + } GL_BindToTMU( bundle->image[ index ], tmu ); } @@ -148,15 +120,9 @@ static void DrawTris (shaderCommands_t *input) { GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); VectorSet4(color, 1, 1, 1, 1); GLSL_SetUniformVec4(sp, UNIFORM_COLOR, color); + GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 0); - if (input->multiDrawPrimitives) - { - R_DrawMultiElementsVao(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex); - } - else - { - R_DrawElementsVao(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex); - } + R_DrawElements(input->numIndexes, input->firstIndex); } qglDepthRange( 0, 1 ); @@ -190,7 +156,6 @@ void RB_BeginSurface( shader_t *shader, int fogNum, int cubemapIndex ) { tess.numIndexes = 0; tess.firstIndex = 0; tess.numVertexes = 0; - tess.multiDrawPrimitives = 0; tess.shader = state; tess.fogNum = fogNum; tess.cubemapIndex = cubemapIndex; @@ -200,6 +165,7 @@ void RB_BeginSurface( shader_t *shader, int fogNum, int cubemapIndex ) { tess.numPasses = state->numUnfoggedPasses; tess.currentStageIteratorFunc = state->optimalStageIteratorFunc; tess.useInternalVao = qtrue; + tess.useCacheVao = qfalse; tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; if (tess.shader->clampTime && tess.shaderTime >= tess.shader->clampTime) { @@ -423,15 +389,9 @@ static void ProjectDlightTexture( void ) { GL_State( GLS_ATEST_GT_0 | GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); } - if (tess.multiDrawPrimitives) - { - shaderCommands_t *input = &tess; - R_DrawMultiElementsVao(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex); - } - else - { - R_DrawElementsVao(tess.numIndexes, tess.firstIndex, tess.minIndex, tess.maxIndex); - } + GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 1); + + R_DrawElements(tess.numIndexes, tess.firstIndex); backEnd.pc.c_totalIndexes += tess.numIndexes; backEnd.pc.c_dlightIndexes += tess.numIndexes; @@ -794,6 +754,7 @@ static void ForwardDlight( void ) { // include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light // where they aren't rendered GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 0); GLSL_SetUniformMat4(sp, UNIFORM_MODELMATRIX, backEnd.or.transformMatrix); @@ -844,14 +805,7 @@ static void ForwardDlight( void ) { // draw // - if (input->multiDrawPrimitives) - { - R_DrawMultiElementsVao(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex); - } - else - { - R_DrawElementsVao(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex); - } + R_DrawElements(input->numIndexes, input->firstIndex); backEnd.pc.c_totalIndexes += tess.numIndexes; backEnd.pc.c_dlightIndexes += tess.numIndexes; @@ -913,6 +867,7 @@ static void ProjectPshadowVBOGLSL( void ) { // include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light // where they aren't rendered GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_EQUAL ); + GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 0); GL_BindToTMU( tr.pshadowMaps[l], TB_DIFFUSEMAP ); @@ -920,14 +875,7 @@ static void ProjectPshadowVBOGLSL( void ) { // draw // - if (input->multiDrawPrimitives) - { - R_DrawMultiElementsVao(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex); - } - else - { - R_DrawElementsVao(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex); - } + R_DrawElements(input->numIndexes, input->firstIndex); backEnd.pc.c_totalIndexes += tess.numIndexes; //backEnd.pc.c_dlightIndexes += tess.numIndexes; @@ -1001,16 +949,9 @@ static void RB_FogPass( void ) { } else { GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); } + GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 0); - if (tess.multiDrawPrimitives) - { - shaderCommands_t *input = &tess; - R_DrawMultiElementsVao(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex); - } - else - { - R_DrawElementsVao(tess.numIndexes, tess.firstIndex, tess.minIndex, tess.maxIndex); - } + R_DrawElements(tess.numIndexes, tess.firstIndex); } @@ -1151,6 +1092,23 @@ static void RB_IterateStagesGeneric( shaderCommands_t *input ) } GL_State( pStage->stateBits ); + if ((pStage->stateBits & GLS_ATEST_BITS) == GLS_ATEST_GT_0) + { + GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 1); + } + else if ((pStage->stateBits & GLS_ATEST_BITS) == GLS_ATEST_LT_80) + { + GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 2); + } + else if ((pStage->stateBits & GLS_ATEST_BITS) == GLS_ATEST_GE_80) + { + GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 3); + } + else + { + GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 0); + } + { vec4_t baseColor; @@ -1391,14 +1349,7 @@ static void RB_IterateStagesGeneric( shaderCommands_t *input ) // // draw // - if (input->multiDrawPrimitives) - { - R_DrawMultiElementsVao(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex); - } - else - { - R_DrawElementsVao(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex); - } + R_DrawElements(input->numIndexes, input->firstIndex); // allow skipping out to show just lightmaps during development if ( r_lightmap->integer && ( pStage->bundle[0].isLightmap || pStage->bundle[1].isLightmap ) ) @@ -1445,6 +1396,7 @@ static void RB_RenderShadowmap( shaderCommands_t *input ) GLSL_SetUniformFloat(sp, UNIFORM_LIGHTRADIUS, backEnd.viewParms.zFar); GL_State( 0 ); + GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 0); // // do multitexture @@ -1455,14 +1407,7 @@ static void RB_RenderShadowmap( shaderCommands_t *input ) // draw // - if (input->multiDrawPrimitives) - { - R_DrawMultiElementsVao(input->multiDrawPrimitives, input->multiDrawMinIndex, input->multiDrawMaxIndex, input->multiDrawNumIndexes, input->multiDrawFirstIndex); - } - else - { - R_DrawElementsVao(input->numIndexes, input->firstIndex, input->minIndex, input->maxIndex); - } + R_DrawElements(input->numIndexes, input->firstIndex); } } } @@ -1598,7 +1543,7 @@ void RB_StageIteratorGeneric( void ) // // now do any dynamic lighting needed // - if ( tess.dlightBits && tess.shader->sort <= SS_OPAQUE + if ( tess.dlightBits && tess.shader->sort <= SS_OPAQUE && r_lightmap->integer == 0 && !(tess.shader->surfaceFlags & (SURF_NODLIGHT | SURF_SKY) ) ) { if (tess.shader->numUnfoggedPasses == 1 && tess.xstages[0]->glslShaderGroup == tr.lightallShader && (tess.xstages[0]->glslShaderIndex & LIGHTDEF_LIGHTTYPE_MASK) && r_dlightMode->integer) @@ -1627,7 +1572,6 @@ void RB_StageIteratorGeneric( void ) } } - /* ** RB_EndSurface */ @@ -1657,6 +1601,12 @@ void RB_EndSurface( void ) { return; } + if (tess.useCacheVao) + { + // upload indexes now + VaoCache_Commit(); + } + // // update performance counters // @@ -1683,7 +1633,6 @@ void RB_EndSurface( void ) { tess.numIndexes = 0; tess.numVertexes = 0; tess.firstIndex = 0; - tess.multiDrawPrimitives = 0; GLimp_LogComment( "----------\n" ); } diff --git a/engine/code/renderergl2/tr_shade_calc.c b/engine/code/renderergl2/tr_shade_calc.c index cb7bcf74..8369ad74 100644 --- a/engine/code/renderergl2/tr_shade_calc.c +++ b/engine/code/renderergl2/tr_shade_calc.c @@ -27,7 +27,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #endif -#define WAVEVALUE( table, base, amplitude, phase, freq ) ((base) + table[ ri.ftol( ( ( (phase) + tess.shaderTime * (freq) ) * FUNCTABLE_SIZE ) ) & FUNCTABLE_MASK ] * (amplitude)) +#define WAVEVALUE( table, base, amplitude, phase, freq ) ((base) + table[ ( (int64_t) ( ( (phase) + tess.shaderTime * (freq) ) * FUNCTABLE_SIZE ) ) & FUNCTABLE_MASK ] * (amplitude)) static float *TableForFunc( genFunc_t func ) { @@ -204,12 +204,12 @@ void RB_CalcBulgeVertexes( deformStage_t *ds ) { const float *st = ( const float * ) tess.texCoords[0]; float *xyz = ( float * ) tess.xyz; int16_t *normal = tess.normal[0]; - float now; + double now; - now = backEnd.refdef.time * ds->bulgeSpeed * 0.001f; + now = backEnd.refdef.time * 0.001 * ds->bulgeSpeed; for ( i = 0; i < tess.numVertexes; i++, xyz += 4, st += 2, normal += 4 ) { - int off; + int64_t off; float scale; vec3_t fNormal; @@ -776,8 +776,8 @@ void RB_CalcScaleTexMatrix( const float scale[2], float *matrix ) */ void RB_CalcScrollTexMatrix( const float scrollSpeed[2], float *matrix ) { - float timeScale = tess.shaderTime; - float adjustedScrollS, adjustedScrollT; + double timeScale = tess.shaderTime; + double adjustedScrollS, adjustedScrollT; adjustedScrollS = scrollSpeed[0] * timeScale; adjustedScrollT = scrollSpeed[1] * timeScale; @@ -805,9 +805,9 @@ void RB_CalcTransformTexMatrix( const texModInfo_t *tmi, float *matrix ) */ void RB_CalcRotateTexMatrix( float degsPerSecond, float *matrix ) { - float timeScale = tess.shaderTime; - float degs; - int index; + double timeScale = tess.shaderTime; + double degs; + int64_t index; float sinValue, cosValue; degs = -degsPerSecond * timeScale; diff --git a/engine/code/renderergl2/tr_shader.c b/engine/code/renderergl2/tr_shader.c index 574cfb15..d9c86279 100644 --- a/engine/code/renderergl2/tr_shader.c +++ b/engine/code/renderergl2/tr_shader.c @@ -744,6 +744,8 @@ static qboolean ParseStage( shaderStage_t *stage, char **text ) // else if ( !Q_stricmp( token, "animMap" ) ) { + int totalImages = 0; + token = COM_ParseExt( text, qfalse ); if ( !token[0] ) { @@ -778,6 +780,12 @@ static qboolean ParseStage( shaderStage_t *stage, char **text ) } stage->bundle[0].numImageAnimations++; } + totalImages++; + } + + if ( totalImages > MAX_IMAGE_ANIMATIONS ) { + ri.Printf( PRINT_WARNING, "WARNING: ignoring excess images for 'animMap' (found %d, max is %d) in shader '%s'\n", + totalImages, MAX_IMAGE_ANIMATIONS, shader.name ); } } else if ( !Q_stricmp( token, "videoMap" ) ) @@ -792,6 +800,8 @@ static qboolean ParseStage( shaderStage_t *stage, char **text ) if (stage->bundle[0].videoMapHandle != -1) { stage->bundle[0].isVideoMap = qtrue; stage->bundle[0].image[0] = tr.scratchImage[stage->bundle[0].videoMapHandle]; + } else { + ri.Printf( PRINT_WARNING, "WARNING: could not load '%s' for 'videoMap' keyword in shader '%s'\n", token, shader.name ); } } // @@ -2245,7 +2255,7 @@ static void CollapseStagesToLightall(shaderStage_t *diffuse, { char normalName[MAX_QPATH]; image_t *normalImg; - imgFlags_t normalFlags = (diffuseImg->flags & ~(IMGFLAG_GENNORMALMAP | IMGFLAG_SRGB)) | IMGFLAG_NOLIGHTSCALE; + imgFlags_t normalFlags = (diffuseImg->flags & ~IMGFLAG_GENNORMALMAP) | IMGFLAG_NOLIGHTSCALE; // try a normalheight image first COM_StripExtension(diffuseImg->imgName, normalName, MAX_QPATH); @@ -2291,7 +2301,7 @@ static void CollapseStagesToLightall(shaderStage_t *diffuse, { char specularName[MAX_QPATH]; image_t *specularImg; - imgFlags_t specularFlags = (diffuseImg->flags & ~(IMGFLAG_GENNORMALMAP | IMGFLAG_SRGB)) | IMGFLAG_NOLIGHTSCALE; + imgFlags_t specularFlags = (diffuseImg->flags & ~IMGFLAG_GENNORMALMAP) | IMGFLAG_NOLIGHTSCALE; COM_StripExtension(diffuseImg->imgName, specularName, MAX_QPATH); Q_strcat(specularName, MAX_QPATH, "_s"); @@ -3062,9 +3072,7 @@ static shader_t *FinishShader( void ) { // // look for multitexture potential // - if ( qglActiveTextureARB ) { - stage = CollapseStagesToGLSL(); - } + stage = CollapseStagesToGLSL(); if ( shader.lightmapIndex >= 0 && !hasLightmapStage ) { if (vertexLightmap) { diff --git a/engine/code/renderergl2/tr_shadows.c b/engine/code/renderergl2/tr_shadows.c index 76371752..520bc3f2 100644 --- a/engine/code/renderergl2/tr_shadows.c +++ b/engine/code/renderergl2/tr_shadows.c @@ -162,7 +162,7 @@ void RB_ShadowTessEnd( void ) { return; } - VectorCopy( backEnd.currentEntity->lightDir, lightDir ); + VectorCopy( backEnd.currentEntity->modelLightDir, lightDir ); // project vertexes away from light direction for ( i = 0 ; i < tess.numVertexes ; i++ ) { @@ -302,7 +302,7 @@ void RB_ProjectionShadowDeform( void ) { groundDist = backEnd.or.origin[2] - backEnd.currentEntity->e.shadowPlane; - VectorCopy( backEnd.currentEntity->lightDir, lightDir ); + VectorCopy( backEnd.currentEntity->modelLightDir, lightDir ); d = DotProduct( lightDir, ground ); // don't let the shadows get too long or go negative if ( d < 0.5 ) { diff --git a/engine/code/renderergl2/tr_sky.c b/engine/code/renderergl2/tr_sky.c index 1ff0c0e8..94f68d26 100644 --- a/engine/code/renderergl2/tr_sky.c +++ b/engine/code/renderergl2/tr_sky.c @@ -366,8 +366,6 @@ static void DrawSkySide( struct image_s *image, const int mins[2], const int max int s, t; int firstVertex = tess.numVertexes; //int firstIndex = tess.numIndexes; - int minIndex = tess.minIndex; - int maxIndex = tess.maxIndex; vec4_t color; //tess.numVertexes = 0; @@ -417,9 +415,6 @@ static void DrawSkySide( struct image_s *image, const int mins[2], const int max } } - tess.minIndex = firstVertex; - tess.maxIndex = tess.numVertexes; - // FIXME: A lot of this can probably be removed for speed, and refactored into a more convenient function RB_UpdateTessVao(ATTR_POSITION | ATTR_TEXCOORD); /* @@ -463,9 +458,11 @@ static void DrawSkySide( struct image_s *image, const int mins[2], const int max VectorSet4(vector, 0.0, 0.0, 0.0, 0.0); GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXOFFTURB, vector); + + GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 0); } - R_DrawElementsVao(tess.numIndexes - tess.firstIndex, tess.firstIndex, tess.minIndex, tess.maxIndex); + R_DrawElements(tess.numIndexes - tess.firstIndex, tess.firstIndex); //qglDrawElements(GL_TRIANGLES, tess.numIndexes - tess.firstIndex, GL_INDEX_TYPE, BUFFER_OFFSET(tess.firstIndex * sizeof(glIndex_t))); @@ -475,8 +472,6 @@ static void DrawSkySide( struct image_s *image, const int mins[2], const int max tess.numIndexes = tess.firstIndex; tess.numVertexes = firstVertex; tess.firstIndex = 0; - tess.minIndex = minIndex; - tess.maxIndex = maxIndex; } static void DrawSkyBox( shader_t *shader ) diff --git a/engine/code/renderergl2/tr_surface.c b/engine/code/renderergl2/tr_surface.c index f624a139..5b6b933e 100644 --- a/engine/code/renderergl2/tr_surface.c +++ b/engine/code/renderergl2/tr_surface.c @@ -68,7 +68,7 @@ void RB_CheckOverflow( int verts, int indexes ) { void RB_CheckVao(vao_t *vao) { - if (vao != glState.currentVao || tess.multiDrawPrimitives >= MAX_MULTIDRAW_PRIMITIVES) + if (vao != glState.currentVao) { RB_EndSurface(); RB_BeginSurface(tess.shader, tess.fogNum, tess.cubemapIndex); @@ -208,18 +208,14 @@ void RB_InstantQuad2(vec4_t quadVerts[4], vec2_t texCoords[4]) tess.indexes[tess.numIndexes++] = 0; tess.indexes[tess.numIndexes++] = 2; tess.indexes[tess.numIndexes++] = 3; - tess.minIndex = 0; - tess.maxIndex = 3; RB_UpdateTessVao(ATTR_POSITION | ATTR_TEXCOORD); - R_DrawElementsVao(tess.numIndexes, tess.firstIndex, tess.minIndex, tess.maxIndex); + R_DrawElements(tess.numIndexes, tess.firstIndex); tess.numIndexes = 0; tess.numVertexes = 0; tess.firstIndex = 0; - tess.minIndex = 0; - tess.maxIndex = 0; } @@ -410,118 +406,59 @@ static void RB_SurfaceVertsAndIndexes( int numVerts, srfVert_t *verts, int numIn tess.numVertexes += numVerts; } -static qboolean RB_SurfaceVao(vao_t *vao, int numVerts, int numIndexes, int firstIndex, int minIndex, int maxIndex, int dlightBits, int pshadowBits, qboolean shaderCheck) +static qboolean RB_SurfaceVaoCached(int numVerts, srfVert_t *verts, int numIndexes, glIndex_t *indexes, int dlightBits, int pshadowBits) { - int i, mergeForward, mergeBack; - GLvoid *firstIndexOffset, *lastIndexOffset; + qboolean recycleVertexBuffer = qfalse; + qboolean recycleIndexBuffer = qfalse; + qboolean endSurface = qfalse; - if (!vao) - { + if (!(!ShaderRequiresCPUDeforms(tess.shader) && !tess.shader->isSky && !tess.shader->isPortal)) return qfalse; - } - if (shaderCheck && !(!ShaderRequiresCPUDeforms(tess.shader) && !tess.shader->isSky && !tess.shader->isPortal)) - { + if (!numIndexes || !numVerts) return qfalse; - } - RB_CheckVao(vao); + VaoCache_BindVao(); tess.dlightBits |= dlightBits; tess.pshadowBits |= pshadowBits; - // merge this into any existing multidraw primitives - mergeForward = -1; - mergeBack = -1; - firstIndexOffset = BUFFER_OFFSET(firstIndex * sizeof(glIndex_t)); - lastIndexOffset = BUFFER_OFFSET((firstIndex + numIndexes) * sizeof(glIndex_t)); + VaoCache_CheckAdd(&endSurface, &recycleVertexBuffer, &recycleIndexBuffer, numVerts, numIndexes); - if (tess.multiDrawPrimitives && r_mergeMultidraws->integer) + if (endSurface) { - i = 0; - - if (r_mergeMultidraws->integer == 1) - { - // lazy merge, only check the last primitive - i = tess.multiDrawPrimitives - 1; - } - - for (; i < tess.multiDrawPrimitives; i++) - { - if (firstIndexOffset == tess.multiDrawFirstIndex[i] + tess.multiDrawNumIndexes[i]) - { - mergeBack = i; - - if (mergeForward != -1) - break; - } - - if (lastIndexOffset == tess.multiDrawFirstIndex[i]) - { - mergeForward = i; - - if (mergeBack != -1) - break; - } - } + RB_EndSurface(); + RB_BeginSurface(tess.shader, tess.fogNum, tess.cubemapIndex); } - if (mergeBack != -1 && mergeForward == -1) - { - tess.multiDrawNumIndexes[mergeBack] += numIndexes; - tess.multiDrawMinIndex[mergeBack] = MIN(tess.multiDrawMinIndex[mergeBack], minIndex); - tess.multiDrawMaxIndex[mergeBack] = MAX(tess.multiDrawMaxIndex[mergeBack], maxIndex); - backEnd.pc.c_multidrawsMerged++; - } - else if (mergeBack == -1 && mergeForward != -1) - { - tess.multiDrawNumIndexes[mergeForward] += numIndexes; - tess.multiDrawFirstIndex[mergeForward] = firstIndexOffset; - tess.multiDrawMinIndex[mergeForward] = MIN(tess.multiDrawMinIndex[mergeForward], minIndex); - tess.multiDrawMaxIndex[mergeForward] = MAX(tess.multiDrawMaxIndex[mergeForward], maxIndex); - backEnd.pc.c_multidrawsMerged++; - } - else if (mergeBack != -1 && mergeForward != -1) - { - tess.multiDrawNumIndexes[mergeBack] += numIndexes + tess.multiDrawNumIndexes[mergeForward]; - tess.multiDrawMinIndex[mergeBack] = MIN(tess.multiDrawMinIndex[mergeBack], MIN(tess.multiDrawMinIndex[mergeForward], minIndex)); - tess.multiDrawMaxIndex[mergeBack] = MAX(tess.multiDrawMaxIndex[mergeBack], MAX(tess.multiDrawMaxIndex[mergeForward], maxIndex)); - tess.multiDrawPrimitives--; + if (recycleVertexBuffer) + VaoCache_RecycleVertexBuffer(); - if (mergeForward != tess.multiDrawPrimitives) - { - tess.multiDrawNumIndexes[mergeForward] = tess.multiDrawNumIndexes[tess.multiDrawPrimitives]; - tess.multiDrawFirstIndex[mergeForward] = tess.multiDrawFirstIndex[tess.multiDrawPrimitives]; - tess.multiDrawMinIndex[mergeForward] = tess.multiDrawMinIndex[tess.multiDrawPrimitives]; - tess.multiDrawMaxIndex[mergeForward] = tess.multiDrawMaxIndex[tess.multiDrawPrimitives]; - } - backEnd.pc.c_multidrawsMerged += 2; - } - else //if (mergeBack == -1 && mergeForward == -1) - { - tess.multiDrawNumIndexes[tess.multiDrawPrimitives] = numIndexes; - tess.multiDrawFirstIndex[tess.multiDrawPrimitives] = firstIndexOffset; - tess.multiDrawMinIndex[tess.multiDrawPrimitives] = minIndex; - tess.multiDrawMaxIndex[tess.multiDrawPrimitives] = maxIndex; - tess.multiDrawPrimitives++; - } + if (recycleIndexBuffer) + VaoCache_RecycleIndexBuffer(); - backEnd.pc.c_multidraws++; + if (!tess.numVertexes) + VaoCache_InitQueue(); - tess.numIndexes += numIndexes; + VaoCache_AddSurface(verts, numVerts, indexes, numIndexes); + + tess.numIndexes += numIndexes; tess.numVertexes += numVerts; + tess.useInternalVao = qfalse; + tess.useCacheVao = qtrue; return qtrue; } + /* ============= RB_SurfaceTriangles ============= */ static void RB_SurfaceTriangles( srfBspSurface_t *srf ) { - if( RB_SurfaceVao (srf->vao, srf->numVerts, srf->numIndexes, - srf->firstIndex, srf->minIndex, srf->maxIndex, srf->dlightBits, srf->pshadowBits, qtrue ) ) + if (RB_SurfaceVaoCached(srf->numVerts, srf->verts, srf->numIndexes, + srf->indexes, srf->dlightBits, srf->pshadowBits)) { return; } @@ -584,8 +521,6 @@ static void RB_SurfaceBeam( void ) tess.numVertexes = 0; tess.numIndexes = 0; tess.firstIndex = 0; - tess.minIndex = 0; - tess.maxIndex = 0; for ( i = 0; i <= NUM_BEAM_SEGS; i++ ) { VectorCopy(start_points[ i % NUM_BEAM_SEGS ], tess.xyz[tess.numVertexes++]); @@ -602,9 +537,6 @@ static void RB_SurfaceBeam( void ) tess.indexes[tess.numIndexes++] = 1 + (i + 1) * 2; } - tess.minIndex = 0; - tess.maxIndex = tess.numVertexes; - // FIXME: A lot of this can probably be removed for speed, and refactored into a more convenient function RB_UpdateTessVao(ATTR_POSITION); @@ -614,13 +546,13 @@ static void RB_SurfaceBeam( void ) GLSL_SetUniformVec4(sp, UNIFORM_COLOR, colorRed); - R_DrawElementsVao(tess.numIndexes, tess.firstIndex, tess.minIndex, tess.maxIndex); + GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 0); + + R_DrawElements(tess.numIndexes, tess.firstIndex); tess.numIndexes = 0; tess.numVertexes = 0; tess.firstIndex = 0; - tess.minIndex = 0; - tess.maxIndex = 0; } //================================================================================ @@ -960,8 +892,8 @@ RB_SurfaceFace ============== */ static void RB_SurfaceFace( srfBspSurface_t *srf ) { - if( RB_SurfaceVao (srf->vao, srf->numVerts, srf->numIndexes, - srf->firstIndex, srf->minIndex, srf->maxIndex, srf->dlightBits, srf->pshadowBits, qtrue ) ) + if (RB_SurfaceVaoCached(srf->numVerts, srf->verts, srf->numIndexes, + srf->indexes, srf->dlightBits, srf->pshadowBits)) { return; } @@ -1028,8 +960,8 @@ static void RB_SurfaceGrid( srfBspSurface_t *srf ) { int pshadowBits; //int *vDlightBits; - if( RB_SurfaceVao (srf->vao, srf->numVerts, srf->numIndexes, - srf->firstIndex, srf->minIndex, srf->maxIndex, srf->dlightBits, srf->pshadowBits, qtrue ) ) + if (RB_SurfaceVaoCached(srf->numVerts, srf->verts, srf->numIndexes, + srf->indexes, srf->dlightBits, srf->pshadowBits)) { return; } @@ -1277,12 +1209,6 @@ static void RB_SurfaceFlare(srfFlare_t *surf) RB_AddFlare(surf, tess.fogNum, surf->origin, surf->color, surf->normal); } -static void RB_SurfaceVaoMesh(srfBspSurface_t * srf) -{ - RB_SurfaceVao (srf->vao, srf->numVerts, srf->numIndexes, srf->firstIndex, - srf->minIndex, srf->maxIndex, srf->dlightBits, srf->pshadowBits, qfalse ); -} - void RB_SurfaceVaoMdvMesh(srfVaoMdvMesh_t * surface) { //mdvModel_t *mdvModel; @@ -1310,8 +1236,6 @@ void RB_SurfaceVaoMdvMesh(srfVaoMdvMesh_t * surface) tess.numIndexes = surface->numIndexes; tess.numVertexes = surface->numVerts; - tess.minIndex = surface->minIndex; - tess.maxIndex = surface->maxIndex; //mdvModel = surface->mdvModel; //mdvSurface = surface->mdvSurface; @@ -1391,6 +1315,5 @@ void (*rb_surfaceTable[SF_NUM_SURFACE_TYPES])( void *) = { (void(*)(void*))RB_IQMSurfaceAnim, // SF_IQM, (void(*)(void*))RB_SurfaceFlare, // SF_FLARE, (void(*)(void*))RB_SurfaceEntity, // SF_ENTITY - (void(*)(void*))RB_SurfaceVaoMesh, // SF_VAO_MESH, (void(*)(void*))RB_SurfaceVaoMdvMesh, // SF_VAO_MDVMESH }; diff --git a/engine/code/renderergl2/tr_vbo.c b/engine/code/renderergl2/tr_vbo.c index 0ac63a40..e4d0ca37 100644 --- a/engine/code/renderergl2/tr_vbo.c +++ b/engine/code/renderergl2/tr_vbo.c @@ -499,6 +499,8 @@ void R_InitVaos(void) R_BindNullVao(); + VaoCache_Init(); + GL_CheckErrors(); } @@ -650,3 +652,315 @@ void RB_UpdateTessVao(unsigned int attribBits) qglBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, tess.numIndexes * sizeof(tess.indexes[0]), tess.indexes); } } + +// FIXME: This sets a limit of 65536 verts/262144 indexes per static surface +// This is higher than the old vq3 limits but is worth noting +#define VAOCACHE_QUEUE_MAX_SURFACES (1 << 10) +#define VAOCACHE_QUEUE_MAX_VERTEXES (1 << 16) +#define VAOCACHE_QUEUE_MAX_INDEXES (VAOCACHE_QUEUE_MAX_VERTEXES * 4) + +typedef struct queuedSurface_s +{ + srfVert_t *vertexes; + int numVerts; + glIndex_t *indexes; + int numIndexes; +} +queuedSurface_t; + +static struct +{ + queuedSurface_t surfaces[VAOCACHE_QUEUE_MAX_SURFACES]; + int numSurfaces; + + srfVert_t vertexes[VAOCACHE_QUEUE_MAX_VERTEXES]; + int vertexCommitSize; + + glIndex_t indexes[VAOCACHE_QUEUE_MAX_INDEXES]; + int indexCommitSize; +} +vcq; + +#define VAOCACHE_MAX_SURFACES (1 << 16) +#define VAOCACHE_MAX_BATCHES (1 << 10) + +// srfVert_t is 60 bytes +// assuming each vert is referenced 4 times, need 16 bytes (4 glIndex_t) per vert +// -> need about 4/15ths the space for indexes as vertexes +#define VAOCACHE_VERTEX_BUFFER_SIZE (16 * 1024 * 1024) +#define VAOCACHE_INDEX_BUFFER_SIZE (5 * 1024 * 1024) + +typedef struct buffered_s +{ + void *data; + int size; + int bufferOffset; +} +buffered_t; + +static struct +{ + vao_t *vao; + buffered_t surfaceIndexSets[VAOCACHE_MAX_SURFACES]; + int numSurfaces; + + int batchLengths[VAOCACHE_MAX_BATCHES]; + int numBatches; + + int vertexOffset; + int indexOffset; +} +vc; + +void VaoCache_Commit(void) +{ + buffered_t *indexSet; + int *batchLength; + queuedSurface_t *surf, *end = vcq.surfaces + vcq.numSurfaces; + + R_BindVao(vc.vao); + + // Search for a matching batch + // FIXME: Use faster search + indexSet = vc.surfaceIndexSets; + batchLength = vc.batchLengths; + for (; batchLength < vc.batchLengths + vc.numBatches; batchLength++) + { + if (*batchLength == vcq.numSurfaces) + { + buffered_t *indexSet2 = indexSet; + for (surf = vcq.surfaces; surf < end; surf++, indexSet2++) + { + if (surf->indexes != indexSet2->data || (surf->numIndexes * sizeof(glIndex_t)) != indexSet2->size) + break; + } + + if (surf == end) + break; + } + + indexSet += *batchLength; + } + + // If found, use it + if (indexSet < vc.surfaceIndexSets + vc.numSurfaces) + { + tess.firstIndex = indexSet->bufferOffset / sizeof(glIndex_t); + //ri.Printf(PRINT_ALL, "firstIndex %d numIndexes %d as %d\n", tess.firstIndex, tess.numIndexes, batchLength - vc.batchLengths); + //ri.Printf(PRINT_ALL, "vc.numSurfaces %d vc.numBatches %d\n", vc.numSurfaces, vc.numBatches); + } + // If not, rebuffer the batch + // FIXME: keep track of the vertexes so we don't have to reupload them every time + else + { + srfVert_t *dstVertex = vcq.vertexes; + glIndex_t *dstIndex = vcq.indexes; + + batchLength = vc.batchLengths + vc.numBatches; + *batchLength = vcq.numSurfaces; + vc.numBatches++; + + tess.firstIndex = vc.indexOffset / sizeof(glIndex_t); + vcq.vertexCommitSize = 0; + vcq.indexCommitSize = 0; + for (surf = vcq.surfaces; surf < end; surf++) + { + glIndex_t *srcIndex = surf->indexes; + int vertexesSize = surf->numVerts * sizeof(srfVert_t); + int indexesSize = surf->numIndexes * sizeof(glIndex_t); + int i, indexOffset = (vc.vertexOffset + vcq.vertexCommitSize) / sizeof(srfVert_t); + + Com_Memcpy(dstVertex, surf->vertexes, vertexesSize); + dstVertex += surf->numVerts; + + vcq.vertexCommitSize += vertexesSize; + + indexSet = vc.surfaceIndexSets + vc.numSurfaces; + indexSet->data = surf->indexes; + indexSet->size = indexesSize; + indexSet->bufferOffset = vc.indexOffset + vcq.indexCommitSize; + vc.numSurfaces++; + + for (i = 0; i < surf->numIndexes; i++) + *dstIndex++ = *srcIndex++ + indexOffset; + + vcq.indexCommitSize += indexesSize; + } + + //ri.Printf(PRINT_ALL, "committing %d to %d, %d to %d as %d\n", vcq.vertexCommitSize, vc.vertexOffset, vcq.indexCommitSize, vc.indexOffset, batchLength - vc.batchLengths); + + if (vcq.vertexCommitSize) + { + qglBindBuffer(GL_ARRAY_BUFFER, vc.vao->vertexesVBO); + qglBufferSubData(GL_ARRAY_BUFFER, vc.vertexOffset, vcq.vertexCommitSize, vcq.vertexes); + vc.vertexOffset += vcq.vertexCommitSize; + } + + if (vcq.indexCommitSize) + { + qglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vc.vao->indexesIBO); + qglBufferSubData(GL_ELEMENT_ARRAY_BUFFER, vc.indexOffset, vcq.indexCommitSize, vcq.indexes); + vc.indexOffset += vcq.indexCommitSize; + } + } +} + +void VaoCache_Init(void) +{ + srfVert_t vert; + int dataSize; + + vc.vao = R_CreateVao("VaoCache", NULL, VAOCACHE_VERTEX_BUFFER_SIZE, NULL, VAOCACHE_INDEX_BUFFER_SIZE, VAO_USAGE_DYNAMIC); + + vc.vao->attribs[ATTR_INDEX_POSITION].enabled = 1; + vc.vao->attribs[ATTR_INDEX_TEXCOORD].enabled = 1; + vc.vao->attribs[ATTR_INDEX_LIGHTCOORD].enabled = 1; + vc.vao->attribs[ATTR_INDEX_NORMAL].enabled = 1; + vc.vao->attribs[ATTR_INDEX_TANGENT].enabled = 1; + vc.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].enabled = 1; + vc.vao->attribs[ATTR_INDEX_COLOR].enabled = 1; + + vc.vao->attribs[ATTR_INDEX_POSITION].count = 3; + vc.vao->attribs[ATTR_INDEX_TEXCOORD].count = 2; + vc.vao->attribs[ATTR_INDEX_LIGHTCOORD].count = 2; + vc.vao->attribs[ATTR_INDEX_NORMAL].count = 4; + vc.vao->attribs[ATTR_INDEX_TANGENT].count = 4; + vc.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].count = 4; + vc.vao->attribs[ATTR_INDEX_COLOR].count = 4; + + vc.vao->attribs[ATTR_INDEX_POSITION].type = GL_FLOAT; + vc.vao->attribs[ATTR_INDEX_TEXCOORD].type = GL_FLOAT; + vc.vao->attribs[ATTR_INDEX_LIGHTCOORD].type = GL_FLOAT; + vc.vao->attribs[ATTR_INDEX_NORMAL].type = GL_SHORT; + vc.vao->attribs[ATTR_INDEX_TANGENT].type = GL_SHORT; + vc.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].type = GL_SHORT; + vc.vao->attribs[ATTR_INDEX_COLOR].type = GL_UNSIGNED_SHORT; + + vc.vao->attribs[ATTR_INDEX_POSITION].normalized = GL_FALSE; + vc.vao->attribs[ATTR_INDEX_TEXCOORD].normalized = GL_FALSE; + vc.vao->attribs[ATTR_INDEX_LIGHTCOORD].normalized = GL_FALSE; + vc.vao->attribs[ATTR_INDEX_NORMAL].normalized = GL_TRUE; + vc.vao->attribs[ATTR_INDEX_TANGENT].normalized = GL_TRUE; + vc.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].normalized = GL_TRUE; + vc.vao->attribs[ATTR_INDEX_COLOR].normalized = GL_TRUE; + + vc.vao->attribs[ATTR_INDEX_POSITION].offset = 0; dataSize = sizeof(vert.xyz); + vc.vao->attribs[ATTR_INDEX_TEXCOORD].offset = dataSize; dataSize += sizeof(vert.st); + vc.vao->attribs[ATTR_INDEX_LIGHTCOORD].offset = dataSize; dataSize += sizeof(vert.lightmap); + vc.vao->attribs[ATTR_INDEX_NORMAL].offset = dataSize; dataSize += sizeof(vert.normal); + vc.vao->attribs[ATTR_INDEX_TANGENT].offset = dataSize; dataSize += sizeof(vert.tangent); + vc.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].offset = dataSize; dataSize += sizeof(vert.lightdir); + vc.vao->attribs[ATTR_INDEX_COLOR].offset = dataSize; dataSize += sizeof(vert.color); + + vc.vao->attribs[ATTR_INDEX_POSITION].stride = dataSize; + vc.vao->attribs[ATTR_INDEX_TEXCOORD].stride = dataSize; + vc.vao->attribs[ATTR_INDEX_LIGHTCOORD].stride = dataSize; + vc.vao->attribs[ATTR_INDEX_NORMAL].stride = dataSize; + vc.vao->attribs[ATTR_INDEX_TANGENT].stride = dataSize; + vc.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].stride = dataSize; + vc.vao->attribs[ATTR_INDEX_COLOR].stride = dataSize; + + Vao_SetVertexPointers(vc.vao); + + vc.numSurfaces = 0; + vc.numBatches = 0; + vc.vertexOffset = 0; + vc.indexOffset = 0; + vcq.vertexCommitSize = 0; + vcq.indexCommitSize = 0; + vcq.numSurfaces = 0; +} + +void VaoCache_BindVao(void) +{ + R_BindVao(vc.vao); +} + +void VaoCache_CheckAdd(qboolean *endSurface, qboolean *recycleVertexBuffer, qboolean *recycleIndexBuffer, int numVerts, int numIndexes) +{ + int vertexesSize = sizeof(srfVert_t) * numVerts; + int indexesSize = sizeof(glIndex_t) * numIndexes; + + if (vc.vao->vertexesSize < vc.vertexOffset + vcq.vertexCommitSize + vertexesSize) + { + //ri.Printf(PRINT_ALL, "out of space in vertex cache: %d < %d + %d + %d\n", vc.vao->vertexesSize, vc.vertexOffset, vc.vertexCommitSize, vertexesSize); + *recycleVertexBuffer = qtrue; + *recycleIndexBuffer = qtrue; + *endSurface = qtrue; + } + + if (vc.vao->indexesSize < vc.indexOffset + vcq.indexCommitSize + indexesSize) + { + //ri.Printf(PRINT_ALL, "out of space in index cache\n"); + *recycleIndexBuffer = qtrue; + *endSurface = qtrue; + } + + if (vc.numSurfaces + vcq.numSurfaces >= VAOCACHE_MAX_SURFACES) + { + //ri.Printf(PRINT_ALL, "out of surfaces in index cache\n"); + *recycleIndexBuffer = qtrue; + *endSurface = qtrue; + } + + if (vc.numBatches >= VAOCACHE_MAX_BATCHES) + { + //ri.Printf(PRINT_ALL, "out of batches in index cache\n"); + *recycleIndexBuffer = qtrue; + *endSurface = qtrue; + } + + if (vcq.numSurfaces >= VAOCACHE_QUEUE_MAX_SURFACES) + { + //ri.Printf(PRINT_ALL, "out of queued surfaces\n"); + *endSurface = qtrue; + } + + if (VAOCACHE_QUEUE_MAX_VERTEXES * sizeof(srfVert_t) < vcq.vertexCommitSize + vertexesSize) + { + //ri.Printf(PRINT_ALL, "out of queued vertexes\n"); + *endSurface = qtrue; + } + + if (VAOCACHE_QUEUE_MAX_INDEXES * sizeof(glIndex_t) < vcq.indexCommitSize + indexesSize) + { + //ri.Printf(PRINT_ALL, "out of queued indexes\n"); + *endSurface = qtrue; + } +} + +void VaoCache_RecycleVertexBuffer(void) +{ + qglBindBuffer(GL_ARRAY_BUFFER, vc.vao->vertexesVBO); + qglBufferData(GL_ARRAY_BUFFER, vc.vao->vertexesSize, NULL, GL_DYNAMIC_DRAW); + vc.vertexOffset = 0; +} + +void VaoCache_RecycleIndexBuffer(void) +{ + qglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vc.vao->indexesIBO); + qglBufferData(GL_ELEMENT_ARRAY_BUFFER, vc.vao->indexesSize, NULL, GL_DYNAMIC_DRAW); + vc.indexOffset = 0; + vc.numSurfaces = 0; + vc.numBatches = 0; +} + +void VaoCache_InitQueue(void) +{ + vcq.vertexCommitSize = 0; + vcq.indexCommitSize = 0; + vcq.numSurfaces = 0; +} + +void VaoCache_AddSurface(srfVert_t *verts, int numVerts, glIndex_t *indexes, int numIndexes) +{ + queuedSurface_t *queueEntry = vcq.surfaces + vcq.numSurfaces; + queueEntry->vertexes = verts; + queueEntry->numVerts = numVerts; + queueEntry->indexes = indexes; + queueEntry->numIndexes = numIndexes; + vcq.numSurfaces++; + + vcq.vertexCommitSize += sizeof(srfVert_t) * numVerts;; + vcq.indexCommitSize += sizeof(glIndex_t) * numIndexes; +} diff --git a/engine/code/renderergl2/tr_world.c b/engine/code/renderergl2/tr_world.c index c4b15339..f9a24424 100644 --- a/engine/code/renderergl2/tr_world.c +++ b/engine/code/renderergl2/tr_world.c @@ -36,7 +36,7 @@ static qboolean R_CullSurface( msurface_t *surf ) { return qfalse; } - if ( *surf->data == SF_GRID && r_nocurves->integer ) { + if ( r_nocurves->integer && *surf->data == SF_GRID ) { return qtrue; } @@ -213,7 +213,6 @@ static int R_DlightSurface( msurface_t *surf, int dlightBits ) { case SF_FACE: case SF_GRID: case SF_TRIANGLES: - case SF_VAO_MESH: ((srfBspSurface_t *)surf->data)->dlightBits = dlightBits; break; @@ -299,7 +298,6 @@ static int R_PshadowSurface( msurface_t *surf, int pshadowBits ) { case SF_FACE: case SF_GRID: case SF_TRIANGLES: - case SF_VAO_MESH: ((srfBspSurface_t *)surf->data)->pshadowBits = pshadowBits; break; @@ -561,43 +559,23 @@ static void R_RecursiveWorldNode( mnode_t *node, uint32_t planeBits, uint32_t dl tr.viewParms.visBounds[1][2] = node->maxs[2]; } - // add merged and unmerged surfaces - if (tr.world->viewSurfaces && !r_nocurves->integer) - view = tr.world->viewSurfaces + node->firstmarksurface; - else - view = tr.world->marksurfaces + node->firstmarksurface; + // add surfaces + view = tr.world->marksurfaces + node->firstmarksurface; c = node->nummarksurfaces; while (c--) { // just mark it as visible, so we don't jump out of the cache derefencing the surface surf = *view; - if (surf < 0) + if (tr.world->surfacesViewCount[surf] != tr.viewCount) { - if (tr.world->mergedSurfacesViewCount[-surf - 1] != tr.viewCount) - { - tr.world->mergedSurfacesViewCount[-surf - 1] = tr.viewCount; - tr.world->mergedSurfacesDlightBits[-surf - 1] = dlightBits; - tr.world->mergedSurfacesPshadowBits[-surf - 1] = pshadowBits; - } - else - { - tr.world->mergedSurfacesDlightBits[-surf - 1] |= dlightBits; - tr.world->mergedSurfacesPshadowBits[-surf - 1] |= pshadowBits; - } + tr.world->surfacesViewCount[surf] = tr.viewCount; + tr.world->surfacesDlightBits[surf] = dlightBits; + tr.world->surfacesPshadowBits[surf] = pshadowBits; } else { - if (tr.world->surfacesViewCount[surf] != tr.viewCount) - { - tr.world->surfacesViewCount[surf] = tr.viewCount; - tr.world->surfacesDlightBits[surf] = dlightBits; - tr.world->surfacesPshadowBits[surf] = pshadowBits; - } - else - { - tr.world->surfacesDlightBits[surf] |= dlightBits; - tr.world->surfacesPshadowBits[surf] |= pshadowBits; - } + tr.world->surfacesDlightBits[surf] |= dlightBits; + tr.world->surfacesPshadowBits[surf] |= pshadowBits; } view++; } @@ -825,14 +803,6 @@ void R_AddWorldSurfaces (void) { R_AddWorldSurface( tr.world->surfaces + i, tr.world->surfacesDlightBits[i], tr.world->surfacesPshadowBits[i] ); tr.refdef.dlightMask |= tr.world->surfacesDlightBits[i]; } - for (i = 0; i < tr.world->numMergedSurfaces; i++) - { - if (tr.world->mergedSurfacesViewCount[i] != tr.viewCount) - continue; - - R_AddWorldSurface( tr.world->mergedSurfaces + i, tr.world->mergedSurfacesDlightBits[i], tr.world->mergedSurfacesPshadowBits[i] ); - tr.refdef.dlightMask |= tr.world->mergedSurfacesDlightBits[i]; - } tr.refdef.dlightMask = ~tr.refdef.dlightMask; } diff --git a/engine/code/sdl/sdl_glimp.c b/engine/code/sdl/sdl_glimp.c index 9f1403f4..2f164306 100644 --- a/engine/code/sdl/sdl_glimp.c +++ b/engine/code/sdl/sdl_glimp.c @@ -53,6 +53,9 @@ cvar_t *r_allowResize; // make window resizable cvar_t *r_centerWindow; cvar_t *r_sdlDriver; +int qglMajorVersion, qglMinorVersion; +int qglesMajorVersion, qglesMinorVersion; + void (APIENTRYP qglActiveTextureARB) (GLenum texture); void (APIENTRYP qglClientActiveTextureARB) (GLenum texture); void (APIENTRYP qglMultiTexCoord2fARB) (GLenum target, GLfloat s, GLfloat t); @@ -60,6 +63,13 @@ void (APIENTRYP qglMultiTexCoord2fARB) (GLenum target, GLfloat s, GLfloat t); void (APIENTRYP qglLockArraysEXT) (GLint first, GLsizei count); void (APIENTRYP qglUnlockArraysEXT) (void); +#define GLE(ret, name, ...) name##proc * qgl##name; +QGL_1_1_PROCS; +QGL_DESKTOP_1_1_PROCS; +QGL_ES_1_1_PROCS; +QGL_3_0_PROCS; +#undef GLE + /* =============== GLimp_MakeCurrent @@ -221,12 +231,103 @@ static void GLimp_DetectAvailableModes(void) SDL_free( modes ); } +/* +=============== +GLimp_GetProcAddresses + +Get addresses for OpenGL functions. +=============== +*/ +static qboolean GLimp_GetProcAddresses( void ) { + qboolean success = qtrue; + const char *version; + +#ifdef __SDL_NOGETPROCADDR__ +#define GLE( ret, name, ... ) qgl##name = gl#name; +#else +#define GLE( ret, name, ... ) qgl##name = (name##proc *) SDL_GL_GetProcAddress("gl" #name); \ + if ( qgl##name == NULL ) { \ + ri.Printf( PRINT_ALL, "ERROR: Missing OpenGL function %s\n", "gl" #name ); \ + success = qfalse; \ + } +#endif + + // OpenGL 1.0 and OpenGL ES 1.0 + GLE(const GLubyte *, GetString, GLenum name) + + if ( !qglGetString ) { + Com_Error( ERR_FATAL, "glGetString is NULL" ); + } + + version = (const char *)qglGetString( GL_VERSION ); + + if ( !version ) { + Com_Error( ERR_FATAL, "GL_VERSION is NULL\n" ); + } + + if ( Q_stricmpn( "OpenGL ES", version, 9 ) == 0 ) { + char profile[6]; // ES, ES-CM, or ES-CL + sscanf( version, "OpenGL %5s %d.%d", profile, &qglesMajorVersion, &qglesMinorVersion ); + // common lite profile (no floating point) is not supported + if ( Q_stricmp( profile, "ES-CL" ) == 0 ) { + qglesMajorVersion = 0; + qglesMinorVersion = 0; + } + } else { + sscanf( version, "%d.%d", &qglMajorVersion, &qglMinorVersion ); + } + + if ( QGL_VERSION_ATLEAST( 1, 1 ) ) { + QGL_1_1_PROCS; + QGL_DESKTOP_1_1_PROCS; + } else if ( qglesMajorVersion == 1 && qglesMinorVersion >= 1 ) { + // OpenGL ES 1.1 (2.0 is not backward compatible) + QGL_1_1_PROCS; + QGL_ES_1_1_PROCS; + // error so this doesn't segfault due to NULL desktop GL functions being used + Com_Error( ERR_FATAL, "Unsupported OpenGL Version: %s\n", version ); + } else { + Com_Error( ERR_FATAL, "Unsupported OpenGL Version: %s\n", version ); + } + + if ( QGL_VERSION_ATLEAST( 3, 0 ) || QGLES_VERSION_ATLEAST( 3, 0 ) ) { + QGL_3_0_PROCS; + } + +#undef GLE + + return success; +} + +/* +=============== +GLimp_ClearProcAddresses + +Clear addresses for OpenGL functions. +=============== +*/ +static void GLimp_ClearProcAddresses( void ) { +#define GLE( ret, name, ... ) qgl##name = NULL; + + qglMajorVersion = 0; + qglMinorVersion = 0; + qglesMajorVersion = 0; + qglesMinorVersion = 0; + + QGL_1_1_PROCS; + QGL_DESKTOP_1_1_PROCS; + QGL_ES_1_1_PROCS; + QGL_3_0_PROCS; + +#undef GLE +} + /* =============== GLimp_SetMode =============== */ -static int GLimp_SetMode(int mode, qboolean fullscreen, qboolean noborder) +static int GLimp_SetMode(int mode, qboolean fullscreen, qboolean noborder, qboolean coreContext) { const char *glstring; int perChannelColorBits; @@ -320,6 +421,7 @@ static int GLimp_SetMode(int mode, qboolean fullscreen, qboolean noborder) // Destroy existing state if it exists if( SDL_glContext != NULL ) { + GLimp_ClearProcAddresses(); SDL_GL_DeleteContext( SDL_glContext ); SDL_glContext = NULL; } @@ -488,10 +590,80 @@ static int GLimp_SetMode(int mode, qboolean fullscreen, qboolean noborder) SDL_SetWindowIcon( SDL_window, icon ); - if( ( SDL_glContext = SDL_GL_CreateContext( SDL_window ) ) == NULL ) + if (coreContext) { - ri.Printf( PRINT_DEVELOPER, "SDL_GL_CreateContext failed: %s\n", SDL_GetError( ) ); - continue; + int profileMask, majorVersion, minorVersion; + SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &profileMask); + SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &majorVersion); + SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minorVersion); + + ri.Printf(PRINT_ALL, "Trying to get an OpenGL 3.2 core context\n"); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); + if ((SDL_glContext = SDL_GL_CreateContext(SDL_window)) == NULL) + { + ri.Printf(PRINT_ALL, "SDL_GL_CreateContext failed: %s\n", SDL_GetError()); + ri.Printf(PRINT_ALL, "Reverting to default context\n"); + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profileMask); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, majorVersion); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minorVersion); + } + else + { + const char *renderer; + + ri.Printf(PRINT_ALL, "SDL_GL_CreateContext succeeded.\n"); + + if ( GLimp_GetProcAddresses() ) + { + renderer = (const char *)qglGetString(GL_RENDERER); + } + else + { + ri.Printf( PRINT_ALL, "GLimp_GetProcAddresses() failed for OpenGL 3.2 core context\n" ); + renderer = NULL; + } + + if (!renderer || (strstr(renderer, "Software Renderer") || strstr(renderer, "Software Rasterizer"))) + { + if ( renderer ) + ri.Printf(PRINT_ALL, "GL_RENDERER is %s, rejecting context\n", renderer); + + GLimp_ClearProcAddresses(); + SDL_GL_DeleteContext(SDL_glContext); + SDL_glContext = NULL; + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profileMask); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, majorVersion); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minorVersion); + } + } + } + else + { + SDL_glContext = NULL; + } + + if ( !SDL_glContext ) + { + if( ( SDL_glContext = SDL_GL_CreateContext( SDL_window ) ) == NULL ) + { + ri.Printf( PRINT_DEVELOPER, "SDL_GL_CreateContext failed: %s\n", SDL_GetError( ) ); + continue; + } + + if ( !GLimp_GetProcAddresses() ) + { + ri.Printf( PRINT_ALL, "GLimp_GetProcAddresses() failed\n" ); + GLimp_ClearProcAddresses(); + SDL_GL_DeleteContext( SDL_glContext ); + SDL_glContext = NULL; + SDL_DestroyWindow( SDL_window ); + SDL_window = NULL; + continue; + } } qglClearColor( 0, 0, 0, 1 ); @@ -533,7 +705,7 @@ static int GLimp_SetMode(int mode, qboolean fullscreen, qboolean noborder) GLimp_StartDriverAndSetMode =============== */ -static qboolean GLimp_StartDriverAndSetMode(int mode, qboolean fullscreen, qboolean noborder) +static qboolean GLimp_StartDriverAndSetMode(int mode, qboolean fullscreen, qboolean noborder, qboolean gl3Core) { rserr_t err; @@ -560,7 +732,7 @@ static qboolean GLimp_StartDriverAndSetMode(int mode, qboolean fullscreen, qbool fullscreen = qfalse; } - err = GLimp_SetMode(mode, fullscreen, noborder); + err = GLimp_SetMode(mode, fullscreen, noborder, gl3Core); switch ( err ) { @@ -577,15 +749,6 @@ static qboolean GLimp_StartDriverAndSetMode(int mode, qboolean fullscreen, qbool return qtrue; } -static qboolean GLimp_HaveExtension(const char *ext) -{ - const char *ptr = Q_stristr( glConfig.extensions_string, ext ); - if (ptr == NULL) - return qfalse; - ptr += strlen(ext); - return ((*ptr == ' ') || (*ptr == '\0')); // verify it's complete string. -} - /* =============== @@ -605,8 +768,8 @@ static void GLimp_InitExtensions( void ) glConfig.textureCompression = TC_NONE; // GL_EXT_texture_compression_s3tc - if ( GLimp_HaveExtension( "GL_ARB_texture_compression" ) && - GLimp_HaveExtension( "GL_EXT_texture_compression_s3tc" ) ) + if ( SDL_GL_ExtensionSupported( "GL_ARB_texture_compression" ) && + SDL_GL_ExtensionSupported( "GL_EXT_texture_compression_s3tc" ) ) { if ( r_ext_compressed_textures->value ) { @@ -626,7 +789,7 @@ static void GLimp_InitExtensions( void ) // GL_S3_s3tc ... legacy extension before GL_EXT_texture_compression_s3tc. if (glConfig.textureCompression == TC_NONE) { - if ( GLimp_HaveExtension( "GL_S3_s3tc" ) ) + if ( SDL_GL_ExtensionSupported( "GL_S3_s3tc" ) ) { if ( r_ext_compressed_textures->value ) { @@ -647,7 +810,7 @@ static void GLimp_InitExtensions( void ) // GL_EXT_texture_env_add glConfig.textureEnvAddAvailable = qfalse; - if ( GLimp_HaveExtension( "EXT_texture_env_add" ) ) + if ( SDL_GL_ExtensionSupported( "GL_EXT_texture_env_add" ) ) { if ( r_ext_texture_env_add->integer ) { @@ -669,7 +832,7 @@ static void GLimp_InitExtensions( void ) qglMultiTexCoord2fARB = NULL; qglActiveTextureARB = NULL; qglClientActiveTextureARB = NULL; - if ( GLimp_HaveExtension( "GL_ARB_multitexture" ) ) + if ( SDL_GL_ExtensionSupported( "GL_ARB_multitexture" ) ) { if ( r_ext_multitexture->value ) { @@ -706,7 +869,7 @@ static void GLimp_InitExtensions( void ) } // GL_EXT_compiled_vertex_array - if ( GLimp_HaveExtension( "GL_EXT_compiled_vertex_array" ) ) + if ( SDL_GL_ExtensionSupported( "GL_EXT_compiled_vertex_array" ) ) { if ( r_ext_compiled_vertex_array->value ) { @@ -729,7 +892,7 @@ static void GLimp_InitExtensions( void ) } textureFilterAnisotropic = qfalse; - if ( GLimp_HaveExtension( "GL_EXT_texture_filter_anisotropic" ) ) + if ( SDL_GL_ExtensionSupported( "GL_EXT_texture_filter_anisotropic" ) ) { if ( r_ext_texture_filter_anisotropic->integer ) { qglGetIntegerv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, (GLint *)&maxAnisotropy ); @@ -764,7 +927,7 @@ This routine is responsible for initializing the OS specific portions of OpenGL =============== */ -void GLimp_Init( void ) +void GLimp_Init( qboolean coreContext) { ri.Printf( PRINT_DEVELOPER, "Glimp_Init( )\n" ); @@ -784,13 +947,13 @@ void GLimp_Init( void ) ri.Sys_GLimpInit( ); // Create the window and set up the context - if(GLimp_StartDriverAndSetMode(r_mode->integer, r_fullscreen->integer, r_noborder->integer)) + if(GLimp_StartDriverAndSetMode(r_mode->integer, r_fullscreen->integer, r_noborder->integer, coreContext)) goto success; // Try again, this time in a platform specific "safe mode" ri.Sys_GLimpSafeInit( ); - if(GLimp_StartDriverAndSetMode(r_mode->integer, r_fullscreen->integer, qfalse)) + if(GLimp_StartDriverAndSetMode(r_mode->integer, r_fullscreen->integer, qfalse, coreContext)) goto success; // Finally, try the default screen resolution @@ -799,7 +962,7 @@ void GLimp_Init( void ) ri.Printf( PRINT_ALL, "Setting r_mode %d failed, falling back on r_mode %d\n", r_mode->integer, R_MODE_FALLBACK ); - if(GLimp_StartDriverAndSetMode(R_MODE_FALLBACK, qfalse, qfalse)) + if(GLimp_StartDriverAndSetMode(R_MODE_FALLBACK, qfalse, qfalse, coreContext)) goto success; } @@ -821,7 +984,37 @@ void GLimp_Init( void ) if (*glConfig.renderer_string && glConfig.renderer_string[strlen(glConfig.renderer_string) - 1] == '\n') glConfig.renderer_string[strlen(glConfig.renderer_string) - 1] = 0; Q_strncpyz( glConfig.version_string, (char *) qglGetString (GL_VERSION), sizeof( glConfig.version_string ) ); - Q_strncpyz( glConfig.extensions_string, (char *) qglGetString (GL_EXTENSIONS), sizeof( glConfig.extensions_string ) ); + + // manually create extension list if using OpenGL 3 + if ( qglGetStringi ) + { + int i, numExtensions, extensionLength, listLength; + const char *extension; + + qglGetIntegerv( GL_NUM_EXTENSIONS, &numExtensions ); + listLength = 0; + + for ( i = 0; i < numExtensions; i++ ) + { + extension = (char *) qglGetStringi( GL_EXTENSIONS, i ); + extensionLength = strlen( extension ); + + if ( ( listLength + extensionLength + 1 ) >= sizeof( glConfig.extensions_string ) ) + break; + + if ( i > 0 ) { + Q_strcat( glConfig.extensions_string, sizeof( glConfig.extensions_string ), " " ); + listLength++; + } + + Q_strcat( glConfig.extensions_string, sizeof( glConfig.extensions_string ), extension ); + listLength += extensionLength; + } + } + else + { + Q_strncpyz( glConfig.extensions_string, (char *) qglGetString (GL_EXTENSIONS), sizeof( glConfig.extensions_string ) ); + } // initialize extensions GLimp_InitExtensions( ); @@ -832,6 +1025,11 @@ void GLimp_Init( void ) ri.IN_Init( SDL_window ); } +void* GLimp_GetProcAddress( const char *func ) +{ + return SDL_GL_GetProcAddress(func); +} + /* =============== diff --git a/engine/code/sdl/sdl_input.c b/engine/code/sdl/sdl_input.c index 4c8ff1fe..6af67518 100644 --- a/engine/code/sdl/sdl_input.c +++ b/engine/code/sdl/sdl_input.c @@ -51,6 +51,8 @@ static cvar_t *in_joystickUseAnalog = NULL; static int vidRestartTime = 0; +static int in_eventTime = 0; + static SDL_Window *SDL_window = NULL; #define CTRL(a) ((a)-'a'+1) @@ -192,7 +194,18 @@ static keyNum_t IN_TranslateSDLToQ3Key( SDL_Keysym *keysym, qboolean down ) { keyNum_t key = 0; - if( keysym->sym >= SDLK_SPACE && keysym->sym < SDLK_DELETE ) + if( keysym->scancode >= SDL_SCANCODE_1 && keysym->scancode <= SDL_SCANCODE_0 ) + { + // Always map the number keys as such even if they actually map + // to other characters (eg, "1" is "&" on an AZERTY keyboard). + // This is required for SDL before 2.0.6, except on Windows + // which already had this behavior. + if( keysym->scancode == SDL_SCANCODE_0 ) + key = '0'; + else + key = '1' + keysym->scancode - SDL_SCANCODE_1; + } + else if( keysym->sym >= SDLK_SPACE && keysym->sym < SDLK_DELETE ) { // These happen to match the ASCII chars key = (int)keysym->sym; @@ -280,6 +293,15 @@ static keyNum_t IN_TranslateSDLToQ3Key( SDL_Keysym *keysym, qboolean down ) case SDLK_CAPSLOCK: key = K_CAPSLOCK; break; default: + if( !( keysym->sym & SDLK_SCANCODE_MASK ) && keysym->scancode <= 95 ) + { + // Map Unicode characters to 95 world keys using the key's scan code. + // FIXME: There aren't enough world keys to cover all the scancodes. + // Maybe create a map of scancode to quake key at start up and on + // key map change; allocate world key numbers as needed similar + // to SDL 1.2. + key = K_WORLD_0 + (int)keysym->scancode; + } break; } } @@ -338,10 +360,13 @@ static void IN_ActivateMouse( void ) { if( in_nograb->modified || !mouseActive ) { - if( in_nograb->integer ) + if( in_nograb->integer ) { + SDL_SetRelativeMouseMode( SDL_FALSE ); SDL_SetWindowGrab( SDL_window, SDL_FALSE ); - else + } else { + SDL_SetRelativeMouseMode( SDL_TRUE ); SDL_SetWindowGrab( SDL_window, SDL_TRUE ); + } in_nograb->modified = qfalse; } @@ -629,7 +654,7 @@ static void IN_GamepadMove( void ) qboolean pressed = SDL_GameControllerGetButton(gamepad, SDL_CONTROLLER_BUTTON_A + i); if (pressed != stick_state.buttons[i]) { - Com_QueueEvent(0, SE_KEY, K_PAD0_A + i, pressed, 0, NULL); + Com_QueueEvent(in_eventTime, SE_KEY, K_PAD0_A + i, pressed, 0, NULL); stick_state.buttons[i] = pressed; } } @@ -709,19 +734,19 @@ static void IN_GamepadMove( void ) // positive to negative/neutral -> keyup if (!posAnalog && posKey && oldAxis > 0 && axis <= 0) - Com_QueueEvent(0, SE_KEY, posKey, qfalse, 0, NULL); + Com_QueueEvent(in_eventTime, SE_KEY, posKey, qfalse, 0, NULL); // negative to positive/neutral -> keyup if (!negAnalog && negKey && oldAxis < 0 && axis >= 0) - Com_QueueEvent(0, SE_KEY, negKey, qfalse, 0, NULL); + Com_QueueEvent(in_eventTime, SE_KEY, negKey, qfalse, 0, NULL); // negative/neutral to positive -> keydown if (!posAnalog && posKey && oldAxis <= 0 && axis > 0) - Com_QueueEvent(0, SE_KEY, posKey, qtrue, 0, NULL); + Com_QueueEvent(in_eventTime, SE_KEY, posKey, qtrue, 0, NULL); // positive/neutral to negative -> keydown if (!negAnalog && negKey && oldAxis >= 0 && axis < 0) - Com_QueueEvent(0, SE_KEY, negKey, qtrue, 0, NULL); + Com_QueueEvent(in_eventTime, SE_KEY, negKey, qtrue, 0, NULL); stick_state.oldaaxes[i] = axis; } @@ -733,7 +758,7 @@ static void IN_GamepadMove( void ) for (i = 0; i < MAX_JOYSTICK_AXIS; i++) { if (translatedAxesSet[i]) - Com_QueueEvent(0, SE_JOYSTICK_AXIS, i, translatedAxes[i], 0, NULL); + Com_QueueEvent(in_eventTime, SE_JOYSTICK_AXIS, i, translatedAxes[i], 0, NULL); } } } @@ -784,7 +809,7 @@ static void IN_JoyMove( void ) balldx *= 2; if (abs(balldy) > 1) balldy *= 2; - Com_QueueEvent( 0, SE_MOUSE, balldx, balldy, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_MOUSE, balldx, balldy, 0, NULL ); } } @@ -799,7 +824,7 @@ static void IN_JoyMove( void ) qboolean pressed = (SDL_JoystickGetButton(stick, i) != 0); if (pressed != stick_state.buttons[i]) { - Com_QueueEvent( 0, SE_KEY, K_JOY1 + i, pressed, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, K_JOY1 + i, pressed, 0, NULL ); stick_state.buttons[i] = pressed; } } @@ -824,32 +849,32 @@ static void IN_JoyMove( void ) // release event switch( ((Uint8 *)&stick_state.oldhats)[i] ) { case SDL_HAT_UP: - Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 0], qfalse, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, hat_keys[4*i + 0], qfalse, 0, NULL ); break; case SDL_HAT_RIGHT: - Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 1], qfalse, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, hat_keys[4*i + 1], qfalse, 0, NULL ); break; case SDL_HAT_DOWN: - Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 2], qfalse, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, hat_keys[4*i + 2], qfalse, 0, NULL ); break; case SDL_HAT_LEFT: - Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 3], qfalse, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, hat_keys[4*i + 3], qfalse, 0, NULL ); break; case SDL_HAT_RIGHTUP: - Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 0], qfalse, 0, NULL ); - Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 1], qfalse, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, hat_keys[4*i + 0], qfalse, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, hat_keys[4*i + 1], qfalse, 0, NULL ); break; case SDL_HAT_RIGHTDOWN: - Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 2], qfalse, 0, NULL ); - Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 1], qfalse, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, hat_keys[4*i + 2], qfalse, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, hat_keys[4*i + 1], qfalse, 0, NULL ); break; case SDL_HAT_LEFTUP: - Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 0], qfalse, 0, NULL ); - Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 3], qfalse, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, hat_keys[4*i + 0], qfalse, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, hat_keys[4*i + 3], qfalse, 0, NULL ); break; case SDL_HAT_LEFTDOWN: - Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 2], qfalse, 0, NULL ); - Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 3], qfalse, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, hat_keys[4*i + 2], qfalse, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, hat_keys[4*i + 3], qfalse, 0, NULL ); break; default: break; @@ -857,32 +882,32 @@ static void IN_JoyMove( void ) // press event switch( ((Uint8 *)&hats)[i] ) { case SDL_HAT_UP: - Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 0], qtrue, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, hat_keys[4*i + 0], qtrue, 0, NULL ); break; case SDL_HAT_RIGHT: - Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 1], qtrue, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, hat_keys[4*i + 1], qtrue, 0, NULL ); break; case SDL_HAT_DOWN: - Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 2], qtrue, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, hat_keys[4*i + 2], qtrue, 0, NULL ); break; case SDL_HAT_LEFT: - Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 3], qtrue, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, hat_keys[4*i + 3], qtrue, 0, NULL ); break; case SDL_HAT_RIGHTUP: - Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 0], qtrue, 0, NULL ); - Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 1], qtrue, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, hat_keys[4*i + 0], qtrue, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, hat_keys[4*i + 1], qtrue, 0, NULL ); break; case SDL_HAT_RIGHTDOWN: - Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 2], qtrue, 0, NULL ); - Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 1], qtrue, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, hat_keys[4*i + 2], qtrue, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, hat_keys[4*i + 1], qtrue, 0, NULL ); break; case SDL_HAT_LEFTUP: - Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 0], qtrue, 0, NULL ); - Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 3], qtrue, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, hat_keys[4*i + 0], qtrue, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, hat_keys[4*i + 3], qtrue, 0, NULL ); break; case SDL_HAT_LEFTDOWN: - Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 2], qtrue, 0, NULL ); - Com_QueueEvent( 0, SE_KEY, hat_keys[4*i + 3], qtrue, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, hat_keys[4*i + 2], qtrue, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, hat_keys[4*i + 3], qtrue, 0, NULL ); break; default: break; @@ -910,7 +935,7 @@ static void IN_JoyMove( void ) if ( axis != stick_state.oldaaxes[i] ) { - Com_QueueEvent( 0, SE_JOYSTICK_AXIS, i, axis, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_JOYSTICK_AXIS, i, axis, 0, NULL ); stick_state.oldaaxes[i] = axis; } } @@ -936,11 +961,11 @@ static void IN_JoyMove( void ) { for( i = 0; i < 16; i++ ) { if( ( axes & ( 1 << i ) ) && !( stick_state.oldaxes & ( 1 << i ) ) ) { - Com_QueueEvent( 0, SE_KEY, joy_keys[i], qtrue, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, joy_keys[i], qtrue, 0, NULL ); } if( !( axes & ( 1 << i ) ) && ( stick_state.oldaxes & ( 1 << i ) ) ) { - Com_QueueEvent( 0, SE_KEY, joy_keys[i], qfalse, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, joy_keys[i], qfalse, 0, NULL ); } } } @@ -972,19 +997,19 @@ static void IN_ProcessEvents( void ) break; if( ( key = IN_TranslateSDLToQ3Key( &e.key.keysym, qtrue ) ) ) - Com_QueueEvent( 0, SE_KEY, key, qtrue, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, key, qtrue, 0, NULL ); if( key == K_BACKSPACE ) - Com_QueueEvent( 0, SE_CHAR, CTRL('h'), 0, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_CHAR, CTRL('h'), 0, 0, NULL ); else if( keys[K_CTRL].down && key >= 'a' && key <= 'z' ) - Com_QueueEvent( 0, SE_CHAR, CTRL(key), 0, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_CHAR, CTRL(key), 0, 0, NULL ); lastKeyDown = key; break; case SDL_KEYUP: if( ( key = IN_TranslateSDLToQ3Key( &e.key.keysym, qfalse ) ) ) - Com_QueueEvent( 0, SE_KEY, key, qfalse, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, key, qfalse, 0, NULL ); lastKeyDown = 0; break; @@ -1029,11 +1054,11 @@ static void IN_ProcessEvents( void ) { if( IN_IsConsoleKey( 0, utf32 ) ) { - Com_QueueEvent( 0, SE_KEY, K_CONSOLE, qtrue, 0, NULL ); - Com_QueueEvent( 0, SE_KEY, K_CONSOLE, qfalse, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, K_CONSOLE, qtrue, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, K_CONSOLE, qfalse, 0, NULL ); } else - Com_QueueEvent( 0, SE_CHAR, utf32, 0, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_CHAR, utf32, 0, 0, NULL ); } } } @@ -1044,7 +1069,7 @@ static void IN_ProcessEvents( void ) { if( !e.motion.xrel && !e.motion.yrel ) break; - Com_QueueEvent( 0, SE_MOUSE, e.motion.xrel, e.motion.yrel, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_MOUSE, e.motion.xrel, e.motion.yrel, 0, NULL ); } break; @@ -1061,7 +1086,7 @@ static void IN_ProcessEvents( void ) case SDL_BUTTON_X2: b = K_MOUSE5; break; default: b = K_AUX1 + ( e.button.button - SDL_BUTTON_X2 + 1 ) % 16; break; } - Com_QueueEvent( 0, SE_KEY, b, + Com_QueueEvent( in_eventTime, SE_KEY, b, ( e.type == SDL_MOUSEBUTTONDOWN ? qtrue : qfalse ), 0, NULL ); } break; @@ -1069,13 +1094,13 @@ static void IN_ProcessEvents( void ) case SDL_MOUSEWHEEL: if( e.wheel.y > 0 ) { - Com_QueueEvent( 0, SE_KEY, K_MWHEELUP, qtrue, 0, NULL ); - Com_QueueEvent( 0, SE_KEY, K_MWHEELUP, qfalse, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, K_MWHEELUP, qtrue, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, K_MWHEELUP, qfalse, 0, NULL ); } else if( e.wheel.y < 0 ) { - Com_QueueEvent( 0, SE_KEY, K_MWHEELDOWN, qtrue, 0, NULL ); - Com_QueueEvent( 0, SE_KEY, K_MWHEELDOWN, qfalse, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, K_MWHEELDOWN, qtrue, 0, NULL ); + Com_QueueEvent( in_eventTime, SE_KEY, K_MWHEELDOWN, qfalse, 0, NULL ); } break; @@ -1099,6 +1124,12 @@ static void IN_ProcessEvents( void ) width = e.window.data1; height = e.window.data2; + // ignore this event on fullscreen + if( cls.glconfig.isFullscreen ) + { + break; + } + // check if size actually changed if( cls.glconfig.vidWidth == width && cls.glconfig.vidHeight == height ) { @@ -1164,6 +1195,9 @@ void IN_Frame( void ) IN_ProcessEvents( ); + // Set event time for next frame to earliest possible time an event could happen + in_eventTime = Sys_Milliseconds( ); + // In case we had to delay actual restart of video system if( ( vidRestartTime != 0 ) && ( vidRestartTime < Sys_Milliseconds( ) ) ) { diff --git a/engine/code/server/server.h b/engine/code/server/server.h index 39d6f4d3..f962c317 100644 --- a/engine/code/server/server.h +++ b/engine/code/server/server.h @@ -172,7 +172,7 @@ typedef struct client_s { int timeoutCount; // must timeout a few frames in a row so debugging doesn't break clientSnapshot_t frames[PACKET_BACKUP]; // updates can be delta'd from here int ping; - int rate; // bytes / second + int rate; // bytes / second, or 0 if unlimited int snapshotMsec; // requests a snapshot every snapshotMsec unless rate choked int pureAuthentic; qboolean gotCP; // TTimo - additional flag to distinguish between a bad pure checksum, and no cp command at all @@ -241,8 +241,10 @@ typedef struct { int nextHeartbeatTime; challenge_t challenges[MAX_CHALLENGES]; // to prevent invalid IPs from connecting netadr_t redirectAddress; // for rcon return messages - - netadr_t authorizeAddress; // for rcon return messages +#ifndef STANDALONE + netadr_t authorizeAddress; // authorize server address +#endif + int masterResolveTime[MAX_MASTER_SERVERS]; // next svs.time that server should do dns lookup for master server } serverStatic_t; #define SERVER_MAXBANS 1024 @@ -280,6 +282,7 @@ extern cvar_t *sv_killserver; extern cvar_t *sv_mapname; extern cvar_t *sv_mapChecksum; extern cvar_t *sv_serverid; +extern cvar_t *sv_rateLimit; extern cvar_t *sv_minRate; extern cvar_t *sv_maxRate; extern cvar_t *sv_dlRate; diff --git a/engine/code/server/sv_bot.c b/engine/code/server/sv_bot.c index d92dfcaf..58503542 100644 --- a/engine/code/server/sv_bot.c +++ b/engine/code/server/sv_bot.c @@ -543,7 +543,7 @@ void SV_BotInitBotLib(void) { // file system access botlib_import.FS_FOpenFile = FS_FOpenFileByMode; - botlib_import.FS_Read = FS_Read2; + botlib_import.FS_Read = FS_Read; botlib_import.FS_Write = FS_Write; botlib_import.FS_FCloseFile = FS_FCloseFile; botlib_import.FS_Seek = FS_Seek; diff --git a/engine/code/server/sv_ccmds.c b/engine/code/server/sv_ccmds.c index 5f26b853..2ba8194d 100644 --- a/engine/code/server/sv_ccmds.c +++ b/engine/code/server/sv_ccmds.c @@ -1260,6 +1260,7 @@ static void SV_ConSay_f(void) { strcat(text, p); + Com_Printf("%s\n", text); SV_SendServerCommand(NULL, "chat \"%s\"", text); } @@ -1299,6 +1300,7 @@ static void SV_ConTell_f(void) { strcat(text, p); + Com_Printf("%s\n", text); SV_SendServerCommand(cl, "chat \"%s\"", text); } @@ -1364,6 +1366,7 @@ static void SV_ConSayto_f(void) { strcat(text, p); + Com_Printf("%s\n", text); SV_SendServerCommand(saytocl, "chat \"%s\"", text); } diff --git a/engine/code/server/sv_client.c b/engine/code/server/sv_client.c index ad759eff..09a2a446 100644 --- a/engine/code/server/sv_client.c +++ b/engine/code/server/sv_client.c @@ -1405,7 +1405,7 @@ void SV_UserinfoChanged( client_t *cl ) { // if the client is on the same subnet as the server and we aren't running an // internet public server, assume they don't need a rate choke if ( Sys_IsLANAddress( cl->netchan.remoteAddress ) && com_dedicated->integer != 2 && sv_lanForceRate->integer == 1) { - cl->rate = 99999; // lans should not rate limit + cl->rate = 0; // no rate limit } else { val = Info_ValueForKey (cl->userinfo, "rate"); if (strlen(val)) { diff --git a/engine/code/server/sv_game.c b/engine/code/server/sv_game.c index fd0eeabd..cf8d39a8 100644 --- a/engine/code/server/sv_game.c +++ b/engine/code/server/sv_game.c @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 1999-2005 Id Software, Inc., 2016 Google Inc. +Copyright (C) 1999-2005 Id Software, Inc., 2016-2017 Google Inc. This file is part of Quake III Arena source code. @@ -327,7 +327,7 @@ intptr_t SV_GameSystemCalls( intptr_t *args ) { case G_FS_FOPEN_FILE: return FS_FOpenFileByMode( VMA(1), VMA(2), args[3] ); case G_FS_READ: - FS_Read2( VMA(1), args[2], args[3] ); + FS_Read( VMA(1), args[2], args[3] ); return 0; case G_FS_WRITE: FS_Write( VMA(1), args[2], args[3] ); @@ -467,7 +467,13 @@ intptr_t SV_GameSystemCalls( intptr_t *args ) { case BOTLIB_GET_CONSOLE_MESSAGE: return SV_BotGetConsoleMessage( args[1], VMA(2), args[3] ); case BOTLIB_USER_COMMAND: - SV_ClientThink( &svs.clients[args[1]], VMA(2) ); + { + int clientNum = args[1]; + + if ( clientNum >= 0 && clientNum < sv_maxclients->integer ) { + SV_ClientThink( &svs.clients[clientNum], VMA(2) ); + } + } return 0; case BOTLIB_AAS_BBOX_AREAS: @@ -962,3 +968,12 @@ qboolean SV_GameCommand( void ) { return VM_Call( gvm, GAME_CONSOLE_COMMAND ); } + +float dmlab_raycast(const float start[3], const float end[3]) { + if ( sv.state != SS_GAME ) { + return 0.0f; + } + trace_t results; + SV_Trace(&results, start, NULL, NULL, end, ENTITYNUM_NONE, CONTENTS_SOLID, qfalse); + return results.fraction; +} diff --git a/engine/code/server/sv_init.c b/engine/code/server/sv_init.c index ae9086ed..85176d9f 100644 --- a/engine/code/server/sv_init.c +++ b/engine/code/server/sv_init.c @@ -644,6 +644,8 @@ void SV_Init (void) sv_hostname = Cvar_Get ("sv_hostname", "noname", CVAR_SERVERINFO | CVAR_ARCHIVE ); sv_maxclients = Cvar_Get ("sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH); + sv_rateLimit = Cvar_Get ("sv_rateLimit", "1", CVAR_ARCHIVE | CVAR_SERVERINFO ); + sv_minRate = Cvar_Get ("sv_minRate", "0", CVAR_ARCHIVE | CVAR_SERVERINFO ); sv_minRate = Cvar_Get ("sv_minRate", "0", CVAR_ARCHIVE | CVAR_SERVERINFO ); sv_maxRate = Cvar_Get ("sv_maxRate", "0", CVAR_ARCHIVE | CVAR_SERVERINFO ); sv_dlRate = Cvar_Get("sv_dlRate", "100", CVAR_ARCHIVE | CVAR_SERVERINFO); diff --git a/engine/code/server/sv_main.c b/engine/code/server/sv_main.c index 993910fe..5a407a49 100644 --- a/engine/code/server/sv_main.c +++ b/engine/code/server/sv_main.c @@ -38,7 +38,6 @@ cvar_t *sv_rconPassword; // password for remote server commands cvar_t *sv_privatePassword; // password for the privateClient slots cvar_t *sv_allowDownload; cvar_t *sv_maxclients; - cvar_t *sv_privateClients; // number of clients reserved for password cvar_t *sv_hostname; cvar_t *sv_master[MAX_MASTER_SERVERS]; // master server ip address @@ -49,6 +48,7 @@ cvar_t *sv_killserver; // menu system can set to 1 to shut server down cvar_t *sv_mapname; cvar_t *sv_mapChecksum; cvar_t *sv_serverid; +cvar_t *sv_rateLimit; // Whether to rate limit. cvar_t *sv_minRate; cvar_t *sv_maxRate; cvar_t *sv_dlRate; @@ -57,7 +57,7 @@ cvar_t *sv_maxPing; cvar_t *sv_gametype; cvar_t *sv_pure; cvar_t *sv_floodProtect; -cvar_t *sv_lanForceRate; // dedicated 1 (LAN) server forces local client rates to 99999 (bug #491) +cvar_t *sv_lanForceRate; // dedicated 1 (LAN) server forces local client rates to unlimited (0) #ifndef STANDALONE cvar_t *sv_strictAuth; #endif @@ -236,6 +236,7 @@ but not on every player enter or exit. ================ */ #define HEARTBEAT_MSEC 300*1000 +#define MASTERDNS_MSEC 24*60*60*1000 void SV_MasterHeartbeat(const char *message) { static netadr_t adr[MAX_MASTER_SERVERS][2]; // [2] for v4 and v6 address for the same address string. @@ -264,12 +265,12 @@ void SV_MasterHeartbeat(const char *message) if(!sv_master[i]->string[0]) continue; - // see if we haven't already resolved the name - // resolving usually causes hitches on win95, so only - // do it when needed - if(sv_master[i]->modified || (adr[i][0].type == NA_BAD && adr[i][1].type == NA_BAD)) + // see if we haven't already resolved the name or if it's been over 24 hours + // resolving usually causes hitches on win95, so only do it when needed + if (sv_master[i]->modified || svs.time > svs.masterResolveTime[i]) { sv_master[i]->modified = qfalse; + svs.masterResolveTime[i] = svs.time + MASTERDNS_MSEC; if(netenabled & NET_ENABLEV4) { @@ -304,16 +305,11 @@ void SV_MasterHeartbeat(const char *message) else Com_Printf( "%s has no IPv6 address.\n", sv_master[i]->string); } + } - if(adr[i][0].type == NA_BAD && adr[i][1].type == NA_BAD) - { - // if the address failed to resolve, clear it - // so we don't take repeated dns hits - Com_Printf("Couldn't resolve address: %s\n", sv_master[i]->string); - Cvar_Set(sv_master[i]->name, ""); - sv_master[i]->modified = qfalse; - continue; - } + if(adr[i][0].type == NA_BAD && adr[i][1].type == NA_BAD) + { + continue; } @@ -483,22 +479,25 @@ SVC_RateLimit */ qboolean SVC_RateLimit( leakyBucket_t *bucket, int burst, int period ) { if ( bucket != NULL ) { - int now = Sys_Milliseconds(); - int interval = now - bucket->lastTime; - int expired = interval / period; - int expiredRemainder = interval % period; + if (sv_rateLimit->integer){ + int now = Sys_Milliseconds(); + int interval = now - bucket->lastTime; + int expired = interval / period; + int expiredRemainder = interval % period; + + if ( expired > bucket->burst || interval < 0 ) { + bucket->burst = 0; + bucket->lastTime = now; + } else { + bucket->burst -= expired; + bucket->lastTime = now - expiredRemainder; + } - if ( expired > bucket->burst || interval < 0 ) { - bucket->burst = 0; - bucket->lastTime = now; + if ( bucket->burst < burst ) { + bucket->burst++; + return qfalse; + } } else { - bucket->burst -= expired; - bucket->lastTime = now - expiredRemainder; - } - - if ( bucket->burst < burst ) { - bucket->burst++; - return qfalse; } } @@ -1197,6 +1196,12 @@ int SV_RateMsec(client_t *client) rate = sv_minRate->integer; } + // check for unlimited rate + if (rate == 0) + { + return 0; + } + if(client->netchan.remoteAddress.type == NA_IP6) messageSize += UDPIP6_HEADER_SIZE; else diff --git a/engine/code/server/sv_snapshot.c b/engine/code/server/sv_snapshot.c index 2237abd3..497eb166 100644 --- a/engine/code/server/sv_snapshot.c +++ b/engine/code/server/sv_snapshot.c @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 1999-2005 Id Software, Inc., 2017 Google Inc. This file is part of Quake III Arena source code. @@ -21,7 +21,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "server.h" - +#include "../deepmind/context.h" /* ============================================================================= @@ -299,6 +299,8 @@ static void SV_AddEntitiesVisibleFromPoint( vec3_t origin, clientSnapshot_t *fra int leafnum; byte *clientpvs; byte *bitvector; + DeepmindContext* ctx = dmlab_context(); + bool has_alt_cameras = ctx->hooks.has_alt_cameras(ctx->userdata); // during an error shutdown message we may need to transmit // the shutdown message after the server has shutdown, so @@ -362,7 +364,7 @@ static void SV_AddEntitiesVisibleFromPoint( vec3_t origin, clientSnapshot_t *fra } // broadcast entities are always sent - if ( ent->r.svFlags & SVF_BROADCAST ) { + if ( ent->r.svFlags & SVF_BROADCAST || has_alt_cameras ) { SV_AddEntToSnapshot( svEnt, ent, eNums ); continue; } diff --git a/engine/code/sys/con_log.c b/engine/code/sys/con_log.c index c3a4a5b8..bc1c5aef 100644 --- a/engine/code/sys/con_log.c +++ b/engine/code/sys/con_log.c @@ -121,7 +121,7 @@ unsigned int CON_LogRead( char *out, unsigned int outSize ) } Com_Memcpy( out, consoleLog + readPos, firstChunk ); - Com_Memcpy( out + firstChunk, out, secondChunk ); + Com_Memcpy( out + firstChunk, consoleLog, secondChunk ); readPos = ( readPos + outSize ) % MAX_LOG; diff --git a/engine/code/sys/con_tty.c b/engine/code/sys/con_tty.c index 9a6ee95c..2c2b595c 100644 --- a/engine/code/sys/con_tty.c +++ b/engine/code/sys/con_tty.c @@ -378,7 +378,7 @@ char *CON_Input( void ) { #ifndef DEDICATED // if not in the game explicitly prepend a slash if needed - if (clc.state != CA_ACTIVE && TTY_con.cursor && + if (clc.state != CA_ACTIVE && con_autochat->integer && TTY_con.cursor && TTY_con.buffer[0] != '/' && TTY_con.buffer[0] != '\\') { memmove(TTY_con.buffer + 1, TTY_con.buffer, sizeof(TTY_con.buffer) - 1); @@ -389,7 +389,11 @@ char *CON_Input( void ) if (TTY_con.buffer[0] == '/' || TTY_con.buffer[0] == '\\') { Q_strncpyz(text, TTY_con.buffer + 1, sizeof(text)); } else if (TTY_con.cursor) { - Com_sprintf(text, sizeof(text), "cmd say %s", TTY_con.buffer); + if (con_autochat->integer) { + Com_sprintf(text, sizeof(text), "cmd say %s", TTY_con.buffer); + } else { + Q_strncpyz(text, TTY_con.buffer, sizeof(text)); + } } else { text[0] = '\0'; } diff --git a/engine/code/sys/sys_local.h b/engine/code/sys/sys_local.h index 96edd98e..4398f875 100644 --- a/engine/code/sys/sys_local.h +++ b/engine/code/sys/sys_local.h @@ -28,12 +28,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define MINSDL_MINOR 0 #define MINSDL_PATCH 0 -// Input subsystem -void IN_Init( void *windowData ); -void IN_Frame( void ); -void IN_Shutdown( void ); -void IN_Restart( void ); - // Console void CON_Shutdown( void ); void CON_Init( void ); diff --git a/engine/code/sys/sys_main.c b/engine/code/sys/sys_main.c index b5b52a55..ea0df044 100644 --- a/engine/code/sys/sys_main.c +++ b/engine/code/sys/sys_main.c @@ -460,25 +460,43 @@ from executable path, then fs_basepath. void *Sys_LoadDll(const char *name, qboolean useSystemLib) { - void *dllhandle; - + void *dllhandle = NULL; + + if(!Sys_DllExtension(name)) + { + Com_Printf("Refusing to attempt to load library \"%s\": Extension not allowed.\n", name); + return NULL; + } + if(useSystemLib) + { Com_Printf("Trying to load \"%s\"...\n", name); + dllhandle = Sys_LoadLibrary(name); + } - if(!useSystemLib || !(dllhandle = Sys_LoadLibrary(name))) + if(!dllhandle) { const char *topDir; char libPath[MAX_OSPATH]; + int len; topDir = Sys_BinaryPath(); if(!*topDir) topDir = "."; - Com_Printf("Trying to load \"%s\" from \"%s\"...\n", name, topDir); - Com_sprintf(libPath, sizeof(libPath), "%s%c%s", topDir, PATH_SEP, name); + len = Com_sprintf(libPath, sizeof(libPath), "%s%c%s", topDir, PATH_SEP, name); + if(len < sizeof(libPath)) + { + Com_Printf("Trying to load \"%s\" from \"%s\"...\n", name, topDir); + dllhandle = Sys_LoadLibrary(libPath); + } + else + { + Com_Printf("Skipping trying to load \"%s\" from \"%s\", file name is too long.\n", name, topDir); + } - if(!(dllhandle = Sys_LoadLibrary(libPath))) + if(!dllhandle) { const char *basePath = Cvar_VariableString("fs_basepath"); @@ -487,9 +505,16 @@ void *Sys_LoadDll(const char *name, qboolean useSystemLib) if(FS_FilenameCompare(topDir, basePath)) { - Com_Printf("Trying to load \"%s\" from \"%s\"...\n", name, basePath); - Com_sprintf(libPath, sizeof(libPath), "%s%c%s", basePath, PATH_SEP, name); - dllhandle = Sys_LoadLibrary(libPath); + len = Com_sprintf(libPath, sizeof(libPath), "%s%c%s", basePath, PATH_SEP, name); + if(len < sizeof(libPath)) + { + Com_Printf("Trying to load \"%s\" from \"%s\"...\n", name, basePath); + dllhandle = Sys_LoadLibrary(libPath); + } + else + { + Com_Printf("Skipping trying to load \"%s\" from \"%s\", file name is too long.\n", name, basePath); + } } if(!dllhandle) @@ -516,6 +541,12 @@ void *Sys_LoadGameDll(const char *name, assert(name); + if(!Sys_DllExtension(name)) + { + Com_Printf("Refusing to attempt to load library \"%s\": Extension not allowed.\n", name); + return NULL; + } + Com_Printf( "Loading DLL file: %s\n", name); libHandle = Sys_LoadLibrary(name); diff --git a/engine/code/sys/sys_unix.c b/engine/code/sys/sys_unix.c index 229af984..a7b40731 100644 --- a/engine/code/sys/sys_unix.c +++ b/engine/code/sys/sys_unix.c @@ -47,6 +47,9 @@ static char homePath[ MAX_OSPATH ] = { 0 }; // Used to store the Steam Quake 3 installation path static char steamPath[ MAX_OSPATH ] = { 0 }; +// Used to store the GOG Quake 3 installation path +static char gogPath[ MAX_OSPATH ] = { 0 }; + /* ================== Sys_DefaultHomePath @@ -106,6 +109,17 @@ char *Sys_SteamPath( void ) return steamPath; } +/* +================ +Sys_GogPath +================ +*/ +char *Sys_GogPath( void ) +{ + // GOG also doesn't let you install Quake 3 on Mac/Linux + return gogPath; +} + /* ================ Sys_Milliseconds @@ -730,72 +744,12 @@ static void Sys_XmessageCommand( dialogType_t type, const char *message, const c ============== Sys_Dialog -Display a *nix dialog box +Display a *nix dialog box (but disabled entirely) ============== */ -dialogResult_t Sys_Dialog( dialogType_t type, const char *message, const char *title ) -{ - typedef enum - { - NONE = 0, - ZENITY, - KDIALOG, - XMESSAGE, - NUM_DIALOG_PROGRAMS - } dialogCommandType_t; - typedef void (*dialogCommandBuilder_t)( dialogType_t, const char *, const char * ); - - const char *session = getenv( "DESKTOP_SESSION" ); - qboolean tried[ NUM_DIALOG_PROGRAMS ] = { qfalse }; - dialogCommandBuilder_t commands[ NUM_DIALOG_PROGRAMS ] = { NULL }; - dialogCommandType_t preferredCommandType = NONE; - int i; - - commands[ ZENITY ] = &Sys_ZenityCommand; - commands[ KDIALOG ] = &Sys_KdialogCommand; - commands[ XMESSAGE ] = &Sys_XmessageCommand; - - // This may not be the best way - if( !Q_stricmp( session, "gnome" ) ) - preferredCommandType = ZENITY; - else if( !Q_stricmp( session, "kde" ) ) - preferredCommandType = KDIALOG; - - for( i = NONE + 1; i < NUM_DIALOG_PROGRAMS; i++ ) - { - if( preferredCommandType != NONE && preferredCommandType != i ) - continue; - - if( !tried[ i ] ) - { - int exitCode; - - commands[ i ]( type, message, title ); - exitCode = Sys_Exec( ); - - if( exitCode >= 0 ) - { - switch( type ) - { - case DT_YES_NO: return exitCode ? DR_NO : DR_YES; - case DT_OK_CANCEL: return exitCode ? DR_CANCEL : DR_OK; - default: return DR_OK; - } - } - - tried[ i ] = qtrue; - - // The preference failed, so start again in order - if( preferredCommandType != NONE ) - { - preferredCommandType = NONE; - i = NONE + 1; - } - } - } - - Com_DPrintf( S_COLOR_YELLOW "WARNING: failed to show a dialog\n" ); - return DR_OK; +dialogResult_t Sys_Dialog( dialogType_t type, const char *message, const char *title ) { + Sys_Print( message ); + return type == DT_YES_NO ? DR_NO : DR_OK; } #endif @@ -892,3 +846,44 @@ qboolean Sys_PIDIsRunning( int pid ) { return kill( pid, 0 ) == 0; } + +/* +================= +Sys_DllExtension + +Check if filename should be allowed to be loaded as a DLL. +================= +*/ +qboolean Sys_DllExtension( const char *name ) { + const char *p; + char c = 0; + + if ( COM_CompareExtension( name, DLL_EXT ) ) { + return qtrue; + } + + // Check for format of filename.so.1.2.3 + p = strstr( name, DLL_EXT "." ); + + if ( p ) { + p += strlen( DLL_EXT ); + + // Check if .so is only followed for periods and numbers. + while ( *p ) { + c = *p; + + if ( !isdigit( c ) && c != '.' ) { + return qfalse; + } + + p++; + } + + // Don't allow filename to end in a period. file.so., file.so.0., etc + if ( c != '.' ) { + return qtrue; + } + } + + return qfalse; +} diff --git a/engine/code/sys/sys_win32.c b/engine/code/sys/sys_win32.c index da7241a7..6979dce9 100644 --- a/engine/code/sys/sys_win32.c +++ b/engine/code/sys/sys_win32.c @@ -39,12 +39,19 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include #include +#ifndef KEY_WOW64_32KEY +#define KEY_WOW64_32KEY 0x0200 +#endif + // Used to determine where to store user-specific files static char homePath[ MAX_OSPATH ] = { 0 }; // Used to store the Steam Quake 3 installation path static char steamPath[ MAX_OSPATH ] = { 0 }; +// Used to store the GOG Quake 3 installation path +static char gogPath[ MAX_OSPATH ] = { 0 }; + #ifndef DEDICATED static UINT timerResolution = 0; #endif @@ -148,6 +155,8 @@ char *Sys_SteamPath( void ) pathLen = MAX_OSPATH; if (RegQueryValueEx(steamRegKey, "InstallLocation", NULL, NULL, (LPBYTE)steamPath, &pathLen)) steamPath[0] = '\0'; + + RegCloseKey(steamRegKey); } #endif @@ -161,6 +170,8 @@ char *Sys_SteamPath( void ) if (steamPath[0]) finishPath = qtrue; + + RegCloseKey(steamRegKey); } #endif @@ -179,6 +190,38 @@ char *Sys_SteamPath( void ) return steamPath; } +/* +================ +Sys_GogPath +================ +*/ +char *Sys_GogPath( void ) +{ +#ifdef GOGPATH_ID + HKEY gogRegKey; + DWORD pathLen = MAX_OSPATH; + + if (!gogPath[0] && !RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\GOG.com\\Games\\" GOGPATH_ID, 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &gogRegKey)) + { + pathLen = MAX_OSPATH; + if (RegQueryValueEx(gogRegKey, "PATH", NULL, NULL, (LPBYTE)gogPath, &pathLen)) + gogPath[0] = '\0'; + + RegCloseKey(gogRegKey); + } + + if (gogPath[0]) + { + if (pathLen == MAX_OSPATH) + pathLen--; + + gogPath[pathLen] = '\0'; + } +#endif + + return gogPath; +} + /* ================ Sys_Milliseconds @@ -799,3 +842,14 @@ qboolean Sys_PIDIsRunning( int pid ) return qfalse; } + +/* +================= +Sys_DllExtension + +Check if filename should be allowed to be loaded as a DLL. +================= +*/ +qboolean Sys_DllExtension( const char *name ) { + return COM_CompareExtension( name, DLL_EXT ); +} diff --git a/engine/code/sys/win_manifest.xml b/engine/code/sys/win_manifest.xml new file mode 100644 index 00000000..dccbcf56 --- /dev/null +++ b/engine/code/sys/win_manifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True/PM + PerMonitorV2, PerMonitor + + + diff --git a/engine/code/sys/win_resource.rc b/engine/code/sys/win_resource.rc index b1c39d50..b9c8c6ea 100644 --- a/engine/code/sys/win_resource.rc +++ b/engine/code/sys/win_resource.rc @@ -70,6 +70,12 @@ BEGIN IDS_STRING1 "Quake3" END +///////////////////////////////////////////////////////////////////////////// +// +// Application Manifest +// +CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "win_manifest.xml" + #endif // English (U.S.) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/engine/code/ui/ui_atoms.c b/engine/code/ui/ui_atoms.c index cdc4aeb0..44ff3b0e 100644 --- a/engine/code/ui/ui_atoms.c +++ b/engine/code/ui/ui_atoms.c @@ -420,18 +420,10 @@ Adjusted for resolution and screen aspect ratio */ void UI_AdjustFrom640( float *x, float *y, float *w, float *h ) { // expect valid pointers -#if 0 - *x = *x * uiInfo.uiDC.scale + uiInfo.uiDC.bias; - *y *= uiInfo.uiDC.scale; - *w *= uiInfo.uiDC.scale; - *h *= uiInfo.uiDC.scale; -#endif - - *x *= uiInfo.uiDC.xscale; + *x = *x * uiInfo.uiDC.xscale + uiInfo.uiDC.bias; *y *= uiInfo.uiDC.yscale; *w *= uiInfo.uiDC.xscale; *h *= uiInfo.uiDC.yscale; - } void UI_DrawNamedPic( float x, float y, float width, float height, const char *picname ) { diff --git a/engine/code/ui/ui_local.h b/engine/code/ui/ui_local.h index 26c024ec..d955661c 100644 --- a/engine/code/ui/ui_local.h +++ b/engine/code/ui/ui_local.h @@ -534,6 +534,9 @@ typedef struct { animation_t animations[MAX_TOTALANIMATIONS]; + qboolean fixedlegs; // true if legs yaw is always the same as torso yaw + qboolean fixedtorso; // true if torso never changes yaw + qhandle_t weaponModel; qhandle_t barrelModel; qhandle_t flashModel; diff --git a/engine/code/ui/ui_main.c b/engine/code/ui/ui_main.c index 8c07375d..2552375c 100644 --- a/engine/code/ui/ui_main.c +++ b/engine/code/ui/ui_main.c @@ -55,20 +55,22 @@ static const int numSkillLevels = ARRAY_LEN( skillLevels ); #define UIAS_LOCAL 0 -#define UIAS_GLOBAL1 1 -#define UIAS_GLOBAL2 2 -#define UIAS_GLOBAL3 3 -#define UIAS_GLOBAL4 4 -#define UIAS_GLOBAL5 5 -#define UIAS_FAVORITES 6 +#define UIAS_GLOBAL0 1 +#define UIAS_GLOBAL1 2 +#define UIAS_GLOBAL2 3 +#define UIAS_GLOBAL3 4 +#define UIAS_GLOBAL4 5 +#define UIAS_GLOBAL5 6 +#define UIAS_FAVORITES 7 static const char *netSources[] = { "Local", - "Internet1", - "Internet2", - "Internet3", - "Internet4", - "Internet5", + "Internet", + "Master1", + "Master2", + "Master3", + "Master4", + "Master5", "Favorites" }; static const int numNetSources = ARRAY_LEN( netSources ); @@ -114,7 +116,7 @@ static int gamecodetoui[] = {4,2,3,0,5,1,6}; static int uitogamecode[] = {4,6,2,3,1,5,7}; -static void UI_StartServerRefresh(qboolean full); +static void UI_StartServerRefresh(qboolean full, qboolean force); static void UI_StopServerRefresh( void ); static void UI_DoServerRefresh( void ); static void UI_FeederSelection(float feederID, int index); @@ -129,6 +131,7 @@ static void UI_ParseTeamInfo(const char *teamFile); static const char *UI_SelectedMap(int index, int *actual); static const char *UI_SelectedHead(int index, int *actual); static int UI_GetIndexFromSelection(int actual); +static void UI_DrawCinematic(int handle, float x, float y, float w, float h); int ProcessNewUI( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6 ); @@ -1000,6 +1003,7 @@ int UI_SourceForLAN(void) { default: case UIAS_LOCAL: return AS_LOCAL; + case UIAS_GLOBAL0: case UIAS_GLOBAL1: case UIAS_GLOBAL2: case UIAS_GLOBAL3: @@ -1112,8 +1116,7 @@ static void UI_DrawClanCinematic(rectDef_t *rect, float scale, vec4_t color) { } if (uiInfo.teamList[i].cinematic >= 0) { trap_CIN_RunCinematic(uiInfo.teamList[i].cinematic); - trap_CIN_SetExtents(uiInfo.teamList[i].cinematic, rect->x, rect->y, rect->w, rect->h); - trap_CIN_DrawCinematic(uiInfo.teamList[i].cinematic); + UI_DrawCinematic(uiInfo.teamList[i].cinematic, rect->x, rect->y, rect->w, rect->h); } else { trap_R_SetColor( color ); UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Metal); @@ -1134,8 +1137,7 @@ static void UI_DrawPreviewCinematic(rectDef_t *rect, float scale, vec4_t color) uiInfo.previewMovie = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.movieList[uiInfo.movieIndex]), 0, 0, 0, 0, (CIN_loop | CIN_silent) ); if (uiInfo.previewMovie >= 0) { trap_CIN_RunCinematic(uiInfo.previewMovie); - trap_CIN_SetExtents(uiInfo.previewMovie, rect->x, rect->y, rect->w, rect->h); - trap_CIN_DrawCinematic(uiInfo.previewMovie); + UI_DrawCinematic(uiInfo.previewMovie, rect->x, rect->y, rect->w, rect->h); } else { uiInfo.previewMovie = -2; } @@ -1258,8 +1260,7 @@ static void UI_DrawMapCinematic(rectDef_t *rect, float scale, vec4_t color, qboo } if (uiInfo.mapList[map].cinematic >= 0) { trap_CIN_RunCinematic(uiInfo.mapList[map].cinematic); - trap_CIN_SetExtents(uiInfo.mapList[map].cinematic, rect->x, rect->y, rect->w, rect->h); - trap_CIN_DrawCinematic(uiInfo.mapList[map].cinematic); + UI_DrawCinematic(uiInfo.mapList[map].cinematic, rect->x, rect->y, rect->w, rect->h); } else { uiInfo.mapList[map].cinematic = -2; } @@ -1339,8 +1340,7 @@ static void UI_DrawNetMapCinematic(rectDef_t *rect, float scale, vec4_t color) { if (uiInfo.serverStatus.currentServerCinematic >= 0) { trap_CIN_RunCinematic(uiInfo.serverStatus.currentServerCinematic); - trap_CIN_SetExtents(uiInfo.serverStatus.currentServerCinematic, rect->x, rect->y, rect->w, rect->h); - trap_CIN_DrawCinematic(uiInfo.serverStatus.currentServerCinematic); + UI_DrawCinematic(uiInfo.serverStatus.currentServerCinematic, rect->x, rect->y, rect->w, rect->h); } else { UI_DrawNetMapPreview(rect, scale, color); } @@ -1779,10 +1779,10 @@ static void UI_DrawRedBlue(rectDef_t *rect, float scale, vec4_t color, int textS } static void UI_DrawCrosshair(rectDef_t *rect, float scale, vec4_t color) { - trap_R_SetColor( color ); - if (uiInfo.currentCrosshair < 0 || uiInfo.currentCrosshair >= NUM_CROSSHAIRS) { - uiInfo.currentCrosshair = 0; + if (!uiInfo.currentCrosshair) { + return; } + trap_R_SetColor( color ); UI_DrawHandlePic( rect->x, rect->y - rect->h, rect->w, rect->h, uiInfo.uiDC.Assets.crosshairShader[uiInfo.currentCrosshair]); trap_R_SetColor( NULL ); } @@ -2489,7 +2489,7 @@ static qboolean UI_NetSource_HandleKey(int flags, float *special, int key) { while(ui_netSource.integer >= UIAS_GLOBAL1 && ui_netSource.integer <= UIAS_GLOBAL5) { - Com_sprintf(cvarname, sizeof(cvarname), "sv_master%d", ui_netSource.integer); + Com_sprintf(cvarname, sizeof(cvarname), "sv_master%d", ui_netSource.integer - UIAS_GLOBAL0); trap_Cvar_VariableStringBuffer(cvarname, masterstr, sizeof(masterstr)); if(*masterstr) break; @@ -2505,9 +2505,7 @@ static qboolean UI_NetSource_HandleKey(int flags, float *special, int key) { } UI_BuildServerDisplayList(qtrue); - if (!(ui_netSource.integer >= UIAS_GLOBAL1 && ui_netSource.integer <= UIAS_GLOBAL5)) { - UI_StartServerRefresh(qtrue); - } + UI_StartServerRefresh(qtrue, qfalse); trap_Cvar_SetValue( "ui_netSource", ui_netSource.integer); return qtrue; } @@ -2746,6 +2744,9 @@ void UI_ServersSort(int column, qboolean force) { uiInfo.serverStatus.sortKey = column; qsort( &uiInfo.serverStatus.displayServers[0], uiInfo.serverStatus.numDisplayServers, sizeof(int), UI_ServersQsortCompare); + + // update displayed levelshot + UI_FeederSelection( FEEDER_SERVERS, uiInfo.serverStatus.currentServer ); } /* @@ -3027,7 +3028,11 @@ static void UI_StartSkirmish(qboolean next) { } } if (g >= GT_TEAM ) { + // send team command for vanilla q3 game qvm trap_Cmd_ExecuteText( EXEC_APPEND, "wait 5; team Red\n" ); + + // set g_localTeamPref for ioq3 game qvm + trap_Cvar_Set( "g_localTeamPref", "Red" ); } } @@ -3260,7 +3265,6 @@ static void UI_RunMenuScript(char **args) { trap_Cvar_Set("ui_cdkeyvalid", "CD Key does not appear to be valid."); } } else if (Q_stricmp(name, "loadArenas") == 0) { - UI_LoadArenas(); UI_MapCountByGameType(qfalse); Menu_SetFeederSelection(NULL, FEEDER_ALLMAPS, 0, "createserver"); } else if (Q_stricmp(name, "saveControls") == 0) { @@ -3279,10 +3283,10 @@ static void UI_RunMenuScript(char **args) { } else if (Q_stricmp(name, "resetScores") == 0) { UI_ClearScores(); } else if (Q_stricmp(name, "RefreshServers") == 0) { - UI_StartServerRefresh(qtrue); + UI_StartServerRefresh(qtrue, qtrue); UI_BuildServerDisplayList(qtrue); } else if (Q_stricmp(name, "RefreshFilter") == 0) { - UI_StartServerRefresh(qfalse); + UI_StartServerRefresh(qfalse, qtrue); UI_BuildServerDisplayList(qtrue); } else if (Q_stricmp(name, "RunSPDemo") == 0) { if (uiInfo.demoAvailable) { @@ -3324,9 +3328,8 @@ static void UI_RunMenuScript(char **args) { uiInfo.nextServerStatusRefresh = 0; uiInfo.nextFindPlayerRefresh = 0; } else if (Q_stricmp(name, "UpdateFilter") == 0) { - if (ui_netSource.integer == UIAS_LOCAL) { - UI_StartServerRefresh(qtrue); - } + // UpdateFilter is called when server broser menu is opened and when a favorite server is deleted. + UI_StartServerRefresh(qtrue, qfalse); UI_BuildServerDisplayList(qtrue); UI_FeederSelection(FEEDER_SERVERS, 0); } else if (Q_stricmp(name, "ServerStatus") == 0) { @@ -3661,6 +3664,11 @@ static void UI_InsertServerIntoDisplayList(int num, int position) { uiInfo.serverStatus.displayServers[i] = uiInfo.serverStatus.displayServers[i-1]; } uiInfo.serverStatus.displayServers[position] = num; + + // update displayed levelshot + if ( position == uiInfo.serverStatus.currentServer ) { + UI_FeederSelection( FEEDER_SERVERS, uiInfo.serverStatus.currentServer ); + } } /* @@ -3786,15 +3794,22 @@ static void UI_BuildServerDisplayList(int force) { // get the ping for this server ping = trap_LAN_GetServerPing(lanSource, i); if (ping > 0 || ui_netSource.integer == UIAS_FAVORITES) { + // Remove favorite servers so they do not appear multiple times + // or appear when the cached server info was not filtered out + // but the new server info is filtered out. + if (ui_netSource.integer == UIAS_FAVORITES) { + UI_RemoveServerFromDisplayList(i); + } trap_LAN_GetServerInfo(lanSource, i, info, MAX_STRING_CHARS); clients = atoi(Info_ValueForKey(info, "clients")); - uiInfo.serverStatus.numPlayersOnServers += clients; if (ui_browserShowEmpty.integer == 0) { if (clients == 0) { - trap_LAN_MarkServerVisible(lanSource, i, qfalse); + if (ping > 0) { + trap_LAN_MarkServerVisible(lanSource, i, qfalse); + } continue; } } @@ -3802,7 +3817,9 @@ static void UI_BuildServerDisplayList(int force) { if (ui_browserShowFull.integer == 0) { maxClients = atoi(Info_ValueForKey(info, "sv_maxclients")); if (clients == maxClients) { - trap_LAN_MarkServerVisible(lanSource, i, qfalse); + if (ping > 0) { + trap_LAN_MarkServerVisible(lanSource, i, qfalse); + } continue; } } @@ -3810,26 +3827,27 @@ static void UI_BuildServerDisplayList(int force) { if (uiInfo.joinGameTypes[ui_joinGameType.integer].gtEnum != -1) { game = atoi(Info_ValueForKey(info, "gametype")); if (game != uiInfo.joinGameTypes[ui_joinGameType.integer].gtEnum) { - trap_LAN_MarkServerVisible(lanSource, i, qfalse); + if (ping > 0) { + trap_LAN_MarkServerVisible(lanSource, i, qfalse); + } continue; } } if (ui_serverFilterType.integer > 0) { if (Q_stricmp(Info_ValueForKey(info, "game"), serverFilters[ui_serverFilterType.integer].basedir) != 0) { - trap_LAN_MarkServerVisible(lanSource, i, qfalse); + if (ping > 0) { + trap_LAN_MarkServerVisible(lanSource, i, qfalse); + } continue; } } - // make sure we never add a favorite server twice - if (ui_netSource.integer == UIAS_FAVORITES) { - UI_RemoveServerFromDisplayList(i); - } // insert the server into the list UI_BinaryServerInsertion(i); // done with this server if (ping > 0) { trap_LAN_MarkServerVisible(lanSource, i, qfalse); + uiInfo.serverStatus.numPlayersOnServers += clients; numinvisible++; } } @@ -3907,11 +3925,21 @@ static int UI_GetServerStatusInfo( const char *serverAddress, serverStatusInfo_t char *p, *score, *ping, *name; int i, len; + if (info) { + memset(info, 0, sizeof(*info)); + } + + // ignore initial unset addresses + if (serverAddress && *serverAddress == '\0') { + return qfalse; + } + + // reset server status request for this address if (!info) { trap_LAN_ServerStatus( serverAddress, NULL, 0); return qfalse; } - memset(info, 0, sizeof(*info)); + if ( trap_LAN_ServerStatus( serverAddress, info->text, sizeof(info->text)) ) { Q_strncpyz(info->address, serverAddress, sizeof(info->address)); p = info->text; @@ -4526,7 +4554,7 @@ static qboolean Team_Parse(char **p) { return qtrue; } - if ( !token || token[0] == 0 ) { + if (!token[0]) { return qfalse; } @@ -4587,7 +4615,7 @@ static qboolean Character_Parse(char **p) { return qtrue; } - if ( !token || token[0] == 0 ) { + if (!token[0]) { return qfalse; } @@ -4643,7 +4671,7 @@ static qboolean Alias_Parse(char **p) { return qtrue; } - if ( !token || token[0] == 0 ) { + if (!token[0]) { return qfalse; } @@ -4692,7 +4720,7 @@ static void UI_ParseTeamInfo(const char *teamFile) { while ( 1 ) { token = COM_ParseExt( &p, qtrue ); - if( !token || token[0] == 0 || token[0] == '}') { + if (!token[0] || token[0] == '}') { break; } @@ -4744,7 +4772,7 @@ static qboolean GameType_Parse(char **p, qboolean join) { return qtrue; } - if ( !token || token[0] == 0 ) { + if (!token[0]) { return qfalse; } @@ -4801,7 +4829,7 @@ static qboolean MapList_Parse(char **p) { return qtrue; } - if ( !token || token[0] == 0 ) { + if (!token[0]) { return qfalse; } @@ -4862,7 +4890,7 @@ static void UI_ParseGameInfo(const char *teamFile) { while ( 1 ) { token = COM_ParseExt( &p, qtrue ); - if( !token || token[0] == 0 || token[0] == '}') { + if (!token[0] || token[0] == '}') { break; } @@ -4947,6 +4975,15 @@ static void UI_StopCinematic(int handle) { } static void UI_DrawCinematic(int handle, float x, float y, float w, float h) { + // adjust coords to get correct placement in wide screen + UI_AdjustFrom640( &x, &y, &w, &h ); + + // CIN_SetExtents takes stretched 640x480 virtualized coords + x *= SCREEN_WIDTH / (float)uiInfo.uiDC.glconfig.vidWidth; + w *= SCREEN_WIDTH / (float)uiInfo.uiDC.glconfig.vidWidth; + y *= SCREEN_HEIGHT / (float)uiInfo.uiDC.glconfig.vidHeight; + h *= SCREEN_HEIGHT / (float)uiInfo.uiDC.glconfig.vidHeight; + trap_CIN_SetExtents(handle, x, y, w, h); trap_CIN_DrawCinematic(handle); } @@ -5052,6 +5089,7 @@ void _UI_Init( qboolean inGameLoad ) { if ( uiInfo.uiDC.glconfig.vidWidth * 480 > uiInfo.uiDC.glconfig.vidHeight * 640 ) { // wide screen uiInfo.uiDC.bias = 0.5 * ( uiInfo.uiDC.glconfig.vidWidth - ( uiInfo.uiDC.glconfig.vidHeight * (640.0/480.0) ) ); + uiInfo.uiDC.xscale = uiInfo.uiDC.yscale; } else { // no wide screen @@ -5130,6 +5168,7 @@ void _UI_Init( qboolean inGameLoad ) { UI_ParseTeamInfo("teaminfo.txt"); UI_LoadTeams(); UI_ParseGameInfo("gameinfo.txt"); + UI_LoadArenas(); #endif menuSet = UI_Cvar_VariableString("ui_menuFiles"); @@ -5157,7 +5196,10 @@ void _UI_Init( qboolean inGameLoad ) { // sets defaults for ui temp cvars uiInfo.effectsColor = gamecodetoui[(int)trap_Cvar_VariableValue("color1")-1]; - uiInfo.currentCrosshair = (int)trap_Cvar_VariableValue("cg_drawCrosshair"); + uiInfo.currentCrosshair = (int)trap_Cvar_VariableValue("cg_drawCrosshair") % NUM_CROSSHAIRS; + if (uiInfo.currentCrosshair < 0) { + uiInfo.currentCrosshair = 0; + } trap_Cvar_Set("ui_mousePitch", (trap_Cvar_VariableValue("m_pitch") >= 0) ? "0" : "1"); uiInfo.serverStatus.currentServerCinematic = -1; @@ -5209,12 +5251,17 @@ UI_MouseEvent */ void _UI_MouseEvent( int dx, int dy ) { + int bias; + + // convert X bias to 640 coords + bias = uiInfo.uiDC.bias / uiInfo.uiDC.xscale; + // update mouse screen position uiInfo.uiDC.cursorx += dx; - if (uiInfo.uiDC.cursorx < 0) - uiInfo.uiDC.cursorx = 0; - else if (uiInfo.uiDC.cursorx > SCREEN_WIDTH) - uiInfo.uiDC.cursorx = SCREEN_WIDTH; + if (uiInfo.uiDC.cursorx < -bias) + uiInfo.uiDC.cursorx = -bias; + else if (uiInfo.uiDC.cursorx > SCREEN_WIDTH+bias) + uiInfo.uiDC.cursorx = SCREEN_WIDTH+bias; uiInfo.uiDC.cursory += dy; if (uiInfo.uiDC.cursory < 0) @@ -5783,7 +5830,7 @@ static cvarTable_t cvarTable[] = { { &ui_blueteam3, "ui_blueteam3", "0", CVAR_ARCHIVE }, { &ui_blueteam4, "ui_blueteam4", "0", CVAR_ARCHIVE }, { &ui_blueteam5, "ui_blueteam5", "0", CVAR_ARCHIVE }, - { &ui_netSource, "ui_netSource", "0", CVAR_ARCHIVE }, + { &ui_netSource, "ui_netSource", "1", CVAR_ARCHIVE }, { &ui_menuFiles, "ui_menuFiles", "ui/menus.txt", CVAR_ARCHIVE }, { &ui_currentTier, "ui_currentTier", "0", CVAR_ARCHIVE }, { &ui_currentMap, "ui_currentMap", "0", CVAR_ARCHIVE }, @@ -5828,6 +5875,7 @@ static cvarTable_t cvarTable[] = { { &ui_realCaptureLimit, "capturelimit", "8", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART}, { &ui_serverStatusTimeOut, "ui_serverStatusTimeOut", "7000", CVAR_ARCHIVE}, + { NULL, "g_localTeamPref", "", 0 }, }; static int cvarTableSize = ARRAY_LEN( cvarTable ); @@ -5857,6 +5905,10 @@ void UI_UpdateCvars( void ) { cvarTable_t *cv; for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) { + if ( !cv->vmCvar ) { + continue; + } + trap_Cvar_Update( cv->vmCvar ); } } @@ -5943,6 +5995,10 @@ static void UI_DoServerRefresh( void ) UI_BuildServerDisplayList(2); // stop the refresh UI_StopServerRefresh(); + } else if ( ui_netSource.integer == UIAS_LOCAL ) { + // no local servers found, check again + trap_Cmd_ExecuteText( EXEC_NOW, "localservers\n" ); + uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 5000; } // UI_BuildServerDisplayList(qfalse); @@ -5953,12 +6009,21 @@ static void UI_DoServerRefresh( void ) UI_StartServerRefresh ================= */ -static void UI_StartServerRefresh(qboolean full) +static void UI_StartServerRefresh(qboolean full, qboolean force) { char *ptr; int lanSource; - qtime_t q; + + // This function is called with force=qfalse when server browser menu opens or net source changes. + // Automatically update local and favorite servers. + // Only auto update master server list if there is no server info cache. + if ( !force && ( ui_netSource.integer >= UIAS_GLOBAL0 && ui_netSource.integer <= UIAS_GLOBAL5 ) ) { + if ( trap_LAN_GetServerCount( UI_SourceForLAN() ) > 0 ) { + return; // have cached list + } + } + trap_RealTime(&q); trap_Cvar_Set( va("ui_lastServerRefresh_%i", ui_netSource.integer), va("%s-%i, %i at %i:%i", MonthAbbrev[q.tm_mon],q.tm_mday, 1900+q.tm_year,q.tm_hour,q.tm_min)); @@ -5981,19 +6046,19 @@ static void UI_StartServerRefresh(qboolean full) // if( ui_netSource.integer == UIAS_LOCAL ) { trap_Cmd_ExecuteText( EXEC_NOW, "localservers\n" ); - uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000; + uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 5000; return; } uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 5000; - if( ui_netSource.integer >= UIAS_GLOBAL1 && ui_netSource.integer <= UIAS_GLOBAL5 ) { + if( ui_netSource.integer >= UIAS_GLOBAL0 && ui_netSource.integer <= UIAS_GLOBAL5 ) { ptr = UI_Cvar_VariableString("debug_protocol"); if (strlen(ptr)) { - trap_Cmd_ExecuteText( EXEC_NOW, va( "globalservers %d %s full empty\n", ui_netSource.integer-1, ptr)); + trap_Cmd_ExecuteText( EXEC_NOW, va( "globalservers %d %s full empty\n", ui_netSource.integer - UIAS_GLOBAL0, ptr ) ); } else { - trap_Cmd_ExecuteText( EXEC_NOW, va( "globalservers %d %d full empty\n", ui_netSource.integer-1, (int)trap_Cvar_VariableValue( "protocol" ) ) ); + trap_Cmd_ExecuteText( EXEC_NOW, va( "globalservers %d %d full empty\n", ui_netSource.integer - UIAS_GLOBAL0, (int)trap_Cvar_VariableValue( "protocol" ) ) ); } } } diff --git a/engine/code/ui/ui_players.c b/engine/code/ui/ui_players.c index 53475d31..7f7ff0e7 100644 --- a/engine/code/ui/ui_players.c +++ b/engine/code/ui/ui_players.c @@ -364,7 +364,7 @@ UI_RunLerpFrame =============== */ static void UI_RunLerpFrame( playerInfo_t *ci, lerpFrame_t *lf, int newAnimation ) { - int f; + int f, numFrames; animation_t *anim; // see if the animation sequence is switching @@ -380,25 +380,41 @@ static void UI_RunLerpFrame( playerInfo_t *ci, lerpFrame_t *lf, int newAnimation // get the next frame based on the animation anim = lf->animation; + if ( !anim->frameLerp ) { + return; // shouldn't happen + } if ( dp_realtime < lf->animationTime ) { lf->frameTime = lf->animationTime; // initial lerp } else { lf->frameTime = lf->oldFrameTime + anim->frameLerp; } f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; - if ( f >= anim->numFrames ) { - f -= anim->numFrames; + + numFrames = anim->numFrames; + if (anim->flipflop) { + numFrames *= 2; + } + if ( f >= numFrames ) { + f -= numFrames; if ( anim->loopFrames ) { f %= anim->loopFrames; f += anim->numFrames - anim->loopFrames; } else { - f = anim->numFrames - 1; + f = numFrames - 1; // the animation is stuck at the end, so it // can immediately transition to another sequence lf->frameTime = dp_realtime; } } - lf->frame = anim->firstFrame + f; + if ( anim->reversed ) { + lf->frame = anim->firstFrame + anim->numFrames - 1 - f; + } + else if (anim->flipflop && f>=anim->numFrames) { + lf->frame = anim->firstFrame + anim->numFrames - 1 - (f%anim->numFrames); + } + else { + lf->frame = anim->firstFrame + f; + } if ( dp_realtime > lf->frameTime ) { lf->frameTime = dp_realtime; } @@ -616,6 +632,16 @@ static void UI_PlayerAngles( playerInfo_t *pi, vec3_t legs[3], vec3_t torso[3], UI_SwingAngles( dest, 15, 30, 0.1f, &pi->torso.pitchAngle, &pi->torso.pitching ); torsoAngles[PITCH] = pi->torso.pitchAngle; + if ( pi->fixedtorso ) { + torsoAngles[PITCH] = 0.0f; + } + + if ( pi->fixedlegs ) { + legsAngles[YAW] = torsoAngles[YAW]; + legsAngles[PITCH] = 0.0f; + legsAngles[ROLL] = 0.0f; + } + // pull the angles back out of the hierarchial chain AnglesSubtract( headAngles, torsoAngles, headAngles ); AnglesSubtract( torsoAngles, legsAngles, torsoAngles ); @@ -1015,7 +1041,7 @@ static qboolean UI_RegisterClientSkin( playerInfo_t *pi, const char *modelName, UI_ParseAnimationFile ====================== */ -static qboolean UI_ParseAnimationFile( const char *filename, animation_t *animations ) { +static qboolean UI_ParseAnimationFile( const char *filename, playerInfo_t *pi ) { char *text_p, *prev; int len; int i; @@ -1024,9 +1050,15 @@ static qboolean UI_ParseAnimationFile( const char *filename, animation_t *animat int skip; char text[20000]; fileHandle_t f; + animation_t *animations; + + animations = pi->animations; memset( animations, 0, sizeof( animation_t ) * MAX_ANIMATIONS ); + pi->fixedlegs = qfalse; + pi->fixedtorso = qfalse; + // load the file len = trap_FS_FOpenFile( filename, &f, FS_READ ); if ( len <= 0 ) { @@ -1051,29 +1083,35 @@ static qboolean UI_ParseAnimationFile( const char *filename, animation_t *animat while ( 1 ) { prev = text_p; // so we can unget token = COM_Parse( &text_p ); - if ( !token ) { + if ( !token[0] ) { break; } if ( !Q_stricmp( token, "footsteps" ) ) { token = COM_Parse( &text_p ); - if ( !token ) { + if ( !token[0] ) { break; } continue; } else if ( !Q_stricmp( token, "headoffset" ) ) { for ( i = 0 ; i < 3 ; i++ ) { token = COM_Parse( &text_p ); - if ( !token ) { + if ( !token[0] ) { break; } } continue; } else if ( !Q_stricmp( token, "sex" ) ) { token = COM_Parse( &text_p ); - if ( !token ) { + if ( !token[0] ) { break; } continue; + } else if ( !Q_stricmp( token, "fixedlegs" ) ) { + pi->fixedlegs = qtrue; + continue; + } else if ( !Q_stricmp( token, "fixedtorso" ) ) { + pi->fixedtorso = qtrue; + continue; } // if it is a number, start parsing animations @@ -1089,7 +1127,17 @@ static qboolean UI_ParseAnimationFile( const char *filename, animation_t *animat for ( i = 0 ; i < MAX_ANIMATIONS ; i++ ) { token = COM_Parse( &text_p ); - if ( !token ) { + if ( !token[0] ) { + if( i >= TORSO_GETFLAG && i <= TORSO_NEGATIVE ) { + animations[i].firstFrame = animations[TORSO_GESTURE].firstFrame; + animations[i].frameLerp = animations[TORSO_GESTURE].frameLerp; + animations[i].initialLerp = animations[TORSO_GESTURE].initialLerp; + animations[i].loopFrames = animations[TORSO_GESTURE].loopFrames; + animations[i].numFrames = animations[TORSO_GESTURE].numFrames; + animations[i].reversed = qfalse; + animations[i].flipflop = qfalse; + continue; + } break; } animations[i].firstFrame = atoi( token ); @@ -1097,24 +1145,32 @@ static qboolean UI_ParseAnimationFile( const char *filename, animation_t *animat if ( i == LEGS_WALKCR ) { skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame; } - if ( i >= LEGS_WALKCR ) { + if ( i >= LEGS_WALKCR && ianimations ) ) { + if ( !UI_ParseAnimationFile( filename, pi ) ) { Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/animation.cfg", modelName ); - if ( !UI_ParseAnimationFile( filename, pi->animations ) ) { + if ( !UI_ParseAnimationFile( filename, pi ) ) { Com_Printf( "Failed to load animation file %s\n", filename ); return qfalse; } diff --git a/engine/code/ui/ui_shared.c b/engine/code/ui/ui_shared.c index c95d386f..e574f78d 100644 --- a/engine/code/ui/ui_shared.c +++ b/engine/code/ui/ui_shared.c @@ -198,6 +198,9 @@ const char *String_Alloc(const char *p) { } str = UI_Alloc(sizeof(stringDef_t)); + if (!str) { + return NULL; + } str->next = NULL; str->str = &strPool[ph]; if (last) { @@ -1121,10 +1124,10 @@ void Menu_TransitionItemByName(menuDef_t *menu, const char *p, rectDef_t rectFro item->window.offsetTime = time; memcpy(&item->window.rectClient, &rectFrom, sizeof(rectDef_t)); memcpy(&item->window.rectEffects, &rectTo, sizeof(rectDef_t)); - item->window.rectEffects2.x = abs(rectTo.x - rectFrom.x) / amt; - item->window.rectEffects2.y = abs(rectTo.y - rectFrom.y) / amt; - item->window.rectEffects2.w = abs(rectTo.w - rectFrom.w) / amt; - item->window.rectEffects2.h = abs(rectTo.h - rectFrom.h) / amt; + item->window.rectEffects2.x = fabs(rectTo.x - rectFrom.x) / amt; + item->window.rectEffects2.y = fabs(rectTo.y - rectFrom.y) / amt; + item->window.rectEffects2.w = fabs(rectTo.w - rectFrom.w) / amt; + item->window.rectEffects2.h = fabs(rectTo.h - rectFrom.h) / amt; Item_UpdatePosition(item); } } @@ -1828,6 +1831,27 @@ qboolean Item_ListBox_HandleKey(itemDef_t *item, int key, qboolean down, qboolea return qtrue; } } + + // Use mouse wheel in vertical and horizontal menus. + // If scrolling 3 items would replace over half of the + // displayed items, only scroll 1 item at a time. + if ( key == K_MWHEELUP ) { + int scroll = viewmax < 6 ? 1 : 3; + listPtr->startPos -= scroll; + if (listPtr->startPos < 0) { + listPtr->startPos = 0; + } + return qtrue; + } + if ( key == K_MWHEELDOWN ) { + int scroll = viewmax < 6 ? 1 : 3; + listPtr->startPos += scroll; + if (listPtr->startPos > max) { + listPtr->startPos = max; + } + return qtrue; + } + // mouse hit if (key == K_MOUSE1 || key == K_MOUSE2) { if (item->window.flags & WINDOW_LB_LEFTARROW) { @@ -4366,7 +4390,7 @@ typedef struct keywordHash_s } keywordHash_t; int KeywordHash_Key(char *keyword) { - int register hash, i; + int hash, i; hash = 0; for (i = 0; keyword[i] != '\0'; i++) { diff --git a/engine/opengl2-readme.md b/engine/opengl2-readme.md index 857a4600..1b1db203 100644 --- a/engine/opengl2-readme.md +++ b/engine/opengl2-readme.md @@ -136,7 +136,7 @@ Cvars for HDR and tonemapping: r_hdr, r_postprocess, and r_toneMap. 0 - No. 1 - Yes. (default) - + * `r_forceAutoExposure` - Cheat. Override built-in and map auto exposure settings and use cvars r_forceAutoExposureMin and @@ -252,7 +252,7 @@ Cvars for image interpolation and generation: FCBI without second derivatives) 2 - Okay but slow (normal FCBI) -* `r_genNormalMaps* - Naively generate normal maps for all +* `r_genNormalMaps` - Naively generate normal maps for all textures. 0 - Don't. (default) 1 - Do. @@ -302,23 +302,6 @@ Cvars for the sunlight and cascaded shadow maps: Cvars that you probably don't care about or shouldn't mess with: -* `r_mergeMultidraws` - Optimize number of calls to - glMultiDrawElements(). - 0 - Don't. - 1 - Do some. (default) - 2 - Do more than necessary (eats CPU). - -* `r_mergeLeafSurfaces` - Merge surfaces that share common materials - and a common leaf. Speeds up rendering. - 0 - Don't. - 1 - Do. (default) - -* `r_recalcMD3Normals` - Recalculate the normals when loading an MD3. - Fixes normal maps in some cases but looks - ugly in others. - 0 - Don't. (default) - 1 - Do. - * `r_depthPrepass` - Do a depth-only pass before rendering. Speeds up rendering in cases where advanced features are used. Required for diff --git a/examples/game_main.c b/examples/game_main.c index f84d0adf..e6dbfd50 100644 --- a/examples/game_main.c +++ b/examples/game_main.c @@ -64,31 +64,36 @@ static const char kUsage[] = " available to the level script. This flag may be provided\n" " multiple times.\n" " -e, --num_episodes: The number of episodes to play. Defaults to 1.\n" + " -p, --print_events: Print events emitted.\n" " -r, --random_seed: A seed value used for randomly generated content; using\n" " the same seed should result in the same content. Defaults\n" " to a fixed value.\n" ; static void process_commandline(int argc, char** argv, EnvCApi* env_c_api, - void* context, int* num_episodes, int* seed) { + void* context, int* num_episodes, int* seed, + bool* log_events) { static struct option long_options[] = { {"help", no_argument, NULL, 'h'}, {"level_script", required_argument, NULL, 'l'}, {"level_setting", required_argument, NULL, 's'}, {"num_episodes", required_argument, NULL, 'e'}, {"random_seed", required_argument, NULL, 'r'}, + {"print_events", no_argument, NULL, 'p'}, {NULL, 0, NULL, 0}}; char *key, *value; - for (int c; (c = getopt_long(argc, argv, "hl:s:e:r:", long_options, 0)) != -1;) { + for (int c; + (c = getopt_long(argc, argv, "hl:s:e:r:p", long_options, 0)) != -1;) { switch (c) { case 'h': fputs(kUsage, stdout); exit(EXIT_SUCCESS); case 'l': if (env_c_api->setting(context, "levelName", optarg) != 0) { - sys_error("Invalid levelName flag '%s'.", optarg); + sys_error("Invalid level_script '%s'. Internal error: %s", optarg, + env_c_api->error_message(context)); } break; case 's': @@ -100,7 +105,8 @@ static void process_commandline(int argc, char** argv, EnvCApi* env_c_api, value[0] = '\0'; ++value; if (env_c_api->setting(context, key, value) != 0) { - sys_error("Failed to apply setting '%s = %s'.", key, value); + sys_error("Invalid level_setting '%s=%s'. Internal error: %s", + key, value, env_c_api->error_message(context)); } break; case 'e': @@ -113,6 +119,9 @@ static void process_commandline(int argc, char** argv, EnvCApi* env_c_api, sys_error("Failed to set random_seed to '%s'.", optarg); } break; + case 'p': + *log_events = true; + break; case ':': case '?': default: @@ -121,11 +130,67 @@ static void process_commandline(int argc, char** argv, EnvCApi* env_c_api, } } +// Prints events to stdout. Returns number printed. +static int print_events(EnvCApi* env_c_api, void* context) { + int event_count = env_c_api->event_count(context); + for (int e = 0; e < event_count; ++e) { + EnvCApi_Event event; + env_c_api->event(context, e, &event); + printf("Event %d: \"%s\" - ", e, + env_c_api->event_type_name(context, event.id)); + for (int obs_id = 0; obs_id < event.observation_count; ++obs_id) { + if (obs_id != 0) { + fputs(", ", stdout); + } + const EnvCApi_Observation* obs = &event.observations[obs_id]; + switch (obs->spec.type) { + case EnvCApi_ObservationString: + printf("\"%.*s\"", obs->spec.shape[0], + obs->payload.string); + break; + case EnvCApi_ObservationDoubles: + if (obs->spec.dims == 1) { + if (obs->spec.shape[0] == 1) { + printf("%f", obs->payload.doubles[0]); + break; + } else if (obs->spec.shape[0] < 6) { + fputs("{", stdout); + for (int i = 0; i < obs->spec.shape[0]; ++i) { + if (i != 0) fputs(", ", stdout); + printf("%f", obs->payload.doubles[0]); + } + fputs("}", stdout); + break; + } + } + fputs("spec.dims; ++i) { + if (i != 0) fputs("x", stdout); + printf("%d", obs->spec.shape[i]); + } + fputs(">", stdout); + break; + case EnvCApi_ObservationBytes: + fputs("spec.dims; ++i) { + if (i != 0) fputs("x", stdout); + printf("%d", obs->spec.shape[i]); + } + fputs(">", stdout); + break; + } + } + fputs("\n", stdout); + } + return event_count; +} + int main(int argc, char** argv) { static const char kRunfiles[] = ".runfiles/org_deepmind_lab"; static EnvCApi env_c_api; static void* context; static char runfiles_path[4096]; + bool log_events = false; if (sizeof(runfiles_path) < strlen(argv[0]) + sizeof(kRunfiles)) { sys_error("Runfiles directory name too long!"); @@ -133,52 +198,75 @@ int main(int argc, char** argv) { strcpy(runfiles_path, argv[0]); strcat(runfiles_path, kRunfiles); - DeepMindLabLaunchParams params; + DeepMindLabLaunchParams params = {}; params.runfiles_path = runfiles_path; + if (dmlab_connect(¶ms, &env_c_api, &context) != 0) { sys_error("Failed to connect RL API"); } if (env_c_api.setting(context, "width", "640") != 0) { - sys_error("Failed to apply default 'width' setting."); + sys_error("Failed to apply default 'width' setting. Internal error: %s", + env_c_api.error_message(context)); } if (env_c_api.setting(context, "height", "480") != 0) { - sys_error("Failed to apply default 'height' setting."); + sys_error("Failed to apply default 'height' setting. Internal error: %s", + env_c_api.error_message(context)); } - if (env_c_api.setting(context, "controls", "internal") != 0) { - sys_error("Failed to apply 'controls' setting."); + if (env_c_api.setting(context, "nativeApp", "true") != 0) { + sys_error("Failed to apply 'nativeApp' setting. Internal error: %s", + env_c_api.error_message(context)); } if (env_c_api.setting(context, "appendCommand", " +set com_maxfps \"250\"") != 0) { - sys_error("Failed to apply 'appendCommand' setting."); + sys_error("Failed to apply 'appendCommand' setting. Internal error: %s", + env_c_api.error_message(context)); } int num_episodes = 1; int seed = 1; - process_commandline(argc, argv, &env_c_api, context, &num_episodes, &seed); + process_commandline(argc, argv, &env_c_api, context, &num_episodes, &seed, + &log_events); if (env_c_api.init(context) != 0) { - sys_error("Failed to init RL API"); + sys_error("Failed to init RL API: %s", env_c_api.error_message(context)); } - for (int episode = 0; episode < num_episodes; ++episode, ++seed) { + EnvCApi_EnvironmentStatus status = EnvCApi_EnvironmentStatus_Running; + for (int episode = 0; + episode < num_episodes && status != EnvCApi_EnvironmentStatus_Error; + ++episode, ++seed) { if (env_c_api.start(context, episode, seed) != 0) { - sys_error("Failed to start environment."); + sys_error("Failed to start environment. Internal error: %s", + env_c_api.error_message(context)); } printf("Episode: %d\n", episode); + if (log_events && print_events(&env_c_api, context) != 0) { + fflush(stdout); + } double score = 0; double reward; - while (env_c_api.advance(context, 1, &reward) == - EnvCApi_EnvironmentStatus_Running) { + + while (EnvCApi_EnvironmentStatus_Running == + (status = env_c_api.advance(context, 1, &reward))) { + int event_count = log_events ? print_events(&env_c_api, context) : 0; if (reward != 0.0) { score += reward; printf("Score: %f\n", score); + } + if (event_count != 0 || reward != 0.0) { fflush(stdout); } } + if (log_events && print_events(&env_c_api, context) != 0) { + fflush(stdout); + } + } + if (status == EnvCApi_EnvironmentStatus_Error) { + sys_error("%s", env_c_api.error_message(context)); } env_c_api.release_context(context); diff --git a/examples/game_main.py b/examples/game_main.py new file mode 100644 index 00000000..d6e529d7 --- /dev/null +++ b/examples/game_main.py @@ -0,0 +1,92 @@ +## Copyright (C) 2016-17 Google Inc. +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 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., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +################################################################################ +"""A working example of deepmind_lab using python.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import argparse +import pprint +import sys +import numpy as np + +import deepmind_lab + + +def run(level_script, config, num_episodes): + """Construct and start the environment.""" + env = deepmind_lab.Lab(level_script, ['RGB_INTERLACED'], config) + env.reset() + + observation_spec = env.observation_spec() + print('Observation spec:') + pprint.pprint(observation_spec) + + action_spec = env.action_spec() + print('Action spec:') + pprint.pprint(action_spec) + + obs = env.observations() # dict of Numpy arrays + rgb_i = obs['RGB_INTERLACED'] + print('Observation shape:', rgb_i.shape) + sys.stdout.flush() + + # Create an action to move forwards. + action = np.zeros([7], dtype=np.intc) + action[3] = 1 + + score = 0 + for _ in xrange(num_episodes): + while env.is_running(): + # Advance the environment 4 frames while executing the action. + reward = env.step(action, num_steps=4) + + if reward != 0: + score += reward + print('Score =', score) + sys.stdout.flush() + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('-l', '--level_script', type=str, + default='seekavoid_arena_01', + help='The level that is to be played. Levels' + 'are Lua scripts, and a script called \"name\" means that' + 'a file \"assets/game_scripts/name.lua is loaded.') + parser.add_argument('-s', '--level_settings', type=str, default=None, + action='append', + help='Applies an opaque key-value setting. The setting is' + 'available to the level script. This flag may be provided' + 'multiple times. Universal settings are `width` and ' + '`height` which give the screen size in pixels, ' + '`fps` which gives the frames per second, and ' + '`random_seed` which can be specified to ensure the ' + 'same content is generated on every run.') + parser.add_argument('--runfiles_path', type=str, default=None, + help='Set the runfiles path to find DeepMind Lab data') + parser.add_argument('--num_episodes', type=int, default=1, + help='The number of episodes to play.') + args = parser.parse_args() + + # Convert list of level setting strings (of the form "key=value") into a + # `config` key/value dictionary. + config = {k:v for k, v in [s.split('=') for s in args.level_settings]} + + if args.runfiles_path: + deepmind_lab.set_runfiles_path(args.runfiles_path) + run(args.level_script, config, args.num_episodes) diff --git a/googletest.BUILD b/googletest.BUILD deleted file mode 100644 index 6d3c5941..00000000 --- a/googletest.BUILD +++ /dev/null @@ -1,28 +0,0 @@ -cc_library( - name = "gtest", - srcs = [ - "googlemock/src/gmock-all.cc", - "googletest/src/gtest-all.cc", - ], - hdrs = glob([ - "**/*.h", - "googletest/src/*.cc", - "googlemock/src/*.cc", - ]), - includes = [ - "googlemock", - "googlemock/include", - "googletest", - "googletest/include", - ], - linkopts = ["-pthread"], - visibility = ["//visibility:public"], -) - -cc_library( - name = "gtest_main", - srcs = ["googlemock/src/gmock_main.cc"], - linkopts = ["-pthread"], - visibility = ["//visibility:public"], - deps = [":gtest"], -) diff --git a/lua_tests/BUILD b/lua_tests/BUILD new file mode 100644 index 00000000..509515d8 --- /dev/null +++ b/lua_tests/BUILD @@ -0,0 +1,56 @@ +# Description: +# Unit tests for DeepMind Lab. + +licenses(["restricted"]) # GPLv2 + +TEST_SCRIPTS = [ + test_script[:-len(".lua")] + for test_script in glob(["*.lua"]) +] + +# Tests that need custom data. +SKIPPED_TESTS = [ + "image_test", + "read_file_test", +] + +test_suite( + name = "lua_unit_test_suite", + tests = [test_script for test_script in TEST_SCRIPTS if test_script not in SKIPPED_TESTS], +) + +[ + cc_test( + name = test_script, + args = ["lua_tests/" + test_script + ".lua"], + data = [test_script + ".lua"], + deps = ["//testing:lua_unit_test_lib"], + ) + for test_script in TEST_SCRIPTS + if test_script not in SKIPPED_TESTS +] + +cc_test( + name = "image_test", + size = "small", + args = ["lua_tests/image_test.lua"], + data = [ + "data/testL.png", + "data/testRGB.png", + "data/testRGBA.png", + ":image_test.lua", + ], + deps = ["//testing:lua_unit_test_lib"], +) + +cc_test( + name = "read_file_test", + size = "small", + args = ["lua_tests/read_file_test.lua"], + data = [ + "data/empty_test_file", + "data/testL.png", + ":read_file_test.lua", + ], + deps = ["//testing:lua_unit_test_lib"], +) diff --git a/lua_tests/asserts_test.lua b/lua_tests/asserts_test.lua new file mode 100644 index 00000000..3a58e194 --- /dev/null +++ b/lua_tests/asserts_test.lua @@ -0,0 +1,281 @@ +local asserts = require 'testing.asserts' +local test_runner = require 'testing.test_runner' + +local tests = {} + +local function shouldFail(fn) + local status, out = pcall(fn) + if status then + error("Expected assert to fire but it did not.", 2) + end +end + +local function shouldFailWithMessage(fn, message) + local status, out = pcall(fn) + if status then + error("Expected assert to fire but it did not.", 2) + end + if type(message) ~= 'table' then message = {message} end + for _, expected in ipairs(message) do + if not out:find(expected) then + error('Expected "' .. expected .. '" to appear in "' .. out .. '"', 2) + end + end +end + + +function tests.EQ_expectSuccess() + asserts.EQ(7, 7) + asserts.EQ(7, 7, 'message') + asserts.EQ(true, true) + asserts.EQ(false, false) + asserts.EQ("a string", "a string") + asserts.EQ(nil, nil) + local tbl = {2, 4, 6} + asserts.EQ(tbl, tbl) +end + +function tests.EQ_expectFailure() + shouldFail(function () asserts.EQ(7, 8) end) + shouldFail(function () asserts.EQ(true, false) end) + shouldFail(function () asserts.EQ(false, true) end) + shouldFail(function () asserts.EQ("a string", "a different string") end) + shouldFail(function () asserts.EQ(nil, {}) end) + + local tbl = {2, 4, 6} + shouldFail(function () asserts.EQ({2, 4, 6}, {2, 4, 6}) end) + + shouldFail(function () asserts.EQ(7, true) end) + shouldFail(function () asserts.EQ(true, 7) end) + shouldFail(function () asserts.EQ(7, false) end) + shouldFail(function () asserts.EQ(false, 7) end) + shouldFail(function () asserts.EQ(7, "a string") end) + shouldFail(function () asserts.EQ("a string", 7) end) + shouldFail(function () asserts.EQ(7, {2, 4, 6}) end) + shouldFail(function () asserts.EQ({2, 4, 6}, 7) end) + + shouldFail(function () asserts.EQ(true, "a string") end) + shouldFail(function () asserts.EQ("a string", true) end) + shouldFail(function () asserts.EQ(true, {2, 4, 6}) end) + shouldFail(function () asserts.EQ({2, 4, 6}, true) end) + + shouldFail(function () asserts.EQ(false, "a string") end) + shouldFail(function () asserts.EQ("a string", false) end) + shouldFail(function () asserts.EQ(false, {2, 4, 6}) end) + shouldFail(function () asserts.EQ({2, 4, 6}, false) end) + + shouldFail(function () asserts.EQ("a string", {2, 4, 6}) end) + shouldFail(function () asserts.EQ({2, 4, 6}, "a string") end) + + + shouldFailWithMessage(function () asserts.EQ(7, 8, 'monkey') end, + {'7', '8', 'monkey'}) +end + + +function tests.NE_expectSuccess() + asserts.NE(7, 8) + asserts.NE(true, false) + asserts.NE(false, true) + asserts.NE("a string", "a different string") + asserts.NE(nil, {}) + asserts.NE({2, 4, 6}, {2, 4, 6}) + + asserts.NE(7, true) + asserts.NE(true, 7) + asserts.NE(7, false) + asserts.NE(false, 7) + asserts.NE(7, "a string") + asserts.NE("a string", 7) + asserts.NE(7, {2, 4, 6}) + asserts.NE({2, 4, 6}, 7) + + asserts.NE(true, "a string") + asserts.NE("a string", true) + asserts.NE(true, {2, 4, 6}) + asserts.NE({2, 4, 6}, true) + + asserts.NE(false, "a string") + asserts.NE("a string", false) + asserts.NE(false, {2, 4, 6}) + asserts.NE({2, 4, 6}, false) + + asserts.NE("a string", {2, 4, 6}) + asserts.NE({2, 4, 6}, "a string") +end + +function tests.NE_expectFailure() + shouldFail(function () asserts.NE(7, 7) end) + shouldFail(function () asserts.NE(7, 7, 'message') end) + shouldFail(function () asserts.NE(true, true) end) + shouldFail(function () asserts.NE(false, false) end) + shouldFail(function () asserts.NE("a string", "a string") end) + shouldFail(function () asserts.NE(nil, nil) end) + local tbl = {2, 4, 6} + shouldFail(function () asserts.NE(tbl, tbl) end) + + shouldFailWithMessage(function () asserts.NE("monkey", "monkey") end, + 'Expected values to differ: monkey') + + shouldFailWithMessage(function () asserts.NE("monkey", "monkey", "magic") end, + 'Expected values to differ: monkey magic') +end + + +function tests.GT_expectSuccess() + asserts.GT(1, 0) + asserts.GT(0, -3) + asserts.GT(3.14, 3) +end + +function tests.GT_expectFailure() + shouldFail(function () asserts.GT(0, 1) end) + shouldFail(function () asserts.GT(-3, 0) end) + shouldFail(function () asserts.GT(3, 3) end) + shouldFail(function () asserts.GT(3, 3.14) end) + + shouldFailWithMessage(function () asserts.GT(0, 1) end, + 'Expected: 0 > 1') + shouldFailWithMessage(function () asserts.GT(0, 1, 'monkey') end, + 'Expected: 0 > 1 monkey') +end + +function tests.GE_expectSuccess() + asserts.GE(0, 0) + asserts.GE(1, 0) + asserts.GE(0, -3) + asserts.GE(3, 3) + asserts.GE(3.14, 3) +end + +function tests.GE_expectFailure() + shouldFail(function () asserts.GE(0, 1) end) + shouldFail(function () asserts.GE(-3, 0) end) + shouldFail(function () asserts.GE(3, 3.14) end) + + shouldFailWithMessage(function () asserts.GE(0, 1) end, + 'Expected: 0 >= 1') + shouldFailWithMessage(function () asserts.GE(0, 1, 'monkey') end, + 'Expected: 0 >= 1 monkey') +end + +function tests.LT_expectSuccess() + asserts.LT(0, 1) + asserts.LT(-3, 0) + asserts.LT(3, 3.14) +end + +function tests.LT_expectFailure() + shouldFail(function () asserts.LT(1, 0) end) + shouldFail(function () asserts.LT(0, -3) end) + shouldFail(function () asserts.LT(3, 3) end) + shouldFail(function () asserts.LT(3.14, 3) end) + + shouldFailWithMessage(function () asserts.LT(1, 0) end, + 'Expected: 1 < 0') + shouldFailWithMessage(function () asserts.LT(1, 0, 'monkey') end, + 'Expected: 1 < 0 monkey') +end + +function tests.LE_expectSuccess() + asserts.LE(0, 0) + asserts.LE(0, 1) + asserts.LE(-3, 0) + asserts.LE(-3, -3) + asserts.LE(3, 3.14) +end + +function tests.LE_expectFailure() + shouldFail(function () asserts.LE(1, 0) end) + shouldFail(function () asserts.LE(0, -3) end) + shouldFail(function () asserts.LE(3.14, 3) end) + + shouldFailWithMessage(function () asserts.LE(1, 0) end, + 'Expected: 1 <= 0') + shouldFailWithMessage(function () asserts.LE(1, 0, 'monkey') end, + 'Expected: 1 <= 0 monkey') +end + + +function tests.TablesEQ_expectSuccess() + asserts.tablesEQ({}, {}) + asserts.tablesEQ({2, 4, 6}, {2, 4, 6}) + local tbl = {'foo', 'bar'} + asserts.tablesEQ(tbl, tbl) + asserts.tablesEQ({pet = 'monkey', power = 'magic'}, + {pet = 'monkey', power = 'magic'}) + + asserts.tablesEQ({2, 4, {nested = 6}}, {2, 4, {nested = 6}}) +end + +function tests.TablesEQ_expectFailure() + shouldFail(function () asserts.tablesEQ({}, {1}) end) + shouldFail(function () asserts.tablesEQ({1}, {}) end) + shouldFail(function () asserts.tablesEQ({2, 4, 6}, {2, 4}) end) + shouldFail(function () asserts.tablesEQ({2, 4}, {2, 4, 6}) end) + shouldFail(function () asserts.tablesEQ({2, 4, 6}, {3, 4, 6}) end) + shouldFail(function () asserts.tablesEQ({2, 4, 6}, {3, 4, 6}) end) + shouldFail(function () asserts.tablesEQ({2, 4, 6}, {2, 5, 6}) end) + shouldFail(function () asserts.tablesEQ({2, 4, 6}, {2, 4, 7}) end) + shouldFail(function () asserts.tablesEQ({2, 4, 6}, {4, 2, 6}) end) + + shouldFail(function () asserts.tablesEQ( + {2, 4, {nested = 6}}, + {2, 4, {nested = 7}}) + end) + + shouldFail(function () asserts.tablesEQ( + {pet = 'monkey', power = 'magic'}, + {pet = 'dog', power = 'slobber'}) + end) + + shouldFailWithMessage(function () asserts.tablesEQ({}, {1}) end, + 'Expected equal table values.') + + shouldFailWithMessage(function () asserts.tablesEQ({}, {1}, 'OOPS') end, + {'Expected equal table values', 'OOPS'}) +end + + + +function tests.TablesNE_expectSuccess() + asserts.tablesNE({}, {1}) + asserts.tablesNE({1}, {}) + asserts.tablesNE({2, 4, 6}, {2, 4}) + asserts.tablesNE({2, 4}, {2, 4, 6}) + asserts.tablesNE({2, 4, 6}, {3, 4, 6}) + asserts.tablesNE({2, 4, 6}, {3, 4, 6}) + asserts.tablesNE({2, 4, 6}, {2, 5, 6}) + asserts.tablesNE({2, 4, 6}, {2, 4, 7}) + asserts.tablesNE({2, 4, 6}, {4, 2, 6}) + + asserts.tablesNE({2, 4, {nested = 6}}, {2, 4, {nested = 7}}) + asserts.tablesNE({pet = 'monkey', power = 'magic'}, + {pet = 'dog', power = 'slobber'}) +end + +function tests.TablesNE_expectFailure() + shouldFail(function () asserts.tablesNE({}, {}) end) + shouldFail(function () asserts.tablesNE({2, 4, 6}, {2, 4, 6}) end) + + local tbl = {'foo', 'bar'} + shouldFail(function () asserts.tablesNE(tbl, tbl) end) + + shouldFail(function () asserts.tablesNE( + {pet = 'monkey', power = 'magic'}, + {pet = 'monkey', power = 'magic'}) + end) + + shouldFail(function () asserts.tablesNE( + {2, 4, {nested = 6}}, + {2, 4, {nested = 6}}) + end) + + shouldFailWithMessage(function () asserts.tablesNE({1}, {1}) end, + 'Expected tables with different values.') + + shouldFailWithMessage(function () asserts.tablesNE({1}, {1}, 'OOPS') end, + {'Expected tables with different values.', 'OOPS'}) +end + +return test_runner.run(tests) diff --git a/lua_tests/colors_test.lua b/lua_tests/colors_test.lua new file mode 100644 index 00000000..046fd13a --- /dev/null +++ b/lua_tests/colors_test.lua @@ -0,0 +1,87 @@ +local asserts = require 'testing.asserts' +local test_runner = require 'testing.test_runner' +local colors = require 'common.colors' + +local tests = {} + + +function tests.hsl_brightRed() + asserts.tablesEQ({colors.hslToRgb(0, 1, 0.5)}, {255, 0, 0}) +end + +function tests.hsl_darkRed() + asserts.tablesEQ({colors.hslToRgb(0, 1, 0.25)}, {127.5, 0, 0}) +end + +function tests.hsl_lightRed() + asserts.tablesEQ({colors.hslToRgb(0, 1, 0.75)}, {255, 127.5, 127.5}) +end + +function tests.hsl_brightGreen() + asserts.tablesEQ({colors.hslToRgb(120, 1, 0.5)}, {0, 255, 0}) +end + +function tests.hsl_brightBlue() + asserts.tablesEQ({colors.hslToRgb(240, 1, 0.5)}, {0, 0, 255}) +end + +function tests.hsl_blueGoesblackIfZeroL() + asserts.tablesEQ({colors.hslToRgb(240, 1, 0)}, {0, 0, 0}) +end + +function tests.hsl_redGoesBlackIfZeroL() + asserts.tablesEQ({colors.hslToRgb(0, 1, 0)}, {0, 0, 0}) +end + +function tests.hsl_redGoesWhiteIfLightness1() + asserts.tablesEQ({colors.hslToRgb(0, 1, 1)}, {255, 255, 255}) +end + +function tests.hsl_increasingLightnessMovesTowardsWhite() + local h, s, l = 30, 0.5, 0 + local rLast, gLast, bLast = colors.hslToRgb(h, s, l) + local deltaL = 0.1 + for l = l + deltaL, 1, deltaL do + local r, g, b = colors.hslToRgb(h, s, l) + asserts.GE(r, rLast) + asserts.GE(g, gLast) + asserts.GE(b, bLast) + rLast, gLast, bLast = r, g, b + end +end + + +function tests.hsv_brightRed() + asserts.tablesEQ({colors.hsvToRgb(0, 1, 1)}, {255, 0, 0}) +end + +function tests.hsv_brightGreen() + asserts.tablesEQ({colors.hsvToRgb(120, 1, 1)}, {0, 255, 0}) +end + +function tests.hsv_darkGreen() + asserts.tablesEQ({colors.hsvToRgb(120, 1, 0.5)}, {0, 127.5, 0}) +end + +function tests.hsv_lightGreen() + asserts.tablesEQ({colors.hsvToRgb(120, 0.5, 1)}, {127.5, 255, 127.5}) +end + +function tests.hsv_brightBlue() + asserts.tablesEQ({colors.hsvToRgb(240, 1, 1)}, {0, 0, 255}) +end + +function tests.hsv_reducingVNeverIncreasesRGB() + local h, s, v = 30, 0.5, 1 + local rLast, gLast, bLast = colors.hsvToRgb(h, s, v) + local deltaV = 0.05 + for v = v - deltaV, 0, -deltaV do + local r, g, b = colors.hsvToRgb(h, s, v) + asserts.LE(r, rLast) + asserts.LE(g, gLast) + asserts.LE(b, bLast) + rLast, gLast, bLast = r, g, b + end +end + +return test_runner.run(tests) diff --git a/lua_tests/combinatorics_test.lua b/lua_tests/combinatorics_test.lua new file mode 100644 index 00000000..8be32698 --- /dev/null +++ b/lua_tests/combinatorics_test.lua @@ -0,0 +1,86 @@ +local asserts = require 'testing.asserts' +local test_runner = require 'testing.test_runner' + +local combinatorics = require 'common.combinatorics' +local set = require 'common.set' + +local tests = {} + +-- Return a set containing every possible result of +-- combinatorics.twoItemSelection(idx, n) for the given `n`. +local function allTwoItemSelections(n) + local result = {} + local resultSize = combinatorics.choose(2, n) + for i = 1, resultSize do + local selection = combinatorics.twoItemSelection(i, n) + set.insert(result, {selection}) + end + return result +end + + +--[[ Return true if `selections` contains all possible 2-number combinations of +the values 1..n. + +Since selections is a set, items are unique. It's sufficient to check that +the size matches, and that all 2-number values are in the range 1..n. +--]] +local function isCompleteTwoItemSelection(selections, n) + local s = set.toList(selections) + + -- Size should be "n choose 2". + local expectedSize = combinatorics.choose(2, n) + if expectedSize ~= #s then return false end + + -- All entries should satisfy 1 <= a < b <= n, for {a, b} in `selections`. + for _, k in pairs(s) do + local valid = 1 <= k[1] and k[1] < k[2] and k[2] <= n + if not valid then return false end + end + + return true +end + +-- Test degenerate cases of the choose function. +function tests.choose_degenerate() + -- "n choose n" = 1 + assert(combinatorics.choose(0, 0) == 1) + assert(combinatorics.choose(1, 1) == 1) + assert(combinatorics.choose(2, 2) == 1) + assert(combinatorics.choose(100, 100) == 1) + + -- "n choose 0" = 1 + assert(combinatorics.choose(0, 1) == 1) + assert(combinatorics.choose(0, 2) == 1) + assert(combinatorics.choose(0, 100) == 1) + + -- "n choose 1" = n + assert(combinatorics.choose(1, 1) == 1) + assert(combinatorics.choose(1, 2) == 2) + assert(combinatorics.choose(1, 100) == 100) + + -- "n choose n-1" = n + assert(combinatorics.choose(1 - 1, 1) == 1) + assert(combinatorics.choose(2 - 1, 2) == 2) + assert(combinatorics.choose(100 - 1, 100) == 100) +end + +-- Test more involved cases of the choose function. +function tests.choose_various() + -- "4 choose 2" = 4! / (2! * 2!) = 6 + assert(combinatorics.choose(2, 4) == 6) + + -- "30 choose 5" = 30! / (25! * 5!) = 142506 + assert(combinatorics.choose(5, 30) == 142506) +end + +-- Generate all possible values for a range of `n`s, and verify with brute force +-- that they're correct. +function tests.twoItemSelection_smallN() + local nToTest = {2, 3, 4, 9, 15, 16} + for _, n in ipairs(nToTest) do + assert(isCompleteTwoItemSelection(allTwoItemSelections(n), n)) + end +end + +return test_runner.run(tests) diff --git a/lua_tests/data/empty_test_file b/lua_tests/data/empty_test_file new file mode 100644 index 00000000..e69de29b diff --git a/lua_tests/data/testL.png b/lua_tests/data/testL.png new file mode 100644 index 00000000..38715f21 Binary files /dev/null and b/lua_tests/data/testL.png differ diff --git a/lua_tests/data/testRGB.png b/lua_tests/data/testRGB.png new file mode 100644 index 00000000..104b2ed4 Binary files /dev/null and b/lua_tests/data/testRGB.png differ diff --git a/lua_tests/data/testRGBA.png b/lua_tests/data/testRGBA.png new file mode 100644 index 00000000..3061e408 Binary files /dev/null and b/lua_tests/data/testRGBA.png differ diff --git a/lua_tests/decorators_custom_floors_test.lua b/lua_tests/decorators_custom_floors_test.lua new file mode 100644 index 00000000..c3d5e066 --- /dev/null +++ b/lua_tests/decorators_custom_floors_test.lua @@ -0,0 +1,53 @@ +local asserts = require 'testing.asserts' +local test_runner = require 'testing.test_runner' + +local custom_floors = require 'decorators.custom_floors' +local tensor = require 'dmlab.system.tensor' + +local tests = {} + +function tests.callsOriginalModifyTexture() + local mock_api = {} + function mock_api:modifyTexture(textureName, texture) + -- Record that this was called + mock_api.modifyTextureCalled = {textureName, texture} + end + + custom_floors.decorate(mock_api) + local NAME = 'some_texture_name' + local TEXTURE = 'pretend_texture' + mock_api:modifyTexture(NAME, TEXTURE) + + asserts.EQ(mock_api.modifyTextureCalled[1], NAME) + asserts.EQ(mock_api.modifyTextureCalled[2], TEXTURE) +end + +function tests.doesNothingToNonMatchingTexture() + local fake_api = {} + custom_floors.decorate(fake_api) + + local NAME = '/some/path/to/lg_floor_placeholder_B_d.tga' + local EXPECT_WHITE = tensor.ByteTensor(4, 4, 4):fill(255) + local texture = EXPECT_WHITE:clone() + + custom_floors.setVariationColor('A', {255, 0, 0}) + fake_api:modifyTexture(NAME, texture) + assert(texture == EXPECT_WHITE) +end + +function tests.appliesColorToMatchingTexture() + local fake_api = {} + custom_floors.decorate(fake_api) + + local NAME = '/some/path/to/lg_floor_placeholder_A_d.tga' + local EXPECT_RED = tensor.ByteTensor(4, 4, 4):fill(255) + EXPECT_RED:select(3, 2):fill(0) + EXPECT_RED:select(3, 3):fill(0) + local texture = tensor.ByteTensor(4, 4, 4):fill(255) + + custom_floors.setVariationColor('A', {255, 0, 0}) + fake_api:modifyTexture(NAME, texture) + assert(texture == EXPECT_RED) +end + +return test_runner.run(tests) diff --git a/lua_tests/game_test.lua b/lua_tests/game_test.lua new file mode 100644 index 00000000..f1d244f6 --- /dev/null +++ b/lua_tests/game_test.lua @@ -0,0 +1,26 @@ +local asserts = require 'testing.asserts' +local test_runner = require 'testing.test_runner' +local helpers = require 'common.helpers' +local io = require 'io' + +local game = require 'dmlab.system.game' + +local tests = {} + +function tests.tempFolder() + asserts.NE(game:tempFolder(), '') + local tempFile = helpers.pathJoin(game:tempFolder(), 'temp.txt') + assert(not helpers.fileExists(tempFile), 'Temp folder should be empty!') + local file = io.open(tempFile, 'w') + asserts.NE(file, nil, 'Failed to create file.') + io.close(file) + assert(helpers.fileExists(tempFile), 'tempFolder file cannot be found!') +end + +function tests.runFiles() + asserts.NE(game:runFiles(), '') + local thisFile = helpers.pathJoin(game:runFiles(), 'lua_tests/game_test.lua') + assert(helpers.fileExists(thisFile), 'runFiles path is incorrect!') +end + +return test_runner.run(tests) diff --git a/lua_tests/geometric_pickups_test.lua b/lua_tests/geometric_pickups_test.lua new file mode 100644 index 00000000..adbf4deb --- /dev/null +++ b/lua_tests/geometric_pickups_test.lua @@ -0,0 +1,36 @@ +local asserts = require 'testing.asserts' +local test_runner = require 'testing.test_runner' +local geometric_pickups = require 'common.geometric_pickups' +local random = require 'common.random' + +local tests = {} + +local CounterMT = { + add = function(self, k) self[k] = (self[k] or 0) + 1 end +} + +local function Counter() + local counter = {} + setmetatable(counter, CounterMT) + return counter +end + +function tests.randomPickupsTest() + random:seed(1) + local allPickups = geometric_pickups.createPickups() + for nTypes = 1, #allPickups do + for nRepeats = 1, 10 do + local keys = geometric_pickups.randomPickups(allPickups, nTypes, nRepeats) + asserts.EQ(nTypes * nRepeats, #keys) + local counter = Counter() + for _, k in ipairs(keys) do + counter:add(k) + end + for k, count in pairs(counter) do + asserts.EQ(nRepeats, count) + end + end + end +end + +return test_runner.run(tests) diff --git a/lua_tests/helpers_test.lua b/lua_tests/helpers_test.lua new file mode 100644 index 00000000..a1bcb61a --- /dev/null +++ b/lua_tests/helpers_test.lua @@ -0,0 +1,224 @@ +local asserts = require 'testing.asserts' +local test_runner = require 'testing.test_runner' + +local helpers = require 'common.helpers' + +local tests = {} + +function tests.findFileInLuaPath_shouldFindCommonHelpers() + -- This must be present, because we require it above: + local path = helpers.findFileInLuaPath('common.helpers') + assert(path) + assert(path:find('/game_scripts/common/helpers.lua')) +end + +function tests.findFileInLuaPath_shouldNotFindMadeUpFile() + local path = helpers.findFileInLuaPath('wiggly.biggly.boo') + asserts.EQ(path, nil) +end + +local function toKeyValueArrays(table) + if table == nil then return {} end + local arr = {} + for k, v in pairs(table) do + arr[#arr + 1] = {k = k, v = v} + end + return arr +end + +local function shallowEqual(tableA, tableB) + if tableA == nil and tableB == nil then return true end + + -- Ensure tables don't reference the same object. + if tableA == tableB then return false end + + -- Convert to arrays + local a = toKeyValueArrays(tableA) + local b = toKeyValueArrays(tableB) + if #a ~= #b then return false end + + -- Check for simple equality between key values. + for i = 1, #a do + if a[i].k ~= b[i].k then return false end + if a[i].v ~= b[i].v then return false end + end + + return true +end + +local function assertShallowCopyIsEqual(orig) + local copy = helpers.shallowCopy(orig) + assert(shallowEqual(orig, copy)) +end + +function tests.shallowCopy_nil() + assertShallowCopyIsEqual(nil) +end + +function tests.shallowCopy_emptyTable() + assertShallowCopyIsEqual({}) +end + +function tests.shallowCopy_oneItemArray() + assertShallowCopyIsEqual({'hello'}) +end + +function tests.shallowCopy_flatArray() + assertShallowCopyIsEqual({'hello', 7, 'swha!?'}) +end + +function tests.shallowCopy_oneItemTable() + assertShallowCopyIsEqual({myKey = 'hello'}) +end + +function tests.shallowCopy_flatTable() + assertShallowCopyIsEqual({ + ['myKey'] = 'hello', + [7] = 'there', + ['thinking'] = 1 + }) +end + +function tests.shallowCopy_parentChildTable() + local leaf = {myKey = 'hello'} + assertShallowCopyIsEqual({root = leaf}) +end + +function tests.shallowCopy_complexKeyTable() + local key = {myKey = 'hello'} + assertShallowCopyIsEqual({[key] = 'yo'}) +end + +function tests.shallowCopy_bigTable() + local key = {myKey = 'hello'} + local leaf = {fancy = 'string'} + local mid = {midKey = leaf} + + assertShallowCopyIsEqual({ + 'try', + 'something', + 'here', + [key] = leaf, + anotherKey = mid + }) +end + +local function deepEqual(tableA, tableB) + if tableA == nil and tableB == nil then return true end + + -- Ensure tables don't reference the same object. + if tableA == tableB then return false end + + -- Convert to arrays + local a = toKeyValueArrays(tableA) + local b = toKeyValueArrays(tableB) + if #a ~= #b then return false end + + for i = 1, #a do + -- Check for simple equality between keys + if a[i].k ~= b[i].k then return false end + if type(a[i].v) ~= type(b[i].v) then return false end + if type(a[i].v) == 'table' then + -- Check for deep equality between table values + if not deepEqual(a[i].v, b[i].v) then return false end + else + -- Check for simple equality between simple values + if a[i].v ~= b[i].v then return false end + end + end + + return true +end + +local function assertDeepCopyIsEqual(orig) + local copy = helpers.deepCopy(orig) + assert(deepEqual(orig, copy)) +end + +function tests.deepCopy_nil() + assertDeepCopyIsEqual(nil) +end + +function tests.deepCopy_emptyTable() + assertDeepCopyIsEqual({}) +end + +function tests.deepCopy_oneItemArray() + assertDeepCopyIsEqual({'hello'}) +end + +function tests.deepCopy_flatArray() + assertDeepCopyIsEqual({'hello', 7, 'swha!?'}) +end + +function tests.deepCopy_oneItemTable() + assertDeepCopyIsEqual({myKey = 'hello'}) +end + +function tests.deepCopy_flatTable() + assertDeepCopyIsEqual({ + ['myKey'] = 'hello', + [7] = 'there', + ['thinking'] = 1 + }) +end + +function tests.deepCopy_parentChildTable() + local leaf = {myKey = 'hello'} + assertDeepCopyIsEqual({root = leaf}) +end + +function tests.deepCopy_complexKeyTable() + local key = {myKey = 'hello'} + assertDeepCopyIsEqual({[key] = 'yo'}) +end + +function tests.deepCopy_bigTable() + local key = {myKey = 'hello'} + local leaf = {fancy = 'string'} + local mid = {midKey = leaf} + + assertDeepCopyIsEqual({ + 'try', + 'something', + 'here', + [key] = leaf, + anotherKey = mid + }) +end + +function tests.fromString_boolean() + asserts.EQ(helpers.fromString("true"), true) + asserts.EQ(helpers.fromString("false"), false) +end + +function tests.fromString_nil() + asserts.EQ(helpers.fromString(nil), nil) +end + +function tests.fromString_number() + asserts.EQ(helpers.fromString("0"), 0) + asserts.EQ(helpers.fromString("1"), 1) + asserts.EQ(helpers.fromString("-1"), -1) + asserts.EQ(helpers.fromString("2"), 2) + asserts.EQ(helpers.fromString("0.0000000001"), 0.0000000001) +end + +function tests.fromString_string() + asserts.EQ(helpers.fromString(""), "") + asserts.EQ(helpers.fromString("random string"), "random string") +end + +function tests.fromString_table() + local random_table = {random_name = 1} + asserts.EQ(helpers.fromString(random_table), random_table) +end + +function tests.pathJoin() + asserts.EQ(helpers.pathJoin('base', 'path'), 'base/path') + asserts.EQ(helpers.pathJoin('base/', 'path'), 'base/path') + asserts.EQ(helpers.pathJoin('base', '/path'), '/path') + asserts.EQ(helpers.pathJoin('base/', '/path'), '/path') +end + +return test_runner.run(tests) diff --git a/lua_tests/human_recognisable_pickups_test.lua b/lua_tests/human_recognisable_pickups_test.lua new file mode 100644 index 00000000..e4da6de8 --- /dev/null +++ b/lua_tests/human_recognisable_pickups_test.lua @@ -0,0 +1,245 @@ +local asserts = require 'testing.asserts' +local test_runner = require 'testing.test_runner' +local hrp = require 'common.human_recognisable_pickups' +local tensor = require 'dmlab.system.tensor' +local random = require 'common.random' + +local tests = {} + +local RED = {255, 0, 0} +local GREEN = {0, 255, 0} + +function tests.allPatternsShouldBeValidAndLoad() + for _, patternName in ipairs(hrp.patterns()) do + local patternTexture = hrp.getPatternTexture(patternName, 1024, 1024) + assert(patternTexture) + asserts.EQ(patternTexture:type(), 'deepmind.lab.tensor.FloatTensor') + asserts.tablesEQ(patternTexture:shape(), {1024, 1024, 4}) + end +end + +function tests.allScalesShouldBeValidToCreate() + local SHAPE = hrp.shapes()[1] + local PATTERN = hrp.patterns()[1] + hrp.reset() + local DEFAULT_PICKUP = hrp.create{ + shape = SHAPE, + pattern = PATTERN, + color1 = GREEN, + color2 = RED, + } + for _, scale in ipairs({'small', 'medium', 'large'}) do + hrp.reset() + local pickup = hrp.create{ + shape = SHAPE, + pattern = PATTERN, + color1 = GREEN, + color2 = RED, + scale = scale, + } + assert(pickup, 'Pickup failed to be created.') + if scale == 'medium' then + asserts.tablesEQ(pickup, DEFAULT_PICKUP, + 'Tables of same scales should match.') + else + asserts.tablesNE(pickup, DEFAULT_PICKUP, + 'Tables of different scales should not match.') + end + end +end + + +function tests.doubleScalesShouldBeValidToCreate() + local SHAPE = hrp.shapes()[1] + local PATTERN = hrp.patterns()[1] + hrp.reset() + local DEFAULT_PICKUP = hrp.create{ + shape = SHAPE, + pattern = PATTERN, + color1 = GREEN, + color2 = RED, + } + for _, scale in ipairs({0.5, 1.0, 2.0}) do + hrp.reset() + local pickup = hrp.create{ + shape = SHAPE, + pattern = PATTERN, + color1 = GREEN, + color2 = RED, + scale = scale, + } + assert(pickup, 'Pickup failed to be created.') + if scale == 1.0 then + asserts.tablesEQ(pickup, DEFAULT_PICKUP, + 'Tables of same scales should match.') + else + asserts.tablesNE(pickup, DEFAULT_PICKUP, + 'Tables of different scales should not match.') + end + end +end + +function tests.allShapesShouldBeValidToCreate() + local PATTERN = hrp.patterns()[1] + for _, shape in ipairs(hrp.shapes()) do + -- Must provide shape, pattern and a pair of colours. + local pickup = hrp.create{ + shape = shape, + pattern = PATTERN, + color1 = GREEN, + color2 = RED + } + assert(pickup) + asserts.EQ(type(pickup), 'table') + end +end + +function tests.applyPatternAndColors_doesNothingIfNoAlphaComponent() + local texture = tensor.ByteTensor(1024, 1024, 4) + texture:fill(127) + texture:select(3, 4):fill(0) + local expected = texture:clone() + local pattern = tensor.FloatTensor(1024, 1024, 3):fill(1) + + hrp.applyPatternAndColors(texture, pattern, RED, GREEN) + asserts.EQ(texture, expected) +end + +function tests.applyPatternAndColors_ShouldApplyColor1WhenPatternIs1() + -- Start with a white texture. + local texture = tensor.ByteTensor(1024, 1024, 4) + texture:fill(255) + + -- Create pattern of all 1's. + local pattern = tensor.FloatTensor(1024, 1024, 3):fill(1) + + -- Expect everything to go red. + local expected = tensor.ByteTensor(1024, 1024, 4) + expected:select(3, 1):fill(255) + expected:select(3, 4):fill(255) + + hrp.applyPatternAndColors(texture, pattern, RED, GREEN) + asserts.EQ(texture, expected) +end + +function tests.applyPatternAndColors_ShouldApplyColor2WhenPatternIs0() + -- Start with a white texture. + local texture = tensor.ByteTensor(1024, 1024, 4) + texture:fill(255) + + -- Create pattern of all 0's. + local pattern = tensor.FloatTensor(1024, 1024, 3) + + -- Expect everything to go green. + local expected = tensor.ByteTensor(1024, 1024, 4) + expected:select(3, 2):fill(255) + expected:select(3, 4):fill(255) + + hrp.applyPatternAndColors(texture, pattern, RED, GREEN) + asserts.EQ(texture, expected) +end + +function tests.applyPatternAndColors_ShouldApplyBothColors() + -- Start with a white texture, with the bottom half transparent. + local texture = tensor.ByteTensor(1024, 1024, 4) + texture:fill(255) + texture:narrow(1, 513, 512):select(3, 4):fill(0) + + -- Create pattern with 1's in the left half, zero's in the right. + local pattern = tensor.FloatTensor(1024, 1024, 3) + pattern:narrow(2, 1, 512):fill(1) + + -- Expect top half to be red and green, bottom half still white. + local expected = tensor.ByteTensor(1024, 1024, 4) + local top = expected:narrow(1, 1, 512) + top:narrow(2, 1, 512):select(3, 1):fill(255) + top:narrow(2, 513, 512):select(3, 2):fill(255) + top:select(3, 4):fill(255) + + expected:narrow(1, 513, 512):narrow(3, 1, 3):fill(255) + + hrp.applyPatternAndColors(texture, pattern, RED, GREEN) + asserts.EQ(texture, expected) +end + +-- Should succeed and return an empty table. +function tests.uniquePickups_empty() + local pickups = hrp.uniquePickups(0) + assert(next(pickups) == nil) +end + +-- Should generate an array with one pickup. +function tests.uniquePickups_one() + local pickups = hrp.uniquePickups(1) + asserts.EQ(1, #pickups) +end + +-- Ensure the generation is random. +function tests.uniquePickups_random() + random:seed(1) + local pickups1 = hrp.uniquePickups(100) + + random:seed(2) + local pickups2 = hrp.uniquePickups(100) + + asserts.tablesNE(pickups1, pickups2) +end + +-- Ensure the randomness is deterministic. +function tests.uniquePickups_repeatable() + random:seed(1) + local pickups1 = hrp.uniquePickups(100) + + random:seed(1) + local pickups2 = hrp.uniquePickups(100) + + asserts.tablesEQ(pickups1, pickups2) +end + +-- Should succeed and return an empty table. +function tests.uniquelyShapedPickups_empty() + local pickups = hrp.uniquelyShapedPickups(0) + assert(next(pickups) == nil) +end + +-- Should generate an array with one pickup. +function tests.uniquelyShapedPickups_one() + local pickups = hrp.uniquelyShapedPickups(1) + asserts.EQ(1, #pickups) +end + +-- Ensure the generation is random. +function tests.uniquelyShapedPickups_random() + random:seed(1) + local pickups1 = hrp.uniquelyShapedPickups(#hrp.shapes()) + + random:seed(2) + local pickups2 = hrp.uniquelyShapedPickups(#hrp.shapes()) + + asserts.tablesNE(pickups1, pickups2) +end + +-- Ensure the randomness is deterministic. +function tests.uniquelyShapedPickups_repeatable() + random:seed(1) + local pickups1 = hrp.uniquelyShapedPickups(#hrp.shapes()) + + random:seed(1) + local pickups2 = hrp.uniquelyShapedPickups(#hrp.shapes()) + + asserts.tablesEQ(pickups1, pickups2) +end + +-- Ensure shapes aren't repeated. +function tests.uniquelyShapedPickups_unique() + random:seed(1) + local pickups = hrp.uniquelyShapedPickups(#hrp.shapes()) + + local shapes = {} + for i, o in ipairs(pickups) do + asserts.EQ(shapes[o.shape], nil) + shapes[o.shape] = true + end +end + +return test_runner.run(tests) diff --git a/lua_tests/image_test.lua b/lua_tests/image_test.lua new file mode 100644 index 00000000..0ca04bd0 --- /dev/null +++ b/lua_tests/image_test.lua @@ -0,0 +1,39 @@ +local image = require 'dmlab.system.image' +local asserts = require 'testing.asserts' +local test_runner = require 'testing.test_runner' +local tensor = require 'dmlab.system.tensor' +local helpers = require 'common.helpers' + +local FILE_NAME = ... +local tests = {} +function tests:loadImageRGB() + local image = image.load(helpers.dirname(FILE_NAME) .. "data/testRGB.png") + local expected = tensor.ByteTensor(32, 32 * 3, 3) + expected:narrow(2, 1, 32):select(3, 1):fill(255) + expected:narrow(2, 33, 32):select(3, 2):fill(255) + expected:narrow(2, 65, 32):select(3, 3):fill(255) + assert(expected == image) +end + +function tests:loadImageRGBA() + local image = image.load(helpers.dirname(FILE_NAME) .. "data/testRGBA.png") + local expected = tensor.ByteTensor(32 * 2, 32 * 3, 4) + expected:narrow(2, 1, 32):select(3, 1):fill(255) + expected:narrow(2, 33, 32):select(3, 2):fill(255) + expected:narrow(2, 65, 32):select(3, 3):fill(255) + expected:narrow(1, 1, 32):select(3, 4):fill(255) + expected:narrow(1, 33, 32):select(3, 4):fill(127) + assert(expected == image) +end + +function tests:loadImageL() + local image = image.load(helpers.dirname(FILE_NAME) .. "data/testL.png") + local expected = tensor.ByteTensor(32, 32, 1) + expected:applyIndexed(function (val, index) + return index[1] + index[2] - 2 + end) + assert(expected == image) +end + + +return test_runner.run(tests) diff --git a/lua_tests/load_to_tensor_test.lua b/lua_tests/load_to_tensor_test.lua new file mode 100644 index 00000000..96c240ce --- /dev/null +++ b/lua_tests/load_to_tensor_test.lua @@ -0,0 +1,23 @@ +local asserts = require 'testing.asserts' +local test_runner = require 'testing.test_runner' +local helpers = require 'common.helpers' +local tensor = require 'dmlab.system.tensor' +local game = require 'dmlab.system.game' + +local tests = {} + +function tests.loadFileToByteTensor() + local tempFile = helpers.pathJoin(game:tempFolder(), 'temp.txt') + local file = io.open(tempFile, 'wb') + asserts.NE(file, nil, 'Failed to create file.') + file:write('aaabbbccc') + file:close(file) + local tensor = game:loadFileToByteTensor(tempFile) + for c = 1, 3 do + asserts.EQ(string.byte('a'), tensor(c):val()) + asserts.EQ(string.byte('b'), tensor(c + 3):val()) + asserts.EQ(string.byte('c'), tensor(c + 6):val()) + end +end + +return test_runner.run(tests) diff --git a/lua_tests/position_trigger_test.lua b/lua_tests/position_trigger_test.lua new file mode 100644 index 00000000..72b58c31 --- /dev/null +++ b/lua_tests/position_trigger_test.lua @@ -0,0 +1,310 @@ +local asserts = require 'testing.asserts' +local PositionTrigger = require 'common.position_trigger' +local set = require 'common.set' +local test_runner = require 'testing.test_runner' + +local tests = {} + +local HAPPINESS_LAYER = [[ +..LL.LL.. +.L..L..L. +..L...L.. +...L.L... +....L.... +]] +local UNHAPPY_POSITION = {50, 50} -- bottom left +local HAPPY_POSITION = {150, 350} -- left-most `L` + +-- Update without any triggers. +function tests.testEmptyUpdate() + local triggers = PositionTrigger.new() + triggers:update(HAPPY_POSITION) +end + +-- Create and remove a trigger multiple times. +function tests.testRemove() + local fired = false + local triggers = PositionTrigger.new() + + for i = 1, 3 do + -- Create. + assert(not triggers:exists("testTrigger")) + triggers:start{ + name = 'testTrigger', + maze = HAPPINESS_LAYER, + triggerWhenEnter = 'L', + callback = function() fired = true end, + } + assert(not fired) + assert(triggers:exists("testTrigger")) + + -- Remove. + triggers:remove("testTrigger") + assert(not fired) + assert(not triggers:exists("testTrigger")) + end +end + +-- Set up two triggers and verify the enter one fires when the first update is +-- on the trigger, and the exit one fires when the second update is off the +-- trigger. +function tests.testFirstUpdateTrigger() + local triggers = PositionTrigger.new() + + -- Create trigger to fire . + -- Ensure it doesn't fire immediately. + local firedEnter = false + local firedExit = false + triggers:start{ + name = 'testEnterTrigger', + maze = HAPPINESS_LAYER, + triggerWhenEnter = 'L', + callback = function() firedEnter = true end, + } + triggers:start{ + name = 'testExitTrigger', + maze = HAPPINESS_LAYER, + triggerWhenExit = 'L', + callback = function() firedExit = true end, + } + assert(not firedEnter) + assert(not firedExit) + assert(triggers:exists("testEnterTrigger")) + assert(triggers:exists("testExitTrigger")) + + -- Update position to one that should fire and check that it fired on enter + -- trigger, but not on exit trigger. + triggers:update(HAPPY_POSITION) + assert(firedEnter) + assert(not triggers:exists("testEnterTrigger")) + assert(not firedExit) + assert(triggers:exists("testExitTrigger")) + + -- Update position to one that's outside, and check that the exit trigger + -- fired. + triggers:update(UNHAPPY_POSITION) + assert(firedExit) + assert(not triggers:exists("testExitTrigger")) +end + +-- Set up two triggers and verify that neither fire when the first update is +-- off the trigger, the enter one fires when the second update is on the +-- trigger, and the exit one fires when the third update is off the trigger. +function tests.testSecondUpdateTrigger() + local triggers = PositionTrigger.new() + + -- Create trigger to fire . + -- Ensure it doesn't fire immediately. + local firedEnter = false + local firedExit = false + triggers:start{ + name = 'testEnterTrigger', + maze = HAPPINESS_LAYER, + triggerWhenEnter = 'L', + callback = function() firedEnter = true end, + } + triggers:start{ + name = 'testExitTrigger', + maze = HAPPINESS_LAYER, + triggerWhenExit = 'L', + callback = function() firedExit = true end, + } + assert(not firedEnter) + assert(not firedExit) + assert(triggers:exists("testEnterTrigger")) + assert(triggers:exists("testExitTrigger")) + + -- Update position to one that's outside, and check that nothing triggers. + triggers:update(UNHAPPY_POSITION) + assert(not firedEnter) + assert(not firedExit) + assert(triggers:exists("testEnterTrigger")) + assert(triggers:exists("testExitTrigger")) + + -- Update position to one that should fire and check that it fired on enter + -- trigger, but not on exit trigger. + triggers:update(HAPPY_POSITION) + assert(firedEnter) + assert(not triggers:exists("testEnterTrigger")) + assert(not firedExit) + assert(triggers:exists("testExitTrigger")) + + -- Update position to one that's outside, and check that the exit trigger + -- fired. + triggers:update(UNHAPPY_POSITION) + assert(firedExit) + assert(not triggers:exists("testExitTrigger")) +end + +-- When we return true from the callback, the trigger should be reset so that +-- it fires again. +function tests.testPersist() + local triggers = PositionTrigger.new() + + local fired = false + triggers:start{ + name = 'testTrigger', + maze = HAPPINESS_LAYER, + triggerWhenEnter = 'L', + callback = function() + fired = true + return true -- request trigger to persist. + end, + } + + -- Update position and assert trigger gets triggered. + assert(not fired) + triggers:update(HAPPY_POSITION) + assert(fired) + + -- Reset trigger, move off the position and back on. + -- Assert trigger gets triggered again. + fired = false + triggers:update(UNHAPPY_POSITION) + assert(not fired) + triggers:update(HAPPY_POSITION) + assert(fired) +end + +--[[ Test starting and triggering multiple triggers in the same maze. + +Key word argements: + +* `maze` (string) text representation of the maze. +* `triggers` (array) values for triggerWhenEnter and triggerWhenExit for + series of triggers. +* `updatePositions` (array) {x,y} positions used in call to triggers:update(). +* `fired` (array) indices of {x,y} positions used in call to + triggers:update(). + +--]] +local function testMultiple(kwargs) + local triggers = PositionTrigger.new() + + -- Create triggers. + local fired = {} + for i = 1, #kwargs.triggers do + triggers:start{ + name = "testTrigger" .. tostring(i), + maze = kwargs.maze, + triggerWhenEnter = kwargs.triggers[i].triggerWhenEnter, + triggerWhenExit = kwargs.triggers[i].triggerWhenExit, + callback = function() + fired[i] = true + return kwargs.triggers[i].persist + end + } + end + + -- Repeatedly call triggers:update() and verify that the appropriate triggers + -- have been fired. + for j = 1, #kwargs.updatePositions do + local currentPosition = kwargs.updatePositions[j] + triggers:update(currentPosition) + + -- Ensure exactly the correct triggers have fired. + assert(set.isSame(fired, kwargs.fired[j])) + + -- Reset which triggers were fired. + fired = {} + end +end + +-- Set up multiple triggers that complete in the order they're created. +function tests.testMultipleInOrder() + testMultiple{ + maze = 'CD\n' .. + 'AB', + triggers = { + {triggerWhenEnter = 'A'}, -- trigger 1 + {triggerWhenEnter = 'B'}, -- trigger 2 + {triggerWhenEnter = 'C'}, -- trigger 3 + {triggerWhenEnter = 'D'}, -- trigger 4 + }, + updatePositions = { + {0, 0}, + {100, 0}, + {0, 100}, + {100, 100}, + }, + fired = { + {[1] = true}, + {[2] = true}, + {[3] = true}, + {[4] = true}, + }, + } +end + +-- Set up multiple triggers, where some trigger at the same time. +function tests.testMultipleSimultaneous() + testMultiple{ + maze = 'AA\n' .. + 'AB', + triggers = { + {triggerWhenEnter = 'A'}, -- trigger 1 + {triggerWhenEnter = 'B'}, -- trigger 2 + }, + updatePositions = { + {100, 100}, + {100, 0}, + {100, 0}, + }, + fired = { + {[1] = true}, + {[2] = true}, + {}, + }, + } +end + +-- Set up multiple triggers, where some trigger on exit. +function tests.testMultipleEnterExit() + testMultiple{ + maze = 'AB', + triggers = { + {triggerWhenEnter = 'A'}, -- trigger 1 + {triggerWhenExit = 'B'}, -- trigger 2 + {triggerWhenExit = 'A'}, -- trigger 3 + }, + updatePositions = { + {100, 0}, + {0, 0}, + {100, 0}, + }, + fired = { + {}, + {[1] = true, [2] = true}, + {[3] = true}, + }, + } +end + +-- Set up multiple triggers, where some are reset after they trigger. +function tests.testMultiplePersist() + testMultiple{ + maze = 'AB', + triggers = { + {triggerWhenEnter = 'A', persist = true}, -- trigger 1 + {triggerWhenEnter = 'B', persist = true}, -- trigger 2 + {triggerWhenExit = 'A', persist = true}, -- trigger 3 + {triggerWhenExit = 'B', persist = true}, -- trigger 4 + }, + updatePositions = { + {0, 0}, + {100, 0}, + {100, 0}, + {0, 0}, + {100, 0}, + }, + fired = { + {[1] = true}, + {[2] = true, [3] = true}, + {}, + {[1] = true, [4] = true}, + {[2] = true, [3] = true}, + }, + } +end + +return test_runner.run(tests) diff --git a/lua_tests/random_test.lua b/lua_tests/random_test.lua new file mode 100644 index 00000000..133286a0 --- /dev/null +++ b/lua_tests/random_test.lua @@ -0,0 +1,218 @@ +local asserts = require 'testing.asserts' +local random = require 'common.random' +local test_runner = require 'testing.test_runner' +local map_maker = require 'dmlab.system.map_maker' +local randomMap = random(map_maker:randomGen()) +local set = require 'common.set' + +local tests = {} + +function tests.testRandomChoice() + random:seed(1) + asserts.EQ(random:choice({1, 1, 1}), 1) + asserts.EQ(random:choice({1, 2, 2, 1}, 2, 3), 2) + asserts.EQ(random:choice(nil), nil) + asserts.EQ(random:choice({}), nil) + asserts.EQ(random:choice({1, 2, 2, 1}, 3, 2), nil) +end + +function tests.testRandomChoiceRandomness() + local t = {1, 2, 3} + local count = {0, 0, 0} + random:seed(1) + for _ = 1, 100 do + local val = random:choice(t) + count[val] = count[val] + 1 + end + asserts.EQ(count[1] + count[2] + count[3], 100) + assert(count[1] > 0) + assert(count[2] > 0) + assert(count[3] > 0) +end + +function tests.testShuffle() + random:seed(2) + asserts.tablesEQ(random:shuffle{}, {}) + asserts.tablesEQ(random:shuffle{1}, {1}) + asserts.tablesEQ(random:shuffle{1, 2}, {2, 1}) + asserts.tablesEQ(random:shuffle{1, 2, 3}, {3, 1, 2}) +end + +function tests.testShuffleInPlace() + random:seed(2) + local seq = {} + random:shuffleInPlace(seq) + asserts.tablesEQ(seq, {}) + seq = {1} + random:shuffleInPlace(seq) + asserts.tablesEQ(seq, {1}) + seq = {1, 2} + random:shuffleInPlace(seq) + asserts.tablesEQ(seq, {2, 1}) + seq = {1, 2, 3} + random:shuffleInPlace(seq) + asserts.tablesEQ(seq, {3, 1, 2}) +end + +function tests.SameSqueunce() + for seed = 1, 100 do + random:seed(seed) + randomMap:seed(seed) + for seq = 1, 1000 do + asserts.EQ(random:normal(0, 1000), randomMap:normal(0, 1000)) + end + end +end + +function tests.testShuffleMap() + randomMap:seed(2) + random:seed(1) + asserts.tablesEQ(randomMap:shuffle{}, {}) + asserts.tablesEQ(randomMap:shuffle{1}, {1}) + asserts.tablesEQ(randomMap:shuffle{1, 2}, {2, 1}) + asserts.tablesEQ(randomMap:shuffle{1, 2, 3}, {3, 1, 2}) +end + +function tests.testShuffleInPlaceN() + random:seed(2) + local seq = {} + random:shuffleInPlace(seq, 1) + asserts.tablesEQ(seq, {}) + seq = {1} + random:shuffleInPlace(seq, 1) + asserts.tablesEQ(seq, {1}) + seq = {1, 2} + random:shuffleInPlace(seq, 1) + asserts.tablesEQ(seq, {2, 1}) + seq = {1, 2, 3} + random:shuffleInPlace(seq, 1) + asserts.tablesEQ(seq, {3, 2, 1}) +end + +function tests.testNormal() + random:seed(2) + asserts.EQ(math.floor(random:normal(0, 1000)), -592) + asserts.EQ(random:normal(3, 0), 3) + + asserts.shouldFail(function () random:normal(3, 'cat') end) + asserts.shouldFail(function () random:normal('cat', 3) end) + asserts.shouldFail(function () random:normal('cat', 'pig') end) + + asserts.shouldFail(function () random:normal(nil, 3) end) + asserts.shouldFail(function () random:normal(0, nil) end) + asserts.shouldFail(function () random:normal(nil, nil) end) +end + +function tests.testDiscreteDistribution() + random:seed(2) + for i = 1, 10 do + asserts.EQ(random:discreteDistribution{0, 1}, 2) + asserts.EQ(random:discreteDistribution{0.5, 0}, 1) + asserts.EQ(random:discreteDistribution{0, 0, 5}, 3) + end + + local hits = {0, 0} + for i = 1, 100 do + local index = random:discreteDistribution{1, 1} + assert(index == 1 or index == 2) + hits[index] = hits[index] + 1 + end + + -- Should have roughly 50-50 hits from equal weights + local ratio = hits[1] / hits[2] + asserts.GT(ratio, 0.8) + asserts.LT(ratio, 1.2) + + asserts.shouldFail(function () random:discreteDistribution({}) end) + asserts.shouldFail(function () random:discreteDistribution(3) end) + asserts.shouldFail(function () random:discreteDistribution('spoon') end) + asserts.shouldFail(function () random:discreteDistribution({1, 'x'}) end) +end + +-- Ensure that generating a complete shuffling generates a valid shuffling. +-- Also tests that we return nil after a complete shuffling. +function tests.testShuffledIndexGeneratorShuffling() + local counts = {1, 2, 3, 10, 16, 100, 997} + + for _, count in ipairs(counts) do + random:seed(1) + local gen = random:shuffledIndexGenerator(count) + + -- Generate a complete shuffling of all numbers 1..count. + local result = {} + for i = 1, count do + local index = gen() + -- Generated values must be in the shuffling range. + assert(1 <= index and index <= count) + set.insert(result, {index}) + end + + -- After a complete shuffling, the generator should return nil for one + -- call. + assert(gen() == nil) + + -- The next call should not be nil, however. + local nextShuffleFirstValue = gen() + assert(nextShuffleFirstValue ~= nil and 1 <= nextShuffleFirstValue and + nextShuffleFirstValue <= count) + + -- Set values are unique and in the range 1..count, resultList will be + -- a shuffling if and only if it is the correct length. + local resultList = set.toList(result) + assert(#resultList == count) + end +end + +-- Test how random the 1st number generated is. Also test the 2nd and later +-- numbers, too. +function tests.testShuffledIndexGeneratorRandomness() + random:seed(2) + + local indices = {1, 2, 7} + local count = 4 + + -- The first value generated should be a number between 1..4, with uniform + -- distribution. We sample the first value `trials` times. + -- + -- We expect 1/4 of our tests to return value 1 the first time we call gen(). + -- For example, if trials is 4000, then we expect about 1000 to return 1. + -- The probability of 890~1110 tests returning 1 is 0.999948 so we use 110 + -- as our error tolerance. + -- This probability is calculated with a Binomial Calculator + -- (e.g. http://stattrek.com/online-calculator/binomial.aspx), + -- using probability-of-success=1/4, trials=4000, successes=890. + local trials = 4000 + local expectedValue = trials / count + local errorTolerance = 110 + + -- We test the randomness of the first value generated, but we also want to + -- test the second and others, too. + for _, index in ipairs(indices) do + -- Count how often each value is generated as the index'th value. + local occurrance = {} + for value = 1, count do + occurrance[value] = 0 + end + + for trial = 1, trials do + local gen = random:shuffledIndexGenerator(count) + + -- Grab the index'th value generated. + for i = 1, index - 1 do gen() end + local value = gen() + assert(1 <= value and value <= count) + + -- Increment the count of occurrences. + occurrance[value] = (occurrance[value] or 0) + 1 + end + + -- Pass if the occurrences are within our error tolerances. + for value = 1, count do + local occurrances = occurrance[value] or 0 + assert(expectedValue - errorTolerance <= occurrances and + occurrances <= expectedValue + errorTolerance) + end + end +end + +return test_runner.run(tests) diff --git a/lua_tests/read_file_test.lua b/lua_tests/read_file_test.lua new file mode 100644 index 00000000..faefa22b --- /dev/null +++ b/lua_tests/read_file_test.lua @@ -0,0 +1,34 @@ +local game = require 'dmlab.system.game' +local asserts = require 'testing.asserts' +local test_runner = require 'testing.test_runner' +local helpers = require 'common.helpers' + +local FILE_NAME = ... +local EMPTY_FILE_NAME = helpers.dirname(FILE_NAME) .. "data/empty_test_file" +local BINARY_FILE_NAME = helpers.dirname(FILE_NAME) .. "data/testL.png" +local tests = {} + +local function readFile(file) + local f = io.open(file, "rb") + local content = f:read("*all") + f:close() + return content +end + +function tests:readEmpty() + local emptyFileContent = game:loadFileToString(EMPTY_FILE_NAME) + asserts.EQ(emptyFileContent, '') +end + +function tests:readBinaryFile() + local binaryFileContent = game:loadFileToString(BINARY_FILE_NAME) + assert(binaryFileContent == readFile(BINARY_FILE_NAME)) +end + +function tests:readSelf() + local textFileContent = game:loadFileToString(FILE_NAME) + assert(textFileContent == readFile(FILE_NAME)) +end + +return test_runner.run(tests) + diff --git a/lua_tests/set_test.lua b/lua_tests/set_test.lua new file mode 100644 index 00000000..1cc78d39 --- /dev/null +++ b/lua_tests/set_test.lua @@ -0,0 +1,106 @@ +local asserts = require 'testing.asserts' +local test_runner = require 'testing.test_runner' + +local set = require 'common.set' + +local Set = set.Set + +local tests = {} + +local function elementCount(t) + local size = 0 + for _, _ in pairs(t) do + size = size + 1 + end + return size +end + +function tests.Set_shouldCreateTableWithTrueValues() + local newSet = Set({'foo', 'bar', 'foo'}) + asserts.EQ(elementCount(newSet), 2) + asserts.EQ(newSet['foo'], true) + asserts.EQ(newSet['bar'], true) +end + +function tests.isSame() + asserts.EQ(set.isSame(Set{}, Set{}), true) + asserts.EQ(set.isSame(Set{}, Set{'foo'}), false) + asserts.EQ(set.isSame(Set{'bar'}, Set{}), false) + asserts.EQ(set.isSame(Set{'bar'}, Set{'foo'}), false) + asserts.EQ(set.isSame(Set{'foo', 'bar'}, Set{'foo', 'bar'}), true) + asserts.EQ(set.isSame(Set{'foo', 'bar'}, Set{'bar', 'foo'}), true) +end + +function tests.toList_shouldReturnDistinctKeys() + local list = set.toList(Set({'foo', 'bar', 'foo'})) + asserts.EQ(#list, 2) + table.sort(list) + asserts.tablesEQ(list, {'bar', 'foo'}) +end + +function tests.intersect_shouldReturnNewSetOfCommonItems() + local lhsElements = {'foo', 'bar', 'baz'} + local rhsElements = {'bar', 'baz', 'fub'} + local lhs = Set(lhsElements) + local rhs = Set(rhsElements) + + local intersection = set.intersect(lhs, rhs) + asserts.EQ(elementCount(intersection), 2) + asserts.EQ(intersection['bar'], true) + asserts.EQ(intersection['baz'], true) + + -- Check inputs were not changed + assert(set.isSame(lhs, Set(lhsElements))) + assert(set.isSame(rhs, Set(rhsElements))) +end + +function tests.difference_shouldReturnNewSetWithFirstMinusSecond() + local lhsElements = {'foo', 'bar', 'baz'} + local rhsElements = {'bar', 'baz', 'fub'} + local lhs = Set(lhsElements) + local rhs = Set(rhsElements) + + local difference = set.difference(lhs, rhs) + asserts.EQ(elementCount(difference), 1) + asserts.EQ(difference['foo'], true) + + -- Check inputs were not changed + assert(set.isSame(lhs, Set(lhsElements))) + assert(set.isSame(rhs, Set(rhsElements))) +end + +function tests.union_shouldReturnFirstPlusSecond() + local lhsElements = {'foo', 'bar', 'baz'} + local rhsElements = {'bar', 'baz', 'fub'} + local lhs = Set(lhsElements) + local rhs = Set(rhsElements) + + local union = set.union(lhs, rhs) + asserts.EQ(elementCount(union), 4) + asserts.EQ(union['foo'], true) + asserts.EQ(union['bar'], true) + asserts.EQ(union['baz'], true) + asserts.EQ(union['fub'], true) + + -- Check inputs were not changed + assert(set.isSame(lhs, Set(lhsElements))) + assert(set.isSame(rhs, Set(rhsElements))) +end + + +function tests.insert_shouldAddNewValuesFromList() + local newSet = Set({'foo', 'bar', 'foo'}) + asserts.EQ(elementCount(newSet), 2) + asserts.EQ(newSet['foo'], true) + asserts.EQ(newSet['bar'], true) + + set.insert(newSet, {'bar', 'baz'}) + asserts.EQ(elementCount(newSet), 3) + asserts.EQ(newSet['foo'], true) + asserts.EQ(newSet['bar'], true) + asserts.EQ(newSet['baz'], true) +end + + + +return test_runner.run(tests) diff --git a/lua_tests/themes_test.lua b/lua_tests/themes_test.lua new file mode 100644 index 00000000..fd6b0249 --- /dev/null +++ b/lua_tests/themes_test.lua @@ -0,0 +1,105 @@ +local test_runner = require 'testing.test_runner' +local asserts = require 'testing.asserts' +local themes = require 'themes.themes' +local texture_sets = require 'themes.texture_sets' +local map_maker = require 'dmlab.system.map_maker' +local random = require 'common.random' +local mapRandomGen = map_maker:randomGen() + +local tests = {} + +function tests.TextureSetMishMash() + for otherSeed = 1, 3 do + random:seed(otherSeed) -- Make sure other seed does not affect results. + mapRandomGen:seed(2) + local mishmash = texture_sets.MISHMASH + local theme = themes.fromTextureSet{ + textureSet = mishmash, + decalFrequency = 0.1 + } + + local vDefault = theme:mazeVariation('default') + asserts.EQ(mishmash.floor[1].tex, vDefault.floor.tex) + asserts.EQ(mishmash.ceiling[1].tex, + vDefault.ceiling.tex) + asserts.EQ(mishmash.wall[1].tex, vDefault.wallN.tex) + asserts.EQ(mishmash.wall[1].tex, vDefault.wallE.tex) + asserts.EQ(mishmash.wall[1].tex, vDefault.wallS.tex) + asserts.EQ(mishmash.wall[1].tex, vDefault.wallW.tex) + + local vA = theme:mazeVariation('A') + asserts.EQ(mishmash.floor[18].tex, vA.floor.tex) + asserts.EQ(mishmash.ceiling[1].tex, vA.ceiling.tex) + asserts.EQ(mishmash.wall[19].tex, vA.wallN.tex) + asserts.EQ(mishmash.wall[19].tex, vA.wallE.tex) + asserts.EQ(mishmash.wall[19].tex, vA.wallS.tex) + asserts.EQ(mishmash.wall[19].tex, vA.wallW.tex) + + local locs = {} + for i = 1, 100 do + locs[i] = {index = i} + end + assert(theme.placeWallDecals) + local wallDecals = theme:placeWallDecals(locs) + asserts.GE(#mishmash.wallDecals, 10) + -- 'decalFrequency' is 0.1 and there are 100 locations. + asserts.EQ(#wallDecals, 10) + asserts.tablesEQ(wallDecals[1], { + decal = {tex = 'decal/lab_games/dec_img_style01_007_nonsolid'}, + index = 93 + }) + assert(theme.placeFloorModels == nil) + end +end + +function tests.TextureSetCustom() + for otherSeed = 1, 3 do + random:seed(otherSeed) -- Make sure other seed does not affect results. + mapRandomGen:seed(2) + local textureSet = { + floor = {{tex = 'floor'}}, + ceiling = {{tex = 'ceiling'}}, + wall = {{tex = 'wall'}}, + wallDecals = {{tex = 'wallDecals'}}, + floorModels = {{mod = 'floorModels'}}, + } + local theme = themes.fromTextureSet{textureSet = textureSet} + + local vDefault = theme:mazeVariation('default') + asserts.EQ('floor', vDefault.floor.tex) + asserts.EQ('ceiling', vDefault.ceiling.tex) + asserts.EQ('wall', vDefault.wallN.tex) + asserts.EQ('wall', vDefault.wallE.tex) + asserts.EQ('wall', vDefault.wallS.tex) + asserts.EQ('wall', vDefault.wallW.tex) + + local vA = theme:mazeVariation('A') + asserts.EQ('floor', vA.floor.tex) + asserts.EQ('ceiling', vA.ceiling.tex) + asserts.EQ('wall', vA.wallN.tex) + asserts.EQ('wall', vA.wallE.tex) + asserts.EQ('wall', vA.wallS.tex) + asserts.EQ('wall', vA.wallW.tex) + + local locs = {} + for i = 1, 100 do + locs[i] = {index = i} + end + assert(theme.placeWallDecals) + local wallDecals = theme:placeWallDecals(locs) + asserts.EQ(#wallDecals, 1) + asserts.tablesEQ(wallDecals[1], { + decal = {tex = 'wallDecals'}, + index = 93 + }) + assert(theme.placeFloorModels) + local floorModels = theme:placeFloorModels(locs) + asserts.EQ(#floorModels, 5) + asserts.tablesEQ(floorModels[1], { + model = {mod = "floorModels"}, + index = 98 + }) + end +end + +return test_runner.run(tests) diff --git a/lua_tests/timer_test.lua b/lua_tests/timer_test.lua new file mode 100644 index 00000000..827fe81b --- /dev/null +++ b/lua_tests/timer_test.lua @@ -0,0 +1,192 @@ +local asserts = require 'testing.asserts' +local helpers = require 'common.helpers' +local test_runner = require 'testing.test_runner' +local Timer = require 'common.timer' + +local tests = {} + +-- Create and remove a timer multiple times. +function tests.testRemove() + local fired = false + local timer = Timer.new() + + for i = 1, 3 do + -- Create. + assert(not timer:exists("testTimer")) + timer:start{ + name = "testTimer", + time = 2, + callback = function() fired = true end, + } + assert(not fired) + assert(timer:exists("testTimer")) + + -- Remove. + timer:remove("testTimer") + assert(not fired) + assert(not timer:exists("testTimer")) + end +end + +-- Set up one timer and verify it fires when time is exactly correct. +function tests.testSingleExact() + local timer = Timer.new() + + -- Create timer to fire at second 1. + -- Ensure it doesn't fire immediately. + assert(not timer:exists("testTimer")) + local fired = false + timer:start{ + name = "testTimer", + time = 1, + callback = function() fired = true end, + } + assert(not fired) + assert(timer:exists("testTimer")) + asserts.EQ(timer:timeRemaining("testTimer"), 1) + + -- Update time to exactly 1 and verify that timer fired. + timer:update(1) + assert(fired) + assert(not timer:exists("testTimer")) + asserts.EQ(timer:timeRemaining("testTimer"), 0) +end + +-- Set up one timer and verify it fires when time is exactly correct. +function tests.testSingleOver() + local timer = Timer.new() + + -- Create timer to fire at second 1. + assert(not timer:exists("testTimer")) + local fired = false + timer:start{ + name = "testTimer", + time = 1, + callback = function() fired = true end, + } + + -- Nothing should happen here, since we're < the time for testTimer. + timer:update(0.99999) + assert(not fired) + assert(timer:exists("testTimer")) + asserts.EQ(timer:timeRemaining("testTimer"), 1 - 0.99999) + + -- Timer should fire now. + timer:update(1000) + assert(fired) + assert(not timer:exists("testTimer")) + asserts.EQ(timer:timeRemaining("testTimer"), 0) +end + +-- Set up one timer and verify it fires over and over again when persisting. +function tests.testPersist() + local timer = Timer.new() + + -- Create timer to fire at second 1. + assert(not timer:exists("testTimer")) + local fired = false + local persist = true + timer:start{ + name = "testTimer", + time = 1, + callback = function() + fired = true + return persist -- Indicate if this timer should be set again. + end, + } + + -- Assert the timer fires but remains active. + timer:update(1) + assert(fired) + assert(timer:exists("testTimer")) + asserts.EQ(timer:timeRemaining("testTimer"), 1) + + -- Update some more and assert it fires again. + fired = false + timer:update(2.0001) + assert(fired) + + -- Update not quite enough and assert it doesn't fire. + fired = false + timer:update(2.9999) + assert(not fired) + + -- Update again, this time requesting it not to persist. + persist = false + timer:update(3) + assert(fired) + assert(not timer:exists("testTimer")) +end + +-- `timerTimes` (array) times that timers are fired off at. +-- `updateTimes` (array) times used in call to timer:update(). Must be +-- increasing. +local function testMultiple(kwargs) + local timer = Timer.new() + + -- Create timers. + local fired = {} + for i = 1, #kwargs.timerTimes do + fired[i] = false + timer:start{ + name = "testTimer" .. tostring(i), + time = kwargs.timerTimes[i], + callback = function() fired[i] = true end, + } + end + + -- Repeatedly call timer:update() and verify that the appropriate timers + -- have been fired. + for j = 1, #kwargs.updateTimes do + local currentTime = kwargs.updateTimes[j] + timer:update(currentTime) + for i = 1, #kwargs.timerTimes do + local timerTime = kwargs.timerTimes[i] + local shouldBeComplete = timerTime <= currentTime + asserts.EQ(fired[i], shouldBeComplete) + asserts.EQ(timer:exists("testTimer" .. tostring(i)), not shouldBeComplete) + end + end +end + +-- Set up multiple timers that complete in the order they're created. +function tests.testMultipleInOrder() + testMultiple{ + timerTimes = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + updateTimes = {0, 1, 3, 3.1, 5, 8.1, 11}, + } +end + +-- Set up multiple timers that complete in the reverse order they're created. +function tests.testMultipleReverseOrder() + testMultiple{ + timerTimes = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + updateTimes = {0, 1, 3, 3.1, 5, 8.1, 11}, + } +end + +-- Set up multiple timers that complete in a mixed up order. +function tests.testMultipleMixedOrder() + testMultiple{ + timerTimes = {4.2, 3.3, 8, 2, 2, 3.1, 5, 8, 4.2, 10}, + updateTimes = {0, 1, 3, 3.1, 5, 8.1, 11}, + } +end + +-- Test formatting. +function tests.testSecondsToString() + asserts.EQ(helpers.secondsToTimeString(0), "0") + asserts.EQ(helpers.secondsToTimeString(3), "3") + asserts.EQ(helpers.secondsToTimeString(21), "21") + asserts.EQ(helpers.secondsToTimeString(59), "59") + asserts.EQ(helpers.secondsToTimeString(60), "1:00") + asserts.EQ(helpers.secondsToTimeString(61), "1:01") + asserts.EQ(helpers.secondsToTimeString(61.1), "1:02") + asserts.EQ(helpers.secondsToTimeString(60 * 2), "2:00") + asserts.EQ(helpers.secondsToTimeString(60 * 60), "1:00:00") + asserts.EQ(helpers.secondsToTimeString(60 * 60 + 1), "1:00:01") + asserts.EQ(helpers.secondsToTimeString(60 * 60 + 61), "1:01:01") + asserts.EQ(helpers.secondsToTimeString(100 * 60 * 60), "100:00:00") +end + +return test_runner.run(tests) diff --git a/public/dmlab.h b/public/dmlab.h index b23a8726..a03d5737 100644 --- a/public/dmlab.h +++ b/public/dmlab.h @@ -21,6 +21,9 @@ #ifndef DML_PUBLIC_DMLAB_H_ #define DML_PUBLIC_DMLAB_H_ +#include + +#include "public/level_cache_types.h" #include "third_party/rl_api/env_c_api.h" #ifdef __cplusplus @@ -29,9 +32,26 @@ extern "C" { typedef struct DeepMindLabLaunchParams_s DeepMindLabLaunchParams; +// Enum depicting which renderer to use for DMLab. +enum DeepMindLabRenderer_Enum { + DeepMindLabRenderer_Software, + DeepMindLabRenderer_Hardware, +}; +typedef enum DeepMindLabRenderer_Enum DeepMindLabRenderer; + struct DeepMindLabLaunchParams_s { // Path to where DeepMind Lab assets are stored. const char* runfiles_path; + DeepMindLabRenderer renderer; + DeepMindLabLevelCacheParams level_cache_params; + + // Optional function for reading from the file system. If set, a call returns + // whether the file 'file_name' was read successfully and if so 'buff' points + // to the content and 'size' contains the size of the file and after use + // 'buff' must be freed with 'free'. Otherwise returns false. + bool (*file_reader_override)(const char* file_name, char** buff, + size_t* size); + const char* optional_temp_folder; }; // Starts an instance of DeepMind Lab and exports the single-player RL diff --git a/public/dmlab_so_loader.cc b/public/dmlab_so_loader.cc index 3289b754..5098d349 100644 --- a/public/dmlab_so_loader.cc +++ b/public/dmlab_so_loader.cc @@ -38,16 +38,10 @@ #include #include -#ifndef DMLAB_SO_LOCATION -#error Must define DMLAB_SO_LOCATION dynamic library path. -#endif - namespace { std::mutex connect_mutex; -constexpr const char kLibraryName[] = "/" DMLAB_SO_LOCATION; - struct InternalContext { void (*release_context)(void* context); void* dlhandle; @@ -111,7 +105,18 @@ int dmlab_connect(const DeepMindLabLaunchParams* params, EnvCApi* env_c_api, std::string so_path; if (params->runfiles_path != nullptr && params->runfiles_path[0] != '\0') { so_path = params->runfiles_path; - so_path += kLibraryName; + + switch (params->renderer) { + case DeepMindLabRenderer_Software: + so_path += "/libdmlab_headless_sw.so"; + break; + case DeepMindLabRenderer_Hardware: + so_path += "/libdmlab_headless_hw.so"; + break; + default: + std::cerr << "Invalid renderer!\n"; + return 1; + } } else { std::cerr << "Require runfiles_diectory!\n"; return 1; diff --git a/public/level_cache_types.h b/public/level_cache_types.h new file mode 100644 index 00000000..57650677 --- /dev/null +++ b/public/level_cache_types.h @@ -0,0 +1,56 @@ +// Copyright (C) 2017 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef DML_PUBLIC_LEVEL_CACHE_TYPES_H_ +#define DML_PUBLIC_LEVEL_CACHE_TYPES_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DeepMindLabLevelCacheParams_s DeepMindLabLevelCacheParams; + +struct DeepMindLabLevelCacheParams_s { + // Tries to fetch a level from the specified caches into pk3_path. + // Returns true if and only if the level was found. + // This function is thread-safe. + bool (*fetch_level_from_cache)(void* level_cache_context, + const char* const cache_paths[], + int num_cache_paths, + const char* key, + const char* pk3_path); + + // Tries to write a level into all of the specified cache paths. + // This function is thread-safe. + void (*write_level_to_cache)(void* level_cache_context, + const char* const cache_paths[], + int num_cache_paths, + const char* key, + const char* pk3_path); + + // Context pointer that must be passed to the functions above. + void* context; +}; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // DML_PUBLIC_LEVEL_CACHE_TYPES_H_ diff --git a/python/dmlab_module.c b/python/dmlab_module.c index 8dd60f34..f4ad7f59 100644 --- a/python/dmlab_module.c +++ b/python/dmlab_module.c @@ -1,4 +1,4 @@ -// Copyright (C) 2016 Google Inc. +// Copyright (C) 2016-2017 Google Inc. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -79,8 +79,9 @@ static PyObject* LabObject_new(PyTypeObject* type, PyObject* args, return NULL; } - DeepMindLabLaunchParams params; + DeepMindLabLaunchParams params = {}; params.runfiles_path = runfiles_path; + params.renderer = DeepMindLabRenderer_Software; if (dmlab_connect(¶ms, self->env_c_api, &self->context) != 0) { PyErr_SetString(PyExc_RuntimeError, "Failed to connect RL API"); @@ -88,13 +89,23 @@ static PyObject* LabObject_new(PyTypeObject* type, PyObject* args, return NULL; } - if (self->env_c_api->setting(self->context, "actionSpec", "Integers") - != 0) { - PyErr_SetString(PyExc_RuntimeError, - "Failed to apply 'actionSpec' setting."); +// When running under TSAN, switch to the interpreted VM, which is +// instrumentable. +// +// It might be a better idea add __attribute__((no_sanitize("thread"))) to +// vm_x86.c, but I have not managed to make that work. +#ifndef __has_feature +# define __has_feature(x) 0 +#endif +#if __has_feature(thread_sanitizer) + if (self->env_c_api->setting(self->context, "vmMode", "interpreted") != 0) { + PyErr_Format(PyExc_RuntimeError, + "Failed to apply 'vmMode' setting - \"%s\"", + self->env_c_api->error_message(self->context)); free(self->env_c_api); return NULL; } +#endif if (self->context == NULL) { Py_DECREF(self); @@ -127,7 +138,8 @@ static int Lab_init(LabObject* self, PyObject* args, PyObject* kwds) { } if (self->env_c_api->setting(self->context, "levelName", level) != 0) { - PyErr_Format(PyExc_RuntimeError, "Invalid levelName flag '%s'", level); + PyErr_Format(PyExc_RuntimeError, "Invalid levelName flag '%s' - \"%s\"", + level, self->env_c_api->error_message(self->context)); return -1; } @@ -139,7 +151,9 @@ static int Lab_init(LabObject* self, PyObject* args, PyObject* kwds) { } if (self->env_c_api->setting(self->context, "fps", "60") != 0) { - PyErr_SetString(PyExc_RuntimeError, "Failed to set fps"); + PyErr_Format(PyExc_RuntimeError, "Failed to set fps - \"%s\"", + self->env_c_api->error_message(self->context)); + return -1; } if (config != NULL) { @@ -154,32 +168,36 @@ static int Lab_init(LabObject* self, PyObject* args, PyObject* kwds) { return -1; } if (self->env_c_api->setting(self->context, key, value) != 0) { - PyErr_Format(PyExc_RuntimeError, "Failed to apply setting '%s = %s'.", - key, value); + PyErr_Format(PyExc_RuntimeError, + "Failed to apply setting '%s = %s' - \"%s\"", key, value, + self->env_c_api->error_message(self->context)); + return -1; } } } if (self->env_c_api->init(self->context) != 0) { - PyErr_Format(PyExc_RuntimeError, "Failed to init environment."); + PyErr_Format(PyExc_RuntimeError, "Failed to init environment - \"%s\"", + self->env_c_api->error_message(self->context)); return -1; } char* observation_name; + int api_observation_count = self->env_c_api->observation_count(self->context); for (int i = 0; i < self->observation_count; ++i) { observation_name = PyString_AsString(PyList_GetItem(observations, i)); if (observation_name == NULL) { return -1; } int j; - for (j = 0; j < self->env_c_api->observation_count(self->context); ++j) { + for (j = 0; j < api_observation_count; ++j) { if (strcmp(self->env_c_api->observation_name(self->context, j), observation_name) == 0) { self->observation_indices[i] = j; break; } } - if (j == self->env_c_api->observation_count(self->context)) { + if (j == api_observation_count) { PyErr_Format(PyExc_ValueError, "Unknown observation '%s'.", observation_name); return -1; @@ -216,7 +234,8 @@ static PyObject* Lab_reset(LabObject* self, PyObject* args, PyObject* kwds) { } if (self->env_c_api->start(self->context, self->episode, seed) != 0) { - PyErr_SetString(PyExc_RuntimeError, "Failed to start environment."); + PyErr_Format(PyExc_RuntimeError, "Failed to start environment - \"%s\"", + self->env_c_api->error_message(self->context)); return NULL; } self->num_steps = 0; @@ -268,11 +287,12 @@ static PyObject* Lab_step(LabObject* self, PyObject* args, PyObject* kwds) { PyArrayObject* discrete = (PyArrayObject*)action_obj; + int action_discrete_count = + self->env_c_api->action_discrete_count(self->context); if (PyArray_NDIM(discrete) != 1 || - PyArray_DIM(discrete, 0) != - self->env_c_api->action_discrete_count(self->context)) { + PyArray_DIM(discrete, 0) != action_discrete_count) { PyErr_Format(PyExc_ValueError, "action must have shape (%i)", - self->env_c_api->action_discrete_count(self->context)); + action_discrete_count); return NULL; } @@ -285,6 +305,11 @@ static PyObject* Lab_step(LabObject* self, PyObject* args, PyObject* kwds) { self->status = self->env_c_api->advance(self->context, num_steps, &reward); self->num_steps += num_steps; + if (self->status == EnvCApi_EnvironmentStatus_Error) { + PyErr_Format(PyExc_ValueError, "Failed to advance environment \"%s\"", + self->env_c_api->error_message(self->context)); + return NULL; + } return PyFloat_FromDouble(reward); } @@ -295,13 +320,14 @@ static int ObservationType2typenum(EnvCApi_ObservationType type) { return NPY_DOUBLE; case EnvCApi_ObservationBytes: return NPY_UINT8; - default: + case EnvCApi_ObservationString: return -1; } + return -1; } static PyObject* Lab_observation_spec(LabObject* self) { - int count = self->env_c_api->observation_count(self->context); + int count = self->observation_count; PyObject* result = PyList_New(count); if (result == NULL) { PyErr_NoMemory(); @@ -312,9 +338,28 @@ static PyObject* Lab_observation_spec(LabObject* self) { PyObject* shape; for (int i = 0; i < count; ++i) { - self->env_c_api->observation_spec(self->context, i, &spec); - type = (PyObject*)PyArray_DescrFromType(ObservationType2typenum(spec.type)) - ->typeobj; + int idx = self->observation_indices[i]; + self->env_c_api->observation_spec(self->context, idx, &spec); + if (spec.type == EnvCApi_ObservationString) { + type = (PyObject*)(&PyString_Type); + shape = PyTuple_New(0); + if (PyList_SetItem(result, i, + Py_BuildValue("{s:s,s:N,s:O}", "name", + self->env_c_api->observation_name( + self->context, idx), + "shape", shape, "dtype", type)) != 0) { + PyErr_SetString(PyExc_RuntimeError, "Unable to populate list"); + return NULL; + } + continue; + } + int observation_type = ObservationType2typenum(spec.type); + if (observation_type == -1) { + PyErr_SetString(PyExc_RuntimeError, "Invalid observation spec."); + return NULL; + } + + type = (PyObject*)PyArray_DescrFromType(observation_type)->typeobj; shape = PyTuple_New(spec.dims); for (int j = 0; j < spec.dims; ++j) { if (PyTuple_SetItem(shape, j, PyInt_FromLong(spec.shape[j])) != 0) { @@ -325,7 +370,7 @@ static PyObject* Lab_observation_spec(LabObject* self) { if (PyList_SetItem( result, i, Py_BuildValue("{s:s,s:N,s:O}", "name", - self->env_c_api->observation_name(self->context, i), + self->env_c_api->observation_name(self->context, idx), "shape", shape, "dtype", type)) != 0) { PyErr_SetString(PyExc_RuntimeError, "Unable to populate list"); return NULL; @@ -364,55 +409,132 @@ static PyObject* Lab_action_spec(LabObject* self) { return discrete; } -static PyObject* Lab_observations(LabObject* self) { - PyObject* result = NULL; - PyArrayObject* array = NULL; +static PyObject* make_observation(const EnvCApi_Observation* observation) { + if (observation->spec.type == EnvCApi_ObservationString) { + PyObject* result = PyString_FromStringAndSize(observation->payload.string, + observation->spec.shape[0]); + if (result == NULL) PyErr_NoMemory(); + return result; + } + + int observation_type = ObservationType2typenum(observation->spec.type); + if (observation_type == -1) { + PyErr_SetString(PyExc_RuntimeError, "Invalid observation spec."); + return NULL; + } + + long* bounds = calloc(observation->spec.dims, sizeof(long)); + if (bounds == NULL) { + PyErr_NoMemory(); + return NULL; + } + + for (int j = 0; j < observation->spec.dims; ++j) { + bounds[j] = observation->spec.shape[j]; + } + + PyArrayObject* array = (PyArrayObject*)PyArray_SimpleNew( + observation->spec.dims, bounds, observation_type); + free(bounds); + if (array == NULL) { + PyErr_NoMemory(); + return NULL; + } + + const void* src_mem = observation->spec.type == EnvCApi_ObservationDoubles + ? (void*)observation->payload.doubles + : (void*)observation->payload.bytes; + memcpy(PyArray_BYTES(array), src_mem, PyArray_NBYTES(array)); + return (PyObject*)array; +} + +static PyObject* Lab_observations(LabObject* self) { if (!is_running(self)) { PyErr_SetString(PyExc_RuntimeError, "Environment in wrong status for call to observations()"); return NULL; } - result = PyDict_New(); + PyObject* result = PyDict_New(); if (result == NULL) { PyErr_NoMemory(); return NULL; } EnvCApi_Observation observation; - long* bounds = NULL; for (int i = 0; i < self->observation_count; ++i) { self->env_c_api->observation(self->context, self->observation_indices[i], &observation); - bounds = calloc(observation.spec.dims, sizeof(long)); - if (bounds == NULL) { - PyErr_NoMemory(); - return NULL; - } - for (int j = 0; j < observation.spec.dims; ++j) { - bounds[j] = observation.spec.shape[j]; - } - const void* src_mem = observation.spec.type == EnvCApi_ObservationDoubles - ? (void*)observation.payload.doubles - : (void*)observation.payload.bytes; - array = (PyArrayObject*)PyArray_SimpleNew( - observation.spec.dims, bounds, - ObservationType2typenum(observation.spec.type)); - free(bounds); - - if (array == NULL) { - PyErr_NoMemory(); + PyObject* entry = make_observation(&observation); + if (entry == NULL) { + Py_DECREF(result); return NULL; } - memcpy(PyArray_BYTES(array), src_mem, PyArray_NBYTES(array)); + // PyDict_SetItemString increments reference count. PyDict_SetItemString(result, self->env_c_api->observation_name( self->context, self->observation_indices[i]), - (PyObject*)array); - Py_DECREF((PyObject*)array); + entry); + Py_DECREF(entry); + } + return result; +} + +static PyObject* Lab_events(LabObject* self) { + switch (self->status) { + case ENV_STATUS_INITIALIZED: + case EnvCApi_EnvironmentStatus_Running: + case EnvCApi_EnvironmentStatus_Terminated: + break; + default: + PyErr_SetString(PyExc_RuntimeError, + "Environment in wrong status for call to events()"); + return NULL; + } + + int event_type_count = self->env_c_api->event_type_count(self->context); + int event_count = self->env_c_api->event_count(self->context); + PyObject* result = PyList_New(event_count); + if (result == NULL) { + PyErr_NoMemory(); + return NULL; + } + + for (int event_id = 0; event_id < event_count; ++event_id) { + EnvCApi_Event event; + self->env_c_api->event(self->context, event_id, &event); + if (0 > event.id || event.id >= event_type_count) { + PyErr_Format(PyExc_RuntimeError, + "Environment generated invalid event id. " + "Event id(%d) must be in range [0, %d).", + event.id, event_type_count); + Py_DECREF(result); + return NULL; + } + PyObject* entry = PyTuple_New(2); + PyTuple_SetItem(entry, 0, + PyString_FromString(self->env_c_api->event_type_name( + self->context, event.id))); + + PyObject* observation_list = PyList_New(event.observation_count); + if (observation_list == NULL) { + Py_DECREF(result); + return NULL; + } + for (int obs_id = 0; obs_id < event.observation_count; ++obs_id) { + PyObject* obs_enty = make_observation(&event.observations[obs_id]); + if (obs_enty == NULL) { + Py_DECREF(observation_list); + Py_DECREF(result); + return NULL; + } + PyList_SetItem(observation_list, obs_id, obs_enty); + } + PyTuple_SetItem(entry, 1, observation_list); + PyList_SetItem(result, event_id, entry); } return result; @@ -445,6 +567,7 @@ static PyMethodDef LabObject_methods[] = { "The shape of the actions"}, {"observations", (PyCFunction)Lab_observations, METH_NOARGS, "Get the observations"}, + {"events", (PyCFunction)Lab_events, METH_NOARGS, "Get the events"}, {"close", (PyCFunction)Lab_close, METH_NOARGS, "Close the environment"}, {NULL} /* Sentinel */ }; @@ -499,11 +622,14 @@ static PyObject* module_set_runfiles_path(PyObject* self, PyObject* args) { if (!PyArg_ParseTuple(args, "s", &new_path)) { return NULL; } - if (sizeof(runfiles_path) < strlen(new_path)) { + + if (strlen(new_path) < sizeof(runfiles_path)) { + strcpy(runfiles_path, new_path); + } else { PyErr_SetString(PyExc_RuntimeError, "Runfiles directory name too long!"); return NULL; } - strcpy(runfiles_path, new_path); + Py_RETURN_TRUE; } @@ -532,17 +658,38 @@ PyMODINIT_FUNC initdeepmind_lab(void) { PyModule_AddObject(m, "Lab", (PyObject*)&deepmind_lab_LabType); #ifdef DEEPMIND_LAB_MODULE_RUNFILES_DIR - static const char kRunfiles[] = "."; + PyObject *v = PyObject_GetAttrString(m, "__file__"); + if (v && PyString_Check(v)) { + const char* file = PyString_AsString(v); + if (strlen(file) < sizeof(runfiles_path)) { + strcpy(runfiles_path, file); + } else { + PyErr_SetString(PyExc_RuntimeError, "Runfiles directory name too long!"); + return; + } + + char* last_slash = strrchr(runfiles_path, '/'); + if (last_slash != NULL) { + *last_slash = '\0'; + } else { + PyErr_SetString(PyExc_RuntimeError, + "Unable to determine runfiles directory!"); + return; + } + } else { + strcpy(runfiles_path, "."); + } #else static const char kRunfiles[] = ".runfiles/org_deepmind_lab"; - if (sizeof(runfiles_path) < - strlen(Py_GetProgramFullPath()) + sizeof(kRunfiles)) { + if (strlen(Py_GetProgramFullPath()) + strlen(kRunfiles) < + sizeof(runfiles_path)) { + strcpy(runfiles_path, Py_GetProgramFullPath()); + strcat(runfiles_path, kRunfiles); + } else { PyErr_SetString(PyExc_RuntimeError, "Runfiles directory name too long!"); return; } - strcpy(runfiles_path, Py_GetProgramFullPath()); #endif - strcat(runfiles_path, kRunfiles); srand(time(NULL)); diff --git a/python/pip_package/BUILD b/python/pip_package/BUILD new file mode 100644 index 00000000..dc94d757 --- /dev/null +++ b/python/pip_package/BUILD @@ -0,0 +1,15 @@ +# Description: +# A tool for building the Deepmind Lab pip package. + +package(default_visibility = ["//visibility:private"]) + +sh_binary( + name = "build_pip_package", + srcs = ["build_pip_package.sh"], + data = [ + "README.md", + "__init__.py", + "setup.py", + "//:deepmind_lab.so", + ], +) diff --git a/python/pip_package/README.md b/python/pip_package/README.md new file mode 100644 index 00000000..7df4c3e4 --- /dev/null +++ b/python/pip_package/README.md @@ -0,0 +1,320 @@ +# DeepMind Lab Python Module + +The DeepMind Lab Python module is the recommended way to use DeepMind Lab +outside of a Bazel project. You can create and run environments using the same +Python API that you would use with a Bazel project, or you can use the built-in +adapter for OpenAI's Gym API. + +## Build and Install + +The build and install process has the following steps: + +- Install project dependencies. +- Build project assets and binaries with Bazel. +- Bundle project assets and binaries into a Python package. +- Install the package. + +Here's the short version if you have already set up the dependencies. + +```sh +git clone git clone https://github.com/deepmind/lab.git && cd lab +bazel build python/pip_package:build_pip_package +./bazel-bin/python/pip_package/build_pip_package /tmp/dmlab_pkg +pip install /tmp/dmlab_pkg/DeepMind_Lab-1.0-py2-none-any.whl --force-reinstall +``` + +#### Dependencies + +First, see +[External dependencies, prerequisites and porting notes](https://github.com/deepmind/lab/blob/master/README.md) +and install any requirements you might be missing. In addition to the +requirements listed there, you may also need: + +- [pip](https://pip.pypa.io/en/stable/installing/) +- [NumPy](https://docs.scipy.org/doc/numpy/user/install.html) +- [virtualenv](https://virtualenv.pypa.io/en/stable/installation/) (optional) +- [OpenAI Gym](https://github.com/openai/gym#installation) (optional) +- [Pyglet](http://pyglet.org) (optional) + +If you prefer, NumPy, Gym, and Pyglet can be installed as part of the +**install** step below so they are added to the virtualenv for your project. +Instructions are provided below for doing so. + +#### Build assets/binaries + +Following this are more detailed instructions on how to build and install if you +haven't followed the short version of the instructions above. To begin, if you +haven't already, clone DeepMind Lab. + +```sh +$ git clone https://github.com/deepmind/lab.git +``` + + +To build the prerequisite assets and binaries for the Python package, change to +the `lab` directory and run the Bazel command to build the pip package script: + +```sh +$ cd lab +$ bazel build python/pip_package:build_pip_package +``` + +If the build command fails, make sure you've grabbed the latest version and +double check that you've installed all of the dependencies for DeepMind Lab, +then [file a bug](https://github.com/deepmind/lab/issues/new). + +Keep in mind that for most changes you make to DeepMind Lab, including adding +new game scripts, models, textures, and code, that you will need to rebuild the +package script and perform the following installation instructions again. + +#### Package assets/binaries + +To create a Python package for DeepMind Lab run the following: + +```sh +$ ./bazel-bin/python/pip_package/build_pip_package /tmp/dmlab_pkg +``` +This script copies all of the relevant files for the package to a temporary +directory and bundles them up into the distribution file saved in the directory +specified. (It needs to be run from the root of the DeepMind Lab source +directory since it expects to find `bazel-bin` in the directory from which it is +called.) + +#### Install + +The recommended way to use the DeepMind Lab Python Module is with Virtualenv, a +tool that allows you to keep the dependencies for projects separate from one +another. Instructions provided are for building and installing with Virtualenv, +but you can skip these steps to install the package with your system-wide +packages. + +First, create a virtual environment in your project directory: + +```sh +$ cd ~/my_agent +$ virtualenv agentenv +``` + +Once you've created your virtualenv, you can activate it using: + +```sh +$ source agentenv/bin/activate +``` + +Once your virtualenv is activated, install any remaining dependencies: + +```sh +(agentenv)$ pip install numpy gym pyglet +``` + +The package generation step will have created a `.whl` file in `/tmp/dmlab_pkg`. +This is the binary distribution file for DeepMind Lab. Install it using: + +```sh +(agentenv)$ pip install /tmp/dmlab_pkg/DeepMind_Lab-1.0-py2-none-any.whl +``` + +After a successful install you're now ready to start using DeepMind Lab as a +standalone module. See the instructions below for using the included DeepMind +Lab Python API or the wrapper for the OpenAI Gym API. + +Finally, when you're done using your virtualenv you can deactivate it by +running: + +```sh +(agentenv)$ deactivate +``` + +#### Testing the Installation + +Create a new file `agent.py` and add the following: + +```python +import deepmind_lab +import numpy as np + +# Create a new environment object. +lab = deepmind_lab.Lab("test_levels/empty_room_test", ['RGB_INTERLACED'], + {'fps': '30', 'width': '80', 'height': '60'}) +lab.reset(seed=1) + +# Execute 100 walk-forward steps and sum the returned rewards from each step. +print sum( + [lab.step(np.array([0,0,0,1,0,0,0], dtype=np.intc)) for i in range(0, 100)]) +``` + +Run `agent.py`: + +```sh +(agentenv)$ python agent.py +``` + +DeepMind Lab prints debugging/diagnostic info to the console, but at the end it +should print out a number showing the reward. For the seed provided, the reward +should be 11.0. + +#### Uninstall + +If you make changes to any files that get bundled in the package then you can +either uninstall the old version of DeepMind Lab before installing the new +version, or invoke the above `pip install` command with the flag +`--force-reinstall`. + +If you really want to say goodbye, just run: + +```sh +$ pip uninstall deepmind_lab +``` + +If you've installed this to your virtualenv and to your system-wide packages you +will need to run this command once for each. + +## DeepMind Lab Python API + +You can use the same API for the standalone Python module as you do when +building with the Bazel project. See: +[DeepMind Lab environment documentation: Python](https://github.com/deepmind/lab/blob/master/docs/python_api.md). + +## Using with OpenAI Gym + +[OpenAI Gym](https://gym.openai.com/) is a toolkit that greatly simplifies +sharing and replicating results for agents on reinforcement learning tasks. +DeepMind Lab follows the same environment model expressed by the +[Gym Environment API](https://github.com/openai/gym/blob/master/gym/core.py) +with synchronous timesteps and a scalar extrinsic reward signal, so the two fit +together naturally. + +If you have used the OpenAI Gym API, you'll be glad to know that you can just +about use your existing agents with DeepMind Lab out of the box. Even if you +have not used Gym you may still find it easier to get started building an agent +since the API is simpler and you'll find more compatible examples for agents +others have created for different environments and tasks. + +#### A Simple Example + +The following example demonstrates running an episode with random actions using +the OpenAI Gym API for DeepMind Lab. + +```python +from deepmind_lab import gym_task +import gym + +gym_task.register_basic_tasks() +env = gym.make('deepmind_lab_seekavoid_arena_01-v1') +env.seed(0) +env.reset() + +done = False +while not done: + obs, reward, done, _ = env.step(env.action_space.sample()) + env.render() + +env.close() +``` + +The only requirements for using deepmind_lab with Gym are either registering +the provided tasks using `register_basic_tasks()` from the `gym_task` submodule +as shown above, or registering your own task described in the **Tasks** section +below. + +#### Changes to Existing Agents + +For compatibility with existing agents you may need to convert +actions/observations to and from `numpy.ndarray`s which are used in the action +and observation spaces for most other environments. + +```python +# Convert a Lab action to a numpy array. +np_action = numpy.concatenate(lab_action) +# Convert a numpy array back to a Lab action. +lab_action = [numpy.array([val]) for val in np_action.tolist()] + +# For observations this is much simpler since all built-in tasks return only one +# observation. +# Convert a Lab observation to a numpy array. +np_observation = lab_observation[0] +# Convert a numpy array back to a Lab observation. +lab_observation = [np_observation] +``` + +#### Methods + +The Gym Environment API exposes five methods: `seed`, `reset`, `step`, `render`, +and `close`. For DeepMind Lab, these each do the following: + +- `seed(seed=None)`: Sets the seed to be used on reset. +- `reset()`: Resets the environment to the beginning of a new episode with the + same task. +- `step(action)`: Performs the provided action for a single timestep. +- `render(mode='human', close=False)`: Renders the environment in the mode + specified. `'human'` mode renders an observation in a pyglet window. + `'rgb_array'` mode returns the observation. `close` just closes the Pyglet + window when True. +- `close()`: Shuts down this instance of the environment. + +The API also exposes three properties: `action_space`, `observation_space`, and +`reward_range`. The values are as follows: + +- `action_space`: An object that inherits from `gym.Space`. An action + represented by the action space will be a list with seven elements of + `numpy.ndarray`, where each element of the list corresponds to a different + action type. You can create a random action with `action_space.sample()` or + build a no-op action with `[numpy.zeros((1,))] * 7`. +- `observation_space`: An object that inherits from `gym.Space` and describes + the shape of possible observations. Observations for the built in tasks are + lists of length one containing a `numpy.ndarray` with shape `(240, 320, 3)` + representing RGB pixel values. Custom tasks may include more or different + observations. +- `reward_range`: The default reward range: `(-inf, inf)` + +#### Tasks + +An up-to-date list of the built-in tasks can be found by running + +```python +[task.label for task in deepmind_lab.gym_task.BASIC_TASKS] +``` + +or by looking in +[gym_task.py](https://github.com/deepmind/lab/blob/master/python/pip_package/gym_task.py). + +To play around with each of these tasks, we recommend building and running the +engine with human controls from the base of the DeepMind Lab sources like so: + +```sh +$ bazel run :game -- --level_script seekavoid_arena_01 +``` + +To customise or configure a task you simply create a +`deepmind_lab.gym_task.GymTask` with the environment parameters you would like +and register it with gym by calling its `register()` method. For example, you +could create a 'seekavoid_arena_01' environment at 84x84 resolution (often used +at DeepMind) with the following: + +```python +seekavoid_small = deepmind_lab.gym_task.GymTask( + 'seekavoid_arena_01', width=84, height=84) +seekavoid_small.register() +env = gym.make(seekavoid_small.label) +``` + +`GymTask` accepts the following parameters: + +- `name` (required) - the filename of any of the level scripts in + `assets/game_scripts` with out the `.lua` extension. +- `version` - An integer specifying the version of the task (only used by Gym to + disambiguate.) +- `observation_types` - A list of strings corresponding to the names of the + observation types you want from the environment. For valid types for a + particular task you can call: +```python +print deepmind_lab.Lab('seekavoid_arena_01',[],{}).observation_spec() +``` +- `fps`, `width`, `height` - Integer values specifying the frames per second and + observation pixel resolutions. +- `side_effects` - A dictionary containing additional flags to pass along to the + environment for logging and recording purposes. Calling this with + non-whitelisted flags will still register the environment for debugging + purposes, but you will have to handle a + `deepmind_lab.gym_task.ConfigurationWhitelistException`. Whitelisted flags are + `logToStdErr`, `vmMode`, and `record`. diff --git a/python/pip_package/__init__.py b/python/pip_package/__init__.py new file mode 100644 index 00000000..f7095169 --- /dev/null +++ b/python/pip_package/__init__.py @@ -0,0 +1,7 @@ +"""Loads deepmind_lab.so.""" + +import imp +import pkg_resources + +imp.load_dynamic(__name__, pkg_resources.resource_filename( + __name__, 'deepmind_lab.so')) diff --git a/python/pip_package/build_pip_package.sh b/python/pip_package/build_pip_package.sh new file mode 100755 index 00000000..0324436c --- /dev/null +++ b/python/pip_package/build_pip_package.sh @@ -0,0 +1,67 @@ +#!/bin/bash +# +# This script creates a temporary directory with the structure required by +# setuptools to build the deepmind_lab package and generates a binary +# distribution in the directory specified. +# +# This should be invoked directly and not via "bazel run" since the working +# directory has to be the root of the build tree. + +set -e + +function main() { + if [[ ${#} -lt 1 ]]; then + echo "No destination dir provided" + exit 1 + fi + + DEST="${1}" + TMPDIR=$(mktemp --directory -t tmp.XXXXXXXXXX) + + if [[ ! -d bazel-bin ]]; then + echo "Could not find bazel-bin. Did you run from the root of the build tree?" + exit 1 + fi + + if [[ ! -d bazel-bin/python/pip_package/build_pip_package.runfiles/org_deepmind_lab ]]; then + # Old-style runfiles structure without the org name. + cp --dereference --recursive -- \ + bazel-bin/python/pip_package/build_pip_package.runfiles \ + "${TMPDIR}/deepmind_lab" + else + # New-style runfiles structure + cp --dereference --recursive -- \ + bazel-bin/python/pip_package/build_pip_package.runfiles/org_deepmind_lab \ + "${TMPDIR}/deepmind_lab" + fi + + cp -- python/pip_package/README.md "${TMPDIR}" + cp -- python/pip_package/setup.py "${TMPDIR}" + for LAB_FILE in "__init__.py" "gym_environment.py" "gym_space.py" \ + "gym_task.py" "render_window.py"; do + cp -- "python/pip_package/${LAB_FILE}" "${TMPDIR}/deepmind_lab/${LAB_FILE}" + done + + MANIFEST_IN="${TMPDIR}/MANIFEST.in" + echo "include README.md" >> "${MANIFEST_IN}" + cd "${TMPDIR}" && find deepmind_lab -type f | awk '$0="include "$0' >> "${MANIFEST_IN}" + + if [[ -z "${PYTHON_BIN_PATH}" ]]; then + PYTHON_BIN_PATH=$(which python || which python3 || true) + fi + if [[ ! -e "${PYTHON_BIN_PATH}" ]]; then + echo "Invalid python path. ${PYTHON_BIN_PATH} cannot be found" 1>&2 + exit 1 + fi + + pushd "${TMPDIR}" > /dev/null + echo $(date) : "=== Building wheel" + "${PYTHON_BIN_PATH}" setup.py bdist_wheel > /dev/null + mkdir --parents -- "${DEST}" + cp -- dist/* "${DEST}" + popd > /dev/null + rm --recursive --force -- "${TMPDIR}" + echo $(date) : "=== Output wheel file is in: ${DEST}" +} + +main "$@" diff --git a/python/pip_package/gym_environment.py b/python/pip_package/gym_environment.py new file mode 100644 index 00000000..10e7b2e1 --- /dev/null +++ b/python/pip_package/gym_environment.py @@ -0,0 +1,197 @@ +"""The OpenAI Gym API wrapper for DeepMind Lab.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import deepmind_lab +from deepmind_lab import gym_space +from deepmind_lab import render_window +import gym +import numpy as np + + +class DeepMindLabGymEnvironment(gym.Env): + """The environment for all DeepMind Lab tasks. + + Rather than constructing this manually, it's recommended that you create a + deepmind_lab.gym_task.GymTask (or use an existing one) then create the + environment |gym.make| with the task name. (Using gym.make is required for gym + monitoring which is needed to log/save episode results, upload results to the + gym leaderboard, and record videos of runs.) + + See the documentation in gym_task.py for details on how to construct an + environment. + """ + + metadata = { + 'render.modes': ['human', 'rgb_array'], + } + + def __init__(self, task): + """Initialises a DeepMindLabGymEnvironment. + + Args: + task: A deepmind_lab.gym_task.GymTask object that contains the + initialisation parameters for an environment task. + """ + self.render_window = None + self._env_seed = None + self.metadata['video.frames_per_second'] = task.fps + + # RGB_INTERLACED is required for 'human' and 'rgb_array' render modes. + observation_types = list(task.observation_types) + if 'RGB_INTERLACED' not in observation_types: + observation_types.append('RGB_INTERLACED') + + params = { + 'fps': str(task.fps), + 'width': str(task.width), + 'height': str(task.height), + } + params.update(task.side_effects) + self.lab = deepmind_lab.Lab(task.name, observation_types, params) + + # Filter the observation spec to include only observation types requested + # in the task. + self.observation_spec = [obs for obs in self.lab.observation_spec() + if obs['name'] in task.observation_types] + + # Required defines for gym.Env. + self.action_space = gym_space.GymSpace(self.lab.action_spec()) + self.observation_space = gym_space.GymSpace(self.observation_spec) + # self.reward_range is not defined for any current DeepMind Lab tasks. + + def _step(self, action): + """Performs one timestep of the environment. + + Called from Env.step() in OpenAI Gym gym/core.py. + + Args: + action: A list of numpy ndarrays, each array corresponding to an action. + For all included tasks there are 7 actions each of shape (1,). + + Returns: + observation: A list of numpy ndarrays, each array corresponding to an + observation specified in observation_types of the GymTask + provided at construction. + reward: A scalar representing the reward accumulated in this timestep. + done: True if and only if the task is finished. + info: An empty dictionary. + + Raises: + ValueError: An invalid action was provided. + """ + if not self.action_space.contains(action): + raise ValueError('{} ({}) invalid'.format(action, type(action))) + + reward = self.lab.step(np.concatenate(action).ravel()) + done = not self.lab.is_running() + return self._observation_list(), reward, done, {} + + def _reset(self): + """Resets the DeepMind Lab environment. + + Called from Env.reset() in OpenAI Gym gym/core.py. + + Returns: + The initial observation for the environment. + """ + self.lab.reset(seed=self._env_seed) + return self._observation_list() + + def _render(self, mode='human', close=False): + """Renders the DeepMind Lab environment in the mode specified. + + Called from Env.render() in OpenAI Gym gym/core.py. + + Args: + mode: 'human' mode renders an observation in a pyglet window. + 'rgb_array' mode returns the observation. + close: If 'human' mode, True closes the window (if one is open) or + prevents a window from opening. False either opens the window or keeps + it open (rendering to the open window). For 'rgb_array' mode this + argument is ignored. + + Returns: + For 'rgb_array', a uint8 numpy array of the observation with shape + (height, width, 3), for 'human' mode, returns None. + + Raises: + ValueError: Unsupported mode provided. + """ + if mode == 'human': + return self._render_window(close) + elif mode == 'rgb_array': + return self._render_rgb_array() + else: + raise ValueError("Render mode must be one of {} but got '{}'".format( + self.metadata['render.modes'], mode)) + + def _render_window(self, close): + """Open a RenderWindow and display the observation there. + + The render window remains open until explicitly closed by calling: + env.render(mode='human', close=True) + + Args: + close: False to open the window, True to close the window. + """ + if close: + if self.render_window: + self.render_window.close() + return + + rgb_array = self._render_rgb_array() + if self.render_window is None: + self.render_window = render_window.RenderWindow( + rgb_array.shape[1], # width + rgb_array.shape[0]) # height + self.render_window.open() + + self.render_window.render(rgb_array) + + def _render_rgb_array(self): + """Return a uint8 numpy array with the RGB_INTERLACED observation.""" + if self.lab.is_running(): + return self.lab.observations()['RGB_INTERLACED'] + else: + # Gym requests observations for video observation after the environment + # has terminated. Return an empty observation. + spec = (spec for spec in self.observation_spec if + spec['name'] == 'RGB_INTERLACED').next() + return np.zeros(spec['shape'], dtype=np.uint8) + + def _close(self): + """Closes the DeepMind Lab environment. + + Called from Env.close() in OpenAI Gym gym/core.py. + """ + self.lab.close() + + def _seed(self, seed=None): + """Sets the seed to be used for the DeepMind Lab environment on reset. + + Called from Env.seed() in OpenAI Gym gym/core.py. + + Args: + seed: The seed for the random number generator in DeepMind Lab. If None or + left unset, the engine uses a random seed. + """ + self._env_seed = seed + + def _observation_list(self): + """Creates a list of observations from the environment. + + The input task.observation_types specifies the types and the order of the + observations returned by observation_list. + + Returns: + A list of observations in the order specified by the task. + """ + observation_list = [] + if self.lab.is_running(): + env_observations = self.lab.observations() + for observation_type in self.observation_spec: + observation_list.append( + env_observations.get(observation_type['name'], None)) + return observation_list diff --git a/python/pip_package/gym_space.py b/python/pip_package/gym_space.py new file mode 100644 index 00000000..beed87bf --- /dev/null +++ b/python/pip_package/gym_space.py @@ -0,0 +1,113 @@ +"""A gym.Space for DeepMind Lab.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import gym +import numpy as np + + +class GymSpace(gym.Space): + """A space created from a DeepMind Lab action/observation spec. + + Actions/Observations take the form of Python lists containing a numpy array + for each action type or observation type. + """ + + def __init__(self, spec): + """Creates a gym.Space from a DeepMind Lab spec. + + Args: + spec: An array of dictionaries, each with any of the keys/values: + 'name': A string containing the name of the action/observation. + 'shape': A tuple with the shape of the action/observation. + 'dtype': The numpy data type of the action/observation. + 'min': The minimum scalar for the action/observation (inclusive). + 'max': The maximum scalar for the action/observation (inclusive). + """ + def iterspec(): + for field in spec: + fullspec = { + 'name': field.get('name', '(unnamed)'), + 'shape': field.get('shape', (1,)), + 'dtype': field.get('dtype', np.intc), + } + kind = np.dtype(fullspec['dtype']).kind + if kind == 'i' or kind == 'u': + fullspec['min'] = field.get('min', np.iinfo(fullspec['dtype']).min) + fullspec['max'] = field.get('max', np.iinfo(fullspec['dtype']).max) + elif fullspec['dtype'].type.kind == 'f': + fullspec['min'] = field.get('min', np.finfo(fullspec['dtype']).min) + fullspec['max'] = field.get('max', np.finfo(fullspec['dtype']).max) + else: + raise ValueError( + 'deepmind_lab.GymSpace does not support data type {}.'.format( + fullspec['dtype'])) + yield fullspec + + self._spec = tuple(iterspec()) + + def sample(self): + """Generates a random action/observation contained by the space. + + Returns: + A list of numpy arrays. + """ + sample_item = [] + for field in self._spec: + kind = np.dtype(field['dtype']).kind + if kind == 'i' or kind == 'u': + sample_item.append(np.random.randint( + field['min'], field['max'] + 1, size=field['shape'], + dtype=field['dtype'])) + else: + sample_item.append(np.random.uniform( + low=field['min'], high=field['max'], size=field['shape']).astype( + field['dtype'])) + return sample_item + + def contains(self, sample): + if len(sample) != len(self._spec): + raise ValueError( + 'Mismatched space length for input. Input list has {sample_len} ' + 'item{s1} but it should have {spec_len} item{s2}.'.format( + sample_len=len(sample), s1='' if len(sample) == 1 else 's', + spec_len=len(self._spec), s2='' if len(self._spec) == 1 else 's')) + + for sample_item, spec_item in zip(sample, self._spec): + if sample_item.shape != spec_item['shape']: + raise ValueError( + 'Mismatched shape for input. Index {index} ({name}) has shape ' + '{sample_shape} but must have shape {spec_shape}.'.format( + index=self._spec.index(spec_item), name=spec_item['name'], + sample_shape=sample_item.shape, spec_shape=spec_item['shape'])) + if sample_item.dtype != spec_item['dtype']: + raise ValueError( + 'Mismatched data type for input. Index {index} ({name}) has dtype ' + '{sample_dtype} but must have dtype {spec_dtype}.'.format( + index=self._spec.index(spec_item), name=spec_item['name'], + sample_dtype=sample_item.dtype, spec_dtype=spec_item['dtype'])) + if ((spec_item['min'] > sample_item).any() or + (spec_item['max'] < sample_item).any()): + return False + return True + + def to_jsonable(self, sample_batch): + serialised = [] + for sample_n in sample_batch: + serialised.append([np.array(sample).tolist() for sample in sample_n]) + return serialised + + def from_jsonable(self, sample_batch): + deserialised = [] + for sample_n in sample_batch: + deserialised.append([np.asarray(sample) for sample in sample_n]) + return deserialised + + def __repr__(self): + return self._spec + + def __eq__(self, other): + if not isinstance(other, GymSpace): + return NotImplemented + return self._spec == other._spec # pylint: disable=protected-access diff --git a/python/pip_package/gym_task.py b/python/pip_package/gym_task.py new file mode 100644 index 00000000..ace3aadc --- /dev/null +++ b/python/pip_package/gym_task.py @@ -0,0 +1,196 @@ +"""GymTask objects contain settings for starting DeepMind Lab. + +You may use the default tasks listed at the bottom, or you can create your +own custom task. + +To create a custom task you simply create a GymTask with the environment +parameters you would like and it's automatically registered with Gym. For +example, you could create a 'seekavoid_arena_01' environment at 84x84 resolution +(often used at DeepMind) with the following: + +seekavoid_small = deepmind_lab.gym_task.GymTask( + 'seekavoid_arena_01', width=84, height=84) +seekavoid_small.register() +env = gym.make(seekavoid_small.label) + +For the name, specify any of the level scripts in assets/game_scripts. +NOTE: At the time being, if you add your own level script you'll need to +repackage and reinstall DeepMind Lab. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import copy +import gym + + +VERSION = 1 +OBSERVATION_TYPES = ('RGB_INTERLACED',) +FPS = 60 +WIDTH = 320 +HEIGHT = 240 +SIDE_EFFECTS = {} +SIDE_EFFECT_WHITELIST = set([ + 'logToStdErr', + 'vmMode', + 'record', +]) + + +class ConfigurationWhitelistException(Exception): + pass + + +class GymTask(object): + """Defines a DeepMind Lab task that can be run using the OpenAI Gym API.""" + + def __init__(self, name, version=VERSION, + observation_types=OBSERVATION_TYPES, + fps=FPS, width=WIDTH, height=HEIGHT, + side_effects=None): + """Creates and registers a DeepMind Lab task with OpenAI Gym. + + Args: + name: The name of the game script in assets/game_scripts without '.lua'. + version: An integer specifying the version of the task (only used by Gym + to disambiguate.) + observation_types: A list of strings corresponding to the names of the + observation types you want from the environment. For valid types for + a particular task you can call: + print deepmind_lab.Lab('seekavoid_arena_01',[],{}).observation_spec() + fps: An integer value specifying the frames per second. + width: The number of pixels wide for screen observations. + height: The number of pixels high for screen observations. + side_effects: A dictionary with string keys and string values passed as + environment params. Only params that don't change the behaviour of the + environment are allowed. + """ + self._name = name + self._version = version + self._observation_types = copy.copy(observation_types) + self._fps = fps + self._width = width + self._height = height + self._side_effects = {} if side_effects is None else side_effects + self._check_whitelisted_side_effects() + + @property + def label(self): + """Returns a unique string for the task parameters to register for Gym.""" + sections = ['deepmind_lab', self.name] + if not self.observation_types: + sections.append('NO_OBSERVATION') + elif self.observation_types != OBSERVATION_TYPES: + sections.append('_'.join(self.observation_types)) + if self.fps != FPS: + sections.append(str(self.fps) + 'fps') + if self.width != WIDTH or self.height != HEIGHT: + sections.append(str(self.width) + 'x' + str(self.height)) + return '_'.join(sections) + '-v' + str(self.version) + + def register(self): + """Register a task with Gym for use with gym.make.""" + gym.envs.registration.register( + id=self.label, + entry_point='deepmind_lab.gym_environment:DeepMindLabGymEnvironment', + kwargs={'task': self}, + nondeterministic=True, + local_only=self._check_local_only()) + + def _check_whitelisted_side_effects(self): + """Checks to see if side_effects are indeed only side effects. + + Raises: + ConfigurationWhitelistException: The task contains parameters that change + the behaviour of the environment. + """ + if not all(param in SIDE_EFFECT_WHITELIST for param in self._side_effects): + invalid = [ + param not in SIDE_EFFECT_WHITELIST for param in self._side_effects] + raise ConfigurationWhitelistException( + 'The provided side_effects list includes keys {} which may modify ' + 'the behaviour of DeepMind Lab. Allowed keys are {}. You may ' + 'handle this exception and continue, but please avoid submitting any ' + 'results with non-whitelisted flags set since they may invalidate ' + 'the results.'.format(invalid, SIDE_EFFECT_WHITELIST)) + + def _check_local_only(self): + return (self.observation_types != OBSERVATION_TYPES or self.fps != FPS or + self.width != WIDTH or self.height != HEIGHT or + self.side_effects) + + @property + def name(self): + return self._name + + @property + def version(self): + return self._version + + @property + def observation_types(self): + return self._observation_types + + @property + def fps(self): + return self._fps + + @property + def width(self): + return self._width + + @property + def height(self): + return self._height + + @property + def side_effects(self): + return self._side_effects + + +BASIC_TASKS = [ + # deepmind_lab_lt_chasm-v1 + GymTask('lt_chasm', version=1), + + # deepmind_lab_lt_hallway_slope-v1 + GymTask('lt_hallway_slope', version=1), + + # deepmind_lab_lt_horseshoe_color-v1 + GymTask('lt_horseshoe_color', version=1), + + # deepmind_lab_lt_space_bounce_hard-v1 + GymTask('lt_space_bounce_hard', version=1), + + # deepmind_lab_nav_maze_random_goal_01-v1 + GymTask('nav_maze_random_goal_01', version=1), + + # deepmind_lab_nav_maze_random_goal_02-v1 + GymTask('nav_maze_random_goal_02', version=1), + + # deepmind_lab_nav_maze_random_goal_03-v1 + GymTask('nav_maze_random_goal_03', version=1), + + # deepmind_lab_nav_maze_static_01-v1 + GymTask('nav_maze_static_01', version=1), + + # deepmind_lab_nav_maze_static_02-v1 + GymTask('nav_maze_static_02', version=1), + + # deepmind_lab_nav_maze_static_03-v1 + GymTask('nav_maze_static_03', version=1), + + # deepmind_lab_random_maze-v1 + GymTask('random_maze', version=1), + + # deepmind_lab_seekavoid_arena_01-v1 + GymTask('seekavoid_arena_01', version=1), + + # deepmind_lab_stairway_to_melon-v1 + GymTask('stairway_to_melon', version=1), +] + + +def register_basic_tasks(): + for task in BASIC_TASKS: + task.register() diff --git a/python/pip_package/render_window.py b/python/pip_package/render_window.py new file mode 100644 index 00000000..f2aedd9b --- /dev/null +++ b/python/pip_package/render_window.py @@ -0,0 +1,65 @@ +"""A render window for DeepMind Lab observations based on pyglet.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import sys + + +def import_pyglet(): + """Checks to see if the user has pyglet installed.""" + if 'pyglet' not in sys.modules: + try: + import pyglet # pylint: disable=g-import-not-at-top + except ImportError as e: + message = ("Pyglet is required for rendering individual frames for " + "DeepMind Lab using env.render(). You may run 'pip install " + "pyglet' to continue to render the observations to a window " + "as you are doing (recommended), or alternatively you can " + "rebuild DeepMind Lab with the flag --define=headless=false " + "and repackage the deepmind_lab Python library to render in " + "DeepMind Lab's native window.") + raise type(e)(e.message + os.linesep + message) + return pyglet + + +class RenderWindow(object): + """A wrapper for pyglet.window that renders an image in a window.""" + + def __init__(self, width, height, display=None): + self.window = None + self.width = width + self.height = height + self.display = display + + def render(self, image): + expected_shape = (self.height, self.width, 3) + if image.shape != expected_shape: + raise ValueError('Image shape must be {}, but got shape {}'.format( + image.shape, expected_shape)) + + if self.window is not None: + self.window.clear() + self.window.switch_to() + self.window.dispatch_events() + self.pyglet.image.ImageData( + self.width, self.height, 'RGB', image.astype('uint8').tobytes(), + # pitch: number of bytes per row. Negative means flip vertically. + pitch=self.width * 3 * -1 + ).blit(0, 0) + self.window.flip() + + def open(self): + self.pyglet = import_pyglet() + if self.window is None: + self.window = self.pyglet.window.Window( + width=self.width, height=self.height, display=self.display) + + def close(self): + if self.window is not None: + self.window.close() + self.window = None + + def __del__(self): + self.close() diff --git a/python/pip_package/setup.py b/python/pip_package/setup.py new file mode 100644 index 00000000..adef24db --- /dev/null +++ b/python/pip_package/setup.py @@ -0,0 +1,13 @@ +"""Setup for the deepmind_lab module.""" + +import setuptools + +setuptools.setup( + name='DeepMind Lab', + version='1.0', + description='DeepMind Lab', + long_description='', + url='https://github.com/deepmind/lab', + author='DeepMind', + packages=setuptools.find_packages(), + include_package_data=True) diff --git a/python/random_agent.py b/python/random_agent.py index e7ad02ea..1b1f5bcb 100644 --- a/python/random_agent.py +++ b/python/random_agent.py @@ -136,15 +136,20 @@ def reset(self): self.action = np.zeros([len(self.action_spec)]) -def run(length, width, height, fps, level): +def run(length, width, height, fps, level, record, demo, video): """Spins up an environment and runs the random agent.""" - env = deepmind_lab.Lab( - level, ['RGB_INTERLACED'], - config={ - 'fps': str(fps), - 'width': str(width), - 'height': str(height) - }) + config = { + 'fps': str(fps), + 'width': str(width), + 'height': str(height) + } + if record: + config['record'] = record + if demo: + config['demo'] = demo + if video: + config['video'] = video + env = deepmind_lab.Lab(level, ['RGB_INTERLACED'], config=config) env.reset() @@ -179,10 +184,18 @@ def run(length, width, height, fps, level): help='Number of frames per second') parser.add_argument('--runfiles_path', type=str, default=None, help='Set the runfiles path to find DeepMind Lab data') - parser.add_argument('--level_script', type=str, default='tests/demo_map', + parser.add_argument('--level_script', type=str, + default='test_levels/empty_room_test', help='The environment level script to load') + parser.add_argument('--record', type=str, default=None, + help='Record the run to a demo file') + parser.add_argument('--demo', type=str, default=None, + help='Play back a recorded demo file') + parser.add_argument('--video', type=str, default=None, + help='Record the demo run as a video') args = parser.parse_args() if args.runfiles_path: deepmind_lab.set_runfiles_path(args.runfiles_path) - run(args.length, args.width, args.height, args.fps, args.level_script) + run(args.length, args.width, args.height, args.fps, args.level_script, + args.record, args.demo, args.video) diff --git a/python/random_agent_simple.py b/python/random_agent_simple.py new file mode 100644 index 00000000..6a9dea2d --- /dev/null +++ b/python/random_agent_simple.py @@ -0,0 +1,81 @@ +# Copyright 2016-17 Google Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +################################################################################ +"""A simple example of a random agent in deepmind_lab.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import argparse +import random +import numpy as np + +import deepmind_lab + + +class RandomAgent(object): + """Basic random agent for DeepMind Lab.""" + def __init__(self, action_spec): + self.action_spec = action_spec + self.action_count = len(action_spec) + + def step(self): + """Choose a random amount of a randomly selected action.""" + action_choice = random.randint(0, self.action_count - 1) + action_amount = random.randint(self.action_spec[action_choice]['min'], + self.action_spec[action_choice]['max']) + action = np.zeros([self.action_count], dtype=np.intc) + action[action_choice] = action_amount + return action + + +def run(width, height, level_script, frame_count): + """Spins up an environment and runs the random agent.""" + config = {'width': str(width), 'height': str(height)} + env = deepmind_lab.Lab(level_script, ['RGB_INTERLACED'], config=config) + env.reset() + + reward = 0 + agent = RandomAgent(env.action_spec()) + for _ in xrange(frame_count): + if not env.is_running(): + print('Environment stopped early') + env.reset() + agent.reset() + action = agent.step() + reward += env.step(action, num_steps=1) + + print('Finished after %i steps. Total reward received is %f' + % (frame_count, reward)) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--frame_count', type=int, default=1000, + help='Number of steps to run the agent') + parser.add_argument('--width', type=int, default=80, + help='Horizontal size of the observations') + parser.add_argument('--height', type=int, default=80, + help='Vertical size of the observations') + parser.add_argument('--runfiles_path', type=str, default=None, + help='Set the runfiles path to find DeepMind Lab data') + parser.add_argument('--level_script', type=str, default='tests/demo_map', + help='The environment level script to load') + + args = parser.parse_args() + if args.runfiles_path: + deepmind_lab.set_runfiles_path(args.runfiles_path) + run(args.width, args.height, args.level_script, args.frame_count) diff --git a/python/tests/BUILD b/python/tests/BUILD new file mode 100644 index 00000000..ff2304fb --- /dev/null +++ b/python/tests/BUILD @@ -0,0 +1,109 @@ +# Description: +# Integration tests via Python. + +licenses(["restricted"]) # GPLv2 + +py_binary( + name = "python_benchmark", + srcs = ["benchmark.py"], + data = ["//:deepmind_lab.so"], + imports = ["python"], + main = "benchmark.py", +) + +py_test( + name = "python_module_test", + size = "large", + srcs = ["dmlab_module_test.py"], + data = ["//:deepmind_lab.so"], + imports = ["python"], + main = "dmlab_module_test.py", +) + +py_test( + name = "python_random_agent_test", + size = "large", # for TSAN + srcs = ["random_agent_test.py"], + data = ["//:deepmind_lab.so"], + imports = ["python"], + main = "random_agent_test.py", + deps = ["//:python_random_agent"], +) + +py_test( + name = "raycast_test", + size = "large", # for TSAN + srcs = ["raycast_test.py"], + data = ["//:deepmind_lab.so"], + imports = ["python"], +) + +py_test( + name = "lookat_test", + srcs = ["lookat_test.py"], + data = ["//:deepmind_lab.so"], + imports = ["python"], +) + +py_test( + name = "player_info_test", + srcs = ["player_info_test.py"], + data = ["//:deepmind_lab.so"], + imports = ["python"], +) + +py_test( + name = "extra_entities_test", + srcs = ["extra_entities_test.py"], + data = ["//:deepmind_lab.so"], + imports = ["python"], +) + +py_test( + name = "extra_entities_with_bots_test", + srcs = ["extra_entities_with_bots_test.py"], + data = ["//:deepmind_lab.so"], + imports = ["python"], +) + +py_test( + name = "episode_time_test", + srcs = ["episode_time_test.py"], + data = ["//:deepmind_lab.so"], + imports = ["python"], +) + +py_test( + name = "determinism_test", + srcs = ["determinism_test.py"], + data = ["//:deepmind_lab.so"], + imports = ["python"], +) + +py_test( + name = "spawn_inventory_test", + srcs = ["spawn_inventory_test.py"], + data = ["//:deepmind_lab.so"], + imports = ["python"], +) + +py_test( + name = "update_inventory_test", + srcs = ["update_inventory_test.py"], + data = ["//:deepmind_lab.so"], + imports = ["python"], +) + +py_test( + name = "entity_info_test", + srcs = ["entity_info_test.py"], + data = ["//:deepmind_lab.so"], + imports = ["python"], +) + +py_test( + name = "debug_observation_test", + srcs = ["debug_observation_test.py"], + data = ["//:deepmind_lab.so"], + imports = ["python"], +) diff --git a/python/benchmark.py b/python/tests/benchmark.py similarity index 100% rename from python/benchmark.py rename to python/tests/benchmark.py diff --git a/python/tests/debug_observation_test.py b/python/tests/debug_observation_test.py new file mode 100644 index 00000000..73e0d58b --- /dev/null +++ b/python/tests/debug_observation_test.py @@ -0,0 +1,100 @@ +# Copyright 2017 Google Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import unittest +import numpy as np + +import deepmind_lab + +ALL_DEBUG_OBSERVATIONS = [ + 'DEBUG.PLAYER_ID', + 'DEBUG.PLAYERS.ARMOR', + 'DEBUG.PLAYERS.GADGET', + 'DEBUG.PLAYERS.GADGET_AMOUNT', + 'DEBUG.PLAYERS.HEALTH', + 'DEBUG.PLAYERS.HOLDING_FLAG', + 'DEBUG.PLAYERS.ID', + 'DEBUG.PLAYERS.EYE.POS', + 'DEBUG.PLAYERS.EYE.ROT', + 'DEBUG.PLAYERS.NAME', + 'DEBUG.PLAYERS.TEAM', +] + + +class DebugObservationTest(unittest.TestCase): + + def test_amount(self): + fps = 60 + player_name = 'PlayerInfoTest' + env = deepmind_lab.Lab( + 'test_levels/debug_observation_test', ALL_DEBUG_OBSERVATIONS, + config={ + 'fps': str(fps), + 'width': '80', + 'height': '80', + 'playerName': player_name, + }) + + action_spec = env.action_spec() + action_index = {action['name']: i for i, action in enumerate(action_spec)} + + action = np.zeros([len(action_spec)], dtype=np.intc) + env.reset() + obs = env.observations() + self.assertEqual(obs['DEBUG.PLAYER_ID'], obs['DEBUG.PLAYERS.ID'][0]) + + names = obs['DEBUG.PLAYERS.NAME'].split('\n') + self.assertEqual(player_name, names[0]) + + self.assertEqual(100, obs['DEBUG.PLAYERS.GADGET_AMOUNT'][0]) + self.assertEqual(100, obs['DEBUG.PLAYERS.GADGET_AMOUNT'][1]) + + ## Empty player's gadget ammo. + action[action_index['FIRE']] = 1 + for _ in xrange(1000): + env.step(action) + obs = env.observations() + if obs['DEBUG.PLAYERS.GADGET_AMOUNT'][0] == 0: + break + else: + self.fail('Failed to empty gadget.') + + self.assertEqual(100, obs['DEBUG.PLAYERS.GADGET_AMOUNT'][1]) + + action[action_index['FIRE']] = 0 + action[action_index['MOVE_BACK_FORWARD']] = 1 + + for _ in xrange(1000): + env.step(action) + obs = env.observations() + if obs['DEBUG.PLAYERS.HEALTH'][0] <= 0: + break + else: + self.fail('Failed to be tagged by agent., health still' + + str(obs['DEBUG.PLAYERS.HEALTH'][0])) + + self.assertGreater(100, obs['DEBUG.PLAYERS.GADGET_AMOUNT'][1]) + +if __name__ == '__main__': + if 'TEST_SRCDIR' in os.environ: + deepmind_lab.set_runfiles_path( + os.path.join(os.environ['TEST_SRCDIR'], + 'org_deepmind_lab')) + unittest.main() diff --git a/python/tests/determinism_test.py b/python/tests/determinism_test.py new file mode 100644 index 00000000..18ba78ef --- /dev/null +++ b/python/tests/determinism_test.py @@ -0,0 +1,134 @@ +# Copyright 2017 Google Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +"""Set of tests for DMLab determinism.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import pprint +import unittest +import numpy as np + +import deepmind_lab + + +# Helper function to create DMLab environment. +def make_dmlab_environment(args=None, observations=None): + config = { + 'fps': '60', + 'width': '32', + 'height': '32', + } + + if args: + for k, v in args.iteritems(): + config[k] = v + if observations is None: + observations = ['DEBUG.POS.TRANS'] + env = deepmind_lab.Lab( + 'seekavoid_arena_01', observations, config=config) + env.reset(episode=1, seed=123) + return env + + +class DeterminismTest(unittest.TestCase): + + # Tests that performing the same action N times produces the same player + # position in each environment. + def test_player_position(self, num_steps=20): + env1 = make_dmlab_environment() + env2 = make_dmlab_environment() + + move_fwd = np.array([0, 0, 0, 1, 0, 0, 0], dtype=np.intc) + for _ in xrange(num_steps): + self.assertEqual(env1.step(move_fwd, 1), env2.step(move_fwd, 1)) + pos1 = env1.observations()['DEBUG.POS.TRANS'] + pos2 = env2.observations()['DEBUG.POS.TRANS'] + self.assertEqual(np.isclose(pos1, pos2).all(), True, + 'Player positions differ!\n' + pprint.pformat({ + 'pos1': pos1, + 'pos2': pos2 + })) + + # Test that skipping render frames simulates the same player positions as not + # frame skipping + def test_frame_skip(self, num_steps=20, repeated_actions=4): + env1 = make_dmlab_environment() + env2 = make_dmlab_environment() + + move_fwd = np.array([0, 0, 0, 1, 0, 0, 0], dtype=np.intc) + for _ in xrange(num_steps): + for _ in xrange(repeated_actions): + env1.step(move_fwd, 1) + env2.step(move_fwd, repeated_actions) + pos1 = env1.observations()['DEBUG.POS.TRANS'] + pos2 = env2.observations()['DEBUG.POS.TRANS'] + self.assertEqual(np.isclose(pos1, pos2).all(), True, + 'Player positions differ!\n' + pprint.pformat({ + 'pos1': pos1, + 'pos2': pos2 + })) + + # Tests that calling step(a, N) is the same as calling step(a, 1) N times. + def test_repeated_actions(self, num_steps=20, repeated_actions=4): + env = make_dmlab_environment() + env_repeated = make_dmlab_environment() + + self.assertEqual(num_steps % repeated_actions, 0) + + move_fwd = np.array([0, 0, 0, 1, 0, 0, 0], dtype=np.intc) + for _ in xrange(int(num_steps / repeated_actions)): + accum_reward = 0.0 + for _ in xrange(repeated_actions): + accum_reward += env.step(move_fwd, 1) + self.assertEqual( + env_repeated.step(move_fwd, repeated_actions), accum_reward) + pos1 = env.observations()['DEBUG.POS.TRANS'] + pos2 = env.observations()['DEBUG.POS.TRANS'] + self.assertEqual(np.isclose(pos1, pos2).all(), True, + 'Player positions differ!\n' + pprint.pformat({ + 'pos1': pos1, + 'pos2': pos2 + })) + + def test_pbo_pixels(self): + env = make_dmlab_environment( + args={'use_pbos': 'false'}, observations=['RGBD']) + pbo_env = make_dmlab_environment( + args={'use_pbos': 'true'}, observations=['RGBD']) + + move_fwd = np.array([0, 0, 0, 1, 0, 0, 0], dtype=np.intc) + for _ in xrange(5): + self.assertEqual(env.step(move_fwd, 1), pbo_env.step(move_fwd, 1)) + pixels = env.observations()['RGBD'] + pbo_pixels = pbo_env.observations()['RGBD'] + + self.assertEqual( + np.isclose(pixels, pbo_pixels).all(), True, + 'Pixels differ using PBOs!\n' + pprint.pformat({ + 'pixels': pixels, + 'pbo pixels': pbo_pixels + })) + + +if __name__ == '__main__': + if os.environ.get('TEST_SRCDIR'): + deepmind_lab.set_runfiles_path( + os.path.join(os.environ['TEST_SRCDIR'], + 'org_deepmind_lab')) + unittest.main() diff --git a/python/dmlab_module_test.py b/python/tests/dmlab_module_test.py similarity index 70% rename from python/dmlab_module_test.py rename to python/tests/dmlab_module_test.py index 832ef57f..5cb34af7 100644 --- a/python/dmlab_module_test.py +++ b/python/tests/dmlab_module_test.py @@ -30,28 +30,29 @@ class DeepMindLabTest(unittest.TestCase): def testInitArgs(self): with self.assertRaisesRegexp(TypeError, 'must be dict, not list'): - deepmind_lab.Lab('tests/demo_map', [], ['wrongconfig']) + deepmind_lab.Lab('test_levels/empty_room_test', [], ['wrongconfig']) with self.assertRaisesRegexp(TypeError, 'str'): - deepmind_lab.Lab('tests/demo_map', [], {'wrongtype': 3}) + deepmind_lab.Lab('test_levels/empty_room_test', [], {'wrongtype': 3}) with self.assertRaisesRegexp(TypeError, 'must be list, not None'): - deepmind_lab.Lab('tests/demo_map', None, {}) + deepmind_lab.Lab('test_levels/empty_room_test', None, {}) with self.assertRaisesRegexp(ValueError, 'Unknown observation'): - deepmind_lab.Lab('tests/demo_map', ['nonexisting_obs'], {}) + deepmind_lab.Lab('test_levels/empty_room_test', ['nonexisting_obs'], {}) def testReset(self): - lab = deepmind_lab.Lab('tests/demo_map', [], {}) + lab = deepmind_lab.Lab('test_levels/empty_room_test', [], {}) with self.assertRaisesRegexp(ValueError, '\'seed\' must be int or None, was \'str\''): lab.reset(seed='invalid') def testSpecs(self): - lab = deepmind_lab.Lab('tests/demo_map', []) + lab = deepmind_lab.Lab('test_levels/empty_room_test', + ['RGB_INTERLACED', 'RGBD']) observation_spec = lab.observation_spec() observation_names = {o['name'] for o in observation_spec} action_names = {a['name'] for a in lab.action_spec()} self.assertSetEqual(observation_names, - {'RGB_INTERLACED', 'RGB', 'RGBD_INTERLACED', 'RGBD'}) + {'RGB_INTERLACED', 'RGBD'}) for o in observation_spec: self.assertIn('shape', o) self.assertDictContainsSubset({'dtype': np.uint8}, o) @@ -74,7 +75,7 @@ def testSpecs(self): def testOpenClose(self): labs = [ - deepmind_lab.Lab('tests/demo_map', []) for _ in range(5)] + deepmind_lab.Lab('test_levels/empty_room_test', []) for _ in range(5)] for lab in labs: self.assertTrue(lab.close()) @@ -87,7 +88,7 @@ def testRun(self, steps=10, observation='RGB_INTERLACED'): action = np.zeros((7,), dtype=np.intc) reward = env.step(action, num_steps=4) - self.assertEqual(obs[observation].shape, (240, 320, 3)) + self.assertEqual(obs[observation].shape, (180, 320, 3)) self.assertEqual(reward, 0.0) def testRunClosed(self): @@ -120,13 +121,74 @@ def testWidthHeight(self, width=80, height=80, steps=10, num_steps=1): self.assertEqual(obs[observations[0]].shape, (4, width, height)) self.assertEqual(reward, 0.0) + def testStringObervations(self): + observation = 'CUSTOM_TEXT' + env = deepmind_lab.Lab( + 'test_levels/text_observation_test', [observation], + config={'height': str(32), + 'width': str(32)}) + spec = env.observation_spec()[0] + self.assertEqual(spec['name'], observation) + self.assertEqual(spec['shape'], ()) + self.assertEqual(spec['dtype'], str) + env.reset() + self.assertEqual(env.observations()[observation], 'Example Output') + + def testEvents(self): + env = deepmind_lab.Lab( + 'test_levels/event_test', [], config={'height': str(32), + 'width': str(32)}) + env.reset(episode=1, seed=7) + events = env.events() + self.assertEqual(len(events), 4) + + name, obs = events[0] + self.assertEqual(name, 'TEXT') + self.assertEqual(obs[0], 'EPISODE 1') + + name, obs = events[1] + self.assertEqual(name, 'DOUBLE') + np.testing.assert_array_equal(obs[0], np.array([[1., 0.], [0., 1.]])) + + name, obs = events[2] + self.assertEqual(name, 'BYTE') + np.testing.assert_array_equal(obs[0], np.array([2, 2], dtype=np.uint8)) + + name, obs = events[3] + self.assertEqual(name, 'ALL') + self.assertEqual(obs[0], 'Text') + np.testing.assert_array_equal(obs[1], np.array([3], dtype=np.uint8)) + np.testing.assert_array_equal(obs[2], np.array([7.])) + + action = np.zeros((7,), dtype=np.intc) + env.step(action, num_steps=1) + self.assertEqual(len(env.events()), 0) + env.step(action, num_steps=58) + self.assertEqual(len(env.events()), 0) + env.step(action, num_steps=1) + self.assertFalse(env.is_running()) + + events = env.events() + self.assertEqual(len(events), 1) + name, obs = events[0] + self.assertEqual(name, 'LOG') + self.assertEqual(obs[0], 'Episode ended') + + env.reset(episode=2, seed=8) + + events = env.events() + self.assertEqual(len(events), 4) + + name, obs = events[0] + self.assertEqual(name, 'TEXT') + self.assertEqual(obs[0], 'EPISODE 2') + def testVeloctyObservations(self, width=80, height=80): noop_action = np.zeros((7,), dtype=np.intc) forward_action = np.array([0, 0, 0, 1, 0, 0, 0], dtype=np.intc) backward_action = - forward_action look_sideways_action = np.array([512, 0, 0, 0, 0, 0, 0], dtype=np.intc) - env = deepmind_lab.Lab('seekavoid_arena_01', ['VEL.TRANS', 'VEL.ROT'], config={'height': str(height), 'width': str(width), diff --git a/python/tests/entity_info_test.py b/python/tests/entity_info_test.py new file mode 100644 index 00000000..a6d6419f --- /dev/null +++ b/python/tests/entity_info_test.py @@ -0,0 +1,96 @@ +# Copyright 2017 Google Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +"""Test for the Entity Info.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import unittest +import numpy as np + +import deepmind_lab + + +class EntityInfo(unittest.TestCase): + + def test_reward_info(self): + env = deepmind_lab.Lab( + 'test_levels/entity_info_test', ['DEBUG.PICKUPS'], + config={ + 'fps': '60', + 'width': '32', + 'height': '32' + }) + + action_spec = env.action_spec() + action_index = {action['name']: i for i, action in enumerate(action_spec)} + + env.reset() + obs = env.observations() + pickups_state = np.array([[150., 50., 16.125, 1., 1.], + [250., 50., 16.125, 1., 1.], + [350., 50., 16.125, 1., 1.], + [450., 50., 16.125, 1., -1.], + [550., 50., 16.125, 1., -1.], + [650., 50., 16.125, 1., -1.]]) + self.assertEqual( + np.isclose(obs['DEBUG.PICKUPS'], pickups_state).all(), True) + + action = np.zeros([len(action_spec)], dtype=np.intc) + action[action_index['MOVE_BACK_FORWARD']] = 1 + reward = 0 + for _ in xrange(0, 60): + reward += env.step(action, 1) + if reward == 3: + break + else: + self.fail('Failed to all positive rewards.') + + pickups_state = np.array([[150., 50., 16.125, 0., 1.], + [250., 50., 16.125, 0., 1.], + [350., 50., 16.125, 0., 1.], + [450., 50., 16.125, 1., -1.], + [550., 50., 16.125, 1., -1.], + [650., 50., 16.125, 1., -1.]]) + obs = env.observations() + self.assertEqual( + np.isclose(obs['DEBUG.PICKUPS'], pickups_state).all(), True) + for _ in xrange(0, 600): + reward += env.step(action, 1) + if reward == 0: + break + else: + self.fail('Failed to pickup last negative reward') + + pickups_state = np.array([[150., 50., 16.125, 0., 1.], + [250., 50., 16.125, 0., 1.], + [350., 50., 16.125, 0., 1.], + [450., 50., 16.125, 0., -1.], + [550., 50., 16.125, 0., -1.], + [650., 50., 16.125, 0., -1.]]) + + obs = env.observations() + self.assertEqual( + np.isclose(obs['DEBUG.PICKUPS'], pickups_state).all(), True) + +if __name__ == '__main__': + if os.environ.get('TEST_SRCDIR'): + deepmind_lab.set_runfiles_path( + os.path.join(os.environ['TEST_SRCDIR'], + 'org_deepmind_lab')) + unittest.main() diff --git a/python/tests/episode_time_test.py b/python/tests/episode_time_test.py new file mode 100644 index 00000000..54b8efb1 --- /dev/null +++ b/python/tests/episode_time_test.py @@ -0,0 +1,60 @@ +# Copyright 2017 Google Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +"""Test for the EpisodeTimeMs callback.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import unittest +import numpy as np + +import deepmind_lab + + +class EpisodeTimeTest(unittest.TestCase): + + def run_at_frame_rate(self, fps): + env = deepmind_lab.Lab( + 'test_levels/episode_time_test', ['EPISODE_TIME_SECONDS'], + config={ + 'fps': str(fps), + 'width': '32', + 'height': '32' + }) + + env.reset() + nop = np.zeros((7,), dtype=np.intc) + + for _ in xrange(0, fps): + env.step(nop, 1) + + obs = env.observations() + self.assertEqual(obs['EPISODE_TIME_SECONDS'][0], 1.0) + + def test_at_60(self): + self.run_at_frame_rate(60) + + def test_at_30(self): + self.run_at_frame_rate(30) + +if __name__ == '__main__': + if os.environ.get('TEST_SRCDIR'): + deepmind_lab.set_runfiles_path( + os.path.join(os.environ['TEST_SRCDIR'], + 'org_deepmind_lab')) + unittest.main() diff --git a/python/tests/extra_entities_test.py b/python/tests/extra_entities_test.py new file mode 100644 index 00000000..cdfb9854 --- /dev/null +++ b/python/tests/extra_entities_test.py @@ -0,0 +1,67 @@ +# Copyright 2017 Google Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +"""Tests for Adding extra Entities.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import unittest +import numpy as np + +import deepmind_lab + + +class ExtraEntitiesTest(unittest.TestCase): + + def test_pickup_apple_run(self): + env = deepmind_lab.Lab( + 'test_levels/extra_entities_test', ['DEBUG.POS.TRANS'], + config={ + 'fps': '60', + 'width': '80', + 'height': '80' + }) + + action_spec = env.action_spec() + action_index = {action['name']: i for i, action in enumerate(action_spec)} + + action = np.zeros([len(action_spec)], dtype=np.intc) + + action[action_index['MOVE_BACK_FORWARD']] = 1 + + reward = env.reset() + expected_reward = 1 + for _ in xrange(60): + reward = env.step(action, 1) + if reward > 0: + self.assertEqual(reward, expected_reward) + if expected_reward == 5: + break + else: + expected_reward += 1 + else: + print(env.observations()['DEBUG.POS.TRANS']) + self.fail('Failed to pickup all apples!') + + +if __name__ == '__main__': + if 'TEST_SRCDIR' in os.environ: + deepmind_lab.set_runfiles_path( + os.path.join(os.environ['TEST_SRCDIR'], + 'org_deepmind_lab')) + unittest.main() diff --git a/python/tests/extra_entities_with_bots_test.py b/python/tests/extra_entities_with_bots_test.py new file mode 100644 index 00000000..0aad40bf --- /dev/null +++ b/python/tests/extra_entities_with_bots_test.py @@ -0,0 +1,56 @@ +# Copyright 2017 Google Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +"""Tests for Adding extra Entities and their interaction with Bots.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import unittest +import numpy as np + +import deepmind_lab + + +class ExtraEntitiesWithBotsTest(unittest.TestCase): + + def test_bot_with_spawned_weapon(self): + env = deepmind_lab.Lab( + 'test_levels/extra_entities_with_bots_test', ['DEBUG.POS.TRANS'], + config={ + 'fps': '60', + 'width': '32', + 'height': '32', + 'spawnWeapons': 'true' + }) + + noop = np.zeros([len(env.action_spec())], dtype=np.intc) + env.reset() + for _ in xrange(6000): + env.step(noop, 1) + if [event for event in env.events() if event[0] == 'PLAYER_TAGGED']: + break + else: + self.fail('Failed to be tagged!') + + +if __name__ == '__main__': + if 'TEST_SRCDIR' in os.environ: + deepmind_lab.set_runfiles_path( + os.path.join(os.environ['TEST_SRCDIR'], + 'org_deepmind_lab')) + unittest.main() diff --git a/python/tests/lookat_test.py b/python/tests/lookat_test.py new file mode 100644 index 00000000..e49a3be0 --- /dev/null +++ b/python/tests/lookat_test.py @@ -0,0 +1,99 @@ +# Copyright 2017 Google Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +"""Basic test for the random Python agent.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import unittest +import numpy as np + +import deepmind_lab + + +class LookatTest(unittest.TestCase): + + def test_lookat_run(self): + kBrushU = 1 + kIsLooking = 3 + env = deepmind_lab.Lab( + 'test_levels/lookat_test', ['LOOK_AT'], + config={ + 'fps': '60', + 'controls': 'external', + 'width': '32', + 'height': '32' + }) + + env.reset() + noop = np.array([0, 0, 0, 0, 0, 0, 0], dtype=np.intc) + + last_look_at = None + for _ in xrange(10): + self.assertEqual(env.step(noop, 1), 0) + next_look_at = env.observations()['LOOK_AT'] + self.assertEqual(1, next_look_at[kIsLooking]) + if last_look_at is not None and np.allclose(last_look_at, next_look_at): + break + last_look_at = next_look_at + else: + self.fail('Too many iterations to stablise!') + + self.assertAlmostEqual(last_look_at[kBrushU], 0.5, delta=0.1) + look_left = np.array([-5, 0, 0, 0, 0, 0, 0], dtype=np.intc) + + for _ in xrange(100): + env.step(look_left, 1) + next_look_at = env.observations()['LOOK_AT'] + if next_look_at[kIsLooking] != 1: + self.assertAlmostEqual(last_look_at[kBrushU], 1.0, delta=0.1) + break + self.assertTrue(next_look_at[kBrushU] >= last_look_at[kBrushU]) + last_look_at = next_look_at + else: + self.fail('Failed to look away from object') + + look_right = np.array([5, 0, 0, 0, 0, 0, 0], dtype=np.intc) + + for _ in xrange(5): + env.step(look_right, 1) + next_look_at = env.observations()['LOOK_AT'] + if next_look_at[kIsLooking] == 1: + last_look_at = next_look_at + break + else: + self.fail('Failed to look back to object') + + for _ in xrange(100): + env.step(look_right, 1) + next_look_at = env.observations()['LOOK_AT'] + if next_look_at[kIsLooking] != 1: + self.assertAlmostEqual(last_look_at[kBrushU], 0.0, delta=0.1) + break + self.assertTrue(next_look_at[kBrushU] <= last_look_at[kBrushU]) + last_look_at = next_look_at + else: + self.fail('Failed to all the way through object') + + +if __name__ == '__main__': + if os.environ.get('TEST_SRCDIR'): + deepmind_lab.set_runfiles_path( + os.path.join(os.environ['TEST_SRCDIR'], + 'org_deepmind_lab')) + unittest.main() diff --git a/python/tests/player_info_test.py b/python/tests/player_info_test.py new file mode 100644 index 00000000..221d8acd --- /dev/null +++ b/python/tests/player_info_test.py @@ -0,0 +1,169 @@ +# Copyright 2017 Google Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import unittest +import numpy as np + +import deepmind_lab + + +def calculate_angle(fps, speed, frames): + # Time dialiation works to make 60fps become exactly 16ms. + time_dialation = 0.96 + + # This is a natural speed to convert between mouse movement and screen + # rotation. + pixels_per_frame_to_deg_per_second = 0.11 * 60 + time_seconds = time_dialation * frames / fps + + angle = speed * time_seconds * pixels_per_frame_to_deg_per_second + + # Angles in the engine are converted to shorts to maintain determinism. + short = int(angle * (65536.0 / 360.0) + 0.5) % 65536 + angle0_360 = short * 360.0 / 65536.0 + + # Return angle normalised to [-180, 180) + return angle0_360 if angle0_360 < 180 else angle0_360 - 360 + + +class PlayerInfo(unittest.TestCase): + + def test_movement(self): + fps = 60 + env = deepmind_lab.Lab( + 'test_levels/empty_room_test', [ + 'VEL.TRANS', + 'VEL.ROT', + 'DEBUG.POS.TRANS', + 'DEBUG.POS.ROT', + 'DEBUG.PLAYER_ID', + ], + config={ + 'fps': str(fps), + 'width': '80', + 'height': '80', + }) + + action_spec = env.action_spec() + action_index = {action['name']: i for i, action in enumerate(action_spec)} + + action = np.zeros([len(action_spec)], dtype=np.intc) + + env.reset() + player_id = env.observations()['DEBUG.PLAYER_ID'] + self.assertEqual(player_id[0], 1) + vel = env.observations()['VEL.TRANS'] + self.assertTrue(np.array_equal(vel, np.array([0, 0, 0]))) + + ops = [{ + 'axis': 0, + 'fact': 320, + 'lr': 0, + 'bf': 1 + }, { + 'axis': 0, + 'fact': -320, + 'lr': 0, + 'bf': -1 + }, { + 'axis': 1, + 'fact': -320, + 'lr': 1, + 'bf': 0 + }, { + 'axis': 1, + 'fact': 320, + 'lr': -1, + 'bf': 0 + }] + + before = env.observations()['DEBUG.POS.TRANS'] + for op in ops: + action[action_index['STRAFE_LEFT_RIGHT']] = op['lr'] + action[action_index['MOVE_BACK_FORWARD']] = op['bf'] + for _ in xrange(60): + env.step(action, 1) + vel = env.observations()['VEL.TRANS'] + vel_axis = vel[op['axis']] / op['fact'] + self.assertLessEqual(0, vel_axis) + if vel_axis >= 1: + break + else: + print(env.observations()['VEL.TRANS']) + self.fail('Failed to reach max velocity') + + action[action_index['STRAFE_LEFT_RIGHT']] = 0 + action[action_index['MOVE_BACK_FORWARD']] = 0 + + for _ in xrange(60): + env.step(action, 1) + vel = env.observations()['VEL.TRANS'] + vel_axis = vel[op['axis']] / op['fact'] + if vel_axis == 0: + break + else: + print(env.observations()['VEL.TRANS']) + self.fail('Failed to stop') + after = env.observations()['DEBUG.POS.TRANS'] + + self.assertTrue(np.allclose(before, after, atol=3.0)) + + frames = 66 + speed = 22 + env.reset() + env.step(action, 2) + action[action_index['LOOK_LEFT_RIGHT_PIXELS_PER_FRAME']] = speed + env.step(action, frames) + action[action_index['LOOK_LEFT_RIGHT_PIXELS_PER_FRAME']] = 0 + env.step(action, 10) + rot = env.observations()['DEBUG.POS.ROT'] + # Angles are stored 0 East and CCW is positive. So negate speed. + yaw = calculate_angle(fps, -speed, frames) + self.assertTrue(np.allclose(rot, np.array([0, yaw, 0]), atol=1e-5)) + action[action_index['LOOK_LEFT_RIGHT_PIXELS_PER_FRAME']] = speed / -2 + env.step(action, frames * 2) + action[action_index['LOOK_LEFT_RIGHT_PIXELS_PER_FRAME']] = 0 + env.step(action, 10) + rot = env.observations()['DEBUG.POS.ROT'] + self.assertTrue(np.allclose(rot, np.array([0, 0, 0]), atol=0.01)) + + speed = -10 + env.reset() + env.step(action, 2) + action[action_index['LOOK_DOWN_UP_PIXELS_PER_FRAME']] = speed + env.step(action, frames) + action[action_index['LOOK_DOWN_UP_PIXELS_PER_FRAME']] = 0 + env.step(action, 10) + rot = env.observations()['DEBUG.POS.ROT'] + pitch = calculate_angle(fps, speed, frames) + self.assertTrue(np.allclose(rot, np.array([pitch, 0, 0]), atol=1e-5)) + action[action_index['LOOK_DOWN_UP_PIXELS_PER_FRAME']] = speed / -2 + env.step(action, frames * 2) + action[action_index['LOOK_DOWN_UP_PIXELS_PER_FRAME']] = 0 + env.step(action, 10) + rot = env.observations()['DEBUG.POS.ROT'] + self.assertTrue(np.allclose(rot, np.array([0, 0, 0]), atol=0.01)) + +if __name__ == '__main__': + if 'TEST_SRCDIR' in os.environ: + deepmind_lab.set_runfiles_path( + os.path.join(os.environ['TEST_SRCDIR'], + 'org_deepmind_lab')) + unittest.main() diff --git a/python/random_agent_test.py b/python/tests/random_agent_test.py similarity index 75% rename from python/random_agent_test.py rename to python/tests/random_agent_test.py index 4fc85a48..fd829295 100644 --- a/python/random_agent_test.py +++ b/python/tests/random_agent_test.py @@ -22,15 +22,15 @@ import os import unittest +from python import random_agent import deepmind_lab -import random_agent class RandomAgentsTest(unittest.TestCase): def test_spring_agent_run(self, length=100): env = deepmind_lab.Lab( - 'tests/demo_map', ['RGB_INTERLACED'], + 'test_levels/empty_room_test', ['RGB_INTERLACED'], config={ 'fps': '60', 'controls': 'external', @@ -54,7 +54,7 @@ def test_spring_agent_run(self, length=100): def test_discretized_random_agent_run(self, length=100): env = deepmind_lab.Lab( - 'tests/demo_map', ['RGB_INTERLACED'], + 'test_levels/empty_room_test', ['RGB_INTERLACED'], config={ 'fps': '60', 'width': '80', @@ -75,6 +75,27 @@ def test_discretized_random_agent_run(self, length=100): reward = env.step(action, 1) self.assertIsInstance(reward, float) + def test_map_frame_count(self, length=100): + env = deepmind_lab.Lab( + 'test_levels/empty_room_test', ['MAP_FRAME_NUMBER'], + config={'fps': '60', + 'width': '80', + 'height': '80'}) + + env.reset() + agent = random_agent.DiscretizedRandomAgent() + + reward = 0 + for frame in xrange(length): + if not env.is_running(): + print('Environment stopped early') + env.reset() + obs = env.observations() + action = agent.step(reward, None) + env.step(action, 1) + frame_number = int(obs['MAP_FRAME_NUMBER']) + self.assertEquals(frame, frame_number) + if __name__ == '__main__': if os.environ.get('TEST_SRCDIR'): diff --git a/python/tests/raycast_test.py b/python/tests/raycast_test.py new file mode 100644 index 00000000..366da73f --- /dev/null +++ b/python/tests/raycast_test.py @@ -0,0 +1,107 @@ +# Copyright 2017 Google Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +"""Tests for Raycast observations.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import unittest +import numpy as np + +import deepmind_lab + + +class RaycastTest(unittest.TestCase): + + def test_pickup_raycast(self): + env = deepmind_lab.Lab( + 'test_levels/raycast_test', ['RAYCASTS', 'DEBUG.POS.TRANS'], + config={ + 'fps': '60', + 'width': '80', + 'height': '80' + }) + + action_spec = env.action_spec() + action_index = {action['name']: i for i, action in enumerate(action_spec)} + + action = np.zeros([len(action_spec)], dtype=np.intc) + + action[action_index['MOVE_BACK_FORWARD']] = 1 + + reward = env.reset() + + raycasts = env.observations()['RAYCASTS'] + self.assertEqual(raycasts[0], 1) + self.assertLess(raycasts[1], 1) + self.assertLess(raycasts[2], 1) + + for _ in xrange(120): + reward = env.step(action, 1) + if reward > 0: + self.assertEqual(reward, 1) + break + else: + print(env.observations()['DEBUG.POS.TRANS']) + self.fail('Failed to pickup 1st apple!') + + raycasts = env.observations()['RAYCASTS'] + self.assertEqual(raycasts[0], 1) + self.assertEqual(raycasts[1], 1) + self.assertLess(raycasts[2], 1) + + action[action_index['STRAFE_LEFT_RIGHT']] = 1 + action[action_index['MOVE_BACK_FORWARD']] = 0 + + for _ in xrange(160): + reward = env.step(action, 1) + if reward > 0: + self.assertEqual(reward, 1) + break + else: + print(env.observations()['DEBUG.POS.TRANS']) + self.fail('Failed to pickup 2st apple!') + + raycasts = env.observations()['RAYCASTS'] + self.assertEqual(raycasts[0], 1) + self.assertEqual(raycasts[1], 1) + self.assertEqual(raycasts[2], 1) + + action[action_index['STRAFE_LEFT_RIGHT']] = 0 + action[action_index['MOVE_BACK_FORWARD']] = -1 + + for _ in xrange(160): + reward = env.step(action, 1) + if reward > 0: + self.assertEqual(reward, 1) + break + else: + print(env.observations()['DEBUG.POS.TRANS']) + self.fail('Failed to pickup 3rd apple!') + + raycasts = env.observations()['RAYCASTS'] + self.assertLess(raycasts[0], 1) + self.assertEqual(raycasts[1], 1) + self.assertEqual(raycasts[2], 1) + +if __name__ == '__main__': + if 'TEST_SRCDIR' in os.environ: + deepmind_lab.set_runfiles_path( + os.path.join(os.environ['TEST_SRCDIR'], + 'org_deepmind_lab')) + unittest.main() diff --git a/python/tests/spawn_inventory_test.py b/python/tests/spawn_inventory_test.py new file mode 100644 index 00000000..6bb8f02a --- /dev/null +++ b/python/tests/spawn_inventory_test.py @@ -0,0 +1,72 @@ +# Copyright 2017 Google Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +"""Tests for inventory modification API.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import unittest +import numpy as np + +import deepmind_lab + + +class ScoreEventTest(unittest.TestCase): + + def test_self_orb(self): + env = deepmind_lab.Lab( + 'test_levels/spawn_inventory_test', ['DEBUG.POS.ROT'], + config={ + 'fps': '60', + 'width': '80', + 'height': '80' + }) + + action_spec = env.action_spec() + action_index = {action['name']: i for i, action in enumerate(action_spec)} + + action = np.zeros([len(action_spec)], dtype=np.intc) + + action[action_index['LOOK_DOWN_UP_PIXELS_PER_FRAME']] = 100 + + reward = env.reset() + for _ in xrange(60): + env.step(action, 1) + if env.observations()['DEBUG.POS.ROT'][0] > 85: + break + else: + self.fail('Failed to look at floor!') + + action[action_index['LOOK_DOWN_UP_PIXELS_PER_FRAME']] = 0 + action[action_index['FIRE']] = 1 + + for _ in xrange(600): + reward = env.step(action, 1) + if reward < 0: + self.assertEqual(reward, -1) + break + else: + self.fail('Failed to orb floor!') + + +if __name__ == '__main__': + if 'TEST_SRCDIR' in os.environ: + deepmind_lab.set_runfiles_path( + os.path.join(os.environ['TEST_SRCDIR'], + 'org_deepmind_lab')) + unittest.main() diff --git a/python/tests/update_inventory_test.py b/python/tests/update_inventory_test.py new file mode 100644 index 00000000..c19876f1 --- /dev/null +++ b/python/tests/update_inventory_test.py @@ -0,0 +1,91 @@ +# Copyright 2017 Google Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +"""Tests for inventory reading.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import unittest +import numpy as np + +import deepmind_lab + +GADGETS = { + 'IMPULSE': 2, ## Contact gadget. + 'RAPID': 3, ## Rapid fire gadget. + 'ORB': 6, ## Area damage gadget. (Knocks players) + 'BEAM': 7, ## Accurate and very rapid fire beam. + 'DISC': 8, ## Powerful but long period between firing. +} + + +class UpdateInventoryTest(unittest.TestCase): + + def test_weapon_auto_switch(self): + env = deepmind_lab.Lab( + 'test_levels/update_inventory_test', ['DEBUG.AMOUNT', 'DEBUG.GADGET'], + config={ + 'fps': '60', + 'width': '80', + 'height': '80' + }) + + action_spec = env.action_spec() + action_index = {action['name']: i for i, action in enumerate(action_spec)} + + action = np.zeros([len(action_spec)], dtype=np.intc) + + action[action_index['FIRE']] = 1 + + env.reset() + + self.assertEqual(env.observations()['DEBUG.GADGET'][0], GADGETS['ORB']) + self.assertEqual(env.observations()['DEBUG.AMOUNT'][0], 2) + + ## Fire two orbs. + for _ in xrange(600): + env.step(action, 1) + self.assertEqual(env.observations()['DEBUG.GADGET'][0], GADGETS['ORB']) + if env.observations()['DEBUG.AMOUNT'][0] == 0: + break + else: + self.fail('Failed use orbs!') + + ## Wait for weapon switch. + for _ in xrange(600): + env.step(action, 1) + if env.observations()['DEBUG.GADGET'][0] == GADGETS['RAPID']: + break + else: + self.fail('Failed to auto switch weapon!') + + self.assertEqual(env.observations()['DEBUG.AMOUNT'][0], 10) + for _ in xrange(600): + env.step(action, 1) + if env.observations()['DEBUG.AMOUNT'][0] == 0: + break + else: + self.fail('Failed to use rapid!') + + +if __name__ == '__main__': + if 'TEST_SRCDIR' in os.environ: + deepmind_lab.set_runfiles_path( + os.path.join(os.environ['TEST_SRCDIR'], + 'org_deepmind_lab')) + unittest.main() diff --git a/q3map2/common/aselib.c b/q3map2/common/aselib.c index 50f970f6..ef1ecac0 100644 --- a/q3map2/common/aselib.c +++ b/q3map2/common/aselib.c @@ -472,7 +472,7 @@ static void ASE_KeyMAP_DIFFUSE( const char *token ){ else { sprintf( ase.materials[ase.numMaterials].name, "(not converted: '%s')", bitmap ); - Sys_Printf( "WARNING: illegal material name '%s'\n", bitmap ); + Sys_FPrintf( SYS_WRN, "WARNING: illegal material name '%s'\n", bitmap ); } } else diff --git a/q3map2/common/cmdlib.c b/q3map2/common/cmdlib.c index 94c260d3..1b65ec8a 100644 --- a/q3map2/common/cmdlib.c +++ b/q3map2/common/cmdlib.c @@ -54,7 +54,7 @@ void *safe_malloc( size_t size ){ void *p; - p = malloc( size ); + p = calloc( size, 1 ); if ( !p ) { Error( "safe_malloc failed on allocation of %i bytes", size ); } @@ -65,7 +65,7 @@ void *safe_malloc( size_t size ){ void *safe_malloc_info( size_t size, char* info ){ void *p; - p = malloc( size ); + p = calloc( size, 1 ); if ( !p ) { Error( "%s: safe_malloc failed on allocation of %i bytes", info, size ); } diff --git a/q3map2/common/imagelib.c b/q3map2/common/imagelib.c index e857c73a..0b25d2c5 100644 --- a/q3map2/common/imagelib.c +++ b/q3map2/common/imagelib.c @@ -755,6 +755,7 @@ void LoadBMP( const char *filename, byte **pic, byte **palette, int *width, int } else { Error( "%s had strange struct size", filename ); + return; } if ( bcPlanes != 1 ) { diff --git a/q3map2/common/inout.c b/q3map2/common/inout.c index ac62549c..890dd448 100644 --- a/q3map2/common/inout.c +++ b/q3map2/common/inout.c @@ -73,8 +73,8 @@ xmlNodePtr xml_NodeForVec( vec3_t v ){ char buf[1024]; sprintf( buf, "%f %f %f", v[0], v[1], v[2] ); - ret = xmlNewNode( NULL, "point" ); - xmlNodeSetContent( ret, buf ); + ret = xmlNewNode( NULL, BAD_CAST "point" ); + xmlNodeSetContent( ret, BAD_CAST buf ); return ret; } @@ -151,15 +151,15 @@ void xml_Select( char *msg, int entitynum, int brushnum, qboolean bError ){ // now build a proper "select" XML node sprintf( buf, "Entity %i, Brush %i: %s", entitynum, brushnum, msg ); - node = xmlNewNode( NULL, "select" ); - xmlNodeSetContent( node, buf ); + node = xmlNewNode( NULL, BAD_CAST "select" ); + xmlNodeSetContent( node, BAD_CAST buf ); level[0] = (int)'0' + ( bError ? SYS_ERR : SYS_WRN ) ; level[1] = 0; - xmlSetProp( node, "level", (char *)&level ); + xmlSetProp( node, BAD_CAST "level", BAD_CAST (char *)&level ); // a 'select' information sprintf( buf, "%i %i", entitynum, brushnum ); - select = xmlNewNode( NULL, "brush" ); - xmlNodeSetContent( select, buf ); + select = xmlNewNode( NULL, BAD_CAST "brush" ); + xmlNodeSetContent( select, BAD_CAST buf ); xmlAddChild( node, select ); xml_SendNode( node ); @@ -178,15 +178,15 @@ void xml_Point( char *msg, vec3_t pt ){ char buf[1024]; char level[2]; - node = xmlNewNode( NULL, "pointmsg" ); - xmlNodeSetContent( node, msg ); + node = xmlNewNode( NULL, BAD_CAST "pointmsg" ); + xmlNodeSetContent( node, BAD_CAST msg ); level[0] = (int)'0' + SYS_ERR; level[1] = 0; - xmlSetProp( node, "level", (char *)&level ); + xmlSetProp( node, BAD_CAST "level", BAD_CAST (char *)&level ); // a 'point' node sprintf( buf, "%g %g %g", pt[0], pt[1], pt[2] ); - point = xmlNewNode( NULL, "point" ); - xmlNodeSetContent( point, buf ); + point = xmlNewNode( NULL, BAD_CAST "point" ); + xmlNodeSetContent( point, BAD_CAST buf ); xmlAddChild( node, point ); xml_SendNode( node ); @@ -202,11 +202,11 @@ void xml_Winding( char *msg, vec3_t p[], int numpoints, qboolean die ){ char level[2]; int i; - node = xmlNewNode( NULL, "windingmsg" ); - xmlNodeSetContent( node, msg ); + node = xmlNewNode( NULL, BAD_CAST "windingmsg" ); + xmlNodeSetContent( node, BAD_CAST msg ); level[0] = (int)'0' + SYS_ERR; level[1] = 0; - xmlSetProp( node, "level", (char *)&level ); + xmlSetProp( node, BAD_CAST "level", BAD_CAST (char *)&level ); // a 'winding' node sprintf( buf, "%i ", numpoints ); for ( i = 0; i < numpoints; i++ ) @@ -219,8 +219,8 @@ void xml_Winding( char *msg, vec3_t p[], int numpoints, qboolean die ){ strcat( buf, smlbuf ); } - winding = xmlNewNode( NULL, "winding" ); - xmlNodeSetContent( winding, buf ); + winding = xmlNewNode( NULL, BAD_CAST "winding" ); + xmlNodeSetContent( winding, BAD_CAST buf ); xmlAddChild( node, winding ); xml_SendNode( node ); @@ -284,19 +284,19 @@ void FPrintf( int flag, char *buf ){ */ if ( !bGotXML ) { // initialize - doc = xmlNewDoc( "1.0" ); - doc->children = xmlNewDocRawNode( doc, NULL, "q3map_feedback", NULL ); + doc = xmlNewDoc( BAD_CAST "1.0" ); + doc->children = xmlNewDocRawNode( doc, NULL, BAD_CAST "q3map_feedback", NULL ); bGotXML = qtrue; } - node = xmlNewNode( NULL, "message" ); + node = xmlNewNode( NULL, BAD_CAST "message" ); { gchar* utf8 = g_locale_to_utf8( buf, -1, NULL, NULL, NULL ); - xmlNodeSetContent( node, utf8 ); + xmlNodeSetContent( node, BAD_CAST utf8 ); g_free( utf8 ); } level[0] = (int)'0' + flag; level[1] = 0; - xmlSetProp( node, "level", (char *)&level ); + xmlSetProp( node, BAD_CAST "level", BAD_CAST (char *)&level ); xml_SendNode( node ); } diff --git a/q3map2/common/threads.c b/q3map2/common/threads.c index 7b66e8b8..7f8fc1ea 100644 --- a/q3map2/common/threads.c +++ b/q3map2/common/threads.c @@ -19,6 +19,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include + #ifndef _WIN32 // The below define is necessary to use // pthreads extensions like pthread_mutexattr_settype @@ -551,7 +553,7 @@ void RunThreadsOn( int workcnt, qboolean showpacifier, void ( *func )( int ) ){ for ( i = 0 ; i < numthreads ; i++ ) { /* Default pthread attributes: joinable & non-realtime scheduling */ - if ( pthread_create( &work_threads[i], NULL, (void*)func, (void*)i ) != 0 ) { + if ( pthread_create( &work_threads[i], NULL, (void*)func, (void*)(uintptr_t)i ) != 0 ) { Error( "pthread_create failed" ); } } diff --git a/q3map2/common/unzip.c b/q3map2/common/unzip.c index c35cc72e..ab96e4dd 100644 --- a/q3map2/common/unzip.c +++ b/q3map2/common/unzip.c @@ -273,6 +273,9 @@ typedef Byte *voidp; /* basic functions */ +/* The callers never check for CRC errors, so the crc32 doesn't need to be calculated: */ +// #define CHECK_CRC32_HASH + const char * zlibVersion OF((void)); /* The application can compare zlibVersion and ZLIB_VERSION for consistency. If the first character differs, the library code actually used is @@ -2414,9 +2417,12 @@ extern int unzReadCurrentFile (unzFile file, void *buf, unsigned len) *(pfile_in_zip_read_info->stream.next_out + i) = *(pfile_in_zip_read_info->stream.next_in + i); +#ifdef CHECK_CRC32_HASH pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32, pfile_in_zip_read_info->stream.next_out, uDoCopy); +#endif + 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; @@ -2444,8 +2450,10 @@ extern int unzReadCurrentFile (unzFile file, void *buf, unsigned len) uTotalOutAfter = pfile_in_zip_read_info->stream.total_out; uOutThis = uTotalOutAfter-uTotalOutBefore; +#ifdef CHECK_CRC32_HASH pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32, bufBefore, (uInt)(uOutThis)); +#endif pfile_in_zip_read_info->rest_read_uncompressed -= uOutThis; @@ -2577,11 +2585,13 @@ extern int unzCloseCurrentFile (unzFile file) return UNZ_PARAMERROR; +#ifdef CHECK_CRC32_HASH 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; } +#endif free(pfile_in_zip_read_info->read_buffer); @@ -2751,7 +2761,7 @@ static const uLong crc_table[256] = { /* ========================================================================= * This function can be used by asm versions of crc32() */ -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) const uLong * get_crc_table() { #ifdef DYNAMIC_CRC_TABLE @@ -2768,7 +2778,7 @@ const uLong * get_crc_table() #define DO8(buf) DO4(buf); DO4(buf); /* ========================================================================= */ -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) uLong crc32(uLong crc, const Byte *buf, uInt len) { if (buf == Z_NULL) return 0L; @@ -3068,7 +3078,7 @@ extern int inflate_flush OF(( */ -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) void inflate_blocks_reset(inflate_blocks_statef *s, z_streamp z, uLong *c) { if (c != Z_NULL) @@ -3087,7 +3097,7 @@ void inflate_blocks_reset(inflate_blocks_statef *s, z_streamp z, uLong *c) } #endif -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) inflate_blocks_statef *inflate_blocks_new(z_streamp z, check_func c, uInt w) { inflate_blocks_statef *s; @@ -3116,7 +3126,7 @@ inflate_blocks_statef *inflate_blocks_new(z_streamp z, check_func c, uInt w) } #endif -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) int inflate_blocks(inflate_blocks_statef *s, z_streamp z, int r) { uInt t; /* temporary storage */ @@ -3361,7 +3371,7 @@ int inflate_blocks(inflate_blocks_statef *s, z_streamp z, int r) } #endif -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) int inflate_blocks_free(inflate_blocks_statef *s, z_streamp z) { inflate_blocks_reset(s, z, Z_NULL); @@ -3373,7 +3383,7 @@ int inflate_blocks_free(inflate_blocks_statef *s, z_streamp z) } #endif -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) void inflate_set_dictionary(inflate_blocks_statef *s, const Byte *d, uInt n) { zmemcpy(s->window, d, n); @@ -3385,7 +3395,7 @@ void inflate_set_dictionary(inflate_blocks_statef *s, const Byte *d, uInt n) * by Z_SYNC_FLUSH or Z_FULL_FLUSH. * IN assertion: s != Z_NULL */ -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) int inflate_blocks_sync_point(inflate_blocks_statef *s) { return s->mode == LENS; @@ -3400,7 +3410,7 @@ uInt inflate_mask[17] = { }; /* copy as much as possible from the sliding window to the output area */ -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) int inflate_flush(inflate_blocks_statef *s, z_streamp z, int r) { uInt n; @@ -3470,7 +3480,7 @@ int inflate_flush(inflate_blocks_statef *s, z_streamp z, int r) * For conditions of distribution and use, see copyright notice in zlib.h */ -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) const char inflate_copyright[] = " inflate 1.1.3 Copyright 1995-1998 Mark Adler "; #endif @@ -3753,7 +3763,7 @@ static int huft_build(uInt *b, uInt n, uInt s, const uInt *d, const uInt *e, inf } -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) int inflate_trees_bits(uInt *c, uInt *bb, inflate_huft * *tb, inflate_huft *hp, z_streamp z) //uInt *c; /* 19 code lengths */ //uInt *bb; /* bits tree desired/actual depth */ @@ -3781,7 +3791,7 @@ int inflate_trees_bits(uInt *c, uInt *bb, inflate_huft * *tb, inflate_huft *hp, } #endif -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) int inflate_trees_dynamic(uInt nl, uInt nd, uInt *c, uInt *bl, uInt *bd, inflate_huft * *tl, inflate_huft * *td, inflate_huft *hp, z_streamp z) //uInt nl; /* number of literal/length codes */ //uInt nd; /* number of distance codes */ @@ -3855,6 +3865,7 @@ int inflate_trees_dynamic(uInt nl, uInt nd, uInt *c, uInt *bl, uInt *bd, inflate subject to change. Applications should only use zlib.h. */ +#if !defined(__linux__) && !defined(__APPLE__) static uInt fixed_bl = 9; static uInt fixed_bd = 5; static inflate_huft fixed_tl[] = { @@ -3997,8 +4008,9 @@ static inflate_huft fixed_td[] = { {{{80,5}},4}, {{{88,5}},769}, {{{84,5}},49}, {{{92,5}},12289}, {{{82,5}},13}, {{{90,5}},3073}, {{{86,5}},193}, {{{192,5}},24577} }; +#endif -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) int inflate_trees_fixed(uInt *bl, uInt *bd, inflate_huft * *tl, inflate_huft * *td, z_streamp z) //uInt *bl; /* literal desired/actual bit depth */ //uInt *bd; /* distance desired/actual bit depth */ @@ -4027,7 +4039,7 @@ int inflate_trees_fixed(uInt *bl, uInt *bd, inflate_huft * *tl, inflate_huft * * at least ten. The ten bytes are six bytes for the longest length/ distance pair plus four bytes for overloading the bit buffer. */ -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) int inflate_fast(uInt bl, uInt bd, inflate_huft *tl, inflate_huft *td, inflate_blocks_statef *s, z_streamp z) { inflate_huft *t; /* temporary pointer */ @@ -4218,7 +4230,7 @@ struct inflate_codes_state { }; -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) inflate_codes_statef *inflate_codes_new(uInt bl, uInt bd, inflate_huft *tl, inflate_huft *td, z_streamp z) { inflate_codes_statef *c; @@ -4237,7 +4249,7 @@ inflate_codes_statef *inflate_codes_new(uInt bl, uInt bd, inflate_huft *tl, infl } #endif -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) int inflate_codes(inflate_blocks_statef *s, z_streamp z, int r) { uInt j; /* temporary storage */ @@ -4405,7 +4417,7 @@ int inflate_codes(inflate_blocks_statef *s, z_streamp z, int r) } #endif -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) void inflate_codes_free(inflate_codes_statef *c, z_streamp z) { ZFREE(z, c); @@ -4415,7 +4427,7 @@ void inflate_codes_free(inflate_codes_statef *c, z_streamp z) /* adler32.c -- compute the Adler-32 checksum of a data stream * Copyright (C) 1995-1998 Mark Adler - * For conditions of distribution and use, see copyright notice in zlib.h + * For conditions of distribution and use, see copyright notice in zlib.h */ #define BASE 65521L /* largest prime smaller than 65536 */ @@ -4434,7 +4446,7 @@ void inflate_codes_free(inflate_codes_statef *c, z_streamp z) #define DO16(buf) DO8(buf,0); DO8(buf,8); /* ========================================================================= */ -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) uLong adler32(uLong adler, const Byte *buf, uInt len) { unsigned long s1 = adler & 0xffff; @@ -4542,7 +4554,7 @@ struct internal_state { }; -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) int inflateReset(z_streamp z) { if (z == Z_NULL || z->state == Z_NULL) @@ -4556,7 +4568,7 @@ int inflateReset(z_streamp z) } #endif -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) int inflateEnd(z_streamp z) { if (z == Z_NULL || z->state == Z_NULL || z->zfree == Z_NULL) @@ -4570,7 +4582,7 @@ int inflateEnd(z_streamp z) } #endif -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) int inflateInit2_(z_streamp z, int w, const char *version, int stream_size) { if (version == Z_NULL || version[0] != ZLIB_VERSION[0] || @@ -4624,7 +4636,7 @@ int inflateInit2_(z_streamp z, int w, const char *version, int stream_size) } #endif -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) int inflateInit_(z_streamp z, const char *version, int stream_size) { return inflateInit2_(z, DEF_WBITS, version, stream_size); @@ -4634,7 +4646,7 @@ int inflateInit_(z_streamp z, const char *version, int stream_size) #define iNEEDBYTE {if(z->avail_in==0)return r;r=f;} #define iNEXTBYTE (z->avail_in--,z->total_in++,*z->next_in++) -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) int inflate(z_streamp z, int f) { int r; @@ -4761,7 +4773,7 @@ int inflate(z_streamp z, int f) } #endif -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) int inflateSetDictionary(z_streamp z, const Byte *dictionary, uInt dictLength) { uInt length = dictLength; @@ -4783,7 +4795,7 @@ int inflateSetDictionary(z_streamp z, const Byte *dictionary, uInt dictLength) } #endif -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) int inflateSync(z_streamp z) { uInt n; /* number of bytes to look at */ @@ -4841,7 +4853,7 @@ int inflateSync(z_streamp z) * decompressing, PPP checks that at the end of input packet, inflate is * waiting for these length bytes. */ -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) int inflateSyncPoint(z_streamp z) { if (z == Z_NULL || z->state == Z_NULL || z->state->blocks == Z_NULL) @@ -4850,7 +4862,7 @@ int inflateSyncPoint(z_streamp z) } #endif -#ifndef __APPLE__ +#if !defined(__linux__) && !defined(__APPLE__) voidp zcalloc (voidp opaque, unsigned items, unsigned size) { if (opaque) items += size - size; /* make compiler happy */ diff --git a/q3map2/common/vfs.c b/q3map2/common/vfs.c index 4a1f2482..f9a54aac 100644 --- a/q3map2/common/vfs.c +++ b/q3map2/common/vfs.c @@ -134,6 +134,7 @@ static void vfsInitPakFile( const char *filename ){ for ( i = 0; i < gi.number_entry; i++ ) { char filename_inzip[NAME_MAX]; + char *filename_lower; unz_file_info file_info; VFS_PAKFILE* file; @@ -146,9 +147,9 @@ static void vfsInitPakFile( const char *filename ){ g_pakFiles = g_slist_append( g_pakFiles, file ); vfsFixDOSName( filename_inzip ); - g_strdown( filename_inzip ); + filename_lower = g_ascii_strdown( filename_inzip, -1 );//-1 null terminated string - file->name = strdup( filename_inzip ); + file->name = strdup( filename_lower ); file->size = file_info.uncompressed_size; file->zipfile = uf; memcpy( &file->zipinfo, uf, sizeof( unz_s ) ); @@ -159,6 +160,7 @@ static void vfsInitPakFile( const char *filename ){ break; } } + g_free( filename_lower ); } } @@ -233,17 +235,18 @@ void vfsShutdown(){ int vfsGetFileCount( const char *filename ){ int i, count = 0; char fixed[NAME_MAX], tmp[NAME_MAX]; + char *lower; GSList *lst; strcpy( fixed, filename ); vfsFixDOSName( fixed ); - g_strdown( fixed ); + lower = g_ascii_strdown( fixed, -1 ); for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) ) { VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data; - if ( strcmp( file->name, fixed ) == 0 ) { + if ( strcmp( file->name, lower ) == 0 ) { count++; } } @@ -251,12 +254,12 @@ int vfsGetFileCount( const char *filename ){ for ( i = 0; i < g_numDirs; i++ ) { strcpy( tmp, g_strDirs[i] ); - strcat( tmp, fixed ); + strcat( tmp, lower ); if ( access( tmp, R_OK ) == 0 ) { count++; } } - + g_free( lower ); return count; } @@ -264,6 +267,7 @@ int vfsGetFileCount( const char *filename ){ int vfsLoadFile( const char *filename, void **bufferptr, int index ){ int i, count = 0; char tmp[NAME_MAX], fixed[NAME_MAX]; + char *lower; GSList *lst; // filename is a full path @@ -297,7 +301,7 @@ int vfsLoadFile( const char *filename, void **bufferptr, int index ){ *bufferptr = NULL; strcpy( fixed, filename ); vfsFixDOSName( fixed ); - g_strdown( fixed ); + lower = g_ascii_strdown( fixed, -1 ); for ( i = 0; i < g_numDirs; i++ ) { @@ -339,7 +343,7 @@ int vfsLoadFile( const char *filename, void **bufferptr, int index ){ { VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data; - if ( strcmp( file->name, fixed ) != 0 ) { + if ( strcmp( file->name, lower ) != 0 ) { continue; } @@ -360,12 +364,13 @@ int vfsLoadFile( const char *filename, void **bufferptr, int index ){ return -1; } else{ + g_free( lower ); return file->size; } } count++; } - + g_free( lower ); return -1; } diff --git a/q3map2/libs/mathlib/m4x4.c b/q3map2/libs/mathlib/m4x4.c index 5dfdd10d..6f3e75ab 100644 --- a/q3map2/libs/mathlib/m4x4.c +++ b/q3map2/libs/mathlib/m4x4.c @@ -702,27 +702,31 @@ void m4_submat( m4x4_t mr, m3x3_t mb, int i, int j ){ idst = 0; for ( ti = 0; ti < 4; ti++ ) { + if ( ti == i ) { + continue; + } if ( ti < i ) { idst = ti; } else - if ( ti > i ) { + { idst = ti - 1; } for ( tj = 0; tj < 4; tj++ ) { + if ( tj == j ) { + continue; + } if ( tj < j ) { jdst = tj; } else - if ( tj > j ) { + { jdst = tj - 1; } - if ( ti != i && tj != j ) { - mb[idst * 3 + jdst] = mr[ti * 4 + tj ]; - } + mb[idst * 3 + jdst] = mr[ti * 4 + tj ]; } } } diff --git a/q3map2/libs/picomodel.h b/q3map2/libs/picomodel.h index 98d5452f..a1f2c5c6 100644 --- a/q3map2/libs/picomodel.h +++ b/q3map2/libs/picomodel.h @@ -105,6 +105,7 @@ struct picoSurface_s int numVertexes, maxVertexes; picoVec3_t *xyz; picoVec3_t *normal; + picoIndex_t *smoothingGroup; int numSTArrays, maxSTArrays; picoVec2_t **st; @@ -216,6 +217,9 @@ const picoModule_t **PicoModuleList( int *numModules ); picoModel_t *PicoLoadModel( char *name, int frameNum ); +typedef size_t(*PicoInputStreamReadFunc)(void* inputStream, unsigned char* buffer, size_t length); +picoModel_t* PicoModuleLoadModelStream(const picoModule_t* module, void* inputStream, PicoInputStreamReadFunc inputStreamRead, size_t streamLength, int frameNum); + /* model functions */ picoModel_t *PicoNewModel( void ); @@ -263,6 +267,7 @@ void PicoSetSurfaceIndex( picoSurface_t *surface, int num void PicoSetSurfaceIndexes( picoSurface_t *surface, int num, picoIndex_t *index, int count ); void PicoSetFaceNormal( picoSurface_t *surface, int num, picoVec3_t normal ); void PicoSetSurfaceSpecial( picoSurface_t *surface, int num, int special ); +void PicoSetSurfaceSmoothingGroup( picoSurface_t *surface, int num, picoIndex_t smoothingGroup ); /* getter functions */ @@ -330,12 +335,12 @@ picoVertexCombinationHash_t *PicoFindVertexCombinationInHashTable( picoVertexCom picoVertexCombinationHash_t *PicoAddVertexCombinationToHashTable( picoVertexCombinationHash_t **hashTable, picoVec3_t xyz, picoVec3_t normal, picoVec3_t st, picoColor_t color, picoIndex_t index ); /* specialized functions */ -int PicoFindSurfaceVertexNum( picoSurface_t *surface, picoVec3_t xyz, picoVec3_t normal, int numSTs, picoVec2_t *st, int numColors, picoColor_t *color ); +int PicoFindSurfaceVertexNum( picoSurface_t *surface, picoVec3_t xyz, picoVec3_t normal, int numSTs, picoVec2_t *st, int numColors, picoColor_t *color, picoIndex_t smoothingGroup ); void PicoFixSurfaceNormals( picoSurface_t *surface ); int PicoRemapModel( picoModel_t *model, char *remapFile ); -void PicoAddTriangleToModel( picoModel_t *model, picoVec3_t** xyz, picoVec3_t** normals, int numSTs, picoVec2_t **st, int numColors, picoColor_t **colors, picoShader_t* shader ); +void PicoAddTriangleToModel( picoModel_t *model, picoVec3_t** xyz, picoVec3_t** normals, int numSTs, picoVec2_t **st, int numColors, picoColor_t **colors, picoShader_t* shader, picoIndex_t* smoothingGroup ); /* end marker */ #ifdef __cplusplus diff --git a/q3map2/libs/picomodel/lwo/clip.c b/q3map2/libs/picomodel/lwo/clip.c index 8a9d2a62..9ea2586b 100644 --- a/q3map2/libs/picomodel/lwo/clip.c +++ b/q3map2/libs/picomodel/lwo/clip.c @@ -22,6 +22,35 @@ void lwFreeClip( lwClip *clip ){ if ( clip ) { lwListFree( (void*) clip->ifilter, (ListFreeFunc) lwFreePlugin ); lwListFree( (void*) clip->pfilter, (ListFreeFunc) lwFreePlugin ); + + switch ( clip->type ) { + case ID_STIL: + _pico_free( clip->source.still.name ); + break; + + case ID_ISEQ: + _pico_free( clip->source.seq.prefix ); + _pico_free( clip->source.seq.suffix ); + break; + + case ID_ANIM: + _pico_free( clip->source.anim.name ); + _pico_free( clip->source.anim.server ); + _pico_free( clip->source.anim.data ); + break; + + case ID_XREF: + _pico_free( clip->source.xref.string ); + break; + + case ID_STCC: + _pico_free( clip->source.cycle.name ); + break; + + default: + break; + } + _pico_free( clip ); } } diff --git a/q3map2/libs/picomodel/lwo/envelope.c b/q3map2/libs/picomodel/lwo/envelope.c index db7000ad..4510dade 100644 --- a/q3map2/libs/picomodel/lwo/envelope.c +++ b/q3map2/libs/picomodel/lwo/envelope.c @@ -75,6 +75,8 @@ lwEnvelope *lwGetEnvelope( picoMemStream_t *fp, int cksize ){ goto Fail; } + key = NULL; + /* process subchunks as they're encountered */ while ( 1 ) { diff --git a/q3map2/libs/picomodel/lwo/lwob.c b/q3map2/libs/picomodel/lwo/lwob.c index 57cc0c52..2f90ab7c 100644 --- a/q3map2/libs/picomodel/lwo/lwob.c +++ b/q3map2/libs/picomodel/lwo/lwob.c @@ -8,6 +8,8 @@ Ernie Wright 17 Sep 00 ====================================================================== */ +#include + #include "../picointernal.h" #include "lwo2.h" @@ -244,6 +246,9 @@ lwSurface *lwGetSurface5( picoMemStream_t *fp, int cksize, lwObject *obj ){ goto Fail; } + tex = NULL; + shdr = NULL; + /* process subchunks as they're encountered */ while ( 1 ) { @@ -382,16 +387,21 @@ lwSurface *lwGetSurface5( picoMemStream_t *fp, int cksize, lwObject *obj ){ case ID_TFLG: flags = getU2( fp ); + if( tex == NULL ) { + break; + } + //only one of the three axis bits should be set if ( flags & 1 ) { - i = 0; + tex->axis = 0; } - if ( flags & 2 ) { - i = 1; + else if ( flags & 2 ) { + tex->axis = 1; } - if ( flags & 4 ) { - i = 2; + else { + assert( flags & 4 ); + tex->axis = 2; } - tex->axis = i; + if ( tex->type == ID_IMAP ) { tex->param.imap.axis = i; } @@ -415,21 +425,33 @@ lwSurface *lwGetSurface5( picoMemStream_t *fp, int cksize, lwObject *obj ){ break; case ID_TSIZ: + if( tex == NULL ) { + break; + } for ( i = 0; i < 3; i++ ) tex->tmap.size.val[ i ] = getF4( fp ); break; case ID_TCTR: + if( tex == NULL ) { + break; + } for ( i = 0; i < 3; i++ ) tex->tmap.center.val[ i ] = getF4( fp ); break; case ID_TFAL: + if( tex == NULL ) { + break; + } for ( i = 0; i < 3; i++ ) tex->tmap.falloff.val[ i ] = getF4( fp ); break; case ID_TVEL: + if( tex == NULL ) { + break; + } for ( i = 0; i < 3; i++ ) v[ i ] = getF4( fp ); tex->tmap.center.eindex = add_tvel( tex->tmap.center.val, v, @@ -437,6 +459,9 @@ lwSurface *lwGetSurface5( picoMemStream_t *fp, int cksize, lwObject *obj ){ break; case ID_TCLR: + if( tex == NULL ) { + break; + } if ( tex->type == ID_PROC ) { for ( i = 0; i < 3; i++ ) tex->param.proc.value[ i ] = getU1( fp ) / 255.0f; @@ -444,40 +469,64 @@ lwSurface *lwGetSurface5( picoMemStream_t *fp, int cksize, lwObject *obj ){ break; case ID_TVAL: + if( tex == NULL ) { + break; + } tex->param.proc.value[ 0 ] = getI2( fp ) / 256.0f; break; case ID_TAMP: + if( tex == NULL ) { + break; + } if ( tex->type == ID_IMAP ) { tex->param.imap.amplitude.val = getF4( fp ); } break; case ID_TIMG: + if( tex == NULL ) { + break; + } s = getS0( fp ); tex->param.imap.cindex = add_clip( s, &obj->clip, &obj->nclips ); break; case ID_TAAS: + if( tex == NULL ) { + break; + } tex->param.imap.aa_strength = getF4( fp ); tex->param.imap.aas_flags = 1; break; case ID_TREF: + if( tex == NULL ) { + break; + } tex->tmap.ref_object = getbytes( fp, sz ); break; case ID_TOPC: + if( tex == NULL ) { + break; + } tex->opacity.val = getF4( fp ); break; case ID_TFP0: + if( tex == NULL ) { + break; + } if ( tex->type == ID_IMAP ) { tex->param.imap.wrapw.val = getF4( fp ); } break; case ID_TFP1: + if( tex == NULL ) { + break; + } if ( tex->type == ID_IMAP ) { tex->param.imap.wraph.val = getF4( fp ); } @@ -494,6 +543,9 @@ lwSurface *lwGetSurface5( picoMemStream_t *fp, int cksize, lwObject *obj ){ break; case ID_SDAT: + if ( !shdr ) { + goto Fail; + } shdr->data = getbytes( fp, sz ); break; diff --git a/q3map2/libs/picomodel/picointernal.c b/q3map2/libs/picomodel/picointernal.c index d4fd8b44..d9eeb656 100644 --- a/q3map2/libs/picomodel/picointernal.c +++ b/q3map2/libs/picomodel/picointernal.c @@ -161,35 +161,23 @@ void *_pico_realloc( void **ptr, size_t oldSize, size_t newSize ){ * as custom clone size (the string is cropped to fit into mem * if needed). -sea */ -char *_pico_clone_alloc( char *str, int size ){ - char *cloned; - size_t cloneSize; +char *_pico_clone_alloc( const char *str ) { + char* cloned; /* sanity check */ if ( str == NULL ) { return NULL; } - /* set real size of cloned string */ - cloneSize = ( size < 0 ) ? strlen( str ) : size; - /* allocate memory */ - cloned = _pico_alloc( cloneSize + 1 ); /* bugfix! */ + cloned = _pico_alloc(strlen(str) + 1); if ( cloned == NULL ) { return NULL; } - /* zero out memory allocated by cloned string */ - memset( cloned,0,cloneSize ); - /* copy input string to cloned string */ - if ( cloneSize < strlen( str ) ) { - memcpy( cloned,str,cloneSize ); - cloned[ cloneSize ] = '\0'; - } - else { - strcpy( cloned,str ); - } + strcpy(cloned, str); + /* return ptr to cloned string */ return cloned; } @@ -275,6 +263,19 @@ void _pico_printf( int level, const char *format, ... ){ _pico_ptr_print( level,str ); } +/* _pico_first_token: +* trims everything after the first whitespace-delimited token +*/ + +void _pico_first_token( char *str ) { + if ( !str || ! * str ) { + return; + } + while (*str && !isspace(*str)) + str++; + *str = '\0'; +} + /* _pico_strltrim: * left trims the given string -sea */ @@ -542,7 +543,7 @@ float _pico_big_float( float src ){ * case-insensitive strstr. -sea */ char *_pico_stristr( char *str, const char *substr ){ - const int sublen = strlen( substr ); + const size_t sublen = strlen( substr ); while ( *str ) { if ( !_pico_strnicmp( str,substr,sublen ) ) { @@ -604,24 +605,24 @@ int _pico_nofname( const char *path, char *dest, int destSize ){ * returns ptr to filename portion in given path or an empty * string otherwise. given 'path' is not altered. -sea */ -char *_pico_nopath( const char *path ){ - char *src; - src = (char *)path + ( strlen( path ) - 1 ); +const char *_pico_nopath( const char *path ){ + const char *src; if ( path == NULL ) { - return (char *)""; + return ""; } - if ( !strchr( (char *)path,'/' ) && !strchr( (char *)path,'\\' ) ) { - return ( (char *)path ); + if ( !strchr( path,'/' ) && !strchr( path,'\\' ) ) { + return ( path ); } + src = path + ( strlen( path ) - 1 ); while ( ( src-- ) != path ) { if ( *src == '/' || *src == '\\' ) { return ( ++src ); } } - return (char *)""; + return ""; } /* _pico_setfext: diff --git a/q3map2/libs/picomodel/picointernal.h b/q3map2/libs/picomodel/picointernal.h index 12b8cd7c..853809ee 100644 --- a/q3map2/libs/picomodel/picointernal.h +++ b/q3map2/libs/picomodel/picointernal.h @@ -116,7 +116,7 @@ extern void ( *_pico_ptr_print )( int, const char* ); void *_pico_alloc( size_t size ); void *_pico_calloc( size_t num, size_t size ); void *_pico_realloc( void **ptr, size_t oldSize, size_t newSize ); -char *_pico_clone_alloc( char *str, int size ); +char *_pico_clone_alloc( const char *str ); void _pico_free( void *ptr ); /* files */ @@ -124,6 +124,7 @@ void _pico_load_file( char *name, unsigned char **buffer, int *bufSiz void _pico_free_file( void *buffer ); /* strings */ +void _pico_first_token(char *str); char *_pico_strltrim( char *str ); char *_pico_strrtrim( char *str ); int _pico_strchcount( char *str, int ch ); @@ -131,7 +132,7 @@ void _pico_printf( int level, const char *format, ... ); char *_pico_stristr( char *str, const char *substr ); void _pico_unixify( char *path ); int _pico_nofname( const char *path, char *dest, int destSize ); -char *_pico_nopath( const char *path ); +const char *_pico_nopath( const char *path ); char *_pico_setfext( char *path, const char *ext ); int _pico_getline( char *buf, int bufsize, char *dest, int destsize ); char *_pico_strlwr( char *str ); diff --git a/q3map2/libs/picomodel/picomodel.c b/q3map2/libs/picomodel/picomodel.c index 767fc74f..8fad3383 100644 --- a/q3map2/libs/picomodel/picomodel.c +++ b/q3map2/libs/picomodel/picomodel.c @@ -143,7 +143,45 @@ void PicoSetPrintFunc( void ( *func )( int, const char* ) ){ } } +picoModel_t *PicoModuleLoadModel( const picoModule_t* pm, char* fileName, picoByte_t* buffer, int bufSize, int frameNum ){ + char *modelFileName, *remapFileName; + + /* see whether this module can load the model file or not */ + if ( pm->canload( fileName, buffer, bufSize ) == PICO_PMV_OK ) { + /* use loader provided by module to read the model data */ + picoModel_t* model = pm->load( fileName, frameNum, buffer, bufSize ); + if ( model == NULL ) { + return NULL; + } + + /* assign pointer to file format module */ + model->module = pm; + + /* get model file name */ + modelFileName = PicoGetModelFileName( model ); + + /* apply model remappings from .remap */ + if ( strlen( modelFileName ) ) { + /* alloc copy of model file name */ + remapFileName = _pico_alloc( strlen( modelFileName ) + 20 ); + if ( remapFileName != NULL ) { + /* copy model file name and change extension */ + strcpy( remapFileName, modelFileName ); + _pico_setfext( remapFileName, "remap" ); + + /* try to remap model; we don't handle the result */ + PicoRemapModel( model, remapFileName ); + + /* free the remap file name string */ + _pico_free( remapFileName ); + } + } + + return model; + } + return NULL; +} /* PicoLoadModel() @@ -155,7 +193,6 @@ picoModel_t *PicoLoadModel( char *fileName, int frameNum ){ picoModel_t *model; picoByte_t *buffer; int bufSize; - char *modelFileName, *remapFileName; /* init */ @@ -194,49 +231,62 @@ picoModel_t *PicoLoadModel( char *fileName, int frameNum ){ continue; } - /* see whether this module can load the model file or not */ - if ( pm->canload( fileName, buffer, bufSize ) == PICO_PMV_OK ) { - /* use loader provided by module to read the model data */ - model = pm->load( fileName, frameNum, buffer, bufSize ); - if ( model == NULL ) { - _pico_free_file( buffer ); - return NULL; - } + model = PicoModuleLoadModel( pm, fileName, buffer, bufSize, frameNum ); + if ( model != NULL ) { + /* model was loaded, so break out of loop */ + break; + } + } + + /* free memory used by file buffer */ + if ( buffer ) { + _pico_free_file( buffer ); + } - /* assign pointer to file format module */ - model->module = pm; + return model; +} - /* get model file name */ - modelFileName = PicoGetModelFileName( model ); +/* + FIXME: From 1.5; Unused yet +*/ - /* apply model remappings from .remap */ - if ( strlen( modelFileName ) ) { - /* alloc copy of model file name */ - remapFileName = _pico_alloc( strlen( modelFileName ) + 20 ); - if ( remapFileName != NULL ) { - /* copy model file name and change extension */ - strcpy( remapFileName, modelFileName ); - _pico_setfext( remapFileName, "remap" ); +picoModel_t *PicoModuleLoadModelStream( const picoModule_t* module, void* inputStream, PicoInputStreamReadFunc inputStreamRead, size_t streamLength, int frameNum ) { + picoModel_t *model; + picoByte_t *buffer; + int bufSize; - /* try to remap model; we don't handle the result */ - PicoRemapModel( model, remapFileName ); + /* init */ + model = NULL; - /* free the remap file name string */ - _pico_free( remapFileName ); - } - } + if ( inputStream == NULL ) { + _pico_printf( PICO_ERROR, "PicoLoadModel: invalid input stream (inputStream == NULL)" ); + return NULL; + } - /* model was loaded, so break out of loop */ - break; - } + if ( inputStreamRead == NULL ) { + _pico_printf( PICO_ERROR, "PicoLoadModel: invalid input stream (inputStreamRead == NULL) "); + return NULL; + } + + buffer = _pico_alloc( streamLength + 1 ); + + bufSize = (int)inputStreamRead( inputStream, buffer, streamLength ); + buffer[ bufSize ] = '\0'; + + { + // dummy filename + char fileName[128]; + fileName[0] = '.'; + strncpy( fileName + 1, module->defaultExts[ 0 ], 126 ); + fileName[127] = '\0'; + model = PicoModuleLoadModel( module, fileName, buffer, bufSize, frameNum ); } /* free memory used by file buffer */ - if ( buffer ) { - _pico_free_file( buffer ); + if ( model != 0 ) { + _pico_free( buffer ); } - /* return */ return model; } @@ -261,10 +311,10 @@ picoModel_t *PicoNewModel( void ){ } /* clear */ - memset( model,0,sizeof( picoModel_t ) ); + memset( model, 0, sizeof( picoModel_t ) ); /* model set up */ - _pico_zero_bounds( model->mins,model->maxs ); + _pico_zero_bounds( model->mins, model->maxs ); /* set initial frame count to 1 -sea */ model->numFrames = 1; @@ -274,7 +324,6 @@ picoModel_t *PicoNewModel( void ){ } - /* PicoFreeModel() frees a model and all associated data @@ -283,7 +332,6 @@ picoModel_t *PicoNewModel( void ){ void PicoFreeModel( picoModel_t *model ){ int i; - /* sanity check */ if ( model == NULL ) { return; @@ -294,6 +342,10 @@ void PicoFreeModel( picoModel_t *model ){ _pico_free( model->name ); } + if ( model->fileName ) { + _pico_free( model->fileName ); + } + /* free shaders */ for ( i = 0; i < model->numShaders; i++ ) PicoFreeShader( model->shader[ i ] ); @@ -309,7 +361,6 @@ void PicoFreeModel( picoModel_t *model ){ } - /* PicoAdjustModel() adjusts a models's memory allocations to handle the requested sizes. @@ -364,7 +415,6 @@ int PicoAdjustModel( picoModel_t *model, int numShaders, int numSurfaces ){ } - /* ---------------------------------------------------------------------------- shaders ---------------------------------------------------------------------------- */ @@ -377,7 +427,6 @@ int PicoAdjustModel( picoModel_t *model, int numShaders, int numSurfaces ){ picoShader_t *PicoNewShader( picoModel_t *model ){ picoShader_t *shader; - /* allocate and clear */ shader = _pico_alloc( sizeof( picoShader_t ) ); if ( shader == NULL ) { @@ -397,9 +446,9 @@ picoShader_t *PicoNewShader( picoModel_t *model ){ shader->model = model; } /* setup default shader colors */ - _pico_set_color( shader->ambientColor,0,0,0,0 ); - _pico_set_color( shader->diffuseColor,255,255,255,1 ); - _pico_set_color( shader->specularColor,0,0,0,0 ); + _pico_set_color( shader->ambientColor, 0, 0, 0, 0 ); + _pico_set_color( shader->diffuseColor, 255, 255, 255, 1 ); + _pico_set_color( shader->specularColor, 0, 0, 0, 0 ); /* no need to do this, but i do it anyway */ shader->transparency = 0; @@ -410,7 +459,6 @@ picoShader_t *PicoNewShader( picoModel_t *model ){ } - /* PicoFreeShader() frees a shader and all associated data -sea @@ -435,7 +483,6 @@ void PicoFreeShader( picoShader_t *shader ){ } - /* PicoFindShader() finds a named shader in a model @@ -444,7 +491,6 @@ void PicoFreeShader( picoShader_t *shader ){ picoShader_t *PicoFindShader( picoModel_t *model, char *name, int caseSensitive ){ int i; - /* sanity checks */ if ( model == NULL || name == NULL ) { /* sea: null name fix */ return NULL; @@ -475,7 +521,6 @@ picoShader_t *PicoFindShader( picoModel_t *model, char *name, int caseSensitive } - /* ---------------------------------------------------------------------------- surfaces ---------------------------------------------------------------------------- */ @@ -518,7 +563,6 @@ picoSurface_t *PicoNewSurface( picoModel_t *model ){ } - /* PicoFreeSurface() frees a surface and all associated data @@ -526,7 +570,6 @@ picoSurface_t *PicoNewSurface( picoModel_t *model ){ void PicoFreeSurface( picoSurface_t *surface ){ int i; - /* dummy check */ if ( surface == NULL ) { return; @@ -535,9 +578,14 @@ void PicoFreeSurface( picoSurface_t *surface ){ /* free bits */ _pico_free( surface->xyz ); _pico_free( surface->normal ); + _pico_free( surface->smoothingGroup ); _pico_free( surface->index ); _pico_free( surface->faceNormal ); + if ( surface->name ) { + _pico_free( surface->name ); + } + /* free arrays */ for ( i = 0; i < surface->numSTArrays; i++ ) _pico_free( surface->st[ i ] ); @@ -551,7 +599,6 @@ void PicoFreeSurface( picoSurface_t *surface ){ } - /* PicoAdjustSurface() adjusts a surface's memory allocations to handle the requested sizes. @@ -561,7 +608,6 @@ void PicoFreeSurface( picoSurface_t *surface ){ int PicoAdjustSurface( picoSurface_t *surface, int numVertexes, int numSTArrays, int numColorArrays, int numIndexes, int numFaceNormals ){ int i; - /* dummy check */ if ( surface == NULL ) { return 0; @@ -591,6 +637,9 @@ int PicoAdjustSurface( picoSurface_t *surface, int numVertexes, int numSTArrays, if ( !_pico_realloc( (void *) &surface->normal, surface->numVertexes * sizeof( *surface->normal ), surface->maxVertexes * sizeof( *surface->normal ) ) ) { return 0; } + if ( !_pico_realloc( (void *) &surface->smoothingGroup, surface->numVertexes * sizeof( *surface->smoothingGroup ), surface->maxVertexes * sizeof( *surface->smoothingGroup ) ) ) { + return 0; + } for ( i = 0; i < surface->numSTArrays; i++ ) if ( !_pico_realloc( (void*) &surface->st[ i ], surface->numVertexes * sizeof( *surface->st[ i ] ), surface->maxVertexes * sizeof( *surface->st[ i ] ) ) ) { return 0; @@ -720,7 +769,7 @@ void PicoSetModelName( picoModel_t *model, char *name ){ _pico_free( model->name ); } - model->name = _pico_clone_alloc( name,-1 ); + model->name = _pico_clone_alloc( name ); } @@ -733,7 +782,7 @@ void PicoSetModelFileName( picoModel_t *model, char *fileName ){ _pico_free( model->fileName ); } - model->fileName = _pico_clone_alloc( fileName,-1 ); + model->fileName = _pico_clone_alloc( fileName ); } @@ -773,7 +822,7 @@ void PicoSetShaderName( picoShader_t *shader, char *name ){ _pico_free( shader->name ); } - shader->name = _pico_clone_alloc( name,-1 ); + shader->name = _pico_clone_alloc( name ); } @@ -786,7 +835,7 @@ void PicoSetShaderMapName( picoShader_t *shader, char *mapName ){ _pico_free( shader->mapName ); } - shader->mapName = _pico_clone_alloc( mapName,-1 ); + shader->mapName = _pico_clone_alloc( mapName ); } @@ -887,7 +936,7 @@ void PicoSetSurfaceName( picoSurface_t *surface, char *name ){ _pico_free( surface->name ); } - surface->name = _pico_clone_alloc( name,-1 ); + surface->name = _pico_clone_alloc( name ); } @@ -990,6 +1039,15 @@ void PicoSetFaceNormal( picoSurface_t *surface, int num, picoVec3_t normal ){ _pico_copy_vec( normal, surface->faceNormal[ num ] ); } +void PicoSetSurfaceSmoothingGroup( picoSurface_t *surface, int num, picoIndex_t smoothingGroup ){ + if ( num < 0 ) { + return; + } + if ( !PicoAdjustSurface( surface, num + 1, 0, 0, 0, 0 ) ) { + return; + } + surface->smoothingGroup[ num ] = smoothingGroup; +} void PicoSetSurfaceSpecial( picoSurface_t *surface, int num, int special ){ if ( surface == NULL || num < 0 || num >= PICO_MAX_SPECIAL ) { @@ -1284,7 +1342,7 @@ picoVec_t *PicoGetSurfaceNormal( picoSurface_t *surface, int num ){ -picoVec_t *PicoGetSurfaceST( picoSurface_t *surface, int array, int num ){ +picoVec_t *PicoGetSurfaceST( picoSurface_t *surface, int array, int num ){ if ( surface == NULL || array < 0 || array > surface->numSTArrays || num < 0 || num > surface->numVertexes ) { return NULL; } @@ -1335,6 +1393,12 @@ picoVec_t *PicoGetFaceNormal( picoSurface_t *surface, int num ){ return surface->faceNormal[ num ]; } +picoIndex_t PicoGetSurfaceSmoothingGroup( picoSurface_t *surface, int num ){ + if ( surface == NULL || num < 0 || num > surface->numVertexes ) { + return -1; + } + return surface->smoothingGroup[ num ]; +} int PicoGetSurfaceSpecial( picoSurface_t *surface, int num ){ if ( surface == NULL || num < 0 || num >= PICO_MAX_SPECIAL ) { @@ -1535,7 +1599,7 @@ picoVertexCombinationHash_t *PicoAddVertexCombinationToHashTable( picoVertexComb fixme: needs non-naive algorithm */ -int PicoFindSurfaceVertexNum( picoSurface_t *surface, picoVec3_t xyz, picoVec3_t normal, int numSTs, picoVec2_t *st, int numColors, picoColor_t *color ){ +int PicoFindSurfaceVertexNum( picoSurface_t *surface, picoVec3_t xyz, picoVec3_t normal, int numSTs, picoVec2_t *st, int numColors, picoColor_t *color, picoIndex_t smoothingGroup ){ int i, j; @@ -1557,6 +1621,11 @@ int PicoFindSurfaceVertexNum( picoSurface_t *surface, picoVec3_t xyz, picoVec3_t continue; } + /* check normal */ + if ( surface->smoothingGroup[ i ] != smoothingGroup ) { + continue; + } + /* check st */ if ( numSTs > 0 && st != NULL ) { for ( j = 0; j < numSTs; j++ ) @@ -1598,125 +1667,419 @@ int PicoFindSurfaceVertexNum( picoSurface_t *surface, picoVec3_t xyz, picoVec3_t fixes broken normals (certain formats bork normals) */ -#define MAX_NORMAL_VOTES 128 -#define EQUAL_NORMAL_EPSILON 0.01 -#define BAD_NORMAL_EPSILON 0.5 +//#define MAX_NORMAL_VOTES 128 +//#define EQUAL_NORMAL_EPSILON 0.01 +//#define BAD_NORMAL_EPSILON 0.5 +// +//void PicoFixSurfaceNormals( picoSurface_t *surface ){ +// int i, j, k, a, b, c, numVotes, faceIndex; +// picoVec3_t votes[ MAX_NORMAL_VOTES ]; +// picoVec3_t *normals, diff; +// picoVec4_t plane; +// +// +// /* dummy check */ +// if ( surface == NULL || surface->numVertexes == 0 ) { +// return; +// } +// +// /* fixme: handle other surface types */ +// if ( surface->type != PICO_TRIANGLES ) { +// return; +// } +// +// /* allocate normal storage */ +// normals = _pico_alloc( surface->numVertexes * sizeof( *normals ) ); +// if ( normals == NULL ) { +// _pico_printf( PICO_ERROR, "PicoFixSurfaceNormals: Unable to allocate memory for temporary normal storage" ); +// return; +// } +// +// /* zero it out */ +// memset( normals, 0, surface->numVertexes * sizeof( *normals ) ); +// +// /* walk vertex list */ +// for ( i = 0; i < surface->numVertexes; i++ ) +// { +// /* zero out votes */ +// numVotes = 0; +// +// /* find all the triangles that reference this vertex */ +// for ( j = 0, faceIndex = 0; j < surface->numIndexes; j += 3, faceIndex++ ) +// { +// /* get triangle */ +// a = surface->index[ j ]; +// b = surface->index[ j + 1 ]; +// c = surface->index[ j + 2 ]; +// +// /* ignore degenerate triangles */ +// if ( a == b || b == c || c == a ) { +// continue; +// } +// +// /* ignore indexes out of range */ +// if ( a < 0 || a >= surface->numVertexes || +// b < 0 || b >= surface->numVertexes || +// c < 0 || c >= surface->numVertexes ) { +// continue; +// } +// +// /* test triangle */ +// if ( a == i || b == i || c == i ) { +// /* if this surface has face normals */ +// if ( surface->numFaceNormals && faceIndex < surface->numFaceNormals ) { +// _pico_copy_vec( surface->faceNormal[ faceIndex ], plane ); +// if ( plane[ 0 ] == 0.f && plane[ 1 ] == 0.f && plane[ 2 ] == 0.f ) { +// /* if null normal, make plane from the 3 points */ +// if ( _pico_calc_plane( plane, surface->xyz[ a ], surface->xyz[ b ], surface->xyz[ c ] ) == 0 ) { +// continue; +// } +// } +// } +// /* make a plane from the 3 points */ +// else if ( _pico_calc_plane( plane, surface->xyz[ a ], surface->xyz[ b ], surface->xyz[ c ] ) == 0 ) { +// continue; +// } +// +// /* see if this normal has already been voted */ +// for ( k = 0; k < numVotes; k++ ) +// { +// _pico_subtract_vec( plane, votes[ k ], diff ); +// if ( fabs( diff[ 0 ] ) < EQUAL_NORMAL_EPSILON && +// fabs( diff[ 1 ] ) < EQUAL_NORMAL_EPSILON && +// fabs( diff[ 2 ] ) < EQUAL_NORMAL_EPSILON ) { +// break; +// } +// } +// +// /* add a new vote? */ +// if ( k == numVotes && numVotes < MAX_NORMAL_VOTES ) { +// _pico_copy_vec( plane, votes[ numVotes ] ); +// numVotes++; +// } +// } +// } +// +// /* tally votes */ +// if ( numVotes > 0 ) { +// /* create average normal */ +// _pico_zero_vec( normals[ i ] ); +// for ( k = 0; k < numVotes; k++ ) +// _pico_add_vec( normals[ i ], votes[ k ], normals[ i ] ); +// +// /* normalize it */ +// if ( _pico_normalize_vec( normals[ i ] ) ) { +// /* test against actual normal */ +// if ( fabs( _pico_dot_vec( normals[ i ], surface->normal[ i ] ) - 1 ) > BAD_NORMAL_EPSILON ) { +// //% printf( "Normal %8d: (%f %f %f) -> (%f %f %f)\n", i, +// //% surface->normal[ i ][ 0 ], surface->normal[ i ][ 1 ], surface->normal[ i ][ 2 ], +// //% normals[ i ][ 0 ], normals[ i ][ 1 ], normals[ i ][ 2 ] ); +// _pico_copy_vec( normals[ i ], surface->normal[ i ] ); +// } +// } +// } +// } +// +// /* free normal storage */ +// _pico_free( normals ); +//} + +typedef struct _IndexArray IndexArray; +struct _IndexArray +{ + picoIndex_t* data; + picoIndex_t* last; +}; + +void indexarray_push_back(IndexArray* self, picoIndex_t value) { + *self->last++ = value; +} + +size_t indexarray_size(IndexArray* self) { + return self->last - self->data; +} + +void indexarray_reserve(IndexArray* self, size_t size) { + self->data = self->last = _pico_calloc(size, sizeof(picoIndex_t)); +} + +void indexarray_clear(IndexArray* self) { + _pico_free(self->data); +} + +typedef struct _BinaryTreeNode BinaryTreeNode; +struct _BinaryTreeNode +{ + picoIndex_t left; + picoIndex_t right; +}; + +typedef struct _BinaryTree BinaryTree; +struct _BinaryTree +{ + BinaryTreeNode* data; + BinaryTreeNode* last; +}; + +void binarytree_extend(BinaryTree* self) { + self->last->left = 0; + self->last->right = 0; + ++self->last; +} + +size_t binarytree_size(BinaryTree* self) { + return self->last - self->data; +} + +void binarytree_reserve(BinaryTree* self, size_t size) { + self->data = self->last = _pico_calloc(size, sizeof(BinaryTreeNode)); +} + +void binarytree_clear(BinaryTree* self) { + _pico_free(self->data); +} + +typedef int(*LessFunc)(void*, picoIndex_t, picoIndex_t); + +typedef struct _UniqueIndices UniqueIndices; +struct _UniqueIndices +{ + BinaryTree tree; + IndexArray indices; + LessFunc lessFunc; + void* lessData; +}; -void PicoFixSurfaceNormals( picoSurface_t *surface ){ - int i, j, k, a, b, c, numVotes, faceIndex; - picoVec3_t votes[ MAX_NORMAL_VOTES ]; - picoVec3_t *normals, diff; - picoVec4_t plane; +size_t UniqueIndices_size(UniqueIndices* self) { + return binarytree_size(&self->tree); +} +void UniqueIndices_reserve(UniqueIndices* self, size_t size) { + binarytree_reserve(&self->tree, size); + indexarray_reserve(&self->indices, size); +} - /* dummy check */ - if ( surface == NULL || surface->numVertexes == 0 ) { - return; +void UniqueIndices_init(UniqueIndices* self, LessFunc lessFunc, void* lessData) { + self->lessFunc = lessFunc; + self->lessData = lessData; +} + +void UniqueIndices_destroy(UniqueIndices* self) { + binarytree_clear(&self->tree); + indexarray_clear(&self->indices); +} + +picoIndex_t UniqueIndices_find_or_insert(UniqueIndices* self, picoIndex_t value) { + picoIndex_t index = 0; + + for (;; ) + { + if (self->lessFunc(self->lessData, value, self->indices.data[index])) { + BinaryTreeNode* node = self->tree.data + index; + if (node->left != 0) { + index = node->left; + continue; + } + else + { + node->left = (picoIndex_t)binarytree_size(&self->tree); + binarytree_extend(&self->tree); + indexarray_push_back(&self->indices, value); + return node->left; + } + } + if (self->lessFunc(self->lessData, self->indices.data[index], value)) { + BinaryTreeNode* node = self->tree.data + index; + if (node->right != 0) { + index = node->right; + continue; + } + else + { + node->right = (picoIndex_t)binarytree_size(&self->tree); + binarytree_extend(&self->tree); + indexarray_push_back(&self->indices, value); + return node->right; + } + } + + return index; } +} - /* fixme: handle other surface types */ - if ( surface->type != PICO_TRIANGLES ) { - return; +picoIndex_t UniqueIndices_insert(UniqueIndices* self, picoIndex_t value) { + if (self->tree.data == self->tree.last) { + binarytree_extend(&self->tree); + indexarray_push_back(&self->indices, value); + return 0; + } + else + { + return UniqueIndices_find_or_insert(self, value); } +} - /* allocate normal storage */ - normals = _pico_alloc( surface->numVertexes * sizeof( *normals ) ); - if ( normals == NULL ) { - _pico_printf( PICO_ERROR, "PicoFixSurfaceNormals: Unable to allocate memory for temporary normal storage" ); - return; +typedef struct picoSmoothVertices_s picoSmoothVertices_t; +struct picoSmoothVertices_s +{ + picoVec3_t* xyz; + picoIndex_t* smoothingGroups; +}; + +int lessSmoothVertex(void* data, picoIndex_t first, picoIndex_t second) { + picoSmoothVertices_t* smoothVertices = data; + + if (smoothVertices->xyz[first][0] != smoothVertices->xyz[second][0]) { + return smoothVertices->xyz[first][0] < smoothVertices->xyz[second][0]; + } + if (smoothVertices->xyz[first][1] != smoothVertices->xyz[second][1]) { + return smoothVertices->xyz[first][1] < smoothVertices->xyz[second][1]; } + if (smoothVertices->xyz[first][2] != smoothVertices->xyz[second][2]) { + return smoothVertices->xyz[first][2] < smoothVertices->xyz[second][2]; + } + if (smoothVertices->smoothingGroups[first] != smoothVertices->smoothingGroups[second]) { + return smoothVertices->smoothingGroups[first] < smoothVertices->smoothingGroups[second]; + } + return 0; +} - /* zero it out */ - memset( normals, 0, surface->numVertexes * sizeof( *normals ) ); +void _pico_vertices_combine_shared_normals(picoVec3_t* xyz, picoIndex_t* smoothingGroups, picoVec3_t* normals, picoIndex_t numVertices) { + UniqueIndices vertices; + IndexArray indices; + picoSmoothVertices_t smoothVertices = { xyz, smoothingGroups }; + UniqueIndices_init(&vertices, lessSmoothVertex, &smoothVertices); + UniqueIndices_reserve(&vertices, numVertices); + indexarray_reserve(&indices, numVertices); - /* walk vertex list */ - for ( i = 0; i < surface->numVertexes; i++ ) { - /* zero out votes */ - numVotes = 0; - - /* find all the triangles that reference this vertex */ - for ( j = 0, faceIndex = 0; j < surface->numIndexes; j += 3, faceIndex++ ) + picoIndex_t i = 0; + for (; i < numVertices; ++i) { - /* get triangle */ - a = surface->index[ j ]; - b = surface->index[ j + 1 ]; - c = surface->index[ j + 2 ]; - - /* ignore degenerate triangles */ - if ( a == b || b == c || c == a ) { - continue; + size_t size = UniqueIndices_size(&vertices); + picoIndex_t index = UniqueIndices_insert(&vertices, i); + if ((size_t)index != size) { + float* normal = normals[vertices.indices.data[index]]; + _pico_add_vec(normal, normals[i], normal); } + indexarray_push_back(&indices, index); + } + } - /* ignore indexes out of range */ - if ( a < 0 || a >= surface->numVertexes || - b < 0 || b >= surface->numVertexes || - c < 0 || c >= surface->numVertexes ) { - continue; + { + picoIndex_t maxIndex = 0; + picoIndex_t* i = indices.data; + for (; i != indices.last; ++i) + { + if (*i <= maxIndex) { + _pico_copy_vec(normals[vertices.indices.data[*i]], normals[i - indices.data]); } + else + { + maxIndex = *i; + } + } + } - /* test triangle */ - if ( a == i || b == i || c == i ) { - /* if this surface has face normals */ - if ( surface->numFaceNormals && faceIndex < surface->numFaceNormals ) { - _pico_copy_vec( surface->faceNormal[ faceIndex ], plane ); - if ( plane[ 0 ] == 0.f && plane[ 1 ] == 0.f && plane[ 2 ] == 0.f ) { - /* if null normal, make plane from the 3 points */ - if ( _pico_calc_plane( plane, surface->xyz[ a ], surface->xyz[ b ], surface->xyz[ c ] ) == 0 ) { - continue; - } - } - } - /* make a plane from the 3 points */ - else if ( _pico_calc_plane( plane, surface->xyz[ a ], surface->xyz[ b ], surface->xyz[ c ] ) == 0 ) { - continue; - } + UniqueIndices_destroy(&vertices); + indexarray_clear(&indices); +} - /* see if this normal has already been voted */ - for ( k = 0; k < numVotes; k++ ) - { - _pico_subtract_vec( plane, votes[ k ], diff ); - if ( fabs( diff[ 0 ] ) < EQUAL_NORMAL_EPSILON && - fabs( diff[ 1 ] ) < EQUAL_NORMAL_EPSILON && - fabs( diff[ 2 ] ) < EQUAL_NORMAL_EPSILON ) { - break; - } - } +typedef picoVec3_t* picoNormalIter_t; +typedef picoIndex_t* picoIndexIter_t; - /* add a new vote? */ - if ( k == numVotes && numVotes < MAX_NORMAL_VOTES ) { - _pico_copy_vec( plane, votes[ numVotes ] ); - numVotes++; - } - } - } +#define THE_CROSSPRODUCTS_OF_ANY_PAIR_OF_EDGES_OF_A_GIVEN_TRIANGLE_ARE_EQUAL 1 - /* tally votes */ - if ( numVotes > 0 ) { - /* create average normal */ - _pico_zero_vec( normals[ i ] ); - for ( k = 0; k < numVotes; k++ ) - _pico_add_vec( normals[ i ], votes[ k ], normals[ i ] ); - - /* normalize it */ - if ( _pico_normalize_vec( normals[ i ] ) ) { - /* test against actual normal */ - if ( fabs( _pico_dot_vec( normals[ i ], surface->normal[ i ] ) - 1 ) > BAD_NORMAL_EPSILON ) { - //% printf( "Normal %8d: (%f %f %f) -> (%f %f %f)\n", i, - //% surface->normal[ i ][ 0 ], surface->normal[ i ][ 1 ], surface->normal[ i ][ 2 ], - //% normals[ i ][ 0 ], normals[ i ][ 1 ], normals[ i ][ 2 ] ); - _pico_copy_vec( normals[ i ], surface->normal[ i ] ); +void _pico_triangles_generate_weighted_normals(picoIndexIter_t first, picoIndexIter_t end, picoVec3_t* xyz, picoVec3_t* normals) { + for (; first != end; first += 3) + { +#if (THE_CROSSPRODUCTS_OF_ANY_PAIR_OF_EDGES_OF_A_GIVEN_TRIANGLE_ARE_EQUAL) + picoVec3_t weightedNormal; + { + float* a = xyz[*(first + 0)]; + float* b = xyz[*(first + 1)]; + float* c = xyz[*(first + 2)]; + picoVec3_t ba, ca; + _pico_subtract_vec(b, a, ba); + _pico_subtract_vec(c, a, ca); + _pico_cross_vec(ca, ba, weightedNormal); + } +#endif + { + int j = 0; + for (; j < 3; ++j) + { + float* normal = normals[*(first + j)]; +#if ( !THE_CROSSPRODUCTS_OF_ANY_PAIR_OF_EDGES_OF_A_GIVEN_TRIANGLE_ARE_EQUAL ) + picoVec3_t weightedNormal; + { + float* a = xyz[*(first + ((j + 0) % 3))]; + float* b = xyz[*(first + ((j + 1) % 3))]; + float* c = xyz[*(first + ((j + 2) % 3))]; + picoVec3_t ba, ca; + _pico_subtract_vec(b, a, ba); + _pico_subtract_vec(c, a, ca); + _pico_cross_vec(ca, ba, weightedNormal); } +#endif + _pico_add_vec(weightedNormal, normal, normal); } } } +} - /* free normal storage */ - _pico_free( normals ); +void _pico_normals_zero(picoNormalIter_t first, picoNormalIter_t last) { + for (; first != last; ++first) + { + _pico_zero_vec(*first); + } } +void _pico_normals_normalize(picoNormalIter_t first, picoNormalIter_t last) { + for (; first != last; ++first) + { + _pico_normalize_vec(*first); + } +} +double _pico_length_vec(picoVec3_t vec) { + return sqrt(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]); +} +#define NORMAL_UNIT_LENGTH_EPSILON 0.01 +#define FLOAT_EQUAL_EPSILON( f, other, epsilon ) ( fabs( f - other ) < epsilon ) + +int _pico_normal_is_unit_length(picoVec3_t normal) { + return FLOAT_EQUAL_EPSILON(_pico_length_vec(normal), 1.0, NORMAL_UNIT_LENGTH_EPSILON); +} + +int _pico_normal_within_tolerance(picoVec3_t normal, picoVec3_t other) { + return _pico_dot_vec(normal, other) > 0.0f; +} + +void _pico_normals_assign_generated_normals(picoNormalIter_t first, picoNormalIter_t last, picoNormalIter_t generated) { + for (; first != last; ++first, ++generated) + { + if (!_pico_normal_is_unit_length(*first) || !_pico_normal_within_tolerance(*first, *generated)) { + _pico_copy_vec(*generated, *first); + } + } +} + +void PicoFixSurfaceNormals(picoSurface_t* surface) { + picoVec3_t* normals = (picoVec3_t*)_pico_calloc(surface->numVertexes, sizeof(picoVec3_t)); + + _pico_normals_zero(normals, normals + surface->numVertexes); + + _pico_triangles_generate_weighted_normals(surface->index, surface->index + surface->numIndexes, surface->xyz, normals); + _pico_vertices_combine_shared_normals(surface->xyz, surface->smoothingGroup, normals, surface->numVertexes); + + _pico_normals_normalize(normals, normals + surface->numVertexes); + + _pico_normals_assign_generated_normals(surface->normal, surface->normal + surface->numVertexes, normals); + + _pico_free(normals); +} /* PicoRemapModel() - sea @@ -1727,8 +2090,8 @@ void PicoFixSurfaceNormals( picoSurface_t *surface ){ #define _prm_error_return \ { \ - _pico_free_parser( p ); \ - _pico_free_file( remapBuffer ); \ + _pico_free_parser( p ); \ + _pico_free_file( remapBuffer ); \ return 0; \ } @@ -1744,7 +2107,7 @@ int PicoRemapModel( picoModel_t *model, char *remapFile ){ } /* load remap file contents */ - _pico_load_file( remapFile,&remapBuffer,&remapBufSize ); + _pico_load_file( remapFile, &remapBuffer, &remapBufSize ); /* check result */ if ( remapBufSize == 0 ) { @@ -1765,12 +2128,12 @@ int PicoRemapModel( picoModel_t *model, char *remapFile ){ while ( 1 ) { /* get next token in remap file */ - if ( !_pico_parse( p,1 ) ) { + if ( !_pico_parse( p, 1 ) ) { break; } /* skip over c++ style comment lines */ - if ( !_pico_stricmp( p->token,"//" ) ) { + if ( !_pico_stricmp( p->token, "//" ) ) { _pico_parse_skip_rest( p ); continue; } @@ -1781,7 +2144,7 @@ int PicoRemapModel( picoModel_t *model, char *remapFile ){ int level = 1; /* check bracket */ - if ( !_pico_parse_check( p,1,"{" ) ) { + if ( !_pico_parse_check( p, 1, "{" ) ) { _prm_error_return; } @@ -1791,15 +2154,14 @@ int PicoRemapModel( picoModel_t *model, char *remapFile ){ picoShader_t *shader; char *materialName; - /* get material name */ - if ( _pico_parse( p,1 ) == NULL ) { + if ( _pico_parse( p, 1 ) == NULL ) { break; } if ( !strlen( p->token ) ) { continue; } - materialName = _pico_clone_alloc( p->token,-1 ); + materialName = _pico_clone_alloc( p->token ); if ( materialName == NULL ) { _prm_error_return; } @@ -1816,26 +2178,26 @@ int PicoRemapModel( picoModel_t *model, char *remapFile ){ } /* get next token (assignment token or shader name) */ - if ( !_pico_parse( p,0 ) ) { + if ( !_pico_parse( p, 0 ) ) { _pico_free( materialName ); _prm_error_return; } /* skip assignment token (if present) */ - if ( !strcmp( p->token,"=>" ) || - !strcmp( p->token,"->" ) || - !strcmp( p->token,"=" ) ) { + if ( !strcmp( p->token, "=>" ) || + !strcmp( p->token, "->" ) || + !strcmp( p->token, "=" ) ) { /* simply grab the next token */ - if ( !_pico_parse( p,0 ) ) { + if ( !_pico_parse( p, 0 ) ) { _pico_free( materialName ); _prm_error_return; } } /* try to find material by name */ - shader = PicoFindShader( model,materialName,0 ); + shader = PicoFindShader( model, materialName, 0 ); /* we've found a material matching the name */ if ( shader != NULL ) { - PicoSetShaderName( shader,p->token ); + PicoSetShaderName( shader, p->token ); } /* free memory used by material name */ _pico_free( materialName ); @@ -1846,29 +2208,29 @@ int PicoRemapModel( picoModel_t *model, char *remapFile ){ } /* block for detailed single material remappings */ /* materials[ "m" ] { key data... } */ - else if ( !_pico_stricmp( p->token,"materials[" ) ) { + else if ( !_pico_stricmp( p->token, "materials[" ) ) { picoShader_t *shader; char *tempMaterialName; int level = 1; /* get material name */ - if ( !_pico_parse( p,0 ) ) { + if ( !_pico_parse( p, 0 ) ) { _prm_error_return; } /* temporary copy of material name */ - tempMaterialName = _pico_clone_alloc( p->token,-1 ); + tempMaterialName = _pico_clone_alloc( p->token ); if ( tempMaterialName == NULL ) { _prm_error_return; } /* check square closing bracket */ - if ( !_pico_parse_check( p,0,"]" ) ) { + if ( !_pico_parse_check( p, 0, "]" ) ) { _prm_error_return; } /* try to find material by name */ - shader = PicoFindShader( model,tempMaterialName,0 ); + shader = PicoFindShader( model, tempMaterialName, 0 ); /* free memory used by temporary material name */ _pico_free( tempMaterialName ); @@ -1881,7 +2243,7 @@ int PicoRemapModel( picoModel_t *model, char *remapFile ){ continue; } /* check opening bracket */ - if ( !_pico_parse_check( p,1,"{" ) ) { + if ( !_pico_parse_check( p, 1, "{" ) ) { _prm_error_return; } @@ -1889,7 +2251,7 @@ int PicoRemapModel( picoModel_t *model, char *remapFile ){ while ( 1 ) { /* get key name */ - if ( _pico_parse( p,1 ) == NULL ) { + if ( _pico_parse( p, 1 ) == NULL ) { break; } if ( !strlen( p->token ) ) { @@ -1908,26 +2270,26 @@ int PicoRemapModel( picoModel_t *model, char *remapFile ){ } /* remap shader name */ - if ( !_pico_stricmp( p->token,"shader" ) ) { - if ( !_pico_parse( p,0 ) ) { + if ( !_pico_stricmp( p->token, "shader" ) ) { + if ( !_pico_parse( p, 0 ) ) { _prm_error_return; } - PicoSetShaderName( shader,p->token ); + PicoSetShaderName( shader, p->token ); } /* remap shader map name */ - else if ( !_pico_stricmp( p->token,"mapname" ) ) { - if ( !_pico_parse( p,0 ) ) { + else if ( !_pico_stricmp( p->token, "mapname" ) ) { + if ( !_pico_parse( p, 0 ) ) { _prm_error_return; } - PicoSetShaderMapName( shader,p->token ); + PicoSetShaderMapName( shader, p->token ); } /* remap shader's ambient color */ - else if ( !_pico_stricmp( p->token,"ambient" ) ) { + else if ( !_pico_stricmp( p->token, "ambient" ) ) { picoColor_t color; picoVec3_t v; /* get vector from parser */ - if ( !_pico_parse_vec( p,v ) ) { + if ( !_pico_parse_vec( p, v ) ) { _prm_error_return; } @@ -1938,15 +2300,15 @@ int PicoRemapModel( picoModel_t *model, char *remapFile ){ color[ 3 ] = 1; /* set new ambient color */ - PicoSetShaderAmbientColor( shader,color ); + PicoSetShaderAmbientColor( shader, color ); } /* remap shader's diffuse color */ - else if ( !_pico_stricmp( p->token,"diffuse" ) ) { + else if ( !_pico_stricmp( p->token, "diffuse" ) ) { picoColor_t color; picoVec3_t v; /* get vector from parser */ - if ( !_pico_parse_vec( p,v ) ) { + if ( !_pico_parse_vec( p, v ) ) { _prm_error_return; } @@ -1957,10 +2319,10 @@ int PicoRemapModel( picoModel_t *model, char *remapFile ){ color[ 3 ] = 1; /* set new ambient color */ - PicoSetShaderDiffuseColor( shader,color ); + PicoSetShaderDiffuseColor( shader, color ); } /* remap shader's specular color */ - else if ( !_pico_stricmp( p->token,"specular" ) ) { + else if ( !_pico_stricmp( p->token, "specular" ) ) { picoColor_t color; picoVec3_t v; @@ -1976,7 +2338,7 @@ int PicoRemapModel( picoModel_t *model, char *remapFile ){ color[ 3 ] = 1; /* set new ambient color */ - PicoSetShaderSpecularColor( shader,color ); + PicoSetShaderSpecularColor( shader, color ); } /* skip rest */ _pico_parse_skip_rest( p ); @@ -2002,8 +2364,8 @@ int PicoRemapModel( picoModel_t *model, char *remapFile ){ void PicoAddTriangleToModel( picoModel_t *model, picoVec3_t** xyz, picoVec3_t** normals, int numSTs, picoVec2_t **st, int numColors, picoColor_t **colors, - picoShader_t* shader ){ - int i,j; + picoShader_t* shader, picoIndex_t* smoothingGroup ){ + int i, j; int vertDataIndex; picoSurface_t* workSurface = NULL; @@ -2038,7 +2400,7 @@ void PicoAddTriangleToModel( picoModel_t *model, picoVec3_t** xyz, picoVec3_t** int newVertIndex = PicoGetSurfaceNumIndexes( workSurface ); /* get the index of the vertex that we're going to store at newVertIndex */ - vertDataIndex = PicoFindSurfaceVertexNum( workSurface, *xyz[i], *normals[i], numSTs, st[i], numColors, colors[i] ); + vertDataIndex = PicoFindSurfaceVertexNum( workSurface, *xyz[i], *normals[i], numSTs, st[i], numColors, colors[i], smoothingGroup[i] ); /* the vertex wasn't found, so create a new vertex in the pool from the data we have */ if ( vertDataIndex == -1 ) { @@ -2058,6 +2420,8 @@ void PicoAddTriangleToModel( picoModel_t *model, picoVec3_t** xyz, picoVec3_t** { PicoSetSurfaceST( workSurface, j, vertDataIndex, st[i][j] ); } + + PicoSetSurfaceSmoothingGroup( workSurface, vertDataIndex, smoothingGroup[ i ] ); } /* add this vertex to the triangle */ diff --git a/q3map2/libs/picomodel/picomodules.c b/q3map2/libs/picomodel/picomodules.c index 6ccafc46..5f298492 100644 --- a/q3map2/libs/picomodel/picomodules.c +++ b/q3map2/libs/picomodel/picomodules.c @@ -54,6 +54,7 @@ extern const picoModule_t picoModuleMDC; extern const picoModule_t picoModuleMD2; extern const picoModule_t picoModuleFM; extern const picoModule_t picoModuleLWO; +extern const picoModule_t picoModuleTerrain; @@ -68,6 +69,7 @@ const picoModule_t *picoModules[] = &picoModuleMD2, /* quake2 md2 */ &picoModuleFM, /* heretic2 fm */ &picoModuleLWO, /* lightwave object */ + &picoModuleTerrain, /* picoterrain object */ &picoModuleOBJ, /* wavefront object */ NULL /* arnold */ }; @@ -81,7 +83,7 @@ const picoModule_t *picoModules[] = this param can be NULL when the count is not needed. */ -const picoModule_t **PicoModuleList( int *numModules ){ +const picoModule_t **PicoModuleList( int *numModules ) { /* get module count */ if ( numModules != NULL ) { for ( ( *numModules ) = 0; picoModules[ *numModules ] != NULL; ( *numModules )++ ) ; diff --git a/q3map2/libs/picomodel/pm_3ds.c b/q3map2/libs/picomodel/pm_3ds.c index 64b77fe6..4065f3e7 100644 --- a/q3map2/libs/picomodel/pm_3ds.c +++ b/q3map2/libs/picomodel/pm_3ds.c @@ -124,7 +124,7 @@ debugChunkNames[] = { CHUNK_OBJECT_UV, "CHUNK_OBJECT_UV" }, { 0, NULL } }; -static char *DebugGetChunkName( int id ){ +static char *DebugGetChunkName( int id ) { int i,max; /* imax? ;) */ max = sizeof( debugChunkNames ) / sizeof( debugChunkNames[0] ); @@ -397,6 +397,9 @@ static int GetMeshShader( T3dsLoaderPers *pers ){ return 0; } + /* ydnar: trim to first whitespace */ + _pico_first_token( shaderName ); + /* now that we have the shader name we need to go through all of */ /* the shaders and check the name against each shader. when we */ /* find a shader in our shader list that matches this name we */ @@ -418,7 +421,7 @@ static int GetMeshShader( T3dsLoaderPers *pers ){ /* we have a valid map name ptr */ if ( mapNamePtr != NULL ) { char temp[128]; - char *name; + const char *name; /* copy map name to local buffer */ strcpy( mapName,mapNamePtr ); @@ -523,7 +526,6 @@ static int DoNextEditorDataChunk( T3dsLoaderPers *pers, long endofs ){ /* read in surface name */ if ( !GetASCIIZ( pers,surfaceName,sizeof( surfaceName ) ) ) { return 0; /* this is bad */ - } //PicoGetSurfaceName /* ignore NULL name surfaces */ @@ -606,10 +608,13 @@ static int DoNextEditorDataChunk( T3dsLoaderPers *pers, long endofs ){ /* but for now we skip the new material's name ... */ if ( pers->shader ) { char *name = (char *)( pers->bufptr + pers->cofs ); - PicoSetShaderName( pers->shader,name ); + char *cleanedName = _pico_clone_alloc( name ); + _pico_first_token( cleanedName ); + PicoSetShaderName( pers->shader, cleanedName ); #ifdef DEBUG_PM_3DS - printf( "NewShader: '%s'\n",name ); + printf( "NewShader: '%s'\n", cleanedName ); #endif + _pico_free( cleanedName ); } } if ( chunk->id == CHUNK_MATDIFFUSE ) { diff --git a/q3map2/libs/picomodel/pm_ase.c b/q3map2/libs/picomodel/pm_ase.c index dc31dd5f..67f40d41 100644 --- a/q3map2/libs/picomodel/pm_ase.c +++ b/q3map2/libs/picomodel/pm_ase.c @@ -105,6 +105,25 @@ static aseSubMaterial_t* _ase_get_submaterial( aseMaterial_t* list, int mtlIdPar return subMtl; } +aseSubMaterial_t* _ase_get_submaterial_or_default( aseMaterial_t* materials, int mtlIdParent, int subMtlId ){ + aseSubMaterial_t* subMtl = _ase_get_submaterial( materials, mtlIdParent, subMtlId ); + if ( subMtl != NULL ) { + return subMtl; + } + + /* ydnar: trying default submaterial */ + subMtl = _ase_get_submaterial( materials, mtlIdParent, 0 ); + if ( subMtl != NULL ) { + return subMtl; + } + + _pico_printf( PICO_ERROR, "Could not find material/submaterial for id %d/%d\n", mtlIdParent, subMtlId ); + return NULL; +} + + + + static aseMaterial_t* _ase_add_material( aseMaterial_t **list, int mtlIdParent ){ aseMaterial_t *mtl = _pico_calloc( 1, sizeof( aseMaterial_t ) ); mtl->mtlId = mtlIdParent; @@ -173,64 +192,6 @@ static void _ase_print_materials( aseMaterial_t *list ){ } #endif //DEBUG_PM_ASE -/* ASE Face management */ -/* These are used to keep an association between a submaterial and a face definition */ -/* They are kept in parallel with the current picoSurface, */ -/* and are used by _ase_submit_triangles to lookup the proper material/submaterial IDs */ -typedef struct aseFace_s -{ - struct aseFace_s* next; - int mtlId; - int subMtlId; - int index[9]; -} aseFace_t; - -/* ASE Face management functions */ -void _ase_add_face( aseFace_t **list, aseFace_t **tail, aseFace_t *newFace ){ - /* insert as head of list */ - if ( !( *list ) ) { - *list = newFace; - } - else - { - ( *tail )->next = newFace; - } - - *tail = newFace; - newFace->next = NULL; - - //tag the color indices so we can detect them and apply the default color to them - newFace->index[6] = -1; - newFace->index[7] = -1; - newFace->index[8] = -1; -} - -aseFace_t* _ase_get_face_for_index( aseFace_t *list, int index ){ - int counter = 0; - aseFace_t* face = list; - - while ( counter < index ) - { - face = face->next; - counter++; - } - return face; -} -static void _ase_free_faces( aseFace_t** list, aseFace_t** tail ){ - aseFace_t* face = *list; - aseFace_t* tempFace = NULL; - - while ( face ) - { - tempFace = face->next; - _pico_free( face ); - face = tempFace; - } - - ( *list ) = NULL; - ( *tail ) = NULL; -} - /* todo: * - apply material specific uv offsets to uv coordinates */ @@ -274,7 +235,65 @@ static int _ase_canload( PM_PARAMS_CANLOAD ){ return PICO_PMV_OK; } +typedef struct aseVertex_s aseVertex_t; +struct aseVertex_s +{ + picoVec3_t xyz; + picoVec3_t normal; + picoIndex_t id; +}; +typedef struct aseTexCoord_s aseTexCoord_t; +struct aseTexCoord_s +{ + picoVec2_t texcoord; +}; + +typedef struct aseColor_s aseColor_t; +struct aseColor_s +{ + picoColor_t color; +}; + +typedef struct aseFace_s aseFace_t; +struct aseFace_s +{ + picoIndex_t indices[9]; + picoIndex_t smoothingGroup; + picoIndex_t materialId; + picoIndex_t subMaterialId; +}; +typedef aseFace_t* aseFacesIter_t; + +picoSurface_t* PicoModelFindOrAddSurface( picoModel_t *model, picoShader_t* shader ){ + /* see if a surface already has the shader */ + int i = 0; + for ( ; i < model->numSurfaces ; i++ ) + { + picoSurface_t* workSurface = model->surface[i]; + if ( workSurface->shader == shader ) { + return workSurface; + } + } + + /* no surface uses this shader yet, so create a new surface */ + + { + /* create a new surface in the model for the unique shader */ + picoSurface_t* workSurface = PicoNewSurface( model ); + if ( !workSurface ) { + _pico_printf( PICO_ERROR, "Could not allocate a new surface!\n" ); + return 0; + } + + /* do surface setup */ + PicoSetSurfaceType( workSurface, PICO_TRIANGLES ); + PicoSetSurfaceName( workSurface, shader->name ); + PicoSetSurfaceShader( workSurface, shader ); + + return workSurface; + } +} /* _ase_submit_triangles - jhefty use the surface and the current face list to look up material/submaterial IDs @@ -286,65 +305,199 @@ static int _ase_canload( PM_PARAMS_CANLOAD ){ indexes 6 7 8 = color indexes (new) */ -static void _ase_submit_triangles( picoSurface_t* surface, picoModel_t* model, aseMaterial_t* materials, aseFace_t* faces ){ - aseFace_t* face; - aseSubMaterial_t* subMtl; - picoVec3_t* xyz[3]; - picoVec3_t* normal[3]; - picoVec2_t* st[3]; - picoColor_t* color[3]; - int i; - - face = faces; - while ( face != NULL ) +#if 0 +typedef picoIndex_t* picoIndexIter_t; + +typedef struct aseUniqueIndices_s aseUniqueIndices_t; +struct aseUniqueIndices_s +{ + picoIndex_t* data; + picoIndex_t* last; + + aseFace_t* faces; +}; + +size_t aseUniqueIndices_size( aseUniqueIndices_t* self ) { + return self->last - self->data; +} + +void aseUniqueIndices_reserve( aseUniqueIndices_t* self, picoIndex_t size ) { + self->data = self->last = (picoIndex_t*)_pico_calloc( size, sizeof( picoIndex_t ) ); +} + +void aseUniqueIndices_clear( aseUniqueIndices_t* self ) { + _pico_free( self->data ); +} + +void aseUniqueIndices_pushBack( aseUniqueIndices_t* self, picoIndex_t index ) { + *self->last++ = index; +} + +picoIndex_t aseFaces_getVertexIndex( aseFace_t* faces, picoIndex_t index ) { + return faces[index / 3].indices[index % 3]; +} + +picoIndex_t aseFaces_getTexCoordIndex( aseFace_t* faces, picoIndex_t index ) { + return faces[index / 3].indices[( index % 3 ) + 3]; +} + +picoIndex_t aseFaces_getColorIndex( aseFace_t* faces, picoIndex_t index ) { + return faces[index / 3].indices[( index % 3 ) + 6]; +} + +int aseUniqueIndex_equal( aseFace_t* faces, picoIndex_t index, picoIndex_t other ) { + return aseFaces_getVertexIndex( faces, index ) == aseFaces_getVertexIndex( faces, other ) + && aseFaces_getTexCoordIndex( faces, index ) == aseFaces_getTexCoordIndex( faces, other ) + && aseFaces_getColorIndex( faces, index ) == aseFaces_getColorIndex( faces, other ); +} + +picoIndex_t aseUniqueIndices_insertUniqueVertex( aseUniqueIndices_t* self, picoIndex_t index ) { + picoIndexIter_t i = self->data; + for (; i != self->last; ++i ) + { + picoIndex_t other = (picoIndex_t)( i - self->data ); + if ( aseUniqueIndex_equal( self->faces, index, other ) ) { + return other; + } + } + + aseUniqueIndices_pushBack( self, index ); + return (picoIndex_t)( aseUniqueIndices_size( self ) - 1 ); +} + +static void _ase_submit_triangles_unshared( picoModel_t* model, aseMaterial_t* materials, aseVertex_t* vertices, aseTexCoord_t* texcoords, aseColor_t* colors, aseFace_t* faces, int numFaces, int meshHasNormals ) { + aseFacesIter_t i = faces, end = faces + numFaces; + + aseUniqueIndices_t indices; + aseUniqueIndices_t remap; + aseUniqueIndices_reserve( &indices, numFaces * 3 ); + aseUniqueIndices_reserve( &remap, numFaces * 3 ); + indices.faces = faces; + + for (; i != end; ++i ) { /* look up the shader for the material/submaterial pair */ - subMtl = _ase_get_submaterial( materials, face->mtlId, face->subMtlId ); + aseSubMaterial_t* subMtl = _ase_get_submaterial_or_default( materials, ( *i ).materialId, ( *i ).subMaterialId ); if ( subMtl == NULL ) { - /* ydnar: trying default submaterial */ - subMtl = _ase_get_submaterial( materials, face->mtlId, 0 ); - if ( subMtl == NULL ) { - _pico_printf( PICO_ERROR, "Could not find material/submaterial for id %d/%d\n", face->mtlId, face->subMtlId ); - return; - } + return; } - /* we pull the data from the surface using the facelist data */ - for ( i = 0 ; i < 3 ; i++ ) { - xyz[i] = (picoVec3_t*) PicoGetSurfaceXYZ( surface, face->index[ i ] ); - normal[i] = (picoVec3_t*) PicoGetSurfaceNormal( surface, face->index[ i ] ); - st[i] = (picoVec2_t*) PicoGetSurfaceST( surface, 0, face->index[ i + 3 ] ); + picoSurface_t* surface = PicoModelFindOrAddSurface( model, subMtl->shader ); + int j; + /* we pull the data from the vertex, color and texcoord arrays using the face index data */ + for ( j = 0 ; j < 3 ; j++ ) + { + picoIndex_t index = (picoIndex_t)( ( ( i - faces ) * 3 ) + j ); + picoIndex_t size = (picoIndex_t)aseUniqueIndices_size( &indices ); + picoIndex_t unique = aseUniqueIndices_insertUniqueVertex( &indices, index ); + + picoIndex_t numVertexes = PicoGetSurfaceNumVertexes( surface ); + picoIndex_t numIndexes = PicoGetSurfaceNumIndexes( surface ); + + aseUniqueIndices_pushBack( &remap, numIndexes ); - if ( face->index [ i + 6] >= 0 ) { - color[i] = (picoColor_t*)PicoGetSurfaceColor( surface, 0, face->index[ i + 6 ] ); + PicoSetSurfaceIndex( surface, numIndexes, remap.data[unique] ); + + if ( unique == size ) { + PicoSetSurfaceXYZ( surface, numVertexes, vertices[( *i ).indices[j]].xyz ); + PicoSetSurfaceNormal( surface, numVertexes, vertices[( *i ).indices[j]].normal ); + PicoSetSurfaceST( surface, 0, numVertexes, texcoords[( *i ).indices[j + 3]].texcoord ); + + if ( ( *i ).indices[j + 6] >= 0 ) { + PicoSetSurfaceColor( surface, 0, numVertexes, colors[( *i ).indices[j + 6]].color ); + } + else + { + PicoSetSurfaceColor( surface, 0, numVertexes, white ); + } + + PicoSetSurfaceSmoothingGroup( surface, numVertexes, ( vertices[( *i ).indices[j]].id * ( 1 << 16 ) ) + ( *i ).smoothingGroup ); + } } - else + } + } + + aseUniqueIndices_clear( &indices ); + aseUniqueIndices_clear( &remap ); +} + +#endif + +static void _ase_submit_triangles( picoModel_t* model, aseMaterial_t* materials, aseVertex_t* vertices, aseTexCoord_t* texcoords, aseColor_t* colors, aseFace_t* faces, int numFaces ){ + aseFacesIter_t i = faces, end = faces + numFaces; + for (; i != end; ++i ) + { + /* look up the shader for the material/submaterial pair */ + aseSubMaterial_t* subMtl = _ase_get_submaterial_or_default( materials, ( *i ).materialId, ( *i ).subMaterialId ); + if ( subMtl == NULL ) { + return; + } + + { + picoVec3_t* xyz[3]; + picoVec3_t* normal[3]; + picoVec2_t* st[3]; + picoColor_t* color[3]; + picoIndex_t smooth[3]; + int j; + /* we pull the data from the vertex, color and texcoord arrays using the face index data */ + for ( j = 0 ; j < 3 ; j++ ) { - color[i] = &white; + xyz[j] = &vertices[( *i ).indices[j]].xyz; + normal[j] = &vertices[( *i ).indices[j]].normal; + st[j] = &texcoords[( *i ).indices[j + 3]].texcoord; + + if ( colors != NULL && ( *i ).indices[j + 6] >= 0 ) { + color[j] = &colors[( *i ).indices[j + 6]].color; + } + else + { + color[j] = &white; + } + + smooth[j] = ( vertices[( *i ).indices[j]].id * ( 1 << 16 ) ) + ( *i ).smoothingGroup; /* don't merge vertices */ + } + /* submit the triangle to the model */ + PicoAddTriangleToModel( model, xyz, normal, 1, st, 1, color, subMtl->shader, smooth ); } + } +} - /* submit the triangle to the model */ - PicoAddTriangleToModel( model, xyz, normal, 1, st, 1, color, subMtl->shader ); - - /* advance to the next face */ - face = face->next; +static void shadername_convert( char* shaderName ){ + /* unix-style path separators */ + char* s = shaderName; + for (; *s != '\0'; ++s ) + { + if ( *s == '\\' ) { + *s = '/'; + } } } + /* _ase_load: * loads a 3dsmax ase model file. */ static picoModel_t *_ase_load( PM_PARAMS_LOAD ){ picoModel_t *model; - picoSurface_t *surface = NULL; picoParser_t *p; char lastNodeName[ 1024 ]; + aseVertex_t* vertices = NULL; + aseTexCoord_t* texcoords = NULL; + aseColor_t* colors = NULL; aseFace_t* faces = NULL; - aseFace_t* facesTail = NULL; + int numVertices = 0; + int numFaces = 0; + int numTextureVertices = 0; + int numTextureVertexFaces = 0; + int numColorVertices = 0; + int numColorVertexFaces = 0; + int vertexId = 0; + aseMaterial_t* materials = NULL; #ifdef DEBUG_PM_ASE @@ -357,8 +510,8 @@ static picoModel_t *_ase_load( PM_PARAMS_LOAD ){ #define _ase_error_return( m ) \ { \ _pico_printf( PICO_ERROR,"%s in ASE, line %d.",m,p->curLine ); \ - _pico_free_parser( p ); \ - PicoFreeModel( model ); \ + _pico_free_parser( p ); \ + PicoFreeModel( model ); \ return NULL; \ } /* create a new pico parser */ @@ -413,15 +566,49 @@ static picoModel_t *_ase_load( PM_PARAMS_LOAD ){ /* model mesh (originally contained within geomobject) */ else if ( !_pico_stricmp( p->token,"*mesh" ) ) { /* finish existing surface */ - //_ase_make_surface( model, &surface ); - _ase_submit_triangles( surface, model,materials,faces ); - _ase_free_faces( &faces,&facesTail ); + _ase_submit_triangles( model, materials, vertices, texcoords, colors, faces, numFaces ); + _pico_free( faces ); + _pico_free( vertices ); + _pico_free( texcoords ); + _pico_free( colors ); + } + else if ( !_pico_stricmp( p->token,"*mesh_numvertex" ) ) { + if ( !_pico_parse_int( p, &numVertices ) ) { + _ase_error_return( "Missing MESH_NUMVERTEX value" ); + } + + vertices = _pico_calloc( numVertices, sizeof( aseVertex_t ) ); + } + else if ( !_pico_stricmp( p->token,"*mesh_numfaces" ) ) { + if ( !_pico_parse_int( p, &numFaces ) ) { + _ase_error_return( "Missing MESH_NUMFACES value" ); + } + + faces = _pico_calloc( numFaces, sizeof( aseFace_t ) ); + } + else if ( !_pico_stricmp( p->token,"*mesh_numtvertex" ) ) { + if ( !_pico_parse_int( p, &numTextureVertices ) ) { + _ase_error_return( "Missing MESH_NUMTVERTEX value" ); + } + + texcoords = _pico_calloc( numTextureVertices, sizeof( aseTexCoord_t ) ); + } + else if ( !_pico_stricmp( p->token,"*mesh_numtvfaces" ) ) { + if ( !_pico_parse_int( p, &numTextureVertexFaces ) ) { + _ase_error_return( "Missing MESH_NUMTVFACES value" ); + } + } + else if ( !_pico_stricmp( p->token,"*mesh_numcvertex" ) ) { + if ( !_pico_parse_int( p, &numColorVertices ) ) { + _ase_error_return( "Missing MESH_NUMCVERTEX value" ); + } - /* allocate new pico surface */ - surface = PicoNewSurface( NULL ); - if ( surface == NULL ) { - PicoFreeModel( model ); - return NULL; + colors = _pico_calloc( numColorVertices, sizeof( aseColor_t ) ); + memset( colors, 255, numColorVertices * sizeof( aseColor_t ) ); /* ydnar: force colors to white initially */ + } + else if ( !_pico_stricmp( p->token,"*mesh_numcvfaces" ) ) { + if ( !_pico_parse_int( p, &numColorVertexFaces ) ) { + _ase_error_return( "Missing MESH_NUMCVFACES value" ); } } /* mesh material reference. this usually comes at the end of */ @@ -430,77 +617,63 @@ static picoModel_t *_ase_load( PM_PARAMS_LOAD ){ /* the material reference id (shader index) now. */ else if ( !_pico_stricmp( p->token,"*material_ref" ) ) { int mtlId; - aseFace_t* face; - - /* we must have a valid surface */ - if ( surface == NULL ) { - _ase_error_return( "Missing mesh for material reference" ); - } /* get the material ref (0..n) */ if ( !_pico_parse_int( p,&mtlId ) ) { _ase_error_return( "Missing material reference ID" ); } - /* fix up all of the aseFaceList in the surface to point to the parent material */ - /* we've already saved off their subMtl */ - face = faces; - while ( face != NULL ) { - face->mtlId = mtlId; - face = face->next; + int i = 0; + /* fix up all of the aseFaceList in the surface to point to the parent material */ + /* we've already saved off their subMtl */ + for (; i < numFaces; ++i ) + { + faces[i].materialId = mtlId; + } } } /* model mesh vertex */ else if ( !_pico_stricmp( p->token,"*mesh_vertex" ) ) { - picoVec3_t v; int index; - /* we must have a valid surface */ - if ( surface == NULL ) { - continue; + if ( numVertices == 0 ) { + _ase_error_return( "Vertex parse error" ); } /* get vertex data (orig: index +y -x +z) */ if ( !_pico_parse_int( p,&index ) ) { _ase_error_return( "Vertex parse error" ); } - if ( !_pico_parse_vec( p,v ) ) { + if ( !_pico_parse_vec( p,vertices[index].xyz ) ) { _ase_error_return( "Vertex parse error" ); } - /* set vertex */ - PicoSetSurfaceXYZ( surface,index,v ); + vertices[index].id = vertexId++; } /* model mesh vertex normal */ else if ( !_pico_stricmp( p->token,"*mesh_vertexnormal" ) ) { - picoVec3_t v; int index; - /* we must have a valid surface */ - if ( surface == NULL ) { - continue; + if ( numVertices == 0 ) { + _ase_error_return( "Vertex parse error" ); } /* get vertex data (orig: index +y -x +z) */ if ( !_pico_parse_int( p,&index ) ) { _ase_error_return( "Vertex parse error" ); } - if ( !_pico_parse_vec( p,v ) ) { + if ( !_pico_parse_vec( p,vertices[index].normal ) ) { _ase_error_return( "Vertex parse error" ); } - - /* set vertex */ - PicoSetSurfaceNormal( surface,index,v ); } /* model mesh face */ else if ( !_pico_stricmp( p->token,"*mesh_face" ) ) { picoIndex_t indexes[3]; int index; - /* we must have a valid surface */ - if ( surface == NULL ) { - continue; + if ( numFaces == 0 ) { + _ase_error_return( "Face parse error" ); } /* get face index */ @@ -526,75 +699,58 @@ static picoModel_t *_ase_load( PM_PARAMS_LOAD ){ _ase_error_return( "Face parse error" ); } - /* set face indexes (note interleaved offset!) */ - PicoSetSurfaceIndex( surface, ( index * 9 + 0 ), indexes[2] ); - PicoSetSurfaceIndex( surface, ( index * 9 + 1 ), indexes[1] ); - PicoSetSurfaceIndex( surface, ( index * 9 + 2 ), indexes[0] ); - /* parse to the subMaterial ID */ while ( 1 ) { - _pico_parse( p,0 ); - if ( !_pico_stricmp( p->token,"*MESH_MTLID" ) ) { - aseFace_t* newFace; - int subMtlId; - - _pico_parse_int( p, &subMtlId ); - newFace = _pico_calloc( 1, sizeof( aseFace_t ) ); - - /* we fix up the mtlId later when we parse the material_ref */ - newFace->mtlId = 0; - newFace->subMtlId = subMtlId; - newFace->index[0] = indexes[2]; - newFace->index[1] = indexes[1]; - newFace->index[2] = indexes[0]; - - _ase_add_face( &faces,&facesTail,newFace ); + if ( !_pico_parse( p,0 ) ) { /* EOL */ break; } + if ( !_pico_stricmp( p->token,"*MESH_SMOOTHING" ) ) { + _pico_parse_int( p, &faces[index].smoothingGroup ); + } + if ( !_pico_stricmp( p->token,"*MESH_MTLID" ) ) { + _pico_parse_int( p, &faces[index].subMaterialId ); + } } + faces[index].materialId = 0; + faces[index].indices[0] = indexes[2]; + faces[index].indices[1] = indexes[1]; + faces[index].indices[2] = indexes[0]; } /* model texture vertex */ else if ( !_pico_stricmp( p->token,"*mesh_tvert" ) ) { - picoVec2_t uv; int index; - /* we must have a valid surface */ - if ( surface == NULL ) { - continue; + if ( numVertices == 0 ) { + _ase_error_return( "Texture Vertex parse error" ); } /* get uv vertex index */ - if ( !_pico_parse_int( p,&index ) ) { - _ase_error_return( "UV vertex parse error" ); + if ( !_pico_parse_int( p,&index ) || index >= numTextureVertices ) { + _ase_error_return( "Texture vertex parse error" ); } /* get uv vertex s */ - if ( !_pico_parse_float( p,&uv[0] ) ) { - _ase_error_return( "UV vertex parse error" ); + if ( !_pico_parse_float( p,&texcoords[index].texcoord[0] ) ) { + _ase_error_return( "Texture vertex parse error" ); } /* get uv vertex t */ - if ( !_pico_parse_float( p,&uv[1] ) ) { - _ase_error_return( "UV vertex parse error" ); + if ( !_pico_parse_float( p,&texcoords[index].texcoord[1] ) ) { + _ase_error_return( "Texture vertex parse error" ); } /* ydnar: invert t */ - uv[ 1 ] = 1.0f - uv[ 1 ]; - - /* set texture vertex */ - PicoSetSurfaceST( surface,0,index,uv ); + texcoords[index].texcoord[ 1 ] = 1.0f - texcoords[index].texcoord[ 1 ]; } /* ydnar: model mesh texture face */ else if ( !_pico_stricmp( p->token, "*mesh_tface" ) ) { picoIndex_t indexes[3]; int index; - aseFace_t* face; - /* we must have a valid surface */ - if ( surface == NULL ) { - continue; + if ( numFaces == 0 ) { + _ase_error_return( "Texture face parse error" ); } /* get face index */ @@ -617,65 +773,52 @@ static picoModel_t *_ase_load( PM_PARAMS_LOAD ){ _ase_error_return( "Texture face parse error" ); } - /* set face indexes (note interleaved offset!) */ - PicoSetSurfaceIndex( surface, ( index * 9 + 3 ), indexes[2] ); - PicoSetSurfaceIndex( surface, ( index * 9 + 4 ), indexes[1] ); - PicoSetSurfaceIndex( surface, ( index * 9 + 5 ), indexes[0] ); - - face = _ase_get_face_for_index( faces,index ); - face->index[3] = indexes[2]; - face->index[4] = indexes[1]; - face->index[5] = indexes[0]; + faces[index].indices[3] = indexes[2]; + faces[index].indices[4] = indexes[1]; + faces[index].indices[5] = indexes[0]; } /* model color vertex */ else if ( !_pico_stricmp( p->token,"*mesh_vertcol" ) ) { - picoColor_t color; int index; float colorInput; - /* we must have a valid surface */ - if ( surface == NULL ) { - continue; + if ( numVertices == 0 ) { + _ase_error_return( "Color Vertex parse error" ); } /* get color vertex index */ if ( !_pico_parse_int( p,&index ) ) { - _ase_error_return( "UV vertex parse error" ); + _ase_error_return( "Color vertex parse error" ); } /* get R component */ if ( !_pico_parse_float( p,&colorInput ) ) { - _ase_error_return( "color vertex parse error" ); + _ase_error_return( "Color vertex parse error" ); } - color[0] = (picoByte_t)( colorInput * 255 ); + colors[index].color[0] = (picoByte_t)( colorInput * 255 ); /* get G component */ if ( !_pico_parse_float( p,&colorInput ) ) { - _ase_error_return( "color vertex parse error" ); + _ase_error_return( "Color vertex parse error" ); } - color[1] = (picoByte_t)( colorInput * 255 ); + colors[index].color[1] = (picoByte_t)( colorInput * 255 ); /* get B component */ if ( !_pico_parse_float( p,&colorInput ) ) { - _ase_error_return( "color vertex parse error" ); + _ase_error_return( "Color vertex parse error" ); } - color[2] = (picoByte_t)( colorInput * 255 ); + colors[index].color[2] = (picoByte_t)( colorInput * 255 ); /* leave alpha alone since we don't get any data from the ASE format */ - color[3] = 255; - - /* set texture vertex */ - PicoSetSurfaceColor( surface,0,index,color ); + colors[index].color[3] = 255; } /* model color face */ else if ( !_pico_stricmp( p->token,"*mesh_cface" ) ) { picoIndex_t indexes[3]; int index; - aseFace_t* face; - /* we must have a valid surface */ - if ( surface == NULL ) { - continue; + if ( numFaces == 0 ) { + _ase_error_return( "Face parse error" ); } /* get face index */ @@ -701,20 +844,14 @@ static picoModel_t *_ase_load( PM_PARAMS_LOAD ){ _ase_error_return( "Face parse error" ); } - /* set face indexes (note interleaved offset!) */ - PicoSetSurfaceIndex( surface, ( index * 9 + 6 ), indexes[2] ); - PicoSetSurfaceIndex( surface, ( index * 9 + 7 ), indexes[1] ); - PicoSetSurfaceIndex( surface, ( index * 9 + 8 ), indexes[0] ); - - face = _ase_get_face_for_index( faces,index ); - face->index[6] = indexes[2]; - face->index[7] = indexes[1]; - face->index[8] = indexes[0]; + faces[index].indices[6] = indexes[2]; + faces[index].indices[7] = indexes[1]; + faces[index].indices[8] = indexes[0]; } /* model material */ else if ( !_pico_stricmp( p->token, "*material" ) ) { aseSubMaterial_t* subMaterial = NULL; - picoShader_t *shader = NULL; + picoShader_t *shader; int level = 1, index; char materialName[ 1024 ]; float transValue = 0.0f, shineValue = 1.0f; @@ -755,6 +892,8 @@ static picoModel_t *_ase_load( PM_PARAMS_LOAD ){ if ( level == subMaterialLevel ) { /* set material name */ + _pico_first_token( materialName ); + shadername_convert( materialName ); PicoSetShaderName( shader, materialName ); /* set shader's transparency */ @@ -944,6 +1083,7 @@ static picoModel_t *_ase_load( PM_PARAMS_LOAD ){ } /* set material name */ + shadername_convert( materialName ); PicoSetShaderName( shader,materialName ); /* set shader's transparency */ @@ -967,6 +1107,34 @@ static picoModel_t *_ase_load( PM_PARAMS_LOAD ){ /* set material map name */ PicoSetShaderMapName( shader, mapname ); + /* extract shadername from bitmap path */ + if ( mapname != NULL ) { + char* p = mapname; + + /* convert to shader-name format */ + shadername_convert( mapname ); + { + /* remove extension */ + char* last_period = strrchr( p, '.' ); + if ( last_period != NULL ) { + *last_period = '\0'; + } + } + + /* find shader path */ + for (; *p != '\0'; ++p ) + { + if ( _pico_strnicmp( p, "models/", 7 ) == 0 || _pico_strnicmp( p, "textures/", 9 ) == 0 ) { + break; + } + } + + if ( *p != '\0' ) { + /* set material name */ + PicoSetShaderName( shader,p ); + } + } + /* this is just a material with 1 submaterial */ subMaterial = _ase_add_submaterial( &materials, index, 0, shader ); } @@ -982,9 +1150,11 @@ static picoModel_t *_ase_load( PM_PARAMS_LOAD ){ } /* ydnar: finish existing surface */ -// _ase_make_surface( model, &surface ); - _ase_submit_triangles( surface, model,materials,faces ); - _ase_free_faces( &faces,&facesTail ); + _ase_submit_triangles( model, materials, vertices, texcoords, colors, faces, numFaces ); + _pico_free( faces ); + _pico_free( vertices ); + _pico_free( texcoords ); + _pico_free( colors ); #ifdef DEBUG_PM_ASE _ase_print_materials( materials ); @@ -995,6 +1165,8 @@ static picoModel_t *_ase_load( PM_PARAMS_LOAD ){ _ase_free_materials( &materials ); + _pico_free_parser( p ); + /* return allocated pico model */ return model; } diff --git a/q3map2/libs/picomodel/pm_fm.c b/q3map2/libs/picomodel/pm_fm.c index a4251337..d50ee038 100644 --- a/q3map2/libs/picomodel/pm_fm.c +++ b/q3map2/libs/picomodel/pm_fm.c @@ -350,7 +350,7 @@ static picoModel_t *_fm_load( PM_PARAMS_LOAD ){ texCoord->t = _pico_little_short( texCoord[i].t ); } // set Skin Name - strncpy( skinname, (char *) fm.fm_skin, FM_SKINPATHSIZE ); + strncpy( skinname, fm.fm_skin->path, FM_SKINPATHSIZE ); #ifdef FM_VERBOSE_DBG // Print out md2 values @@ -425,7 +425,7 @@ static picoModel_t *_fm_load( PM_PARAMS_LOAD ){ #endif continue; } - else if ( ( p_index_LUT[triangle->index_xyz[j]].next == NULL ) ) { // Not equal to Main entry, and no LL entry + else if ( p_index_LUT[triangle->index_xyz[j]].next == NULL ) { // Not equal to Main entry, and no LL entry // Add first entry of LL from Main p_index_LUT2 = (index_LUT_t *)_pico_alloc( sizeof( index_LUT_t ) ); if ( p_index_LUT2 == NULL ) { diff --git a/q3map2/libs/picomodel/pm_lwo.c b/q3map2/libs/picomodel/pm_lwo.c index f1d82dd9..dfb64c6e 100644 --- a/q3map2/libs/picomodel/pm_lwo.c +++ b/q3map2/libs/picomodel/pm_lwo.c @@ -103,7 +103,7 @@ static picoModel_t *_lwo_load( PM_PARAMS_LOAD ){ lwPolygon *pol; lwPolVert *v; lwVMapPt *vm; - char name[ 64 ]; + char name[ 256 ]; int i, j, k, numverts; picoModel_t *picoModel; @@ -235,6 +235,7 @@ static picoModel_t *_lwo_load( PM_PARAMS_LOAD ){ /* detox and set shader name */ strncpy( name, surface->name, sizeof( name ) ); + _pico_first_token( name ); _pico_setfext( name, "" ); _pico_unixify( name ); PicoSetShaderName( picoShader, name ); @@ -282,9 +283,22 @@ static picoModel_t *_lwo_load( PM_PARAMS_LOAD ){ xyz[ 1 ] = pt->pos[ 2 ]; xyz[ 2 ] = pt->pos[ 1 ]; +///* doom3 lwo data doesn't seem to have smoothing-angle information */ +//#if 0 + if ( surface->smooth <= 0 ) { + /* use face normals */ normal[ 0 ] = v->norm[ 0 ]; normal[ 1 ] = v->norm[ 2 ]; normal[ 2 ] = v->norm[ 1 ]; + } + else +//#endif + { + /* smooth normals later */ + normal[ 0 ] = 0; + normal[ 1 ] = 0; + normal[ 2 ] = 0; + } st[ 0 ] = xyz[ defaultSTAxis[ 0 ] ] * defaultXYZtoSTScale[ 0 ]; st[ 1 ] = xyz[ defaultSTAxis[ 1 ] ] * defaultXYZtoSTScale[ 1 ]; diff --git a/q3map2/libs/picomodel/pm_md2.c b/q3map2/libs/picomodel/pm_md2.c index c1011cfb..c49481bc 100644 --- a/q3map2/libs/picomodel/pm_md2.c +++ b/q3map2/libs/picomodel/pm_md2.c @@ -519,7 +519,7 @@ static picoModel_t *_md2_load( PM_PARAMS_LOAD ){ continue; } - else if ( ( p_index_LUT[p_md2Triangle->index_xyz[j]].next == NULL ) ) { // Not equal to Main entry, and no LL entry + else if ( p_index_LUT[p_md2Triangle->index_xyz[j]].next == NULL ) { // Not equal to Main entry, and no LL entry // Add first entry of LL from Main p_index_LUT2 = (index_LUT_t *)_pico_alloc( sizeof( index_LUT_t ) ); if ( p_index_LUT2 == NULL ) { diff --git a/q3map2/libs/picomodel/pm_obj.c b/q3map2/libs/picomodel/pm_obj.c index 2f6e93c4..05221fe5 100644 --- a/q3map2/libs/picomodel/pm_obj.c +++ b/q3map2/libs/picomodel/pm_obj.c @@ -241,7 +241,7 @@ static int _obj_mtl_load( picoModel_t *model ){ return 0; \ } /* alloc copy of model file name */ - fileName = _pico_clone_alloc( model->fileName,-1 ); + fileName = _pico_clone_alloc( model->fileName ); if ( fileName == NULL ) { return 0; } @@ -863,12 +863,15 @@ static picoModel_t *_obj_load( PM_PARAMS_LOAD ){ else if ( !_pico_stricmp( p->token, "usemtl" ) ) { char *materialName; materialName = _pico_parse( p, 0 ); - if( materialName || strlen( materialName ) ) { + if( materialName && strlen( materialName ) ) { picoShader_t *shader; shader = PicoFindShader( model, materialName, 0 ); if( !shader ) { shader = PicoNewShader( model ); - PicoSetShaderName( shader, materialName ); + if( shader ) { + PicoSetShaderMapName( shader, materialName ); + PicoSetShaderName( shader, materialName ); + } } if( shader && curSurface ) { PicoSetSurfaceShader( curSurface, shader ); diff --git a/q3map2/libs/picomodel/pm_terrain.c b/q3map2/libs/picomodel/pm_terrain.c new file mode 100644 index 00000000..f5d16423 --- /dev/null +++ b/q3map2/libs/picomodel/pm_terrain.c @@ -0,0 +1,607 @@ +/* ----------------------------------------------------------------------------- + + PicoModel Library + + Copyright (c) 2003, Randy Reddig & seaw0lf + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list + of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + Neither the names of the copyright holders nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + ----------------------------------------------------------------------------- */ + + + +/* marker */ +#define PM_TERRAIN_C + + + +/* dependencies */ +#include "picointernal.h" + + + +typedef struct tga_s +{ + 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; +} +tga_t; + + + +/* + _terrain_load_tga_buffer() + loads a tga image into a newly allocated image buffer + fixme: replace/clean this function + */ + +void _terrain_load_tga_buffer( unsigned char *buffer, unsigned char **pic, int *width, int *height ) { + int row, column; + int columns, rows, numPixels; + unsigned char *pixbuf; + unsigned char *buf_p; + tga_t targa_header; + unsigned char *targa_rgba; + + + *pic = NULL; + + if ( buffer == NULL ) { + 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 = _pico_little_short( *(short*)buf_p ); + buf_p += 2; + targa_header.colormap_length = _pico_little_short( *(short*) buf_p ); + buf_p += 2; + targa_header.colormap_size = *buf_p++; + targa_header.x_origin = _pico_little_short( *(short*) buf_p ); + buf_p += 2; + targa_header.y_origin = _pico_little_short( *(short*) buf_p ); + buf_p += 2; + targa_header.width = _pico_little_short( *(short*) buf_p ); + buf_p += 2; + targa_header.height = _pico_little_short( *(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 ) { + _pico_printf( PICO_ERROR, "Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported\n" ); + pic = NULL; + return; + } + + if ( targa_header.colormap_type != 0 ) { + _pico_printf( PICO_ERROR, "Indexed color TGA images not supported\n" ); + return; + } + + if ( targa_header.pixel_size != 32 && targa_header.pixel_size != 24 && targa_header.image_type != 3 ) { + _pico_printf( PICO_ERROR, "Only 32 or 24 bit TGA images supported (not indexed color)\n" ); + pic = NULL; + return; + } + + columns = targa_header.width; + rows = targa_header.height; + numPixels = columns * rows; + + if ( width ) { + *width = columns; + } + if ( height ) { + *height = rows; + } + + targa_rgba = _pico_alloc( numPixels * 4 ); + *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: + break; + } + } + } + } + + /* rle encoded pixels */ + else if ( targa_header.image_type == 10 ) { + 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: + //Error("LoadTGA: illegal pixel_size '%d' in file '%s'\n", targa_header.pixel_size, name ); + 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: + //Sysprintf("LoadTGA: illegal pixel_size '%d' in file '%s'\n", targa_header.pixel_size, name ); + 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:; + } + } + + /* fix vertically flipped image */ + if ( ( targa_header.attributes & ( 1 << 5 ) ) ) { + int flip; + for ( row = 0; row < .5f * rows; row++ ) + { + for ( column = 0; column < columns; column++ ) + { + flip = *( (int*)targa_rgba + row * columns + column ); + *( (int*)targa_rgba + row * columns + column ) = *( (int*)targa_rgba + ( ( rows - 1 ) - row ) * columns + column ); + *( (int*)targa_rgba + ( ( rows - 1 ) - row ) * columns + column ) = flip; + } + } + } +} + + + +/* + _terrain_canload() + validates a picoterrain file + */ + +static int _terrain_canload( PM_PARAMS_CANLOAD ) { + picoParser_t *p; + + + /* keep the friggin compiler happy */ + /**fileName = *fileName*/; + + /* create pico parser */ + p = _pico_new_parser( (picoByte_t*) buffer, bufSize ); + if ( p == NULL ) { + return PICO_PMV_ERROR_MEMORY; + } + + /* get first token */ + if ( _pico_parse_first( p ) == NULL ) { + return PICO_PMV_ERROR_IDENT; + } + + /* check first token */ + if ( _pico_stricmp( p->token, "picoterrain" ) ) { + _pico_free_parser( p ); + return PICO_PMV_ERROR_IDENT; + } + + /* free the pico parser object */ + _pico_free_parser( p ); + + /* file seems to be a valid picoterrain file */ + return PICO_PMV_OK; +} + + + +/* + _terrain_load() + loads a picoterrain file + */ + +static picoModel_t *_terrain_load( PM_PARAMS_LOAD ) { + int i, j, v, pw[ 5 ], r; + picoParser_t *p; + + char *shader, *heightmapFile, *colormapFile; + picoVec3_t scale, origin; + + unsigned char *imageBuffer; + int imageBufSize, w, h, cw, ch; + unsigned char *heightmap, *colormap, *heightPixel, *colorPixel; + + picoModel_t *picoModel; + picoSurface_t *picoSurface; + picoShader_t *picoShader; + picoVec3_t xyz, normal; + picoVec2_t st; + picoColor_t color; + + + /* keep the friggin compiler happy */ + /**fileName = *fileName*/; + + /* create pico parser */ + p = _pico_new_parser( (picoByte_t*) buffer, bufSize ); + if ( p == NULL ) { + return NULL; + } + + /* get first token */ + if ( _pico_parse_first( p ) == NULL ) { + return NULL; + } + + /* check first token */ + if ( _pico_stricmp( p->token, "picoterrain" ) ) { + _pico_printf( PICO_ERROR, "Invalid PicoTerrain model" ); + _pico_free_parser( p ); + return NULL; + } + + /* setup */ + shader = heightmapFile = colormapFile = NULL; + _pico_set_vec( scale, 512, 512, 32 ); + + /* parse ase model file */ + while ( 1 ) + { + /* get first token on line */ + if ( !_pico_parse_first( p ) ) { + break; + } + + /* skip empty lines */ + if ( !p->token || !p->token[ 0 ] ) { + continue; + } + + /* shader */ + if ( !_pico_stricmp( p->token, "shader" ) ) { + if ( _pico_parse( p, 0 ) && p->token[ 0 ] ) { + if ( shader != NULL ) { + _pico_free( shader ); + } + shader = _pico_clone_alloc( p->token ); + } + } + + /* heightmap */ + else if ( !_pico_stricmp( p->token, "heightmap" ) ) { + if ( _pico_parse( p, 0 ) && p->token[ 0 ] ) { + if ( heightmapFile != NULL ) { + _pico_free( heightmapFile ); + } + heightmapFile = _pico_clone_alloc( p->token ); + } + } + + /* colormap */ + else if ( !_pico_stricmp( p->token, "colormap" ) ) { + if ( _pico_parse( p, 0 ) && p->token[ 0 ] ) { + if ( colormapFile != NULL ) { + _pico_free( colormapFile ); + } + colormapFile = _pico_clone_alloc( p->token ); + } + } + + /* scale */ + else if ( !_pico_stricmp( p->token, "scale" ) ) { + _pico_parse_vec( p, scale ); + } + + /* skip unparsed rest of line and continue */ + _pico_parse_skip_rest( p ); + } + + /* ----------------------------------------------------------------- */ + + /* load heightmap */ + heightmap = imageBuffer = NULL; + _pico_load_file( heightmapFile, &imageBuffer, &imageBufSize ); + _terrain_load_tga_buffer( imageBuffer, &heightmap, &w, &h ); + _pico_free( heightmapFile ); + _pico_free_file( imageBuffer ); + + if ( heightmap == NULL || w < 2 || h < 2 ) { + _pico_printf( PICO_ERROR, "PicoTerrain model with invalid heightmap" ); + if ( shader != NULL ) { + _pico_free( shader ); + } + if ( colormapFile != NULL ) { + _pico_free( colormapFile ); + } + _pico_free_parser( p ); + return NULL; + } + + /* set origin (bottom lowest corner of terrain mesh) */ + _pico_set_vec( origin, ( w / -2 ) * scale[ 0 ], ( h / -2 ) * scale[ 1 ], -128 * scale[ 2 ] ); + + /* load colormap */ + colormap = imageBuffer = NULL; + _pico_load_file( colormapFile, &imageBuffer, &imageBufSize ); + _terrain_load_tga_buffer( imageBuffer, &colormap, &cw, &ch ); + _pico_free( colormapFile ); + _pico_free_file( imageBuffer ); + + if ( cw != w || ch != h ) { + _pico_printf( PICO_WARNING, "PicoTerrain colormap/heightmap size mismatch" ); + _pico_free( colormap ); + colormap = NULL; + } + + /* ----------------------------------------------------------------- */ + + /* create new pico model */ + picoModel = PicoNewModel(); + if ( picoModel == NULL ) { + _pico_printf( PICO_ERROR, "Unable to allocate a new model" ); + return NULL; + } + + /* do model setup */ + PicoSetModelFrameNum( picoModel, frameNum ); + PicoSetModelNumFrames( picoModel, 1 ); /* sea */ + PicoSetModelName( picoModel, fileName ); + PicoSetModelFileName( picoModel, fileName ); + + /* allocate new pico surface */ + picoSurface = PicoNewSurface( picoModel ); + if ( picoSurface == NULL ) { + _pico_printf( PICO_ERROR, "Unable to allocate a new model surface" ); + PicoFreeModel( picoModel ); /* sea */ + return NULL; + } + + /* terrain surfaces are triangle meshes */ + PicoSetSurfaceType( picoSurface, PICO_TRIANGLES ); + + /* set surface name */ + PicoSetSurfaceName( picoSurface, "picoterrain" ); + + /* create new pico shader */ + picoShader = PicoNewShader( picoModel ); + if ( picoShader == NULL ) { + _pico_printf( PICO_ERROR, "Unable to allocate a new model shader" ); + PicoFreeModel( picoModel ); + _pico_free( shader ); + return NULL; + } + + /* detox and set shader name */ + _pico_setfext( shader, "" ); + _pico_unixify( shader ); + PicoSetShaderName( picoShader, shader ); + _pico_free( shader ); + + /* associate current surface with newly created shader */ + PicoSetSurfaceShader( picoSurface, picoShader ); + + /* make bogus normal */ + _pico_set_vec( normal, 0.0f, 0.0f, 0.0f ); + + /* create mesh */ + for ( j = 0; j < h; j++ ) + { + for ( i = 0; i < w; i++ ) + { + /* get pointers */ + v = i + ( j * w ); + heightPixel = heightmap + v * 4; + colorPixel = colormap + ? colormap + v * 4 + : NULL; + + /* set xyz */ + _pico_set_vec( xyz, origin[ 0 ] + scale[ 0 ] * i, + origin[ 1 ] + scale[ 1 ] * j, + origin[ 2 ] + scale[ 2 ] * heightPixel[ 0 ] ); + PicoSetSurfaceXYZ( picoSurface, v, xyz ); + + /* set normal */ + PicoSetSurfaceNormal( picoSurface, v, normal ); + + /* set st */ + st[ 0 ] = (float) i; + st[ 1 ] = (float) j; + PicoSetSurfaceST( picoSurface, 0, v, st ); + + /* set color */ + if ( colorPixel != NULL ) { + _pico_set_color( color, colorPixel[ 0 ], colorPixel[ 1 ], colorPixel[ 2 ], colorPixel[ 3 ] ); + } + else{ + _pico_set_color( color, 255, 255, 255, 255 ); + } + PicoSetSurfaceColor( picoSurface, 0, v, color ); + + /* set triangles (zero alpha in heightmap suppresses this quad) */ + if ( i < ( w - 1 ) && j < ( h - 1 ) && heightPixel[ 3 ] >= 128 ) { + /* set indexes */ + pw[ 0 ] = i + ( j * w ); + pw[ 1 ] = i + ( ( j + 1 ) * w ); + pw[ 2 ] = i + 1 + ( ( j + 1 ) * w ); + pw[ 3 ] = i + 1 + ( j * w ); + pw[ 4 ] = i + ( j * w ); /* same as pw[ 0 ] */ + + /* set radix */ + r = ( i + j ) & 1; + + /* make first triangle */ + PicoSetSurfaceIndex( picoSurface, ( v * 6 + 0 ), (picoIndex_t) pw[ r + 0 ] ); + PicoSetSurfaceIndex( picoSurface, ( v * 6 + 1 ), (picoIndex_t) pw[ r + 1 ] ); + PicoSetSurfaceIndex( picoSurface, ( v * 6 + 2 ), (picoIndex_t) pw[ r + 2 ] ); + + /* make second triangle */ + PicoSetSurfaceIndex( picoSurface, ( v * 6 + 3 ), (picoIndex_t) pw[ r + 0 ] ); + PicoSetSurfaceIndex( picoSurface, ( v * 6 + 4 ), (picoIndex_t) pw[ r + 2 ] ); + PicoSetSurfaceIndex( picoSurface, ( v * 6 + 5 ), (picoIndex_t) pw[ r + 3 ] ); + } + } + } + + /* free stuff */ + _pico_free_parser( p ); + _pico_free( heightmap ); + _pico_free( colormap ); + + /* return the new pico model */ + return picoModel; +} + + + +/* pico file format module definition */ +const picoModule_t picoModuleTerrain = +{ + "1.3", /* module version string */ + "PicoTerrain", /* module display name */ + "Randy Reddig", /* author's name */ + "2003 Randy Reddig", /* module copyright */ + { + "picoterrain", NULL, NULL, NULL /* default extensions to use */ + }, + _terrain_canload, /* validation routine */ + _terrain_load, /* load routine */ + NULL, /* save validation routine */ + NULL /* save routine */ +}; diff --git a/q3map2/q3map2/bsp.c b/q3map2/q3map2/bsp.c index 384933d6..8646387a 100644 --- a/q3map2/q3map2/bsp.c +++ b/q3map2/q3map2/bsp.c @@ -347,12 +347,12 @@ void ProcessWorldModel( void ){ Sys_FPrintf( SYS_NOXML, "******* leaked *******\n" ); Sys_FPrintf( SYS_NOXML, "**********************\n" ); polyline = LeakFile( tree ); - leaknode = xmlNewNode( NULL, "message" ); - xmlNodeSetContent( leaknode, "MAP LEAKED\n" ); + leaknode = xmlNewNode( NULL, BAD_CAST "message" ); + xmlNodeSetContent( leaknode, BAD_CAST "MAP LEAKED\n" ); xmlAddChild( leaknode, polyline ); level[0] = (int) '0' + SYS_ERR; level[1] = 0; - xmlSetProp( leaknode, "level", (char*) &level ); + xmlSetProp( leaknode, BAD_CAST "level", BAD_CAST (char*) &level ); xml_SendNode( leaknode ); if ( leaktest ) { Sys_Printf( "--- MAP LEAKED, ABORTING LEAKTEST ---\n" ); @@ -778,7 +778,7 @@ int BSPMain( int argc, char **argv ){ else if ( !strcmp( argv[ i ], "-np" ) ) { npDegrees = atof( argv[ i + 1 ] ); if ( npDegrees < 0.0f ) { - shadeAngleDegrees = 0.0f; + npDegrees = 0.0f; } else if ( npDegrees > 0.0f ) { Sys_Printf( "Forcing nonplanar surfaces with a breaking angle of %f degrees\n", npDegrees ); diff --git a/q3map2/q3map2/bspfile_abstract.c b/q3map2/q3map2/bspfile_abstract.c index 3f464e2c..f7698a82 100644 --- a/q3map2/q3map2/bspfile_abstract.c +++ b/q3map2/q3map2/bspfile_abstract.c @@ -343,6 +343,9 @@ int CopyLump( bspHeader_t *header, int lump, void *dest, int size ){ void AddLump( FILE *file, bspHeader_t *header, int lumpNum, const void *data, int length ){ bspLump_t *lump; + char pad[3] = {'\0', '\0', '\0'}; + unsigned int lengthU = length; + unsigned int padLength = ((lengthU + 3) / 4) * 4 - lengthU; /* add lump to bsp file header */ @@ -351,7 +354,10 @@ void AddLump( FILE *file, bspHeader_t *header, int lumpNum, const void *data, in lump->length = LittleLong( length ); /* write lump to file */ - SafeWrite( file, data, ( length + 3 ) & ~3 ); + SafeWrite( file, data, length ); + if ( padLength != 0 ) { + SafeWrite( file, pad, padLength ); + } } diff --git a/q3map2/q3map2/bspfile_ibsp.c b/q3map2/q3map2/bspfile_ibsp.c index 792f73fe..93d23be0 100644 --- a/q3map2/q3map2/bspfile_ibsp.c +++ b/q3map2/q3map2/bspfile_ibsp.c @@ -1,6 +1,6 @@ /* ------------------------------------------------------------------------------- - Copyright (C) 1999-2007 id Software, Inc. and contributors. + Copyright (C) 1999-2007 id Software, Inc., 2016 Google Inc. and contributors For a list of contributors, see the accompanying CONTRIBUTORS file. This file is part of GtkRadiant. @@ -529,7 +529,6 @@ void LoadIBSPFile( const char *filename ){ void WriteIBSPFile( const char *filename ){ ibspHeader_t outheader, *header; FILE *file; - time_t t; char marker[ 1024 ]; int size; @@ -549,8 +548,7 @@ void WriteIBSPFile( const char *filename ){ SafeWrite( file, (bspHeader_t*) header, sizeof( *header ) ); /* overwritten later */ /* add marker lump */ - time( &t ); - sprintf( marker, "I LOVE MY Q3MAP2 %s on %s)", Q3MAP_VERSION, asctime( localtime( &t ) ) ); + sprintf( marker, "I LOVE MY Q3MAP2 %s)", Q3MAP_VERSION ); AddLump( file, (bspHeader_t*) header, 0, marker, strlen( marker ) + 1 ); /* add lumps */ diff --git a/q3map2/q3map2/leakfile.c b/q3map2/q3map2/leakfile.c index be3f157f..f25e0fe4 100644 --- a/q3map2/q3map2/leakfile.c +++ b/q3map2/q3map2/leakfile.c @@ -82,7 +82,7 @@ xmlNodePtr LeakFile( tree_t *tree ){ Error( "Couldn't open %s\n", filename ); } - xml_node = xmlNewNode( NULL, "polyline" ); + xml_node = xmlNewNode( NULL, BAD_CAST "polyline" ); count = 0; node = &tree->outside_node; diff --git a/q3map2/q3map2/light.c b/q3map2/q3map2/light.c index 2fec7dc8..785bb73f 100644 --- a/q3map2/q3map2/light.c +++ b/q3map2/q3map2/light.c @@ -973,6 +973,11 @@ int LightContributionToSample( trace_t *trace ){ /* return to sender */ return 1; + } + + /* unknown light type */ + else { + return -1; } /* ydnar: changed to a variable number */ @@ -1776,7 +1781,7 @@ void LightWorld( void ){ SetupEnvelopes( qfalse, fastbounce ); if ( numLights == 0 ) { Sys_Printf( "No diffuse light to calculate, ending radiosity.\n" ); - break; + return; } /* add to lightgrid */ @@ -1817,6 +1822,9 @@ void LightWorld( void ){ bounce--; b++; } + /* ydnar: store off lightmaps */ + StoreSurfaceLightmaps(); + } @@ -2305,9 +2313,6 @@ int LightMain( int argc, char **argv ){ /* light the world */ LightWorld(); - /* ydnar: store off lightmaps */ - StoreSurfaceLightmaps(); - /* write out the bsp */ UnparseEntities(); Sys_Printf( "Writing %s\n", source ); diff --git a/q3map2/q3map2/light_ydnar.c b/q3map2/q3map2/light_ydnar.c index 19fa4e80..2d0bfb95 100644 --- a/q3map2/q3map2/light_ydnar.c +++ b/q3map2/q3map2/light_ydnar.c @@ -1595,61 +1595,54 @@ void DirtyRawLightmap( int rawLightmapNum ){ static qboolean SubmapRawLuxel( rawLightmap_t *lm, int x, int y, float bx, float by, int *sampleCluster, vec3_t sampleOrigin, vec3_t sampleNormal ){ int i, *cluster, *cluster2; - float *origin, *origin2, *normal; //% , *normal2; - vec3_t originVecs[ 2 ]; //% , normalVecs[ 2 ]; - + float *origin, *origin2, *normal; + vec3_t originVecs[ 2 ]; /* calulate x vector */ if ( ( x < ( lm->sw - 1 ) && bx >= 0.0f ) || ( x == 0 && bx <= 0.0f ) ) { cluster = SUPER_CLUSTER( x, y ); origin = SUPER_ORIGIN( x, y ); - //% normal = SUPER_NORMAL( x, y ); cluster2 = SUPER_CLUSTER( x + 1, y ); origin2 = *cluster2 < 0 ? SUPER_ORIGIN( x, y ) : SUPER_ORIGIN( x + 1, y ); - //% normal2 = *cluster2 < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x + 1, y ); } else if ( ( x > 0 && bx <= 0.0f ) || ( x == ( lm->sw - 1 ) && bx >= 0.0f ) ) { cluster = SUPER_CLUSTER( x - 1, y ); origin = *cluster < 0 ? SUPER_ORIGIN( x, y ) : SUPER_ORIGIN( x - 1, y ); - //% normal = *cluster < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x - 1, y ); cluster2 = SUPER_CLUSTER( x, y ); origin2 = SUPER_ORIGIN( x, y ); - //% normal2 = SUPER_NORMAL( x, y ); } else{ Sys_FPrintf( SYS_WRN, "WARNING: Spurious lightmap S vector\n" ); + VectorClear( originVecs[0] ); + origin = originVecs[0]; + origin2 = originVecs[0]; } VectorSubtract( origin2, origin, originVecs[ 0 ] ); - //% VectorSubtract( normal2, normal, normalVecs[ 0 ] ); /* calulate y vector */ if ( ( y < ( lm->sh - 1 ) && bx >= 0.0f ) || ( y == 0 && bx <= 0.0f ) ) { cluster = SUPER_CLUSTER( x, y ); origin = SUPER_ORIGIN( x, y ); - //% normal = SUPER_NORMAL( x, y ); cluster2 = SUPER_CLUSTER( x, y + 1 ); origin2 = *cluster2 < 0 ? SUPER_ORIGIN( x, y ) : SUPER_ORIGIN( x, y + 1 ); - //% normal2 = *cluster2 < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x, y + 1 ); } else if ( ( y > 0 && bx <= 0.0f ) || ( y == ( lm->sh - 1 ) && bx >= 0.0f ) ) { cluster = SUPER_CLUSTER( x, y - 1 ); origin = *cluster < 0 ? SUPER_ORIGIN( x, y ) : SUPER_ORIGIN( x, y - 1 ); - //% normal = *cluster < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x, y - 1 ); cluster2 = SUPER_CLUSTER( x, y ); origin2 = SUPER_ORIGIN( x, y ); - //% normal2 = SUPER_NORMAL( x, y ); } else{ Sys_FPrintf( SYS_WRN, "WARNING: Spurious lightmap T vector\n" ); + VectorClear( originVecs[1] ); + origin = originVecs[1]; + origin2 = originVecs[1]; } VectorSubtract( origin2, origin, originVecs[ 1 ] ); - //% VectorSubtract( normal2, normal, normalVecs[ 1 ] ); /* calculate new origin */ - //% VectorMA( origin, bx, originVecs[ 0 ], sampleOrigin ); - //% VectorMA( sampleOrigin, by, originVecs[ 1 ], sampleOrigin ); for ( i = 0; i < 3; i++ ) sampleOrigin[ i ] = sampleOrigin[ i ] + ( bx * originVecs[ 0 ][ i ] ) + ( by * originVecs[ 1 ][ i ] ); @@ -1660,10 +1653,6 @@ static qboolean SubmapRawLuxel( rawLightmap_t *lm, int x, int y, float bx, float } /* calculate new normal */ - //% VectorMA( normal, bx, normalVecs[ 0 ], sampleNormal ); - //% VectorMA( sampleNormal, by, normalVecs[ 1 ], sampleNormal ); - //% if( VectorNormalize( sampleNormal, sampleNormal ) <= 0.0f ) - //% return qfalse; normal = SUPER_NORMAL( x, y ); VectorCopy( normal, sampleNormal ); diff --git a/q3map2/q3map2/shaders.c b/q3map2/q3map2/shaders.c index 07835443..01e488f0 100644 --- a/q3map2/q3map2/shaders.c +++ b/q3map2/q3map2/shaders.c @@ -657,9 +657,10 @@ static shaderInfo_t *AllocShaderInfo( void ){ void FinishShader( shaderInfo_t *si ){ int x, y; - float st[ 2 ], o[ 2 ], dist, bestDist; - vec4_t color, bestColor, delta; - + float dist, bestDist; + vec4_t color, bestColor, delta, average; + int image_width, image_height; + byte *current_pixel; /* don't double-dip */ if ( si->finished ) { @@ -680,26 +681,33 @@ void FinishShader( shaderInfo_t *si ){ VectorSet( si->vecs[ 1 ], 0, ( 1.0f / ( si->shaderHeight * 0.5f ) ), 0 ); } + current_pixel = si->shaderImage->pixels; + image_width = si->shaderImage->width; + image_height = si->shaderImage->height; + /* find pixel coordinates best matching the average color of the image */ bestDist = 99999999; - o[ 0 ] = 1.0f / si->shaderImage->width; - o[ 1 ] = 1.0f / si->shaderImage->height; - for ( y = 0, st[ 1 ] = 0.0f; y < si->shaderImage->height; y++, st[ 1 ] += o[ 1 ] ) + VectorCopy( si->averageColor, average ); + average[ 3 ] = si->averageColor[ 3 ]; + + for ( y = 0; y < image_height; y++ ) { - for ( x = 0, st[ 0 ] = 0.0f; x < si->shaderImage->width; x++, st[ 0 ] += o[ 0 ] ) + for ( x = 0; x < image_width; x++ ) { /* sample the shader image */ - RadSampleImage( si->shaderImage->pixels, si->shaderImage->width, si->shaderImage->height, st, color ); + VectorCopy( current_pixel, color ); + color[ 3 ] = current_pixel[ 3 ]; + current_pixel += 4; /* determine error squared */ - VectorSubtract( color, si->averageColor, delta ); - delta[ 3 ] = color[ 3 ] - si->averageColor[ 3 ]; + VectorSubtract( color, average, delta ); + delta[ 3 ] = color[ 3 ] - average[ 3 ]; dist = delta[ 0 ] * delta[ 0 ] + delta[ 1 ] * delta[ 1 ] + delta[ 2 ] * delta[ 2 ] + delta[ 3 ] * delta[ 3 ]; if ( dist < bestDist ) { VectorCopy( color, bestColor ); bestColor[ 3 ] = color[ 3 ]; - si->stFlat[ 0 ] = st[ 0 ]; - si->stFlat[ 1 ] = st[ 1 ]; + si->stFlat[ 0 ] = (float) x / image_width; + si->stFlat[ 1 ] = (float) y / image_height; } } } @@ -1227,6 +1235,9 @@ static void ParseShaderFile( const char *filename ){ if ( !Q_stricmp( token, "q3map_sunext" ) ) { ext = qtrue; } + else { + ext = qfalse; + } /* allocate sun */ sun = safe_malloc( sizeof( *sun ) ); diff --git a/q3map2/q3map2/tjunction.c b/q3map2/q3map2/tjunction.c index 50e5d7c4..a1b66462 100644 --- a/q3map2/q3map2/tjunction.c +++ b/q3map2/q3map2/tjunction.c @@ -400,6 +400,7 @@ void FixSurfaceJunctions( mapDrawSurface_t *ds ) { if ( numVerts == MAX_SURFACE_VERTS ) { Error( "MAX_SURFACE_VERTS" ); } + memset(&verts[ numVerts ], 0, sizeof(verts[ numVerts ])); /* take the exact intercept point */ VectorCopy( p->xyz, verts[ numVerts ].xyz ); diff --git a/q3map2/q3map2/visflow.c b/q3map2/q3map2/visflow.c index 20443ab5..cf559c37 100644 --- a/q3map2/q3map2/visflow.c +++ b/q3map2/q3map2/visflow.c @@ -116,16 +116,15 @@ fixedWinding_t *AllocStackWinding( pstack_t *stack ){ void FreeStackWinding( fixedWinding_t *w, pstack_t *stack ){ int i; - i = w - stack->windings; - - if ( i < 0 || i > 2 ) { - return; // not from local - - } - if ( stack->freewindings[i] ) { - Error( "FreeStackWinding: allready free" ); + for (i = 0; i < sizeof(stack->windings) / sizeof(stack->windings[0]); i++) { + if (w == &stack->windings[i]) { + if ( stack->freewindings[i] ) { + Error( "FreeStackWinding: already free" ); + } + stack->freewindings[i] = 1; + break; + } } - stack->freewindings[i] = 1; } /* diff --git a/testing/BUILD b/testing/BUILD new file mode 100644 index 00000000..f815bb5f --- /dev/null +++ b/testing/BUILD @@ -0,0 +1,90 @@ +# Description: +# End-to-end tests for DeepMind Lab. + +licenses(["restricted"]) # GPLv2 + +cc_test( + name = "recording_test", + srcs = ["recording_test.cc"], + data = [ + "//:assets_bots_pk3", + "//:assets_oa_pk3", + "//:assets_pk3", + "//:libdmlab_headless_hw.so", + "//:map_assets", + "//:non_pk3_assets", + "//:vm_pk3", + ], + deps = [ + ":env_observation", + ":env_observation_util", + "//:dmlablib", + "//deepmind/support:test_srcdir", + "//deepmind/util:files", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "env_observation", + srcs = ["env_observation.cc"], + hdrs = ["env_observation.h"], + deps = [ + "//:dmlablib", + "//third_party/rl_api:env_c_api", + ], +) + +cc_library( + name = "env_observation_util", + srcs = ["env_observation_util.cc"], + hdrs = ["env_observation_util.h"], + deps = [ + ":env_observation", + "@jpeg_archive//:jpeg", + ], +) + +cc_library( + name = "lua_unit_test_lib", + testonly = 1, + srcs = ["lua_unit_test.cc"], + data = [ + "//:assets_bots_pk3", + "//:assets_oa_pk3", + "//:assets_pk3", + "//:map_assets", + "//:non_pk3_assets", + "//:vm_pk3", + ], + visibility = ["//lua_tests:__pkg__"], + deps = [ + "//:dmlablib", + "//deepmind/support:test_srcdir", + "@com_google_googletest//:gtest", + ], +) + +cc_library( + name = "load_level_test_lib", + testonly = 1, + srcs = ["load_level_test.cc"], + data = [ + "//:assets_bots_pk3", + "//:assets_oa_pk3", + "//:assets_pk3", + "//:libdmlab_headless_hw.so", + "//:map_assets", + "//:non_pk3_assets", + "//:vm_pk3", + ], + visibility = ["//:__pkg__"], + deps = [ + ":env_observation", + "//:dmlablib", + "//deepmind/support:logging", + "//deepmind/support:test_srcdir", + "//deepmind/util:files", + "@com_google_googletest//:gtest", + ], +) diff --git a/testing/env_observation.cc b/testing/env_observation.cc new file mode 100644 index 00000000..73fe815b --- /dev/null +++ b/testing/env_observation.cc @@ -0,0 +1,53 @@ +// Copyright (C) 2016 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include "public/dmlab.h" +#include "testing/env_observation.h" + +namespace deepmind { +namespace lab { +namespace internal { +namespace { + +int PayloadLength(const EnvCApi_Observation& obs) { + return std::accumulate(obs.spec.shape, obs.spec.shape + obs.spec.dims, + obs.spec.dims ? 1 : 0, std::multiplies()); +} + +} // namespace + +template<> std::vector ReadPayload(const EnvCApi_Observation& obs) { + assert(obs.spec.type == EnvCApi_ObservationDoubles); + return std::vector(obs.payload.doubles, + obs.payload.doubles + PayloadLength(obs)); +} + +template<> std::vector ReadPayload( + const EnvCApi_Observation& obs) { + assert(obs.spec.type == EnvCApi_ObservationBytes); + return std::vector(obs.payload.bytes, + obs.payload.bytes + PayloadLength(obs)); +} + +} // namespace internal +} // namespace lab +} // namespace deepmind diff --git a/testing/env_observation.h b/testing/env_observation.h new file mode 100644 index 00000000..7dc6597b --- /dev/null +++ b/testing/env_observation.h @@ -0,0 +1,58 @@ +// Copyright (C) 2016 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef DML_TESTING_ENV_OBSERVATION_H_ +#define DML_TESTING_ENV_OBSERVATION_H_ + +#include + +#include "public/dmlab.h" +#include "third_party/rl_api/env_c_api.h" + +namespace deepmind { +namespace lab { +namespace internal { + +// ReadPayload returns a copy of the payload from the observation provided. +template std::vector ReadPayload(const EnvCApi_Observation& obs); +template<> std::vector ReadPayload(const EnvCApi_Observation& obs); +template<> std::vector ReadPayload( + const EnvCApi_Observation& obs); + +} // namespace internal + +// A wrapper for EnvCApi_Observation that copies and saves the observation. +template +class EnvObservation { + public: + explicit EnvObservation(const EnvCApi_Observation& obs) : + payload_(internal::ReadPayload(obs)), + shape_(obs.spec.shape, obs.spec.shape + obs.spec.dims) {} + + const std::vector& payload() const { return payload_; } + const std::vector& shape() const { return shape_; } + + private: + std::vector payload_; + std::vector shape_; +}; + +} // namespace lab +} // namespace deepmind + +#endif // DML_TESTING_ENV_OBSERVATION_H_ diff --git a/testing/env_observation_util.cc b/testing/env_observation_util.cc new file mode 100644 index 00000000..0a9b2ae6 --- /dev/null +++ b/testing/env_observation_util.cc @@ -0,0 +1,105 @@ +// Copyright (C) 2016 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +// jpeglib requires FILE to be defined prior to inclusion. +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "testing/env_observation_util.h" + +namespace deepmind { +namespace lab { +namespace { + +// Resamples an interlaced observation of byte format to grid_size x grid_size. +std::vector ResampleInterlaced( + const EnvObservation& obs, int grid_size) { + assert(obs.shape().size() == 3); + const int height = obs.shape()[0]; + const int width = obs.shape()[1]; + const int colours = obs.shape()[2]; + const int row_size = width * colours; + + std::vector buckets(grid_size * grid_size * colours, 0); + std::vector buckets_norm(buckets.size(), 0); + + int count = obs.payload().size(); + for (int loc = 0; loc < count; ++loc) { + int row = grid_size * (loc / row_size) / height; + int col = grid_size * (loc % row_size) / row_size; + int colour = loc % colours; + int bucket = (row * colours * grid_size) + (col * colours) + colour; + buckets[bucket] += obs.payload()[loc]; + buckets_norm[bucket] += 1; + } + + std::transform(buckets.begin(), buckets.end(), buckets_norm.begin(), + buckets.begin(), std::divides()); + return buckets; +} + +} // namespace + +double CompareInterlacedObservations(const EnvObservation& lhs, + const EnvObservation& rhs, + int grid_size) { + std::vector histogram_l(ResampleInterlaced(lhs, grid_size)); + std::vector histogram_r(ResampleInterlaced(rhs, grid_size)); + + return std::inner_product( + histogram_l.begin(), histogram_l.end(), histogram_r.begin(), 0.0, + std::plus(), [](double a, double b) { return std::abs(a - b); }); +} + +void SaveInterlacedRGBObservationToJpg( + const EnvObservation& obs, const std::string& path) { + unsigned char* bytes = const_cast(&obs.payload()[0]); + int width = obs.shape()[1]; + int height = obs.shape()[0]; + + const int kColourComponents = 3; + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_compress(&cinfo); + cinfo.image_width = width; + cinfo.image_height = height; + cinfo.input_components = kColourComponents; + cinfo.in_color_space = JCS_RGB; + std::FILE* outfile = std::fopen(path.c_str(), "wb"); + jpeg_stdio_dest(&cinfo, outfile); + jpeg_set_defaults(&cinfo); + jpeg_start_compress(&cinfo, TRUE); + while (cinfo.next_scanline < cinfo.image_height) { + JSAMPROW samp = &bytes[cinfo.next_scanline * kColourComponents * width]; + jpeg_write_scanlines(&cinfo, &samp, 1); + } + jpeg_finish_compress(&cinfo); + fclose(outfile); + jpeg_destroy_compress(&cinfo); +} + +} // namespace lab +} // namespace deepmind diff --git a/testing/env_observation_util.h b/testing/env_observation_util.h new file mode 100644 index 00000000..9399e8a2 --- /dev/null +++ b/testing/env_observation_util.h @@ -0,0 +1,32 @@ +#ifndef DML_TESTING_ENV_OBSERVATION_UTIL_H_ +#define DML_TESTING_ENV_OBSERVATION_UTIL_H_ + +#include + +#include "testing/env_observation.h" + +namespace deepmind { +namespace lab { + +// Compares two observations, returning a number representing their similarity. +// Observations shall be bytes of shape [h, w, c] (with c being the number of +// colour components). +// Both observations are resampled to shape [grid_size, grid_size, c] and the +// L1 norm of the component-wise difference between the observations is +// returned. +// If there are three colour components and you want each component to be on +// average less than 1% different, then your comparison number would be: +// grid_size * grid_size * 3 * (0.01 * 255) +double CompareInterlacedObservations(const EnvObservation& lhs, + const EnvObservation& rhs, + int grid_size); + +// Writes an image to the path specified with the contents of the observation. +// Observations shall be bytes of shape [h, w, 3]. +void SaveInterlacedRGBObservationToJpg( + const EnvObservation& obs, const std::string& path); + +} // namespace lab +} // namespace deepmind + +#endif // DML_TESTING_ENV_OBSERVATION_UTIL_H_ diff --git a/testing/load_level_test.cc b/testing/load_level_test.cc new file mode 100644 index 00000000..4e87e4fb --- /dev/null +++ b/testing/load_level_test.cc @@ -0,0 +1,104 @@ +#include + +#include +#include + +#include "deepmind/support/logging.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "deepmind/support/test_srcdir.h" +#include "deepmind/util/files.h" +#include "public/dmlab.h" +#include "testing/env_observation.h" + +namespace deepmind { +namespace lab { +namespace { + +using ::testing::ElementsAre; + +const char* test_script = nullptr; + +// Tests that the level loads and that we're able to advance a few steps in the +// environment. +TEST(LoadLevelTest, LoadLevelAndWait) { + std::string runfiles_path(TestSrcDir()); + + DeepMindLabLaunchParams params = {}; + params.runfiles_path = runfiles_path.c_str(); + params.renderer = DeepMindLabRenderer_Software; + + EnvCApi env_c_api; + void* context; + dmlab_connect(¶ms, &env_c_api, &context); + ASSERT_EQ(env_c_api.setting(context, "width", "64"), 0); + ASSERT_EQ(env_c_api.setting(context, "height", "36"), 0); + ASSERT_EQ(env_c_api.setting(context, "logToStdErr", "true"), 0); + ASSERT_EQ(env_c_api.setting(context, "fps", "15"), 0); + ASSERT_EQ(env_c_api.setting(context, "invocationMode", "testbed"), 0); + ASSERT_EQ(env_c_api.setting(context, "levelName", test_script), 0); + +// When running under TSAN, switch to the interpreted VM ("1"), which is +// instrumentable. +#ifndef __has_feature +#define __has_feature(x) 0 +#endif +#if __has_feature(thread_sanitizer) + ASSERT_EQ(env_c_api.setting(context, "vmMode", "interpreted"), 0); +#endif + + ASSERT_EQ(env_c_api.init(context), 0); + env_c_api.start(context, 0 /*episode*/, 1 /*seed*/); + + double reward; + env_c_api.advance(context, 10 /*steps*/, &reward); + + EnvCApi_Observation observation; + env_c_api.observation(context, 0, &observation); + EnvObservation test_observation(observation); + + EXPECT_THAT(test_observation.shape(), ElementsAre(36, 64, 3)); +} + +// Invokes seed_test.lua to check that the random seed is initialised in the +// start method of the level. +TEST(LoadLevelTest, RandomSeedTest) { + const std::string runfiles_path = TestSrcDir(); + + DeepMindLabLaunchParams params = {}; + params.runfiles_path = runfiles_path.c_str(); + params.renderer = DeepMindLabRenderer_Software; + + EnvCApi env_c_api; + void* context; + ASSERT_EQ(dmlab_connect(¶ms, &env_c_api, &context), 0); + ASSERT_EQ(env_c_api.setting(context, "levelName", "test_levels/seed_test"), + 0); + ASSERT_EQ(env_c_api.setting(context, "testLevelScript", test_script), 0); + +// When running under TSAN, switch to the interpreted VM ("1"), which is +// instrumentable. +#ifndef __has_feature +#define __has_feature(x) 0 +#endif +#if __has_feature(thread_sanitizer) + ASSERT_EQ(env_c_api.setting(context, "vmMode", "interpreted"), 0); +#endif + + if (env_c_api.init(context) != 0) { + FAIL() << "Inspect output for errors."; + } + env_c_api.release_context(context); +} + +} // namespace +} // namespace lab +} // namespace deepmind + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + QCHECK_EQ(argc, 2) << "Usage: load_level_test "; + deepmind::lab::test_script = argv[1]; + + return RUN_ALL_TESTS(); +} diff --git a/testing/lua_unit_test.cc b/testing/lua_unit_test.cc new file mode 100644 index 00000000..0ef45d69 --- /dev/null +++ b/testing/lua_unit_test.cc @@ -0,0 +1,57 @@ +// Copyright (C) 2016 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "gtest/gtest.h" +#include "deepmind/support/test_srcdir.h" +#include "public/dmlab.h" + +namespace deepmind { +namespace lab { +namespace { + +const char* test_script = nullptr; + +TEST(LuaUnitTest, RunsTest) { + const std::string runfiles_path = TestSrcDir(); + const std::string test_script_path = runfiles_path + "/" + test_script; + + DeepMindLabLaunchParams params = {}; + params.runfiles_path = runfiles_path.c_str(); + params.renderer = DeepMindLabRenderer_Software; + + EnvCApi env_c_api; + void* context; + ASSERT_EQ(dmlab_connect(¶ms, &env_c_api, &context), 0); + ASSERT_EQ(env_c_api.setting(context, "levelName", test_script_path.c_str()), + 0); + if (env_c_api.init(context) != 0) { + ADD_FAILURE() << env_c_api.error_message(context); + } + env_c_api.release_context(context); +} + +} // namespace +} // namespace lab +} // namespace deepmind + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + deepmind::lab::test_script = argv[1]; + + return RUN_ALL_TESTS(); +} diff --git a/testing/recording_test.cc b/testing/recording_test.cc new file mode 100644 index 00000000..4cecbf09 --- /dev/null +++ b/testing/recording_test.cc @@ -0,0 +1,327 @@ +// Copyright (C) 2016 Google Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include + +#include "gtest/gtest.h" +#include "deepmind/support/test_srcdir.h" +#include "deepmind/util/files.h" +#include "public/dmlab.h" +#include "testing/env_observation.h" +#include "testing/env_observation_util.h" + +namespace deepmind { +namespace lab { +namespace { + +using ::testing::UnitTest; + +// A map used to simplify passing settings to dmlab before initialization. +using SettingsMap = std::unordered_map; + +const int kFramesPerSecond = 30; + +class RecordingTest : public ::testing::Test { + std::string GetTestEnv(const char* env_name) { + char* env = std::getenv(env_name); + return env ? env : ""; + } + + protected: + const std::string test_name; + const std::string demo_path; + const std::string runfiles_path; + + RecordingTest() + : test_name(UnitTest::GetInstance()->current_test_info()->name()), + demo_path(util::GetTempDirectory() + "/dmlab"), + runfiles_path(TestSrcDir()) {} + + bool ApplyAllSettingsSuccessfully(const SettingsMap& settings, EnvCApi* api, + void* context) { + auto set_setting = [api, context](const SettingsMap::value_type& k_v) { + return api->setting(context, k_v.first.c_str(), k_v.second.c_str()) == 0; + }; + return std::all_of(settings.begin(), settings.end(), set_setting); + } + + int ConfigureVM(EnvCApi* api, void* context) { + // When running under TSAN, switch to the interpreted VM ("1"), which is + // instrumentable. +#ifndef __has_feature +# define __has_feature(x) 0 +#endif +#if __has_feature(thread_sanitizer) + return api->setting(context, "vmMode", "interpreted"); +#else + return 0; +#endif + } + + SettingsMap DefaultSettings() { + return { + {"width", "64"}, + {"height", "48"}, + {"demofiles", demo_path}, + {"logToStdErr", "true"}, + {"fps", std::to_string(kFramesPerSecond)}, + }; + } + + ~RecordingTest() { + util::RemoveDirectory(demo_path); + } + + // Advances through one second of steps and grabs an observation toward the + // end. + EnvObservation AdvanceOneSecond(EnvCApi* env_c_api, + void* context) { + // Depending on when the final demo snapshot was taken on the server, the + // menu may appear before the last demo advances through the final step, so + // take a comparison observation just short of the end. + const int kNumStepsAfterObservation = 3; + + EnvCApi_Observation observation; + double reward; + env_c_api->advance( + context, kFramesPerSecond - kNumStepsAfterObservation, &reward); + env_c_api->observation(context, 0, &observation); + EnvObservation recording_observation(observation); + env_c_api->advance(context, kNumStepsAfterObservation, &reward); + return recording_observation; + } +}; + +// Ensure dmlab saves recordings to /demos/ on the home path. +TEST_F(RecordingTest, SavesRecordingToDemoDirectory) { + DeepMindLabLaunchParams params = {}; + params.runfiles_path = runfiles_path.c_str(); + params.renderer = DeepMindLabRenderer_Software; + + EnvCApi env_c_api; + void* context; + dmlab_connect(¶ms, &env_c_api, &context); + + SettingsMap settings = DefaultSettings(); + settings["levelName"] = "test_levels/recording_test"; + settings["record"] = test_name; + ASSERT_TRUE(ApplyAllSettingsSuccessfully(settings, &env_c_api, context)); + ASSERT_EQ(0, ConfigureVM(&env_c_api, context)); + + env_c_api.init(context); + ASSERT_EQ(env_c_api.start(context, 0 /*episode*/, 1 /*seed*/), 0) + << "Unable to start recording"; + + // Record one second of frames. Repeat twice to advance through two maps. + double reward; + env_c_api.advance(context, kFramesPerSecond, &reward); + env_c_api.advance(context, kFramesPerSecond, &reward); + + env_c_api.release_context(context); + + // Check demo files exist. + struct stat path_stat; + std::string demo_1_path(demo_path + "/demos/" + test_name + "/00001.dm_71"); + EXPECT_EQ(stat(demo_1_path.c_str(), &path_stat), 0) << + "Demo 1 file does not exist: " << demo_1_path; + std::string demo_2_path(demo_path + "/demos/" + test_name + "/00002.dm_71"); + EXPECT_EQ(stat(demo_2_path.c_str(), &path_stat), 0) << + "Demo 2 file does not exist: " << demo_2_path; +} + +// Ensure dmlab plays a demo. +TEST_F(RecordingTest, PlaysDemoFromDemoDirectory) { + DeepMindLabLaunchParams params = {}; + params.runfiles_path = runfiles_path.c_str(); + params.renderer = DeepMindLabRenderer_Software; + + EnvCApi env_c_api; + void* context; + dmlab_connect(¶ms, &env_c_api, &context); + + SettingsMap settings = DefaultSettings(); + settings["levelName"] = "test_levels/recording_test"; + settings["record"] = test_name; + ASSERT_TRUE(ApplyAllSettingsSuccessfully(settings, &env_c_api, context)); + ASSERT_EQ(0, ConfigureVM(&env_c_api, context)); + + env_c_api.init(context); + env_c_api.start(context, 0 /*episode*/, 1 /*seed*/); + + // Record one second of frames. Repeat twice to advance through two maps. + EnvObservation recording_1_observation = + AdvanceOneSecond(&env_c_api, context); + EnvObservation recording_2_observation = + AdvanceOneSecond(&env_c_api, context); + + env_c_api.release_context(context); + + // Start the context for the demo + dmlab_connect(¶ms, &env_c_api, &context); + + settings = DefaultSettings(); + settings["levelName"] = "test_levels/recording_test"; + settings["demo"] = test_name; + ASSERT_TRUE(ApplyAllSettingsSuccessfully(settings, &env_c_api, context)); + ASSERT_EQ(0, ConfigureVM(&env_c_api, context)); + + env_c_api.init(context); + ASSERT_EQ(env_c_api.start(context, 0 /*episode*/, 1 /*seed*/), 0) + << "Unable to start demo"; + + // Play back one second of frames. + EnvObservation demo_1_observation = + AdvanceOneSecond(&env_c_api, context); + EnvObservation demo_2_observation = + AdvanceOneSecond(&env_c_api, context); + + EXPECT_LT(CompareInterlacedObservations( + recording_1_observation, demo_1_observation, 2), 1.0); + EXPECT_LT(CompareInterlacedObservations( + recording_2_observation, demo_2_observation, 2), 1.0); + + EXPECT_GT(CompareInterlacedObservations( + recording_1_observation, demo_2_observation, 2), 1.0); + env_c_api.release_context(context); + + if (char* undeclared_outputs = std::getenv("TEST_UNDECLARED_OUTPUTS_DIR")) { + std::string jpeg(std::string(undeclared_outputs) + "/" + test_name + "."); + SaveInterlacedRGBObservationToJpg(recording_1_observation, jpeg + "R1.jpg"); + SaveInterlacedRGBObservationToJpg(recording_2_observation, jpeg + "R2.jpg"); + SaveInterlacedRGBObservationToJpg(demo_1_observation, jpeg + "D1.jpg"); + SaveInterlacedRGBObservationToJpg(demo_2_observation, jpeg + "D2.jpg"); + } +} + +// Ensure dmlab records a video. +TEST_F(RecordingTest, RecordsMultipleVideos) { + DeepMindLabLaunchParams params = {}; + params.runfiles_path = runfiles_path.c_str(); + params.renderer = DeepMindLabRenderer_Software; + + EnvCApi env_c_api; + void* context; + dmlab_connect(¶ms, &env_c_api, &context); + + SettingsMap settings = DefaultSettings(); + settings["levelName"] = "test_levels/recording_test"; + settings["record"] = test_name; + ASSERT_TRUE(ApplyAllSettingsSuccessfully(settings, &env_c_api, context)); + ASSERT_EQ(0, ConfigureVM(&env_c_api, context)); + + env_c_api.init(context); + env_c_api.start(context, 0 /*episode*/, 1 /*seed*/); + + // Record one second of frames. Repeat twice to advance through two maps. + AdvanceOneSecond(&env_c_api, context); + AdvanceOneSecond(&env_c_api, context); + env_c_api.release_context(context); + + // Start the context for the demo + dmlab_connect(¶ms, &env_c_api, &context); + + settings = DefaultSettings(); + settings["levelName"] = "test_levels/recording_test"; + settings["demo"] = test_name; + settings["video"] = test_name; + ASSERT_TRUE(ApplyAllSettingsSuccessfully(settings, &env_c_api, context)); + ASSERT_EQ(0, ConfigureVM(&env_c_api, context)); + + env_c_api.init(context); + ASSERT_EQ(env_c_api.start(context, 0 /*episode*/, 1 /*seed*/), 0) + << "Unable to start demo"; + + // Play back one second of frames. + AdvanceOneSecond(&env_c_api, context); + AdvanceOneSecond(&env_c_api, context); + + env_c_api.release_context(context); + + // Check video files exist. + struct stat path_stat; + std::string video_1_path(demo_path + "/videos/" + test_name + "/00001.avi"); + EXPECT_EQ(stat(video_1_path.c_str(), &path_stat), 0) << + "Video 1 file does not exist: " << video_1_path; + std::string video_2_path(demo_path + "/videos/" + test_name + "/00002.avi"); + EXPECT_EQ(stat(video_2_path.c_str(), &path_stat), 0) << + "Video 2 file does not exist: " << video_2_path; +} + + +TEST_F(RecordingTest, TotalScorePreserved) { + DeepMindLabLaunchParams params = {}; + params.runfiles_path = runfiles_path.c_str(); + params.renderer = DeepMindLabRenderer_Software; + + EnvCApi env_c_api; + void* context; + dmlab_connect(¶ms, &env_c_api, &context); + + SettingsMap settings = DefaultSettings(); + settings["levelName"] = "test_levels/recording_test"; + settings["record"] = test_name; + ASSERT_TRUE(ApplyAllSettingsSuccessfully(settings, &env_c_api, context)); + ASSERT_EQ(0, ConfigureVM(&env_c_api, context)); + + env_c_api.init(context); + ASSERT_EQ(env_c_api.start(context, 0 /*episode*/, 1 /*seed*/), 0) + << "Unable to start recording"; + + // Record one second of frames. Repeat twice to advance through two maps. + double recording_reward_1; + const int move_forward[] = {0, 0, 0, 1, 0, 0, 0}; + env_c_api.act(context, move_forward, nullptr); + env_c_api.advance(context, kFramesPerSecond, &recording_reward_1); + double recording_reward_2; + env_c_api.advance(context, kFramesPerSecond, &recording_reward_2); + + env_c_api.release_context(context); + + // Start the context for the demo + dmlab_connect(¶ms, &env_c_api, &context); + + settings = DefaultSettings(); + settings["levelName"] = "test_levels/recording_test"; + settings["demo"] = test_name; + ASSERT_TRUE(ApplyAllSettingsSuccessfully(settings, &env_c_api, context)); + ASSERT_EQ(0, ConfigureVM(&env_c_api, context)); + + env_c_api.init(context); + ASSERT_EQ(env_c_api.start(context, 0 /*episode*/, 1 /*seed*/), 0) + << "Unable to start demo"; + + // Play back one second of frames for two maps. + double demo_reward_1; + env_c_api.advance(context, kFramesPerSecond, &demo_reward_1); + double demo_reward_2; + env_c_api.advance(context, kFramesPerSecond, &demo_reward_2); + env_c_api.release_context(context); + + EXPECT_EQ(recording_reward_1, 1); + EXPECT_EQ(recording_reward_2, 2); + EXPECT_EQ(demo_reward_1, recording_reward_1); + EXPECT_EQ(demo_reward_2, recording_reward_2); +} + +} // namespace +} // namespace lab +} // namespace deepmind diff --git a/third_party/GL/BUILD b/third_party/GL/BUILD new file mode 100644 index 00000000..f5eda632 --- /dev/null +++ b/third_party/GL/BUILD @@ -0,0 +1,16 @@ +# Description: +# Build rule for EGL dependencies. + +licenses(["notice"]) # Khronos + +cc_library( + name = "EGL_headers_sys", + srcs = [ + "include/EGL/egl.h", + "include/EGL/eglext.h", + "include/EGL/eglplatform.h", + ], + defines = ["MESA_EGL_NO_X11_HEADERS"], + includes = ["include"], + visibility = ["//visibility:public"], +) diff --git a/third_party/GL/include/EGL/egl.h b/third_party/GL/include/EGL/egl.h new file mode 100644 index 00000000..93a21873 --- /dev/null +++ b/third_party/GL/include/EGL/egl.h @@ -0,0 +1,303 @@ +#ifndef __egl_h_ +#define __egl_h_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** Copyright (c) 2013-2017 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. +*/ +/* +** This header is generated from the Khronos OpenGL / OpenGL ES XML +** API Registry. The current version of the Registry, generator scripts +** used to make the header, and the header can be found at +** http://www.khronos.org/registry/egl +** +** Khronos $Git commit SHA1: a732b061e7 $ on $Git commit date: 2017-06-17 23:27:53 +0100 $ +*/ + +#include + +/* Generated on date 20170627 */ + +/* Generated C header for: + * API: egl + * Versions considered: .* + * Versions emitted: .* + * Default extensions included: None + * Additional extensions included: _nomatch_^ + * Extensions removed: _nomatch_^ + */ + +#ifndef EGL_VERSION_1_0 +#define EGL_VERSION_1_0 1 +typedef unsigned int EGLBoolean; +typedef void *EGLDisplay; +#include +#include +typedef void *EGLConfig; +typedef void *EGLSurface; +typedef void *EGLContext; +typedef void (*__eglMustCastToProperFunctionPointerType)(void); +#define EGL_ALPHA_SIZE 0x3021 +#define EGL_BAD_ACCESS 0x3002 +#define EGL_BAD_ALLOC 0x3003 +#define EGL_BAD_ATTRIBUTE 0x3004 +#define EGL_BAD_CONFIG 0x3005 +#define EGL_BAD_CONTEXT 0x3006 +#define EGL_BAD_CURRENT_SURFACE 0x3007 +#define EGL_BAD_DISPLAY 0x3008 +#define EGL_BAD_MATCH 0x3009 +#define EGL_BAD_NATIVE_PIXMAP 0x300A +#define EGL_BAD_NATIVE_WINDOW 0x300B +#define EGL_BAD_PARAMETER 0x300C +#define EGL_BAD_SURFACE 0x300D +#define EGL_BLUE_SIZE 0x3022 +#define EGL_BUFFER_SIZE 0x3020 +#define EGL_CONFIG_CAVEAT 0x3027 +#define EGL_CONFIG_ID 0x3028 +#define EGL_CORE_NATIVE_ENGINE 0x305B +#define EGL_DEPTH_SIZE 0x3025 +#define EGL_DONT_CARE EGL_CAST(EGLint,-1) +#define EGL_DRAW 0x3059 +#define EGL_EXTENSIONS 0x3055 +#define EGL_FALSE 0 +#define EGL_GREEN_SIZE 0x3023 +#define EGL_HEIGHT 0x3056 +#define EGL_LARGEST_PBUFFER 0x3058 +#define EGL_LEVEL 0x3029 +#define EGL_MAX_PBUFFER_HEIGHT 0x302A +#define EGL_MAX_PBUFFER_PIXELS 0x302B +#define EGL_MAX_PBUFFER_WIDTH 0x302C +#define EGL_NATIVE_RENDERABLE 0x302D +#define EGL_NATIVE_VISUAL_ID 0x302E +#define EGL_NATIVE_VISUAL_TYPE 0x302F +#define EGL_NONE 0x3038 +#define EGL_NON_CONFORMANT_CONFIG 0x3051 +#define EGL_NOT_INITIALIZED 0x3001 +#define EGL_NO_CONTEXT EGL_CAST(EGLContext,0) +#define EGL_NO_DISPLAY EGL_CAST(EGLDisplay,0) +#define EGL_NO_SURFACE EGL_CAST(EGLSurface,0) +#define EGL_PBUFFER_BIT 0x0001 +#define EGL_PIXMAP_BIT 0x0002 +#define EGL_READ 0x305A +#define EGL_RED_SIZE 0x3024 +#define EGL_SAMPLES 0x3031 +#define EGL_SAMPLE_BUFFERS 0x3032 +#define EGL_SLOW_CONFIG 0x3050 +#define EGL_STENCIL_SIZE 0x3026 +#define EGL_SUCCESS 0x3000 +#define EGL_SURFACE_TYPE 0x3033 +#define EGL_TRANSPARENT_BLUE_VALUE 0x3035 +#define EGL_TRANSPARENT_GREEN_VALUE 0x3036 +#define EGL_TRANSPARENT_RED_VALUE 0x3037 +#define EGL_TRANSPARENT_RGB 0x3052 +#define EGL_TRANSPARENT_TYPE 0x3034 +#define EGL_TRUE 1 +#define EGL_VENDOR 0x3053 +#define EGL_VERSION 0x3054 +#define EGL_WIDTH 0x3057 +#define EGL_WINDOW_BIT 0x0004 +EGLAPI EGLBoolean EGLAPIENTRY eglChooseConfig (EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config); +EGLAPI EGLBoolean EGLAPIENTRY eglCopyBuffers (EGLDisplay dpy, EGLSurface surface, EGLNativePixmapType target); +EGLAPI EGLContext EGLAPIENTRY eglCreateContext (EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list); +EGLAPI EGLSurface EGLAPIENTRY eglCreatePbufferSurface (EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list); +EGLAPI EGLSurface EGLAPIENTRY eglCreatePixmapSurface (EGLDisplay dpy, EGLConfig config, EGLNativePixmapType pixmap, const EGLint *attrib_list); +EGLAPI EGLSurface EGLAPIENTRY eglCreateWindowSurface (EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglDestroyContext (EGLDisplay dpy, EGLContext ctx); +EGLAPI EGLBoolean EGLAPIENTRY eglDestroySurface (EGLDisplay dpy, EGLSurface surface); +EGLAPI EGLBoolean EGLAPIENTRY eglGetConfigAttrib (EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value); +EGLAPI EGLBoolean EGLAPIENTRY eglGetConfigs (EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config); +EGLAPI EGLDisplay EGLAPIENTRY eglGetCurrentDisplay (void); +EGLAPI EGLSurface EGLAPIENTRY eglGetCurrentSurface (EGLint readdraw); +EGLAPI EGLDisplay EGLAPIENTRY eglGetDisplay (EGLNativeDisplayType display_id); +EGLAPI EGLint EGLAPIENTRY eglGetError (void); +EGLAPI __eglMustCastToProperFunctionPointerType EGLAPIENTRY eglGetProcAddress (const char *procname); +EGLAPI EGLBoolean EGLAPIENTRY eglInitialize (EGLDisplay dpy, EGLint *major, EGLint *minor); +EGLAPI EGLBoolean EGLAPIENTRY eglMakeCurrent (EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryContext (EGLDisplay dpy, EGLContext ctx, EGLint attribute, EGLint *value); +EGLAPI const char *EGLAPIENTRY eglQueryString (EGLDisplay dpy, EGLint name); +EGLAPI EGLBoolean EGLAPIENTRY eglQuerySurface (EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint *value); +EGLAPI EGLBoolean EGLAPIENTRY eglSwapBuffers (EGLDisplay dpy, EGLSurface surface); +EGLAPI EGLBoolean EGLAPIENTRY eglTerminate (EGLDisplay dpy); +EGLAPI EGLBoolean EGLAPIENTRY eglWaitGL (void); +EGLAPI EGLBoolean EGLAPIENTRY eglWaitNative (EGLint engine); +#endif /* EGL_VERSION_1_0 */ + +#ifndef EGL_VERSION_1_1 +#define EGL_VERSION_1_1 1 +#define EGL_BACK_BUFFER 0x3084 +#define EGL_BIND_TO_TEXTURE_RGB 0x3039 +#define EGL_BIND_TO_TEXTURE_RGBA 0x303A +#define EGL_CONTEXT_LOST 0x300E +#define EGL_MIN_SWAP_INTERVAL 0x303B +#define EGL_MAX_SWAP_INTERVAL 0x303C +#define EGL_MIPMAP_TEXTURE 0x3082 +#define EGL_MIPMAP_LEVEL 0x3083 +#define EGL_NO_TEXTURE 0x305C +#define EGL_TEXTURE_2D 0x305F +#define EGL_TEXTURE_FORMAT 0x3080 +#define EGL_TEXTURE_RGB 0x305D +#define EGL_TEXTURE_RGBA 0x305E +#define EGL_TEXTURE_TARGET 0x3081 +EGLAPI EGLBoolean EGLAPIENTRY eglBindTexImage (EGLDisplay dpy, EGLSurface surface, EGLint buffer); +EGLAPI EGLBoolean EGLAPIENTRY eglReleaseTexImage (EGLDisplay dpy, EGLSurface surface, EGLint buffer); +EGLAPI EGLBoolean EGLAPIENTRY eglSurfaceAttrib (EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint value); +EGLAPI EGLBoolean EGLAPIENTRY eglSwapInterval (EGLDisplay dpy, EGLint interval); +#endif /* EGL_VERSION_1_1 */ + +#ifndef EGL_VERSION_1_2 +#define EGL_VERSION_1_2 1 +typedef unsigned int EGLenum; +typedef void *EGLClientBuffer; +#define EGL_ALPHA_FORMAT 0x3088 +#define EGL_ALPHA_FORMAT_NONPRE 0x308B +#define EGL_ALPHA_FORMAT_PRE 0x308C +#define EGL_ALPHA_MASK_SIZE 0x303E +#define EGL_BUFFER_PRESERVED 0x3094 +#define EGL_BUFFER_DESTROYED 0x3095 +#define EGL_CLIENT_APIS 0x308D +#define EGL_COLORSPACE 0x3087 +#define EGL_COLORSPACE_sRGB 0x3089 +#define EGL_COLORSPACE_LINEAR 0x308A +#define EGL_COLOR_BUFFER_TYPE 0x303F +#define EGL_CONTEXT_CLIENT_TYPE 0x3097 +#define EGL_DISPLAY_SCALING 10000 +#define EGL_HORIZONTAL_RESOLUTION 0x3090 +#define EGL_LUMINANCE_BUFFER 0x308F +#define EGL_LUMINANCE_SIZE 0x303D +#define EGL_OPENGL_ES_BIT 0x0001 +#define EGL_OPENVG_BIT 0x0002 +#define EGL_OPENGL_ES_API 0x30A0 +#define EGL_OPENVG_API 0x30A1 +#define EGL_OPENVG_IMAGE 0x3096 +#define EGL_PIXEL_ASPECT_RATIO 0x3092 +#define EGL_RENDERABLE_TYPE 0x3040 +#define EGL_RENDER_BUFFER 0x3086 +#define EGL_RGB_BUFFER 0x308E +#define EGL_SINGLE_BUFFER 0x3085 +#define EGL_SWAP_BEHAVIOR 0x3093 +#define EGL_UNKNOWN EGL_CAST(EGLint,-1) +#define EGL_VERTICAL_RESOLUTION 0x3091 +EGLAPI EGLBoolean EGLAPIENTRY eglBindAPI (EGLenum api); +EGLAPI EGLenum EGLAPIENTRY eglQueryAPI (void); +EGLAPI EGLSurface EGLAPIENTRY eglCreatePbufferFromClientBuffer (EGLDisplay dpy, EGLenum buftype, EGLClientBuffer buffer, EGLConfig config, const EGLint *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglReleaseThread (void); +EGLAPI EGLBoolean EGLAPIENTRY eglWaitClient (void); +#endif /* EGL_VERSION_1_2 */ + +#ifndef EGL_VERSION_1_3 +#define EGL_VERSION_1_3 1 +#define EGL_CONFORMANT 0x3042 +#define EGL_CONTEXT_CLIENT_VERSION 0x3098 +#define EGL_MATCH_NATIVE_PIXMAP 0x3041 +#define EGL_OPENGL_ES2_BIT 0x0004 +#define EGL_VG_ALPHA_FORMAT 0x3088 +#define EGL_VG_ALPHA_FORMAT_NONPRE 0x308B +#define EGL_VG_ALPHA_FORMAT_PRE 0x308C +#define EGL_VG_ALPHA_FORMAT_PRE_BIT 0x0040 +#define EGL_VG_COLORSPACE 0x3087 +#define EGL_VG_COLORSPACE_sRGB 0x3089 +#define EGL_VG_COLORSPACE_LINEAR 0x308A +#define EGL_VG_COLORSPACE_LINEAR_BIT 0x0020 +#endif /* EGL_VERSION_1_3 */ + +#ifndef EGL_VERSION_1_4 +#define EGL_VERSION_1_4 1 +#define EGL_DEFAULT_DISPLAY EGL_CAST(EGLNativeDisplayType,0) +#define EGL_MULTISAMPLE_RESOLVE_BOX_BIT 0x0200 +#define EGL_MULTISAMPLE_RESOLVE 0x3099 +#define EGL_MULTISAMPLE_RESOLVE_DEFAULT 0x309A +#define EGL_MULTISAMPLE_RESOLVE_BOX 0x309B +#define EGL_OPENGL_API 0x30A2 +#define EGL_OPENGL_BIT 0x0008 +#define EGL_SWAP_BEHAVIOR_PRESERVED_BIT 0x0400 +EGLAPI EGLContext EGLAPIENTRY eglGetCurrentContext (void); +#endif /* EGL_VERSION_1_4 */ + +#ifndef EGL_VERSION_1_5 +#define EGL_VERSION_1_5 1 +typedef void *EGLSync; +typedef intptr_t EGLAttrib; +typedef khronos_utime_nanoseconds_t EGLTime; +typedef void *EGLImage; +#define EGL_CONTEXT_MAJOR_VERSION 0x3098 +#define EGL_CONTEXT_MINOR_VERSION 0x30FB +#define EGL_CONTEXT_OPENGL_PROFILE_MASK 0x30FD +#define EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY 0x31BD +#define EGL_NO_RESET_NOTIFICATION 0x31BE +#define EGL_LOSE_CONTEXT_ON_RESET 0x31BF +#define EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT 0x00000001 +#define EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT 0x00000002 +#define EGL_CONTEXT_OPENGL_DEBUG 0x31B0 +#define EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE 0x31B1 +#define EGL_CONTEXT_OPENGL_ROBUST_ACCESS 0x31B2 +#define EGL_OPENGL_ES3_BIT 0x00000040 +#define EGL_CL_EVENT_HANDLE 0x309C +#define EGL_SYNC_CL_EVENT 0x30FE +#define EGL_SYNC_CL_EVENT_COMPLETE 0x30FF +#define EGL_SYNC_PRIOR_COMMANDS_COMPLETE 0x30F0 +#define EGL_SYNC_TYPE 0x30F7 +#define EGL_SYNC_STATUS 0x30F1 +#define EGL_SYNC_CONDITION 0x30F8 +#define EGL_SIGNALED 0x30F2 +#define EGL_UNSIGNALED 0x30F3 +#define EGL_SYNC_FLUSH_COMMANDS_BIT 0x0001 +#define EGL_FOREVER 0xFFFFFFFFFFFFFFFFull +#define EGL_TIMEOUT_EXPIRED 0x30F5 +#define EGL_CONDITION_SATISFIED 0x30F6 +#define EGL_NO_SYNC EGL_CAST(EGLSync,0) +#define EGL_SYNC_FENCE 0x30F9 +#define EGL_GL_COLORSPACE 0x309D +#define EGL_GL_COLORSPACE_SRGB 0x3089 +#define EGL_GL_COLORSPACE_LINEAR 0x308A +#define EGL_GL_RENDERBUFFER 0x30B9 +#define EGL_GL_TEXTURE_2D 0x30B1 +#define EGL_GL_TEXTURE_LEVEL 0x30BC +#define EGL_GL_TEXTURE_3D 0x30B2 +#define EGL_GL_TEXTURE_ZOFFSET 0x30BD +#define EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x30B3 +#define EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x30B4 +#define EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x30B5 +#define EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x30B6 +#define EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x30B7 +#define EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x30B8 +#define EGL_IMAGE_PRESERVED 0x30D2 +#define EGL_NO_IMAGE EGL_CAST(EGLImage,0) +EGLAPI EGLSync EGLAPIENTRY eglCreateSync (EGLDisplay dpy, EGLenum type, const EGLAttrib *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglDestroySync (EGLDisplay dpy, EGLSync sync); +EGLAPI EGLint EGLAPIENTRY eglClientWaitSync (EGLDisplay dpy, EGLSync sync, EGLint flags, EGLTime timeout); +EGLAPI EGLBoolean EGLAPIENTRY eglGetSyncAttrib (EGLDisplay dpy, EGLSync sync, EGLint attribute, EGLAttrib *value); +EGLAPI EGLImage EGLAPIENTRY eglCreateImage (EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLAttrib *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglDestroyImage (EGLDisplay dpy, EGLImage image); +EGLAPI EGLDisplay EGLAPIENTRY eglGetPlatformDisplay (EGLenum platform, void *native_display, const EGLAttrib *attrib_list); +EGLAPI EGLSurface EGLAPIENTRY eglCreatePlatformWindowSurface (EGLDisplay dpy, EGLConfig config, void *native_window, const EGLAttrib *attrib_list); +EGLAPI EGLSurface EGLAPIENTRY eglCreatePlatformPixmapSurface (EGLDisplay dpy, EGLConfig config, void *native_pixmap, const EGLAttrib *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglWaitSync (EGLDisplay dpy, EGLSync sync, EGLint flags); +#endif /* EGL_VERSION_1_5 */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/third_party/GL/include/EGL/eglext.h b/third_party/GL/include/EGL/eglext.h new file mode 100644 index 00000000..d2def032 --- /dev/null +++ b/third_party/GL/include/EGL/eglext.h @@ -0,0 +1,1241 @@ +#ifndef __eglext_h_ +#define __eglext_h_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** Copyright (c) 2013-2017 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. +*/ +/* +** This header is generated from the Khronos OpenGL / OpenGL ES XML +** API Registry. The current version of the Registry, generator scripts +** used to make the header, and the header can be found at +** http://www.khronos.org/registry/egl +** +** Khronos $Git commit SHA1: a732b061e7 $ on $Git commit date: 2017-06-17 23:27:53 +0100 $ +*/ + +#include + +#define EGL_EGLEXT_VERSION 20170627 + +/* Generated C header for: + * API: egl + * Versions considered: .* + * Versions emitted: _nomatch_^ + * Default extensions included: egl + * Additional extensions included: _nomatch_^ + * Extensions removed: _nomatch_^ + */ + +#ifndef EGL_KHR_cl_event +#define EGL_KHR_cl_event 1 +#define EGL_CL_EVENT_HANDLE_KHR 0x309C +#define EGL_SYNC_CL_EVENT_KHR 0x30FE +#define EGL_SYNC_CL_EVENT_COMPLETE_KHR 0x30FF +#endif /* EGL_KHR_cl_event */ + +#ifndef EGL_KHR_cl_event2 +#define EGL_KHR_cl_event2 1 +typedef void *EGLSyncKHR; +typedef intptr_t EGLAttribKHR; +typedef EGLSyncKHR (EGLAPIENTRYP PFNEGLCREATESYNC64KHRPROC) (EGLDisplay dpy, EGLenum type, const EGLAttribKHR *attrib_list); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLSyncKHR EGLAPIENTRY eglCreateSync64KHR (EGLDisplay dpy, EGLenum type, const EGLAttribKHR *attrib_list); +#endif +#endif /* EGL_KHR_cl_event2 */ + +#ifndef EGL_KHR_client_get_all_proc_addresses +#define EGL_KHR_client_get_all_proc_addresses 1 +#endif /* EGL_KHR_client_get_all_proc_addresses */ + +#ifndef EGL_KHR_config_attribs +#define EGL_KHR_config_attribs 1 +#define EGL_CONFORMANT_KHR 0x3042 +#define EGL_VG_COLORSPACE_LINEAR_BIT_KHR 0x0020 +#define EGL_VG_ALPHA_FORMAT_PRE_BIT_KHR 0x0040 +#endif /* EGL_KHR_config_attribs */ + +#ifndef EGL_KHR_context_flush_control +#define EGL_KHR_context_flush_control 1 +#define EGL_CONTEXT_RELEASE_BEHAVIOR_NONE_KHR 0 +#define EGL_CONTEXT_RELEASE_BEHAVIOR_KHR 0x2097 +#define EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR 0x2098 +#endif /* EGL_KHR_context_flush_control */ + +#ifndef EGL_KHR_create_context +#define EGL_KHR_create_context 1 +#define EGL_CONTEXT_MAJOR_VERSION_KHR 0x3098 +#define EGL_CONTEXT_MINOR_VERSION_KHR 0x30FB +#define EGL_CONTEXT_FLAGS_KHR 0x30FC +#define EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR 0x30FD +#define EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR 0x31BD +#define EGL_NO_RESET_NOTIFICATION_KHR 0x31BE +#define EGL_LOSE_CONTEXT_ON_RESET_KHR 0x31BF +#define EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR 0x00000001 +#define EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR 0x00000002 +#define EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR 0x00000004 +#define EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR 0x00000001 +#define EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR 0x00000002 +#define EGL_OPENGL_ES3_BIT_KHR 0x00000040 +#endif /* EGL_KHR_create_context */ + +#ifndef EGL_KHR_create_context_no_error +#define EGL_KHR_create_context_no_error 1 +#define EGL_CONTEXT_OPENGL_NO_ERROR_KHR 0x31B3 +#endif /* EGL_KHR_create_context_no_error */ + +#ifndef EGL_KHR_debug +#define EGL_KHR_debug 1 +typedef void *EGLLabelKHR; +typedef void *EGLObjectKHR; +typedef void (EGLAPIENTRY *EGLDEBUGPROCKHR)(EGLenum error,const char *command,EGLint messageType,EGLLabelKHR threadLabel,EGLLabelKHR objectLabel,const char* message); +#define EGL_OBJECT_THREAD_KHR 0x33B0 +#define EGL_OBJECT_DISPLAY_KHR 0x33B1 +#define EGL_OBJECT_CONTEXT_KHR 0x33B2 +#define EGL_OBJECT_SURFACE_KHR 0x33B3 +#define EGL_OBJECT_IMAGE_KHR 0x33B4 +#define EGL_OBJECT_SYNC_KHR 0x33B5 +#define EGL_OBJECT_STREAM_KHR 0x33B6 +#define EGL_DEBUG_MSG_CRITICAL_KHR 0x33B9 +#define EGL_DEBUG_MSG_ERROR_KHR 0x33BA +#define EGL_DEBUG_MSG_WARN_KHR 0x33BB +#define EGL_DEBUG_MSG_INFO_KHR 0x33BC +#define EGL_DEBUG_CALLBACK_KHR 0x33B8 +typedef EGLint (EGLAPIENTRYP PFNEGLDEBUGMESSAGECONTROLKHRPROC) (EGLDEBUGPROCKHR callback, const EGLAttrib *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDEBUGKHRPROC) (EGLint attribute, EGLAttrib *value); +typedef EGLint (EGLAPIENTRYP PFNEGLLABELOBJECTKHRPROC) (EGLDisplay display, EGLenum objectType, EGLObjectKHR object, EGLLabelKHR label); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLint EGLAPIENTRY eglDebugMessageControlKHR (EGLDEBUGPROCKHR callback, const EGLAttrib *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryDebugKHR (EGLint attribute, EGLAttrib *value); +EGLAPI EGLint EGLAPIENTRY eglLabelObjectKHR (EGLDisplay display, EGLenum objectType, EGLObjectKHR object, EGLLabelKHR label); +#endif +#endif /* EGL_KHR_debug */ + +#ifndef EGL_KHR_display_reference +#define EGL_KHR_display_reference 1 +#define EGL_TRACK_REFERENCES_KHR 0x3352 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDISPLAYATTRIBKHRPROC) (EGLDisplay dpy, EGLint name, EGLAttrib *value); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglQueryDisplayAttribKHR (EGLDisplay dpy, EGLint name, EGLAttrib *value); +#endif +#endif /* EGL_KHR_display_reference */ + +#ifndef EGL_KHR_fence_sync +#define EGL_KHR_fence_sync 1 +typedef khronos_utime_nanoseconds_t EGLTimeKHR; +#ifdef KHRONOS_SUPPORT_INT64 +#define EGL_SYNC_PRIOR_COMMANDS_COMPLETE_KHR 0x30F0 +#define EGL_SYNC_CONDITION_KHR 0x30F8 +#define EGL_SYNC_FENCE_KHR 0x30F9 +typedef EGLSyncKHR (EGLAPIENTRYP PFNEGLCREATESYNCKHRPROC) (EGLDisplay dpy, EGLenum type, const EGLint *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYSYNCKHRPROC) (EGLDisplay dpy, EGLSyncKHR sync); +typedef EGLint (EGLAPIENTRYP PFNEGLCLIENTWAITSYNCKHRPROC) (EGLDisplay dpy, EGLSyncKHR sync, EGLint flags, EGLTimeKHR timeout); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLGETSYNCATTRIBKHRPROC) (EGLDisplay dpy, EGLSyncKHR sync, EGLint attribute, EGLint *value); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLSyncKHR EGLAPIENTRY eglCreateSyncKHR (EGLDisplay dpy, EGLenum type, const EGLint *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglDestroySyncKHR (EGLDisplay dpy, EGLSyncKHR sync); +EGLAPI EGLint EGLAPIENTRY eglClientWaitSyncKHR (EGLDisplay dpy, EGLSyncKHR sync, EGLint flags, EGLTimeKHR timeout); +EGLAPI EGLBoolean EGLAPIENTRY eglGetSyncAttribKHR (EGLDisplay dpy, EGLSyncKHR sync, EGLint attribute, EGLint *value); +#endif +#endif /* KHRONOS_SUPPORT_INT64 */ +#endif /* EGL_KHR_fence_sync */ + +#ifndef EGL_KHR_get_all_proc_addresses +#define EGL_KHR_get_all_proc_addresses 1 +#endif /* EGL_KHR_get_all_proc_addresses */ + +#ifndef EGL_KHR_gl_colorspace +#define EGL_KHR_gl_colorspace 1 +#define EGL_GL_COLORSPACE_KHR 0x309D +#define EGL_GL_COLORSPACE_SRGB_KHR 0x3089 +#define EGL_GL_COLORSPACE_LINEAR_KHR 0x308A +#endif /* EGL_KHR_gl_colorspace */ + +#ifndef EGL_KHR_gl_renderbuffer_image +#define EGL_KHR_gl_renderbuffer_image 1 +#define EGL_GL_RENDERBUFFER_KHR 0x30B9 +#endif /* EGL_KHR_gl_renderbuffer_image */ + +#ifndef EGL_KHR_gl_texture_2D_image +#define EGL_KHR_gl_texture_2D_image 1 +#define EGL_GL_TEXTURE_2D_KHR 0x30B1 +#define EGL_GL_TEXTURE_LEVEL_KHR 0x30BC +#endif /* EGL_KHR_gl_texture_2D_image */ + +#ifndef EGL_KHR_gl_texture_3D_image +#define EGL_KHR_gl_texture_3D_image 1 +#define EGL_GL_TEXTURE_3D_KHR 0x30B2 +#define EGL_GL_TEXTURE_ZOFFSET_KHR 0x30BD +#endif /* EGL_KHR_gl_texture_3D_image */ + +#ifndef EGL_KHR_gl_texture_cubemap_image +#define EGL_KHR_gl_texture_cubemap_image 1 +#define EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_X_KHR 0x30B3 +#define EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X_KHR 0x30B4 +#define EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y_KHR 0x30B5 +#define EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_KHR 0x30B6 +#define EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z_KHR 0x30B7 +#define EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_KHR 0x30B8 +#endif /* EGL_KHR_gl_texture_cubemap_image */ + +#ifndef EGL_KHR_image +#define EGL_KHR_image 1 +typedef void *EGLImageKHR; +#define EGL_NATIVE_PIXMAP_KHR 0x30B0 +#define EGL_NO_IMAGE_KHR EGL_CAST(EGLImageKHR,0) +typedef EGLImageKHR (EGLAPIENTRYP PFNEGLCREATEIMAGEKHRPROC) (EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYIMAGEKHRPROC) (EGLDisplay dpy, EGLImageKHR image); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLImageKHR EGLAPIENTRY eglCreateImageKHR (EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglDestroyImageKHR (EGLDisplay dpy, EGLImageKHR image); +#endif +#endif /* EGL_KHR_image */ + +#ifndef EGL_KHR_image_base +#define EGL_KHR_image_base 1 +#define EGL_IMAGE_PRESERVED_KHR 0x30D2 +#endif /* EGL_KHR_image_base */ + +#ifndef EGL_KHR_image_pixmap +#define EGL_KHR_image_pixmap 1 +#endif /* EGL_KHR_image_pixmap */ + +#ifndef EGL_KHR_lock_surface +#define EGL_KHR_lock_surface 1 +#define EGL_READ_SURFACE_BIT_KHR 0x0001 +#define EGL_WRITE_SURFACE_BIT_KHR 0x0002 +#define EGL_LOCK_SURFACE_BIT_KHR 0x0080 +#define EGL_OPTIMAL_FORMAT_BIT_KHR 0x0100 +#define EGL_MATCH_FORMAT_KHR 0x3043 +#define EGL_FORMAT_RGB_565_EXACT_KHR 0x30C0 +#define EGL_FORMAT_RGB_565_KHR 0x30C1 +#define EGL_FORMAT_RGBA_8888_EXACT_KHR 0x30C2 +#define EGL_FORMAT_RGBA_8888_KHR 0x30C3 +#define EGL_MAP_PRESERVE_PIXELS_KHR 0x30C4 +#define EGL_LOCK_USAGE_HINT_KHR 0x30C5 +#define EGL_BITMAP_POINTER_KHR 0x30C6 +#define EGL_BITMAP_PITCH_KHR 0x30C7 +#define EGL_BITMAP_ORIGIN_KHR 0x30C8 +#define EGL_BITMAP_PIXEL_RED_OFFSET_KHR 0x30C9 +#define EGL_BITMAP_PIXEL_GREEN_OFFSET_KHR 0x30CA +#define EGL_BITMAP_PIXEL_BLUE_OFFSET_KHR 0x30CB +#define EGL_BITMAP_PIXEL_ALPHA_OFFSET_KHR 0x30CC +#define EGL_BITMAP_PIXEL_LUMINANCE_OFFSET_KHR 0x30CD +#define EGL_LOWER_LEFT_KHR 0x30CE +#define EGL_UPPER_LEFT_KHR 0x30CF +typedef EGLBoolean (EGLAPIENTRYP PFNEGLLOCKSURFACEKHRPROC) (EGLDisplay dpy, EGLSurface surface, const EGLint *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLUNLOCKSURFACEKHRPROC) (EGLDisplay dpy, EGLSurface surface); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglLockSurfaceKHR (EGLDisplay dpy, EGLSurface surface, const EGLint *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglUnlockSurfaceKHR (EGLDisplay dpy, EGLSurface surface); +#endif +#endif /* EGL_KHR_lock_surface */ + +#ifndef EGL_KHR_lock_surface2 +#define EGL_KHR_lock_surface2 1 +#define EGL_BITMAP_PIXEL_SIZE_KHR 0x3110 +#endif /* EGL_KHR_lock_surface2 */ + +#ifndef EGL_KHR_lock_surface3 +#define EGL_KHR_lock_surface3 1 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYSURFACE64KHRPROC) (EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLAttribKHR *value); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglQuerySurface64KHR (EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLAttribKHR *value); +#endif +#endif /* EGL_KHR_lock_surface3 */ + +#ifndef EGL_KHR_mutable_render_buffer +#define EGL_KHR_mutable_render_buffer 1 +#define EGL_MUTABLE_RENDER_BUFFER_BIT_KHR 0x1000 +#endif /* EGL_KHR_mutable_render_buffer */ + +#ifndef EGL_KHR_no_config_context +#define EGL_KHR_no_config_context 1 +#define EGL_NO_CONFIG_KHR EGL_CAST(EGLConfig,0) +#endif /* EGL_KHR_no_config_context */ + +#ifndef EGL_KHR_partial_update +#define EGL_KHR_partial_update 1 +#define EGL_BUFFER_AGE_KHR 0x313D +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSETDAMAGEREGIONKHRPROC) (EGLDisplay dpy, EGLSurface surface, EGLint *rects, EGLint n_rects); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglSetDamageRegionKHR (EGLDisplay dpy, EGLSurface surface, EGLint *rects, EGLint n_rects); +#endif +#endif /* EGL_KHR_partial_update */ + +#ifndef EGL_KHR_platform_android +#define EGL_KHR_platform_android 1 +#define EGL_PLATFORM_ANDROID_KHR 0x3141 +#endif /* EGL_KHR_platform_android */ + +#ifndef EGL_KHR_platform_gbm +#define EGL_KHR_platform_gbm 1 +#define EGL_PLATFORM_GBM_KHR 0x31D7 +#endif /* EGL_KHR_platform_gbm */ + +#ifndef EGL_KHR_platform_wayland +#define EGL_KHR_platform_wayland 1 +#define EGL_PLATFORM_WAYLAND_KHR 0x31D8 +#endif /* EGL_KHR_platform_wayland */ + +#ifndef EGL_KHR_platform_x11 +#define EGL_KHR_platform_x11 1 +#define EGL_PLATFORM_X11_KHR 0x31D5 +#define EGL_PLATFORM_X11_SCREEN_KHR 0x31D6 +#endif /* EGL_KHR_platform_x11 */ + +#ifndef EGL_KHR_reusable_sync +#define EGL_KHR_reusable_sync 1 +#ifdef KHRONOS_SUPPORT_INT64 +#define EGL_SYNC_STATUS_KHR 0x30F1 +#define EGL_SIGNALED_KHR 0x30F2 +#define EGL_UNSIGNALED_KHR 0x30F3 +#define EGL_TIMEOUT_EXPIRED_KHR 0x30F5 +#define EGL_CONDITION_SATISFIED_KHR 0x30F6 +#define EGL_SYNC_TYPE_KHR 0x30F7 +#define EGL_SYNC_REUSABLE_KHR 0x30FA +#define EGL_SYNC_FLUSH_COMMANDS_BIT_KHR 0x0001 +#define EGL_FOREVER_KHR 0xFFFFFFFFFFFFFFFFull +#define EGL_NO_SYNC_KHR EGL_CAST(EGLSyncKHR,0) +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSIGNALSYNCKHRPROC) (EGLDisplay dpy, EGLSyncKHR sync, EGLenum mode); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglSignalSyncKHR (EGLDisplay dpy, EGLSyncKHR sync, EGLenum mode); +#endif +#endif /* KHRONOS_SUPPORT_INT64 */ +#endif /* EGL_KHR_reusable_sync */ + +#ifndef EGL_KHR_stream +#define EGL_KHR_stream 1 +typedef void *EGLStreamKHR; +typedef khronos_uint64_t EGLuint64KHR; +#ifdef KHRONOS_SUPPORT_INT64 +#define EGL_NO_STREAM_KHR EGL_CAST(EGLStreamKHR,0) +#define EGL_CONSUMER_LATENCY_USEC_KHR 0x3210 +#define EGL_PRODUCER_FRAME_KHR 0x3212 +#define EGL_CONSUMER_FRAME_KHR 0x3213 +#define EGL_STREAM_STATE_KHR 0x3214 +#define EGL_STREAM_STATE_CREATED_KHR 0x3215 +#define EGL_STREAM_STATE_CONNECTING_KHR 0x3216 +#define EGL_STREAM_STATE_EMPTY_KHR 0x3217 +#define EGL_STREAM_STATE_NEW_FRAME_AVAILABLE_KHR 0x3218 +#define EGL_STREAM_STATE_OLD_FRAME_AVAILABLE_KHR 0x3219 +#define EGL_STREAM_STATE_DISCONNECTED_KHR 0x321A +#define EGL_BAD_STREAM_KHR 0x321B +#define EGL_BAD_STATE_KHR 0x321C +typedef EGLStreamKHR (EGLAPIENTRYP PFNEGLCREATESTREAMKHRPROC) (EGLDisplay dpy, const EGLint *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYSTREAMKHRPROC) (EGLDisplay dpy, EGLStreamKHR stream); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMATTRIBKHRPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLint value); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYSTREAMKHRPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLint *value); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYSTREAMU64KHRPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLuint64KHR *value); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLStreamKHR EGLAPIENTRY eglCreateStreamKHR (EGLDisplay dpy, const EGLint *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglDestroyStreamKHR (EGLDisplay dpy, EGLStreamKHR stream); +EGLAPI EGLBoolean EGLAPIENTRY eglStreamAttribKHR (EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLint value); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryStreamKHR (EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLint *value); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryStreamu64KHR (EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLuint64KHR *value); +#endif +#endif /* KHRONOS_SUPPORT_INT64 */ +#endif /* EGL_KHR_stream */ + +#ifndef EGL_KHR_stream_attrib +#define EGL_KHR_stream_attrib 1 +#ifdef KHRONOS_SUPPORT_INT64 +typedef EGLStreamKHR (EGLAPIENTRYP PFNEGLCREATESTREAMATTRIBKHRPROC) (EGLDisplay dpy, const EGLAttrib *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSETSTREAMATTRIBKHRPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLAttrib value); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYSTREAMATTRIBKHRPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLAttrib *value); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMCONSUMERACQUIREATTRIBKHRPROC) (EGLDisplay dpy, EGLStreamKHR stream, const EGLAttrib *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMCONSUMERRELEASEATTRIBKHRPROC) (EGLDisplay dpy, EGLStreamKHR stream, const EGLAttrib *attrib_list); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLStreamKHR EGLAPIENTRY eglCreateStreamAttribKHR (EGLDisplay dpy, const EGLAttrib *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglSetStreamAttribKHR (EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLAttrib value); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryStreamAttribKHR (EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLAttrib *value); +EGLAPI EGLBoolean EGLAPIENTRY eglStreamConsumerAcquireAttribKHR (EGLDisplay dpy, EGLStreamKHR stream, const EGLAttrib *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglStreamConsumerReleaseAttribKHR (EGLDisplay dpy, EGLStreamKHR stream, const EGLAttrib *attrib_list); +#endif +#endif /* KHRONOS_SUPPORT_INT64 */ +#endif /* EGL_KHR_stream_attrib */ + +#ifndef EGL_KHR_stream_consumer_gltexture +#define EGL_KHR_stream_consumer_gltexture 1 +#ifdef EGL_KHR_stream +#define EGL_CONSUMER_ACQUIRE_TIMEOUT_USEC_KHR 0x321E +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMCONSUMERGLTEXTUREEXTERNALKHRPROC) (EGLDisplay dpy, EGLStreamKHR stream); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMCONSUMERACQUIREKHRPROC) (EGLDisplay dpy, EGLStreamKHR stream); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMCONSUMERRELEASEKHRPROC) (EGLDisplay dpy, EGLStreamKHR stream); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglStreamConsumerGLTextureExternalKHR (EGLDisplay dpy, EGLStreamKHR stream); +EGLAPI EGLBoolean EGLAPIENTRY eglStreamConsumerAcquireKHR (EGLDisplay dpy, EGLStreamKHR stream); +EGLAPI EGLBoolean EGLAPIENTRY eglStreamConsumerReleaseKHR (EGLDisplay dpy, EGLStreamKHR stream); +#endif +#endif /* EGL_KHR_stream */ +#endif /* EGL_KHR_stream_consumer_gltexture */ + +#ifndef EGL_KHR_stream_cross_process_fd +#define EGL_KHR_stream_cross_process_fd 1 +typedef int EGLNativeFileDescriptorKHR; +#ifdef EGL_KHR_stream +#define EGL_NO_FILE_DESCRIPTOR_KHR EGL_CAST(EGLNativeFileDescriptorKHR,-1) +typedef EGLNativeFileDescriptorKHR (EGLAPIENTRYP PFNEGLGETSTREAMFILEDESCRIPTORKHRPROC) (EGLDisplay dpy, EGLStreamKHR stream); +typedef EGLStreamKHR (EGLAPIENTRYP PFNEGLCREATESTREAMFROMFILEDESCRIPTORKHRPROC) (EGLDisplay dpy, EGLNativeFileDescriptorKHR file_descriptor); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLNativeFileDescriptorKHR EGLAPIENTRY eglGetStreamFileDescriptorKHR (EGLDisplay dpy, EGLStreamKHR stream); +EGLAPI EGLStreamKHR EGLAPIENTRY eglCreateStreamFromFileDescriptorKHR (EGLDisplay dpy, EGLNativeFileDescriptorKHR file_descriptor); +#endif +#endif /* EGL_KHR_stream */ +#endif /* EGL_KHR_stream_cross_process_fd */ + +#ifndef EGL_KHR_stream_fifo +#define EGL_KHR_stream_fifo 1 +#ifdef EGL_KHR_stream +#define EGL_STREAM_FIFO_LENGTH_KHR 0x31FC +#define EGL_STREAM_TIME_NOW_KHR 0x31FD +#define EGL_STREAM_TIME_CONSUMER_KHR 0x31FE +#define EGL_STREAM_TIME_PRODUCER_KHR 0x31FF +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYSTREAMTIMEKHRPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLTimeKHR *value); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglQueryStreamTimeKHR (EGLDisplay dpy, EGLStreamKHR stream, EGLenum attribute, EGLTimeKHR *value); +#endif +#endif /* EGL_KHR_stream */ +#endif /* EGL_KHR_stream_fifo */ + +#ifndef EGL_KHR_stream_producer_aldatalocator +#define EGL_KHR_stream_producer_aldatalocator 1 +#ifdef EGL_KHR_stream +#endif /* EGL_KHR_stream */ +#endif /* EGL_KHR_stream_producer_aldatalocator */ + +#ifndef EGL_KHR_stream_producer_eglsurface +#define EGL_KHR_stream_producer_eglsurface 1 +#ifdef EGL_KHR_stream +#define EGL_STREAM_BIT_KHR 0x0800 +typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATESTREAMPRODUCERSURFACEKHRPROC) (EGLDisplay dpy, EGLConfig config, EGLStreamKHR stream, const EGLint *attrib_list); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLSurface EGLAPIENTRY eglCreateStreamProducerSurfaceKHR (EGLDisplay dpy, EGLConfig config, EGLStreamKHR stream, const EGLint *attrib_list); +#endif +#endif /* EGL_KHR_stream */ +#endif /* EGL_KHR_stream_producer_eglsurface */ + +#ifndef EGL_KHR_surfaceless_context +#define EGL_KHR_surfaceless_context 1 +#endif /* EGL_KHR_surfaceless_context */ + +#ifndef EGL_KHR_swap_buffers_with_damage +#define EGL_KHR_swap_buffers_with_damage 1 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSWAPBUFFERSWITHDAMAGEKHRPROC) (EGLDisplay dpy, EGLSurface surface, EGLint *rects, EGLint n_rects); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglSwapBuffersWithDamageKHR (EGLDisplay dpy, EGLSurface surface, EGLint *rects, EGLint n_rects); +#endif +#endif /* EGL_KHR_swap_buffers_with_damage */ + +#ifndef EGL_KHR_vg_parent_image +#define EGL_KHR_vg_parent_image 1 +#define EGL_VG_PARENT_IMAGE_KHR 0x30BA +#endif /* EGL_KHR_vg_parent_image */ + +#ifndef EGL_KHR_wait_sync +#define EGL_KHR_wait_sync 1 +typedef EGLint (EGLAPIENTRYP PFNEGLWAITSYNCKHRPROC) (EGLDisplay dpy, EGLSyncKHR sync, EGLint flags); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLint EGLAPIENTRY eglWaitSyncKHR (EGLDisplay dpy, EGLSyncKHR sync, EGLint flags); +#endif +#endif /* EGL_KHR_wait_sync */ + +#ifndef EGL_ANDROID_blob_cache +#define EGL_ANDROID_blob_cache 1 +typedef khronos_ssize_t EGLsizeiANDROID; +typedef void (*EGLSetBlobFuncANDROID) (const void *key, EGLsizeiANDROID keySize, const void *value, EGLsizeiANDROID valueSize); +typedef EGLsizeiANDROID (*EGLGetBlobFuncANDROID) (const void *key, EGLsizeiANDROID keySize, void *value, EGLsizeiANDROID valueSize); +typedef void (EGLAPIENTRYP PFNEGLSETBLOBCACHEFUNCSANDROIDPROC) (EGLDisplay dpy, EGLSetBlobFuncANDROID set, EGLGetBlobFuncANDROID get); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI void EGLAPIENTRY eglSetBlobCacheFuncsANDROID (EGLDisplay dpy, EGLSetBlobFuncANDROID set, EGLGetBlobFuncANDROID get); +#endif +#endif /* EGL_ANDROID_blob_cache */ + +#ifndef EGL_ANDROID_create_native_client_buffer +#define EGL_ANDROID_create_native_client_buffer 1 +#define EGL_NATIVE_BUFFER_USAGE_ANDROID 0x3143 +#define EGL_NATIVE_BUFFER_USAGE_PROTECTED_BIT_ANDROID 0x00000001 +#define EGL_NATIVE_BUFFER_USAGE_RENDERBUFFER_BIT_ANDROID 0x00000002 +#define EGL_NATIVE_BUFFER_USAGE_TEXTURE_BIT_ANDROID 0x00000004 +typedef EGLClientBuffer (EGLAPIENTRYP PFNEGLCREATENATIVECLIENTBUFFERANDROIDPROC) (const EGLint *attrib_list); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLClientBuffer EGLAPIENTRY eglCreateNativeClientBufferANDROID (const EGLint *attrib_list); +#endif +#endif /* EGL_ANDROID_create_native_client_buffer */ + +#ifndef EGL_ANDROID_framebuffer_target +#define EGL_ANDROID_framebuffer_target 1 +#define EGL_FRAMEBUFFER_TARGET_ANDROID 0x3147 +#endif /* EGL_ANDROID_framebuffer_target */ + +#ifndef EGL_ANDROID_front_buffer_auto_refresh +#define EGL_ANDROID_front_buffer_auto_refresh 1 +#define EGL_FRONT_BUFFER_AUTO_REFRESH_ANDROID 0x314C +#endif /* EGL_ANDROID_front_buffer_auto_refresh */ + +#ifndef EGL_ANDROID_image_native_buffer +#define EGL_ANDROID_image_native_buffer 1 +#define EGL_NATIVE_BUFFER_ANDROID 0x3140 +#endif /* EGL_ANDROID_image_native_buffer */ + +#ifndef EGL_ANDROID_native_fence_sync +#define EGL_ANDROID_native_fence_sync 1 +#define EGL_SYNC_NATIVE_FENCE_ANDROID 0x3144 +#define EGL_SYNC_NATIVE_FENCE_FD_ANDROID 0x3145 +#define EGL_SYNC_NATIVE_FENCE_SIGNALED_ANDROID 0x3146 +#define EGL_NO_NATIVE_FENCE_FD_ANDROID -1 +typedef EGLint (EGLAPIENTRYP PFNEGLDUPNATIVEFENCEFDANDROIDPROC) (EGLDisplay dpy, EGLSyncKHR sync); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLint EGLAPIENTRY eglDupNativeFenceFDANDROID (EGLDisplay dpy, EGLSyncKHR sync); +#endif +#endif /* EGL_ANDROID_native_fence_sync */ + +#ifndef EGL_ANDROID_presentation_time +#define EGL_ANDROID_presentation_time 1 +typedef khronos_stime_nanoseconds_t EGLnsecsANDROID; +typedef EGLBoolean (EGLAPIENTRYP PFNEGLPRESENTATIONTIMEANDROIDPROC) (EGLDisplay dpy, EGLSurface surface, EGLnsecsANDROID time); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglPresentationTimeANDROID (EGLDisplay dpy, EGLSurface surface, EGLnsecsANDROID time); +#endif +#endif /* EGL_ANDROID_presentation_time */ + +#ifndef EGL_ANDROID_recordable +#define EGL_ANDROID_recordable 1 +#define EGL_RECORDABLE_ANDROID 0x3142 +#endif /* EGL_ANDROID_recordable */ + +#ifndef EGL_ANGLE_d3d_share_handle_client_buffer +#define EGL_ANGLE_d3d_share_handle_client_buffer 1 +#define EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE 0x3200 +#endif /* EGL_ANGLE_d3d_share_handle_client_buffer */ + +#ifndef EGL_ANGLE_device_d3d +#define EGL_ANGLE_device_d3d 1 +#define EGL_D3D9_DEVICE_ANGLE 0x33A0 +#define EGL_D3D11_DEVICE_ANGLE 0x33A1 +#endif /* EGL_ANGLE_device_d3d */ + +#ifndef EGL_ANGLE_query_surface_pointer +#define EGL_ANGLE_query_surface_pointer 1 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYSURFACEPOINTERANGLEPROC) (EGLDisplay dpy, EGLSurface surface, EGLint attribute, void **value); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglQuerySurfacePointerANGLE (EGLDisplay dpy, EGLSurface surface, EGLint attribute, void **value); +#endif +#endif /* EGL_ANGLE_query_surface_pointer */ + +#ifndef EGL_ANGLE_surface_d3d_texture_2d_share_handle +#define EGL_ANGLE_surface_d3d_texture_2d_share_handle 1 +#endif /* EGL_ANGLE_surface_d3d_texture_2d_share_handle */ + +#ifndef EGL_ANGLE_window_fixed_size +#define EGL_ANGLE_window_fixed_size 1 +#define EGL_FIXED_SIZE_ANGLE 0x3201 +#endif /* EGL_ANGLE_window_fixed_size */ + +#ifndef EGL_ARM_implicit_external_sync +#define EGL_ARM_implicit_external_sync 1 +#define EGL_SYNC_PRIOR_COMMANDS_IMPLICIT_EXTERNAL_ARM 0x328A +#endif /* EGL_ARM_implicit_external_sync */ + +#ifndef EGL_ARM_pixmap_multisample_discard +#define EGL_ARM_pixmap_multisample_discard 1 +#define EGL_DISCARD_SAMPLES_ARM 0x3286 +#endif /* EGL_ARM_pixmap_multisample_discard */ + +#ifndef EGL_EXT_bind_to_front +#define EGL_EXT_bind_to_front 1 +#define EGL_FRONT_BUFFER_EXT 0x3464 +#endif /* EGL_EXT_bind_to_front */ + +#ifndef EGL_EXT_buffer_age +#define EGL_EXT_buffer_age 1 +#define EGL_BUFFER_AGE_EXT 0x313D +#endif /* EGL_EXT_buffer_age */ + +#ifndef EGL_EXT_client_extensions +#define EGL_EXT_client_extensions 1 +#endif /* EGL_EXT_client_extensions */ + +#ifndef EGL_EXT_compositor +#define EGL_EXT_compositor 1 +#define EGL_PRIMARY_COMPOSITOR_CONTEXT_EXT 0x3460 +#define EGL_EXTERNAL_REF_ID_EXT 0x3461 +#define EGL_COMPOSITOR_DROP_NEWEST_FRAME_EXT 0x3462 +#define EGL_COMPOSITOR_KEEP_NEWEST_FRAME_EXT 0x3463 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLCOMPOSITORSETCONTEXTLISTEXTPROC) (const EGLint *external_ref_ids, EGLint num_entries); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLCOMPOSITORSETCONTEXTATTRIBUTESEXTPROC) (EGLint external_ref_id, const EGLint *context_attributes, EGLint num_entries); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLCOMPOSITORSETWINDOWLISTEXTPROC) (EGLint external_ref_id, const EGLint *external_win_ids, EGLint num_entries); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLCOMPOSITORSETWINDOWATTRIBUTESEXTPROC) (EGLint external_win_id, const EGLint *window_attributes, EGLint num_entries); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLCOMPOSITORBINDTEXWINDOWEXTPROC) (EGLint external_win_id); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLCOMPOSITORSETSIZEEXTPROC) (EGLint external_win_id, EGLint width, EGLint height); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLCOMPOSITORSWAPPOLICYEXTPROC) (EGLint external_win_id, EGLint policy); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglCompositorSetContextListEXT (const EGLint *external_ref_ids, EGLint num_entries); +EGLAPI EGLBoolean EGLAPIENTRY eglCompositorSetContextAttributesEXT (EGLint external_ref_id, const EGLint *context_attributes, EGLint num_entries); +EGLAPI EGLBoolean EGLAPIENTRY eglCompositorSetWindowListEXT (EGLint external_ref_id, const EGLint *external_win_ids, EGLint num_entries); +EGLAPI EGLBoolean EGLAPIENTRY eglCompositorSetWindowAttributesEXT (EGLint external_win_id, const EGLint *window_attributes, EGLint num_entries); +EGLAPI EGLBoolean EGLAPIENTRY eglCompositorBindTexWindowEXT (EGLint external_win_id); +EGLAPI EGLBoolean EGLAPIENTRY eglCompositorSetSizeEXT (EGLint external_win_id, EGLint width, EGLint height); +EGLAPI EGLBoolean EGLAPIENTRY eglCompositorSwapPolicyEXT (EGLint external_win_id, EGLint policy); +#endif +#endif /* EGL_EXT_compositor */ + +#ifndef EGL_EXT_create_context_robustness +#define EGL_EXT_create_context_robustness 1 +#define EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT 0x30BF +#define EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT 0x3138 +#define EGL_NO_RESET_NOTIFICATION_EXT 0x31BE +#define EGL_LOSE_CONTEXT_ON_RESET_EXT 0x31BF +#endif /* EGL_EXT_create_context_robustness */ + +#ifndef EGL_EXT_device_base +#define EGL_EXT_device_base 1 +typedef void *EGLDeviceEXT; +#define EGL_NO_DEVICE_EXT EGL_CAST(EGLDeviceEXT,0) +#define EGL_BAD_DEVICE_EXT 0x322B +#define EGL_DEVICE_EXT 0x322C +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDEVICEATTRIBEXTPROC) (EGLDeviceEXT device, EGLint attribute, EGLAttrib *value); +typedef const char *(EGLAPIENTRYP PFNEGLQUERYDEVICESTRINGEXTPROC) (EGLDeviceEXT device, EGLint name); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDEVICESEXTPROC) (EGLint max_devices, EGLDeviceEXT *devices, EGLint *num_devices); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDISPLAYATTRIBEXTPROC) (EGLDisplay dpy, EGLint attribute, EGLAttrib *value); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglQueryDeviceAttribEXT (EGLDeviceEXT device, EGLint attribute, EGLAttrib *value); +EGLAPI const char *EGLAPIENTRY eglQueryDeviceStringEXT (EGLDeviceEXT device, EGLint name); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryDevicesEXT (EGLint max_devices, EGLDeviceEXT *devices, EGLint *num_devices); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryDisplayAttribEXT (EGLDisplay dpy, EGLint attribute, EGLAttrib *value); +#endif +#endif /* EGL_EXT_device_base */ + +#ifndef EGL_EXT_device_drm +#define EGL_EXT_device_drm 1 +#define EGL_DRM_DEVICE_FILE_EXT 0x3233 +#endif /* EGL_EXT_device_drm */ + +#ifndef EGL_EXT_device_enumeration +#define EGL_EXT_device_enumeration 1 +#endif /* EGL_EXT_device_enumeration */ + +#ifndef EGL_EXT_device_openwf +#define EGL_EXT_device_openwf 1 +#define EGL_OPENWF_DEVICE_ID_EXT 0x3237 +#endif /* EGL_EXT_device_openwf */ + +#ifndef EGL_EXT_device_query +#define EGL_EXT_device_query 1 +#endif /* EGL_EXT_device_query */ + +#ifndef EGL_EXT_gl_colorspace_bt2020_linear +#define EGL_EXT_gl_colorspace_bt2020_linear 1 +#define EGL_GL_COLORSPACE_BT2020_LINEAR_EXT 0x333F +#endif /* EGL_EXT_gl_colorspace_bt2020_linear */ + +#ifndef EGL_EXT_gl_colorspace_bt2020_pq +#define EGL_EXT_gl_colorspace_bt2020_pq 1 +#define EGL_GL_COLORSPACE_BT2020_PQ_EXT 0x3340 +#endif /* EGL_EXT_gl_colorspace_bt2020_pq */ + +#ifndef EGL_EXT_gl_colorspace_display_p3 +#define EGL_EXT_gl_colorspace_display_p3 1 +#define EGL_GL_COLORSPACE_DISPLAY_P3_EXT 0x3363 +#endif /* EGL_EXT_gl_colorspace_display_p3 */ + +#ifndef EGL_EXT_gl_colorspace_display_p3_linear +#define EGL_EXT_gl_colorspace_display_p3_linear 1 +#define EGL_GL_COLORSPACE_DISPLAY_P3_LINEAR_EXT 0x3362 +#endif /* EGL_EXT_gl_colorspace_display_p3_linear */ + +#ifndef EGL_EXT_gl_colorspace_scrgb +#define EGL_EXT_gl_colorspace_scrgb 1 +#define EGL_GL_COLORSPACE_SCRGB_EXT 0x3351 +#endif /* EGL_EXT_gl_colorspace_scrgb */ + +#ifndef EGL_EXT_gl_colorspace_scrgb_linear +#define EGL_EXT_gl_colorspace_scrgb_linear 1 +#define EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT 0x3350 +#endif /* EGL_EXT_gl_colorspace_scrgb_linear */ + +#ifndef EGL_EXT_image_dma_buf_import +#define EGL_EXT_image_dma_buf_import 1 +#define EGL_LINUX_DMA_BUF_EXT 0x3270 +#define EGL_LINUX_DRM_FOURCC_EXT 0x3271 +#define EGL_DMA_BUF_PLANE0_FD_EXT 0x3272 +#define EGL_DMA_BUF_PLANE0_OFFSET_EXT 0x3273 +#define EGL_DMA_BUF_PLANE0_PITCH_EXT 0x3274 +#define EGL_DMA_BUF_PLANE1_FD_EXT 0x3275 +#define EGL_DMA_BUF_PLANE1_OFFSET_EXT 0x3276 +#define EGL_DMA_BUF_PLANE1_PITCH_EXT 0x3277 +#define EGL_DMA_BUF_PLANE2_FD_EXT 0x3278 +#define EGL_DMA_BUF_PLANE2_OFFSET_EXT 0x3279 +#define EGL_DMA_BUF_PLANE2_PITCH_EXT 0x327A +#define EGL_YUV_COLOR_SPACE_HINT_EXT 0x327B +#define EGL_SAMPLE_RANGE_HINT_EXT 0x327C +#define EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT 0x327D +#define EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT 0x327E +#define EGL_ITU_REC601_EXT 0x327F +#define EGL_ITU_REC709_EXT 0x3280 +#define EGL_ITU_REC2020_EXT 0x3281 +#define EGL_YUV_FULL_RANGE_EXT 0x3282 +#define EGL_YUV_NARROW_RANGE_EXT 0x3283 +#define EGL_YUV_CHROMA_SITING_0_EXT 0x3284 +#define EGL_YUV_CHROMA_SITING_0_5_EXT 0x3285 +#endif /* EGL_EXT_image_dma_buf_import */ + +#ifndef EGL_EXT_image_dma_buf_import_modifiers +#define EGL_EXT_image_dma_buf_import_modifiers 1 +#define EGL_DMA_BUF_PLANE3_FD_EXT 0x3440 +#define EGL_DMA_BUF_PLANE3_OFFSET_EXT 0x3441 +#define EGL_DMA_BUF_PLANE3_PITCH_EXT 0x3442 +#define EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT 0x3443 +#define EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT 0x3444 +#define EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT 0x3445 +#define EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT 0x3446 +#define EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT 0x3447 +#define EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT 0x3448 +#define EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT 0x3449 +#define EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT 0x344A +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDMABUFFORMATSEXTPROC) (EGLDisplay dpy, EGLint max_formats, EGLint *formats, EGLint *num_formats); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDMABUFMODIFIERSEXTPROC) (EGLDisplay dpy, EGLint format, EGLint max_modifiers, EGLuint64KHR *modifiers, EGLBoolean *external_only, EGLint *num_modifiers); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglQueryDmaBufFormatsEXT (EGLDisplay dpy, EGLint max_formats, EGLint *formats, EGLint *num_formats); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryDmaBufModifiersEXT (EGLDisplay dpy, EGLint format, EGLint max_modifiers, EGLuint64KHR *modifiers, EGLBoolean *external_only, EGLint *num_modifiers); +#endif +#endif /* EGL_EXT_image_dma_buf_import_modifiers */ + +#ifndef EGL_EXT_image_implicit_sync_control +#define EGL_EXT_image_implicit_sync_control 1 +#define EGL_IMPORT_SYNC_TYPE_EXT 0x3470 +#define EGL_IMPORT_IMPLICIT_SYNC_EXT 0x3471 +#define EGL_IMPORT_EXPLICIT_SYNC_EXT 0x3472 +#endif /* EGL_EXT_image_implicit_sync_control */ + +#ifndef EGL_EXT_multiview_window +#define EGL_EXT_multiview_window 1 +#define EGL_MULTIVIEW_VIEW_COUNT_EXT 0x3134 +#endif /* EGL_EXT_multiview_window */ + +#ifndef EGL_EXT_output_base +#define EGL_EXT_output_base 1 +typedef void *EGLOutputLayerEXT; +typedef void *EGLOutputPortEXT; +#define EGL_NO_OUTPUT_LAYER_EXT EGL_CAST(EGLOutputLayerEXT,0) +#define EGL_NO_OUTPUT_PORT_EXT EGL_CAST(EGLOutputPortEXT,0) +#define EGL_BAD_OUTPUT_LAYER_EXT 0x322D +#define EGL_BAD_OUTPUT_PORT_EXT 0x322E +#define EGL_SWAP_INTERVAL_EXT 0x322F +typedef EGLBoolean (EGLAPIENTRYP PFNEGLGETOUTPUTLAYERSEXTPROC) (EGLDisplay dpy, const EGLAttrib *attrib_list, EGLOutputLayerEXT *layers, EGLint max_layers, EGLint *num_layers); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLGETOUTPUTPORTSEXTPROC) (EGLDisplay dpy, const EGLAttrib *attrib_list, EGLOutputPortEXT *ports, EGLint max_ports, EGLint *num_ports); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLOUTPUTLAYERATTRIBEXTPROC) (EGLDisplay dpy, EGLOutputLayerEXT layer, EGLint attribute, EGLAttrib value); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYOUTPUTLAYERATTRIBEXTPROC) (EGLDisplay dpy, EGLOutputLayerEXT layer, EGLint attribute, EGLAttrib *value); +typedef const char *(EGLAPIENTRYP PFNEGLQUERYOUTPUTLAYERSTRINGEXTPROC) (EGLDisplay dpy, EGLOutputLayerEXT layer, EGLint name); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLOUTPUTPORTATTRIBEXTPROC) (EGLDisplay dpy, EGLOutputPortEXT port, EGLint attribute, EGLAttrib value); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYOUTPUTPORTATTRIBEXTPROC) (EGLDisplay dpy, EGLOutputPortEXT port, EGLint attribute, EGLAttrib *value); +typedef const char *(EGLAPIENTRYP PFNEGLQUERYOUTPUTPORTSTRINGEXTPROC) (EGLDisplay dpy, EGLOutputPortEXT port, EGLint name); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglGetOutputLayersEXT (EGLDisplay dpy, const EGLAttrib *attrib_list, EGLOutputLayerEXT *layers, EGLint max_layers, EGLint *num_layers); +EGLAPI EGLBoolean EGLAPIENTRY eglGetOutputPortsEXT (EGLDisplay dpy, const EGLAttrib *attrib_list, EGLOutputPortEXT *ports, EGLint max_ports, EGLint *num_ports); +EGLAPI EGLBoolean EGLAPIENTRY eglOutputLayerAttribEXT (EGLDisplay dpy, EGLOutputLayerEXT layer, EGLint attribute, EGLAttrib value); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryOutputLayerAttribEXT (EGLDisplay dpy, EGLOutputLayerEXT layer, EGLint attribute, EGLAttrib *value); +EGLAPI const char *EGLAPIENTRY eglQueryOutputLayerStringEXT (EGLDisplay dpy, EGLOutputLayerEXT layer, EGLint name); +EGLAPI EGLBoolean EGLAPIENTRY eglOutputPortAttribEXT (EGLDisplay dpy, EGLOutputPortEXT port, EGLint attribute, EGLAttrib value); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryOutputPortAttribEXT (EGLDisplay dpy, EGLOutputPortEXT port, EGLint attribute, EGLAttrib *value); +EGLAPI const char *EGLAPIENTRY eglQueryOutputPortStringEXT (EGLDisplay dpy, EGLOutputPortEXT port, EGLint name); +#endif +#endif /* EGL_EXT_output_base */ + +#ifndef EGL_EXT_output_drm +#define EGL_EXT_output_drm 1 +#define EGL_DRM_CRTC_EXT 0x3234 +#define EGL_DRM_PLANE_EXT 0x3235 +#define EGL_DRM_CONNECTOR_EXT 0x3236 +#endif /* EGL_EXT_output_drm */ + +#ifndef EGL_EXT_output_openwf +#define EGL_EXT_output_openwf 1 +#define EGL_OPENWF_PIPELINE_ID_EXT 0x3238 +#define EGL_OPENWF_PORT_ID_EXT 0x3239 +#endif /* EGL_EXT_output_openwf */ + +#ifndef EGL_EXT_pixel_format_float +#define EGL_EXT_pixel_format_float 1 +#define EGL_COLOR_COMPONENT_TYPE_EXT 0x3339 +#define EGL_COLOR_COMPONENT_TYPE_FIXED_EXT 0x333A +#define EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT 0x333B +#endif /* EGL_EXT_pixel_format_float */ + +#ifndef EGL_EXT_platform_base +#define EGL_EXT_platform_base 1 +typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYEXTPROC) (EGLenum platform, void *native_display, const EGLint *attrib_list); +typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC) (EGLDisplay dpy, EGLConfig config, void *native_window, const EGLint *attrib_list); +typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPLATFORMPIXMAPSURFACEEXTPROC) (EGLDisplay dpy, EGLConfig config, void *native_pixmap, const EGLint *attrib_list); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLDisplay EGLAPIENTRY eglGetPlatformDisplayEXT (EGLenum platform, void *native_display, const EGLint *attrib_list); +EGLAPI EGLSurface EGLAPIENTRY eglCreatePlatformWindowSurfaceEXT (EGLDisplay dpy, EGLConfig config, void *native_window, const EGLint *attrib_list); +EGLAPI EGLSurface EGLAPIENTRY eglCreatePlatformPixmapSurfaceEXT (EGLDisplay dpy, EGLConfig config, void *native_pixmap, const EGLint *attrib_list); +#endif +#endif /* EGL_EXT_platform_base */ + +#ifndef EGL_EXT_platform_device +#define EGL_EXT_platform_device 1 +#define EGL_PLATFORM_DEVICE_EXT 0x313F +#endif /* EGL_EXT_platform_device */ + +#ifndef EGL_EXT_platform_wayland +#define EGL_EXT_platform_wayland 1 +#define EGL_PLATFORM_WAYLAND_EXT 0x31D8 +#endif /* EGL_EXT_platform_wayland */ + +#ifndef EGL_EXT_platform_x11 +#define EGL_EXT_platform_x11 1 +#define EGL_PLATFORM_X11_EXT 0x31D5 +#define EGL_PLATFORM_X11_SCREEN_EXT 0x31D6 +#endif /* EGL_EXT_platform_x11 */ + +#ifndef EGL_EXT_protected_content +#define EGL_EXT_protected_content 1 +#define EGL_PROTECTED_CONTENT_EXT 0x32C0 +#endif /* EGL_EXT_protected_content */ + +#ifndef EGL_EXT_protected_surface +#define EGL_EXT_protected_surface 1 +#endif /* EGL_EXT_protected_surface */ + +#ifndef EGL_EXT_stream_consumer_egloutput +#define EGL_EXT_stream_consumer_egloutput 1 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMCONSUMEROUTPUTEXTPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLOutputLayerEXT layer); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglStreamConsumerOutputEXT (EGLDisplay dpy, EGLStreamKHR stream, EGLOutputLayerEXT layer); +#endif +#endif /* EGL_EXT_stream_consumer_egloutput */ + +#ifndef EGL_EXT_surface_CTA861_3_metadata +#define EGL_EXT_surface_CTA861_3_metadata 1 +#define EGL_CTA861_3_MAX_CONTENT_LIGHT_LEVEL_EXT 0x3360 +#define EGL_CTA861_3_MAX_FRAME_AVERAGE_LEVEL_EXT 0x3361 +#endif /* EGL_EXT_surface_CTA861_3_metadata */ + +#ifndef EGL_EXT_surface_SMPTE2086_metadata +#define EGL_EXT_surface_SMPTE2086_metadata 1 +#define EGL_SMPTE2086_DISPLAY_PRIMARY_RX_EXT 0x3341 +#define EGL_SMPTE2086_DISPLAY_PRIMARY_RY_EXT 0x3342 +#define EGL_SMPTE2086_DISPLAY_PRIMARY_GX_EXT 0x3343 +#define EGL_SMPTE2086_DISPLAY_PRIMARY_GY_EXT 0x3344 +#define EGL_SMPTE2086_DISPLAY_PRIMARY_BX_EXT 0x3345 +#define EGL_SMPTE2086_DISPLAY_PRIMARY_BY_EXT 0x3346 +#define EGL_SMPTE2086_WHITE_POINT_X_EXT 0x3347 +#define EGL_SMPTE2086_WHITE_POINT_Y_EXT 0x3348 +#define EGL_SMPTE2086_MAX_LUMINANCE_EXT 0x3349 +#define EGL_SMPTE2086_MIN_LUMINANCE_EXT 0x334A +#define EGL_METADATA_SCALING_EXT 50000 +#endif /* EGL_EXT_surface_SMPTE2086_metadata */ + +#ifndef EGL_EXT_swap_buffers_with_damage +#define EGL_EXT_swap_buffers_with_damage 1 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC) (EGLDisplay dpy, EGLSurface surface, EGLint *rects, EGLint n_rects); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglSwapBuffersWithDamageEXT (EGLDisplay dpy, EGLSurface surface, EGLint *rects, EGLint n_rects); +#endif +#endif /* EGL_EXT_swap_buffers_with_damage */ + +#ifndef EGL_EXT_yuv_surface +#define EGL_EXT_yuv_surface 1 +#define EGL_YUV_ORDER_EXT 0x3301 +#define EGL_YUV_NUMBER_OF_PLANES_EXT 0x3311 +#define EGL_YUV_SUBSAMPLE_EXT 0x3312 +#define EGL_YUV_DEPTH_RANGE_EXT 0x3317 +#define EGL_YUV_CSC_STANDARD_EXT 0x330A +#define EGL_YUV_PLANE_BPP_EXT 0x331A +#define EGL_YUV_BUFFER_EXT 0x3300 +#define EGL_YUV_ORDER_YUV_EXT 0x3302 +#define EGL_YUV_ORDER_YVU_EXT 0x3303 +#define EGL_YUV_ORDER_YUYV_EXT 0x3304 +#define EGL_YUV_ORDER_UYVY_EXT 0x3305 +#define EGL_YUV_ORDER_YVYU_EXT 0x3306 +#define EGL_YUV_ORDER_VYUY_EXT 0x3307 +#define EGL_YUV_ORDER_AYUV_EXT 0x3308 +#define EGL_YUV_SUBSAMPLE_4_2_0_EXT 0x3313 +#define EGL_YUV_SUBSAMPLE_4_2_2_EXT 0x3314 +#define EGL_YUV_SUBSAMPLE_4_4_4_EXT 0x3315 +#define EGL_YUV_DEPTH_RANGE_LIMITED_EXT 0x3318 +#define EGL_YUV_DEPTH_RANGE_FULL_EXT 0x3319 +#define EGL_YUV_CSC_STANDARD_601_EXT 0x330B +#define EGL_YUV_CSC_STANDARD_709_EXT 0x330C +#define EGL_YUV_CSC_STANDARD_2020_EXT 0x330D +#define EGL_YUV_PLANE_BPP_0_EXT 0x331B +#define EGL_YUV_PLANE_BPP_8_EXT 0x331C +#define EGL_YUV_PLANE_BPP_10_EXT 0x331D +#endif /* EGL_EXT_yuv_surface */ + +#ifndef EGL_HI_clientpixmap +#define EGL_HI_clientpixmap 1 +struct EGLClientPixmapHI { + void *pData; + EGLint iWidth; + EGLint iHeight; + EGLint iStride; +}; +#define EGL_CLIENT_PIXMAP_POINTER_HI 0x8F74 +typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPIXMAPSURFACEHIPROC) (EGLDisplay dpy, EGLConfig config, struct EGLClientPixmapHI *pixmap); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLSurface EGLAPIENTRY eglCreatePixmapSurfaceHI (EGLDisplay dpy, EGLConfig config, struct EGLClientPixmapHI *pixmap); +#endif +#endif /* EGL_HI_clientpixmap */ + +#ifndef EGL_HI_colorformats +#define EGL_HI_colorformats 1 +#define EGL_COLOR_FORMAT_HI 0x8F70 +#define EGL_COLOR_RGB_HI 0x8F71 +#define EGL_COLOR_RGBA_HI 0x8F72 +#define EGL_COLOR_ARGB_HI 0x8F73 +#endif /* EGL_HI_colorformats */ + +#ifndef EGL_IMG_context_priority +#define EGL_IMG_context_priority 1 +#define EGL_CONTEXT_PRIORITY_LEVEL_IMG 0x3100 +#define EGL_CONTEXT_PRIORITY_HIGH_IMG 0x3101 +#define EGL_CONTEXT_PRIORITY_MEDIUM_IMG 0x3102 +#define EGL_CONTEXT_PRIORITY_LOW_IMG 0x3103 +#endif /* EGL_IMG_context_priority */ + +#ifndef EGL_IMG_image_plane_attribs +#define EGL_IMG_image_plane_attribs 1 +#define EGL_NATIVE_BUFFER_MULTIPLANE_SEPARATE_IMG 0x3105 +#define EGL_NATIVE_BUFFER_PLANE_OFFSET_IMG 0x3106 +#endif /* EGL_IMG_image_plane_attribs */ + +#ifndef EGL_MESA_drm_image +#define EGL_MESA_drm_image 1 +#define EGL_DRM_BUFFER_FORMAT_MESA 0x31D0 +#define EGL_DRM_BUFFER_USE_MESA 0x31D1 +#define EGL_DRM_BUFFER_FORMAT_ARGB32_MESA 0x31D2 +#define EGL_DRM_BUFFER_MESA 0x31D3 +#define EGL_DRM_BUFFER_STRIDE_MESA 0x31D4 +#define EGL_DRM_BUFFER_USE_SCANOUT_MESA 0x00000001 +#define EGL_DRM_BUFFER_USE_SHARE_MESA 0x00000002 +typedef EGLImageKHR (EGLAPIENTRYP PFNEGLCREATEDRMIMAGEMESAPROC) (EGLDisplay dpy, const EGLint *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLEXPORTDRMIMAGEMESAPROC) (EGLDisplay dpy, EGLImageKHR image, EGLint *name, EGLint *handle, EGLint *stride); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLImageKHR EGLAPIENTRY eglCreateDRMImageMESA (EGLDisplay dpy, const EGLint *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglExportDRMImageMESA (EGLDisplay dpy, EGLImageKHR image, EGLint *name, EGLint *handle, EGLint *stride); +#endif +#endif /* EGL_MESA_drm_image */ + +#ifndef EGL_MESA_image_dma_buf_export +#define EGL_MESA_image_dma_buf_export 1 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLEXPORTDMABUFIMAGEQUERYMESAPROC) (EGLDisplay dpy, EGLImageKHR image, int *fourcc, int *num_planes, EGLuint64KHR *modifiers); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLEXPORTDMABUFIMAGEMESAPROC) (EGLDisplay dpy, EGLImageKHR image, int *fds, EGLint *strides, EGLint *offsets); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglExportDMABUFImageQueryMESA (EGLDisplay dpy, EGLImageKHR image, int *fourcc, int *num_planes, EGLuint64KHR *modifiers); +EGLAPI EGLBoolean EGLAPIENTRY eglExportDMABUFImageMESA (EGLDisplay dpy, EGLImageKHR image, int *fds, EGLint *strides, EGLint *offsets); +#endif +#endif /* EGL_MESA_image_dma_buf_export */ + +#ifndef EGL_MESA_platform_gbm +#define EGL_MESA_platform_gbm 1 +#define EGL_PLATFORM_GBM_MESA 0x31D7 +#endif /* EGL_MESA_platform_gbm */ + +#ifndef EGL_MESA_platform_surfaceless +#define EGL_MESA_platform_surfaceless 1 +#define EGL_PLATFORM_SURFACELESS_MESA 0x31DD +#endif /* EGL_MESA_platform_surfaceless */ + +#ifndef EGL_NOK_swap_region +#define EGL_NOK_swap_region 1 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSWAPBUFFERSREGIONNOKPROC) (EGLDisplay dpy, EGLSurface surface, EGLint numRects, const EGLint *rects); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglSwapBuffersRegionNOK (EGLDisplay dpy, EGLSurface surface, EGLint numRects, const EGLint *rects); +#endif +#endif /* EGL_NOK_swap_region */ + +#ifndef EGL_NOK_swap_region2 +#define EGL_NOK_swap_region2 1 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSWAPBUFFERSREGION2NOKPROC) (EGLDisplay dpy, EGLSurface surface, EGLint numRects, const EGLint *rects); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglSwapBuffersRegion2NOK (EGLDisplay dpy, EGLSurface surface, EGLint numRects, const EGLint *rects); +#endif +#endif /* EGL_NOK_swap_region2 */ + +#ifndef EGL_NOK_texture_from_pixmap +#define EGL_NOK_texture_from_pixmap 1 +#define EGL_Y_INVERTED_NOK 0x307F +#endif /* EGL_NOK_texture_from_pixmap */ + +#ifndef EGL_NV_3dvision_surface +#define EGL_NV_3dvision_surface 1 +#define EGL_AUTO_STEREO_NV 0x3136 +#endif /* EGL_NV_3dvision_surface */ + +#ifndef EGL_NV_coverage_sample +#define EGL_NV_coverage_sample 1 +#define EGL_COVERAGE_BUFFERS_NV 0x30E0 +#define EGL_COVERAGE_SAMPLES_NV 0x30E1 +#endif /* EGL_NV_coverage_sample */ + +#ifndef EGL_NV_coverage_sample_resolve +#define EGL_NV_coverage_sample_resolve 1 +#define EGL_COVERAGE_SAMPLE_RESOLVE_NV 0x3131 +#define EGL_COVERAGE_SAMPLE_RESOLVE_DEFAULT_NV 0x3132 +#define EGL_COVERAGE_SAMPLE_RESOLVE_NONE_NV 0x3133 +#endif /* EGL_NV_coverage_sample_resolve */ + +#ifndef EGL_NV_cuda_event +#define EGL_NV_cuda_event 1 +#define EGL_CUDA_EVENT_HANDLE_NV 0x323B +#define EGL_SYNC_CUDA_EVENT_NV 0x323C +#define EGL_SYNC_CUDA_EVENT_COMPLETE_NV 0x323D +#endif /* EGL_NV_cuda_event */ + +#ifndef EGL_NV_depth_nonlinear +#define EGL_NV_depth_nonlinear 1 +#define EGL_DEPTH_ENCODING_NV 0x30E2 +#define EGL_DEPTH_ENCODING_NONE_NV 0 +#define EGL_DEPTH_ENCODING_NONLINEAR_NV 0x30E3 +#endif /* EGL_NV_depth_nonlinear */ + +#ifndef EGL_NV_device_cuda +#define EGL_NV_device_cuda 1 +#define EGL_CUDA_DEVICE_NV 0x323A +#endif /* EGL_NV_device_cuda */ + +#ifndef EGL_NV_native_query +#define EGL_NV_native_query 1 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYNATIVEDISPLAYNVPROC) (EGLDisplay dpy, EGLNativeDisplayType *display_id); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYNATIVEWINDOWNVPROC) (EGLDisplay dpy, EGLSurface surf, EGLNativeWindowType *window); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYNATIVEPIXMAPNVPROC) (EGLDisplay dpy, EGLSurface surf, EGLNativePixmapType *pixmap); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglQueryNativeDisplayNV (EGLDisplay dpy, EGLNativeDisplayType *display_id); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryNativeWindowNV (EGLDisplay dpy, EGLSurface surf, EGLNativeWindowType *window); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryNativePixmapNV (EGLDisplay dpy, EGLSurface surf, EGLNativePixmapType *pixmap); +#endif +#endif /* EGL_NV_native_query */ + +#ifndef EGL_NV_post_convert_rounding +#define EGL_NV_post_convert_rounding 1 +#endif /* EGL_NV_post_convert_rounding */ + +#ifndef EGL_NV_post_sub_buffer +#define EGL_NV_post_sub_buffer 1 +#define EGL_POST_SUB_BUFFER_SUPPORTED_NV 0x30BE +typedef EGLBoolean (EGLAPIENTRYP PFNEGLPOSTSUBBUFFERNVPROC) (EGLDisplay dpy, EGLSurface surface, EGLint x, EGLint y, EGLint width, EGLint height); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglPostSubBufferNV (EGLDisplay dpy, EGLSurface surface, EGLint x, EGLint y, EGLint width, EGLint height); +#endif +#endif /* EGL_NV_post_sub_buffer */ + +#ifndef EGL_NV_robustness_video_memory_purge +#define EGL_NV_robustness_video_memory_purge 1 +#define EGL_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV 0x334C +#endif /* EGL_NV_robustness_video_memory_purge */ + +#ifndef EGL_NV_stream_consumer_gltexture_yuv +#define EGL_NV_stream_consumer_gltexture_yuv 1 +#define EGL_YUV_PLANE0_TEXTURE_UNIT_NV 0x332C +#define EGL_YUV_PLANE1_TEXTURE_UNIT_NV 0x332D +#define EGL_YUV_PLANE2_TEXTURE_UNIT_NV 0x332E +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMCONSUMERGLTEXTUREEXTERNALATTRIBSNVPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLAttrib *attrib_list); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglStreamConsumerGLTextureExternalAttribsNV (EGLDisplay dpy, EGLStreamKHR stream, EGLAttrib *attrib_list); +#endif +#endif /* EGL_NV_stream_consumer_gltexture_yuv */ + +#ifndef EGL_NV_stream_cross_display +#define EGL_NV_stream_cross_display 1 +#define EGL_STREAM_CROSS_DISPLAY_NV 0x334E +#endif /* EGL_NV_stream_cross_display */ + +#ifndef EGL_NV_stream_cross_object +#define EGL_NV_stream_cross_object 1 +#define EGL_STREAM_CROSS_OBJECT_NV 0x334D +#endif /* EGL_NV_stream_cross_object */ + +#ifndef EGL_NV_stream_cross_partition +#define EGL_NV_stream_cross_partition 1 +#define EGL_STREAM_CROSS_PARTITION_NV 0x323F +#endif /* EGL_NV_stream_cross_partition */ + +#ifndef EGL_NV_stream_cross_process +#define EGL_NV_stream_cross_process 1 +#define EGL_STREAM_CROSS_PROCESS_NV 0x3245 +#endif /* EGL_NV_stream_cross_process */ + +#ifndef EGL_NV_stream_cross_system +#define EGL_NV_stream_cross_system 1 +#define EGL_STREAM_CROSS_SYSTEM_NV 0x334F +#endif /* EGL_NV_stream_cross_system */ + +#ifndef EGL_NV_stream_fifo_next +#define EGL_NV_stream_fifo_next 1 +#define EGL_PENDING_FRAME_NV 0x3329 +#define EGL_STREAM_TIME_PENDING_NV 0x332A +#endif /* EGL_NV_stream_fifo_next */ + +#ifndef EGL_NV_stream_fifo_synchronous +#define EGL_NV_stream_fifo_synchronous 1 +#define EGL_STREAM_FIFO_SYNCHRONOUS_NV 0x3336 +#endif /* EGL_NV_stream_fifo_synchronous */ + +#ifndef EGL_NV_stream_frame_limits +#define EGL_NV_stream_frame_limits 1 +#define EGL_PRODUCER_MAX_FRAME_HINT_NV 0x3337 +#define EGL_CONSUMER_MAX_FRAME_HINT_NV 0x3338 +#endif /* EGL_NV_stream_frame_limits */ + +#ifndef EGL_NV_stream_metadata +#define EGL_NV_stream_metadata 1 +#define EGL_MAX_STREAM_METADATA_BLOCKS_NV 0x3250 +#define EGL_MAX_STREAM_METADATA_BLOCK_SIZE_NV 0x3251 +#define EGL_MAX_STREAM_METADATA_TOTAL_SIZE_NV 0x3252 +#define EGL_PRODUCER_METADATA_NV 0x3253 +#define EGL_CONSUMER_METADATA_NV 0x3254 +#define EGL_PENDING_METADATA_NV 0x3328 +#define EGL_METADATA0_SIZE_NV 0x3255 +#define EGL_METADATA1_SIZE_NV 0x3256 +#define EGL_METADATA2_SIZE_NV 0x3257 +#define EGL_METADATA3_SIZE_NV 0x3258 +#define EGL_METADATA0_TYPE_NV 0x3259 +#define EGL_METADATA1_TYPE_NV 0x325A +#define EGL_METADATA2_TYPE_NV 0x325B +#define EGL_METADATA3_TYPE_NV 0x325C +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDISPLAYATTRIBNVPROC) (EGLDisplay dpy, EGLint attribute, EGLAttrib *value); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSETSTREAMMETADATANVPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLint n, EGLint offset, EGLint size, const void *data); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYSTREAMMETADATANVPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLenum name, EGLint n, EGLint offset, EGLint size, void *data); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglQueryDisplayAttribNV (EGLDisplay dpy, EGLint attribute, EGLAttrib *value); +EGLAPI EGLBoolean EGLAPIENTRY eglSetStreamMetadataNV (EGLDisplay dpy, EGLStreamKHR stream, EGLint n, EGLint offset, EGLint size, const void *data); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryStreamMetadataNV (EGLDisplay dpy, EGLStreamKHR stream, EGLenum name, EGLint n, EGLint offset, EGLint size, void *data); +#endif +#endif /* EGL_NV_stream_metadata */ + +#ifndef EGL_NV_stream_remote +#define EGL_NV_stream_remote 1 +#define EGL_STREAM_STATE_INITIALIZING_NV 0x3240 +#define EGL_STREAM_TYPE_NV 0x3241 +#define EGL_STREAM_PROTOCOL_NV 0x3242 +#define EGL_STREAM_ENDPOINT_NV 0x3243 +#define EGL_STREAM_LOCAL_NV 0x3244 +#define EGL_STREAM_PRODUCER_NV 0x3247 +#define EGL_STREAM_CONSUMER_NV 0x3248 +#define EGL_STREAM_PROTOCOL_FD_NV 0x3246 +#endif /* EGL_NV_stream_remote */ + +#ifndef EGL_NV_stream_reset +#define EGL_NV_stream_reset 1 +#define EGL_SUPPORT_RESET_NV 0x3334 +#define EGL_SUPPORT_REUSE_NV 0x3335 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLRESETSTREAMNVPROC) (EGLDisplay dpy, EGLStreamKHR stream); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglResetStreamNV (EGLDisplay dpy, EGLStreamKHR stream); +#endif +#endif /* EGL_NV_stream_reset */ + +#ifndef EGL_NV_stream_socket +#define EGL_NV_stream_socket 1 +#define EGL_STREAM_PROTOCOL_SOCKET_NV 0x324B +#define EGL_SOCKET_HANDLE_NV 0x324C +#define EGL_SOCKET_TYPE_NV 0x324D +#endif /* EGL_NV_stream_socket */ + +#ifndef EGL_NV_stream_socket_inet +#define EGL_NV_stream_socket_inet 1 +#define EGL_SOCKET_TYPE_INET_NV 0x324F +#endif /* EGL_NV_stream_socket_inet */ + +#ifndef EGL_NV_stream_socket_unix +#define EGL_NV_stream_socket_unix 1 +#define EGL_SOCKET_TYPE_UNIX_NV 0x324E +#endif /* EGL_NV_stream_socket_unix */ + +#ifndef EGL_NV_stream_sync +#define EGL_NV_stream_sync 1 +#define EGL_SYNC_NEW_FRAME_NV 0x321F +typedef EGLSyncKHR (EGLAPIENTRYP PFNEGLCREATESTREAMSYNCNVPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLenum type, const EGLint *attrib_list); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLSyncKHR EGLAPIENTRY eglCreateStreamSyncNV (EGLDisplay dpy, EGLStreamKHR stream, EGLenum type, const EGLint *attrib_list); +#endif +#endif /* EGL_NV_stream_sync */ + +#ifndef EGL_NV_sync +#define EGL_NV_sync 1 +typedef void *EGLSyncNV; +typedef khronos_utime_nanoseconds_t EGLTimeNV; +#ifdef KHRONOS_SUPPORT_INT64 +#define EGL_SYNC_PRIOR_COMMANDS_COMPLETE_NV 0x30E6 +#define EGL_SYNC_STATUS_NV 0x30E7 +#define EGL_SIGNALED_NV 0x30E8 +#define EGL_UNSIGNALED_NV 0x30E9 +#define EGL_SYNC_FLUSH_COMMANDS_BIT_NV 0x0001 +#define EGL_FOREVER_NV 0xFFFFFFFFFFFFFFFFull +#define EGL_ALREADY_SIGNALED_NV 0x30EA +#define EGL_TIMEOUT_EXPIRED_NV 0x30EB +#define EGL_CONDITION_SATISFIED_NV 0x30EC +#define EGL_SYNC_TYPE_NV 0x30ED +#define EGL_SYNC_CONDITION_NV 0x30EE +#define EGL_SYNC_FENCE_NV 0x30EF +#define EGL_NO_SYNC_NV EGL_CAST(EGLSyncNV,0) +typedef EGLSyncNV (EGLAPIENTRYP PFNEGLCREATEFENCESYNCNVPROC) (EGLDisplay dpy, EGLenum condition, const EGLint *attrib_list); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYSYNCNVPROC) (EGLSyncNV sync); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLFENCENVPROC) (EGLSyncNV sync); +typedef EGLint (EGLAPIENTRYP PFNEGLCLIENTWAITSYNCNVPROC) (EGLSyncNV sync, EGLint flags, EGLTimeNV timeout); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSIGNALSYNCNVPROC) (EGLSyncNV sync, EGLenum mode); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLGETSYNCATTRIBNVPROC) (EGLSyncNV sync, EGLint attribute, EGLint *value); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLSyncNV EGLAPIENTRY eglCreateFenceSyncNV (EGLDisplay dpy, EGLenum condition, const EGLint *attrib_list); +EGLAPI EGLBoolean EGLAPIENTRY eglDestroySyncNV (EGLSyncNV sync); +EGLAPI EGLBoolean EGLAPIENTRY eglFenceNV (EGLSyncNV sync); +EGLAPI EGLint EGLAPIENTRY eglClientWaitSyncNV (EGLSyncNV sync, EGLint flags, EGLTimeNV timeout); +EGLAPI EGLBoolean EGLAPIENTRY eglSignalSyncNV (EGLSyncNV sync, EGLenum mode); +EGLAPI EGLBoolean EGLAPIENTRY eglGetSyncAttribNV (EGLSyncNV sync, EGLint attribute, EGLint *value); +#endif +#endif /* KHRONOS_SUPPORT_INT64 */ +#endif /* EGL_NV_sync */ + +#ifndef EGL_NV_system_time +#define EGL_NV_system_time 1 +typedef khronos_utime_nanoseconds_t EGLuint64NV; +#ifdef KHRONOS_SUPPORT_INT64 +typedef EGLuint64NV (EGLAPIENTRYP PFNEGLGETSYSTEMTIMEFREQUENCYNVPROC) (void); +typedef EGLuint64NV (EGLAPIENTRYP PFNEGLGETSYSTEMTIMENVPROC) (void); +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLuint64NV EGLAPIENTRY eglGetSystemTimeFrequencyNV (void); +EGLAPI EGLuint64NV EGLAPIENTRY eglGetSystemTimeNV (void); +#endif +#endif /* KHRONOS_SUPPORT_INT64 */ +#endif /* EGL_NV_system_time */ + +#ifndef EGL_TIZEN_image_native_buffer +#define EGL_TIZEN_image_native_buffer 1 +#define EGL_NATIVE_BUFFER_TIZEN 0x32A0 +#endif /* EGL_TIZEN_image_native_buffer */ + +#ifndef EGL_TIZEN_image_native_surface +#define EGL_TIZEN_image_native_surface 1 +#define EGL_NATIVE_SURFACE_TIZEN 0x32A1 +#endif /* EGL_TIZEN_image_native_surface */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/third_party/GL/include/EGL/eglplatform.h b/third_party/GL/include/EGL/eglplatform.h new file mode 100644 index 00000000..0f1255b1 --- /dev/null +++ b/third_party/GL/include/EGL/eglplatform.h @@ -0,0 +1,142 @@ +#ifndef __eglplatform_h_ +#define __eglplatform_h_ + +/* +** Copyright (c) 2007-2016 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. +*/ + +/* Platform-specific types and definitions for egl.h + * $Revision: 30994 $ on $Date: 2015-04-30 13:36:48 -0700 (Thu, 30 Apr 2015) $ + * + * Adopters may modify khrplatform.h and this file to suit their platform. + * You are encouraged to submit all modifications to the Khronos group so that + * they can be included in future versions of this file. Please submit changes + * by sending them to the public Khronos Bugzilla (http://khronos.org/bugzilla) + * by filing a bug against product "EGL" component "Registry". + */ + +#include + +/* Macros used in EGL function prototype declarations. + * + * EGL functions should be prototyped as: + * + * EGLAPI return-type EGLAPIENTRY eglFunction(arguments); + * typedef return-type (EXPAPIENTRYP PFNEGLFUNCTIONPROC) (arguments); + * + * KHRONOS_APICALL and KHRONOS_APIENTRY are defined in KHR/khrplatform.h + */ + +#ifndef EGLAPI +#define EGLAPI KHRONOS_APICALL +#endif + +#ifndef EGLAPIENTRY +#define EGLAPIENTRY KHRONOS_APIENTRY +#endif +#define EGLAPIENTRYP EGLAPIENTRY* + +/* The types NativeDisplayType, NativeWindowType, and NativePixmapType + * are aliases of window-system-dependent types, such as X Display * or + * Windows Device Context. They must be defined in platform-specific + * code below. The EGL-prefixed versions of Native*Type are the same + * types, renamed in EGL 1.3 so all types in the API start with "EGL". + * + * Khronos STRONGLY RECOMMENDS that you use the default definitions + * provided below, since these changes affect both binary and source + * portability of applications using EGL running on different EGL + * implementations. + */ + +#if defined(_WIN32) || defined(__VC32__) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) /* Win32 and WinCE */ +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include + +typedef HDC EGLNativeDisplayType; +typedef HBITMAP EGLNativePixmapType; +typedef HWND EGLNativeWindowType; + +#elif defined(__APPLE__) || defined(__WINSCW__) || defined(__SYMBIAN32__) /* Symbian */ + +typedef int EGLNativeDisplayType; +typedef void *EGLNativeWindowType; +typedef void *EGLNativePixmapType; + +#elif defined(__ANDROID__) || defined(ANDROID) + +struct ANativeWindow; +struct egl_native_pixmap_t; + +typedef struct ANativeWindow* EGLNativeWindowType; +typedef struct egl_native_pixmap_t* EGLNativePixmapType; +typedef void* EGLNativeDisplayType; + +#elif defined(__unix__) + +#ifdef MESA_EGL_NO_X11_HEADERS + +typedef void *EGLNativeDisplayType; +typedef khronos_uintptr_t EGLNativePixmapType; +typedef khronos_uintptr_t EGLNativeWindowType; + +#else + +/* X11 (tentative) */ +#include +#include + +typedef Display *EGLNativeDisplayType; +typedef Pixmap EGLNativePixmapType; +typedef Window EGLNativeWindowType; + +#endif /* MESA_EGL_NO_X11_HEADERS */ + +#else +#error "Platform not recognized" +#endif + +/* EGL 1.2 types, renamed for consistency in EGL 1.3 */ +typedef EGLNativeDisplayType NativeDisplayType; +typedef EGLNativePixmapType NativePixmapType; +typedef EGLNativeWindowType NativeWindowType; + + +/* Define EGLint. This must be a signed integral type large enough to contain + * all legal attribute names and values passed into and out of EGL, whether + * their type is boolean, bitmask, enumerant (symbolic constant), integer, + * handle, or other. While in general a 32-bit integer will suffice, if + * handles are 64 bit types, then EGLint should be defined as a signed 64-bit + * integer type. + */ +typedef khronos_int32_t EGLint; + + +/* C++ / C typecast macros for special EGL handle values */ +#if defined(__cplusplus) +#define EGL_CAST(type, value) (static_cast(value)) +#else +#define EGL_CAST(type, value) ((type) (value)) +#endif + +#endif /* __eglplatform_h */ diff --git a/third_party/GL/util/BUILD b/third_party/GL/util/BUILD new file mode 100644 index 00000000..b1133eea --- /dev/null +++ b/third_party/GL/util/BUILD @@ -0,0 +1,23 @@ +# Description: +# EGL Utility function required to correctly create and initialize an EGL +# display on multi-GPU machines. + +licenses(["notice"]) # Apache 2.0 (Google-authored) + +cc_library( + name = "egl_util", + srcs = ["egl_util.cc"], + hdrs = ["egl_util.h"], + visibility = ["//visibility:public"], + deps = ["//third_party/GL:EGL_headers_sys"], +) + +cc_test( + name = "egl_util_test", + srcs = ["egl_util_test.cc"], + deps = [ + ":egl_util", + "//third_party/GL:EGL_headers_sys", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/third_party/GL/util/LICENSE b/third_party/GL/util/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/third_party/GL/util/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/third_party/GL/util/egl_util.cc b/third_party/GL/util/egl_util.cc new file mode 100644 index 00000000..b3e038ab --- /dev/null +++ b/third_party/GL/util/egl_util.cc @@ -0,0 +1,159 @@ +// Copyright 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "third_party/GL/util/egl_util.h" + +#include +#include + +#include +#include +#include +#include +#include + +namespace { + +// Maximum number of EGL devices to query. Currently the maximum number of +// devices a machine could have is 16, but we allow space for 32. +constexpr int kMaxDevices = 32; + +// Helper function for loading EGL extensions. +template +T LoadEGLFunction(const char* func_name) { + using VoidFunc = void(); + T func; + *reinterpret_cast(&func) = eglGetProcAddress(func_name); + if (func == nullptr) { + std::cerr << "Failed to load EGL function " << func_name << "\n"; + } + return func; +} + +// Mutex used to lock the display_reference_map and eglInitialize/egTerminate +// calls. +std::mutex* get_display_mutex() { + static std::mutex* display_reference_mutex = new std::mutex(); + return display_reference_mutex; +} + +std::unordered_map* get_display_reference_map() { + static std::unordered_map* display_reference_map = + new std::unordered_map(); + return display_reference_map; +} + +void IncrementDisplayRefCount(EGLDisplay display) { + auto* display_map = get_display_reference_map(); + auto iter_inserted = display_map->emplace(display, 0).first; + ++iter_inserted->second; +} + +// Helper to decrement reference count for provided EGLDisplay. Returns the +// reference count after decrementing the provided counter. If the EGLDisplay is +// not found, return -1. +int DecrementDisplayRefCount(EGLDisplay display) { + auto* display_map = get_display_reference_map(); + auto it = display_map->find(display); + if (it != display_map->end()) { + int ref_count = --it->second; + if (ref_count == 0) display_map->erase(it); + return ref_count; + } else { + return -1; + } +} + +EGLBoolean TerminateInitializedEGLDisplayNoLock(EGLDisplay display) { + if (display == EGL_NO_DISPLAY) { + return eglTerminate(display); + } + int ref_count = DecrementDisplayRefCount(display); + if (ref_count == 0) { + return eglTerminate(display); + } else if (ref_count > 0) { + return EGL_TRUE; + } else { + std::cerr << "Could not find EGLDisplay Reference count! Either we didn't " + "create EGLDisplay with CreateInitializedEGLDisplay() or we " + "have already terminated the display.\n"; + return EGL_FALSE; + } +} + +} // namespace + +extern "C" EGLDisplay CreateInitializedEGLDisplayAtIndex(int device_index) { + // Load EGL extension functions for querying EGL devices manually. This + // extension isn't officially supported in EGL 1.4, so try and manually + // load them using eglGetProcAddress. + auto eglQueryDevicesEXT = + LoadEGLFunction("eglQueryDevicesEXT"); + if (eglQueryDevicesEXT == nullptr) return EGL_NO_DISPLAY; + + auto eglGetPlatformDisplayEXT = + LoadEGLFunction( + "eglGetPlatformDisplayEXT"); + if (eglGetPlatformDisplayEXT == nullptr) return EGL_NO_DISPLAY; + + EGLDeviceEXT egl_devices[kMaxDevices]; + EGLint num_devices = 0; + auto egl_error = eglGetError(); + if (!eglQueryDevicesEXT(kMaxDevices, egl_devices, &num_devices) || + egl_error != EGL_SUCCESS) { + std::cerr << "eglQueryDevicesEXT Failed. EGL error " << std::hex + << eglGetError() << "\n"; + return EGL_NO_DISPLAY; + } + + // Go through each device and try to initialize the display. + for (EGLint i = 0; i < num_devices; ++i) { + // First try and get a valid EGL display. + auto display = eglGetPlatformDisplayEXT(EGL_PLATFORM_DEVICE_EXT, + egl_devices[i], nullptr); + if (eglGetError() == EGL_SUCCESS && display != EGL_NO_DISPLAY) { + // Aquire lock before calling eglInitialize() and incrementing ref count. + std::lock_guard display_guard(*get_display_mutex()); + + // Now try to initialize the display. This can fail when we don't have + // access to the device. + int major, minor; + EGLBoolean initialized = eglInitialize(display, &major, &minor); + if (eglGetError() == EGL_SUCCESS && initialized == EGL_TRUE) { + IncrementDisplayRefCount(display); + if (--device_index < 0) { + return display; + } else { + TerminateInitializedEGLDisplayNoLock(display); + } + } + } + } + + std::cerr << "Failed to create and initialize a valid EGL display! " + << "Devices tried: " << num_devices << "\n"; + return EGL_NO_DISPLAY; +} + +extern "C" EGLDisplay CreateInitializedEGLDisplay() { + return CreateInitializedEGLDisplayAtIndex(0); +} + +extern "C" EGLBoolean TerminateInitializedEGLDisplay(EGLDisplay display) { + // Acquire lock before terminating and decrementing display ref count. + std::lock_guard display_guard(*get_display_mutex()); + return TerminateInitializedEGLDisplayNoLock(display); +} diff --git a/third_party/GL/util/egl_util.h b/third_party/GL/util/egl_util.h new file mode 100644 index 00000000..26cd228a --- /dev/null +++ b/third_party/GL/util/egl_util.h @@ -0,0 +1,48 @@ +// Copyright 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef THIRD_PARTY_GL_UTIL_EGL_UTIL_H_ +#define THIRD_PARTY_GL_UTIL_EGL_UTIL_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Creates and initializes an EGL display at the specified device_index. Unlike +// the standard eglGetDisplay(), this function takes a device_index, iterates +// through all the available devices on the machine using EGL extensions, and +// returns the Nth successfully initialized EGLDisplay. This allows us to get a +// valid EGL display on multi-GPU machines, where we limit access to a sub-set +// of the available GPU devices. Returns an initialized EGLDisplay or +// EGL_NO_DISPLAY on error. +EGLDisplay CreateInitializedEGLDisplayAtIndex(int device_index); + +// Helper function to create EGL display at device index 0. +EGLDisplay CreateInitializedEGLDisplay(); + +// Helper function to only call eglTerminate() once all instances created from +// CreateInitializedEGLDisplay() have been terminated. This is necessary because +// calling eglTerminate will invalidate *all* contexts associated with a given +// display within the same address space. +EGLBoolean TerminateInitializedEGLDisplay(EGLDisplay display); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // THIRD_PARTY_GL_UTIL_EGL_UTIL_H_ diff --git a/third_party/GL/util/egl_util_test.cc b/third_party/GL/util/egl_util_test.cc new file mode 100644 index 00000000..6facf688 --- /dev/null +++ b/third_party/GL/util/egl_util_test.cc @@ -0,0 +1,301 @@ +// Copyright 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "third_party/GL/util/egl_util.h" + +#include +#include +#include +#include +#include +#include + +#include "gtest/gtest.h" + +namespace { + +// typedefs for other required EGL functions. +using EGLGetErrorFunc = EGLint (*)(void); +using EGLGetProcAddressFunc = + __eglMustCastToProperFunctionPointerType (*)(const char* func_name); +using EGLInitializeFunc = EGLBoolean (*)(EGLDisplay display, EGLint* major, + EGLint* minor); +using EGLTerminateFunc = EGLBoolean (*)(EGLDisplay display); + +// Global function pointers that we can override at runtime. +EGLGetErrorFunc egl_get_error_func = nullptr; +EGLGetProcAddressFunc egl_get_proc_address_func = nullptr; +EGLInitializeFunc egl_initialize_func = nullptr; +EGLTerminateFunc egl_terminate_func = nullptr; + +PFNEGLQUERYDEVICESEXTPROC egl_query_devices_ext_proc = nullptr; +PFNEGLGETPLATFORMDISPLAYEXTPROC egl_get_platform_display_ext_proc = nullptr; + +// Implemention for EGL functions used in egl_util.cc. These forward to test +// functions defined at runtime that can be mocked in unit tests. +extern "C" { +EGLAPI EGLint EGLAPIENTRY eglGetError(void) { + if (egl_get_error_func == nullptr) std::abort(); + return egl_get_error_func(); +} + +EGLAPI __eglMustCastToProperFunctionPointerType EGLAPIENTRY +eglGetProcAddress(const char* func_name) { + if (egl_get_proc_address_func == nullptr) std::abort(); + return egl_get_proc_address_func(func_name); +} + +EGLAPI EGLBoolean EGLAPIENTRY eglInitialize(EGLDisplay dpy, EGLint* major, + EGLint* minor) { + if (egl_initialize_func == nullptr) std::abort(); + return egl_initialize_func(dpy, major, minor); +} + +EGLAPI EGLBoolean EGLAPIENTRY eglTerminate(EGLDisplay dpy) { + if (egl_terminate_func == nullptr) std::abort(); + return egl_terminate_func(dpy); +} +} // extern "C" + +// Testing implementation of EGLDisplay. Stores ID which can be used to identify +// a given device. +struct EGLTestDisplay { + int id; +}; + +// Testing implementation of EGLDevice. Stores ID which can be used to identify +// a given device. +struct EGLTestDevice { + int id; + EGLTestDisplay display; +}; + +// Device ID's set for each device. Used to check we initialized the correct +// device in tests. +constexpr int kTestDeviceId0 = 0; +constexpr int kTestDeviceId1 = 1; +constexpr int kTestDeviceId2 = 2; + +// Default array of EGLDevices, used in EGL function implementations. +static EGLTestDevice egl_test_devices[] = {{kTestDeviceId0, {kTestDeviceId0}}, + {kTestDeviceId1, {kTestDeviceId1}}, + {kTestDeviceId2, {kTestDeviceId2}}}; +constexpr std::size_t kNumDevices = + sizeof(egl_test_devices) / sizeof(egl_test_devices[0]); + +__eglMustCastToProperFunctionPointerType DefaultEGLGetProcAddress( + const char* func_name) { + if (std::strcmp(func_name, "eglQueryDevicesEXT") == 0) { + return reinterpret_cast<__eglMustCastToProperFunctionPointerType>( + egl_query_devices_ext_proc); + } else if (std::strcmp(func_name, "eglGetPlatformDisplayEXT") == 0) { + return reinterpret_cast<__eglMustCastToProperFunctionPointerType>( + egl_get_platform_display_ext_proc); + } else { + return nullptr; + } +} + +EGLBoolean DefaultEGLInitialize(EGLDisplay display, EGLint* major, + EGLint* minor) { + if (major) *major = 1; + if (minor) *minor = 4; + return std::find_if(std::begin(egl_test_devices), std::end(egl_test_devices), + [display](const EGLTestDevice& device) { + return &device.display == display; + }) != std::end(egl_test_devices); +} + +EGLBoolean DefaultEGLTerminate(EGLDisplay display) { + if (display == EGL_NO_DISPLAY) return EGL_FALSE; + return std::find_if(std::begin(egl_test_devices), std::end(egl_test_devices), + [display](const EGLTestDevice& device) { + return &device.display == display; + }) != std::end(egl_test_devices); +} + +EGLBoolean DefaultEGLQueryDevicesEXT(EGLint max_devices, EGLDeviceEXT* devices, + EGLint* num_devices) { + if (devices == nullptr) std::abort(); + if (num_devices == nullptr) std::abort(); + + *num_devices = std::min( + max_devices, + std::distance(std::begin(egl_test_devices), std::end(egl_test_devices))); + std::transform(egl_test_devices, egl_test_devices + *num_devices, devices, + [](EGLTestDevice& x) { return &x; }); + return EGL_TRUE; +} + +EGLDisplay DefaultEGLGetPlatformDisplayEXT(EGLenum platform, void* device_ext, + const EGLint* attrib_list) { + if (platform == EGL_PLATFORM_DEVICE_EXT) { + auto device_it = + std::find_if(std::begin(egl_test_devices), std::end(egl_test_devices), + [device_ext](const EGLTestDevice& device) { + return &device == device_ext; + }); + if (device_it != std::end(egl_test_devices)) { + return &device_it->display; + } + } + return EGL_NO_DISPLAY; +} + +// Fixture to reset EGL function pointers to default implementations for each +// test. +class EGLUtilTest : public ::testing::Test { + protected: + void SetUp() override { + egl_get_error_func = []() -> EGLint { return EGL_SUCCESS; }; + egl_get_proc_address_func = DefaultEGLGetProcAddress; + egl_initialize_func = DefaultEGLInitialize; + egl_terminate_func = DefaultEGLTerminate; + egl_query_devices_ext_proc = DefaultEGLQueryDevicesEXT; + egl_get_platform_display_ext_proc = DefaultEGLGetPlatformDisplayEXT; + } + + void TearDown() override { + egl_get_error_func = nullptr; + egl_get_proc_address_func = nullptr; + egl_initialize_func = nullptr; + egl_query_devices_ext_proc = nullptr; + egl_get_platform_display_ext_proc = nullptr; + } +}; + +TEST_F(EGLUtilTest, CheckCreateEGLDisplay) { + auto display = CreateInitializedEGLDisplay(); + ASSERT_NE(EGL_NO_DISPLAY, display); + + // Expect to get first display. + EXPECT_EQ(kTestDeviceId0, static_cast(display)->id); + EXPECT_EQ(EGL_TRUE, TerminateInitializedEGLDisplay(display)); +} + +TEST_F(EGLUtilTest, CheckNoDevices) { + egl_query_devices_ext_proc = [](EGLint max_devices, EGLDeviceEXT* devices, + EGLint* num_devices) -> EGLBoolean { + if (num_devices == nullptr) std::abort(); + *num_devices = 0; + return EGL_TRUE; + }; + + EXPECT_EQ(EGL_NO_DISPLAY, CreateInitializedEGLDisplay()); + + egl_query_devices_ext_proc = [](EGLint, EGLDeviceEXT*, + EGLint*) -> EGLBoolean { return EGL_FALSE; }; + + EXPECT_EQ(EGL_NO_DISPLAY, CreateInitializedEGLDisplay()); +} + +TEST_F(EGLUtilTest, CheckFailExtensions) { + egl_get_proc_address_func = + [](const char*) -> __eglMustCastToProperFunctionPointerType { + return nullptr; + }; + EXPECT_EQ(EGL_NO_DISPLAY, CreateInitializedEGLDisplay()); + + // Only fail for eglGetPlatformDisplayEXT + egl_get_proc_address_func = + [](const char* func_name) -> __eglMustCastToProperFunctionPointerType { + if (std::strcmp(func_name, "eglGetPlatformDisplayEXT") == 0) { + return nullptr; + } else { + return DefaultEGLGetProcAddress(func_name); + } + }; + EXPECT_EQ(EGL_NO_DISPLAY, CreateInitializedEGLDisplay()); +} + +TEST_F(EGLUtilTest, CheckFailInitialize) { + egl_initialize_func = [](EGLDisplay, EGLint*, EGLint*) -> EGLBoolean { + return EGL_FALSE; + }; + + EXPECT_EQ(EGL_NO_DISPLAY, CreateInitializedEGLDisplay()); +} + +TEST_F(EGLUtilTest, CheckUnavailableDevices) { + // Only return EGLDisplay for device1 and fail others. + egl_get_platform_display_ext_proc = []( + EGLenum platform, void* device_ext, + const EGLint* attrib_list) -> EGLDisplay { + auto* test_device = static_cast(device_ext); + if (platform == EGL_PLATFORM_DEVICE_EXT && + test_device->id == kTestDeviceId1) { + return &test_device->display; + } + return EGL_NO_DISPLAY; + }; + auto display = CreateInitializedEGLDisplay(); + ASSERT_NE(EGL_NO_DISPLAY, display); + EXPECT_EQ(kTestDeviceId1, static_cast(display)->id); + + // Now test EGLInitialize only succeeds for device2. This is the usual way to + // determine if an EGLDisplay is actually usable, as eglGetPlatformDisplayEXT + // usually succeeds, even if we can't actually use the device. + egl_get_platform_display_ext_proc = DefaultEGLGetPlatformDisplayEXT; + egl_initialize_func = [](EGLDisplay display, EGLint* major, + EGLint* minor) -> EGLBoolean { + auto* test_display = static_cast(display); + if (test_display && test_display->id == kTestDeviceId2) { + return DefaultEGLInitialize(display, major, minor); + } + return EGL_FALSE; + }; + display = CreateInitializedEGLDisplay(); + ASSERT_NE(EGL_NO_DISPLAY, display); + EXPECT_EQ(kTestDeviceId2, static_cast(display)->id); + EXPECT_EQ(EGL_TRUE, TerminateInitializedEGLDisplay(display)); +} + +TEST_F(EGLUtilTest, CheckTerminateRefCounting) { + static int call_count = 0; + egl_terminate_func = [](EGLDisplay display) -> EGLBoolean { + if (display == EGL_NO_DISPLAY) return EGL_FALSE; + call_count++; + return EGL_TRUE; + }; + auto d1 = CreateInitializedEGLDisplay(); + ASSERT_NE(EGL_NO_DISPLAY, d1); + + auto d2 = CreateInitializedEGLDisplay(); + EXPECT_EQ(d1, d2); + + EXPECT_EQ(EGL_TRUE, TerminateInitializedEGLDisplay(d1)); + EXPECT_EQ(EGL_TRUE, TerminateInitializedEGLDisplay(d2)); + EXPECT_EQ(1, call_count); + + // Try and terminate again. Should fail. + EXPECT_EQ(EGL_FALSE, TerminateInitializedEGLDisplay(d1)); +} + +TEST_F(EGLUtilTest, CheckInitializeDisplayAtIndex) { + auto d0 = CreateInitializedEGLDisplayAtIndex(0); + ASSERT_NE(EGL_NO_DISPLAY, d0); + + auto d1 = CreateInitializedEGLDisplayAtIndex(1); + EXPECT_NE(d0, d1); + + auto invalid_device = CreateInitializedEGLDisplayAtIndex(kNumDevices+1); + EXPECT_EQ(EGL_NO_DISPLAY, invalid_device); + + EXPECT_EQ(EGL_TRUE, TerminateInitializedEGLDisplay(d0)); + EXPECT_EQ(EGL_TRUE, TerminateInitializedEGLDisplay(d1)); +} + +} // namespace diff --git a/third_party/rl_api/BUILD b/third_party/rl_api/BUILD index 9ff2368b..1bed2ec0 100644 --- a/third_party/rl_api/BUILD +++ b/third_party/rl_api/BUILD @@ -3,12 +3,21 @@ licenses(["notice"]) # Apache 2.0 (Google-authored) +exports_files(["LICENSE"]) + cc_library( name = "env_c_api", hdrs = ["env_c_api.h"], visibility = ["//visibility:public"], ) +cc_library( + name = "env_c_api_bind", + hdrs = ["env_c_api_bind.h"], + visibility = ["//visibility:public"], + deps = [":env_c_api"], +) + cc_binary( name = "libenv_c_api_example.so", testonly = 1, @@ -19,9 +28,9 @@ cc_binary( ], linkshared = 1, linkstatic = 1, - visibility = ["//visibility:public"], deps = [ ":env_c_api", + ":env_c_api_bind", ":env_c_api_example.lds", ], ) @@ -36,6 +45,6 @@ cc_test( ], deps = [ ":env_c_api", - "@googletest//:gtest_main", + "@com_google_googletest//:gtest_main", ], ) diff --git a/third_party/rl_api/env_c_api.h b/third_party/rl_api/env_c_api.h index 2aeb8fa8..431884a2 100644 --- a/third_party/rl_api/env_c_api.h +++ b/third_party/rl_api/env_c_api.h @@ -1,4 +1,4 @@ -// Copyright 2016 Google Inc. +// Copyright 2016-2017 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -53,7 +53,7 @@ #define DEEPMIND_ENV_C_API_H_ #define DEEPMIND_ENV_C_API_VERSION_MAJOR 1 -#define DEEPMIND_ENV_C_API_VERSION_MINOR 0 +#define DEEPMIND_ENV_C_API_VERSION_MINOR 2 #include @@ -77,13 +77,13 @@ typedef struct EnvCApi_Observation_s EnvCApi_Observation; typedef struct EnvCApi_Event_s EnvCApi_Event; // The status of an environment. This status changes as the environment evolves. -// If the status is not 'EnvCApi_EnvironmentStatus_Running' or -// 'EnvCApi_EnvironmentStatus_Interrupted' the environment has to be reset -// before further use, or destroyed. +// The meaning of the status values is as follows, but see also the "Run loop" +// section below for details. // -// If the status is 'EnvCApi_EnvironmentStatus_Interrupted' 'act' and 'advance' -// can no longer be called. -// See below for details. +// * Running: An episode is currently running. Actions can be taken. +// * Terminated: An episode has reached a terminal state and has thus ended. +// * Interrupted: A running episode has ended without reaching a terminal state. +// * Error: An error has occurred, the episode is no longer active. enum EnvCApi_EnvironmentStatus_enum { EnvCApi_EnvironmentStatus_Running, EnvCApi_EnvironmentStatus_Interrupted, @@ -95,14 +95,17 @@ typedef enum EnvCApi_EnvironmentStatus_enum EnvCApi_EnvironmentStatus; // The fundamental data type of an observation; part of the observation spec. enum EnvCApi_ObservationType_enum { EnvCApi_ObservationDoubles, - EnvCApi_ObservationBytes + EnvCApi_ObservationBytes, + EnvCApi_ObservationString }; typedef enum EnvCApi_ObservationType_enum EnvCApi_ObservationType; -// An observation is a multidimensional array of numbers. The data type of the -// numbers is described by "type" (int or double). The data is serialized as a +// An observation is either a string or a multidimensional array of numbers. The +// data type of the numbers is described by "type". The data is serialized as a // flat array of size shape[0] * shape[1] * ... * shape[dims - 1], laid out in -// strides in row-major (C-style) order. +// strides in row-major (C-style) order. If the "type" is +// "EnvCApi_ObservationString" then the "dims" shall be 1 and "shape[0]" the +// length of the string. struct EnvCApi_ObservationSpec_s { EnvCApi_ObservationType type; int dims; @@ -115,6 +118,7 @@ struct EnvCApi_Observation_s { union { const double* doubles; const unsigned char* bytes; + const char* string; } payload; }; @@ -156,20 +160,25 @@ struct EnvCApi_s { // Applies a setting to the environment. Can be called multiple times. If the // same key is used more than once, the last key-value pair is used. This - // function shall only be called before init, and it shall return zero - // on success and non-zero on failure. + // function shall only be called before 'init', and it shall return zero + // on success and non-zero on failure. On failure an associated error message + // maybe retrieved by calling 'error_message'. int (*setting)(void* context, const char* key, const char* value); - // Shall be called after all calls to setting and before init. It shall return - // zero on success and non-zero on failure. Most functions from this API shall - // only be called after a successful call of init; see their individual - // documentation. + // Shall be called after all calls of 'setting' and before 'start'. It shall + // return zero on success and non-zero on failure. Most functions from this + // API shall only be called after a successful call of 'init'; see their + // individual documentation. On failure an associated error message + // maybe retrieved by calling 'error_message'. int (*init)(void* context); - // Launches an episode using 'episode_id' and 'seed'. Returns zero on success - // and non-zero on failure. Some functions from this API shall only be called - // after a successful call of start; see their individual documentation. - // This function shall only be called after a successful call of init. + // Launches an episode using 'episode_id' and 'seed'. Returns zero on success, + // or EAGAIN (from ), or any other non-zero value on failure. If + // EAGAIN is returned, this function may be called again until successful. + // Some functions from this API shall only be called after a successful call + // of 'start'; see their individual documentation. On failure an associated + // error message maybe retrieved by calling 'error_message'. + // This function shall only be called after a successful call of 'init'. int (*start)(void* context, int episode_id, int seed); // Releases the resources associated with 'context', which shall not be used @@ -177,12 +186,17 @@ struct EnvCApi_s { // is created, only how it is destroyed.) void (*release_context)(void* context); + // Returns an error message associated with a failed call. + // Shall only be called immediately a failed call to 'setting', 'init', + // 'start' or 'advance'. + const char* (*error_message)(void* context); + // Meta data querying ///////////////////// // // Functions in this section shall only be called after a successful call of - // init, but beyond that they may be called at any time, regardless of whether - // start has been called or whether the episode has terminated. + // 'init', but beyond that they may be called at any time, regardless of + // whether 'start' has been called or whether the episode has terminated. // Name of the environment. const char* (*environment_name)(void* context); @@ -251,7 +265,7 @@ struct EnvCApi_s { /////////// // // Functions in this section shall only be called after a successful call of - // start. Moreover, if 'advance' is called, no further function from this + // 'start'. Moreover, if 'advance' is called, no further function from this // section shall be called unless the returned status was "Running". // Writes the observation at the given index to '*obs' @@ -288,7 +302,8 @@ struct EnvCApi_s { // The reward accrued during this call shall be stored in '*reward' (even if // it is zero). The return value describes the new status of the environment. // If it is anything other than "Running", the episode is over, and 'start' - // needs to be called to start a new episode. + // needs to be called to start a new episode. If the returned status was + // Error, an associated message maybe retrieved by calling 'error_message'. EnvCApi_EnvironmentStatus (*advance)(void* context, int num_steps, double* reward); }; diff --git a/third_party/rl_api/env_c_api_bind.h b/third_party/rl_api/env_c_api_bind.h new file mode 100644 index 00000000..8185831c --- /dev/null +++ b/third_party/rl_api/env_c_api_bind.h @@ -0,0 +1,121 @@ +// Copyright 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef THIRD_PARTY_RL_API_ENV_C_API_BIND_H_ +#define THIRD_PARTY_RL_API_ENV_C_API_BIND_H_ + +#include + +#include "third_party/rl_api/env_c_api.h" + +namespace deepmind { +namespace rl_api { + +// Binds all function pointers in `api` with the equivalent "CamelCased" member +// functions in `env`, with the exception of `release_context` (see below). + +// Example: +// +// EnvCApi api; +// void* context; +// std::unique_ptr env(new MyClass); +// deepmind::rl_api::Bind(std::move(env), &env_c_api, &context); +// +// Will set the function pointers in `api`: +// +// int (*setting) (void* context, const char* key, const char* value); +// const char* (*environment_name) (void* context); +// // etc. +// +// To functions equivalent to: +// +// static int setting(void* context, const char* key, const char* value) { +// return static_cast(context)->Setting(key, value); +// } +// +// static const char* environment_name(void* context) { +// return static_cast(context)->EnvironmentName(); +// } +// // etc. +// +// Portability note. The instantiated functions do not use C-language linkage +// and may not work. +// +// The `context` now owns the environment, and the environment must be released +// via api->release_context(`context`). This does not call MyClass::Release and +// instead deletes the `context`. +template +void Bind(std::unique_ptr env, EnvCApi* api, void** context); + +namespace internal { + +template +struct CtxBinder { + template + static Ret Func(void* context, Args... args) { + return (static_cast(context)->*MemFunc)(args...); + } + + template + static Ret Func(void* context, Args... args) { + return (static_cast(context)->*MemFunc)(args...); + } +}; + +template +CtxBinder CtxBind(Ret (T::*MemFunc)(Args...)); + +template +CtxBinder CtxBind(Ret (T::*MemFunc)(Args...) const); + +} // namespace internal + +#define DEEPMIND_RL_API_BIND(member) \ + decltype(internal::CtxBind(&T::member))::template Func<&T::member> + +template +void Bind(std::unique_ptr env, EnvCApi* api, void** context) { + *context = env.release(); + api->setting = DEEPMIND_RL_API_BIND(Setting); + api->init = DEEPMIND_RL_API_BIND(Init); + api->start = DEEPMIND_RL_API_BIND(Start); + api->error_message = DEEPMIND_RL_API_BIND(ErrorMessage); + api->environment_name = DEEPMIND_RL_API_BIND(EnvironmentName); + api->action_discrete_count = DEEPMIND_RL_API_BIND(ActionDiscreteCount); + api->action_discrete_name = DEEPMIND_RL_API_BIND(ActionDiscreteName); + api->action_discrete_bounds = DEEPMIND_RL_API_BIND(ActionDiscreteBounds); + api->action_continuous_count = DEEPMIND_RL_API_BIND(ActionContinuousCount); + api->action_continuous_name = DEEPMIND_RL_API_BIND(ActionContinuousName); + api->action_continuous_bounds = DEEPMIND_RL_API_BIND(ActionContinuousBounds); + api->observation_count = DEEPMIND_RL_API_BIND(ObservationCount); + api->observation_name = DEEPMIND_RL_API_BIND(ObservationName); + api->observation_spec = DEEPMIND_RL_API_BIND(ObservationSpec); + api->event_type_count = DEEPMIND_RL_API_BIND(EventTypeCount); + api->event_type_name = DEEPMIND_RL_API_BIND(EventTypeName); + api->fps = DEEPMIND_RL_API_BIND(Fps); + api->observation = DEEPMIND_RL_API_BIND(Observation); + api->event_count = DEEPMIND_RL_API_BIND(EventCount); + api->event = DEEPMIND_RL_API_BIND(Event); + api->act = DEEPMIND_RL_API_BIND(Act); + api->advance = DEEPMIND_RL_API_BIND(Advance); + api->release_context = [](void* context) { delete static_cast(context); }; +} +#undef DEEPMIND_RL_API_BIND + +} // namespace rl_api +} // namespace deepmind + +#endif // THIRD_PARTY_RL_API_ENV_C_API_BIND_H_ diff --git a/third_party/rl_api/env_c_api_example.cc b/third_party/rl_api/env_c_api_example.cc index 6f687f16..f3228c9b 100644 --- a/third_party/rl_api/env_c_api_example.cc +++ b/third_party/rl_api/env_c_api_example.cc @@ -1,4 +1,4 @@ -// Copyright 2016 Google Inc. +// Copyright 2016-2017 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,10 +17,14 @@ #include #include #include +#include +#include #include "third_party/rl_api/env_c_api.h" +#include "third_party/rl_api/env_c_api_bind.h" namespace { + const int kDiscreteActionCount = 2; const char* const kDiscreteActionNames[] = {"PADX", "PADY"}; @@ -37,28 +41,36 @@ const struct { double max_value; } kContinuousActionBounds[] = {{-1.0, 1.0}}; -const int kObservationCount = 2; -enum ObservationTypes { ObservationTypes_Bytes, ObservationTypes_Doubles }; -const char* const kObservationNames[] = {"BYTES", "DOUBLES"}; +const int kObservationCount = 3; +enum ObservationTypes { + ObservationTypes_Bytes, + ObservationTypes_Doubles, + ObservationTypes_String +}; + +const char* const kObservationNames[] = {"BYTES", "DOUBLES", "STRING"}; const int kObservationBytesDim[] = {10, 2}; const int kObservationDoublesDim[] = {7}; +const int kObservationStringDim[] = {0}; const EnvCApi_ObservationSpec kObservationSpecs[] = { {EnvCApi_ObservationBytes, 2, kObservationBytesDim}, - {EnvCApi_ObservationDoubles, 1, kObservationDoublesDim}}; + {EnvCApi_ObservationDoubles, 1, kObservationDoublesDim}, + {EnvCApi_ObservationString, 1, kObservationStringDim}}; class MyGame { public: - int setting(const char* key, const char* value) { + int Setting(const char* key, const char* value) { // Doesn't accept any settings. return 1; } - int init() { return 0; } + int Init() { return 0; } // Launch level using episode id and seed. - int start(int episode_id, int seed) { + int Start(int episode_id, int seed) { + steps_ = 0; episode_id_ = episode_id; seed_ = seed; for (auto& byte_ref : observation_bytes_) { @@ -67,63 +79,66 @@ class MyGame { for (auto& double_ref : observation_doubles_) { double_ref = 127; } + string_ = std::to_string(episode_id) + ":" + std::to_string(steps_); return 0; } - const char* environment_name() const { return "example_environment"; } + const char* ErrorMessage() const { return "No error message."; } + + const char* EnvironmentName() const { return "example_environment"; } // The number of discrete actions. - int action_discrete_count() const { return kDiscreteActionCount; } + int ActionDiscreteCount() const { return kDiscreteActionCount; } // Discrete action name. 'discrete_idx' < action_discrete_count(). - const char* action_discrete_name(int discrete_idx) const { + const char* ActionDiscreteName(int discrete_idx) const { assert(discrete_idx < kDiscreteActionCount); return kDiscreteActionNames[discrete_idx]; } // The discrete action inclusive range. - void action_discrete_bounds(int discrete_idx, int* min_value, - int* max_value) const { + void ActionDiscreteBounds(int discrete_idx, int* min_value, + int* max_value) const { *min_value = kDiscreteActionBounds[discrete_idx].min_value; *max_value = kDiscreteActionBounds[discrete_idx].max_value; } // The number of continuous actions. - int action_continuous_count() const { return kContinuousActionCount; } + int ActionContinuousCount() const { return kContinuousActionCount; } // Continuous action name. 'continuous_idx' < action_continuous_count(). - const char* action_continuous_name(int continuous_idx) const { + const char* ActionContinuousName(int continuous_idx) const { assert(continuous_idx < kContinuousActionCount); return kContinuousActionNames[continuous_idx]; } // The continuous action inclusive range. - void action_continuous_bounds(int continuous_idx, double* min_value_out, - double* max_value_out) const { + void ActionContinuousBounds(int continuous_idx, double* min_value_out, + double* max_value_out) const { *min_value_out = kContinuousActionBounds[continuous_idx].min_value; *max_value_out = kContinuousActionBounds[continuous_idx].max_value; } - int observation_count() const { return kObservationCount; } - const char* observation_name(int observation_idx) { + int ObservationCount() const { return kObservationCount; } + const char* ObservationName(int observation_idx) { assert(observation_idx < kObservationCount); return kObservationNames[observation_idx]; } // The shape of the observation parameter. - void observation_spec(int observation_idx, - EnvCApi_ObservationSpec* spec) const { + void ObservationSpec(int observation_idx, + EnvCApi_ObservationSpec* spec) const { assert(observation_idx < kObservationCount); *spec = kObservationSpecs[observation_idx]; } - int event_type_count() const { return 0; } - const char* event_type_name(int event_type_idx) const { std::abort(); } + int EventTypeCount() const { return 0; } + const char* EventTypeName(int event_type_idx) const { std::abort(); } - int fps() { return 60; } + int Fps() { return 60; } - void observation(int observation_idx, EnvCApi_Observation* observation) { - observation_spec(observation_idx, &observation->spec); + void Observation(int observation_idx, EnvCApi_Observation* observation) { + ObservationSpec(observation_idx, &observation->spec); switch (observation_idx) { case ObservationTypes_Bytes: observation->payload.bytes = observation_bytes_; @@ -131,19 +146,25 @@ class MyGame { case ObservationTypes_Doubles: observation->payload.doubles = observation_doubles_; break; + case ObservationTypes_String: + string_shape_[0] = string_.size(); + observation->payload.string = string_.c_str(); + observation->spec.shape = string_shape_; + break; } } - int event_count() { return 0; } + int EventCount() { return 0; } - void event(int event_idx, EnvCApi_Event* event) {} + void Event(int event_idx, EnvCApi_Event* event) {} - void act(const int actions_discrete[], const double actions_continuous[]) {} + void Act(const int actions_discrete[], const double actions_continuous[]) {} - EnvCApi_EnvironmentStatus advance(int num_steps, double* reward) { + EnvCApi_EnvironmentStatus Advance(int num_steps, double* reward) { steps_ += num_steps; *reward = reward_; reward_ = 0; + string_ = std::to_string(episode_id_) + ":" + std::to_string(steps_); return EnvCApi_EnvironmentStatus_Running; } @@ -154,77 +175,14 @@ class MyGame { double reward_; unsigned char observation_bytes_[10 * 2]; double observation_doubles_[7]; + int string_shape_[1]; + std::string string_; }; -// Memory cleanup. -static void release_context(void* context) { - delete static_cast(context); -} - -template -struct CtxBinder { - template - static Ret Func(void* context, Args... args) { - assert(context != nullptr); - return (static_cast(context)->*MemFunc)(args...); - } - - template - static Ret Func(void* context, Args... args) { - assert(context != nullptr); - return (static_cast(context)->*MemFunc)(args...); - } -}; - -template -CtxBinder CtxBind(Ret (T::*MemFunc)(Args...)); - -template -CtxBinder CtxBind(Ret (T::*MemFunc)(Args...) const); - } // namespace -// An example for isolating global symbols: This translation unit may be linked -// in such a way that global symbols like the following two do not clash with -// symbols in the client application. This kind of isolation is valuable when -// the environment is built from code that was not designed with reusablity in -// mind. -extern "C" { -int odr_example; -int violate_odr_maybe() { return ++odr_example; } -} - -// Portability note. This has not got C-language linkage but seems to work. -#define BIND_C(class_member) \ - decltype(CtxBind(&class_member))::Func<&class_member> - extern "C" int env_c_api_example_connect(EnvCApi* env_c_api, void** context) { - violate_odr_maybe(); - - *context = new MyGame(); - env_c_api->setting = BIND_C(MyGame::setting); - env_c_api->init = BIND_C(MyGame::init); - env_c_api->start = BIND_C(MyGame::start); - env_c_api->environment_name = BIND_C(MyGame::environment_name); - env_c_api->action_discrete_count = BIND_C(MyGame::action_discrete_count); - env_c_api->action_discrete_name = BIND_C(MyGame::action_discrete_name); - env_c_api->action_discrete_bounds = BIND_C(MyGame::action_discrete_bounds); - env_c_api->action_continuous_count = BIND_C(MyGame::action_continuous_count); - env_c_api->action_continuous_name = BIND_C(MyGame::action_continuous_name); - env_c_api->action_continuous_bounds = - BIND_C(MyGame::action_continuous_bounds); - env_c_api->observation_count = BIND_C(MyGame::observation_count); - env_c_api->observation_name = BIND_C(MyGame::observation_name); - env_c_api->observation_spec = BIND_C(MyGame::observation_spec); - env_c_api->event_type_count = BIND_C(MyGame::event_type_count); - env_c_api->event_type_name = BIND_C(MyGame::event_type_name); - env_c_api->fps = BIND_C(MyGame::fps); - env_c_api->observation = BIND_C(MyGame::observation); - env_c_api->event_count = BIND_C(MyGame::event_count); - env_c_api->event = BIND_C(MyGame::event); - env_c_api->act = BIND_C(MyGame::act); - env_c_api->advance = BIND_C(MyGame::advance); - env_c_api->release_context = release_context; + auto game = std::unique_ptr(new MyGame()); + deepmind::rl_api::Bind(std::move(game), env_c_api, context); return 0; } -#undef BIND_C diff --git a/third_party/rl_api/env_c_api_example_test.cc b/third_party/rl_api/env_c_api_example_test.cc index d8e1e9e5..b5d232ad 100644 --- a/third_party/rl_api/env_c_api_example_test.cc +++ b/third_party/rl_api/env_c_api_example_test.cc @@ -1,4 +1,4 @@ -// Copyright 2016 Google Inc. +// Copyright 2016-2017 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,29 +20,220 @@ #include "third_party/rl_api/env_c_api.h" #include "third_party/rl_api/env_c_api_example.h" -TEST(EnvCApiExampleTest, HasDiscreteCount) { +constexpr int kDiscreteActionsCount = 2; +constexpr int kDiscreteActions[] = {0, 0}; + +constexpr int kContinuousActionsCount = 1; +constexpr double kContinuousActions[] = {0}; + +TEST(EnvCApiExampleTest, HasDiscreteActions) { EnvCApi env_c_api; void* context; env_c_api_example_connect(&env_c_api, &context); - env_c_api.init(context); + EXPECT_EQ(0, env_c_api.init(context)); int discrete_count = env_c_api.action_discrete_count(context); - EXPECT_GT(discrete_count, 0); + EXPECT_EQ(kDiscreteActionsCount, discrete_count); for (int i = 0; i < discrete_count; ++i) { - std::string name = env_c_api.action_discrete_name(context, i); - EXPECT_FALSE(name.empty()); + EXPECT_STRNE("", env_c_api.action_discrete_name(context, i)); + } + + env_c_api.release_context(context); +} + +TEST(EnvCApiExampleTest, HasContinuousActions) { + EnvCApi env_c_api; + void* context; + + env_c_api_example_connect(&env_c_api, &context); + EXPECT_EQ(0, env_c_api.init(context)); + int continuous_count = env_c_api.action_continuous_count(context); + EXPECT_EQ(kContinuousActionsCount, continuous_count); + for (int i = 0; i < continuous_count; ++i) { + EXPECT_STRNE("", env_c_api.action_continuous_name(context, i)); + } + + env_c_api.release_context(context); +} + +TEST(EnvCApiExampleTest, HasObservations) { + EnvCApi env_c_api; + void* context; + env_c_api_example_connect(&env_c_api, &context); + EXPECT_EQ(0, env_c_api.init(context)); + int observation_count = env_c_api.observation_count(context); + EXPECT_GT(observation_count, 0); + for (int idx = 0; idx < observation_count; ++idx) { + EXPECT_STRNE("", env_c_api.observation_name(context, idx)); + } + + env_c_api.release_context(context); +} + +TEST(EnvCApiExampleTest, ObservationString) { + EnvCApi env_c_api; + void* context; + env_c_api_example_connect(&env_c_api, &context); + EXPECT_EQ(0, env_c_api.init(context)); + int observation_count = env_c_api.observation_count(context); + EXPECT_GT(observation_count, 0); + std::vector string_ids; + for (int idx = 0; idx < observation_count; ++idx) { + EnvCApi_ObservationSpec spec; + env_c_api.observation_spec(context, idx, &spec); + if (spec.type == EnvCApi_ObservationString) { + string_ids.push_back(idx); + ASSERT_EQ(1, spec.dims); + EXPECT_EQ(0, spec.shape[0]); + } + } + + env_c_api.start(context, /*episode=*/1, /*seed=*/1234); + + ASSERT_EQ(1, string_ids.size()); + + int string_id = string_ids.front(); + { + EnvCApi_Observation obs; + env_c_api.observation(context, string_id, &obs); + EXPECT_EQ(EnvCApi_ObservationString, obs.spec.type); + ASSERT_EQ(1, obs.spec.dims); + EXPECT_NE(0, obs.spec.shape[0]); + EXPECT_EQ("1:0", std::string(obs.payload.string, obs.spec.shape[0])); + } + + double reward; + EXPECT_EQ(EnvCApi_EnvironmentStatus_Running, + env_c_api.advance(context, /*num_steps=*/5, &reward)); + EXPECT_EQ(0.0, reward); + + { + EnvCApi_Observation obs; + env_c_api.observation(context, string_id, &obs); + EXPECT_EQ(EnvCApi_ObservationString, obs.spec.type); + ASSERT_EQ(1, obs.spec.dims); + EXPECT_NE(0, obs.spec.shape[0]); + EXPECT_EQ("1:5", std::string(obs.payload.string, obs.spec.shape[0])); } env_c_api.release_context(context); } -extern "C" { - int odr_example; - void violate_odr_maybe() { --odr_example; } +TEST(EnvCApiExampleTest, ObservationDoubles) { + EnvCApi env_c_api; + void* context; + env_c_api_example_connect(&env_c_api, &context); + EXPECT_EQ(0, env_c_api.init(context)); + int observation_count = env_c_api.observation_count(context); + EXPECT_GT(observation_count, 0); + std::vector doubles_ids; + for (int idx = 0; idx < observation_count; ++idx) { + EnvCApi_ObservationSpec spec; + env_c_api.observation_spec(context, idx, &spec); + if (spec.type == EnvCApi_ObservationDoubles) { + doubles_ids.push_back(idx); + ASSERT_EQ(1, spec.dims); + EXPECT_EQ(7, spec.shape[0]); + } + } + + env_c_api.start(context, /*episode=*/1, /*seed=*/1234); + ASSERT_EQ(1, doubles_ids.size()); + + int doubles_id = doubles_ids.front(); + { + EnvCApi_Observation obs; + env_c_api.observation(context, doubles_id, &obs); + EXPECT_EQ(EnvCApi_ObservationDoubles, obs.spec.type); + ASSERT_EQ(1, obs.spec.dims); + EXPECT_EQ(7, obs.spec.shape[0]); + std::vector double_observation( + obs.payload.doubles, obs.payload.doubles + obs.spec.shape[0]); + + for (double v : double_observation) { + EXPECT_EQ(127.0, v); + } + } + env_c_api.act(context, kDiscreteActions, kContinuousActions); + double reward; + EXPECT_EQ(EnvCApi_EnvironmentStatus_Running, + env_c_api.advance(context, /*num_steps=*/5, &reward)); + EXPECT_EQ(0.0, reward); + { + EnvCApi_Observation obs; + env_c_api.observation(context, doubles_id, &obs); + EXPECT_EQ(EnvCApi_ObservationDoubles, obs.spec.type); + ASSERT_EQ(1, obs.spec.dims); + EXPECT_EQ(7, obs.spec.shape[0]); + std::vector double_observation( + obs.payload.doubles, obs.payload.doubles + obs.spec.shape[0]); + + for (double v : double_observation) { + EXPECT_EQ(127.0, v); + } + } + + env_c_api.release_context(context); } -TEST(EnvCApiExampleTest, ODR) { - // This test demonstrates that we can use global symbols without regard to - // the global symbols used by the library that provides the RL environment. - violate_odr_maybe(); +TEST(EnvCApiExampleTest, ObservationBytes) { + EnvCApi env_c_api; + void* context; + env_c_api_example_connect(&env_c_api, &context); + EXPECT_EQ(0, env_c_api.init(context)); + int observation_count = env_c_api.observation_count(context); + EXPECT_GT(observation_count, 0); + std::vector bytes_ids; + for (int idx = 0; idx < observation_count; ++idx) { + EnvCApi_ObservationSpec spec; + env_c_api.observation_spec(context, idx, &spec); + if (spec.type == EnvCApi_ObservationBytes) { + bytes_ids.push_back(idx); + ASSERT_EQ(2, spec.dims); + EXPECT_EQ(10, spec.shape[0]); + EXPECT_EQ(2, spec.shape[1]); + } + } + + env_c_api.start(context, /*episode=*/1, /*seed=*/1234); + ASSERT_EQ(1, bytes_ids.size()); + + int bytes_id = bytes_ids.front(); + { + EnvCApi_Observation obs; + env_c_api.observation(context, bytes_id, &obs); + EXPECT_EQ(EnvCApi_ObservationBytes, obs.spec.type); + ASSERT_EQ(2, obs.spec.dims); + EXPECT_EQ(10, obs.spec.shape[0]); + EXPECT_EQ(2, obs.spec.shape[1]); + std::vector byte_observation( + obs.payload.bytes, + obs.payload.bytes + obs.spec.shape[0] * obs.spec.shape[1]); + + for (unsigned char v : byte_observation) { + EXPECT_EQ(127, v); + } + } + env_c_api.act(context, kDiscreteActions, kContinuousActions); + double reward; + EXPECT_EQ(EnvCApi_EnvironmentStatus_Running, + env_c_api.advance(context, /*num_steps=*/5, &reward)); + EXPECT_EQ(0.0, reward); + { + EnvCApi_Observation obs; + env_c_api.observation(context, bytes_id, &obs); + EXPECT_EQ(EnvCApi_ObservationBytes, obs.spec.type); + ASSERT_EQ(2, obs.spec.dims); + EXPECT_EQ(10, obs.spec.shape[0]); + EXPECT_EQ(2, obs.spec.shape[1]); + std::vector byte_observation( + obs.payload.bytes, + obs.payload.bytes + obs.spec.shape[0] * obs.spec.shape[1]); + + for (unsigned char v : byte_observation) { + EXPECT_EQ(127, v); + } + } + + env_c_api.release_context(context); }