]> de.git.xonotic.org Git - xonotic/netradiant.git/commitdiff
netradiant: strip 16-bit png to 8-bit, fix #153 illwieckz/png16 master
authorThomas Debesse <dev@illwieckz.net>
Sat, 30 Mar 2024 00:19:15 +0000 (01:19 +0100)
committerThomas Debesse <dev@illwieckz.net>
Sat, 30 Mar 2024 00:19:15 +0000 (01:19 +0100)
373 files changed:
.gitmodules
CMakeLists.txt
Makefile
README.md
cmake/FindGLIB.cmake
cmake/FindGTK2.cmake
cmake/FindGtkGLExt.cmake
cmake/FindMinizip.cmake
cmake/FindPango.cmake
contrib/bobtoolz/CMakeLists.txt
contrib/bobtoolz/DPatch.cpp
contrib/bobtoolz/bobToolz-GTK.cpp
contrib/bobtoolz/bobToolz-GTK.h [new file with mode: 0644]
contrib/bobtoolz/dialogs/dialogs-gtk.cpp
contrib/bobtoolz/funchandlers-GTK.cpp
contrib/bobtoolz/misc.cpp
contrib/bobtoolz/visfind.cpp
contrib/brushexport/interface.cpp
contrib/brushexport/plugin.cpp
contrib/brushexport/plugin.h
contrib/camera/bitmaps/camera_insp.png
contrib/camera/camera.cpp
contrib/gtkgensurf/plugin.cpp
contrib/meshtex/.gitattributes [new file with mode: 0644]
contrib/meshtex/.gitignore [new file with mode: 0644]
contrib/meshtex/AllocatedMatrix.h [new file with mode: 0644]
contrib/meshtex/COMPILING [new file with mode: 0644]
contrib/meshtex/Doxyfile [new file with mode: 0644]
contrib/meshtex/GeneralFunctionDialog.cpp [new file with mode: 0644]
contrib/meshtex/GeneralFunctionDialog.h [new file with mode: 0644]
contrib/meshtex/GenericDialog.cpp [new file with mode: 0644]
contrib/meshtex/GenericDialog.h [new file with mode: 0644]
contrib/meshtex/GenericMainMenu.cpp [new file with mode: 0644]
contrib/meshtex/GenericMainMenu.h [new file with mode: 0644]
contrib/meshtex/GenericPluginUI.cpp [new file with mode: 0644]
contrib/meshtex/GenericPluginUI.h [new file with mode: 0644]
contrib/meshtex/GenericPluginUIMessages.h [new file with mode: 0644]
contrib/meshtex/GetInfoDialog.cpp [new file with mode: 0644]
contrib/meshtex/GetInfoDialog.h [new file with mode: 0644]
contrib/meshtex/HISTORY [new file with mode: 0644]
contrib/meshtex/LICENSE [new file with mode: 0644]
contrib/meshtex/MainMenu.cpp [new file with mode: 0644]
contrib/meshtex/MainMenu.h [new file with mode: 0644]
contrib/meshtex/MeshEntity.cpp [new file with mode: 0644]
contrib/meshtex/MeshEntity.h [new file with mode: 0644]
contrib/meshtex/MeshEntityMessages.h [new file with mode: 0644]
contrib/meshtex/MeshVisitor.cpp [new file with mode: 0644]
contrib/meshtex/MeshVisitor.h [new file with mode: 0644]
contrib/meshtex/PluginModule.cpp [new file with mode: 0644]
contrib/meshtex/PluginModule.h [new file with mode: 0644]
contrib/meshtex/PluginProperties.h [new file with mode: 0644]
contrib/meshtex/PluginRegistration.cpp [new file with mode: 0644]
contrib/meshtex/PluginUI.cpp [new file with mode: 0644]
contrib/meshtex/PluginUI.h [new file with mode: 0644]
contrib/meshtex/PluginUIMessages.h [new file with mode: 0644]
contrib/meshtex/README.md [new file with mode: 0644]
contrib/meshtex/RefCounted.cpp [new file with mode: 0644]
contrib/meshtex/RefCounted.h [new file with mode: 0644]
contrib/meshtex/SetScaleDialog.cpp [new file with mode: 0644]
contrib/meshtex/SetScaleDialog.h [new file with mode: 0644]
contrib/meshtex/UtilityMacros.h [new file with mode: 0644]
contrib/meshtex/docs/docs.7z [new file with mode: 0644]
contrib/meshtex/localdirs.vsprops [new file with mode: 0644]
contrib/meshtex/mainpage.dox [new file with mode: 0644]
contrib/meshtex/meshtex.def [new file with mode: 0644]
contrib/meshtex/meshtex.rc [new file with mode: 0644]
contrib/meshtex/meshtex.sln [new file with mode: 0644]
contrib/meshtex/meshtex.vcproj [new file with mode: 0644]
contrib/meshtex/modules.dox [new file with mode: 0644]
contrib/meshtex/resource.h [new file with mode: 0644]
contrib/prtview/ConfigDialog.cpp
contrib/prtview/LoadPortalFileDialog.cpp
contrib/prtview/prtview.cpp
contrib/prtview/prtview.h
docs/Blendmodes_cheatsheet.jpg [new file with mode: 0644]
easy-builder
flake.nix [new file with mode: 0644]
gamepack-manager
icons/mime/map.xml [deleted file]
icons/x-netradiant-map.xml [new file with mode: 0644]
include/ientity.h
include/ifilter.h
include/iselection.h
include/ishaders.h
library-bundler
libs/CMakeLists.txt
libs/crunch
libs/gtkutil/CMakeLists.txt
libs/gtkutil/accelerator.cpp
libs/gtkutil/cursor.cpp
libs/gtkutil/cursor.h
libs/gtkutil/dialog.cpp
libs/gtkutil/filechooser.cpp
libs/gtkutil/image.cpp
libs/gtkutil/paned.cpp
libs/gtkutil/paned.h
libs/gtkutil/toolbar.cpp
libs/gtkutil/toolbar.h
libs/gtkutil/widget.h
libs/gtkutil/window.cpp
libs/gtkutil/xorrectangle.cpp
libs/mathlib.h
libs/mathlib/mathlib.c
libs/os/file.h
libs/picomodel/CMakeLists.txt
libs/picomodel/picointernal.c
libs/picomodel/picointernal.h
libs/picomodel/picomodules.c
libs/picomodel/pm_fm.h
libs/picomodel/pm_iqm.c [new file with mode: 0644]
libs/picomodel/pm_md3.c
libs/picomodel/pm_mdc.c
libs/render.h
libs/scenelib.h
libs/splines/math_matrix.h
libs/string/string.h
libs/stringio.h
libs/transformpath/CMakeLists.txt [new file with mode: 0644]
libs/transformpath/transformpath.cpp [new file with mode: 0644]
libs/transformpath/transformpath.h [new file with mode: 0644]
libs/uilib/uilib.h
libs/uniquenames.h
oldstuff/TODO-Garux [new file with mode: 0644]
plugins/CMakeLists.txt
plugins/entity/angles.h
plugins/entity/doom3group.cpp
plugins/entity/entity.cpp
plugins/entity/entity.h
plugins/entity/group.cpp
plugins/entity/light.cpp
plugins/entity/miscmodel.cpp
plugins/entity/namedentity.h
plugins/entity/targetable.h
plugins/image/tga.cpp
plugins/imagepng/plugin.cpp
plugins/mapq3/write.cpp
plugins/md3model/md5.cpp
plugins/shaders/plugin.cpp
plugins/shaders/shaders.cpp
radiant/CMakeLists.txt
radiant/autosave.cpp
radiant/brush.cpp
radiant/brush.h
radiant/brush_primit.cpp
radiant/brushmanip.cpp
radiant/brushmodule.cpp
radiant/brushmodule.h
radiant/build.cpp
radiant/build.h
radiant/camwindow.cpp
radiant/camwindow.h
radiant/commands.cpp
radiant/commands.h
radiant/console.cpp
radiant/csg.cpp
radiant/csg.h
radiant/eclass_doom3.cpp
radiant/eclass_fgd.cpp
radiant/entity.cpp
radiant/entity.h
radiant/entityinspector.cpp
radiant/entitylist.cpp
radiant/environment.cpp
radiant/environment.h
radiant/filterbar.cpp [new file with mode: 0644]
radiant/filterbar.h [new file with mode: 0644]
radiant/filters.cpp
radiant/findtexturedialog.cpp
radiant/grid.cpp
radiant/groupdialog.cpp
radiant/gtkdlgs.cpp
radiant/gtkdlgs.h
radiant/gtkmisc.cpp
radiant/gtkmisc.h
radiant/gtktheme.cpp [new file with mode: 0644]
radiant/gtktheme.h [new file with mode: 0644]
radiant/main.cpp
radiant/mainframe.cpp
radiant/mainframe.h
radiant/map.cpp
radiant/map.h
radiant/mru.cpp
radiant/multimon.cpp
radiant/patch.cpp
radiant/patch.h
radiant/patchdialog.cpp
radiant/patchmanip.cpp
radiant/patchmanip.h
radiant/plugintoolbar.cpp
radiant/plugintoolbar.h
radiant/points.cpp
radiant/preferences.cpp
radiant/preferences.h
radiant/qe3.cpp
radiant/referencecache.cpp
radiant/renderer.h
radiant/renderstate.cpp
radiant/select.cpp
radiant/select.h
radiant/selection.cpp
radiant/server.cpp
radiant/shaders.cpp
radiant/shaders.h
radiant/surfacedialog.cpp
radiant/texwindow.cpp
radiant/view.h
radiant/watchbsp.cpp
radiant/winding.cpp
radiant/xywindow.cpp
radiant/xywindow.h
setup/data/tools-src/black.png [new file with mode: 0644]
setup/data/tools-src/brush_flipx.png [new file with mode: 0644]
setup/data/tools-src/brush_flipy.png [new file with mode: 0644]
setup/data/tools-src/brush_flipz.png [new file with mode: 0644]
setup/data/tools-src/brush_rotatex.png [new file with mode: 0644]
setup/data/tools-src/brush_rotatey.png [new file with mode: 0644]
setup/data/tools-src/brush_rotatez.png [new file with mode: 0644]
setup/data/tools-src/dontselectcurve.png [new file with mode: 0644]
setup/data/tools-src/dontselectmodel.png [new file with mode: 0644]
setup/data/tools-src/f-decals.png [new file with mode: 0644]
setup/data/tools-src/garux/icon_garux.png [new file with mode: 0644]
setup/data/tools-src/garux/logo_garux.png [new file with mode: 0644]
setup/data/tools-src/garux/radiant_garux.ico [new file with mode: 0644]
setup/data/tools-src/garux/splash_garux.png [new file with mode: 0644]
setup/data/tools-src/lightinspector.png [new file with mode: 0644]
setup/data/tools-src/noFalloff.png [new file with mode: 0644]
setup/data/tools-src/patch_bend.png [new file with mode: 0644]
setup/data/tools-src/patch_drilldown.png [new file with mode: 0644]
setup/data/tools-src/patch_insdel.png [new file with mode: 0644]
setup/data/tools-src/patch_showboundingbox.png [new file with mode: 0644]
setup/data/tools-src/patch_weld.png [new file with mode: 0644]
setup/data/tools-src/popup_selection.png [new file with mode: 0644]
setup/data/tools-src/scalelockx.png [new file with mode: 0644]
setup/data/tools-src/scalelocky.png [new file with mode: 0644]
setup/data/tools-src/scalelockz.png [new file with mode: 0644]
setup/data/tools-src/selection_makehollow.png [new file with mode: 0644]
setup/data/tools-src/selection_selectcompletetall.png [new file with mode: 0644]
setup/data/tools-src/selection_selectcompletetall_old.png [new file with mode: 0644]
setup/data/tools-src/selection_selectpartialtall.png [new file with mode: 0644]
setup/data/tools-src/selection_selectpartialtall_old.png [new file with mode: 0644]
setup/data/tools-src/show_entities.png [new file with mode: 0644]
setup/data/tools-src/texbro_tags.xcf.bz2 [new file with mode: 0644]
setup/data/tools-src/textures_popup.png [new file with mode: 0644]
setup/data/tools-src/view_cameratoggle.png [new file with mode: 0644]
setup/data/tools-src/view_cameraupdate.png [new file with mode: 0644]
setup/data/tools-src/white.png [new file with mode: 0644]
setup/data/tools/bitmaps/black.png [deleted file]
setup/data/tools/bitmaps/brush_flip_hor.png [new file with mode: 0644]
setup/data/tools/bitmaps/brush_flip_vert.png [new file with mode: 0644]
setup/data/tools/bitmaps/brush_flipx.png [deleted file]
setup/data/tools/bitmaps/brush_flipy.png [deleted file]
setup/data/tools/bitmaps/brush_flipz.png [deleted file]
setup/data/tools/bitmaps/brush_rotate_anti.png [new file with mode: 0644]
setup/data/tools/bitmaps/brush_rotate_clock.png [new file with mode: 0644]
setup/data/tools/bitmaps/brush_rotatex.png [deleted file]
setup/data/tools/bitmaps/brush_rotatey.png [deleted file]
setup/data/tools/bitmaps/brush_rotatez.png [deleted file]
setup/data/tools/bitmaps/cap_bevel.png
setup/data/tools/bitmaps/cap_curve.png
setup/data/tools/bitmaps/cap_cylinder.png
setup/data/tools/bitmaps/cap_endcap.png
setup/data/tools/bitmaps/cap_ibevel.png
setup/data/tools/bitmaps/cap_iendcap.png
setup/data/tools/bitmaps/csgtool_diagonal.png [new file with mode: 0644]
setup/data/tools/bitmaps/csgtool_expand.png [new file with mode: 0644]
setup/data/tools/bitmaps/csgtool_extrude.png [new file with mode: 0644]
setup/data/tools/bitmaps/csgtool_pull.png [new file with mode: 0644]
setup/data/tools/bitmaps/csgtool_removeinner.png [new file with mode: 0644]
setup/data/tools/bitmaps/csgtool_shrink.png [new file with mode: 0644]
setup/data/tools/bitmaps/csgtool_wrap.png [new file with mode: 0644]
setup/data/tools/bitmaps/dontselectcurve.png [deleted file]
setup/data/tools/bitmaps/dontselectmodel.png [deleted file]
setup/data/tools/bitmaps/f-areaportal.png [new file with mode: 0644]
setup/data/tools/bitmaps/f-caulk.png [new file with mode: 0644]
setup/data/tools/bitmaps/f-clip.png [new file with mode: 0644]
setup/data/tools/bitmaps/f-details.png [new file with mode: 0644]
setup/data/tools/bitmaps/f-entities.png [new file with mode: 0644]
setup/data/tools/bitmaps/f-funcgroups.png [new file with mode: 0644]
setup/data/tools/bitmaps/f-hide.png [new file with mode: 0644]
setup/data/tools/bitmaps/f-hint.png [new file with mode: 0644]
setup/data/tools/bitmaps/f-invert.png [new file with mode: 0644]
setup/data/tools/bitmaps/f-lights.png [new file with mode: 0644]
setup/data/tools/bitmaps/f-liquids.png [new file with mode: 0644]
setup/data/tools/bitmaps/f-models.png [new file with mode: 0644]
setup/data/tools/bitmaps/f-region.png [new file with mode: 0644]
setup/data/tools/bitmaps/f-reset.png [new file with mode: 0644]
setup/data/tools/bitmaps/f-structural.png [new file with mode: 0644]
setup/data/tools/bitmaps/f-translucent.png [new file with mode: 0644]
setup/data/tools/bitmaps/f-triggers.png [new file with mode: 0644]
setup/data/tools/bitmaps/f-world.png [new file with mode: 0644]
setup/data/tools/bitmaps/lightinspector.png [deleted file]
setup/data/tools/bitmaps/noFalloff.png [deleted file]
setup/data/tools/bitmaps/patch_bend.png [deleted file]
setup/data/tools/bitmaps/patch_drilldown.png [deleted file]
setup/data/tools/bitmaps/patch_insdel.png [deleted file]
setup/data/tools/bitmaps/patch_showboundingbox.png [deleted file]
setup/data/tools/bitmaps/patch_weld.png [deleted file]
setup/data/tools/bitmaps/popup_selection.png [deleted file]
setup/data/tools/bitmaps/redo.png
setup/data/tools/bitmaps/refresh_models.png
setup/data/tools/bitmaps/scalelockx.png [deleted file]
setup/data/tools/bitmaps/scalelocky.png [deleted file]
setup/data/tools/bitmaps/scalelockz.png [deleted file]
setup/data/tools/bitmaps/select_mouserotate.png
setup/data/tools/bitmaps/selection_makehollow.png [deleted file]
setup/data/tools/bitmaps/selection_selectcompletetall.png [deleted file]
setup/data/tools/bitmaps/selection_selectpartialtall.png [deleted file]
setup/data/tools/bitmaps/show_entities.png [deleted file]
setup/data/tools/bitmaps/texbro_gtk-find-and-replace.png [new file with mode: 0644]
setup/data/tools/bitmaps/texbro_refresh.png [new file with mode: 0644]
setup/data/tools/bitmaps/texbro_tags.png [new file with mode: 0644]
setup/data/tools/bitmaps/texbro_view.png [new file with mode: 0644]
setup/data/tools/bitmaps/textures_popup.png [deleted file]
setup/data/tools/bitmaps/undo.png
setup/data/tools/bitmaps/view_cameratoggle.png [deleted file]
setup/data/tools/bitmaps/view_cameraupdate.png [deleted file]
setup/data/tools/bitmaps/white.png [deleted file]
setup/data/tools/bitmaps/window5.png [new file with mode: 0644]
setup/data/tools/plugins/bitmaps/bobtoolz_cleanup.png
setup/data/tools/plugins/bitmaps/bobtoolz_cleanup_old.png [new file with mode: 0644]
setup/data/tools/plugins/bitmaps/bobtoolz_merge.png
setup/data/tools/plugins/bitmaps/bobtoolz_merge_old.png [new file with mode: 0644]
setup/data/tools/plugins/bitmaps/bobtoolz_poly.png
setup/data/tools/plugins/bitmaps/bobtoolz_poly_old.png [new file with mode: 0644]
setup/data/tools/plugins/bitmaps/bobtoolz_split.png
setup/data/tools/plugins/bitmaps/bobtoolz_split_old.png [new file with mode: 0644]
setup/data/tools/plugins/bitmaps/bobtoolz_splitcol.png
setup/data/tools/plugins/bitmaps/bobtoolz_splitrow.png
setup/macos/NetRadiant
tools/CMakeLists.txt
tools/heretic2/common/cmdlib.c
tools/quake2/qdata/images.c
tools/quake2/qdata/qdata.c
tools/quake2/qdata/video.c
tools/quake3/common/inout.c
tools/quake3/common/miniz.c [new file with mode: 0644]
tools/quake3/common/miniz.h [new file with mode: 0644]
tools/quake3/common/scriplib.c
tools/quake3/common/scriplib.h
tools/quake3/common/vfs.c
tools/quake3/common/vfs.h
tools/quake3/q3data/q3data.c
tools/quake3/q3map2/brush.c
tools/quake3/q3map2/bsp.c
tools/quake3/q3map2/bspfile_abstract.c
tools/quake3/q3map2/bspfile_ibsp.c
tools/quake3/q3map2/bspfile_rbsp.c
tools/quake3/q3map2/convert_map.c
tools/quake3/q3map2/fog.c
tools/quake3/q3map2/game_quake3.h
tools/quake3/q3map2/help.c
tools/quake3/q3map2/image.c
tools/quake3/q3map2/light.c
tools/quake3/q3map2/light_bounce.c
tools/quake3/q3map2/light_trace.c
tools/quake3/q3map2/light_ydnar.c
tools/quake3/q3map2/lightmaps_ydnar.c
tools/quake3/q3map2/main.c
tools/quake3/q3map2/map.c
tools/quake3/q3map2/model.c
tools/quake3/q3map2/path_init.c
tools/quake3/q3map2/portals.c
tools/quake3/q3map2/q3map2.h
tools/quake3/q3map2/shaders.c
tools/quake3/q3map2/surface.c
tools/quake3/q3map2/surface_foliage.c
tools/quake3/q3map2/surface_meta.c
tools/quake3/q3map2/tjunction.c
tools/quake3/q3map2/vis.c
tools/quake3/q3map2/visflow.c
tools/quake3/q3map2/writebsp.c
tools/unvanquished/CMakeLists.txt [deleted file]
tools/unvanquished/daemonmap [deleted submodule]

index f8d6fc747fcc64e60bffbafa291bcd25918c2920..d20f6e35835027edcedd18a4d484cbf4841ded21 100644 (file)
@@ -1,6 +1,3 @@
 [submodule "libs/crunch"]
        path = libs/crunch
        url = https://github.com/DaemonEngine/crunch.git
-[submodule "tools/unvanquished/daemonmap"]
-       path = tools/unvanquished/daemonmap
-       url = https://github.com/DaemonEngine/daemonmap.git
index b2fa296e953cd9c6c2fcfec248b5a3afd8bd3ab1..e5cd054448942698c89e7ba4d11ed583b486ebf2 100644 (file)
@@ -14,13 +14,17 @@ set(BUILTINS_PKGCONFIG_DIR "${BUILTINS_INSTALL_DIR}/lib/pkgconfig")
 option(BUILTIN_GTKGLEXT "Builtin GtkGLExt" OFF)
 option(BUILTIN_GTKTHEME_MOJAVE "Builtin Mojave GTK theme" OFF)
 
+if(NOT CMAKE_BUILD_TYPE)
+    set(CMAKE_BUILD_TYPE Release)
+endif()
+
 if (APPLE)
     set(BUILTIN_GTKGLEXT ON)
     set(BUILTIN_GTKTHEME_MOJAVE ON)
 endif ()
 
 set(BUILTIN_GTKGLEXT_BUILT OFF CACHE INTERNAL "...")
-if (EXISTS "${BUILTINS_PKGCONFIG_DIR}gtkglext-1.0.pc")
+if (EXISTS "${BUILTINS_PKGCONFIG_DIR}/gtkglext-1.0.pc")
     set(BUILTIN_GTKGLEXT_BUILT ON)
 endif ()
 
@@ -53,12 +57,6 @@ else ()
     set(DEFAULT_BUILD_CRUNCH OFF CACHE INTERNAL "...")
 endif ()
 
-if (EXISTS "${PROJECT_SOURCE_DIR}/tools/unvanquished/daemonmap/tools/quake3/q3map2/main.c")
-    set(DEFAULT_BUILD_DAEMONMAP ON CACHE INTERNAL "...")
-else ()
-    set(DEFAULT_BUILD_DAEMONMAP OFF CACHE INTERNAL "...")
-endif ()
-
 #-----------------------------------------------------------------------
 # Build options
 #-----------------------------------------------------------------------
@@ -66,10 +64,10 @@ endif ()
 option(BUILD_RADIANT "Build the GUI" ON)
 option(BUILD_TOOLS "Build the tools" ON)
 option(BUILD_CRUNCH "Build Crunch image support" ${DEFAULT_BUILD_CRUNCH})
-option(BUILD_DAEMONMAP "Build daemonmap navigation mesh generator" ${DEFAULT_BUILD_DAEMONMAP})
 option(DOWNLOAD_GAMEPACKS "Download game packs" ON)
 option(USE_WERROR "Build with -Werror -pedantic-errors" OFF)
 option(FHS_INSTALL "Install according to Filesystem Hierarchy Standard" OFF)
+option(FHS_INSTALL_ABSOLUTE "Install using absolute paths (requires FHS_INSTALL)" OFF)
 
 set(BUILD_BINARIES OFF CACHE INTERNAL "...")
 if (BUILD_RADIANT OR BUILD_TOOLS)
@@ -77,7 +75,8 @@ if (BUILD_RADIANT OR BUILD_TOOLS)
 endif ()
 
 if (BUILD_BINARIES)
-    if (WIN32 OR APPLE
+    if (WIN32
+        OR APPLE
         OR "${CMAKE_SYSTEM_NAME}" STREQUAL "Linux"
         OR "${CMAKE_SYSTEM_NAME}" STREQUAL "FreeBSD" )
         set(BUNDLING_SUPPORTED ON)
@@ -94,7 +93,11 @@ if (BUILD_BINARIES)
     endif ()
 
     if (BUNDLE_LIBRARIES AND BUNDLING_SUPPORTED)
-        set(FHS_INSTALL OFF CACHE PATH "Disabled because of BUNDLE_LIBRARIES" FORCE)
+        set(FHS_INSTALL OFF CACHE PATH "Disabled because of BUNDLE_LIBRARIES is enabled" FORCE)
+    endif ()
+
+    if (NOT FHS_INSTALL AND FHS_INSTALL_ABSOLUTE)
+        set(FHS_INSTALL_ABSOLUTE OFF CACHE PATH "Disabled because of FHS_INSTALL is disabled" FORCE)
     endif ()
 endif ()
 
@@ -121,19 +124,28 @@ add_definitions(-DRADIANT_BASENAME="${RADIANT_BASENAME}")
 set(RADIANT_BIN_DIR ${FINAL_INSTALL_PREFIX} CACHE INTERNAL "...")
 set(RADIANT_LIB_DIR ${FINAL_INSTALL_PREFIX}/lib CACHE INTERNAL "...")
 set(RADIANT_ADDONS_DIR ${FINAL_INSTALL_PREFIX} CACHE INTERNAL "...")
-set(RADIANT_ETC_DIR ${FINAL_INSTALL_PREFIX}/etc CACHE INTERNAL "...")
 set(RADIANT_SHARE_DIR ${FINAL_INSTALL_PREFIX}/share CACHE INTERNAL "...")
 set(RADIANT_DATA_DIR ${FINAL_INSTALL_PREFIX} CACHE INTERNAL "...")
 
-if (FHS_INSTALL AND NOT BUNDLE_LIBRARIES)
+if (FHS_INSTALL)
+    add_definitions(-DRADIANT_FHS_INSTALL='ON')
+
     set(RADIANT_BIN_DIR ${FINAL_INSTALL_PREFIX}/bin)
-    set(RADIANT_LIB_DIR ${FINAL_INSTALL_PREFIX}/lib)
+    set(RADIANT_LIB_DIR ${FINAL_INSTALL_PREFIX}/lib/${CMAKE_LIBRARY_ARCHITECTURE})
     set(RADIANT_ADDONS_DIR ${RADIANT_LIB_DIR}/${RADIANT_BASENAME})
-    set(RADIANT_ETC_DIR ${FINAL_INSTALL_PREFIX}/etc)
     set(RADIANT_SHARE_DIR ${FINAL_INSTALL_PREFIX}/share)
     set(RADIANT_DATA_DIR ${RADIANT_SHARE_DIR}/${RADIANT_BASENAME})
+    set(RADIANT_LIB_DIR ${FINAL_INSTALL_PREFIX}/lib/${CMAKE_LIBRARY_ARCHITECTURE})
+
+    if (FHS_INSTALL_ABSOLUTE)
+        add_definitions(-DRADIANT_ADDONS_DIR="${RADIANT_ADDONS_DIR}")
+        add_definitions(-DRADIANT_DATA_DIR="${RADIANT_DATA_DIR}")
+    else ()
+        add_definitions(-DRADIANT_LIB_ARCH="${CMAKE_LIBRARY_ARCHITECTURE}")
+    endif ()
 endif ()
 
+
 set(GAMEPACKS_DOWNLOAD_DIR ${PROJECT_BINARY_DIR}/download CACHE PATH "Where to store downloaded game packs")
 
 #-----------------------------------------------------------------------
@@ -148,19 +160,21 @@ set(RADIANT_VERSION "${RADIANT_VERSION_MAJOR}.${RADIANT_VERSION_MINOR}.${RADIANT
 
 set(RADIANT_ABOUTMSG "Custom build" CACHE STRING "About message")
 
-find_package(Git REQUIRED)
+set(RADIANT_VERSION_STRING "${RADIANT_VERSION}n")
 
-execute_process(
-     COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD
-     WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
-     OUTPUT_VARIABLE GIT_VERSION
-     OUTPUT_STRIP_TRAILING_WHITESPACE
-)
+if (NOT DEFINED GIT_VERSION)
+     find_package(Git REQUIRED)
 
-set(RADIANT_VERSION_STRING "${RADIANT_VERSION}n")
+     execute_process(
+          COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD
+          WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+          OUTPUT_VARIABLE GIT_VERSION
+          OUTPUT_STRIP_TRAILING_WHITESPACE
+     )
 
-if (GIT_VERSION)
-    set(RADIANT_VERSION_STRING "${RADIANT_VERSION_STRING}-git-${GIT_VERSION}")
+     if (GIT_VERSION)
+         set(RADIANT_VERSION_STRING "${RADIANT_VERSION_STRING}-git-${GIT_VERSION}")
+     endif ()
 endif ()
 
 message(STATUS "Building ${PROJECT_NAME} ${RADIANT_VERSION_STRING} ${RADIANT_ABOUTMSG}")
@@ -246,15 +260,29 @@ if (BUILD_RADIANT)
         if (GTK_TARGET EQUAL 2)
             add_definitions(-DWORKAROUND_WINDOWS_GTK2_GLWIDGET=1)
         endif ()
-    endif ()
-
-    if (APPLE)
+    elseif (APPLE)
         if (GTK_TARGET EQUAL 2)
             add_definitions(-DWORKAROUND_MACOS_GTK2_DESTROY=1)
             add_definitions(-DWORKAROUND_MACOS_GTK2_GLWIDGET=1)
             add_definitions(-DWORKAROUND_MACOS_GTK2_LAGGYPOINTER=1)
         endif ()
-    endif ()
+    endif()
+
+    # Should be set here because the define is used in libs/
+    set(RADIANT_IQM_PLUGIN_HELP "IQM Plugin used by NetRadiant (iqmmodel, picomodel)")
+    set(RADIANT_IQM_PLUGIN "" CACHE STRING "${RADIANT_IQM_PLUGIN_HELP}")
+    if("${RADIANT_IQM_PLUGIN}" STREQUAL "iqmmodel")
+    elseif("${RADIANT_IQM_PLUGIN}" STREQUAL "picomodel")
+    else()
+        if(APPLE)
+            # The picomodel iqm library is buggy on recent macOS
+            set(RADIANT_IQM_PLUGIN "iqmmodel" CACHE STRING "${RADIANT_IQM_PLUGIN_HELP}" FORCE)
+        else()
+            set(RADIANT_IQM_PLUGIN "picomodel" CACHE STRING "${RADIANT_IQM_PLUGIN_HELP}" FORCE)
+        endif()
+    endif()
+    add_definitions(-DRADIANT_IQM_PLUGIN_${RADIANT_IQM_PLUGIN}=1)
+    add_definitions(-DRADIANT_IQM_PLUGIN="${RADIANT_IQM_PLUGIN}")
 endif ()
 
 #-----------------------------------------------------------------------
@@ -373,11 +401,12 @@ if (BUILTIN_GTKGLEXT)
 
     set(GTKGLEXT_CFLAGS "$ENV{CFLAGS} ${GTKGLEXT_CFLAGS}")
 
-    if (NOT CMAKE_BUILD_TYPE MATCHES Release)
-        set(GTKGLEXT_CFLAGS "${GTKGLEXT_CFLAGS} -g")
-    endif ()
 
-    set(CONFIGURE_OPTIONS --disable-gtk-doc --disable-gtk-doc-html --disable-gtk-doc-pdf)
+    set(CONFIGURE_OPTIONS --disable-gtk-doc --disable-gtk-doc-html --disable-gtk-doc-pdf --disable-dependency-tracking --without-x)
+
+    if (CMAKE_BUILD_TYPE MATCHES Release)
+        set(CONFIGURE_OPTIONS ${CONFIGURE_OPTIONS} --disable-debug)
+    endif ()
 
     if (APPLE)
         set(GTKGLEXT_GIT_TAG macos)
@@ -386,17 +415,15 @@ if (BUILTIN_GTKGLEXT)
     endif ()
 
     if (APPLE)
-        set(GTKGLEXT_CFLAGS "${GTKGLEXT_CFLAGS} -DGL_SILENCE_DEPRECATION=1")
-        # FIXME: OpenGL deprecation warnings are not silenced
-        # in Objective C code.
+        set(GTKGLEXT_CFLAGS "${GTKGLEXT_CFLAGS} -DGL_SILENCE_DEPRECATION=1 -Wno-deprecated-declarations")
     endif ()
 
     ExternalProject_Add(gtkglext
         GIT_REPOSITORY https://gitlab.gnome.org/illwieckz/gtkglext.git
         GIT_TAG ${GTKGLEXT_GIT_TAG}
         BUILD_IN_SOURCE ON
-        CONFIGURE_COMMAND export CFLAGS=${GTKGLEXT_LDFLAGS}
-        CONFIGURE_COMMAND && export LDFLAGS=${GTKGLEXT_CFLAGS}
+        CONFIGURE_COMMAND export CCFLAGS=${GTKGLEXT_CFLAGS}
+        CONFIGURE_COMMAND && export LDFLAGS=${GTKGLEXT_LDFLAGS}
         CONFIGURE_COMMAND && export NOCONFIGURE=1
         CONFIGURE_COMMAND && ./autogen.sh
         CONFIGURE_COMMAND && ./configure --prefix "${BUILTINS_INSTALL_DIR}" ${CONFIGURE_OPTIONS}
@@ -556,8 +583,8 @@ if (BUILD_BINARIES AND FHS_INSTALL AND NOT WIN32 AND NOT APPLE)
         DESTINATION ${RADIANT_SHARE_DIR}/applications
     )
     install(FILES
-        icons/mime/map.xml
-        DESTINATION ${RADIANT_SHARE_DIR}/mime/model
+        icons/x-netradiant-map.xml
+        DESTINATION ${RADIANT_SHARE_DIR}/mime/application
     )
 endif ()
 
index 4f1fc2e8a270332a039e76ce816e2956381dfd69..2a63b9cc6ae13b24d3445fff5381b75291ddf4b8 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -15,7 +15,7 @@ MAKEFILE_CONF      ?= Makefile.conf
 # user customizable stuf
 # you may override this in Makefile.conf or the environment
 BUILD              ?= debug
-# or: release, or: extradebug, or: profile
+# or: release, or: debug, or: extradebug, or: profile, or: native
 OS                 ?= $(shell uname)
 # or: Linux, Win32, Darwin
 LDFLAGS            ?=
@@ -498,7 +498,9 @@ endif
 %.o: %.c $(if $(findstring $(DEPEND_ON_MAKEFILE),yes),$(wildcard Makefile*),) | dependencies-check
        $(CC) $< $(CFLAGS) $(CFLAGS_COMMON) $(CPPFLAGS_EXTRA) $(CPPFLAGS_COMMON) $(CPPFLAGS) $(TARGET_ARCH) -c -o $@
 
-
+ifeq ($(OS),Win32)
+$(INSTALLDIR)/q3map2.$(EXE): LDFLAGS_EXTRA := -Wl,--large-address-aware,--stack,4194304
+endif
 $(INSTALLDIR)/q3map2.$(EXE): LIBS_EXTRA := $(LIBS_XML) $(LIBS_GLIB) $(LIBS_PNG) $(LIBS_JPEG) $(LIBS_WEBP) $(LIBS_ZLIB)
 $(INSTALLDIR)/q3map2.$(EXE): CPPFLAGS_EXTRA := $(CPPFLAGS_XML) $(CPPFLAGS_GLIB) $(CPPFLAGS_PNG) $(CPPFLAGS_JPEG) $(CPPFLAGS_WEBP) -Itools/quake3/common -Ilibs -Iinclude
 $(INSTALLDIR)/q3map2.$(EXE): \
@@ -512,6 +514,7 @@ $(INSTALLDIR)/q3map2.$(EXE): \
        tools/quake3/common/scriplib.o \
        tools/quake3/common/threads.o \
        tools/quake3/common/vfs.o \
+       tools/quake3/common/miniz.o \
        tools/quake3/q3map2/brush.o \
        tools/quake3/q3map2/brush_primit.o \
        tools/quake3/q3map2/bspfile_abstract.o \
@@ -625,6 +628,7 @@ $(INSTALLDIR)/q3data.$(EXE): \
        tools/quake3/common/scriplib.o \
        tools/quake3/common/trilib.o \
        tools/quake3/common/vfs.o \
+       tools/quake3/common/miniz.o \
        tools/quake3/q3data/3dslib.o \
        tools/quake3/q3data/compress.o \
        tools/quake3/q3data/images.o \
@@ -672,6 +676,7 @@ $(INSTALLDIR)/radiant.$(EXE): \
        radiant/error.o \
        radiant/feedback.o \
        radiant/filetypes.o \
+       radiant/filterbar.o \
        radiant/filters.o \
        radiant/findtexturedialog.o \
        radiant/glwidget.o \
@@ -679,6 +684,7 @@ $(INSTALLDIR)/radiant.$(EXE): \
        radiant/groupdialog.o \
        radiant/gtkdlgs.o \
        radiant/gtkmisc.o \
+       radiant/gtktheme.o \
        radiant/help.o \
        radiant/image.o \
        radiant/mainframe.o \
index 3a84cbad53b13b905868044faa6bf096ee4ba725..dae993ca340c28ec1d66fc3197999d5867b6eaaa 100644 (file)
--- a/README.md
+++ b/README.md
@@ -50,6 +50,8 @@ cd netradiant
 
 To fetch default game packages you'll need Git, Subversion, Wget and `unzip`.
 
+It's possible to build against GTK3 using the `-DGTK_TARGET=3` cmake option, but some problems may be encountered, see [GUI/GTK](https://gitlab.com/xonotic/netradiant/-/issues?label_name[]=GUI%2FGTK3) issues. GTK2 remains recommended for now.
+
 
 ### Ubuntu:
 
@@ -77,8 +79,8 @@ export PATH="/mingw64/bin:${PATH}"
 Install the dependencies this way:
 
 ```sh
-pacman -S --needed base-devel git \
-    mingw-w64-$(uname -m)-{ntldd-git,subversion,unzip,toolchain,cmake,make,gtk2,gtkglext,libwebp,minizip-git}
+pacman -S --needed base-devel git subversion unzip \
+    mingw-w64-$(uname -m)-{ntldd-git,toolchain,cmake,make,gtk2,gtkglext,libwebp,minizip-git}
 ```
 
 Explicitely use `mingw-w64-x86_64-` or `mingw-w64-i686-` prefix instead of `mingw-w64-$(uname -m)` if you need to target a non-default architecture.
@@ -98,7 +100,7 @@ If you plan to build a bundle, you also need to install `patchelf`
 Note: some dependencies of gtk+ seems to only be pulled with gtk+3, gtkglext seems to require libffi.
 
 ```sh
-brew install cmake glib libffi gtk+ gtk+3 pkgconfig minizip webp coreutils gnu-sed wget sassc
+brew install cmake glib gobject-introspection libffi gtk+ gtk+3 gtk-doc pkgconfig minizip webp coreutils gnu-sed wget sassc
 brew link --force gettext
 ```
 
@@ -143,7 +145,7 @@ For supported system, bundling dependencies can be done this way:
 ./easy-builder -DBUNDLE_LIBRARIES=ON
 ```
 
-Note: always do bundling on a clean system without unrelated software installed.
+Note: Always do bundling on a clean and fresh system without unrelated software installed.
 
 
 ## Advanced compilation
@@ -158,6 +160,7 @@ This project uses the usual CMake workflow:
 ```sh
 cmake -G "Unix Makefiles" -S. -Bbuild -DCMAKE_BUILD_TYPE=Debug
 cmake --build build -- -j$(nproc)
+cmake --install build
 ```
 
 
@@ -166,6 +169,7 @@ cmake --build build -- -j$(nproc)
 ```sh
 cmake -G "Unix Makefiles" -S. -Bbuild -DCMAKE_BUILD_TYPE=Release
 cmake --build build -- -j$(nproc)
+cmake --install build
 ```
 
 Note: macOS users need to build built-in GtkGLExt before building NetRadiant:
@@ -175,6 +179,7 @@ cmake -G "Unix Makefiles" -S. -Bbuild -DCMAKE_BUILD_TYPE=Release
 cmake --build build -- -j$(nproc) builtins
 cmake -G "Unix Makefiles" -S. -Bbuild
 cmake --build build -- -j$(nproc)
+cmake --install build
 ```
 
 
@@ -184,12 +189,14 @@ The initial build will download the gamepacks and build NetRadiant and tools. If
 
 ```sh
 cmake --build build --target binaries -- -j$(nproc)
+cmake --install build
 ```
 
 You should still periodically update gamepacks:
 
 ```sh
 cmake --build build --target gamepacks
+cmake --install build
 ```
 
 
@@ -203,8 +210,6 @@ Options:
   Do not build NetRadiant (default: `ON`, build netradiant graphical editor);
 * `BUILD_TOOLS=OFF`  
   Do not build q3map2 and other tools (default: `ON`, build command line tools);
-* `BUILD_DAEMONMAP=OFF`  
-  Do not build daemonmap tool (default: `ON` if submodule is there, buils daemonmap navigation mesh generator);
 * `BUILD_CRUNCH=OFF`  
   Disable crunch support (default: `ON` if submodule is there, enable crunch support);
 * `RADIANT_ABOUTMSG="Custom build by $(whoami)"`  
@@ -222,8 +227,6 @@ Targets:
      * `quake3`         Compile all the Quake 3 tools:
          - `q3map2`     Compile the Quake 3 map compiler;
          - `q3data`     Compile the q3data tool;
-     * `unvanquished`   Compile all the Unvanquished tools: `daemonmap`, `q3map3`, `q4data`;
-         - `daemonmap`  Compile the daemonmap navigation mesh generator.
 
 Type `make help` to get an exhaustive list of targets.
 
@@ -262,6 +265,10 @@ Target:
 
 * `install` Install files.
 
+```sh
+cmake --install build
+```
+
 
 ## Additonnal information
 
index cf12399e3f95c8d24043058a1bff32eb92e6e229..e1e09e35575e5f55920d5224320a4d2e68cb57c6 100644 (file)
@@ -9,6 +9,9 @@ if (PKG_CONFIG_FOUND)
         set(_pkgconfig_REQUIRED REQUIRED)
     endif ()
     pkg_check_modules(GLIB ${_pkgconfig_REQUIRED} glib-2.0)
+    if (GLIB_LINK_LIBRARIES)
+        set(GLIB_LIBRARIES ${GLIB_LINK_LIBRARIES}) # HACK
+    endif ()
 else ()
     find_path(GLIB_INCLUDE_DIRS glib.h)
     find_library(GLIB_LIBRARIES glib-2.0)
index ff205261d74b2301d43497622e9f9671f622f03c..6f21791d3eeaac20776b75ce8988880c2bf833af 100644 (file)
@@ -4,6 +4,9 @@ if (PKG_CONFIG_FOUND)
         set(_pkgconfig_REQUIRED REQUIRED)
     endif ()
     pkg_check_modules(GTK2 ${_pkgconfig_REQUIRED} gtk+-2.0)
+    if (GTK2_LINK_LIBRARIES)
+        set(GTK2_LIBRARIES ${GTK2_LINK_LIBRARIES}) # HACK
+    endif ()
 else ()
     find_path(GTK2_INCLUDE_DIRS gtk.h)
     # find_library(GTK2_LIBRARIES)
index fa8ddda9af38d324e0789a37bc6faf7d0c2c8441..7090890bdfaf54baa109ef9ace6163519c03d313 100644 (file)
@@ -11,6 +11,9 @@ if (PKG_CONFIG_FOUND)
     elseif (WIN32)
         pkg_check_modules(GtkGLExt ${_pkgconfig_REQUIRED} gtkglext-win32-1.0)
     endif ()
+    if (GtkGLExt_LINK_LIBRARIES)
+        set(GtkGLExt_LIBRARIES ${GtkGLExt_LINK_LIBRARIES}) # HACK
+    endif ()
 else ()
     find_path(GtkGLExt_INCLUDE_DIRS gtkglwidget.h)
     # find_library(GtkGLExt_LIBRARIES)
index 0de098f438fb258aba1d66bf397cb802e660db09..8aeea29614c10c4dfadcc90e98ca7de9c186a67f 100644 (file)
@@ -4,6 +4,9 @@ if (PKG_CONFIG_FOUND)
         set(_pkgconfig_REQUIRED REQUIRED)
     endif ()
     pkg_check_modules(Minizip ${_pkgconfig_REQUIRED} minizip)
+    if (Minizip_LINK_LIBRARIES)
+        set(Minizip_LIBRARIES ${Minizip_LINK_LIBRARIES}) # HACK
+    endif ()
 else ()
     find_path(Minizip_INCLUDE_DIRS unzip.h)
     # find_library(Minizip_LIBRARIES)
index 67359ef05887d013654475160f4425f373541c42..aaf64a16c707162dc97f4ae4caf90f107ec8191a 100644 (file)
@@ -5,6 +5,12 @@ if (PKG_CONFIG_FOUND)
     endif ()
     pkg_search_module(Pango ${_pkgconfig_REQUIRED} pango pangocairo)
     pkg_search_module(PangoFT2 ${_pkgconfig_REQUIRED} pangoft2)
+    if (Pango_LINK_LIBRARIES)
+        set(Pango_LIBRARIES ${Pango_LINK_LIBRARIES}) # HACK
+    endif()
+    if (PangoFT2_LINK_LIBRARIES)
+        set(PangoFT2_LIBRARIES ${PangoFT2_LINK_LIBRARIES}) # HACK
+    endif()
 else ()
     # find_path(Pango_INCLUDE_DIRS)
     # find_library(Pango_LIBRARIES)
index c9b83cb74e68326dfc7863cb13f4943435f6be2b..3425a1dfd5516a43e935abbb1f37a5526cc617c8 100644 (file)
@@ -2,7 +2,7 @@ radiant_plugin(bobtoolz
         dialogs/dialogs-gtk.cpp dialogs/dialogs-gtk.h
 
         bobToolz.h
-        bobToolz-GTK.cpp
+        bobToolz-GTK.cpp bobToolz-GTK.h
         bsploader.cpp bsploader.h
         cportals.cpp CPortals.h
         ctfresource_gtk.h
index be49df4ef4ab9aa06f539161164e9bfce751be70..1f281ac13ea6963629aa7a00497e057bb2b22b31 100644 (file)
@@ -486,18 +486,23 @@ std::list<DPatch> DPatch::Split(){
        int i;
        int x, y;
 
-       if ( width >= 5 ) {
+       if ( height >= 5 ) {
                std::list<DPatch> patchColList = SplitCols();
                for ( std::list<DPatch>::iterator patchesCol = patchColList.begin(); patchesCol != patchColList.end(); patchesCol++ )
                {
-                       std::list<DPatch> patchRowList = ( *patchesCol ).SplitRows();
-                       for ( std::list<DPatch>::iterator patchesRow = patchRowList.begin(); patchesRow != patchRowList.end(); patchesRow++ )
-                       {
-                               patchList.push_front( *patchesRow );
+                       if( width >= 5 ){
+                               std::list<DPatch> patchRowList = ( *patchesCol ).SplitRows();
+                               for ( std::list<DPatch>::iterator patchesRow = patchRowList.begin(); patchesRow != patchRowList.end(); patchesRow++ )
+                               {
+                                       patchList.push_front( *patchesRow );
+                               }
+                       }
+                       else{
+                               patchList.push_front( *patchesCol );
                        }
                }
        }
-       else if ( height >= 5 ) {
+       else if ( width >= 5 ) {
                std::list<DPatch> patchRowList = SplitRows();
                for ( std::list<DPatch>::iterator patchesRow = patchRowList.begin(); patchesRow != patchRowList.end(); patchesRow++ )
                {
index 898c94cb00f63357d8c9d34ee144b47d779b88ed..c1ac8b1de987cc0c18268b9de520caafcc5caa47 100644 (file)
@@ -163,7 +163,7 @@ const char* QERPlug_GetCommandTitleList(){
 }
 
 
-const int NUM_TOOLBARBUTTONS = 14;
+const int NUM_TOOLBARBUTTONS = 13;
 
 std::size_t ToolbarButtonCount( void ) {
        return NUM_TOOLBARBUTTONS;
@@ -176,27 +176,27 @@ virtual const char* getImage() const {
        switch ( mIndex ) {
        case 0: return "bobtoolz_cleanup.png";
        case 1: return "bobtoolz_poly.png";
-       case 2: return "bobtoolz_caulk.png";
-       case 3: return "";
-       case 4: return "bobtoolz_treeplanter.png";
-       case 5: return "bobtoolz_trainpathplot.png";
-       case 6: return "bobtoolz_dropent.png";
-       case 7: return "";
-       case 8: return "bobtoolz_merge.png";
-       case 9: return "bobtoolz_split.png";
-       case 10: return "bobtoolz_splitrow.png";
-       case 11: return "bobtoolz_splitcol.png";
-       case 12: return "";
-       case 13: return "bobtoolz_turnedge.png";
+//     case 2: return "bobtoolz_caulk.png";
+       case 2: return "";
+       case 3: return "bobtoolz_treeplanter.png";
+       case 4: return "bobtoolz_trainpathplot.png";
+       case 5: return "bobtoolz_dropent.png";
+       case 6: return "";
+       case 7: return "bobtoolz_merge.png";
+       case 8: return "bobtoolz_split.png";
+       case 9: return "bobtoolz_splitrow.png";
+       case 10: return "bobtoolz_splitcol.png";
+       case 11: return "";
+       case 12: return "bobtoolz_turnedge.png";
        }
        return NULL;
 }
 virtual EType getType() const {
        switch ( mIndex ) {
-       case 3: return eSpace;
-       case 4: return eToggleButton;
-       case 7: return eSpace;
-       case 12: return eSpace;
+       case 2: return eSpace;
+       case 3: return eToggleButton;
+       case 6: return eSpace;
+       case 11: return eSpace;
        default: return eButton;
        }
 }
@@ -204,15 +204,15 @@ virtual const char* getText() const {
        switch ( mIndex ) {
        case 0: return "Cleanup";
        case 1: return "Polygons";
-       case 2: return "Caulk";
-       case 4: return "Tree Planter";
-       case 5: return "Plot Splines";
-       case 6: return "Drop Entity";
-       case 8: return "Merge 2 Patches";
-       case 9: return "Split Patch";
-       case 10: return "Split Patch Rows";
-       case 11: return "Split Patch Columns";
-       case 13: return "Flip Terrain";
+//     case 2: return "Caulk";
+       case 3: return "Tree Planter";
+       case 4: return "Plot Splines";
+       case 5: return "Drop Entity";
+       case 7: return "Merge 2 Patches";
+       case 8: return "Split Patch";
+       case 9: return "Split Patch Rows";
+       case 10: return "Split Patch Columns";
+       case 12: return "Flip Terrain";
        }
        return NULL;
 }
@@ -220,15 +220,15 @@ virtual const char* getTooltip() const {
        switch ( mIndex ) {
        case 0: return "Brush Cleanup";
        case 1: return "Polygons";
-       case 2: return "Caulk selection";
-       case 4: return "Tree Planter";
-       case 5: return "Plot Splines";
-       case 6: return "Drop Entity";
-       case 8: return "Merge 2 Patches";
-       case 9: return "Split Patch";
-       case 10: return "Split Patch Rows";
-       case 11: return "Split Patch Columns";
-       case 13: return "Flip Terrain (Turn Edge)";
+//     case 2: return "Caulk selection";
+       case 3: return "Tree Planter";
+       case 4: return "Plot Splines";
+       case 5: return "Drop Entity";
+       case 7: return "Merge 2 Patches";
+       case 8: return "Split Patch";
+       case 9: return "Split Patch Rows";
+       case 10: return "Split Patch Columns";
+       case 12: return "Flip Terrain (Turn Edge)";
        }
        return NULL;
 }
@@ -239,15 +239,15 @@ virtual void activate() const {
        switch ( mIndex ) {
        case 0: DoFixBrushes(); break;
        case 1: DoPolygonsTB(); break;
-       case 2: DoCaulkSelection(); break;
-       case 4: DoTreePlanter(); break;
-       case 5: DoTrainPathPlot(); break;
-       case 6: DoDropEnts(); break;
-       case 8: DoMergePatches(); break;
-       case 9: DoSplitPatch(); break;
-       case 10: DoSplitPatchRows(); break;
-       case 11: DoSplitPatchCols(); break;
-       case 13: DoFlipTerrain(); break;
+//     case 2: DoCaulkSelection(); break;
+       case 3: DoTreePlanter(); break;
+       case 4: DoTrainPathPlot(); break;
+       case 5: DoDropEnts(); break;
+       case 7: DoMergePatches(); break;
+       case 8: DoSplitPatch(); break;
+       case 9: DoSplitPatchRows(); break;
+       case 10: DoSplitPatchCols(); break;
+       case 12: DoFlipTerrain(); break;
        }
 }
 
diff --git a/contrib/bobtoolz/bobToolz-GTK.h b/contrib/bobtoolz/bobToolz-GTK.h
new file mode 100644 (file)
index 0000000..5e6b3d9
--- /dev/null
@@ -0,0 +1,8 @@
+#if !defined(INCLUDED_BOBTOOLZGTK_H)
+#define INCLUDED_BOBTOOLZGTK_H
+
+#include <uilib/uilib.h>
+
+static ui::Window main_window{ui::null};
+
+#endif
index a5732ff5cb30b00b87a9dd79221bb8905f0680ff..cfbf12c4071a757587a918d521be3c6256ffcce5 100644 (file)
@@ -29,6 +29,8 @@
 #include "../lists.h"
 #include "../misc.h"
 
+#include "../bobToolz-GTK.h"
+
 
 /*--------------------------------
         Callback Functions
@@ -211,6 +213,7 @@ EMessageBoxReturn DoMessageBox( const char* lpText, const char* lpCaption, EMess
        int loop = 1;
 
        auto window = ui::Window( ui::window_type::TOP );
+       gtk_window_set_transient_for( GTK_WINDOW( window ), main_window );
        window.connect( "delete_event", G_CALLBACK( custom_dialog_delete_callback ), NULL );
        window.connect( "destroy", G_CALLBACK( gtk_widget_destroy ), NULL );
        gtk_window_set_title( window, lpCaption );
@@ -294,7 +297,7 @@ EMessageBoxReturn DoMessageBox( const char* lpText, const char* lpCaption, EMess
                ret = eIDNO;
        }
 
-       gtk_window_set_position( window, GTK_WIN_POS_CENTER );
+       gtk_window_set_position( GTK_WINDOW( window ),GTK_WIN_POS_CENTER_ON_PARENT );
        window.show();
        gtk_grab_add( window );
 
@@ -405,6 +408,7 @@ EMessageBoxReturn DoPolygonBox( PolygonRS* rs ){
        int loop = 1;
 
        auto window = ui::Window( ui::window_type::TOP );
+       gtk_window_set_transient_for( GTK_WINDOW( window ), main_window );
 
        window.connect( "delete_event", G_CALLBACK( custom_dialog_delete_callback ), NULL );
        window.connect( "destroy", G_CALLBACK( gtk_widget_destroy ), NULL );
@@ -526,7 +530,8 @@ EMessageBoxReturn DoPolygonBox( PolygonRS* rs ){
 
        // ---- /vbox ----
 
-       gtk_window_set_position( window, GTK_WIN_POS_CENTER );
+       gtk_window_set_position( GTK_WINDOW( window ),GTK_WIN_POS_CENTER_ON_PARENT );
+       gtk_window_set_modal( GTK_WINDOW( window ), TRUE );
        window.show();
        gtk_grab_add( window );
 
@@ -1273,6 +1278,8 @@ EMessageBoxReturn DoResetTextureBox( ResetTextureRS* rs, ui::Window main_window
        int loop = 1;
 
        auto window = main_window.create_dialog_window( "Texture Reset", G_CALLBACK( custom_dialog_delete_callback ), &dialog );
+       gtk_window_set_transient_for( GTK_WINDOW( window ), main_window );
+       gtk_window_set_modal( GTK_WINDOW( window ), TRUE );
 
        window.connect( "destroy", G_CALLBACK( gtk_widget_destroy ), NULL );
 
@@ -1488,6 +1495,7 @@ EMessageBoxReturn DoResetTextureBox( ResetTextureRS* rs, ui::Window main_window
 
        // ---- /vbox ----
 
+       gtk_window_set_position( GTK_WINDOW( window ),GTK_WIN_POS_CENTER_ON_PARENT );
        window.show();
        gtk_grab_add( window );
 
index decbf8ad49825f1ae49404e33ff484b97100bbb0..b6ef6ff2d3b1a90d134685e3d9f26d08a6c8803d 100644 (file)
@@ -463,7 +463,7 @@ void DoMergePatches(){
        merge_info = mrgPatches[0].IsMergable( &mrgPatches[1] );
 
        if ( merge_info.mergable ) {
-               globalOutputStream() << merge_info.pos1 << " " <<  merge_info.pos2;
+//             globalOutputStream() << merge_info.pos1 << " " <<  merge_info.pos2;
                //Message removed, No tools give feedback on success.
                //globalOutputStream() << "bobToolz MergePatches: Patches Mergable.\n";
                DPatch* newPatch = mrgPatches[0].MergePatches( merge_info, &mrgPatches[0], &mrgPatches[1] );
@@ -481,10 +481,17 @@ void DoMergePatches(){
                }
                else
                {
+                       newPatch->BuildInRadiant( patches[0]->path().parent().get_pointer() );
+
+                       scene::Instance& parent = *( patches[1]->parent() );
                        Path_deleteTop( patches[0]->path() );
                        Path_deleteTop( patches[1]->path() );
+                       Entity* entity = Node_getEntity( parent.path().top() );
+                       if ( entity != 0
+                               && Node_getTraversable( parent.path().top() )->empty() ) {
+                               Path_deleteTop( parent.path() );
+                       }
 
-                       newPatch->BuildInRadiant();
                        delete newPatch;
                }
        }
@@ -519,7 +526,7 @@ void DoSplitPatch() {
 
        std::list<DPatch> patchList = patch.Split();
        for ( std::list<DPatch>::iterator patches = patchList.begin(); patches != patchList.end(); patches++ ) {
-               ( *patches ).BuildInRadiant();
+               ( *patches ).BuildInRadiant( instance.path().parent().get_pointer() );
        }
 
        Path_deleteTop( instance.path() );
@@ -549,7 +556,7 @@ void DoSplitPatchCols() {
 
        std::list<DPatch> patchList = patch.SplitCols();
        for ( std::list<DPatch>::iterator patches = patchList.begin(); patches != patchList.end(); patches++ ) {
-               ( *patches ).BuildInRadiant();
+               ( *patches ).BuildInRadiant( instance.path().parent().get_pointer() );
        }
 
        Path_deleteTop( instance.path() );
@@ -579,7 +586,7 @@ void DoSplitPatchRows() {
 
        std::list<DPatch> patchList = patch.SplitRows();
        for ( std::list<DPatch>::iterator patches = patchList.begin(); patches != patchList.end(); patches++ ) {
-               ( *patches ).BuildInRadiant();
+               ( *patches ).BuildInRadiant( instance.path().parent().get_pointer() );
        }
 
        Path_deleteTop( instance.path() );
index f73c9787a0267a0fe49236bac473542e91f1c986..b0f7bcfb7689b54f00d67ea8d9144de6a8592c94 100644 (file)
@@ -384,7 +384,7 @@ void MakeNormal( const vec_t* va, const vec_t* vb, const vec_t* vc, vec_t* out )
 }
 
 char* GetFilename( char* buffer, const char* filename ) {
-       strcpy( buffer, GlobalRadiant().getAppPath() );
+       strcpy( buffer, GlobalRadiant().getDataPath() );
        strcat( buffer, "plugins/" );
        strcat( buffer, filename );
        return buffer;
index 629c163336688a4b1ca5808e84a0f7eef5617801..33aa5bc881b69d032584244de5cf23755c48320c 100644 (file)
@@ -209,7 +209,7 @@ std::list<DWinding*> *CreateTrace( dleaf_t *leaf, int c, vis_header *header, byt
                }
        }
 
-       delete repeatlist;
+       delete [] repeatlist;
 
        return pointlist;
 }
index 068325ea2bbf88b6a56d00026bfaa5806e162822..ead267890a0326d55f109f274e996afe93cf0bf1 100644 (file)
@@ -5,6 +5,7 @@
 #include "callbacks.h"
 #include "support.h"
 #include "gtkutil/dialog.h"
+#include "plugin.h"
 
 #define GLADE_HOOKUP_OBJECT( component,widget,name ) \
        g_object_set_data_full( G_OBJECT( component ), name, \
@@ -20,6 +21,8 @@ ui::Widget create_w_plugplug2( ui::Window main_window ){
 
        auto w_plugplug2 = main_window.create_dialog_window( "BrushExport", G_CALLBACK( dialog_delete_callback ), &dialog );
        gtk_widget_set_name( w_plugplug2, "w_plugplug2" );
+       gtk_window_set_position( GTK_WINDOW( w_plugplug2 ), GTK_WIN_POS_CENTER_ON_PARENT );
+       gtk_window_set_transient_for( GTK_WINDOW( w_plugplug2 ), GTK_WINDOW( g_pRadiantWnd ) );
        gtk_window_set_destroy_with_parent( w_plugplug2, TRUE );
 
        auto vbox1 = ui::VBox( FALSE, 0 );
index 3bef9d5a713636003c2590cbdb82c578bbc67e9d..9b40fd69327bd8665f80c3442ca526382debfa9c 100644 (file)
@@ -49,12 +49,15 @@ void CreateWindow( ui::Window main_window );
 void DestroyWindow( void );
 bool IsWindowOpen( void );
 
+ui::Widget g_pRadiantWnd{ui::null};
+
 namespace BrushExport
 {
 ui::Window g_mainwnd{ui::null};
 
 const char* init( void* hApp, void* pMainWidget ){
        g_mainwnd = ui::Window::from(pMainWidget);
+       g_pRadiantWnd = ui::Window::from(pMainWidget);
        ASSERT_TRUE( g_mainwnd );
        return "";
 }
index a2dc6cb63edc734f2af1377da84862ecde7d85f5..5808cd98d8578a1ad9977dc5302905c1d69e5896 100644 (file)
 #if !defined( INCLUDED_BRUSH_EXPORT_H )
 #define INCLUDED_BRUSH_EXPORT_H
 
+#include <uilib/uilib.h>
+
 #define PLUGIN_NAME "BrushExport"
 #define PLUGIN_VERSION "2.0"
 
+extern ui::Widget g_pRadiantWnd;
+
 #endif
index fd9c24f43b9cb5edb4682d288021999f4b28fd67..52966151dcaa4e6ab8eaa32282ee4f38d4eccf69 100644 (file)
Binary files a/contrib/camera/bitmaps/camera_insp.png and b/contrib/camera/bitmaps/camera_insp.png differ
index 08b0b5bbbc6eca36f0969db174e5ff8d355442c2..3831c0beb384a91bac81c340e3cd6a31fafa096b 100644 (file)
@@ -137,7 +137,7 @@ class CameraInspectorButton : public IToolbarButton
 {
 public:
 virtual const char* getImage() const {
-       return "camera_insp.jpg";
+       return "camera_insp.png";
 }
 virtual const char* getText() const {
        return "Inspector";
index 3af80d63a98292025e6dd70178190234ab4e8ded..6875476bb13c9983e9bd7ddcb9b5eadda9363870 100644 (file)
@@ -17,6 +17,8 @@
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
+#include <uilib/uilib.h>
+
 #include "gensurf.h"
 
 // Global plugin FuncTable
@@ -32,7 +34,7 @@ bool g_bInitDone;
 #include "iplugin.h"
 
 const char* QERPlug_Init( void* hApp, void* pMainWidget ){
-       g_pRadiantWnd = (GtkWidget*)pMainWidget;
+       g_pRadiantWnd = ui::Window::from(pMainWidget);
 
        return "GenSurf for Q3Radiant";
 }
diff --git a/contrib/meshtex/.gitattributes b/contrib/meshtex/.gitattributes
new file mode 100644 (file)
index 0000000..dfe0770
--- /dev/null
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto
diff --git a/contrib/meshtex/.gitignore b/contrib/meshtex/.gitignore
new file mode 100644 (file)
index 0000000..db8ef93
--- /dev/null
@@ -0,0 +1,10 @@
+# Visual Studio generated files
+*.aps
+*.ncb
+*.suo
+*.user
+CodeVersion.h
+
+# build results
+Debug/
+Release/
diff --git a/contrib/meshtex/AllocatedMatrix.h b/contrib/meshtex/AllocatedMatrix.h
new file mode 100644 (file)
index 0000000..3967d6c
--- /dev/null
@@ -0,0 +1,70 @@
+/**
+ * @file AllocatedMatrix.h
+ * Declares and implements the AllocatedMatrix template class.
+ * @ingroup meshtex-util
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(INCLUDED_ALLOCATEDMATRIX_H)
+#define INCLUDED_ALLOCATEDMATRIX_H
+
+#include "debugging/debugging.h"
+#include "ipatch.h"
+
+/**
+ * Matrix subclass that allocates its data array on construction and
+ * deallocates it on destruction.
+ *
+ * @ingroup meshtex-util
+ */
+template<typename Element>
+class AllocatedMatrix : public Matrix<Element>
+{
+       //std::size_t m_x, m_y;
+       //Element* m_data;
+public: // public methods
+
+   /**
+    * Constructor. Allocates a data array of the appropriate size.
+    *
+    * @param x Matrix x dimension.
+    * @param y Matrix y dimension.
+    */
+   //AllocatedMatrix(std::size_t x, std::size_t y) : m_x(x), m_y(y), m_data(_allocated = new Element[x*y]){} //doesnt work.
+   //AllocatedMatrix(std::size_t x, std::size_t y) : Matrix(x, y, (_allocated = new Element[x*y])) {} //msvc
+   typedef Matrix<Element> matrix_type;
+   AllocatedMatrix(std::size_t x, std::size_t y) : matrix_type(x, y, (_allocated = new Element[x*y])) {}
+
+
+   /**
+    * Destructor. Deallocates the data array.
+    */
+   ~AllocatedMatrix() { delete [] _allocated; }
+
+private: // private member vars
+
+   /**
+    * Pointer to the data array so that the destructor can find it for deletion.
+    */
+   Element *_allocated;
+};
+
+#endif // #if !defined(INCLUDED_ALLOCATEDMATRIX_H)
diff --git a/contrib/meshtex/COMPILING b/contrib/meshtex/COMPILING
new file mode 100644 (file)
index 0000000..56ce951
--- /dev/null
@@ -0,0 +1,65 @@
+*** Downloading the latest source:
+
+https://github.com/neogeographica/MeshTex
+
+
+*** Downloading dependences:
+
+This project depends on sources/SDKs for GtkRadiant 1.5, GTK+ 2, and STLport 5.  It was most recently compiled against minor versions GtkRadiant 1.5.0, GTK+ 2.24.10 (32-bit), and STLport 5.2.1.  At the time of writing this file, those were available at:
+
+  - GtkRadiant 1.5: https://github.com/TTimo/GtkRadiant/tree/1.5-release
+  - GTK+ 2 stack: http://www.gtk.org/download/index.php
+  - STLport 5: http://sourceforge.net/projects/stlport/files/STLport/
+
+Note that you should get the entire GTK+ stack (linked as an "all-in-one bundle" on the GTK+ download page), not just the gtk library.
+
+Unzip the resources that you download and place them in a handy location that can be accessed by the computer you will be using to build the plugin.  You do not need to build any libraries for these dependences:
+
+  - For GtkRadiant, only the header files are important.
+  - The GTK+ download includes prebuilt libraries.
+  - STLport does not need a library built, as long as you are not using STL iostreams.  In the folder where you placed the STLport distribution, navigate into stlport\stl\config and open user_config.h in a text editor.  Uncomment the line that defines _STLP_NO_IOSTREAMS to a value of 1.
+
+You also need an installation of the GtkRadiant 1.5 application in order to actually use the plugin.  Rather than building it from source, you will probably just want to download and execute the usual GtkRadiant installer: http://icculus.org/gtkradiant/downloads.html#gtkr15
+
+It can also be helpful to have git installed... which you probably do, if you got this from GitHub.  Nevertheless it's not actually necessary to have git available during the compile process; it's just used to gather some information if it is available, as described below.
+
+
+*** Building:
+
+There's no platform-specific code in the MeshTex plugin, but currently I'm only building it on Windows.  Adventurous Mac/Linux porters are welcome to have a go.
+
+meshtex.sln is a Visual Studio 2008 solution.  (It may very well also import correctly into later versions of Visual Studio.)
+
+Open meshtex.sln using Visual Studio.  Before building the project you must configure a few directories, as follows:
+
+- In the left pane, choose the Property Manager tab.
+- Under either the "Debug | Win32" or "Release | Win32" folder (it doesn't matter which), double-click on "localdirs".  This opens a custom properties dialog.
+- On the left side of the custom properties dialog, select "User Macros".  This will display definitions for some necessary directories on the right side of the dialog.
+- On the right side, double-click on "RadiantSrcDir".  This will open a dialog to edit its value.
+- Replace the Value entry here with the correct path to the root of your GtkRadiant source.  The directory referenced here should be the one that CONTAINS the "include" and "libs" directories.
+- Click OK to set that value for "RadiantSrcDir".
+- Similarly you should edit the value for "StlportDir".  The directory referenced here is the one that CONTAINS the "stlport" directory for your downloaded STLport source.
+- Similarly you should edit the value for "GtkDir".  The directory referenced here is the one that CONTAINS the "include" and "lib" directories for your downloaded GTK+ SDK.
+- Similarly you should edit the value for "GitDir".  The directory referenced here is the one that contains the "git.exe" program on your system.  If you don't have git installed, this can be any bogus path.  It is used in a pre-build step as described below.
+- Finally, edit the value for "RadiantExeDir".  The directory referenced here is the one that contains "GtkRadiant.exe" for your working installation of the GtkRadiant 1.5 application.  It is used in a post-build step as described below.
+- Click the OK button to accept and close the dialog.
+
+Once you have configured the directories, it seems to be helpful to quit out of Visual Studio (save your changes when prompted) and restart before attempting to build.
+
+The build process has a pre-build step that uses <GitDir>/git to find the current git commit hash for the codebase that you are building.  This commit hash is placed in a generated header file, CodeVersion.h.  If the git executable cannot be located then the string "n/a" will be used for the commit hash.  During the build process the commit hash will be used as part of the product version string that will be visible in the DLL properties.  This information is just FYI; you don't need to do anything about this other than set the value of GitDir as per above if you do have git installed.
+
+The build process also has a post-build step that will copy meshtex.dll to <RadiantExeDir>/plugins after each build.  If RadiantExeDir points to a protected directory that requires elevated user permissions (such as "Program Files (x86)") then you will need to be running Visual Studio as administrator in order for this copy to succeed.
+
+If you do NOT want the build process to perform the post-build copy of meshtex.dll, then follow these steps:
+
+- Right click on the "meshtex" project in the Solution Explorer or Property Manager, and choose "properties".  This opens its properties dialog.
+- In the "Configuration" dropdown, choose "All Configurations".
+- On the left side of the dialog, find "Post-Build Event" under "Build Events", which is under "Configuration Properties".  Select "Post-Build Event".
+- The right-hand side should now show the command line and description for the copy process.
+- The right-hand side also has a property called "Excluded From Build".  Change that from "No" to "Yes".
+- Click the OK button to accept and close the dialog.
+
+
+You should be ready to build!  Press F7, or select "Build Solution" from the "Build" menu on the top bar.
+
+After building, you can run GtkRadiant by pressing F5, or selecting "Start Debugging" from the "Debug" menu on the top bar.  If you have not removed the post-build copy step -- and that step has succeeded -- then GtkRadiant will have the most recently built version of the plugin installed.
diff --git a/contrib/meshtex/Doxyfile b/contrib/meshtex/Doxyfile
new file mode 100644 (file)
index 0000000..2ca3c81
--- /dev/null
@@ -0,0 +1,1795 @@
+# Doxyfile 1.8.1.1
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file 
+# that follow. The default is UTF-8 which is also the encoding used for all 
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the 
+# iconv built into libc) for the transcoding. See 
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or sequence of words) that should 
+# identify the project. Note that if you do not use Doxywizard you need 
+# to put quotes around the project name if it contains spaces.
+
+PROJECT_NAME           = MeshTex
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. 
+# This could be handy for archiving the generated documentation or 
+# if some version control system is used.
+
+PROJECT_NUMBER         = 3.0
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description 
+# for a project that appears at the top of each page and should give viewer 
+# a quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          = 
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is 
+# included in the documentation. The maximum height of the logo should not 
+# exceed 55 pixels and the maximum width should not exceed 200 pixels. 
+# Doxygen will copy the logo to the output directory.
+
+PROJECT_LOGO           = 
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) 
+# base path where the generated documentation will be put. 
+# If a relative path is entered, it will be relative to the location 
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = docs
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 
+# 4096 sub-directories (in 2 levels) under the output directory of each output 
+# format and will distribute the generated files over these directories. 
+# Enabling this option can be useful when feeding doxygen a huge amount of 
+# source files, where putting all generated files in the same directory would 
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all 
+# documentation generated by doxygen is written. Doxygen will use this 
+# information to generate all constant output in the proper language. 
+# The default language is English, other supported languages are: 
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, 
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, 
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English 
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, 
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, 
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will 
+# include brief member descriptions after the members that are listed in 
+# the file and class documentation (similar to JavaDoc). 
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend 
+# the brief description of a member or function before the detailed description. 
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the 
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator 
+# that is used to form the text in various listings. Each string 
+# in this list, if found as the leading text of the brief description, will be 
+# stripped from the text and the result after processing the whole list, is 
+# used as the annotated text. Otherwise, the brief description is used as-is. 
+# If left blank, the following values are used ("$name" is automatically 
+# replaced with the name of the entity): "The $name class" "The $name widget" 
+# "The $name file" "is" "provides" "specifies" "contains" 
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       = "The $name class" \
+                         "The $name widget" \
+                         "The $name file" \
+                         is \
+                         provides \
+                         specifies \
+                         contains \
+                         represents \
+                         a \
+                         an \
+                         the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then 
+# Doxygen will generate a detailed section even if there is only a brief 
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all 
+# inherited members of a class in the documentation of that class as if those 
+# members were ordinary class members. Constructors, destructors and assignment 
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full 
+# path before files name in the file list and in the header files. If set 
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag 
+# can be used to strip a user-defined part of the path. Stripping is 
+# only done if one of the specified strings matches the left-hand part of 
+# the path. The tag can be used to show relative paths in the file list. 
+# If left blank the directory from which doxygen is run is used as the 
+# path to strip.
+
+STRIP_FROM_PATH        = 
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of 
+# the path mentioned in the documentation of a class, which tells 
+# the reader which header file to include in order to use a class. 
+# If left blank only the name of the header file containing the class 
+# definition is used. Otherwise one should specify the include paths that 
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    = 
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter 
+# (but less readable) file names. This can be useful if your file system 
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen 
+# will interpret the first line (until the first dot) of a JavaDoc-style 
+# comment as the brief description. If set to NO, the JavaDoc 
+# comments will behave just like regular Qt-style comments 
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF      = YES
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will 
+# interpret the first line (until the first dot) of a Qt-style 
+# comment as the brief description. If set to NO, the comments 
+# will behave just like regular Qt-style comments (thus requiring 
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen 
+# treat a multi-line C++ special comment block (i.e. a block of //! or /// 
+# comments) as a brief description. This used to be the default behaviour. 
+# The new default is to treat a multi-line C++ comment block as a detailed 
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented 
+# member inherits the documentation from any documented member that it 
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce 
+# a new page for each member. If set to NO, the documentation of a member will 
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. 
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that acts 
+# as commands in the documentation. An alias has the form "name=value". 
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to 
+# put the command \sideeffect (or @sideeffect) in the documentation, which 
+# will result in a user-defined paragraph with heading "Side Effects:". 
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                = 
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only). 
+# A mapping has the form "name=value". For example adding 
+# "class=itcl::class" will allow you to use the command class in the 
+# itcl::class meaning.
+
+TCL_SUBST              = 
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C 
+# sources only. Doxygen will then generate output that is more tailored for C. 
+# For instance, some of the names that are used will be different. The list 
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java 
+# sources only. Doxygen will then generate output that is more tailored for 
+# Java. For instance, namespaces will be presented as packages, qualified 
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran 
+# sources only. Doxygen will then generate output that is more tailored for 
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL 
+# sources. Doxygen will then generate output that is tailored for 
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it 
+# parses. With this tag you can assign which parser to use for a given extension. 
+# Doxygen has a built-in mapping, but you can override or extend it using this 
+# tag. The format is ext=language, where ext is a file extension, and language 
+# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, 
+# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make 
+# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C 
+# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions 
+# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING      = 
+
+# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all 
+# comments according to the Markdown format, which allows for more readable 
+# documentation. See http://daringfireball.net/projects/markdown/ for details. 
+# The output of markdown processing is further processed by doxygen, so you 
+# can mix doxygen, HTML, and XML commands with Markdown formatting. 
+# Disable only in case of backward compatibilities issues.
+
+MARKDOWN_SUPPORT       = NO
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want 
+# to include (a tag file for) the STL sources as input, then you should 
+# set this tag to YES in order to let doxygen match functions declarations and 
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. 
+# func(std::string) {}). This also makes the inheritance and collaboration 
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = YES
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to 
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. 
+# Doxygen will parse them like normal C++ but will assume all classes use public 
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter 
+# and setter methods for a property. Setting this option to YES (the default) 
+# will make doxygen replace the get and set methods by a property in the 
+# documentation. This will only work if the methods are indeed getting or 
+# setting a simple type. If this is not the case, or you want to show the 
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT   = NO
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC 
+# tag is set to YES, then doxygen will reuse the documentation of the first 
+# member in the group (if any) for the other members of the group. By default 
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of 
+# the same type (for instance a group of public functions) to be put as a 
+# subgroup of that type (e.g. under the Public Functions section). Set it to 
+# NO to prevent subgrouping. Alternatively, this can be done per class using 
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and 
+# unions are shown inside the group in which they are included (e.g. using 
+# @ingroup) instead of on a separate page (for HTML and Man pages) or 
+# section (for LaTeX and RTF).
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and 
+# unions with only public data fields will be shown inline in the documentation 
+# of the scope in which they are defined (i.e. file, namespace, or group 
+# documentation), provided this scope is documented. If set to NO (the default), 
+# structs, classes, and unions are shown on a separate page (for HTML and Man 
+# pages) or section (for LaTeX and RTF).
+
+INLINE_SIMPLE_STRUCTS  = NO
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum 
+# is documented as struct, union, or enum with the name of the typedef. So 
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct 
+# with name TypeT. When disabled the typedef will appear as a member of a file, 
+# namespace, or class. And the struct will be named TypeS. This can typically 
+# be useful for C code in case the coding convention dictates that all compound 
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT   = YES
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to 
+# determine which symbols to keep in memory and which to flush to disk. 
+# When the cache is full, less often used symbols will be written to disk. 
+# For small to medium size projects (<1000 input files) the default value is 
+# probably good enough. For larger projects a too small cache size can cause 
+# doxygen to be busy swapping symbols to and from disk most of the time 
+# causing a significant performance penalty. 
+# If the system has enough physical memory increasing the cache will improve the 
+# performance by keeping more symbols in memory. Note that the value works on 
+# a logarithmic scale so increasing the size by one will roughly double the 
+# memory usage. The cache size is given by this formula: 
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, 
+# corresponding to a cache size of 2^16 = 65536 symbols.
+
+SYMBOL_CACHE_SIZE      = 0
+
+# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be 
+# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given 
+# their name and scope. Since this can be an expensive process and often the 
+# same symbol appear multiple times in the code, doxygen keeps a cache of 
+# pre-resolved symbols. If the cache is too small doxygen will become slower. 
+# If the cache is too large, memory is wasted. The cache size is given by this 
+# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, 
+# corresponding to a cache size of 2^16 = 65536 symbols.
+
+LOOKUP_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in 
+# documentation are documented, even if no documentation was available. 
+# Private class members and static file members will be hidden unless 
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class 
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = YES
+
+# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal
+# scope will be included in the documentation.
+
+EXTRACT_PACKAGE        = YES
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file 
+# will be included in the documentation.
+
+EXTRACT_STATIC         = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) 
+# defined locally in source files will be included in the documentation. 
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local 
+# methods, which are defined in the implementation section but not in 
+# the interface are included in the documentation. 
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = YES
+
+# If this flag is set to YES, the members of anonymous namespaces will be 
+# extracted and appear in the documentation as a namespace called 
+# 'anonymous_namespace{file}', where file will be replaced with the base 
+# name of the file that contains the anonymous namespace. By default 
+# anonymous namespaces are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all 
+# undocumented members of documented classes, files or namespaces. 
+# If set to NO (the default) these members will be included in the 
+# various overviews, but no documentation section is generated. 
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all 
+# undocumented classes that are normally visible in the class hierarchy. 
+# If set to NO (the default) these classes will be included in the various 
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = YES
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all 
+# friend (class|struct|union) declarations. 
+# If set to NO (the default) these declarations will be included in the 
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any 
+# documentation blocks found inside the body of a function. 
+# If set to NO (the default) these blocks will be appended to the 
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation 
+# that is typed after a \internal command is included. If the tag is set 
+# to NO (the default) then the documentation will be excluded. 
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate 
+# file names in lower-case letters. If set to YES upper-case letters are also 
+# allowed. This is useful if you have classes or files whose names only differ 
+# in case and if your file system supports case sensitive file names. Windows 
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = NO
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen 
+# will show members with their full class and namespace scopes in the 
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen 
+# will put a list of the files that are included by a file in the documentation 
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen 
+# will list include files with double quotes in the documentation 
+# rather than with sharp brackets.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] 
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen 
+# will sort the (detailed) documentation of file and class members 
+# alphabetically by member name. If set to NO the members will appear in 
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the 
+# brief documentation of file, namespace and class members alphabetically 
+# by member name. If set to NO (the default) the members will appear in 
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen 
+# will sort the (brief and detailed) documentation of class members so that 
+# constructors and destructors are listed first. If set to NO (the default) 
+# the constructors will appear in the respective orders defined by 
+# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. 
+# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO 
+# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the 
+# hierarchy of group names into alphabetical order. If set to NO (the default) 
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be 
+# sorted by fully-qualified names, including namespaces. If set to 
+# NO (the default), the class list will be sorted only by class name, 
+# not including the namespace part. 
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. 
+# Note: This option applies only to the class list, not to the 
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to 
+# do proper type resolution of all parameters of a function it will reject a 
+# match between the prototype and the implementation of a member function even 
+# if there is only one candidate or it is obvious which candidate to choose 
+# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen 
+# will still accept a match between prototype and implementation in such cases.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or 
+# disable (NO) the todo list. This list is created by putting \todo 
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or 
+# disable (NO) the test list. This list is created by putting \test 
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or 
+# disable (NO) the bug list. This list is created by putting \bug 
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or 
+# disable (NO) the deprecated list. This list is created by putting 
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional 
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       = 
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines 
+# the initial value of a variable or macro consists of for it to appear in 
+# the documentation. If the initializer consists of more lines than specified 
+# here it will be hidden. Use a value of 0 to hide initializers completely. 
+# The appearance of the initializer of individual variables and macros in the 
+# documentation can be controlled using \showinitializer or \hideinitializer 
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated 
+# at the bottom of the documentation of classes and structs. If set to YES the 
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. 
+# This will remove the Files entry from the Quick Index and from the 
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the 
+# Namespaces page.  This will remove the Namespaces entry from the Quick Index 
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES        = NO
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that 
+# doxygen should invoke to get the current version for each file (typically from 
+# the version control system). Doxygen will invoke the program by executing (via 
+# popen()) the command <command> <input-file>, where <command> is the value of 
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file 
+# provided by doxygen. Whatever the program writes to standard output 
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    = 
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed 
+# by doxygen. The layout file controls the global structure of the generated 
+# output files in an output format independent way. To create the layout file 
+# that represents doxygen's defaults, run doxygen with the -l option. 
+# You can optionally specify a file name after the option, if omitted 
+# DoxygenLayout.xml will be used as the name of the layout file.
+
+LAYOUT_FILE            = 
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files 
+# containing the references data. This must be a list of .bib files. The 
+# .bib extension is automatically appended if omitted. Using this command 
+# requires the bibtex tool to be installed. See also 
+# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style 
+# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this 
+# feature you need bibtex and perl available in the search path.
+
+CITE_BIB_FILES         = 
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated 
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are 
+# generated by doxygen. Possible values are YES and NO. If left blank 
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings 
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will 
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for 
+# potential errors in the documentation, such as not documenting some 
+# parameters in a documented function, or documenting parameters that 
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# The WARN_NO_PARAMDOC option can be enabled to get warnings for 
+# functions that are documented, but have no documentation for their parameters 
+# or return value. If set to NO (the default) doxygen will only warn about 
+# wrong or incomplete parameter documentation, but not about the absence of 
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that 
+# doxygen can produce. The string should contain the $file, $line, and $text 
+# tags, which will be replaced by the file and line number from which the 
+# warning originated and the warning text. Optionally the format may contain 
+# $version, which will be replaced by the version of the file (if it could 
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning 
+# and error messages should be written. If left blank the output is written 
+# to stderr.
+
+WARN_LOGFILE           = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain 
+# documented source files. You may enter file names like "myfile.cpp" or 
+# directories like "/usr/src/myproject". Separate the files or directories 
+# with spaces.
+
+INPUT                  = 
+
+# This tag can be used to specify the character encoding of the source files 
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is 
+# also the default input encoding. Doxygen uses libiconv (or the iconv built 
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for 
+# the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the 
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank the following patterns are tested: 
+# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh 
+# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py 
+# *.f90 *.f *.for *.vhd *.vhdl
+
+FILE_PATTERNS          = *.cpp \
+                         *.h \
+                         *.dox
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories 
+# should be searched for input files as well. Possible values are YES and NO. 
+# If left blank NO is used.
+
+RECURSIVE              = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be 
+# excluded from the INPUT source files. This way you can easily exclude a 
+# subdirectory from a directory tree whose root is specified with the INPUT tag. 
+# Note that relative paths are relative to the directory from which doxygen is 
+# run.
+
+EXCLUDE                = resource.h
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or 
+# directories that are symbolic links (a Unix file system feature) are excluded 
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the 
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude 
+# certain files from those directories. Note that the wildcards are matched 
+# against the file with absolute path, so to exclude all test directories 
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       = 
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names 
+# (namespaces, classes, functions, etc.) that should be excluded from the 
+# output. The symbol name can be a fully qualified name, a word, or if the 
+# wildcard * is used, a substring. Examples: ANamespace, AClass, 
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        = 
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or 
+# directories that contain example code fragments that are included (see 
+# the \include command).
+
+EXAMPLE_PATH           = 
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the 
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank all files are included.
+
+EXAMPLE_PATTERNS       = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be 
+# searched for input files to be used with the \include or \dontinclude 
+# commands irrespective of the value of the RECURSIVE tag. 
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or 
+# directories that contain image that are included in the documentation (see 
+# the \image command).
+
+IMAGE_PATH             = docs/images
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should 
+# invoke to filter for each input file. Doxygen will invoke the filter program 
+# by executing (via popen()) the command <filter> <input-file>, where <filter> 
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an 
+# input file. Doxygen will then use the output that the filter program writes 
+# to standard output.  If FILTER_PATTERNS is specified, this tag will be 
+# ignored.
+
+INPUT_FILTER           = 
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern 
+# basis.  Doxygen will compare the file name with each pattern and apply the 
+# filter if there is a match.  The filters are a list of the form: 
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further 
+# info on how filters are used. If FILTER_PATTERNS is empty or if 
+# non of the patterns match the file name, INPUT_FILTER is applied.
+
+FILTER_PATTERNS        = 
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using 
+# INPUT_FILTER) will be used to filter the input files when producing source 
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file 
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) 
+# and it is also possible to disable source filtering for a specific pattern 
+# using *.ext= (so without naming a filter). This option only has effect when 
+# FILTER_SOURCE_FILES is enabled.
+
+FILTER_SOURCE_PATTERNS = 
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will 
+# be generated. Documented entities will be cross-referenced with these sources. 
+# Note: To get rid of all source code in the generated output, make sure also 
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body 
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct 
+# doxygen to hide any special comment blocks from generated source code 
+# fragments. Normal C, C++ and Fortran comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES 
+# then for each documented function all documented 
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES 
+# then for each documented function all documented entities 
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) 
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from 
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will 
+# link to the source code.  Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code 
+# will point to the HTML generated by the htags(1) tool instead of doxygen 
+# built-in source browser. The htags tool is part of GNU's global source 
+# tagging system (see http://www.gnu.org/software/global/global.html). You 
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen 
+# will generate a verbatim copy of the header file for each class for 
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index 
+# of all compounds will be generated. Enable this if the project 
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = YES
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then 
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns 
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 2
+
+# In case all classes in a project start with a common prefix, all 
+# classes will be put under the same header in the alphabetical index. 
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that 
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will 
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for 
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank 
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard header. Note that when using a custom header you are responsible  
+# for the proper inclusion of any scripts and style sheets that doxygen 
+# needs, which is dependent on the configuration options used. 
+# It is advised to generate a default header using "doxygen -w html 
+# header.html footer.html stylesheet.css YourConfigFile" and then modify 
+# that header. Note that the header is subject to change so you typically 
+# have to redo this when upgrading to a newer version of doxygen or when 
+# changing the value of configuration settings such as GENERATE_TREEVIEW!
+
+HTML_HEADER            = 
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard footer.
+
+HTML_FOOTER            = 
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading 
+# style sheet that is used by each HTML page. It can be used to 
+# fine-tune the look of the HTML output. If the tag is left blank doxygen 
+# will generate a default style sheet. Note that doxygen will try to copy 
+# the style sheet file to the HTML output directory, so don't put your own 
+# style sheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        = 
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or 
+# other source files which should be copied to the HTML output directory. Note 
+# that these files will be copied to the base HTML output directory. Use the 
+# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these 
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that 
+# the files will be copied as-is; there are no commands or markers available.
+
+HTML_EXTRA_FILES       = 
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. 
+# Doxygen will adjust the colors in the style sheet and background images 
+# according to this color. Hue is specified as an angle on a colorwheel, 
+# see http://en.wikipedia.org/wiki/Hue for more information. 
+# For instance the value 0 represents red, 60 is yellow, 120 is green, 
+# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. 
+# The allowed range is 0 to 359.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of 
+# the colors in the HTML output. For a value of 0 the output will use 
+# grayscales only. A value of 255 will produce the most vivid colors.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to 
+# the luminance component of the colors in the HTML output. Values below 
+# 100 gradually make the output lighter, whereas values above 100 make 
+# the output darker. The value divided by 100 is the actual gamma applied, 
+# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, 
+# and 100 does not change the gamma.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML 
+# page will contain the date and time when the page was generated. Setting 
+# this to NO can help when comparing the output of multiple runs.
+
+HTML_TIMESTAMP         = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML 
+# documentation will contain sections that can be hidden and shown after the 
+# page has loaded.
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of 
+# entries shown in the various tree structured indices initially; the user 
+# can expand and collapse entries dynamically later on. Doxygen will expand 
+# the tree to such a level that at most the specified number of entries are 
+# visible (unless a fully collapsed tree already exceeds this amount). 
+# So setting the number of entries 1 will produce a full collapsed tree by 
+# default. 0 is a special value representing an infinite number of entries 
+# and will result in a full expanded tree by default.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files 
+# will be generated that can be used as input for Apple's Xcode 3 
+# integrated development environment, introduced with OSX 10.5 (Leopard). 
+# To create a documentation set, doxygen will generate a Makefile in the 
+# HTML output directory. Running make will produce the docset in that 
+# directory and running "make install" will install the docset in 
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find 
+# it at startup. 
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html 
+# for more information.
+
+GENERATE_DOCSET        = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the 
+# feed. A documentation feed provides an umbrella under which multiple 
+# documentation sets from a single provider (such as a company or product suite) 
+# can be grouped.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that 
+# should uniquely identify the documentation set bundle. This should be a 
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen 
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify 
+# the documentation publisher. This should be a reverse domain-name style 
+# string, e.g. com.mycompany.MyDocSet.documentation.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files 
+# will be generated that can be used as input for tools like the 
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) 
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can 
+# be used to specify the file name of the resulting .chm file. You 
+# can add a path in front of the file if the result should not be 
+# written to the html output directory.
+
+CHM_FILE               = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can 
+# be used to specify the location (absolute path including file name) of 
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run 
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag 
+# controls if a separate .chi index file is generated (YES) or that 
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING 
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file 
+# content.
+
+CHM_INDEX_ENCODING     = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag 
+# controls whether a binary table of contents is generated (YES) or a 
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members 
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and 
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated 
+# that can be used as input for Qt's qhelpgenerator to generate a 
+# Qt Compressed Help (.qch) of the generated HTML documentation.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can 
+# be used to specify the file name of the resulting .qch file. 
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE               = 
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating 
+# Qt Help Project output. For more information please see 
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating 
+# Qt Help Project output. For more information please see 
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to 
+# add. For more information please see 
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME   = 
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the 
+# custom filter to add. For more information please see 
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters"> 
+# Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS  = 
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this 
+# project's 
+# filter section matches. 
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes"> 
+# Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS  = 
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can 
+# be used to specify the location of Qt's qhelpgenerator. 
+# If non-empty doxygen will try to run qhelpgenerator on the generated 
+# .qhp file.
+
+QHG_LOCATION           = 
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files  
+# will be generated, which together with the HTML files, form an Eclipse help 
+# plugin. To install this plugin and make it available under the help contents 
+# menu in Eclipse, the contents of the directory containing the HTML and XML 
+# files needs to be copied into the plugins directory of eclipse. The name of 
+# the directory within the plugins directory should be the same as 
+# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before 
+# the help appears.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the eclipse help plugin. When installing the plugin 
+# the directory name containing the HTML and XML files should also have 
+# this name.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) 
+# at top of each HTML page. The value NO (the default) enables the index and 
+# the value YES disables it. Since the tabs have the same information as the 
+# navigation tree you can set this option to NO if you already set 
+# GENERATE_TREEVIEW to YES.
+
+DISABLE_INDEX          = YES
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index 
+# structure should be generated to display hierarchical information. 
+# If the tag value is set to YES, a side panel will be generated 
+# containing a tree-like index structure (just like the one that 
+# is generated for HTML Help). For this to work a browser that supports 
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). 
+# Windows users are probably better off using the HTML help feature. 
+# Since the tree basically has the same information as the tab index you 
+# could consider to set DISABLE_INDEX to NO when enabling this option.
+
+GENERATE_TREEVIEW      = YES
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values 
+# (range [0,1..20]) that doxygen will group on one line in the generated HTML 
+# documentation. Note that a value of 0 will completely suppress the enum 
+# values from appearing in the overview section.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be 
+# used to set the initial width (in pixels) of the frame in which the tree 
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open 
+# links to external symbols imported via tag files in a separate window.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of Latex formulas included 
+# as images in the HTML documentation. The default is 10. Note that 
+# when you change the font size after a successful doxygen run you need 
+# to manually remove any form_*.png images from the HTML output directory 
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images 
+# generated for formulas are transparent PNGs. Transparent PNGs are 
+# not supported properly for IE 6.0, but are supported on all modern browsers. 
+# Note that when changing this option you need to delete any form_*.png files 
+# in the HTML output before the changes have effect.
+
+FORMULA_TRANSPARENT    = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax 
+# (see http://www.mathjax.org) which uses client side Javascript for the 
+# rendering instead of using prerendered bitmaps. Use this if you do not 
+# have LaTeX installed or if you want to formulas look prettier in the HTML 
+# output. When enabled you may also need to install MathJax separately and 
+# configure the path to it using the MATHJAX_RELPATH option.
+
+USE_MATHJAX            = NO
+
+# When MathJax is enabled you need to specify the location relative to the 
+# HTML output directory using the MATHJAX_RELPATH option. The destination 
+# directory should contain the MathJax.js script. For instance, if the mathjax 
+# directory is located at the same level as the HTML output directory, then 
+# MATHJAX_RELPATH should be ../mathjax. The default value points to 
+# the MathJax Content Delivery Network so you can quickly see the result without 
+# installing MathJax.  However, it is strongly recommended to install a local 
+# copy of MathJax from http://www.mathjax.org before deployment.
+
+MATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension 
+# names that should be enabled during MathJax rendering.
+
+MATHJAX_EXTENSIONS     = 
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box 
+# for the HTML output. The underlying search engine uses javascript 
+# and DHTML and should work on any modern browser. Note that when using 
+# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets 
+# (GENERATE_DOCSET) there is already a search function so this one should 
+# typically be disabled. For large projects the javascript based search engine 
+# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
+
+SEARCHENGINE           = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be 
+# implemented using a PHP enabled web server instead of at the web client 
+# using Javascript. Doxygen will generate the search PHP script and index 
+# file to put on the web server. The advantage of the server 
+# based approach is that it scales better to large projects and allows 
+# full text search. The disadvantages are that it is more difficult to setup 
+# and does not have live searching capabilities.
+
+SERVER_BASED_SEARCH    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will 
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be 
+# invoked. If left blank `latex' will be used as the default command name. 
+# Note that when enabling USE_PDFLATEX this option is only used for 
+# generating bitmaps for formulas in the HTML output, but not in the 
+# Makefile that is written to the output directory.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to 
+# generate index for LaTeX. If left blank `makeindex' will be used as the 
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact 
+# LaTeX documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used 
+# by the printer. Possible values are: a4, letter, legal and 
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX 
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         = 
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for 
+# the generated latex document. The header should contain everything until 
+# the first chapter. If it is left blank doxygen will generate a 
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           = 
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for 
+# the generated latex document. The footer should contain everything after 
+# the last chapter. If it is left blank doxygen will generate a 
+# standard footer. Notice: only use this tag if you know what you are doing!
+
+LATEX_FOOTER           = 
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated 
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will 
+# contain links (just like the HTML output) instead of page references 
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of 
+# plain latex in the generated Makefile. Set this option to YES to get a 
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. 
+# command to the generated LaTeX files. This will instruct LaTeX to keep 
+# running if errors occur, instead of asking the user for help. 
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not 
+# include the index chapters (such as File Index, Compound Index, etc.) 
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include 
+# source code with syntax highlighting in the LaTeX output. 
+# Note that which sources are shown also depends on other settings 
+# such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE      = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the 
+# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See 
+# http://en.wikipedia.org/wiki/BibTeX for more info.
+
+LATEX_BIB_STYLE        = plain
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output 
+# The RTF output is optimized for Word 97 and may not look very pretty with 
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact 
+# RTF documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated 
+# will contain hyperlink fields. The RTF file will 
+# contain links (just like the HTML output) instead of page references. 
+# This makes the output suitable for online browsing using WORD or other 
+# programs which support those fields. 
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load style sheet definitions from file. Syntax is similar to doxygen's 
+# config file, i.e. a series of assignments. You only have to provide 
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    = 
+
+# Set optional variables used in the generation of an rtf document. 
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will 
+# generate man pages
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to 
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output, 
+# then it will generate one additional man file for each entity 
+# documented in the real man page(s). These additional files 
+# only source the real man page, but without them the man command 
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will 
+# generate an XML file that captures the structure of 
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_SCHEMA             = 
+
+# The XML_DTD tag can be used to specify an XML DTD, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_DTD                = 
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will 
+# dump the program listings (including syntax highlighting 
+# and cross-referencing information) to the XML output. Note that 
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will 
+# generate an AutoGen Definitions (see autogen.sf.net) file 
+# that captures the structure of the code including all 
+# documentation. Note that this feature is still experimental 
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will 
+# generate a Perl module file that captures the structure of 
+# the code including all documentation. Note that this 
+# feature is still experimental and incomplete at the 
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate 
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able 
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be 
+# nicely formatted so it can be parsed by a human reader.  This is useful 
+# if you want to understand what is going on.  On the other hand, if this 
+# tag is set to NO the size of the Perl module output will be much smaller 
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file 
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. 
+# This is useful so different doxyrules.make files included by the same 
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will 
+# evaluate all C-preprocessor directives found in the sources and include 
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro 
+# names in the source code. If set to NO (the default) only conditional 
+# compilation will be performed. Macro expansion can be done in a controlled 
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES 
+# then the macro expansion is limited to the macros specified with the 
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files 
+# pointed to by INCLUDE_PATH will be searched when a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that 
+# contain include files that are not input files but should be processed by 
+# the preprocessor.
+
+INCLUDE_PATH           = 
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard 
+# patterns (like *.h and *.hpp) to filter out the header-files in the 
+# directories. If left blank, the patterns specified with FILE_PATTERNS will 
+# be used.
+
+INCLUDE_FILE_PATTERNS  = 
+
+# The PREDEFINED tag can be used to specify one or more macro names that 
+# are defined before the preprocessor is started (similar to the -D option of 
+# gcc). The argument of the tag is a list of macros of the form: name 
+# or name=definition (no spaces). If the definition and the = are 
+# omitted =1 is assumed. To prevent a macro definition from being 
+# undefined via #undef or recursively expanded use the := operator 
+# instead of the = operator.
+
+PREDEFINED             = 
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then 
+# this tag can be used to specify a list of macro names that should be expanded. 
+# The macro definition that is found in the sources will be used. 
+# Use the PREDEFINED tag if you want to use a different macro definition that 
+# overrules the definition found in the source code.
+
+EXPAND_AS_DEFINED      = 
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then 
+# doxygen's preprocessor will remove all references to function-like macros 
+# that are alone on a line, have an all uppercase name, and do not end with a 
+# semicolon, because these will confuse the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles. For each 
+# tag file the location of the external documentation should be added. The 
+# format of a tag file without this location is as follows: 
+#   TAGFILES = file1 file2 ... 
+# Adding location for the tag files is done as follows: 
+#   TAGFILES = file1=loc1 "file2 = loc2" ... 
+# where "loc1" and "loc2" can be relative or absolute paths 
+# or URLs. Note that each tag file must have a unique name (where the name does 
+# NOT include the path). If a tag file is not located in the directory in which 
+# doxygen is run, you must also specify the path to the tagfile here.
+
+TAGFILES               = 
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create 
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       = 
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed 
+# in the class index. If set to NO only the inherited external classes 
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed 
+# in the modules index. If set to NO, only the current project's groups will 
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script 
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will 
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base 
+# or super classes. Setting the tag to NO turns the diagrams off. Note that 
+# this option also works with HAVE_DOT disabled, but it is recommended to 
+# install and use dot, since it yields more powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc 
+# command. Doxygen will then run the mscgen tool (see 
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the 
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where 
+# the mscgen tool resides. If left empty the tool is assumed to be found in the 
+# default search path.
+
+MSCGEN_PATH            = 
+
+# If set to YES, the inheritance and collaboration graphs will hide 
+# inheritance and usage relations if the target is undocumented 
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is 
+# available from the path. This tool is part of Graphviz, a graph visualization 
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section 
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is 
+# allowed to run in parallel. When set to 0 (the default) doxygen will 
+# base this on the number of processors available in the system. You can set it 
+# explicitly to a value larger than 0 to get control over the balance 
+# between CPU load and processing speed.
+
+DOT_NUM_THREADS        = 0
+
+# By default doxygen will use the Helvetica font for all dot files that 
+# doxygen generates. When you want a differently looking font you can specify 
+# the font name using DOT_FONTNAME. You need to make sure dot is able to find 
+# the font, which can be done by putting it in a standard location or by setting 
+# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the 
+# directory containing the font.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. 
+# The default size is 10pt.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the Helvetica font. 
+# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to 
+# set the path where dot can find it.
+
+DOT_FONTPATH           = 
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect inheritance relations. Setting this tag to YES will force the 
+# CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect implementation dependencies (inheritance, containment, and 
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and 
+# collaboration diagrams in a style similar to the OMG's Unified Modeling 
+# Language.
+
+UML_LOOK               = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside 
+# the class node. If there are many fields or methods and many nodes the 
+# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS 
+# threshold limits the number of items for each type to make the size more 
+# managable. Set this to 0 for no limit. Note that the threshold may be 
+# exceeded by 50% before the limit is enforced.
+
+UML_LIMIT_NUM_FIELDS   = 10
+
+# If set to YES, the inheritance and collaboration graphs will show the 
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT 
+# tags are set to YES then doxygen will generate a graph for each documented 
+# file showing the direct and indirect include dependencies of the file with 
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and 
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each 
+# documented header file showing the documented files that directly or 
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then 
+# doxygen will generate a call dependency graph for every global function 
+# or class method. Note that enabling this option will significantly increase 
+# the time of a run. So in most cases it will be better to enable call graphs 
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then 
+# doxygen will generate a caller dependency graph for every global function 
+# or class method. Note that enabling this option will significantly increase 
+# the time of a run. So in most cases it will be better to enable caller 
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES 
+# then doxygen will show the dependencies a directory has on other directories 
+# in a graphical way. The dependency relations are determined by the #include 
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images 
+# generated by dot. Possible values are svg, png, jpg, or gif. 
+# If left blank png will be used. If you choose svg you need to set 
+# HTML_FILE_EXTENSION to xhtml in order to make the SVG files 
+# visible in IE 9+ (other browsers do not have this requirement).
+
+DOT_IMAGE_FORMAT       = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to 
+# enable generation of interactive SVG images that allow zooming and panning. 
+# Note that this requires a modern browser other than Internet Explorer. 
+# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you 
+# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files 
+# visible. Older versions of IE do not have SVG support.
+
+INTERACTIVE_SVG        = NO
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be 
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               = 
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that 
+# contain dot files that are included in the documentation (see the 
+# \dotfile command).
+
+DOTFILE_DIRS           = 
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that 
+# contain msc files that are included in the documentation (see the 
+# \mscfile command).
+
+MSCFILE_DIRS           = 
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of 
+# nodes that will be shown in the graph. If the number of nodes in a graph 
+# becomes larger than this value, doxygen will truncate the graph, which is 
+# visualized by representing a node as a red box. Note that doxygen if the 
+# number of direct children of the root node in a graph is already larger than 
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note 
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the 
+# graphs generated by dot. A depth value of 3 means that only nodes reachable 
+# from the root by following a path via at most 3 edges will be shown. Nodes 
+# that lay further from the root node will be omitted. Note that setting this 
+# option to 1 or 2 may greatly reduce the computation time needed for large 
+# code bases. Also note that the size of a graph can be further restricted by 
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent 
+# background. This is disabled by default, because dot on Windows does not 
+# seem to support this out of the box. Warning: Depending on the platform used, 
+# enabling this option may lead to badly anti-aliased labels on the edges of 
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output 
+# files in one run (i.e. multiple -o and -T options on the command line). This 
+# makes dot run faster, but since only newer versions of dot (>1.8.10) 
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will 
+# generate a legend page explaining the meaning of the various boxes and 
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will 
+# remove the intermediate dot files that are used to generate 
+# the various graphs.
+
+DOT_CLEANUP            = YES
diff --git a/contrib/meshtex/GeneralFunctionDialog.cpp b/contrib/meshtex/GeneralFunctionDialog.cpp
new file mode 100644 (file)
index 0000000..f6daebc
--- /dev/null
@@ -0,0 +1,703 @@
+/**
+ * @file GeneralFunctionDialog.cpp
+ * Implements the GeneralFunctionDialog class.
+ * @ingroup meshtex-ui
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gtk/gtk.h>
+
+#include "GenericPluginUI.h"
+#include "GeneralFunctionDialog.h"
+#include "PluginUIMessages.h"
+
+#include "iundo.h"
+
+
+/**
+ * Constructor. See MeshEntity::GeneralFunction for details of how these
+ * arguments are interpreted.
+ *
+ * @param sFactors      Factors to determine the S texture coords; NULL if S
+ *                      axis unaffected.
+ * @param tFactors      Factors to determine the T texture coords; NULL if T
+ *                      axis unaffected.
+ * @param alignRow      Pointer to zero-point row; if NULL, row 0 is assumed.
+ * @param alignCol      Pointer to zero-point column; if NULL, column 0 is
+ *                      assumed.
+ * @param refRow        Pointer to reference row description, including how
+ *                      to use the reference; NULL if no reference.
+ * @param refCol        Pointer to reference column description, including
+ *                      how to use the reference; NULL if no reference.
+ * @param surfaceValues true if calculations are for S/T values on the mesh
+ *                      surface; false if calculations are for S/T values at
+ *                      the control points.
+ */
+GeneralFunctionDialog::GeneralFunctionVisitor::GeneralFunctionVisitor(
+   const MeshEntity::GeneralFunctionFactors *sFactors,
+   const MeshEntity::GeneralFunctionFactors *tFactors,
+   const MeshEntity::SliceDesignation *alignRow,
+   const MeshEntity::SliceDesignation *alignCol,
+   const MeshEntity::RefSliceDescriptor *refRow,
+   const MeshEntity::RefSliceDescriptor *refCol,
+   bool surfaceValues) :
+   _sFactors(sFactors),
+   _tFactors(tFactors),
+   _alignRow(alignRow),
+   _alignCol(alignCol),
+   _refRow(refRow),
+   _refCol(refCol),
+   _surfaceValues(surfaceValues)
+{
+}
+
+/**
+ * Visitor action; invoke MeshEntity::GeneralFunction on a mesh.
+ *
+ * @param [in,out] meshEntity The mesh.
+ *
+ * @return true.
+ */
+bool
+GeneralFunctionDialog::GeneralFunctionVisitor::Execute(MeshEntity& meshEntity) const
+{
+   meshEntity.GeneralFunction(_sFactors, _tFactors,
+                              _alignRow, _alignCol, _refRow, _refCol,
+                              _surfaceValues);
+   return true;
+}
+
+/**
+ * Constructor. Configure the dialog window and create all the contained
+ * widgets. Connect widgets to callbacks as necessary.
+ *
+ * @param key The unique key identifying this dialog.
+ */
+GeneralFunctionDialog::GeneralFunctionDialog(const std::string& key) :
+   GenericDialog(key),
+   _nullVisitor(new MeshVisitor())
+{
+   // Enable the usual handling of the close event.
+   CreateWindowCloseCallback();
+
+   // Configure the dialog window.
+   gtk_window_set_resizable(GTK_WINDOW(_dialog), FALSE);
+   gtk_window_set_title(GTK_WINDOW(_dialog), DIALOG_GEN_FUNC_TITLE);
+   gtk_container_set_border_width(GTK_CONTAINER(_dialog), 10);
+
+   // Create the contained widgets.
+
+   GtkWidget *table;
+   GtkWidget *entry;
+   GtkWidget *applybutton, *refbutton, *button;
+   GtkWidget *separator;
+   GtkWidget *label;
+   GtkWidget *mainvbox, *vbox, *hbox, *mainhbox;
+   GtkWidget *frame;
+
+   table = gtk_table_new(6, 13, FALSE);
+   gtk_table_set_row_spacing(GTK_TABLE(table), 0, 5);
+   gtk_table_set_row_spacing(GTK_TABLE(table), 1, 10);
+   gtk_table_set_row_spacing(GTK_TABLE(table), 3, 15);
+   gtk_table_set_row_spacing(GTK_TABLE(table), 4, 15);
+   gtk_container_add(GTK_CONTAINER(_dialog), table);
+   gtk_widget_show(table);
+
+   hbox = gtk_hbox_new(TRUE, 10);
+   gtk_table_attach(GTK_TABLE(table), hbox, 0, 13, 0, 1, GTK_SHRINK, GTK_EXPAND, 0, 0);
+   gtk_widget_show(hbox);
+
+   // Mutually exclusive "Surface values" and "Control values" radio buttons.
+
+   button = gtk_radio_button_new_with_label(NULL,
+                                            DIALOG_GEN_FUNC_SURFACE_VALUES);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "surface", button);
+   gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+   gtk_widget_show(button);
+
+   button = gtk_radio_button_new_with_label(
+      gtk_radio_button_group(GTK_RADIO_BUTTON(button)),
+                             DIALOG_GEN_FUNC_CONTROL_VALUES);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "control", button);
+   gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
+   gtk_widget_show(button);
+
+   separator = gtk_hseparator_new();
+   gtk_table_attach_defaults(GTK_TABLE(table), separator, 0, 13, 1, 2);
+   gtk_widget_show(separator);
+
+   // Checkbox for the "S" row of factors. All the other widgets on this row
+   // will have a dependence registered on this checkbox; i.e. they will only
+   // be active when it is checked.
+
+   applybutton = gtk_check_button_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "s_apply", applybutton);
+   gtk_table_attach_defaults(GTK_TABLE(table), applybutton, 0, 1, 2, 3);
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(applybutton), TRUE);
+   gtk_widget_show(applybutton);
+
+   label = gtk_label_new(DIALOG_GEN_FUNC_S_FUNC_LABEL);
+   gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 2, 3);
+   gtk_widget_show(label);
+
+   UIInstance().RegisterWidgetDependence(applybutton, label);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "s_oldval", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "1.0");
+   gtk_table_attach_defaults(GTK_TABLE(table), entry, 2, 3, 2, 3);
+   gtk_widget_set_usize(entry, 50, -2);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(applybutton, entry);
+
+   label = gtk_label_new(DIALOG_GEN_FUNC_OLD_S_LABEL);
+   gtk_table_attach_defaults(GTK_TABLE(table), label, 3, 4, 2, 3);
+   gtk_widget_show(label);
+
+   UIInstance().RegisterWidgetDependence(applybutton, label);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "s_rowdist", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "0.0");
+   gtk_table_attach_defaults(GTK_TABLE(table), entry, 4, 5, 2, 3);
+   gtk_widget_set_usize(entry, 50, -2);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(applybutton, entry);
+
+   label = gtk_label_new(DIALOG_GEN_FUNC_ROW_DIST_LABEL);
+   gtk_table_attach_defaults(GTK_TABLE(table), label, 5, 6, 2, 3);
+   gtk_widget_show(label);
+
+   UIInstance().RegisterWidgetDependence(applybutton, label);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "s_coldist", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "0.0");
+   gtk_table_attach_defaults(GTK_TABLE(table), entry, 6, 7, 2, 3);
+   gtk_widget_set_usize(entry, 50, -2);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(applybutton, entry);
+
+   label = gtk_label_new(DIALOG_GEN_FUNC_COL_DIST_LABEL);
+   gtk_table_attach_defaults(GTK_TABLE(table), label, 7, 8, 2, 3);
+   gtk_widget_show(label);
+
+   UIInstance().RegisterWidgetDependence(applybutton, label);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "s_rownum", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "0.0");
+   gtk_table_attach_defaults(GTK_TABLE(table), entry, 8, 9, 2, 3);
+   gtk_widget_set_usize(entry, 50, -2);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(applybutton, entry);
+
+   label = gtk_label_new(DIALOG_GEN_FUNC_ROW_NUM_LABEL);
+   gtk_table_attach_defaults(GTK_TABLE(table), label, 9, 10, 2, 3);
+   gtk_widget_show(label);
+
+   UIInstance().RegisterWidgetDependence(applybutton, label);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "s_colnum", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "0.0");
+   gtk_table_attach_defaults(GTK_TABLE(table), entry, 10, 11, 2, 3);
+   gtk_widget_set_usize(entry, 50, -2);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(applybutton, entry);
+
+   label = gtk_label_new(DIALOG_GEN_FUNC_COL_NUM_LABEL);
+   gtk_table_attach_defaults(GTK_TABLE(table), label, 11, 12, 2, 3);
+   gtk_widget_show(label);
+
+   UIInstance().RegisterWidgetDependence(applybutton, label);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "s_constant", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "0.0");
+   gtk_table_attach_defaults(GTK_TABLE(table), entry, 12, 13, 2, 3);
+   gtk_widget_set_usize(entry, 50, -2);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(applybutton, entry);
+
+   // Checkbox for the "T" row of factors. All the other widgets on this row
+   // will have a dependence registered on this checkbox; i.e. they will only
+   // be active when it is checked.
+
+   applybutton = gtk_check_button_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "t_apply", applybutton);
+   gtk_table_attach_defaults(GTK_TABLE(table), applybutton, 0, 1, 3, 4);
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(applybutton), TRUE);
+   gtk_widget_show(applybutton);
+
+   label = gtk_label_new(DIALOG_GEN_FUNC_T_FUNC_LABEL);
+   gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 3, 4);
+   gtk_widget_show(label);
+
+   UIInstance().RegisterWidgetDependence(applybutton, label);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "t_oldval", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "1.0");
+   gtk_table_attach_defaults(GTK_TABLE(table), entry, 2, 3, 3, 4);
+   gtk_widget_set_usize(entry, 50, -2);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(applybutton, entry);
+
+   label = gtk_label_new(DIALOG_GEN_FUNC_OLD_T_LABEL);
+   gtk_table_attach_defaults(GTK_TABLE(table), label, 3, 4, 3, 4);
+   gtk_widget_show(label);
+
+   UIInstance().RegisterWidgetDependence(applybutton, label);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "t_rowdist", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "0.0");
+   gtk_table_attach_defaults(GTK_TABLE(table), entry, 4, 5, 3, 4);
+   gtk_widget_set_usize(entry, 50, -2);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(applybutton, entry);
+
+   label = gtk_label_new(DIALOG_GEN_FUNC_ROW_DIST_LABEL);
+   gtk_table_attach_defaults(GTK_TABLE(table), label, 5, 6, 3, 4);
+   gtk_widget_show(label);
+
+   UIInstance().RegisterWidgetDependence(applybutton, label);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "t_coldist", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "0.0");
+   gtk_table_attach_defaults(GTK_TABLE(table), entry, 6, 7, 3, 4);
+   gtk_widget_set_usize(entry, 50, -2);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(applybutton, entry);
+
+   label = gtk_label_new(DIALOG_GEN_FUNC_COL_DIST_LABEL);
+   gtk_table_attach_defaults(GTK_TABLE(table), label, 7, 8, 3, 4);
+   gtk_widget_show(label);
+
+   UIInstance().RegisterWidgetDependence(applybutton, label);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "t_rownum", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "0.0");
+   gtk_table_attach_defaults(GTK_TABLE(table), entry, 8, 9, 3, 4);
+   gtk_widget_set_usize(entry, 50, -2);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(applybutton, entry);
+
+   label = gtk_label_new(DIALOG_GEN_FUNC_ROW_NUM_LABEL);
+   gtk_table_attach_defaults(GTK_TABLE(table), label, 9, 10, 3, 4);
+   gtk_widget_show(label);
+
+   UIInstance().RegisterWidgetDependence(applybutton, label);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "t_colnum", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "0.0");
+   gtk_table_attach_defaults(GTK_TABLE(table), entry, 10, 11, 3, 4);
+   gtk_widget_set_usize(entry, 50, -2);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(applybutton, entry);
+
+   label = gtk_label_new(DIALOG_GEN_FUNC_COL_NUM_LABEL);
+   gtk_table_attach_defaults(GTK_TABLE(table), label, 11, 12, 3, 4);
+   gtk_widget_show(label);
+
+   UIInstance().RegisterWidgetDependence(applybutton, label);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "t_constant", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "0.0");
+   gtk_table_attach_defaults(GTK_TABLE(table), entry, 12, 13, 3, 4);
+   gtk_widget_set_usize(entry, 50, -2);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(applybutton, entry);
+
+   mainhbox = gtk_hbox_new(TRUE, 0);
+   gtk_table_attach(GTK_TABLE(table), mainhbox, 0, 13, 4, 5, GTK_SHRINK, GTK_EXPAND, 0, 0);
+   gtk_widget_show(mainhbox);
+
+   mainvbox = gtk_vbox_new(FALSE, 0);
+   gtk_box_pack_start(GTK_BOX(mainhbox), mainvbox, FALSE, FALSE, 0);
+   gtk_widget_show(mainvbox);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_start(GTK_BOX(mainvbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   frame = gtk_frame_new(DIALOG_GEN_FUNC_COL_ALIGN_FRAME_LABEL);
+   gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 5);
+   gtk_widget_show(frame);
+
+   vbox = gtk_vbox_new(FALSE, 0);
+   gtk_container_add(GTK_CONTAINER(frame), vbox);
+   gtk_widget_show(vbox);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   // Widgets for specifying the alignment column.
+
+   button = gtk_radio_button_new(NULL);
+   gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 5);
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+   gtk_widget_show(button);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "col_num_align", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "0");
+   gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
+   gtk_widget_set_usize(entry, 25, -2);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(button, entry);
+
+   button = gtk_radio_button_new_with_label(
+      gtk_radio_button_group(GTK_RADIO_BUTTON(button)),
+                             DIALOG_GEN_FUNC_MAX_OPT_LABEL);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "col_max_align", button);
+   gtk_box_pack_end(GTK_BOX(hbox), button, TRUE, FALSE, 5);
+   gtk_widget_show(button);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_start(GTK_BOX(mainvbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   // Widgets for specifying the reference row & usage.
+
+   refbutton = gtk_check_button_new_with_label(DIALOG_GEN_FUNC_REF_ROW_FRAME_LABEL);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "row_ref", refbutton);
+   gtk_widget_show(refbutton);
+
+   frame = gtk_frame_new(NULL);
+   gtk_frame_set_label_widget(GTK_FRAME(frame), refbutton);
+   gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 5);
+   gtk_widget_show(frame);
+
+   vbox = gtk_vbox_new(FALSE, 0);
+   gtk_container_add(GTK_CONTAINER(frame), vbox);
+   gtk_widget_show(vbox);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   button = gtk_radio_button_new(NULL);
+   gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 5);
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+   gtk_widget_set_sensitive(button, FALSE);
+   gtk_widget_show(button);
+
+   UIInstance().RegisterWidgetDependence(refbutton, button);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "row_num_ref", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "0");
+   gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
+   gtk_widget_set_usize(entry, 25, -2);
+   gtk_widget_set_sensitive(entry, FALSE);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(refbutton, entry);
+   UIInstance().RegisterWidgetDependence(button, entry);
+
+   button = gtk_radio_button_new_with_label(
+      gtk_radio_button_group(GTK_RADIO_BUTTON(button)),
+                             DIALOG_GEN_FUNC_MAX_OPT_LABEL);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "row_max_ref", button);
+   gtk_box_pack_end(GTK_BOX(hbox), button, TRUE, FALSE, 5);
+   gtk_widget_set_sensitive(button, FALSE);
+   gtk_widget_show(button);
+
+   UIInstance().RegisterWidgetDependence(refbutton, button);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_end(GTK_BOX(vbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   button = gtk_check_button_new_with_label(DIALOG_GEN_FUNC_REF_TOTAL_OPT_LABEL);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "row_ref_total", button);
+   gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 5);
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+   gtk_widget_set_sensitive(button, FALSE);
+   gtk_widget_show(button);
+
+   UIInstance().RegisterWidgetDependence(refbutton, button);
+
+   mainvbox = gtk_vbox_new(FALSE, 0);
+   gtk_box_pack_end(GTK_BOX(mainhbox), mainvbox, FALSE, FALSE, 0);
+   gtk_widget_show(mainvbox);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_start(GTK_BOX(mainvbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   frame = gtk_frame_new(DIALOG_GEN_FUNC_ROW_ALIGN_FRAME_LABEL);
+   gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 5);
+   gtk_widget_show(frame);
+
+   vbox = gtk_vbox_new(FALSE, 0);
+   gtk_container_add(GTK_CONTAINER(frame), vbox);
+   gtk_widget_show(vbox);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   // Widgets for specifying the alignment row.
+
+   button = gtk_radio_button_new(NULL);
+   gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 5);
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+   gtk_widget_show(button);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "row_num_align", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "0");
+   gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
+   gtk_widget_set_usize(entry, 25, -2);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(button, entry);
+
+   button = gtk_radio_button_new_with_label(
+      gtk_radio_button_group(GTK_RADIO_BUTTON(button)),
+                             DIALOG_GEN_FUNC_MAX_OPT_LABEL);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "row_max_align", button);
+   gtk_box_pack_end(GTK_BOX(hbox), button, TRUE, FALSE, 5);
+   gtk_widget_show(button);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_start(GTK_BOX(mainvbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   // Widgets for specifying the reference column & usage.
+
+   refbutton = gtk_check_button_new_with_label(DIALOG_GEN_FUNC_REF_COL_FRAME_LABEL);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "col_ref", refbutton);
+   gtk_widget_show(refbutton);
+
+   frame = gtk_frame_new(NULL);
+   gtk_frame_set_label_widget(GTK_FRAME(frame), refbutton);
+   gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 5);
+   gtk_widget_show(frame);
+
+   vbox = gtk_vbox_new(FALSE, 0);
+   gtk_container_add(GTK_CONTAINER(frame), vbox);
+   gtk_widget_show(vbox);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   button = gtk_radio_button_new(NULL);
+   gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 5);
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+   gtk_widget_set_sensitive(button, FALSE);
+   gtk_widget_show(button);
+
+   UIInstance().RegisterWidgetDependence(refbutton, button);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "col_num_ref", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "0");
+   gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
+   gtk_widget_set_usize(entry, 25, -2);
+   gtk_widget_set_sensitive(entry, FALSE);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(refbutton, entry);
+   UIInstance().RegisterWidgetDependence(button, entry);
+
+   button = gtk_radio_button_new_with_label(
+      gtk_radio_button_group(GTK_RADIO_BUTTON(button)),
+                             DIALOG_GEN_FUNC_MAX_OPT_LABEL);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "col_max_ref", button);
+   gtk_box_pack_end(GTK_BOX(hbox), button, TRUE, FALSE, 5);
+   gtk_widget_set_sensitive(button, FALSE);
+   gtk_widget_show(button);
+
+   UIInstance().RegisterWidgetDependence(refbutton, button);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_end(GTK_BOX(vbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   button = gtk_check_button_new_with_label(DIALOG_GEN_FUNC_REF_TOTAL_OPT_LABEL);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "col_ref_total", button);
+   gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 5);
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+   gtk_widget_set_sensitive(button, FALSE);
+   gtk_widget_show(button);
+
+   UIInstance().RegisterWidgetDependence(refbutton, button);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_table_attach_defaults(GTK_TABLE(table), hbox, 0, 13, 5, 6);
+   gtk_widget_show(hbox);
+
+   // Create Cancel button and hook it to callback.
+
+   button = gtk_button_new_with_label(DIALOG_CANCEL_BUTTON);
+   gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
+   gtk_widget_set_usize(button, 60, -2);
+   gtk_widget_show(button);
+
+   CreateCancelButtonCallback(button);
+
+   // Create Apply button and hook it to callback.
+
+   button = gtk_button_new_with_label(DIALOG_APPLY_BUTTON);
+   gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 10);
+   gtk_widget_set_usize (button, 60, -2);
+   gtk_widget_show(button);
+
+   CreateApplyButtonCallback(button);
+
+   // Create OK button and hook it to callback.
+
+   button = gtk_button_new_with_label(DIALOG_OK_BUTTON);
+   gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
+   gtk_widget_set_usize (button, 60, -2);
+   gtk_widget_show(button);
+
+   CreateOkButtonCallback(button);
+}
+
+/**
+ * Destructor.
+ */
+GeneralFunctionDialog::~GeneralFunctionDialog()
+{
+}
+
+/**
+ * Handler for the Apply logic for this dialog. Apply the specified equations
+ * to the selected mesh entities.
+ *
+ * @return true if any meshes are selected, false otherwise.
+ */
+bool
+GeneralFunctionDialog::Apply()
+{
+   // Before doing anything, check to see if there are some meshes selected.
+   _nullVisitor->ResetVisitedCount();
+   GlobalSelectionSystem().foreachSelected(*_nullVisitor);
+   if (_nullVisitor->GetVisitedCount() == 0)
+   {
+      // Nope. Warn and bail out.
+      GenericPluginUI::WarningReportDialog(DIALOG_WARNING_TITLE,
+                                           DIALOG_NOMESHES_MSG);
+      return false;
+   }
+
+   // See if we're going to be affecting the S and/or T texture axis.
+   bool sApply = NamedToggleWidgetActive("s_apply");
+   bool tApply = NamedToggleWidgetActive("t_apply");
+
+   if (!sApply && !tApply)
+   {
+      // Not affecting either, so bail out.
+      return true;
+   }
+
+   // OK read the remaining info from the widgets.
+
+   MeshEntity::GeneralFunctionFactors s, t;
+   MeshEntity::GeneralFunctionFactors *sFactors = NULL;
+   MeshEntity::GeneralFunctionFactors *tFactors = NULL;
+   if (sApply)
+   {
+      // S axis is affected, so read the S factors.
+      s.oldValue = (float)atof(NamedEntryWidgetText("s_oldval"));
+      s.rowDistance = (float)atof(NamedEntryWidgetText("s_rowdist"));
+      s.colDistance = (float)atof(NamedEntryWidgetText("s_coldist"));
+      s.rowNumber = (float)atof(NamedEntryWidgetText("s_rownum"));
+      s.colNumber = (float)atof(NamedEntryWidgetText("s_colnum"));
+      s.constant = (float)atof(NamedEntryWidgetText("s_constant"));
+      sFactors = &s;
+   }
+   if (tApply)
+   {
+      // T axis is affected, so read the T factors.
+      t.oldValue = (float)atof(NamedEntryWidgetText("t_oldval"));
+      t.rowDistance = (float)atof(NamedEntryWidgetText("t_rowdist"));
+      t.colDistance = (float)atof(NamedEntryWidgetText("t_coldist"));
+      t.rowNumber = (float)atof(NamedEntryWidgetText("t_rownum"));
+      t.colNumber = (float)atof(NamedEntryWidgetText("t_colnum"));
+      t.constant = (float)atof(NamedEntryWidgetText("t_constant"));
+      tFactors = &t;
+   }
+   MeshEntity::SliceDesignation alignRow, alignCol;
+   alignRow.maxSlice = NamedToggleWidgetActive("row_max_align");
+   alignRow.index = atoi(NamedEntryWidgetText("row_num_align"));
+   alignCol.maxSlice = NamedToggleWidgetActive("col_max_align");
+   alignCol.index = atoi(NamedEntryWidgetText("col_num_align"));
+   MeshEntity::RefSliceDescriptor row, col;
+   MeshEntity::RefSliceDescriptor *refRow = NULL;
+   MeshEntity::RefSliceDescriptor *refCol = NULL;
+   if (NamedToggleWidgetActive("row_ref"))
+   {
+      // Reference row is specified, so get that info.
+      row.designation.maxSlice = NamedToggleWidgetActive("row_max_ref");
+      row.designation.index = atoi(NamedEntryWidgetText("row_num_ref"));
+      row.totalLengthOnly = NamedToggleWidgetActive("row_ref_total");
+      refRow = &row;
+   }
+   if (NamedToggleWidgetActive("col_ref"))
+   {
+      // Reference column is specified, so get that info.
+      col.designation.maxSlice = NamedToggleWidgetActive("col_max_ref");
+      col.designation.index = atoi(NamedEntryWidgetText("col_num_ref"));
+      col.totalLengthOnly = NamedToggleWidgetActive("col_ref_total");
+      refCol = &col;
+   }
+   bool surfaceValues = NamedToggleWidgetActive("surface");
+
+   // Let Radiant know the name of the operation responsible for the changes
+   // that are about to happen.
+   UndoableCommand undo(_triggerCommand.c_str());
+
+   // Apply the specified equation to every selected mesh.
+   SmartPointer<GeneralFunctionVisitor> funcVisitor(
+      new GeneralFunctionVisitor(sFactors, tFactors,
+                                 &alignRow, &alignCol, refRow, refCol,
+                                 surfaceValues));
+   GlobalSelectionSystem().foreachSelected(*funcVisitor);
+
+   // Done!
+   return true;
+}
diff --git a/contrib/meshtex/GeneralFunctionDialog.h b/contrib/meshtex/GeneralFunctionDialog.h
new file mode 100644 (file)
index 0000000..2ef2357
--- /dev/null
@@ -0,0 +1,85 @@
+/**
+ * @file GeneralFunctionDialog.h
+ * Declares the GeneralFunctionDialog class.
+ * @ingroup meshtex-ui
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(INCLUDED_GENERALFUNCTIONDIALOG_H)
+#define INCLUDED_GENERALFUNCTIONDIALOG_H
+
+#include "GenericDialog.h"
+#include "MeshVisitor.h"
+
+/**
+ * Subclass of GenericDialog that implements the window summoned by selecting
+ * the General Function menu entry. This window is used to specify equations
+ * for setting S and T values as a linear combination of various factors.
+ *
+ * @image html genfunc.png
+ * 
+ * @ingroup meshtex-ui
+ */
+class GeneralFunctionDialog : public GenericDialog
+{
+private: // private types
+
+   /**
+    * Visitor for applying the chosen equation to a mesh.
+    */
+   class GeneralFunctionVisitor : public MeshVisitor
+   {
+   public:
+      GeneralFunctionVisitor(const MeshEntity::GeneralFunctionFactors *sFactors,
+                             const MeshEntity::GeneralFunctionFactors *tFactors,
+                             const MeshEntity::SliceDesignation *alignRow,
+                             const MeshEntity::SliceDesignation *alignCol,
+                             const MeshEntity::RefSliceDescriptor *refRow,
+                             const MeshEntity::RefSliceDescriptor *refCol,
+                             bool surfaceValues);
+   private:
+      bool Execute(MeshEntity& meshEntity) const;
+   private:
+      const MeshEntity::GeneralFunctionFactors *_sFactors;
+      const MeshEntity::GeneralFunctionFactors *_tFactors;
+      const MeshEntity::SliceDesignation *_alignRow;
+      const MeshEntity::SliceDesignation *_alignCol;
+      const MeshEntity::RefSliceDescriptor *_refRow;
+      const MeshEntity::RefSliceDescriptor *_refCol;
+      bool _surfaceValues;
+   };
+
+public: // public methods
+
+   GeneralFunctionDialog(const std::string& key);
+   ~GeneralFunctionDialog();
+   bool Apply();
+
+private: // private member vars
+
+   /**
+    * Action-less mesh visitor used purely to count the number of selected mesh
+    * entities.
+    */
+   SmartPointer<MeshVisitor> _nullVisitor;
+};
+
+#endif // #if !defined(INCLUDED_GENERALFUNCTIONDIALOG_H)
\ No newline at end of file
diff --git a/contrib/meshtex/GenericDialog.cpp b/contrib/meshtex/GenericDialog.cpp
new file mode 100644 (file)
index 0000000..d007828
--- /dev/null
@@ -0,0 +1,268 @@
+/**
+ * @file GenericDialog.cpp
+ * Implements the GenericDialog class.
+ * @ingroup generic-ui
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gtk/gtk.h>
+
+#include "GenericDialog.h"
+#include "GenericPluginUI.h"
+
+
+/**
+ * Constructor. Create the GTK+ widget for the dialog window (not visible
+ * yet). Initialize callback IDs to zero (invalid). Note that as this is a
+ * protected method, GenericDialog objects cannot be created directly; only
+ * subclasses of GenericDialog can be created.
+ *
+ * @param key Unique key to identify this dialog widget.
+ */
+GenericDialog::GenericDialog(const std::string& key) :
+   _dialog(gtk_window_new(GTK_WINDOW_TOPLEVEL)),
+   _window(NULL),
+   _key(key),
+   _okCallbackID(0),
+   _applyCallbackID(0),
+   _cancelCallbackID(0)
+{
+   // XXX Should we go ahead invoke CreateWindowCloseCallback here (and make it
+   // private) rather than leaving that to the subclass constructors? Depends on
+   // whether it's plausible that a dialog would ever NOT want the usual
+   // handling for the close event.
+}
+
+/**
+ * Virtual destructor. Destroy the GTK+ dialog widget (and therefore its
+ * contained widgets) if necessary.
+ */
+GenericDialog::~GenericDialog()
+{
+   if (_dialog != NULL)
+   {
+      gtk_widget_destroy(_dialog);
+   }
+}
+
+/**
+ * Get the unique key that identifies this dialog widget.
+ *
+ * @return The key.
+ */
+const std::string&
+GenericDialog::GetKey() const
+{
+   return _key;
+}
+
+/**
+ * Mark this window widget as a modal dialog for a parent window.
+ *
+ * @param window The parent window.
+ */
+void
+GenericDialog::SetWindow(GtkWidget *window)
+{
+   // Remember the parent window.
+   _window = window;
+   // Mark this widget as a modal dialog for it.
+   if (_dialog != NULL)
+   {
+      gtk_window_set_transient_for(GTK_WINDOW(_dialog), GTK_WINDOW(_window));
+   }
+}
+
+/**
+ * Raise this dialog window to the top of the window stack.
+ */
+void
+GenericDialog::Raise()
+{
+   // Don't bother if not visible.
+   if (GTK_WIDGET_VISIBLE(_dialog))
+   {
+      gdk_window_raise(_dialog->window);
+   }
+}
+
+/**
+ * Make this dialog window visible and foreground.
+ *
+ * @param triggerCommand The command token that summoned the dialog.
+ */
+void
+GenericDialog::Show(const std::string& triggerCommand)
+{
+   // Remember the command token that summoned the dialog; subclasses can make
+   // use of this information.
+   _triggerCommand = triggerCommand;
+   // Show the window if it is currently hidden.
+   if (!GTK_WIDGET_VISIBLE(_dialog))
+   {
+      gtk_widget_show(_dialog);
+   }
+   // Raise the window to the top of the stack.
+   Raise();
+}
+
+/**
+ * Hide this dialog window.
+ */
+void
+GenericDialog::Hide()
+{
+   // Bail out if the window is already invisible.
+   if (!GTK_WIDGET_VISIBLE(_dialog))
+   {
+      return;
+   }
+   // Hide the window.
+   gtk_widget_hide(_dialog);
+   // If there's a parent window, raise it to the top of the stack.
+   if (_window == NULL)
+   {
+      return;
+   }
+   gdk_window_raise(_window->window);
+}
+
+/**
+ * Default handler for Apply logic. This method should be overriden by
+ * subclass implementations that need to execute some logic when OK or Apply
+ * is clicked. The return value should be the success of that logic. A
+ * successful OK will cause the window to be hidden.
+ *
+ * @return true if the apply logic executed; always the case in this skeleton
+ *         implementation.
+ */
+bool
+GenericDialog::Apply()
+{
+   // Default logic does nothing.
+   return true;
+}
+
+/**
+ * Callback for window-close event.
+ *
+ * @param widget     This dialog window widget.
+ * @param event      The event that instigated the callback.
+ * @param callbackID Unique numerical ID for the callback.
+ *
+ * @return TRUE as defined by glib.
+ */
+gint
+GenericDialog::CloseEventCallback(GtkWidget *widget,
+                                  GdkEvent* event,
+                                  gpointer callbackID)
+{
+   // All we need to do is hide the window.
+   Hide();
+   return TRUE;
+}
+
+/**
+ * Callback for clicking on OK/Apply/Cancel button.
+ *
+ * @param widget     This dialog window widget.
+ * @param callbackID Unique numerical ID for the callback.
+ */
+void
+GenericDialog::FinalizeCallback(GtkWidget *widget,
+                                gpointer callbackID)
+{
+   // Assume success until we have to do something.
+   bool success = true;
+   // If this is not a Cancel callback, run the Apply logic.
+   if (callbackID != _cancelCallbackID)
+   {
+      success = Apply();
+   }
+   // Hide the window if this is a cancel or a successful OK callback.
+   if (success && callbackID != _applyCallbackID)
+   {
+      Hide();
+   }
+}
+
+/**
+ * Register the callback for the close-window event.
+ */
+void
+GenericDialog::CreateWindowCloseCallback()
+{
+   // The close-window event will trigger the CloseEventCallback method.
+   const GenericPluginUI::DialogEventCallbackMethod
+      <GenericDialog, &GenericDialog::CloseEventCallback> closeCallback(*this);
+   UIInstance().RegisterDialogEventCallback(_dialog, "delete_event", closeCallback);
+}
+
+/**
+ * Register the callback for the OK button.
+ *
+ * @param button The OK button widget.
+ */
+void
+GenericDialog::CreateOkButtonCallback(GtkWidget *button)
+{
+   // Clicking the OK button will trigger the FinalizeCallback method. Since
+   // FinalizeCallback can be used for multiple buttons, we'll save the specific
+   // callback ID associated with the OK button.
+   const GenericPluginUI::DialogSignalCallbackMethod
+      <GenericDialog, &GenericDialog::FinalizeCallback> finalizeCallback(*this);
+   _okCallbackID =
+      UIInstance().RegisterDialogSignalCallback(button, "clicked", finalizeCallback);
+}
+
+/**
+ * Register the callback for the Apply button.
+ *
+ * @param button The Apply button widget.
+ */
+void
+GenericDialog::CreateApplyButtonCallback(GtkWidget *button)
+{
+   // Clicking the Apply button will trigger the FinalizeCallback method. Since
+   // FinalizeCallback can be used for multiple buttons, we'll save the specific
+   // callback ID associated with the Apply button.
+   const GenericPluginUI::DialogSignalCallbackMethod
+      <GenericDialog, &GenericDialog::FinalizeCallback> finalizeCallback(*this);
+   _applyCallbackID =
+      UIInstance().RegisterDialogSignalCallback(button, "clicked", finalizeCallback);
+}
+
+/**
+ * Register the callback for the Cancel button.
+ *
+ * @param button The Cancel button widget.
+ */
+void
+GenericDialog::CreateCancelButtonCallback(GtkWidget *button)
+{
+   // Clicking the Cancel button will trigger the FinalizeCallback method. Since
+   // FinalizeCallback can be used for multiple buttons, we'll save the specific
+   // callback ID associated with the Cancel button.
+   const GenericPluginUI::DialogSignalCallbackMethod
+      <GenericDialog, &GenericDialog::FinalizeCallback> finalizeCallback(*this);
+   _cancelCallbackID =
+      UIInstance().RegisterDialogSignalCallback(button, "clicked", finalizeCallback);
+}
diff --git a/contrib/meshtex/GenericDialog.h b/contrib/meshtex/GenericDialog.h
new file mode 100644 (file)
index 0000000..705a2fc
--- /dev/null
@@ -0,0 +1,156 @@
+/**
+ * @file GenericDialog.h
+ * Declares the GenericDialog class.
+ * @ingroup generic-ui
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(INCLUDED_GENERICDIALOG_H)
+#define INCLUDED_GENERICDIALOG_H
+
+#include <string>
+#include <gdk/gdk.h>
+#include <glib.h>
+
+#include "RefCounted.h"
+
+#include "qerplugin.h"
+
+/**
+ * Macro to get a handle on a widget inside the dialog by name.
+ *
+ * @param widgetName Name of the contained widget to find.
+ */
+#define NamedWidget(widgetName) \
+   (gtk_object_get_data(GTK_OBJECT(_dialog), widgetName))
+
+/**
+ * Macro to enable/disable a widget inside the dialog, selected by name.
+ *
+ * @param widgetName Name of the contained widget to enable/disable.
+ */
+#define NamedToggleWidgetActive(widgetName) \
+   (GTK_TOGGLE_BUTTON(NamedWidget(widgetName))->active)
+
+/**
+ * Macro to read text from a widget inside the dialog, selected by name.
+ *
+ * @param widgetName Name of the contained widget to read from.
+ */
+#define NamedEntryWidgetText(widgetName) \
+   (gtk_entry_get_text(GTK_ENTRY(NamedWidget(widgetName))))
+
+/**
+ * Framework for a basic dialog window with OK/Apply/Cancel actions.
+ *
+ * A subclass should handle decorating/customizing the window, populating it
+ * with contained widgets, customizing the Apply logic, and registering the
+ * appropriate OK/Apply/Cancel buttons.
+ *
+ * @ingroup generic-ui
+ */
+class GenericDialog : public RefCounted
+{
+protected: // protected methods
+
+   /// @name Lifecycle
+   //@{
+   GenericDialog(const std::string& key);
+   virtual ~GenericDialog();
+   //@}
+
+private: // private methods
+
+   /// @name Unimplemented to prevent copy/assignment
+   //@{
+   GenericDialog(const GenericDialog&);
+   const GenericDialog& operator=(const GenericDialog&);
+   //@}
+
+public: // public methods
+
+   /// @name Interrogation
+   //@{
+   const std::string& GetKey() const;
+   //@}
+   /// @name Window management
+   //@{
+   virtual void SetWindow(GtkWidget *window);
+   virtual void Raise();
+   virtual void Show(const std::string& triggerCommand);
+   virtual void Hide();
+   //@}
+   /// @name Callback implementation
+   //@{
+   virtual bool Apply();
+   virtual gint CloseEventCallback(GtkWidget *widget,
+                                   GdkEvent* event,
+                                   gpointer callbackID);
+   virtual void FinalizeCallback(GtkWidget *widget,
+                                 gpointer callbackID);
+   //@}
+   /// @name Callback creation
+   //@{
+   void CreateWindowCloseCallback();
+   void CreateOkButtonCallback(GtkWidget *button);
+   void CreateApplyButtonCallback(GtkWidget *button);
+   void CreateCancelButtonCallback(GtkWidget *button);
+   //@}
+
+protected: // protected member vars
+
+   /**
+    * This dialog widget.
+    */
+   GtkWidget *_dialog;
+
+   /**
+    * Parent window.
+    */
+   GtkWidget *_window;
+
+   /**
+    * Unique key for this dialog.
+    */
+   const std::string _key;
+
+   /**
+    * Command token that most recently summoned this dialog.
+    */
+   std::string _triggerCommand;
+
+   /**
+    * Callback ID associated with an OK button; 0 if none.
+    */
+   gpointer _okCallbackID;
+
+   /**
+    * Callback ID associated with an Apply button; 0 if none.
+    */
+   gpointer _applyCallbackID;
+
+   /**
+    * Callback ID associated with a Cancel button; 0 if none.
+    */
+   gpointer _cancelCallbackID;
+};
+
+#endif // #if !defined(INCLUDED_GENERICDIALOG_H)
diff --git a/contrib/meshtex/GenericMainMenu.cpp b/contrib/meshtex/GenericMainMenu.cpp
new file mode 100644 (file)
index 0000000..0cf4236
--- /dev/null
@@ -0,0 +1,218 @@
+/**
+ * @file GenericMainMenu.cpp
+ * Implements the GenericMainMenu class.
+ * @ingroup generic-ui
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "GenericMainMenu.h"
+#include "GenericPluginUI.h"
+#include "GenericPluginUIMessages.h"
+#include "PluginProperties.h"
+
+
+/**
+ * Default constructor. Note that as this is a protected method,
+ * GenericMainMenu objects cannot be created directly; only subclasses of
+ * GenericMainMenu can be created.
+ */
+GenericMainMenu::GenericMainMenu()
+{
+}
+
+/**
+ * Virtual destructor.
+ */
+GenericMainMenu::~GenericMainMenu()
+{
+}
+
+/**
+ * Invoke the handler for the given command token.
+ *
+ * @param command The command token.
+ */
+void
+GenericMainMenu::Dispatch(const char *command)
+{
+   // Convert token to string.
+   std::string commandString(command);
+   // Invoke the function from the dispatch map that corresponds to the command.
+   // The command key should always be in the map, since the set of commands
+   // advertised to Radiant is the same as the set used to make the map.
+#if defined(_DEBUG)
+   ASSERT_MESSAGE(_dispatchMap.find(commandString) != _dispatchMap.end(),
+                  "dispatched plugin command unknown");
+#endif
+   _dispatchMap[commandString](commandString);
+}
+
+/**
+ * Handle a command that summons a dialog window.
+ *
+ * @param commandString The command token.
+ */
+void
+GenericMainMenu::CommandDialogShow(const std::string& commandString)
+{
+   // Look up the dialog window for this command.
+   DialogMap::const_iterator dialogMapIter = _dialogMap.find(commandString);
+   // Seems somewhat more plausible that we could hit an error condition here
+   // where there is no associated dialog, so handle that more gracefully
+   // than an assert.
+   GenericDialog *dialog;
+   if (dialogMapIter == _dialogMap.end() ||
+       (dialog = dialogMapIter->second) == NULL)
+   {
+      std::string message(commandString + ": " + DIALOG_INTERNAL_ERROR);
+      GenericPluginUI::ErrorReportDialog(DIALOG_ERROR_TITLE, message.c_str());
+      return;
+   }
+   // If we have the dialog though, let's summon it.
+   dialog->Show(commandString);
+}
+
+/**
+ * Get the command list for the plugin menu, as a semicolon-separated string
+ * of tokens representing each command.
+ *
+ * @return The command list string.
+ */
+const std::string&
+GenericMainMenu::GetCommandList() const
+{
+   return _menuCommands;
+}
+
+/**
+ * Get the command label list for the plugin menu, as a semicolon-separated
+ * string of labels to appear in the menu.
+ *
+ * @return The command label list string.
+ */
+const std::string&
+GenericMainMenu::GetCommandLabelList() const
+{
+   return _menuCommandLabels;
+}
+
+/**
+ * Invoked before beginning construction of the command list (by subsequent
+ * Add* invocations). In this base class, BeginEntries does nothing.
+ */
+void
+GenericMainMenu::BeginEntries()
+{
+}
+
+/**
+ * Append a command-group separator to the command list. This should be done
+ * between an invocation of BeginEntries and EndEntries. (And presumably in
+ * the neighborhood of invocations of AddEntry and/or AddDialogShowEntry.)
+ */
+void
+GenericMainMenu::AddSeparator()
+{
+   // Our internal command and command-label strings actually grow backwards,
+   // so prepend the separator to them.
+   static std::string separatorString(MENU_SEPARATOR);
+   _menuCommandLabels = separatorString + ";" + _menuCommandLabels;
+   _menuCommands = separatorString + ";" + _menuCommands;
+}
+
+/**
+ * Append a command to the command list that will trigger a callback function
+ * when the menu entry is selected. Invoking AddEntry should be done between
+ * an invocation of BeginEntries and EndEntries.
+ *
+ * @param commandLabel    The command label.
+ * @param command         The command token, unique for this plugin.
+ *                        Emptystring is interpreted as: re-use the label
+ *                        for the token.
+ * @param commandCallback The command callback function.
+ *
+ * @return The globally-unique command token: plugin name + "." + command.
+ */
+std::string
+GenericMainMenu::AddEntry(const char *commandLabel,
+                          const char *command,
+                          const CommandCallback& commandCallback)
+{
+   // Our internal command-label string actually grows backwards, so prepend the
+   // command label to it.
+   std::string commandLabelString(commandLabel);
+   _menuCommandLabels = commandLabelString + ";" + _menuCommandLabels;
+   // Use the label for the token if no token specified.
+   std::string commandString(command);
+   if (commandString.empty())
+   {
+      commandString = commandLabelString;
+   }
+   // Add the plugin name in front of the command token to globally uniquify it.
+   commandString = std::string(PLUGIN_NAME) + "." + commandString;
+   // Our internal command string actually grows backwards, so prepend the
+   // command token to it.
+   _menuCommands = commandString + ";" + _menuCommands;
+   // Save the association between the globally-unique token and callback.
+   _dispatchMap[commandString] = commandCallback;
+   // Return the globally-unique command token.
+   return commandString;
+}
+
+/**
+ * Append a command to the command list that will summon a dialog when the
+ * menu entry is selected. Invoking AddDialogShowEntry should be done between
+ * an invocation of BeginEntries and EndEntries.
+ *
+ * @param commandLabel The command label.
+ * @param command      The command token, unique for this plugin. Emptystring
+ *                     is interpreted as: re-use the label for the token.
+ * @param dialog       The dialog this command should summon.
+ */
+void
+GenericMainMenu::AddDialogShowEntry(const char *commandLabel,
+                                    const char *command,
+                                    const SmartPointer<GenericDialog>& dialog)
+{
+   // Create a new command callback specifically for summoning that dialog.
+   const CommandCallbackMethod
+      <GenericMainMenu, &GenericMainMenu::CommandDialogShow> commandDialogShow(*this);
+   // Register the command and its callback via AddEntry, and save the
+   // association between the globally-unique token and dialog.
+   _dialogMap.insert(
+      std::make_pair(AddEntry(commandLabel, command, commandDialogShow),
+                     dialog));
+}
+
+/**
+ * Invoked after ending construction of the command list. In this base class,
+ * EndEntries only removes spurious semicolons left behind by the way we
+ * constructed the lists.
+ */
+void
+GenericMainMenu::EndEntries()
+{
+   if (_menuCommandLabels.length() > 0)
+   {
+      _menuCommandLabels.resize(_menuCommandLabels.length() - 1);
+      _menuCommands.resize(_menuCommands.length() - 1);
+   }
+}
diff --git a/contrib/meshtex/GenericMainMenu.h b/contrib/meshtex/GenericMainMenu.h
new file mode 100644 (file)
index 0000000..af97226
--- /dev/null
@@ -0,0 +1,156 @@
+/**
+ * @file GenericMainMenu.h
+ * Declares the GenericMainMenu class.
+ * @ingroup generic-ui
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(INCLUDED_GENERICMAINMENU_H)
+#define INCLUDED_GENERICMAINMENU_H
+
+#include <string>
+#include <map>
+
+#include "RefCounted.h"
+#include "GenericDialog.h"
+
+#include "generic/callback.h"
+#include "generic/referencecounted.h"
+
+/**
+ * String used in lists of command tokens or command labels to separate groups
+ * of commands.
+ */
+#define MENU_SEPARATOR "-"
+
+/**
+ * Framework for a Radiant plugin's main menu. This object handles the menu
+ * logic: what commands exist and how to execute them. The actual menu
+ * display is handled by Radiant.
+ * 
+ * A subclass should handle creating the command list.
+ *
+ * @ingroup generic-ui
+ */
+class GenericMainMenu : public RefCounted
+{
+protected: // protected methods
+
+   /// @name Lifecycle
+   //@{
+   GenericMainMenu();
+   virtual ~GenericMainMenu();
+   //@}
+
+private: // private methods
+
+   /// @name Unimplemented to prevent copy/assignment
+   //@{
+   GenericMainMenu(const GenericMainMenu&);
+   const GenericMainMenu& operator=(const GenericMainMenu&);
+   //@}
+
+public: // public methods
+
+   /// @name Service the plugin interface
+   //@{
+   virtual void Dispatch(const char *command);
+   virtual void CommandDialogShow(const std::string& commandString);
+   const std::string& GetCommandList() const;
+   const std::string& GetCommandLabelList() const;
+   //@}
+
+protected: // protected types
+   /**
+    * Function signature for a menu command callback. The callback takes a
+    * string argument (the command token); it has no return value.
+    */
+   typedef Callback1<const std::string&, void> CommandCallback;
+
+   /**
+    * An instance of this class can be used as a
+    * GenericMainMenu::CommandCallback, in situations where the callback is a
+    * method to be invoked on a target object. When invoking this constructor,
+    * the target object is the constructor argument, and the target object class
+    * and method are template parameters. The target object's method must have
+    * an appropriate signature for CommandCallback: one string argument, void
+    * return.
+    */
+   template<typename ObjectClass, void (ObjectClass::*member)(const std::string&)>
+   class CommandCallbackMethod :
+      public MemberCaller1<ObjectClass, const std::string&, member>
+   {
+   public:
+      /**
+       * Constructor.
+       *
+       * @param object The object on which to invoke the callback method.
+       */
+      CommandCallbackMethod(ObjectClass& object) :
+         MemberCaller1<ObjectClass, const std::string&, member>(object) {}
+   };
+
+protected: // protected methods
+
+   /// @name Command list construction
+   //@{
+   virtual void BeginEntries();
+   virtual void AddSeparator();
+   virtual std::string AddEntry(const char *commandLabel,
+                                const char *command,
+                                const CommandCallback& commandCallback);
+   virtual void AddDialogShowEntry(const char *commandLabel,
+                                   const char *command,
+                                   const SmartPointer<GenericDialog>& dialog);
+   virtual void EndEntries();
+   //@}
+
+private: // private types
+
+   /**
+    * Type for a map between string and reference-counted dialog window.
+    */
+   typedef std::map<std::string, SmartPointer<GenericDialog> > DialogMap;
+
+private: // private member vars
+
+   /**
+    * Semicolon-separated string of command tokens.
+    */
+   std::string _menuCommands;
+
+   /**
+    * Semicolon-separated string of command labels.
+    */
+   std::string _menuCommandLabels;
+
+   /**
+    * Associations between command tokens and callbacks.
+    */
+   std::map<std::string, CommandCallback> _dispatchMap;
+
+   /**
+    * Associations between command tokens and dialog windows.
+    */
+   DialogMap _dialogMap;
+};
+
+#endif // #if !defined(INCLUDED_GENERICMAINMENU_H)
\ No newline at end of file
diff --git a/contrib/meshtex/GenericPluginUI.cpp b/contrib/meshtex/GenericPluginUI.cpp
new file mode 100644 (file)
index 0000000..2da58c8
--- /dev/null
@@ -0,0 +1,374 @@
+/**
+ * @file GenericPluginUI.cpp
+ * Implements the GenericPluginUI class.
+ * @ingroup generic-ui
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gtk/gtk.h>
+
+#include "GenericPluginUI.h"
+
+
+/**
+ * Default constructor. Initialize object state; the initial callback ID to
+ * use is 1 because 0 is reserved for "invalid". Note that as this is a
+ * protected method, GenericPluginUI objects cannot be created directly; only
+ * subclasses of GenericPluginUI can be created.
+ */
+GenericPluginUI::GenericPluginUI() :
+   _window(NULL),
+   _mainMenu(NULL),
+   _callbackID(1),
+   _widgetControlCallback(*this)
+{
+}
+
+/**
+ * Virtual destructor. Remove references to UI elements (which should
+ * trigger garbage collection).
+ */
+GenericPluginUI::~GenericPluginUI()
+{
+   // Remove any reference to the menu object.
+   if (_mainMenu != NULL)
+   {
+      delete _mainMenu;
+   }
+   // The _dialogMap will also be deleted by the normal destructor operation,
+   // which will clear any references we hold on dialog windows.
+}
+
+/**
+ * Register the command menu.
+ *
+ * @param mainMenu The command menu.
+ */
+void
+GenericPluginUI::RegisterMainMenu(SmartPointer<GenericMainMenu>& mainMenu)
+{
+   // Remember the menu object, and hold a reference on it so it won't be
+   // garbage-collected while this object exists.
+   _mainMenu = new SmartPointer<GenericMainMenu>(mainMenu);
+}
+
+/**
+ * Register a dialog window.
+ *
+ * @param dialog The dialog.
+ */
+void
+GenericPluginUI::RegisterDialog(SmartPointer<GenericDialog>& dialog)
+{
+   // Remember the association between key and dialog, and hold a reference
+   // on it so it won't be garbage-collected while this object exists.
+   _dialogMap.insert(std::make_pair(dialog->GetKey(), dialog));
+}
+
+/**
+ * Specify the main application window.
+ *
+ * @param window The main window.
+ */
+void
+GenericPluginUI::SetWindow(GtkWidget *window)
+{
+   // Remember it.
+   _window = window;
+   // Set it as the parent for every dialog window.
+   DialogMap::const_iterator dialogMapIter = _dialogMap.begin();
+   for (; dialogMapIter != _dialogMap.end(); ++dialogMapIter)
+   {
+      if (dialogMapIter->second.get() != NULL)
+      {
+         dialogMapIter->second->SetWindow(window);
+      }
+   }
+}
+
+/**
+ * Get the command menu.
+ *
+ * @return The command menu, or NULL if none has been registered.
+ */
+GenericMainMenu *
+GenericPluginUI::MainMenu()
+{
+   if (_mainMenu == NULL)
+   {
+      return NULL;
+   }
+   return _mainMenu->get();
+}
+
+/**
+ * Get the dialog identified by the specified key.
+ *
+ * @param key The key.
+ *
+ * @return The dialog, or NULL if none found for that key.
+ */
+GenericDialog *
+GenericPluginUI::Dialog(const std::string& key)
+{
+   DialogMap::const_iterator dialogMapIter = _dialogMap.find(key);
+   if (dialogMapIter == _dialogMap.end())
+   {
+      return NULL;
+   }
+   return dialogMapIter->second;
+}
+
+/**
+ * Generic event callback used to invoke the specific callback functions
+ * registered with this manager. Those specific callbacks are not themselves
+ * registered directly with GTK+ because they may be methods that must be
+ * invoked on objects. (Unlike this function, which is a static method.)
+ *
+ * @param widget The widget generating the event.
+ * @param event  The event.
+ * @param data   ID of the specific callback registered with this manager.
+ *
+ * @return The return value from the specific callback.
+ */
+gint
+GenericPluginUI::DialogEventCallbackDispatch(GtkWidget *widget,
+                                             GdkEvent* event,
+                                             gpointer data)
+{
+   // Look up the callback ID in our registration map.
+   DialogEventCallbackMap::iterator dialogEventCallbackMapIter =
+      UIInstance()._dialogEventCallbackMap.find(data);
+   if (dialogEventCallbackMapIter == UIInstance()._dialogEventCallbackMap.end())
+   {
+      // If we didn't find it, nothing to do.
+      return TRUE;
+   }
+   // Otherwise invoke that callback.
+   return dialogEventCallbackMapIter->second(widget, event, data);
+}
+
+/**
+ * Generic signal callback used to invoke the specific callback functions
+ * registered with this manager. Those specific callbacks are not themselves
+ * registered directly with GTK+ because they may be methods that must be
+ * invoked on objects. (Unlike this function, which is a static method.)
+ *
+ * @param widget The widget generating the signal.
+ * @param data   ID of the specific callback registered with this manager.
+ */
+void
+GenericPluginUI::DialogSignalCallbackDispatch(GtkWidget *widget,
+                                              gpointer data)
+{
+   // Look up the callback ID in our registration map.
+   DialogSignalCallbackMap::iterator dialogSignalCallbackMapIter =
+      UIInstance()._dialogSignalCallbackMap.find(data);
+   if (dialogSignalCallbackMapIter == UIInstance()._dialogSignalCallbackMap.end())
+   {
+      // If we didn't find it, nothing to do.
+      return;
+   }
+   // Otherwise invoke that callback.
+   dialogSignalCallbackMapIter->second(widget, data);
+}
+
+/**
+ * Register a function to be invoked when a widget generates an event.
+ *
+ * @param widget   The widget generating the event.
+ * @param name     The name of the event.
+ * @param callback The callback function.
+ *
+ * @return The unique ID for the registered callback function.
+ */
+gpointer
+GenericPluginUI::RegisterDialogEventCallback(GtkWidget *widget,
+                                             const gchar *name,
+                                             const DialogEventCallback& callback)
+{
+   // Get the next callback ID to use.
+   gpointer callbackID = GUINT_TO_POINTER(_callbackID++);
+   // Make that event on that dialog widget trigger our event dispatch.
+   g_signal_connect(G_OBJECT(widget), name,
+                    G_CALLBACK(DialogEventCallbackDispatch), callbackID);
+   // Save the association between callback ID and function.
+   _dialogEventCallbackMap.insert(std::make_pair(callbackID, callback));
+   // Return the generated unique callback ID.
+   return callbackID;
+}
+
+/**
+ * Register a function to be invoked when a widget generates a signal.
+ *
+ * @param widget   The widget generating the signal.
+ * @param name     The name of the signal.
+ * @param callback The callback function.
+ *
+ * @return The unique ID for the registered callback function.
+ */
+gpointer
+GenericPluginUI::RegisterDialogSignalCallback(GtkWidget *widget,
+                                              const gchar *name,
+                                              const DialogSignalCallback& callback)
+{
+   // Get the next callback ID to use.
+   gpointer callbackID = GUINT_TO_POINTER(_callbackID++);
+   // Make that signal on that dialog widget trigger our signal dispatch.
+   g_signal_connect(G_OBJECT(widget), name,
+                    G_CALLBACK(DialogSignalCallbackDispatch), callbackID);
+   // Save the association between callback ID and function.
+   _dialogSignalCallbackMap.insert(std::make_pair(callbackID, callback));
+   // Return the generated unique callback ID.
+   return callbackID;
+}
+
+/**
+ * Declare that the controllee widget should be inactive when the
+ * controller widget is inactive. The controllee will be active only
+ * when all of its controllers allow it to be so.
+ *
+ * @param controller The controller widget.
+ * @param controllee The controllee widget.
+ */
+void
+GenericPluginUI::RegisterWidgetDependence(GtkWidget *controller,
+                                          GtkWidget *controllee)
+{
+   // Make sure we get a callback when the controller is toggled.
+   if (_widgetControlMap.find(controller) == _widgetControlMap.end())
+   {
+      RegisterDialogSignalCallback(controller, "clicked", _widgetControlCallback);
+   }
+   // Save the association.
+   _widgetControlMap[controller].push_back(controllee);
+   _widgetControlledByMap[controllee].push_back(controller);
+}
+
+/**
+ * Declare that the controllee widget should be inactive when the
+ * controller widget is active. The controllee will be active only
+ * when all of its controllers allow it to be so.
+ *
+ * @param controller The controller widget.
+ * @param controllee The controllee widget.
+ */
+void
+GenericPluginUI::RegisterWidgetAntiDependence(GtkWidget *controller,
+                                              GtkWidget *controllee)
+{
+   // Make sure we get a callback when the controller is toggled.
+   if (_widgetControlMap.find(controller) == _widgetControlMap.end())
+   {
+      RegisterDialogSignalCallback(controller, "clicked", _widgetControlCallback);
+   }
+   // Save the association.
+   _widgetControlMap[controller].push_back(controllee);
+   _widgetAntiControlledByMap[controllee].push_back(controller);
+}
+
+/**
+ * Manage the state of controllee widgets when a controller widget is clicked.
+ *
+ * @param widget     The controller widget.
+ * @param callbackID Unique numerical ID for the callback.
+ */
+void
+GenericPluginUI::WidgetControlCallback(GtkWidget *widget,
+                                       gpointer callbackID)
+{
+   // Iterate over all controllees registered for this widget.
+   std::vector<GtkWidget *>::iterator controlleeIter =
+      _widgetControlMap[widget].begin();
+   for (; controlleeIter != _widgetControlMap[widget].end(); ++controlleeIter)
+   {
+      GtkWidget *controllee = *controlleeIter;
+      std::vector<GtkWidget *>::iterator controllerIter;
+      // Start with an assumption that the controllee widget will be active.
+      bool sensitive = true;
+      // Look for a dependence on any widget.
+      controllerIter = _widgetControlledByMap[controllee].begin();
+      for (; controllerIter != _widgetControlledByMap[controllee].end(); ++controllerIter)
+      {
+         // Dependence found; honor it.
+         if (!(GTK_TOGGLE_BUTTON(*controllerIter)->active))
+         {
+            sensitive = false;
+            break;
+         }
+      }
+      // Look for an anti-dependence on any widget.
+      controllerIter = _widgetAntiControlledByMap[controllee].begin();
+      for (; controllerIter != _widgetAntiControlledByMap[controllee].end(); ++controllerIter)
+      {
+         // Anti-dependence found; honor it.
+         if (GTK_TOGGLE_BUTTON(*controllerIter)->active)
+         {
+            sensitive = false;
+            break;
+         }
+      }
+      // Set the active state of the controllee appropriately.
+      gtk_widget_set_sensitive(controllee, sensitive);
+   }
+}
+
+/**
+ * Generate an error dialog.
+ *
+ * @param title   The dialog title.
+ * @param message The error message.
+ */
+void
+GenericPluginUI::ErrorReportDialog(const char *title,
+                                   const char *message)
+{
+   // Pass this operation to Radiant.
+   GlobalRadiant().m_pfnMessageBox(UIInstance()._window, message, title, eMB_OK, eMB_ICONERROR);
+}
+
+/**
+ * Generate a warning dialog.
+ *
+ * @param title   The dialog title.
+ * @param message The warning message.
+ */
+void
+GenericPluginUI::WarningReportDialog(const char *title,
+                                     const char *message)
+{
+   // Pass this operation to Radiant.
+   GlobalRadiant().m_pfnMessageBox(UIInstance()._window, message, title, eMB_OK, eMB_ICONWARNING);
+}
+
+/**
+ * Generate an info dialog.
+ *
+ * @param title   The dialog title.
+ * @param message The info message.
+ */
+void
+GenericPluginUI::InfoReportDialog(const char *title,
+                                  const char *message)
+{
+   // Pass this operation to Radiant.
+   GlobalRadiant().m_pfnMessageBox(UIInstance()._window, message, title, eMB_OK, eMB_ICONDEFAULT);
+}
\ No newline at end of file
diff --git a/contrib/meshtex/GenericPluginUI.h b/contrib/meshtex/GenericPluginUI.h
new file mode 100644 (file)
index 0000000..8b6c241
--- /dev/null
@@ -0,0 +1,276 @@
+/**
+ * @file GenericPluginUI.h
+ * Declares the GenericPluginUI class.
+ * @ingroup generic-ui
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(INCLUDED_GENERICPLUGINUI_H)
+#define INCLUDED_GENERICPLUGINUI_H
+
+#include <vector>
+#include <gdk/gdk.h>
+#include <glib.h>
+
+#include "GenericMainMenu.h"
+#include "GenericDialog.h"
+
+#include "qerplugin.h"
+#include "generic/referencecounted.h"
+
+/**
+ * Framework for a manager of a command menu and dialog windows. It is
+ * responsible for:
+ * - holding a reference on those objects for lifecycle management
+ * - providing lookup facilities for those objects
+ * - mapping GTK+ event/signal callbacks into method invocations on
+ * the dialog objects
+ * - providing automatic handling of widgets that should be active or
+ * inactive based on the active state of other widgets
+ * - providing utility functions for generating message popups
+ *
+ * A subclass should handle the creation and registration of the UI objects.
+ *
+ * @ingroup generic-ui
+ */
+class GenericPluginUI
+{
+protected: // protected methods
+
+   /// @name Lifecycle
+   //@{
+   GenericPluginUI();
+   virtual ~GenericPluginUI();
+   //@}
+
+private: // private methods
+
+   /// @name Unimplemented to prevent copy/assignment
+   //@{
+   GenericPluginUI(const GenericPluginUI&);
+   const GenericPluginUI& operator=(const GenericPluginUI&);
+   //@}
+
+public: // public types
+
+   /**
+    * Type for GTK+ event callbacks. The callback takes a GtkWidget* argument
+    * (widget generating the event), a GdkEvent* argument (the event), and a
+    * gpointer argument (the callback ID); it returns gint (success as TRUE or
+    * FALSE).
+    */
+   typedef Callback3<GtkWidget *, GdkEvent *, gpointer, gint> DialogEventCallback;
+
+   /**
+    * Type for GTK+ signal callbacks. The callback takes a GtkWidget* argument
+    * (widget generating the signal) and a gpointer argument (the callback data);
+    * it has no return value.
+    */
+   typedef Callback2<GtkWidget *, gpointer, void> DialogSignalCallback;
+
+   /**
+    * An instance of this class can be used as a
+    * GenericPluginUI::DialogEventCallback, in situations where the callback is
+    * a method to be invoked on a target object. When invoking this constructor,
+    * the target object is the constructor argument, and the target object class
+    * and method are template parameters. The target object's method must have
+    * an appropriate signature for DialogEventCallback: one GtkWidget* argument,
+    * one GdkEvent* argument, one gpointer argument, gint return.
+    */
+   template<typename ObjectClass, gint (ObjectClass::*member)(GtkWidget *, GdkEvent*, gpointer)>
+   class DialogEventCallbackMethod : public BindFirstOpaque3<Member3<ObjectClass, GtkWidget *, GdkEvent*, gpointer, gint, member> >
+   {
+   public:
+      /**
+       * Constructor.
+       *
+       * @param object The object on which to invoke the callback method.
+       */
+      DialogEventCallbackMethod(ObjectClass& object) :
+         BindFirstOpaque3<Member3<ObjectClass, GtkWidget *, GdkEvent *, gpointer, gint, member> >(object) {}
+   };
+
+   /**
+    * An instance of this class can be used as a
+    * GenericPluginUI::DialogSignalCallback, in situations where the callback is
+    * a method to be invoked on a target object. When invoking this constructor,
+    * the target object is the constructor argument, and the target object class
+    * and method are template parameters. The target object's method must have
+    * an appropriate signature for DialogSignalCallback: one GtkWidget* argument,
+    * one gpointer argument, void return.
+    */
+   template<typename ObjectClass, void (ObjectClass::*member)(GtkWidget *, gpointer)>
+   class DialogSignalCallbackMethod : public BindFirstOpaque2<Member2<ObjectClass, GtkWidget *, gpointer, void, member> >
+   {
+   public:
+      /**
+       * Constructor.
+       *
+       * @param object The object on which to invoke the callback method.
+       */
+      DialogSignalCallbackMethod(ObjectClass& object) :
+         BindFirstOpaque2<Member2<ObjectClass, GtkWidget *, gpointer, void, member> >(object) {}
+   };
+
+public: // public methods
+
+   /// @name Setup
+   //@{
+   void RegisterMainMenu(SmartPointer<GenericMainMenu>& mainMenu);
+   void RegisterDialog(SmartPointer<GenericDialog>& dialog);
+   void SetWindow(GtkWidget *window);
+   //@}
+   /// @name Lookup
+   //@{
+   GenericMainMenu *MainMenu();
+   GenericDialog *Dialog(const std::string& key);
+   //@}
+   /// @name Event/signal dispatch
+   //@{
+   static gint DialogEventCallbackDispatch(GtkWidget *widget,
+                                           GdkEvent* event,
+                                           gpointer data);
+   static void DialogSignalCallbackDispatch(GtkWidget *widget,
+                                            gpointer data);
+   gpointer RegisterDialogEventCallback(GtkWidget *widget,
+                                        const gchar *name,
+                                        const DialogEventCallback& callback);
+   gpointer RegisterDialogSignalCallback(GtkWidget *widget,
+                                         const gchar *name,
+                                         const DialogSignalCallback& callback);
+   //@}
+   /// @name Widget dependence
+   //@{
+   void RegisterWidgetDependence(GtkWidget *controller,
+                                 GtkWidget *controllee);
+   void RegisterWidgetAntiDependence(GtkWidget *controller,
+                                     GtkWidget *controllee);
+   void WidgetControlCallback(GtkWidget *widget,
+                              gpointer callbackID);
+   //@}
+   /// @name Message popups
+   //@{
+   static void ErrorReportDialog(const char *title,
+                                 const char *message);
+   static void WarningReportDialog(const char *title,
+                                   const char *message);
+   static void InfoReportDialog(const char *title,
+                                const char *message);
+   //@}
+
+private: // private types
+
+   /**
+    * Type for a map between string and reference-counted dialog window.
+    */
+   typedef std::map<std::string, SmartPointer<GenericDialog> > DialogMap;
+
+   /**
+    * Type for a map between gpointer (for callback ID) and event callback.
+    */
+   typedef std::map<gpointer, DialogEventCallback> DialogEventCallbackMap;
+
+   /**
+    * Type for a map between gpointer (for callback ID) and signal callback.
+    */
+   typedef std::map<gpointer, DialogSignalCallback> DialogSignalCallbackMap;
+
+   /**
+    * Type for a map between a widget and a vector of widgets.
+    */
+   typedef std::map<GtkWidget *, std::vector<GtkWidget *> > WidgetDependenceMap;
+
+private: // private member vars
+
+   /**
+    * The parent window.
+    */
+   GtkWidget *_window;
+
+   /**
+    * Pointer to a reference-counted handle on the main menu object.
+    */
+   SmartPointer<GenericMainMenu> *_mainMenu;
+
+   /**
+    * Next ID to use when registering an event or signal callback. Starts at 1;
+    * 0 is reserved to mean invalid.
+    */
+   unsigned _callbackID;
+
+   /**
+    * Callback to implement widget-active dependences.
+    */
+   const DialogSignalCallbackMethod<GenericPluginUI, &GenericPluginUI::WidgetControlCallback>
+      _widgetControlCallback;
+
+   /**
+    * Associations between keys and dialog windows.
+    */
+   DialogMap _dialogMap;
+
+   /**
+    * Associations between callback IDs and event callbacks.
+    */
+   DialogEventCallbackMap _dialogEventCallbackMap;
+
+   /**
+    * Associations between callback IDs and signal callbacks.
+    */
+   DialogSignalCallbackMap _dialogSignalCallbackMap;
+
+   /**
+    * Associations between controller and controllee widgets for all dependences
+    * and anti-dependences.
+    */
+   WidgetDependenceMap _widgetControlMap;
+
+   /**
+    * Associations between controller and controllee widgets for dependences
+    * only.
+    */
+   WidgetDependenceMap _widgetControlledByMap;
+
+   /**
+    * Associations between controller and controllee widgets for anti-
+    * dependences only.
+    */
+   WidgetDependenceMap _widgetAntiControlledByMap;
+};
+
+
+/**
+ * Get the singleton instance of the UI manager.
+ *
+ * @internal
+ * This function is not implemented in the GenericPluginUI.cpp file. It must
+ * be implemented somewhere, because it is invoked from various places even
+ * within the generic UI code. The implementation of this function should
+ * return a reference to the singleton instance of a GenericPluginUI subclass.
+ * @endinternal
+ *
+ * @return Reference to a singleton that implements GenericPluginUI.
+ *
+ * @relates GenericPluginUI
+ */
+GenericPluginUI& UIInstance();
+
+#endif // #if !defined(INCLUDED_GENERICPLUGINUI_H)
diff --git a/contrib/meshtex/GenericPluginUIMessages.h b/contrib/meshtex/GenericPluginUIMessages.h
new file mode 100644 (file)
index 0000000..0e0d0cc
--- /dev/null
@@ -0,0 +1,47 @@
+/**
+ * @file GenericPluginUIMessages.h
+ * String constants for messages shown in dialogs.
+ * @ingroup generic-ui
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(INCLUDED_GENERICPLUGINUIMESSAGES_H)
+#define INCLUDED_GENERICPLUGINUIMESSAGES_H
+
+/// @name Window titles
+//@{
+#define DIALOG_ERROR_TITLE "Error"
+#define DIALOG_WARNING_TITLE "Warning"
+//@}
+
+/// @name Window content
+//@{
+#define DIALOG_INTERNAL_ERROR "Internal error in plugin code."
+//@}
+
+/// @name Button labels
+//@{
+#define DIALOG_CANCEL_BUTTON "Cancel"
+#define DIALOG_APPLY_BUTTON "Apply"
+#define DIALOG_OK_BUTTON "OK"
+//@}
+
+#endif // #if !defined(INCLUDED_GENERICPLUGINUIMESSAGES_H)
diff --git a/contrib/meshtex/GetInfoDialog.cpp b/contrib/meshtex/GetInfoDialog.cpp
new file mode 100644 (file)
index 0000000..9c9fd29
--- /dev/null
@@ -0,0 +1,287 @@
+/**
+ * @file GetInfoDialog.cpp
+ * Implements the GetInfoDialog class.
+ * @ingroup meshtex-ui
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gtk/gtk.h>
+
+#include "GenericPluginUI.h"
+#include "GetInfoDialog.h"
+#include "PluginUIMessages.h"
+
+
+/**
+ * Constructor. See MeshEntity::GetInfo for details of how these arguments
+ * are interpreted.
+ *
+ * @param refRow             Pointer to reference row number; NULL if none.
+ * @param refCol             Pointer to reference column number; NULL if none.
+ * @param rowTexInfoCallback Pointer to callback for reference row info; NULL
+ *                           if none.
+ * @param colTexInfoCallback Pointer to callback for reference column info;
+ *                           NULL if none.
+ */
+GetInfoDialog::GetInfoVisitor::GetInfoVisitor(
+   const int *refRow,
+   const int *refCol,
+   const MeshEntity::TexInfoCallback *rowTexInfoCallback,
+   const MeshEntity::TexInfoCallback *colTexInfoCallback) :
+   _refRow(refRow),
+   _refCol(refCol),
+   _rowTexInfoCallback(rowTexInfoCallback),
+   _colTexInfoCallback(colTexInfoCallback)
+{
+}
+
+/**
+ * Visitor action; invoke MeshEntity::GetInfo on a mesh.
+ *
+ * @param [in,out] meshEntity The mesh.
+ *
+ * @return true.
+ */
+bool
+GetInfoDialog::GetInfoVisitor::Execute(MeshEntity& meshEntity) const
+{
+   meshEntity.GetInfo(_refRow, _refCol, _rowTexInfoCallback, _colTexInfoCallback);
+   return true;
+}
+
+/**
+ * Constructor. Connect the row and column texture info callbacks to the
+ * appropriate methods on the Set S/T Scale dialog object. Configure the
+ * dialog window and create all the contained widgets. Connect widgets to
+ * callbacks as necessary.
+ *
+ * @param key            The unique key identifying this dialog.
+ * @param setScaleDialog Reference-counted handle on the Set S/T Scale dialog.
+ */
+GetInfoDialog::GetInfoDialog(const std::string& key,
+                             SmartPointer<SetScaleDialog>& setScaleDialog) :
+   GenericDialog(key),
+   _setScaleDialog(setScaleDialog),
+   _rowTexInfoCallback(
+      MeshEntity::TexInfoCallbackMethod<SetScaleDialog,
+                                        &SetScaleDialog::PopulateSWidgets>(*setScaleDialog)),
+   _colTexInfoCallback(
+      MeshEntity::TexInfoCallbackMethod<SetScaleDialog,
+                                        &SetScaleDialog::PopulateTWidgets>(*setScaleDialog)),
+   _nullVisitor(new MeshVisitor())
+{
+   // Enable the usual handling of the close event.
+   CreateWindowCloseCallback();
+
+   // Configure the dialog window.
+   gtk_window_set_resizable(GTK_WINDOW(_dialog), FALSE);
+   gtk_window_set_title(GTK_WINDOW(_dialog), DIALOG_GET_INFO_TITLE);
+   gtk_container_set_border_width(GTK_CONTAINER(_dialog), 10);
+
+   // Create the contained widgets.
+
+   GtkWidget *table;
+   GtkWidget *entry;
+   GtkWidget *button;
+   GtkWidget *label;
+   GtkWidget *hbox;
+
+   table = gtk_table_new(4, 3, FALSE);
+   gtk_table_set_row_spacing(GTK_TABLE(table), 1, 10);
+   gtk_table_set_row_spacing(GTK_TABLE(table), 2, 15);
+   gtk_container_add(GTK_CONTAINER(_dialog), table);
+   gtk_widget_show(table);
+
+   // Widgets for specifying the reference row if any.
+
+   button = gtk_check_button_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "s_apply", button);
+   gtk_table_attach_defaults(GTK_TABLE(table), button, 0, 1, 0, 1);
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+   gtk_widget_show(button);
+
+   label = gtk_label_new(DIALOG_GET_INFO_S_ROW_HEADER);
+   gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_RIGHT);
+   gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 0, 1);
+   gtk_widget_show(label);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "s_ref_row", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "0");
+   gtk_table_attach_defaults(GTK_TABLE(table), entry, 2, 3, 0, 1);
+   gtk_widget_set_usize(entry, 50, -2);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(button, label);
+   UIInstance().RegisterWidgetDependence(button, entry);
+
+   // Widgets for specifying the reference column if any.
+
+   button = gtk_check_button_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "t_apply", button);
+   gtk_table_attach_defaults(GTK_TABLE(table), button, 0, 1, 1, 2);
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+   gtk_widget_show(button);
+
+   label = gtk_label_new(DIALOG_GET_INFO_T_COL_HEADER);
+   gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_RIGHT);
+   gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 1, 2);
+   gtk_widget_show(label);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "t_ref_col", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "0");
+   gtk_table_attach_defaults(GTK_TABLE(table), entry, 2, 3, 1, 2);
+   gtk_widget_set_usize(entry, 50, -2);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(button, label);
+   UIInstance().RegisterWidgetDependence(button, entry);
+
+   // Checkbox to enable the callbacks to Set S/T Scale.
+
+   button = gtk_check_button_new_with_label(DIALOG_GET_INFO_XFER_OPT_LABEL);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "transfer", button);
+   gtk_table_attach(GTK_TABLE(table), button, 0, 3, 2, 3, GTK_EXPAND, GTK_EXPAND, 0, 0);
+   gtk_widget_show(button);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_table_attach_defaults(GTK_TABLE(table), hbox, 0, 3, 3, 4);
+   gtk_widget_show(hbox);
+
+   // Create Cancel button and hook it to callback.
+
+   button = gtk_button_new_with_label(DIALOG_CANCEL_BUTTON);
+   gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
+   gtk_widget_set_usize(button, 60, -2);
+   gtk_widget_show(button);
+
+   CreateCancelButtonCallback(button);
+
+   // Create Apply button and hook it to callback.
+
+   button = gtk_button_new_with_label(DIALOG_APPLY_BUTTON);
+   gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 10);
+   gtk_widget_set_usize (button, 60, -2);
+   gtk_widget_show(button);
+
+   CreateApplyButtonCallback(button);
+
+   // Create OK button and hook it to callback.
+
+   button = gtk_button_new_with_label(DIALOG_OK_BUTTON);
+   gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
+   gtk_widget_set_usize (button, 60, -2);
+   gtk_widget_show(button);
+
+   CreateOkButtonCallback(button);
+}
+
+/**
+ * Destructor.
+ */
+GetInfoDialog::~GetInfoDialog()
+{
+}
+
+/**
+ * Handler for the Apply logic for this dialog. Interrogate the selected mesh
+ * entities.
+ *
+ * @return false if no meshes are selected, false if multiple meshes are
+ *         selected along with the transfer option, true otherwise.
+ */
+bool
+GetInfoDialog::Apply()
+{
+   // Before doing anything, check to see if there are some meshes selected.
+   _nullVisitor->ResetVisitedCount();
+   GlobalSelectionSystem().foreachSelected(*_nullVisitor);
+   if (_nullVisitor->GetVisitedCount() == 0)
+   {
+      // Nope. Warn and bail out.
+      GenericPluginUI::WarningReportDialog(DIALOG_WARNING_TITLE,
+                                           DIALOG_NOMESHES_MSG);
+      return false;
+   }
+
+   // If the option to transfer info to Set S/T Scale is active, then only one
+   // mesh may be selected.
+   bool transfer = NamedToggleWidgetActive("transfer");
+   if (transfer && _nullVisitor->GetVisitedCount() != 1)
+   {
+      // Multiple selected. Warn and bail out.
+      GenericPluginUI::ErrorReportDialog(DIALOG_ERROR_TITLE,
+                                         DIALOG_MULTIMESHES_ERROR);
+      return false;
+   }
+
+   // OK read the remaining info from the widgets.
+
+   bool sApply = NamedToggleWidgetActive("s_apply");
+   bool tApply = NamedToggleWidgetActive("t_apply");
+
+   int row, col;
+   int *refRow = NULL;
+   int *refCol = NULL;
+   MeshEntity::TexInfoCallback *rowTexInfoCallback = NULL;
+   MeshEntity::TexInfoCallback *colTexInfoCallback = NULL;
+   if (sApply)
+   {
+      // Reference row is specified, so get that info.
+      row = atoi(NamedEntryWidgetText("s_ref_row"));
+      refRow = &row;
+      if (transfer)
+      {
+         // If transferring to Set S/T Scale, get that callback.
+         rowTexInfoCallback = &_rowTexInfoCallback;
+      }
+   }
+   if (tApply)
+   {
+      // Reference column is specified, so get that info.
+      col = atoi(NamedEntryWidgetText("t_ref_col"));
+      refCol = &col;
+      if (transfer)
+      {
+         // If transferring to Set S/T Scale, get that callback.
+         colTexInfoCallback = &_colTexInfoCallback;
+      }
+   }
+
+   // We don't need to instantiate an UndoableCommand since we won't be making
+   // any changes.
+
+   // Interrogate every selected mesh.
+   SmartPointer<GetInfoVisitor> infoVisitor(
+      new GetInfoVisitor(refRow, refCol, rowTexInfoCallback, colTexInfoCallback));
+   GlobalSelectionSystem().foreachSelected(*infoVisitor);
+
+   // If we populated something in the Set S/T Scale dialog, give that dialog a
+   // courtesy raise.
+   if (transfer)
+   {
+      _setScaleDialog->Raise();
+   }
+
+   // Done!
+   return true;
+}
diff --git a/contrib/meshtex/GetInfoDialog.h b/contrib/meshtex/GetInfoDialog.h
new file mode 100644 (file)
index 0000000..ef266d5
--- /dev/null
@@ -0,0 +1,99 @@
+/**
+ * @file GetInfoDialog.h
+ * Declares the GetInfoDialog class.
+ * @ingroup meshtex-ui
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(INCLUDED_GETINFODIALOG_H)
+#define INCLUDED_GETINFODIALOG_H
+
+#include "GenericDialog.h"
+#include "SetScaleDialog.h"
+#include "MeshVisitor.h"
+
+#include "generic/referencecounted.h"
+
+/**
+ * Subclass of GenericDialog that implements the window summoned by selecting
+ * the Get Info menu entry. This window allows the user to query information
+ * about selected meshes and optionally transfer some of that information to
+ * the Set S/T Scale dialog.
+ * 
+ * @image html getinfo.png
+ *
+ * @ingroup meshtex-ui
+ */
+class GetInfoDialog : public GenericDialog
+{
+private: // private types
+
+   /**
+    * Visitor for interrogating a mesh.
+    */
+   class GetInfoVisitor : public MeshVisitor
+   {
+   public:
+      GetInfoVisitor(const int *refRow,
+                     const int *refCol,
+                     const MeshEntity::TexInfoCallback *rowTexInfoCallback,
+                     const MeshEntity::TexInfoCallback *colTexInfoCallback);
+   private:
+      bool Execute(MeshEntity& meshEntity) const;
+   private:
+      const int *_refRow;
+      const int *_refCol;
+      const MeshEntity::TexInfoCallback *_rowTexInfoCallback;
+      const MeshEntity::TexInfoCallback *_colTexInfoCallback;
+   };
+
+public: // public methods
+
+   GetInfoDialog(const std::string& key,
+                 SmartPointer<SetScaleDialog>& setScaleDialog);
+   ~GetInfoDialog();
+   bool Apply();
+
+private: // private member vars
+
+   /**
+    * Handle on the Set S/T Scale dialog.
+    */
+   SmartPointer<SetScaleDialog> _setScaleDialog;
+
+   /**
+    * Callback to process row texture scale information from a query.
+    */
+   MeshEntity::TexInfoCallback _rowTexInfoCallback;
+
+   /**
+    * Callback to process column texture scale information from a query.
+    */
+   MeshEntity::TexInfoCallback _colTexInfoCallback;
+
+   /**
+    * Action-less mesh visitor used purely to count the number of selected mesh
+    * entities.
+    */
+   SmartPointer<MeshVisitor> _nullVisitor;
+};
+
+#endif // #if !defined(INCLUDED_GETINFODIALOG_H)
\ No newline at end of file
diff --git a/contrib/meshtex/HISTORY b/contrib/meshtex/HISTORY
new file mode 100644 (file)
index 0000000..5cef999
--- /dev/null
@@ -0,0 +1,41 @@
+Version history
+
+3.0 in progress:
+- Rewrite.  Bugfixes along the way.  Bugs undoubtedly introduced.
+- Moved to GtkRadiant 1.5 compatibility.
+- A few new features, TBD (to be documented).
+- Dropped Linux build for now.
+
+...time passes, things happen, continents drift...
+
+2.4 May 17, 2001:
+- Flipped the T scaling, to be consistent with the way Natural scaling works
+  in the new GtkRadiant.
+- Linux version still untested, but it's been updated too.
+
+2.3 May 13, 2001:
+- Added a Version resource.
+- Added a TEST compile of the plugin for Linux... no idea if it works.
+
+2.2 May 8, 2001:
+- Flipped sense of min/max align if scale/reps is negative.
+- Disallowed scale/reps of zero.
+
+2.1 May 5, 2001:
+- Fixed max align for natural scale without reference row.
+
+2.0 May 5, 2001:
+- Added the Set S/T Scale dialog and the General Function dialog.
+- Reworked Get Info, added capability to transfer scale info to the
+  Set S/T Scale dialog.
+- General noodlings to make negative scales work, other fixes.
+- Updated the texture mapping explanation and moved it into its own file.
+
+1.1 April 28, 2001:
+- Fixed the Min/Max Align functions, they weren't getting dispatched.
+- Fixed the Stretch/Shrink functions to work if the min S/T is nonzero.
+- Added Both Align, S Align, and T Align.
+- Added the +/- indicator in the info dialog.
+
+1.0 April 27, 2001:
+- Hey, it works and stuff.
diff --git a/contrib/meshtex/LICENSE b/contrib/meshtex/LICENSE
new file mode 100644 (file)
index 0000000..ca3a430
--- /dev/null
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    &lt;one line to give the program's name and a brief idea of what it does.&gt;
+    Copyright (C) &lt;year&gt;  &lt;name of author&gt;
+
+    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.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  &lt;signature of Ty Coon&gt;, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/contrib/meshtex/MainMenu.cpp b/contrib/meshtex/MainMenu.cpp
new file mode 100644 (file)
index 0000000..08b708c
--- /dev/null
@@ -0,0 +1,236 @@
+/**
+ * @file MainMenu.cpp
+ * Implements the MainMenu class.
+ * @ingroup meshtex-ui
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "MainMenu.h"
+#include "PluginUI.h"
+#include "PluginUIMessages.h"
+
+#include "scenelib.h"
+#include "iundo.h"
+
+
+/**
+ * Constructor.
+ *
+ * @param visitorFunctor The functor for the mesh preset function.
+ * @param axes           The texture axes to affect.
+ */
+MainMenu::PresetFuncVisitor::PresetFuncVisitor(
+   const VisitorFunctor& visitorFunctor,
+   MeshEntity::TextureAxisSelection axes) :
+   _visitorFunctor(visitorFunctor),
+   _axes(axes)
+{
+}
+
+/**
+ * Visitor action; invoke a preset function on a mesh.
+ *
+ * @param [in,out] meshEntity The mesh entity.
+ *
+ * @return true.
+ */
+bool
+MainMenu::PresetFuncVisitor::Execute(MeshEntity& meshEntity) const
+{
+   (meshEntity.*_visitorFunctor)(_axes);
+   return true;
+}
+
+/**
+ * Constructor. Instantiate the command callbacks and construct the command
+ * list.
+ *
+ * @param setScaleDialog Reference-counted handle on the Set S/T Scale dialog.
+ * @param getInfoDialog  Reference-counted handle on the Get Info dialog.
+ * @param genFuncDialog  Reference-counted handle on the General Function
+ *                       dialog.
+ */
+MainMenu::MainMenu(SmartPointer<GenericDialog>& setScaleDialog,
+                   SmartPointer<GenericDialog>& getInfoDialog,
+                   SmartPointer<GenericDialog>& genFuncDialog) :
+   _commandMeshVisitor(*this)
+{
+   // Like _commandMeshVisitor, the callbacks for the Help and About commands
+   // also specify functions on this object. 
+   const CommandCallbackMethod
+      <MainMenu, &MainMenu::CommandHelp> commandHelp(*this);
+   const CommandCallbackMethod
+      <MainMenu, &MainMenu::CommandAbout> commandAbout(*this);
+
+   // Start constructing the command list.
+   BeginEntries();
+   // First two commands summon dialogs.
+   AddDialogShowEntry("Set S/T Scale...", "SetScale", setScaleDialog);
+   AddDialogShowEntry("Get Info...", "GetInfo", getInfoDialog);
+   AddSeparator();
+   // Next command summons a dialog.
+   AddDialogShowEntry("General Function...", "GeneralFunction", genFuncDialog);
+   AddSeparator();
+   // The next few groups of commands are all similar "preset function"
+   // commands. They will each trigger the CommandMeshVisitor callback when
+   // selected in the menu. CommandMeshVisitor needs a visitor object to apply
+   // to the selected meshes, which we are specifying here. 
+   AddMeshVisitorEntry("S&T Align Auto", "STMinMaxAlignAuto",
+                       SmartPointer<MeshVisitor>(
+                          new PresetFuncVisitor(&MeshEntity::MinMaxAlignAutoScale,
+                                                MeshEntity::ALL_TEX_AXES)));
+   AddMeshVisitorEntry("S Align Auto", "SMinMaxAlignAuto",
+                       SmartPointer<MeshVisitor>(
+                          new PresetFuncVisitor(&MeshEntity::MinMaxAlignAutoScale,
+                                                MeshEntity::S_TEX_AXIS_ONLY)));
+   AddMeshVisitorEntry("T Align Auto", "TMinMaxAlignAuto",
+                       SmartPointer<MeshVisitor>(
+                          new PresetFuncVisitor(&MeshEntity::MinMaxAlignAutoScale,
+                                                MeshEntity::T_TEX_AXIS_ONLY)));
+   AddSeparator();
+   AddMeshVisitorEntry("S Align Stretch", "SMinMaxAlignStretch",
+                       SmartPointer<MeshVisitor>(
+                          new PresetFuncVisitor(&MeshEntity::MinMaxAlignStretch,
+                                                MeshEntity::S_TEX_AXIS_ONLY)));
+   AddMeshVisitorEntry("S Align Shrink", "SMinMaxAlignShrink",
+                       SmartPointer<MeshVisitor>(
+                          new PresetFuncVisitor(&MeshEntity::MinMaxAlignShrink,
+                                                MeshEntity::S_TEX_AXIS_ONLY)));
+   AddMeshVisitorEntry("T Align Stretch", "TMinMaxAlignStretch",
+                       SmartPointer<MeshVisitor>(
+                          new PresetFuncVisitor(&MeshEntity::MinMaxAlignStretch,
+                                                MeshEntity::T_TEX_AXIS_ONLY)));
+   AddMeshVisitorEntry("T Align Shrink", "TMinMaxAlignShrink",
+                   SmartPointer<MeshVisitor>(
+                      new PresetFuncVisitor(&MeshEntity::MinMaxAlignShrink,
+                                            MeshEntity::T_TEX_AXIS_ONLY)));
+   AddSeparator();
+   AddMeshVisitorEntry("S Min Align", "SMinAlign",
+                       SmartPointer<MeshVisitor>(
+                          new PresetFuncVisitor(&MeshEntity::MinAlign,
+                                                MeshEntity::S_TEX_AXIS_ONLY)));
+   AddMeshVisitorEntry("S Max Align", "SMaxAlign",
+                       SmartPointer<MeshVisitor>(
+                          new PresetFuncVisitor(&MeshEntity::MaxAlign,
+                                                MeshEntity::S_TEX_AXIS_ONLY)));
+   AddMeshVisitorEntry("T Min Align", "TMinAlign",
+                       SmartPointer<MeshVisitor>(
+                          new PresetFuncVisitor(&MeshEntity::MinAlign,
+                                                MeshEntity::T_TEX_AXIS_ONLY)));
+   AddMeshVisitorEntry("T Max Align", "TMaxAlign",
+                       SmartPointer<MeshVisitor>(
+                          new PresetFuncVisitor(&MeshEntity::MaxAlign,
+                                                MeshEntity::T_TEX_AXIS_ONLY)));
+   AddSeparator();
+   // These commands each invoke a unique callback when selected.
+   AddEntry("Help...", "Help", commandHelp);
+   AddEntry("About...", "About", commandAbout);
+   // Done!
+   EndEntries();
+}
+
+/**
+ * Destructor.
+ */
+MainMenu::~MainMenu()
+{
+}
+
+/**
+ * Callback common to all of the commands that trigger a processing by mesh
+ * visitor when selected. This callback does its own internal dispatch to
+ * distinctly handle the various commands.
+ *
+ * @param commandString The command token.
+ */
+void
+MainMenu::CommandMeshVisitor(const std::string& commandString)
+{
+   // Look up the registered mesh visitor for this command.
+   VisitorMap::const_iterator visitorMapIter = _visitorMap.find(commandString);
+   MeshVisitor *meshVisitor;
+   if (visitorMapIter == _visitorMap.end() ||
+       (meshVisitor = visitorMapIter->second) == NULL)
+   {
+      // That's odd, there isn't one. Bail out.
+      std::string message(commandString + ": " + DIALOG_INTERNAL_ERROR);
+      GenericPluginUI::ErrorReportDialog(DIALOG_ERROR_TITLE, message.c_str());
+      return;
+   }
+   // Let Radiant know the name of the operation responsible for the changes
+   // that are about to happen.
+   UndoableCommand undo(commandString.c_str());
+   // Apply the visitor to every selected mesh.
+   meshVisitor->ResetVisitedCount();
+   GlobalSelectionSystem().foreachSelected(*meshVisitor);
+   if (meshVisitor->GetVisitedCount() == 0)
+   {
+      // Warn if there weren't any meshes selected (so nothing happened). 
+      GenericPluginUI::WarningReportDialog(DIALOG_WARNING_TITLE,
+                                           DIALOG_NOMESHES_MSG);
+   }
+}
+
+/**
+ * Callback triggered when the Help menu entry is selected.
+ *
+ * @param commandString The command token.
+ */
+void
+MainMenu::CommandHelp(const std::string& commandString)
+{
+   // Pop up a hopefully somewhat helpful message dialog.
+   GenericPluginUI::InfoReportDialog(DIALOG_HELP_TITLE,
+                                     DIALOG_HELP_MSG);
+}
+
+/**
+ * Callback triggered when the About menu entry is selected.
+ *
+ * @param commandString The command token.
+ */
+void
+MainMenu::CommandAbout(const std::string& commandString)
+{
+   // Pop up a message dialog that describes the plugin.
+   GenericPluginUI::InfoReportDialog(DIALOG_ABOUT_TITLE,
+                                     DIALOG_ABOUT_MSG);
+}
+
+/**
+ * Register a mesh visitor to be used to implement a specified command.
+ *
+ * @param commandLabel The command label.
+ * @param command      The command token.
+ * @param visitor      The mesh visitor.
+ */
+void
+MainMenu::AddMeshVisitorEntry(const char *commandLabel,
+                              const char *command,
+                              const SmartPointer<MeshVisitor>& visitor)
+{
+   // Add to the command list, and indicate that CommandMeshVisitor is the
+   // callback for this command. Then save the association between command and
+   // visitor, for CommandMeshVisitor to reference.
+   _visitorMap.insert(
+      std::make_pair(AddEntry(commandLabel, command, _commandMeshVisitor),
+                     visitor));
+}
\ No newline at end of file
diff --git a/contrib/meshtex/MainMenu.h b/contrib/meshtex/MainMenu.h
new file mode 100644 (file)
index 0000000..d6e3296
--- /dev/null
@@ -0,0 +1,105 @@
+/**
+ * @file MainMenu.h
+ * Declares the MainMenu class.
+ * @ingroup meshtex-ui
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(INCLUDED_MAINMENU_H)
+#define INCLUDED_MAINMENU_H
+
+#include "GenericMainMenu.h"
+#include "MeshVisitor.h"
+
+/**
+ * Subclass of GenericMainMenu that constructs the commands for this plugin.
+ *
+ * @ingroup meshtex-ui
+ */
+class MainMenu : public GenericMainMenu
+{
+private: // private types
+
+   /**
+    * Visitor for invoking a function on a MeshEntity when that function does
+    * not require any arguments other than the texture axes. These operations
+    * are triggered immediately on selecting a menu entry, rather than being
+    * triggered by applying some other dialog.
+    */
+   class PresetFuncVisitor : public MeshVisitor
+   {
+   public:
+      /**
+       * Function signature for a preset function.
+       */
+      typedef void(MeshEntity::*VisitorFunctor)(MeshEntity::TextureAxisSelection axes);
+   public:
+      PresetFuncVisitor(const VisitorFunctor& visitorFunctor,
+                        MeshEntity::TextureAxisSelection axes);
+   private:
+      bool Execute(MeshEntity& meshEntity) const;
+
+   private:
+      const VisitorFunctor _visitorFunctor;
+      const MeshEntity::TextureAxisSelection _axes;
+   };
+
+public: // public methods
+
+   MainMenu(SmartPointer<GenericDialog>& setScaleDialog,
+            SmartPointer<GenericDialog>& getInfoDialog,
+            SmartPointer<GenericDialog>& genFuncDialog);
+   ~MainMenu();
+   void CommandMeshVisitor(const std::string& commandString);
+   void CommandHelp(const std::string& commandString);
+   void CommandAbout(const std::string& commandString);
+
+private: // private methods
+
+   void AddMeshVisitorEntry(const char *commandLabel,
+                            const char *command,
+                            const SmartPointer<MeshVisitor>& visitor);
+
+private: // private types
+
+   /**
+    * Type for a map between a string and a reference-counted visitor.
+    */
+   typedef std::map<std::string, SmartPointer<MeshVisitor> > VisitorMap;
+
+private:
+
+   /**
+    * Associations between commands and visitors that implement them.
+    */
+   VisitorMap _visitorMap;
+
+   /**
+    * Callback for all of the commands that trigger CommandMeshVisitor. This is
+    * stored in a member var rather than a local var just because otherwise it
+    * would need to be passed around and clutter up an already ugly set of
+    * invocations.
+    */
+   const CommandCallbackMethod
+      <MainMenu, &MainMenu::CommandMeshVisitor> _commandMeshVisitor;
+};
+
+#endif // #if !defined(INCLUDED_MAINMENU_H)
\ No newline at end of file
diff --git a/contrib/meshtex/MeshEntity.cpp b/contrib/meshtex/MeshEntity.cpp
new file mode 100644 (file)
index 0000000..faac7ee
--- /dev/null
@@ -0,0 +1,1821 @@
+/**
+ * @file MeshEntity.cpp
+ * Implements the MeshEntity class.
+ * @ingroup meshtex-core
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <cmath>
+
+#include "MeshEntity.h"
+#include "MeshEntityMessages.h"
+
+#include "ishaders.h"
+#include "texturelib.h"
+
+
+/**
+ * Size of buffer for composing messages to send to the info callback.
+ */
+#define INFO_BUFFER_SIZE 1024
+
+/**
+ * Stop successive refinement of path length estimates when the change in values
+ * is equal to or less than this tolerance.
+ */
+#define UNITS_ERROR_BOUND 0.5
+
+/**
+ * Macro to get ROW_SLICE_TYPE from COL_SLICE_TYPE and vice versa, used in
+ * code that can operate on either kind of slice. This does mean that the
+ * numerical values assigned to ROW_SLICE_TYPE and COL_SLICE_TYPE are
+ * meaningful.
+ *
+ * @param sliceType Kind of slice to find the other of, so to speak.
+ */
+#define OtherSliceType(sliceType) (1 - (sliceType))
+
+/**
+ * Macro to find the number of control points in a slice, which is equal to
+ * the number of slices of the other kind.
+ *
+ * @param sliceType Kind of slice to measure.
+ */
+#define SliceSize(sliceType) (_numSlices[OtherSliceType(sliceType)])
+
+/**
+ * Macro to get rid of negative-zero values.
+ *
+ * @param floatnum Number to be sanitized.
+ */
+#define SanitizeFloat(floatnum) ((floatnum) == -0.0f ? 0.0f : (floatnum))
+
+
+/**
+ * For a given slice kind, which texture axis (S or T) normally changes
+ * along it.
+ */
+MeshEntity::TextureAxis MeshEntity::_naturalAxis[NUM_SLICE_TYPES] =
+   { S_TEX_AXIS, T_TEX_AXIS };
+
+/**
+ * For a given slice kind, whether Radiant's "natural" scale along a texture
+ * axis is backwards compared to the progression of the indices of the
+ * orthogonal slices.
+ */
+bool MeshEntity::_radiantScaleInverted[NUM_SLICE_TYPES] = { false, true };
+
+/**
+ * For a given slice kind, whether Radiant's interpretation of tiling along a
+ * texture axis is backwards compared to the progression of the indices of
+ * the orthogonal slices.
+ */
+bool MeshEntity::_radiantTilesInverted[NUM_SLICE_TYPES] = { false, false };
+
+/**
+  * Message format strings for describing texture mapping on a slice.
+  */
+const char *MeshEntity::_infoSliceFormatString[NUM_SLICE_TYPES] =
+   { INFO_ROW_FORMAT, INFO_COL_FORMAT };
+
+/**
+ * Message format strings for describing texture mapping on a slice in the
+ * unusual case where the scale value is infinite.
+ */
+const char *MeshEntity::_infoSliceInfscaleFormatString[NUM_SLICE_TYPES] =
+   { INFO_ROW_INFSCALE_FORMAT, INFO_COL_INFSCALE_FORMAT };
+
+/**
+ * Message format strings for warning that a scale value is infinite and
+ * cannot be transferred to the Set S/T Scale dialog.
+ */
+const char *MeshEntity::_warningSliceInfscaleFormatString[NUM_SLICE_TYPES] =
+   { WARNING_ROW_INFSCALE, WARNING_COL_INFSCALE };
+
+/**
+ * Message format strings for an illegal slice number error.
+ */
+const char *MeshEntity::_errorBadSliceString[NUM_SLICE_TYPES] =
+   { ERROR_BAD_ROW, ERROR_BAD_COL };
+
+/**
+ * Message format strings for a scale = 0 error.
+ */
+const char *MeshEntity::_errorSliceZeroscaleString[NUM_SLICE_TYPES] =
+   { ERROR_ROW_ZEROSCALE, ERROR_COL_ZEROSCALE };
+
+/**
+ * Message format strings for a tiles = 0 error.
+ */
+const char *MeshEntity::_errorSliceZerotilesString[NUM_SLICE_TYPES] =
+   { ERROR_ROW_ZEROTILES, ERROR_COL_ZEROTILES };
+
+
+/**
+ * Constructor. If the constructor is unable to process the input mesh, then
+ * the internal valid flag (queryable through IsValid) is set false, and the
+ * errorReportCallback is invoked.
+ *
+ * @param mesh                  The patch mesh to construct a wrapper for.
+ * @param infoReportCallback    Callback for future informational messages.
+ * @param warningReportCallback Callback for future warning messages.
+ * @param errorReportCallback   Callback for future error messages.
+ */
+MeshEntity::MeshEntity(scene::Node& mesh,
+                       const MessageCallback& infoReportCallback,
+                       const MessageCallback& warningReportCallback,
+                       const MessageCallback& errorReportCallback) :
+   _mesh(mesh),
+   _infoReportCallback(infoReportCallback),
+   _warningReportCallback(warningReportCallback),
+   _errorReportCallback(errorReportCallback)
+{
+   // Get a handle on the control points, for future manipulation.
+   _meshData = GlobalPatchCreator().Patch_getControlPoints(_mesh);
+   // Record some useful characteristics of the mesh.
+   _numSlices[ROW_SLICE_TYPE] = static_cast<int>(_meshData.x());
+   _numSlices[COL_SLICE_TYPE] = static_cast<int>(_meshData.y());
+   const char *shaderName = GlobalPatchCreator().Patch_getShader(_mesh);
+   IShader *shader = GlobalShaderSystem().getShaderForName(shaderName);
+   qtexture_t *texture = shader->getTexture();
+   if (texture != NULL)
+   {
+      _naturalTexUnits[S_TEX_AXIS] = texture->width / 2.0f;
+      _naturalTexUnits[T_TEX_AXIS] = texture->height / 2.0f;
+   }
+   // We don't need the shader for anything else now.
+   shader->DecRef();
+   // Check for valid mesh; bail if not.
+   if (_numSlices[ROW_SLICE_TYPE] < 3 ||
+       _numSlices[COL_SLICE_TYPE] < 3 ||
+       texture == NULL)
+   {
+      _valid = false;
+      _errorReportCallback(ERROR_BAD_MESH);
+      return;
+   }
+   _valid = true;
+   // Find the worldspace extents of the mesh now... they won't change during
+   // the lifetime of this object.
+   UpdatePosMinMax(X_POS_AXIS);
+   UpdatePosMinMax(Y_POS_AXIS);
+   UpdatePosMinMax(Z_POS_AXIS);
+   // We'll calculate the S/T extents lazily.
+   _texMinMaxDirty[S_TEX_AXIS] = true;
+   _texMinMaxDirty[T_TEX_AXIS] = true;
+}
+
+/**
+ * Destructor. Note that this only destroys the wrapper object, not the patch
+ * mesh itself.
+ */
+MeshEntity::~MeshEntity()
+{
+}
+
+/**
+ * Query if the patch mesh is valid, in the characteristics that this wrapper
+ * class cares about. If not valid then the results of operations on this
+ * wrapper object are undefined.
+ *
+ * @return true if valid, false if not.
+ */
+bool
+MeshEntity::IsValid() const
+{
+   return _valid;
+}
+
+/**
+ * Get information about the patch mesh.
+ * 
+ * A message string describing general mesh information (number of rows/cols,
+ * min/max texture coords, extent in worldspace) will be composed and sent to
+ * the infoReportCallback that was specified when this wrapper object was
+ * constructed.
+ * 
+ * Optionally this method can do additional reporting on a specific
+ * "reference row" and "reference column". If a reference row and/or column
+ * is specified, then information about the reference slice(s) will be added
+ * to the information message. If a reference row/col is specified AND a
+ * corresponding row/col TexInfoCallback is specified, then the scale and
+ * tiling values for the reference slice will also be passed to the relevant
+ * callback.
+ *
+ * @param refRow             Pointer to reference row number; NULL if none.
+ * @param refCol             Pointer to reference column number; NULL if none.
+ * @param rowTexInfoCallback Pointer to callback for reference row info; NULL
+ *                           if none.
+ * @param colTexInfoCallback Pointer to callback for reference column info; NULL
+ *                           if none.
+ */
+void
+MeshEntity::GetInfo(const int *refRow,
+                    const int *refCol,
+                    const TexInfoCallback *rowTexInfoCallback,
+                    const TexInfoCallback *colTexInfoCallback)
+{
+   // Prep a message buffer to compose the response.
+   char messageBuffer[INFO_BUFFER_SIZE + 1];
+   messageBuffer[INFO_BUFFER_SIZE] = 0;
+   size_t bufferOffset = 0;
+   // Get reference row info if requested; this will be written into the message
+   // buffer as well as sent to the row callback (if any).
+   if (refRow != NULL)
+   {
+      ReportSliceTexInfo(ROW_SLICE_TYPE, *refRow, _naturalAxis[ROW_SLICE_TYPE],
+                         messageBuffer + bufferOffset,
+                         INFO_BUFFER_SIZE - bufferOffset,
+                         rowTexInfoCallback);
+      // Move the message buffer pointer along.
+      bufferOffset = strlen(messageBuffer);
+   }
+   // Get reference column info if requested; this will be written into the
+   // message buffer as well as sent to the column callback (if any).
+   if (refCol != NULL)
+   {
+      ReportSliceTexInfo(COL_SLICE_TYPE, *refCol, _naturalAxis[COL_SLICE_TYPE],
+                         messageBuffer + bufferOffset,
+                         INFO_BUFFER_SIZE - bufferOffset,
+                         colTexInfoCallback);
+      // Move the message buffer pointer along.
+      bufferOffset = strlen(messageBuffer);
+   }
+   // Make sure we have up-to-date S/T extents.
+   UpdateTexMinMax(S_TEX_AXIS);
+   UpdateTexMinMax(T_TEX_AXIS);
+   // Add general mesh info to the message.
+   snprintf(messageBuffer + bufferOffset, INFO_BUFFER_SIZE - bufferOffset,
+            INFO_MESH_FORMAT,
+            _numSlices[ROW_SLICE_TYPE],
+            SanitizeFloat(_texMin[S_TEX_AXIS]), SanitizeFloat(_texMax[S_TEX_AXIS]),
+            _numSlices[COL_SLICE_TYPE],
+            SanitizeFloat(_texMin[T_TEX_AXIS]), SanitizeFloat(_texMax[T_TEX_AXIS]),
+            SanitizeFloat(_posMin[X_POS_AXIS]), SanitizeFloat(_posMax[X_POS_AXIS]),
+            SanitizeFloat(_posMin[Y_POS_AXIS]), SanitizeFloat(_posMax[Y_POS_AXIS]),
+            SanitizeFloat(_posMin[Z_POS_AXIS]), SanitizeFloat(_posMax[Z_POS_AXIS]));
+   // Send the response.
+   _infoReportCallback(messageBuffer);
+}
+
+/**
+ * For each of the specified texture axes, shift the lowest-valued texture
+ * coordinates off of the mesh until an integral texture coordinate (texture
+ * boundary) is on the mesh edge.
+ *
+ * @param axes The texture axes to align.
+ */
+void
+MeshEntity::MinAlign(TextureAxisSelection axes)
+{
+   // Implement this by applying MinAlignInt to each specified axis.
+   ProcessForAxes(&MeshEntity::MinAlignInt, axes);
+}
+
+/**
+ * For each of the specified texture axes, shift the highest-valued texture
+ * coordinates off of the mesh until an integral texture coordinate (texture
+ * boundary) is on the mesh edge.
+ *
+ * @param axes The texture axes to align.
+ */
+void
+MeshEntity::MaxAlign(TextureAxisSelection axes)
+{
+   // Implement this by applying MaxAlignInt to each specified axis.
+   ProcessForAxes(&MeshEntity::MaxAlignInt, axes);
+}
+
+/**
+ * For each of the specified texture axes, perform either MinMaxAlignStretch
+ * or MinMaxAlignShrink; the chosen operation will be the one with the least
+ * absolute change in the value of the texture scale.
+ *
+ * @param axes The texture axes to align.
+ */
+void
+MeshEntity::MinMaxAlignAutoScale(TextureAxisSelection axes)
+{
+   // Implement this by applying MinMaxAlignAutoScaleInt to each specified axis.
+   ProcessForAxes(&MeshEntity::MinMaxAlignAutoScaleInt, axes);
+}
+
+/**
+ * For each of the specified texture axes, align a texture boundary to one
+ * edge of the mesh, then increase the texture scale to align a texture
+ * boundary to the other edge of the mesh as well.
+ *
+ * @param axes The texture axes to align.
+ */
+void
+MeshEntity::MinMaxAlignStretch(TextureAxisSelection axes)
+{
+   // Implement this by applying MinMaxAlignStretchInt to each specified axis.
+   ProcessForAxes(&MeshEntity::MinMaxAlignStretchInt, axes);
+}
+
+/**
+ * For each of the specified texture axes, align a texture boundary to one
+ * edge of the mesh, then decrease the texture scale to align a texture
+ * boundary to the other edge of the mesh as well.
+ *
+ * @param axes The texture axes to align.
+ */
+void
+MeshEntity::MinMaxAlignShrink(TextureAxisSelection axes)
+{
+   // Implement this by applying MinMaxAlignShrinkInt to each specified axis.
+   ProcessForAxes(&MeshEntity::MinMaxAlignShrinkInt, axes);
+}
+
+/**
+ * Set the texture scaling along the rows or columns of the mesh. This
+ * affects only the texture axis that is naturally associated with rows (S)
+ * or columns (T) according to the chosen sliceType.
+ * 
+ * The scaling may be input either as a multiple of the natural scale that
+ * Radiant would choose for this texture, or as the number of tiles of the
+ * texture that should fit on the mesh's row/column.
+ * 
+ * Among the slices perpendicular to the direction of scaling, an alignment
+ * slice is used to fix the position of the texture boundary.
+ * 
+ * A reference slice may optionally be chosen among the slices parallel to
+ * the scaling direction. If a reference slice is not specified, then the
+ * texture coordinates are independently determined for each slice. If a
+ * reference slice is specified, its texture coordinates are calculated first
+ * and used to affect the other slices. The reference slice's amount of
+ * texture tiling will be re-used for all other slices; optionally, the
+ * texture coordinate at each control point within the reference slice can be
+ * copied to the corresponding control point in every other slice.
+ *
+ * @param sliceType           Choose to scale along rows or columns.
+ * @param alignSlice          Pointer to alignment slice description; if NULL,
+ *                            slice 0 is assumed.
+ * @param refSlice            Pointer to reference slice description,
+ *                            including how to use the reference; NULL if no
+ *                            reference.
+ * @param naturalScale        true if naturalScaleOrTiles is a factor of the
+ *                            Radiant natural scale; false if
+ *                            naturalScaleOrTiles is a number of tiles.
+ * @param naturalScaleOrTiles Scaling determinant, interpreted according to
+ *                            the naturalScale parameter.
+ */
+void
+MeshEntity::SetScale(SliceType sliceType,
+                     const SliceDesignation *alignSlice,
+                     const RefSliceDescriptor *refSlice,
+                     bool naturalScale,
+                     float naturalScaleOrTiles)
+{
+   // We're about to make changes!
+   CreateUndoPoint();
+
+   // Check for bad inputs. Also convert from natural scale to raw scale.
+   if (alignSlice != NULL && !alignSlice->maxSlice)
+   {
+      if (alignSlice->index < 0 ||
+          alignSlice->index >= (int)SliceSize(sliceType))
+      {
+         _errorReportCallback(_errorBadSliceString[OtherSliceType(sliceType)]);
+         return;
+      }
+   }
+   if (refSlice != NULL && !refSlice->designation.maxSlice)
+   {
+      if (refSlice->designation.index < 0 ||
+          refSlice->designation.index >= (int)_numSlices[sliceType])
+      {
+         _errorReportCallback(_errorBadSliceString[sliceType]);
+         return;
+      }
+   }
+   TextureAxis axis = _naturalAxis[sliceType];
+   float rawScaleOrTiles = naturalScaleOrTiles;
+   if (naturalScale)
+   {
+      // In this case, naturalScaleOrTiles (copied to rawScaleOrTiles) was a
+      // natural-scale factor.
+      if (rawScaleOrTiles == 0)
+      {
+         _errorReportCallback(_errorSliceZeroscaleString[sliceType]);
+         return;
+      }
+      // If Radiant's internal orientation is backwards, account for that.
+      if (_radiantScaleInverted[sliceType])
+      {
+         rawScaleOrTiles = -rawScaleOrTiles;
+      }
+      // Raw scale is the divisor necessary to get texture coordinate from
+      // worldspace distance, so we can derive that from the "natural" scale.
+      rawScaleOrTiles *= _naturalTexUnits[axis];
+   }
+   else
+   {
+      // In this case, naturalScaleOrTiles (copied to rawScaleOrTiles) was a
+      // tiling amount.
+      if (rawScaleOrTiles == 0)
+      {
+         // XXX We could try to make zero-tiles work ("infinite scale": all
+         // values for this axis are the same along the slice). Need to sort
+         // out divide-by-zero dangers down the road.
+         _errorReportCallback(_errorSliceZerotilesString[sliceType]);
+         return;
+      }
+      // If Radiant's internal orientation is backwards, account for that.
+      if (_radiantTilesInverted[sliceType])
+      {
+         rawScaleOrTiles = -rawScaleOrTiles;
+      }
+   }
+
+   // Make sure we have a definite number for the alignment slice, and for the
+   // reference slice if any.
+   int alignSliceInt =
+      InternalSliceDesignation(alignSlice, (SliceType)OtherSliceType(sliceType));
+   RefSliceDescriptorInt descriptor;
+   RefSliceDescriptorInt *refSliceInt = // will be NULL if no reference
+      InternalRefSliceDescriptor(refSlice, sliceType, descriptor);
+
+   // Generate the surface texture coordinates using the mesh control points
+   // and the designated scaling/tiling.
+   AllocatedMatrix<float> surfaceValues(_meshData.x(), _meshData.y());
+   GenScaledDistanceValues(sliceType, alignSliceInt, refSliceInt,
+                           naturalScale, rawScaleOrTiles,
+                           surfaceValues);
+
+   // Derive the control point values necessary to achieve those surface values.
+   GenControlTexFromSurface(axis, surfaceValues);
+
+   // Done!
+   CommitChanges();
+}
+
+/**
+ * Set the mesh's texture coordinates according to a linear combination of
+ * factors. This equation can be used to set the texture coordinates at the
+ * control points themselves, or to directly set the texture coordinates at
+ * the locations on the mesh surface that correspond to each half-patch
+ * interval.
+ * 
+ * An alignment row is used as the zero-point for any calculations of row
+ * number or of distance along a column surface when processing the equation.
+ * An alignment column is similarly used. (Note that the number identifying
+ * the alignment row/column is the real number used to index into the mesh;
+ * it's not the modified number as affected by the alignment column/row when
+ * processing the equation. We don't want to be stuck in a chicken-and-egg
+ * situation.)
+ * 
+ * Calculations of distance along row/col surface may optionally be affected
+ * by a designated reference row/col. The reference row/col can be used as a
+ * source of end-to-end distance only, in which case the proportional spacing
+ * of the control points within the affected row/col will determine the
+ * distance value to be used for each control point. Or, the distance value
+ * for every control point in the reference row/col can be copied to each
+ * corresponding control point in the other rows/cols.
+ *
+ * @param sFactors      Factors to determine the S texture coords; NULL if S
+ *                      axis unaffected.
+ * @param tFactors      Factors to determine the T texture coords; NULL if T
+ *                      axis unaffected.
+ * @param alignRow      Pointer to zero-point row; if NULL, row 0 is assumed.
+ * @param alignCol      Pointer to zero-point column; if NULL, column 0 is
+ *                      assumed.
+ * @param refRow        Pointer to reference row description, including how
+ *                      to use the reference; NULL if no reference.
+ * @param refCol        Pointer to reference column description, including
+ *                      how to use the reference; NULL if no reference.
+ * @param surfaceValues true if calculations are for S/T values on the mesh
+ *                      surface; false if calculations are for S/T values at
+ *                      the control points.
+ */
+void
+MeshEntity::GeneralFunction(const GeneralFunctionFactors *sFactors,
+                            const GeneralFunctionFactors *tFactors,
+                            const SliceDesignation *alignRow,
+                            const SliceDesignation *alignCol,
+                            const RefSliceDescriptor *refRow,
+                            const RefSliceDescriptor *refCol,
+                            bool surfaceValues)
+{
+   // We're about to make changes!
+   CreateUndoPoint();
+
+   // Make sure we have a definite number for the alignment slices, and for the
+   // reference slices if any.
+   int alignRowInt = InternalSliceDesignation(alignRow, ROW_SLICE_TYPE);
+   int alignColInt = InternalSliceDesignation(alignRow, COL_SLICE_TYPE);
+   RefSliceDescriptorInt rowDescriptor;
+   RefSliceDescriptorInt *refRowInt = // will be NULL if no row reference
+      InternalRefSliceDescriptor(refRow, ROW_SLICE_TYPE, rowDescriptor);
+   RefSliceDescriptorInt colDescriptor;
+   RefSliceDescriptorInt *refColInt = // will be NULL if no column reference
+      InternalRefSliceDescriptor(refCol, COL_SLICE_TYPE, colDescriptor);
+
+   // Get the surface row/col distance values at each half-patch interval, if
+   // the input factors care about distances.
+   AllocatedMatrix<float> rowDistances(_meshData.x(), _meshData.y());
+   AllocatedMatrix<float> colDistances(_meshData.x(), _meshData.y());
+   if ((sFactors != NULL && sFactors->rowDistance != 0.0f) ||
+       (tFactors != NULL && tFactors->rowDistance != 0.0f))
+   {
+      GenScaledDistanceValues(ROW_SLICE_TYPE,
+                              alignColInt,
+                              refRowInt,
+                              true,
+                              1.0f,
+                              rowDistances);
+   }
+   if ((sFactors != NULL && sFactors->colDistance != 0.0f) ||
+       (tFactors != NULL && tFactors->colDistance != 0.0f))
+   {
+      GenScaledDistanceValues(COL_SLICE_TYPE,
+                              alignRowInt,
+                              refColInt,
+                              true,
+                              1.0f,
+                              colDistances);
+   }
+
+   // Modify the S axis if requested.
+   if (sFactors != NULL)
+   {
+      GeneralFunctionInt(*sFactors, S_TEX_AXIS, alignRowInt, alignColInt, surfaceValues,
+                         rowDistances, colDistances);
+   }
+
+   // Modify the T axis if requested.
+   if (tFactors != NULL)
+   {
+      GeneralFunctionInt(*tFactors, T_TEX_AXIS, alignRowInt, alignColInt, surfaceValues,
+                         rowDistances, colDistances);
+   }
+
+   // Done!
+   CommitChanges();
+}
+
+/**
+ * Update the internally stored information for the min and max extent of the
+ * mesh on the specified worldspace axis.
+ *
+ * @param axis The worldspace axis.
+ */
+void
+MeshEntity::UpdatePosMinMax(PositionAxis axis)
+{
+   // Iterate over all control points to find the min and max values.
+   _posMin[axis] = _meshData(0, 0).m_vertex[axis];
+   _posMax[axis] = _posMin[axis];
+   for (unsigned rowIndex = 0; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex++)
+   {
+      for (unsigned colIndex = 0; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex++)
+      {
+         float current = _meshData(rowIndex, colIndex).m_vertex[axis];
+         if (current < _posMin[axis])
+         {
+            _posMin[axis] = current;
+         }
+         if (current > _posMax[axis])
+         {
+            _posMax[axis] = current;
+         }
+      }
+   }
+}
+
+/**
+ * Update the internally stored information for the min and max extent of the
+ * mesh on the specified texture axis.
+ *
+ * @param axis The texture axis.
+ */
+void
+MeshEntity::UpdateTexMinMax(TextureAxis axis)
+{
+   // Bail out if no operations have possibly changed these values.
+   if (!_texMinMaxDirty[axis])
+   {
+      return;
+   }
+
+   // Iterate over all control points to find the min and max values.
+   _texMin[axis] = _meshData(0, 0).m_texcoord[axis];
+   _texMax[axis] = _texMin[axis];
+   for (unsigned rowIndex = 0; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex++)
+   {
+      for (unsigned colIndex = 0; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex++)
+      {
+         float current = _meshData(rowIndex, colIndex).m_texcoord[axis];
+         if (current < _texMin[axis])
+         {
+            _texMin[axis] = current;
+         }
+         if (current > _texMax[axis])
+         {
+            _texMax[axis] = current;
+         }
+      }
+   }
+
+   // See if the min and max are on texture boundaries.
+   _texMinAligned[axis] = (floorf(_texMin[axis]) == _texMin[axis]);
+   _texMaxAligned[axis] = (floorf(_texMax[axis]) == _texMax[axis]);
+
+   // Values are good until next relevant operation.
+   _texMinMaxDirty[axis] = false;
+}
+
+/**
+ * Interface to the Radiant undo buffer; save the current state of the mesh
+ * to allow rollback to this point by an undo operation.
+ */
+void
+MeshEntity::CreateUndoPoint()
+{
+   GlobalPatchCreator().Patch_undoSave(_mesh);
+}
+
+/**
+ * Commit the changes to the mesh so that they will be reflected in Radiant.
+ */
+void
+MeshEntity::CommitChanges()
+{
+   GlobalPatchCreator().Patch_controlPointsChanged(_mesh);
+   // Radiant undo-buffer behavior requires this:
+   CreateUndoPoint();
+}
+
+/**
+ * Convert from SliceDesignation to a slice number. Interpret max slice if
+ * necessary, and fall back to slice 0 if unspecified.
+ *
+ * @param sliceDesignation Pointer to slice description; may be NULL.
+ * @param sliceType        Slice kind (row or column).
+ *
+ * @return The slice number.
+ */
+int
+MeshEntity::InternalSliceDesignation(const SliceDesignation *sliceDesignation,
+                                     SliceType sliceType)
+{
+   if (sliceDesignation != NULL)
+   {
+      // Interpret "max slice" if necessary.
+      if (sliceDesignation->maxSlice)
+      {
+         return _numSlices[sliceType] - 1;
+      }
+      else
+      {
+         return sliceDesignation->index;
+      }
+   }
+   else
+   {
+      // 0 if unspecified.
+      return 0;
+   }
+}
+
+/**
+ * Convert from RefSliceDescriptor to RefSliceDescriptorInt. Interpret max
+ * slice if necessary. Populate specified RefSliceDescriptorInt if input is
+ * non-NULL and return pointer to it; otherwise return NULL.
+ *
+ * @param refSlice          Pointer to reference slice description; may be
+ *                          NULL.
+ * @param sliceType         Slice kind (row or column).
+ * @param [out] refSliceInt RefSliceDescriptorInt to populate.
+ *
+ * @return NULL if input RefSliceDescriptor is NULL; else, pointer to populated
+ *         RefSliceDescriptorInt.
+ */
+MeshEntity::RefSliceDescriptorInt *
+MeshEntity::InternalRefSliceDescriptor(const RefSliceDescriptor *refSlice,
+                                       SliceType sliceType,
+                                       RefSliceDescriptorInt& refSliceInt)
+{
+   if (refSlice != NULL)
+   {
+      // Preserve totalLengthOnly.
+      refSliceInt.totalLengthOnly = refSlice->totalLengthOnly;
+      // Convert slice designator to a slice number.
+      refSliceInt.index = InternalSliceDesignation(&(refSlice->designation), sliceType);
+      return &refSliceInt;
+   }
+   else
+   {
+      // NULL if unspecified.
+      return NULL;
+   }
+}
+
+/**
+ * Given a slice and a number of times that its texture should tile along it,
+ * find the appropriate texture scale. This result is determined by the
+ * surface length along the slice.
+ *
+ * @param sliceType Slice kind (row or column).
+ * @param slice     Slice number, among slices of that kind in mesh.
+ * @param axis      The texture axis of interest.
+ * @param tiles     Number of times the texture tiles.
+ *
+ * @return The texture scale corresponding to the tiling amount.
+ */
+float
+MeshEntity::GetSliceTexScale(SliceType sliceType,
+                             int slice,
+                             TextureAxis axis,
+                             float tiles)
+{
+   // XXX Similar to GenScaledDistanceValues; refactor for shared code?
+
+   // We're going to be walking patches along the mesh, choosing the patches
+   // that surround/affect the slice we are interested in. We'll calculate
+   // the length of the slice's surface across each patch & add those up.
+   
+   // A SlicePatchContext will contain all the necessary information to
+   // evaluate our slice's surface length within each patch. Some aspects of
+   // the SlicePatchContext will vary as we move from patch to patch, but we
+   // can go ahead and calculate now any stuff that is invariant along the
+   // slice's direction.
+   SlicePatchContext context;
+   context.sliceType = sliceType;
+   if (slice != 0)
+   {
+      // This is the position of the slice within each patch, in the direction
+      // orthogonal to the slice. Even-numbered slices are at the edge of the
+      // patch (position 1.0), while odd-numbered slices are in the middle
+      // (position 0.5).
+      context.position = 1.0f - ((float)(slice & 0x1) / 2.0f);
+   }
+   else
+   {
+      // For the first slice, we can't give it the usual treatment for even-
+      // numbered slices (since there is no patch "before" it), so it gets
+      // position 0.0 instead.
+      context.position = 0.0f;
+   }
+   // This is the slice of the same kind that defines the 0.0 edge of the
+   // patch. It will be the next lowest even-numbered slice. (Note the
+   // integer division here.)
+   context.edgeSlice[sliceType] = 2 * ((slice - 1) / 2);
+
+   // Now it's time to walk the patches.
+
+   // We start off with no cumulative distance yet.
+   float cumulativeDistance = 0.0f;
+
+   // By iterating over the number of control points in this slice by
+   // increments of 2, we'll be walking the slice in patch-sized steps. Since
+   // we are only interested in the total length, we don't need to check in at
+   // finer granularities.
+   for (unsigned halfPatch = 2; halfPatch < SliceSize(sliceType); halfPatch += 2)
+   {
+      // Find the slice-of-other-kind that defines the patch edge orthogonal
+      // to our slice.
+      context.edgeSlice[OtherSliceType(sliceType)] = 2 * ((halfPatch - 1) / 2);
+      // Estimate the slice length along the surface of the patch.
+      float segmentLengthEstimate = EstimateSegmentLength(0.0f, 1.0f, context);
+      // Recursively refine that estimate until it is good enough, then add it
+      // to our cumulative distance.
+      cumulativeDistance += RefineSegmentLength(0.0f, 1.0f, context,
+                                                segmentLengthEstimate,
+                                                UNITS_ERROR_BOUND);
+   }
+
+   // The scale along this slice is defined as the surface length divided by
+   // the "natural" number of texture units along that length.
+   return cumulativeDistance / (tiles * _naturalTexUnits[axis]);
+}
+
+/**
+ * Populate the SliceTexInfo for the indicated slice and texture axis.
+ *
+ * @param sliceType  Slice kind (row or column).
+ * @param slice      Slice number, among slices of that kind in mesh.
+ * @param axis       The texture axis of interest.
+ * @param [out] info Information on scale, tiles, and min/max for the
+ *                   specified texture axis.
+ *
+ * @return true on success, false if slice cannot be processed.
+ */
+bool
+MeshEntity::GetSliceTexInfo(SliceType sliceType,
+                            int slice,
+                            TextureAxis axis,
+                            SliceTexInfo& info)
+{
+   // Bail out now if slice # is bad.
+   if (slice < 0 ||
+       slice >= (int)_numSlices[sliceType])
+   {
+      _errorReportCallback(_errorBadSliceString[sliceType]);
+      return false;
+   }
+
+   // Calculate the # of times the texture is tiled along the specified axis
+   // on this slice, and find the min and max values for that axis.
+   float texBegin =
+      MatrixElement(_meshData, sliceType, slice, 0).m_texcoord[axis];
+   float texEnd =
+      MatrixElement(_meshData, sliceType, slice, SliceSize(sliceType) - 1).m_texcoord[axis];
+   info.tiles = texEnd - texBegin;
+   if (texBegin < texEnd)
+   {
+      info.min = texBegin;
+      info.max = texEnd;
+   }
+   else
+   {
+      info.min = texEnd;
+      info.max = texBegin;
+   }
+
+   // Calculate the texture scale along this slice, using the tiling info
+   // along with the texture size and the length of the slice's surface.
+   info.scale = GetSliceTexScale(sliceType, slice, axis, info.tiles);
+   return true;
+}
+
+/**
+ * Take the information from GetSliceTexInfo and sanitize it for reporting.
+ * Optionally print to a provided message buffer and/or supply data to a
+ * provided TexInfoCallback.
+ *
+ * @param sliceType         Slice kind (row or column).
+ * @param slice             Slice number, among slices of that kind in mesh.
+ * @param axis              The texture axis of interest.
+ * @param messageBuffer     Buffer for message data; NULL if none.
+ * @param messageBufferSize Size of the message buffer.
+ * @param texInfoCallback   Callback for passing texture scale/tiles info;
+ *                          NULL if none.
+ */
+void
+MeshEntity::ReportSliceTexInfo(SliceType sliceType,
+                               int slice,
+                               TextureAxis axis,
+                               char *messageBuffer,
+                               unsigned messageBufferSize,
+                               const TexInfoCallback *texInfoCallback)
+{
+   // Fetch the raw info.
+   SliceTexInfo info;
+   if (!GetSliceTexInfo(sliceType, slice, axis, info))
+   {
+      return;
+   }
+
+   // Account for Radiant-inverted.
+   if (_radiantScaleInverted[sliceType])
+   {
+      info.scale = -info.scale;
+   }
+   if (_radiantTilesInverted[sliceType])
+   {
+      info.tiles = -info.tiles;
+   }
+
+   // Send texture info to callback if one is provided.
+   bool infscale = (info.scale > FLT_MAX || info.scale < -FLT_MAX);
+   if (texInfoCallback != NULL)
+   {
+      if (!infscale)
+      {
+         (*texInfoCallback)(SanitizeFloat(info.scale), SanitizeFloat(info.tiles));
+      }
+      else
+      {
+         // "Infinite scale" prevents us from invoking the callback, so
+         // raise a warning about that.
+         _warningReportCallback(_warningSliceInfscaleFormatString[sliceType]);
+      }
+   }
+
+   // Write texture info to buffer if one is provided.
+   if (messageBuffer != NULL)
+   {
+      if (!infscale)
+      {
+         snprintf(messageBuffer, messageBufferSize,
+            _infoSliceFormatString[sliceType], slice,
+            SanitizeFloat(info.scale), SanitizeFloat(info.tiles),
+            SanitizeFloat(info.min), SanitizeFloat(info.max));
+      }
+      else
+      {
+         // Special handling for "infinite scale".
+         snprintf(messageBuffer, messageBufferSize,
+            _infoSliceInfscaleFormatString[sliceType], slice,
+            SanitizeFloat(info.tiles),
+            SanitizeFloat(info.min), SanitizeFloat(info.max));
+      }
+   }
+}
+
+/**
+ * Apply some function with the InternalImpl signature to each of the
+ * designated texture axes. The undo point and state commit operations
+ * are handled here for such functions.
+ *
+ * @param internalImpl The function to apply.
+ * @param axes         The texture axes to affect.
+ */
+void
+MeshEntity::ProcessForAxes(InternalImpl internalImpl,
+                           TextureAxisSelection axes)
+{
+   // We're about to make changes!
+   CreateUndoPoint();
+
+   // Apply the function to the requested axes.
+   bool sChanged = false;
+   bool tChanged = false;
+   if (axes != T_TEX_AXIS_ONLY)
+   {
+      sChanged = (*this.*internalImpl)(S_TEX_AXIS);
+   }
+   if (axes != S_TEX_AXIS_ONLY)
+   {
+      tChanged = (*this.*internalImpl)(T_TEX_AXIS);
+   }
+
+   // Done! Commit changes if necessary.
+   if (sChanged || tChanged)
+   {
+      CommitChanges();
+   }
+}
+
+/**
+ * Add an offset to all control point values for the given texture axis.
+ *
+ * @param axis  The texture axis to affect.
+ * @param shift The offset to add.
+ */
+void
+MeshEntity::Shift(TextureAxis axis,
+                  float shift)
+{
+   // Iterate over all control points and add the offset.
+   for (unsigned rowIndex = 0; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex++)
+   {
+      for (unsigned colIndex = 0; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex++)
+      {
+         _meshData(rowIndex, colIndex).m_texcoord[axis] += shift;
+      }
+   }
+
+   // This operation might have changed texture min/max.
+   _texMinMaxDirty[axis] = true;
+}
+
+/**
+ * On the given texture axis, find the distance of all control point values
+ * from the current minimum value and multiply that distance by the given
+ * scale factor.
+ *
+ * @param axis  The texture axis to affect.
+ * @param scale The scale factor.
+ */
+void
+MeshEntity::Scale(TextureAxis axis,
+                  float scale)
+{
+   // Make sure the min value is updated; we'll need it below.
+   UpdateTexMinMax(axis);
+
+   // Iterate over all control points and apply the scale factor.
+   for (unsigned rowIndex = 0; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex++)
+   {
+      for (unsigned colIndex = 0; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex++)
+      {
+         // Leave the current minimum edge in place and scale out from that.
+         _meshData(rowIndex, colIndex).m_texcoord[axis] =
+            ((_meshData(rowIndex, colIndex).m_texcoord[axis] - _texMin[axis]) * scale) +
+            _texMin[axis];
+      }
+   }
+
+   // This operation might have changed texture min/max.
+   _texMinMaxDirty[axis] = true;
+}
+
+/**
+ * Implementation of MinAlign for a single texture axis.
+ *
+ * @param axis The texture axis to affect.
+ *
+ * @return true if the mesh was changed, false if not.
+ */
+bool
+MeshEntity::MinAlignInt(TextureAxis axis)
+{
+   // Make sure the min-aligned value is updated.
+   UpdateTexMinMax(axis);
+
+   // If already aligned, we're done.
+   if (_texMinAligned[axis])
+   {
+      // Didn't make changes.
+      return false;
+   }
+
+   // Otherwise shift by the necessary amount to align.
+   Shift(axis, ceilf(_texMin[axis]) - _texMin[axis]);
+
+   // Made changes.
+   return true;
+}
+
+/**
+ * Implementation of MaxAlign for a single texture axis.
+ *
+ * @param axis The texture axis to affect.
+ *
+ * @return true if the mesh was changed, false if not.
+ */
+bool
+MeshEntity::MaxAlignInt(TextureAxis axis)
+{
+   // Make sure the max-aligned value is updated.
+   UpdateTexMinMax(axis);
+
+   // If already aligned, we're done.
+   if (_texMaxAligned[axis])
+   {
+      // Didn't make changes.
+      return false;
+   }
+
+   // Otherwise shift by the necessary amount to align.
+   Shift(axis, ceilf(_texMax[axis]) - _texMax[axis]);
+
+   // Made changes.
+   return true;
+}
+
+/**
+ * Implementation of MinMaxAlignAutoScale for a single texture axis.
+ *
+ * @param axis The texture axis to affect.
+ *
+ * @return true if the mesh was changed, false if not.
+ */
+bool
+MeshEntity::MinMaxAlignAutoScaleInt(TextureAxis axis)
+{
+   // Make sure the max value is updated.
+   UpdateTexMinMax(axis);
+
+   // Choose to stretch or shrink, based on which will cause less change.
+   if ((_texMax[axis] - floorf(_texMax[axis])) < 0.5)
+   {
+      return MinMaxAlignStretchInt(axis);
+   }
+   else
+   {
+      return MinMaxAlignShrinkInt(axis);
+   }
+}
+
+/**
+ * The meat of MinMaxAlignStretchInt and MinMaxAlignShrinkInt.
+ *
+ * @param axis The texture axis to affect.
+ * @param op   Whether to stretch or shrink.
+ *
+ * @return true if the mesh was changed, false if not.
+ */
+bool
+MeshEntity::MinMaxAlignScale(TextureAxis axis,
+                             ScaleOperation op)
+{
+   // First make sure we are min-aligned.
+   bool changed = MinAlignInt(axis);
+
+   // Make sure the min/max values are updated.
+   UpdateTexMinMax(axis);
+
+   // More work to do if not max-aligned.
+   if (!_texMaxAligned[axis])
+   {
+      // Find the current tiling.
+      float oldRepeats = _texMax[axis] - _texMin[axis];
+      // Find the desired tiling, depending on whether we are stretching or
+      // shrinking.
+      float newRepeats;
+      if (op == STRETCH_SCALE_OP)
+      {
+         newRepeats = floorf(_texMax[axis]) - _texMin[axis];
+      }
+      else
+      {
+         newRepeats = ceilf(_texMax[axis]) - _texMin[axis];
+      }
+      // Apply the necessary scaling to get the desired tiling.
+      Scale(axis, newRepeats / oldRepeats);
+      // Made changes.
+      changed = true;
+   }
+
+   return changed;
+}
+
+/**
+ * Implementation of MinMaxAlignStretch for a single texture axis.
+ *
+ * @param axis The texture axis to affect.
+ *
+ * @return true if the mesh was changed, false if not.
+ */
+bool
+MeshEntity::MinMaxAlignStretchInt(TextureAxis axis)
+{
+   // Hand off to MinMaxAlignScale.
+   return MinMaxAlignScale(axis, STRETCH_SCALE_OP);
+}
+
+/**
+ * Implementation of MinMaxAlignShrink for a single texture axis.
+ *
+ * @param axis The texture axis to affect.
+ *
+ * @return true if the mesh was changed, false if not.
+ */
+bool
+MeshEntity::MinMaxAlignShrinkInt(TextureAxis axis)
+{
+   // Hand off to MinMaxAlignScale.
+   return MinMaxAlignScale(axis, SHRINK_SCALE_OP);
+}
+
+/**
+ * Calculate the d(x, y, or z)/dt of a patch slice, evaluated at a given t
+ * (parameter for the Bezier function, between 0 and 1).
+ *
+ * @param axis    The worldspace axis of interest.
+ * @param t       Bezier parameter.
+ * @param context The slice and patch.
+ *
+ * @return d(x, y, or z)/dt at the given t.
+ */
+float
+MeshEntity::SliceParametricSpeedComponent(PositionAxis axis,
+                                          float t,
+                                          const SlicePatchContext& context)
+{
+   float a = 1.0f - context.position;
+   float b = 2.0f * context.position * a;
+   a *= a;
+   float c = context.position * context.position;
+   float d = 2.0f * t - 2.0f;
+   float e = 2.0f - 4.0f * t;
+   float f = 2.0f * t;
+   int patchStartCol = context.edgeSlice[COL_SLICE_TYPE];
+   int patchStartRow = context.edgeSlice[ROW_SLICE_TYPE];
+
+   if (context.sliceType == ROW_SLICE_TYPE)
+   {
+      return
+         _meshData(patchStartRow+0, patchStartCol+0).m_vertex[axis] * a * d +
+         _meshData(patchStartRow+0, patchStartCol+1).m_vertex[axis] * a * e +
+         _meshData(patchStartRow+0, patchStartCol+2).m_vertex[axis] * a * f +
+         _meshData(patchStartRow+1, patchStartCol+0).m_vertex[axis] * b * d +
+         _meshData(patchStartRow+1, patchStartCol+1).m_vertex[axis] * b * e +
+         _meshData(patchStartRow+1, patchStartCol+2).m_vertex[axis] * b * f +
+         _meshData(patchStartRow+2, patchStartCol+0).m_vertex[axis] * c * d +
+         _meshData(patchStartRow+2, patchStartCol+1).m_vertex[axis] * c * e +
+         _meshData(patchStartRow+2, patchStartCol+2).m_vertex[axis] * c * f;
+   }
+   else
+   {
+      return
+         _meshData(patchStartRow+0, patchStartCol+0).m_vertex[axis] * a * d +
+         _meshData(patchStartRow+1, patchStartCol+0).m_vertex[axis] * a * e +
+         _meshData(patchStartRow+2, patchStartCol+0).m_vertex[axis] * a * f +
+         _meshData(patchStartRow+0, patchStartCol+1).m_vertex[axis] * b * d +
+         _meshData(patchStartRow+1, patchStartCol+1).m_vertex[axis] * b * e +
+         _meshData(patchStartRow+2, patchStartCol+1).m_vertex[axis] * b * f +
+         _meshData(patchStartRow+0, patchStartCol+2).m_vertex[axis] * c * d +
+         _meshData(patchStartRow+1, patchStartCol+2).m_vertex[axis] * c * e +
+         _meshData(patchStartRow+2, patchStartCol+2).m_vertex[axis] * c * f;
+   }
+}
+
+/**
+ * Calculates the rate of change in worldspace units of a patch slice,
+ * evaluated at a given t (parameter for the Bezier function, between 0 and
+ * 1).
+ *
+ * @param t       Bezier parameter.
+ * @param context The slice and patch.
+ *
+ * @return Path length.
+ */
+float
+MeshEntity::SliceParametricSpeed(float t,
+                                 const SlicePatchContext& context)
+{
+   float xDotEval = SliceParametricSpeedComponent(X_POS_AXIS, t, context);
+   float yDotEval = SliceParametricSpeedComponent(Y_POS_AXIS, t, context);
+   float zDotEval = SliceParametricSpeedComponent(Z_POS_AXIS, t, context);
+   return sqrtf(xDotEval*xDotEval + yDotEval*yDotEval + zDotEval*zDotEval);
+}
+
+/**
+ * Estimate the surface length of a slice segment, using ten point
+ * Gauss-Legendre integration of the parametric speed function. The value
+ * returned will always be positive (absolute value).
+ *
+ * @param startPosition Bezier parameter value for the start point of the
+ *                      slice segment.
+ * @param endPosition   Bezier parameter value for the end point of the slice
+ *                      segment.
+ * @param context       The slice and patch.
+ *
+ * @return Estimate of segment length.
+ */
+float
+MeshEntity::EstimateSegmentLength(float startPosition,
+                                  float endPosition,
+                                  const SlicePatchContext& context)
+{
+   // Gauss-Legendre implementation taken from "Numerical Recipes in C".
+
+   static float x[] = {0.0f, 0.1488743389f, 0.4333953941f, 0.6794095682f, 0.8650633666f, 0.9739065285f};
+   static float w[] = {0.0f, 0.2955242247f, 0.2692667193f, 0.2190863625f, 0.1494513491f, 0.0666713443f};
+
+   float xm = 0.5f * (endPosition + startPosition);
+   float xr = 0.5f * (endPosition - startPosition);
+   float s = 0.0f;
+
+   for (unsigned j = 1; j <= 5; j++) {
+      float dx = xr * x[j];
+      s += w[j] * (SliceParametricSpeed(xm + dx, context) +
+                   SliceParametricSpeed(xm - dx, context));
+   }
+
+   return fabsf(s * xr);
+}
+
+/**
+ * Recursively improve the estimate of the surface length of a slice segment,
+ * by estimating the length of its halves, until the change between estimates
+ * is equal to or less than an acceptable error threshold.
+ *
+ * @param startPosition         Bezier parameter value for the start point of
+ *                              the slice segment.
+ * @param endPosition           Bezier parameter value for the end point of the
+ *                              slice segment.
+ * @param context               The slice and patch.
+ * @param segmentLengthEstimate Starting estimate for segment legnth.
+ * @param maxError              Max acceptable variance between estimates.
+ *
+ * @return Improved estimate of segment length.
+ */
+float
+MeshEntity::RefineSegmentLength(float startPosition,
+                                float endPosition,
+                                const SlicePatchContext& context,
+                                float segmentLengthEstimate,
+                                float maxError)
+{
+   // Estimate the lengths of the two halves of this segment.
+   float midPosition = (startPosition + endPosition) / 2.0f;
+   float leftLength = EstimateSegmentLength(startPosition, midPosition, context);
+   float rightLength = EstimateSegmentLength(midPosition, endPosition, context);
+
+   // If the sum of the half-segment estimates is too far off from the
+   // whole-segment estimate, then we're in a regime with too much error in
+   // the estimates. Recurse to refine the half-segment estimates.
+   if (fabsf(segmentLengthEstimate - (leftLength + rightLength)) > maxError)
+   {
+      leftLength = RefineSegmentLength(startPosition, midPosition,
+                                       context,
+                                       leftLength,
+                                       maxError / 2.0f);
+      rightLength = RefineSegmentLength(midPosition, endPosition,
+                                        context,
+                                        rightLength,
+                                        maxError / 2.0f);
+   }
+
+   // Return the sum of the (refined) half-segment estimates.
+   return (leftLength + rightLength);
+}
+
+/**
+ * Derive control point texture coordinates (on a given texture axis) from a
+ * set of mesh surface texture coordinates.
+ *
+ * @param axis          The texture axis of interest.
+ * @param surfaceValues The surface texture coordinates.
+ */
+void
+MeshEntity::GenControlTexFromSurface(TextureAxis axis,
+                                     const Matrix<float>& surfaceValues)
+{
+   // The control points on even rows & even columns (i.e. patch corners)
+   // have texture coordinates that match the surface values.
+   for (unsigned rowIndex = 0; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex += 2)
+   {
+      for (unsigned colIndex = 0; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex += 2)
+      {
+         _meshData(rowIndex, colIndex).m_texcoord[axis] =
+            surfaceValues(rowIndex, colIndex);
+      }
+   }
+
+   // Set the control points on odd rows & even columns (i.e. the centers of
+   // columns that are patch edges).
+   for (unsigned rowIndex = 1; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex += 2)
+   {
+      for (unsigned colIndex = 0; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex += 2)
+      {
+         _meshData(rowIndex, colIndex).m_texcoord[axis] =
+            2.0f * surfaceValues(rowIndex, colIndex) -
+            (surfaceValues(rowIndex - 1, colIndex) +
+             surfaceValues(rowIndex + 1, colIndex)) / 2.0f;
+      }
+   }
+
+   // Set the control points on even rows & odd columns (i.e. the centers of
+   // rows that are patch edges).
+   for (unsigned rowIndex = 0; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex += 2)
+   {
+      for (unsigned colIndex = 1; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex += 2)
+      {
+         _meshData(rowIndex, colIndex).m_texcoord[axis] =
+            2.0f * surfaceValues(rowIndex, colIndex) -
+            (surfaceValues(rowIndex, colIndex - 1) +
+             surfaceValues(rowIndex, colIndex + 1)) / 2.0f;
+      }
+   }
+
+   // And finally on odd rows & odd columns (i.e. patch centers).
+   for (unsigned rowIndex = 1; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex += 2)
+   {
+      for (unsigned colIndex = 1; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex += 2)
+      {
+         _meshData(rowIndex, colIndex).m_texcoord[axis] =
+            4.0f * surfaceValues(rowIndex, colIndex) -
+            (surfaceValues(rowIndex, colIndex - 1) +
+             surfaceValues(rowIndex, colIndex + 1) +
+             surfaceValues(rowIndex - 1, colIndex) +
+             surfaceValues(rowIndex + 1, colIndex)) / 2.0f -
+            (surfaceValues(rowIndex - 1, colIndex - 1) +
+             surfaceValues(rowIndex + 1, colIndex + 1) +
+             surfaceValues(rowIndex - 1, colIndex + 1) +
+             surfaceValues(rowIndex + 1, colIndex - 1)) / 4.0f;
+      }
+   }
+
+   // This operation might have changed texture min/max.
+   _texMinMaxDirty[axis] = true;
+}
+
+/**
+ * Overwrite control point texture coordinates (on a given texture axis) with
+ * the input texture coordinates.
+ *
+ * @param axis   The texture axis of interest.
+ * @param values The input texture coordinates.
+ */
+void
+MeshEntity::CopyControlTexFromValues(TextureAxis axis,
+                                     const Matrix<float>& values)
+{
+   // Iterate over all control points and just do a straight copy.
+   for (unsigned rowIndex = 0; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex++)
+   {
+      for (unsigned colIndex = 0; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex++)
+      {
+         _meshData(rowIndex, colIndex).m_texcoord[axis] =
+            values(rowIndex, colIndex);
+      }
+   }
+
+   // This operation might have changed texture min/max.
+   _texMinMaxDirty[axis] = true;
+}
+
+/**
+ * Derive a set of surface texture coordinates (on a given texture axis) from
+ * the control point texture coordinates.
+ *
+ * @param axis                The texture axis of interest.
+ * @param [out] surfaceValues The surface texture coordinates.
+ */
+void
+MeshEntity::GenSurfaceFromControlTex(TextureAxis axis,
+                                     Matrix<float>& surfaceValues)
+{
+   // The surface values on even rows & even columns (i.e. patch corners)
+   // have texture coordinates that match the control points.
+   for (unsigned rowIndex = 0; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex += 2)
+   {
+      for (unsigned colIndex = 0; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex += 2)
+      {
+         surfaceValues(rowIndex, colIndex) =
+            _meshData(rowIndex, colIndex).m_texcoord[axis];
+      }
+   }
+
+   // Set the surface values on odd rows & odd columns (i.e. patch centers).
+   for (unsigned rowIndex = 1; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex += 2)
+   {
+      for (unsigned colIndex = 1; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex += 2)
+      {
+         surfaceValues(rowIndex, colIndex) =
+            _meshData(rowIndex, colIndex).m_texcoord[axis] / 4.0f +
+            (_meshData(rowIndex, colIndex - 1).m_texcoord[axis] +
+             _meshData(rowIndex, colIndex + 1).m_texcoord[axis] +
+             _meshData(rowIndex - 1, colIndex).m_texcoord[axis] +
+             _meshData(rowIndex + 1, colIndex).m_texcoord[axis]) / 8.0f +
+            (_meshData(rowIndex - 1, colIndex - 1).m_texcoord[axis] +
+             _meshData(rowIndex + 1, colIndex + 1).m_texcoord[axis] +
+             _meshData(rowIndex - 1, colIndex + 1).m_texcoord[axis] +
+             _meshData(rowIndex + 1, colIndex - 1).m_texcoord[axis]) / 16.0f;
+      }
+   }
+
+   // Set the surface values on even rows & odd columns (i.e. the centers of
+   // rows that are patch edges).
+   for (unsigned rowIndex = 0; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex += 2)
+   {
+      for (unsigned colIndex = 1; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex += 2)
+      {
+         surfaceValues(rowIndex, colIndex) =
+            _meshData(rowIndex, colIndex).m_texcoord[axis] / 2.0f +
+            (_meshData(rowIndex, colIndex - 1).m_texcoord[axis] +
+             _meshData(rowIndex, colIndex + 1).m_texcoord[axis]) / 4.0f;
+      }
+   }
+
+   // And finally on odd rows & even columns (i.e. the centers of columns that
+   // are patch edges).
+   for (unsigned rowIndex = 1; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex += 2)
+   {
+      for (unsigned colIndex = 0; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex += 2)
+      {
+         surfaceValues(rowIndex, colIndex) =
+            _meshData(rowIndex, colIndex).m_texcoord[axis] / 2.0f +
+            (_meshData(rowIndex - 1, colIndex).m_texcoord[axis] +
+             _meshData(rowIndex + 1, colIndex).m_texcoord[axis]) / 4.0f;
+      }
+   }
+}
+
+/**
+ * Copy the control point texture coordinates (on a given texture axis) to
+ * the output texture coordinates parameter.
+ *
+ * @param axis         The texture axis of interest.
+ * @param [out] values The output texture coordinates.
+ */
+void
+MeshEntity::CopyValuesFromControlTex(TextureAxis axis,
+                                     Matrix<float>& values)
+{
+   // Iterate over all control points and just do a straight copy.
+   for (unsigned rowIndex = 0; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex++)
+   {
+      for (unsigned colIndex = 0; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex++)
+      {
+         values(rowIndex, colIndex) =
+            _meshData(rowIndex, colIndex).m_texcoord[axis];
+      }
+   }
+}
+
+/**
+ * Generate a set of values based on surface slice lengths and some amount of
+ * desired scaling or tiling.
+ * 
+ * This method does a great deal of the work for the SetScale public method;
+ * refer to that method's comment header for more details about the alignment
+ * slice and reference slice inputs. The main difference from the SetScale
+ * input parameters is that the scale/tiles factor has been processed some.
+ * It has been flipped if necessary to account for Radiant's internal
+ * scale/tiles orientation differing from the sensible external orientation.
+ * And natural scaling has been converted to raw scaling, which is the actual
+ * desired divisor to get texture coordinates from worldspace lengths.
+ *
+ * @param sliceType       Process rows or colums.
+ * @param alignSlice      Pointer to alignment slice description; if NULL,
+ *                        slice 0 is assumed.
+ * @param refSlice        Pointer to reference slice description, including how
+ *                        to use the reference; NULL if no reference.
+ * @param rawScale        true if rawScaleOrTiles is a scale factor; false if
+ *                        rawScaleOrTiles is a number of tiles.
+ * @param rawScaleOrTiles Scaling determinant, interpreted according to the
+ *                        rawScale parameter.
+ * @param [out] values    The generated values.
+ */
+void
+MeshEntity::GenScaledDistanceValues(SliceType sliceType,
+                                    int alignSlice,
+                                    const RefSliceDescriptorInt *refSlice,
+                                    bool rawScale,
+                                    float rawScaleOrTiles,
+                                    Matrix<float>& values)
+{
+   // For every half-patch interval along the surface, we want to generate a
+   // value based on the surface distance along the row (or column) to that
+   // spot. So, first we need to determine those distances.
+
+   // XXX Similar to GetSliceTexScale; refactor for shared code?
+
+   // We're going to be walking patches along the mesh, choosing the patches
+   // that surround/affect the slice we are interested in.
+   
+   // A SlicePatchContext will contain all the necessary information to
+   // evaluate our slice's surface length within each patch.
+   SlicePatchContext context;
+   context.sliceType = sliceType;
+
+   // Pick the slices that we will measure.
+   int firstSlice, lastSlice;
+   if (refSlice != NULL && !refSlice->totalLengthOnly)
+   {
+      // If a reference slice is provided, and totalLengthOnly is false, then we
+      // will only need to measure the reference slice. The values generated for
+      // it will later be copied to other slices.
+      firstSlice = lastSlice = refSlice->index;
+   }
+   else
+   {
+      // Otherwise we'll measure all of the slices.
+      firstSlice = 0;
+      lastSlice = _numSlices[sliceType] - 1;
+   }
+
+   // Iterate over the slices that need to be measured.
+   for (int slice = firstSlice; slice <= lastSlice; slice++)
+   {
+      // Some aspects of the SlicePatchContext will vary as we move from patch
+      // to patch, but we can go ahead and calculate now any stuff that is
+      // invariant along the slice's direction.
+      if (slice != 0)
+      {
+         // This is the position of the slice within each patch, in the
+         // direction orthogonal to the slice. Even-numbered slices are at the
+         // edge of the patch (position 1.0), while odd-numbered slices are in
+         // the middle (position 0.5).
+         context.position = 1.0f - ((float)(slice & 0x1) / 2.0f);
+      }
+      else
+      {
+         // For the first slice, we can't give it the usual treatment for even-
+         // numbered slices (since there is no patch "before" it), so it gets
+         // position 0.0 instead.
+         context.position = 0.0f;
+      }
+      // This is the slice of the same kind that defines the 0.0 edge of the
+      // patch. It will be the next lowest even-numbered slice. (Note the
+      // integer division here.)
+      context.edgeSlice[sliceType] = 2 * ((slice - 1) / 2);
+
+      // The alignment slice marks the zero-point from which we will be
+      // calculating the distances. So the cumulative distance there is zero.
+      MatrixElement(values, sliceType, slice, alignSlice) = 0.0f;
+
+      // Now we're going to calculate distances for control points "greater
+      // than" the one marked by the alignment slice.
+
+      // Start with zero cumulative distance.
+      float cumulativeDistance = 0.0f;
+      // Each pair of control points delineates a "half patch" (the middle
+      // control point corresponds to surface coords generated from t=0.5).
+      // Since distance measurements are done within each patch, and we want to
+      // measure the distance at half-patch increments, we need to alternate
+      // doing the segment measurements from 0 to 0.5 and from 0.5 to 1.0 (for
+      // values of t). We start with a t target of 0.5 or 1.0 depending on the
+      // even/odd nature of the alignment slice.
+      float slicewiseFraction = (float)((alignSlice & 0x1) + 1) / 2.0f;
+      // Iterate over the control points greater than the alignment point.
+      for (int halfPatch = alignSlice + 1; halfPatch < (int)SliceSize(sliceType); halfPatch++)
+      {
+         // Find the slice-of-other-kind that defines the patch edge orthogonal
+         // to our slice.
+         context.edgeSlice[OtherSliceType(sliceType)] = 2 * ((halfPatch - 1) / 2);
+         // Estimate the slice length along the surface of the half patch.
+         float segmentLengthEstimate =
+            EstimateSegmentLength(slicewiseFraction - 0.5f, slicewiseFraction, context);
+         // Recursively refine that estimate until it is good enough, then add it
+         // to our cumulative distance.
+         cumulativeDistance +=
+            RefineSegmentLength(slicewiseFraction - 0.5f, slicewiseFraction, context,
+                                segmentLengthEstimate, UNITS_ERROR_BOUND);
+         // Store that cumulative distance in the output array.
+         MatrixElement(values, sliceType, slice, halfPatch) = cumulativeDistance;
+         // Flip to measure the other half patch.
+         slicewiseFraction = 1.5f - slicewiseFraction;
+      }
+
+      // Now we're going to calculate distances for control points "less
+      // than" the one marked by the alignment slice.
+
+      // Start with zero cumulative distance.
+      cumulativeDistance = 0.0f;
+      // We need to alternate doing the segment measurements from 1.0 to 0.5 and
+      // from 0.5 to 0 (for values of t). We start with a t target of 0.5 or 0
+      // depending on the even/odd nature of the alignment slice.
+      slicewiseFraction = (float)((alignSlice - 1) & 0x1) / 2.0f;
+      // Iterate over the control points less than the alignment point.
+      for (int halfPatch = alignSlice - 1; halfPatch >= 0; halfPatch--)
+      {
+         // Find the slice-of-other-kind that defines the patch edge orthogonal
+         // to our slice.
+         context.edgeSlice[OtherSliceType(sliceType)] = 2 * ((halfPatch - 1) / 2);
+         // Estimate the slice length along the surface of the half patch.
+         float segmentLengthEstimate =
+            EstimateSegmentLength(slicewiseFraction + 0.5f, slicewiseFraction, context);
+         // Recursively refine that estimate until it is good enough, then add it
+         // to our cumulative distance. (Which is negative on this side!)
+         cumulativeDistance -=
+            RefineSegmentLength(slicewiseFraction + 0.5f, slicewiseFraction, context,
+                                segmentLengthEstimate, UNITS_ERROR_BOUND);
+         // Store that cumulative distance in the output array.
+         MatrixElement(values, sliceType, slice, halfPatch) = cumulativeDistance;
+         // Flip to measure the other half patch.
+         slicewiseFraction = 0.5f - slicewiseFraction;
+      }
+   }
+
+   // Now we may adjust the distance values based on scaling/tiling input.
+   // If there's a reference slice, we're going to need to know the total slice
+   // length, so save that away.
+   float refTotalLength;
+   if (refSlice != NULL)
+   {
+      refTotalLength =
+         MatrixElement(values, sliceType, refSlice->index, SliceSize(sliceType) - 1) -
+         MatrixElement(values, sliceType, refSlice->index, 0);
+   }
+   else
+   {
+      refTotalLength = 1.0f; // unused, but avoid uninitialized-var warning
+   }
+
+#if defined(_DEBUG)
+    ASSERT_MESSAGE(refTotalLength != 0.0f, "calculated length of reference slice is zero");
+    ASSERT_MESSAGE(rawScaleOrTiles != 0.0f, "internal scale or tiles value is zero");
+#endif
+
+   // Iterate over the slices we're processing and adjust the distance values
+   // (remember that this may just be the reference slice).
+   for (int slice = firstSlice; slice <= lastSlice; slice++)
+   {
+      // Figure out what we're going to divide the distances by.
+      float scaleFactor;
+      if (rawScale)
+      {
+         // In this case we've just been passed in the value to divide by.
+         scaleFactor = rawScaleOrTiles;
+         if (refSlice != NULL)
+         {
+            // However if there's a reference slice, adjust the divisor by the
+            // ratio of this slice's length to the reference slice's length.
+            // (Which is a NOP if this slice actually is the reference slice.)
+            // Example: if the ref slice is length 2, this slice is length 3,
+            // and the raw scale factor is 4... then all distances on the ref
+            // slice would be divided by 4 * 2 / 2 = 4, and distances on this
+            // slice would be divided by 4 * 3 / 2 = 6.
+            scaleFactor *=
+               ((MatrixElement(values, sliceType, slice, SliceSize(sliceType) - 1) -
+                 MatrixElement(values, sliceType, slice, 0)) /
+                refTotalLength);
+         }
+      }
+      else
+      {
+         // In this case we've been passed in a desired tiling value. We're
+         // going to want to eventually divide the distances by length / tiles.
+         // Example: if this slice is length 6 and the desired tiling is 3, we
+         // will divide the distances on this slice by 6 / 3 = 2.
+         scaleFactor =
+            (MatrixElement(values, sliceType, slice, SliceSize(sliceType) - 1) -
+             MatrixElement(values, sliceType, slice, 0)) /
+            rawScaleOrTiles;
+      }
+      // Adjust the distances for this slice by the divisor we calculated.
+      for (unsigned halfPatch = 0; halfPatch < SliceSize(sliceType); halfPatch++)
+      {
+         MatrixElement(values, sliceType, slice, halfPatch) /= scaleFactor;
+      }
+   }
+
+   // One final step if we have a reference slice and totalLengthOnly is false.
+   // In that case, up until this point we have only been processing the
+   // reference slice. Now we have to copy the reference slice's values to all
+   // other slices.
+   // (These loops also copy the reference slice to itself, which is fine.)
+   if (refSlice != NULL && !refSlice->totalLengthOnly)
+   {
+      for (unsigned slice = 0; slice < _numSlices[sliceType]; slice++)
+      {
+         for (unsigned halfPatch = 0; halfPatch < SliceSize(sliceType); halfPatch++)
+         {
+            MatrixElement(values, sliceType, slice, halfPatch) =
+               MatrixElement(values, sliceType, refSlice->index, halfPatch);
+         }
+      }
+   }
+}
+
+/**
+ * Generate coordinates for a specified texture axis based on a linear
+ * combination of factors. This method does the final work for the
+ * GeneralFunction public method.
+ *
+ * @param factors       Factors to determine the texture coords.
+ * @param axis          The texture axis to process.
+ * @param alignRow      Zero-point row.
+ * @param alignCol      Zero-point column.
+ * @param surfaceValues true if calculations are for S/T values on the mesh
+ *                      surface; false if calculations are for S/T values at
+ *                      the control points.
+ * @param rowDistances  Surface distance-along-row values (measured from
+ *                      alignment column) for spots corresponding to each
+ *                      control point.
+ * @param colDistances  Surface distance-along-column values (measured from
+ *                      alignment row) for spots corresponding to each
+ *                      control point.
+ */
+void
+MeshEntity::GeneralFunctionInt(const GeneralFunctionFactors& factors,
+                               TextureAxis axis,
+                               int alignRow,
+                               int alignCol,
+                               bool surfaceValues,
+                               const Matrix<float>& rowDistances,
+                               const Matrix<float>& colDistances)
+{
+   // Grab the "original value" info if the equation uses it.
+   AllocatedMatrix<float> oldValues(_meshData.x(), _meshData.y());
+   AllocatedMatrix<float> newValues(_meshData.x(), _meshData.y());
+   if (factors.oldValue != 0.0f)
+   {
+      if (surfaceValues)
+      {
+         // Will be manipulating surface values.
+         GenSurfaceFromControlTex(axis, oldValues);
+      }
+      else
+      {
+         // Will be manipulating control point values.
+         CopyValuesFromControlTex(axis, oldValues);
+      }
+   }
+
+   // Iterate over all values and apply the equation.
+   for (int rowIndex = 0; rowIndex < (int)_numSlices[ROW_SLICE_TYPE]; rowIndex++)
+   {
+      for (int colIndex = 0; colIndex < (int)_numSlices[COL_SLICE_TYPE]; colIndex++)
+      {
+         newValues(rowIndex, colIndex) =
+            factors.oldValue * oldValues(rowIndex, colIndex) +
+            factors.rowDistance * rowDistances(rowIndex, colIndex) +
+            factors.colDistance * colDistances(rowIndex, colIndex) +
+            factors.rowNumber * (rowIndex - alignRow) +
+            factors.colNumber * (colIndex - alignCol) +
+            factors.constant;
+      }
+   }
+
+   // Store the generated values.
+   if (surfaceValues)
+   {
+      // If we're manipulating surface values, figure the necessary control
+      // point values to make those.
+      GenControlTexFromSurface(axis, newValues);
+   }
+   else
+   {
+      // If we're manipulating control point values, store the new values.
+      CopyControlTexFromValues(axis, newValues);
+   }
+}
\ No newline at end of file
diff --git a/contrib/meshtex/MeshEntity.h b/contrib/meshtex/MeshEntity.h
new file mode 100644 (file)
index 0000000..2b74d8d
--- /dev/null
@@ -0,0 +1,467 @@
+/**
+ * @file MeshEntity.h
+ * Declares the MeshEntity class.
+ * @ingroup meshtex-core
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(INCLUDED_MESHENTITY_H)
+#define INCLUDED_MESHENTITY_H
+
+#include "AllocatedMatrix.h"
+
+#include "scenelib.h"
+#include "ipatch.h"
+#include "generic/callback.h"
+
+/**
+ * Wrapper for a biquadratic Bezier patch mesh entity from Radiant.
+ * Instantiate this for a given patch mesh, then use the methods to
+ * interrogate or modify the mesh. It is intended that this object be
+ * instantiated, used, and then discarded before resuming other Radiant
+ * operations, as the implementation assumes that several basic mesh
+ * characteristics will remain constant for the life of this object.
+ *
+ * @ingroup meshtex-core
+ */
+class MeshEntity
+{
+public: // public types
+
+   /**
+    * Values that represent the texture axes an operation should manipulate.
+    */
+   enum TextureAxisSelection
+   {
+      S_TEX_AXIS_ONLY,  ///< manipulate the S values
+      T_TEX_AXIS_ONLY,  ///< manipulate the T values
+      ALL_TEX_AXES      ///< manipulate both S and T values
+   };
+
+   /**
+    * Values that represent the kinds of patch mesh slices. Note that the
+    * assigned integer values are significant; do not change.
+    */
+   enum SliceType
+   {
+      ROW_SLICE_TYPE = 0,  ///< row 
+      COL_SLICE_TYPE = 1,  ///< column 
+      NUM_SLICE_TYPES = 2  ///< number of kinds of slice 
+   };
+
+   /**
+    * Type for info/warning/error callbacks. The callback takes a const
+    * char* argument (the message string); it has no return value.
+    */
+   typedef Callback1<const char *, void> MessageCallback;
+
+   /**
+    * Type for texture scale info callbacks. The callback takes two float
+    * arguments (scale and tiles); it has no return value.
+    */
+   typedef Callback2<float, float, void> TexInfoCallback;
+
+   /**
+    * Type for defining how to manipulate control point or surface values
+    * according to some linear combination of various values.
+    */
+   typedef struct {
+      float oldValue;     ///< coefficient for original value
+      float rowDistance;  ///< coefficient for surface distance along row
+      float colDistance;  ///< coefficient for surface distance along col
+      float rowNumber;    ///< coefficient for row number
+      float colNumber;    ///< coefficient for column number
+      float constant;     ///< constant
+   } GeneralFunctionFactors;
+
+   /**
+    * Type for choosing a particular slice of a known kind (row or column).
+    */
+   typedef struct {
+      bool maxSlice;  ///< if true, choose slice with highest index
+      int index;      ///< if maxSlice is false, choose slice with this index
+   } SliceDesignation;
+
+   /**
+    * Type for choosing a reference slice of a known kind (row or column) and
+    * indicating how to use it for reference. Reference can be made to the total
+    * slice surface length alone, or to each individual control point.
+    */
+   typedef struct {
+      SliceDesignation designation;  ///< the slice
+      bool totalLengthOnly;          ///< if true, reference total length only
+   } RefSliceDescriptor;
+
+   /**
+    * An instance of this class can be used as a MeshEntity::TexInfoCallback, in
+    * situations where the callback is a method to be invoked on a target
+    * object. When invoking this constructor, the target object is the
+    * constructor argument, and the target object class and method are template
+    * parameters. The target object's method must have an appropriate signature
+    * for TexInfoCallback: two float arguments, void return.
+    */
+   template<typename ObjectClass, void (ObjectClass::*member)(float, float)>
+   class TexInfoCallbackMethod :
+      public BindFirstOpaque2<Member2<ObjectClass, float, float, void, member> >
+   {
+   public:
+      /**
+       * Constructor.
+       *
+       * @param object The object on which to invoke the callback method.
+       */
+      TexInfoCallbackMethod(ObjectClass& object) :
+         BindFirstOpaque2<Member2<ObjectClass, float, float, void, member> >(object) {}
+   };
+
+public: // public methods
+
+   /// @name Lifecycle
+   //@{
+   MeshEntity(scene::Node& mesh,
+              const MessageCallback& infoReportCallback,
+              const MessageCallback& warningReportCallback,
+              const MessageCallback& errorReportCallback);
+   ~MeshEntity();
+   //@}
+   /// @name Interrogation
+   //@{
+   bool IsValid() const;
+   void GetInfo(const int *refRow,
+                const int *refCol,
+                const TexInfoCallback *rowTexInfoCallback,
+                const TexInfoCallback *colTexInfoCallback);
+   //@}
+   /// @name Simple modification
+   //@{
+   void MinAlign(TextureAxisSelection axes);
+   void MaxAlign(TextureAxisSelection axes);
+   void MinMaxAlignAutoScale(TextureAxisSelection axes);
+   void MinMaxAlignStretch(TextureAxisSelection axes);
+   void MinMaxAlignShrink(TextureAxisSelection axes);
+   //@}
+   /// @name Complex modification
+   //@{
+   void SetScale(SliceType sliceType,
+                 const SliceDesignation *alignSlice,
+                 const RefSliceDescriptor *refSlice,
+                 bool naturalScale,
+                 float naturalScaleOrTiles);
+   void GeneralFunction(const GeneralFunctionFactors *sFactors,
+                        const GeneralFunctionFactors *tFactors,
+                        const SliceDesignation *alignRow,
+                        const SliceDesignation *alignCol,
+                        const RefSliceDescriptor *refRow,
+                        const RefSliceDescriptor *refCol,
+                        bool surfaceValues);
+   //@}
+
+private: // private methods
+
+   /// @name Unimplemented to prevent copy/assignment
+   //@{
+   MeshEntity(const MeshEntity&);
+   const MeshEntity& operator=(const MeshEntity&);
+   //@}
+
+private: // private types
+
+   /**
+    * Values that represent the kinds of texture axis.
+    */
+   enum TextureAxis
+   {
+      S_TEX_AXIS = 0,   ///< S texture axis 
+      T_TEX_AXIS = 1,   ///< T texture axis
+      NUM_TEX_AXES = 2  ///< number of kinds of texture axis
+   };
+
+   /**
+    * Values that represent the kinds of position (spatial) axis.
+    */
+   enum PositionAxis
+   {
+      X_POS_AXIS = 0,   ///< X position axis 
+      Y_POS_AXIS = 1,   ///< Y position axis
+      Z_POS_AXIS = 2,   ///< Z position axis
+      NUM_POS_AXES = 3  ///< number of kinds of position axis
+   };
+
+   /**
+    * Values that represent ways of scaling a texture to make it aligned.
+    */
+   enum ScaleOperation
+   {
+      STRETCH_SCALE_OP,  ///< scale by stretching
+      SHRINK_SCALE_OP    ///< scale by shrinking
+   };
+
+   /**
+    * Type for orienting a slice within a particular patch.
+    */
+   typedef struct {
+      SliceType sliceType; ///< slice type (row or column)
+      float position;      ///< fractional dist from patch edge (0, 0.5, or 1)
+      int edgeSlice[NUM_SLICE_TYPES];  ///< indices of slices at patch edges
+   } SlicePatchContext;
+
+   /**
+    * Type for describing the application of a texture along a given slice,
+    * on a specified texture axis.
+    */
+   typedef struct {
+      float scale;  ///< texture scale along axis
+      float tiles;  ///< # of times the texture tiles along axis
+      float min;    ///< minimum value for that texture axis
+      float max;    ///< maximum value for that texture axis
+   } SliceTexInfo;
+
+   /**
+    * Type for internal representation of a reference slice of a given kind
+    * (row or column), specifying the slice and indicating how to use it for
+    * reference. Any external specification of "max slice" has been replaced
+    * with an explicit slice number. Reference can be made to the total slice
+    * length alone, or to the distance to each individual control point.
+    */
+   typedef struct {
+      unsigned index;        ///< choose slice with this number
+      bool totalLengthOnly;  ///< if true, reference total length only
+   } RefSliceDescriptorInt;
+
+   /**
+    * Function signature for a private method that applies a preset
+    * transformation on a given texture axis.
+    */
+   typedef bool(MeshEntity::*InternalImpl)(TextureAxis axis);
+
+private: // private template methods
+
+   /**
+    * Utility template function for accessing a matrix element from code that
+    * operates on either kind of slice.
+    *
+    * @param matrix    The matrix holding the mesh control points.
+    * @param sliceType Slice kind (row or column).
+    * @param slice     Slice number, among slices of that type in mesh.
+    * @param index     Element index along the slice.
+    *
+    * @return The matrix element; can be used as lvalue or rvalue.
+    */
+   template<typename Element>
+   inline static Element& MatrixElement(Matrix<Element>& matrix,
+                                        SliceType sliceType,
+                                        int slice,
+                                        int index) {
+      return (sliceType == ROW_SLICE_TYPE ? matrix(slice, index) : 
+                                            matrix(index, slice));
+   }
+
+private: // private methods
+
+   /// @name Internal state refresh
+   //@{
+   void UpdatePosMinMax(PositionAxis axis);
+   void UpdateTexMinMax(TextureAxis axis);
+   //@}
+   /// @name Radiant state management
+   //@{
+   void CreateUndoPoint();
+   void CommitChanges();
+   //@}
+   /// @name Argument resolution
+   //@{
+   int InternalSliceDesignation(const SliceDesignation *sliceDesignation,
+                                SliceType sliceType);
+   RefSliceDescriptorInt *InternalRefSliceDescriptor(const RefSliceDescriptor *refSlice,
+                                                     SliceType sliceType,
+                                                     RefSliceDescriptorInt& refSliceInt);
+   //@}
+   /// @name Subroutines for interrogation
+   //@{
+   float GetSliceTexScale(SliceType sliceType,
+                          int slice,
+                          TextureAxis axis,
+                          float tiles);
+   bool GetSliceTexInfo(SliceType sliceType,
+                        int slice,
+                        TextureAxis axis,
+                        SliceTexInfo& info);
+   void ReportSliceTexInfo(SliceType sliceType,
+                           int slice,
+                           TextureAxis axis,
+                           char *messageBuffer,
+                           unsigned messageBufferSize,
+                           const TexInfoCallback *texInfoCallback);
+   //@}
+   /// @name Subroutines for simple modification
+   //@{
+   void ProcessForAxes(InternalImpl internalImpl,
+                       TextureAxisSelection axes);
+   void Shift(TextureAxis axis,
+              float shift);
+   void Scale(TextureAxis axis,
+              float scale);
+   bool MinAlignInt(TextureAxis axis);
+   bool MaxAlignInt(TextureAxis axis);
+   bool MinMaxAlignAutoScaleInt(TextureAxis axis);
+   bool MinMaxAlignScale(TextureAxis axis,
+                         ScaleOperation op);
+   bool MinMaxAlignStretchInt(TextureAxis axis);
+   bool MinMaxAlignShrinkInt(TextureAxis axis);
+   //@}
+   /// @name Surface measurement
+   //@{
+   float SliceParametricSpeedComponent(PositionAxis axis,
+                                       float t,
+                                       const SlicePatchContext& context);
+   float SliceParametricSpeed(float t,
+                              const SlicePatchContext& context);
+   float EstimateSegmentLength(float startPosition,
+                               float endPosition,
+                               const SlicePatchContext& context);
+   float RefineSegmentLength(float startPosition,
+                             float endPosition,
+                             const SlicePatchContext &context,
+                             float segmentLengthEstimate,
+                             float maxError);
+   //@}
+   /// @name Subroutines for complex modification
+   //@{
+   void GenControlTexFromSurface(TextureAxis axis,
+                                 const Matrix<float>& surfaceValues);
+   void CopyControlTexFromValues(TextureAxis axis,
+                                 const Matrix<float>& values);
+   void GenSurfaceFromControlTex(TextureAxis axis,
+                                 Matrix<float>& surfaceValues);
+   void CopyValuesFromControlTex(TextureAxis axis,
+                                 Matrix<float>& values);
+   void GenScaledDistanceValues(SliceType sliceType,
+                                int alignSlice,
+                                const RefSliceDescriptorInt *refSlice,
+                                bool rawScale,
+                                float rawScaleOrTiles,
+                                Matrix<float>& values);
+   void GeneralFunctionInt(const GeneralFunctionFactors& factors,
+                           TextureAxis axis,
+                           int alignRow,
+                           int alignCol,
+                           bool surfaceValues,
+                           const Matrix<float>& rowDistances,
+                           const Matrix<float>& colDistances);
+   //@}
+
+private: // private static member vars
+
+   static TextureAxis _naturalAxis[NUM_SLICE_TYPES];
+   static bool _radiantScaleInverted[NUM_SLICE_TYPES];
+   static bool _radiantTilesInverted[NUM_SLICE_TYPES];
+   static const char *_infoSliceFormatString[NUM_SLICE_TYPES];
+   static const char *_infoSliceInfscaleFormatString[NUM_SLICE_TYPES];
+   static const char *_warningSliceInfscaleFormatString[NUM_SLICE_TYPES];
+   static const char *_errorBadSliceString[NUM_SLICE_TYPES];
+   static const char *_errorSliceZeroscaleString[NUM_SLICE_TYPES];
+   static const char *_errorSliceZerotilesString[NUM_SLICE_TYPES];
+
+private: // private member vars
+
+   /**
+    * Handle for the Node object in Radiant that is the patch mesh entity.
+    */
+   scene::Node& _mesh;
+
+   /**
+    * Flag to indicate whether this object was properly generated from the
+    * supplied entity.
+    */
+   bool _valid;
+
+   /**
+    * The control points of the mesh. Modifying the data in this matrix will
+    * modify the mesh entity directly; it is NOT a copy of the entity's data.
+    */
+   PatchControlMatrix _meshData;
+
+   /**
+    * Callback function used to report information about the mesh.
+    */
+   const MessageCallback _infoReportCallback;
+
+   /**
+    * Callback function used to deliver warning messages.
+    */
+   const MessageCallback _warningReportCallback;
+
+   /**
+    * Callback function used to deliver error messages when operations on the
+    * mesh fail.
+    */
+   const MessageCallback _errorReportCallback;
+
+   /**
+    * The number of grid units that would constitute a "natural" scale along
+    * each texture axis, using the mesh's current texture. Radiant's natural
+    * scale is 1/2 as many grid units as there are texture pixels.
+    */
+   float _naturalTexUnits[NUM_TEX_AXES];
+
+   /**
+    * The number of mesh slices of each kind (row or column).
+    */
+   unsigned _numSlices[NUM_SLICE_TYPES];
+
+   /**
+    * Whether the values for a texture axis have been modified since the last
+    * time their min/max/aligned state was calculated.
+    */
+   bool _texMinMaxDirty[NUM_TEX_AXES];
+
+   /**
+    * The minimum values, across the entire mesh, for each texture axis.
+    */
+   float _texMin[NUM_TEX_AXES];
+
+   /**
+    * The maximum values, across the entire mesh, for each texture axis.
+    */
+   float _texMax[NUM_TEX_AXES];
+
+   /**
+    * Whether the minimum value for a texture axis is on a texture boundary.
+    */
+   bool _texMinAligned[NUM_TEX_AXES];
+
+   /**
+    * Whether the maximum value for a texture axis is on a texture boundary.
+    */
+   bool _texMaxAligned[NUM_TEX_AXES];
+
+   /**
+    * The minimum values, across the entire mesh, for each position axis.
+    */
+   float _posMin[NUM_POS_AXES];
+
+   /**
+    * The maximum values, across the entire mesh, for each position axis.
+    */
+   float _posMax[NUM_POS_AXES];
+};
+
+#endif // #if !defined(INCLUDED_MESHENTITY_H)
\ No newline at end of file
diff --git a/contrib/meshtex/MeshEntityMessages.h b/contrib/meshtex/MeshEntityMessages.h
new file mode 100644 (file)
index 0000000..7300c75
--- /dev/null
@@ -0,0 +1,55 @@
+/**
+ * @file MeshEntityMessages.h
+ * String constants for messages shown in dialogs.
+ * @ingroup meshtex-core
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(INCLUDED_MESHENTITYMESSAGES_H)
+#define INCLUDED_MESHENTITYMESSAGES_H
+
+/// @name Informational messages
+//@{
+#define INFO_ROW_FORMAT "On row %d:\n  S natural scale: %f\n  S tiles: %f\n  S min: %f\n  S max: %f\n\n"
+#define INFO_ROW_INFSCALE_FORMAT "On row %d:\n  S scale: (infinite)\n  S tiles: %f\n  S min: %f\n  S max: %f\n\n"
+#define INFO_COL_FORMAT "On column %d:\n  T natural scale: %f\n  T tiles: %f\n  T min: %f\n  T max: %f\n\n"
+#define INFO_COL_INFSCALE_FORMAT "On column %d:\n  T scale: (infinite)\n  T tiles: %f\n  T min: %f\n  T max: %f\n\n"
+#define INFO_MESH_FORMAT "Over entire mesh:\n  Rows: %d\n  S min: %f\n  S max: %f\n  Columns: %d\n  T min: %f\n  T max: %f\n\nWorldspace extents: (%.2f/%.2f,%.2f/%.2f,%.2f/%.2f)"
+//@}
+
+/// @name Warning messages
+//@{
+#define WARNING_ROW_INFSCALE "The selected reference row has an infinite scale (S does not vary along the row). The row information cannot be automatically transferred to the Set S/T Scale dialog."
+#define WARNING_COL_INFSCALE "The selected reference column has an infinite scale (T does not vary along the column). The column information cannot be automatically transferred to the Set S/T Scale dialog."
+//@}
+
+/// @name Error messages
+//@{
+#define ERROR_BAD_MESH "Unable to read patch mesh."
+#define ERROR_BAD_ROW "Illegal row # specified."
+#define ERROR_BAD_COL "Illegal column # specified."
+#define ERROR_ROW_ZEROSCALE "A scale of zero cannot be applied; the S values of the row will not be changed."
+#define ERROR_COL_ZEROSCALE "A scale of zero cannot be applied; the T values of the column will not be changed."
+#define ERROR_ROW_ZEROTILES "A tile count of zero cannot be applied; the S values of the row will not be changed."
+#define ERROR_COL_ZEROTILES "A tile count of zero cannot be applied; the T values of the column will not be changed."
+//@}
+
+#endif // #if !defined(INCLUDED_MESHENTITYMESSAGES_H)
\ No newline at end of file
diff --git a/contrib/meshtex/MeshVisitor.cpp b/contrib/meshtex/MeshVisitor.cpp
new file mode 100644 (file)
index 0000000..5f2488f
--- /dev/null
@@ -0,0 +1,132 @@
+/**
+ * @file MeshVisitor.cpp
+ * Implements the MeshVisitor class.
+ * @ingroup meshtex-util
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "MeshVisitor.h"
+#include "GenericPluginUI.h"
+#include "PluginUIMessages.h"
+
+
+/**
+ * Use GenericPluginUI::InfoReportDialog as MeshEntity info callback.
+ */
+const MeshEntity::MessageCallback MeshVisitor::_infoReportCallback(
+   PointerCaller1<const char,
+                  const char *,
+                  &GenericPluginUI::InfoReportDialog>(DIALOG_MESH_INFO_TITLE));
+
+/**
+ * Use GenericPluginUI::WarningReportDialog as MeshEntity warning callback.
+ */
+const MeshEntity::MessageCallback MeshVisitor::_warningReportCallback(
+   PointerCaller1<const char,
+                  const char *,
+                  &GenericPluginUI::WarningReportDialog>(DIALOG_WARNING_TITLE));
+
+/**
+ * Use GenericPluginUI::ErrorReportDialog as MeshEntity error callback.
+ */
+const MeshEntity::MessageCallback MeshVisitor::_errorReportCallback(
+   PointerCaller1<const char,
+                  const char *,
+                  &GenericPluginUI::ErrorReportDialog>(DIALOG_ERROR_TITLE));
+
+
+/**
+ * Default constructor.
+ */
+MeshVisitor::MeshVisitor() :
+   _visitedCount(0)
+{
+}
+
+/**
+ * Virtual destructor.
+ */
+MeshVisitor::~MeshVisitor()
+{
+}
+
+/**
+ * Reset the visited count to zero.
+ */
+void
+MeshVisitor::ResetVisitedCount()
+{
+   _visitedCount = 0;
+}
+
+/**
+ * Get the visited count. This is the number of times Execute has been
+ * invoked since visitor construction or the most recent reset of the count.
+ *
+ * @return The visited count.
+ */
+unsigned
+MeshVisitor::GetVisitedCount()
+{
+   return _visitedCount;
+}
+
+/**
+ * Visit a specified scene graph node.
+ *
+ * @param [in,out] instance The scene graph node.
+ */
+void
+MeshVisitor::visit(scene::Instance& instance) const
+{
+   if (Node_isPatch(instance.path().top()))
+   {
+      // If it's a patch mesh, try creating a MeshEntity.
+      MeshEntity meshEntity(instance.path().top(),
+                            _infoReportCallback,
+                            _warningReportCallback,
+                            _errorReportCallback);
+      if (meshEntity.IsValid())
+      {
+         // If we have a valid MeshEntity, invoke Execute.
+         if (Execute(meshEntity))
+         {
+            // Count the number of affected meshes.
+            _visitedCount++;
+         }
+      }
+   }
+}
+
+/**
+ * Execute function performed for visited meshes. This implementation does
+ * nothing; subclasses should override it.
+ *
+ * @param meshEntity The MeshEntity.
+ *
+ * @return true if the mesh was successfully visited; always the case in this
+ *         skeleton implementation.
+ */
+bool
+MeshVisitor::Execute(MeshEntity& meshEntity) const
+{
+   return true;
+}
diff --git a/contrib/meshtex/MeshVisitor.h b/contrib/meshtex/MeshVisitor.h
new file mode 100644 (file)
index 0000000..75438cf
--- /dev/null
@@ -0,0 +1,72 @@
+/**
+ * @file MeshVisitor.h
+ * Declares the MeshVisitor class.
+ * @ingroup meshtex-util
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(INCLUDED_MESHVISITOR_H)
+#define INCLUDED_MESHVISITOR_H
+
+#include "RefCounted.h"
+#include "MeshEntity.h"
+
+#include "iselection.h"
+
+/**
+ * Visitor that will invoke Execute with a MeshEntity argument if the visited
+ * node is a valid mesh. Subclasses should override Execute to perform
+ * operations on the MeshEntity.
+ *
+ * @ingroup meshtex-util
+ */
+class MeshVisitor : public SelectionSystem::Visitor, public RefCounted
+{
+public: // public methods
+
+   MeshVisitor();
+   virtual ~MeshVisitor();
+   void ResetVisitedCount();
+   unsigned GetVisitedCount();
+   void visit(scene::Instance& instance) const;
+
+protected: // protected methods
+
+   virtual bool Execute(MeshEntity& meshEntity) const;
+
+private: // private static member vars
+
+   /// @name Callbacks to use when constructing the MeshEntity
+   //@{
+   static const MeshEntity::MessageCallback _infoReportCallback;
+   static const MeshEntity::MessageCallback _warningReportCallback;
+   static const MeshEntity::MessageCallback _errorReportCallback;
+   //@}
+
+private: // private member vars
+
+   /**
+    * Track the number of Execute invocations.
+    */
+   mutable unsigned _visitedCount;
+};
+
+#endif // #if !defined(INCLUDED_MESHVISITOR_H)
\ No newline at end of file
diff --git a/contrib/meshtex/PluginModule.cpp b/contrib/meshtex/PluginModule.cpp
new file mode 100644 (file)
index 0000000..6a2032e
--- /dev/null
@@ -0,0 +1,159 @@
+/**
+ * @file PluginModule.cpp
+ * Implements the PluginModule class.
+ * @ingroup generic-plugin
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "PluginModule.h"
+#include "GenericPluginUI.h"
+
+
+/**
+ * Plugin function table.
+ */
+_QERPluginTable PluginModule::_pluginAPI;
+
+/**
+ * Default constructor. Initialize the function table.
+ */
+PluginModule::PluginModule()
+{
+   _pluginAPI.m_pfnQERPlug_Init = &QERPluginInit;
+   _pluginAPI.m_pfnQERPlug_GetName = &QERPluginGetName;
+   _pluginAPI.m_pfnQERPlug_GetCommandList = &QERPluginGetCommandList;
+   _pluginAPI.m_pfnQERPlug_GetCommandTitleList = &QERPluginGetCommandTitleList;
+   _pluginAPI.m_pfnQERPlug_Dispatch = &QERPluginDispatch;
+}
+
+/**
+ * Destructor.
+ */
+PluginModule::~PluginModule()
+{
+}
+
+/**
+ * Fetch a pointer to the function table.
+ */
+_QERPluginTable *
+PluginModule::getTable()
+{
+   return &_pluginAPI;
+}
+
+/**
+ * Initialize plugin.
+ *
+ * @param hApp        Dummy arg; ignored.
+ * @param pMainWidget Main window widget.
+ *
+ * @return The plugin name.
+ */
+const char *
+PluginModule::QERPluginInit(void *hApp,
+                            void *pMainWidget)
+{
+   // Inform the UI of the main app window.
+   UIInstance().SetWindow((GtkWidget *)pMainWidget);
+   // Return the plugin name.
+   return PLUGIN_NAME;
+}
+
+/**
+ * Get the plugin's name.
+ *
+ * @return The plugin name.
+ */
+const char *
+PluginModule::QERPluginGetName()
+{
+   // Return the plugin name.
+   return PLUGIN_NAME;
+}
+
+/**
+ * Get the command list for the plugin menu, as a semicolon-separated string
+ * of tokens representing each command.
+ *
+ * @return The command list string.
+ */
+const char *
+PluginModule::QERPluginGetCommandList()
+{
+   // Bail out if the plugin menu doesn't exist.
+   if (UIInstance().MainMenu() == NULL)
+   {
+      return "";
+   }
+   // Get the command list from the menu.
+   return UIInstance().MainMenu()->GetCommandList().c_str();
+}
+
+/**
+ * Get the command label list for the plugin menu, as a semicolon-separated
+ * string of labels to appear in the menu.
+ *
+ * @return The command label list string.
+ */
+const char *
+PluginModule::QERPluginGetCommandTitleList()
+{
+   // Bail out if the plugin menu doesn't exist.
+   if (UIInstance().MainMenu() == NULL)
+   {
+      return "";
+   }
+   // Get the command label list from the menu.
+   return UIInstance().MainMenu()->GetCommandLabelList().c_str();
+}
+
+/**
+ * Invoke a plugin command.
+ *
+ * @param command      The command token.
+ * @param vMin         3-element float vector definining min corner of
+ *                     selection.
+ * @param vMax         3-element float vector definining max corner of
+ *                     selection.
+ * @param bSingleBrush Dummy arg; ignored.
+ */
+void
+PluginModule::QERPluginDispatch(const char *command,
+                                float *vMin,
+                                float *vMax,
+                                bool bSingleBrush)
+{
+   // Bail out if the plugin menu doesn't exist.
+   if (UIInstance().MainMenu() == NULL)
+   {
+      // XXX This shouldn't happen; might as well drop an ASSERT or error
+      // message in here. First make sure there's no odd Radiant-exiting
+      // corner case race that could trigger it though.
+      return;
+   }
+   // Send the command dispatch to the menu.
+   // XXX For my particular use case I don't need vMin or vMax, but for
+   // generality's sake those values should be passed along here, and then I
+   // can drop them when the flow gets to MeshTex-specific code... that will
+   // require changes for several types/signatures though.
+   UIInstance().MainMenu()->Dispatch(command);
+}
diff --git a/contrib/meshtex/PluginModule.h b/contrib/meshtex/PluginModule.h
new file mode 100644 (file)
index 0000000..fc8c802
--- /dev/null
@@ -0,0 +1,88 @@
+/**
+ * @file PluginModule.h
+ * Declares the PluginModule class.
+ * @ingroup generic-plugin
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(INCLUDED_PLUGINMODULE_H)
+#define INCLUDED_PLUGINMODULE_H
+
+#include "PluginProperties.h"
+
+#include "iplugin.h"
+#include "typesystem.h"
+#include "qerplugin.h"
+
+/**
+ * A singleton object that handles registering as a plugin with Radiant.
+ *
+ * @ingroup generic-plugin
+ */
+class PluginModule : public TypeSystemRef
+{
+public: // public types
+
+   /// @cond DOXYGEN_INTERNAL_RADIANT_ODDITIES_SKIP
+
+   /**
+    * Prepare for uses of the token Type in singletonmodule.h; indicate
+    * that _QERPluginTable should be substituted there.
+    */
+   typedef _QERPluginTable Type;
+
+   /**
+    * Radiant constant wrapper for the plugin's name string.
+    */
+   STRING_CONSTANT(Name, PLUGIN_NAME);
+
+   /// @endcond
+
+public: // public methods
+
+   /// @name Lifecycle
+   //@{
+   PluginModule();
+   ~PluginModule();
+   //@}
+   /// @name Plugin function table interface
+   //@{
+   _QERPluginTable *getTable();
+   //@}
+   /// @name Plugin function table members
+   //@{
+   static const char *QERPluginInit(void *hApp,
+                                    void *pMainWidget);
+   static const char *QERPluginGetName();
+   static const char *QERPluginGetCommandList();
+   static const char *QERPluginGetCommandTitleList();
+   static void QERPluginDispatch(const char *command,
+                                 float *vMin,
+                                 float *vMax,
+                                 bool bSingleBrush);
+   //@}
+
+private: // private member vars
+
+   static _QERPluginTable _pluginAPI;
+};
+
+#endif // #if !defined(INCLUDED_PLUGINMODULE_H)
diff --git a/contrib/meshtex/PluginProperties.h b/contrib/meshtex/PluginProperties.h
new file mode 100644 (file)
index 0000000..deae699
--- /dev/null
@@ -0,0 +1,45 @@
+/**
+ * @file PluginProperties.h
+ * Information about this plugin used in areas like plugin registration and
+ * generating the About dialog.
+ * @ingroup meshtex-plugin
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(INCLUDED_PLUGINPROPERTIES_H)
+#define INCLUDED_PLUGINPROPERTIES_H
+
+#include "UtilityMacros.h"
+//#include "CodeVersion.h"
+
+#define PLUGIN_NAME "MeshTex"
+#define PLUGIN_VERSION_MAJOR_NUMERIC 3
+#define PLUGIN_VERSION_MINOR_NUMERIC 0
+#define PLUGIN_VERSION STRINGIFY_MACRO(PLUGIN_VERSION_MAJOR_NUMERIC) "." STRINGIFY_MACRO(PLUGIN_VERSION_MINOR_NUMERIC) " beta" \
+            ", commit "
+#define PLUGIN_AUTHOR "Joel Baxter"
+#define PLUGIN_AUTHOR_EMAIL "jl@neogeographica.com"
+#define PLUGIN_COPYRIGHT_DATE "2012"
+#define PLUGIN_DESCRIPTION "Align and scale textures on patch meshes"
+#define PLUGIN_FILE_BASENAME "meshtex"
+#define PLUGIN_FILE_DESCRIPTION "patch mesh texturing plugin for GtkRadiant 1.5"
+
+#endif // #if !defined(INCLUDED_PLUGINPROPERTIES_H)
diff --git a/contrib/meshtex/PluginRegistration.cpp b/contrib/meshtex/PluginRegistration.cpp
new file mode 100644 (file)
index 0000000..218f358
--- /dev/null
@@ -0,0 +1,90 @@
+/**
+ * @file PluginRegistration.cpp
+ * Declares and implements the MeshTexPluginDependencies class, and implements
+ * the plugin library's exported function Radiant_RegisterModules.
+ * @ingroup meshtex-plugin
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "PluginModule.h"
+
+#include "iundo.h"
+#include "iscenegraph.h"
+#include "iselection.h"
+#include "ishaders.h"
+#include "ipatch.h"
+#include "modulesystem/singletonmodule.h"
+
+
+/**
+ * Definition (in the declared superclasses) of the Radiant systems that this
+ * plugin depends on. GlobalRadiantModule, GlobalUndoModule,
+ * GlobalSceneGraphModule, GlobalSelectionModule, GlobalShadersModule, and of
+ * course GlobalPatchModule.
+ *
+ * @ingroup meshtex-plugin.
+ */
+class MeshTexPluginDependencies :
+   public GlobalRadiantModuleRef,
+   public GlobalUndoModuleRef,
+   public GlobalSceneGraphModuleRef,
+   public GlobalSelectionModuleRef,
+   public GlobalShadersModuleRef,
+   public GlobalPatchModuleRef
+{
+public:
+   /**
+    * Default constructor. This function's only responsibility is to pass
+    * arguments to superclass constructors.
+    */
+   MeshTexPluginDependencies() :
+      GlobalShadersModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("shaders")),
+      GlobalPatchModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("patchtypes"))
+   {
+   }
+};
+
+
+/**
+ * Register as a plugin with Radiant.
+ *
+ * @param server Radiant's module (library) manager.
+ *
+ * @relatesalso MeshTexPluginDependencies
+ */
+
+ typedef SingletonModule<PluginModule, MeshTexPluginDependencies> SingletonMeshTexPluginModule;
+
+SingletonMeshTexPluginModule g_MeshTexPluginModule;
+
+extern "C" void RADIANT_DLLEXPORT
+Radiant_RegisterModules(ModuleServer& server)
+{
+   // Set ourselves up as a plugin with the necessary dependences.
+   //static SingletonModule<PluginModule, MeshTexPluginDependencies> singleton;
+   // Alert Radiant that there's at least one module to be managed, and
+   // initialize some necessary stuff.
+   // XXX As far as I can tell it's not necessary for EVERY library to do this
+   // but it doesn't hurt, and this way we ensure it gets done.
+   initialiseModule(server);
+   // Register this library. Now we're active as a plugin.
+   g_MeshTexPluginModule.selfRegister();
+}
diff --git a/contrib/meshtex/PluginUI.cpp b/contrib/meshtex/PluginUI.cpp
new file mode 100644 (file)
index 0000000..fcbeef4
--- /dev/null
@@ -0,0 +1,99 @@
+/**
+ * @file PluginUI.cpp
+ * Implements the PluginUI class.
+ * @ingroup meshtex-ui
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "PluginUI.h"
+#include "SetScaleDialog.h"
+#include "GetInfoDialog.h"
+#include "GeneralFunctionDialog.h"
+#include "MainMenu.h"
+
+
+/**
+ * Default constructor. Instantiate and register the UI elements (main menu
+ * and dialogs)
+ */
+PluginUI::PluginUI()
+{
+       PluginUI::singleton = this;
+   // Instantiate and register the Set S/T Scale dialog. We need a non-generic
+   // handle on this one too, because it will be used as input to the Get Info
+   // dialog constructor below.
+   SmartPointer<SetScaleDialog> setScaleDialog(
+      new SetScaleDialog("SetScale"));
+   SmartPointer<GenericDialog> setScaleDialogGeneric(setScaleDialog);
+   RegisterDialog(setScaleDialogGeneric);
+   // Instantiate and register the Get Info dialog. Constructor needs a handle
+   // on the Set S/T Scale dialog (since it may need to send texture info to
+   // it).
+   SmartPointer<GenericDialog> getInfoDialogGeneric(
+      new GetInfoDialog("GetInfo", setScaleDialog));
+   RegisterDialog(getInfoDialogGeneric);
+   // Instantiate and register the General Function dialog.
+   SmartPointer<GenericDialog> genFuncDialogGeneric(
+      new GeneralFunctionDialog("GeneralFunction"));
+   RegisterDialog(genFuncDialogGeneric);
+   // Instantiate and register the main menu. Constructor needs generic
+   // handles on all dialogs (since it may need to raise them).
+   SmartPointer<GenericMainMenu> mainMenuGeneric(
+      new ::MainMenu(setScaleDialogGeneric,
+                     getInfoDialogGeneric,
+                     genFuncDialogGeneric));
+   RegisterMainMenu(mainMenuGeneric);
+}
+
+/**
+ * Destructor.
+ */
+PluginUI::~PluginUI()
+{
+}
+
+PluginUI* PluginUI::singleton = 0;
+/**
+ * Get the singleton instance of the UI manager. Note that callers should
+ * almost certainly invoke the UIInstance global function instead of using
+ * this method.
+ *
+ * @return Handle to the UI manager instance.
+ */
+PluginUI&
+PluginUI::Instance()
+{
+   //static PluginUI singleton;
+   //return singleton;
+    if(!singleton)
+            singleton = new PluginUI();
+        return *singleton;
+}
+
+/**
+ * Get the singleton instance of the UI manager.
+ *
+ * @return Reference to a singleton that implements GenericPluginUI.
+ */
+GenericPluginUI& UIInstance()
+{
+   return PluginUI::Instance();
+}
diff --git a/contrib/meshtex/PluginUI.h b/contrib/meshtex/PluginUI.h
new file mode 100644 (file)
index 0000000..9dcb084
--- /dev/null
@@ -0,0 +1,61 @@
+/**
+ * @file PluginUI.h
+ * Declares the PluginUI class.
+ * @ingroup meshtex-ui
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(INCLUDED_PLUGINUI_H)
+#define INCLUDED_PLUGINUI_H
+
+#include "GenericPluginUI.h"
+
+/**
+ * Subclass of GenericPluginUI that instantiates and registers the UI
+ * elements (main menu and dialogs).
+ *
+ * @ingroup meshtex-ui
+ */
+class PluginUI : public GenericPluginUI
+{
+private: // private methods
+
+   /// @name Private to prevent external instantiation
+   //@{
+   PluginUI();
+   ~PluginUI();
+   //@}
+   // C++ 03
+   // ========
+   // Dont forget to declare these two. You want to make sure they
+   // are unacceptable otherwise you may accidentally get copies of
+   // your singleton appearing.
+   PluginUI(PluginUI const&);              // Don't Implement
+   void operator=(PluginUI const&); // Don't implement
+
+   static PluginUI* singleton;
+
+public: // public methods
+
+   static PluginUI& Instance();
+};
+
+#endif // #if !defined(INCLUDED_PLUGINUI_H)
diff --git a/contrib/meshtex/PluginUIMessages.h b/contrib/meshtex/PluginUIMessages.h
new file mode 100644 (file)
index 0000000..7d8066d
--- /dev/null
@@ -0,0 +1,92 @@
+/**
+ * @file PluginUIMessages.h
+ * String constants for messages shown in dialogs.
+ * @ingroup meshtex-ui
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(INCLUDED_PLUGINUIMESSAGES_H)
+#define INCLUDED_PLUGINUIMESSAGES_H
+
+#include "GenericPluginUIMessages.h"
+#include "PluginProperties.h"
+
+/// @name Window titles
+//@{
+#define DIALOG_GET_INFO_TITLE "Get Info"
+#define DIALOG_MESH_INFO_TITLE "Mesh Info"
+#define DIALOG_SET_SCALE_TITLE "Set S/T Scale"
+#define DIALOG_GEN_FUNC_TITLE "General Function"
+#define DIALOG_ABOUT_TITLE "About"
+#define DIALOG_HELP_TITLE "Help"
+//@}
+
+/// @name Popups
+//@{
+#define DIALOG_MULTIMESHES_ERROR "Must select only one patch mesh for this function."
+#define DIALOG_NOMESHES_MSG "No valid patch meshes selected."
+#define DIALOG_ABOUT_MSG PLUGIN_NAME " " PLUGIN_VERSION "\n\n" PLUGIN_DESCRIPTION "\n\n" PLUGIN_AUTHOR " (" PLUGIN_AUTHOR_EMAIL ")"
+#define DIALOG_HELP_MSG "The Set S/T Scale, Get Info, and General Function dialogs will affect patch meshes that are selected when OK or Apply is clicked. For the other menu options, select the mesh(es) before selecting the option."
+//@}
+
+/// @name Get Info
+//@{
+#define DIALOG_GET_INFO_S_ROW_HEADER " S ref row:"
+#define DIALOG_GET_INFO_T_COL_HEADER " T ref col:"
+#define DIALOG_GET_INFO_XFER_OPT_LABEL "Transfer reference scale to Set S/T Scale"
+//@}
+
+/// @name Set S/T Scale
+//@{
+#define DIALOG_SET_SCALE_S_ACTIVE_OPT_LABEL "Set S"
+#define DIALOG_SET_SCALE_T_ACTIVE_OPT_LABEL "Set T"
+#define DIALOG_SET_SCALE_METHOD_FRAME_TITLE "Scaling"
+#define DIALOG_SET_SCALE_TILES_OPT_LABEL "# Tiles"
+#define DIALOG_SET_SCALE_NATURAL_OPT_LABEL "Natural *"
+#define DIALOG_SET_SCALE_MAX_OPT_LABEL "Max"
+#define DIALOG_SET_SCALE_S_ALIGN_FRAME_TITLE "\"Zero\" col"
+#define DIALOG_SET_SCALE_T_ALIGN_FRAME_TITLE "\"Zero\" row"
+#define DIALOG_SET_SCALE_S_REF_ROW_OPT_LABEL "Use reference row"
+#define DIALOG_SET_SCALE_T_REF_COL_OPT_LABEL "Use reference col"
+#define DIALOG_SET_SCALE_REF_TOTAL_OPT_LABEL "Total length only"
+//@}
+
+/// @name General Function
+//@{
+#define DIALOG_GEN_FUNC_SURFACE_VALUES "Surface values"
+#define DIALOG_GEN_FUNC_CONTROL_VALUES "Control values"
+#define DIALOG_GEN_FUNC_S_FUNC_LABEL " S =  "
+#define DIALOG_GEN_FUNC_T_FUNC_LABEL " T =  "
+#define DIALOG_GEN_FUNC_OLD_S_LABEL " * old_S  + "
+#define DIALOG_GEN_FUNC_OLD_T_LABEL " * old_T  + "
+#define DIALOG_GEN_FUNC_ROW_DIST_LABEL " * row_dist  + "
+#define DIALOG_GEN_FUNC_COL_DIST_LABEL " * col_dist  + "
+#define DIALOG_GEN_FUNC_ROW_NUM_LABEL " * row_num  + "
+#define DIALOG_GEN_FUNC_COL_NUM_LABEL " * col_num  + "
+#define DIALOG_GEN_FUNC_MAX_OPT_LABEL DIALOG_SET_SCALE_MAX_OPT_LABEL
+#define DIALOG_GEN_FUNC_COL_ALIGN_FRAME_LABEL DIALOG_SET_SCALE_S_ALIGN_FRAME_TITLE
+#define DIALOG_GEN_FUNC_ROW_ALIGN_FRAME_LABEL DIALOG_SET_SCALE_T_ALIGN_FRAME_TITLE
+#define DIALOG_GEN_FUNC_REF_ROW_FRAME_LABEL DIALOG_SET_SCALE_S_REF_ROW_OPT_LABEL " (for distances)"
+#define DIALOG_GEN_FUNC_REF_COL_FRAME_LABEL DIALOG_SET_SCALE_T_REF_COL_OPT_LABEL " (for distances)"
+#define DIALOG_GEN_FUNC_REF_TOTAL_OPT_LABEL DIALOG_SET_SCALE_REF_TOTAL_OPT_LABEL
+//@}
+
+#endif // #if !defined(INCLUDED_PLUGINUIMESSAGES_H)
diff --git a/contrib/meshtex/README.md b/contrib/meshtex/README.md
new file mode 100644 (file)
index 0000000..a47d9e9
--- /dev/null
@@ -0,0 +1,7 @@
+MeshTex is a plugin for GtkRadiant 1.5.  More interesting user documentation at [neogeographica.com](http://neogeographica.com/).
+
+Doxygen-generated code documentation is in the docs/html folder; viewable online at [rawgithub.com](https://rawgithub.com/neogeographica/MeshTex/master/docs/html/index.html).
+
+Build instructions are in the COMPILING file.
+
+MeshTex is licensed under the GNU GPL v2.  The LICENSE file contains a copy of the GPL.
diff --git a/contrib/meshtex/RefCounted.cpp b/contrib/meshtex/RefCounted.cpp
new file mode 100644 (file)
index 0000000..25270b7
--- /dev/null
@@ -0,0 +1,63 @@
+/**
+ * @file RefCounted.cpp
+ * Implements the RefCounted class.
+ * @ingroup generic-util
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "RefCounted.h"
+
+
+/**
+ * Default constructor. Initialize reference count to zero.
+ */
+RefCounted::RefCounted() :
+   _refCount(0)
+{
+}
+
+/**
+ * Virtual destructor.
+ */
+RefCounted::~RefCounted()
+{
+}
+
+/**
+ * Increment reference count.
+ */
+void
+RefCounted::IncRef()
+{
+   ++_refCount;
+}
+
+/**
+ * Decrement reference count, and self-delete if count is <= 0.
+ */
+void
+RefCounted::DecRef()
+{
+   if (--_refCount <= 0)
+   {
+      delete this;
+   }
+}
diff --git a/contrib/meshtex/RefCounted.h b/contrib/meshtex/RefCounted.h
new file mode 100644 (file)
index 0000000..0f10848
--- /dev/null
@@ -0,0 +1,62 @@
+/**
+ * @file RefCounted.h
+ * Declares the RefCounted class.
+ * @ingroup generic-util
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(INCLUDED_REFCOUNTED_H)
+#define INCLUDED_REFCOUNTED_H
+
+/**
+ * A mixin for maintaining a reference count associated with an object, and
+ * destroying that object when the count falls to zero. Since this class
+ * implements the IncRef and DecRef interfaces used by the Radiant
+ * SmartPointer and SmartReference templates, those templates can
+ * automatically handle the reference counting for classes that inherit from
+ * RefCounted.
+ *
+ * @ingroup generic-util
+ */
+class RefCounted
+{
+public: // public methods
+
+   /// @name Lifecycle
+   //@{
+   RefCounted();
+   virtual ~RefCounted();
+   //@}
+   /// @name Reference counting
+   //@{
+   void IncRef();
+   void DecRef();
+   //@}
+
+private: // private member vars
+
+   /**
+    * Reference count.
+    */
+   int _refCount;
+};
+
+#endif // #if !defined(INCLUDED_REFCOUNTED_H)
\ No newline at end of file
diff --git a/contrib/meshtex/SetScaleDialog.cpp b/contrib/meshtex/SetScaleDialog.cpp
new file mode 100644 (file)
index 0000000..2634578
--- /dev/null
@@ -0,0 +1,704 @@
+/**
+ * @file SetScaleDialog.cpp
+ * Implements the SetScaleDialog class.
+ * @ingroup meshtex-ui
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gtk/gtk.h>
+
+#include "GenericPluginUI.h"
+#include "SetScaleDialog.h"
+#include "PluginUIMessages.h"
+
+#include "iundo.h"
+
+
+/**
+ * Size of buffer for the text conversion of float values written to various
+ * widgets.
+ */
+#define ENTRY_BUFFER_SIZE 128
+
+
+/**
+ * Constructor.
+ *
+ * @param rowArgs The row (S axis) arguments; NULL if none.
+ * @param colArgs The column (T axis) arguments; NULL if none.
+ */
+SetScaleDialog::SetScaleVisitor::SetScaleVisitor(
+   const SliceArgs *rowArgs,
+   const SliceArgs *colArgs) :
+   _rowArgs(rowArgs),
+   _colArgs(colArgs)
+{
+}
+
+/**
+ * Visitor action; invoke MeshEntity::SetScale on a mesh.
+ *
+ * @param [in,out] meshEntity The mesh entity.
+ *
+ * @return true.
+ */
+bool
+SetScaleDialog::SetScaleVisitor::Execute(MeshEntity& meshEntity) const
+{
+   if (_rowArgs != NULL)
+   {
+      meshEntity.SetScale(MeshEntity::ROW_SLICE_TYPE,
+                          _rowArgs->alignSlice, _rowArgs->refSlice,
+                          _rowArgs->naturalScale, _rowArgs->scaleOrTiles);
+   }
+   if (_colArgs != NULL)
+   {
+      meshEntity.SetScale(MeshEntity::COL_SLICE_TYPE,
+                          _colArgs->alignSlice, _colArgs->refSlice,
+                          _colArgs->naturalScale, _colArgs->scaleOrTiles);
+   }
+   return true;
+}
+
+/**
+ * Constructor. Configure the dialog window and create all the contained
+ * widgets. Connect widgets to callbacks as necessary.
+ *
+ * @param key The unique key identifying this dialog.
+ */
+SetScaleDialog::SetScaleDialog(const std::string& key) :
+   GenericDialog(key),
+   _nullVisitor(new MeshVisitor())
+{
+   // Enable the usual handling of the close event.
+   CreateWindowCloseCallback();
+
+   // Configure the dialog window.
+   gtk_window_set_resizable(GTK_WINDOW(_dialog), FALSE);
+   gtk_window_set_title(GTK_WINDOW(_dialog), DIALOG_SET_SCALE_TITLE);
+   gtk_container_set_border_width(GTK_CONTAINER(_dialog), 10);
+
+   // Create the contained widgets.
+
+   GtkWidget *table;
+   GtkWidget *entry;
+   GtkWidget *applybutton, *refbutton, *button;
+   GtkWidget *label;
+   GtkWidget *mainvbox, *vbox, *hbox;
+   GtkWidget *frame;
+
+   table = gtk_table_new(2, 2, FALSE);
+   gtk_table_set_row_spacing(GTK_TABLE(table), 0, 15);
+   gtk_table_set_col_spacing(GTK_TABLE(table), 0, 10);
+   gtk_container_add(GTK_CONTAINER(_dialog), table);
+   gtk_widget_show(table);
+
+   // Checkbox for the "S" grouping of widgets. All the widgets in that
+   // grouping will have a dependence registered on this checkbox; i.e. they
+   // will only be active when it is checked.
+
+   applybutton = gtk_check_button_new_with_label(DIALOG_SET_SCALE_S_ACTIVE_OPT_LABEL);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "s_apply", applybutton);
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(applybutton), TRUE);
+   gtk_widget_show(applybutton);
+
+   frame = gtk_frame_new(NULL);
+   gtk_frame_set_label_widget(GTK_FRAME(frame), applybutton);
+   gtk_table_attach_defaults(GTK_TABLE(table), frame, 0, 1, 0, 1);
+   gtk_widget_show(frame);
+
+   mainvbox = gtk_vbox_new(FALSE, 0);
+   gtk_container_add(GTK_CONTAINER(frame), mainvbox);
+   gtk_widget_show(mainvbox);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_start(GTK_BOX(mainvbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   // Widgets for specifying S scaling.
+
+   label = gtk_label_new(DIALOG_SET_SCALE_METHOD_FRAME_TITLE);
+   gtk_widget_show(label);
+
+   UIInstance().RegisterWidgetDependence(applybutton, label);
+
+   frame = gtk_frame_new(NULL);
+   gtk_frame_set_label_widget(GTK_FRAME(frame), label);
+   gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 5);
+   gtk_widget_show(frame);
+
+   vbox = gtk_vbox_new(FALSE, 0);
+   gtk_container_add(GTK_CONTAINER(frame), vbox);
+   gtk_widget_show(vbox);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_end(GTK_BOX(vbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   button = gtk_radio_button_new_with_label(NULL, DIALOG_SET_SCALE_TILES_OPT_LABEL);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "s_tiling", button);
+   gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 5);
+   gtk_widget_show(button);
+
+   UIInstance().RegisterWidgetDependence(applybutton, button);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "s_tiles", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "1");
+   gtk_box_pack_end(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
+   gtk_widget_set_usize(entry, 50, -2);
+   gtk_widget_set_sensitive(entry, FALSE);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(applybutton, entry);
+   UIInstance().RegisterWidgetDependence(button, entry);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_end(GTK_BOX(vbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   button = gtk_radio_button_new_with_label(
+      gtk_radio_button_group(GTK_RADIO_BUTTON(button)),
+                             DIALOG_SET_SCALE_NATURAL_OPT_LABEL);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "s_natural", button);
+   gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 5);
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+   gtk_widget_show(button);
+
+   UIInstance().RegisterWidgetDependence(applybutton, button);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "s_scale", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "1");
+   gtk_box_pack_end(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
+   gtk_widget_set_usize(entry, 50, -2);
+   gtk_widget_set_sensitive(entry, TRUE);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(applybutton, entry);
+   UIInstance().RegisterWidgetDependence(button, entry);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_start(GTK_BOX(mainvbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   // Widgets for specifying the alignment column.
+
+   label = gtk_label_new(DIALOG_SET_SCALE_S_ALIGN_FRAME_TITLE);
+   gtk_widget_show(label);
+
+   UIInstance().RegisterWidgetDependence(applybutton, label);
+
+   frame = gtk_frame_new(NULL);
+   gtk_frame_set_label_widget(GTK_FRAME(frame), label);
+   gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 5);
+   gtk_widget_show(frame);
+
+   vbox = gtk_vbox_new(FALSE, 0);
+   gtk_container_add(GTK_CONTAINER(frame), vbox);
+   gtk_widget_show(vbox);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   button = gtk_radio_button_new(NULL);
+   gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 5);
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+   gtk_widget_show(button);
+
+   UIInstance().RegisterWidgetDependence(applybutton, button);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "col_num_align", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "0");
+   gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
+   gtk_widget_set_usize(entry, 25, -2);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(applybutton, entry);
+   UIInstance().RegisterWidgetDependence(button, entry);
+
+   button = gtk_radio_button_new_with_label(
+      gtk_radio_button_group(GTK_RADIO_BUTTON(button)),
+                             DIALOG_SET_SCALE_MAX_OPT_LABEL);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "col_max_align", button);
+   gtk_box_pack_end(GTK_BOX(hbox), button, TRUE, FALSE, 5);
+   gtk_widget_show(button);
+
+   UIInstance().RegisterWidgetDependence(applybutton, button);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_start(GTK_BOX(mainvbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   // Widgets for specifying the reference row & usage.
+
+   refbutton = gtk_check_button_new_with_label(DIALOG_SET_SCALE_S_REF_ROW_OPT_LABEL);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "row_ref", refbutton);
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(refbutton), TRUE);
+   gtk_widget_show(refbutton);
+
+   UIInstance().RegisterWidgetDependence(applybutton, refbutton);
+
+   frame = gtk_frame_new(NULL);
+   gtk_frame_set_label_widget(GTK_FRAME(frame), refbutton);
+   gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 5);
+   gtk_widget_show(frame);
+
+   vbox = gtk_vbox_new(FALSE, 0);
+   gtk_container_add(GTK_CONTAINER(frame), vbox);
+   gtk_widget_show(vbox);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   button = gtk_radio_button_new(NULL);
+   gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 5);
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+   gtk_widget_show(button);
+
+   UIInstance().RegisterWidgetDependence(applybutton, button);
+   UIInstance().RegisterWidgetDependence(refbutton, button);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "row_num_ref", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "0");
+   gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
+   gtk_widget_set_usize(entry, 25, -2);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(applybutton, entry);
+   UIInstance().RegisterWidgetDependence(refbutton, entry);
+   UIInstance().RegisterWidgetDependence(button, entry);
+
+   button = gtk_radio_button_new_with_label(
+      gtk_radio_button_group(GTK_RADIO_BUTTON(button)),
+                             DIALOG_SET_SCALE_MAX_OPT_LABEL);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "row_max_ref", button);
+   gtk_box_pack_end(GTK_BOX(hbox), button, TRUE, FALSE, 5);
+   gtk_widget_show(button);
+
+   UIInstance().RegisterWidgetDependence(applybutton, button);
+   UIInstance().RegisterWidgetDependence(refbutton, button);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_end(GTK_BOX(vbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   button = gtk_check_button_new_with_label(DIALOG_SET_SCALE_REF_TOTAL_OPT_LABEL);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "row_ref_total", button);
+   gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 5);
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+   gtk_widget_show(button);
+
+   UIInstance().RegisterWidgetDependence(applybutton, button);
+   UIInstance().RegisterWidgetDependence(refbutton, button);
+
+   // Checkbox for the "T" grouping of widgets. All the widgets in that
+   // grouping will have a dependence registered on this checkbox; i.e. they
+   // will only be active when it is checked.
+
+   applybutton = gtk_check_button_new_with_label(DIALOG_SET_SCALE_T_ACTIVE_OPT_LABEL);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "t_apply", applybutton);
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(applybutton), TRUE);
+   gtk_widget_show(applybutton);
+
+   frame = gtk_frame_new(NULL);
+   gtk_frame_set_label_widget(GTK_FRAME(frame), applybutton);
+   gtk_table_attach_defaults(GTK_TABLE(table), frame, 1, 2, 0, 1);
+   gtk_widget_show(frame);
+
+   mainvbox = gtk_vbox_new(FALSE, 0);
+   gtk_container_add(GTK_CONTAINER(frame), mainvbox);
+   gtk_widget_show(mainvbox);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_start(GTK_BOX(mainvbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   // Widgets for specifying T scaling.
+
+   label = gtk_label_new(DIALOG_SET_SCALE_METHOD_FRAME_TITLE);
+   gtk_widget_show(label);
+
+   UIInstance().RegisterWidgetDependence(applybutton, label);
+
+   frame = gtk_frame_new(NULL);
+   gtk_frame_set_label_widget(GTK_FRAME(frame), label);
+   gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 5);
+   gtk_widget_show(frame);
+
+   vbox = gtk_vbox_new(FALSE, 0);
+   gtk_container_add(GTK_CONTAINER(frame), vbox);
+   gtk_widget_show(vbox);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_end(GTK_BOX(vbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   button = gtk_radio_button_new_with_label(NULL, DIALOG_SET_SCALE_TILES_OPT_LABEL);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "t_tiling", button);
+   gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 5);
+   gtk_widget_show(button);
+
+   UIInstance().RegisterWidgetDependence(applybutton, button);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "t_tiles", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "1");
+   gtk_box_pack_end(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
+   gtk_widget_set_usize(entry, 50, -2);
+   gtk_widget_set_sensitive(entry, FALSE);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(applybutton, entry);
+   UIInstance().RegisterWidgetDependence(button, entry);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_end(GTK_BOX(vbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   button = gtk_radio_button_new_with_label(
+      gtk_radio_button_group(GTK_RADIO_BUTTON(button)),
+                             DIALOG_SET_SCALE_NATURAL_OPT_LABEL);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "t_natural", button);
+   gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 5);
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+   gtk_widget_show(button);
+
+   UIInstance().RegisterWidgetDependence(applybutton, button);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "t_scale", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "1");
+   gtk_box_pack_end(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
+   gtk_widget_set_usize(entry, 50, -2);
+   gtk_widget_set_sensitive(entry, TRUE);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(applybutton, entry);
+   UIInstance().RegisterWidgetDependence(button, entry);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_start(GTK_BOX(mainvbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   // Widgets for specifying the alignment row.
+
+   label = gtk_label_new(DIALOG_SET_SCALE_T_ALIGN_FRAME_TITLE);
+   gtk_widget_show(label);
+
+   UIInstance().RegisterWidgetDependence(applybutton, label);
+
+   frame = gtk_frame_new(NULL);
+   gtk_frame_set_label_widget(GTK_FRAME(frame), label);
+   gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 5);
+   gtk_widget_show(frame);
+
+   vbox = gtk_vbox_new(FALSE, 0);
+   gtk_container_add(GTK_CONTAINER(frame), vbox);
+   gtk_widget_show(vbox);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   button = gtk_radio_button_new(NULL);
+   gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 5);
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+   gtk_widget_show(button);
+
+   UIInstance().RegisterWidgetDependence(applybutton, button);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "row_num_align", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "0");
+   gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
+   gtk_widget_set_usize(entry, 25, -2);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(applybutton, entry);
+   UIInstance().RegisterWidgetDependence(button, entry);
+
+   button = gtk_radio_button_new_with_label(
+      gtk_radio_button_group(GTK_RADIO_BUTTON(button)),
+                             DIALOG_SET_SCALE_MAX_OPT_LABEL);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "row_max_align", button);
+   gtk_box_pack_end(GTK_BOX(hbox), button, TRUE, FALSE, 5);
+   gtk_widget_show(button);
+
+   UIInstance().RegisterWidgetDependence(applybutton, button);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_start(GTK_BOX(mainvbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   // Widgets for specifying the reference column & usage.
+
+   refbutton = gtk_check_button_new_with_label(DIALOG_SET_SCALE_T_REF_COL_OPT_LABEL);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "col_ref", refbutton);
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(refbutton), TRUE);
+   gtk_widget_show(refbutton);
+
+   UIInstance().RegisterWidgetDependence(applybutton, refbutton);
+
+   frame = gtk_frame_new(NULL);
+   gtk_frame_set_label_widget(GTK_FRAME(frame), refbutton);
+   gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 5);
+   gtk_widget_show(frame);
+
+   vbox = gtk_vbox_new(FALSE, 0);
+   gtk_container_add(GTK_CONTAINER(frame), vbox);
+   gtk_widget_show(vbox);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   button = gtk_radio_button_new(NULL);
+   gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 5);
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+   gtk_widget_show(button);
+
+   UIInstance().RegisterWidgetDependence(applybutton, button);
+   UIInstance().RegisterWidgetDependence(refbutton, button);
+
+   entry = gtk_entry_new();
+   gtk_object_set_data(GTK_OBJECT(_dialog), "col_num_ref", entry);
+   gtk_entry_set_text(GTK_ENTRY(entry), "0");
+   gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
+   gtk_widget_set_usize(entry, 25, -2);
+   gtk_widget_show(entry);
+
+   UIInstance().RegisterWidgetDependence(applybutton, entry);
+   UIInstance().RegisterWidgetDependence(refbutton, entry);
+   UIInstance().RegisterWidgetDependence(button, entry);
+
+   button = gtk_radio_button_new_with_label(
+      gtk_radio_button_group(GTK_RADIO_BUTTON(button)),
+                             DIALOG_SET_SCALE_MAX_OPT_LABEL);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "col_max_ref", button);
+   gtk_box_pack_end(GTK_BOX(hbox), button, TRUE, FALSE, 5);
+   gtk_widget_show(button);
+
+   UIInstance().RegisterWidgetDependence(applybutton, button);
+   UIInstance().RegisterWidgetDependence(refbutton, button);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_box_pack_end(GTK_BOX(vbox), hbox, TRUE, TRUE, 5);
+   gtk_widget_show(hbox);
+
+   button = gtk_check_button_new_with_label(DIALOG_SET_SCALE_REF_TOTAL_OPT_LABEL);
+   gtk_object_set_data(GTK_OBJECT(_dialog), "col_ref_total", button);
+   gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 5);
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+   gtk_widget_show(button);
+
+   UIInstance().RegisterWidgetDependence(applybutton, button);
+   UIInstance().RegisterWidgetDependence(refbutton, button);
+
+   hbox = gtk_hbox_new(FALSE, 0);
+   gtk_table_attach_defaults(GTK_TABLE(table), hbox, 0, 2, 1, 2);
+   gtk_widget_show(hbox);
+
+   // Create Cancel button and hook it to callback.
+
+   button = gtk_button_new_with_label(DIALOG_CANCEL_BUTTON);
+   gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
+   gtk_widget_set_usize(button, 60, -2);
+   gtk_widget_show(button);
+
+   CreateCancelButtonCallback(button);
+
+   // Create Apply button and hook it to callback.
+
+   button = gtk_button_new_with_label(DIALOG_APPLY_BUTTON);
+   gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 10);
+   gtk_widget_set_usize (button, 60, -2);
+   gtk_widget_show(button);
+
+   CreateApplyButtonCallback(button);
+
+   // Create OK button and hook it to callback.
+
+   button = gtk_button_new_with_label(DIALOG_OK_BUTTON);
+   gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
+   gtk_widget_set_usize (button, 60, -2);
+   gtk_widget_show(button);
+
+   CreateOkButtonCallback(button);
+}
+
+/**
+ * Destructor.
+ */
+SetScaleDialog::~SetScaleDialog()
+{
+}
+
+/**
+ * Handler for the Apply logic for this dialog. Apply the specified scaling to
+ * the selected mesh entities.
+ *
+ * @return true if any meshes are selected, false otherwise.
+ */
+bool
+SetScaleDialog::Apply()
+{
+   // Before doing anything, check to see if there are some meshes selected.
+   _nullVisitor->ResetVisitedCount();
+   GlobalSelectionSystem().foreachSelected(*_nullVisitor);
+   if (_nullVisitor->GetVisitedCount() == 0)
+   {
+      // Nope. Warn and bail out.
+      GenericPluginUI::WarningReportDialog(DIALOG_WARNING_TITLE,
+                                           DIALOG_NOMESHES_MSG);
+      return false;
+   }
+
+   // See if we're going to be affecting the S and/or T texture axis.
+   bool sApply = NamedToggleWidgetActive("s_apply");
+   bool tApply = NamedToggleWidgetActive("t_apply");
+
+   if (!sApply && !tApply)
+   {
+      // Not affecting either, so bail out.
+      return true;
+   }
+
+   // OK read the remaining info from the widgets.
+
+   MeshEntity::SliceDesignation alignCol, alignRow;
+   MeshEntity::RefSliceDescriptor refRow, refCol;
+   SetScaleVisitor::SliceArgs row, col;
+   SetScaleVisitor::SliceArgs *rowArgs = NULL;
+   SetScaleVisitor::SliceArgs *colArgs = NULL;
+   if (sApply)
+   {
+      // S axis is affected, so read the S info.
+      row.naturalScale = NamedToggleWidgetActive("s_natural");
+      if (row.naturalScale)
+      {
+         row.scaleOrTiles = (float)atof(NamedEntryWidgetText("s_scale"));
+      }
+      else
+      {
+         row.scaleOrTiles = (float)atof(NamedEntryWidgetText("s_tiles"));
+      }
+      alignCol.maxSlice = NamedToggleWidgetActive("col_max_align");
+      alignCol.index = atoi(NamedEntryWidgetText("col_num_align"));
+      row.alignSlice = &alignCol;
+      row.refSlice = NULL;
+      if (NamedToggleWidgetActive("row_ref"))
+      {
+         // Reference row is specified, so get that info.
+         refRow.designation.maxSlice = NamedToggleWidgetActive("row_max_ref");
+         refRow.designation.index = atoi(NamedEntryWidgetText("row_num_ref"));
+         refRow.totalLengthOnly = NamedToggleWidgetActive("row_ref_total");
+         row.refSlice = &refRow;
+      }
+      rowArgs = &row;
+   }
+   if (tApply)
+   {
+      // T axis is affected, so read the T info.
+      col.naturalScale = NamedToggleWidgetActive("t_natural");
+      if (col.naturalScale)
+      {
+         col.scaleOrTiles = (float)atof(NamedEntryWidgetText("t_scale"));
+      }
+      else
+      {
+         col.scaleOrTiles = (float)atof(NamedEntryWidgetText("t_tiles"));
+      }
+      alignRow.maxSlice = NamedToggleWidgetActive("row_max_align");
+      alignRow.index = atoi(NamedEntryWidgetText("row_num_align"));
+      col.alignSlice = &alignRow;
+      col.refSlice = NULL;
+      if (NamedToggleWidgetActive("col_ref"))
+      {
+         // Reference column is specified, so get that info.
+         refCol.designation.maxSlice = NamedToggleWidgetActive("col_max_ref");
+         refCol.designation.index = atoi(NamedEntryWidgetText("col_num_ref"));
+         refCol.totalLengthOnly = NamedToggleWidgetActive("col_ref_total");
+         col.refSlice = &refCol;
+      }
+      colArgs = &col;
+   }
+
+   // Let Radiant know the name of the operation responsible for the changes
+   // that are about to happen.
+   UndoableCommand undo(_triggerCommand.c_str());
+
+   // Apply the specified scaling to every selected mesh.
+   SmartPointer<SetScaleVisitor> scaleVisitor(new SetScaleVisitor(rowArgs, colArgs));
+   GlobalSelectionSystem().foreachSelected(*scaleVisitor);
+
+   // Done!
+   return true;
+}
+
+/**
+ * Allow an external caller to set some of the S-axis entries.
+ *
+ * @param scale Texture scaling.
+ * @param tiles Texture tiles.
+ */
+void
+SetScaleDialog::PopulateSWidgets(float scale,
+                                 float tiles)
+{
+   // Use the texture info to populate some of our widgets.
+   PopulateEntry("s_scale", scale);
+   PopulateEntry("s_tiles", tiles);
+}
+
+/**
+ * Allow an external caller to set some of the T-axis entries.
+ *
+ * @param scale Texture scaling.
+ * @param tiles Texture tiles.
+ */
+void
+SetScaleDialog::PopulateTWidgets(float scale,
+                                 float tiles)
+{
+   // Use the texture info to populate some of our widgets.
+   PopulateEntry("t_scale", scale);
+   PopulateEntry("t_tiles", tiles);
+}
+
+/**
+ * Populate a text widget with a floating point number.
+ *
+ * @param widgetName Name of the widget.
+ * @param value      The number to write to the widget.
+ */
+void
+SetScaleDialog::PopulateEntry(const char *widgetName,
+                              float value)
+{
+   static char entryBuffer[ENTRY_BUFFER_SIZE + 1] = { 0 };
+   snprintf(entryBuffer, ENTRY_BUFFER_SIZE, "%f", value);
+   gtk_entry_set_text(GTK_ENTRY(NamedWidget(widgetName)), entryBuffer);
+}
diff --git a/contrib/meshtex/SetScaleDialog.h b/contrib/meshtex/SetScaleDialog.h
new file mode 100644 (file)
index 0000000..605cf6b
--- /dev/null
@@ -0,0 +1,110 @@
+/**
+ * @file SetScaleDialog.h
+ * Declares the SetScaleDialog class.
+ * @ingroup meshtex-ui
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(INCLUDED_SETSCALEDIALOG_H)
+#define INCLUDED_SETSCALEDIALOG_H
+
+#include "GenericDialog.h"
+#include "MeshVisitor.h"
+
+/**
+ * Subclass of GenericDialog that implements the window summoned by selecting
+ * the Set S/T Scale menu entry. This window is used to control the scaling
+ * of the S and/or T texture axes.
+ * 
+ * @image html setscale.png
+ *
+ * @ingroup meshtex-ui
+ */
+class SetScaleDialog : public GenericDialog
+{
+private: // private types
+
+   /**
+    * Visitor for applying the chosen scaling to a mesh.
+    */
+   class SetScaleVisitor : public MeshVisitor
+   {
+   public:
+      /**
+       * Encapsulation of all the arguments that can affect either the S or T
+       * texture axis. See MeshEntity::SetScale for details of how these arguments
+       * are interpreted.
+       */
+      typedef struct {
+         /**
+          * Pointer to alignment slice description; if NULL, slice 0 is assumed.
+          */
+         const MeshEntity::SliceDesignation *alignSlice;
+         /**
+          * Pointer to reference slice description, including how to use the reference;
+          * NULL if no reference.
+          */
+         const MeshEntity::RefSliceDescriptor *refSlice;
+         /**
+          * true if naturalScaleOrTiles is a factor of the Radiant natural scale;
+          * false if naturalScaleOrTiles is a number of tiles.
+          */
+         bool naturalScale;
+         /**
+          * Scaling determinant, interpreted according to the naturalScale parameter.
+          */
+         float scaleOrTiles;
+      } SliceArgs;
+   public:
+      SetScaleVisitor(const SliceArgs *rowArgs,
+                      const SliceArgs *colArgs);
+   private:
+      bool Execute(MeshEntity& meshEntity) const;
+   private:
+      const SliceArgs *_rowArgs;
+      const SliceArgs *_colArgs;
+   };
+
+public: // public methods
+
+   SetScaleDialog(const std::string& key);
+   ~SetScaleDialog();
+   bool Apply();
+   void PopulateSWidgets(float scale,
+                         float tiles);
+   void PopulateTWidgets(float scale,
+                         float tiles);
+
+private: // private methods
+
+   void PopulateEntry(const char *widgetName,
+                      float value);
+
+private: // private member vars
+
+   /**
+    * Action-less mesh visitor used purely to count the number of selected mesh
+    * entities.
+    */
+   SmartPointer<MeshVisitor> _nullVisitor;
+};
+
+#endif // #if !defined(INCLUDED_SETSCALEDIALOG_H)
\ No newline at end of file
diff --git a/contrib/meshtex/UtilityMacros.h b/contrib/meshtex/UtilityMacros.h
new file mode 100644 (file)
index 0000000..9f670b7
--- /dev/null
@@ -0,0 +1,43 @@
+/**
+ * @file UtilityMacros.h
+ * Basic macros.
+ * @ingroup generic-util
+ */
+
+/*
+ * Copyright 2012 Joel Baxter
+ *
+ * This file is part of MeshTex.
+ *
+ * MeshTex is free software: you can 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.
+ *
+ * MeshTex is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MeshTex.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(INCLUDED_UTILITYMACROS_H)
+#define INCLUDED_UTILITYMACROS_H
+
+/**
+ * Convert a token to a string at compile-time.
+ *
+ * @param A The token to stringify.
+ */
+#define STRINGIFY(A) #A
+
+/**
+ * Convert a macro's value to a string at compile-time.
+ *
+ * @param A The name of the macro to process.
+ */
+#define STRINGIFY_MACRO(A) STRINGIFY(A)
+
+#endif // #if !defined(INCLUDED_UTILITYMACROS_H)
diff --git a/contrib/meshtex/docs/docs.7z b/contrib/meshtex/docs/docs.7z
new file mode 100644 (file)
index 0000000..21c96bf
Binary files /dev/null and b/contrib/meshtex/docs/docs.7z differ
diff --git a/contrib/meshtex/localdirs.vsprops b/contrib/meshtex/localdirs.vsprops
new file mode 100644 (file)
index 0000000..2458475
--- /dev/null
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioPropertySheet
+       ProjectType="Visual C++"
+       Version="8.00"
+       Name="localdirs"
+       >
+       <UserMacro
+               Name="RadiantExeDir"
+               Value="C:\Program Files (x86)\GtkRadiant 1.5.0"
+               PerformEnvironmentSet="true"
+       />
+       <UserMacro
+               Name="RadiantSrcDir"
+               Value="D:/GtkRadiant 1.5.0 source"
+               PerformEnvironmentSet="true"
+       />
+       <UserMacro
+               Name="StlportDir"
+               Value="D:/STLPort-5.2.1"
+               PerformEnvironmentSet="true"
+       />
+       <UserMacro
+               Name="GtkDir"
+               Value="D:/gtk2-2.24.10"
+               PerformEnvironmentSet="true"
+       />
+       <UserMacro
+               Name="GitDir"
+               Value="C:\Users\Joel Baxter\AppData\Local\GitHub\PortableGit_015aa71ef18c047ce8509ffb2f9e4bb0e3e73f13\cmd"
+       />
+</VisualStudioPropertySheet>
diff --git a/contrib/meshtex/mainpage.dox b/contrib/meshtex/mainpage.dox
new file mode 100644 (file)
index 0000000..b518258
--- /dev/null
@@ -0,0 +1,62 @@
+/**
+ * @mainpage
+ *
+ * @section org Organization
+ *
+ * At a high level this design separates code specific to MeshTex functionality
+ * from code that could apply to Radiant plugins in general, especially plugins
+ * that have similar interface needs (i.e. accept some numbers/choices from user
+ * input and apply to selected objects). Within both of those broad categories,
+ * further separation is enforced between the plugin framework, patch mesh
+ * manipulation, and UI. See the Modules page of this documentation for
+ * specifics.
+ *
+ * @section flow Control Flow
+ *
+ * A Radiant plugin is a library that exports only one function:
+ * Radiant_RegisterModules. This returns a function table that Radiant will
+ * use to identify the plugin, populate the main menu for the plugin, and notify
+ * the plugin when one of those menu entries is selected.
+ *
+ * When a menu entry is selected, the plugin code is sent a string token that
+ * identifies the entry, and it must use that token to decide what to do.
+ *
+ * The MeshTex menu entries lead to three kinds of operations:
+ *   - Raise a popup message dialog with fixed text, such as the Help dialog.
+ *   - Execute an operation on selected patch mesh entities. This operation is
+ *     completely specified by the selected menu entry.
+ *   - Raise a window that accepts further input. When the OK or Apply button is
+ *     clicked on that window, execute an operation on selected patch mesh
+ *     entities, using values read from the window's input widgets as arguments.
+ *
+ * Most MeshTex operations follow this general flow:
+ *   - Create a visitor object that understands how to execute the operation on
+ *     any input that is a valid patch mesh.
+ *   - Instantiate the Radiant class UndoableCommand to identify the operation.
+ *   - Use the Radiant function GlobalSelectionSystem().foreachSelected to
+ *     iterate over the currently selected objects and pass them to the visitor
+ *     object.
+ *   - Destroy the UndoableCommand object (implicitly on scope exit).
+ *
+ * The visitor object follows these steps when processing a mesh:
+ *   - Use the Radiant function GlobalPatchCreator().Patch_undoSave if it is
+ *     about to modify the mesh.
+ *   - Read mesh characteristics and control point data, and possibly modify
+ *     the texture coordinates in the control point data.
+ *   - If the mesh was modified, use the Radiant functions
+ *     GlobalPatchCreator().Patch_controlPointsChanged and
+ *     GlobalPatchCreator().Patch_undoSave.
+ *
+ * The complete list of Radiant systems used by MeshTex is defined in the
+ * MeshTexPluginDependencies class declaration.
+ *
+ * @section external External Dependences
+ *
+ * This version of MeshTex uses types and functions from these other sources:
+ *   - GtkRadiant 1.5: https://github.com/TTimo/GtkRadiant/tree/1.5-release
+ *   - GTK+ 2 stack: http://www.gtk.org/download/index.php
+ *   - STLport 5: http://sourceforge.net/projects/stlport/files/STLport/
+ *
+ * The specific minor versions last used to build MeshTex are GtkRadiant 1.5.0,
+ * GTK+ 2.24.10, and STLport 5.2.1.
+ */
\ No newline at end of file
diff --git a/contrib/meshtex/meshtex.def b/contrib/meshtex/meshtex.def
new file mode 100644 (file)
index 0000000..cac5dd7
--- /dev/null
@@ -0,0 +1,3 @@
+LIBRARY        "meshtex"
+EXPORTS
+       Radiant_RegisterModules @1
\ No newline at end of file
diff --git a/contrib/meshtex/meshtex.rc b/contrib/meshtex/meshtex.rc
new file mode 100644 (file)
index 0000000..1e0eef0
--- /dev/null
@@ -0,0 +1,101 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "afxres.h"
+#include "PluginProperties.h"
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE 
+BEGIN
+    "resource.h\0"
+END
+
+2 TEXTINCLUDE 
+BEGIN
+    "#include ""afxres.h""\r\n"
+    "#include ""PluginProperties.h\0"
+END
+
+3 TEXTINCLUDE 
+BEGIN
+    "\r\0"
+END
+
+#endif    // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION PLUGIN_VERSION_MAJOR_NUMERIC,PLUGIN_VERSION_MINOR_NUMERIC,0,0
+ PRODUCTVERSION PLUGIN_VERSION_MAJOR_NUMERIC,PLUGIN_VERSION_MINOR_NUMERIC,0,0
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x4L
+ FILETYPE 0x2L
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904b0"
+        BEGIN
+            VALUE "CompanyName", " "
+            VALUE "FileDescription", PLUGIN_FILE_DESCRIPTION
+            VALUE "FileVersion", PLUGIN_VERSION
+            VALUE "InternalName", PLUGIN_FILE_BASENAME
+            VALUE "LegalCopyright", "Copyright " PLUGIN_COPYRIGHT_DATE " " PLUGIN_AUTHOR
+            VALUE "OriginalFilename", PLUGIN_FILE_BASENAME ".dll"
+            VALUE "ProductName", PLUGIN_NAME
+            VALUE "ProductVersion", PLUGIN_VERSION
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1200
+    END
+END
+
+#endif    // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif    // not APSTUDIO_INVOKED
+
diff --git a/contrib/meshtex/meshtex.sln b/contrib/meshtex/meshtex.sln
new file mode 100644 (file)
index 0000000..bc754ad
--- /dev/null
@@ -0,0 +1,20 @@
+
+Microsoft Visual Studio Solution File, Format Version 10.00
+# Visual Studio 2008
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "meshtex", "meshtex.vcproj", "{C8DA1D8D-EB09-4367-9023-5DE6FBF52E37}"
+EndProject
+Global
+       GlobalSection(SolutionConfigurationPlatforms) = preSolution
+               Debug|Win32 = Debug|Win32
+               Release|Win32 = Release|Win32
+       EndGlobalSection
+       GlobalSection(ProjectConfigurationPlatforms) = postSolution
+               {C8DA1D8D-EB09-4367-9023-5DE6FBF52E37}.Debug|Win32.ActiveCfg = Debug|Win32
+               {C8DA1D8D-EB09-4367-9023-5DE6FBF52E37}.Debug|Win32.Build.0 = Debug|Win32
+               {C8DA1D8D-EB09-4367-9023-5DE6FBF52E37}.Release|Win32.ActiveCfg = Release|Win32
+               {C8DA1D8D-EB09-4367-9023-5DE6FBF52E37}.Release|Win32.Build.0 = Release|Win32
+       EndGlobalSection
+       GlobalSection(SolutionProperties) = preSolution
+               HideSolutionNode = FALSE
+       EndGlobalSection
+EndGlobal
diff --git a/contrib/meshtex/meshtex.vcproj b/contrib/meshtex/meshtex.vcproj
new file mode 100644 (file)
index 0000000..d21c817
--- /dev/null
@@ -0,0 +1,385 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+       ProjectType="Visual C++"
+       Version="9.00"
+       Name="meshtex"
+       ProjectGUID="{C8DA1D8D-EB09-4367-9023-5DE6FBF52E37}"
+       RootNamespace="meshtex"
+       Keyword="Win32Proj"
+       TargetFrameworkVersion="196613"
+       >
+       <Platforms>
+               <Platform
+                       Name="Win32"
+               />
+       </Platforms>
+       <ToolFiles>
+       </ToolFiles>
+       <Configurations>
+               <Configuration
+                       Name="Debug|Win32"
+                       OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+                       IntermediateDirectory="$(ConfigurationName)"
+                       ConfigurationType="2"
+                       InheritedPropertySheets=".\localdirs.vsprops"
+                       CharacterSet="2"
+                       >
+                       <Tool
+                               Name="VCPreBuildEventTool"
+                               Description="Capturing git commit hash (if known)"
+                               CommandLine="set GitCommit=n/a&#x0D;&#x0A;for /f &quot;delims=&quot; %%a in (&apos;&quot;$(GitDir)\git&quot; rev-parse --short HEAD 2^&gt;NUL&apos;) do (set GitCommit=%%a)&#x0D;&#x0A;echo #define CODE_ID %GitCommit% &gt; &quot;$(ProjectDir)CodeVersion.h&quot;&#x0D;&#x0A;exit /b 0&#x0D;&#x0A;"
+                       />
+                       <Tool
+                               Name="VCCustomBuildTool"
+                       />
+                       <Tool
+                               Name="VCXMLDataGeneratorTool"
+                       />
+                       <Tool
+                               Name="VCWebServiceProxyGeneratorTool"
+                       />
+                       <Tool
+                               Name="VCMIDLTool"
+                       />
+                       <Tool
+                               Name="VCCLCompilerTool"
+                               Optimization="0"
+                               AdditionalIncludeDirectories="$(RadiantSrcDir)/include;$(RadiantSrcDir)/libs;$(StlportDir)/stlport;$(GtkDir)/include/glib-2.0;$(GtkDir)/include/gtk-2.0;$(GtkDir)/lib/glib-2.0/include;$(GtkDir)/lib/gtk-2.0/include;$(GtkDir)/include/cairo;$(GtkDir)/include/pango-1.0;$(GtkDir)/include/atk-1.0;$(GtkDir)/include/gdk-pixbuf-2.0"
+                               PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_USRDLL;MESHTEX_EXPORTS"
+                               StringPooling="true"
+                               MinimalRebuild="true"
+                               ExceptionHandling="0"
+                               BasicRuntimeChecks="3"
+                               RuntimeLibrary="3"
+                               BufferSecurityCheck="false"
+                               UsePrecompiledHeader="0"
+                               WarningLevel="4"
+                               DebugInformationFormat="4"
+                               DisableSpecificWarnings="4100;4127;4512;4996"
+                       />
+                       <Tool
+                               Name="VCManagedResourceCompilerTool"
+                       />
+                       <Tool
+                               Name="VCResourceCompilerTool"
+                       />
+                       <Tool
+                               Name="VCPreLinkEventTool"
+                       />
+                       <Tool
+                               Name="VCLinkerTool"
+                               AdditionalDependencies="glib-2.0.lib gdk-win32-2.0.lib gtk-win32-2.0.lib gobject-2.0.lib"
+                               LinkIncremental="2"
+                               AdditionalLibraryDirectories="$(GtkDir)/lib"
+                               IgnoreDefaultLibraryNames=""
+                               ModuleDefinitionFile="meshtex.def"
+                               GenerateDebugInformation="true"
+                               SubSystem="2"
+                               RandomizedBaseAddress="0"
+                               DataExecutionPrevention="0"
+                               TargetMachine="1"
+                       />
+                       <Tool
+                               Name="VCALinkTool"
+                       />
+                       <Tool
+                               Name="VCManifestTool"
+                       />
+                       <Tool
+                               Name="VCXDCMakeTool"
+                       />
+                       <Tool
+                               Name="VCBscMakeTool"
+                       />
+                       <Tool
+                               Name="VCFxCopTool"
+                       />
+                       <Tool
+                               Name="VCAppVerifierTool"
+                       />
+                       <Tool
+                               Name="VCPostBuildEventTool"
+                               Description="Copy to GtkRadiant plugins directory"
+                               CommandLine="copy /Y &quot;$(TargetDir)$(TargetFileName)&quot; &quot;$(RadiantExeDir)\plugins\$(TargetFileName)&quot;"
+                       />
+               </Configuration>
+               <Configuration
+                       Name="Release|Win32"
+                       OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+                       IntermediateDirectory="$(ConfigurationName)"
+                       ConfigurationType="2"
+                       InheritedPropertySheets=".\localdirs.vsprops"
+                       CharacterSet="2"
+                       WholeProgramOptimization="1"
+                       >
+                       <Tool
+                               Name="VCPreBuildEventTool"
+                               Description="Capturing git commit hash (if known)"
+                               CommandLine="set GitCommit=n/a&#x0D;&#x0A;for /f &quot;delims=&quot; %%a in (&apos;&quot;$(GitDir)\git&quot; rev-parse --short HEAD 2^&gt;NUL&apos;) do (set GitCommit=%%a)&#x0D;&#x0A;echo #define CODE_ID %GitCommit% &gt; &quot;$(ProjectDir)CodeVersion.h&quot;&#x0D;&#x0A;exit /b 0&#x0D;&#x0A;"
+                               ExcludedFromBuild="false"
+                       />
+                       <Tool
+                               Name="VCCustomBuildTool"
+                       />
+                       <Tool
+                               Name="VCXMLDataGeneratorTool"
+                       />
+                       <Tool
+                               Name="VCWebServiceProxyGeneratorTool"
+                       />
+                       <Tool
+                               Name="VCMIDLTool"
+                       />
+                       <Tool
+                               Name="VCCLCompilerTool"
+                               Optimization="4"
+                               InlineFunctionExpansion="2"
+                               EnableIntrinsicFunctions="true"
+                               FavorSizeOrSpeed="1"
+                               AdditionalIncludeDirectories="$(RadiantSrcDir)/include;$(RadiantSrcDir)/libs;$(StlportDir)/stlport;$(GtkDir)/include/glib-2.0;$(GtkDir)/include/gtk-2.0;$(GtkDir)/lib/glib-2.0/include;$(GtkDir)/lib/gtk-2.0/include;$(GtkDir)/include/cairo;$(GtkDir)/include/pango-1.0;$(GtkDir)/include/atk-1.0;$(GtkDir)/include/gdk-pixbuf-2.0"
+                               PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;MESHTEX_EXPORTS"
+                               StringPooling="true"
+                               ExceptionHandling="0"
+                               RuntimeLibrary="2"
+                               BufferSecurityCheck="false"
+                               EnableFunctionLevelLinking="true"
+                               UsePrecompiledHeader="0"
+                               WarningLevel="4"
+                               DebugInformationFormat="3"
+                               DisableSpecificWarnings="4100;4127;4512;4996"
+                       />
+                       <Tool
+                               Name="VCManagedResourceCompilerTool"
+                       />
+                       <Tool
+                               Name="VCResourceCompilerTool"
+                       />
+                       <Tool
+                               Name="VCPreLinkEventTool"
+                       />
+                       <Tool
+                               Name="VCLinkerTool"
+                               AdditionalDependencies="glib-2.0.lib gdk-win32-2.0.lib gtk-win32-2.0.lib gobject-2.0.lib"
+                               LinkIncremental="1"
+                               AdditionalLibraryDirectories="$(GtkDir)/lib"
+                               ModuleDefinitionFile="meshtex.def"
+                               GenerateDebugInformation="true"
+                               SubSystem="2"
+                               OptimizeReferences="2"
+                               EnableCOMDATFolding="2"
+                               RandomizedBaseAddress="0"
+                               DataExecutionPrevention="0"
+                               TargetMachine="1"
+                       />
+                       <Tool
+                               Name="VCALinkTool"
+                       />
+                       <Tool
+                               Name="VCManifestTool"
+                       />
+                       <Tool
+                               Name="VCXDCMakeTool"
+                       />
+                       <Tool
+                               Name="VCBscMakeTool"
+                       />
+                       <Tool
+                               Name="VCFxCopTool"
+                       />
+                       <Tool
+                               Name="VCAppVerifierTool"
+                       />
+                       <Tool
+                               Name="VCPostBuildEventTool"
+                               Description="Copy to GtkRadiant plugins directory"
+                               CommandLine="copy /Y &quot;$(TargetDir)$(TargetFileName)&quot; &quot;$(RadiantExeDir)\plugins\$(TargetFileName)&quot;"
+                       />
+               </Configuration>
+       </Configurations>
+       <References>
+       </References>
+       <Files>
+               <Filter
+                       Name="src"
+                       >
+                       <Filter
+                               Name="generic"
+                               >
+                               <Filter
+                                       Name="util"
+                                       >
+                                       <File
+                                               RelativePath=".\RefCounted.cpp"
+                                               >
+                                       </File>
+                                       <File
+                                               RelativePath=".\RefCounted.h"
+                                               >
+                                       </File>
+                                       <File
+                                               RelativePath=".\UtilityMacros.h"
+                                               >
+                                       </File>
+                               </Filter>
+                               <Filter
+                                       Name="plugin"
+                                       >
+                                       <File
+                                               RelativePath=".\PluginModule.cpp"
+                                               >
+                                       </File>
+                                       <File
+                                               RelativePath=".\PluginModule.h"
+                                               >
+                                       </File>
+                               </Filter>
+                               <Filter
+                                       Name="ui"
+                                       >
+                                       <File
+                                               RelativePath=".\GenericDialog.cpp"
+                                               >
+                                       </File>
+                                       <File
+                                               RelativePath=".\GenericDialog.h"
+                                               >
+                                       </File>
+                                       <File
+                                               RelativePath=".\GenericMainMenu.cpp"
+                                               >
+                                       </File>
+                                       <File
+                                               RelativePath=".\GenericMainMenu.h"
+                                               >
+                                       </File>
+                                       <File
+                                               RelativePath=".\GenericPluginUI.cpp"
+                                               >
+                                       </File>
+                                       <File
+                                               RelativePath=".\GenericPluginUI.h"
+                                               >
+                                       </File>
+                                       <File
+                                               RelativePath=".\GenericPluginUIMessages.h"
+                                               >
+                                       </File>
+                               </Filter>
+                       </Filter>
+                       <Filter
+                               Name="meshtex"
+                               >
+                               <Filter
+                                       Name="util"
+                                       >
+                                       <File
+                                               RelativePath=".\AllocatedMatrix.h"
+                                               >
+                                       </File>
+                                       <File
+                                               RelativePath=".\MeshVisitor.cpp"
+                                               >
+                                       </File>
+                                       <File
+                                               RelativePath=".\MeshVisitor.h"
+                                               >
+                                       </File>
+                               </Filter>
+                               <Filter
+                                       Name="ui"
+                                       >
+                                       <File
+                                               RelativePath=".\GeneralFunctionDialog.cpp"
+                                               >
+                                       </File>
+                                       <File
+                                               RelativePath=".\GeneralFunctionDialog.h"
+                                               >
+                                       </File>
+                                       <File
+                                               RelativePath=".\GetInfoDialog.cpp"
+                                               >
+                                       </File>
+                                       <File
+                                               RelativePath=".\GetInfoDialog.h"
+                                               >
+                                       </File>
+                                       <File
+                                               RelativePath=".\MainMenu.cpp"
+                                               >
+                                       </File>
+                                       <File
+                                               RelativePath=".\MainMenu.h"
+                                               >
+                                       </File>
+                                       <File
+                                               RelativePath=".\PluginUI.cpp"
+                                               >
+                                       </File>
+                                       <File
+                                               RelativePath=".\PluginUI.h"
+                                               >
+                                       </File>
+                                       <File
+                                               RelativePath=".\PluginUIMessages.h"
+                                               >
+                                       </File>
+                                       <File
+                                               RelativePath=".\SetScaleDialog.cpp"
+                                               >
+                                       </File>
+                                       <File
+                                               RelativePath=".\SetScaleDialog.h"
+                                               >
+                                       </File>
+                               </Filter>
+                               <Filter
+                                       Name="plugin"
+                                       >
+                                       <File
+                                               RelativePath=".\PluginProperties.h"
+                                               >
+                                       </File>
+                                       <File
+                                               RelativePath=".\PluginRegistration.cpp"
+                                               >
+                                       </File>
+                               </Filter>
+                               <Filter
+                                       Name="core"
+                                       >
+                                       <File
+                                               RelativePath=".\MeshEntity.cpp"
+                                               >
+                                       </File>
+                                       <File
+                                               RelativePath=".\MeshEntity.h"
+                                               >
+                                       </File>
+                                       <File
+                                               RelativePath=".\MeshEntityMessages.h"
+                                               >
+                                       </File>
+                               </Filter>
+                       </Filter>
+                       <Filter
+                               Name="windows"
+                               >
+                               <File
+                                       RelativePath=".\meshtex.def"
+                                       >
+                               </File>
+                               <File
+                                       RelativePath=".\meshtex.rc"
+                                       >
+                               </File>
+                               <File
+                                       RelativePath=".\resource.h"
+                                       >
+                               </File>
+                       </Filter>
+               </Filter>
+       </Files>
+       <Globals>
+       </Globals>
+</VisualStudioProject>
diff --git a/contrib/meshtex/modules.dox b/contrib/meshtex/modules.dox
new file mode 100644 (file)
index 0000000..d99cedf
--- /dev/null
@@ -0,0 +1,31 @@
+/** @defgroup generic Generic
+ *  Broadly applicable to Radiant plugin implementation.
+ *  @{
+ */
+   /** @defgroup generic-plugin Generic Plugin
+    *  Framework for defining and registering a plugin.
+    */
+   /** @defgroup generic-ui Generic UI
+    *  Framework for implementing and managing a main menu and dialog windows.
+    */
+   /** @defgroup generic-util Generic Util
+    *  Universal utility classes.
+    */
+/** @} */
+/** @defgroup meshtex MeshTex
+ *  Specific to the MeshTex plugin.
+ *  @{
+ */
+   /** @defgroup meshtex-core MeshTex Core
+    *  Logic for manipulating patch mesh entities.
+    */
+   /** @defgroup meshtex-plugin MeshTex Plugin
+    *  Defining and registering the MeshTex plugin.
+    */
+   /** @defgroup meshtex-ui MeshTex UI
+    *  Implementing and managing the MeshTex main menu and dialog windows.
+    */
+   /** @defgroup meshtex-util MeshTex Util
+    *  Utility classes for patch mesh manipulation.
+    */
+/** @} */
\ No newline at end of file
diff --git a/contrib/meshtex/resource.h b/contrib/meshtex/resource.h
new file mode 100644 (file)
index 0000000..4752599
--- /dev/null
@@ -0,0 +1,14 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by meshtex.rc
+
+// Next default values for new objects
+// 
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE        101
+#define _APS_NEXT_COMMAND_VALUE         40001
+#define _APS_NEXT_CONTROL_VALUE         1001
+#define _APS_NEXT_SYMED_VALUE           101
+#endif
+#endif
index 688a66b18f20223bf611af831e12dee113208642..d030b55ee327bfcb05ad012c8c777c2b4cd6f1be 100644 (file)
@@ -29,8 +29,6 @@
 #include "prtview.h"
 #include "portals.h"
 
-ui::Window config_dialog{ui::null};
-
 static void dialog_button_callback( ui::Widget widget, gpointer data ){
        int *loop, *ret;
 
@@ -61,7 +59,9 @@ static int DoColor( PackedColour *c ){
        clr.green = (guint16) (GetBValue(*c) * (65535 / 255));
 
        auto dlg = ui::Widget::from(gtk_color_selection_dialog_new( "Choose Color" ));
-       gtk_window_set_transient_for( GTK_WINDOW( dlg ), config_dialog );
+       gtk_window_set_transient_for( GTK_WINDOW( dlg ), GTK_WINDOW( g_pRadiantWnd ) );
+       gtk_window_set_position( GTK_WINDOW( dlg ),GTK_WIN_POS_CENTER_ON_PARENT );
+       gtk_window_set_modal( GTK_WINDOW( dlg ), TRUE );
        gtk_color_selection_set_current_color( GTK_COLOR_SELECTION( gtk_color_selection_dialog_get_color_selection(GTK_COLOR_SELECTION_DIALOG(dlg)) ), &clr );
        dlg.connect( "delete_event", G_CALLBACK( dialog_delete_callback ), NULL );
        dlg.connect( "destroy", G_CALLBACK( gtk_widget_destroy ), NULL );
@@ -241,12 +241,14 @@ void DoConfigDialog( ui::Window main_window ){
        int loop = 1, ret = IDCANCEL;
        ModalDialog dialog;
 
-       auto dlg = main_window.create_dialog_window( "Portal Viewer Configuration", G_CALLBACK( custom_dialog_delete_callback ), &dialog );
-       
-       dlg.connect( "destroy", G_CALLBACK( gtk_widget_destroy ), NULL );
+       auto dlg = ui::Window( ui::window_type::TOP );
+       gtk_window_set_title( dlg, "Portal Viewer Configuration" );
+       dlg.connect( "delete_event",
+                                               G_CALLBACK( dialog_delete_callback ), NULL );
+       dlg.connect( "destroy",
+                                               G_CALLBACK( gtk_widget_destroy ), NULL );
        g_object_set_data( G_OBJECT( dlg ), "loop", &loop );
        g_object_set_data( G_OBJECT( dlg ), "ret", &ret );
-       config_dialog = dlg;
 
        auto vbox = ui::VBox( FALSE, 5 );
        vbox.show();
index 9ebcd69b916ddba8f24a5eeb35684d4930c3c67a..2d6b4d75fdb70520af1eb96f2e6f4ea2d2e9bddf 100644 (file)
@@ -59,6 +59,10 @@ static void change_clicked(ui::Widget widget, gpointer data ){
                                                                                         GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
                                                                                         nullptr));
 
+       gtk_window_set_transient_for( GTK_WINDOW( file_sel ), GTK_WINDOW( g_pRadiantWnd ) );
+       gtk_window_set_position( GTK_WINDOW( file_sel ),GTK_WIN_POS_CENTER_ON_PARENT );
+       gtk_window_set_modal( GTK_WINDOW( file_sel ), TRUE );
+
        gtk_file_chooser_set_filename( GTK_FILE_CHOOSER(file_sel), portals.fn );
 
        if (gtk_dialog_run (GTK_DIALOG (file_sel)) == GTK_RESPONSE_ACCEPT)
@@ -78,6 +82,11 @@ int DoLoadPortalFileDialog(){
        int loop = 1, ret = IDCANCEL;
 
        auto dlg = ui::Window( ui::window_type::TOP );
+
+       gtk_window_set_transient_for( GTK_WINDOW( dlg ), GTK_WINDOW( g_pRadiantWnd ) );
+       gtk_window_set_position( GTK_WINDOW( dlg ),GTK_WIN_POS_CENTER_ON_PARENT );
+       gtk_window_set_modal( GTK_WINDOW( dlg ), TRUE );
+
        gtk_window_set_title( dlg, "Load .prt" );
        dlg.connect( "delete_event",
                                                G_CALLBACK( dialog_delete_callback ), NULL );
index 14aeec1325bb80feec981cda000f8170d6c21bb0..b99d2086f66b3a37cce1e7be0320ddd1b942f598 100644 (file)
@@ -198,8 +198,10 @@ static const char *PLUGIN_COMMANDS =
        Q3R_CMD_LOAD;
 
 
+ui::Widget g_pRadiantWnd{ui::null};
 
 const char* QERPlug_Init( void *hApp, void* pMainWidget ){
+       g_pRadiantWnd = ui::Window::from(pMainWidget);
        main_window = ui::Window::from(pMainWidget);
        ASSERT_TRUE( main_window );
 
index e6942cc99ea3811b0ad7b6bd94c407c9b7092f68..0028210252742d41e6665b182525ab2dd36fd7db 100644 (file)
@@ -20,6 +20,8 @@
 #if !defined( INCLUDED_PRTVIEW_H )
 #define INCLUDED_PRTVIEW_H
 
+#include <uilib/uilib.h>
+
 #define PLUGIN_NAME "Portal Viewer"
 #define PLUGIN_VERSION "1.0"
 
@@ -31,6 +33,8 @@ void SaveConfig();
 int INIGetInt( const char *key, int def );
 void INISetInt( const char *key, int val, const char *comment = 0 );
 
+extern ui::Widget g_pRadiantWnd;
+
 const int IDOK                = 1;
 const int IDCANCEL            = 2;
 
diff --git a/docs/Blendmodes_cheatsheet.jpg b/docs/Blendmodes_cheatsheet.jpg
new file mode 100644 (file)
index 0000000..b2e4c39
Binary files /dev/null and b/docs/Blendmodes_cheatsheet.jpg differ
index 220649fd9cf2b8d6d0a4cc2a2c074e87913377d1..47020d2d2d4892c4c0c8151beea9765bc4eccdbb 100755 (executable)
@@ -94,8 +94,7 @@ do
 done
 
 declare -a fetch_submodules_cmd
-for submodule_file in 'libs/crunch/inc/crn_decomp.h' \
-       'tools/unvanquished/daemonmap/tools/quake3/q3map2/main.c'
+for submodule_file in 'libs/crunch/inc/crn_decomp.h'
 do
        if ! [ -f "${project_source_dir}/${submodule_file}" ]
        then
diff --git a/flake.nix b/flake.nix
new file mode 100644 (file)
index 0000000..63ed6c5
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,41 @@
+{
+  #description = "a toolset to manage and build `pk3` or `dpk` source directories";
+
+  inputs = {
+    nixpkgs.url = "flake:nixpkgs";
+  };
+
+  outputs = { self, nixpkgs }:
+    let
+      pkgs = nixpkgs.legacyPackages.x86_64-linux;
+    in {
+
+      packages.x86_64-linux.quake-tools =
+        pkgs.stdenv.mkDerivation {
+          name = "quake-tools";
+
+          src = pkgs.lib.cleanSource ./.;
+
+          cmakeFlags = [
+            "-DGIT_VERSION=nix" # meh
+            "-DDOWNLOAD_GAMEPACKS=OFF"
+            "-DBUNDLE_LIBRARIES=OFF"
+            "-DBUILD_CRUNCH=OFF"
+            "-DBUILD_RADIANT=OFF"
+            "-DBUILD_TOOLS=ON"
+            "-DFHS_INSTALL=ON"
+          ];
+
+          buildInputs = with pkgs; [
+            pkg-config gtk2 glib libwebp libxml2 minizip
+          ];
+          nativeBuildInputs = with pkgs; [
+            cmake subversion unzip
+            python3 python38Packages.pyyaml
+          ];
+
+          postInstall = "rm -r $out/share";
+        };
+
+    };
+}
index 980a9a3cfa4c1498a2d2c73599ac363b2bbe6a66..a248987db9f5826f9a71da2b24cd7a18a82ba8a1 100755 (executable)
@@ -40,48 +40,64 @@ cat <<\EOF
 # Obsolete packs                                      #
 #######################################################
 
-# Quake2World was renamed as Quetoo
-# Other gamepacks have better version available
-
-# OpenArena     no   unknown      zip     http://ingar.intranifty.net/files/netradiant/gamepacks/OpenArenaPack.zip
-# Quake         no   proprietary  zip     http://ingar.intranifty.net/files/netradiant/gamepacks/QuakePack.zip
-# Quake2World   no   GPL          svn     svn://jdolan.dyndns.org/quake2world/trunk/gtkradiant
-# Tremulous     no   proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/TremulousPack/branches/1.5/
-# Unvanquished  no   unknown      zip     http://ingar.intranifty.net/gtkradiant/files/gamepacks/UnvanquishedPack.zip
-# Warsow        no   GPL          svn     https://svn.bountysource.com/wswpack/trunk/netradiant/games/WarsowPack/
-# Warsow        no   GPL          zip     http://ingar.intranifty.net/files/netradiant/gamepacks/WarsowPack.zip
+# Quake2World was renamed as Quetoo.
+# JediAcademy and JediOutcast gamepacks are unusable.
+# Other gamepacks have better version available.
+
+# JediAcademy    proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/JAPack/branches/1.5/
+# JediOutcast    proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/JK2Pack/trunk/
+# Kingpin        unknown      zip     http://download.kingpin.info/kingpin/editing/maps/map_editors/NetRadiant/addon/Kingpinpack.zip
+# Neverball      proprietary  zip     http://ingar.intranifty.net/files/netradiant/gamepacks/NeverballPack.zip
+# OpenArena      unknown      zip     http://ingar.intranifty.net/files/netradiant/gamepacks/OpenArenaPack.zip
+# Quake2World    GPL          svn     svn://jdolan.dyndns.org/quake2world/trunk/gtkradiant
+# Quake3         proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/Q3Pack/trunk/ 29
+# Quake          proprietary  zip     http://ingar.intranifty.net/files/netradiant/gamepacks/QuakePack.zip
+# Tremulous      proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/TremulousPack/branches/1.5/
+# Tremulous      proprietary  zip     http://ingar.intranifty.net/files/netradiant/gamepacks/TremulousPack.zip
+# Unvanquished   unknown      zip     http://ingar.intranifty.net/gtkradiant/files/gamepacks/UnvanquishedPack.zip
+# Warfork        GPL          zip     https://cdn.discordapp.com/attachments/611741789237411850/659512520553267201/netradiant_warfork_gamepack.zip
+# Warsow         GPL          svn     https://svn.bountysource.com/wswpack/trunk/netradiant/games/WarsowPack/
+# Warsow         GPL          zip     http://ingar.intranifty.net/files/netradiant/gamepacks/WarsowPack.zip
 
 #######################################################
 # Usable packs                                        #
 #######################################################
 
-AlienArena      yes  GPL          svn     https://svn.code.sf.net/p/alienarena-cc/code/trunk/tools/netradiant_gamepack/AlienArenaPack
-DarkPlaces      yes  GPL          svn     svn://svn.icculus.org/gtkradiant-gamepacks/DarkPlacesPack/branches/1.5/
-Doom3           yes  proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/Doom3Pack/branches/1.5/
-ET              yes  proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/ETPack/branches/1.5/
-Heretic2        yes  proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/Her2Pack/branches/1.5/
-JediAcademy     no   proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/JAPack/branches/1.5/
-Kingpin         yes  unknown      zip     http://download.kingpin.info/kingpin/editing/maps/map_editors/NetRadiant/addon/Kingpinpack.zip
-Neverball       yes  proprietary  zip     http://ingar.intranifty.net/files/netradiant/gamepacks/NeverballPack.zip
-Nexuiz          yes  GPL          gitdir  git://git.icculus.org/divverent/nexuiz.git misc/netradiant-NexuizPack master
-OpenArena       yes  GPL          git     https://github.com/NeonKnightOA/oagamepack.git
-Osirion         yes  GPL          zip     http://ingar.intranifty.net/files/netradiant/gamepacks/OsirionPack.zip
-Prey            yes  proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/PreyPack/trunk/
-Q3              yes  proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/Q3Pack/trunk/ 29
-Q3Rally         yes  proprietary  svn     https://svn.code.sf.net/p/q3rallysa/code/tools/radiant-config/radiant15-netradiant/
-Quake2          yes  proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/Q2Pack/branches/1.5/
-Quake4          yes  proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/Q4Pack/branches/1.5/
-Quake           yes  GPL          zip     http://ingar.intranifty.net/files/netradiant/gamepacks/Quake1Pack.zip
-Quetoo          yes  GPL          svn     svn://svn.icculus.org/gtkradiant-gamepacks/QuetooPack/branches/1.5/
-SmokinGuns      yes  unknown      git     https://github.com/smokin-guns/smokinguns-mapeditor-support.git
-Tremulous       yes  proprietary  zip     http://ingar.intranifty.net/files/netradiant/gamepacks/TremulousPack.zip
-TurtleArena     yes  proprietary  git     https://github.com/Turtle-Arena/turtle-arena-radiant-pack.git
-UFOAI           yes  proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/UFOAIPack/branches/1.5/
-Unvanquished    yes  BSD          git     https://github.com/Unvanquished/unvanquished-mapeditor-support.git
-Warsow          yes  GPL          git     https://github.com/Warsow/NetRadiantPack.git
-Wolf            yes  proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/WolfPack/branches/1.5/
-WoP             yes  proprietary  git     https://github.com/PadWorld-Entertainment/wop-mapeditor-support.git
-Xonotic         yes  GPL          git     https://gitlab.com/xonotic/netradiant-xonoticpack.git
+AlienArena      GPL          svn     https://svn.code.sf.net/p/alienarena-cc/code/trunk/tools/netradiant_gamepack/AlienArenaPack
+DarkPlaces      GPL          svn     svn://svn.icculus.org/gtkradiant-gamepacks/DarkPlacesPack/branches/1.5/
+Doom3           proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/Doom3Pack/branches/1.5/
+ET              proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/ETPack/branches/1.5/
+Heretic2        proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/Her2Pack/branches/1.5/
+JediAcademy     proprietary  git     https://gitlab.com/netradiant/gamepacks/jediacademy-mapeditor-support.git
+JediOutcast     proprietary  git     https://gitlab.com/netradiant/gamepacks/jedioutcast-mapeditor-support.git
+Kingpin         unknown      git     https://gitlab.com/netradiant/gamepacks/kingpin-mapeditor-support.git
+Neverball       proprietary  git     https://gitlab.com/netradiant/gamepacks/neverball-mapeditor-support.git
+Nexuiz          GPL          gitdir  git://git.icculus.org/divverent/nexuiz.git misc/netradiant-NexuizPack master
+OpenArena       GPL          git     https://github.com/NeonKnightOA/oagamepack.git
+Osirion         GPL          zip     http://ingar.intranifty.net/files/netradiant/gamepacks/OsirionPack.zip
+Prey            proprietary  git     https://gitlab.com/netradiant/gamepacks/prey-mapeditor-support.git
+Q3Rally         proprietary  svn     https://svn.code.sf.net/p/q3rallysa/code/tools/radiant-config/radiant15-netradiant/
+Quake2          proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/Q2Pack/branches/1.5/
+Quake3          proprietary  git     https://gitlab.com/netradiant/gamepacks/quake3-mapeditor-support.git
+Quake4          proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/Q4Pack/branches/1.5/
+QuakeLive       proprietary  git     https://gitlab.com/netradiant/gamepacks/quakelive-mapeditor-support.git
+Quake           GPL          zip     http://ingar.intranifty.net/files/netradiant/gamepacks/Quake1Pack.zip
+Quetoo          GPL          svn     svn://svn.icculus.org/gtkradiant-gamepacks/QuetooPack/branches/1.5/
+SmokinGuns      unknown      git     https://github.com/smokin-guns/smokinguns-mapeditor-support.git
+SoF2            unknown      git     https://gitlab.com/netradiant/gamepacks/sof2-mapeditor-support.git
+STVEF           unknown      git     https://gitlab.com/netradiant/gamepacks/stvef-mapeditor-support.git
+Tremulous       proprietary  git     https://gitlab.com/netradiant/gamepacks/tremulous-mapeditor-support.git
+TurtleArena     proprietary  git     https://github.com/Turtle-Arena/turtle-arena-radiant-pack.git
+UFOAI           proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/UFOAIPack/branches/1.5/
+Unvanquished    BSD          git     https://github.com/Unvanquished/unvanquished-mapeditor-support.git
+UrbanTerror     unknown      git     https://gitlab.com/netradiant/gamepacks/urbanterror-mapeditor-support.git
+Warfork         GPL          git     https://gitlab.com/netradiant/gamepacks/warfork-mapeditor-support.git
+Warsow          GPL          git     https://github.com/Warsow/NetRadiantPack.git
+Wolf            proprietary  svn     svn://svn.icculus.org/gtkradiant-gamepacks/WolfPack/branches/1.5/
+WoP             proprietary  git     https://github.com/PadWorld-Entertainment/wop-mapeditor-support.git
+Wrath           GPL          git     https://gitlab.com/netradiant/gamepacks/wrath-mapeditor-support.git
+Xonotic         GPL          git     https://gitlab.com/xonotic/netradiant-xonoticpack.git
+ZEQ2Lite        unknown      git     https://gitlab.com/netradiant/gamepacks/zeq2lite-mapeditor-support.git
 EOF
 }
 
@@ -156,7 +172,7 @@ printGamePackDB () {
 
 printLicenseList () {
        printGamePackDB \
-       | awk '{ print $3 }' \
+       | awk '{ print $2 }' \
        | sort -u
 }
 
@@ -203,7 +219,7 @@ printNameListByLicense () {
        for license in ${license_list}
        do
                printGamePackDB \
-               | awk '$3 == "'"${license}"'"' \
+               | awk '$2 == "'"${license}"'"' \
                | awk '{ print $1 }'
        done | sort -u
 }
@@ -226,9 +242,6 @@ printNameListByName () {
                                name_list="$(printNameList)"
                                break
                                ;;
-                       'validated')
-                               name_list="${name_list} validated"
-                               ;;
                        *)
                                if printNameList | inList "${name}"
                                then
@@ -244,13 +257,6 @@ printNameListByName () {
 
        for name in ${name_list}
        do
-               if [ "${name}" = 'validated' ]
-               then
-                       printGamePackDB \
-                       | awk '$2 == "yes"' \
-                       | awk '{ print $1 }'
-               fi
-
                printGamePackDB \
                | awk '$1 == "'"${name}"'"' \
                | awk '{ print $1 }'
@@ -281,11 +287,12 @@ downloadExtraUrls ()
 {
        if [ -f 'extra-urls.txt' ]
        then
-               while IFS='     ' read -r extra_file extra_url
+               local line
+               while read line
                do
-                       (
-                               ${WGET} -O "${extra_file}" "${extra_url}"
-                       ) </dev/null
+                       local extra_file="$(echo "${line}" | cut -f1 -d$'\t')"
+                       local extra_url="$(echo "${line}" | cut -f2 -d$'\t')"
+                       ${WGET} -O "${extra_file}" "${extra_url}" < /dev/null
                done < 'extra-urls.txt'
        fi
 }
@@ -304,10 +311,9 @@ downloadPack () {
        download_dir="${1}"
        name="${2}"
 
-       validation="$(getValue "${name}" 2)"
-       license="$(getValue "${name}" 3)"
-       source_type="$(getValue "${name}" 4)"
-       source_url="$(getValue "${name}" 5)"
+       license="$(getValue "${name}" 2)"
+       source_type="$(getValue "${name}" 3)"
+       source_url="$(getValue "${name}" 4)"
 
        pack="${name}${pack_suffix}"
 
@@ -331,7 +337,7 @@ downloadPack () {
 
                case "${source_type}" in
                        'svn')
-                               reference="$(getValue "${name}" 6)"
+                               reference="$(getValue "${name}" 5)"
                                if [ -z "${reference}" ]
                                then
                                        reference='HEAD'
@@ -372,8 +378,8 @@ downloadPack () {
                                ${RM_R} 'zipdownload'
                                ;;
                        'gitdir')
-                               local subdir="$(getValue "${name}" 6)"
-                               local branch="$(getValue "${name}" 7)"
+                               local subdir="$(getValue "${name}" 5)"
+                               local branch="$(getValue "${name}" 6)"
                                ${RM_R} "${pack}"
                                ${GIT} archive --remote="${source_url}" --prefix="${pack}/" "${branch}":"${subdir}" \
                                | ${TAR} xvf -
@@ -439,24 +445,18 @@ installPack () {
 
        # Some per-game workaround for malformed gamepack
        case "${name}" in
-               'JediAcademy')
-                       pack="${pack}/Tools"
-                       ;;
-               'Prey'|'Q3')
-                       pack="${pack}/tools"
-                       ;;
                'Wolf')
                        pack="${pack}/bin"
                        ;;
        esac
 
+       # Game packs built with mkeditorpacks
        if [ -d "${download_dir}/${pack}/build/netradiant" ]
        then
-               # mkeditorpacks-based gamepack
                pack="${pack}/build/netradiant"
        elif [ -d "${download_dir}/${pack}/netradiant" ]
+       # Other known layout
        then
-               # other layout
                pack="${pack}/netradiant"
        fi
 
@@ -524,7 +524,7 @@ printHelp () {
        SELECTIONS:
        ${tab}-n, --name NAMES…
        ${tab}${tab}select games by name (default: none)
-       ${tab}${tab}special keyword: validated, all, none
+       ${tab}${tab}special keyword: all, none
        ${tab}${tab}available games:
        $(printNameList | ${SED} -e 's/^/\t\t\t/')
 
@@ -557,8 +557,8 @@ printHelp () {
        ${tab}${prog_name} --license GPL BSD --list-selected
        ${tab}${prog_name} --license GPL BSD --download --install
 
-       ${tab}${prog_name} --name validated --list-selected
-       ${tab}${prog_name} --name validated --download --install
+       ${tab}${prog_name} --name all --list-selected
+       ${tab}${prog_name} --name all --download --install
 
        EOF
 
diff --git a/icons/mime/map.xml b/icons/mime/map.xml
deleted file mode 100644 (file)
index f206bdb..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
-    <mime-type type="model/map">
-    <generic-icon name="text-x-generic"/>
-    <sub-class-of type="text/plain"/>
-    <comment>Game level source</comment>
-    <glob pattern="*.map"/>
-  </mime-type>
-</mime-info>
diff --git a/icons/x-netradiant-map.xml b/icons/x-netradiant-map.xml
new file mode 100644 (file)
index 0000000..60ff0c8
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
+    <mime-type type="application/x-netradiant-map">
+    <generic-icon name="text-x-generic"/>
+    <sub-class-of type="text/plain"/>
+    <comment>Game level source</comment>
+    <glob pattern="*.map"/>
+  </mime-type>
+</mime-info>
index dd3f253c8082be09116f28f25c213f776adae627..797e00a196da857ddb4ff91facb436d7bdd3fdbe 100644 (file)
@@ -127,6 +127,8 @@ virtual void setLightRadii( bool lightRadii ) = 0;
 virtual bool getLightRadii() const = 0;
 virtual void setShowNames( bool showNames ) = 0;
 virtual bool getShowNames() = 0;
+virtual void setShowTargetNames( bool showNames ) = 0;
+virtual bool getShowTargetNames() = 0;
 virtual void setShowAngles( bool showAngles ) = 0;
 virtual bool getShowAngles() = 0;
 
index 7efbb8a9c878eee60bf753db0753ed1ef9d2de87..f75a217a4de23a13ce1fccbdb03608fd53442c03 100644 (file)
@@ -47,6 +47,7 @@ enum
        EXCLUDE_BOTCLIP          = 0x00040000,
        EXCLUDE_VISPORTALS       = 0x00080000,
        EXCLUDE_DECALS           = 0x00100000,
+       EXCLUDE_FUNC_GROUPS       = 0x00200000,
 };
 
 class Filter
index 9207271e1c4c52a6bc8f8557ab03cb7bb2ef3c96..05bad4483eed05b9e58e0d05aff7fb965dc7f4dc 100644 (file)
@@ -121,10 +121,11 @@ virtual void addSelectionChangeCallback( const SelectionChangeHandler& handler )
 virtual void NudgeManipulator( const Vector3& nudge, const Vector3& view ) = 0;
 
 virtual void translateSelected( const Vector3& translation ) = 0;
-virtual void rotateSelected( const Quaternion& rotation ) = 0;
+virtual void rotateSelected( const Quaternion& rotation, bool snapOrigin ) = 0;
 virtual void scaleSelected( const Vector3& scaling ) = 0;
 
 virtual void pivotChanged() const = 0;
+virtual void setCustomPivotOrigin( Vector3& point ) const = 0;
 };
 
 #include "modulesystem.h"
index b580e2c4c1b2aa33b7998d17d6a364ff6c65aaa2..5539a1441dd04379bb11a676f54222e9fa79ad48 100644 (file)
@@ -31,14 +31,13 @@ enum
        QER_NOCARVE = 1 << 1,
        QER_NODRAW = 1 << 2,
        QER_NONSOLID = 1 << 3,
-       QER_WATER = 1 << 4,
-       QER_LAVA = 1 << 5,
-       QER_FOG = 1 << 6,
-       QER_ALPHATEST = 1 << 7,
-       QER_CULL = 1 << 8,
-       QER_AREAPORTAL = 1 << 9,
-       QER_CLIP = 1 << 10,
-       QER_BOTCLIP = 1 << 11,
+       QER_LIQUID = 1 << 4,
+       QER_FOG = 1 << 5,
+       QER_ALPHATEST = 1 << 6,
+       QER_CULL = 1 << 7,
+       QER_AREAPORTAL = 1 << 8,
+       QER_CLIP = 1 << 9,
+       QER_BOTCLIP = 1 << 10,
 };
 
 struct qtexture_t;
index b08b9c89c0e2b71cf7dbddb7c8ff89bcbbe5c0b1..e9a7ccddf12d1eef487ed2cb93015a2ea0eb7310 100755 (executable)
@@ -39,7 +39,7 @@ Common::getPath () {
                if [ "${file_path}" = '-' ]
                then
                        tr '\n' '\0' \
-                       | xargs -0 -n1 -P1 -I{} \
+                       | xargs -0 -P1 -I{} \
                                cygpath --unix '{}'
                else
                        cygpath --unix "${file_path}"
@@ -105,7 +105,7 @@ Multi::excludeLdd () {
                                        Common::noOp
                                # FreeBSD specific
                                elif echo "${ldd_line}" \
-                                       | egrep -q '/libc++|/libgxxrt'
+                                       | egrep -q '/libc\+\+|/libgxxrt'
                                then
                                        Common::noOp
                                else
@@ -176,17 +176,17 @@ Multi::getRootPrefix () {
        local lib_file="${1}"
 
        case "${system_name}" in
-               'linux'|'freebsd')
-                       echo "${lib_file}" \
-                       | cut -f2 -d'/'
+               'linux')
+                       echo 'usr'
+                       ;;
+               'freebsd'|'macos')
+                       echo 'usr/local'
                        ;;
                'windows')
                        basename "${lib_file}" \
                        | xargs -n1 -P1 which \
                        | cut -f2 -d'/'
                        ;;
-               'macos')
-                       echo 'usr/local'
        esac
 }
 
@@ -448,7 +448,7 @@ Windows::listLibForManifest () {
                -name '*.dll' \
                -exec basename {} \; \
        | tr '\n' '\0' \
-       | xargs -0 -n1 -P1 -I{} \
+       | xargs -0 -P1 -I{} \
                printf '  <file name="{}"/>\n'
 }
 
index a306996a019d2c2384435702261d9aa25d74a03d..f0c157f530b4abddf77028280930bf3a6b3cdfdc 100644 (file)
@@ -29,6 +29,7 @@ add_subdirectory(signal)
 add_subdirectory(splines)
 add_subdirectory(stream)
 add_subdirectory(string)
+add_subdirectory(transformpath)
 add_subdirectory(xml)
 
 add_library(libs
index 86585e543b9934b4b4be129ea370bd153aa62080..9c25f6c4b4827bd6f2b70981cd68ddbc31ecdc6c 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 86585e543b9934b4b4be129ea370bd153aa62080
+Subproject commit 9c25f6c4b4827bd6f2b70981cd68ddbc31ecdc6c
index 80eca88c62457f699f813b1d7cce4f00b93a0366..1d6df92b721ba88411df3b0e9549298e07fa7353 100644 (file)
@@ -27,6 +27,7 @@ add_library(gtkutil STATIC
 target_include_directories(gtkutil PRIVATE uilib)
 target_link_libraries(gtkutil PRIVATE uilib)
 
+find_package(GTK${GTK_TARGET} REQUIRED)
 target_include_directories(gtkutil PRIVATE ${GTK${GTK_TARGET}_INCLUDE_DIRS})
 target_link_libraries(gtkutil PRIVATE ${GTK${GTK_TARGET}_LIBRARIES})
 
index 414a8eac1c8246792bcbb272283972781d12f054..b5aa9531671fcbb08fe3933dd73d34cf8c22d25f 100644 (file)
@@ -329,14 +329,14 @@ AcceleratorMap g_keydown_accelerators;
 AcceleratorMap g_keyup_accelerators;
 
 bool Keys_press( PressedKeys::Keys& keys, guint keyval ){
-       if ( keys.insert( keyval ).second ) {
+       if ( keys.insert( gdk_keyval_to_upper( keyval ) ).second ) {
                return AcceleratorMap_activate( g_keydown_accelerators, accelerator_for_event_key( keyval, 0 ) );
        }
        return g_keydown_accelerators.find( accelerator_for_event_key( keyval, 0 ) ) != g_keydown_accelerators.end();
 }
 
 bool Keys_release( PressedKeys::Keys& keys, guint keyval ){
-       if ( keys.erase( keyval ) != 0 ) {
+       if ( keys.erase( gdk_keyval_to_upper( keyval ) ) != 0 ) {
                return AcceleratorMap_activate( g_keyup_accelerators, accelerator_for_event_key( keyval, 0 ) );
        }
        return g_keyup_accelerators.find( accelerator_for_event_key( keyval, 0 ) ) != g_keyup_accelerators.end();
@@ -352,7 +352,9 @@ void Keys_releaseAll( PressedKeys::Keys& keys, guint state ){
 
 gboolean PressedKeys_key_press(ui::Widget widget, GdkEventKey* event, PressedKeys* pressedKeys ){
        //globalOutputStream() << "pressed: " << event->keyval << "\n";
-       return event->state == 0 && Keys_press( pressedKeys->keys, event->keyval );
+       //return event->state == 0 && Keys_press( pressedKeys->keys, event->keyval );
+       //NumLock perspective window fix
+       return ( event->state & ALLOWED_MODIFIERS ) == 0 && Keys_press( pressedKeys->keys, event->keyval );
 }
 
 gboolean PressedKeys_key_release(ui::Widget widget, GdkEventKey* event, PressedKeys* pressedKeys ){
@@ -499,10 +501,10 @@ void global_accel_connect_window( ui::Window window ){
        unsigned int override_handler = window.connect( "key_press_event", G_CALLBACK( override_global_accelerators ), 0 );
        g_object_set_data( G_OBJECT( window ), "override_handler", gint_to_pointer( override_handler ) );
 
+       GlobalPressedKeys_connect( window );
+
        unsigned int special_key_press_handler = window.connect( "key_press_event", G_CALLBACK( accelerator_key_event ), &g_special_accelerators );
        g_object_set_data( G_OBJECT( window ), "special_key_press_handler", gint_to_pointer( special_key_press_handler ) );
-
-       GlobalPressedKeys_connect( window );
 #else
        unsigned int key_press_handler = window.connect( "key_press_event", G_CALLBACK( accelerator_key_event ), &g_keydown_accelerators );
        unsigned int key_release_handler = window.connect( "key_release_event", G_CALLBACK( accelerator_key_event ), &g_keyup_accelerators );
index fd11c7799844b2600ae79e033a670612597a6ec1..445fb18361e34bcc230a886a89dd84acaf24b505 100644 (file)
 #include <gdk/gdk.h>
 #include <gtk/gtk.h>
 
+// Note: NetRadiantCustom disables them but we still make use of them.
+#if 1
+/* Note: here is an alternative implementation,
+it may be useful to try it on platforms were
+       gdk_cursor_new(GDK_BLANK_CURSOR)
+does not work:
+
+GdkCursor* create_blank_cursor(){
+       GdkPixmap *pixmap;
+       GdkBitmap *mask;
+       char buffer [( 32 * 32 ) / 8];
+       memset( buffer, 0, ( 32 * 32 ) / 8 );
+       GdkColor white = {0, 0xffff, 0xffff, 0xffff};
+       GdkColor black = {0, 0x0000, 0x0000, 0x0000};
+       pixmap = gdk_bitmap_create_from_data( 0, buffer, 32, 32 );
+       mask   = gdk_bitmap_create_from_data( 0, buffer, 32, 32 );
+       GdkCursor *cursor = gdk_cursor_new_from_pixmap( pixmap, mask, &white, &black, 1, 1 );
+       gdk_drawable_unref( pixmap );
+       gdk_drawable_unref( mask );
+
+       return cursor;
+}
+*/
 GdkCursor* create_blank_cursor(){
-       return gdk_cursor_new( GDK_BLANK_CURSOR );
+       return gdk_cursor_new(GDK_BLANK_CURSOR);
 }
 
 void set_cursor( ui::Widget widget, GdkCursorType cursor_type ){
        GdkCursor* cursor = gdk_cursor_new( cursor_type );
-       gdk_window_set_cursor( gtk_widget_get_window( widget ), cursor );
+       gdk_window_set_cursor( gtk_widget_get_window(widget), cursor );
        gdk_cursor_unref( cursor );
 }
 
@@ -44,6 +67,7 @@ void blank_cursor( ui::Widget widget ){
 void default_cursor( ui::Widget widget ){
        gdk_window_set_cursor( gtk_widget_get_window( widget ), NULL );
 }
+#endif
 
 void Sys_GetCursorPos( ui::Widget widget, int *x, int *y ){
        GdkDisplay *display = gtk_widget_get_display( GTK_WIDGET( widget ) );
@@ -71,6 +95,13 @@ gboolean FreezePointer::motion_delta(ui::Widget widget, GdkEventMotion *event, F
        Sys_GetCursorPos( widget, &current_x, &current_y );
        int dx = current_x - self->last_x;
        int dy = current_y - self->last_y;
+#if 0 // NetRadiantCustom
+       int ddx = current_x - self->center_x;
+       int ddy = current_y - self->center_y;
+#else
+       int ddx = current_x - self->recorded_x;
+       int ddy = current_y - self->recorded_y;
+#endif
        self->last_x = current_x;
        self->last_y = current_y;
        if ( dx != 0 || dy != 0 ) {
@@ -82,7 +113,6 @@ gboolean FreezePointer::motion_delta(ui::Widget widget, GdkEventMotion *event, F
 
                gdk_window_get_origin( gtk_widget_get_window( widget ), &window_x, &window_y);
 
-
                translated_x = current_x - ( window_x );
                translated_y = current_y - ( window_y );
 
@@ -136,12 +166,16 @@ gboolean FreezePointer::motion_delta(ui::Widget widget, GdkEventMotion *event, F
                        self->last_y = reposition_y;
                }
 #else
-               int ddx = current_x - self->recorded_x;
-               int ddy = current_y - self->recorded_y;
                if (ddx < -32 || ddx > 32 || ddy < -32 || ddy > 32) {
+#if 0 // NetRadiantCustom
+                       Sys_SetCursorPos( widget, self->center_x, self->center_y );
+                       self->last_x = self->center_x;
+                       self->last_y = self->center_y;
+#else
                        Sys_SetCursorPos( widget, self->recorded_x, self->recorded_y );
                        self->last_x = self->recorded_x;
                        self->last_y = self->recorded_y;
+#endif
                }
 #endif
                self->m_function( dx, dy, event->state, self->m_data );
@@ -149,6 +183,8 @@ gboolean FreezePointer::motion_delta(ui::Widget widget, GdkEventMotion *event, F
        return FALSE;
 }
 
+/* NetRadiantCustom did this instead:
+void FreezePointer::freeze_pointer(ui::Window window, ui::Widget widget, FreezePointer::MotionDeltaFunction function, void *data) */
 void FreezePointer::freeze_pointer(ui::Widget widget, FreezePointer::MotionDeltaFunction function, void *data)
 {
        /* FIXME: This bug can happen if the pointer goes outside of the
@@ -188,18 +224,42 @@ void FreezePointer::freeze_pointer(ui::Widget widget, FreezePointer::MotionDelta
        third-party updates that may fix the mouse pointer
        visibility issue. */
 #else
-       GdkCursor* cursor = create_blank_cursor();
+#if 0 // NetRadiantCustom
+       //GdkCursor* cursor = create_blank_cursor();
+       GdkCursor* cursor = gdk_cursor_new( GDK_BLANK_CURSOR );
        //GdkGrabStatus status =
+       /*      fixes cursor runaways during srsly quick drags in camera
+       drags with pressed buttons have no problem at all w/o this      */
+       gdk_pointer_grab( gtk_widget_get_window( widget ), TRUE, mask, 0, cursor, GDK_CURRENT_TIME );
+       //gdk_window_set_cursor ( GTK_WIDGET( window )->window, cursor );
+       /*      is needed to fix activating neighbour widgets, that happens, if using upper one */
+       gtk_grab_add( widget );
+       m_weedjet = widget;
+#else
+       GdkCursor* cursor = create_blank_cursor();
+       //GdkGrabStatus status =
        gdk_pointer_grab( gtk_widget_get_window( widget ), TRUE, mask, 0, cursor, GDK_CURRENT_TIME );
        gdk_cursor_unref( cursor );
+#endif
 #endif
 
        Sys_GetCursorPos( widget, &recorded_x, &recorded_y );
 
-       Sys_SetCursorPos( widget, recorded_x, recorded_y );
+#if 0 // NetRadiantCustom
+       /*      using center for tracking for max safety        */
+       gdk_window_get_origin( GTK_WIDGET( widget )->window, &center_x, &center_y );
+       auto allocation = widget.dimensions();
+       center_y += allocation.height / 2;
+       center_x += allocation.width / 2;
+
+       Sys_SetCursorPos( widget, center_x, center_y );
 
+       last_x = center_x;
+       last_y = center_y;
+#else
        last_x = recorded_x;
        last_y = recorded_y;
+#endif
 
        m_function = function;
        m_data = data;
@@ -207,7 +267,8 @@ void FreezePointer::freeze_pointer(ui::Widget widget, FreezePointer::MotionDelta
        handle_motion = widget.connect( "motion_notify_event", G_CALLBACK( motion_delta ), this );
 }
 
-void FreezePointer::unfreeze_pointer(ui::Widget widget)
+// Only NetRadiantCustom uses centerize code.
+void FreezePointer::unfreeze_pointer(ui::Widget widget, bool centerize)
 {
        g_signal_handler_disconnect( G_OBJECT( widget ), handle_motion );
 
@@ -217,9 +278,27 @@ void FreezePointer::unfreeze_pointer(ui::Widget widget)
 #if defined(WORKAROUND_MACOS_GTK2_LAGGYPOINTER)
        /* The pointer was visible during all the move operation,
        so, keep the current position. */
+#else
+       // NetRadiantCustom still uses window instead of widget.
+#if 0 // NetRadiantCustom
+       if ( centerize ){
+               Sys_SetCursorPos( window, center_x, center_y );
+       }
+       else{
+               Sys_SetCursorPos( window, recorded_x, recorded_y );
+       }
 #else
        Sys_SetCursorPos( widget, recorded_x, recorded_y );
 #endif
+#endif
 
+//     gdk_window_set_cursor( GTK_WIDGET( window )->window, 0 );
        gdk_pointer_ungrab( GDK_CURRENT_TIME );
+
+#if 0 // NetRadiantCustom
+       if ( m_weedjet )
+       {
+               gtk_grab_remove( m_weedjet );
+       }
+#endif
 }
index 77cb776123d7a03621f64fd32b66af2a17951497..0a19fab14dda0a158389f15351b831fde47d2c06 100644 (file)
@@ -22,6 +22,7 @@
 #if !defined( INCLUDED_GTKUTIL_CURSOR_H )
 #define INCLUDED_GTKUTIL_CURSOR_H
 
+// This is probably removable if set_cursor is not used.
 #include <gdk/gdk.h>
 #include <uilib/uilib.h>
 
 typedef struct _GdkCursor GdkCursor;
 typedef struct _GdkEventMotion GdkEventMotion;
 
+// NetRadiantCustom disables them but we still make use of them.
+#if 1
 GdkCursor* create_blank_cursor();
 void set_cursor( ui::Widget widget, GdkCursorType cursor_type );
 void blank_cursor( ui::Widget widget );
 void default_cursor( ui::Widget widget );
+#endif
 void Sys_GetCursorPos( ui::Widget widget, int *x, int *y );
 void Sys_SetCursorPos( ui::Widget widget, int x, int y );
 
@@ -107,18 +111,25 @@ void motion_delta( int x, int y, unsigned int state ){
 class FreezePointer
 {
 unsigned int handle_motion;
-int recorded_x, recorded_y, last_x, last_y;
+int recorded_x, recorded_y, last_x, last_y, center_x, center_y;
+ui::Widget m_weedjet{ui::null};
 typedef void ( *MotionDeltaFunction )( int x, int y, unsigned int state, void* data );
 MotionDeltaFunction m_function;
 void* m_data;
 public:
 FreezePointer() : handle_motion( 0 ), m_function( 0 ), m_data( 0 ){
 }
+/* NetRadiantCustom does this instead:
+static gboolean motion_delta( ui::Window widget, GdkEventMotion *event, FreezePointer* self ); */
 static gboolean motion_delta( ui::Widget widget, GdkEventMotion *event, FreezePointer* self );
 
+/* NetRadiantCustom does this instead:
+void freeze_pointer( ui::Window window, ui::Widget widget, MotionDeltaFunction function, void* data ); */
 void freeze_pointer( ui::Widget widget, MotionDeltaFunction function, void* data );
 
-void unfreeze_pointer( ui::Widget widget );
+/* NetRadiantCustom does this instead:
+void unfreeze_pointer( ui::Window window, bool centerize ); */
+void unfreeze_pointer( ui::Widget widget, bool centerize );
 };
 
 #endif
index 66470543c48e307c5e0573721c4e90cccac410f9..dc4ef7af6bb7cfa4791eb7ebea30add58b3d6253 100644 (file)
@@ -172,6 +172,7 @@ ui::Window create_simple_modal_dialog_window( const char* title, ModalDialog& di
 
        auto button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &dialog );
        alignment.add(button);
+       gtk_widget_grab_focus (button);
 
        return window;
 }
index ac3d5a612e8573ef92562e136aa7ce7bbcae5f3a..abfc225dcc4b05136bf91fff90ec924d52ccffe4 100644 (file)
@@ -186,6 +186,15 @@ const char* file_dialog_show( ui::Window parent, bool open, const char* title, c
        // we should add all important paths as shortcut folder...
        // gtk_file_chooser_add_shortcut_folder(GTK_FILE_CHOOSER(dialog), "/tmp/", NULL);
 
+       if ( open && masks.m_filters.size() > 1 ){
+               GtkFileFilter* filter = gtk_file_filter_new();
+               gtk_file_filter_set_name( filter, "Supported formats" );
+               for ( std::size_t i = 0; i < masks.m_filters.size(); ++i )
+               {
+                       gtk_file_filter_add_pattern( filter, masks.m_filters[i].c_str() );
+               }
+               gtk_file_chooser_add_filter( GTK_FILE_CHOOSER( dialog ), filter );
+       }
 
        for ( std::size_t i = 0; i < masks.m_filters.size(); ++i )
        {
@@ -200,7 +209,7 @@ const char* file_dialog_show( ui::Window parent, bool open, const char* title, c
 
                if ( !string_equal( pattern, "*" ) ) {
                        GtkFileFilter* filter = gtk_file_chooser_get_filter( GTK_FILE_CHOOSER( dialog ) );
-                       if ( filter != 0 ) { // no filter set? some file-chooser implementations may allow the user to set no filter, which we treat as 'all files'
+                       if ( filter != 0 && !string_equal( gtk_file_filter_get_name( filter ), "Supported formats" ) ) { // no filter set? some file-chooser implementations may allow the user to set no filter, which we treat as 'all files'
                                type = masks.GetTypeForGTKMask( gtk_file_filter_get_name( filter ) ).m_type;
                                // last ext separator
                                const char* extension = path_get_extension( g_file_dialog_file );
index 210b055c076a74f45cabcda6e38f984ed497aecf..a79b5b144b1d77bc86e296c1045ce886d531eabd 100644 (file)
@@ -44,6 +44,7 @@ GdkPixbuf* pixbuf_new_from_file_with_mask( const char* filename ){
        }
        else
        {
+               //GdkPixbuf* rgba = gdk_pixbuf_add_alpha( rgb, TRUE, 255, 0, 255 ); //pink to alpha
                GdkPixbuf* rgba = gdk_pixbuf_add_alpha( rgb, FALSE, 255, 0, 255 );
                g_object_unref( rgb );
                return rgba;
index 4f724eef96bd6c247bd11044f0911278a5de39d4..bc0d1f7ac53e812d5563e8ee6bdd8c5c79721f65 100644 (file)
  */
 
 #include "paned.h"
-
-#include <gtk/gtk.h>
-#include <uilib/uilib.h>
-
 #include "frame.h"
 
-
-class PanedState
-{
-public:
-float position;
-int size;
-};
-
 gboolean hpaned_allocate(ui::Widget widget, GtkAllocation* allocation, PanedState* paned ){
        if ( paned->size != allocation->width ) {
                paned->size = allocation->width;
@@ -61,7 +49,7 @@ PanedState g_hpaned = { 0.5f, -1, };
 PanedState g_vpaned1 = { 0.5f, -1, };
 PanedState g_vpaned2 = { 0.5f, -1, };
 
-ui::HPaned create_split_views( ui::Widget topleft, ui::Widget topright, ui::Widget botleft, ui::Widget botright ){
+ui::Widget create_split_views( ui::Widget topleft, ui::Widget topright, ui::Widget botleft, ui::Widget botright, ui::Widget& vsplit1, ui::Widget& vsplit2 ){
        auto hsplit = ui::HPaned(ui::New);
        hsplit.show();
 
@@ -70,25 +58,27 @@ ui::HPaned create_split_views( ui::Widget topleft, ui::Widget topright, ui::Widg
 
        {
                auto vsplit = ui::VPaned(ui::New);
-               gtk_paned_add1( GTK_PANED( hsplit ), vsplit  );
-               vsplit.show();
+               vsplit1 = vsplit;
+               gtk_paned_add1( GTK_PANED( hsplit ), GTK_WIDGET( vsplit ) );
+               gtk_widget_show( GTK_WIDGET( vsplit ) );
 
                vsplit.connect( "size_allocate", G_CALLBACK( vpaned_allocate ), &g_vpaned1 );
                vsplit.connect( "notify::position", G_CALLBACK( paned_position ), &g_vpaned1 );
 
-               gtk_paned_add1( GTK_PANED( vsplit ), create_framed_widget( topleft  ) );
-               gtk_paned_add2( GTK_PANED( vsplit ), create_framed_widget( topright  ) );
+               gtk_paned_add1( GTK_PANED( vsplit ), GTK_WIDGET( create_framed_widget( topleft ) ) );
+               gtk_paned_add2( GTK_PANED( vsplit ), GTK_WIDGET( create_framed_widget( botleft ) ) );
        }
        {
                auto vsplit = ui::VPaned(ui::New);
-               gtk_paned_add2( GTK_PANED( hsplit ), vsplit  );
-               vsplit.show();
+               vsplit2 = vsplit;
+               gtk_paned_add2( GTK_PANED( hsplit ), GTK_WIDGET( vsplit ) );
+               gtk_widget_show( GTK_WIDGET( vsplit ) );
 
                vsplit.connect( "size_allocate", G_CALLBACK( vpaned_allocate ), &g_vpaned2 );
                vsplit.connect( "notify::position", G_CALLBACK( paned_position ), &g_vpaned2 );
 
-               gtk_paned_add1( GTK_PANED( vsplit ), create_framed_widget( botleft  ) );
-               gtk_paned_add2( GTK_PANED( vsplit ), create_framed_widget( botright  ) );
+               gtk_paned_add1( GTK_PANED( vsplit ), GTK_WIDGET( create_framed_widget( topright ) ) );
+               gtk_paned_add2( GTK_PANED( vsplit ), GTK_WIDGET( create_framed_widget( botright ) ) );
        }
        return hsplit;
 }
index 842996efb5e3db6097b3cfa1899766e50c99cecd..1a19d9c541874ac2cc8d52035da6734d4c41d446 100644 (file)
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
-#include <uilib/uilib.h>
-
 #if !defined( INCLUDED_GTKUTIL_PANED_H )
 #define INCLUDED_GTKUTIL_PANED_H
 
-ui::HPaned create_split_views( ui::Widget topleft, ui::Widget topright, ui::Widget botleft, ui::Widget botright );
+#include <gtk/gtk.h>
+#include <uilib/uilib.h>
+
+class PanedState
+{
+public:
+float position;
+int size;
+};
+
+gboolean hpaned_allocate( ui::Widget widget, GtkAllocation* allocation, PanedState* paned );
+gboolean vpaned_allocate( ui::Widget widget, GtkAllocation* allocation, PanedState* paned );
+gboolean paned_position( ui::Widget widget, gpointer dummy, PanedState* paned );
 
+ui::Widget create_split_views( ui::Widget topleft, ui::Widget botleft, ui::Widget topright, ui::Widget botright, ui::Widget& vsplit1, ui::Widget& vsplit2 );
 #endif
index 049d05ec1fc62fd4684ecb79661fb54077a70551..8df8802d27d1b250d8c16d2c1ed8909b63d3ec41 100644 (file)
@@ -37,6 +37,12 @@ void toolbar_append( ui::Toolbar toolbar, ui::ToolItem button, const char* descr
        toolbar.add(button);
 }
 
+ui::ToolButton toolbar_append_button( ui::Toolbar toolbar, const char* description, const char* icon ){
+       auto button = ui::ToolButton::from(gtk_tool_button_new(new_local_image(icon), nullptr));
+       toolbar_append(toolbar, button, description);
+       return button;
+}
+
 ui::ToolButton toolbar_append_button( ui::Toolbar toolbar, const char* description, const char* icon, const Callback<void()>& callback ){
        auto button = ui::ToolButton::from(gtk_tool_button_new(new_local_image(icon), nullptr));
        button_connect_callback(button, callback);
index f3481783028943c680be1f49d4522905f802f7fd..c995e1be4a3879dfe5d013ffe73d9f2f66034cb2 100644 (file)
@@ -28,6 +28,7 @@
 class Command;
 class Toggle;
 
+ui::ToolButton toolbar_append_button( ui::Toolbar toolbar, const char* description, const char* icon );
 ui::ToolButton toolbar_append_button( ui::Toolbar toolbar, const char* description, const char* icon, const Callback<void()>& callback );
 ui::ToolButton toolbar_append_button( ui::Toolbar toolbar, const char* description, const char* icon, const Command& command );
 ui::ToggleToolButton toolbar_append_toggle_button( ui::Toolbar toolbar, const char* description, const char* icon, const Callback<void()>& callback );
index 28395b3516da90470c318b408d330fb85e498810..ff93211dc632e46e85983530be09c7f13b4d98ba 100644 (file)
 #include "debugging/debugging.h"
 #include "property.h"
 
+#define GARUX_GTK_WORKAROUND
+#ifndef GARUX_GTK_WORKAROUND
+inline bool widget_is_visible( GtkWidget* widget ){
+       return GTK_WIDGET_VISIBLE( widget ) != FALSE;
+}
+
+inline void widget_set_visible( GtkWidget* widget, bool show ){
+       if ( show ) {
+               /* workaround for gtk 2.24 issue: not displayed glwidget after toggle */
+               GtkWidget* glwidget = GTK_WIDGET( g_object_get_data( G_OBJECT( widget ), "glwidget" ) );
+               if ( glwidget ){
+                       //if ( widget_is_visible( glwidget ) )
+                               //globalOutputStream() << "glwidget have been already visible :0\n"; /* is not hidden aswell, according to this */
+                       gtk_widget_hide( glwidget );
+                       gtk_widget_show( glwidget );
+               }
+               gtk_widget_show( widget );
+       }
+       else
+       {
+               gtk_widget_hide( widget );
+       }
+}
+#endif
+
 class ToggleItem {
     Callback<void(const Callback<void(bool)> &)> m_exportCallback;
     typedef std::list<Callback<void(bool)>> ImportCallbacks;
index 80b79738892daabe2590316fc95c1cfa999fa3ba..c8671a2fbcf0ab25976c5658d26a02d6abf87e44 100644 (file)
@@ -39,6 +39,15 @@ inline void CHECK_RESTORE( ui::Widget w ){
 #endif // WORKAROUND_WINDOWS_GTK2_GLWIDGET
 
                w.show();
+#define GARUX_GTK_WORKAROUND
+#ifndef GARUX_GTK_WORKAROUND
+               /* workaround for gtk 2.24 issue: not displayed glwidget after min/restore */
+               GtkWidget* glwidget = GTK_WIDGET( g_object_get_data( G_OBJECT( w ), "glwidget" ) );
+               if ( glwidget ){
+                       gtk_widget_hide( glwidget );
+                       gtk_widget_show( glwidget );
+               }
+#endif
        }
 }
 
@@ -119,6 +128,23 @@ ui::Window create_floating_window( const char* title, ui::Window parent ){
                connect_floating_window_destroy_present( window, parent );
                g_object_set_data( G_OBJECT( window ), "floating_handler", gint_to_pointer( connect_floating( parent, window ) ) );
                window.connect( "destroy", G_CALLBACK( destroy_disconnect_floating ), parent );
+/*
+               //gtk_window_set_type_hint (window,GDK_WINDOW_TYPE_HINT_UTILITY);
+               //gtk_window_set_type_hint (window,GDK_WINDOW_TYPE_HINT_DIALOG);
+               gtk_window_set_keep_above ( window, TRUE );
+               GtkWidget* widget = GTK_WIDGET( window );
+               gtk_widget_realize ( widget );
+               GdkWindow* gdk_window = gtk_widget_get_window( widget );
+               //gdk_window_set_decorations ( gdk_window, (GdkWMDecoration)(GDK_DECOR_BORDER|GDK_DECOR_RESIZEH|GDK_DECOR_TITLE|GDK_DECOR_MENU|GDK_DECOR_MINIMIZE|GDK_DECOR_MAXIMIZE) );
+               //gdk_window_set_functions ( gdk_window, (GdkWMFunction)( GDK_FUNC_RESIZE|GDK_FUNC_MOVE|GDK_FUNC_MINIMIZE|GDK_FUNC_MAXIMIZE|GDK_FUNC_CLOSE ) );
+               //gdk_window_set_decorations ( gdk_window, (GdkWMDecoration)( GDK_DECOR_ALL ) );
+               //gdk_window_set_functions ( gdk_window, (GdkWMFunction)( GDK_FUNC_ALL ) );
+               //gdk_window_set_type_hint ( gdk_window, GDK_WINDOW_TYPE_HINT_DIALOG );
+               //gdk_window_set_type_hint ( gdk_window, GDK_WINDOW_TYPE_HINT_UTILITY );
+               //gdk_window_set_type_hint ( gdk_window, GDK_WINDOW_TYPE_HINT_NORMAL );
+               gdk_window_set_skip_taskbar_hint ( gdk_window, TRUE );
+               gdk_window_set_skip_pager_hint ( gdk_window, TRUE );
+*/
        }
 
        return window;
index 56ae908a2e911c3ab7e65b6220d18dc6eed2e684..be6384e210d59f23b142e112d2870a424ae36d17 100644 (file)
@@ -2,6 +2,17 @@
 
 #include <gtk/gtk.h>
 
+#define GARUX_DISABLE_BAD_XORRECTANGLE
+
+#if !defined(GARUX_DISABLE_BAD_XORRECTANGLE) && GTK_TARGET == 2
+#include "gtkutil/glwidget.h"
+#include "igl.h"
+
+#include <gtk/gtkglwidget.h>
+#endif // !GARUX_DISABLE_BAD_XORRECTANGLE && GTK_TARGET == 2
+
+//#include "stream/stringstream.h"
+
 bool XORRectangle::initialised() const
 {
     return !!cr;
@@ -46,9 +57,63 @@ XORRectangle::~XORRectangle()
 void XORRectangle::set(rectangle_t rectangle)
 {
     if (gtk_widget_get_realized(m_widget)) {
-        lazy_init();
-        draw();
-        m_rectangle = rectangle;
-        draw();
+#if !defined(GARUX_DISABLE_BAD_XORRECTANGLE) && GTK_TARGET == 2
+               if( m_rectangle.w != rectangle.w || m_rectangle.h != rectangle.h ){
+               //if( !(m_rectangle.w == 0 && m_rectangle.h == 0 && rectangle.w == 0 && rectangle.h == 0) ){
+               //globalOutputStream() << "m_x" << m_rectangle.x << " m_y" << m_rectangle.y << " m_w" << m_rectangle.w << " m_h" << m_rectangle.h << "\n";
+               //globalOutputStream() << "__x" << rectangle.x << " __y" << rectangle.y << " __w" << rectangle.w << " __h" << rectangle.h << "\n";
+                       if ( glwidget_make_current( m_widget ) != FALSE ) {
+                               GlobalOpenGL_debugAssertNoErrors();
+
+                               gint width, height;
+                               gdk_gl_drawable_get_size( gtk_widget_get_gl_drawable( m_widget ), &width, &height );
+
+                               glViewport( 0, 0, width, height );
+                               glMatrixMode( GL_PROJECTION );
+                               glLoadIdentity();
+                               glOrtho( 0, width, 0, height, -100, 100 );
+
+                               glMatrixMode( GL_MODELVIEW );
+                               glLoadIdentity();
+
+                               glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
+                               glDisable( GL_DEPTH_TEST );
+
+                               glDrawBuffer( GL_FRONT );
+
+                               glEnable( GL_BLEND );
+                               glBlendFunc( GL_ONE_MINUS_DST_COLOR, GL_ZERO );
+
+                               glLineWidth( 2 );
+                               glColor3f( 1, 1, 1 );
+                               glDisable( GL_TEXTURE_2D );
+                               glBegin( GL_LINE_LOOP );
+                               glVertex2f( m_rectangle.x, m_rectangle.y + m_rectangle.h );
+                               glVertex2f( m_rectangle.x + m_rectangle.w, m_rectangle.y + m_rectangle.h );
+                               glVertex2f( m_rectangle.x + m_rectangle.w, m_rectangle.y );
+                               glVertex2f( m_rectangle.x, m_rectangle.y );
+                               glEnd();
+
+                               glBegin( GL_LINE_LOOP );
+                               glVertex2f( rectangle.x, rectangle.y + rectangle.h );
+                               glVertex2f( rectangle.x + rectangle.w, rectangle.y + rectangle.h );
+                               glVertex2f( rectangle.x + rectangle.w, rectangle.y );
+                               glVertex2f( rectangle.x, rectangle.y );
+                               glEnd();
+
+                               glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+                               glDrawBuffer( GL_BACK );
+                               GlobalOpenGL_debugAssertNoErrors();
+                               //glwidget_swap_buffers( m_widget );
+                               glwidget_make_current( m_widget );
+                       }
+               }
+               m_rectangle = rectangle;
+#else // GARUX_DISABLE_BAD_XORRECTANGLE || GTK_TARGET != 2
+               lazy_init();
+               draw();
+               m_rectangle = rectangle;
+               draw();
+#endif // !GARUX_DISABLE_BAD_XORRECTANGLE && GTK_TARGET == 2
     }
 }
index 64f3f6e43d8db5e2019fc4410d859483fbd20f73..8ed115d606d7973d6c16bb692974b5c57ad6a956 100644 (file)
@@ -101,7 +101,20 @@ void _CrossProduct( vec3_t v1, vec3_t v2, vec3_t cross );
 // I need this define in order to test some of the regression tests from time to time.
 // This define affect the precision of VectorNormalize() function only.
 #define MATHLIB_VECTOR_NORMALIZE_PRECISION_FIX 1
-vec_t VectorNormalize( const vec3_t in, vec3_t out );
+vec_t VectorAccurateNormalize( const vec3_t in, vec3_t out );
+vec_t VectorFastNormalize_( const vec3_t in, vec3_t out );
+#if MATHLIB_VECTOR_NORMALIZE_PRECISION_FIX
+#define VectorNormalize VectorAccurateNormalize
+#else
+#define VectorNormalize VectorFastNormalize_
+#endif
+
+#if 0 //use fastnormalize in a few -light spots
+       #define VectorFastNormalize VectorFastNormalize_
+#else
+       #define VectorFastNormalize VectorNormalize
+#endif
+
 vec_t ColorNormalize( const vec3_t in, vec3_t out );
 void VectorInverse( vec3_t v );
 void VectorPolar( vec3_t v, float radius, float theta, float phi );
index da26a92072ec95c865506ec30ab3e5bfea404f70..f8af1c7bb7da8db8eb420513172cedfbc548ef07 100644 (file)
@@ -153,9 +153,7 @@ void _VectorCopy( vec3_t in, vec3_t out ){
        out[2] = in[2];
 }
 
-vec_t VectorNormalize( const vec3_t in, vec3_t out ) {
-
-#if MATHLIB_VECTOR_NORMALIZE_PRECISION_FIX
+vec_t VectorAccurateNormalize( const vec3_t in, vec3_t out ) {
 
        // The sqrt() function takes double as an input and returns double as an
        // output according the the man pages on Debian and on FreeBSD.  Therefore,
@@ -179,26 +177,30 @@ vec_t VectorNormalize( const vec3_t in, vec3_t out ) {
        out[2] = (vec_t) ( z / length );
 
        return (vec_t) length;
+}
 
-#else
+vec_t VectorFastNormalize_( const vec3_t in, vec3_t out ) {
 
-       vec_t length, ilength;
+       // SmileTheory: This is ioquake3's VectorNormalize2
+       //              for when accuracy matters less than speed
+       float length, ilength;
 
-       length = (vec_t)sqrt( in[0] * in[0] + in[1] * in[1] + in[2] * in[2] );
-       if ( length == 0 ) {
+       length = in[0] * in[0] + in[1] * in[1] + in[2] * in[2];
+
+       if ( length ) {
+               /* writing it this way allows gcc to recognize that rsqrt can be used */
+               ilength = 1 / (float)sqrt( length );
+               /* sqrt(length) = length * (1 / sqrt(length)) */
+               length *= ilength;
+               out[0] = in[0] * ilength;
+               out[1] = in[1] * ilength;
+               out[2] = in[2] * ilength;
+       }
+       else {
                VectorClear( out );
-               return 0;
        }
 
-       ilength = 1.0f / length;
-       out[0] = in[0] * ilength;
-       out[1] = in[1] * ilength;
-       out[2] = in[2] * ilength;
-
        return length;
-
-#endif
-
 }
 
 vec_t ColorNormalize( const vec3_t in, vec3_t out ) {
index 144627e65a9dcea34a7ad9adf8a03aef9a613d9d..252085c2b8013628ecbcbd541ed47373410827d6 100644 (file)
 #if GDEF_OS_WINDOWS
 #define S_ISDIR( mode ) ( mode & _S_IFDIR )
 #include <io.h> // _access()
+
+#ifndef F_OK
+#define F_OK 0x00
+#endif
+
+#ifndef W_OK
+#define W_OK 0x02
+#endif
+
+#ifndef R_OK
+#define R_OK 0x04
+#endif
+
 #define access( path, mode ) _access( path, mode )
 #else
 #include <unistd.h> // access()
index 4800fb46af957ba1704a71bdae0ae60990c6c3b4..6e0ab120fea07f81762679c5d128abcc5585cc3b 100644 (file)
@@ -1,3 +1,7 @@
+if("${RADIANT_IQM_PLUGIN}" STREQUAL "picomodel")
+        set(PICO_IQM_FILE "pm_iqm.c")
+endif()
+
 add_library(picomodel STATIC
         lwo/clip.c
         lwo/envelope.c
@@ -16,6 +20,7 @@ add_library(picomodel STATIC
         pm_3ds.c
         pm_ase.c
         pm_fm.c pm_fm.h
+        ${PICO_IQM_FILE}
         pm_lwo.c
         pm_md2.c
         pm_md3.c
index 614d7c53b04a7ee43a9b6e9b935d7dfa7f99aa59..54866de101522a7440d3952a0b8cea1f22011a67 100644 (file)
@@ -602,12 +602,12 @@ int _pico_nofname( const char *path, char *dest, int destSize ){
  *  string otherwise. given 'path' is not altered. -sea
  */
 const char *_pico_nopath( const char *path ){
-       const char *src;
-       src = path + ( strlen( path ) - 1 );
-
        if ( path == NULL ) {
                return "";
        }
+       const char *src;
+       src = path + ( strlen( path ) - 1 );
+
        if ( !strchr( path,'/' ) && !strchr( path,'\\' ) ) {
                return ( path );
        }
@@ -741,7 +741,28 @@ void _pico_deduce_shadernames( picoModel_t *model ){
 
                const char* mapname = model->shader[i]->mapName;
                const char* shadername = model->shader[i]->name;
-               if( mapname && *mapname )
+
+               /* Detect intentional material name to not replace it with texture name.
+
+               Reimplement commits by Garux:
+               https://github.com/Garux/netradiant-custom/commit/1bd3e7ae186b55fb61e3738d2493432c0b1f5a7b
+               https://github.com/Garux/netradiant-custom/commit/ea21eee2254fb2e667732d8f1b0f83c439a89bfa
+
+               This attempts to restore proper material behaviour when the mapper knows what he is doing,
+               also called Julius' case or correct case because Julius is always correct™
+               while keeping the fallback for other situations, also called newbie's case
+               which may be compatible with third-party tools not following Quake 3 conventions.
+
+               See: https://gitlab.com/xonotic/netradiant/-/merge_requests/179#note_777324051 */
+               if ( shadername && *shadername &&
+                       ( _pico_strnicmp( shadername, "models/", 7 ) == 0
+                       || _pico_strnicmp( shadername, "models\\", 7 ) == 0
+                       || _pico_strnicmp( shadername, "textures/", 9 ) == 0
+                       || _pico_strnicmp( shadername, "textures\\", 9 ) == 0 ) )
+               {
+                       _pico_deduce_shadername( model->fileName, shadername, model->shader[i] );
+               }
+               else if( mapname && *mapname )
                        _pico_deduce_shadername( model->fileName, mapname, model->shader[i] );
                else if( shadername && *shadername )
                        _pico_deduce_shadername( model->fileName, shadername, model->shader[i] );
index 1ec806ad6190255cef2667270101003e01f46316..98b6cf8f1cd0ff1c7f20766a576c2215dd0b9c67 100644 (file)
@@ -80,6 +80,10 @@ extern "C"
 extern const picoColor_t picoColor_white;
 
 /* types */
+#ifndef byte
+       typedef unsigned char byte;
+#endif
+
 typedef struct picoParser_s
 {
        const char *buffer;
index 77bb4918b22af6a9385cc71af558c912dc347d56..9a2278f2b812e9cd76e9829ee4fcb8b401e3f93b 100644 (file)
@@ -48,6 +48,9 @@ extern const picoModule_t picoModuleMD2;
 extern const picoModule_t picoModuleFM;
 extern const picoModule_t picoModuleLWO;
 extern const picoModule_t picoModuleTerrain;
+#if defined(RADIANT_IQM_PLUGIN_picomodel)
+extern const picoModule_t picoModuleIQM;
+#endif // defined(RADIANT_IQM_PLUGIN_picomodel)
 
 
 
@@ -64,6 +67,9 @@ const picoModule_t *picoModules[] =
        &picoModuleLWO,     /* lightwave object */
        &picoModuleTerrain, /* picoterrain object */
        &picoModuleOBJ,     /* wavefront object */
+#if defined(RADIANT_IQM_PLUGIN_picomodel)
+       &picoModuleIQM,     /* interquake model */
+#endif // defined(RADIANT_IQM_PLUGIN_picomodel)
        NULL                /* arnold */
 };
 
index 6fa317ca8714b42f18e1cc20495f739c7a68a030..8ffcc405c923bf3bbd1a0df2270e7d38fdd3f3fd 100644 (file)
 #define INFO_HEIGHT 5
 #define INFO_Y ( SKINPAGE_HEIGHT - INFO_HEIGHT )
 
-#ifndef byte
-       #define byte unsigned char
-#endif
-
 
 //
 //     Generic header on every chunk
diff --git a/libs/picomodel/pm_iqm.c b/libs/picomodel/pm_iqm.c
new file mode 100644 (file)
index 0000000..739cbef
--- /dev/null
@@ -0,0 +1,359 @@
+/* -----------------------------------------------------------------------------
+
+   InterQuake Model - PicoModel Library
+
+   Copyright (c) 2018-2021, FTE Team <fteqw.org>
+   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.
+
+   ----------------------------------------------------------------------------- */
+
+/* dependencies */
+#include "picointernal.h"
+
+extern const picoModule_t picoModuleIQM;
+
+#define IQM_MAGIC "INTERQUAKEMODEL"    //15+null
+
+/*
+   ========================================================================
+
+   .IQM triangle model file format
+
+   ========================================================================
+ */
+
+enum
+{
+       IQM_POSITION = 0,
+       IQM_TEXCOORD = 1,
+       IQM_NORMAL = 2,
+       IQM_TANGENT = 3,
+       IQM_BLENDINDEXES = 4,
+       IQM_BLENDWEIGHTS = 5,
+       IQM_COLOR = 6,
+       IQM_CUSTOM = 0x10
+};
+
+enum
+{
+       IQM_BYTE = 0,
+       IQM_UBYTE = 1,
+       IQM_SHORT = 2,
+       IQM_USHORT = 3,
+       IQM_INT = 4,
+       IQM_UINT = 5,
+       IQM_HALF = 6,
+       IQM_FLOAT = 7,
+       IQM_DOUBLE = 8
+};
+
+// animflags
+#define IQM_LOOP 1
+
+typedef struct iqmHeader_s {
+       byte id[16];
+       unsigned int version;
+       unsigned int filesize;
+       unsigned int flags;
+       unsigned int num_text, ofs_text;
+       unsigned int num_meshes, ofs_meshes;
+       unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays;
+       unsigned int num_triangles, ofs_triangles, ofs_neighbors;
+       unsigned int num_joints, ofs_joints;
+       unsigned int num_poses, ofs_poses;
+       unsigned int num_anims, ofs_anims;
+       unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds;
+       unsigned int num_comment, ofs_comment;
+       unsigned int num_extensions, ofs_extensions;
+} iqmHeader_t;
+
+typedef struct iqmmesh_s {
+       unsigned int name;
+       unsigned int material;
+       unsigned int first_vertex;
+       unsigned int num_vertexes;
+       unsigned int first_triangle;
+       unsigned int num_triangles;
+} iqmmesh_t;
+
+typedef struct iqmvertexarray_s {
+       unsigned int type;
+       unsigned int flags;
+       unsigned int format;
+       unsigned int size;
+       unsigned int offset;
+} iqmvertexarray_t;
+
+//is anyone actually going to run this on a big-endian cpu?
+static iqmHeader_t SwapHeader(const iqmHeader_t *h)
+{
+       iqmHeader_t r = *h;
+       r.version = _pico_little_long(h->version);
+       r.filesize = _pico_little_long(h->filesize);
+       r.flags = _pico_little_long(h->flags);
+       r.num_text = _pico_little_long(h->num_text);
+       r.ofs_text = _pico_little_long(h->ofs_text);
+       r.num_meshes = _pico_little_long(h->num_meshes);
+       r.ofs_meshes = _pico_little_long(h->ofs_meshes);
+       r.num_vertexarrays = _pico_little_long(h->num_vertexarrays);
+       r.num_vertexes = _pico_little_long(h->num_vertexes);
+       r.ofs_vertexarrays = _pico_little_long(h->ofs_vertexarrays);
+       r.num_triangles = _pico_little_long(h->num_triangles);
+       r.ofs_triangles = _pico_little_long(h->ofs_triangles);
+       r.ofs_neighbors = _pico_little_long(h->ofs_neighbors);
+       r.num_joints = _pico_little_long(h->num_joints);
+       r.ofs_joints = _pico_little_long(h->ofs_joints);
+       r.num_poses = _pico_little_long(h->num_poses);
+       r.ofs_poses = _pico_little_long(h->ofs_poses);
+       r.num_anims = _pico_little_long(h->num_anims);
+       r.ofs_anims = _pico_little_long(h->ofs_anims);
+       r.num_frames = _pico_little_long(h->num_frames);
+       r.num_framechannels = _pico_little_long(h->num_framechannels);
+       r.ofs_frames = _pico_little_long(h->ofs_frames);
+       r.ofs_bounds = _pico_little_long(h->ofs_bounds);
+       r.num_comment = _pico_little_long(h->num_comment);
+       r.ofs_comment = _pico_little_long(h->ofs_comment);
+       r.num_extensions = _pico_little_long(h->num_extensions);
+       r.ofs_extensions = _pico_little_long(h->ofs_extensions);
+       return r;
+}
+
+// _iqm_canload()
+static int _iqm_canload( PM_PARAMS_CANLOAD ){
+       iqmHeader_t h;
+
+       //make sure there's enough data for the header...
+       if ((size_t)bufSize < sizeof(h))
+               return PICO_PMV_ERROR_SIZE;
+       h = SwapHeader(buffer);
+
+       //make sure its actually an iqm
+       if (memcmp(h.id, IQM_MAGIC, sizeof(h.id)))
+               return PICO_PMV_ERROR_IDENT;
+       //v1 is flawed, we don't know about anything higher either.
+       if (h.version != 2)
+               return PICO_PMV_ERROR_VERSION;
+       //make sure its not truncated
+       if ((size_t)h.filesize != (size_t)bufSize)
+               return PICO_PMV_ERROR_SIZE;
+
+       //looks like we can probably use it.
+       return PICO_PMV_OK;
+}
+
+// _iqm_load() loads an interquake model file.
+static picoModel_t *_iqm_load( PM_PARAMS_LOAD ){
+       picoModel_t     *picoModel;
+       picoSurface_t   *picoSurface;
+       picoShader_t    *picoShader;
+       const float *inf;
+       const byte *inb;
+       picoVec3_t xyz, normal;
+       picoVec2_t st;
+       picoColor_t color;
+
+       iqmHeader_t h;
+       iqmmesh_t m;
+       iqmvertexarray_t a;
+       size_t s, t, j, i;
+       const char *stringtable;
+       char skinname[512];
+       const unsigned int *tri;
+
+       //just in case
+       if (_iqm_canload(fileName, buffer, bufSize) != PICO_PMV_OK)
+       {
+               _pico_printf( PICO_ERROR, "%s is not an IQM File!", fileName );
+               return NULL;
+       }
+       h = SwapHeader(buffer);
+       stringtable = (const char*)buffer + h.ofs_text;
+
+       // do frame check
+       if ( h.num_anims != 0 ) {
+               _pico_printf( PICO_WARNING, "%s has animations! Using base pose only.", fileName );
+       }
+
+       /* 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 );
+
+       for (s = 0; s < h.num_meshes; s++)
+       {
+               m = ((const iqmmesh_t*)((const char*)buffer + h.ofs_meshes))[s];
+               m.first_triangle = _pico_little_long(m.first_triangle);
+               m.first_vertex = _pico_little_long(m.first_vertex);
+               m.material = _pico_little_long(m.material);
+               m.name = _pico_little_long(m.name);
+               m.num_triangles = _pico_little_long(m.num_triangles);
+               m.num_vertexes = _pico_little_long(m.num_vertexes);
+
+               // allocate new pico surface
+               picoSurface = PicoNewSurface( picoModel );
+               if ( picoSurface == NULL ) {
+                       _pico_printf( PICO_ERROR, "Unable to allocate a new model surface" );
+                       PicoFreeModel( picoModel );
+                       return NULL;
+               }
+
+               // detox Skin name
+               memcpy(skinname, stringtable+m.material, sizeof(skinname));
+               _pico_setfext( skinname, "" );
+               _pico_unixify( skinname );
+
+               PicoSetSurfaceType( picoSurface, PICO_TRIANGLES );
+               PicoSetSurfaceName( picoSurface, stringtable+m.name );
+               picoShader = PicoNewShader( picoModel );
+               if ( picoShader == NULL ) {
+                       _pico_printf( PICO_ERROR, "Unable to allocate a new model shader" );
+                       PicoFreeModel( picoModel );
+                       return NULL;
+               }
+
+               PicoSetShaderName( picoShader, skinname );
+
+               // associate current surface with newly created shader
+               PicoSetSurfaceShader( picoSurface, picoShader );
+
+
+               // spew the surface's indexes
+               tri = (const unsigned int *)((const char *)buffer+h.ofs_triangles) + m.first_triangle*3;
+               for (t = 0; t < m.num_triangles*3; t++)
+                       PicoSetSurfaceIndex( picoSurface, t, _pico_little_long(*tri++) - m.first_vertex );
+
+               for ( j = 0; j < h.num_vertexarrays; j++)
+               {
+                       a = ((const iqmvertexarray_t*)((const char*)buffer + h.ofs_vertexarrays))[j];
+                       a.flags = _pico_little_long(a.flags);
+                       a.format = _pico_little_long(a.format);
+                       a.offset = _pico_little_long(a.offset);
+                       a.size = _pico_little_long(a.size);
+                       a.type = _pico_little_long(a.type);
+
+                       switch(a.type)
+                       {
+                       case IQM_POSITION:
+                               if (a.format == IQM_FLOAT && a.size >= 3)
+                               {
+                                       inf = (const float*)((const char *)buffer + a.offset) + m.first_vertex*a.size;
+                                       for ( i = 0; i < m.num_vertexes; i++, inf += a.size )
+                                       {
+                                               xyz[0] = _pico_little_float(inf[0]);
+                                               xyz[1] = _pico_little_float(inf[1]);
+                                               xyz[2] = _pico_little_float(inf[2]);
+                                               PicoSetSurfaceXYZ( picoSurface, i, xyz );
+                                       }
+                               }
+                               break;
+                       case IQM_TEXCOORD:
+                               if (a.format == IQM_FLOAT && a.size >= 2)
+                               {
+                                       inf = (const float*)((const char *)buffer + a.offset) + m.first_vertex*a.size;
+                                       for ( i = 0; i < m.num_vertexes; i++, inf += a.size )
+                                       {
+                                               st[0] = _pico_little_float(inf[0]);
+                                               st[1] = _pico_little_float(inf[1]);
+                                               PicoSetSurfaceST( picoSurface, 0, i, st );
+                                       }
+                               }
+                               break;
+                       case IQM_NORMAL:
+                               if (a.format == IQM_FLOAT && a.size >= 3)
+                               {
+                                       inf = (const float*)((const char *)buffer + a.offset) + m.first_vertex*a.size;
+                                       for ( i = 0; i < m.num_vertexes; i++, inf += a.size )
+                                       {
+                                               normal[0] = _pico_little_float(inf[0]);
+                                               normal[1] = _pico_little_float(inf[1]);
+                                               normal[2] = _pico_little_float(inf[2]);
+                                               PicoSetSurfaceNormal( picoSurface, i, normal );
+                                       }
+                               }
+                               break;
+                       case IQM_COLOR:
+                               if (a.format == IQM_UBYTE && a.size >= 3)
+                               {
+                                       inb = (const byte*)((const char *)buffer + a.offset) + m.first_vertex*a.size;
+                                       for ( i = 0; i < m.num_vertexes; i++, inb += a.size )
+                                       {
+                                               color[0] = inb[0];
+                                               color[1] = inb[1];
+                                               color[2] = inb[2];
+                                               color[3] = (a.size>=4)?inb[3]:255;
+                                               PicoSetSurfaceColor( picoSurface, 0, i, color );
+                                       }
+                               }
+                               else if (a.format == IQM_FLOAT && a.size >= 3)
+                               {
+                                       inf = (const float*)((const char *)buffer + a.offset) + m.first_vertex*a.size;
+                                       for ( i = 0; i < m.num_vertexes; i++, inf += a.size )
+                                       {
+                                               color[0] = inf[0]*255;
+                                               color[1] = inf[1]*255;
+                                               color[2] = inf[2]*255;
+                                               color[3] = (a.size>=4)?inf[3]*255:255;
+                                               PicoSetSurfaceColor( picoSurface, 0, i, color );
+                                       }
+                               }
+                               break;
+                       case IQM_TANGENT:
+                       case IQM_BLENDINDEXES:
+                       case IQM_BLENDWEIGHTS:
+                       case IQM_CUSTOM:
+                               break;  // these attributes are not relevant.
+                       }
+               }
+       }
+
+       return picoModel;
+}
+
+/* pico file format module definition */
+const picoModule_t picoModuleIQM =
+{
+       "0.1",                          /* module version string */
+       "InterQuake Model",             /* module display name */
+       "Spoike",                       /* author's name */
+       "2018-2021 FTE Team",           /* module copyright */
+       {
+               "iqm", NULL, NULL, NULL /* default extensions to use */
+       },
+       _iqm_canload,                   /* validation routine */
+       _iqm_load,                      /* load routine */
+       NULL,                           /* save validation routine */
+       NULL                            /* save routine */
+};
index 1b93c7b4d93b2fe875638332fc32510df88a2cda..0540c54ea0cfe3f45b78aee1aeebdf1f1c3056e7 100644 (file)
@@ -149,7 +149,7 @@ static int _md3_canload( PM_PARAMS_CANLOAD ){
        md3 = (const md3_t*) buffer;
 
        /* check md3 magic */
-       if ( *( (const int*) md3->magic ) != *( (const int*) MD3_MAGIC ) ) {
+       if ( memcmp( md3->magic, MD3_MAGIC, 4 ) != 0 ) {
                return PICO_PMV_ERROR_IDENT;
        }
 
@@ -199,7 +199,7 @@ static picoModel_t *_md3_load( PM_PARAMS_LOAD ){
        md3 = (md3_t*) bb;
 
        /* check ident and version */
-       if ( *( (int*) md3->magic ) != *( (int*) MD3_MAGIC ) || _pico_little_long( md3->version ) != MD3_VERSION ) {
+       if ( memcmp( md3->magic, MD3_MAGIC, 4 ) != 0 || _pico_little_long( md3->version ) != MD3_VERSION ) {
                /* not an md3 file (todo: set error) */
                _pico_free( bb0 );
                return NULL;
index ec40d5a9c842ae2fccb766505bf3dd52cee4d3ab..816adebb6ff1c0d35ae4c9ee5f600d6302b06156 100644 (file)
@@ -45,7 +45,7 @@ const float MDC_MAX_OFS         = 127.0f;
 const float MDC_DIST_SCALE      = 0.05f;
 
 /* mdc decoding normal table */
-double mdcNormals[ 256 ][ 3 ] =
+const double mdcNormals[ 256 ][ 3 ] =
 {
        { 1.000000, 0.000000, 0.000000 },
        { 0.980785, 0.195090, 0.000000 },
index d568edb386216a826a823d6d6799cf24ce45ec83..b2e6c5e92da32472977e1a439845d3b4deea3350 100644 (file)
@@ -84,7 +84,7 @@ void insert( RenderIndex index ){
        m_data.push_back( index );
 }
 void swap( IndexBuffer& other ){
-       std::swap( m_data, m_data );
+       std::swap( m_data, other.m_data );
 }
 };
 
index e1c05d464d8ba5ee096c55f6f08c2a122db47e9e..d15ce8021bdd483c22d84a4dacbda5ba1852e9db 100644 (file)
@@ -740,6 +740,10 @@ bool parentSelected() const {
        }
        return m_parentSelected;
 }
+Instance* parent() const
+{
+       return m_parent;
+}
 };
 }
 
@@ -763,7 +767,14 @@ public:
 InstanceWalker( const Functor& functor ) : m_functor( functor ){
 }
 bool pre( const scene::Path& path, scene::Instance& instance ) const {
-       m_functor( instance );
+       //m_functor( instance );
+       //return true;
+       if ( path.top().get().visible() ) {
+               m_functor( instance );
+       }
+       else{
+               return false;
+       }
        return true;
 }
 };
index b4427dcde922c6159953971174c4b2b595140c6c..b981567e0cd2adf9ad591ad7ded7d4001c8a85e2 100644 (file)
@@ -75,8 +75,13 @@ friend mat3_t   SkewSymmetric( idVec3 const &src );
 ID_INLINE mat3_t::mat3_t() {
 }
 
-ID_INLINE mat3_t::mat3_t(float src[3][3]) {
-    memcpy(mat, src, sizeof(float) * 3 * 3);
+ID_INLINE mat3_t::mat3_t( float src[ 3 ][ 3 ] ) {
+       //memcpy( mat, src, sizeof( src ) );
+       for( unsigned int i = 0; i < 3; i++ ) {
+               mat[i].x = src[i][0];
+               mat[i].y = src[i][1];
+               mat[i].z = src[i][2];
+       }
 }
 
 ID_INLINE mat3_t::mat3_t( idVec3 const &x, idVec3 const &y, idVec3 const &z ) {
index 5d63925d2c6ce14764bfa33ccfeb06eb5025665b..e41779cd72d52e19153b551caafe76110348b6d4 100644 (file)
@@ -150,6 +150,11 @@ inline bool string_equal_suffix( const char* string, const char* suffix){
        return string_equal_n( s , suffix, string_length( suffix ) );
 }
 
+inline bool string_equal_suffix_nocase( const char* string, const char* suffix){
+       const char *s = string + string_length( string ) - string_length( suffix );
+       return string_equal_nocase_n( s , suffix, string_length( suffix ) );
+}
+
 /// \brief Copies \p other into \p string and returns \p string.
 /// Assumes that the space allocated for \p string is at least string_length(other) + 1.
 /// O(n)
index 47772ee47845d139e9bac1e625811a893249bbc1..544b433674a73ca292a37168150e08c886fde2af 100644 (file)
@@ -220,7 +220,7 @@ inline bool string_parse_size( const char* string, std::size_t& i ){
 }
 
 
-#define RETURN_FALSE_IF_FAIL(expression) do { if (!(expression)) return false; } while (0)
+#define RETURN_FALSE_IF_FAIL( expression ) do{ if ( !expression ) {return false; } }while( 0 )
 
 inline void Tokeniser_unexpectedError( Tokeniser& tokeniser, const char* token, const char* expected ){
        globalErrorStream() << Unsigned( tokeniser.getLine() ) << ":" << Unsigned( tokeniser.getColumn() ) << ": parse error at '" << ( token != 0 ? token : "#EOF" ) << "': expected '" << expected << "'\n";
@@ -232,6 +232,15 @@ inline bool Tokeniser_getFloat( Tokeniser& tokeniser, float& f ){
        if ( token != 0 && string_parse_float( token, f ) ) {
                return true;
        }
+       //fallback for 1.#IND 1.#INF 1.#QNAN cases, happening sometimes after rotating & often scaling with tex lock in BP mode
+       else if ( token != 0 && strstr( token, ".#" ) ) {
+               globalErrorStream() << "Warning: " << Unsigned( tokeniser.getLine() ) << ":" << Unsigned( tokeniser.getColumn() ) << ": expected parse problem at '" << token << "': wanted '#number'\nProcessing anyway\n";
+       #define GARUX_DISABLE_QNAN_FALLBACK
+       #ifndef GARUX_DISABLE_QNAN_FALLBACK
+//             *strstr( token, ".#" ) = '\0';
+       #endif
+               return true;
+       }
        Tokeniser_unexpectedError( tokeniser, token, "#number" );
        return false;
 }
diff --git a/libs/transformpath/CMakeLists.txt b/libs/transformpath/CMakeLists.txt
new file mode 100644 (file)
index 0000000..5d3d0aa
--- /dev/null
@@ -0,0 +1,3 @@
+add_library(transformpath STATIC
+        transformpath.cpp transformpath.h
+        )
diff --git a/libs/transformpath/transformpath.cpp b/libs/transformpath/transformpath.cpp
new file mode 100644 (file)
index 0000000..9828c40
--- /dev/null
@@ -0,0 +1,452 @@
+/* SPDX-License-Identifier: MIT License
+
+Copyright © 2021 Thomas “illwieckz” Debesse
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the “Software”),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+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 SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE. */
+
+/* transformpath: transform file path based on keywords.
+
+This is not an environment variable parser, this only supports
+a set of keywords using common environment name syntax when it
+exists to make the strings easier to read.
+
+Supported substitution keywords,
+Windows:
+
+- %HOMEPATH%
+- %USERPROFILE%
+- %ProgramFiles%
+- %ProgramFiles(x86)%
+- %ProgramW6432%
+- %APPDATA%
+- [CSIDL_MYDOCUMENTS]
+
+Supported substitution keywords,
+Linux, FreeBSD, macOS:
+
+- ~
+- ${HOME}
+
+Supported substitution keywords,
+Linux, FreeBSD, other XDG systems:
+
+- ${XDG_CONFIG_HOME}
+- ${XDG_DATA_HOME}
+
+Examples,
+game engine directories:
+
+- Windows: %ProgramFiles%\Unvanquished
+- Linux: ${XDG_DATA_HOME}/unvanquished/base
+- macOS: ${HOME}/Games/Unvanquished
+
+Examples,
+game home directories:
+
+- Windows: [CSIDL_MYDOCUMENTS]\My Games\Unvanquished
+- Linux: ${XDG_DATA_HOME}/unvanquished
+- macOS: ${HOME}/Application Data/Unvanquished
+
+*/
+
+#include "globaldefs.h"
+#include "stringio.h"
+
+#include <cstring>
+#include <string>
+
+#if GDEF_OS_WINDOWS
+#include <windows.h>
+#include <iostream>
+#include <shlobj.h>
+#pragma comment(lib, "shell32.lib")
+#endif // !GDEF_OS_WINDOWS
+
+#if GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS
+#include <unistd.h>
+#include <sys/types.h>
+#include <pwd.h>
+#endif // !GDEF_OS_LINUX && !GDEF_OS_BSD && !GDEF_OS_MACOS
+
+static std::string getEnvVar( const char* name )
+{
+       const char *env = getenv( name );
+       env = env ? env : "";
+       std::string path( env );
+       return path;
+}
+
+#if GDEF_OS_WINDOWS
+static std::string getUserProfilePath();
+#endif // !GDEF_OS_WINDOWS
+
+static std::string getUserName()
+{
+#if GDEF_OS_WINDOWS
+       std::string path = getEnvVar( "USERNAME" );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       globalErrorStream() << "%USERNAME% not found.\n";
+
+       return "";
+#endif // !GDEF_OS_WINDOWS
+
+#if GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS
+       std::string path = getEnvVar( "USERNAME" );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       globalErrorStream() << "${USERNAME} not found, guessing…\n";
+
+       path = getEnvVar( "LOGNAME" );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       globalErrorStream() << "${LOGNAME} not found, guessing…\n";
+
+       path = getEnvVar( "USER" );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       globalErrorStream() << "${USER} not found.\n";
+
+       return "";
+#endif // !GDEF_OS_LINUX && !GDEF_OS_BSD && !GDEF_OS_MACOS
+}
+
+static std::string getHomePath()
+{
+#if GDEF_OS_WINDOWS
+       std::string path = getEnvVar( "HOMEPATH" );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       globalErrorStream() << "%HOMEPATH% not found, guessing…\n";
+
+       std::string path1 = getUserProfilePath();
+
+       if ( ! path1.empty() )
+       {
+               return path1;
+       }
+
+       globalErrorStream() << "%HOMEPATH% not found.\n";
+
+       return "";
+#endif // !GDEF_OS_WINDOWS
+
+#if GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS
+       // Get the path environment variable.
+       std::string path = getEnvVar( "HOME" );
+
+       // Look up path directory in password database.
+       if( ! path.empty() )
+       {
+               return path;
+       }
+
+       globalErrorStream() << "${HOME} not found, guessing…\n";
+
+       static char     buf[ 4096 ];
+       struct passwd pw, *pwp;
+
+       if ( getpwuid_r( getuid(), &pw, buf, sizeof( buf ), &pwp ) == 0 )
+       {
+               return std::string( pw.pw_dir );
+       }
+
+       globalErrorStream() << "${HOME} not found, guessing…\n";
+
+       std::string path1( "/home/" );
+
+       std::string path2 = getUserName();
+
+       if ( ! path2.empty() )
+       {
+               return path1 + path2;
+       }
+
+       globalErrorStream() << "${HOME} not found…\n";
+
+       return "";
+#endif // !GDEF_OS_LINUX && !GDEF_OS_BSD && !GDEF_OS_MACOS
+}
+
+#if GDEF_OS_WINDOWS
+static std::string getSystemDrive()
+{
+       std::string path = getEnvVar( "SYSTEMDRIVE" );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       globalErrorStream() << "%SYSTEMDRIVE% not found, guessing…\n";
+
+       return "C:";
+}
+
+static std::string getUserProfilePath()
+{
+       std::string path = getEnvVar( "USERPROFILE" );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       globalErrorStream() << "%USERPROFILE% not found, guessing…\n";
+
+       std::string path1 = getSystemDrive();
+       std::string path2 = getUserName();
+
+       if ( ! path2.empty() )
+       {
+               return path1 + "\\Users\\" + path2;
+       }
+
+       globalErrorStream() << "%USERPROFILE% not found.\n";
+
+       return "";
+}
+
+static std::string getProgramFilesPath()
+{
+       std::string path = getEnvVar( "ProgramFiles" );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       globalErrorStream() << "%ProgramFiles% not found, guessing…\n";
+
+       std::string path1 = getSystemDrive();
+       return path1 + "\\Program Files";
+}
+
+static std::string getProgramFilesX86Path()
+{
+       std::string path = getEnvVar( "ProgramFiles(x86)" );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       globalErrorStream() << "%ProgramFiles(x86)% not found, guessing…\n";
+
+       return getProgramFilesPath();
+}
+
+static std::string getProgramW6432Path()
+{
+       std::string path = getEnvVar( "ProgramFilesW6432" );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       globalErrorStream() << "%ProgramW6432% not found, guessing…\n";
+
+       return getProgramFilesPath();
+}
+
+static std::string getAppDataPath()
+{
+       std::string path = getEnvVar( "APPDATA" );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       globalErrorStream() << "%APPDATA% not found, guessing…\n";
+
+       std::string path1 = getUserProfilePath();
+
+       if ( ! path1.empty() )
+       {
+               return path1 + "\\AppData\\Roaming";
+       }
+
+       globalErrorStream() << "%APPDATA% not found.\n";
+
+       return std::string( "" );
+}
+
+/* TODO: see also qFOLDERID_SavedGames in mainframe.cpp,
+HomePaths_Realise and other things like that,
+they look to be game paths, not NetRadiant paths. */
+
+static std::string getMyDocumentsPath()
+{
+       CHAR path[ MAX_PATH ];
+       HRESULT result = SHGetFolderPath(NULL, CSIDL_MYDOCUMENTS, NULL, SHGFP_TYPE_CURRENT, path);
+
+       if ( result == S_OK )
+       {
+               return std::string( path );
+       }
+
+       globalErrorStream() << "[CSIDL_MYDOCUMENTS] not found, guessing…\n";
+
+       std::string path1 = getHomePath();
+
+       if ( ! path1.empty() )
+       {
+               path1 += "\\Documents";
+               
+               return path1;
+       }
+
+       globalErrorStream() << "[CSIDL_MYDOCUMENTS] not found.\n";
+
+       return "";
+}
+#endif // !GDEF_OS_WINDOWS
+
+#if GDEF_OS_XDG
+static std::string getXdgConfigHomePath()
+{
+       /* FIXME: we may want to rely on g_get_user_config_dir()
+       provided by GLib. */
+
+       std::string path = getEnvVar( "XDG_CONFIG_HOME" );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       // This is not an error.
+       // globalErrorStream() << "${XDG_CONFIG_HOME} not found, guessing…\n";
+
+       std::string path1 = getHomePath();
+
+       if ( ! path1.empty() )
+       {
+               return path1 + "/.config";
+       }
+
+       globalErrorStream() << "${XDG_CONFIG_HOME} not found.\n";
+
+       return "";
+}
+
+static std::string getXdgDataHomePath()
+{
+       std::string path = getEnvVar( "XDG_DATA_HOME" );
+
+       if ( ! path.empty() )
+       {
+               return path;
+       }
+
+       // This is not an error.
+       // globalErrorStream() << "${XDG_DATA_HOME} not found, guessing…\n";
+
+       std::string path1 = getHomePath();
+
+       if ( ! path1.empty() )
+       {
+               return path1 + "/.local/share";
+       }
+
+       globalErrorStream() << "${XDG_DATA_HOME} not found.\n";
+
+       return "";
+}
+#endif // GDEF_OS_XDG
+
+struct pathTransformer_t
+{
+       std::string pattern;
+       std::string ( *function )();
+};
+
+static const pathTransformer_t pathTransformers[] =
+{
+#if GDEF_OS_WINDOWS
+       { "%HOMEPATH%", getHomePath },
+       { "%USERPROFILE%", getUserProfilePath },
+       { "%ProgramFiles%", getProgramFilesPath },
+       { "%ProgramFiles(x86)%", getProgramFilesX86Path },
+       { "%ProgramW6432%", getProgramW6432Path },
+       { "%APPDATA%", getAppDataPath },
+       { "[CSIDL_MYDOCUMENTS]", getMyDocumentsPath },
+#endif // GDEF_OS_WINDOWS
+
+#if GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS
+       { "~", getHomePath },
+       { "${HOME}", getHomePath },
+#endif // GDEF_OS_LINUX || GDEF_OS_BSD || GDEF_OS_MACOS
+
+#if GDEF_OS_XDG
+       { "${XDG_CONFIG_HOME}", getXdgConfigHomePath },
+       { "${XDG_DATA_HOME}", getXdgDataHomePath },
+#endif // GDEF_OS_XDG
+};
+
+/* If no transformation succeeds, the path will be returned untransformed. */
+std::string transformPath( std::string transformedPath )
+{
+       for ( const pathTransformer_t &pathTransformer : pathTransformers )
+       {
+               if ( transformedPath.find( pathTransformer.pattern, 0 ) == 0 )
+               {
+                       globalOutputStream() << "Path Transforming: '" << transformedPath.c_str() << "'\n";
+
+                       std::string path = pathTransformer.function();
+
+                       if ( ! path.empty() )
+                       {
+                               transformedPath.replace( 0, pathTransformer.pattern.length(), path );
+
+                               globalOutputStream() << "Path Transformed: '" << transformedPath.c_str() << "'\n";
+
+                               return transformedPath;
+                       }
+
+                       break;
+               }
+       }
+
+       globalErrorStream() << "Path not transformed: '" << transformedPath.c_str() << "'\n";
+
+       return transformedPath;
+}
diff --git a/libs/transformpath/transformpath.h b/libs/transformpath/transformpath.h
new file mode 100644 (file)
index 0000000..172971a
--- /dev/null
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: MIT License
+
+Copyright © 2021 Thomas “illwieckz” Debesse
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the “Software”),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+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 SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE. */
+
+/* marker */
+#ifndef TRANSFORMPATH_H
+#define TRANSFORMPATH_H
+
+#include <string>
+
+std::string transformPath( std::string transformedPath );
+
+#endif // TRANSFORMPATH_H
index d8f41c85d88559371a36f86c2ed90a33235537c3..abefc9e57490dfcdf9266404a95afa5815032d7d 100644 (file)
@@ -1,6 +1,7 @@
 #ifndef INCLUDED_UILIB_H
 #define INCLUDED_UILIB_H
 
+#include <cstdint>
 #include <string>
 #include <glib-object.h>
 
index 8ff67f15b55150829fa103413b03593d4e3f1992..7ef0755e3a6e62f91ec7028d9ccd5af5c2f1225c 100644 (file)
@@ -106,9 +106,34 @@ inline void name_write( char* buffer, name_t name ){
 
 inline name_t name_read( const char* name ){
        const char* end = name + strlen( name );
+
+#if GDEF_OS_MACOS
+       /* HACK: Apple shipped a clang built for macOS with an optimization enabled
+       that is not available on macOS. This compiler error may then be faced:
+
+         ld: Undefined symbols:
+           _memrchr, referenced from:
+               name_read(char const*) in map.cpp.o
+
+       This is a compiler error:
+
+       > On Mac OSX (macOS version 12.4, sdk version 12.1) llvm can replace call
+       > to strrchr() with call to memrchr() when string length is known at
+       > compile time. This results in link error, because memrchr is not present
+       > in libSystem.
+       > -- https://github.com/llvm/llvm-project/issues/62254
+
+       We workaround this by making the string length not known at build time
+       on macOS to avoid triggering the unavailable compiler optimization. */
+
+       const char* volatile numbers = "1234567890";
+#else
+       const char* numbers = "1234567890";
+#endif
+
        for ( const char* p = end; end != name; --p )
        {
-               if ( strrchr( "1234567890", *p ) == NULL ) {
+               if ( strrchr( numbers, *p ) == NULL ) {
                        break;
                }
                end = p;
@@ -187,17 +212,25 @@ name_t make_unique( const name_t& name ) const {
        char buf[80];
        name_t r( "","" );
        name_write( buf, name );
+       #ifdef _DEBUG
        globalErrorStream() << "find unique name for " << buf << "\n";
        globalErrorStream() << "> currently registered names:\n";
+       #endif
        for ( names_t::const_iterator i = m_names.begin(); i != m_names.end(); ++i )
        {
+               #ifdef _DEBUG
                globalErrorStream() << ">> " << i->first.c_str() << ": ";
+               #endif
                for ( PostFixes::postfixes_t::const_iterator j = i->second.m_postfixes.begin(); j != i->second.m_postfixes.end(); ++j )
                {
                        j->first.write( buf );
+                       #ifdef _DEBUG
                        globalErrorStream() << " '" << buf << "'";
+                       #endif
                }
+               #ifdef _DEBUG
                globalErrorStream() << "\n";
+               #endif
        }
        names_t::const_iterator i = m_names.find( name.first );
        if ( i == m_names.end() ) {
@@ -208,7 +241,9 @@ name_t make_unique( const name_t& name ) const {
                r = name_t( name.first, ( *i ).second.make_unique( name.second ) );
        }
        name_write( buf, r );
+       #ifdef _DEBUG
        globalErrorStream() << "> unique name is " << buf << "\n";
+       #endif
        return r;
 }
 
diff --git a/oldstuff/TODO-Garux b/oldstuff/TODO-Garux
new file mode 100644 (file)
index 0000000..60573df
--- /dev/null
@@ -0,0 +1,149 @@
+[-z-]: make rotate dialog non-modal
+    
+
+
+BUGS
+
+MSI: installer bug with new folders? : create custom dir, click New Folder icon, type "FOLDER\" - gets stuck
+GTK2: gtk2 crashes when trying to use bitmap fonts such as MS Sans Serif http://bugzilla.gnome.org/show_bug.cgi?id=142579
+GTK2: alt+tab while mouse button is held down: see http://bugzilla.gnome.org/show_bug.cgi?id=145156
+UI: changing resolution in floating-windows mode can screw up window positions.
+HalfLife: half-life maps saved in q1 map format are not supported - currently have to convert them to hammer map format using hammer editor. And vice versa.
+Entity: creating a new entity with all the brushes of another entity selected results in the latter entity having no brushes.
+SConscript: build fails if SETUP=1
+SConscript: svn.py fails if not using C locale - set LC_ALL?
+GUI: can't use arrow keys to navigate in camera view when capslock is enabled
+GUI: screensaver causes: gdkgc-win32.c: line 905 (gdk_win32_hdc_get): assertion failed: (win32_gc->hdc == NULL)
+
+
+FEATURES
+
+- paint-select or equivalent (e.g. area-selection with occlusion)
+- select-complete-tall or equivalent (e.g. subtract-from-selection modifier key)
+- texture pane names are often illegible, becuase 1. they are long and overlap each other and 2. they overlap the outline rectangles around the images themselves.
+
+
+Build: document build-menu xml format.
+The build menu in GtkRadiant 1.5 is entirely customisable - you can make it run qbsp3/qvis3/arghrad or any tool you want. Use 'Build > Customize...' to edit the menu.
+
+Menu commands are the shell commands that Radiant will execute when you choose the menu item. You can add as many commands as you want to a single menu item, and they will be executed in sequence. The commands contain variables, specified using []. The values of variables will be substituted when the command is executed.
+
+For example:
+<pre>[q2map] -bsp "[MapFile]"</pre>
+becomes:
+<pre>"C:\Program Files\GtkRadiant 1.5.0\q2map" -fs_basepath "c:\quake2" -bsp "c:\quake2\baseq2\maps\blah.map"</pre>
+This uses the predefined variable 'MapFile' and the custom variable 'q2map'. 'q2map' is defined in the XML file, and 'MapFile' is the full path to your map.
+The 'MapFile' variable is enclosed in quotes, because the path to your map may contain spaces.
+At the moment you can only create custom variables by editing the XML file. A custom variable for arghrad would look something like this:
+<pre><var name="arghrad">"[RadiantPath]arghrad"</var></pre>
+This variable could then be used in a command like this:
+<pre>[arghrad] "[MapFile]"</pre>
+
+Entity: option to filter non-world entities (e.g. not func_group or func_static)
+Rotate Tool: if more than one object is selected, with different local orientations, use parent-space rotation pivot instead of local-space
+Brush: MMB+ctrl to paint texture on whole brush/patch.
+Camera: add alternative highlighting styles (used to be J).
+Doom3: filter func_splinemovers
+Entity: draw arrowheads to show direction of connection-lines.
+? MMB to select a texture should also apply that texture to all selected faces.
+Mouse: support 2-button mouse.
+Grid: background colour should be different when the smallest grid is invisible due to being zoomed out.
+Brush: option to disable dots on selected faces when not in face mode.
+Entity: draw direction arrow for func_door and func_button angle.
+Build Menu: support for editing variables.
+Shaders: handle doom3 materials with multiple bumpmaps stage - use first stage, ignore later stages.
+Brush: warn when a brush is dragged into a configuration with <0 volume
+Textures: add option to give new brushes a specific texture instead of the last selected.
+? QE-tool: click anywhere on xy view to drag entity instead of requiring clicking directly on entity.
+UserDocs: how to use multi-vertex selection - replaces vertex-edit-splits-faces option:
+UserDocs: how to use parent-selection:
+  Parent-selection works like Maya: it allows you to 'reparent' brushes
+  onto other entities than the one they're currently part of. To use it,
+  select some brushes, select an entity, Edit -> Parent.
+Textures: add anisotropic filtering.
+Preferences: allow preference settings to be shared across games.
+Preferences: add colour 'theme' files using prefs format.
+Preferences: sensible default size for prefs window.
+Doom3: add model browser.
+Doom3: s_diversity light key.
+HalfLife: enable HL-mode on linux/osx.
+Renderer: doom3 'parallel' and 'spot' light support.
+Entity: add mouse-editing for doom3 light_center key
+Shaders: add support for texture transforms.
+Shaders: add support for 'addnormals' keyword - e.g. models/mapobjects/healthgui/healthguidirty
+TGA Loader: check that true-colour images with palettes are properly handled.
+Module System: reinstate 'refresh' feature.
+Surface Inspector: add button for 'axial' projection for doom3.
+Build: fix hardcoded engine-launch commands - use similar system to build-menu command description.
+Filters: use q2/heretic2 content flags to filter brushes.
+? Surface Inspector: allow material names not relative to 'textures/' for doom3
+Module System: add versioning for module-system api.
+svn: remove install/ dir, create it during build process on win32
+Editing: add option to choose the default startup tool mode.
+Renderer: lighting for doom3 materials without bumpmaps (e.g. mcity/mchangar2)
+Renderer: realtime doom3 materials preview
+Renderer: realtime doom3 shadows preview
+Linux: Provide .tar.gz of example-map data for et/wolf.
+Textures Window: add inner dark outline to distinguish 'is-shader' outline from white textures.
+HalfLife2: add HL2 map load/save.
+Selection: add move-pivot mode to allow rotation/scale around a custom pivot-point.
+Selection: add rotate increment for rotate manipulator.
+Selection: visibly distinguish between entity and brush selections
+Selection: need 'add to selection' and 'subtract from selection' modifiers
+Selection: Finish scale manipulator.
+FaceCopy/PasteTexture: Make face-copy/paste-texture shortcuts customisable.
+Manual: add documentation about search paths for .ent/.def/.fgd, shaders etc for each game.
+Halflife: add support for cstrike fgd.
+HalfLife: disable patches
+HalfLife: add HL .mdl model loader.
+HalfLife: add HL .spr support.
+HalfLife: support fgd 'flags' attributes.
+Model: add support for doom3 md5anim format
+Model: support doom3 ragdolls
+VFS: add ability to browse VFS from file-open dialogs.
+Installer: enable q3 brush-primitives map support.
+Installer: add editor manual to linux installer
+Map: add conversion between map formats
+Map: add conversion between entity definition formats
+Build: add build-menu dmap support (doom3)
+Entity: optionally draw target connection lines thicker than one pixel.
+Entity: add specialised attribute-entry in entity-inspector for integer/real/color attribute types.
+Patch: add cap-texture, fit-texture and natural-texture toolbar buttons
+Patch: draw patches in wireframe from the back, make patches selectable from the back
+Patch: add option for convert-selection-to-new-brush/patch
+Patch: fix bobtoolz merge-patches feature
+Patch: fix insert/remove rows/cols indicated by current selected patch vertices.
+Autosave/Snapshots: Add support for multi-file maps.
+Quake2: Q2 hint transparency support
+Shortcuts: make shortcut list editable within radiant.
+Shortcuts: convert shortcuts.ini to xml.
+Shortcuts: warn when duplicate shortcuts are registered
+Shortcuts: rename commands in order to group shortcuts list better.
+upgrade to new API for SymGetModuleInfo - required for compiling with Visual Studio 8.0
+Doom3: lights should stay in place while resizing
+
+
+LOW priority features
+
+Selection: Add shear manipulator?
+Textures Window: Improve texture-manipulation and texture-browsing tools.
+Undo: make selections undoable?
+Win32 Installer: Automatically upgrade existing installation.
+General: refactor game-specific hacks to be parameterised by .game file
+Patch: Overlays, Bend Mode, Thicken.
+Brush: Add brush-specific plugin API.
+Entity: Draw light style numbers.
+... Entity: Show models with model2 key.
+Entity: Interpret _remap* key (_MindLink_).
+Entity: Support _origin _angles _scale on groups.
+Selection: Add Primitive-mode shortcut key/button.
+Selection: Customisable manipulator size - +/- to change the size of the translate/rotate tool. 
+Selection: Add optional screen-relative control for constrained rotations.
+Clipper: Change selection/manipulation to be consistent with other component editing.
+Filtering: Either deselect filtered nodes, or render filtered nodes that are selected.
+Filtering: Add customisable filter presets to set/unset multiple filters at once.
+Texdef: Make texdef formats abstract, add conversion between texdef formats (use generic affine-texture-matrix format for conversions).
+Textures Window: Precise display of texture size when selecting.  (tooltip, possibly)
+Status: 'Size of brush' display on status bar.
+Colours: maya scheme default?
+Quake: add support for adjusting gamma on quake palette?
index 8db4b7fa212ea1010dea0fea6eb11927da510765..db306aeb26a7238bcd501ffe2d549ca134b1d3cf 100644 (file)
@@ -26,10 +26,17 @@ add_subdirectory(imagehl)
 add_subdirectory(imagepng)
 add_subdirectory(imageq2)
 add_subdirectory(imagewebp)
-add_subdirectory(iqmmodel)
+
+if("${RADIANT_IQM_PLUGIN}" STREQUAL "iqmmodel")
+    add_subdirectory(iqmmodel)
+endif()
+
 add_subdirectory(mapq3)
 add_subdirectory(mapxml)
 add_subdirectory(md3model)
+
+# picomodel: md3, obj, ase, (optional) iqm
 add_subdirectory(model)
+
 add_subdirectory(shaders)
 add_subdirectory(vfspk3)
index 54f51f9bfe939efdbdd64f6e9ceafc63b64ea827..e44c6fcfc14ed006308f7f0e0f833f17ecab58f0 100644 (file)
@@ -73,8 +73,9 @@ inline void write_angles( const Vector3& angles, Entity* entity ){
                char value[64];
 
                if ( angles[0] == 0 && angles[1] == 0 ) {
+                       float yaw = angles[2];
                        entity->setKeyValue( "angles", "" );
-                       write_angle( angles[2], entity );
+                       write_angle( yaw, entity );
                }
                else
                {
index ceb19b0b876055350f306b2989fcf0efe567bcac..b3daa405a8320077de5f72b10cbd5c40017111b1 100644 (file)
@@ -91,8 +91,8 @@ NameKeys m_nameKeys;
 TraversableObserverPairRelay m_traverseObservers;
 Doom3GroupOrigin m_funcStaticOrigin;
 RenderablePivot m_renderOrigin;
-RenderableNamedEntity m_renderName;
 mutable Vector3 m_name_origin;
+RenderableNamedEntity m_renderName;
 ModelSkinKey m_skin;
 
 public:
@@ -259,8 +259,8 @@ Doom3Group( EntityClass* eclass, scene::Node& node, const Callback<void()>& tran
        m_named( m_entity ),
        m_nameKeys( m_entity ),
        m_funcStaticOrigin( m_traverse, m_origin ),
-       m_renderName( m_named, m_name_origin ),
        m_name_origin( g_vector3_identity ),
+       m_renderName( m_named, m_name_origin ),
        m_skin( SkinChangedCaller( *this ) ),
        m_curveNURBS( boundsChanged ),
        m_curveCatmullRom( boundsChanged ),
@@ -278,7 +278,8 @@ Doom3Group( const Doom3Group& other, scene::Node& node, const Callback<void()>&
        m_named( m_entity ),
        m_nameKeys( m_entity ),
        m_funcStaticOrigin( m_traverse, m_origin ),
-       m_renderName( m_named, g_vector3_identity ),
+       m_name_origin( g_vector3_identity ),
+       m_renderName( m_named, m_name_origin ),
        m_skin( SkinChangedCaller( *this ) ),
        m_curveNURBS( boundsChanged ),
        m_curveCatmullRom( boundsChanged ),
index dfd2a6408c4aa878cf062e357976b9aa8bd71fda..77ad814349c6f50a02b9f4e28466eba3b0e04b2b 100644 (file)
@@ -111,9 +111,10 @@ EntityCreator::KeyValueChangedFunc KeyValue::m_entityKeyValueChanged = 0;
 Counter* EntityKeyValues::m_counter = 0;
 
 bool g_showNames = true;
+bool g_showTargetNames = false;
 bool g_showAngles = true;
 bool g_newLightDraw = true;
-bool g_lightRadii = false;
+bool g_lightRadii = true;
 
 class ConnectEntities
 {
@@ -208,22 +209,48 @@ void connectEntities( const scene::Path& path, const scene::Path& targetPath, in
        else
        {
                ConnectEntities connector( e1, e2, index );
-               const char* value = e2->getKeyValue( "targetname" );
-               if ( !string_empty( value ) ) {
-                       connector.connect( value );
+               //killconnect
+               if( index == 1 ){
+                       const char* value = e2->getKeyValue( "targetname" );
+                       if ( !string_empty( value ) ) {
+                               connector.connect( value );
+                       }
+                       else
+                       {
+                               const char* type = e2->getKeyValue( "classname" );
+                               if ( string_empty( type ) ) {
+                                       type = "t";
+                               }
+                               StringOutputStream key( 64 );
+                               key << type << "1";
+                               GlobalNamespace().makeUnique( key.c_str(), ConnectEntities::ConnectCaller( connector ) );
+                       }
                }
-               else
-               {
-                       const char* type = e2->getKeyValue( "classname" );
-                       if ( string_empty( type ) ) {
-                               type = "t";
+               //normal connect
+               else{
+                       //prioritize existing target key
+                       //checking, if ent got other connected ones already, could be better solution
+                       const char* value = e1->getKeyValue( "target" );
+                       if ( !string_empty( value ) ) {
+                               connector.connect( value );
+                       }
+                       else{
+                               value = e2->getKeyValue( "targetname" );
+                               if ( !string_empty( value ) ) {
+                                       connector.connect( value );
+                               }
+                               else{
+                                       const char* type = e2->getKeyValue( "classname" );
+                                       if ( string_empty( type ) ) {
+                                               type = "t";
+                                       }
+                                       StringOutputStream key( 64 );
+                                       key << type << "1";
+                                       GlobalNamespace().makeUnique( key.c_str(), ConnectEntities::ConnectCaller( connector ) );
+                               }
                        }
-                       StringOutputStream key( 64 );
-                       key << type << "1";
-                       GlobalNamespace().makeUnique( key.c_str(), ConnectEntities::ConnectCaller( connector ) );
                }
        }
-
        SceneChangeNotify();
 }
 void setLightRadii( bool lightRadii ){
@@ -238,6 +265,12 @@ void setShowNames( bool showNames ){
 bool getShowNames(){
        return g_showNames;
 }
+void setShowTargetNames( bool showNames ){
+       g_showTargetNames = showNames;
+}
+bool getShowTargetNames(){
+       return g_showTargetNames;
+}
 void setShowAngles( bool showAngles ){
        g_showAngles = showAngles;
 }
@@ -281,7 +314,7 @@ bool filter( const Entity& entity ) const {
 }
 };
 
-filter_entity_classname g_filter_entity_world( "worldspawn" );
+//filter_entity_classname g_filter_entity_world( "worldspawn" );
 filter_entity_classname g_filter_entity_func_group( "func_group" );
 filter_entity_classname g_filter_entity_light( "light" );
 filter_entity_classname g_filter_entity_misc_model( "misc_model" );
@@ -301,9 +334,20 @@ bool filter( const Entity& entity ) const {
 filter_entity_doom3model g_filter_entity_doom3model;
 
 
+class filter_entity_world : public EntityFilter
+{
+public:
+bool filter( const Entity& entity ) const {
+       return string_equal( entity.getKeyValue( "classname" ), "worldspawn" )
+                  || string_equal( entity.getKeyValue( "classname" ), "func_group" );
+}
+};
+
+filter_entity_world g_filter_entity_world;
+
 void Entity_InitFilters(){
        add_entity_filter( g_filter_entity_world, EXCLUDE_WORLD );
-       add_entity_filter( g_filter_entity_func_group, EXCLUDE_WORLD );
+       add_entity_filter( g_filter_entity_func_group, EXCLUDE_FUNC_GROUPS );
        add_entity_filter( g_filter_entity_world, EXCLUDE_ENT, true );
        add_entity_filter( g_filter_entity_trigger, EXCLUDE_TRIGGERS );
        add_entity_filter( g_filter_entity_misc_model, EXCLUDE_MODELS );
@@ -331,6 +375,7 @@ void Entity_Construct( EGameType gameType ){
        }
 
        GlobalPreferenceSystem().registerPreference( "SI_ShowNames", make_property_string( g_showNames ) );
+       GlobalPreferenceSystem().registerPreference( "SI_ShowTargetNames", make_property_string( g_showTargetNames ) );
        GlobalPreferenceSystem().registerPreference( "SI_ShowAngles", make_property_string( g_showAngles ) );
        GlobalPreferenceSystem().registerPreference( "NewLightStyle", make_property_string( g_newLightDraw ) );
        GlobalPreferenceSystem().registerPreference( "LightRadiuses", make_property_string( g_lightRadii ) );
index ae294ab7c262004fc1058b92eef25d7297977327..ba5f991fcf0288c16d4b383badf97513980d78c0 100644 (file)
@@ -39,6 +39,7 @@ void Entity_Construct( EGameType gameType = eGameTypeQuake3 );
 void Entity_Destroy();
 
 extern bool g_showNames;
+extern bool g_showTargetNames;
 extern bool g_showAngles;
 extern bool g_newLightDraw;
 extern bool g_lightRadii;
index bad7fc54e793b102a8bcd2caae1f6bd896497b9d..b2c01e9f1f3496d96509c81c4c87968e3023cf12 100644 (file)
@@ -63,8 +63,8 @@ NameKeys m_nameKeys;
 OriginKey m_originKey;
 Vector3 m_origin;
 
-RenderableNamedEntity m_renderName;
 mutable Vector3 m_name_origin;
+RenderableNamedEntity m_renderName;
 
 Callback<void()> m_transformChanged;
 Callback<void()> m_evaluateTransform;
@@ -83,8 +83,8 @@ Group( EntityClass* eclass, scene::Node& node, const Callback<void()>& transform
        m_nameKeys( m_entity ),
        m_originKey( OriginChangedCaller( *this ) ),
        m_origin( ORIGINKEY_IDENTITY ),
-       m_renderName( m_named, m_name_origin ),
        m_name_origin( g_vector3_identity ),
+       m_renderName( m_named, m_name_origin ),
        m_transformChanged( transformChanged ),
        m_evaluateTransform( evaluateTransform ){
        construct();
@@ -96,7 +96,8 @@ Group( const Group& other, scene::Node& node, const Callback<void()>& transformC
        m_nameKeys( m_entity ),
        m_originKey( OriginChangedCaller( *this ) ),
        m_origin( ORIGINKEY_IDENTITY ),
-       m_renderName( m_named, g_vector3_identity ),
+       m_name_origin( g_vector3_identity ),
+       m_renderName( m_named, m_name_origin ),
        m_transformChanged( transformChanged ),
        m_evaluateTransform( evaluateTransform ){
        construct();
index 9d8b92472c5f3165f546dc4c62beb109da1246e7..890d2e8cd33e49e58a0d660bf0f47063750da519 100644 (file)
@@ -531,10 +531,12 @@ Callback<void()> m_changed;
 bool m_useCenterKey;
 
 Doom3LightRadius( const char* defaultRadius ) : m_defaultRadius( 300, 300, 300 ), m_center( 0, 0, 0 ), m_useCenterKey( false ){
-       if ( !string_parse_vector3( defaultRadius, m_defaultRadius ) ) {
+       if ( g_lightType == LIGHTTYPE_DOOM3 ){
+               if ( !string_parse_vector3( defaultRadius, m_defaultRadius ) ) {
                globalErrorStream() << "Doom3LightRadius: failed to parse default light radius\n";
-       }
+               }
        m_radius = m_defaultRadius;
+       }
 }
 
 void lightRadiusChanged( const char* value ){
@@ -653,11 +655,11 @@ void render( RenderStateFlags state ) const {
        aabb_draw_wire( points );
 }
 };
-
+/*
 inline void default_extents( Vector3& extents ){
-       extents = Vector3( 8, 8, 8 );
+       extents = Vector3( 12, 12, 12 );
 }
-
+*/
 class ShaderRef
 {
 CopiedString m_name;
@@ -771,6 +773,8 @@ Doom3GroupOrigin m_funcStaticOrigin;
 LightRadii m_radii;
 Doom3LightRadius m_doom3Radius;
 
+AABB m_aabb_light;
+
 RenderLightRadiiWire m_radii_wire;
 RenderLightRadiiFill m_radii_fill;
 RenderLightRadiiBox m_radii_box;
@@ -803,16 +807,14 @@ RenderLightProjection m_renderProjection;
 
 LightShader m_shader;
 
-AABB m_aabb_light;
-
 Callback<void()> m_transformChanged;
 Callback<void()> m_boundsChanged;
 Callback<void()> m_evaluateTransform;
 
 void construct(){
        default_rotation( m_rotation );
-       m_aabb_light.origin = Vector3( 0, 0, 0 );
-       default_extents( m_aabb_light.extents );
+       //m_aabb_light.origin = Vector3( 0, 0, 0 );
+       //default_extents( m_aabb_light.extents );
 
        m_keyObservers.insert( "classname", ClassnameFilter::ClassnameChangedCaller( m_filter ) );
        m_keyObservers.insert( Static<KeyIsName>::instance().m_nameKey, NamedEntity::IdentifierChangedCaller( m_named ) );
@@ -980,6 +982,7 @@ Light( EntityClass* eclass, scene::Node& node, const Callback<void()>& transform
        m_nameKeys( m_entity ),
        m_funcStaticOrigin( m_traverse, m_originKey.m_origin ),
        m_doom3Radius( EntityClass_valueForKey( m_entity.getEntityClass(), "light_radius" ) ),
+       m_aabb_light( Vector3( 0, 0, 0 ), Vector3( 12, 12, 12 ) ),
        m_radii_wire( m_radii, m_aabb_light.origin ),
        m_radii_fill( m_radii, m_aabb_light.origin ),
        m_radii_box( m_aabb_light.origin ),
@@ -1003,6 +1006,7 @@ Light( const Light& other, scene::Node& node, const Callback<void()>& transformC
        m_nameKeys( m_entity ),
        m_funcStaticOrigin( m_traverse, m_originKey.m_origin ),
        m_doom3Radius( EntityClass_valueForKey( m_entity.getEntityClass(), "light_radius" ) ),
+       m_aabb_light( Vector3( 0, 0, 0 ), Vector3( 12, 12, 12 ) ),
        m_radii_wire( m_radii, m_aabb_light.origin ),
        m_radii_fill( m_radii, m_aabb_light.origin ),
        m_radii_box( m_aabb_light.origin ),
@@ -1143,7 +1147,7 @@ void renderSolid( Renderer& renderer, const VolumeTest& volume, const Matrix4& l
 }
 void renderWireframe( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld, bool selected ) const {
        renderSolid( renderer, volume, localToWorld, selected );
-       if ( g_showNames ) {
+       if ( g_showNames && !string_equal( m_named.name(), "light" ) ) {
                renderer.addRenderable( m_renderName, localToWorld );
        }
 }
index ac541a6f34f270ca6ab3e1d2ef6056712802e188..4568aec1c966ef67a839ef2a48d3411f63fdf94b 100644 (file)
@@ -199,7 +199,7 @@ void renderSolid( Renderer& renderer, const VolumeTest& volume, const Matrix4& l
 }
 void renderWireframe( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld, bool selected ) const {
        renderSolid( renderer, volume, localToWorld, selected );
-       if ( g_showNames ) {
+       if ( g_showNames  && !string_equal( m_named.name(), "misc_model" ) ) {
                renderer.addRenderable( m_renderName, localToWorld );
        }
 }
index 857b10b44a5e41a0066da12e01fc5fc8feb0a08a..8e604ec58341af392d07719eaebb5f5c09463e63 100644 (file)
@@ -26,6 +26,7 @@
 #include "eclasslib.h"
 #include "generic/callback.h"
 #include "nameable.h"
+#include "entity.h" //g_showTargetNames
 
 #include <set>
 
@@ -62,6 +63,9 @@ const char* name() const {
        }
        return m_name.c_str();
 }
+const char* classname() const {
+       return m_entity.getEntityClass().name();
+}
 void attach( const NameCallback& callback ){
        m_changed.insert( callback );
 }
@@ -92,7 +96,7 @@ RenderableNamedEntity( const NamedEntity& named, const Vector3& position )
 }
 void render( RenderStateFlags state ) const {
        glRasterPos3fv( vector3_to_array( m_position ) );
-       GlobalOpenGL().drawString( m_named.name() );
+       GlobalOpenGL().drawString( g_showTargetNames ? m_named.name() : m_named.classname() );
 }
 };
 
index 99046a508f2af58aa491ce95f59d2ba70437ccd9..cc840b1f4bc1216a7cf25af9c21822daddc281f7 100644 (file)
@@ -184,9 +184,57 @@ TargetLinesPushBack( RenderablePointVector& targetLines, const Vector3& worldPos
        m_targetLines( targetLines ), m_worldPosition( worldPosition ), m_volume( volume ){
 }
 void operator()( const Vector3& worldPosition ) const {
-       if ( m_volume.TestLine( segment_for_startend( m_worldPosition, worldPosition ) ) ) {
+       Vector3 dir( worldPosition - m_worldPosition );//end - start
+       double len = vector3_length( dir );
+       if ( len != 0 && m_volume.TestLine( segment_for_startend( m_worldPosition, worldPosition ) ) ) {
                m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( m_worldPosition ) ) );
                m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( worldPosition ) ) );
+
+               Vector3 mid( ( worldPosition + m_worldPosition ) * 0.5f );
+               //vector3_normalise( dir );
+               dir /= len;
+               Vector3 hack( 0.57735, 0.57735, 0.57735 );
+               int maxI = 0;
+               float max = 0;
+               for ( int i = 0; i < 3 ; ++i ){
+                       if ( dir[i] < 0 ){
+                               hack[i] *= -1;
+                       }
+                       if ( fabs( dir[i] ) > max ){
+                               maxI = i;
+                       }
+               }
+               hack[maxI] *= -1;
+
+               Vector3 ort( vector3_cross( dir, hack ) );
+               //vector3_normalise( ort );
+               Vector3 wing1( mid - dir*12 + ort*6 );
+               Vector3 wing2( wing1 - ort*12 );
+
+               if( len <= 512 || len > 768 ){
+                       m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( mid ) ) );
+                       m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( wing1 ) ) );
+                       m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( mid ) ) );
+                       m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( wing2 ) ) );
+               }
+               if( len > 512 ){
+                       Vector3 wing1_delta( mid - wing1 );
+                       Vector3 wing2_delta( mid - wing2 );
+                       Vector3 point( m_worldPosition + dir*256 );
+                       wing1 = point - wing1_delta;
+                       wing2 = point - wing2_delta;
+                       m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( point ) ) );
+                       m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( wing1 ) ) );
+                       m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( point ) ) );
+                       m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( wing2 ) ) );
+                       point =  worldPosition - dir*256;
+                       wing1 = point - wing1_delta;
+                       wing2 = point - wing2_delta;
+                       m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( point ) ) );
+                       m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( wing1 ) ) );
+                       m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( point ) ) );
+                       m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( wing2 ) ) );
+               }
        }
 }
 };
@@ -253,7 +301,7 @@ RenderableTargetingEntity( TargetingEntity& targets )
 }
 void compile( const VolumeTest& volume, const Vector3& world_position ) const {
        m_target_lines.clear();
-       m_target_lines.reserve( m_targets.size() * 2 );
+       m_target_lines.reserve( m_targets.size() * 14 );
        TargetingEntity_forEach( m_targets, TargetLinesPushBack( m_target_lines, world_position, volume ) );
 }
 void render( Renderer& renderer, const VolumeTest& volume, const Vector3& world_position ) const {
index 4fa8731f5a3b7600ead4d9e16982b0002567bb58..88233a9af05a56ffdca7bb25df4901ee70d9977d 100644 (file)
@@ -413,4 +413,4 @@ Image* LoadTGABuff( const byte* buffer ){
 Image* LoadTGA( ArchiveFile& file ){
        ScopedArchiveBuffer buffer( file );
        return LoadTGABuff( buffer.buffer );
-}
+}
\ No newline at end of file
index 449bbf1bca9d689fa810bf09c63f114b1baecc7a..b6cde5813a60a8adfb047a815da4414a028f2176 100644 (file)
@@ -109,6 +109,11 @@ Image* LoadPNGBuff( unsigned char* fbuffer ){
                png_set_expand_gray_1_2_4_to_8( png_ptr );
        }
 
+       // Strip 16-bit to 8-bit
+       if ( bit_depth == 16 ) {
+               png_set_strip_16( png_ptr );
+       }
+
        if ( png_get_valid( png_ptr, info_ptr, PNG_INFO_tRNS ) ) {
                png_set_tRNS_to_alpha( png_ptr );
        }
index 4bfbbe78da0097a8d8974a4c658ac32b7eb0767f..cbf0ec05140334b31be28215bee76a8c1cbb84ce 100644 (file)
@@ -72,6 +72,10 @@ bool pre( scene::Node &node ) const {
 
        Entity* entity = Node_getEntity( node );
        if ( entity != 0 ) {
+               if( entity->isContainer() && Node_getTraversable( node )->empty() && !string_equal( entity->getKeyValue( "classname" ), "worldspawn" ) ){
+                       globalErrorStream() << "discarding empty group entity: # = " << g_count_entities << "; classname = " << entity->getKeyValue( "classname" ) << "\n";
+                       return false;
+               }
                if ( m_writeComments ) {
                        m_writer.writeToken( "//" );
                        m_writer.writeToken( "entity" );
index e1c5de56d23f0ea0e4ec149a6c62115d9a0992b8..8fea68d7844ba904a28318b9a4dfa3ac7547f44d 100644 (file)
@@ -29,7 +29,7 @@
 
 #include "model.h"
 
-#define MD5_RETURN_FALSE_IF_FAIL(expression) do { if (!(expression)) { globalErrorStream() << "md5 parse failed: " #expression "\n"; return false; } } while (0)
+#define MD5_RETURN_FALSE_IF_FAIL( expression ) do{ if ( !( expression ) ) { globalErrorStream() << "md5 parse failed: " # expression "\n"; return false; } }while( 0 )
 
 bool MD5_parseToken( Tokeniser& tokeniser, const char* string ){
        const char* token = tokeniser.getToken();
index 4576a6416e7ef4502c60ddcd845414d36cf1437e..6a24236dfecf5511742ae5e4ffc093bc9d480ae4 100644 (file)
@@ -39,6 +39,7 @@ class ShadersDependencies :
 ImageModuleRef m_bitmapModule;
 public:
 ShadersDependencies() :
+       //m_bitmapModule( "bmp" ){
        m_bitmapModule( "png" ){
 }
 ImageModuleRef& getBitmapModule(){
index 58c5232581958eebfd36082a1a86336d7e32b76f..14308bad3b10beb8a005cdfe677bb7b64e066d82 100644 (file)
@@ -635,7 +635,7 @@ bool ShaderTemplate::parseDoom3( Tokeniser& tokeniser ){
                                m_nFlags |= QER_NONSOLID;
                        }
                        else if ( string_equal_nocase( token, "liquid" ) ) {
-                               m_nFlags |= QER_WATER;
+                               m_nFlags |= QER_LIQUID;
                        }
                        else if ( string_equal_nocase( token, "areaportal" ) ) {
                                m_nFlags |= QER_AREAPORTAL;
@@ -1277,6 +1277,7 @@ bool ShaderTemplate::parseQuake3( Tokeniser& tokeniser ){
 
                                if ( string_equal_nocase( surfaceparm, "fog" ) ) {
                                        m_nFlags |= QER_FOG;
+                                       m_nFlags |= QER_TRANS;
                                        if ( m_fTrans == 1.0f ) { // has not been explicitly set by qer_trans
                                                m_fTrans = 0.35f;
                                        }
@@ -1287,11 +1288,10 @@ bool ShaderTemplate::parseQuake3( Tokeniser& tokeniser ){
                                else if ( string_equal_nocase( surfaceparm, "nonsolid" ) ) {
                                        m_nFlags |= QER_NONSOLID;
                                }
-                               else if ( string_equal_nocase( surfaceparm, "water" ) ) {
-                                       m_nFlags |= QER_WATER;
-                               }
-                               else if ( string_equal_nocase( surfaceparm, "lava" ) ) {
-                                       m_nFlags |= QER_LAVA;
+                               else if ( string_equal_nocase( surfaceparm, "water" ) ||
+                                                       string_equal_nocase( surfaceparm, "lava" ) ||
+                                                       string_equal_nocase( surfaceparm, "slime") ){
+                                       m_nFlags |= QER_LIQUID;
                                }
                                else if ( string_equal_nocase( surfaceparm, "areaportal" ) ) {
                                        m_nFlags |= QER_AREAPORTAL;
index 12ee1dfa8bd8c7a736328b21aaf4126dd1119888..f405a2598389c35baa93584ed47eeb3443afc805 100644 (file)
@@ -1,6 +1,7 @@
 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
 
 find_package(OpenGL REQUIRED)
+find_package(GTK${GTK_TARGET} REQUIRED)
 
 string(SUBSTRING ${CMAKE_SHARED_MODULE_SUFFIX} 1 -1 _clibext)
 add_definitions(-DCMAKE_SHARED_MODULE_SUFFIX="${_clibext}")
@@ -34,12 +35,14 @@ set(RADIANTLIST
     error.cpp error.h
     feedback.cpp feedback.h
     filetypes.cpp filetypes.h
+    filterbar.cpp filterbar.h
     filters.cpp filters.h
     findtexturedialog.cpp findtexturedialog.h
     grid.cpp grid.h
     groupdialog.cpp groupdialog.h
     gtkdlgs.cpp gtkdlgs.h
     gtkmisc.cpp gtkmisc.h
+       gtktheme.cpp gtktheme.h
     help.cpp help.h
     image.cpp image.h
     main.cpp main.h
@@ -123,6 +126,7 @@ target_link_libraries(${RADIANT_BASENAME}
     splines
     stream
     string
+    transformpath
     uilib
     xmllib
 )
@@ -153,4 +157,23 @@ if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux"
     target_link_libraries(${RADIANT_BASENAME} -no-pie)
 endif()
 
+if(NOT WIN32 AND NOT APPLE)
+    if(GTK_TARGET EQUAL 3)
+        if(OpenGL_GL_PREFERENCE STREQUAL "GLVND")
+            # This should not be needed, as FindOpenGL should take care of
+            # this, but maybe GTK rewrites this badly?
+
+            # If not -lOpenGL:
+            #   /usr/bin/ld: CMakeFiles/netradiant.dir/qgl.cpp.o: in function `QGL_sharedContextCreated(OpenGLBinding&)':
+            #   qgl.cpp:(.text+0x1c52): undefined reference to `glAccum'
+
+            # If not -lGL:
+            #   /usr/bin/ld: CMakeFiles/netradiant.dir/qgl.cpp.o: in function `QGL_Shutdown(OpenGLBinding&)':
+            #   qgl.cpp:(.text+0x2d): undefined reference to `glXQueryExtension'
+
+            target_link_libraries(${RADIANT_BASENAME} -lOpenGL -lGL)
+        endif()
+    endif()
+endif()
+
 copy_dlls(${RADIANT_BASENAME})
index 9921d4be4d9cd100e6b109cabab3b87fc3345f0c..59da27a40f7ddc368a3d064d2d380cef8e515e68 100644 (file)
@@ -106,7 +106,7 @@ void Map_Snapshot(){
  */
 
 bool g_AutoSave_Enabled = true;
-int m_AutoSave_Frequency = 5;
+int m_AutoSave_Frequency = 15;
 bool g_SnapShots_Enabled = false;
 
 namespace
index 5e38cc1d62e785797468ceb7a103bf26cbd5f40a..443a69ec53ba42d3b83aed33110d932b53aa7414 100644 (file)
@@ -35,7 +35,7 @@ void Brush_textureChanged(){
 QuantiseFunc Face::m_quantise;
 EBrushType Face::m_type;
 EBrushType FacePlane::m_type;
-bool g_brush_texturelock_enabled = true;
+bool g_brush_texturelock_enabled = false;
 
 EBrushType Brush::m_type;
 double Brush::m_maxWorldCoord = 0;
index 7eeeb1df7aefd1b9af7c03f6fc9075659949f48e..eebf4bb28fe2f884e48b2016869e01ff3e5d7354 100644 (file)
@@ -306,8 +306,8 @@ inline void brush_check_shader( const char* name ){
 class FaceShaderObserver
 {
 public:
-       virtual void realiseShader() = 0;
-       virtual void unrealiseShader() = 0;
+virtual void realiseShader() = 0;
+virtual void unrealiseShader() = 0;
 };
 
 typedef ReferencePair<FaceShaderObserver> FaceShaderObserverPair;
@@ -316,20 +316,20 @@ typedef ReferencePair<FaceShaderObserver> FaceShaderObserverPair;
 class ContentsFlagsValue
 {
 public:
-       ContentsFlagsValue(){
-       }
+ContentsFlagsValue(){
+}
 
-       ContentsFlagsValue( int surfaceFlags, int contentFlags, int value, bool specified ) :
-               m_surfaceFlags( surfaceFlags ),
-               m_contentFlags( contentFlags ),
-               m_value( value ),
-               m_specified( specified ){
-       }
+ContentsFlagsValue( int surfaceFlags, int contentFlags, int value, bool specified ) :
+       m_surfaceFlags( surfaceFlags ),
+       m_contentFlags( contentFlags ),
+       m_value( value ),
+       m_specified( specified ){
+}
 
-       int m_surfaceFlags;
-       int m_contentFlags;
-       int m_value;
-       bool m_specified;
+int m_surfaceFlags;
+int m_contentFlags;
+int m_value;
+bool m_specified;
 };
 
 inline void ContentsFlagsValue_assignMasked( ContentsFlagsValue& flags, const ContentsFlagsValue& other ){
@@ -348,279 +348,280 @@ inline void ContentsFlagsValue_assignMasked( ContentsFlagsValue& flags, const Co
 class FaceShader : public ModuleObserver
 {
 public:
-       class SavedState
-       {
-       public:
-               CopiedString m_shader;
-               ContentsFlagsValue m_flags;
+class SavedState
+{
+public:
+CopiedString m_shader;
+ContentsFlagsValue m_flags;
 
-               SavedState( const FaceShader& faceShader ){
-                       m_shader = faceShader.getShader();
-                       m_flags = faceShader.m_flags;
-               }
+SavedState( const FaceShader& faceShader ){
+       m_shader = faceShader.getShader();
+       m_flags = faceShader.m_flags;
+}
 
-               void exportState( FaceShader& faceShader ) const {
-                       faceShader.setShader( m_shader.c_str() );
-                       faceShader.m_flags = m_flags;
-               }
-       };
+void exportState( FaceShader& faceShader ) const {
+       faceShader.setShader( m_shader.c_str() );
+       //faceShader.setFlags( m_flags );
+       faceShader.m_flags = m_flags;
+}
+};
 
-       CopiedString m_shader;
-       Shader* m_state;
-       ContentsFlagsValue m_flags;
-       FaceShaderObserverPair m_observers;
-       bool m_instanced;
-       bool m_realised;
+CopiedString m_shader;
+Shader* m_state;
+ContentsFlagsValue m_flags;
+FaceShaderObserverPair m_observers;
+bool m_instanced;
+bool m_realised;
 
-       FaceShader( const char* shader, const ContentsFlagsValue& flags = ContentsFlagsValue( 0, 0, 0, false ) ) :
-               m_shader( shader ),
-               m_state( 0 ),
-               m_flags( flags ),
-               m_instanced( false ),
-               m_realised( false ){
-               captureShader();
-       }
+FaceShader( const char* shader, const ContentsFlagsValue& flags = ContentsFlagsValue( 0, 0, 0, false ) ) :
+       m_shader( shader ),
+       m_state( 0 ),
+       m_flags( flags ),
+       m_instanced( false ),
+       m_realised( false ){
+       captureShader();
+}
 
-       ~FaceShader(){
-               releaseShader();
-       }
+~FaceShader(){
+       releaseShader();
+}
 
-       // copy-construction not supported
-       FaceShader( const FaceShader& other );
+// copy-construction not supported
+FaceShader( const FaceShader& other );
 
-       void instanceAttach(){
-               m_instanced = true;
-               m_state->incrementUsed();
-       }
+void instanceAttach(){
+       m_instanced = true;
+       m_state->incrementUsed();
+}
 
-       void instanceDetach(){
-               m_state->decrementUsed();
-               m_instanced = false;
-       }
+void instanceDetach(){
+       m_state->decrementUsed();
+       m_instanced = false;
+}
 
-       void captureShader(){
-               ASSERT_MESSAGE( m_state == 0, "shader cannot be captured" );
-               brush_check_shader( m_shader.c_str() );
-               m_state = GlobalShaderCache().capture( m_shader.c_str() );
-               m_state->attach( *this );
-       }
+void captureShader(){
+       ASSERT_MESSAGE( m_state == 0, "shader cannot be captured" );
+       brush_check_shader( m_shader.c_str() );
+       m_state = GlobalShaderCache().capture( m_shader.c_str() );
+       m_state->attach( *this );
+}
 
-       void releaseShader(){
-               ASSERT_MESSAGE( m_state != 0, "shader cannot be released" );
-               m_state->detach( *this );
-               GlobalShaderCache().release( m_shader.c_str() );
-               m_state = 0;
-       }
+void releaseShader(){
+       ASSERT_MESSAGE( m_state != 0, "shader cannot be released" );
+       m_state->detach( *this );
+       GlobalShaderCache().release( m_shader.c_str() );
+       m_state = 0;
+}
 
-       void realise(){
-               ASSERT_MESSAGE( !m_realised, "FaceTexdef::realise: already realised" );
-               m_realised = true;
-               m_observers.forEach([](FaceShaderObserver &observer) {
-                       observer.realiseShader();
-               });
-       }
+void realise(){
+       ASSERT_MESSAGE( !m_realised, "FaceTexdef::realise: already realised" );
+       m_realised = true;
+       m_observers.forEach([](FaceShaderObserver &observer) {
+               observer.realiseShader();
+       });
+}
 
-       void unrealise(){
-               ASSERT_MESSAGE( m_realised, "FaceTexdef::unrealise: already unrealised" );
-               m_observers.forEach([](FaceShaderObserver &observer) {
-                       observer.unrealiseShader();
-               });
-               m_realised = false;
-       }
+void unrealise(){
+       ASSERT_MESSAGE( m_realised, "FaceTexdef::unrealise: already unrealised" );
+       m_observers.forEach([](FaceShaderObserver &observer) {
+               observer.unrealiseShader();
+       });
+       m_realised = false;
+}
 
-       void attach( FaceShaderObserver& observer ){
-               m_observers.attach( observer );
-               if ( m_realised ) {
-                       observer.realiseShader();
-               }
+void attach( FaceShaderObserver& observer ){
+       m_observers.attach( observer );
+       if ( m_realised ) {
+               observer.realiseShader();
        }
+}
 
-       void detach( FaceShaderObserver& observer ){
-               if ( m_realised ) {
-                       observer.unrealiseShader();
-               }
-               m_observers.detach( observer );
+void detach( FaceShaderObserver& observer ){
+       if ( m_realised ) {
+               observer.unrealiseShader();
        }
+       m_observers.detach( observer );
+}
 
-       const char* getShader() const {
-               return m_shader.c_str();
+const char* getShader() const {
+       return m_shader.c_str();
+}
+void setShader( const char* name ){
+       if ( m_instanced ) {
+               m_state->decrementUsed();
        }
-       void setShader( const char* name ){
-               if ( m_instanced ) {
-                       m_state->decrementUsed();
-               }
-               releaseShader();
-               m_shader = name;
-               captureShader();
-               if ( m_instanced ) {
-                       m_state->incrementUsed();
-               }
+       releaseShader();
+       m_shader = name;
+       captureShader();
+       if ( m_instanced ) {
+               m_state->incrementUsed();
        }
+}
 
-       ContentsFlagsValue getFlags() const {
-               ASSERT_MESSAGE( m_realised, "FaceShader::getFlags: flags not valid when unrealised" );
-               if ( !m_flags.m_specified ) {
-                       return ContentsFlagsValue(
-                                          m_state->getTexture().surfaceFlags,
-                                          m_state->getTexture().contentFlags,
-                                          m_state->getTexture().value,
-                                          true
-                                          );
-               }
-               return m_flags;
+ContentsFlagsValue getFlags() const {
+       ASSERT_MESSAGE( m_realised, "FaceShader::getFlags: flags not valid when unrealised" );
+       if ( !m_flags.m_specified ) {
+               return ContentsFlagsValue(
+                                  m_state->getTexture().surfaceFlags,
+                                  m_state->getTexture().contentFlags,
+                                  m_state->getTexture().value,
+                                  true
+                                  );
        }
+       return m_flags;
+}
 
-       void setFlags( const ContentsFlagsValue& flags ){
-               ASSERT_MESSAGE( m_realised, "FaceShader::setFlags: flags not valid when unrealised" );
-               ContentsFlagsValue_assignMasked( m_flags, flags );
-       }
+void setFlags( const ContentsFlagsValue& flags ){
+       ASSERT_MESSAGE( m_realised, "FaceShader::setFlags: flags not valid when unrealised" );
+       ContentsFlagsValue_assignMasked( m_flags, flags );
+}
 
-       Shader* state() const {
-               return m_state;
-       }
+Shader* state() const {
+       return m_state;
+}
 
-       std::size_t width() const {
-               if ( m_realised ) {
-                       return m_state->getTexture().width;
-               }
-               return 1;
+std::size_t width() const {
+       if ( m_realised ) {
+               return m_state->getTexture().width;
        }
+       return 1;
+}
 
-       std::size_t height() const {
-               if ( m_realised ) {
-                       return m_state->getTexture().height;
-               }
-               return 1;
+std::size_t height() const {
+       if ( m_realised ) {
+               return m_state->getTexture().height;
        }
+       return 1;
+}
 
-       unsigned int shaderFlags() const {
-               if ( m_realised ) {
-                       return m_state->getFlags();
-               }
-               return 0;
+unsigned int shaderFlags() const {
+       if ( m_realised ) {
+               return m_state->getFlags();
        }
+       return 0;
+}
 };
 
 
 class FaceTexdef : public FaceShaderObserver
 {
-       // not copyable
-       FaceTexdef( const FaceTexdef& other );
+// not copyable
+FaceTexdef( const FaceTexdef& other );
 
-       // not assignable
-       FaceTexdef& operator=( const FaceTexdef& other );
+// not assignable
+FaceTexdef& operator=( const FaceTexdef& other );
 
 public:
-       class SavedState
-       {
-       public:
-               TextureProjection m_projection;
-
-               SavedState( const FaceTexdef& faceTexdef ){
-                       m_projection = faceTexdef.m_projection;
-               }
-
-               void exportState( FaceTexdef& faceTexdef ) const {
-                       Texdef_Assign( faceTexdef.m_projection, m_projection );
-               }
-       };
+class SavedState
+{
+public:
+TextureProjection m_projection;
 
-       FaceShader& m_shader;
-       TextureProjection m_projection;
-       bool m_projectionInitialised;
-       bool m_scaleApplied;
+SavedState( const FaceTexdef& faceTexdef ){
+       m_projection = faceTexdef.m_projection;
+}
 
-       FaceTexdef(
-               FaceShader& shader,
-               const TextureProjection& projection,
-               bool projectionInitialised = true
-               ) :
-               m_shader( shader ),
-               m_projection( projection ),
-               m_projectionInitialised( projectionInitialised ),
-               m_scaleApplied( false ){
-               m_shader.attach( *this );
-       }
+void exportState( FaceTexdef& faceTexdef ) const {
+       Texdef_Assign( faceTexdef.m_projection, m_projection );
+}
+};
 
-       ~FaceTexdef(){
-               m_shader.detach( *this );
-       }
+FaceShader& m_shader;
+TextureProjection m_projection;
+bool m_projectionInitialised;
+bool m_scaleApplied;
 
-       void addScale(){
-               ASSERT_MESSAGE( !m_scaleApplied, "texture scale aready added" );
-               m_scaleApplied = true;
-               m_projection.m_brushprimit_texdef.addScale( m_shader.width(), m_shader.height() );
-       }
+FaceTexdef(
+       FaceShader& shader,
+       const TextureProjection& projection,
+       bool projectionInitialised = true
+       ) :
+       m_shader( shader ),
+       m_projection( projection ),
+       m_projectionInitialised( projectionInitialised ),
+       m_scaleApplied( false ){
+       m_shader.attach( *this );
+}
 
-       void removeScale(){
-               ASSERT_MESSAGE( m_scaleApplied, "texture scale aready removed" );
-               m_scaleApplied = false;
-               m_projection.m_brushprimit_texdef.removeScale( m_shader.width(), m_shader.height() );
-       }
+~FaceTexdef(){
+       m_shader.detach( *this );
+}
 
-       void realiseShader(){
-               if ( m_projectionInitialised && !m_scaleApplied ) {
-                       addScale();
-               }
-       }
+void addScale(){
+       ASSERT_MESSAGE( !m_scaleApplied, "texture scale aready added" );
+       m_scaleApplied = true;
+       m_projection.m_brushprimit_texdef.addScale( m_shader.width(), m_shader.height() );
+}
 
-       void unrealiseShader(){
-               if ( m_projectionInitialised && m_scaleApplied ) {
-                       removeScale();
-               }
-       }
+void removeScale(){
+       ASSERT_MESSAGE( m_scaleApplied, "texture scale aready removed" );
+       m_scaleApplied = false;
+       m_projection.m_brushprimit_texdef.removeScale( m_shader.width(), m_shader.height() );
+}
 
-       void setTexdef( const TextureProjection& projection ){
-               removeScale();
-               Texdef_Assign( m_projection, projection );
+void realiseShader(){
+       if ( m_projectionInitialised && !m_scaleApplied ) {
                addScale();
        }
+}
 
-       void shift( float s, float t ){
-               ASSERT_MESSAGE( texdef_sane( m_projection.m_texdef ), "FaceTexdef::shift: bad texdef" );
+void unrealiseShader(){
+       if ( m_projectionInitialised && m_scaleApplied ) {
                removeScale();
-               Texdef_Shift( m_projection, s, t );
-               addScale();
        }
+}
 
-       void scale( float s, float t ){
-               removeScale();
-               Texdef_Scale( m_projection, s, t );
-               addScale();
-       }
+void setTexdef( const TextureProjection& projection ){
+       removeScale();
+       Texdef_Assign( m_projection, projection );
+       addScale();
+}
 
-       void rotate( float angle ){
-               removeScale();
-               Texdef_Rotate( m_projection, angle );
-               addScale();
-       }
+void shift( float s, float t ){
+       ASSERT_MESSAGE( texdef_sane( m_projection.m_texdef ), "FaceTexdef::shift: bad texdef" );
+       removeScale();
+       Texdef_Shift( m_projection, s, t );
+       addScale();
+}
 
-       void fit( const Vector3& normal, const Winding& winding, float s_repeat, float t_repeat ){
-               Texdef_FitTexture( m_projection, m_shader.width(), m_shader.height(), normal, winding, s_repeat, t_repeat );
-       }
+void scale( float s, float t ){
+       removeScale();
+       Texdef_Scale( m_projection, s, t );
+       addScale();
+}
 
-       void emitTextureCoordinates( Winding& winding, const Vector3& normal, const Matrix4& localToWorld ){
-               Texdef_EmitTextureCoordinates( m_projection, m_shader.width(), m_shader.height(), winding, normal, localToWorld );
-       }
+void rotate( float angle ){
+       removeScale();
+       Texdef_Rotate( m_projection, angle );
+       addScale();
+}
 
-       void transform( const Plane3& plane, const Matrix4& matrix ){
-               removeScale();
-               Texdef_transformLocked( m_projection, m_shader.width(), m_shader.height(), plane, matrix );
-               addScale();
-       }
+void fit( const Vector3& normal, const Winding& winding, float s_repeat, float t_repeat ){
+       Texdef_FitTexture( m_projection, m_shader.width(), m_shader.height(), normal, winding, s_repeat, t_repeat );
+}
 
-       TextureProjection normalised() const {
-               brushprimit_texdef_t tmp( m_projection.m_brushprimit_texdef );
-               tmp.removeScale( m_shader.width(), m_shader.height() );
-               return TextureProjection( m_projection.m_texdef, tmp, m_projection.m_basis_s, m_projection.m_basis_t );
-       }
+void emitTextureCoordinates( Winding& winding, const Vector3& normal, const Matrix4& localToWorld ){
+       Texdef_EmitTextureCoordinates( m_projection, m_shader.width(), m_shader.height(), winding, normal, localToWorld );
+}
 
-       void setBasis( const Vector3& normal ){
-               Matrix4 basis;
-               Normal_GetTransform( normal, basis );
-               m_projection.m_basis_s = Vector3( basis.xx(), basis.yx(), basis.zx() );
-               m_projection.m_basis_t = Vector3( -basis.xy(), -basis.yy(), -basis.zy() );
-       }
+void transform( const Plane3& plane, const Matrix4& matrix ){
+       removeScale();
+       Texdef_transformLocked( m_projection, m_shader.width(), m_shader.height(), plane, matrix );
+       addScale();
+}
+
+TextureProjection normalised() const {
+       brushprimit_texdef_t tmp( m_projection.m_brushprimit_texdef );
+       tmp.removeScale( m_shader.width(), m_shader.height() );
+       return TextureProjection( m_projection.m_texdef, tmp, m_projection.m_basis_s, m_projection.m_basis_t );
+}
+
+void setBasis( const Vector3& normal ){
+       Matrix4 basis;
+       Normal_GetTransform( normal, basis );
+       m_projection.m_basis_s = Vector3( basis.xx(), basis.yx(), basis.zx() );
+       m_projection.m_basis_t = Vector3( -basis.xy(), -basis.yy(), -basis.zy() );
+}
 };
 
 inline void planepts_print( const PlanePoints& planePoints, TextOutputStream& ostream ){
@@ -642,192 +643,192 @@ inline Plane3 Plane3_applyTransform( const Plane3& plane, const Matrix4& matrix
 
 class FacePlane
 {
-       PlanePoints m_planepts;
-       Plane3 m_planeCached;
-       Plane3 m_plane;
+PlanePoints m_planepts;
+Plane3 m_planeCached;
+Plane3 m_plane;
 public:
-       Vector3 m_funcStaticOrigin;
+Vector3 m_funcStaticOrigin;
 
-       static EBrushType m_type;
+static EBrushType m_type;
 
-       static bool isDoom3Plane(){
-               return FacePlane::m_type == eBrushTypeDoom3 || FacePlane::m_type == eBrushTypeQuake4;
-       }
+static bool isDoom3Plane(){
+       return FacePlane::m_type == eBrushTypeDoom3 || FacePlane::m_type == eBrushTypeQuake4;
+}
 
        FacePlane& operator=(const FacePlane&) = default;
 
-       class SavedState
+class SavedState
+{
+public:
+PlanePoints m_planepts;
+Plane3 m_plane;
+
+SavedState( const FacePlane& facePlane ){
+       if ( facePlane.isDoom3Plane() ) {
+               m_plane = facePlane.m_plane;
+       }
+       else
        {
-       public:
-               PlanePoints m_planepts;
-               Plane3 m_plane;
+               planepts_assign( m_planepts, facePlane.planePoints() );
+       }
+}
 
-               SavedState( const FacePlane& facePlane ){
-                       if ( facePlane.isDoom3Plane() ) {
-                               m_plane = facePlane.m_plane;
-                       }
-                       else
-                       {
-                               planepts_assign( m_planepts, facePlane.planePoints() );
-                       }
-               }
+void exportState( FacePlane& facePlane ) const {
+       if ( facePlane.isDoom3Plane() ) {
+               facePlane.m_plane = m_plane;
+               facePlane.updateTranslated();
+       }
+       else
+       {
+               planepts_assign( facePlane.planePoints(), m_planepts );
+               facePlane.MakePlane();
+       }
+}
+};
 
-               void exportState( FacePlane& facePlane ) const {
-                       if ( facePlane.isDoom3Plane() ) {
-                               facePlane.m_plane = m_plane;
-                               facePlane.updateTranslated();
-                       }
-                       else
-                       {
-                               planepts_assign( facePlane.planePoints(), m_planepts );
-                               facePlane.MakePlane();
-                       }
-               }
-       };
+FacePlane() : m_funcStaticOrigin( 0, 0, 0 ){
+}
 
-       FacePlane() : m_funcStaticOrigin( 0, 0, 0 ){
+FacePlane( const FacePlane& other ) : m_funcStaticOrigin( 0, 0, 0 ){
+       if ( !isDoom3Plane() ) {
+               planepts_assign( m_planepts, other.m_planepts );
+               MakePlane();
        }
-
-       FacePlane( const FacePlane& other ) : m_funcStaticOrigin( 0, 0, 0 ){
-               if ( !isDoom3Plane() ) {
-                       planepts_assign( m_planepts, other.m_planepts );
-                       MakePlane();
-               }
-               else
-               {
-                       m_plane = other.m_plane;
-                       updateTranslated();
-               }
+       else
+       {
+               m_plane = other.m_plane;
+               updateTranslated();
        }
+}
 
-       void MakePlane(){
-               if ( !isDoom3Plane() ) {
+void MakePlane(){
+       if ( !isDoom3Plane() ) {
 #if 0
-                       if ( check_plane_is_integer( m_planepts ) ) {
-                               globalErrorStream() << "non-integer planepts: ";
-                               planepts_print( m_planepts, globalErrorStream() );
-                               globalErrorStream() << "\n";
-                       }
-#endif
-                       m_planeCached = plane3_for_points( m_planepts );
+               if ( check_plane_is_integer( m_planepts ) ) {
+                       globalErrorStream() << "non-integer planepts: ";
+                       planepts_print( m_planepts, globalErrorStream() );
+                       globalErrorStream() << "\n";
                }
+#endif
+               m_planeCached = plane3_for_points( m_planepts );
        }
+}
 
-       void reverse(){
-               if ( !isDoom3Plane() ) {
-                       vector3_swap( m_planepts[0], m_planepts[2] );
-                       MakePlane();
-               }
-               else
-               {
-                       m_planeCached = plane3_flipped( m_plane );
-                       updateSource();
-               }
+void reverse(){
+       if ( !isDoom3Plane() ) {
+               vector3_swap( m_planepts[0], m_planepts[2] );
+               MakePlane();
        }
+       else
+       {
+               m_planeCached = plane3_flipped( m_plane );
+               updateSource();
+       }
+}
 
-       void transform( const Matrix4& matrix, bool mirror ){
-               if ( !isDoom3Plane() ) {
+void transform( const Matrix4& matrix, bool mirror ){
+       if ( !isDoom3Plane() ) {
 
 #if 0
-                       bool off = check_plane_is_integer( planePoints() );
+               bool off = check_plane_is_integer( planePoints() );
 #endif
 
-                       matrix4_transform_point( matrix, m_planepts[0] );
-                       matrix4_transform_point( matrix, m_planepts[1] );
-                       matrix4_transform_point( matrix, m_planepts[2] );
+               matrix4_transform_point( matrix, m_planepts[0] );
+               matrix4_transform_point( matrix, m_planepts[1] );
+               matrix4_transform_point( matrix, m_planepts[2] );
 
-                       if ( mirror ) {
-                               reverse();
-                       }
+               if ( mirror ) {
+                       reverse();
+               }
 
 #if 0
-                       if ( check_plane_is_integer( planePoints() ) ) {
-                               if ( !off ) {
-                                       globalErrorStream() << "caused by transform\n";
-                               }
+               if ( check_plane_is_integer( planePoints() ) ) {
+                       if ( !off ) {
+                               globalErrorStream() << "caused by transform\n";
                        }
-#endif
-                       MakePlane();
-               }
-               else
-               {
-                       m_planeCached = Plane3_applyTransform( m_planeCached, matrix );
-                       updateSource();
                }
+#endif
+               MakePlane();
+       }
+       else
+       {
+               m_planeCached = Plane3_applyTransform( m_planeCached, matrix );
+               updateSource();
        }
+}
 
-       void offset( float offset ){
-               if ( !isDoom3Plane() ) {
-                       Vector3 move( vector3_scaled( m_planeCached.normal(), -offset ) );
+void offset( float offset ){
+       if ( !isDoom3Plane() ) {
+               Vector3 move( vector3_scaled( m_planeCached.normal(), -offset ) );
 
-                       vector3_subtract( m_planepts[0], move );
-                       vector3_subtract( m_planepts[1], move );
-                       vector3_subtract( m_planepts[2], move );
+               vector3_subtract( m_planepts[0], move );
+               vector3_subtract( m_planepts[1], move );
+               vector3_subtract( m_planepts[2], move );
 
-                       MakePlane();
-               }
-               else
-               {
-                       m_planeCached.d += offset;
-                       updateSource();
-               }
+               MakePlane();
        }
-
-       void updateTranslated(){
-               m_planeCached = Plane3_applyTranslation( m_plane, m_funcStaticOrigin );
+       else
+       {
+               m_planeCached.d += offset;
+               updateSource();
        }
+}
 
-       void updateSource(){
-               m_plane = Plane3_applyTranslation( m_planeCached, vector3_negated( m_funcStaticOrigin ) );
-       }
+void updateTranslated(){
+       m_planeCached = Plane3_applyTranslation( m_plane, m_funcStaticOrigin );
+}
 
+void updateSource(){
+       m_plane = Plane3_applyTranslation( m_planeCached, vector3_negated( m_funcStaticOrigin ) );
+}
 
-       PlanePoints& planePoints(){
-               return m_planepts;
-       }
 
-       const PlanePoints& planePoints() const {
-               return m_planepts;
-       }
+PlanePoints& planePoints(){
+       return m_planepts;
+}
 
-       const Plane3& plane3() const {
-               return m_planeCached;
-       }
+const PlanePoints& planePoints() const {
+       return m_planepts;
+}
 
-       void setDoom3Plane( const Plane3& plane ){
-               m_plane = plane;
-               updateTranslated();
-       }
+const Plane3& plane3() const {
+       return m_planeCached;
+}
 
-       const Plane3& getDoom3Plane() const {
-               return m_plane;
-       }
+void setDoom3Plane( const Plane3& plane ){
+       m_plane = plane;
+       updateTranslated();
+}
 
-       void copy( const FacePlane& other ){
-               if ( !isDoom3Plane() ) {
-                       planepts_assign( m_planepts, other.m_planepts );
-                       MakePlane();
-               }
-               else
-               {
-                       m_planeCached = other.m_plane;
-                       updateSource();
-               }
+const Plane3& getDoom3Plane() const {
+       return m_plane;
+}
+
+void copy( const FacePlane& other ){
+       if ( !isDoom3Plane() ) {
+               planepts_assign( m_planepts, other.m_planepts );
+               MakePlane();
+       }
+       else
+       {
+               m_planeCached = other.m_plane;
+               updateSource();
        }
+}
 
-       void copy( const Vector3& p0, const Vector3& p1, const Vector3& p2 ){
-               if ( !isDoom3Plane() ) {
-                       m_planepts[0] = p0;
-                       m_planepts[1] = p1;
-                       m_planepts[2] = p2;
-                       MakePlane();
-               }
-               else
-               {
-                       m_planeCached = plane3_for_points( p2, p1, p0 );
-                       updateSource();
-               }
+void copy( const Vector3& p0, const Vector3& p1, const Vector3& p2 ){
+       if ( !isDoom3Plane() ) {
+               m_planepts[0] = p0;
+               m_planepts[1] = p1;
+               m_planepts[2] = p2;
+               MakePlane();
+       }
+       else
+       {
+               m_planeCached = plane3_for_points( p2, p1, p0 );
+               updateSource();
        }
+}
 };
 
 inline void Winding_testSelect( Winding& winding, SelectionTest& test, SelectionIntersection& best ){
@@ -851,7 +852,7 @@ class Face;
 class FaceFilter
 {
 public:
-       virtual bool filter( const Face& face ) const = 0;
+virtual bool filter( const Face& face ) const = 0;
 };
 
 bool face_filtered( Face& face );
@@ -868,10 +869,10 @@ extern bool g_brush_texturelock_enabled;
 class FaceObserver
 {
 public:
-       virtual void planeChanged() = 0;
-       virtual void connectivityChanged() = 0;
-       virtual void shaderChanged() = 0;
-       virtual void evaluateTransform() = 0;
+virtual void planeChanged() = 0;
+virtual void connectivityChanged() = 0;
+virtual void shaderChanged() = 0;
+virtual void evaluateTransform() = 0;
 };
 
 class Face :
@@ -1310,21 +1311,21 @@ bool is_bounded() const {
 
 class FaceVertexId
 {
-       std::size_t m_face;
-       std::size_t m_vertex;
+std::size_t m_face;
+std::size_t m_vertex;
 
 public:
-       FaceVertexId( std::size_t face, std::size_t vertex )
-               : m_face( face ), m_vertex( vertex ){
-       }
+FaceVertexId( std::size_t face, std::size_t vertex )
+       : m_face( face ), m_vertex( vertex ){
+}
 
-       std::size_t getFace() const {
-               return m_face;
-       }
+std::size_t getFace() const {
+       return m_face;
+}
 
-       std::size_t getVertex() const {
-               return m_vertex;
-       }
+std::size_t getVertex() const {
+       return m_vertex;
+}
 };
 
 typedef std::size_t faceIndex_t;
@@ -1388,7 +1389,7 @@ typedef std::vector<Brush*> brush_vector_t;
 class BrushFilter
 {
 public:
-       virtual bool filter( const Brush& brush ) const = 0;
+virtual bool filter( const Brush& brush ) const = 0;
 };
 
 bool brush_filtered( Brush& brush );
@@ -1431,81 +1432,81 @@ inline FaceVertexId next_vertex( const Faces& faces, FaceVertexId faceVertex ){
 
 class SelectableEdge
 {
-       Vector3 getEdge() const {
-               const Winding& winding = getFace().getWinding();
-               return vector3_mid( winding[m_faceVertex.getVertex()].vertex, winding[Winding_next( winding, m_faceVertex.getVertex() )].vertex );
-       }
+Vector3 getEdge() const {
+       const Winding& winding = getFace().getWinding();
+       return vector3_mid( winding[m_faceVertex.getVertex()].vertex, winding[Winding_next( winding, m_faceVertex.getVertex() )].vertex );
+}
 
 public:
-       Faces& m_faces;
-       FaceVertexId m_faceVertex;
+Faces& m_faces;
+FaceVertexId m_faceVertex;
 
-       SelectableEdge( Faces& faces, FaceVertexId faceVertex )
-               : m_faces( faces ), m_faceVertex( faceVertex ){
-       }
+SelectableEdge( Faces& faces, FaceVertexId faceVertex )
+       : m_faces( faces ), m_faceVertex( faceVertex ){
+}
 
-       SelectableEdge& operator=( const SelectableEdge& other ){
-               m_faceVertex = other.m_faceVertex;
-               return *this;
-       }
+SelectableEdge& operator=( const SelectableEdge& other ){
+       m_faceVertex = other.m_faceVertex;
+       return *this;
+}
 
-       Face& getFace() const {
-               return *m_faces[m_faceVertex.getFace()];
-       }
+Face& getFace() const {
+       return *m_faces[m_faceVertex.getFace()];
+}
 
-       void testSelect( SelectionTest& test, SelectionIntersection& best ){
-               test.TestPoint( getEdge(), best );
-       }
+void testSelect( SelectionTest& test, SelectionIntersection& best ){
+       test.TestPoint( getEdge(), best );
+}
 };
 
 class SelectableVertex
 {
-       Vector3 getVertex() const {
-               return getFace().getWinding()[m_faceVertex.getVertex()].vertex;
-       }
+Vector3 getVertex() const {
+       return getFace().getWinding()[m_faceVertex.getVertex()].vertex;
+}
 
 public:
-       Faces& m_faces;
-       FaceVertexId m_faceVertex;
+Faces& m_faces;
+FaceVertexId m_faceVertex;
 
-       SelectableVertex( Faces& faces, FaceVertexId faceVertex )
-               : m_faces( faces ), m_faceVertex( faceVertex ){
-       }
+SelectableVertex( Faces& faces, FaceVertexId faceVertex )
+       : m_faces( faces ), m_faceVertex( faceVertex ){
+}
 
-       SelectableVertex& operator=( const SelectableVertex& other ){
-               m_faceVertex = other.m_faceVertex;
-               return *this;
-       }
+SelectableVertex& operator=( const SelectableVertex& other ){
+       m_faceVertex = other.m_faceVertex;
+       return *this;
+}
 
-       Face& getFace() const {
-               return *m_faces[m_faceVertex.getFace()];
-       }
+Face& getFace() const {
+       return *m_faces[m_faceVertex.getFace()];
+}
 
-       void testSelect( SelectionTest& test, SelectionIntersection& best ){
-               test.TestPoint( getVertex(), best );
-       }
+void testSelect( SelectionTest& test, SelectionIntersection& best ){
+       test.TestPoint( getVertex(), best );
+}
 };
 
 class BrushObserver
 {
 public:
-       virtual void reserve( std::size_t size ) = 0;
-       virtual void clear() = 0;
-       virtual void push_back( Face& face ) = 0;
-       virtual void pop_back() = 0;
-       virtual void erase( std::size_t index ) = 0;
-       virtual void connectivityChanged() = 0;
-       virtual void edge_clear() = 0;
-       virtual void edge_push_back( SelectableEdge& edge ) = 0;
-       virtual void vertex_clear() = 0;
-       virtual void vertex_push_back( SelectableVertex& vertex ) = 0;
-       virtual void DEBUG_verify() const = 0;
+virtual void reserve( std::size_t size ) = 0;
+virtual void clear() = 0;
+virtual void push_back( Face& face ) = 0;
+virtual void pop_back() = 0;
+virtual void erase( std::size_t index ) = 0;
+virtual void connectivityChanged() = 0;
+virtual void edge_clear() = 0;
+virtual void edge_push_back( SelectableEdge& edge ) = 0;
+virtual void vertex_clear() = 0;
+virtual void vertex_push_back( SelectableVertex& vertex ) = 0;
+virtual void DEBUG_verify() const = 0;
 };
 
 class BrushVisitor
 {
 public:
-       virtual void visit( Face& face ) const = 0;
+virtual void visit( Face& face ) const = 0;
 };
 
 class Brush :
@@ -1520,878 +1521,883 @@ class Brush :
        public BrushDoom3
 {
 private:
-       scene::Node* m_node;
-       typedef UniqueSet<BrushObserver*> Observers;
-       Observers m_observers;
-       UndoObserver* m_undoable_observer;
-       MapFile* m_map;
+scene::Node* m_node;
+typedef UniqueSet<BrushObserver*> Observers;
+Observers m_observers;
+UndoObserver* m_undoable_observer;
+MapFile* m_map;
 
-       // state
-       Faces m_faces;
-       // ----
+// state
+Faces m_faces;
+// ----
 
-       // cached data compiled from state
-       Array<PointVertex> m_faceCentroidPoints;
-       RenderablePointArray m_render_faces;
+// cached data compiled from state
+Array<PointVertex> m_faceCentroidPoints;
+RenderablePointArray m_render_faces;
 
-       Array<PointVertex> m_uniqueVertexPoints;
-       typedef std::vector<SelectableVertex> SelectableVertices;
-       SelectableVertices m_select_vertices;
-       RenderablePointArray m_render_vertices;
+Array<PointVertex> m_uniqueVertexPoints;
+typedef std::vector<SelectableVertex> SelectableVertices;
+SelectableVertices m_select_vertices;
+RenderablePointArray m_render_vertices;
 
-       Array<PointVertex> m_uniqueEdgePoints;
-       typedef std::vector<SelectableEdge> SelectableEdges;
-       SelectableEdges m_select_edges;
-       RenderablePointArray m_render_edges;
+Array<PointVertex> m_uniqueEdgePoints;
+typedef std::vector<SelectableEdge> SelectableEdges;
+SelectableEdges m_select_edges;
+RenderablePointArray m_render_edges;
 
-       Array<EdgeRenderIndices> m_edge_indices;
-       Array<EdgeFaces> m_edge_faces;
+Array<EdgeRenderIndices> m_edge_indices;
+Array<EdgeFaces> m_edge_faces;
 
-       AABB m_aabb_local;
-       // ----
+AABB m_aabb_local;
+// ----
 
-       Callback<void()> m_evaluateTransform;
-       Callback<void()> m_boundsChanged;
+Callback<void()> m_evaluateTransform;
+Callback<void()> m_boundsChanged;
 
-       mutable bool m_planeChanged;   // b-rep evaluation required
-       mutable bool m_transformChanged;   // transform evaluation required
-       // ----
+mutable bool m_planeChanged;   // b-rep evaluation required
+mutable bool m_transformChanged;   // transform evaluation required
+// ----
 
 public:
-       STRING_CONSTANT( Name, "Brush" );
-
-       Callback<void()> m_lightsChanged;
-
-       // static data
-       static Shader* m_state_point;
-       // ----
-
-       static EBrushType m_type;
-       static double m_maxWorldCoord;
-
-       Brush( scene::Node& node, const Callback<void()>& evaluateTransform, const Callback<void()>& boundsChanged ) :
-               m_node( &node ),
-               m_undoable_observer( 0 ),
-               m_map( 0 ),
-               m_render_faces( m_faceCentroidPoints, GL_POINTS ),
-               m_render_vertices( m_uniqueVertexPoints, GL_POINTS ),
-               m_render_edges( m_uniqueEdgePoints, GL_POINTS ),
-               m_evaluateTransform( evaluateTransform ),
-               m_boundsChanged( boundsChanged ),
-               m_planeChanged( false ),
-               m_transformChanged( false ){
-               planeChanged();
-       }
-       Brush( const Brush& other, scene::Node& node, const Callback<void()>& evaluateTransform, const Callback<void()>& boundsChanged ) :
-               m_node( &node ),
-               m_undoable_observer( 0 ),
-               m_map( 0 ),
-               m_render_faces( m_faceCentroidPoints, GL_POINTS ),
-               m_render_vertices( m_uniqueVertexPoints, GL_POINTS ),
-               m_render_edges( m_uniqueEdgePoints, GL_POINTS ),
-               m_evaluateTransform( evaluateTransform ),
-               m_boundsChanged( boundsChanged ),
-               m_planeChanged( false ),
-               m_transformChanged( false ){
-               copy( other );
-       }
-
-       Brush( const Brush& other ) :
-               TransformNode( other ),
-               Bounded( other ),
-               Cullable( other ),
-               Snappable(),
-               Undoable( other ),
-               FaceObserver( other ),
-               Filterable( other ),
-               Nameable( other ),
-               BrushDoom3( other ),
-               m_node( 0 ),
-               m_undoable_observer( 0 ),
-               m_map( 0 ),
-               m_render_faces( m_faceCentroidPoints, GL_POINTS ),
-               m_render_vertices( m_uniqueVertexPoints, GL_POINTS ),
-               m_render_edges( m_uniqueEdgePoints, GL_POINTS ),
-               m_planeChanged( false ),
-               m_transformChanged( false ){
-               copy( other );
-       }
-
-       ~Brush(){
-               ASSERT_MESSAGE( m_observers.empty(), "Brush::~Brush: observers still attached" );
-       }
-
-       // assignment not supported
-       Brush& operator=( const Brush& other );
-
-       void setDoom3GroupOrigin( const Vector3& origin ){
-               //globalOutputStream() << "func_static origin before: " << m_funcStaticOrigin << " after: " << origin << "\n";
-               for ( Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i )
-               {
-                       ( *i )->getPlane().m_funcStaticOrigin = origin;
-                       ( *i )->getPlane().updateTranslated();
-                       ( *i )->planeChanged();
-               }
-               planeChanged();
-       }
-
-       void attach( BrushObserver& observer ){
-               for ( Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i )
-               {
-                       observer.push_back( *( *i ) );
-               }
+STRING_CONSTANT( Name, "Brush" );
 
-               for ( SelectableEdges::iterator i = m_select_edges.begin(); i != m_select_edges.end(); ++i )
-               {
-                       observer.edge_push_back( *i );
-               }
+Callback<void()> m_lightsChanged;
 
-               for ( SelectableVertices::iterator i = m_select_vertices.begin(); i != m_select_vertices.end(); ++i )
-               {
-                       observer.vertex_push_back( *i );
-               }
+// static data
+static Shader* m_state_point;
+// ----
 
-               m_observers.insert( &observer );
-       }
+static EBrushType m_type;
+static double m_maxWorldCoord;
 
-       void detach( BrushObserver& observer ){
-               m_observers.erase( &observer );
+Brush( scene::Node& node, const Callback<void()>& evaluateTransform, const Callback<void()>& boundsChanged ) :
+       m_node( &node ),
+       m_undoable_observer( 0 ),
+       m_map( 0 ),
+       m_render_faces( m_faceCentroidPoints, GL_POINTS ),
+       m_render_vertices( m_uniqueVertexPoints, GL_POINTS ),
+       m_render_edges( m_uniqueEdgePoints, GL_POINTS ),
+       m_evaluateTransform( evaluateTransform ),
+       m_boundsChanged( boundsChanged ),
+       m_planeChanged( false ),
+       m_transformChanged( false ){
+       planeChanged();
+}
+Brush( const Brush& other, scene::Node& node, const Callback<void()>& evaluateTransform, const Callback<void()>& boundsChanged ) :
+       m_node( &node ),
+       m_undoable_observer( 0 ),
+       m_map( 0 ),
+       m_render_faces( m_faceCentroidPoints, GL_POINTS ),
+       m_render_vertices( m_uniqueVertexPoints, GL_POINTS ),
+       m_render_edges( m_uniqueEdgePoints, GL_POINTS ),
+       m_evaluateTransform( evaluateTransform ),
+       m_boundsChanged( boundsChanged ),
+       m_planeChanged( false ),
+       m_transformChanged( false ){
+       copy( other );
+}
+
+Brush( const Brush& other ) :
+       TransformNode( other ),
+       Bounded( other ),
+       Cullable( other ),
+       Snappable(),
+       Undoable( other ),
+       FaceObserver( other ),
+       Filterable( other ),
+       Nameable( other ),
+       BrushDoom3( other ),
+       m_node( 0 ),
+       m_undoable_observer( 0 ),
+       m_map( 0 ),
+       m_render_faces( m_faceCentroidPoints, GL_POINTS ),
+       m_render_vertices( m_uniqueVertexPoints, GL_POINTS ),
+       m_render_edges( m_uniqueEdgePoints, GL_POINTS ),
+       m_planeChanged( false ),
+       m_transformChanged( false ){
+       copy( other );
+}
+
+~Brush(){
+       ASSERT_MESSAGE( m_observers.empty(), "Brush::~Brush: observers still attached" );
+}
+
+// assignment not supported
+Brush& operator=( const Brush& other );
+
+void setDoom3GroupOrigin( const Vector3& origin ){
+       //globalOutputStream() << "func_static origin before: " << m_funcStaticOrigin << " after: " << origin << "\n";
+       for ( Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i )
+       {
+               ( *i )->getPlane().m_funcStaticOrigin = origin;
+               ( *i )->getPlane().updateTranslated();
+               ( *i )->planeChanged();
        }
+       planeChanged();
+}
 
-       void forEachFace( const BrushVisitor& visitor ) const {
-               for ( Faces::const_iterator i = m_faces.begin(); i != m_faces.end(); ++i )
-               {
-                       visitor.visit( *( *i ) );
-               }
+void attach( BrushObserver& observer ){
+       for ( Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i )
+       {
+               observer.push_back( *( *i ) );
        }
 
-       void forEachFace_instanceAttach( MapFile* map ) const {
-               for ( Faces::const_iterator i = m_faces.begin(); i != m_faces.end(); ++i )
-               {
-                       ( *i )->instanceAttach( map );
-               }
+       for ( SelectableEdges::iterator i = m_select_edges.begin(); i != m_select_edges.end(); ++i )
+       {
+               observer.edge_push_back( *i );
        }
 
-       void forEachFace_instanceDetach( MapFile* map ) const {
-               for ( Faces::const_iterator i = m_faces.begin(); i != m_faces.end(); ++i )
-               {
-                       ( *i )->instanceDetach( map );
-               }
+       for ( SelectableVertices::iterator i = m_select_vertices.begin(); i != m_select_vertices.end(); ++i )
+       {
+               observer.vertex_push_back( *i );
        }
 
-       InstanceCounter m_instanceCounter;
+       m_observers.insert( &observer );
+}
 
-       void instanceAttach( const scene::Path& path ){
-               if ( ++m_instanceCounter.m_count == 1 ) {
-                       m_map = path_find_mapfile( path.begin(), path.end() );
-                       m_undoable_observer = GlobalUndoSystem().observer( this );
-                       GlobalFilterSystem().registerFilterable( *this );
-                       forEachFace_instanceAttach( m_map );
-               }
-               else
-               {
-                       ASSERT_MESSAGE( path_find_mapfile( path.begin(), path.end() ) == m_map, "node is instanced across more than one file" );
-               }
-       }
+void detach( BrushObserver& observer ){
+       m_observers.erase( &observer );
+}
 
-       void instanceDetach( const scene::Path& path ){
-               if ( --m_instanceCounter.m_count == 0 ) {
-                       forEachFace_instanceDetach( m_map );
-                       GlobalFilterSystem().unregisterFilterable( *this );
-                       m_map = 0;
-                       m_undoable_observer = 0;
-                       GlobalUndoSystem().release( this );
-               }
+void forEachFace( const BrushVisitor& visitor ) const {
+       for ( Faces::const_iterator i = m_faces.begin(); i != m_faces.end(); ++i )
+       {
+               visitor.visit( *( *i ) );
        }
+}
 
-       // nameable
-       const char* name() const {
-               return "brush";
+void forEachFace_instanceAttach( MapFile* map ) const {
+       for ( Faces::const_iterator i = m_faces.begin(); i != m_faces.end(); ++i )
+       {
+               ( *i )->instanceAttach( map );
        }
+}
 
-       void attach( const NameCallback& callback ){
+void forEachFace_instanceDetach( MapFile* map ) const {
+       for ( Faces::const_iterator i = m_faces.begin(); i != m_faces.end(); ++i )
+       {
+               ( *i )->instanceDetach( map );
        }
+}
 
-       void detach( const NameCallback& callback ){
-       }
+InstanceCounter m_instanceCounter;
 
-       // filterable
-       void updateFiltered(){
-               if ( m_node != 0 ) {
-                       if ( brush_filtered( *this ) ) {
-                               m_node->enable( scene::Node::eFiltered );
-                       }
-                       else
-                       {
-                               m_node->disable( scene::Node::eFiltered );
-                       }
-               }
+void instanceAttach( const scene::Path& path ){
+       if ( ++m_instanceCounter.m_count == 1 ) {
+               m_map = path_find_mapfile( path.begin(), path.end() );
+               m_undoable_observer = GlobalUndoSystem().observer( this );
+               GlobalFilterSystem().registerFilterable( *this );
+               forEachFace_instanceAttach( m_map );
        }
-
-       // observer
-       void planeChanged(){
-               m_planeChanged = true;
-               aabbChanged();
-               m_lightsChanged();
+       else
+       {
+               ASSERT_MESSAGE( path_find_mapfile( path.begin(), path.end() ) == m_map, "node is instanced across more than one file" );
        }
+}
 
-       void shaderChanged(){
-               updateFiltered();
-               planeChanged();
+void instanceDetach( const scene::Path& path ){
+       if ( --m_instanceCounter.m_count == 0 ) {
+               forEachFace_instanceDetach( m_map );
+               GlobalFilterSystem().unregisterFilterable( *this );
+               m_map = 0;
+               m_undoable_observer = 0;
+               GlobalUndoSystem().release( this );
        }
+}
 
-       void evaluateBRep() const {
-               if ( m_planeChanged ) {
-                       m_planeChanged = false;
-                       const_cast<Brush*>( this )->buildBRep();
-               }
-       }
+// nameable
+const char* name() const {
+       return "brush";
+}
 
-       void transformChanged(){
-               m_transformChanged = true;
-               planeChanged();
-       }
+void attach( const NameCallback& callback ){
+}
 
-       typedef MemberCaller<Brush, void(), &Brush::transformChanged> TransformChangedCaller;
+void detach( const NameCallback& callback ){
+}
 
-       void evaluateTransform(){
-               if ( m_transformChanged ) {
-                       m_transformChanged = false;
-                       revertTransform();
-                       m_evaluateTransform();
+// filterable
+void updateFiltered(){
+       if ( m_node != 0 ) {
+               if ( brush_filtered( *this ) ) {
+                       m_node->enable( scene::Node::eFiltered );
+               }
+               else
+               {
+                       m_node->disable( scene::Node::eFiltered );
                }
        }
+}
 
-       const Matrix4& localToParent() const {
-               return g_matrix4_identity;
-       }
+// observer
+void planeChanged(){
+       m_planeChanged = true;
+       aabbChanged();
+       m_lightsChanged();
+}
 
-       void aabbChanged(){
-               m_boundsChanged();
-       }
+void shaderChanged(){
+       updateFiltered();
+       planeChanged();
+}
 
-       const AABB& localAABB() const {
-               evaluateBRep();
-               return m_aabb_local;
+void evaluateBRep() const {
+       if ( m_planeChanged ) {
+               m_planeChanged = false;
+               const_cast<Brush*>( this )->buildBRep();
        }
+}
 
-       VolumeIntersectionValue intersectVolume( const VolumeTest& test, const Matrix4& localToWorld ) const {
-               return test.TestAABB( m_aabb_local, localToWorld );
-       }
+void transformChanged(){
+       m_transformChanged = true;
+       planeChanged();
+}
 
-       void renderComponents( SelectionSystem::EComponentMode mode, Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld ) const {
-               switch ( mode )
-               {
-               case SelectionSystem::eVertex:
-                       renderer.addRenderable( m_render_vertices, localToWorld );
-                       break;
-               case SelectionSystem::eEdge:
-                       renderer.addRenderable( m_render_edges, localToWorld );
-                       break;
-               case SelectionSystem::eFace:
-                       renderer.addRenderable( m_render_faces, localToWorld );
-                       break;
-               default:
-                       break;
-               }
+typedef MemberCaller<Brush, void(), &Brush::transformChanged> TransformChangedCaller;
+
+void evaluateTransform(){
+       if ( m_transformChanged ) {
+               m_transformChanged = false;
+               revertTransform();
+               m_evaluateTransform();
        }
+}
+
+const Matrix4& localToParent() const {
+       return g_matrix4_identity;
+}
 
-       void transform( const Matrix4& matrix ){
-               bool mirror = matrix4_handedness( matrix ) == MATRIX4_LEFTHANDED;
+void aabbChanged(){
+       m_boundsChanged();
+}
 
-               for ( Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i )
-               {
-                       ( *i )->transform( matrix, mirror );
-               }
-       }
+const AABB& localAABB() const {
+       evaluateBRep();
+       return m_aabb_local;
+}
 
-       void snapto( float snap ){
-               for ( Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i )
-               {
-                       ( *i )->snapto( snap );
-               }
+VolumeIntersectionValue intersectVolume( const VolumeTest& test, const Matrix4& localToWorld ) const {
+       return test.TestAABB( m_aabb_local, localToWorld );
+}
+
+void renderComponents( SelectionSystem::EComponentMode mode, Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld ) const {
+       switch ( mode )
+       {
+       case SelectionSystem::eVertex:
+               renderer.addRenderable( m_render_vertices, localToWorld );
+               break;
+       case SelectionSystem::eEdge:
+               renderer.addRenderable( m_render_edges, localToWorld );
+               break;
+       case SelectionSystem::eFace:
+               renderer.addRenderable( m_render_faces, localToWorld );
+               break;
+       default:
+               break;
        }
+}
 
-       void revertTransform(){
-               for ( Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i )
-               {
-                       ( *i )->revertTransform();
-               }
+void transform( const Matrix4& matrix ){
+       bool mirror = matrix4_handedness( matrix ) == MATRIX4_LEFTHANDED;
+
+       for ( Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i )
+       {
+               ( *i )->transform( matrix, mirror );
        }
+}
 
-       void freezeTransform(){
-               for ( Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i )
-               {
-                       ( *i )->freezeTransform();
-               }
+void snapto( float snap ){
+       for ( Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i )
+       {
+               ( *i )->snapto( snap );
        }
+}
 
-       /// \brief Returns the absolute index of the \p faceVertex.
-       std::size_t absoluteIndex( FaceVertexId faceVertex ){
-               std::size_t index = 0;
-               for ( std::size_t i = 0; i < faceVertex.getFace(); ++i )
-               {
-                       index += m_faces[i]->getWinding().numpoints;
-               }
-               return index + faceVertex.getVertex();
+void revertTransform(){
+       for ( Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i )
+       {
+               ( *i )->revertTransform();
        }
+}
 
-       void appendFaces( const Faces& other ){
-               clear();
-               for ( Faces::const_iterator i = other.begin(); i != other.end(); ++i )
-               {
-                       push_back( *i );
-               }
+void freezeTransform(){
+       for ( Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i )
+       {
+               ( *i )->freezeTransform();
        }
+}
 
-       /// \brief The undo memento for a brush stores only the list of face references - the faces are not copied.
-       class BrushUndoMemento : public UndoMemento
+/// \brief Returns the absolute index of the \p faceVertex.
+std::size_t absoluteIndex( FaceVertexId faceVertex ){
+       std::size_t index = 0;
+       for ( std::size_t i = 0; i < faceVertex.getFace(); ++i )
        {
-       public:
-               BrushUndoMemento( const Faces& faces ) : m_faces( faces ){
+               index += m_faces[i]->getWinding().numpoints;
        }
+       return index + faceVertex.getVertex();
+}
 
-       void release(){
-               delete this;
+void appendFaces( const Faces& other ){
+       clear();
+       for ( Faces::const_iterator i = other.begin(); i != other.end(); ++i )
+       {
+               push_back( *i );
        }
+}
 
-       Faces m_faces;
-       };
+/// \brief The undo memento for a brush stores only the list of face references - the faces are not copied.
+class BrushUndoMemento : public UndoMemento
+{
+public:
+BrushUndoMemento( const Faces& faces ) : m_faces( faces ){
+}
 
-       void undoSave(){
-               if ( m_map != 0 ) {
-                       m_map->changed();
-               }
-               if ( m_undoable_observer != 0 ) {
-                       m_undoable_observer->save( this );
-               }
-       }
+void release(){
+       delete this;
+}
+
+Faces m_faces;
+};
 
-       UndoMemento* exportState() const {
-               return new BrushUndoMemento( m_faces );
+void undoSave(){
+       if ( m_map != 0 ) {
+               m_map->changed();
        }
+       if ( m_undoable_observer != 0 ) {
+               m_undoable_observer->save( this );
+       }
+}
 
-       void importState( const UndoMemento* state ){
-               undoSave();
-               appendFaces( static_cast<const BrushUndoMemento*>( state )->m_faces );
-               planeChanged();
+UndoMemento* exportState() const {
+       return new BrushUndoMemento( m_faces );
+}
 
-               for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
-               {
-                       ( *i )->DEBUG_verify();
-               }
-       }
+void importState( const UndoMemento* state ){
+       undoSave();
+       appendFaces( static_cast<const BrushUndoMemento*>( state )->m_faces );
+       planeChanged();
 
-       bool isDetail(){
-               return !m_faces.empty() && m_faces.front()->isDetail();
+       for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
+       {
+               ( *i )->DEBUG_verify();
        }
+}
 
-       /// \brief Appends a copy of \p face to the end of the face list.
+bool isDetail(){
+       return !m_faces.empty() && m_faces.front()->isDetail();
+}
+
+/// \brief Appends a copy of \p face to the end of the face list.
        std::shared_ptr<Face> addFace( const Face& face ){
-               if ( m_faces.size() == c_brush_maxFaces ) {
-                       return 0;
-               }
-               undoSave();
-               push_back( std::make_shared<Face>( face, this ) );
-               m_faces.back()->setDetail( isDetail() );
-               planeChanged();
-               return m_faces.back();
+       if ( m_faces.size() == c_brush_maxFaces ) {
+               return 0;
        }
+       undoSave();
+               push_back( std::make_shared<Face>( face, this ) );
+       m_faces.back()->setDetail( isDetail() );
+       planeChanged();
+       return m_faces.back();
+}
 
-       /// \brief Appends a new face constructed from the parameters to the end of the face list.
+/// \brief Appends a new face constructed from the parameters to the end of the face list.
        std::shared_ptr<Face> addPlane( const Vector3& p0, const Vector3& p1, const Vector3& p2, const char* shader, const TextureProjection& projection ){
-               if ( m_faces.size() == c_brush_maxFaces ) {
-                       return 0;
-               }
-               undoSave();
-               push_back( std::make_shared<Face>( p0, p1, p2, shader, projection, this ) );
-               m_faces.back()->setDetail( isDetail() );
-               planeChanged();
-               return m_faces.back();
+       if ( m_faces.size() == c_brush_maxFaces ) {
+               return 0;
        }
+       undoSave();
+               push_back( std::make_shared<Face>( p0, p1, p2, shader, projection, this ) );
+       m_faces.back()->setDetail( isDetail() );
+       planeChanged();
+       return m_faces.back();
+}
 
-       static void constructStatic( EBrushType type ){
-               m_type = type;
-               Face::m_type = type;
-               FacePlane::m_type = type;
+static void constructStatic( EBrushType type ){
+       m_type = type;
+       Face::m_type = type;
+       FacePlane::m_type = type;
 
-               g_bp_globals.m_texdefTypeId = TEXDEFTYPEID_QUAKE;
-               if ( m_type == eBrushTypeQuake3BP || m_type == eBrushTypeDoom3 || m_type == eBrushTypeQuake4 ) {
-                       g_bp_globals.m_texdefTypeId = TEXDEFTYPEID_BRUSHPRIMITIVES;
-                       // g_brush_texturelock_enabled = true; // bad idea, this overrides user setting
-               }
-               else if ( m_type == eBrushTypeHalfLife ) {
-                       g_bp_globals.m_texdefTypeId = TEXDEFTYPEID_HALFLIFE;
-                       // g_brush_texturelock_enabled = true; // bad idea, this overrides user setting
-               }
+       g_bp_globals.m_texdefTypeId = TEXDEFTYPEID_QUAKE;
+       if ( m_type == eBrushTypeQuake3BP || m_type == eBrushTypeDoom3 || m_type == eBrushTypeQuake4 ) {
+               g_bp_globals.m_texdefTypeId = TEXDEFTYPEID_BRUSHPRIMITIVES;
+               // g_brush_texturelock_enabled = true; // bad idea, this overrides user setting
+       }
+       else if ( m_type == eBrushTypeHalfLife ) {
+               g_bp_globals.m_texdefTypeId = TEXDEFTYPEID_HALFLIFE;
+               // g_brush_texturelock_enabled = true; // bad idea, this overrides user setting
+       }
 
-               Face::m_quantise = ( m_type == eBrushTypeQuake ) ? quantiseInteger : quantiseFloating;
+       Face::m_quantise = ( m_type == eBrushTypeQuake ) ? quantiseInteger : quantiseFloating;
 
-               m_state_point = GlobalShaderCache().capture( "$POINT" );
-       }
+       m_state_point = GlobalShaderCache().capture( "$POINT" );
+}
 
-       static void destroyStatic(){
-               GlobalShaderCache().release( "$POINT" );
-       }
+static void destroyStatic(){
+       GlobalShaderCache().release( "$POINT" );
+}
 
-       std::size_t DEBUG_size(){
-               return m_faces.size();
-       }
+std::size_t DEBUG_size(){
+       return m_faces.size();
+}
 
-       typedef Faces::const_iterator const_iterator;
+typedef Faces::const_iterator const_iterator;
 
-       const_iterator begin() const {
-               return m_faces.begin();
-       }
+const_iterator begin() const {
+       return m_faces.begin();
+}
 
-       const_iterator end() const {
-               return m_faces.end();
-       }
+const_iterator end() const {
+       return m_faces.end();
+}
 
        std::shared_ptr<Face> back(){
-               return m_faces.back();
-       }
+       return m_faces.back();
+}
 
        const std::shared_ptr<Face> back() const {
-               return m_faces.back();
-       }
+       return m_faces.back();
+}
 
-       void reserve( std::size_t count ){
-               m_faces.reserve( count );
-               for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
-               {
-                       ( *i )->reserve( count );
-               }
+void reserve( std::size_t count ){
+       m_faces.reserve( count );
+       for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
+       {
+               ( *i )->reserve( count );
        }
+}
 
-       void push_back( Faces::value_type face ){
-               m_faces.push_back( face );
-               if ( m_instanceCounter.m_count != 0 ) {
-                       m_faces.back()->instanceAttach( m_map );
-               }
-               for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
-               {
-                       ( *i )->push_back( *face );
-                       ( *i )->DEBUG_verify();
-               }
+void push_back( Faces::value_type face ){
+       m_faces.push_back( face );
+       if ( m_instanceCounter.m_count != 0 ) {
+               m_faces.back()->instanceAttach( m_map );
        }
+       for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
+       {
+               ( *i )->push_back( *face );
+               ( *i )->DEBUG_verify();
+       }
+}
 
-       void pop_back(){
-               if ( m_instanceCounter.m_count != 0 ) {
-                       m_faces.back()->instanceDetach( m_map );
-               }
-               m_faces.pop_back();
-               for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
-               {
-                       ( *i )->pop_back();
-                       ( *i )->DEBUG_verify();
-               }
+void pop_back(){
+       if ( m_instanceCounter.m_count != 0 ) {
+               m_faces.back()->instanceDetach( m_map );
        }
+       m_faces.pop_back();
+       for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
+       {
+               ( *i )->pop_back();
+               ( *i )->DEBUG_verify();
+       }
+}
 
-       void erase( std::size_t index ){
-               if ( m_instanceCounter.m_count != 0 ) {
-                       m_faces[index]->instanceDetach( m_map );
-               }
-               m_faces.erase( m_faces.begin() + index );
-               for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
-               {
-                       ( *i )->erase( index );
-                       ( *i )->DEBUG_verify();
-               }
+void erase( std::size_t index ){
+       if ( m_instanceCounter.m_count != 0 ) {
+               m_faces[index]->instanceDetach( m_map );
        }
+       m_faces.erase( m_faces.begin() + index );
+       for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
+       {
+               ( *i )->erase( index );
+               ( *i )->DEBUG_verify();
+       }
+}
 
-       void connectivityChanged(){
-               for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
-               {
-                       ( *i )->connectivityChanged();
-               }
+void connectivityChanged(){
+       for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
+       {
+               ( *i )->connectivityChanged();
        }
+}
 
 
-       void clear(){
-               undoSave();
-               if ( m_instanceCounter.m_count != 0 ) {
-                       forEachFace_instanceDetach( m_map );
-               }
-               m_faces.clear();
-               for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
-               {
-                       ( *i )->clear();
-                       ( *i )->DEBUG_verify();
-               }
+void clear(){
+       undoSave();
+       if ( m_instanceCounter.m_count != 0 ) {
+               forEachFace_instanceDetach( m_map );
        }
-
-       std::size_t size() const {
-               return m_faces.size();
+       m_faces.clear();
+       for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
+       {
+               ( *i )->clear();
+               ( *i )->DEBUG_verify();
        }
+}
 
-       bool empty() const {
-               return m_faces.empty();
-       }
+std::size_t size() const {
+       return m_faces.size();
+}
 
-       /// \brief Returns true if any face of the brush contributes to the final B-Rep.
-       bool hasContributingFaces() const {
-               for ( const_iterator i = begin(); i != end(); ++i )
-               {
-                       if ( ( *i )->contributes() ) {
-                               return true;
-                       }
+bool empty() const {
+       return m_faces.empty();
+}
+
+/// \brief Returns true if any face of the brush contributes to the final B-Rep.
+bool hasContributingFaces() const {
+       for ( const_iterator i = begin(); i != end(); ++i )
+       {
+               if ( ( *i )->contributes() ) {
+                       return true;
                }
-               return false;
        }
+       return false;
+}
 
-       /// \brief Removes faces that do not contribute to the brush. This is useful for cleaning up after CSG operations on the brush.
-       /// Note: removal of empty faces is not performed during direct brush manipulations, because it would make a manipulation irreversible if it created an empty face.
-       void removeEmptyFaces(){
-               evaluateBRep();
+/// \brief Removes faces that do not contribute to the brush. This is useful for cleaning up after CSG operations on the brush.
+/// Note: removal of empty faces is not performed during direct brush manipulations, because it would make a manipulation irreversible if it created an empty face.
+void removeEmptyFaces(){
+       evaluateBRep();
 
+       {
+               std::size_t i = 0;
+               while ( i < m_faces.size() )
                {
-                       std::size_t i = 0;
-                       while ( i < m_faces.size() )
+                       if ( !m_faces[i]->contributes() ) {
+                               erase( i );
+                               planeChanged();
+                       }
+                       else
                        {
-                               if ( !m_faces[i]->contributes() ) {
-                                       erase( i );
-                                       planeChanged();
-                               }
-                               else
-                               {
-                                       ++i;
-                               }
+                               ++i;
                        }
                }
        }
+}
 
-       /// \brief Constructs \p winding from the intersection of \p plane with the other planes of the brush.
-       void windingForClipPlane( Winding& winding, const Plane3& plane ) const {
-               FixedWinding buffer[2];
-               bool swap = false;
+/// \brief Constructs \p winding from the intersection of \p plane with the other planes of the brush.
+void windingForClipPlane( Winding& winding, const Plane3& plane ) const {
+       FixedWinding buffer[2];
+       bool swap = false;
 
-               // get a poly that covers an effectively infinite area
-               Winding_createInfinite( buffer[swap], plane, m_maxWorldCoord + 1 );
+       // get a poly that covers an effectively infinite area
+       Winding_createInfinite( buffer[swap], plane, m_maxWorldCoord + 1 );
 
-               // chop the poly by all of the other faces
+       // chop the poly by all of the other faces
+       {
+               for ( std::size_t i = 0; i < m_faces.size(); ++i )
                {
-                       for ( std::size_t i = 0; i < m_faces.size(); ++i )
-                       {
-                               const Face& clip = *m_faces[i];
+                       const Face& clip = *m_faces[i];
 
-                               if ( plane3_equal( clip.plane3(), plane )
-                                        || !plane3_valid( clip.plane3() ) || !plane_unique( i )
-                                        || plane3_opposing( plane, clip.plane3() ) ) {
-                                       continue;
-                               }
+                       if ( plane3_equal( clip.plane3(), plane )
+                                || !plane3_valid( clip.plane3() ) || !plane_unique( i )
+                                || plane3_opposing( plane, clip.plane3() ) ) {
+                               continue;
+                       }
 
-                               buffer[!swap].clear();
+                       if( buffer[swap].points.empty() ){
+                               //globalErrorStream() << "windingForClipPlane: about to feed empty winding\n";
+                               break;
+                       }
+
+                       buffer[!swap].clear();
 
 #if BRUSH_CONNECTIVITY_DEBUG
-                               globalOutputStream() << "clip vs face: " << i << "\n";
+                       globalOutputStream() << "clip vs face: " << i << "\n";
 #endif
 
-                               {
-                                       // flip the plane, because we want to keep the back side
-                                       Plane3 clipPlane( vector3_negated( clip.plane3().normal() ), -clip.plane3().dist() );
-                                       Winding_Clip( buffer[swap], plane, clipPlane, i, buffer[!swap] );
-                               }
+                       {
+                               // flip the plane, because we want to keep the back side
+                               Plane3 clipPlane( vector3_negated( clip.plane3().normal() ), -clip.plane3().dist() );
+                               Winding_Clip( buffer[swap], plane, clipPlane, i, buffer[!swap] );
+                       }
 
 #if BRUSH_CONNECTIVITY_DEBUG
-                               for ( FixedWinding::Points::iterator k = buffer[!swap].points.begin(), j = buffer[!swap].points.end() - 1; k != buffer[!swap].points.end(); j = k, ++k )
-                               {
-                                       if ( vector3_length_squared( vector3_subtracted( ( *k ).vertex, ( *j ).vertex ) ) < 1 ) {
-                                               globalOutputStream() << "v: " << std::distance( buffer[!swap].points.begin(), j ) << " tiny edge adjacent to face " << ( *j ).adjacent << "\n";
-                                       }
+                       for ( FixedWinding::Points::iterator k = buffer[!swap].points.begin(), j = buffer[!swap].points.end() - 1; k != buffer[!swap].points.end(); j = k, ++k )
+                       {
+                               if ( vector3_length_squared( vector3_subtracted( ( *k ).vertex, ( *j ).vertex ) ) < 1 ) {
+                                       globalOutputStream() << "v: " << std::distance( buffer[!swap].points.begin(), j ) << " tiny edge adjacent to face " << ( *j ).adjacent << "\n";
                                }
+                       }
 #endif
 
-                               //ASSERT_MESSAGE(buffer[!swap].numpoints != 1, "created single-point winding");
+                       //ASSERT_MESSAGE(buffer[!swap].numpoints != 1, "created single-point winding");
 
-                               swap = !swap;
-                       }
+                       swap = !swap;
                }
+       }
 
-               Winding_forFixedWinding( winding, buffer[swap] );
+       Winding_forFixedWinding( winding, buffer[swap] );
 
 #if BRUSH_CONNECTIVITY_DEBUG
-               Winding_printConnectivity( winding );
+       Winding_printConnectivity( winding );
 
-               for ( Winding::iterator i = winding.begin(), j = winding.end() - 1; i != winding.end(); j = i, ++i )
-               {
-                       if ( vector3_length_squared( vector3_subtracted( ( *i ).vertex, ( *j ).vertex ) ) < 1 ) {
-                               globalOutputStream() << "v: " << std::distance( winding.begin(), j ) << " tiny edge adjacent to face " << ( *j ).adjacent << "\n";
-                       }
+       for ( Winding::iterator i = winding.begin(), j = winding.end() - 1; i != winding.end(); j = i, ++i )
+       {
+               if ( vector3_length_squared( vector3_subtracted( ( *i ).vertex, ( *j ).vertex ) ) < 1 ) {
+                       globalOutputStream() << "v: " << std::distance( winding.begin(), j ) << " tiny edge adjacent to face " << ( *j ).adjacent << "\n";
                }
-#endif
        }
+#endif
+}
 
-       void update_wireframe( RenderableWireframe& wire, const bool* faces_visible ) const {
-               wire.m_faceVertex.resize( m_edge_indices.size() );
-               wire.m_vertices = m_uniqueVertexPoints.data();
-               wire.m_size = 0;
-               for ( std::size_t i = 0; i < m_edge_faces.size(); ++i )
-               {
-                       if ( faces_visible[m_edge_faces[i].first]
-                                || faces_visible[m_edge_faces[i].second] ) {
-                               wire.m_faceVertex[wire.m_size++] = m_edge_indices[i];
-                       }
+void update_wireframe( RenderableWireframe& wire, const bool* faces_visible ) const {
+       wire.m_faceVertex.resize( m_edge_indices.size() );
+       wire.m_vertices = m_uniqueVertexPoints.data();
+       wire.m_size = 0;
+       for ( std::size_t i = 0; i < m_edge_faces.size(); ++i )
+       {
+               if ( faces_visible[m_edge_faces[i].first]
+                        || faces_visible[m_edge_faces[i].second] ) {
+                       wire.m_faceVertex[wire.m_size++] = m_edge_indices[i];
                }
        }
+}
 
 
-       void update_faces_wireframe( Array<PointVertex>& wire, const bool* faces_visible ) const {
-               std::size_t count = 0;
-               for ( std::size_t i = 0; i < m_faceCentroidPoints.size(); ++i )
-               {
-                       if ( faces_visible[i] ) {
-                               ++count;
-                       }
+void update_faces_wireframe( Array<PointVertex>& wire, const bool* faces_visible ) const {
+       std::size_t count = 0;
+       for ( std::size_t i = 0; i < m_faceCentroidPoints.size(); ++i )
+       {
+               if ( faces_visible[i] ) {
+                       ++count;
                }
+       }
 
-               wire.resize( count );
-               Array<PointVertex>::iterator p = wire.begin();
-               for ( std::size_t i = 0; i < m_faceCentroidPoints.size(); ++i )
-               {
-                       if ( faces_visible[i] ) {
-                               *p++ = m_faceCentroidPoints[i];
-                       }
+       wire.resize( count );
+       Array<PointVertex>::iterator p = wire.begin();
+       for ( std::size_t i = 0; i < m_faceCentroidPoints.size(); ++i )
+       {
+               if ( faces_visible[i] ) {
+                       *p++ = m_faceCentroidPoints[i];
                }
        }
+}
 
-       /// \brief Makes this brush a deep-copy of the \p other.
-       void copy( const Brush& other ){
-               for ( Faces::const_iterator i = other.m_faces.begin(); i != other.m_faces.end(); ++i )
-               {
-                       addFace( *( *i ) );
-               }
-               planeChanged();
+/// \brief Makes this brush a deep-copy of the \p other.
+void copy( const Brush& other ){
+       for ( Faces::const_iterator i = other.m_faces.begin(); i != other.m_faces.end(); ++i )
+       {
+               addFace( *( *i ) );
        }
+       planeChanged();
+}
 
-       private:
-       void edge_push_back( FaceVertexId faceVertex ){
-               m_select_edges.push_back( SelectableEdge( m_faces, faceVertex ) );
-               for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
-               {
-                       ( *i )->edge_push_back( m_select_edges.back() );
-               }
+private:
+void edge_push_back( FaceVertexId faceVertex ){
+       m_select_edges.push_back( SelectableEdge( m_faces, faceVertex ) );
+       for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
+       {
+               ( *i )->edge_push_back( m_select_edges.back() );
        }
+}
 
-       void edge_clear(){
-               m_select_edges.clear();
-               for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
-               {
-                       ( *i )->edge_clear();
-               }
+void edge_clear(){
+       m_select_edges.clear();
+       for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
+       {
+               ( *i )->edge_clear();
        }
+}
 
-       void vertex_push_back( FaceVertexId faceVertex ){
-               m_select_vertices.push_back( SelectableVertex( m_faces, faceVertex ) );
-               for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
-               {
-                       ( *i )->vertex_push_back( m_select_vertices.back() );
-               }
+void vertex_push_back( FaceVertexId faceVertex ){
+       m_select_vertices.push_back( SelectableVertex( m_faces, faceVertex ) );
+       for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
+       {
+               ( *i )->vertex_push_back( m_select_vertices.back() );
        }
+}
 
-       void vertex_clear(){
-               m_select_vertices.clear();
-               for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
-               {
-                       ( *i )->vertex_clear();
-               }
+void vertex_clear(){
+       m_select_vertices.clear();
+       for ( Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i )
+       {
+               ( *i )->vertex_clear();
        }
+}
 
-       /// \brief Returns true if the face identified by \p index is preceded by another plane that takes priority over it.
-       bool plane_unique( std::size_t index ) const {
-               // duplicate plane
-               for ( std::size_t i = 0; i < m_faces.size(); ++i )
-               {
-                       if ( index != i && !plane3_inside( m_faces[index]->plane3(), m_faces[i]->plane3(), index < i ) ) {
-                               return false;
-                       }
+/// \brief Returns true if the face identified by \p index is preceded by another plane that takes priority over it.
+bool plane_unique( std::size_t index ) const {
+       // duplicate plane
+       for ( std::size_t i = 0; i < m_faces.size(); ++i )
+       {
+               if ( index != i && !plane3_inside( m_faces[index]->plane3(), m_faces[i]->plane3(), index < i ) ) {
+                       return false;
                }
-               return true;
        }
+       return true;
+}
 
-       /// \brief Removes edges that are smaller than the tolerance used when generating brush windings.
-       void removeDegenerateEdges(){
-               for ( std::size_t i = 0; i < m_faces.size(); ++i )
+/// \brief Removes edges that are smaller than the tolerance used when generating brush windings.
+void removeDegenerateEdges(){
+       for ( std::size_t i = 0; i < m_faces.size(); ++i )
+       {
+               Winding& winding = m_faces[i]->getWinding();
+               for ( Winding::iterator j = winding.begin(); j != winding.end(); )
                {
-                       Winding& winding = m_faces[i]->getWinding();
-                       for ( Winding::iterator j = winding.begin(); j != winding.end(); )
-                       {
-                               std::size_t index = std::distance( winding.begin(), j );
-                               std::size_t next = Winding_next( winding, index );
-                               if ( Edge_isDegenerate( winding[index].vertex, winding[next].vertex ) ) {
+                       std::size_t index = std::distance( winding.begin(), j );
+                       std::size_t next = Winding_next( winding, index );
+                       if ( Edge_isDegenerate( winding[index].vertex, winding[next].vertex ) ) {
 #if BRUSH_DEGENERATE_DEBUG
-                                       globalOutputStream() << "Brush::buildWindings: face " << i << ": degenerate edge adjacent to " << winding[index].adjacent << "\n";
+                               globalOutputStream() << "Brush::buildWindings: face " << i << ": degenerate edge adjacent to " << winding[index].adjacent << "\n";
 #endif
-                                       Winding& other = m_faces[winding[index].adjacent]->getWinding();
-                                       std::size_t adjacent = Winding_FindAdjacent( other, i );
-                                       if ( adjacent != c_brush_maxFaces ) {
-                                               other.erase( other.begin() + adjacent );
-                                       }
-                                       winding.erase( j );
-                               }
-                               else
-                               {
-                                       ++j;
+                               Winding& other = m_faces[winding[index].adjacent]->getWinding();
+                               std::size_t adjacent = Winding_FindAdjacent( other, i );
+                               if ( adjacent != c_brush_maxFaces ) {
+                                       other.erase( other.begin() + adjacent );
                                }
+                               winding.erase( j );
+                       }
+                       else
+                       {
+                               ++j;
                        }
                }
        }
+}
 
-       /// \brief Invalidates faces that have only two vertices in their winding, while preserving edge-connectivity information.
-       void removeDegenerateFaces(){
-               // save adjacency info for degenerate faces
-               for ( std::size_t i = 0; i < m_faces.size(); ++i )
-               {
-                       Winding& degen = m_faces[i]->getWinding();
+/// \brief Invalidates faces that have only two vertices in their winding, while preserving edge-connectivity information.
+void removeDegenerateFaces(){
+       // save adjacency info for degenerate faces
+       for ( std::size_t i = 0; i < m_faces.size(); ++i )
+       {
+               Winding& degen = m_faces[i]->getWinding();
 
-                       if ( degen.numpoints == 2 ) {
+               if ( degen.numpoints == 2 ) {
 #if BRUSH_DEGENERATE_DEBUG
-                               globalOutputStream() << "Brush::buildWindings: face " << i << ": degenerate winding adjacent to " << degen[0].adjacent << ", " << degen[1].adjacent << "\n";
+                       globalOutputStream() << "Brush::buildWindings: face " << i << ": degenerate winding adjacent to " << degen[0].adjacent << ", " << degen[1].adjacent << "\n";
 #endif
-                               // this is an "edge" face, where the plane touches the edge of the brush
-                               {
-                                       Winding& winding = m_faces[degen[0].adjacent]->getWinding();
-                                       std::size_t index = Winding_FindAdjacent( winding, i );
-                                       if ( index != c_brush_maxFaces ) {
+                       // this is an "edge" face, where the plane touches the edge of the brush
+                       {
+                               Winding& winding = m_faces[degen[0].adjacent]->getWinding();
+                               std::size_t index = Winding_FindAdjacent( winding, i );
+                               if ( index != c_brush_maxFaces ) {
 #if BRUSH_DEGENERATE_DEBUG
-                                               globalOutputStream() << "Brush::buildWindings: face " << degen[0].adjacent << ": remapping adjacent " << winding[index].adjacent << " to " << degen[1].adjacent << "\n";
+                                       globalOutputStream() << "Brush::buildWindings: face " << degen[0].adjacent << ": remapping adjacent " << winding[index].adjacent << " to " << degen[1].adjacent << "\n";
 #endif
-                                               winding[index].adjacent = degen[1].adjacent;
-                                       }
+                                       winding[index].adjacent = degen[1].adjacent;
                                }
+                       }
 
-                               {
-                                       Winding& winding = m_faces[degen[1].adjacent]->getWinding();
-                                       std::size_t index = Winding_FindAdjacent( winding, i );
-                                       if ( index != c_brush_maxFaces ) {
+                       {
+                               Winding& winding = m_faces[degen[1].adjacent]->getWinding();
+                               std::size_t index = Winding_FindAdjacent( winding, i );
+                               if ( index != c_brush_maxFaces ) {
 #if BRUSH_DEGENERATE_DEBUG
-                                               globalOutputStream() << "Brush::buildWindings: face " << degen[1].adjacent << ": remapping adjacent " << winding[index].adjacent << " to " << degen[0].adjacent << "\n";
+                                       globalOutputStream() << "Brush::buildWindings: face " << degen[1].adjacent << ": remapping adjacent " << winding[index].adjacent << " to " << degen[0].adjacent << "\n";
 #endif
-                                               winding[index].adjacent = degen[0].adjacent;
-                                       }
+                                       winding[index].adjacent = degen[0].adjacent;
                                }
-
-                               degen.resize( 0 );
                        }
+
+                       degen.resize( 0 );
                }
        }
+}
 
-       /// \brief Removes edges that have the same adjacent-face as their immediate neighbour.
-       void removeDuplicateEdges(){
-               // verify face connectivity graph
-               for ( std::size_t i = 0; i < m_faces.size(); ++i )
+/// \brief Removes edges that have the same adjacent-face as their immediate neighbour.
+void removeDuplicateEdges(){
+       // verify face connectivity graph
+       for ( std::size_t i = 0; i < m_faces.size(); ++i )
+       {
+               //if(m_faces[i]->contributes())
                {
-                       //if(m_faces[i]->contributes())
+                       Winding& winding = m_faces[i]->getWinding();
+                       for ( std::size_t j = 0; j != winding.numpoints; )
                        {
-                               Winding& winding = m_faces[i]->getWinding();
-                               for ( std::size_t j = 0; j != winding.numpoints; )
-                               {
-                                       std::size_t next = Winding_next( winding, j );
-                                       if ( winding[j].adjacent == winding[next].adjacent ) {
+                               std::size_t next = Winding_next( winding, j );
+                               if ( winding[j].adjacent == winding[next].adjacent ) {
 #if BRUSH_DEGENERATE_DEBUG
-                                               globalOutputStream() << "Brush::buildWindings: face " << i << ": removed duplicate edge adjacent to face " << winding[j].adjacent << "\n";
+                                       globalOutputStream() << "Brush::buildWindings: face " << i << ": removed duplicate edge adjacent to face " << winding[j].adjacent << "\n";
 #endif
-                                               winding.erase( winding.begin() + next );
-                                       }
-                                       else
-                                       {
-                                               ++j;
-                                       }
+                                       winding.erase( winding.begin() + next );
+                               }
+                               else
+                               {
+                                       ++j;
                                }
                        }
                }
        }
+}
 
-       /// \brief Removes edges that do not have a matching pair in their adjacent-face.
-       void verifyConnectivityGraph(){
-               // verify face connectivity graph
-               for ( std::size_t i = 0; i < m_faces.size(); ++i )
+/// \brief Removes edges that do not have a matching pair in their adjacent-face.
+void verifyConnectivityGraph(){
+       // verify face connectivity graph
+       for ( std::size_t i = 0; i < m_faces.size(); ++i )
+       {
+               //if(m_faces[i]->contributes())
                {
-                       //if(m_faces[i]->contributes())
+                       Winding& winding = m_faces[i]->getWinding();
+                       for ( Winding::iterator j = winding.begin(); j != winding.end(); )
                        {
-                               Winding& winding = m_faces[i]->getWinding();
-                               for ( Winding::iterator j = winding.begin(); j != winding.end(); )
-                               {
 #if BRUSH_CONNECTIVITY_DEBUG
-                                       globalOutputStream() << "Brush::buildWindings: face " << i << ": adjacent to face " << ( *j ).adjacent << "\n";
+                               globalOutputStream() << "Brush::buildWindings: face " << i << ": adjacent to face " << ( *j ).adjacent << "\n";
 #endif
-                                       // remove unidirectional graph edges
-                                       if ( ( *j ).adjacent == c_brush_maxFaces
-                                                || Winding_FindAdjacent( m_faces[( *j ).adjacent]->getWinding(), i ) == c_brush_maxFaces ) {
+                               // remove unidirectional graph edges
+                               if ( ( *j ).adjacent == c_brush_maxFaces
+                                        || Winding_FindAdjacent( m_faces[( *j ).adjacent]->getWinding(), i ) == c_brush_maxFaces ) {
 #if BRUSH_CONNECTIVITY_DEBUG
-                                               globalOutputStream() << "Brush::buildWindings: face " << i << ": removing unidirectional connectivity graph edge adjacent to face " << ( *j ).adjacent << "\n";
+                                       globalOutputStream() << "Brush::buildWindings: face " << i << ": removing unidirectional connectivity graph edge adjacent to face " << ( *j ).adjacent << "\n";
 #endif
-                                               winding.erase( j );
-                                       }
-                                       else
-                                       {
-                                               ++j;
-                                       }
+                                       winding.erase( j );
+                               }
+                               else
+                               {
+                                       ++j;
                                }
                        }
                }
        }
+}
 
-       /// \brief Returns true if the brush is a finite volume. A brush without a finite volume extends past the maximum world bounds and is not valid.
-       bool isBounded(){
-               for ( const_iterator i = begin(); i != end(); ++i )
-               {
-                       if ( !( *i )->is_bounded() ) {
-                               return false;
-                       }
+/// \brief Returns true if the brush is a finite volume. A brush without a finite volume extends past the maximum world bounds and is not valid.
+bool isBounded(){
+       for ( const_iterator i = begin(); i != end(); ++i )
+       {
+               if ( !( *i )->is_bounded() ) {
+                       return false;
                }
-               return true;
        }
+       return true;
+}
+
+/// \brief Constructs the polygon windings for each face of the brush. Also updates the brush bounding-box and face texture-coordinates.
+bool buildWindings(){
 
-       /// \brief Constructs the polygon windings for each face of the brush. Also updates the brush bounding-box and face texture-coordinates.
-       bool buildWindings(){
+       {
+               m_aabb_local = AABB();
 
+               for ( std::size_t i = 0; i < m_faces.size(); ++i )
                {
-                       m_aabb_local = AABB();
+                       Face& f = *m_faces[i];
 
-                       for ( std::size_t i = 0; i < m_faces.size(); ++i )
+                       if ( !plane3_valid( f.plane3() ) || !plane_unique( i ) ) {
+                               f.getWinding().resize( 0 );
+                       }
+                       else
                        {
-                               Face& f = *m_faces[i];
-
-                               if ( !plane3_valid( f.plane3() ) || !plane_unique( i ) ) {
-                                       f.getWinding().resize( 0 );
-                               }
-                               else
-                               {
 #if BRUSH_CONNECTIVITY_DEBUG
-                                       globalOutputStream() << "face: " << i << "\n";
+                               globalOutputStream() << "face: " << i << "\n";
 #endif
-                                       windingForClipPlane( f.getWinding(), f.plane3() );
-
-                                       // update brush bounds
-                                       const Winding& winding = f.getWinding();
-                                       for ( Winding::const_iterator i = winding.begin(); i != winding.end(); ++i )
-                                       {
-                                               aabb_extend_by_point_safe( m_aabb_local, ( *i ).vertex );
-                                       }
+                               windingForClipPlane( f.getWinding(), f.plane3() );
 
-                                       // update texture coordinates
-                                       f.EmitTextureCoordinates();
+                               // update brush bounds
+                               const Winding& winding = f.getWinding();
+                               for ( Winding::const_iterator i = winding.begin(); i != winding.end(); ++i )
+                               {
+                                       aabb_extend_by_point_safe( m_aabb_local, ( *i ).vertex );
                                }
+
+                               // update texture coordinates
+                               f.EmitTextureCoordinates();
                        }
                }
+       }
 
-               bool degenerate = !isBounded();
-
-               if ( !degenerate ) {
-                       // clean up connectivity information.
-                       // these cleanups must be applied in a specific order.
-                       removeDegenerateEdges();
-                       removeDegenerateFaces();
-                       removeDuplicateEdges();
-                       verifyConnectivityGraph();
-               }
+       bool degenerate = !isBounded();
 
-               return degenerate;
+       if ( !degenerate ) {
+               // clean up connectivity information.
+               // these cleanups must be applied in a specific order.
+               removeDegenerateEdges();
+               removeDegenerateFaces();
+               removeDuplicateEdges();
+               verifyConnectivityGraph();
        }
 
-       /// \brief Constructs the face windings and updates anything that depends on them.
-       void buildBRep();
+       return degenerate;
+}
+
+/// \brief Constructs the face windings and updates anything that depends on them.
+void buildBRep();
 };
 
 
@@ -2399,32 +2405,32 @@ class FaceInstance;
 
 class FaceInstanceSet
 {
-       typedef SelectionList<FaceInstance> FaceInstances;
-       FaceInstances m_faceInstances;
+typedef SelectionList<FaceInstance> FaceInstances;
+FaceInstances m_faceInstances;
 public:
-       void insert( FaceInstance& faceInstance ){
-               m_faceInstances.append( faceInstance );
-       }
+void insert( FaceInstance& faceInstance ){
+       m_faceInstances.append( faceInstance );
+}
 
-       void erase( FaceInstance& faceInstance ){
-               m_faceInstances.erase( faceInstance );
-       }
+void erase( FaceInstance& faceInstance ){
+       m_faceInstances.erase( faceInstance );
+}
 
-       template<typename Functor>
-       void foreach( Functor functor ){
-               for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
-               {
-                       functor( *( *i ) );
-               }
+template<typename Functor>
+void foreach( Functor functor ){
+       for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
+       {
+               functor( *( *i ) );
        }
+}
 
-       bool empty() const {
-               return m_faceInstances.empty();
-       }
+bool empty() const {
+       return m_faceInstances.empty();
+}
 
-       FaceInstance& last() const {
-               return m_faceInstances.back();
-       }
+FaceInstance& last() const {
+       return m_faceInstances.back();
+}
 };
 
 extern FaceInstanceSet g_SelectedFaceInstances;
@@ -2470,503 +2476,542 @@ inline bool triangles_same_winding( const BasicVector3<Element>& x1, const Basic
 }
 
 
-typedef const Plane3* PlanePointer;
-typedef PlanePointer* PlanesIterator;
-
 class VectorLightList : public LightList
 {
-       typedef std::vector<const RendererLight*> Lights;
-       Lights m_lights;
+typedef std::vector<const RendererLight*> Lights;
+Lights m_lights;
 public:
-       void addLight( const RendererLight& light ){
-               m_lights.push_back( &light );
-       }
+void addLight( const RendererLight& light ){
+       m_lights.push_back( &light );
+}
 
-       void clear(){
-               m_lights.clear();
-       }
+void clear(){
+       m_lights.clear();
+}
 
-       void evaluateLights() const {
-       }
+void evaluateLights() const {
+}
+
+void lightsChanged() const {
+}
 
-       void lightsChanged() const {
+void forEachLight( const RendererLightCallback& callback ) const {
+       for ( Lights::const_iterator i = m_lights.begin(); i != m_lights.end(); ++i )
+       {
+               callback( *( *i ) );
        }
+}
+};
 
-       void forEachLight( const RendererLightCallback& callback ) const {
-               for ( Lights::const_iterator i = m_lights.begin(); i != m_lights.end(); ++i )
-               {
-                       callback( *( *i ) );
-               }
+class BoolSelector : public Selector
+{
+bool m_selected;
+SelectionIntersection m_intersection;
+Selectable* m_selectable;
+public:
+BoolSelector() : m_selected( false ){
+}
+
+void pushSelectable( Selectable& selectable ){
+       m_intersection = SelectionIntersection();
+       m_selectable = &selectable;
+}
+void popSelectable(){
+       if ( m_intersection.valid() ) {
+               m_selected = true;
        }
+       m_intersection = SelectionIntersection();
+}
+void addIntersection( const SelectionIntersection& intersection ){
+       assign_if_closer( m_intersection, intersection );
+}
+
+bool isSelected(){
+       return m_selected;
+}
 };
 
-class FaceInstance
-{
-       Face* m_face;
-       ObservedSelectable m_selectable;
-       ObservedSelectable m_selectableVertices;
-       ObservedSelectable m_selectableEdges;
-       SelectionChangeCallback m_selectionChanged;
+class FaceInstance
+{
+Face* m_face;
+ObservedSelectable m_selectable;
+ObservedSelectable m_selectableVertices;
+ObservedSelectable m_selectableEdges;
+SelectionChangeCallback m_selectionChanged;
+
+VertexSelection m_vertexSelection;
+VertexSelection m_edgeSelection;
+
+public:
+mutable VectorLightList m_lights;
+
+FaceInstance( Face& face, const SelectionChangeCallback& observer ) :
+       m_face( &face ),
+       m_selectable( SelectedChangedCaller( *this ) ),
+       m_selectableVertices( observer ),
+       m_selectableEdges( observer ),
+       m_selectionChanged( observer ){
+}
+
+FaceInstance( const FaceInstance& other ) :
+       m_face( other.m_face ),
+       m_selectable( SelectedChangedCaller( *this ) ),
+       m_selectableVertices( other.m_selectableVertices ),
+       m_selectableEdges( other.m_selectableEdges ),
+       m_selectionChanged( other.m_selectionChanged ){
+}
 
-       VertexSelection m_vertexSelection;
-       VertexSelection m_edgeSelection;
+FaceInstance& operator=( const FaceInstance& other ){
+       m_face = other.m_face;
+       return *this;
+}
 
-public:
-       mutable VectorLightList m_lights;
+Face& getFace(){
+       return *m_face;
+}
 
-       FaceInstance( Face& face, const SelectionChangeCallback& observer ) :
-               m_face( &face ),
-               m_selectable( SelectedChangedCaller( *this ) ),
-               m_selectableVertices( observer ),
-               m_selectableEdges( observer ),
-               m_selectionChanged( observer ){
-       }
+const Face& getFace() const {
+       return *m_face;
+}
 
-       FaceInstance( const FaceInstance& other ) :
-               m_face( other.m_face ),
-               m_selectable( SelectedChangedCaller( *this ) ),
-               m_selectableVertices( other.m_selectableVertices ),
-               m_selectableEdges( other.m_selectableEdges ),
-               m_selectionChanged( other.m_selectionChanged ){
+void selectedChanged( const Selectable& selectable ){
+       if ( selectable.isSelected() ) {
+               g_SelectedFaceInstances.insert( *this );
        }
-
-       FaceInstance& operator=( const FaceInstance& other ){
-               m_face = other.m_face;
-               return *this;
+       else
+       {
+               g_SelectedFaceInstances.erase( *this );
        }
+       m_selectionChanged( selectable );
+}
 
-       Face& getFace(){
-               return *m_face;
-       }
+typedef MemberCaller<FaceInstance, void(const Selectable&), &FaceInstance::selectedChanged> SelectedChangedCaller;
 
-       const Face& getFace() const {
-               return *m_face;
-       }
+bool selectedVertices() const {
+       return !m_vertexSelection.empty();
+}
 
-       void selectedChanged( const Selectable& selectable ){
-               if ( selectable.isSelected() ) {
-                       g_SelectedFaceInstances.insert( *this );
-               }
-               else
-               {
-                       g_SelectedFaceInstances.erase( *this );
-               }
-               m_selectionChanged( selectable );
-       }
+bool selectedEdges() const {
+       return !m_edgeSelection.empty();
+}
 
-       typedef MemberCaller<FaceInstance, void(const Selectable&), &FaceInstance::selectedChanged> SelectedChangedCaller;
+bool isSelected() const {
+       return m_selectable.isSelected();
+}
 
-       bool selectedVertices() const {
-               return !m_vertexSelection.empty();
-       }
+bool selectedComponents() const {
+       return selectedVertices() || selectedEdges() || isSelected();
+}
 
-       bool selectedEdges() const {
-               return !m_edgeSelection.empty();
+bool selectedComponents( SelectionSystem::EComponentMode mode ) const {
+       switch ( mode )
+       {
+       case SelectionSystem::eVertex:
+               return selectedVertices();
+       case SelectionSystem::eEdge:
+               return selectedEdges();
+       case SelectionSystem::eFace:
+               return isSelected();
+       default:
+               return false;
        }
+}
 
-       bool isSelected() const {
-               return m_selectable.isSelected();
-       }
+void setSelected( SelectionSystem::EComponentMode mode, bool select ){
+       switch ( mode )
+       {
+       case SelectionSystem::eFace:
+               m_selectable.setSelected( select );
+               break;
+       case SelectionSystem::eVertex:
+               ASSERT_MESSAGE( !select, "select-all not supported" );
 
-       bool selectedComponents() const {
-               return selectedVertices() || selectedEdges() || isSelected();
-       }
+               m_vertexSelection.clear();
+               m_selectableVertices.setSelected( false );
+               break;
+       case SelectionSystem::eEdge:
+               ASSERT_MESSAGE( !select, "select-all not supported" );
 
-       bool selectedComponents( SelectionSystem::EComponentMode mode ) const {
-               switch ( mode )
-               {
-               case SelectionSystem::eVertex:
-                       return selectedVertices();
-               case SelectionSystem::eEdge:
-                       return selectedEdges();
-               case SelectionSystem::eFace:
-                       return isSelected();
-               default:
-                       return false;
-               }
+               m_edgeSelection.clear();
+               m_selectableEdges.setSelected( false );
+               break;
+       default:
+               break;
        }
+}
 
-       void setSelected( SelectionSystem::EComponentMode mode, bool select ){
-               switch ( mode )
-               {
-               case SelectionSystem::eFace:
-                       m_selectable.setSelected( select );
-                       break;
-               case SelectionSystem::eVertex:
-                       ASSERT_MESSAGE( !select, "select-all not supported" );
-
-                       m_vertexSelection.clear();
-                       m_selectableVertices.setSelected( false );
-                       break;
-               case SelectionSystem::eEdge:
-                       ASSERT_MESSAGE( !select, "select-all not supported" );
-
-                       m_edgeSelection.clear();
-                       m_selectableEdges.setSelected( false );
-                       break;
-               default:
-                       break;
+template<typename Functor>
+void SelectedVertices_foreach( Functor functor ) const {
+       for ( VertexSelection::const_iterator i = m_vertexSelection.begin(); i != m_vertexSelection.end(); ++i )
+       {
+               std::size_t index = Winding_FindAdjacent( getFace().getWinding(), *i );
+               if ( index != c_brush_maxFaces ) {
+                       functor( getFace().getWinding()[index].vertex );
                }
        }
+}
 
-       template<typename Functor>
-       void SelectedVertices_foreach( Functor functor ) const {
-               for ( VertexSelection::const_iterator i = m_vertexSelection.begin(); i != m_vertexSelection.end(); ++i )
-               {
-                       std::size_t index = Winding_FindAdjacent( getFace().getWinding(), *i );
-                       if ( index != c_brush_maxFaces ) {
-                               functor( getFace().getWinding()[index].vertex );
-                       }
+template<typename Functor>
+void SelectedEdges_foreach( Functor functor ) const {
+       for ( VertexSelection::const_iterator i = m_edgeSelection.begin(); i != m_edgeSelection.end(); ++i )
+       {
+               std::size_t index = Winding_FindAdjacent( getFace().getWinding(), *i );
+               if ( index != c_brush_maxFaces ) {
+                       const Winding& winding = getFace().getWinding();
+                       std::size_t adjacent = Winding_next( winding, index );
+                       functor( vector3_mid( winding[index].vertex, winding[adjacent].vertex ) );
                }
        }
+}
 
-       template<typename Functor>
-       void SelectedEdges_foreach( Functor functor ) const {
-               for ( VertexSelection::const_iterator i = m_edgeSelection.begin(); i != m_edgeSelection.end(); ++i )
-               {
-                       std::size_t index = Winding_FindAdjacent( getFace().getWinding(), *i );
-                       if ( index != c_brush_maxFaces ) {
-                               const Winding& winding = getFace().getWinding();
-                               std::size_t adjacent = Winding_next( winding, index );
-                               functor( vector3_mid( winding[index].vertex, winding[adjacent].vertex ) );
-                       }
-               }
+template<typename Functor>
+void SelectedFaces_foreach( Functor functor ) const {
+       if ( isSelected() ) {
+               functor( centroid() );
        }
+}
 
-       template<typename Functor>
-       void SelectedFaces_foreach( Functor functor ) const {
-               if ( isSelected() ) {
-                       functor( centroid() );
-               }
-       }
+template<typename Functor>
+void SelectedComponents_foreach( Functor functor ) const {
+       SelectedVertices_foreach( functor );
+       SelectedEdges_foreach( functor );
+       SelectedFaces_foreach( functor );
+}
 
-       template<typename Functor>
-       void SelectedComponents_foreach( Functor functor ) const {
-               SelectedVertices_foreach( functor );
-               SelectedEdges_foreach( functor );
-               SelectedFaces_foreach( functor );
-       }
+void iterate_selected( AABB& aabb ) const {
+       SelectedComponents_foreach([&](const Vector3 &point) {
+               aabb_extend_by_point_safe(aabb, point);
+       });
+}
 
-       void iterate_selected( AABB& aabb ) const {
-               SelectedComponents_foreach([&](const Vector3 &point) {
-                       aabb_extend_by_point_safe(aabb, point);
-               });
-       }
+void iterate_selected( RenderablePointVector& points ) const {
+       SelectedComponents_foreach([&](const Vector3 &point) {
+               const Colour4b colour_selected(0, 0, 255, 255);
+               points.push_back(pointvertex_for_windingpoint(point, colour_selected));
+       });
+}
 
-       void iterate_selected( RenderablePointVector& points ) const {
-               SelectedComponents_foreach([&](const Vector3 &point) {
-                       const Colour4b colour_selected(0, 0, 255, 255);
-                       points.push_back(pointvertex_for_windingpoint(point, colour_selected));
-               });
-       }
+bool intersectVolume( const VolumeTest& volume, const Matrix4& localToWorld ) const {
+       return m_face->intersectVolume( volume, localToWorld );
+}
 
-       bool intersectVolume( const VolumeTest& volume, const Matrix4& localToWorld ) const {
-               return m_face->intersectVolume( volume, localToWorld );
+void render( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld ) const {
+       if ( !m_face->isFiltered() && m_face->contributes() && intersectVolume( volume, localToWorld ) ) {
+               renderer.PushState();
+               if ( selectedComponents() ) {
+                       renderer.Highlight( Renderer::eFace );
+               }
+               m_face->render( renderer, localToWorld );
+               renderer.PopState();
        }
+}
 
-       void render( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld ) const {
-               if ( !m_face->isFiltered() && m_face->contributes() && intersectVolume( volume, localToWorld ) ) {
-                       renderer.PushState();
-                       if ( selectedComponents() ) {
-                               renderer.Highlight( Renderer::eFace );
-                       }
-                       m_face->render( renderer, localToWorld );
-                       renderer.PopState();
-               }
+void testSelect( SelectionTest& test, SelectionIntersection& best ){
+       if ( !m_face->isFiltered() ) {
+               m_face->testSelect( test, best );
        }
+}
 
-       void testSelect( SelectionTest& test, SelectionIntersection& best ){
-               if ( !m_face->isFiltered() ) {
-                       m_face->testSelect( test, best );
-               }
+void testSelect( Selector& selector, SelectionTest& test ){
+       SelectionIntersection best;
+       testSelect( test, best );
+       if ( best.valid() ) {
+               Selector_add( selector, m_selectable, best );
        }
+}
 
-       void testSelect( Selector& selector, SelectionTest& test ){
+void testSelect_centroid( Selector& selector, SelectionTest& test ){
+       if ( m_face->contributes() && !m_face->isFiltered() ) {
                SelectionIntersection best;
-               testSelect( test, best );
+               m_face->testSelect_centroid( test, best );
                if ( best.valid() ) {
                        Selector_add( selector, m_selectable, best );
                }
        }
+}
 
-       void testSelect_centroid( Selector& selector, SelectionTest& test ){
-               if ( m_face->contributes() && !m_face->isFiltered() ) {
-                       SelectionIntersection best;
-                       m_face->testSelect_centroid( test, best );
-                       if ( best.valid() ) {
-                               Selector_add( selector, m_selectable, best );
-                       }
+void selectPlane( Selector& selector, const Line& line, const PlaneCallback& selectedPlaneCallback ){
+       for ( Winding::const_iterator i = getFace().getWinding().begin(); i != getFace().getWinding().end(); ++i )
+       {
+               Vector3 v( vector3_subtracted( line_closest_point( line, ( *i ).vertex ), ( *i ).vertex ) );
+               double dot = vector3_dot( getFace().plane3().normal(), v );
+               //globalOutputStream() << dot << "\n";
+               //epsilon to prevent perpendicular faces pickup
+               if ( dot <= 0.005 ) {
+                       return;
                }
        }
 
-       void selectPlane( Selector& selector, const Line& line, PlanesIterator first, PlanesIterator last, const PlaneCallback& selectedPlaneCallback ){
-               for ( Winding::const_iterator i = getFace().getWinding().begin(); i != getFace().getWinding().end(); ++i )
-               {
-                       Vector3 v( vector3_subtracted( line_closest_point( line, ( *i ).vertex ), ( *i ).vertex ) );
-                       double dot = vector3_dot( getFace().plane3().normal(), v );
-                       if ( dot <= 0 ) {
-                               return;
-                       }
-               }
+       Selector_add( selector, m_selectable );
 
-               Selector_add( selector, m_selectable );
+       selectedPlaneCallback( getFace().plane3() );
+}
 
-               selectedPlaneCallback( getFace().plane3() );
+void selectReversedPlane( Selector& selector, const SelectedPlanes& selectedPlanes ){
+       if ( selectedPlanes.contains( plane3_flipped( getFace().plane3() ) ) ) {
+               Selector_add( selector, m_selectable );
        }
+}
 
-       void selectReversedPlane( Selector& selector, const SelectedPlanes& selectedPlanes ){
-               if ( selectedPlanes.contains( plane3_flipped( getFace().plane3() ) ) ) {
-                       Selector_add( selector, m_selectable );
+bool trySelectPlane( const Line& line ){
+       for ( Winding::const_iterator i = getFace().getWinding().begin(); i != getFace().getWinding().end(); ++i ){
+               Vector3 v( vector3_subtracted( line_closest_point( line, ( *i ).vertex ), ( *i ).vertex ) );
+               double dot = vector3_dot( getFace().plane3().normal(), v );
+               //epsilon to prevent perpendicular faces pickup
+               if ( dot <= 0.005 ) {
+                       return false;
                }
        }
+       return true;
+}
 
-       void transformComponents( const Matrix4& matrix ){
-               if ( isSelected() ) {
-                       m_face->transform( matrix, false );
+void transformComponents( const Matrix4& matrix ){
+       if ( isSelected() ) {
+               m_face->transform( matrix, false );
+       }
+       if ( selectedVertices() ) {
+               if ( m_vertexSelection.size() == 1 ) {
+                       matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[1] );
+                       m_face->assign_planepts( m_face->m_move_planeptsTransformed );
                }
-               if ( selectedVertices() ) {
-                       if ( m_vertexSelection.size() == 1 ) {
-                               matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[1] );
-                               m_face->assign_planepts( m_face->m_move_planeptsTransformed );
-                       }
-                       else if ( m_vertexSelection.size() == 2 ) {
-                               matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[1] );
-                               matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[2] );
-                               m_face->assign_planepts( m_face->m_move_planeptsTransformed );
-                       }
-                       else if ( m_vertexSelection.size() >= 3 ) {
-                               matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[0] );
-                               matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[1] );
-                               matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[2] );
-                               m_face->assign_planepts( m_face->m_move_planeptsTransformed );
-                       }
+               else if ( m_vertexSelection.size() == 2 ) {
+                       matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[1] );
+                       matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[2] );
+                       m_face->assign_planepts( m_face->m_move_planeptsTransformed );
                }
-               if ( selectedEdges() ) {
-                       if ( m_edgeSelection.size() == 1 ) {
-                               matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[0] );
-                               matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[1] );
-                               m_face->assign_planepts( m_face->m_move_planeptsTransformed );
-                       }
-                       else if ( m_edgeSelection.size() >= 2 ) {
-                               matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[0] );
-                               matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[1] );
-                               matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[2] );
-                               m_face->assign_planepts( m_face->m_move_planeptsTransformed );
-                       }
+               else if ( m_vertexSelection.size() >= 3 ) {
+                       matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[0] );
+                       matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[1] );
+                       matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[2] );
+                       m_face->assign_planepts( m_face->m_move_planeptsTransformed );
                }
        }
-
-       void snapto( float snap ){
-               m_face->snapto( snap );
-       }
-
-       void snapComponents( float snap ){
-               if ( isSelected() ) {
-                       snapto( snap );
-               }
-               if ( selectedVertices() ) {
-                       vector3_snap( m_face->m_move_planepts[0], snap );
-                       vector3_snap( m_face->m_move_planepts[1], snap );
-                       vector3_snap( m_face->m_move_planepts[2], snap );
-                       m_face->assign_planepts( m_face->m_move_planepts );
-                       planepts_assign( m_face->m_move_planeptsTransformed, m_face->m_move_planepts );
-                       m_face->freezeTransform();
+       if ( selectedEdges() ) {
+               if ( m_edgeSelection.size() == 1 ) {
+                       matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[0] );
+                       matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[1] );
+                       m_face->assign_planepts( m_face->m_move_planeptsTransformed );
                }
-               if ( selectedEdges() ) {
-                       vector3_snap( m_face->m_move_planepts[0], snap );
-                       vector3_snap( m_face->m_move_planepts[1], snap );
-                       vector3_snap( m_face->m_move_planepts[2], snap );
-                       m_face->assign_planepts( m_face->m_move_planepts );
-                       planepts_assign( m_face->m_move_planeptsTransformed, m_face->m_move_planepts );
-                       m_face->freezeTransform();
+               else if ( m_edgeSelection.size() >= 2 ) {
+                       matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[0] );
+                       matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[1] );
+                       matrix4_transform_point( matrix, m_face->m_move_planeptsTransformed[2] );
+                       m_face->assign_planepts( m_face->m_move_planeptsTransformed );
                }
        }
+}
+
+void snapto( float snap ){
+       m_face->snapto( snap );
+}
 
-       void update_move_planepts_vertex( std::size_t index ){
-               m_face->update_move_planepts_vertex( index, m_face->m_move_planepts );
+void snapComponents( float snap ){
+       if ( isSelected() ) {
+               snapto( snap );
+       }
+       if ( selectedVertices() ) {
+               vector3_snap( m_face->m_move_planepts[0], snap );
+               vector3_snap( m_face->m_move_planepts[1], snap );
+               vector3_snap( m_face->m_move_planepts[2], snap );
+               m_face->assign_planepts( m_face->m_move_planepts );
+               planepts_assign( m_face->m_move_planeptsTransformed, m_face->m_move_planepts );
+               m_face->freezeTransform();
+       }
+       if ( selectedEdges() ) {
+               vector3_snap( m_face->m_move_planepts[0], snap );
+               vector3_snap( m_face->m_move_planepts[1], snap );
+               vector3_snap( m_face->m_move_planepts[2], snap );
+               m_face->assign_planepts( m_face->m_move_planepts );
+               planepts_assign( m_face->m_move_planeptsTransformed, m_face->m_move_planepts );
+               m_face->freezeTransform();
        }
+}
 
-       void update_move_planepts_vertex2( std::size_t index, std::size_t other ){
-               const std::size_t numpoints = m_face->getWinding().numpoints;
-               ASSERT_MESSAGE( index < numpoints, "select_vertex: invalid index" );
+void update_move_planepts_vertex( std::size_t index ){
+       m_face->update_move_planepts_vertex( index, m_face->m_move_planepts );
+}
 
-               const std::size_t opposite = Winding_Opposite( m_face->getWinding(), index, other );
+void update_move_planepts_vertex2( std::size_t index, std::size_t other ){
+       const std::size_t numpoints = m_face->getWinding().numpoints;
+       ASSERT_MESSAGE( index < numpoints, "select_vertex: invalid index" );
 
-               if ( triangle_reversed( index, other, opposite ) ) {
-                       std::swap( index, other );
-               }
+       const std::size_t opposite = Winding_Opposite( m_face->getWinding(), index, other );
 
-               ASSERT_MESSAGE(
-                       triangles_same_winding(
-                               m_face->getWinding()[opposite].vertex,
-                               m_face->getWinding()[index].vertex,
-                               m_face->getWinding()[other].vertex,
-                               m_face->getWinding()[0].vertex,
-                               m_face->getWinding()[1].vertex,
-                               m_face->getWinding()[2].vertex
-                               ),
-                       "update_move_planepts_vertex2: error"
-                       );
-
-               m_face->m_move_planepts[0] = m_face->getWinding()[opposite].vertex;
-               m_face->m_move_planepts[1] = m_face->getWinding()[index].vertex;
-               m_face->m_move_planepts[2] = m_face->getWinding()[other].vertex;
-               planepts_quantise( m_face->m_move_planepts, GRID_MIN ); // winding points are very inaccurate
-       }
-
-       void update_selection_vertex(){
-               if ( m_vertexSelection.size() == 0 ) {
-                       m_selectableVertices.setSelected( false );
-               }
-               else
-               {
-                       m_selectableVertices.setSelected( true );
+       if ( triangle_reversed( index, other, opposite ) ) {
+               std::swap( index, other );
+       }
 
-                       if ( m_vertexSelection.size() == 1 ) {
-                               std::size_t index = Winding_FindAdjacent( getFace().getWinding(), *m_vertexSelection.begin() );
+       ASSERT_MESSAGE(
+               triangles_same_winding(
+                       m_face->getWinding()[opposite].vertex,
+                       m_face->getWinding()[index].vertex,
+                       m_face->getWinding()[other].vertex,
+                       m_face->getWinding()[0].vertex,
+                       m_face->getWinding()[1].vertex,
+                       m_face->getWinding()[2].vertex
+                       ),
+               "update_move_planepts_vertex2: error"
+               );
 
-                               if ( index != c_brush_maxFaces ) {
-                                       update_move_planepts_vertex( index );
-                               }
-                       }
-                       else if ( m_vertexSelection.size() == 2 ) {
-                               std::size_t index = Winding_FindAdjacent( getFace().getWinding(), *m_vertexSelection.begin() );
-                               std::size_t other = Winding_FindAdjacent( getFace().getWinding(), *( ++m_vertexSelection.begin() ) );
+       m_face->m_move_planepts[0] = m_face->getWinding()[opposite].vertex;
+       m_face->m_move_planepts[1] = m_face->getWinding()[index].vertex;
+       m_face->m_move_planepts[2] = m_face->getWinding()[other].vertex;
+       planepts_quantise( m_face->m_move_planepts, GRID_MIN ); // winding points are very inaccurate
+}
 
-                               if ( index != c_brush_maxFaces
-                                        && other != c_brush_maxFaces ) {
-                                       update_move_planepts_vertex2( index, other );
-                               }
-                       }
-               }
+void update_selection_vertex(){
+       if ( m_vertexSelection.size() == 0 ) {
+               m_selectableVertices.setSelected( false );
        }
+       else
+       {
+               m_selectableVertices.setSelected( true );
 
-       void select_vertex( std::size_t index, bool select ){
-               if ( select ) {
-                       VertexSelection_insert( m_vertexSelection, getFace().getWinding()[index].adjacent );
-               }
-               else
-               {
-                       VertexSelection_erase( m_vertexSelection, getFace().getWinding()[index].adjacent );
+               if ( m_vertexSelection.size() == 1 ) {
+                       std::size_t index = Winding_FindAdjacent( getFace().getWinding(), *m_vertexSelection.begin() );
+
+                       if ( index != c_brush_maxFaces ) {
+                               update_move_planepts_vertex( index );
+                       }
                }
+               else if ( m_vertexSelection.size() == 2 ) {
+                       std::size_t index = Winding_FindAdjacent( getFace().getWinding(), *m_vertexSelection.begin() );
+                       std::size_t other = Winding_FindAdjacent( getFace().getWinding(), *( ++m_vertexSelection.begin() ) );
 
-               SceneChangeNotify();
-               update_selection_vertex();
+                       if ( index != c_brush_maxFaces
+                                && other != c_brush_maxFaces ) {
+                               update_move_planepts_vertex2( index, other );
+                       }
+               }
        }
+}
 
-       bool selected_vertex( std::size_t index ) const {
-               return VertexSelection_find( m_vertexSelection, getFace().getWinding()[index].adjacent ) != m_vertexSelection.end();
+void select_vertex( std::size_t index, bool select ){
+       if ( select ) {
+               VertexSelection_insert( m_vertexSelection, getFace().getWinding()[index].adjacent );
+       }
+       else
+       {
+               VertexSelection_erase( m_vertexSelection, getFace().getWinding()[index].adjacent );
        }
 
-       void update_move_planepts_edge( std::size_t index ){
-               std::size_t numpoints = m_face->getWinding().numpoints;
-               ASSERT_MESSAGE( index < numpoints, "select_edge: invalid index" );
+       SceneChangeNotify();
+       update_selection_vertex();
+}
 
-               std::size_t adjacent = Winding_next( m_face->getWinding(), index );
-               std::size_t opposite = Winding_Opposite( m_face->getWinding(), index );
-               m_face->m_move_planepts[0] = m_face->getWinding()[index].vertex;
-               m_face->m_move_planepts[1] = m_face->getWinding()[adjacent].vertex;
-               m_face->m_move_planepts[2] = m_face->getWinding()[opposite].vertex;
-               planepts_quantise( m_face->m_move_planepts, GRID_MIN ); // winding points are very inaccurate
-       }
+bool selected_vertex( std::size_t index ) const {
+       return VertexSelection_find( m_vertexSelection, getFace().getWinding()[index].adjacent ) != m_vertexSelection.end();
+}
 
-       void update_selection_edge(){
-               if ( m_edgeSelection.size() == 0 ) {
-                       m_selectableEdges.setSelected( false );
-               }
-               else
-               {
-                       m_selectableEdges.setSelected( true );
+void update_move_planepts_edge( std::size_t index ){
+       std::size_t numpoints = m_face->getWinding().numpoints;
+       ASSERT_MESSAGE( index < numpoints, "select_edge: invalid index" );
 
-                       if ( m_edgeSelection.size() == 1 ) {
-                               std::size_t index = Winding_FindAdjacent( getFace().getWinding(), *m_edgeSelection.begin() );
+       std::size_t adjacent = Winding_next( m_face->getWinding(), index );
+       std::size_t opposite = Winding_Opposite( m_face->getWinding(), index );
+       m_face->m_move_planepts[0] = m_face->getWinding()[index].vertex;
+       m_face->m_move_planepts[1] = m_face->getWinding()[adjacent].vertex;
+       m_face->m_move_planepts[2] = m_face->getWinding()[opposite].vertex;
+       planepts_quantise( m_face->m_move_planepts, GRID_MIN ); // winding points are very inaccurate
+}
 
-                               if ( index != c_brush_maxFaces ) {
-                                       update_move_planepts_edge( index );
-                               }
-                       }
-               }
+void update_selection_edge(){
+       if ( m_edgeSelection.size() == 0 ) {
+               m_selectableEdges.setSelected( false );
        }
+       else
+       {
+               m_selectableEdges.setSelected( true );
 
-       void select_edge( std::size_t index, bool select ){
-               if ( select ) {
-                       VertexSelection_insert( m_edgeSelection, getFace().getWinding()[index].adjacent );
-               }
-               else
-               {
-                       VertexSelection_erase( m_edgeSelection, getFace().getWinding()[index].adjacent );
-               }
+               if ( m_edgeSelection.size() == 1 ) {
+                       std::size_t index = Winding_FindAdjacent( getFace().getWinding(), *m_edgeSelection.begin() );
 
-               SceneChangeNotify();
-               update_selection_edge();
+                       if ( index != c_brush_maxFaces ) {
+                               update_move_planepts_edge( index );
+                       }
+               }
        }
+}
 
-       bool selected_edge( std::size_t index ) const {
-               return VertexSelection_find( m_edgeSelection, getFace().getWinding()[index].adjacent ) != m_edgeSelection.end();
+void select_edge( std::size_t index, bool select ){
+       if ( select ) {
+               VertexSelection_insert( m_edgeSelection, getFace().getWinding()[index].adjacent );
        }
-
-       const Vector3& centroid() const {
-               return m_face->centroid();
+       else
+       {
+               VertexSelection_erase( m_edgeSelection, getFace().getWinding()[index].adjacent );
        }
 
-       void connectivityChanged(){
-               // This occurs when a face is added or removed.
-               // The current vertex and edge selections no longer valid and must be cleared.
-               m_vertexSelection.clear();
-               m_selectableVertices.setSelected( false );
-               m_edgeSelection.clear();
-               m_selectableEdges.setSelected( false );
-       }
+       SceneChangeNotify();
+       update_selection_edge();
+}
+
+bool selected_edge( std::size_t index ) const {
+       return VertexSelection_find( m_edgeSelection, getFace().getWinding()[index].adjacent ) != m_edgeSelection.end();
+}
+
+const Vector3& centroid() const {
+       return m_face->centroid();
+}
+
+void connectivityChanged(){
+       // This occurs when a face is added or removed.
+       // The current vertex and edge selections no longer valid and must be cleared.
+       m_vertexSelection.clear();
+       m_selectableVertices.setSelected( false );
+       m_edgeSelection.clear();
+       m_selectableEdges.setSelected( false );
+}
 };
 
 class BrushClipPlane : public OpenGLRenderable
 {
-       Plane3 m_plane;
-       Winding m_winding;
-       static Shader* m_state;
+Plane3 m_plane;
+Winding m_winding;
+static Shader* m_state;
 public:
-       static void constructStatic(){
-               m_state = GlobalShaderCache().capture( "$CLIPPER_OVERLAY" );
-       }
+static void constructStatic(){
+       m_state = GlobalShaderCache().capture( "$CLIPPER_OVERLAY" );
+}
 
-       static void destroyStatic(){
-               GlobalShaderCache().release( "$CLIPPER_OVERLAY" );
-       }
+static void destroyStatic(){
+       GlobalShaderCache().release( "$CLIPPER_OVERLAY" );
+}
 
-       void setPlane( const Brush& brush, const Plane3& plane ){
-               m_plane = plane;
-               if ( plane3_valid( m_plane ) ) {
-                       brush.windingForClipPlane( m_winding, m_plane );
-               }
-               else
-               {
-                       m_winding.resize( 0 );
-               }
+void setPlane( const Brush& brush, const Plane3& plane ){
+       m_plane = plane;
+       if ( plane3_valid( m_plane ) ) {
+               brush.windingForClipPlane( m_winding, m_plane );
        }
+       else
+       {
+               m_winding.resize( 0 );
+       }
+}
 
-       void render( RenderStateFlags state ) const {
-               if ( ( state & RENDER_FILL ) != 0 ) {
-                       Winding_Draw( m_winding, m_plane.normal(), state );
-               }
-               else
-               {
-                       Winding_DrawWireframe( m_winding );
+void render( RenderStateFlags state ) const {
+       if ( ( state & RENDER_FILL ) != 0 ) {
+               Winding_Draw( m_winding, m_plane.normal(), state );
+       }
+       else
+       {
+               Winding_DrawWireframe( m_winding );
 
-                       // also draw a line indicating the direction of the cut
-                       Vector3 lineverts[2];
-                       Winding_Centroid( m_winding, m_plane, lineverts[0] );
-                       lineverts[1] = vector3_added( lineverts[0], vector3_scaled( m_plane.normal(), Brush::m_maxWorldCoord * 4 ) );
+               // also draw a line indicating the direction of the cut
+               Vector3 lineverts[2];
+               Winding_Centroid( m_winding, m_plane, lineverts[0] );
+               lineverts[1] = vector3_added( lineverts[0], vector3_scaled( m_plane.normal(), Brush::m_maxWorldCoord * 4 ) );
 
-                       glVertexPointer( 3, GL_FLOAT, sizeof( Vector3 ), &lineverts[0] );
-                       glDrawArrays( GL_LINES, 0, GLsizei( 2 ) );
-               }
+               glVertexPointer( 3, GL_FLOAT, sizeof( Vector3 ), &lineverts[0] );
+               glDrawArrays( GL_LINES, 0, GLsizei( 2 ) );
        }
+}
 
-       void render( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld ) const {
-               renderer.SetState( m_state, Renderer::eWireframeOnly );
-               renderer.SetState( m_state, Renderer::eFullMaterials );
-               renderer.addRenderable( *this, localToWorld );
-       }
+void render( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld ) const {
+       renderer.SetState( m_state, Renderer::eWireframeOnly );
+       renderer.SetState( m_state, Renderer::eFullMaterials );
+       renderer.addRenderable( *this, localToWorld );
+}
 };
 
 inline void Face_addLight( const FaceInstance& face, const Matrix4& localToWorld, const RendererLight& light ){
@@ -2984,114 +3029,142 @@ typedef std::vector<FaceInstance> FaceInstances;
 
 class EdgeInstance : public Selectable
 {
-       FaceInstances& m_faceInstances;
-       SelectableEdge* m_edge;
+FaceInstances& m_faceInstances;
+SelectableEdge* m_edge;
 
-       void select_edge( bool select ){
-               FaceVertexId faceVertex = m_edge->m_faceVertex;
-               m_faceInstances[faceVertex.getFace()].select_edge( faceVertex.getVertex(), select );
-               faceVertex = next_edge( m_edge->m_faces, faceVertex );
-               m_faceInstances[faceVertex.getFace()].select_edge( faceVertex.getVertex(), select );
-       }
-
-       bool selected_edge() const {
-               FaceVertexId faceVertex = m_edge->m_faceVertex;
-               if ( !m_faceInstances[faceVertex.getFace()].selected_edge( faceVertex.getVertex() ) ) {
-                       return false;
-               }
-               faceVertex = next_edge( m_edge->m_faces, faceVertex );
-               if ( !m_faceInstances[faceVertex.getFace()].selected_edge( faceVertex.getVertex() ) ) {
-                       return false;
-               }
+void select_edge( bool select ){
+       FaceVertexId faceVertex = m_edge->m_faceVertex;
+       m_faceInstances[faceVertex.getFace()].select_edge( faceVertex.getVertex(), select );
+       faceVertex = next_edge( m_edge->m_faces, faceVertex );
+       m_faceInstances[faceVertex.getFace()].select_edge( faceVertex.getVertex(), select );
+}
 
-               return true;
+bool selected_edge() const {
+       FaceVertexId faceVertex = m_edge->m_faceVertex;
+       if ( !m_faceInstances[faceVertex.getFace()].selected_edge( faceVertex.getVertex() ) ) {
+               return false;
        }
+       faceVertex = next_edge( m_edge->m_faces, faceVertex );
+       if ( !m_faceInstances[faceVertex.getFace()].selected_edge( faceVertex.getVertex() ) ) {
+               return false;
+       }
+
+       return true;
+}
 
 public:
-       EdgeInstance( FaceInstances& faceInstances, SelectableEdge& edge )
-               : m_faceInstances( faceInstances ), m_edge( &edge ){
-       }
-       EdgeInstance& operator=( const EdgeInstance& other ){
-               m_edge = other.m_edge;
-               return *this;
-       }
+EdgeInstance( FaceInstances& faceInstances, SelectableEdge& edge )
+       : m_faceInstances( faceInstances ), m_edge( &edge ){
+}
+EdgeInstance& operator=( const EdgeInstance& other ){
+       m_edge = other.m_edge;
+       return *this;
+}
 
-       void setSelected( bool select ){
-               select_edge( select );
-       }
+void setSelected( bool select ){
+       select_edge( select );
+}
 
-       bool isSelected() const {
-               return selected_edge();
-       }
+bool isSelected() const {
+       return selected_edge();
+}
 
 
-       void testSelect( Selector& selector, SelectionTest& test ){
-               SelectionIntersection best;
-               m_edge->testSelect( test, best );
-               if ( best.valid() ) {
-                       Selector_add( selector, *this, best );
-               }
+void testSelect( Selector& selector, SelectionTest& test ){
+       SelectionIntersection best;
+       m_edge->testSelect( test, best );
+       if ( best.valid() ) {
+               Selector_add( selector, *this, best );
        }
+}
 };
 
 class VertexInstance : public Selectable
 {
-       FaceInstances& m_faceInstances;
-       SelectableVertex* m_vertex;
+FaceInstances& m_faceInstances;
+SelectableVertex* m_vertex;
 
-       void select_vertex( bool select ){
-               FaceVertexId faceVertex = m_vertex->m_faceVertex;
-               do
-               {
-                       m_faceInstances[faceVertex.getFace()].select_vertex( faceVertex.getVertex(), select );
-                       faceVertex = next_vertex( m_vertex->m_faces, faceVertex );
-               }
-               while ( faceVertex.getFace() != m_vertex->m_faceVertex.getFace() );
+void select_vertex( bool select ){
+       FaceVertexId faceVertex = m_vertex->m_faceVertex;
+       do
+       {
+               m_faceInstances[faceVertex.getFace()].select_vertex( faceVertex.getVertex(), select );
+               faceVertex = next_vertex( m_vertex->m_faces, faceVertex );
        }
+       while ( faceVertex.getFace() != m_vertex->m_faceVertex.getFace() );
+}
 
-       bool selected_vertex() const {
-               FaceVertexId faceVertex = m_vertex->m_faceVertex;
-               do
-               {
-                       if ( !m_faceInstances[faceVertex.getFace()].selected_vertex( faceVertex.getVertex() ) ) {
-                               return false;
-                       }
-                       faceVertex = next_vertex( m_vertex->m_faces, faceVertex );
+bool selected_vertex() const {
+       FaceVertexId faceVertex = m_vertex->m_faceVertex;
+       do
+       {
+               if ( !m_faceInstances[faceVertex.getFace()].selected_vertex( faceVertex.getVertex() ) ) {
+                       return false;
                }
-               while ( faceVertex.getFace() != m_vertex->m_faceVertex.getFace() );
-               return true;
+               faceVertex = next_vertex( m_vertex->m_faces, faceVertex );
        }
+       while ( faceVertex.getFace() != m_vertex->m_faceVertex.getFace() );
+       return true;
+}
 
-       public:
-       VertexInstance( FaceInstances& faceInstances, SelectableVertex& vertex )
-               : m_faceInstances( faceInstances ), m_vertex( &vertex ){
-       }
-       VertexInstance& operator=( const VertexInstance& other ){
-               m_vertex = other.m_vertex;
-               return *this;
-       }
+public:
+VertexInstance( FaceInstances& faceInstances, SelectableVertex& vertex )
+       : m_faceInstances( faceInstances ), m_vertex( &vertex ){
+}
+VertexInstance& operator=( const VertexInstance& other ){
+       m_vertex = other.m_vertex;
+       return *this;
+}
+
+void setSelected( bool select ){
+       select_vertex( select );
+}
+
+bool isSelected() const {
+       return selected_vertex();
+}
 
-       void setSelected( bool select ){
-               select_vertex( select );
+void testSelect( Selector& selector, SelectionTest& test ){
+       SelectionIntersection best;
+       m_vertex->testSelect( test, best );
+       if ( best.valid() ) {
+               Selector_add( selector, *this, best );
        }
+}
 
-       bool isSelected() const {
-               return selected_vertex();
+void selectVerticesOnPlanes( SelectionTest& test ){
+       Line line( test.getNear(), test.getFar() );
+       FaceVertexId faceVertex = m_vertex->m_faceVertex;
+       do
+       {
+               if( m_faceInstances[faceVertex.getFace()].trySelectPlane( line ) ){
+                       //m_faceInstances[faceVertex.getFace()].select_vertex( faceVertex.getVertex(), true );
+                       setSelected( true );
+               }
+               faceVertex = next_vertex( m_vertex->m_faces, faceVertex );
        }
+       while ( faceVertex.getFace() != m_vertex->m_faceVertex.getFace() );
+}
 
-       void testSelect( Selector& selector, SelectionTest& test ){
-               SelectionIntersection best;
-               m_vertex->testSelect( test, best );
-               if ( best.valid() ) {
-                       Selector_add( selector, *this, best );
+void selectVerticesOnTestedFaces( SelectionTest& test ){
+       FaceVertexId faceVertex = m_vertex->m_faceVertex;
+       do
+       {
+               BoolSelector selector;
+               m_faceInstances[faceVertex.getFace()].testSelect( selector, test );
+               if( selector.isSelected() ){
+                       setSelected( true );
                }
+               faceVertex = next_vertex( m_vertex->m_faces, faceVertex );
        }
+       while ( faceVertex.getFace() != m_vertex->m_faceVertex.getFace() );
+}
 };
 
 class BrushInstanceVisitor
 {
 public:
-       virtual void visit( FaceInstance& face ) const = 0;
+virtual void visit( FaceInstance& face ) const = 0;
 };
 
 class BrushInstance :
@@ -3106,498 +3179,546 @@ class BrushInstance :
        public PlaneSelectable,
        public LightCullable
 {
-       class TypeCasts
-       {
-               InstanceTypeCastTable m_casts;
-       public:
-               TypeCasts(){
-                       InstanceStaticCast<BrushInstance, Selectable>::install( m_casts );
-                       InstanceContainedCast<BrushInstance, Bounded>::install( m_casts );
-                       InstanceContainedCast<BrushInstance, Cullable>::install( m_casts );
-                       InstanceStaticCast<BrushInstance, Renderable>::install( m_casts );
-                       InstanceStaticCast<BrushInstance, SelectionTestable>::install( m_casts );
-                       InstanceStaticCast<BrushInstance, ComponentSelectionTestable>::install( m_casts );
-                       InstanceStaticCast<BrushInstance, ComponentEditable>::install( m_casts );
-                       InstanceStaticCast<BrushInstance, ComponentSnappable>::install( m_casts );
-                       InstanceStaticCast<BrushInstance, PlaneSelectable>::install( m_casts );
-                       InstanceIdentityCast<BrushInstance>::install( m_casts );
-                       InstanceContainedCast<BrushInstance, Transformable>::install( m_casts );
-               }
-
-               InstanceTypeCastTable& get(){
-                       return m_casts;
-               }
-       };
+class TypeCasts
+{
+InstanceTypeCastTable m_casts;
+public:
+TypeCasts(){
+       InstanceStaticCast<BrushInstance, Selectable>::install( m_casts );
+       InstanceContainedCast<BrushInstance, Bounded>::install( m_casts );
+       InstanceContainedCast<BrushInstance, Cullable>::install( m_casts );
+       InstanceStaticCast<BrushInstance, Renderable>::install( m_casts );
+       InstanceStaticCast<BrushInstance, SelectionTestable>::install( m_casts );
+       InstanceStaticCast<BrushInstance, ComponentSelectionTestable>::install( m_casts );
+       InstanceStaticCast<BrushInstance, ComponentEditable>::install( m_casts );
+       InstanceStaticCast<BrushInstance, ComponentSnappable>::install( m_casts );
+       InstanceStaticCast<BrushInstance, PlaneSelectable>::install( m_casts );
+       InstanceIdentityCast<BrushInstance>::install( m_casts );
+       InstanceContainedCast<BrushInstance, Transformable>::install( m_casts );
+}
+
+InstanceTypeCastTable& get(){
+       return m_casts;
+}
+};
 
 
-       Brush& m_brush;
+Brush& m_brush;
 
-       FaceInstances m_faceInstances;
+FaceInstances m_faceInstances;
 
-       typedef std::vector<EdgeInstance> EdgeInstances;
-       EdgeInstances m_edgeInstances;
-       typedef std::vector<VertexInstance> VertexInstances;
-       VertexInstances m_vertexInstances;
+typedef std::vector<EdgeInstance> EdgeInstances;
+EdgeInstances m_edgeInstances;
+typedef std::vector<VertexInstance> VertexInstances;
+VertexInstances m_vertexInstances;
 
-       ObservedSelectable m_selectable;
+ObservedSelectable m_selectable;
 
-       mutable RenderableWireframe m_render_wireframe;
-       mutable RenderablePointVector m_render_selected;
-       mutable AABB m_aabb_component;
-       mutable Array<PointVertex> m_faceCentroidPointsCulled;
-       RenderablePointArray m_render_faces_wireframe;
-       mutable bool m_viewChanged;   // requires re-evaluation of view-dependent cached data
+mutable RenderableWireframe m_render_wireframe;
+mutable RenderablePointVector m_render_selected;
+mutable AABB m_aabb_component;
+mutable Array<PointVertex> m_faceCentroidPointsCulled;
+RenderablePointArray m_render_faces_wireframe;
+mutable bool m_viewChanged;   // requires re-evaluation of view-dependent cached data
 
-       BrushClipPlane m_clipPlane;
+BrushClipPlane m_clipPlane;
 
-       static Shader* m_state_selpoint;
+static Shader* m_state_selpoint;
 
-       const LightList* m_lightList;
+const LightList* m_lightList;
 
-       TransformModifier m_transform;
+TransformModifier m_transform;
 
-       BrushInstance( const BrushInstance& other ); // NOT COPYABLE
-       BrushInstance& operator=( const BrushInstance& other ); // NOT ASSIGNABLE
+BrushInstance( const BrushInstance& other ); // NOT COPYABLE
+BrushInstance& operator=( const BrushInstance& other ); // NOT ASSIGNABLE
 
 public:
-       static Counter* m_counter;
+static Counter* m_counter;
 
-       typedef LazyStatic<TypeCasts> StaticTypeCasts;
+typedef LazyStatic<TypeCasts> StaticTypeCasts;
 
-       void lightsChanged(){
-               m_lightList->lightsChanged();
-       }
+void lightsChanged(){
+       m_lightList->lightsChanged();
+}
 
-       typedef MemberCaller<BrushInstance, void(), &BrushInstance::lightsChanged> LightsChangedCaller;
+typedef MemberCaller<BrushInstance, void(), &BrushInstance::lightsChanged> LightsChangedCaller;
 
-       STRING_CONSTANT( Name, "BrushInstance" );
+STRING_CONSTANT( Name, "BrushInstance" );
 
-       BrushInstance( const scene::Path& path, scene::Instance* parent, Brush& brush ) :
-               Instance( path, parent, this, StaticTypeCasts::instance().get() ),
-               m_brush( brush ),
-               m_selectable( SelectedChangedCaller( *this ) ),
-               m_render_selected( GL_POINTS ),
-               m_render_faces_wireframe( m_faceCentroidPointsCulled, GL_POINTS ),
-               m_viewChanged( false ),
-               m_transform( Brush::TransformChangedCaller( m_brush ), ApplyTransformCaller( *this ) ){
-               m_brush.instanceAttach( Instance::path() );
-               m_brush.attach( *this );
-               m_counter->increment();
+BrushInstance( const scene::Path& path, scene::Instance* parent, Brush& brush ) :
+       Instance( path, parent, this, StaticTypeCasts::instance().get() ),
+       m_brush( brush ),
+       m_selectable( SelectedChangedCaller( *this ) ),
+       m_render_selected( GL_POINTS ),
+       m_render_faces_wireframe( m_faceCentroidPointsCulled, GL_POINTS ),
+       m_viewChanged( false ),
+       m_transform( Brush::TransformChangedCaller( m_brush ), ApplyTransformCaller( *this ) ){
+       m_brush.instanceAttach( Instance::path() );
+       m_brush.attach( *this );
+       m_counter->increment();
 
-               m_lightList = &GlobalShaderCache().attach( *this );
-               m_brush.m_lightsChanged = LightsChangedCaller( *this ); ///\todo Make this work with instancing.
+       m_lightList = &GlobalShaderCache().attach( *this );
+       m_brush.m_lightsChanged = LightsChangedCaller( *this ); ///\todo Make this work with instancing.
 
-               Instance::setTransformChangedCallback( LightsChangedCaller( *this ) );
-       }
+       Instance::setTransformChangedCallback( LightsChangedCaller( *this ) );
+}
 
-       ~BrushInstance(){
-               Instance::setTransformChangedCallback( Callback<void()>() );
+~BrushInstance(){
+       Instance::setTransformChangedCallback( Callback<void()>() );
 
-               m_brush.m_lightsChanged = Callback<void()>();
-               GlobalShaderCache().detach( *this );
+       m_brush.m_lightsChanged = Callback<void()>();
+       GlobalShaderCache().detach( *this );
 
-               m_counter->decrement();
-               m_brush.detach( *this );
-               m_brush.instanceDetach( Instance::path() );
-       }
+       m_counter->decrement();
+       m_brush.detach( *this );
+       m_brush.instanceDetach( Instance::path() );
+}
 
-       Brush& getBrush(){
-               return m_brush;
-       }
-       const Brush& getBrush() const {
-               return m_brush;
-       }
+Brush& getBrush(){
+       return m_brush;
+}
+const Brush& getBrush() const {
+       return m_brush;
+}
 
-       Bounded& get( NullType<Bounded>){
-               return m_brush;
-       }
+Bounded& get( NullType<Bounded>){
+       return m_brush;
+}
 
-       Cullable& get( NullType<Cullable>){
-               return m_brush;
-       }
+Cullable& get( NullType<Cullable>){
+       return m_brush;
+}
 
-       Transformable& get( NullType<Transformable>){
-               return m_transform;
-       }
+Transformable& get( NullType<Transformable>){
+       return m_transform;
+}
 
-       void selectedChanged( const Selectable& selectable ){
-               GlobalSelectionSystem().getObserver ( SelectionSystem::ePrimitive )( selectable );
-               GlobalSelectionSystem().onSelectedChanged( *this, selectable );
+void selectedChanged( const Selectable& selectable ){
+       GlobalSelectionSystem().getObserver ( SelectionSystem::ePrimitive )( selectable );
+       GlobalSelectionSystem().onSelectedChanged( *this, selectable );
 
-               Instance::selectedChanged();
-       }
-       typedef MemberCaller<BrushInstance, void(const Selectable&), &BrushInstance::selectedChanged> SelectedChangedCaller;
+       Instance::selectedChanged();
+}
+typedef MemberCaller<BrushInstance, void(const Selectable&), &BrushInstance::selectedChanged> SelectedChangedCaller;
 
-       void selectedChangedComponent( const Selectable& selectable ){
-               GlobalSelectionSystem().getObserver ( SelectionSystem::eComponent )( selectable );
-               GlobalSelectionSystem().onComponentSelection( *this, selectable );
-       }
-       typedef MemberCaller<BrushInstance, void(const Selectable&), &BrushInstance::selectedChangedComponent> SelectedChangedComponentCaller;
+void selectedChangedComponent( const Selectable& selectable ){
+       GlobalSelectionSystem().getObserver ( SelectionSystem::eComponent )( selectable );
+       GlobalSelectionSystem().onComponentSelection( *this, selectable );
+}
+typedef MemberCaller<BrushInstance, void(const Selectable&), &BrushInstance::selectedChangedComponent> SelectedChangedComponentCaller;
 
-       const BrushInstanceVisitor& forEachFaceInstance( const BrushInstanceVisitor& visitor ){
-               for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
-               {
-                       visitor.visit( *i );
-               }
-               return visitor;
+const BrushInstanceVisitor& forEachFaceInstance( const BrushInstanceVisitor& visitor ){
+       for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
+       {
+               visitor.visit( *i );
        }
+       return visitor;
+}
 
-       static void constructStatic(){
-               m_state_selpoint = GlobalShaderCache().capture( "$SELPOINT" );
-       }
+static void constructStatic(){
+       m_state_selpoint = GlobalShaderCache().capture( "$SELPOINT" );
+}
 
-       static void destroyStatic(){
-               GlobalShaderCache().release( "$SELPOINT" );
-       }
+static void destroyStatic(){
+       GlobalShaderCache().release( "$SELPOINT" );
+}
 
-       void clear(){
-               m_faceInstances.clear();
-       }
+void clear(){
+       m_faceInstances.clear();
+}
 
-       void reserve( std::size_t size ){
-               m_faceInstances.reserve( size );
-       }
+void reserve( std::size_t size ){
+       m_faceInstances.reserve( size );
+}
 
-       void push_back( Face& face ){
-               m_faceInstances.push_back( FaceInstance( face, SelectedChangedComponentCaller( *this ) ) );
-       }
+void push_back( Face& face ){
+       m_faceInstances.push_back( FaceInstance( face, SelectedChangedComponentCaller( *this ) ) );
+}
 
-       void pop_back(){
-               ASSERT_MESSAGE( !m_faceInstances.empty(), "erasing invalid element" );
-               m_faceInstances.pop_back();
-       }
+void pop_back(){
+       ASSERT_MESSAGE( !m_faceInstances.empty(), "erasing invalid element" );
+       m_faceInstances.pop_back();
+}
 
-       void erase( std::size_t index ){
-               ASSERT_MESSAGE( index < m_faceInstances.size(), "erasing invalid element" );
-               m_faceInstances.erase( m_faceInstances.begin() + index );
-       }
+void erase( std::size_t index ){
+       ASSERT_MESSAGE( index < m_faceInstances.size(), "erasing invalid element" );
+       m_faceInstances.erase( m_faceInstances.begin() + index );
+}
 
-       void connectivityChanged(){
-               for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
-               {
-                       ( *i ).connectivityChanged();
-               }
+void connectivityChanged(){
+       for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
+       {
+               ( *i ).connectivityChanged();
        }
+}
 
-       void edge_clear(){
-               m_edgeInstances.clear();
-       }
+void edge_clear(){
+       m_edgeInstances.clear();
+}
 
-       void edge_push_back( SelectableEdge& edge ){
-               m_edgeInstances.push_back( EdgeInstance( m_faceInstances, edge ) );
-       }
+void edge_push_back( SelectableEdge& edge ){
+       m_edgeInstances.push_back( EdgeInstance( m_faceInstances, edge ) );
+}
 
-       void vertex_clear(){
-               m_vertexInstances.clear();
-       }
+void vertex_clear(){
+       m_vertexInstances.clear();
+}
 
-       void vertex_push_back( SelectableVertex& vertex ){
-               m_vertexInstances.push_back( VertexInstance( m_faceInstances, vertex ) );
-       }
+void vertex_push_back( SelectableVertex& vertex ){
+       m_vertexInstances.push_back( VertexInstance( m_faceInstances, vertex ) );
+}
 
-       void DEBUG_verify() const {
-               ASSERT_MESSAGE( m_faceInstances.size() == m_brush.DEBUG_size(), "FATAL: mismatch" );
-       }
+void DEBUG_verify() const {
+       ASSERT_MESSAGE( m_faceInstances.size() == m_brush.DEBUG_size(), "FATAL: mismatch" );
+}
 
-       bool isSelected() const {
-               return m_selectable.isSelected();
-       }
+bool isSelected() const {
+       return m_selectable.isSelected();
+}
 
-       void setSelected( bool select ){
-               m_selectable.setSelected( select );
+void setSelected( bool select ){
+       m_selectable.setSelected( select );
+       if ( !select && parent() ){
+               Selectable* sel_parent = Instance_getSelectable( *parent() );
+               if ( sel_parent && sel_parent->isSelected() )
+                       sel_parent->setSelected( false );
        }
+}
 
-       void update_selected() const {
-               m_render_selected.clear();
-               for ( FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
-               {
-                       if ( ( *i ).getFace().contributes() ) {
-                               ( *i ).iterate_selected( m_render_selected );
-                       }
+void update_selected() const {
+       m_render_selected.clear();
+       for ( FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
+       {
+               if ( ( *i ).getFace().contributes() ) {
+                       ( *i ).iterate_selected( m_render_selected );
                }
        }
+}
 
-       void evaluateViewDependent( const VolumeTest& volume, const Matrix4& localToWorld ) const {
-               if ( m_viewChanged ) {
-                       m_viewChanged = false;
+void evaluateViewDependent( const VolumeTest& volume, const Matrix4& localToWorld ) const {
+       if ( m_viewChanged ) {
+               m_viewChanged = false;
 
-                       bool faces_visible[c_brush_maxFaces];
+               bool faces_visible[c_brush_maxFaces];
+               {
+                       bool* j = faces_visible;
+                       for ( FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i, ++j )
                        {
-                               bool* j = faces_visible;
-                               for ( FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i, ++j )
-                               {
-                                       *j = ( *i ).intersectVolume( volume, localToWorld );
-                               }
+                               *j = ( *i ).intersectVolume( volume, localToWorld );
                        }
-
-                       m_brush.update_wireframe( m_render_wireframe, faces_visible );
-                       m_brush.update_faces_wireframe( m_faceCentroidPointsCulled, faces_visible );
                }
+
+               m_brush.update_wireframe( m_render_wireframe, faces_visible );
+               m_brush.update_faces_wireframe( m_faceCentroidPointsCulled, faces_visible );
        }
+}
 
-       void renderComponentsSelected( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld ) const {
-               m_brush.evaluateBRep();
+void renderComponentsSelected( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld ) const {
+       m_brush.evaluateBRep();
 
-               update_selected();
-               if ( !m_render_selected.empty() ) {
-                       renderer.Highlight( Renderer::ePrimitive, false );
-                       renderer.SetState( m_state_selpoint, Renderer::eWireframeOnly );
-                       renderer.SetState( m_state_selpoint, Renderer::eFullMaterials );
-                       renderer.addRenderable( m_render_selected, localToWorld );
-               }
+       update_selected();
+       if ( !m_render_selected.empty() ) {
+               renderer.Highlight( Renderer::ePrimitive, false );
+               renderer.SetState( m_state_selpoint, Renderer::eWireframeOnly );
+               renderer.SetState( m_state_selpoint, Renderer::eFullMaterials );
+               renderer.addRenderable( m_render_selected, localToWorld );
        }
+}
 
-       void renderComponents( Renderer& renderer, const VolumeTest& volume ) const {
-               m_brush.evaluateBRep();
+void renderComponents( Renderer& renderer, const VolumeTest& volume ) const {
+       m_brush.evaluateBRep();
 
-               const Matrix4& localToWorld = Instance::localToWorld();
+       const Matrix4& localToWorld = Instance::localToWorld();
 
-               renderer.SetState( m_brush.m_state_point, Renderer::eWireframeOnly );
-               renderer.SetState( m_brush.m_state_point, Renderer::eFullMaterials );
+       renderer.SetState( m_brush.m_state_point, Renderer::eWireframeOnly );
+       renderer.SetState( m_brush.m_state_point, Renderer::eFullMaterials );
 
-               if ( volume.fill() && GlobalSelectionSystem().ComponentMode() == SelectionSystem::eFace ) {
-                       evaluateViewDependent( volume, localToWorld );
-                       renderer.addRenderable( m_render_faces_wireframe, localToWorld );
-               }
-               else
-               {
-                       m_brush.renderComponents( GlobalSelectionSystem().ComponentMode(), renderer, volume, localToWorld );
-               }
+       if ( volume.fill() && GlobalSelectionSystem().ComponentMode() == SelectionSystem::eFace ) {
+               evaluateViewDependent( volume, localToWorld );
+               renderer.addRenderable( m_render_faces_wireframe, localToWorld );
+       }
+       else
+       {
+               m_brush.renderComponents( GlobalSelectionSystem().ComponentMode(), renderer, volume, localToWorld );
        }
+}
 
-       void renderClipPlane( Renderer& renderer, const VolumeTest& volume ) const {
-               if ( GlobalSelectionSystem().ManipulatorMode() == SelectionSystem::eClip && isSelected() ) {
-                       m_clipPlane.render( renderer, volume, localToWorld() );
-               }
+void renderClipPlane( Renderer& renderer, const VolumeTest& volume ) const {
+       if ( GlobalSelectionSystem().ManipulatorMode() == SelectionSystem::eClip && isSelected() ) {
+               m_clipPlane.render( renderer, volume, localToWorld() );
        }
+}
 
-       void renderCommon( Renderer& renderer, const VolumeTest& volume ) const {
-               bool componentMode = GlobalSelectionSystem().Mode() == SelectionSystem::eComponent;
+void renderCommon( Renderer& renderer, const VolumeTest& volume ) const {
+       bool componentMode = GlobalSelectionSystem().Mode() == SelectionSystem::eComponent;
 
-               if ( componentMode && isSelected() ) {
-                       renderComponents( renderer, volume );
-               }
+       if ( componentMode && isSelected() ) {
+               renderComponents( renderer, volume );
+       }
 
-               if ( parentSelected() ) {
-                       if ( !componentMode ) {
-                               renderer.Highlight( Renderer::eFace );
-                       }
-                       renderer.Highlight( Renderer::ePrimitive );
+       if ( parentSelected() ) {
+               if ( !componentMode ) {
+                       renderer.Highlight( Renderer::eFace );
                }
+               renderer.Highlight( Renderer::ePrimitive );
        }
+}
 
-       void renderSolid( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld ) const {
-               //renderCommon(renderer, volume);
-
-               m_lightList->evaluateLights();
+void renderSolid( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld ) const {
+       //renderCommon(renderer, volume);
 
-               for ( FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
-               {
-                       renderer.setLights( ( *i ).m_lights );
-                       ( *i ).render( renderer, volume, localToWorld );
-               }
+       m_lightList->evaluateLights();
 
-               renderComponentsSelected( renderer, volume, localToWorld );
+       for ( FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
+       {
+               renderer.setLights( ( *i ).m_lights );
+               ( *i ).render( renderer, volume, localToWorld );
        }
 
-       void renderWireframe( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld ) const {
-               //renderCommon(renderer, volume);
+       renderComponentsSelected( renderer, volume, localToWorld );
+}
 
-               evaluateViewDependent( volume, localToWorld );
+void renderWireframe( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld ) const {
+       //renderCommon(renderer, volume);
 
-               if ( m_render_wireframe.m_size != 0 ) {
-                       renderer.addRenderable( m_render_wireframe, localToWorld );
-               }
+       evaluateViewDependent( volume, localToWorld );
 
-               renderComponentsSelected( renderer, volume, localToWorld );
+       if ( m_render_wireframe.m_size != 0 ) {
+               renderer.addRenderable( m_render_wireframe, localToWorld );
        }
 
-       void renderSolid( Renderer& renderer, const VolumeTest& volume ) const {
-               m_brush.evaluateBRep();
+       renderComponentsSelected( renderer, volume, localToWorld );
+}
 
-               renderClipPlane( renderer, volume );
+void renderSolid( Renderer& renderer, const VolumeTest& volume ) const {
+       m_brush.evaluateBRep();
 
-               renderSolid( renderer, volume, localToWorld() );
-       }
+       renderClipPlane( renderer, volume );
 
-       void renderWireframe( Renderer& renderer, const VolumeTest& volume ) const {
-               m_brush.evaluateBRep();
+       renderSolid( renderer, volume, localToWorld() );
+}
 
-               renderClipPlane( renderer, volume );
+void renderWireframe( Renderer& renderer, const VolumeTest& volume ) const {
+       m_brush.evaluateBRep();
 
-               renderWireframe( renderer, volume, localToWorld() );
-       }
+       renderClipPlane( renderer, volume );
 
-       void viewChanged() const {
-               m_viewChanged = true;
-       }
+       renderWireframe( renderer, volume, localToWorld() );
+}
 
-       void testSelect( Selector& selector, SelectionTest& test ){
-               test.BeginMesh( localToWorld() );
+void viewChanged() const {
+       m_viewChanged = true;
+}
 
-               SelectionIntersection best;
-               for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
-               {
-                       ( *i ).testSelect( test, best );
-               }
-               if ( best.valid() ) {
-                       selector.addIntersection( best );
-               }
+void testSelect( Selector& selector, SelectionTest& test ){
+       test.BeginMesh( localToWorld() );
+
+       SelectionIntersection best;
+       for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
+       {
+               ( *i ).testSelect( test, best );
        }
+       if ( best.valid() ) {
+               selector.addIntersection( best );
+       }
+}
 
-       bool isSelectedComponents() const {
-               for ( FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
-               {
-                       if ( ( *i ).selectedComponents() ) {
-                               return true;
-                       }
+bool isSelectedComponents() const {
+       for ( FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
+       {
+               if ( ( *i ).selectedComponents() ) {
+                       return true;
                }
-               return false;
        }
+       return false;
+}
 
-       void setSelectedComponents( bool select, SelectionSystem::EComponentMode mode ){
-               for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
-               {
-                       ( *i ).setSelected( mode, select );
-               }
+void setSelectedComponents( bool select, SelectionSystem::EComponentMode mode ){
+       for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
+       {
+               ( *i ).setSelected( mode, select );
        }
+}
 
-       void testSelectComponents( Selector& selector, SelectionTest& test, SelectionSystem::EComponentMode mode ){
-               test.BeginMesh( localToWorld() );
+void testSelectComponents( Selector& selector, SelectionTest& test, SelectionSystem::EComponentMode mode ){
+       test.BeginMesh( localToWorld() );
 
-               switch ( mode )
-               {
-               case SelectionSystem::eVertex:
+       switch ( mode )
+       {
+       case SelectionSystem::eVertex:
+       {
+               for ( VertexInstances::iterator i = m_vertexInstances.begin(); i != m_vertexInstances.end(); ++i )
                {
-                       for ( VertexInstances::iterator i = m_vertexInstances.begin(); i != m_vertexInstances.end(); ++i )
-                       {
-                               ( *i ).testSelect( selector, test );
-                       }
+                       ( *i ).testSelect( selector, test );
                }
-               break;
-               case SelectionSystem::eEdge:
+       }
+       break;
+       case SelectionSystem::eEdge:
+       {
+               for ( EdgeInstances::iterator i = m_edgeInstances.begin(); i != m_edgeInstances.end(); ++i )
                {
-                       for ( EdgeInstances::iterator i = m_edgeInstances.begin(); i != m_edgeInstances.end(); ++i )
+                       ( *i ).testSelect( selector, test );
+               }
+       }
+       break;
+       case SelectionSystem::eFace:
+       {
+               if ( test.getVolume().fill() ) {
+                       for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
                        {
                                ( *i ).testSelect( selector, test );
                        }
                }
-               break;
-               case SelectionSystem::eFace:
+               else
                {
-                       if ( test.getVolume().fill() ) {
-                               for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
-                               {
-                                       ( *i ).testSelect( selector, test );
-                               }
-                       }
-                       else
+                       for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
                        {
-                               for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
-                               {
-                                       ( *i ).testSelect_centroid( selector, test );
-                               }
+                               ( *i ).testSelect_centroid( selector, test );
                        }
                }
+       }
+       break;
+       default:
                break;
-               default:
-                       break;
-               }
        }
+}
 
-       void selectPlanes( Selector& selector, SelectionTest& test, const PlaneCallback& selectedPlaneCallback ){
-               test.BeginMesh( localToWorld() );
-
-               PlanePointer brushPlanes[c_brush_maxFaces];
-               PlanesIterator j = brushPlanes;
-
-               for ( Brush::const_iterator i = m_brush.begin(); i != m_brush.end(); ++i )
+void invertComponentSelection( SelectionSystem::EComponentMode mode ){
+       switch ( mode )
+       {
+       case SelectionSystem::eVertex:
+       {
+               for ( VertexInstances::iterator i = m_vertexInstances.begin(); i != m_vertexInstances.end(); ++i )
                {
-                       *j++ = &( *i )->plane3();
+                       ( *i ).setSelected( !( *i ).isSelected() );
                }
-
-               for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
+       }
+       break;
+       case SelectionSystem::eEdge:
+       {
+               for ( EdgeInstances::iterator i = m_edgeInstances.begin(); i != m_edgeInstances.end(); ++i )
                {
-                       ( *i ).selectPlane( selector, Line( test.getNear(), test.getFar() ), brushPlanes, j, selectedPlaneCallback );
+                       ( *i ).setSelected( !( *i ).isSelected() );
                }
        }
-
-       void selectReversedPlanes( Selector& selector, const SelectedPlanes& selectedPlanes ){
+       break;
+       case SelectionSystem::eFace:
+       {
                for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
                {
-                       ( *i ).selectReversedPlane( selector, selectedPlanes );
+                       if( !( *i ).getFace().isFiltered() )
+                               ( *i ).setSelected( mode, !( *i ).isSelected() );
                }
        }
+       break;
+       default:
+               break;
+       }
+}
 
+void selectPlanes( Selector& selector, SelectionTest& test, const PlaneCallback& selectedPlaneCallback ){
+       test.BeginMesh( localToWorld() );
 
-       void transformComponents( const Matrix4& matrix ){
-               for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
-               {
-                       ( *i ).transformComponents( matrix );
-               }
+       for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
+       {
+               ( *i ).selectPlane( selector, Line( test.getNear(), test.getFar() ), selectedPlaneCallback );
+       }
+}
+
+void selectReversedPlanes( Selector& selector, const SelectedPlanes& selectedPlanes ){
+       for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
+       {
+               ( *i ).selectReversedPlane( selector, selectedPlanes );
        }
+}
 
-       const AABB& getSelectedComponentsBounds() const {
-               m_aabb_component = AABB();
 
-               for ( FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
-               {
-                       ( *i ).iterate_selected( m_aabb_component );
-               }
+void selectVerticesOnPlanes( SelectionTest& test ){
+       test.BeginMesh( localToWorld() );
 
-               return m_aabb_component;
+       for ( VertexInstances::iterator i = m_vertexInstances.begin(); i != m_vertexInstances.end(); ++i ){
+               ( *i ).selectVerticesOnPlanes( test );
        }
+}
 
-       void snapComponents( float snap ){
-               for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
-               {
-                       ( *i ).snapComponents( snap );
-               }
+
+void selectVerticesOnTestedFaces( SelectionTest& test ){
+       test.BeginMesh( localToWorld() );
+
+       for ( VertexInstances::iterator i = m_vertexInstances.begin(); i != m_vertexInstances.end(); ++i ){
+               ( *i ).selectVerticesOnTestedFaces( test );
        }
+}
 
-       void evaluateTransform(){
-               Matrix4 matrix( m_transform.calculateTransform() );
-               //globalOutputStream() << "matrix: " << matrix << "\n";
 
-               if ( m_transform.getType() == TRANSFORM_PRIMITIVE ) {
-                       m_brush.transform( matrix );
-               }
-               else
-               {
-                       transformComponents( matrix );
-               }
+void transformComponents( const Matrix4& matrix ){
+       for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
+       {
+               ( *i ).transformComponents( matrix );
        }
+}
 
-       void applyTransform(){
-               m_brush.revertTransform();
-               evaluateTransform();
-               m_brush.freezeTransform();
+const AABB& getSelectedComponentsBounds() const {
+       m_aabb_component = AABB();
+
+       for ( FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
+       {
+               ( *i ).iterate_selected( m_aabb_component );
        }
 
-       typedef MemberCaller<BrushInstance, void(), &BrushInstance::applyTransform> ApplyTransformCaller;
+       return m_aabb_component;
+}
 
-       void setClipPlane( const Plane3& plane ){
-               m_clipPlane.setPlane( m_brush, plane );
+void snapComponents( float snap ){
+       for ( FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
+       {
+               ( *i ).snapComponents( snap );
        }
+}
+
+void evaluateTransform(){
+       Matrix4 matrix( m_transform.calculateTransform() );
+       //globalOutputStream() << "matrix: " << matrix << "\n";
 
-       bool testLight( const RendererLight& light ) const {
-               return light.testAABB( worldAABB() );
+       if ( m_transform.getType() == TRANSFORM_PRIMITIVE ) {
+               m_brush.transform( matrix );
        }
+       else
+       {
+               transformComponents( matrix );
+       }
+}
 
-       void insertLight( const RendererLight& light ){
-               const Matrix4& localToWorld = Instance::localToWorld();
-               for ( FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
-               {
-                       Face_addLight( *i, localToWorld, light );
-               }
+void applyTransform(){
+       m_brush.revertTransform();
+       evaluateTransform();
+       m_brush.freezeTransform();
+}
+
+typedef MemberCaller<BrushInstance, void(), &BrushInstance::applyTransform> ApplyTransformCaller;
+
+void setClipPlane( const Plane3& plane ){
+       m_clipPlane.setPlane( m_brush, plane );
+}
+
+bool testLight( const RendererLight& light ) const {
+       return light.testAABB( worldAABB() );
+}
+
+void insertLight( const RendererLight& light ){
+       const Matrix4& localToWorld = Instance::localToWorld();
+       for ( FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
+       {
+               Face_addLight( *i, localToWorld, light );
        }
+}
 
-       void clearLights(){
-               for ( FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
-               {
-                       ( *i ).m_lights.clear();
-               }
+void clearLights(){
+       for ( FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i )
+       {
+               ( *i ).m_lights.clear();
        }
+}
 };
 
 inline BrushInstance* Instance_getBrush( scene::Instance& instance ){
@@ -3608,17 +3729,17 @@ inline BrushInstance* Instance_getBrush( scene::Instance& instance ){
 template<typename Functor>
 class BrushSelectedVisitor : public SelectionSystem::Visitor
 {
-       const Functor& m_functor;
+const Functor& m_functor;
 public:
-       BrushSelectedVisitor( const Functor& functor ) : m_functor( functor ){
-       }
+BrushSelectedVisitor( const Functor& functor ) : m_functor( functor ){
+}
 
-       void visit( scene::Instance& instance ) const {
-               BrushInstance* brush = Instance_getBrush( instance );
-               if ( brush != 0 ) {
-                       m_functor( *brush );
-               }
+void visit( scene::Instance& instance ) const {
+       BrushInstance* brush = Instance_getBrush( instance );
+       if ( brush != 0 ) {
+               m_functor( *brush );
        }
+}
 };
 
 template<typename Functor>
@@ -3630,18 +3751,18 @@ inline const Functor& Scene_forEachSelectedBrush( const Functor& functor ){
 template<typename Functor>
 class BrushVisibleSelectedVisitor : public SelectionSystem::Visitor
 {
-       const Functor& m_functor;
+const Functor& m_functor;
 public:
-       BrushVisibleSelectedVisitor( const Functor& functor ) : m_functor( functor ){
-       }
+BrushVisibleSelectedVisitor( const Functor& functor ) : m_functor( functor ){
+}
 
-       void visit( scene::Instance& instance ) const {
-               BrushInstance* brush = Instance_getBrush( instance );
-               if ( brush != 0
-                        && instance.path().top().get().visible() ) {
-                       m_functor( *brush );
-               }
+void visit( scene::Instance& instance ) const {
+       BrushInstance* brush = Instance_getBrush( instance );
+       if ( brush != 0
+                && instance.path().top().get().visible() ) {
+               m_functor( *brush );
        }
+}
 };
 
 template<typename Functor>
@@ -3652,28 +3773,28 @@ inline const Functor& Scene_forEachVisibleSelectedBrush( const Functor& functor
 
 class BrushForEachFace
 {
-       const BrushInstanceVisitor& m_visitor;
+const BrushInstanceVisitor& m_visitor;
 public:
-       BrushForEachFace( const BrushInstanceVisitor& visitor ) : m_visitor( visitor ){
-       }
+BrushForEachFace( const BrushInstanceVisitor& visitor ) : m_visitor( visitor ){
+}
 
-       void operator()( BrushInstance& brush ) const {
-               brush.forEachFaceInstance( m_visitor );
-       }
+void operator()( BrushInstance& brush ) const {
+       brush.forEachFaceInstance( m_visitor );
+}
 };
 
 template<class Functor>
 class FaceInstanceVisitFace : public BrushInstanceVisitor
 {
-       const Functor& functor;
+const Functor& functor;
 public:
-       FaceInstanceVisitFace( const Functor& functor )
-               : functor( functor ){
-       }
+FaceInstanceVisitFace( const Functor& functor )
+       : functor( functor ){
+}
 
-       void visit( FaceInstance& face ) const {
-               functor( face.getFace() );
-       }
+void visit( FaceInstance& face ) const {
+       functor( face.getFace() );
+}
 };
 
 template<typename Functor>
@@ -3685,15 +3806,15 @@ inline const Functor& Brush_forEachFace( BrushInstance& brush, const Functor& fu
 template<class Functor>
 class FaceVisitAll : public BrushVisitor
 {
-       const Functor& functor;
+const Functor& functor;
 public:
-       FaceVisitAll( const Functor& functor )
-               : functor( functor ){
-       }
+FaceVisitAll( const Functor& functor )
+       : functor( functor ){
+}
 
-       void visit( Face& face ) const {
-               functor( face );
-       }
+void visit( Face& face ) const {
+       functor( face );
+}
 };
 
 template<typename Functor>
@@ -3711,15 +3832,15 @@ inline const Functor& Brush_forEachFace( Brush& brush, const Functor& functor ){
 template<class Functor>
 class FaceInstanceVisitAll : public BrushInstanceVisitor
 {
-       const Functor& functor;
+const Functor& functor;
 public:
-       FaceInstanceVisitAll( const Functor& functor )
-               : functor( functor ){
-       }
+FaceInstanceVisitAll( const Functor& functor )
+       : functor( functor ){
+}
 
-       void visit( FaceInstance& face ) const {
-               functor( face );
-       }
+void visit( FaceInstance& face ) const {
+       functor( face );
+}
 };
 
 template<typename Functor>
@@ -3738,33 +3859,33 @@ template<typename Type, typename Functor>
 class InstanceIfVisible : public Functor
 {
 public:
-       InstanceIfVisible( const Functor& functor ) : Functor( functor ){
-       }
+InstanceIfVisible( const Functor& functor ) : Functor( functor ){
+}
 
-       void operator()( scene::Instance& instance ){
-               if ( instance.path().top().get().visible() ) {
-                       Functor::operator()( instance );
-               }
+void operator()( scene::Instance& instance ){
+       if ( instance.path().top().get().visible() ) {
+               Functor::operator()( instance );
        }
+}
 };
 
 template<typename Functor>
 class BrushVisibleWalker : public scene::Graph::Walker
 {
-       const Functor& m_functor;
+const Functor& m_functor;
 public:
-       BrushVisibleWalker( const Functor& functor ) : m_functor( functor ){
-       }
+BrushVisibleWalker( const Functor& functor ) : m_functor( functor ){
+}
 
-       bool pre( const scene::Path& path, scene::Instance& instance ) const {
-               if ( path.top().get().visible() ) {
-                       BrushInstance* brush = Instance_getBrush( instance );
-                       if ( brush != 0 ) {
-                               m_functor( *brush );
-                       }
+bool pre( const scene::Path& path, scene::Instance& instance ) const {
+       if ( path.top().get().visible() ) {
+               BrushInstance* brush = Instance_getBrush( instance );
+               if ( brush != 0 ) {
+                       m_functor( *brush );
                }
-               return true;
        }
+       return true;
+}
 };
 
 template<typename Functor>
@@ -3801,14 +3922,14 @@ inline const Functor& Scene_ForEachSelectedBrush_ForEachFaceInstance( scene::Gra
 template<typename Functor>
 class FaceVisitorWrapper
 {
-       const Functor& functor;
+const Functor& functor;
 public:
-       FaceVisitorWrapper( const Functor& functor ) : functor( functor ){
-       }
+FaceVisitorWrapper( const Functor& functor ) : functor( functor ){
+}
 
-       void operator()( FaceInstance& faceInstance ) const {
-               functor( faceInstance.getFace() );
-       }
+void operator()( FaceInstance& faceInstance ) const {
+       functor( faceInstance.getFace() );
+}
 };
 
 template<typename Functor>
index 2b11807e548721443ecb88748c62410a40f51d13..c2042d1e6e2dcdb8174aaa5b237141165af0ca5e 100644 (file)
@@ -1109,7 +1109,27 @@ void Texdef_FitTexture( TextureProjection& projection, std::size_t width, std::s
        bounds.extents.z() = 1;
 
        // the bounds of a perfectly fitted texture transform
-       AABB perfect( Vector3( s_repeat * 0.5, t_repeat * 0.5, 0 ), Vector3( s_repeat * 0.5, t_repeat * 0.5, 1 ) );
+       AABB perfect;
+       if( t_repeat == 0 && s_repeat == 0 ){
+               //bad user's input
+               t_repeat = s_repeat = 1;
+               perfect.origin = Vector3( s_repeat * 0.5, t_repeat * 0.5, 0 );
+               perfect.extents = Vector3( s_repeat * 0.5, t_repeat * 0.5, 1 );
+       }
+       if( t_repeat == 0 ){
+               //fit width
+               perfect.origin = Vector3( s_repeat * 0.5, s_repeat * 0.5 * bounds.extents.y() / bounds.extents.x(), 0 );
+               perfect.extents = Vector3( s_repeat * 0.5, s_repeat * 0.5 * bounds.extents.y() / bounds.extents.x(), 1 );
+       }
+       else if( s_repeat == 0 ){
+               //fit height
+               perfect.origin = Vector3( t_repeat * 0.5 * bounds.extents.x() / bounds.extents.y(), t_repeat * 0.5, 0 );
+               perfect.extents = Vector3( t_repeat * 0.5 * bounds.extents.x() / bounds.extents.y(), t_repeat * 0.5, 1 );
+       }
+       else{
+               perfect.origin = Vector3( s_repeat * 0.5, t_repeat * 0.5, 0 );
+               perfect.extents = Vector3( s_repeat * 0.5, t_repeat * 0.5, 1 );
+       }
 
        // the difference between the current texture transform and the perfectly fitted transform
        Matrix4 matrix( matrix4_translation_for_vec3( bounds.origin - perfect.origin ) );
index a3f7351ec43c89fe3b2aa97df670b336ab102a25..426f90834c2950495dbce8ec5de99529f96349c6 100644 (file)
@@ -139,24 +139,29 @@ void Brush_ConstructPrism( Brush& brush, const AABB& bounds, std::size_t sides,
                double sv = sin( i * 3.14159265 * 2 / sides );
                double cv = cos( i * 3.14159265 * 2 / sides );
 
-               planepts[0][( axis + 1 ) % 3] = static_cast<float>( floor( mid[( axis + 1 ) % 3] + radius * cv + 0.5 ) );
-               planepts[0][( axis + 2 ) % 3] = static_cast<float>( floor( mid[( axis + 2 ) % 3] + radius * sv + 0.5 ) );
+//             planepts[0][( axis + 1 ) % 3] = static_cast<float>( floor( mid[( axis + 1 ) % 3] + radius * cv + 0.5 ) );
+//             planepts[0][( axis + 2 ) % 3] = static_cast<float>( floor( mid[( axis + 2 ) % 3] + radius * sv + 0.5 ) );
+               planepts[0][( axis + 1 ) % 3] = static_cast<float>( mid[( axis + 1 ) % 3] + radius * cv );
+               planepts[0][( axis + 2 ) % 3] = static_cast<float>( mid[( axis + 2 ) % 3] + radius * sv );
                planepts[0][axis] = mins[axis];
 
                planepts[1][( axis + 1 ) % 3] = planepts[0][( axis + 1 ) % 3];
                planepts[1][( axis + 2 ) % 3] = planepts[0][( axis + 2 ) % 3];
                planepts[1][axis] = maxs[axis];
 
-               planepts[2][( axis + 1 ) % 3] = static_cast<float>( floor( planepts[0][( axis + 1 ) % 3] - radius * sv + 0.5 ) );
-               planepts[2][( axis + 2 ) % 3] = static_cast<float>( floor( planepts[0][( axis + 2 ) % 3] + radius * cv + 0.5 ) );
+//             planepts[2][( axis + 1 ) % 3] = static_cast<float>( floor( planepts[0][( axis + 1 ) % 3] - radius * sv + 0.5 ) );
+//             planepts[2][( axis + 2 ) % 3] = static_cast<float>( floor( planepts[0][( axis + 2 ) % 3] + radius * cv + 0.5 ) );
+               planepts[2][( axis + 1 ) % 3] = static_cast<float>( planepts[0][( axis + 1 ) % 3] - radius * sv );
+               planepts[2][( axis + 2 ) % 3] = static_cast<float>( planepts[0][( axis + 2 ) % 3] + radius * cv );
                planepts[2][axis] = maxs[axis];
+               //globalOutputStream() << planepts[0] << "   " << planepts[2] << "  #" << i << "   sin " << sv << "  cos " << cv << "\n";
 
                brush.addPlane( planepts[0], planepts[1], planepts[2], shader, projection );
        }
 }
 
 const std::size_t c_brushCone_minSides = 3;
-const std::size_t c_brushCone_maxSides = 32;
+const std::size_t c_brushCone_maxSides = c_brush_maxFaces - 1;
 const char* const c_brushCone_name = "brushCone";
 
 void Brush_ConstructCone( Brush& brush, const AABB& bounds, std::size_t sides, const char* shader, const TextureProjection& projection ){
@@ -190,16 +195,20 @@ void Brush_ConstructCone( Brush& brush, const AABB& bounds, std::size_t sides, c
                double sv = sin( i * 3.14159265 * 2 / sides );
                double cv = cos( i * 3.14159265 * 2 / sides );
 
-               planepts[0][0] = static_cast<float>( floor( mid[0] + radius * cv + 0.5 ) );
-               planepts[0][1] = static_cast<float>( floor( mid[1] + radius * sv + 0.5 ) );
+               planepts[0][0] = static_cast<float>( mid[0] + radius * cv );
+               planepts[0][1] = static_cast<float>( mid[1] + radius * sv );
+//             planepts[0][0] = static_cast<float>( floor( mid[0] + radius * cv + 0.5 ) );
+//             planepts[0][1] = static_cast<float>( floor( mid[1] + radius * sv + 0.5 ) );
                planepts[0][2] = mins[2];
 
                planepts[1][0] = mid[0];
                planepts[1][1] = mid[1];
                planepts[1][2] = maxs[2];
 
-               planepts[2][0] = static_cast<float>( floor( planepts[0][0] - radius * sv + 0.5 ) );
-               planepts[2][1] = static_cast<float>( floor( planepts[0][1] + radius * cv + 0.5 ) );
+               planepts[2][0] = static_cast<float>( planepts[0][0] - radius * sv );
+               planepts[2][1] = static_cast<float>( planepts[0][1] + radius * cv );
+//             planepts[2][0] = static_cast<float>( floor( planepts[0][0] - radius * sv + 0.5 ) );
+//             planepts[2][1] = static_cast<float>( floor( planepts[0][1] + radius * cv + 0.5 ) );
                planepts[2][2] = maxs[2];
 
                brush.addPlane( planepts[0], planepts[1], planepts[2], shader, projection );
@@ -626,6 +635,9 @@ bool pre( const scene::Path& path, scene::Instance& instance ) const {
                        Instance_getSelectable( instance )->setSelected( true );
                }
        }
+       else{
+               return false;
+       }
        return true;
 }
 };
@@ -638,8 +650,8 @@ void Scene_BrushSelectByShader_Component( scene::Graph& graph, const char* name
        Scene_ForEachSelectedBrush_ForEachFaceInstance(graph, [&](FaceInstance &face) {
                printf("checking %s = %s\n", face.getFace().GetShader(), name);
                if (shader_equal(face.getFace().GetShader(), name)) {
-                       face.setSelected(SelectionSystem::eFace, true);
-               }
+               face.setSelected( SelectionSystem::eFace, true );
+       }
        });
 }
 
@@ -649,7 +661,7 @@ void Scene_BrushGetTexdef_Selected( scene::Graph& graph, TextureProjection& proj
                if (!done) {
                        done = true;
                        face.GetTexdef(projection);
-               }
+}
        });
 }
 
@@ -847,12 +859,18 @@ filter_brush_all_faces g_filter_brush_caulk( &g_filter_face_caulk );
 filter_face_shader_prefix g_filter_face_caulk_ja( "textures/system/caulk" );
 filter_brush_all_faces g_filter_brush_caulk_ja( &g_filter_face_caulk_ja );
 
-filter_face_shader_prefix g_filter_face_liquids( "textures/liquids/" );
+filter_face_flags g_filter_face_liquids( QER_LIQUID );
 filter_brush_any_face g_filter_brush_liquids( &g_filter_face_liquids );
 
+filter_face_shader_prefix g_filter_face_liquidsdir( "textures/liquids/" );
+filter_brush_any_face g_filter_brush_liquidsdir( &g_filter_face_liquidsdir );
+
 filter_face_shader g_filter_face_hint( "textures/common/hint" );
 filter_brush_any_face g_filter_brush_hint( &g_filter_face_hint );
 
+filter_face_shader g_filter_face_hintlocal( "textures/common/hintlocal" );
+filter_brush_any_face g_filter_brush_hintlocal( &g_filter_face_hintlocal );
+
 filter_face_shader g_filter_face_hint_q2( "textures/hint" );
 filter_brush_any_face g_filter_brush_hint_q2( &g_filter_face_hint_q2 );
 
@@ -863,7 +881,7 @@ filter_face_shader g_filter_face_subtlehint( "textures/common/subtlehint" );
 filter_brush_any_face g_filter_brush_subtlehint( &g_filter_face_subtlehint );
 
 filter_face_shader g_filter_face_areaportal( "textures/common/areaportal" );
-filter_brush_all_faces g_filter_brush_areaportal( &g_filter_face_areaportal );
+filter_brush_any_face g_filter_brush_areaportal( &g_filter_face_areaportal );
 
 filter_face_shader g_filter_face_visportal( "textures/editor/visportal" );
 filter_brush_any_face g_filter_brush_visportal( &g_filter_face_visportal );
@@ -874,8 +892,8 @@ filter_brush_all_faces g_filter_brush_clusterportal( &g_filter_face_clusterporta
 filter_face_shader g_filter_face_lightgrid( "textures/common/lightgrid" );
 filter_brush_all_faces g_filter_brush_lightgrid( &g_filter_face_lightgrid );
 
-filter_face_flags g_filter_face_translucent( QER_TRANS );
-filter_brush_all_faces g_filter_brush_translucent( &g_filter_face_translucent );
+filter_face_flags g_filter_face_translucent( QER_TRANS | QER_ALPHATEST );
+filter_brush_any_face g_filter_brush_translucent( &g_filter_face_translucent );
 
 filter_face_contents g_filter_face_detail( BRUSH_DETAIL_MASK );
 filter_brush_all_faces g_filter_brush_detail( &g_filter_face_detail );
@@ -896,7 +914,9 @@ void BrushFilters_construct(){
        add_face_filter( g_filter_face_caulk, EXCLUDE_CAULK );
        add_face_filter( g_filter_face_caulk_ja, EXCLUDE_CAULK );
        add_brush_filter( g_filter_brush_liquids, EXCLUDE_LIQUIDS );
+       add_brush_filter( g_filter_brush_liquidsdir, EXCLUDE_LIQUIDS );
        add_brush_filter( g_filter_brush_hint, EXCLUDE_HINTSSKIPS );
+       add_brush_filter( g_filter_brush_hintlocal, EXCLUDE_HINTSSKIPS );
        add_brush_filter( g_filter_brush_hint_q2, EXCLUDE_HINTSSKIPS );
        add_brush_filter( g_filter_brush_hint_ja, EXCLUDE_HINTSSKIPS );
        add_brush_filter( g_filter_brush_subtlehint, EXCLUDE_HINTSSKIPS );
@@ -1266,10 +1286,10 @@ void Brush_constructMenu( ui::Menu menu ){
                if ( g_Layout_enableDetachableMenus.m_value ) {
                        menu_tearoff( menu_in_menu );
                }
-               create_menu_item_with_mnemonic( menu_in_menu, "Make _Hollow", "CSGMakeHollow" );
-               create_menu_item_with_mnemonic( menu_in_menu, "Make _Room", "CSGMakeRoom" );
                create_menu_item_with_mnemonic( menu_in_menu, "CSG _Subtract", "CSGSubtract" );
                create_menu_item_with_mnemonic( menu_in_menu, "CSG _Merge", "CSGMerge" );
+               create_menu_item_with_mnemonic( menu_in_menu, "Make _Room", "CSGRoom" );
+               create_menu_item_with_mnemonic( menu_in_menu, "CSG _Tool", "CSGTool" );
        }
        menu_separator( menu );
        {
@@ -1285,7 +1305,7 @@ void Brush_constructMenu( ui::Menu menu ){
        menu_separator( menu );
        create_menu_item_with_mnemonic( menu, "Make detail", "MakeDetail" );
        create_menu_item_with_mnemonic( menu, "Make structural", "MakeStructural" );
-       create_menu_item_with_mnemonic( menu, "Snap selection to _grid", "SnapToGrid" );
+//     create_menu_item_with_mnemonic( menu, "Snap selection to _grid", "SnapToGrid" );
 
        create_check_menu_item_with_mnemonic( menu, "Texture Lock", "TogTexLock" );
        menu_separator( menu );
index 2ab573e514601c7d8b451eca7a8caa448b1e1ae3..6a9565df8906a24823573a8ff180c288d3be49bb 100644 (file)
@@ -100,6 +100,16 @@ int Brush_toggleFormatCount(){
        return 1;
 }
 
+void Brush_switchFormat( bool switch_format ){
+       if ( switch_format )
+       {
+               g_useAlternativeTextureProjection.m_latched = g_useAlternativeTextureProjection.m_value;
+               g_useAlternativeTextureProjection.m_value = !g_useAlternativeTextureProjection.m_value;
+               PreferencesDialog_restartRequired( g_useAlternativeTextureProjection.m_description );
+               PreferencesDialog_restartIfRequired();
+       }
+}
+
 void Brush_Construct( EBrushType type ){
        if ( type == eBrushTypeQuake3 ) {
                g_showAlternativeTextureProjectionOption = true;
@@ -113,6 +123,7 @@ void Brush_Construct( EBrushType type ){
                        "AlternativeTextureProjection",
                        make_property_string( g_useAlternativeTextureProjection.m_latched )
                        );
+
                g_useAlternativeTextureProjection.useLatched();
 
                if ( g_useAlternativeTextureProjection.m_value ) {
index a5c67e3c88d65819779499089974e173000ec0e1..5d8deee836967a8b113e7033b9a5c90311cb7cd4 100644 (file)
@@ -26,5 +26,6 @@ void Brush_clipperColourChanged();
 void Brush_unlatchPreferences();
 int Brush_toggleFormatCount();
 void Brush_toggleFormat( int i );
+void Brush_switchFormat( bool switch_format );
 
 #endif
index 4e5ca1863a75fa86136835ac37b444be2c26b941..5e71e821496cbaba0b3fe77b827e9fe49a08ffc1 100644 (file)
@@ -677,6 +677,7 @@ class ProjectList
 public:
 Project& m_project;
 ui::ListStore m_store{ui::null};
+GtkWidget* m_buildView;
 bool m_changed;
 ProjectList( Project& project ) : m_project( project ), m_changed( false ){
 }
@@ -711,6 +712,8 @@ gboolean project_cell_edited(ui::CellRendererText cell, gchar* path_string, gcha
 
                gtk_list_store_set( projectList->m_store, &iter, 0, new_text, -1 );
                projectList->m_store.append();
+               //make command field activatable
+               g_signal_emit_by_name( G_OBJECT( gtk_tree_view_get_selection( GTK_TREE_VIEW( projectList->m_buildView ) ) ), "changed" );
        }
 
        gtk_tree_path_free( path );
@@ -895,6 +898,7 @@ ui::Window BuildMenuDialog_construct( ModalDialog& modal, ProjectList& projectLi
 
                                        view.show();
 
+                                       projectList.m_buildView = buildView;
                                        projectList.m_store = store;
                                        scr.add(view);
 
@@ -919,6 +923,9 @@ ui::Window BuildMenuDialog_construct( ModalDialog& modal, ProjectList& projectLi
 
                                        auto renderer = ui::CellRendererText(ui::New);
                                        object_set_boolean_property( G_OBJECT( renderer ), "editable", TRUE );
+                                       g_object_set( G_OBJECT( renderer ), "wrap-mode", PANGO_WRAP_WORD, NULL );
+                                       //g_object_set( G_OBJECT( renderer ), "ellipsize", PANGO_ELLIPSIZE_MIDDLE, NULL );
+                                       object_set_int_property( G_OBJECT( renderer ), "wrap-width", 640 );
                                        renderer.connect( "edited", G_CALLBACK( commands_cell_edited ), store );
 
                                        auto column = ui::TreeViewColumn( "", renderer, {{"text", 0}} );
@@ -950,6 +957,7 @@ ui::Window BuildMenuDialog_construct( ModalDialog& modal, ProjectList& projectLi
 namespace
 {
 CopiedString g_buildMenu;
+CopiedString g_lastExecutedBuild;
 }
 
 void LoadBuildMenu();
@@ -990,6 +998,7 @@ BuildMenuItem( const char* name, ui::MenuItem item )
        : m_name( name ), m_item( item ){
 }
 void run(){
+       g_lastExecutedBuild = m_name;
        RunBSP( m_name );
 }
 typedef MemberCaller<BuildMenuItem, void(), &BuildMenuItem::run> RunCaller;
@@ -1063,3 +1072,13 @@ void BuildMenu_Construct(){
 void BuildMenu_Destroy(){
        SaveBuildMenu();
 }
+
+
+void Build_runRecentExecutedBuild(){
+       if( g_lastExecutedBuild.empty() ){
+               g_BuildMenuItems.begin()->run();
+       }
+       else{
+               RunBSP( g_lastExecutedBuild.c_str() );
+       }
+}
index 4e49414637fa0a292adc1521ce7040143ef7dcb5..f6cfe9c309231e28c74655a6c5e386d91fa1891c 100644 (file)
@@ -41,5 +41,6 @@ void BuildMenu_Destroy();
 void Build_constructMenu( ui::Menu menu );
 extern ui::Menu g_bsp_menu;
 
+void Build_runRecentExecutedBuild();
 
 #endif
index fb05ae0f0603d4f67f4ce9eab97da361d4dc435c..0257fa55205390dfc2de8b63b41b384362dd2a93 100644 (file)
@@ -76,6 +76,7 @@ void CameraMovedNotify(){
 
 struct camwindow_globals_private_t
 {
+       int m_nFOV;
        int m_nMoveSpeed;
        bool m_bCamLinkSpeed;
        int m_nAngleSpeed;
@@ -86,14 +87,15 @@ struct camwindow_globals_private_t
        int m_nStrafeMode;
 
        camwindow_globals_private_t() :
+               m_nFOV( 110 ),
                m_nMoveSpeed( 100 ),
                m_bCamLinkSpeed( true ),
                m_nAngleSpeed( 3 ),
                m_bCamInverseMouse( false ),
                m_bCamDiscrete( true ),
-               m_bCubicClipping( true ),
+               m_bCubicClipping( false ),
                m_showStats( true ),
-               m_nStrafeMode( 0 ){
+               m_nStrafeMode( 3 ){
        }
 
 };
@@ -145,14 +147,13 @@ struct camera_t
 
        bool m_strafe; // true when in strafemode toggled by the ctrl-key
        bool m_strafe_forward; // true when in strafemode by ctrl-key and shift is pressed for forward strafing
+       bool m_strafe_forward_invert; //silly option to invert forward strafing to support old fegs
 
        unsigned int movementflags; // movement flags
        Timer m_keycontrol_timer;
        guint m_keymove_handler;
 
 
-       float fieldOfView;
-
        DeferredMotionDelta m_mouseMove;
 
        static void motionDelta( int x, int y, void* data ){
@@ -176,7 +177,6 @@ struct camera_t
                movementflags( 0 ),
                m_keycontrol_timer(),
                m_keymove_handler( 0 ),
-               fieldOfView( 90.0f ),
                m_mouseMove( motionDelta, this ),
                m_view( view ),
                m_update( update ){
@@ -205,7 +205,7 @@ float Camera_getFarClipPlane( camera_t& camera ){
 
 void Camera_updateProjection( camera_t& camera ){
        float farClip = Camera_getFarClipPlane( camera );
-       camera.projection = projection_for_camera( farClip / 4096.0f, farClip, camera.fieldOfView, camera.width, camera.height );
+       camera.projection = projection_for_camera( farClip / 4096.0f, farClip, (float)g_camwindow_globals_private.m_nFOV, camera.width, camera.height );
 
        camera.m_view->Construct( camera.projection, camera.modelview, camera.width, camera.height );
 }
@@ -286,7 +286,7 @@ void Camera_FreeMove( camera_t& camera, int dx, int dy ){
 
                camera.origin -= camera.vright * strafespeed * dx;
                if ( camera.m_strafe_forward ) {
-                       camera.origin += camera.vpn * strafespeed * dy;
+                       camera.origin += camera.m_strafe_forward_invert ? ( camera.vpn * strafespeed * dy ) : ( -camera.vpn * strafespeed * dy );
                }
                else{
                        camera.origin += camera.vup * strafespeed * dy;
@@ -367,7 +367,10 @@ const unsigned int MOVE_UP = 1 << 6;
 const unsigned int MOVE_DOWN = 1 << 7;
 const unsigned int MOVE_PITCHUP = 1 << 8;
 const unsigned int MOVE_PITCHDOWN = 1 << 9;
-const unsigned int MOVE_ALL = MOVE_FORWARD | MOVE_BACK | MOVE_ROTRIGHT | MOVE_ROTLEFT | MOVE_STRAFERIGHT | MOVE_STRAFELEFT | MOVE_UP | MOVE_DOWN | MOVE_PITCHUP | MOVE_PITCHDOWN;
+const unsigned int MOVE_FOCUS = 1 << 10;
+const unsigned int MOVE_ALL = MOVE_FORWARD | MOVE_BACK | MOVE_ROTRIGHT | MOVE_ROTLEFT | MOVE_STRAFERIGHT | MOVE_STRAFELEFT | MOVE_UP | MOVE_DOWN | MOVE_PITCHUP | MOVE_PITCHDOWN | MOVE_FOCUS;
+
+Vector3 Camera_getFocusPos( camera_t& camera );
 
 void Cam_KeyControl( camera_t& camera, float dtime ){
        // Update angles
@@ -412,6 +415,9 @@ void Cam_KeyControl( camera_t& camera, float dtime ){
        if ( camera.movementflags & MOVE_DOWN ) {
                vector3_add( camera.origin, vector3_scaled( g_vector3_axis_z, -dtime * g_camwindow_globals_private.m_nMoveSpeed ) );
        }
+       if ( camera.movementflags & MOVE_FOCUS ) {
+               camera.origin = Camera_getFocusPos( camera );
+       }
 
        Camera_updateModelview( camera );
 }
@@ -515,6 +521,12 @@ void Camera_PitchDown_KeyUp( camera_t& camera ){
        Camera_clearMovementFlags( camera, MOVE_PITCHDOWN );
 }
 
+void Camera_Focus_KeyDown( camera_t& camera ){
+       Camera_setMovementFlags( camera, MOVE_FOCUS );
+}
+void Camera_Focus_KeyUp( camera_t& camera ){
+       Camera_clearMovementFlags( camera, MOVE_FOCUS );
+}
 
 typedef ReferenceCaller<camera_t, void(), &Camera_MoveForward_KeyDown> FreeMoveCameraMoveForwardKeyDownCaller;
 typedef ReferenceCaller<camera_t, void(), &Camera_MoveForward_KeyUp> FreeMoveCameraMoveForwardKeyUpCaller;
@@ -529,7 +541,12 @@ typedef ReferenceCaller<camera_t, void(), &Camera_MoveUp_KeyUp> FreeMoveCameraMo
 typedef ReferenceCaller<camera_t, void(), &Camera_MoveDown_KeyDown> FreeMoveCameraMoveDownKeyDownCaller;
 typedef ReferenceCaller<camera_t, void(), &Camera_MoveDown_KeyUp> FreeMoveCameraMoveDownKeyUpCaller;
 
+typedef ReferenceCaller<camera_t, void(), &Camera_Focus_KeyDown> FreeMoveCameraFocusKeyDownCaller;
+typedef ReferenceCaller<camera_t, void(), &Camera_Focus_KeyUp> FreeMoveCameraFocusKeyUpCaller;
 
+const float MIN_FOV = 60;
+const float MAX_FOV = 179;
+const float FOV_STEP = 10;
 const float SPEED_MOVE = 32;
 const float SPEED_TURN = 22.5;
 const float MIN_CAM_SPEED = 10;
@@ -626,16 +643,12 @@ void Camera_motionDelta( int x, int y, unsigned int state, void* data ){
 
        cam->m_mouseMove.motion_delta( x, y, state );
 
+       cam->m_strafe_forward_invert = false;
+
        switch ( g_camwindow_globals_private.m_nStrafeMode )
        {
        case 0:
-               cam->m_strafe = ( state & GDK_CONTROL_MASK ) != 0;
-               if ( cam->m_strafe ) {
-                       cam->m_strafe_forward = ( state & GDK_SHIFT_MASK ) != 0;
-               }
-               else{
-                       cam->m_strafe_forward = false;
-               }
+               cam->m_strafe = false;
                break;
        case 1:
                cam->m_strafe = ( state & GDK_CONTROL_MASK ) != 0 && ( state & GDK_SHIFT_MASK ) == 0;
@@ -645,9 +658,24 @@ void Camera_motionDelta( int x, int y, unsigned int state, void* data ){
                cam->m_strafe = ( state & GDK_CONTROL_MASK ) != 0 && ( state & GDK_SHIFT_MASK ) == 0;
                cam->m_strafe_forward = cam->m_strafe;
                break;
+       case 4:
+               cam->m_strafe_forward_invert = true; // fall through
+       default: /* 3 & 4 */
+               cam->m_strafe = ( state & GDK_CONTROL_MASK ) != 0;
+               if ( cam->m_strafe ) {
+                       cam->m_strafe_forward = ( state & GDK_SHIFT_MASK ) != 0;
+               }
+               else{
+                       cam->m_strafe_forward = false;
+               }
+               break;
        }
 }
 
+
+
+
+
 class CamWnd
 {
 View m_view;
@@ -709,7 +737,7 @@ static void releaseStates(){
 
 camera_t& getCamera(){
        return m_Camera;
-};
+}
 
 void BenchMark();
 void Cam_ChangeFloor( bool up );
@@ -797,7 +825,7 @@ void Camera_setAngles( CamWnd& camwnd, const Vector3& angles ){
 // CamWnd class
 
 gboolean enable_freelook_button_press( ui::Widget widget, GdkEventButton* event, CamWnd* camwnd ){
-       if ( event->type == GDK_BUTTON_PRESS && event->button == 3 ) {
+       if ( event->type == GDK_BUTTON_PRESS && event->button == 3 && modifiers_for_state( event->state ) == c_modifierNone ) {
                camwnd->EnableFreeMove();
                return TRUE;
        }
@@ -805,7 +833,7 @@ gboolean enable_freelook_button_press( ui::Widget widget, GdkEventButton* event,
 }
 
 gboolean disable_freelook_button_press( ui::Widget widget, GdkEventButton* event, CamWnd* camwnd ){
-       if ( event->type == GDK_BUTTON_PRESS && event->button == 3 ) {
+       if ( event->type == GDK_BUTTON_PRESS && event->button == 3 && modifiers_for_state( event->state ) == c_modifierNone ) {
                camwnd->DisableFreeMove();
                return TRUE;
        }
@@ -830,6 +858,7 @@ void camwnd_update_xor_rectangle( CamWnd& self, rect_t area ){
 
 gboolean selection_button_press( ui::Widget widget, GdkEventButton* event, WindowObserver* observer ){
        if ( event->type == GDK_BUTTON_PRESS ) {
+               gtk_widget_grab_focus( widget );
                observer->onMouseDown( WindowVector_forDouble( event->x, event->y ), button_for_button( event->button ), modifiers_for_state( event->state ) );
        }
        return FALSE;
@@ -872,9 +901,39 @@ gboolean selection_motion_freemove( ui::Widget widget, GdkEventMotion *event, Wi
 }
 
 gboolean wheelmove_scroll( ui::Widget widget, GdkEventScroll* event, CamWnd* camwnd ){
+       //gtk_window_set_focus( camwnd->m_parent, camwnd->m_gl_widget );
+       gtk_widget_grab_focus( camwnd->m_gl_widget );
+       if( !gtk_window_is_active( camwnd->m_parent ) )
+               gtk_window_present( camwnd->m_parent );
+
        if ( event->direction == GDK_SCROLL_UP ) {
                Camera_Freemove_updateAxes( camwnd->getCamera() );
-               Camera_setOrigin( *camwnd, vector3_added( Camera_getOrigin( *camwnd ), vector3_scaled( camwnd->getCamera().forward, static_cast<float>( g_camwindow_globals_private.m_nMoveSpeed ) ) ) );
+               if( camwnd->m_bFreeMove || !g_camwindow_globals.m_bZoomInToPointer ){
+                       Camera_setOrigin( *camwnd, vector3_added( Camera_getOrigin( *camwnd ), vector3_scaled( camwnd->getCamera().forward, static_cast<float>( g_camwindow_globals_private.m_nMoveSpeed ) ) ) );
+               }
+               else{
+                       //Matrix4 maa = matrix4_multiplied_by_matrix4( camwnd->getCamera().projection, camwnd->getCamera().modelview );
+                       Matrix4 maa = camwnd->getCamera().m_view->GetViewMatrix();
+                       matrix4_affine_invert( maa );
+
+                       float x = static_cast<float>( event->x );
+                       float y = static_cast<float>( event->y );
+                       Vector3 normalized;
+
+                       normalized[0] = 2.0f * ( x ) / static_cast<float>( camwnd->getCamera().width ) - 1.0f;
+                       normalized[1] = 2.0f * ( y )/ static_cast<float>( camwnd->getCamera().height ) - 1.0f;
+                       normalized[1] *= -1.f;
+                       normalized[2] = 0.f;
+
+                       normalized *= 16.0f;
+                               //globalOutputStream() << normalized << " normalized    ";
+                       matrix4_transform_point( maa, normalized );
+                               //globalOutputStream() << normalized << "\n";
+                       Vector3 norm = vector3_normalised( normalized - Camera_getOrigin( *camwnd ) );
+                               //globalOutputStream() << normalized - Camera_getOrigin( *camwnd ) << "  normalized - Camera_getOrigin( *camwnd )\n";
+                               //globalOutputStream() << norm << "  norm\n";
+                       Camera_setOrigin( *camwnd, vector3_added( Camera_getOrigin( *camwnd ), vector3_scaled( norm, static_cast<float>( g_camwindow_globals_private.m_nMoveSpeed ) ) ) );
+               }
        }
        else if ( event->direction == GDK_SCROLL_DOWN ) {
                Camera_Freemove_updateAxes( camwnd->getCamera() );
@@ -911,83 +970,107 @@ void KeyEvent_disconnect( const char* name ){
 }
 
 void CamWnd_registerCommands( CamWnd& camwnd ){
-       GlobalKeyEvents_insert( "CameraForward", Accelerator( GDK_KEY_Up ),
+       GlobalKeyEvents_insert( "CameraForward", accelerator_null(),
                                                        ReferenceCaller<camera_t, void(), Camera_MoveForward_KeyDown>( camwnd.getCamera() ),
                                                        ReferenceCaller<camera_t, void(), Camera_MoveForward_KeyUp>( camwnd.getCamera() )
                                                        );
-       GlobalKeyEvents_insert( "CameraBack", Accelerator( GDK_KEY_Down ),
+       GlobalKeyEvents_insert( "CameraBack", accelerator_null(),
                                                        ReferenceCaller<camera_t, void(), Camera_MoveBack_KeyDown>( camwnd.getCamera() ),
                                                        ReferenceCaller<camera_t, void(), Camera_MoveBack_KeyUp>( camwnd.getCamera() )
                                                        );
-       GlobalKeyEvents_insert( "CameraLeft", Accelerator( GDK_KEY_Left ),
+       GlobalKeyEvents_insert( "CameraLeft", accelerator_null(),
                                                        ReferenceCaller<camera_t, void(), Camera_RotateLeft_KeyDown>( camwnd.getCamera() ),
                                                        ReferenceCaller<camera_t, void(), Camera_RotateLeft_KeyUp>( camwnd.getCamera() )
                                                        );
-       GlobalKeyEvents_insert( "CameraRight", Accelerator( GDK_KEY_Right ),
+       GlobalKeyEvents_insert( "CameraRight", accelerator_null(),
                                                        ReferenceCaller<camera_t, void(), Camera_RotateRight_KeyDown>( camwnd.getCamera() ),
                                                        ReferenceCaller<camera_t, void(), Camera_RotateRight_KeyUp>( camwnd.getCamera() )
                                                        );
-       GlobalKeyEvents_insert( "CameraStrafeRight", Accelerator( GDK_KEY_period ),
+       GlobalKeyEvents_insert( "CameraStrafeRight", accelerator_null(),
                                                        ReferenceCaller<camera_t, void(), Camera_MoveRight_KeyDown>( camwnd.getCamera() ),
                                                        ReferenceCaller<camera_t, void(), Camera_MoveRight_KeyUp>( camwnd.getCamera() )
                                                        );
-       GlobalKeyEvents_insert( "CameraStrafeLeft", Accelerator( GDK_KEY_comma ),
+       GlobalKeyEvents_insert( "CameraStrafeLeft", accelerator_null(),
                                                        ReferenceCaller<camera_t, void(), Camera_MoveLeft_KeyDown>( camwnd.getCamera() ),
                                                        ReferenceCaller<camera_t, void(), Camera_MoveLeft_KeyUp>( camwnd.getCamera() )
                                                        );
-       GlobalKeyEvents_insert( "CameraUp", Accelerator( 'D' ),
+       GlobalKeyEvents_insert( "CameraUp", accelerator_null(),
                                                        ReferenceCaller<camera_t, void(), Camera_MoveUp_KeyDown>( camwnd.getCamera() ),
                                                        ReferenceCaller<camera_t, void(), Camera_MoveUp_KeyUp>( camwnd.getCamera() )
                                                        );
-       GlobalKeyEvents_insert( "CameraDown", Accelerator( 'C' ),
+       GlobalKeyEvents_insert( "CameraDown", accelerator_null(),
                                                        ReferenceCaller<camera_t, void(), Camera_MoveDown_KeyDown>( camwnd.getCamera() ),
                                                        ReferenceCaller<camera_t, void(), Camera_MoveDown_KeyUp>( camwnd.getCamera() )
                                                        );
-       GlobalKeyEvents_insert( "CameraAngleDown", Accelerator( 'A' ),
+       GlobalKeyEvents_insert( "CameraAngleUp", accelerator_null(),
+                                                       ReferenceCaller<camera_t, void(), Camera_PitchUp_KeyDown>( camwnd.getCamera() ),
+                                                       ReferenceCaller<camera_t, void(), Camera_PitchUp_KeyUp>( camwnd.getCamera() )
+                                                       );
+       GlobalKeyEvents_insert( "CameraAngleDown", accelerator_null(),
                                                        ReferenceCaller<camera_t, void(), Camera_PitchDown_KeyDown>( camwnd.getCamera() ),
                                                        ReferenceCaller<camera_t, void(), Camera_PitchDown_KeyUp>( camwnd.getCamera() )
                                                        );
-       GlobalKeyEvents_insert( "CameraAngleUp", Accelerator( 'Z' ),
-                                                       ReferenceCaller<camera_t, void(), Camera_PitchUp_KeyDown>( camwnd.getCamera() ),
-                                                       ReferenceCaller<camera_t, void(), Camera_PitchUp_KeyUp>( camwnd.getCamera() )
+
+
+       GlobalKeyEvents_insert( "CameraFreeMoveForward", accelerator_null(),
+                                                       FreeMoveCameraMoveForwardKeyDownCaller( camwnd.getCamera() ),
+                                                       FreeMoveCameraMoveForwardKeyUpCaller( camwnd.getCamera() )
+                                                       );
+       GlobalKeyEvents_insert( "CameraFreeMoveBack", accelerator_null(),
+                                                       FreeMoveCameraMoveBackKeyDownCaller( camwnd.getCamera() ),
+                                                       FreeMoveCameraMoveBackKeyUpCaller( camwnd.getCamera() )
+                                                       );
+       GlobalKeyEvents_insert( "CameraFreeMoveLeft", accelerator_null(),
+                                                       FreeMoveCameraMoveLeftKeyDownCaller( camwnd.getCamera() ),
+                                                       FreeMoveCameraMoveLeftKeyUpCaller( camwnd.getCamera() )
+                                                       );
+       GlobalKeyEvents_insert( "CameraFreeMoveRight", accelerator_null(),
+                                                       FreeMoveCameraMoveRightKeyDownCaller( camwnd.getCamera() ),
+                                                       FreeMoveCameraMoveRightKeyUpCaller( camwnd.getCamera() )
                                                        );
 
-       GlobalKeyEvents_insert( "CameraFreeMoveForward", Accelerator( GDK_KEY_Up ),
+       GlobalKeyEvents_insert( "CameraFreeMoveForward2", accelerator_null(),
                                                        FreeMoveCameraMoveForwardKeyDownCaller( camwnd.getCamera() ),
                                                        FreeMoveCameraMoveForwardKeyUpCaller( camwnd.getCamera() )
                                                        );
-       GlobalKeyEvents_insert( "CameraFreeMoveBack", Accelerator( GDK_KEY_Down ),
+       GlobalKeyEvents_insert( "CameraFreeMoveBack2", accelerator_null(),
                                                        FreeMoveCameraMoveBackKeyDownCaller( camwnd.getCamera() ),
                                                        FreeMoveCameraMoveBackKeyUpCaller( camwnd.getCamera() )
                                                        );
-       GlobalKeyEvents_insert( "CameraFreeMoveLeft", Accelerator( GDK_KEY_Left ),
+       GlobalKeyEvents_insert( "CameraFreeMoveLeft2", accelerator_null(),
                                                        FreeMoveCameraMoveLeftKeyDownCaller( camwnd.getCamera() ),
                                                        FreeMoveCameraMoveLeftKeyUpCaller( camwnd.getCamera() )
                                                        );
-       GlobalKeyEvents_insert( "CameraFreeMoveRight", Accelerator( GDK_KEY_Right ),
+       GlobalKeyEvents_insert( "CameraFreeMoveRight2", accelerator_null(),
                                                        FreeMoveCameraMoveRightKeyDownCaller( camwnd.getCamera() ),
                                                        FreeMoveCameraMoveRightKeyUpCaller( camwnd.getCamera() )
                                                        );
-       GlobalKeyEvents_insert( "CameraFreeMoveUp", Accelerator( 'D' ),
+
+       GlobalKeyEvents_insert( "CameraFreeMoveUp", accelerator_null(),
                                                        FreeMoveCameraMoveUpKeyDownCaller( camwnd.getCamera() ),
                                                        FreeMoveCameraMoveUpKeyUpCaller( camwnd.getCamera() )
                                                        );
-       GlobalKeyEvents_insert( "CameraFreeMoveDown", Accelerator( 'C' ),
+       GlobalKeyEvents_insert( "CameraFreeMoveDown", accelerator_null(),
                                                        FreeMoveCameraMoveDownKeyDownCaller( camwnd.getCamera() ),
                                                        FreeMoveCameraMoveDownKeyUpCaller( camwnd.getCamera() )
                                                        );
 
-       GlobalCommands_insert( "CameraForward", ReferenceCaller<camera_t, void(), Camera_MoveForward_Discrete>( camwnd.getCamera() ), Accelerator( GDK_KEY_Up ) );
-       GlobalCommands_insert( "CameraBack", ReferenceCaller<camera_t, void(), Camera_MoveBack_Discrete>( camwnd.getCamera() ), Accelerator( GDK_KEY_Down ) );
-       GlobalCommands_insert( "CameraLeft", ReferenceCaller<camera_t, void(), Camera_RotateLeft_Discrete>( camwnd.getCamera() ), Accelerator( GDK_KEY_Left ) );
-       GlobalCommands_insert( "CameraRight", ReferenceCaller<camera_t, void(), Camera_RotateRight_Discrete>( camwnd.getCamera() ), Accelerator( GDK_KEY_Right ) );
-       GlobalCommands_insert( "CameraStrafeRight", ReferenceCaller<camera_t, void(), Camera_MoveRight_Discrete>( camwnd.getCamera() ), Accelerator( GDK_KEY_period ) );
-       GlobalCommands_insert( "CameraStrafeLeft", ReferenceCaller<camera_t, void(), Camera_MoveLeft_Discrete>( camwnd.getCamera() ), Accelerator( GDK_KEY_comma ) );
+       GlobalKeyEvents_insert( "CameraFreeFocus", accelerator_null(),
+                                                       FreeMoveCameraFocusKeyDownCaller( camwnd.getCamera() ),
+                                                       FreeMoveCameraFocusKeyUpCaller( camwnd.getCamera() )
+                                                       );
+
+       GlobalCommands_insert( "CameraForward", ReferenceCaller<camera_t, void(), Camera_MoveForward_Discrete>( camwnd.getCamera() ) );
+       GlobalCommands_insert( "CameraBack", ReferenceCaller<camera_t, void(), Camera_MoveBack_Discrete>( camwnd.getCamera() ) );
+       GlobalCommands_insert( "CameraLeft", ReferenceCaller<camera_t, void(), Camera_RotateLeft_Discrete>( camwnd.getCamera() ) );
+       GlobalCommands_insert( "CameraRight", ReferenceCaller<camera_t, void(), Camera_RotateRight_Discrete>( camwnd.getCamera() ) );
+       GlobalCommands_insert( "CameraStrafeRight", ReferenceCaller<camera_t, void(), Camera_MoveRight_Discrete>( camwnd.getCamera() ) );
+       GlobalCommands_insert( "CameraStrafeLeft", ReferenceCaller<camera_t, void(), Camera_MoveLeft_Discrete>( camwnd.getCamera() ) );
 
-       GlobalCommands_insert( "CameraUp", ReferenceCaller<camera_t, void(), Camera_MoveUp_Discrete>( camwnd.getCamera() ), Accelerator( 'D' ) );
-       GlobalCommands_insert( "CameraDown", ReferenceCaller<camera_t, void(), Camera_MoveDown_Discrete>( camwnd.getCamera() ), Accelerator( 'C' ) );
-       GlobalCommands_insert( "CameraAngleUp", ReferenceCaller<camera_t, void(), Camera_PitchUp_Discrete>( camwnd.getCamera() ), Accelerator( 'A' ) );
-       GlobalCommands_insert( "CameraAngleDown", ReferenceCaller<camera_t, void(), Camera_PitchDown_Discrete>( camwnd.getCamera() ), Accelerator( 'Z' ) );
+       GlobalCommands_insert( "CameraUp", ReferenceCaller<camera_t, void(), Camera_MoveUp_Discrete>( camwnd.getCamera() ) );
+       GlobalCommands_insert( "CameraDown", ReferenceCaller<camera_t, void(), Camera_MoveDown_Discrete>( camwnd.getCamera() ) );
+       GlobalCommands_insert( "CameraAngleUp", ReferenceCaller<camera_t, void(), Camera_PitchUp_Discrete>( camwnd.getCamera() ) );
+       GlobalCommands_insert( "CameraAngleDown", ReferenceCaller<camera_t, void(), Camera_PitchDown_Discrete>( camwnd.getCamera() ) );
 }
 
 void CamWnd_Move_Enable( CamWnd& camwnd ){
@@ -1056,20 +1139,20 @@ struct CamWnd_Move_Discrete {
        }
 
        static void Import_(CamWnd &camwnd, bool value) {
-               if (g_camwindow_globals_private.m_bCamDiscrete) {
-                       CamWnd_Move_Discrete_Disable(camwnd);
+       if ( g_camwindow_globals_private.m_bCamDiscrete ) {
+               CamWnd_Move_Discrete_Disable( camwnd );
                } else {
-                       CamWnd_Move_Disable(camwnd);
-               }
+               CamWnd_Move_Disable( camwnd );
+       }
 
-               g_camwindow_globals_private.m_bCamDiscrete = value;
+       g_camwindow_globals_private.m_bCamDiscrete = value;
 
-               if (g_camwindow_globals_private.m_bCamDiscrete) {
-                       CamWnd_Move_Discrete_Enable(camwnd);
+       if ( g_camwindow_globals_private.m_bCamDiscrete ) {
+               CamWnd_Move_Discrete_Enable( camwnd );
                } else {
-                       CamWnd_Move_Enable(camwnd);
-               }
+               CamWnd_Move_Enable( camwnd );
        }
+}
 };
 
 
@@ -1116,8 +1199,16 @@ void CamWnd_Add_Handlers_FreeMove( CamWnd& camwnd ){
        KeyEvent_connect( "CameraFreeMoveBack" );
        KeyEvent_connect( "CameraFreeMoveLeft" );
        KeyEvent_connect( "CameraFreeMoveRight" );
+
+       KeyEvent_connect( "CameraFreeMoveForward2" );
+       KeyEvent_connect( "CameraFreeMoveBack2" );
+       KeyEvent_connect( "CameraFreeMoveLeft2" );
+       KeyEvent_connect( "CameraFreeMoveRight2" );
+
        KeyEvent_connect( "CameraFreeMoveUp" );
        KeyEvent_connect( "CameraFreeMoveDown" );
+
+       KeyEvent_connect( "CameraFreeFocus" );
 }
 
 void CamWnd_Remove_Handlers_FreeMove( CamWnd& camwnd ){
@@ -1125,9 +1216,17 @@ void CamWnd_Remove_Handlers_FreeMove( CamWnd& camwnd ){
        KeyEvent_disconnect( "CameraFreeMoveBack" );
        KeyEvent_disconnect( "CameraFreeMoveLeft" );
        KeyEvent_disconnect( "CameraFreeMoveRight" );
+
+       KeyEvent_disconnect( "CameraFreeMoveForward2" );
+       KeyEvent_disconnect( "CameraFreeMoveBack2" );
+       KeyEvent_disconnect( "CameraFreeMoveLeft2" );
+       KeyEvent_disconnect( "CameraFreeMoveRight2" );
+
        KeyEvent_disconnect( "CameraFreeMoveUp" );
        KeyEvent_disconnect( "CameraFreeMoveDown" );
 
+       KeyEvent_disconnect( "CameraFreeFocus" );
+
        g_signal_handler_disconnect( G_OBJECT( camwnd.m_gl_widget ), camwnd.m_selection_button_press_handler );
        g_signal_handler_disconnect( G_OBJECT( camwnd.m_gl_widget ), camwnd.m_selection_button_release_handler );
        g_signal_handler_disconnect( G_OBJECT( camwnd.m_gl_widget ), camwnd.m_selection_motion_handler );
@@ -1216,6 +1315,9 @@ bool pre( const scene::Path& path, scene::Instance& instance ) const {
                        m_bestDown = floorHeight;
                }
        }
+       else if( !path.top().get().visible() ){
+               return false;
+       }
        return true;
 }
 };
@@ -1292,6 +1394,8 @@ void CamWnd::EnableFreeMove(){
 
        gtk_window_set_focus( m_parent, m_gl_widget );
        m_freemove_handle_focusout = m_gl_widget.connect( "focus_out_event", G_CALLBACK( camwindow_freemove_focusout ), this );
+       /* We chose to replace m_parent by m_gl_widget but NetRadiantCustom does:
+       m_freezePointer.freeze_pointer( m_parent, m_gl_widget, Camera_motionDelta, &m_Camera ); */
        m_freezePointer.freeze_pointer( m_gl_widget, Camera_motionDelta, &m_Camera );
 
        CamWnd_Update( *this );
@@ -1307,7 +1411,8 @@ void CamWnd::DisableFreeMove(){
        CamWnd_Remove_Handlers_FreeMove( *this );
        CamWnd_Add_Handlers_Move( *this );
 
-       m_freezePointer.unfreeze_pointer( m_gl_widget );
+       m_freezePointer.unfreeze_pointer( m_gl_widget, true );
+
        g_signal_handler_disconnect( G_OBJECT( m_gl_widget ), m_freemove_handle_focusout );
 
        CamWnd_Update( *this );
@@ -1391,7 +1496,7 @@ void render( const Matrix4& modelview, const Matrix4& projection ){
    Cam_Draw
    ==============
  */
-
+/*
 void ShowStatsToggle(){
        g_camwindow_globals_private.m_showStats ^= 1;
 }
@@ -1403,6 +1508,22 @@ void ShowStatsExport( const Callback<void(bool)> &importer ){
 FreeCaller<void(const Callback<void(bool)>&), ShowStatsExport> g_show_stats_caller;
 Callback<void(const Callback<void(bool)> &)> g_show_stats_callback( g_show_stats_caller );
 ToggleItem g_show_stats( g_show_stats_callback );
+*/
+
+void ShowStatsToggle(){
+       g_camwindow_globals_private.m_showStats ^= 1;
+//     g_show_stats.update();
+       UpdateAllWindows();
+}
+typedef FreeCaller<void(), ShowStatsToggle> ShowStatsToggleCaller;
+void ShowStatsExport( const Callback<void(bool)> & importer ){
+       importer( g_camwindow_globals_private.m_showStats );
+}
+typedef FreeCaller<void(const Callback<void(bool)> &), ShowStatsExport> ShowStatsExportCaller;
+
+ShowStatsExportCaller g_show_stats_caller;
+Callback<void(const Callback<void(bool)> &)> g_show_stats_callback( g_show_stats_caller );
+ToggleItem g_show_stats( g_show_stats_callback );
 
 void CamWnd::Cam_Draw(){
        glViewport( 0, 0, m_Camera.width, m_Camera.height );
@@ -1493,9 +1614,9 @@ void CamWnd::Cam_Draw(){
                break;
        }
 
-       if ( !g_xywindow_globals.m_bNoStipple ) {
+//     if ( !g_xywindow_globals.m_bNoStipple ) {
                globalstate |= RENDER_LINESTIPPLE | RENDER_POLYGONSTIPPLE;
-       }
+//     }
 
        {
                CamRenderer renderer( globalstate, m_state_select2, m_state_select1, m_view.getViewer() );
@@ -1596,10 +1717,6 @@ void CamWnd::BenchMark(){
 }
 
 
-void fill_view_camera_menu( ui::Menu menu ){
-       create_check_menu_item_with_mnemonic( menu, "Camera View", "ToggleCamera" );
-}
-
 void GlobalCamera_ResetAngles(){
        CamWnd& camwnd = *g_camwnd;
        Vector3 angles;
@@ -1608,6 +1725,55 @@ void GlobalCamera_ResetAngles(){
        Camera_setAngles( camwnd, angles );
 }
 
+#include "select.h"
+
+Vector3 Camera_getFocusPos( camera_t& camera ){
+       Vector3 camorigin( Camera_getOrigin( camera ) );
+       AABB aabb( aabb_for_minmax( Select_getWorkZone().d_work_min, Select_getWorkZone().d_work_max ) );
+       View& view = *( camera.m_view );
+#if 0
+       Vector3 angles( Camera_getAngles( camera ) );
+       Vector3 radangles( degrees_to_radians( angles[0] ), degrees_to_radians( angles[1] ), degrees_to_radians( angles[2] ) );
+       Vector3 viewvector;
+       viewvector[0] = cos( radangles[1] ) * cos( radangles[0] );
+       viewvector[1] = sin( radangles[1] ) * cos( radangles[0] );
+       viewvector[2] = sin( radangles[0] );
+#elif 0
+       Vector3 viewvector( -view.GetModelview()[2], -view.GetModelview()[6], -view.GetModelview()[10] );
+#elif 1
+       Vector3 viewvector( -camera.vpn );
+#endif
+
+       Plane3 frustumPlanes[4];
+       frustumPlanes[0] = plane3_translated( view.getFrustum().left, camorigin - aabb.origin );
+       frustumPlanes[1] = plane3_translated( view.getFrustum().right, camorigin - aabb.origin );
+       frustumPlanes[2] = plane3_translated( view.getFrustum().top, camorigin - aabb.origin );
+       frustumPlanes[3] = plane3_translated( view.getFrustum().bottom, camorigin - aabb.origin );
+
+       float offset = 64.0f;
+
+       Vector3 corners[8];
+       aabb_corners( aabb, corners );
+
+       for ( size_t i = 0; i < 4; ++i ){
+               for ( size_t j = 0; j < 8; ++j ){
+                       Ray ray( aabb.origin, -viewvector );
+                       //Plane3 newplane( frustumPlanes[i].normal(), vector3_dot( frustumPlanes[i].normal(), corners[j] - frustumPlanes[i].normal() * 16.0f ) );
+                       Plane3 newplane( frustumPlanes[i].normal(), vector3_dot( frustumPlanes[i].normal(), corners[j] ) );
+                       float d = vector3_dot( ray.direction, newplane.normal() );
+                       if( d != 0 ){
+                               float s = vector3_dot( newplane.normal() * newplane.dist() - ray.origin, newplane.normal() ) / d;
+                               offset = std::max( offset, s );
+                       }
+               }
+       }
+       return ( aabb.origin - viewvector * offset );
+}
+
+void GlobalCamera_FocusOnSelected(){
+       Camera_setOrigin( *g_camwnd, Camera_getFocusPos( g_camwnd->getCamera() ) );
+}
+
 void Camera_ChangeFloorUp(){
        CamWnd& camwnd = *g_camwnd;
        camwnd.Cam_ChangeFloor( true );
@@ -1632,8 +1798,8 @@ void Camera_CubeIn(){
 void Camera_CubeOut(){
        CamWnd& camwnd = *g_camwnd;
        g_camwindow_globals.m_nCubicScale++;
-       if ( g_camwindow_globals.m_nCubicScale > 23 ) {
-               g_camwindow_globals.m_nCubicScale = 23;
+       if ( g_camwindow_globals.m_nCubicScale > 46 ) {
+               g_camwindow_globals.m_nCubicScale = 46;
        }
        Camera_updateProjection( camwnd.getCamera() );
        CamWnd_Update( camwnd );
@@ -1671,7 +1837,7 @@ void Camera_ToggleFarClip(){
 
 
 void CamWnd_constructToolbar( ui::Toolbar toolbar ){
-       toolbar_append_toggle_button( toolbar, "Cubic clip the camera view (\\)", "view_cubicclipping.png", "ToggleCubicClip" );
+       toolbar_append_toggle_button( toolbar, "Cubic clip the camera view (Ctrl + \\)", "view_cubicclipping.png", "ToggleCubicClip" );
 }
 
 void CamWnd_registerShortcuts(){
@@ -1681,6 +1847,8 @@ void CamWnd_registerShortcuts(){
                command_connect_accelerator( "TogglePreview" );
        }
 
+       command_connect_accelerator( "FOVInc" );
+       command_connect_accelerator( "FOVDec" );
        command_connect_accelerator( "CameraSpeedInc" );
        command_connect_accelerator( "CameraSpeedDec" );
 }
@@ -1779,29 +1947,31 @@ struct RenderMode {
 
        static void Import(int value) {
                switch (value) {
-                       case 0:
-                               CamWnd_SetMode(cd_wire);
-                               break;
-                       case 1:
-                               CamWnd_SetMode(cd_solid);
-                               break;
-                       case 2:
-                               CamWnd_SetMode(cd_texture);
-                               break;
-                       case 3:
-                               CamWnd_SetMode(cd_lighting);
-                               break;
-                       default:
-                               CamWnd_SetMode(cd_texture);
-               }
+       case 0:
+               CamWnd_SetMode( cd_wire );
+               break;
+       case 1:
+               CamWnd_SetMode( cd_solid );
+               break;
+       case 2:
+               CamWnd_SetMode( cd_texture );
+               break;
+       case 3:
+               CamWnd_SetMode( cd_lighting );
+               break;
+       default:
+               CamWnd_SetMode( cd_texture );
        }
+}
 };
 
 void Camera_constructPreferences( PreferencesPage& page ){
+       page.appendSlider( "FOV", g_camwindow_globals_private.m_nFOV, TRUE, 0, 0, 100, MIN_FOV, MAX_FOV, 1, 10 );
        page.appendSlider( "Movement Speed", g_camwindow_globals_private.m_nMoveSpeed, TRUE, 0, 0, 100, MIN_CAM_SPEED, MAX_CAM_SPEED, 1, 10 );
        page.appendCheckBox( "", "Link strafe speed to movement speed", g_camwindow_globals_private.m_bCamLinkSpeed );
        page.appendSlider( "Rotation Speed", g_camwindow_globals_private.m_nAngleSpeed, TRUE, 0, 0, 3, 1, 180, 1, 10 );
        page.appendCheckBox( "", "Invert mouse vertical axis", g_camwindow_globals_private.m_bCamInverseMouse );
+       page.appendCheckBox( "", "Zoom In to Mouse pointer", g_camwindow_globals.m_bZoomInToPointer );
        page.appendCheckBox(
                "", "Discrete movement",
                make_property<CamWnd_Move_Discrete>()
@@ -1831,7 +2001,7 @@ void Camera_constructPreferences( PreferencesPage& page ){
                        );
        }
 
-       const char* strafe_mode[] = { "Both", "Forward", "Up" };
+       const char* strafe_mode[] = { "None", "Up", "Forward", "Both", "Both Inverted" };
 
        page.appendCombo(
                "Strafe Mode",
@@ -1851,6 +2021,31 @@ void Camera_registerPreferencesPage(){
 #include "stringio.h"
 #include "dialog.h"
 
+void FOV_increase(){
+       CamWnd& camwnd = *g_camwnd;
+       if ( g_camwindow_globals_private.m_nFOV <= ( MAX_FOV - FOV_STEP - 10 ) ) {
+               g_camwindow_globals_private.m_nFOV += FOV_STEP;
+       }
+       else {
+               g_camwindow_globals_private.m_nFOV = MAX_FOV - 10;
+       }
+       Camera_updateProjection( camwnd.getCamera() );
+       CamWnd_Update( camwnd );
+}
+
+void FOV_decrease(){
+       CamWnd& camwnd = *g_camwnd;
+       if ( g_camwindow_globals_private.m_nFOV >= ( MIN_FOV + FOV_STEP ) ) {
+               g_camwindow_globals_private.m_nFOV -= FOV_STEP;
+       }
+       else {
+               g_camwindow_globals_private.m_nFOV = MIN_FOV;
+       }
+       Camera_updateProjection( camwnd.getCamera() );
+       CamWnd_Update( camwnd );
+}
+
+
 void CameraSpeed_increase(){
        if ( g_camwindow_globals_private.m_nMoveSpeed <= ( MAX_CAM_SPEED - CAM_SPEED_STEP - 10 ) ) {
                g_camwindow_globals_private.m_nMoveSpeed += CAM_SPEED_STEP;
@@ -1872,6 +2067,7 @@ void CameraSpeed_decrease(){
 /// \brief Initialisation for things that have the same lifespan as this module.
 void CamWnd_Construct(){
        GlobalCommands_insert( "CenterView", makeCallbackF(GlobalCamera_ResetAngles), Accelerator( GDK_KEY_End ) );
+       GlobalCommands_insert( "CameraFocusOnSelected", makeCallbackF( GlobalCamera_FocusOnSelected ), Accelerator( GDK_KEY_Tab ) );
 
        GlobalToggles_insert( "ToggleCubicClip", makeCallbackF(Camera_ToggleFarClip), ToggleItem::AddCallbackCaller( g_getfarclip_item ), Accelerator( '\\', (GdkModifierType)GDK_CONTROL_MASK ) );
        GlobalCommands_insert( "CubicClipZoomIn", makeCallbackF(Camera_CubeIn), Accelerator( '[', (GdkModifierType)GDK_CONTROL_MASK ) );
@@ -1881,13 +2077,16 @@ void CamWnd_Construct(){
        GlobalCommands_insert( "DownFloor", makeCallbackF(Camera_ChangeFloorDown), Accelerator( GDK_KEY_Next ) );
 
        GlobalToggles_insert( "ToggleCamera", ToggleShown::ToggleCaller( g_camera_shown ), ToggleItem::AddCallbackCaller( g_camera_shown.m_item ), Accelerator( 'C', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
-       GlobalCommands_insert( "LookThroughSelected", makeCallbackF(GlobalCamera_LookThroughSelected) );
-       GlobalCommands_insert( "LookThroughCamera", makeCallbackF(GlobalCamera_LookThroughCamera) );
+//     GlobalCommands_insert( "LookThroughSelected", makeCallbackF(GlobalCamera_LookThroughSelected) );
+//     GlobalCommands_insert( "LookThroughCamera", makeCallbackF(GlobalCamera_LookThroughCamera) );
 
        if ( g_pGameDescription->mGameType == "doom3" ) {
                GlobalCommands_insert( "TogglePreview", makeCallbackF(CamWnd_TogglePreview), Accelerator( GDK_KEY_F3 ) );
        }
 
+       GlobalCommands_insert( "FOVInc", makeCallbackF(FOV_increase), Accelerator( GDK_KEY_KP_Multiply, (GdkModifierType)GDK_SHIFT_MASK ) );
+       GlobalCommands_insert( "FOVDec", makeCallbackF(FOV_decrease), Accelerator( GDK_KEY_KP_Divide, (GdkModifierType)GDK_SHIFT_MASK ) );
+
        GlobalCommands_insert( "CameraSpeedInc", makeCallbackF(CameraSpeed_increase), Accelerator( GDK_KEY_KP_Add, (GdkModifierType)GDK_SHIFT_MASK ) );
        GlobalCommands_insert( "CameraSpeedDec", makeCallbackF(CameraSpeed_decrease), Accelerator( GDK_KEY_KP_Subtract, (GdkModifierType)GDK_SHIFT_MASK ) );
 
@@ -1895,22 +2094,33 @@ void CamWnd_Construct(){
        GlobalShortcuts_insert( "CameraBack", Accelerator( GDK_KEY_Down ) );
        GlobalShortcuts_insert( "CameraLeft", Accelerator( GDK_KEY_Left ) );
        GlobalShortcuts_insert( "CameraRight", Accelerator( GDK_KEY_Right ) );
-       GlobalShortcuts_insert( "CameraStrafeRight", Accelerator( GDK_KEY_period ) );
-       GlobalShortcuts_insert( "CameraStrafeLeft", Accelerator( GDK_KEY_comma ) );
+       GlobalShortcuts_insert( "CameraStrafeRight", Accelerator( 'D' ) );
+       GlobalShortcuts_insert( "CameraStrafeLeft", Accelerator( 'A' ) );
+
+       GlobalShortcuts_insert( "CameraUp", accelerator_null() );
+       GlobalShortcuts_insert( "CameraDown", accelerator_null() );
+       GlobalShortcuts_insert( "CameraAngleUp", accelerator_null() );
+       GlobalShortcuts_insert( "CameraAngleDown", accelerator_null() );
+
+       GlobalShortcuts_insert( "CameraFreeMoveForward", Accelerator( 'W' ) );
+       GlobalShortcuts_insert( "CameraFreeMoveBack", Accelerator( 'S' ) );
+       GlobalShortcuts_insert( "CameraFreeMoveLeft", Accelerator( 'A' ) );
+       GlobalShortcuts_insert( "CameraFreeMoveRight", Accelerator( 'D' ) );
+
+       GlobalShortcuts_insert( "CameraFreeMoveForward2", Accelerator( GDK_KEY_Up ) );
+       GlobalShortcuts_insert( "CameraFreeMoveBack2", Accelerator( GDK_KEY_Down ) );
+       GlobalShortcuts_insert( "CameraFreeMoveLeft2", Accelerator( GDK_KEY_Left ) );
+       GlobalShortcuts_insert( "CameraFreeMoveRight2", Accelerator( GDK_KEY_Right ) );
 
-       GlobalShortcuts_insert( "CameraUp", Accelerator( 'D' ) );
-       GlobalShortcuts_insert( "CameraDown", Accelerator( 'C' ) );
-       GlobalShortcuts_insert( "CameraAngleUp", Accelerator( 'A' ) );
-       GlobalShortcuts_insert( "CameraAngleDown", Accelerator( 'Z' ) );
+       GlobalShortcuts_insert( "CameraFreeMoveUp", accelerator_null() );
+       GlobalShortcuts_insert( "CameraFreeMoveDown", accelerator_null() );
 
-       GlobalShortcuts_insert( "CameraFreeMoveForward", Accelerator( GDK_KEY_Up ) );
-       GlobalShortcuts_insert( "CameraFreeMoveBack", Accelerator( GDK_KEY_Down ) );
-       GlobalShortcuts_insert( "CameraFreeMoveLeft", Accelerator( GDK_KEY_Left ) );
-       GlobalShortcuts_insert( "CameraFreeMoveRight", Accelerator( GDK_KEY_Right ) );
+       GlobalShortcuts_insert( "CameraFreeFocus", Accelerator( GDK_KEY_Tab ) );
 
        GlobalToggles_insert( "ShowStats", makeCallbackF(ShowStatsToggle), ToggleItem::AddCallbackCaller( g_show_stats ) );
 
        GlobalPreferenceSystem().registerPreference( "ShowStats", make_property_string( g_camwindow_globals_private.m_showStats ) );
+       GlobalPreferenceSystem().registerPreference( "FOV", make_property_string( g_camwindow_globals_private.m_nFOV ) );
        GlobalPreferenceSystem().registerPreference( "MoveSpeed", make_property_string( g_camwindow_globals_private.m_nMoveSpeed ) );
        GlobalPreferenceSystem().registerPreference( "CamLinkSpeed", make_property_string( g_camwindow_globals_private.m_bCamLinkSpeed ) );
        GlobalPreferenceSystem().registerPreference( "AngleSpeed", make_property_string( g_camwindow_globals_private.m_nAngleSpeed ) );
@@ -1922,6 +2132,7 @@ void CamWnd_Construct(){
        GlobalPreferenceSystem().registerPreference( "SI_Colors12", make_property_string( g_camwindow_globals.color_selbrushes3d ) );
        GlobalPreferenceSystem().registerPreference( "CameraRenderMode", make_property_string<RenderMode>() );
        GlobalPreferenceSystem().registerPreference( "StrafeMode", make_property_string( g_camwindow_globals_private.m_nStrafeMode ) );
+       GlobalPreferenceSystem().registerPreference( "3DZoomInToPointer", make_property_string( g_camwindow_globals.m_bZoomInToPointer ) );
 
        CamWnd_constructStatic();
 
index d0722a79cef512d3878e06b4b7714b80a9ca8f16..de5d6453b8e8acdc5e040510af8b9a35b58aa763 100644 (file)
@@ -39,7 +39,6 @@ void CamWnd_setParent( CamWnd& camwnd, ui::Window parent );
 
 void GlobalCamera_setCamWnd( CamWnd& camwnd );
 
-void fill_view_camera_menu( ui::Menu menu );
 void CamWnd_constructToolbar( ui::Toolbar toolbar );
 void CamWnd_registerShortcuts();
 
@@ -48,6 +47,8 @@ void GlobalCamera_Benchmark();
 const Vector3& Camera_getOrigin( CamWnd& camwnd );
 void Camera_setOrigin( CamWnd& camwnd, const Vector3& origin );
 
+void GlobalCamera_FocusOnSelected();
+
 enum
 {
        CAMERA_PITCH = 0, // up / down
@@ -66,10 +67,13 @@ struct camwindow_globals_t
 
        int m_nCubicScale;
 
+       bool m_bZoomInToPointer;
+
        camwindow_globals_t() :
                color_cameraback( 0.25f, 0.25f, 0.25f ),
                color_selbrushes3d( 1.0f, 0.f, 0.f ),
-               m_nCubicScale( 13 ){
+               m_nCubicScale( 26 ),
+               m_bZoomInToPointer( true ){
        }
 
 };
index 9c1b7682246febb16f70c082faba1da1b418dbd2..4276805658975a1da8eb03c6961e067dfb0b3a18 100644 (file)
 #include "gtkutil/messagebox.h"
 #include "gtkmisc.h"
 
+#define NETRADIANT_CUSTOM_FULLY_MERGED 0
+#if NETRADIANT_CUSTOM_FULLY_MERGED
+// For deleting old shortcuts.ini file
+#include "preferences.h"
+#include "unistd.h"
+#endif // NETRADIANT_CUSTOM_FULLY_MERGED
+
 typedef std::pair<Accelerator, int> ShortcutValue; // accelerator, isRegistered
 typedef std::map<CopiedString, ShortcutValue> Shortcuts;
 
@@ -219,6 +226,11 @@ void accelerator_edit_button_clicked( ui::Button btn, gpointer dialogptr ){
        if ( !gtk_tree_selection_get_selected( sel, &model, &iter ) ) {
                return;
        }
+       if ( dialog.m_waiting_for_key ) {
+               // unhighlight highlit
+               dialog.m_waiting_for_key = false;
+               gtk_list_store_set( GTK_LIST_STORE( dialog.m_model ), &dialog.m_command_iter, 2, false, -1 );
+       }
        dialog.m_command_iter = iter;
        dialog.m_model = ui::TreeModel::from(model);
 
@@ -232,6 +244,14 @@ void accelerator_edit_button_clicked( ui::Button btn, gpointer dialogptr ){
        dialog.m_waiting_for_key = true;
 }
 
+gboolean accelerator_tree_butt_press( GtkWidget* widget, GdkEventButton* event, gpointer dialogptr ){
+       if ( event->type == GDK_2BUTTON_PRESS && event->button == 1 ) {
+               accelerator_edit_button_clicked( ui::Button( ui::null ), dialogptr );
+               return TRUE;
+       }
+       return FALSE;
+}
+
 bool accelerator_window_key_press( ui::Window widget, GdkEventKey *event, gpointer dialogptr ){
        command_list_dialog_t &dialog = *(command_list_dialog_t *) dialogptr;
 
@@ -408,7 +428,9 @@ void DoCommandListDlg(){
                        auto view = ui::TreeView(ui::TreeModel::from(store._handle));
                        dialog.m_list = view;
 
-                       gtk_tree_view_set_enable_search(view, false ); // annoying
+                       //gtk_tree_view_set_enable_search( GTK_TREE_VIEW( view ), false ); // annoying
+
+                       g_signal_connect( G_OBJECT( view ), "button_press_event", G_CALLBACK( accelerator_tree_butt_press ), &dialog );
 
                        {
                                auto renderer = ui::CellRendererText(ui::New);
@@ -427,31 +449,19 @@ void DoCommandListDlg(){
 
                        {
                                // Initialize dialog
-                               StringOutputStream path( 256 );
-                               path << SettingsPath_get() << "commandlist.txt";
-                               globalOutputStream() << "Writing the command list to " << path.c_str() << "\n";
                                class BuildCommandList : public CommandVisitor
                                {
-                               TextFileOutputStream m_commandList;
                                ui::ListStore m_store;
 public:
-                               BuildCommandList( const char* filename, ui::ListStore store ) : m_commandList( filename ), m_store( store ){
+                               BuildCommandList( ui::ListStore store ) : m_store( store ){
                                }
                                void visit( const char* name, Accelerator& accelerator ){
                                        StringOutputStream modifiers;
                                        modifiers << accelerator;
 
                                        m_store.append(0, name, 1, modifiers.c_str(), 2, false, 3, 800);
-
-                                       if ( !m_commandList.failed() ) {
-                                               int l = strlen( name );
-                                               m_commandList << name;
-                                               while ( l++ < 25 )
-                                                       m_commandList << ' ';
-                                               m_commandList << modifiers.c_str() << '\n';
-                                       }
                                }
-                               } visitor( path.c_str(), store );
+                               } visitor( store );
 
                                GlobalShortcuts_foreach( visitor );
                        }
@@ -487,11 +497,21 @@ public:
 
 #include "profile/profile.h"
 
-const char* const COMMANDS_VERSION = "1.0-gtk-accelnames";
+const char* const COMMANDS_VERSION = "1.1-gtk-accelnames";
+
+void DeleteOldCommandMap(){
+#if NETRADIANT_CUSTOM_FULLY_MERGED
+// To enable when NetRadiant and NetRadiant-custom are fully merged together.
+       StringOutputStream path( 256 );
+       path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
+       path << "shortcuts.ini";
+       unlink( path.c_str() );
+#endif
+}
 
-void SaveCommandMap( const char* path ){
+void SaveCommandMap(){
        StringOutputStream strINI( 256 );
-       strINI << path << "shortcuts.ini";
+       strINI << SettingsPath_get() << "shortcuts.ini";
 
        TextFileOutputStream file( strINI.c_str() );
        if ( !file.failed() ) {
@@ -515,6 +535,8 @@ public:
                } visitor( file );
                GlobalShortcuts_foreach( visitor );
        }
+       
+       DeleteOldCommandMap();
 }
 
 const char* stringrange_find( const char* first, const char* last, char c ){
@@ -558,9 +580,9 @@ std::size_t count() const {
 }
 };
 
-void LoadCommandMap( const char* path ){
+void LoadCommandMap(){
        StringOutputStream strINI( 256 );
-       strINI << path << "shortcuts.ini";
+       strINI << SettingsPath_get() << "shortcuts.ini";
 
        FILE* f = fopen( strINI.c_str(), "r" );
        if ( f != 0 ) {
index 8c6b9f19063e1a9d272e34c80c654214f1bdb4ae..9e0e9059f4d491afd105cb029f9f3210d58d6163 100644 (file)
@@ -47,8 +47,8 @@ const KeyEvent& GlobalKeyEvents_find( const char* name );
 
 void DoCommandListDlg();
 
-void LoadCommandMap( const char* path );
-void SaveCommandMap( const char* path );
+void LoadCommandMap();
+void SaveCommandMap();
 
 
 #endif
index abb1def4655cb9fcb0c62ad3afbfdc2236dae0b7..214bbea771ebf0645b8c81f024c660bbb8422718 100644 (file)
@@ -72,6 +72,7 @@ void Sys_EnableLogFile( bool enable ){
                        time( &localtime );
                        globalOutputStream() << "Today is: " << ctime( &localtime )
                                                                 << "This is " RADIANT_NAME " " RADIANT_VERSION " compiled " __DATE__ "\n" RADIANT_ABOUTMSG "\n";
+                       globalOutputStream() << "IQM plugin: " RADIANT_IQM_PLUGIN "\n";
                }
                else{
                        ui::alert( ui::root, "Failed to create log file, check write permissions in " RADIANT_NAME " directory.\n",
@@ -140,6 +141,9 @@ ui::Widget Console_constructWindow( ui::Window toplevel ){
        return scr;
 }
 
+//#pragma GCC push_options
+//#pragma GCC optimize ("O0")
+
 class GtkTextBufferOutputStream : public TextOutputStream
 {
 GtkTextBuffer* textBuffer;
@@ -148,72 +152,74 @@ GtkTextTag* tag;
 public:
 GtkTextBufferOutputStream( GtkTextBuffer* textBuffer, GtkTextIter* iter, GtkTextTag* tag ) : textBuffer( textBuffer ), iter( iter ), tag( tag ){
 }
-std::size_t write( const char* buffer, std::size_t length ){
+std::size_t __attribute__((optimize("O0"))) write( const char* buffer, std::size_t length ){
        gtk_text_buffer_insert_with_tags( textBuffer, iter, buffer, gint( length ), tag, NULL );
        return length;
 }
 };
 
+//#pragma GCC pop_options
+
 // This function is meant to be used with gtk_idle_add. It will free its argument.
 static gboolean Gtk_Idle_Print( gpointer data ){
        Gtk_Idle_Print_Data *args = reinterpret_cast<Gtk_Idle_Print_Data *>(data);
        g_assert(g_console);
 
-       auto buffer = gtk_text_view_get_buffer( g_console );
+                       auto buffer = gtk_text_view_get_buffer( g_console );
 
-       GtkTextIter iter;
-       gtk_text_buffer_get_end_iter( buffer, &iter );
+                       GtkTextIter iter;
+                       gtk_text_buffer_get_end_iter( buffer, &iter );
 
-       static auto end = gtk_text_buffer_create_mark( buffer, "end", &iter, FALSE );
+                       static auto end = gtk_text_buffer_create_mark( buffer, "end", &iter, FALSE );
 
-       const GdkColor yellow = { 0, 0xb0ff, 0xb0ff, 0x0000 };
-       const GdkColor red = { 0, 0xffff, 0x0000, 0x0000 };
+                       const GdkColor yellow = { 0, 0xb0ff, 0xb0ff, 0x0000 };
+                       const GdkColor red = { 0, 0xffff, 0x0000, 0x0000 };
 
-       static auto error_tag = gtk_text_buffer_create_tag( buffer, "red_foreground", "foreground-gdk", &red, NULL );
-       static auto warning_tag = gtk_text_buffer_create_tag( buffer, "yellow_foreground", "foreground-gdk", &yellow, NULL );
-       static auto standard_tag = gtk_text_buffer_create_tag( buffer, "black_foreground", NULL );
-       GtkTextTag* tag;
+                       static auto error_tag = gtk_text_buffer_create_tag( buffer, "red_foreground", "foreground-gdk", &red, NULL );
+                       static auto warning_tag = gtk_text_buffer_create_tag( buffer, "yellow_foreground", "foreground-gdk", &yellow, NULL );
+                       static auto standard_tag = gtk_text_buffer_create_tag( buffer, "black_foreground", NULL );
+                       GtkTextTag* tag;
        switch ( args->level )
-       {
-       case SYS_WRN:
-               tag = warning_tag;
-               break;
-       case SYS_ERR:
-               tag = error_tag;
-               break;
-       case SYS_STD:
-       case SYS_VRB:
-       default:
-               tag = standard_tag;
-               break;
-       }
-
-       {
-               GtkTextBufferOutputStream textBuffer( buffer, &iter, tag );
-               if ( !globalCharacterSet().isUTF8() ) {
-                       BufferedTextOutputStream<GtkTextBufferOutputStream> buffered( textBuffer );
+                       {
+                       case SYS_WRN:
+                               tag = warning_tag;
+                               break;
+                       case SYS_ERR:
+                               tag = error_tag;
+                               break;
+                       case SYS_STD:
+                       case SYS_VRB:
+                       default:
+                               tag = standard_tag;
+                               break;
+                       }
+
+                       {
+                               GtkTextBufferOutputStream textBuffer( buffer, &iter, tag );
+                               if ( !globalCharacterSet().isUTF8() ) {
+                                       BufferedTextOutputStream<GtkTextBufferOutputStream> buffered( textBuffer );
                        buffered << StringRange( args->buf, args->buf + args->length );
-               }
-               else
-               {
+                               }
+                               else
+                               {
                        textBuffer << StringRange( args->buf, args->buf + args->length );
-               }
-       }
+                               }
+                       }
 
-       // update console widget immediatly if we're doing something time-consuming
+                       // update console widget immediatly if we're doing something time-consuming
        if ( args->contains_newline ) {
-               gtk_text_view_scroll_mark_onscreen( g_console, end );
+                               gtk_text_view_scroll_mark_onscreen( g_console, end );
 
-               if ( !ScreenUpdates_Enabled() && gtk_widget_get_realized( g_console ) ) {
-                       ScreenUpdates_process();
-               }
-       }
+                               if ( !ScreenUpdates_Enabled() && gtk_widget_get_realized( g_console ) ) {
+                                       ScreenUpdates_process();
+                               }
+                       }
 
        free( args->buf );
        free( args );
 
        return FALSE; // call this once, not repeatedly
-}
+               }
 
 // Print logs to the in-game console and/or to the log file.
 // This function is thread safe.
index f28bdc98d202d0465f8bac6654f4e05b2abbfe4b..5d20bf1c742b9753953e804201eee50a27109bc6 100644 (file)
 #include "brushnode.h"
 #include "grid.h"
 
+/*
 void Face_makeBrush( Face& face, const Brush& brush, brush_vector_t& out, float offset ){
        if ( face.contributes() ) {
                out.push_back( new Brush( brush ) );
                std::shared_ptr<Face> newFace = out.back()->addFace( face );
+               face.getPlane().offset( -offset );
+               face.planeChanged();
                if ( newFace != 0 ) {
                        newFace->flipWinding();
                        newFace->getPlane().offset( offset );
@@ -42,7 +45,7 @@ void Face_makeBrush( Face& face, const Brush& brush, brush_vector_t& out, float
        }
 }
 
-void Face_makeRoom( Face &face, const Brush &brush, brush_vector_t &out, float offset ){
+void Face_extrude( Face& face, const Brush& brush, brush_vector_t& out, float offset ){
        if ( face.contributes() ) {
                face.getPlane().offset( offset );
                out.push_back( new Brush( brush ) );
@@ -54,28 +57,293 @@ void Face_makeRoom( Face &face, const Brush &brush, brush_vector_t &out, float o
                }
        }
 }
+*/
+#include "preferences.h"
+#include "texwindow.h"
+
+typedef std::vector<DoubleVector3> doublevector_vector_t;
+
+enum eHollowType
+{
+       diag = 0,
+       wrap = 1,
+       extrude = 2,
+       pull = 3,
+       room = 4,
+};
 
-void Brush_makeHollow( const Brush &brush, brush_vector_t &out, float offset ){
-       Brush_forEachFace( brush, [&]( Face &face ) {
-               Face_makeBrush( face, brush, out, offset );
-       } );
+const char* getCaulkShader(){
+       const char* gotShader = g_pGameDescription->getKeyValue( "shader_caulk" );
+       if ( gotShader && *gotShader ){
+               return gotShader;
+       }
+       return "textures/common/caulk";
 }
 
-void Brush_makeRoom( const Brush &brush, brush_vector_t &out, float offset ){
-       Brush_forEachFace( brush, [&]( Face &face ) {
-               Face_makeRoom( face, brush, out, offset );
-       } );
+class CaulkFace
+{
+DoubleVector3 ExclusionAxis;
+double &mindot;
+double &maxdot;
+doublevector_vector_t &exclude_vec;
+public:
+CaulkFace( DoubleVector3 ExclusionAxis,
+                       double &mindot,
+                       double &maxdot,
+                       doublevector_vector_t &exclude_vec ):
+                       ExclusionAxis( ExclusionAxis ),
+                       mindot( mindot ),
+                       maxdot( maxdot ),
+                       exclude_vec( exclude_vec ){}
+void operator()( Face& face ) const {
+       double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
+       if( dot == 0 || ( dot > mindot + 0.005 && dot < maxdot - 0.005 ) ){
+               if( !exclude_vec.empty() ){
+                       for ( doublevector_vector_t::const_iterator i = exclude_vec.begin(); i != exclude_vec.end(); ++i ){
+                               if( ( *i ) == face.getPlane().plane3().normal() ){
+                                       return;
+                               }
+                       }
+               }
+               face.SetShader( getCaulkShader() );
+       }
 }
+};
 
-class BrushHollowSelectedWalker : public scene::Graph::Walker
+class FaceMakeBrush
+{
+const Brush& brush;
+brush_vector_t& out;
+float offset;
+eHollowType HollowType;
+DoubleVector3 ExclusionAxis;
+double &mindot;
+double &maxdot;
+doublevector_vector_t &exclude_vec;
+bool caulk;
+bool RemoveInner;
+public:
+FaceMakeBrush( const Brush& brush,
+                       brush_vector_t& out,
+                       float offset,
+                       eHollowType HollowType,
+                       DoubleVector3 ExclusionAxis,
+                       double &mindot,
+                       double &maxdot,
+                       doublevector_vector_t &exclude_vec,
+                       bool caulk,
+                       bool RemoveInner )
+       : brush( brush ),
+       out( out ),
+       offset( offset ),
+       HollowType( HollowType ),
+       ExclusionAxis( ExclusionAxis ),
+       mindot( mindot ),
+       maxdot( maxdot ),
+       exclude_vec( exclude_vec ),
+       caulk( caulk ),
+       RemoveInner( RemoveInner ){
+}
+void operator()( Face& face ) const {
+       double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
+       if( dot == 0 || ( dot > mindot + 0.005 && dot < maxdot - 0.005 ) ){
+               if( !exclude_vec.empty() ){
+                       for ( doublevector_vector_t::const_iterator i = exclude_vec.begin(); i != exclude_vec.end(); ++i ){
+                               if( ( *i ) == face.getPlane().plane3().normal() ){
+                                       return;
+                               }
+                       }
+               }
+
+               if( HollowType == pull ){
+                       if ( face.contributes() ) {
+                               face.getPlane().offset( offset );
+                               face.planeChanged();
+                               out.push_back( new Brush( brush ) );
+                               face.getPlane().offset( -offset );
+                               face.planeChanged();
+
+                               if( caulk ){
+                                       Brush_forEachFace( *out.back(), CaulkFace( ExclusionAxis, mindot, maxdot, exclude_vec ) );
+                               }
+                               std::shared_ptr<Face> newFace = out.back()->addFace( face );
+                               if ( newFace != 0 ) {
+                                       newFace->flipWinding();
+                               }
+                       }
+               }
+               else if( HollowType == wrap ){
+                       //Face_makeBrush( face, brush, out, offset );
+                       if ( face.contributes() ) {
+                               face.undoSave();
+                               out.push_back( new Brush( brush ) );
+                               if( !RemoveInner && caulk )
+                                       face.SetShader( getCaulkShader() );
+                               std::shared_ptr<Face> newFace = out.back()->addFace( face );
+                               face.getPlane().offset( -offset );
+                               face.planeChanged();
+                               if( caulk )
+                                       face.SetShader( getCaulkShader() );
+                               if ( newFace != 0 ) {
+                                       newFace->flipWinding();
+                                       newFace->getPlane().offset( offset );
+                                       newFace->planeChanged();
+                               }
+                       }
+               }
+               else if( HollowType == extrude ){
+                       if ( face.contributes() ) {
+                               //face.undoSave();
+                               out.push_back( new Brush( brush ) );
+                               out.back()->clear();
+
+                               std::shared_ptr<Face> newFace = out.back()->addFace( face );
+                               if ( newFace != 0 ) {
+                                       newFace->getPlane().offset( offset );
+                                       newFace->planeChanged();
+                               }
+
+                               if( !RemoveInner && caulk )
+                                       face.SetShader( getCaulkShader() );
+                               newFace = out.back()->addFace( face );
+                               if ( newFace != 0 ) {
+                                       newFace->flipWinding();
+                               }
+                               Winding& winding = face.getWinding();
+                               TextureProjection projection;
+                               TexDef_Construct_Default( projection );
+                               for ( Winding::iterator j = winding.begin(); j != winding.end(); ++j ){
+                                       std::size_t index = std::distance( winding.begin(), j );
+                                       std::size_t next = Winding_next( winding, index );
+
+                                       out.back()->addPlane( winding[index].vertex, winding[next].vertex, winding[next].vertex + face.getPlane().plane3().normal() * offset, TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ), projection );
+                               }
+                       }
+               }
+               else if( HollowType == diag ){
+                       if ( face.contributes() ) {
+                               out.push_back( new Brush( brush ) );
+                               out.back()->clear();
+
+                               std::shared_ptr<Face> newFace = out.back()->addFace( face );
+                               if ( newFace != 0 ) {
+
+                                       newFace->planeChanged();
+                               }
+                               newFace = out.back()->addFace( face );
+
+                               if ( newFace != 0 ) {
+                                       if( !RemoveInner && caulk )
+                                               newFace->SetShader( getCaulkShader() );
+                                       newFace->flipWinding();
+                                       newFace->getPlane().offset( offset );
+                                       newFace->planeChanged();
+                               }
+
+                               Winding& winding = face.getWinding();
+                               TextureProjection projection;
+                               TexDef_Construct_Default( projection );
+                               for ( Winding::iterator i = winding.begin(); i != winding.end(); ++i ){
+                                       std::size_t index = std::distance( winding.begin(), i );
+                                       std::size_t next = Winding_next( winding, index );
+                                       Vector3 BestPoint;
+                                       float bestdist = 999999;
+
+                                       for( Brush::const_iterator j = brush.begin(); j != brush.end(); ++j ){
+                                               Winding& winding2 = ( *j )->getWinding();
+                                               for ( Winding::iterator k = winding2.begin(); k != winding2.end(); ++k ){
+                                                       std::size_t index2 = std::distance( winding2.begin(), k );
+                                                       float testdist = vector3_length( winding[index].vertex - winding2[index2].vertex );
+                                                       if( testdist < bestdist ){
+                                                               bestdist = testdist;
+                                                               BestPoint = winding2[index2].vertex;
+                                                       }
+                                               }
+                                       }
+                                       out.back()->addPlane( winding[next].vertex, winding[index].vertex, BestPoint, caulk? getCaulkShader() : TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ), projection );
+                               }
+                       }
+               }
+       }
+}
+};
+
+class FaceExclude
 {
-float m_offset;
-bool m_makeRoom;
+DoubleVector3 ExclusionAxis;
+double &mindot;
+double &maxdot;
 public:
-BrushHollowSelectedWalker( float offset, bool makeRoom )
-       : m_offset( offset ), m_makeRoom( makeRoom ){
+FaceExclude( DoubleVector3 ExclusionAxis, double &mindot, double &maxdot )
+       : ExclusionAxis( ExclusionAxis ), mindot( mindot ), maxdot( maxdot ){
+}
+void operator()( Face& face ) const {
+       if( vector3_length_squared( ExclusionAxis ) != 0 ){
+               double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
+               if( dot < mindot ){
+                       mindot = dot;
+               }
+               else if( dot > maxdot ){
+                       maxdot = dot;
+               }
+       }
 }
+};
 
+class FaceOffset
+{
+float offset;
+DoubleVector3 ExclusionAxis;
+double &mindot;
+double &maxdot;
+doublevector_vector_t &exclude_vec;
+public:
+FaceOffset( float offset, DoubleVector3 ExclusionAxis, double &mindot, double &maxdot, doublevector_vector_t &exclude_vec )
+       : offset( offset ), ExclusionAxis( ExclusionAxis ), mindot( mindot ), maxdot( maxdot ), exclude_vec( exclude_vec ){
+}
+void operator()( Face& face ) const {
+       double dot = vector3_dot( face.getPlane().plane3().normal(), ExclusionAxis );
+       if( dot == 0 || ( dot > mindot + 0.005 && dot < maxdot - 0.005 ) ){
+               if( !exclude_vec.empty() ){
+                       for ( doublevector_vector_t::const_iterator i = exclude_vec.begin(); i != exclude_vec.end(); ++i ){
+                               if( ( *i ) == face.getPlane().plane3().normal() ){
+                                       return;
+                               }
+                       }
+               }
+               face.undoSave();
+               face.getPlane().offset( offset );
+               face.planeChanged();
+       }
+}
+};
+
+class FaceExcludeSelected
+{
+doublevector_vector_t &outvec;
+public:
+FaceExcludeSelected( doublevector_vector_t &outvec ): outvec( outvec ){
+}
+void operator()( FaceInstance& face ) const {
+       if( face.isSelected() ){
+               outvec.push_back( face.getFace().getPlane().plane3().normal() );
+       }
+}
+};
+
+
+DoubleVector3 getExclusion();
+bool getCaulk();
+bool getRemoveInner();
+
+class BrushHollowSelectedWalker : public scene::Graph::Walker
+{
+float offset;
+eHollowType HollowType;
+public:
+BrushHollowSelectedWalker( float offset, eHollowType HollowType )
+       : offset( offset ), HollowType( HollowType ){
+}
 bool pre( const scene::Path& path, scene::Instance& instance ) const {
        if ( path.top().get().visible() ) {
                Brush* brush = Node_getBrush( path.top() );
@@ -83,22 +351,56 @@ bool pre( const scene::Path& path, scene::Instance& instance ) const {
                         && Instance_getSelectable( instance )->isSelected()
                         && path.size() > 1 ) {
                        brush_vector_t out;
-
-                       if ( m_makeRoom ) {
-                               Brush_makeRoom(* brush, out, m_offset );
+                       doublevector_vector_t exclude_vec;
+                       double mindot = 0;
+                       double maxdot = 0;
+                       if( HollowType != room ){
+                               Brush_forEachFace( *brush, FaceExclude( getExclusion(), mindot, maxdot ) );
+                               if( mindot == 0 && maxdot == 0 ){
+                                       Brush_ForEachFaceInstance( *Instance_getBrush( instance ), FaceExcludeSelected( exclude_vec ) );
+                               }
                        }
-                       else
-                       {
-                               Brush_makeHollow(* brush, out, m_offset );
+                       if( HollowType == room ){
+                               Brush* tmpbrush = new Brush( *brush );
+                               tmpbrush->removeEmptyFaces();
+                               Brush_forEachFace( *brush, FaceMakeBrush( *brush, out, offset, pull, DoubleVector3( 0, 0, 0 ), mindot, maxdot, exclude_vec, true, true ) );
+                               delete tmpbrush;
+                       }
+                       else if( HollowType == pull ){
+                               if( !getRemoveInner() && getCaulk() ){
+                                       Brush_forEachFace( *brush, CaulkFace( getExclusion(), mindot, maxdot, exclude_vec ) );
+                               }
+                               Brush* tmpbrush = new Brush( *brush );
+                               tmpbrush->removeEmptyFaces();
+                               Brush_forEachFace( *tmpbrush, FaceMakeBrush( *tmpbrush, out, offset, HollowType, getExclusion(), mindot, maxdot, exclude_vec, getCaulk(), getRemoveInner() ) );
+                               delete tmpbrush;
+                       }
+                       else if( HollowType == diag ){
+                               Brush* tmpbrush = new Brush( *brush );
+                               Brush_forEachFace( *tmpbrush, FaceOffset( offset, getExclusion(), mindot, maxdot, exclude_vec ) );
+                               tmpbrush->removeEmptyFaces();
+                               Brush_forEachFace( *tmpbrush, FaceMakeBrush( *brush, out, offset, HollowType, getExclusion(), mindot, maxdot, exclude_vec, getCaulk(), getRemoveInner() ) );
+                               delete tmpbrush;
+                               if( !getRemoveInner() && getCaulk() ){
+                                       Brush_forEachFace( *brush, CaulkFace( getExclusion(), mindot, maxdot, exclude_vec ) );
+                               }
+                       }
+                       else{
+                               Brush_forEachFace( *brush, FaceMakeBrush( *brush, out, offset, HollowType, getExclusion(), mindot, maxdot, exclude_vec, getCaulk(), getRemoveInner() ) );
                        }
-
                        for ( brush_vector_t::const_iterator i = out.begin(); i != out.end(); ++i )
                        {
                                ( *i )->removeEmptyFaces();
+                               if( ( *i )->hasContributingFaces() ){
                                NodeSmartReference node( ( new BrushNode() )->node() );
                                Node_getBrush( node )->copy( *( *i ) );
                                delete ( *i );
                                Node_getTraversable( path.parent() )->insert( node );
+                                       //path.push( makeReference( node.get() ) );
+                                       //selectPath( path, true );
+                                       //Instance_getSelectable( *GlobalSceneGraph().find( path ) )->setSelected( true );
+                                       //Path_deleteTop( path );
+                               }
                        }
                }
        }
@@ -127,7 +429,7 @@ bool pre( const scene::Path& path, scene::Instance& instance ) const {
        return true;
 }
 };
-
+/*
 class BrushDeleteSelected : public scene::Graph::Walker
 {
 public:
@@ -145,31 +447,58 @@ void post( const scene::Path& path, scene::Instance& instance ) const {
        }
 }
 };
+*/
+#include "ientity.h"
 
-void Scene_BrushMakeHollow_Selected( scene::Graph& graph, bool makeRoom ){
-       GlobalSceneGraph().traverse( BrushHollowSelectedWalker( GetGridSize(), makeRoom ) );
-       GlobalSceneGraph().traverse( BrushDeleteSelected() );
+class BrushDeleteSelected : public scene::Graph::Walker
+{
+scene::Node* m_keepNode;
+mutable bool m_eraseParent;
+public:
+BrushDeleteSelected( scene::Node* keepNode ): m_keepNode( keepNode ), m_eraseParent( false ){
+}
+BrushDeleteSelected(): m_keepNode( NULL ), m_eraseParent( false ){
+}
+bool pre( const scene::Path& path, scene::Instance& instance ) const {
+       return true;
 }
+void post( const scene::Path& path, scene::Instance& instance ) const {
+       //globalOutputStream() << path.size() << "\n";
+       if ( path.top().get().visible() ) {
+               Brush* brush = Node_getBrush( path.top() );
+               if ( brush != 0
+                        && Instance_getSelectable( instance )->isSelected()
+                        && path.size() > 1 ) {
+                       scene::Node& parent = path.parent();
+                       Path_deleteTop( path );
+                       if( Node_getTraversable( parent )->empty() ){
+                               m_eraseParent = true;
+                               //globalOutputStream() << "Empty node?!.\n";
+                       }
+               }
+       }
+       if( m_eraseParent && !Node_isPrimitive( path.top() ) && path.size() > 1 ){
+               //globalOutputStream() << "about to Delete empty node!.\n";
+               m_eraseParent = false;
+               Entity* entity = Node_getEntity( path.top() );
+               if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map )
+                       && Node_getTraversable( path.top() )->empty() && path.top().get_pointer() != m_keepNode ) {
+                       //globalOutputStream() << "now Deleting empty node!.\n";
+                       Path_deleteTop( path );
+               }
+       }
+}
+};
 
 /*
    =============
-   CSG_MakeHollow
+   CSG_MakeRoom
    =============
  */
-
-void CSG_MakeHollow( void ){
-       UndoableCommand undo( "brushHollow" );
-
-       Scene_BrushMakeHollow_Selected( GlobalSceneGraph(), false );
-
-       SceneChangeNotify();
-}
-
 void CSG_MakeRoom( void ){
-       UndoableCommand undo( "brushRoom" );
-
-       Scene_BrushMakeHollow_Selected( GlobalSceneGraph(), true );
-
+       UndoableCommand undo( "makeRoom" );
+       GlobalSceneGraph().traverse( BrushHollowSelectedWalker( GetGridSize(), room ) );
+       GlobalSceneGraph().traverse( BrushDeleteSelected() );
        SceneChangeNotify();
 }
 
@@ -313,13 +642,17 @@ class SubtractBrushesFromUnselected : public scene::Graph::Walker
 const brush_vector_t& m_brushlist;
 std::size_t& m_before;
 std::size_t& m_after;
+mutable bool m_eraseParent;
 public:
 SubtractBrushesFromUnselected( const brush_vector_t& brushlist, std::size_t& before, std::size_t& after )
-       : m_brushlist( brushlist ), m_before( before ), m_after( after ){
+       : m_brushlist( brushlist ), m_before( before ), m_after( after ), m_eraseParent( false ){
 }
 
 bool pre( const scene::Path& path, scene::Instance& instance ) const {
-       return true;
+       if ( path.top().get().visible() ) {
+               return true;
+       }
+       return false;
 }
 
 void post( const scene::Path& path, scene::Instance& instance ) const {
@@ -368,10 +701,22 @@ void post( const scene::Path& path, scene::Instance& instance ) const {
                                        }
                                        delete b;
                                }
+                               scene::Node& parent = path.parent();
                                Path_deleteTop( path );
+                               if( Node_getTraversable( parent )->empty() ){
+                                       m_eraseParent = true;
+                               }
                        }
                }
        }
+       if( m_eraseParent && !Node_isPrimitive( path.top() ) && path.size() > 1 ){
+               m_eraseParent = false;
+               Entity* entity = Node_getEntity( path.top() );
+               if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map )
+                       && Node_getTraversable( path.top() )->empty() ) {
+                       Path_deleteTop( path );
+               }
+       }
 }
 };
 
@@ -420,51 +765,51 @@ void post( const scene::Path& path, scene::Instance& instance ) const {
                return;
        }
 
-       Brush* brush = Node_getBrush( path.top() );
+               Brush* brush = Node_getBrush( path.top() );
        if ( brush == nullptr || !Instance_getSelectable( instance )->isSelected() ) {
                return;
        }
 
-       Plane3 plane( plane3_for_points( m_p0, m_p1, m_p2 ) );
+                       Plane3 plane( plane3_for_points( m_p0, m_p1, m_p2 ) );
        if ( !plane3_valid( plane ) ) {
                return;
        }
 
-       brushsplit_t split = Brush_classifyPlane( *brush, m_split == eFront ? plane3_flipped( plane ) : plane );
-       if ( split.counts[ePlaneBack] && split.counts[ePlaneFront] ) {
-               // the plane intersects this brush
-               if ( m_split == eFrontAndBack ) {
-                       NodeSmartReference node( ( new BrushNode() )->node() );
-                       Brush* fragment = Node_getBrush( node );
-                       fragment->copy( *brush );
+                               brushsplit_t split = Brush_classifyPlane( *brush, m_split == eFront ? plane3_flipped( plane ) : plane );
+                               if ( split.counts[ePlaneBack] && split.counts[ePlaneFront] ) {
+                                       // the plane intersects this brush
+                                       if ( m_split == eFrontAndBack ) {
+                                               NodeSmartReference node( ( new BrushNode() )->node() );
+                                               Brush* fragment = Node_getBrush( node );
+                                               fragment->copy( *brush );
                        std::shared_ptr<Face> newFace =
                                fragment->addPlane( m_p0, m_p1, m_p2, m_shader, m_projection );
-                       if ( newFace != 0 && m_split != eFront ) {
-                               newFace->flipWinding();
-                       }
-                       fragment->removeEmptyFaces();
-                       ASSERT_MESSAGE( !fragment->empty(), "brush left with no faces after split" );
+                                               if ( newFace != 0 && m_split != eFront ) {
+                                                       newFace->flipWinding();
+                                               }
+                                               fragment->removeEmptyFaces();
+                                               ASSERT_MESSAGE( !fragment->empty(), "brush left with no faces after split" );
 
-                       Node_getTraversable( path.parent() )->insert( node );
-                       {
-                               scene::Path fragmentPath = path;
-                               fragmentPath.top() = makeReference( node.get() );
-                               selectPath( fragmentPath, true );
-                       }
-               }
+                                               Node_getTraversable( path.parent() )->insert( node );
+                                               {
+                                                       scene::Path fragmentPath = path;
+                                                       fragmentPath.top() = makeReference( node.get() );
+                                                       selectPath( fragmentPath, true );
+                                               }
+                                       }
 
                std::shared_ptr<Face> newFace = brush->addPlane( m_p0, m_p1, m_p2, m_shader, m_projection );
-               if ( newFace != 0 && m_split == eFront ) {
-                       newFace->flipWinding();
-               }
-               brush->removeEmptyFaces();
-               ASSERT_MESSAGE( !brush->empty(), "brush left with no faces after split" );
-       }
-       else
-       // the plane does not intersect this brush
-       if ( m_split != eFrontAndBack && split.counts[ePlaneBack] != 0 ) {
-               // the brush is "behind" the plane
-               Path_deleteTop( path );
+                                       if ( newFace != 0 && m_split == eFront ) {
+                                               newFace->flipWinding();
+                                       }
+                                       brush->removeEmptyFaces();
+                                       ASSERT_MESSAGE( !brush->empty(), "brush left with no faces after split" );
+                               }
+                               else
+                               // the plane does not intersect this brush
+                               if ( m_split != eFrontAndBack && split.counts[ePlaneBack] != 0 ) {
+                                       // the brush is "behind" the plane
+                                       Path_deleteTop( path );
        }
 }
 };
@@ -618,7 +963,7 @@ void CSG_Merge( void ){
                ASSERT_MESSAGE( !brush->empty(), "brush left with no faces after merge" );
 
                // free the original brushes
-               GlobalSceneGraph().traverse( BrushDeleteSelected() );
+               GlobalSceneGraph().traverse( BrushDeleteSelected( merged_path.parent().get_pointer() ) );
 
                merged_path.pop();
                Node_getTraversable( merged_path.top() )->insert( node );
@@ -630,3 +975,327 @@ void CSG_Merge( void ){
                SceneChangeNotify();
        }
 }
+
+
+
+
+
+
+/*
+   =============
+   CSG_Tool
+   =============
+ */
+#include "mainframe.h"
+#include <gtk/gtk.h>
+#include "gtkutil/dialog.h"
+#include "gtkutil/button.h"
+#include "gtkutil/accelerator.h"
+#include "xywindow.h"
+#include "camwindow.h"
+
+struct CSGToolDialog
+{
+       GtkSpinButton* spin;
+       bool allocated{false};
+       ui::Window window{ui::null};
+       GtkToggleButton *radFaces, *radProj, *radCam, *caulk, *removeInner;
+};
+
+CSGToolDialog g_csgtool_dialog;
+
+DoubleVector3 getExclusion(){
+       if( gtk_toggle_button_get_active( g_csgtool_dialog.radProj ) ){
+               if( GlobalXYWnd_getCurrentViewType() == YZ ){
+                       return DoubleVector3( 1, 0, 0 );
+               }
+               else if( GlobalXYWnd_getCurrentViewType() == XZ ){
+                       return DoubleVector3( 0, 1, 0 );
+               }
+               else if( GlobalXYWnd_getCurrentViewType() == XY ){
+                       return DoubleVector3( 0, 0, 1 );
+               }
+       }
+       if( gtk_toggle_button_get_active( g_csgtool_dialog.radCam ) ){
+               Vector3 angles( Camera_getAngles( *g_pParentWnd->GetCamWnd() ) );
+//             globalOutputStream() << angles << " angles\n";
+               DoubleVector3 radangles( degrees_to_radians( angles[0] ), degrees_to_radians( angles[1] ), degrees_to_radians( angles[2] ) );
+//             globalOutputStream() << radangles << " radangles\n";
+//             x = cos(yaw)*cos(pitch)
+//             y = sin(yaw)*cos(pitch)
+//             z = sin(pitch)
+               DoubleVector3 viewvector;
+               viewvector[0] = cos( radangles[1] ) * cos( radangles[0] );
+               viewvector[1] = sin( radangles[1] ) * cos( radangles[0] );
+               viewvector[2] = sin( radangles[0] );
+//             globalOutputStream() << viewvector << " viewvector\n";
+               return viewvector;
+       }
+       return DoubleVector3( 0, 0, 0 );
+}
+
+bool getCaulk(){
+               if( gtk_toggle_button_get_active( g_csgtool_dialog.caulk ) ){
+               return true;
+       }
+       return false;
+}
+
+bool getRemoveInner(){
+               if( gtk_toggle_button_get_active( g_csgtool_dialog.removeInner ) ){
+               return true;
+       }
+       return false;
+}
+
+class BrushFaceOffset
+{
+float offset;
+public:
+BrushFaceOffset( float offset )
+       : offset( offset ){
+}
+void operator()( BrushInstance& brush ) const {
+       double mindot = 0;
+       double maxdot = 0;
+       doublevector_vector_t exclude_vec;
+       Brush_forEachFace( brush, FaceExclude( getExclusion(), mindot, maxdot ) );
+       if( mindot == 0 && maxdot == 0 ){
+               Brush_ForEachFaceInstance( brush, FaceExcludeSelected( exclude_vec ) );
+       }
+       Brush_forEachFace( brush, FaceOffset( offset, getExclusion(), mindot, maxdot, exclude_vec ) );
+}
+};
+
+//=================DLG
+
+static gboolean CSGdlg_HollowDiag( GtkWidget *widget, CSGToolDialog* dialog ){
+       float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
+       UndoableCommand undo( "brushHollow::Diag" );
+       GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, diag ) );
+       if( getRemoveInner() )
+               GlobalSceneGraph().traverse( BrushDeleteSelected() );
+       SceneChangeNotify();
+       return TRUE;
+}
+
+static gboolean CSGdlg_HollowWrap( GtkWidget *widget, CSGToolDialog* dialog ){
+       float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
+       UndoableCommand undo( "brushHollow::Wrap" );
+       GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, wrap ) );
+       if( getRemoveInner() )
+               GlobalSceneGraph().traverse( BrushDeleteSelected() );
+       SceneChangeNotify();
+       return TRUE;
+}
+
+static gboolean CSGdlg_HollowExtrude( GtkWidget *widget, CSGToolDialog* dialog ){
+       float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
+       UndoableCommand undo( "brushHollow::Extrude" );
+       GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, extrude ) );
+       if( getRemoveInner() )
+               GlobalSceneGraph().traverse( BrushDeleteSelected() );
+       SceneChangeNotify();
+       return TRUE;
+}
+
+static gboolean CSGdlg_HollowPull( GtkWidget *widget, CSGToolDialog* dialog ){
+       float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
+       UndoableCommand undo( "brushHollow::Pull" );
+       GlobalSceneGraph().traverse( BrushHollowSelectedWalker( offset, pull ) );
+       if( getRemoveInner() )
+               GlobalSceneGraph().traverse( BrushDeleteSelected() );
+       SceneChangeNotify();
+       return TRUE;
+}
+
+static gboolean CSGdlg_BrushShrink( GtkWidget *widget, CSGToolDialog* dialog ){
+       gtk_spin_button_update ( dialog->spin );
+       float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
+       offset *= -1;
+       UndoableCommand undo( "Shrink brush" );
+//     GlobalSceneGraph().traverse( OffsetBrushFacesSelectedWalker( offset ) );
+       //Scene_ForEachSelectedBrush_ForEachFace( GlobalSceneGraph(), BrushFaceOffset( offset ) );
+       Scene_forEachSelectedBrush( BrushFaceOffset( offset ) );
+       SceneChangeNotify();
+       return TRUE;
+}
+
+static gboolean CSGdlg_BrushExpand( GtkWidget *widget, CSGToolDialog* dialog ){
+       gtk_spin_button_update ( dialog->spin );
+       float offset = static_cast<float>( gtk_spin_button_get_value( dialog->spin ) );
+       UndoableCommand undo( "Expand brush" );
+//     GlobalSceneGraph().traverse( OffsetBrushFacesSelectedWalker( offset ) );
+       //Scene_ForEachSelectedBrush_ForEachFace( GlobalSceneGraph(), BrushFaceOffset( offset ) );
+       Scene_forEachSelectedBrush( BrushFaceOffset( offset ) );
+       SceneChangeNotify();
+       return TRUE;
+}
+
+static gboolean CSGdlg_grid2spin( GtkWidget *widget, CSGToolDialog* dialog ){
+       gtk_spin_button_set_value( dialog->spin, GetGridSize() );
+       return TRUE;
+}
+
+static gboolean CSGdlg_delete( GtkWidget *widget, GdkEventAny *event, CSGToolDialog* dialog ){
+       gtk_widget_hide( GTK_WIDGET( dialog->window ) );
+       return TRUE;
+}
+
+void CSG_Tool(){
+       // FIXME: there is probably improvements to do less raw GTK stuff, more GTK wrapper
+       if ( !g_csgtool_dialog.allocated ) {
+               g_csgtool_dialog.allocated = true;
+               g_csgtool_dialog.window = MainFrame_getWindow().create_dialog_window( "CSG Tool", G_CALLBACK( CSGdlg_delete ), &g_csgtool_dialog );
+               gtk_window_set_type_hint( g_csgtool_dialog.window, GDK_WINDOW_TYPE_HINT_UTILITY );
+
+               //GtkAccelGroup* accel = gtk_accel_group_new();
+               //gtk_window_add_accel_group( g_csgtool_dialog.window, accel );
+               global_accel_connect_window( g_csgtool_dialog.window );
+
+               {
+                       auto hbox = create_dialog_hbox( 4, 4 );
+                       gtk_container_add( GTK_CONTAINER( g_csgtool_dialog.window ), GTK_WIDGET( hbox ) );
+                       {
+                               auto table = create_dialog_table( 3, 8, 4, 4 );
+                               gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
+                               {
+                                       //GtkWidget* label = gtk_label_new( "<->" );
+                                       //gtk_widget_show( label );
+                                       auto button = ui::Button( "Grid->" );
+                                       table.attach( button, {0, 1, 0, 1}, {0, 0} );
+                                       button.show();
+                                       g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_grid2spin ), &g_csgtool_dialog );
+                               }
+                               {
+                                       GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 16, 0, 9999, 1, 10, 0 ) );
+                                       GtkSpinButton* spin = GTK_SPIN_BUTTON( gtk_spin_button_new( adj, 1, 3 ) );
+                                       gtk_widget_show( GTK_WIDGET( spin ) );
+                                       gtk_widget_set_tooltip_text( GTK_WIDGET( spin ), "Thickness" );
+                                       gtk_table_attach( table, GTK_WIDGET( spin ), 1, 2, 0, 1,
+                                                                         (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
+                                                                         (GtkAttachOptions) ( 0 ), 0, 0 );
+                                       gtk_widget_set_size_request( GTK_WIDGET( spin ), 64, -1 );
+                                       gtk_spin_button_set_numeric( spin, TRUE );
+
+                                       g_csgtool_dialog.spin = spin;
+                               }
+                               {
+                                       //radio button group for choosing the exclude axis
+                                       GtkWidget* radFaces = gtk_radio_button_new_with_label( NULL, "-faces" );
+                                       gtk_widget_set_tooltip_text( radFaces, "Exclude selected faces" );
+                                       GtkWidget* radProj = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(radFaces), "-proj" );
+                                       gtk_widget_set_tooltip_text( radProj, "Exclude faces, most orthogonal to active projection" );
+                                       GtkWidget* radCam = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(radFaces), "-cam" );
+                                       gtk_widget_set_tooltip_text( radCam, "Exclude faces, most orthogonal to camera view" );
+
+                                       gtk_widget_show( radFaces );
+                                       gtk_widget_show( radProj );
+                                       gtk_widget_show( radCam );
+
+                                       gtk_table_attach( table, radFaces, 2, 3, 0, 1,
+                                                                       (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
+                                                                       (GtkAttachOptions) ( 0 ), 0, 0 );
+                                       gtk_table_attach( table, radProj, 3, 4, 0, 1,
+                                                                       (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
+                                                                       (GtkAttachOptions) ( 0 ), 0, 0 );
+                                       gtk_table_attach( table, radCam, 4, 5, 0, 1,
+                                                                       (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
+                                                                       (GtkAttachOptions) ( 0 ), 0, 0 );
+
+                                       g_csgtool_dialog.radFaces = GTK_TOGGLE_BUTTON( radFaces );
+                                       g_csgtool_dialog.radProj = GTK_TOGGLE_BUTTON( radProj );
+                                       g_csgtool_dialog.radCam = GTK_TOGGLE_BUTTON( radCam );
+                               }
+                               {
+                                       GtkWidget* button = gtk_toggle_button_new();
+                                       auto ubutton = ui::Button::from( button );
+                                       button_set_icon( ubutton, "f-caulk.png" );
+                                       gtk_button_set_relief( GTK_BUTTON( button ), GTK_RELIEF_NONE );
+                                       table.attach( ubutton, { 6, 7, 0, 1 }, { GTK_EXPAND, 0 } );
+                                       gtk_widget_set_tooltip_text( button, "Caulk some faces" );
+                                       gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( button ), TRUE );
+                                       ubutton.show();
+                                       g_csgtool_dialog.caulk = GTK_TOGGLE_BUTTON( button );
+                               }
+                               {
+                                       GtkWidget* button = gtk_toggle_button_new();
+                                       auto ubutton = ui::Button::from( button );
+                                       button_set_icon( ubutton, "csgtool_removeinner.png" );
+                                       gtk_button_set_relief( GTK_BUTTON( button ), GTK_RELIEF_NONE );
+                                       table.attach( ubutton, { 7, 8, 0, 1 }, { GTK_EXPAND, 0 } );
+                                       gtk_widget_set_tooltip_text( button, "Remove inner brush" );
+                                       gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( button ), TRUE );
+                                       ubutton.show();
+                                       g_csgtool_dialog.removeInner = GTK_TOGGLE_BUTTON( button );
+                               }
+                               {
+                                       GtkWidget* sep = gtk_hseparator_new();
+                                       gtk_widget_show( sep );
+                                       gtk_table_attach( table, sep, 0, 8, 1, 2,
+                                                                       (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
+                                                                       (GtkAttachOptions) ( 0 ), 0, 0 );
+                               }
+                               {
+                                       GtkWidget* button = gtk_button_new();
+                                       auto ubutton = ui::Button::from( button );
+                                       button_set_icon( ubutton, "csgtool_shrink.png" );
+                                       table.attach( ubutton, { 0, 1, 2, 3 }, { GTK_EXPAND, 0 } );
+                                       gtk_widget_set_tooltip_text( button, "Shrink brush" );
+                                       ubutton.show();
+                                       g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_BrushShrink ), &g_csgtool_dialog );
+                               }
+                               {
+                                       GtkWidget* button = gtk_button_new();
+                                       auto ubutton = ui::Button::from( button );
+                                       button_set_icon( ubutton, "csgtool_expand.png" );
+                                       table.attach( ubutton, { 1, 2, 2, 3 }, { GTK_EXPAND, 0 } );
+                                       gtk_widget_set_tooltip_text( button, "Expand brush" );
+                                       ubutton.show();
+                                       g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_BrushExpand ), &g_csgtool_dialog );
+                               }
+                               {
+                                       GtkWidget* button = gtk_button_new();
+                                       auto ubutton = ui::Button::from( button );
+                                       button_set_icon( ubutton, "csgtool_diagonal.png" );
+                                       table.attach( ubutton, { 3, 4, 2, 3 }, { GTK_EXPAND, 0 } );
+                                       gtk_widget_set_tooltip_text( button, "Hollow::diagonal joints" );
+                                       ubutton.show();
+                                       g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowDiag ), &g_csgtool_dialog );
+                               }
+                               {
+                                       GtkWidget* button = gtk_button_new();
+                                       auto ubutton = ui::Button::from( button );
+                                       button_set_icon( ubutton, "csgtool_wrap.png" );
+                                       table.attach( ubutton, { 4, 5, 2, 3 }, { GTK_EXPAND, 0 } );
+                                       gtk_widget_set_tooltip_text( button, "Hollow::warp" );
+                                       ubutton.show();
+                                       g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowWrap ), &g_csgtool_dialog );
+                               }
+                               {
+                                       GtkWidget* button = gtk_button_new();
+                                       auto ubutton = ui::Button::from( button );
+                                       button_set_icon( ubutton, "csgtool_extrude.png" );
+                                       table.attach( ubutton, { 5, 6, 2, 3 }, { GTK_EXPAND, 0 } );
+                                       gtk_widget_set_tooltip_text( button, "Hollow::extrude faces" );
+                                       ubutton.show();
+                                       g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowExtrude ), &g_csgtool_dialog );
+                               }
+                               {
+                                       GtkWidget* button = gtk_button_new();
+                                       auto ubutton = ui::Button::from( button );
+                                       button_set_icon( ubutton, "csgtool_pull.png" );
+                                       table.attach( ubutton, { 6, 7, 2, 3 }, { GTK_EXPAND, 0 } );
+                                       gtk_widget_set_tooltip_text( button, "Hollow::pull faces" );
+                                       ubutton.show();
+                                       g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( CSGdlg_HollowPull ), &g_csgtool_dialog );
+                               }
+
+                       }
+               }
+       }
+
+       gtk_widget_show( GTK_WIDGET( g_csgtool_dialog.window ) );
+       gtk_window_present( g_csgtool_dialog.window );
+}
+
index e05c9effbb4b65ab408e9be5637cd071c6d851cd..082f7f10ff711c102b9d3093e0a66dc25d9315ca 100644 (file)
@@ -22,7 +22,6 @@
 #if !defined( INCLUDED_CSG_H )
 #define INCLUDED_CSG_H
 
-void CSG_MakeHollow( void );
 
 void CSG_MakeRoom( void );
 
@@ -30,6 +29,8 @@ void CSG_Subtract( void );
 
 void CSG_Merge( void );
 
+void CSG_Tool( void );
+
 namespace scene
 {
 class Graph;
index 4c7525ea4fdb9219bd60c0b958946954c5b2db5a..b6a5344037ef8b6038e51bc8ce2dcf2f30947f7c 100644 (file)
@@ -84,7 +84,7 @@ inline void printParseError( const char* message ){
        globalErrorStream() << message;
 }
 
-#define PARSE_RETURN_FALSE_IF_FAIL(expression) do { if (!( expression)) { printParseError(FILE_LINE "\nparse failed: " #expression "\n"); return false; } } while (0)
+#define PARSE_RETURN_FALSE_IF_FAIL( expression ) do{ if ( !( expression ) ) { printParseError( FILE_LINE "\nparse failed: " # expression "\n" ); return false; } }while( 0 )
 
 bool EntityClassDoom3_parseToken( Tokeniser& tokeniser ){
        const char* token = tokeniser.getToken();
index b92afbfc6f1980a9676bfcf37edd338a5e517e71..123adfc6e11d5bfc2ccf0f2e16f6fa7076cbfb37 100644 (file)
@@ -88,11 +88,32 @@ void EntityClassFGD_forEach( EntityClassVisitor& visitor ){
        }
 }
 
+#define PARSE_ERROR "error parsing fgd entity class definition at line " << tokeniser.getLine() << ':' << tokeniser.getColumn()
+
+static bool s_fgd_warned = false;
+
 inline bool EntityClassFGD_parseToken( Tokeniser& tokeniser, const char* token ){
-       return string_equal( tokeniser.getToken(), token );
+       const bool w = s_fgd_warned;
+       const bool ok = string_equal( tokeniser.getToken(), token );
+       if( !ok ){
+               globalErrorStream() << PARSE_ERROR << "\nExpected " << makeQuoted( token ) << '\n';
+               s_fgd_warned = true;
+}
+       return w || ok;
 }
 
-const char *PARSE_ERROR = "error parsing entity class definition";
+/* FIXME
+#define ERROR_FGD( message )\
+do{\
+       if( s_fgd_warned )\
+               globalErrorStream() << message << '\n';\
+       else{\
+               ERROR_MESSAGE( message );\
+               s_fgd_warned = true;\
+       }\
+}while( 0 )
+*/
+#define ERROR_FGD( message ) {}
 
 void EntityClassFGD_parseSplitString( Tokeniser& tokeniser, CopiedString& string ){
        StringOutputStream buffer( 256 );
@@ -186,7 +207,6 @@ void EntityClassFGD_parseClass( Tokeniser& tokeniser, bool fixedsize, bool isBas
                // hl2 below
                else if ( string_equal( property, "sphere" )
                                  || string_equal( property, "sweptplayerhull" )
-                                 || string_equal( property, "studio" )
                                  || string_equal( property, "studioprop" )
                                  || string_equal( property, "lightprop" )
                                  || string_equal( property, "lightcone" )
@@ -197,6 +217,17 @@ void EntityClassFGD_parseClass( Tokeniser& tokeniser, bool fixedsize, bool isBas
                        }
                        ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );
                }
+               else if ( string_equal( property, "studio" ) ) {
+                       ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
+                       const char *token = tokeniser.getToken();
+                       if ( string_equal( token, ")" ) ) {
+                               tokeniser.ungetToken();
+                       }
+                       else{
+                               entityClass->m_modelpath = token;
+                       }
+                       ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );
+               }
                else if ( string_equal( property, "line" )
                                  || string_equal( property, "cylinder" ) ) {
                        ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
@@ -228,9 +259,25 @@ void EntityClassFGD_parseClass( Tokeniser& tokeniser, bool fixedsize, bool isBas
                }
                else if ( string_equal( property, "halfgridsnap" ) ) {
                }
+               else if ( string_equal( property, "flags" ) ) {
+                       ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
+                       for (;; )
+                       {
+                               const char* base = tokeniser.getToken();
+                               if ( string_equal( base, ")" ) ) {
+                                       break;
+                               }
+                               else if ( !string_equal( base, "," ) ) {
+                                       if( string_equal_nocase( base, "Angle" ) ){
+                                               // FIXME
+                                               // entityClass->has_angles = true;
+                                       }
+                               }
+                       }
+               }
                else
                {
-                       ERROR_MESSAGE( PARSE_ERROR );
+                       ERROR_FGD( PARSE_ERROR );
                }
        }
 
@@ -240,6 +287,16 @@ void EntityClassFGD_parseClass( Tokeniser& tokeniser, bool fixedsize, bool isBas
                ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ":" ), PARSE_ERROR );
 
                EntityClassFGD_parseSplitString( tokeniser, entityClass->m_comments );
+
+               const char* urlSeparator = tokeniser.getToken();
+               if ( string_equal( urlSeparator, ":" ) ) {
+                       CopiedString tmp;
+                       EntityClassFGD_parseSplitString( tokeniser, tmp );
+               }
+               else
+               {
+                       tokeniser.ungetToken();
+               }
        }
 
        tokeniser.nextLine();
@@ -283,12 +340,8 @@ void EntityClassFGD_parseClass( Tokeniser& tokeniser, bool fixedsize, bool isBas
                ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );
 
                if ( string_equal_nocase( type.c_str(), "flags" ) ) {
-                       EntityClassAttribute attribute;
-
                        ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "=" ), PARSE_ERROR );
-                       tokeniser.nextLine();
                        ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "[" ), PARSE_ERROR );
-                       tokeniser.nextLine();
                        for (;; )
                        {
                                const char* flag = tokeniser.getToken();
@@ -298,9 +351,17 @@ void EntityClassFGD_parseClass( Tokeniser& tokeniser, bool fixedsize, bool isBas
                                }
                                else
                                {
+                                       const size_t bit = std::log2( atoi( flag ) );
+                                       ASSERT_MESSAGE( bit < MAX_FLAGS, "invalid flag bit" << PARSE_ERROR );
+                                       ASSERT_MESSAGE( string_empty( entityClass->flagnames[bit] ), "non-unique flag bit" << PARSE_ERROR );
+
                                        ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ":" ), PARSE_ERROR );
-                                       //const char* name =
-                                       tokeniser.getToken();
+
+                                       const char* name = tokeniser.getToken();
+                                       strcpy( entityClass->flagnames[bit], name );
+                                       EntityClassAttribute *attribute = &EntityClass_insertAttribute( *entityClass, name, EntityClassAttribute( "flag", name ) ).second;
+                                       // FIXME spawn flags are not implemented yet
+                                       // entityClass->flagAttributes[bit] = attribute;
                                        {
                                                const char* defaultSeparator = tokeniser.getToken();
                                                if ( string_equal( defaultSeparator, ":" ) ) {
@@ -308,7 +369,7 @@ void EntityClassFGD_parseClass( Tokeniser& tokeniser, bool fixedsize, bool isBas
                                                        {
                                                                const char* descriptionSeparator = tokeniser.getToken();
                                                                if ( string_equal( descriptionSeparator, ":" ) ) {
-                                                                       EntityClassFGD_parseSplitString( tokeniser, attribute.m_description );
+                                                                       EntityClassFGD_parseSplitString( tokeniser, attribute->m_description );
                                                                }
                                                                else
                                                                {
@@ -324,7 +385,6 @@ void EntityClassFGD_parseClass( Tokeniser& tokeniser, bool fixedsize, bool isBas
                                }
                                tokeniser.nextLine();
                        }
-                       EntityClass_insertAttribute( *entityClass, key.c_str(), attribute );
                }
                else if ( string_equal_nocase( type.c_str(), "choices" ) ) {
                        EntityClassAttribute attribute;
@@ -380,6 +440,15 @@ void EntityClassFGD_parseClass( Tokeniser& tokeniser, bool fixedsize, bool isBas
                                        ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ":" ), PARSE_ERROR );
                                        const char* name = tokeniser.getToken();
                                        listType.push_back( name, tmp.c_str() );
+
+                                       const char* descriptionSeparator = tokeniser.getToken();
+                                       if ( string_equal( descriptionSeparator, ":" ) ) {
+                                               EntityClassFGD_parseSplitString( tokeniser, tmp );
+                                       }
+                                       else
+                                       {
+                                               tokeniser.ungetToken();
+                                       }
                                }
                                tokeniser.nextLine();
                        }
@@ -462,7 +531,7 @@ void EntityClassFGD_parseClass( Tokeniser& tokeniser, bool fixedsize, bool isBas
                }
                else
                {
-                       ERROR_MESSAGE( "unknown key type: " << makeQuoted( type.c_str() ) );
+                       ERROR_FGD( "unknown key type: " << makeQuoted( type ) );
                }
                tokeniser.nextLine();
        }
@@ -489,18 +558,18 @@ void EntityClassFGD_parse( TextInputStream& inputStream, const char* path ){
                if ( blockType == 0 ) {
                        break;
                }
-               if ( string_equal( blockType, "@SolidClass" ) ) {
+               if ( string_equal_nocase( blockType, "@SolidClass" ) ) {
                        EntityClassFGD_parseClass( tokeniser, false, false );
                }
-               else if ( string_equal( blockType, "@BaseClass" ) ) {
+               else if ( string_equal_nocase( blockType, "@BaseClass" ) ) {
                        EntityClassFGD_parseClass( tokeniser, false, true );
                }
-               else if ( string_equal( blockType, "@PointClass" )
+               else if ( string_equal_nocase( blockType, "@PointClass" )
                          // hl2 below
-                                 || string_equal( blockType, "@KeyFrameClass" )
-                                 || string_equal( blockType, "@MoveClass" )
-                                 || string_equal( blockType, "@FilterClass" )
-                                 || string_equal( blockType, "@NPCClass" ) ) {
+                      || string_equal_nocase( blockType, "@KeyFrameClass" )
+                      || string_equal_nocase( blockType, "@MoveClass" )
+                      || string_equal_nocase( blockType, "@FilterClass" )
+                      || string_equal_nocase( blockType, "@NPCClass" ) ) {
                        EntityClassFGD_parseClass( tokeniser, true, false );
                }
                // hl2 below
@@ -521,7 +590,7 @@ void EntityClassFGD_parse( TextInputStream& inputStream, const char* path ){
                }
                else
                {
-                       ERROR_MESSAGE( "unknown block type: " << makeQuoted( blockType ) );
+                       ERROR_FGD( "unknown block type: " << makeQuoted( blockType ) );
                }
        }
 
@@ -593,6 +662,14 @@ void EntityClassFGD_resolveInheritance( EntityClass* derivedClass ){
                                {
                                        EntityClass_insertAttribute( *derivedClass, ( *k ).first.c_str(), ( *k ).second );
                                }
+
+                               for( size_t flag = 0; flag < MAX_FLAGS; ++flag ){
+                                       if( !string_empty( parentClass->flagnames[flag] ) && string_empty( derivedClass->flagnames[flag] ) ){
+                                               strcpy( derivedClass->flagnames[flag], parentClass->flagnames[flag] );
+                                               // FIXME spawn flags are not implemented yet
+                                               // derivedClass->flagAttributes[flag] = parentClass->flagAttributes[flag];
+                                       }
+                               }
                        }
                }
        }
@@ -607,20 +684,47 @@ EntityClassFGD() : m_unrealised( 3 ){
 }
 void realise(){
        if ( --m_unrealised == 0 ) {
-               StringOutputStream filename( 256 );
-               filename << GlobalRadiant().getGameToolsPath() << GlobalRadiant().getGameName() << "/halflife.fgd";
-               EntityClassFGD_loadFile( filename.c_str() );
+
+                       {
+                               StringOutputStream baseDirectory( 256 );
+                               baseDirectory << GlobalRadiant().getGameToolsPath() << GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" ) << '/';
+                               StringOutputStream gameDirectory( 256 );
+                               gameDirectory << GlobalRadiant().getGameToolsPath() << GlobalRadiant().getGameName() << '/';
+
+                               const auto pathLess = []( const CopiedString& one, const CopiedString& other ){
+                                       return path_less( one.c_str(), other.c_str() );
+                               };
+                               std::map<CopiedString, const char*, decltype( pathLess )> name_path( pathLess );
+
+                               const auto constructDirectory = [&name_path]( const char* directory, const char* extension ){
+                                       globalOutputStream() << "EntityClass: searching " << makeQuoted( directory ) << " for *." << extension << '\n';
+                                       Directory_forEach( directory, matchFileExtension( extension, [directory, &name_path]( const char *name ){
+                                               name_path.emplace( name, directory );
+                                       } ) );
+                               };
+
+                               constructDirectory( baseDirectory.c_str(), "fgd" );
+                               if ( !string_equal( baseDirectory.c_str(), gameDirectory.c_str() ) ) {
+                                       constructDirectory( gameDirectory.c_str(), "fgd" );
+                               }
+
+                               for( const auto& [ name, path ] : name_path ){
+                                       StringOutputStream filename( 256 );
+                                       filename << path << name.c_str();
+                                       EntityClassFGD_loadFile( filename.c_str() );
+                               }
+                       }
 
                {
                        for ( EntityClasses::iterator i = g_EntityClassFGD_classes.begin(); i != g_EntityClassFGD_classes.end(); ++i )
                        {
                                EntityClassFGD_resolveInheritance( ( *i ).second );
-                               if ( ( *i ).second->fixedsize && string_empty( ( *i ).second->m_modelpath.c_str() ) ) {
+                                       if ( ( *i ).second->fixedsize && ( *i ).second->m_modelpath.empty() ) {
                                        if ( !( *i ).second->sizeSpecified ) {
-                                               globalErrorStream() << "size not specified for entity class: " << makeQuoted( ( *i ).second->m_name.c_str() ) << '\n';
+                                                       globalErrorStream() << "size not specified for entity class: " << makeQuoted( ( *i ).second->name() ) << '\n';
                                        }
                                        if ( !( *i ).second->colorSpecified ) {
-                                               globalErrorStream() << "color not specified for entity class: " << makeQuoted( ( *i ).second->m_name.c_str() ) << '\n';
+                                                       globalErrorStream() << "color not specified for entity class: " << makeQuoted( ( *i ).second->name() ) << '\n';
                                        }
                                }
                        }
index adb4ed11089aaebc74a47b33d10137b1301df8f3..3a7eddb258218c287da5bc146daeca682c1f6a0c 100644 (file)
@@ -47,6 +47,9 @@
 #include "qe3.h"
 #include "commands.h"
 
+#include "brushmanip.h"
+#include "patchmanip.h"
+
 #include "uilib/uilib.h"
 
 struct entity_globals_t
@@ -100,17 +103,20 @@ void post( const scene::Path& path, scene::Instance& instance ) const {
 
                EntityCopyingVisitor visitor( *Node_getEntity( node ) );
 
-               entity->forEachKeyValue( visitor );
+               //entity->forEachKeyValue( visitor );
 
                NodeSmartReference child( path.top().get() );
                NodeSmartReference parent( path.parent().get() );
-               Node_getTraversable( parent )->erase( child );
+               //Node_getTraversable( parent )->erase( child );
                if ( Node_getTraversable( child ) != 0
                         && Node_getTraversable( node ) != 0
                         && node_is_group( node ) ) {
                        parentBrushes( child, node );
                }
                Node_getTraversable( parent )->insert( node );
+               /* must do this after inserting node, otherwise problem: targeted + having model + not loaded b4 new entities aren't selectable normally + rendered only while 0 0 0 is rendered */
+               entity->forEachKeyValue( visitor );
+               Node_getTraversable( parent )->erase( child );
        }
 }
 };
@@ -306,10 +312,23 @@ void Entity_createFromSelection( const char* name, const Vector3& origin ){
 
        bool isModel = ( string_compare_nocase_n( name, "misc_", 5 ) == 0 && string_equal_nocase( name + string_length( name ) - 5, "model" ) ) // misc_*model (also misc_model)
                                   || string_equal_nocase( name, "model_static" )
-                                  || ( GlobalSelectionSystem().countSelected() == 0 && string_equal_nocase( name, "func_static" ) );
+                                  || ( GlobalSelectionSystem().countSelected() == 0 && string_equal_nocase( name, "func_static" ) && g_pGameDescription->mGameType == "doom3" );
 
        bool brushesSelected = Scene_countSelectedBrushes( GlobalSceneGraph() ) != 0;
 
+       //is important to have retexturing here; if doing in the end, undo doesn't succeed;
+       if ( string_compare_nocase_n( name, "trigger_", 8 ) == 0 && brushesSelected ){
+               const char* shader = g_pGameDescription->getKeyValue( "shader_trigger" );
+               if ( shader && *shader ){
+                       Scene_PatchSetShader_Selected( GlobalSceneGraph(), shader );
+                       Scene_BrushSetShader_Selected( GlobalSceneGraph(), shader );
+               }
+               else{
+                       Scene_PatchSetShader_Selected( GlobalSceneGraph(), "textures/common/trigger" );
+                       Scene_BrushSetShader_Selected( GlobalSceneGraph(), "textures/common/trigger" );
+               }
+       }
+
        if ( !( entityClass->fixedsize || isModel ) && !brushesSelected ) {
                globalErrorStream() << "failed to create a group entity - no brushes are selected\n";
                return;
@@ -472,6 +491,10 @@ void Entity_normalizeColor(){
                const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
                Entity* entity = Node_getEntity( path.top() );
 
+               if( entity == 0 && path.size() == 3 ){
+                       entity = Node_getEntity( path.parent() );
+               }
+
                if ( entity != 0 ) {
                        const char* strColor = entity->getKeyValue( "_color" );
                        if ( !string_empty( strColor ) ) {
@@ -485,6 +508,9 @@ void Entity_normalizeColor(){
                                                         g_entity_globals.color_entity[1],
                                                         g_entity_globals.color_entity[2] );
 
+                                       StringOutputStream command( 256 );
+                                       command << "entityNormalizeColour " << buffer;
+                                       UndoableCommand undo( command.c_str() );
                                        Scene_EntitySetKeyValue_Selected( "_color", buffer );
                                }
                        }
@@ -494,10 +520,13 @@ void Entity_normalizeColor(){
 
 void Entity_setColour(){
        if ( GlobalSelectionSystem().countSelected() != 0 ) {
-               bool normalize = false;
                const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
                Entity* entity = Node_getEntity( path.top() );
 
+               if( entity == 0 && path.size() == 3 ){
+                       entity = Node_getEntity( path.parent() );
+               }
+
                if ( entity != 0 ) {
                        const char* strColor = entity->getKeyValue( "_color" );
                        if ( !string_empty( strColor ) ) {
@@ -506,21 +535,15 @@ void Entity_setColour(){
                                        g_entity_globals.color_entity = rgb;
                                }
                        }
-
-                       if ( g_pGameDescription->mGameType == "doom3" ) {
-                               normalize = false;
-                       }
-
                        if ( color_dialog( MainFrame_getWindow(), g_entity_globals.color_entity ) ) {
-                               if ( normalize ) {
-                                       NormalizeColor( g_entity_globals.color_entity );
-                               }
-
                                char buffer[128];
                                sprintf( buffer, "%g %g %g", g_entity_globals.color_entity[0],
                                                 g_entity_globals.color_entity[1],
                                                 g_entity_globals.color_entity[2] );
 
+                               StringOutputStream command( 256 );
+                               command << "entitySetColour " << buffer;
+                               UndoableCommand undo( command.c_str() );
                                Scene_EntitySetKeyValue_Selected( "_color", buffer );
                        }
                }
@@ -560,7 +583,7 @@ const char *misc_model_dialog( ui::Widget parent ){
        }
        return 0;
 }
-
+/*
 struct LightRadii {
        static void Export(const EntityCreator &self, const Callback<void(bool)> &returnz) {
                returnz(self.getLightRadii());
@@ -585,15 +608,31 @@ void Entity_constructPage( PreferenceGroup& group ){
 void Entity_registerPreferencesPage(){
        PreferencesDialog_addDisplayPage( makeCallbackF(Entity_constructPage) );
 }
+*/
+
+void ShowLightRadiiToggle(){
+       GlobalEntityCreator().setLightRadii( !GlobalEntityCreator().getLightRadii() );
+       UpdateAllWindows();
+}
+typedef FreeCaller<void(), ShowLightRadiiToggle> ShowLightRadiiToggleCaller;
+void ShowLightRadiiExport( const Callback<void(bool)> & importer ){
+       GlobalEntityCreator().getLightRadii();
+}
+typedef FreeCaller<void(const Callback<void(bool)> &), ShowLightRadiiExport> ShowLightRadiiExportCaller;
 
+ShowLightRadiiExportCaller g_show_lightradii_caller;
+Callback<void(const Callback<void(bool)> &)> g_show_lightradii_callback( g_show_lightradii_caller );
+ToggleItem g_show_lightradii( g_show_lightradii_callback );
 
 void Entity_constructMenu( ui::Menu menu ){
        create_menu_item_with_mnemonic( menu, "_Regroup", "GroupSelection" );
        create_menu_item_with_mnemonic( menu, "_Ungroup", "UngroupSelection" );
        create_menu_item_with_mnemonic( menu, "_Connect", "ConnectSelection" );
-       create_menu_item_with_mnemonic( menu, "_KillConnect", "KillConnectSelection" );
+       if ( g_pGameDescription->mGameType == "nexuiz" ) {
+               create_menu_item_with_mnemonic( menu, "_KillConnect", "KillConnectSelection" );
+       }
        create_menu_item_with_mnemonic( menu, "_Select Color...", "EntityColor" );
-       create_menu_item_with_mnemonic( menu, "_Normalize Color...", "NormalizeColor" );
+       create_menu_item_with_mnemonic( menu, "_Normalize Color", "NormalizeColor" );
 }
 
 
@@ -605,10 +644,12 @@ void Entity_Construct(){
        GlobalCommands_insert( "GroupSelection", makeCallbackF(Entity_groupSelected) );
        GlobalCommands_insert( "UngroupSelection", makeCallbackF(Entity_ungroupSelected) );
 
+       GlobalToggles_insert( "ShowLightRadiuses", makeCallbackF( ShowLightRadiiToggle ), ToggleItem::AddCallbackCaller( g_show_lightradii ) );
+
        GlobalPreferenceSystem().registerPreference( "SI_Colors5", make_property_string( g_entity_globals.color_entity ) );
        GlobalPreferenceSystem().registerPreference( "LastLightIntensity", make_property_string( g_iLastLightIntensity ) );
 
-       Entity_registerPreferencesPage();
+//     Entity_registerPreferencesPage();
 }
 
 void Entity_Destroy(){
index fca38f92cd4c598852325ceccc19a725b4f9912f..d655774cde72d9207c9a34ecfd01fcf0e5cbf8a9 100644 (file)
@@ -33,6 +33,7 @@ void Scene_EntitySetClassname_Selected( const char* classname );
 
 
 const char* misc_model_dialog( ui::Widget parent );
+void Entity_setColour();
 
 void Entity_constructMenu( ui::Menu menu );
 
index e74dd3f0834f637719ed9c0c2d9fcda4b72d0638..deb7680098dbebabd84d217c5d1642eb0c0b708b 100644 (file)
@@ -36,7 +36,6 @@
 #include <gdk/gdkkeysyms.h>
 #include <uilib/uilib.h>
 
-
 #include "os/path.h"
 #include "eclasslib.h"
 #include "scenelib.h"
@@ -64,6 +63,8 @@
 #include "textureentry.h"
 #include "groupdialog.h"
 
+#include "select.h"
+
 ui::Entry numeric_entry_new(){
        auto entry = ui::Entry(ui::New);
        entry.show();
@@ -140,7 +141,7 @@ void release(){
        delete this;
 }
 void apply(){
-       Scene_EntitySetKeyValue_Selected_Undoable( m_key.c_str(), m_check.active() ? "1" : "0" );
+       Scene_EntitySetKeyValue_Selected_Undoable( m_key.c_str(), m_check.active() ? "1" : "" );
 }
 typedef MemberCaller<BooleanAttribute, void(), &BooleanAttribute::apply> ApplyCaller;
 
@@ -209,6 +210,52 @@ ShaderAttribute( const char* key ) : StringAttribute( key ){
 };
 
 
+class ColorAttribute : public EntityAttribute
+{
+CopiedString m_key;
+BrowsedPathEntry m_entry;
+NonModalEntry m_nonModal;
+public:
+ColorAttribute( const char* key ) :
+       m_key( key ),
+       m_entry( BrowseCaller( *this ) ),
+       m_nonModal( ApplyCaller( *this ), UpdateCaller( *this ) ){
+       m_nonModal.connect( m_entry.m_entry.m_entry );
+}
+void release(){
+       delete this;
+}
+ui::Widget getWidget() const {
+       return ui::Widget( m_entry.m_entry.m_frame );
+}
+void apply(){
+       StringOutputStream value( 64 );
+       value << gtk_entry_get_text( GTK_ENTRY( m_entry.m_entry.m_entry ) );
+       Scene_EntitySetKeyValue_Selected_Undoable( m_key.c_str(), value.c_str() );
+}
+typedef MemberCaller<ColorAttribute, void(), &ColorAttribute::apply> ApplyCaller;
+void update(){
+       StringOutputStream value( 64 );
+       value << SelectedEntity_getValueForKey( m_key.c_str() );
+       gtk_entry_set_text( GTK_ENTRY( m_entry.m_entry.m_entry ), value.c_str() );
+}
+typedef MemberCaller<ColorAttribute, void(), &ColorAttribute::update> UpdateCaller;
+void browse( const BrowsedPathEntry::SetPathCallback& setPath ){
+       //const char *filename = misc_model_dialog( gtk_widget_get_toplevel( GTK_WIDGET( m_entry.m_entry.m_frame ) ) );
+
+       /* hijack BrowsedPathEntry to call colour chooser */
+       Entity_setColour();
+
+//     if ( filename != 0 ) {
+//             setPath( filename );
+//             apply();
+//     }
+       update();
+}
+typedef MemberCaller<ColorAttribute, void(const BrowsedPathEntry::SetPathCallback&), &ColorAttribute::browse> BrowseCaller;
+};
+
+
 class ModelAttribute : public EntityAttribute
 {
 CopiedString m_key;
@@ -703,8 +750,10 @@ typedef MemberCaller<ListAttribute, void(), &ListAttribute::update> UpdateCaller
 
 namespace
 {
+GtkWidget* g_entity_split0 = 0;
 ui::Widget g_entity_split1{ui::null};
 ui::Widget g_entity_split2{ui::null};
+int g_entitysplit0_position;
 int g_entitysplit1_position;
 int g_entitysplit2_position;
 
@@ -718,6 +767,8 @@ GtkCheckButton* g_entitySpawnflagsCheck[MAX_FLAGS];
 ui::Entry g_entityKeyEntry{ui::null};
 ui::Entry g_entityValueEntry{ui::null};
 
+GtkToggleButton* g_focusToggleButton;
+
 ui::ListStore g_entlist_store{ui::null};
 ui::ListStore g_entprops_store{ui::null};
 const EntityClass* g_current_flags = 0;
@@ -831,6 +882,36 @@ void SetComment( EntityClass* eclass ){
        g_current_comment = eclass;
 
        g_entityClassComment.text(eclass->comments());
+
+       GtkTextBuffer* buffer = gtk_text_view_get_buffer( g_entityClassComment );
+       //gtk_text_buffer_set_text( buffer, eclass->comments(), -1 );
+       const char* comment = eclass->comments(), *c;
+       int offset = 0, pattern_start = -1, spaces = 0;
+
+       gtk_text_buffer_set_text( buffer, comment, -1 );
+
+       // Catch patterns like "\nstuff :" used to describe keys and spawnflags, and make them bold for readability.
+
+       for( c = comment; *c; ++c, ++offset ) {
+               if( *c == '\n' ) {
+                       pattern_start = offset;
+                       spaces = 0;
+               }
+               else if( pattern_start >= 0 && ( *c < 'a' || *c > 'z' ) && ( *c < 'A' || *c > 'Z' ) && ( *c < '0' || *c > '9' ) && ( *c != '_' ) ) {
+                       if( *c == ':' && spaces <= 1 ) {
+                               GtkTextIter iter_start, iter_end;
+
+                               gtk_text_buffer_get_iter_at_offset( buffer, &iter_start, pattern_start );
+                               gtk_text_buffer_get_iter_at_offset( buffer, &iter_end, offset );
+                               gtk_text_buffer_apply_tag_by_name( buffer, "bold", &iter_start, &iter_end );
+                       }
+
+                       if( *c == ' ' )
+                               ++spaces;
+                       else
+                               pattern_start = -1;
+               }
+       }
 }
 
 void SurfaceFlags_setEntityClass( EntityClass* eclass ){
@@ -931,7 +1012,7 @@ Creators m_creators;
 public:
 EntityAttributeFactory(){
        m_creators.insert( Creators::value_type( "string", &StatelessAttributeCreator<StringAttribute>::create ) );
-       m_creators.insert( Creators::value_type( "color", &StatelessAttributeCreator<StringAttribute>::create ) );
+       m_creators.insert( Creators::value_type( "color", &StatelessAttributeCreator<ColorAttribute>::create ) );
        m_creators.insert( Creators::value_type( "integer", &StatelessAttributeCreator<StringAttribute>::create ) );
        m_creators.insert( Creators::value_type( "real", &StatelessAttributeCreator<StringAttribute>::create ) );
        m_creators.insert( Creators::value_type( "shader", &StatelessAttributeCreator<ShaderAttribute>::create ) );
@@ -1153,6 +1234,14 @@ void EntityInspector_clearKeyValue(){
        }
 }
 
+static gint EntityInspector_clearKeyValueKB( GtkEntry* widget, GdkEventKey* event, gpointer data ){
+       if ( event->keyval == GDK_KEY_Delete ) {
+               EntityInspector_clearKeyValue();
+               return TRUE;
+       }
+       return FALSE;
+}
+
 void EntityInspector_clearAllKeyValues(){
        UndoableCommand undo( "entityClear" );
 
@@ -1189,15 +1278,15 @@ static gint EntityClassList_button_press( ui::Widget widget, GdkEventButton *eve
 }
 
 static gint EntityClassList_keypress( ui::Widget widget, GdkEventKey* event, gpointer data ){
-       unsigned int code = gdk_keyval_to_upper( event->keyval );
-
        if ( event->keyval == GDK_KEY_Return ) {
                EntityClassList_createEntity();
                return TRUE;
        }
 
        // select the entity that starts with the key pressed
-       if ( code <= 'Z' && code >= 'A' ) {
+/*
+       unsigned int code = gdk_keyval_to_upper( event->keyval );
+       if ( code <= 'Z' && code >= 'A' && event->state == 0 ) {
                auto view = ui::TreeView(g_entityClassList);
                GtkTreeModel* model;
                GtkTreeIter iter;
@@ -1230,6 +1319,7 @@ static gint EntityClassList_keypress( ui::Widget widget, GdkEventKey* event, gpo
 
                return TRUE;
        }
+*/
        return FALSE;
 }
 
@@ -1259,7 +1349,7 @@ static void SpawnflagCheck_toggled( ui::Widget widget, gpointer data ){
 static gint EntityEntry_keypress( ui::Entry widget, GdkEventKey* event, gpointer data ){
        if ( event->keyval == GDK_KEY_Return ) {
                if ( widget._handle == g_entityKeyEntry._handle ) {
-                       g_entityValueEntry.text( "" );
+                       // g_entityValueEntry.text( "" );
                        gtk_window_set_focus( widget.window(), g_entityValueEntry  );
                }
                else
@@ -1268,8 +1358,18 @@ static gint EntityEntry_keypress( ui::Entry widget, GdkEventKey* event, gpointer
                }
                return TRUE;
        }
+       if ( event->keyval == GDK_KEY_Tab ) {
+               if ( widget._handle == g_entityKeyEntry._handle ) {
+                       gtk_window_set_focus( widget.window(), g_entityValueEntry );
+               }
+               else
+               {
+                       gtk_window_set_focus( widget.window(), g_entityKeyEntry );
+               }
+               return TRUE;
+       }
        if ( event->keyval == GDK_KEY_Escape ) {
-               gtk_window_set_focus( widget.window(), NULL );
+               // gtk_window_set_focus( widget.window(), NULL );
                return TRUE;
        }
 
@@ -1277,18 +1377,49 @@ static gint EntityEntry_keypress( ui::Entry widget, GdkEventKey* event, gpointer
 }
 
 void EntityInspector_destroyWindow( ui::Widget widget, gpointer data ){
+       g_entitysplit0_position = gtk_paned_get_position( GTK_PANED( g_entity_split0 ) );
        g_entitysplit1_position = gtk_paned_get_position( GTK_PANED( g_entity_split1 ) );
        g_entitysplit2_position = gtk_paned_get_position( GTK_PANED( g_entity_split2 ) );
-
        g_entityInspector_windowConstructed = false;
        GlobalEntityAttributes_clear();
 }
 
+static gint EntityInspector_hideWindowKB( GtkWidget* widget, GdkEventKey* event, gpointer data ){
+       //if ( event->keyval == GDK_KEY_Escape && GTK_WIDGET_VISIBLE( GTK_WIDGET( widget ) ) ) {
+       if ( event->keyval == GDK_KEY_Escape  ) {
+               //GroupDialog_showPage( g_page_entity );
+               gtk_widget_hide( GTK_WIDGET( GroupDialog_getWindow() ) );
+               return TRUE;
+       }
+       /* this doesn't work, if tab is bound (func is not called then) */
+       if ( event->keyval == GDK_KEY_Tab ) {
+               gtk_window_set_focus( GTK_WINDOW( gtk_widget_get_toplevel( GTK_WIDGET( widget ) ) ), GTK_WIDGET( g_entityKeyEntry ) );
+               return TRUE;
+       }
+       return FALSE;
+}
+
+void EntityInspector_selectTargeting( GtkButton *button, gpointer user_data ){
+       bool focus = gtk_toggle_button_get_active( g_focusToggleButton );
+       Select_ConnectedEntities( true, false, focus );
+}
+
+void EntityInspector_selectTargets( GtkButton *button, gpointer user_data ){
+       bool focus = gtk_toggle_button_get_active( g_focusToggleButton );
+       Select_ConnectedEntities( false, true, focus );
+}
+
+void EntityInspector_selectConnected( GtkButton *button, gpointer user_data ){
+       bool focus = gtk_toggle_button_get_active( g_focusToggleButton );
+       Select_ConnectedEntities( true, true, focus );
+}
+
 ui::Widget EntityInspector_constructWindow( ui::Window toplevel ){
     auto vbox = ui::VBox( FALSE, 2 );
        vbox.show();
        gtk_container_set_border_width( GTK_CONTAINER( vbox ), 2 );
 
+       g_signal_connect( G_OBJECT( toplevel ), "key_press_event", G_CALLBACK( EntityInspector_hideWindowKB ), 0 );
        vbox.connect( "destroy", G_CALLBACK( EntityInspector_destroyWindow ), 0 );
 
        {
@@ -1300,7 +1431,8 @@ ui::Widget EntityInspector_constructWindow( ui::Window toplevel ){
 
                {
                        ui::Widget split2 = ui::VPaned(ui::New);
-                       gtk_paned_add1( GTK_PANED( split1 ), split2 );
+                       //gtk_paned_add1( GTK_PANED( split1 ), split2 );
+                       gtk_paned_pack1( GTK_PANED( split1 ), split2, FALSE, FALSE );
                        split2.show();
 
                        g_entity_split2 = split2;
@@ -1309,7 +1441,8 @@ ui::Widget EntityInspector_constructWindow( ui::Window toplevel ){
                                // class list
                                auto scr = ui::ScrolledWindow(ui::New);
                                scr.show();
-                               gtk_paned_add1( GTK_PANED( split2 ), scr );
+                               //gtk_paned_add1( GTK_PANED( split2 ), scr );
+                               gtk_paned_pack1( GTK_PANED( split2 ), scr, FALSE, FALSE );
                                gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scr ), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS );
                                gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( scr ), GTK_SHADOW_IN );
 
@@ -1317,7 +1450,7 @@ ui::Widget EntityInspector_constructWindow( ui::Window toplevel ){
                                        ui::ListStore store = ui::ListStore::from(gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_POINTER ));
 
                                        auto view = ui::TreeView( ui::TreeModel::from( store._handle ));
-                                       gtk_tree_view_set_enable_search(view, FALSE );
+                                       // gtk_tree_view_set_enable_search(view, FALSE );
                                        gtk_tree_view_set_headers_visible( view, FALSE );
                                        view.connect( "button_press_event", G_CALLBACK( EntityClassList_button_press ), 0 );
                                        view.connect( "key_press_event", G_CALLBACK( EntityClassList_keypress ), 0 );
@@ -1346,7 +1479,8 @@ ui::Widget EntityInspector_constructWindow( ui::Window toplevel ){
                        {
                                auto scr = ui::ScrolledWindow(ui::New);
                                scr.show();
-                               gtk_paned_add2( GTK_PANED( split2 ), scr );
+                               //gtk_paned_add2( GTK_PANED( split2 ), scr );
+                               gtk_paned_pack2( GTK_PANED( split2 ), scr, FALSE, FALSE );
                                gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scr ), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS );
                                gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW( scr ), GTK_SHADOW_IN );
 
@@ -1358,19 +1492,25 @@ ui::Widget EntityInspector_constructWindow( ui::Window toplevel ){
                                        text.show();
                                        scr.add(text);
                                        g_entityClassComment = text;
+                                       {
+                                               GtkTextBuffer *buffer = gtk_text_view_get_buffer( text );
+                                               gtk_text_buffer_create_tag( buffer, "bold", "weight", PANGO_WEIGHT_BOLD, NULL );
+                                       }
                                }
                        }
                }
 
                {
-                       ui::Widget split2 = ui::VPaned(ui::New);
-                       gtk_paned_add2( GTK_PANED( split1 ), split2 );
-                       split2.show();
+                       ui::Widget split0 = ui::VPaned(ui::New);
+                       //gtk_paned_add2( GTK_PANED( split1 ), split0 );
+                       gtk_paned_pack2( GTK_PANED( split1 ), split0, FALSE, FALSE );
+                       split0.show();
+                       g_entity_split0 = split0;
 
                        {
                 auto vbox2 = ui::VBox( FALSE, 2 );
                                vbox2.show();
-                               gtk_paned_pack1( GTK_PANED( split2 ), vbox2, FALSE, FALSE );
+                               gtk_paned_pack1( GTK_PANED( split0 ), vbox2, FALSE, FALSE );
 
                                {
                                        // Spawnflags (4 colums wide max, or window gets too wide.)
@@ -1403,6 +1543,7 @@ ui::Widget EntityInspector_constructWindow( ui::Window toplevel ){
                                                auto view = ui::TreeView(ui::TreeModel::from(store._handle));
                                                gtk_tree_view_set_enable_search(view, FALSE );
                                                gtk_tree_view_set_headers_visible(view, FALSE );
+                                               g_signal_connect( G_OBJECT( view ), "key_press_event", G_CALLBACK( EntityInspector_clearKeyValueKB ), 0 );
 
                                                {
                                                        auto renderer = ui::CellRendererText(ui::New);
@@ -1473,22 +1614,73 @@ ui::Widget EntityInspector_constructWindow( ui::Window toplevel ){
                                }
 
                                {
-                                       auto hbox = ui::HBox( TRUE, 4 );
+                                       auto hbox = ui::HBox( FALSE, 4 );
                                        hbox.show();
                                        vbox2.pack_start( hbox, FALSE, TRUE, 0 );
 
                                        {
                                                auto button = ui::Button( "Clear All" );
+#define GARUX_DISABLE_BUTTON_NOFOCUS
+#ifndef GARUX_DISABLE_BUTTON_NOFOCUS
+                                               GTK_WIDGET_UNSET_FLAGS( GTK_WIDGET( button ), GTK_CAN_FOCUS );
+#endif // GARUX_DISABLE_BUTTON_NOFOCUS
                                                button.show();
                                                button.connect( "clicked", G_CALLBACK( EntityInspector_clearAllKeyValues ), 0 );
                                                hbox.pack_start( button, TRUE, TRUE, 0 );
                                        }
                                        {
                                                auto button = ui::Button( "Delete Key" );
+#ifndef GARUX_DISABLE_BUTTON_NOFOCUS
+                                               GTK_WIDGET_UNSET_FLAGS( GTK_WIDGET( button ), GTK_CAN_FOCUS );
+#endif // GARUX_DISABLE_BUTTON_NOFOCUS
                                                button.show();
                                                button.connect( "clicked", G_CALLBACK( EntityInspector_clearKeyValue ), 0 );
                                                hbox.pack_start( button, TRUE, TRUE, 0 );
                                        }
+                                       {
+                                               GtkButton* button = GTK_BUTTON( gtk_button_new_with_label( "<" ) );
+                                               gtk_widget_set_tooltip_text( GTK_WIDGET( button ), "Select targeting entities" );
+#ifndef GARUX_DISABLE_BUTTON_NOFOCUS
+                                               GTK_WIDGET_UNSET_FLAGS( GTK_WIDGET( button ), GTK_CAN_FOCUS );
+#endif // GARUX_DISABLE_BUTTON_NOFOCUS
+                                               gtk_widget_show( GTK_WIDGET( button ) );
+                                               g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( EntityInspector_selectTargeting ), 0 );
+                                               gtk_box_pack_start( hbox, GTK_WIDGET( button ), FALSE, FALSE, 0 );
+                                       }
+                                       {
+                                               GtkButton* button = GTK_BUTTON( gtk_button_new_with_label( ">" ) );
+                                               gtk_widget_set_tooltip_text( GTK_WIDGET( button ), "Select targets" );
+#ifndef GARUX_DISABLE_BUTTON_NOFOCUS
+                                               GTK_WIDGET_UNSET_FLAGS( GTK_WIDGET( button ), GTK_CAN_FOCUS );
+#endif // GARUX_DISABLE_BUTTON_NOFOCUS
+                                               gtk_widget_show( GTK_WIDGET( button ) );
+                                               g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( EntityInspector_selectTargets ), 0 );
+                                               gtk_box_pack_start( hbox, GTK_WIDGET( button ), FALSE, FALSE, 0 );
+                                       }
+                                       {
+                                               GtkButton* button = GTK_BUTTON( gtk_button_new_with_label( "<->" ) );
+                                               gtk_widget_set_tooltip_text( GTK_WIDGET( button ), "Select connected entities" );
+#ifndef GARUX_DISABLE_BUTTON_NOFOCUS
+                                               GTK_WIDGET_UNSET_FLAGS( GTK_WIDGET( button ), GTK_CAN_FOCUS );
+#endif // GARUX_DISABLE_BUTTON_NOFOCUS
+                                               gtk_widget_show( GTK_WIDGET( button ) );
+                                               g_signal_connect( G_OBJECT( button ), "clicked", G_CALLBACK( EntityInspector_selectConnected ), 0 );
+                                               gtk_box_pack_start( hbox, GTK_WIDGET( button ), FALSE, FALSE, 0 );
+                                       }
+                                       {
+                                               GtkWidget* button = gtk_toggle_button_new();
+                                               GtkImage* image = GTK_IMAGE( gtk_image_new_from_stock( GTK_STOCK_ZOOM_IN, GTK_ICON_SIZE_SMALL_TOOLBAR ) );
+                                               gtk_widget_show( GTK_WIDGET( image ) );
+                                               gtk_container_add( GTK_CONTAINER( button ), GTK_WIDGET( image ) );
+                                               gtk_button_set_relief( GTK_BUTTON( button ), GTK_RELIEF_NONE );
+#ifndef GARUX_DISABLE_BUTTON_NOFOCUS
+                                               GTK_WIDGET_UNSET_FLAGS( button, GTK_CAN_FOCUS );
+#endif // GARUX_DISABLE_BUTTON_NOFOCUS
+                                               gtk_box_pack_start( hbox, button, FALSE, FALSE, 0 );
+                                               gtk_widget_set_tooltip_text( button, "Focus on Selected" );
+                                               gtk_widget_show( button );
+                                               g_focusToggleButton = GTK_TOGGLE_BUTTON( button );
+                                       }
                                }
                        }
 
@@ -1506,27 +1698,23 @@ ui::Widget EntityInspector_constructWindow( ui::Window toplevel ){
 
                                viewport.add(g_attributeBox);
                                scr.add(viewport);
-                               gtk_paned_pack2( GTK_PANED( split2 ), scr, FALSE, FALSE );
+                               gtk_paned_pack2( GTK_PANED( split0 ), scr, FALSE, FALSE );
                        }
                }
        }
 
 
        {
-               // show the sliders in any case
-               if ( g_entitysplit2_position > 22 ) {
-                       gtk_paned_set_position( GTK_PANED( g_entity_split2 ), g_entitysplit2_position );
-               }
-               else {
+               // show the sliders in any case //no need, gtk can care
+               /*if ( g_entitysplit2_position < 22 ) {
                        g_entitysplit2_position = 22;
-                       gtk_paned_set_position( GTK_PANED( g_entity_split2 ), 22 );
-               }
-               if ( ( g_entitysplit1_position - g_entitysplit2_position ) > 27 ) {
-                       gtk_paned_set_position( GTK_PANED( g_entity_split1 ), g_entitysplit1_position );
-               }
-               else {
-                       gtk_paned_set_position( GTK_PANED( g_entity_split1 ), g_entitysplit2_position + 27 );
-               }
+               }*/
+               gtk_paned_set_position( GTK_PANED( g_entity_split2 ), g_entitysplit2_position );
+               /*if ( ( g_entitysplit1_position - g_entitysplit2_position ) < 27 ) {
+                       g_entitysplit1_position = g_entitysplit2_position + 27;
+               }*/
+               gtk_paned_set_position( GTK_PANED( g_entity_split1 ), g_entitysplit1_position );
+               gtk_paned_set_position( GTK_PANED( g_entity_split0 ), g_entitysplit0_position );
        }
 
        g_entityInspector_windowConstructed = true;
@@ -1574,6 +1762,7 @@ EntityInspector g_EntityInspector;
 void EntityInspector_construct(){
        GlobalEntityClassManager().attach( g_EntityInspector );
 
+       GlobalPreferenceSystem().registerPreference( "EntitySplit0", make_property_string( g_entitysplit0_position ) );
        GlobalPreferenceSystem().registerPreference( "EntitySplit1", make_property_string( g_entitysplit1_position ) );
        GlobalPreferenceSystem().registerPreference( "EntitySplit2", make_property_string( g_entitysplit2_position ) );
 
index 0230fe6d01cf9a7e2e2a80e169cbc4050efa9e05..6dc57fc918bcbf951b911d6d1799a9065259777c 100644 (file)
@@ -25,6 +25,7 @@
 
 #include <uilib/uilib.h>
 #include <gtk/gtk.h>
+#include <gtk/gtkcheckbutton.h>
 
 #include "string/string.h"
 #include "scenelib.h"
@@ -40,6 +41,8 @@
 
 #include "treemodel.h"
 
+#include "mainframe.h"
+
 void RedrawEntityList();
 typedef FreeCaller<void(), RedrawEntityList> RedrawEntityListCaller;
 
@@ -60,6 +63,7 @@ IdleDraw m_idleDraw;
 WindowPositionTracker m_positionTracker;
 
 ui::Window m_window;
+ui::Widget m_check;
 ui::TreeView m_tree_view{ui::null};
 ui::TreeModel m_tree_model{ui::null};
 bool m_selection_disabled;
@@ -68,6 +72,7 @@ EntityList() :
        m_dirty( EntityList::eDefault ),
        m_idleDraw( RedrawEntityListCaller() ),
        m_window( ui::null ),
+       m_check( ui::null ),
        m_selection_disabled( false ){
 }
 
@@ -154,6 +159,9 @@ static gboolean entitylist_tree_select( ui::TreeSelection selection, ui::TreeMod
                getEntityList().m_selection_disabled = true;
                selectable->setSelected( path_currently_selected == FALSE );
                getEntityList().m_selection_disabled = false;
+               if( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( getEntityList().m_check ) ) ){
+                       FocusAllViews();
+               }
                return TRUE;
        }
 
@@ -292,8 +300,13 @@ void EntityList_constructWindow( ui::Window main_window ){
        getEntityList().m_window = window;
 
        {
+               ui::VBox vbox = ui::VBox( FALSE, 0 );
+               gtk_container_set_border_width( GTK_CONTAINER( vbox ), 0 );
+               vbox.show();
+               window.add( vbox );
+
                auto scr = create_scrolled_window( ui::Policy::AUTOMATIC, ui::Policy::AUTOMATIC );
-               window.add(scr);
+               vbox.pack_start( scr, TRUE, TRUE, 0 );
 
                {
             auto view = ui::TreeView(ui::New);
@@ -316,6 +329,13 @@ void EntityList_constructWindow( ui::Window main_window ){
                        scr.add(view);
                        getEntityList().m_tree_view = view;
                }
+               {
+                       ui::Widget check = ui::Widget::from( gtk_check_button_new_with_label( "Focus on Selected" ) );
+                       gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( check ), FALSE );
+                       check.show();
+                       vbox.pack_start( check, FALSE, FALSE, 0 );
+                       getEntityList().m_check = check;
+               }
        }
 
        EntityList_ConnectSignals( getEntityList().m_tree_view );
index d6be70d54a6b1eb1db87d3f368d9fc1d2ed49499..1e02a14153336f81fdb408ae11cc2b4d51b25b85 100644 (file)
@@ -110,7 +110,8 @@ bool gamedetect_check_game( char const *gamefile, const char *checkfile1, const
 void gamedetect(){
        // if we're inside a Nexuiz install
        // default to nexuiz.game (unless the user used an option to inhibit this)
-       bool nogamedetect = false;
+       //bool nogamedetect = false;
+       bool nogamedetect = true;
        int i;
        for ( i = 1; i < g_argc - 1; ++i )
        {
@@ -208,6 +209,20 @@ bool portable_app_setup(){
        return false;
 }
 
+
+const char* openCmdMap;
+
+void cmdMap(){
+       openCmdMap = NULL;
+       for ( int i = 1; i < g_argc; ++i )
+       {
+               //if ( !stricmp( g_argv[i] + strlen(g_argv[i]) - 4, ".map" ) ){
+               if( string_equal_suffix_nocase( g_argv[i], ".map" ) ){
+                       openCmdMap = g_argv[i];
+               }
+       }
+}
+
 #if GDEF_OS_POSIX
 
 #include <stdlib.h>
@@ -280,40 +295,63 @@ void environment_init( int argc, char const* argv[] ){
        }
 
        {
+#if defined(RADIANT_FHS_INSTALL)
                StringOutputStream buffer;
-               buffer << app_path.c_str() << "../lib/" << RADIANT_BASENAME << "/";
-               if ( file_is_directory( buffer.c_str() ) ) {
-                       lib_path = buffer.c_str();
-               }
-               else {
-                       lib_path = app_path.c_str();
-               }
+       #if defined(RADIANT_ADDONS_DIR)
+               buffer << RADIANT_ADDONS_DIR << "/";
+       #else
+               buffer << app_path.c_str() << "../lib/";
+               buffer << RADIANT_LIB_ARCH << "/";
+               buffer << RADIANT_BASENAME << "/";
+       #endif
+               lib_path = buffer.c_str();
+#else
+               lib_path = app_path.c_str();
+#endif
        }
 
        {
+#if defined(RADIANT_FHS_INSTALL)
                StringOutputStream buffer;
-               buffer << app_path.c_str() << "../share/" << RADIANT_BASENAME << "/";
-               if ( file_is_directory( buffer.c_str() ) ) {
-                       data_path = buffer.c_str();
-               }
-               else {
-                       data_path = app_path.c_str();
-               }
+       #if defined(RADIANT_DATA_DIR)
+               buffer << RADIANT_DATA_DIR << "/";
+       #else
+               buffer << app_path.c_str() << "../share/";
+               buffer << RADIANT_BASENAME << "/";
+       #endif
+               data_path = buffer.c_str();
+#else
+               data_path = app_path.c_str();
+#endif
        }
 
        if ( !portable_app_setup() ) {
-               // this is used on both Linux and macOS
-               // but a macOS specific code may be written instead
                StringOutputStream home( 256 );
-               home << DirectoryCleaned(g_get_user_config_dir()) << "/" << RADIANT_BASENAME << "/";
-               // first create ~/.config
-               // since it may be missing on brand new home directory
-               Q_mkdir( g_get_user_config_dir() );
-               // then create ~/.config/netradiant
+#if GDEF_OS_MACOS
+               /* This is used on macOS, this will produce
+               ~/Library/Application Support/NetRadiant folder. */
+               home << DirectoryCleaned( g_get_home_dir() );
+               Q_mkdir( home.c_str() );
+               home << "Library/";
+               Q_mkdir( home.c_str() );
+               home << "Application Support/";
+               Q_mkdir( home.c_str() );
+               home << RADIANT_NAME << "/";
+#else // if GDEF_OS_XDG
+               /* This is used on both Linux and FreeBSD,
+               this will produce ~/.config/netradiant folder
+               when environment has default settings, the
+               XDG_CONFIG_HOME variable modifies it. */
+               home << DirectoryCleaned( g_get_user_config_dir() );
+               Q_mkdir( home.c_str() );
+               home << RADIANT_BASENAME << "/";
+#endif // ! GDEF_OS_MACOS
                Q_mkdir( home.c_str() );
                home_path = home.c_str();
        }
+
        gamedetect();
+       cmdMap();
 }
 
 #elif GDEF_OS_WINDOWS
@@ -352,13 +390,18 @@ void environment_init( int argc, char const* argv[] ){
 
        if ( !portable_app_setup() ) {
                char *appdata = getenv( "APPDATA" );
+
                StringOutputStream home( 256 );
                home << PathCleaned( appdata );
-               home << "/NetRadiantSettings/";
+               home << "/";
+               home << RADIANT_NAME;
+               home << "/";
+
                Q_mkdir( home.c_str() );
                home_path = home.c_str();
        }
        gamedetect();
+       cmdMap();
 }
 
 #else
index 61494f861d590bfd4b554d4ab5f8897b6f380bc1..a7de6d4f0fbc896354a8eab7d6d9288d74001eb2 100644 (file)
@@ -34,4 +34,6 @@ const char *environment_get_data_path();
 extern int g_argc;
 extern char const** g_argv;
 
+extern const char* openCmdMap;
+
 #endif
diff --git a/radiant/filterbar.cpp b/radiant/filterbar.cpp
new file mode 100644 (file)
index 0000000..d8fb993
--- /dev/null
@@ -0,0 +1,307 @@
+#include "filterbar.h"
+
+#include "gtk/gtk.h"
+
+#include "gtkmisc.h"
+#include "gtkutil/widget.h"
+#include "stream/stringstream.h"
+#include "select.h"
+#include "iundo.h"
+#include "preferences.h"
+
+#include "commands.h"
+#include "gtkutil/accelerator.h"
+#include "generic/callback.h"
+
+#include "entity.h"
+int ToggleActions = 0;
+int ButtonNum = 0;
+
+
+gboolean ToggleActions0( ui::Widget widget, GdkEvent *event, gpointer user_data ){
+       ToggleActions = 0;
+       return FALSE;
+       //globalOutputStream() << "ToggleActions\n";
+}
+
+
+void SetCommonShader( const char* key, const char* shader ){
+       const char* gotShader = g_pGameDescription->getKeyValue( key );
+       UndoableCommand undo( "textureNameSetSelected" );
+       if ( gotShader && *gotShader ){
+               Select_SetShader( gotShader );
+       }
+       else{
+               Select_SetShader( shader );
+       }
+}
+
+
+gboolean Areaportals_button_press( ui::Widget widget, GdkEventButton *event, gpointer data ){
+       if ( event->button == 3 && event->type == GDK_BUTTON_PRESS ) {
+               if ( ButtonNum == 1 ){
+                       ToggleActions %= 2;
+               }
+               else{
+                       ToggleActions = 0;
+                       ButtonNum = 1;
+               }
+               if( ToggleActions == 0 ){
+                       SetCommonShader( "shader_nodraw", "textures/common/nodraw" );
+               }
+               else if( ToggleActions == 1 ){
+                       SetCommonShader( "shader_nodrawnonsolid", "textures/common/nodrawnonsolid" );
+               }
+               //SetCommonShader( "shader_caulk", "textures/common/caulk" );
+               //globalOutputStream() << "Found '" << "fullname" << "'\n";
+               ToggleActions++;
+               return TRUE;
+       }
+       return FALSE;
+}
+
+
+gboolean Caulk_button_press( ui::Widget widget, GdkEventButton *event, gpointer data ){
+       if ( event->button == 3 && event->type == GDK_BUTTON_PRESS ) {
+               SetCommonShader( "shader_caulk", "textures/common/caulk" );
+               ToggleActions = 0;
+               return TRUE;
+       }
+       return FALSE;
+}
+
+
+gboolean Clip_button_press( ui::Widget widget, GdkEventButton *event, gpointer data ){
+       if ( event->button == 3 && event->type == GDK_BUTTON_PRESS ) {
+               if ( ButtonNum == 3 ){
+                       ToggleActions %= 2;
+               }
+               else{
+                       ToggleActions = 0;
+                       ButtonNum = 3;
+               }
+               if( ToggleActions == 0 ){
+                       SetCommonShader( "shader_clip", "textures/common/clip" );
+               }
+               else if( ToggleActions == 1 ){
+                       SetCommonShader( "shader_weapclip", "textures/common/weapclip" );
+               }
+               ToggleActions++;
+               return TRUE;
+       }
+       return FALSE;
+}
+
+
+gboolean Liquids_button_press( ui::Widget widget, GdkEventButton *event, gpointer data ){
+       if ( event->button == 3 && event->type == GDK_BUTTON_PRESS ) {
+               if ( ButtonNum == 4 ){
+                       ToggleActions %= 3;
+               }
+               else{
+                       ToggleActions = 0;
+                       ButtonNum = 4;
+               }
+               if( ToggleActions == 0 ){
+                       SetCommonShader( "shader_watercaulk", "textures/common/watercaulk" );
+               }
+               else if( ToggleActions == 1 ){
+                       SetCommonShader( "shader_lavacaulk", "textures/common/lavacaulk" );
+               }
+               else if( ToggleActions == 2 ){
+                       SetCommonShader( "shader_slimecaulk", "textures/common/slimecaulk" );
+               }
+               ToggleActions++;
+               return TRUE;
+       }
+       return FALSE;
+}
+
+
+gboolean Hint_button_press( ui::Widget widget, GdkEventButton *event, gpointer data ){
+       if ( event->button == 3 && event->type == GDK_BUTTON_PRESS ) {
+               if ( ButtonNum == 5 ){
+                       ToggleActions %= 3;
+               }
+               else{
+                       ToggleActions = 0;
+                       ButtonNum = 5;
+               }
+               if( ToggleActions == 0 ){
+                       SetCommonShader( "shader_hint", "textures/common/hint" );
+               }
+               else if( ToggleActions == 1 ){
+                       SetCommonShader( "shader_hintlocal", "textures/common/hintlocal" );
+               }
+               else if( ToggleActions == 2 ){
+                       SetCommonShader( "shader_hintskip", "textures/common/hintskip" );
+               }
+               ToggleActions++;
+               return TRUE;
+       }
+       return FALSE;
+}
+
+
+gboolean Trigger_button_press( ui::Widget widget, GdkEventButton *event, gpointer data ){
+       if ( event->button == 3 && event->type == GDK_BUTTON_PRESS ) {
+               SetCommonShader( "shader_trigger", "textures/common/trigger" );
+               ToggleActions = 0;
+               return TRUE;
+       }
+       return FALSE;
+}
+
+gboolean Func_Groups_button_press( GtkWidget *widget, GdkEventButton *event, gpointer data ){
+       if ( event->button == 3 && event->type == GDK_BUTTON_PRESS ) {
+               UndoableCommand undo( "create func_group" );
+               Entity_createFromSelection( "func_group", g_vector3_identity );
+               ToggleActions = 0;
+               return TRUE;
+       }
+       return FALSE;
+}
+
+
+gboolean Detail_button_press( ui::Widget widget, GdkEventButton *event, gpointer data ){
+       if ( event->button == 3 && event->type == GDK_BUTTON_PRESS ) {
+               GlobalCommands_find( "MakeDetail" ).m_callback();
+               ToggleActions = 0;
+               return TRUE;
+       }
+       return FALSE;
+}
+
+
+gboolean Structural_button_press( ui::Widget widget, GdkEventButton *event, gpointer data ){
+       if ( event->button == 3 && event->type == GDK_BUTTON_PRESS ) {
+               GlobalCommands_find( "MakeStructural" ).m_callback();
+               ToggleActions = 0;
+               return TRUE;
+       }
+       return FALSE;
+}
+
+gboolean Region_button_press( ui::Widget widget, GdkEventButton *event, gpointer data ){
+       if ( event->button == 3 && event->type == GDK_BUTTON_PRESS ) {
+               GlobalCommands_find( "RegionOff" ).m_callback();
+               ToggleActions = 0;
+               return TRUE;
+       }
+       return FALSE;
+}
+
+gboolean Hide_button_press( GtkWidget *widget, GdkEventButton *event, gpointer data ){
+       if ( event->button == 3 && event->type == GDK_BUTTON_PRESS ) {
+               GlobalCommands_find( "ShowHidden" ).m_callback();
+               ToggleActions = 0;
+               return TRUE;
+       }
+       return FALSE;
+}
+
+ui::Toolbar create_filter_toolbar(){
+                       auto toolbar = ui::Toolbar::from( gtk_toolbar_new() );
+                       gtk_orientable_set_orientation( GTK_ORIENTABLE(toolbar), GTK_ORIENTATION_HORIZONTAL );
+                       gtk_toolbar_set_style( toolbar, GTK_TOOLBAR_ICONS );
+//                     gtk_toolbar_set_show_arrow( toolbar, TRUE );
+                       toolbar.show();
+
+
+                       auto space = [&]() {
+                               auto btn = ui::ToolItem::from(gtk_separator_tool_item_new());
+                               btn.show();
+                               toolbar.add(btn);
+                       };
+
+                       g_signal_connect( G_OBJECT( toolbar ), "enter_notify_event", G_CALLBACK( ToggleActions0 ), 0 );
+
+                       toolbar_append_toggle_button( toolbar, "World (ALT + 1)", "f-world.png", "FilterWorldBrushes" );
+
+                       {
+                               auto button = toolbar_append_toggle_button( toolbar, "Structural (CTRL + SHIFT + D)\nRightClick: MakeStructural", "f-structural.png", "FilterStructural" );
+                               g_signal_connect( G_OBJECT( button ), "button_press_event", G_CALLBACK( Structural_button_press ), 0 );
+                       }
+
+                       {
+                               auto button = toolbar_append_toggle_button( toolbar, "Details (CTRL + D)\nRightClick: MakeDetail", "f-details.png", "FilterDetails" );
+                               g_signal_connect( G_OBJECT( button ), "button_press_event", G_CALLBACK( Detail_button_press ), 0 );
+                       }
+
+                       {
+                               auto button = toolbar_append_toggle_button( toolbar, "Func_Groups\nRightClick: create func_group", "f-funcgroups.png", "FilterFuncGroups" );
+                               g_signal_connect( G_OBJECT( button ), "button_press_event", G_CALLBACK( Func_Groups_button_press ), 0 );
+
+                               toolbar_append_toggle_button( toolbar, "Patches (CTRL + P)", "patch_wireframe.png", "FilterPatches" );
+                       }
+
+                       space();
+
+                       {
+                               if ( g_pGameDescription->mGameType == "doom3" ) {
+                                       auto button = toolbar_append_toggle_button( toolbar, "Areaportals (ALT + 3)\nRightClick: toggle tex\n\tnoDraw\n\tnoDrawNonSolid", "f-areaportal.png", "FilterVisportals" );
+                                       g_signal_connect( G_OBJECT( button ), "button_press_event", G_CALLBACK( Areaportals_button_press ), 0 );
+                               }
+                               else{
+                                       auto button = toolbar_append_toggle_button( toolbar, "Areaportals (ALT + 3)\nRightClick: toggle tex\n\tnoDraw\n\tnoDrawNonSolid", "f-areaportal.png", "FilterAreaportals" );
+                                       g_signal_connect( G_OBJECT( button ), "button_press_event", G_CALLBACK( Areaportals_button_press ), 0 );
+                               }
+                       }
+
+                       toolbar_append_toggle_button( toolbar, "Translucent (ALT + 4)", "f-translucent.png", "FilterTranslucent" );
+
+                       {
+                               auto button = toolbar_append_toggle_button( toolbar, "Liquids (ALT + 5)\nRightClick: toggle tex\n\twaterCaulk\n\tlavaCaulk\n\tslimeCaulk", "f-liquids.png", "FilterLiquids" );
+                               g_signal_connect( G_OBJECT( button ), "button_press_event", G_CALLBACK( Liquids_button_press ), 0 );
+                       }
+
+                       {
+                               auto button = toolbar_append_toggle_button( toolbar, "Caulk (ALT + 6)\nRightClick: tex Caulk", "f-caulk.png", "FilterCaulk" );
+                               g_signal_connect( G_OBJECT( button ), "button_press_event", G_CALLBACK( Caulk_button_press ), 0 );
+                       }
+
+                       {
+                               auto button = toolbar_append_toggle_button( toolbar, "Clips (ALT + 7)\nRightClick: toggle tex\n\tplayerClip\n\tweapClip", "f-clip.png", "FilterClips" );
+                               g_signal_connect( G_OBJECT( button ), "button_press_event", G_CALLBACK( Clip_button_press ), 0 );
+                       }
+
+                       {
+                               auto button = toolbar_append_toggle_button( toolbar, "HintsSkips (CTRL + H)\nRightClick: toggle tex\n\thint\n\thintLocal\n\thintSkip", "f-hint.png", "FilterHintsSkips" );
+                               g_signal_connect( G_OBJECT( button ), "button_press_event", G_CALLBACK( Hint_button_press ), 0 );
+                       }
+
+                       //toolbar_append_toggle_button( toolbar, "Paths (ALT + 8)", "texture_lock.png", "FilterPaths" );
+
+                       space();
+
+                       toolbar_append_toggle_button( toolbar, "Entities (ALT + 2)", "f-entities.png", "FilterEntities" );
+                       toolbar_append_toggle_button( toolbar, "Lights (ALT + 0)", "f-lights.png", "FilterLights" );
+                       toolbar_append_toggle_button( toolbar, "Models (SHIFT + M)", "f-models.png", "FilterModels" );
+
+                       {
+                               auto button = toolbar_append_toggle_button( toolbar, "Triggers (CTRL + SHIFT + T)\nRightClick: tex Trigger", "f-triggers.png", "FilterTriggers" );
+                               g_signal_connect( G_OBJECT( button ), "button_press_event", G_CALLBACK( Trigger_button_press ), 0 );
+                       }
+
+                       //toolbar_append_toggle_button( toolbar, "Decals (SHIFT + D)", "f-decals.png", "FilterDecals" );
+
+                       space();
+
+                       toolbar_append_button( toolbar, "InvertFilters", "f-invert.png", "InvertFilters" );
+
+                       toolbar_append_button( toolbar, "ResetFilters", "f-reset.png", "ResetFilters" );
+
+                       space();
+
+                       {
+                               auto button = toolbar_append_toggle_button( toolbar, "Region Set Selection (CTRL + SHIFT + R)\nRightClick: Region Off", "f-region.png", "RegionSetSelection" );
+                               g_signal_connect( G_OBJECT( button ), "button_press_event", G_CALLBACK( Region_button_press ), 0 );
+                       }
+
+                       {
+                               auto button = toolbar_append_toggle_button( toolbar, "Hide Selected (H)\nRightClick: Show Hidden (SHIFT + H)", "f-hide.png", "HideSelected" );
+                               g_signal_connect( G_OBJECT( button ), "button_press_event", G_CALLBACK( Hide_button_press ), 0 );
+                       }
+
+                       return toolbar;
+}
diff --git a/radiant/filterbar.h b/radiant/filterbar.h
new file mode 100644 (file)
index 0000000..ee70536
--- /dev/null
@@ -0,0 +1,8 @@
+#if !defined( INCLUDED_FILTERBAR_H )
+#define INCLUDED_FILTERBAR_H
+
+#include <uilib/uilib.h>
+
+ui::Toolbar create_filter_toolbar();
+
+#endif
index 3957af62e41b74fd9df16a629fb07b006d0ce800..3a9516056679556478d3fe9809e2db4f2ab4d96a 100644 (file)
@@ -205,6 +205,7 @@ void Filters_constructMenu( ui::Menu menu_in_menu ){
                create_check_menu_item_with_mnemonic( menu_in_menu, "Botclips", "FilterBotClips" );
                create_check_menu_item_with_mnemonic( menu_in_menu, "Decals", "FilterDecals" );
        }
+       create_check_menu_item_with_mnemonic( menu_in_menu, "FuncGroups", "FilterFuncGroups" );
        // filter manipulation
        menu_separator( menu_in_menu );
        create_menu_item_with_mnemonic( menu_in_menu, "Invert filters", "InvertFilters" );
@@ -252,6 +253,7 @@ void ConstructFilters(){
                add_filter_command( EXCLUDE_BOTCLIP, "FilterBotClips", Accelerator( 'M', (GdkModifierType)GDK_MOD1_MASK ) );
                add_filter_command( EXCLUDE_DECALS, "FilterDecals", Accelerator( 'D', (GdkModifierType)GDK_SHIFT_MASK ) );
        }
+       add_filter_command( EXCLUDE_FUNC_GROUPS, "FilterFuncGroups", accelerator_null() );
 
        PerformFiltering();
 }
index d74fc6578ac7dcabc442a1ee328a58019e839659..fcb78edd0d8e3bf7ea34553b0d174124c58a3e1d 100644 (file)
@@ -46,6 +46,7 @@
 class FindTextureDialog : public Dialog
 {
 public:
+WindowPositionTracker m_position_tracker;
 static void setReplaceStr( const char* name );
 static void setFindStr( const char* name );
 static bool isOpen();
@@ -122,6 +123,7 @@ static gint replace_focus_in( ui::Widget widget, GdkEventFocus *event, gpointer
 
 FindTextureDialog::FindTextureDialog(){
        m_bSelectedOnly = FALSE;
+       m_position_tracker.setPosition( WindowPosition( -1, -1, 0, 0 ) );
 }
 
 FindTextureDialog::~FindTextureDialog(){
@@ -134,6 +136,8 @@ ui::Window FindTextureDialog::BuildDialog(){
 
        auto dlg = ui::Window(create_floating_window( "Find / Replace Texture(s)", m_parent ));
 
+       m_position_tracker.connect( dlg );
+
        auto hbox = ui::HBox( FALSE, 5 );
        hbox.show();
        dlg.add(hbox);
@@ -154,7 +158,8 @@ ui::Window FindTextureDialog::BuildDialog(){
     table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
        gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
 
-       label = ui::Label( "Replace:" );
+       label = ui::Label( "Replace:*" );
+       gtk_widget_set_tooltip_text( label, "Empty = search mode" );
        label.show();
     table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
        gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
@@ -168,6 +173,7 @@ ui::Window FindTextureDialog::BuildDialog(){
        GlobalTextureEntryCompletion::instance().connect( entry );
 
        entry = ui::Entry(ui::New);
+       gtk_widget_set_tooltip_text( entry, "Empty = search mode" );
        entry.show();
     table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
        entry.connect( "focus_in_event",
@@ -230,7 +236,10 @@ void FindTextureDialog::setReplaceStr( const char* name ){
 }
 
 void FindTextureDialog::show(){
+       // workaround for strange gtk behaviour - modifying the contents of a window while it is not visible causes the window position to change without sending a configure_event
+       g_FindTextureDialog.m_position_tracker.sync( g_FindTextureDialog.GetWidget() );
        g_FindTextureDialog.ShowDlg();
+       gtk_window_present( g_FindTextureDialog.GetWidget() );
 }
 
 
@@ -250,8 +259,11 @@ void FindTextureDialog_selectTexture( const char* name ){
        g_FindTextureDialog.updateTextures( name );
 }
 
+#include "preferencesystem.h"
+
 void FindTextureDialog_Construct(){
        GlobalCommands_insert( "FindReplaceTextures", FindTextureDialog::ShowCaller() );
+       GlobalPreferenceSystem().registerPreference( "FindReplacehWnd",  make_property_string<WindowPositionTracker_String>( g_FindTextureDialog.m_position_tracker ) );
 }
 
 void FindTextureDialog_Destroy(){
index df1e1c1a2e0df41a3a0607f6813622a1f036c3f2..8bfb21af194d6b1a99c1e015c654e302fc6b6de2 100644 (file)
@@ -228,7 +228,7 @@ void Grid_constructMenu( ui::Menu menu ){
 }
 
 void Grid_registerShortcuts(){
-       command_connect_accelerator( "ToggleGrid" );
+//     command_connect_accelerator( "ToggleGrid" );
        command_connect_accelerator( "GridDown" );
        command_connect_accelerator( "GridUp" );
        command_connect_accelerator( "ToggleGridSnap" );
index d96bc7c271bea3be9beae9c5f8974eb08d7bd0d0..b7054e2b54a53e7bb56d4f917ebfafeab3da9f4f 100644 (file)
@@ -59,6 +59,17 @@ void Create( ui::Window parent );
 void Show(){
        // workaround for strange gtk behaviour - modifying the contents of a window while it is not visible causes the window position to change without sending a configure_event
        m_position_tracker.sync( m_window );
+#define GARUX_GTK_WORKAROUND
+#ifndef GARUX_GTK_WORKAROUND
+       /* workaround for gtk 2.24 issue: not displayed glwidget after toggle */
+       GtkWidget* glwidget = GTK_WIDGET( g_object_get_data( G_OBJECT( m_window ), "glwidget" ) );
+       if ( glwidget ){
+               //if ( widget_is_visible( glwidget ) )
+                       //globalOutputStream() << "glwidget have been already visible :0\n"; /* is not hidden aswell, according to this */
+               gtk_widget_hide( glwidget );
+               gtk_widget_show( glwidget );
+       }
+#endif
        m_window.show();
 }
 void Hide(){
index 4c09dc48aeb64cf710dbc31a5781b9eef9ff72d7..ad06904355b920f18737228bad3943e7c678f44d 100644 (file)
@@ -48,6 +48,7 @@
 
 #include <gdk/gdkkeysyms.h>
 #include <uilib/uilib.h>
+#include <gtk/gtkspinbutton.h>
 
 #include "os/path.h"
 #include "math/aabb.h"
@@ -69,6 +70,9 @@
 #include "url.h"
 #include "cmdlib.h"
 
+#include "qerplugin.h"
+#include "os/file.h"
+
 
 
 // =============================================================================
@@ -333,7 +337,7 @@ void ProjectSettingsDialog_ok( ProjectSettingsDialog& dialog ){
 }
 
 void DoProjectSettings(){
-       if ( ConfirmModified( "Edit Project Settings" ) ) {
+       //if ( ConfirmModified( "Edit Project Settings" ) ) {
                ModalDialog modal;
                ProjectSettingsDialog dialog;
 
@@ -344,7 +348,7 @@ void DoProjectSettings(){
                }
 
                window.destroy();
-       }
+       //}
 }
 
 // =============================================================================
@@ -352,6 +356,8 @@ void DoProjectSettings(){
 
 void DoSides( int type, int axis ){
        ModalDialog dialog;
+       //GtkEntry* sides_entry;
+       GtkWidget* sides_spin;
 
        auto window = MainFrame_getWindow().create_dialog_window("Arbitrary sides", G_CALLBACK(dialog_delete_callback ), &dialog );
 
@@ -367,11 +373,39 @@ void DoSides( int type, int axis ){
                        label.show();
                        hbox.pack_start( label, FALSE, FALSE, 0 );
                }
+//             {
+//                     auto entry = sides_entry;
+//                     entry.show();
+//                     hbox.pack_start( entry, FALSE, FALSE, 0 );
+//                     gtk_widget_grab_focus( entry  );
+//             }
                {
-                       auto entry = sides_entry;
-                       entry.show();
-                       hbox.pack_start( entry, FALSE, FALSE, 0 );
-                       gtk_widget_grab_focus( entry  );
+                       GtkAdjustment* adj;
+                       EBrushPrefab BrushPrefabType = (EBrushPrefab)type;
+                       switch ( BrushPrefabType )
+                       {
+                       case eBrushPrism :
+                       case eBrushCone :
+                               adj = GTK_ADJUSTMENT( gtk_adjustment_new( 8, 3, 1022, 1, 10, 0 ) );
+                               break;
+                       case eBrushSphere :
+                               adj = GTK_ADJUSTMENT( gtk_adjustment_new( 8, 3, 31, 1, 10, 0 ) );
+                               break;
+                       case eBrushRock :
+                               adj = GTK_ADJUSTMENT( gtk_adjustment_new( 32, 10, 1000, 1, 10, 0 ) );
+                               break;
+                       default:
+                               adj = GTK_ADJUSTMENT( gtk_adjustment_new( 8, 3, 31, 1, 10, 0 ) );
+                               break;
+                       }
+
+                       GtkWidget* spin = gtk_spin_button_new( adj, 1, 0 );
+                       gtk_widget_show( spin );
+                       gtk_box_pack_start( GTK_BOX( hbox ), spin, FALSE, FALSE, 0 );
+                       gtk_widget_set_size_request( spin, 64, -1 );
+                       gtk_spin_button_set_numeric( GTK_SPIN_BUTTON( spin ), TRUE );
+
+                       sides_spin = spin;
                }
                {
             auto vbox = create_dialog_vbox( 4 );
@@ -391,9 +425,12 @@ void DoSides( int type, int axis ){
        }
 
        if ( modal_dialog_show( window, dialog ) == eIDOK ) {
-               const char *str = gtk_entry_get_text( sides_entry );
+//             const char *str = gtk_entry_get_text( sides_entry );
 
-               Scene_BrushConstructPrefab( GlobalSceneGraph(), (EBrushPrefab)type, atoi( str ), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
+//             Scene_BrushConstructPrefab( GlobalSceneGraph(), (EBrushPrefab)type, atoi( str ), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
+               gtk_spin_button_update ( GTK_SPIN_BUTTON( sides_spin ) );
+               int sides = static_cast<int>( gtk_spin_button_get_value( GTK_SPIN_BUTTON( sides_spin ) ) );
+               Scene_BrushConstructPrefab( GlobalSceneGraph(), (EBrushPrefab)type, sides, TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
        }
 
        window.destroy();
@@ -404,19 +441,19 @@ void DoSides( int type, int axis ){
 
 void about_button_changelog( ui::Widget widget, gpointer data ){
        StringOutputStream log( 256 );
-       log << "https://gitlab.com/xonotic/netradiant/commits/master";
+       log << "https://gitlab.com/xonotic/netradiant/-/commits/master";
        OpenURL( log.c_str() );
 }
 
 void about_button_credits( ui::Widget widget, gpointer data ){
        StringOutputStream cred( 256 );
-       cred << "https://gitlab.com/xonotic/netradiant/graphs/master";
+       cred << "https://gitlab.com/xonotic/netradiant/-/graphs/master";
        OpenURL( cred.c_str() );
 }
 
 void about_button_issues( ui::Widget widget, gpointer data ){
        StringOutputStream cred( 256 );
-       cred << "https://gitlab.com/xonotic/netradiant/issues";
+       cred << "https://gitlab.com/xonotic/netradiant/-/issues";
        OpenURL( cred.c_str() );
 }
 
@@ -664,13 +701,13 @@ EMessageBoxReturn DoTextureLayout( float *fx, float *fy ){
                        }
                }
        }
-       
+
        // Initialize with last used values
        char buf[16];
-       
+
        sprintf( buf, "%f", last_used_texture_layout_scale_x );
        x.text( buf );
-       
+
        sprintf( buf, "%f", last_used_texture_layout_scale_y );
        y.text( buf );
 
@@ -681,7 +718,7 @@ EMessageBoxReturn DoTextureLayout( float *fx, float *fy ){
        if ( ret == eIDOK ) {
                *fx = static_cast<float>( atof( gtk_entry_get_text( x ) ) );
                *fy = static_cast<float>( atof( gtk_entry_get_text( y ) ) );
-       
+
                // Remember last used values
                last_used_texture_layout_scale_x = *fx;
                last_used_texture_layout_scale_y = *fy;
@@ -698,12 +735,13 @@ EMessageBoxReturn DoTextureLayout( float *fx, float *fy ){
 // master window widget
 static ui::Window text_editor{ui::null};
 static ui::Widget text_widget{ui::null}; // slave, text widget from the gtk editor
+static GtkTextBuffer* text_buffer_;
 
 static gint editor_delete( ui::Widget widget, gpointer data ){
-       if ( ui::alert( widget.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
+/*     if ( ui::alert( widget.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
                return TRUE;
        }
-
+*/
        text_editor.hide();
 
        return TRUE;
@@ -711,32 +749,41 @@ static gint editor_delete( ui::Widget widget, gpointer data ){
 
 static void editor_save( ui::Widget widget, gpointer data ){
        FILE *f = fopen( (char*)g_object_get_data( G_OBJECT( data ), "filename" ), "w" );
-       gpointer text = g_object_get_data( G_OBJECT( data ), "text" );
+       //gpointer text = g_object_get_data( G_OBJECT( data ), "text" );
 
        if ( f == 0 ) {
                ui::alert( ui::Widget::from(data).window(), "Error saving file !" );
                return;
        }
 
-       char *str = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
+       /* Obtain iters for the start and end of points of the buffer */
+       GtkTextIter start;
+       GtkTextIter end;
+       gtk_text_buffer_get_start_iter (text_buffer_, &start);
+       gtk_text_buffer_get_end_iter (text_buffer_, &end);
+
+       /* Get the entire buffer text. */
+       char *str = gtk_text_buffer_get_text (text_buffer_, &start, &end, FALSE);
+
+       //char *str = gtk_editable_get_chars( GTK_EDITABLE( text ), 0, -1 );
        fwrite( str, 1, strlen( str ), f );
        fclose( f );
+       g_free (str);
 }
 
 static void editor_close( ui::Widget widget, gpointer data ){
-       if ( ui::alert( text_editor.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
+/*     if ( ui::alert( text_editor.window(), "Close the shader editor ?", "Radiant", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::NO ) {
                return;
        }
-
+*/
        text_editor.hide();
 }
 
 static void CreateGtkTextEditor(){
        auto dlg = ui::Window( ui::window_type::TOP );
 
-       dlg.connect( "delete_event",
-                                         G_CALLBACK( editor_delete ), 0 );
-       gtk_window_set_default_size( dlg, 600, 300 );
+       dlg.connect( "", G_CALLBACK( editor_delete ), 0 );
+       gtk_window_set_default_size( dlg, 400, 600 );
 
        auto vbox = ui::VBox( FALSE, 5 );
        vbox.show();
@@ -777,7 +824,7 @@ static void CreateGtkTextEditor(){
        text_widget = text;
 }
 
-static void DoGtkTextEditor( const char* filename, guint cursorpos ){
+static void DoGtkTextEditor( const char* filename, guint cursorpos, int length ){
        if ( !text_editor ) {
                CreateGtkTextEditor(); // build it the first time we need it
 
@@ -802,7 +849,7 @@ static void DoGtkTextEditor( const char* filename, guint cursorpos ){
                gtk_window_set_title( text_editor, filename );
 
                auto text_buffer = gtk_text_view_get_buffer(ui::TextView::from(text_widget));
-               gtk_text_buffer_set_text( text_buffer, (char*)buf, len );
+               gtk_text_buffer_set_text( text_buffer, (char*)buf, length );
 
                old_filename = g_object_get_data( G_OBJECT( text_editor ), "filename" );
                if ( old_filename ) {
@@ -812,10 +859,11 @@ static void DoGtkTextEditor( const char* filename, guint cursorpos ){
 
                // trying to show later
                text_editor.show();
+               gtk_window_present( GTK_WINDOW( text_editor ) );
 
-#if GDEF_OS_WINDOWS
+//#if GDEF_OS_WINDOWS
                ui::process();
-#endif
+//#endif
 
                // only move the cursor if it's not exceeding the size..
                // NOTE: this is erroneous, cursorpos is the offset in bytes, not in characters
@@ -826,12 +874,14 @@ static void DoGtkTextEditor( const char* filename, guint cursorpos ){
                        // character offset, not byte offset
                        gtk_text_buffer_get_iter_at_offset( text_buffer, &text_iter, cursorpos );
                        gtk_text_buffer_place_cursor( text_buffer, &text_iter );
+                       gtk_text_view_scroll_to_iter( GTK_TEXT_VIEW( text_widget ), &text_iter, 0, TRUE, 0, 0);
                }
 
-#if GDEF_OS_WINDOWS
+//#if GDEF_OS_WINDOWS
                gtk_widget_queue_draw( text_widget );
-#endif
+//#endif
 
+               text_buffer_ = text_buffer;
                free( buf );
                fclose( f );
        }
@@ -1023,40 +1073,72 @@ EMessageBoxReturn DoShaderInfoDlg( const char* name, const char* filename, const
 #include <gdk/gdkwin32.h>
 #endif
 
-#if GDEF_OS_WINDOWS
-// use the file associations to open files instead of builtin Gtk editor
-bool g_TextEditor_useWin32Editor = true;
-#else
-// custom shader editor
-bool g_TextEditor_useCustomEditor = false;
 CopiedString g_TextEditor_editorCommand( "" );
-#endif
 
-void DoTextEditor( const char* filename, int cursorpos ){
-#if GDEF_OS_WINDOWS
-       if ( g_TextEditor_useWin32Editor ) {
-               globalOutputStream() << "opening file '" << filename << "' (line " << cursorpos << " info ignored)\n";
-               ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", filename, 0, 0, SW_SHOW );
-               return;
+void DoTextEditor( const char* filename, int cursorpos, int length, bool external_editor ){
+       //StringOutputStream paths[4]( 256 );
+       StringOutputStream paths[4] = { StringOutputStream(256) };
+       StringOutputStream* goodpath = 0;
+
+       const char* gamename = GlobalRadiant().getGameName();
+       const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
+       const char* enginePath = GlobalRadiant().getEnginePath();
+       const char* homeEnginePath = g_qeglobals.m_userEnginePath.c_str();
+
+       paths[0] << homeEnginePath << gamename << '/' << filename;
+       paths[1] << enginePath << gamename << '/' << filename;
+       paths[2] << homeEnginePath << basegame << '/' << filename;
+       paths[3] << enginePath << basegame << '/' << filename;
+
+       for ( std::size_t i = 0; i < 4; ++i ){
+               if ( file_exists( paths[i].c_str() ) ){
+                       goodpath = &paths[i];
+                       break;
+               }
        }
+
+       if( goodpath ){
+               globalOutputStream() << "opening file '" << goodpath->c_str() << "' (line " << cursorpos << " info ignored)\n";
+               if( external_editor ){
+                       if( g_TextEditor_editorCommand.empty() ){
+#ifdef WIN32
+                               ShellExecute( (HWND)GDK_WINDOW_HWND( GTK_WIDGET( MainFrame_getWindow() )->window ), 0, goodpath->c_str(), 0, 0, SW_SHOWNORMAL );
+//                             SHELLEXECUTEINFO ShExecInfo;
+//                             ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
+//                             ShExecInfo.fMask = 0;
+//                             ShExecInfo.hwnd = (HWND)GDK_WINDOW_HWND( GTK_WIDGET( MainFrame_getWindow() )->window );
+//                             ShExecInfo.lpVerb = NULL;
+//                             ShExecInfo.lpFile = goodpath->c_str();
+//                             ShExecInfo.lpParameters = NULL;
+//                             ShExecInfo.lpDirectory = NULL;
+//                             ShExecInfo.nShow = SW_SHOWNORMAL;
+//                             ShExecInfo.hInstApp = NULL;
+//                             ShellExecuteEx(&ShExecInfo);
 #else
-       // check if a custom editor is set
-       if ( g_TextEditor_useCustomEditor && !g_TextEditor_editorCommand.empty() ) {
-               StringOutputStream strEditCommand( 256 );
-               strEditCommand << g_TextEditor_editorCommand.c_str() << " \"" << filename << "\"";
-
-               globalOutputStream() << "Launching: " << strEditCommand.c_str() << "\n";
-               // note: linux does not return false if the command failed so it will assume success
-               if ( Q_Exec( 0, const_cast<char*>( strEditCommand.c_str() ), 0, true, false ) == false ) {
-                       globalOutputStream() << "Failed to execute " << strEditCommand.c_str() << ", using default\n";
+                               globalOutputStream() << "Failed to open '" << goodpath->c_str() << "'\nSet Shader Editor Command in preferences\n";
+#endif
+                       }
+                       else{
+                               StringOutputStream strEditCommand( 256 );
+                               strEditCommand << g_TextEditor_editorCommand.c_str() << " \"" << goodpath->c_str() << "\"";
+
+                               globalOutputStream() << "Launching: " << strEditCommand.c_str() << "\n";
+                               // note: linux does not return false if the command failed so it will assume success
+                               if ( Q_Exec( 0, const_cast<char*>( strEditCommand.c_str() ), 0, true, false ) == false ) {
+                                       globalOutputStream() << "Failed to execute " << strEditCommand.c_str() << "\n";
+                               }
+                               else
+                               {
+                                       // the command (appeared) to run successfully, no need to do anything more
+                                       return;
+                               }
+                       }
                }
-               else
-               {
-                       // the command (appeared) to run successfully, no need to do anything more
-                       return;
+               else{
+                       DoGtkTextEditor( goodpath->c_str(), cursorpos, length );
                }
        }
-#endif
-
-       DoGtkTextEditor( filename, cursorpos );
+       else{
+               globalOutputStream() << "Failed to open '" << filename << "'\nOne sits in .pk3 most likely!\n";
+       }
 }
index 0dad793a95552463b4fafb7a11df2530765b7ff1..551102a4c5edccd25687409ad303128229685e4f 100644 (file)
@@ -39,7 +39,7 @@ EMessageBoxReturn DoLightIntensityDlg( int *intensity );
 EMessageBoxReturn DoShaderTagDlg( CopiedString *tag, const char* title );
 EMessageBoxReturn DoShaderInfoDlg( const char* name, const char* filename, const char* title );
 EMessageBoxReturn DoTextureLayout( float *fx, float *fy );
-void DoTextEditor( const char* filename, int cursorpos );
+void DoTextEditor( const char* filename, int cursorpos, int length, bool external_editor );
 
 void DoProjectSettings();
 
@@ -48,13 +48,8 @@ void DoSides( int type, int axis );
 void DoAbout();
 
 
-#if GDEF_OS_WINDOWS
-extern bool g_TextEditor_useWin32Editor;
-#else
 #include "string/stringfwd.h"
-extern bool g_TextEditor_useCustomEditor;
 extern CopiedString g_TextEditor_editorCommand;
-#endif
 
 
 #endif
index 85554502c3e1d4d4eef18ea229a4f665e3b5feb2..702f6ac4e38a339b2c7743bfccf60608495d7400 100644 (file)
@@ -43,7 +43,6 @@
 #include "gtkutil/dialog.h"
 #include "gtkutil/filechooser.h"
 #include "gtkutil/menu.h"
-#include "gtkutil/toolbar.h"
 #include "commands.h"
 
 
index e867d6a213b6611a38836666e246758725cb7231..2019452b90720b85b39922c1567ffdf63c3e16dd 100644 (file)
@@ -32,6 +32,7 @@
 #define INCLUDED_GTKMISC_H
 
 #include <uilib/uilib.h>
+#include "gtkutil/toolbar.h"
 
 void command_connect_accelerator( const char* commandName );
 void command_disconnect_accelerator( const char* commandName );
diff --git a/radiant/gtktheme.cpp b/radiant/gtktheme.cpp
new file mode 100644 (file)
index 0000000..c9508f5
--- /dev/null
@@ -0,0 +1,758 @@
+/***************************************************************************
+                          main.cpp  -  description
+                             -------------------
+    begin                : Wed Jan  1 19:06:46 GMT+4 2003
+    copyright            : (C) 2003 - 2005 by Alex Shaduri
+    email                : ashaduri '@' gmail.com
+ ***************************************************************************/
+
+#define GARUX_DISABLE_GTKTHEME
+#ifndef GARUX_DISABLE_GTKTHEME
+
+#include <iostream>
+#include <fstream>
+#include <stdlib.h>
+#include <sstream>
+#include <gtk/gtk.h>
+
+#ifdef _WIN32
+#include <io.h>
+#else
+#include <sys/stat.h>
+#endif
+
+#include <unistd.h>
+
+
+#include <sys/types.h>
+#include <string.h>
+#include <stdio.h>
+#include <gdk/gdkkeysyms.h>
+
+
+#include "gtktheme.h"
+#include "mainframe.h"
+//#include "gtkutil/window.h"
+
+// ------------------------------------------------------
+
+
+std::string& get_orig_theme();
+std::string& get_orig_font();
+
+std::string get_current_theme();
+std::string get_current_font();
+
+std::string get_selected_theme();
+std::string get_selected_font();
+
+void set_theme(const std::string& theme_name, const std::string& font);
+void apply_theme(const std::string& theme_name, const std::string& font);
+
+
+
+GtkWidget* g_main_rc_window = NULL;
+
+
+static std::string s_rc_file;
+
+
+
+// ------------------------------------------------------
+
+
+
+GtkWidget* lookup_widget (GtkWidget *widget, const gchar *widget_name){
+    GtkWidget *parent, *found_widget;
+
+    for (;;)
+    {
+        if (GTK_IS_MENU (widget))
+            parent = gtk_menu_get_attach_widget (GTK_MENU (widget));
+        else
+            parent = widget->parent;
+        if (!parent)
+            parent = (GtkWidget*)g_object_get_data (G_OBJECT (widget), "GladeParentKey");
+        if (parent == NULL)
+            break;
+        widget = parent;
+    }
+
+    found_widget = (GtkWidget*) g_object_get_data (G_OBJECT (widget),
+                   widget_name);
+    if (!found_widget)
+        g_warning ("Widget not found: %s", widget_name);
+    return found_widget;
+}
+
+
+
+
+
+
+void on_main_cancel_button_clicked( GtkButton *button, gpointer user_data ){
+       set_theme( get_orig_theme(), get_orig_font() );
+       gtk_widget_destroy( g_main_rc_window );
+       g_main_rc_window = NULL;
+}
+
+
+void on_main_reset_button_clicked( GtkButton *button, gpointer user_data ){
+       set_theme( get_orig_theme(), get_orig_font() );
+}
+
+
+gboolean on_main_window_delete_event(GtkWidget *widget, GdkEvent *event, gpointer user_data ){
+       set_theme( get_orig_theme(), get_orig_font() );
+       gtk_widget_destroy( g_main_rc_window );
+       g_main_rc_window = NULL;
+       return TRUE;
+}
+
+
+void on_main_use_default_font_radio_toggled ( GtkToggleButton *togglebutton, gpointer user_data ){
+       bool default_font = gtk_toggle_button_get_active( togglebutton );
+
+       gtk_widget_set_sensitive( lookup_widget( g_main_rc_window, "main_font_selector_button" ), !default_font );
+
+       apply_theme( get_selected_theme(), get_selected_font() );
+}
+
+
+void on_main_font_selector_button_font_set( GtkFontButton *fontbutton, gpointer user_data ){
+       apply_theme( get_selected_theme(), get_selected_font() );
+}
+
+void on_main_ok_button_clicked( GtkButton *button, gpointer user_data ){
+       gtk_widget_destroy( g_main_rc_window );
+       g_main_rc_window = NULL;
+}
+
+
+
+#define GLADE_HOOKUP_OBJECT(component,widget,name) \
+  g_object_set_data_full (G_OBJECT (component), name, \
+    gtk_widget_ref (widget), (GDestroyNotify) gtk_widget_unref)
+
+#define GLADE_HOOKUP_OBJECT_NO_REF(component,widget,name) \
+  g_object_set_data (G_OBJECT (component), name, widget)
+
+GtkWidget*
+create_rc_window (void)
+{
+  GtkWidget *main_window;
+  GtkWidget *main_hbox;
+  GtkWidget *vbox1;
+  GtkWidget *hbox1;
+  GtkWidget *frame2;
+  GtkWidget *alignment3;
+  GtkWidget *scrolledwindow3;
+  GtkWidget *main_themelist;
+  GtkWidget *label1234;
+  GtkWidget *frame3;
+  GtkWidget *alignment4;
+  GtkWidget *vbox7;
+  GtkWidget *hbox8;
+  GtkWidget *vbox9;
+  GtkWidget *main_use_default_font_radio;
+  GSList *main_use_default_font_radio_group = NULL;
+  GtkWidget *main_use_custom_font_radio;
+  GtkWidget *alignment5;
+  GtkWidget *vbox10;
+  GtkWidget *hbox9;
+  GtkWidget *vbox11;
+  GtkWidget *main_font_selector_button;
+  GtkWidget *label669;
+  GtkWidget *vbox13;
+  GtkWidget *hbuttonbox1;
+  GtkWidget *hbox7;
+  GtkWidget *vbox6;
+  GtkWidget *hbox5;
+  GtkWidget *main_ok_button;
+  GtkWidget *main_cancel_button;
+  GtkWidget *main_reset_button;
+  GtkWidget *alignment2;
+  GtkWidget *hbox6;
+  GtkWidget *image1;
+  GtkWidget *label667;
+  GtkAccelGroup *accel_group;
+  GtkTooltips *tooltips;
+
+  tooltips = gtk_tooltips_new ();
+
+  accel_group = gtk_accel_group_new ();
+
+  main_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  gtk_widget_set_name (main_window, "main_window");
+  gtk_window_set_title (GTK_WINDOW (main_window), "Gtk2 Theme Selector");
+  gtk_window_set_transient_for( GTK_WINDOW (main_window), MainFrame_getWindow() );
+  gtk_window_set_destroy_with_parent( GTK_WINDOW (main_window), TRUE );
+
+  //gtk_window_set_keep_above ( GTK_WINDOW( main_window ), TRUE );
+
+  main_hbox = gtk_hbox_new (FALSE, 0);
+  gtk_widget_set_name (main_hbox, "main_hbox");
+  gtk_widget_show (main_hbox);
+  gtk_container_add (GTK_CONTAINER (main_window), main_hbox);
+  gtk_widget_set_size_request (main_hbox, 310, 320);
+  gtk_window_resize(GTK_WINDOW(main_window), 310, 640);
+
+  vbox1 = gtk_vbox_new (FALSE, 0);
+  gtk_widget_set_name (vbox1, "vbox1");
+  gtk_widget_show (vbox1);
+  gtk_box_pack_start (GTK_BOX (main_hbox), vbox1, TRUE, TRUE, 0);
+  gtk_container_set_border_width (GTK_CONTAINER (vbox1), 3);
+
+  hbox1 = gtk_hbox_new (FALSE, 0);
+  gtk_widget_set_name (hbox1, "hbox1");
+  gtk_widget_show (hbox1);
+  gtk_box_pack_start (GTK_BOX (vbox1), hbox1, TRUE, TRUE, 0);
+
+  frame2 = gtk_frame_new (NULL);
+  gtk_widget_set_name (frame2, "frame2");
+  gtk_widget_show (frame2);
+  gtk_box_pack_start (GTK_BOX (hbox1), frame2, TRUE, TRUE, 0);
+  gtk_frame_set_shadow_type (GTK_FRAME (frame2), GTK_SHADOW_NONE);
+
+  alignment3 = gtk_alignment_new (0.5, 0.5, 1, 1);
+  gtk_widget_set_name (alignment3, "alignment3");
+  gtk_widget_show (alignment3);
+  gtk_container_add (GTK_CONTAINER (frame2), alignment3);
+  gtk_alignment_set_padding (GTK_ALIGNMENT (alignment3), 0, 0, 12, 0);
+
+  scrolledwindow3 = gtk_scrolled_window_new (NULL, NULL);
+  gtk_widget_set_name (scrolledwindow3, "scrolledwindow3");
+  gtk_widget_show (scrolledwindow3);
+  gtk_container_add (GTK_CONTAINER (alignment3), scrolledwindow3);
+  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow3), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow3), GTK_SHADOW_IN);
+
+  main_themelist = gtk_tree_view_new ();
+  gtk_widget_set_name (main_themelist, "main_themelist");
+  gtk_widget_show (main_themelist);
+  gtk_container_add (GTK_CONTAINER (scrolledwindow3), main_themelist);
+  GTK_WIDGET_SET_FLAGS (main_themelist, GTK_CAN_DEFAULT);
+  gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (main_themelist), FALSE);
+
+  label1234 = gtk_label_new ("<b>Theme</b>");
+  gtk_widget_set_name (label1234, "label1234");
+  gtk_widget_show (label1234);
+  gtk_frame_set_label_widget (GTK_FRAME (frame2), label1234);
+  gtk_label_set_use_markup (GTK_LABEL (label1234), TRUE);
+
+  frame3 = gtk_frame_new (NULL);
+  gtk_widget_set_name (frame3, "frame3");
+  gtk_widget_show (frame3);
+  gtk_box_pack_start (GTK_BOX (vbox1), frame3, FALSE, FALSE, 9);
+  gtk_frame_set_shadow_type (GTK_FRAME (frame3), GTK_SHADOW_NONE);
+
+  alignment4 = gtk_alignment_new (0.5, 0.5, 1, 1);
+  gtk_widget_set_name (alignment4, "alignment4");
+  gtk_widget_show (alignment4);
+  gtk_container_add (GTK_CONTAINER (frame3), alignment4);
+  gtk_alignment_set_padding (GTK_ALIGNMENT (alignment4), 0, 0, 12, 0);
+
+  vbox7 = gtk_vbox_new (FALSE, 0);
+  gtk_widget_set_name (vbox7, "vbox7");
+  gtk_widget_show (vbox7);
+  gtk_container_add (GTK_CONTAINER (alignment4), vbox7);
+
+  hbox8 = gtk_hbox_new (FALSE, 0);
+  gtk_widget_set_name (hbox8, "hbox8");
+  gtk_widget_show (hbox8);
+  gtk_box_pack_start (GTK_BOX (vbox7), hbox8, FALSE, FALSE, 0);
+
+  vbox9 = gtk_vbox_new (FALSE, 0);
+  gtk_widget_set_name (vbox9, "vbox9");
+  gtk_widget_show (vbox9);
+  gtk_box_pack_start (GTK_BOX (hbox8), vbox9, TRUE, TRUE, 0);
+
+  main_use_default_font_radio = gtk_radio_button_new_with_mnemonic (NULL, "Use theme default font");
+  gtk_widget_set_name (main_use_default_font_radio, "main_use_default_font_radio");
+  gtk_widget_show (main_use_default_font_radio);
+  gtk_box_pack_start (GTK_BOX (vbox9), main_use_default_font_radio, FALSE, FALSE, 0);
+  gtk_radio_button_set_group (GTK_RADIO_BUTTON (main_use_default_font_radio), main_use_default_font_radio_group);
+  main_use_default_font_radio_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (main_use_default_font_radio));
+
+  main_use_custom_font_radio = gtk_radio_button_new_with_mnemonic (NULL, "Use custom font:");
+  gtk_widget_set_name (main_use_custom_font_radio, "main_use_custom_font_radio");
+  gtk_widget_show (main_use_custom_font_radio);
+  gtk_box_pack_start (GTK_BOX (vbox9), main_use_custom_font_radio, FALSE, FALSE, 0);
+  gtk_radio_button_set_group (GTK_RADIO_BUTTON (main_use_custom_font_radio), main_use_default_font_radio_group);
+  main_use_default_font_radio_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (main_use_custom_font_radio));
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (main_use_custom_font_radio), TRUE);
+  //gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (main_use_custom_font_radio), FALSE);
+
+  alignment5 = gtk_alignment_new (0.5, 0.5, 1, 1);
+  gtk_widget_set_name (alignment5, "alignment5");
+  gtk_widget_show (alignment5);
+  gtk_box_pack_start (GTK_BOX (vbox9), alignment5, TRUE, TRUE, 0);
+  gtk_alignment_set_padding (GTK_ALIGNMENT (alignment5), 0, 0, 12, 0);
+
+  vbox10 = gtk_vbox_new (FALSE, 0);
+  gtk_widget_set_name (vbox10, "vbox10");
+  gtk_widget_show (vbox10);
+  gtk_container_add (GTK_CONTAINER (alignment5), vbox10);
+
+  hbox9 = gtk_hbox_new (FALSE, 0);
+  gtk_widget_set_name (hbox9, "hbox9");
+  gtk_widget_show (hbox9);
+  gtk_box_pack_start (GTK_BOX (vbox10), hbox9, FALSE, FALSE, 0);
+
+  vbox11 = gtk_vbox_new (FALSE, 0);
+  gtk_widget_set_name (vbox11, "vbox11");
+  gtk_widget_show (vbox11);
+  gtk_box_pack_start (GTK_BOX (hbox9), vbox11, TRUE, TRUE, 0);
+
+  main_font_selector_button = gtk_font_button_new ();
+  gtk_widget_set_name (main_font_selector_button, "main_font_selector_button");
+  gtk_widget_show (main_font_selector_button);
+  gtk_box_pack_start (GTK_BOX (vbox11), main_font_selector_button, FALSE, FALSE, 0);
+
+  label669 = gtk_label_new ("<b>Font</b>");
+  gtk_widget_set_name (label669, "label669");
+  gtk_widget_show (label669);
+  gtk_frame_set_label_widget (GTK_FRAME (frame3), label669);
+  gtk_label_set_use_markup (GTK_LABEL (label669), TRUE);
+
+  vbox13 = gtk_vbox_new (FALSE, 0);
+  gtk_widget_set_name (vbox13, "vbox13");
+  gtk_widget_show (vbox13);
+  gtk_box_pack_start (GTK_BOX (vbox1), vbox13, FALSE, FALSE, 0);
+
+  hbuttonbox1 = gtk_hbutton_box_new ();
+  gtk_widget_set_name (hbuttonbox1, "hbuttonbox1");
+  gtk_widget_show (hbuttonbox1);
+  gtk_box_pack_start (GTK_BOX (vbox1), hbuttonbox1, FALSE, FALSE, 0);
+
+  hbox7 = gtk_hbox_new (FALSE, 0);
+  gtk_widget_set_name (hbox7, "hbox7");
+  gtk_widget_show (hbox7);
+  gtk_box_pack_start (GTK_BOX (vbox1), hbox7, FALSE, TRUE, 6);
+
+  vbox6 = gtk_vbox_new (FALSE, 0);
+  gtk_widget_set_name (vbox6, "vbox6");
+  gtk_widget_show (vbox6);
+  gtk_box_pack_start (GTK_BOX (vbox1), vbox6, FALSE, FALSE, 0);
+
+  hbox5 = gtk_hbox_new (TRUE, 0);
+  gtk_widget_set_name (hbox5, "hbox5");
+  gtk_widget_show (hbox5);
+  gtk_box_pack_start (GTK_BOX (vbox6), hbox5, FALSE, FALSE, 0);
+  gtk_container_set_border_width (GTK_CONTAINER (hbox5), 4);
+
+  main_ok_button = gtk_button_new_from_stock ("gtk-ok");
+  gtk_widget_set_name (main_ok_button, "main_ok_button");
+  gtk_widget_show (main_ok_button);
+  gtk_box_pack_end (GTK_BOX (hbox5), main_ok_button, TRUE, TRUE, 4);
+  GTK_WIDGET_SET_FLAGS (main_ok_button, GTK_CAN_DEFAULT);
+
+  main_cancel_button = gtk_button_new_from_stock ("gtk-cancel");
+  gtk_widget_set_name (main_cancel_button, "main_cancel_button");
+  gtk_widget_show (main_cancel_button);
+  gtk_box_pack_end (GTK_BOX (hbox5), main_cancel_button, TRUE, TRUE, 4);
+  GTK_WIDGET_SET_FLAGS (main_cancel_button, GTK_CAN_DEFAULT);
+
+  main_reset_button = gtk_button_new ();
+  gtk_widget_set_name (main_reset_button, "main_reset_button");
+  gtk_widget_show (main_reset_button);
+  gtk_box_pack_end (GTK_BOX (hbox5), main_reset_button, TRUE, TRUE, 4);
+
+  alignment2 = gtk_alignment_new (0.5, 0.5, 0, 0);
+  gtk_widget_set_name (alignment2, "alignment2");
+  gtk_widget_show (alignment2);
+  gtk_container_add (GTK_CONTAINER (main_reset_button), alignment2);
+
+  hbox6 = gtk_hbox_new (FALSE, 2);
+  gtk_widget_set_name (hbox6, "hbox6");
+  gtk_widget_show (hbox6);
+  gtk_container_add (GTK_CONTAINER (alignment2), hbox6);
+
+  image1 = gtk_image_new_from_stock ("gtk-revert-to-saved", GTK_ICON_SIZE_BUTTON);
+  gtk_widget_set_name (image1, "image1");
+  gtk_widget_show (image1);
+  gtk_box_pack_start (GTK_BOX (hbox6), image1, FALSE, FALSE, 0);
+
+  label667 = gtk_label_new_with_mnemonic ("_Reset");
+  gtk_widget_set_name (label667, "label667");
+  gtk_widget_show (label667);
+  gtk_box_pack_start (GTK_BOX (hbox6), label667, FALSE, FALSE, 0);
+
+
+  g_signal_connect ((gpointer) main_window, "delete_event",
+                    G_CALLBACK (on_main_window_delete_event),
+                    NULL);
+  g_signal_connect ((gpointer) main_use_default_font_radio, "toggled",
+                    G_CALLBACK (on_main_use_default_font_radio_toggled),
+                    NULL);
+  g_signal_connect ((gpointer) main_font_selector_button, "font_set",
+                    G_CALLBACK (on_main_font_selector_button_font_set),
+                    NULL);
+  g_signal_connect ((gpointer) main_cancel_button, "clicked",
+                    G_CALLBACK (on_main_cancel_button_clicked),
+                    NULL);
+  g_signal_connect ((gpointer) main_reset_button, "clicked",
+                    G_CALLBACK (on_main_reset_button_clicked),
+                    NULL);
+  g_signal_connect ((gpointer) main_ok_button, "clicked",
+                    G_CALLBACK (on_main_ok_button_clicked),
+                    NULL);
+
+  /* Store pointers to all widgets, for use by lookup_widget(). */
+  GLADE_HOOKUP_OBJECT_NO_REF (main_window, main_window, "main_window");
+  GLADE_HOOKUP_OBJECT (main_window, main_hbox, "main_hbox");
+  GLADE_HOOKUP_OBJECT (main_window, vbox1, "vbox1");
+  GLADE_HOOKUP_OBJECT (main_window, hbox1, "hbox1");
+  GLADE_HOOKUP_OBJECT (main_window, frame2, "frame2");
+  GLADE_HOOKUP_OBJECT (main_window, alignment3, "alignment3");
+  GLADE_HOOKUP_OBJECT (main_window, scrolledwindow3, "scrolledwindow3");
+  GLADE_HOOKUP_OBJECT (main_window, main_themelist, "main_themelist");
+  GLADE_HOOKUP_OBJECT (main_window, label1234, "label1234");
+  GLADE_HOOKUP_OBJECT (main_window, frame3, "frame3");
+  GLADE_HOOKUP_OBJECT (main_window, alignment4, "alignment4");
+  GLADE_HOOKUP_OBJECT (main_window, vbox7, "vbox7");
+  GLADE_HOOKUP_OBJECT (main_window, hbox8, "hbox8");
+  GLADE_HOOKUP_OBJECT (main_window, vbox9, "vbox9");
+  GLADE_HOOKUP_OBJECT (main_window, main_use_default_font_radio, "main_use_default_font_radio");
+  GLADE_HOOKUP_OBJECT (main_window, main_use_custom_font_radio, "main_use_custom_font_radio");
+  GLADE_HOOKUP_OBJECT (main_window, alignment5, "alignment5");
+  GLADE_HOOKUP_OBJECT (main_window, vbox10, "vbox10");
+  GLADE_HOOKUP_OBJECT (main_window, hbox9, "hbox9");
+  GLADE_HOOKUP_OBJECT (main_window, vbox11, "vbox11");
+  GLADE_HOOKUP_OBJECT (main_window, main_font_selector_button, "main_font_selector_button");
+  GLADE_HOOKUP_OBJECT (main_window, label669, "label669");
+  GLADE_HOOKUP_OBJECT (main_window, vbox13, "vbox13");
+  GLADE_HOOKUP_OBJECT (main_window, hbuttonbox1, "hbuttonbox1");
+  GLADE_HOOKUP_OBJECT (main_window, hbox7, "hbox7");
+  GLADE_HOOKUP_OBJECT (main_window, vbox6, "vbox6");
+  GLADE_HOOKUP_OBJECT (main_window, hbox5, "hbox5");
+  GLADE_HOOKUP_OBJECT (main_window, main_ok_button, "main_ok_button");
+  GLADE_HOOKUP_OBJECT (main_window, main_cancel_button, "main_cancel_button");
+  GLADE_HOOKUP_OBJECT (main_window, main_reset_button, "main_reset_button");
+  GLADE_HOOKUP_OBJECT (main_window, alignment2, "alignment2");
+  GLADE_HOOKUP_OBJECT (main_window, hbox6, "hbox6");
+  GLADE_HOOKUP_OBJECT (main_window, image1, "image1");
+  GLADE_HOOKUP_OBJECT (main_window, label667, "label667");
+
+  GLADE_HOOKUP_OBJECT_NO_REF (main_window, tooltips, "tooltips");
+
+  gtk_widget_grab_default (main_themelist);
+  gtk_window_add_accel_group (GTK_WINDOW (main_window), accel_group);
+
+  return main_window;
+}
+
+
+
+
+static std::string gchar_to_string(gchar* gstr)
+{
+       std::string str = (gstr ? gstr : "");
+       g_free(gstr);
+       return str;
+}
+
+
+
+// ------------------------------------------------------
+
+
+static std::string s_orig_theme;
+static std::string s_orig_font;
+
+std::string& get_orig_theme()
+{
+       return s_orig_theme;
+}
+
+
+std::string& get_orig_font()
+{
+       return s_orig_font;
+}
+
+
+// ------------------------------------------------------
+
+
+std::string get_current_theme()
+{
+
+       GtkSettings* settings = gtk_settings_get_default();
+       gchar* theme;
+       g_object_get(settings, "gtk-theme-name", &theme, NULL);
+
+       /* dummy check */
+       if( !g_ascii_isalnum( theme[0] ) ){
+               g_free( theme );
+               return "";
+       }
+       return gchar_to_string(theme);
+}
+
+
+
+
+std::string get_current_font()
+{
+       return gchar_to_string( pango_font_description_to_string( gtk_rc_get_style( g_main_rc_window )->font_desc ) );
+}
+
+
+
+// ------------------------------------------------------
+
+
+
+std::string get_selected_theme()
+{
+       GtkTreeView* treeview = GTK_TREE_VIEW(lookup_widget(g_main_rc_window, "main_themelist"));
+       GtkTreeModel* model = gtk_tree_view_get_model(treeview);
+       GtkTreeSelection* selection = gtk_tree_view_get_selection(treeview);
+
+       GtkTreeIter iter;
+       gtk_tree_selection_get_selected(selection, 0, &iter);
+
+       gchar* theme_name;
+       gtk_tree_model_get(model, &iter, 0, &theme_name, -1);
+//     std::cout << theme_name << "\n";
+       return gchar_to_string(theme_name);
+}
+
+
+
+std::string get_selected_font()
+{
+//     GtkWidget* fontentry = lookup_widget(g_main_rc_window, "main_fontentry");
+//     return gtk_entry_get_text(GTK_ENTRY(fontentry));
+
+       bool default_font = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(lookup_widget(g_main_rc_window, "main_use_default_font_radio")));
+
+       if (default_font)
+               return "";
+
+       GtkWidget* fontbutton = lookup_widget(g_main_rc_window, "main_font_selector_button");
+       return gtk_font_button_get_font_name(GTK_FONT_BUTTON(fontbutton));
+}
+
+
+// ------------------------------------------------------
+
+
+
+static void themelist_selection_changed_cb(GtkTreeSelection* selection, gpointer data)
+{
+       if (gtk_tree_selection_get_selected (selection, 0, 0))
+               apply_theme(get_selected_theme(), get_current_font());
+}
+
+
+
+// ------------------------------------------------------
+
+
+
+static void populate_with_themes(GtkWidget* w)
+{
+
+       std::string search_path = gchar_to_string(gtk_rc_get_theme_dir());
+
+       if (search_path.size() && search_path[search_path.size() -1] != G_DIR_SEPARATOR)
+               search_path += G_DIR_SEPARATOR_S;
+
+       GDir* gdir = g_dir_open(search_path.c_str(), 0, NULL);
+       if (gdir == NULL)
+               return;
+
+
+       char* name;
+       GList* glist = 0;
+
+       while ( (name = const_cast<char*>(g_dir_read_name(gdir))) != NULL ) {
+               std::string filename = name;
+
+//             if (g_ascii_strup(fname.c_str(), -1) == "Default")
+//                     continue;
+
+               std::string fullname = search_path + filename;
+               std::string rc = fullname; rc += G_DIR_SEPARATOR_S; rc += "gtk-2.0"; rc += G_DIR_SEPARATOR_S; rc += "gtkrc";
+
+               bool is_dir = 0;
+               if (g_file_test(fullname.c_str(), G_FILE_TEST_IS_DIR))
+                       is_dir = 1;
+
+               if (is_dir && g_file_test(rc.c_str(), G_FILE_TEST_IS_REGULAR)) {
+                       glist = g_list_insert_sorted(glist, g_strdup(filename.c_str()), (GCompareFunc)strcmp);
+               }
+       }
+
+       g_dir_close(gdir);
+
+
+
+
+       // ---------------- tree
+
+
+       GtkTreeView* treeview = GTK_TREE_VIEW(w);
+       GtkListStore *store = gtk_list_store_new (1, G_TYPE_STRING);
+       gtk_tree_view_set_model(treeview, GTK_TREE_MODEL(store));
+
+       GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes (
+                                                                                               "Theme", gtk_cell_renderer_text_new(),
+                                                                                               "text", 0,
+                                                                                               NULL);
+       gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
+       gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+
+
+       GtkTreeIter   iter;
+
+       int i =0, curr=0;
+       while (char* theme = (char*)g_list_nth_data(glist, i)) {
+               gtk_list_store_append (store, &iter);
+               gtk_list_store_set (store, &iter, 0, theme, -1);
+
+               if (strcmp(theme, get_current_theme().c_str()) == 0) {
+                       curr = i;
+               }
+
+               ++i;
+       }
+
+
+       GtkTreeSelection* selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
+
+       // set the default theme
+
+       // THIS IS IMPORTANT!!!
+       gtk_widget_grab_focus(w);
+
+       std::stringstream str;
+       str << curr;
+       GtkTreePath* selpath = gtk_tree_path_new_from_string (str.str().c_str());
+       if (selpath) {
+               gtk_tree_selection_select_path(selection, selpath);
+               gtk_tree_view_scroll_to_cell(treeview, selpath, NULL, true, 0.5, 0.0);
+               gtk_tree_path_free(selpath);
+       }
+
+       g_signal_connect (G_OBJECT (selection), "changed",
+                  G_CALLBACK (themelist_selection_changed_cb), NULL);
+
+       g_object_unref (G_OBJECT (store));
+
+
+       // ---------------- font
+
+
+       GtkWidget* fontbutton = lookup_widget(g_main_rc_window, "main_font_selector_button");
+       gtk_font_button_set_font_name(GTK_FONT_BUTTON(fontbutton), get_current_font().c_str());
+
+
+}
+
+
+
+
+// ------------------------------------------------------
+
+
+
+
+void gtkThemeDlg(){
+       if( g_main_rc_window ) return;
+
+       s_rc_file = std::string( AppPath_get() ) + G_DIR_SEPARATOR_S + ".gtkrc-2.0.radiant";
+
+       g_main_rc_window = create_rc_window();
+
+    populate_with_themes( lookup_widget( g_main_rc_window, "main_themelist" ) );
+
+       get_orig_theme() = get_current_theme();
+       get_orig_font() = get_current_font();
+
+       gtk_widget_show ( g_main_rc_window );
+}
+
+
+
+// -------------------------------
+
+
+
+void set_theme(const std::string& theme_name, const std::string& font)
+{
+       if( theme_name.empty() ) return;
+
+       // tree
+       GtkTreeView* treeview = GTK_TREE_VIEW(lookup_widget(g_main_rc_window, "main_themelist"));
+       GtkTreeModel* model = gtk_tree_view_get_model(treeview);
+       GtkTreeSelection* selection = gtk_tree_view_get_selection(treeview);
+
+       GtkTreeIter iter;
+       gtk_tree_model_get_iter_first(model, &iter);
+
+       while(gtk_tree_model_iter_next(model, &iter)) {
+
+               gchar* text;
+               gtk_tree_model_get (model, &iter, 0, &text, -1);
+               std::string theme = gchar_to_string(text);
+
+               if (theme_name == theme) {
+                       gtk_tree_selection_select_iter(selection, &iter);
+                       break;
+               }
+
+       }
+
+
+       // font
+       if (font != "") {
+               GtkWidget* fontbutton = lookup_widget(g_main_rc_window, "main_font_selector_button");
+               //gtk_font_button_set_font_name(GTK_FONT_BUTTON(fontbutton), get_current_font().c_str());
+               gtk_font_button_set_font_name(GTK_FONT_BUTTON(fontbutton), font.c_str());
+       }
+
+
+       apply_theme(get_selected_theme(), get_selected_font());
+
+}
+
+
+
+
+void apply_theme(const std::string& theme_name, const std::string& font)
+{
+
+       std::stringstream strstr;
+       strstr << "gtk-theme-name = \"" << theme_name << "\"\n";
+
+       if (font != "")
+               strstr << "style \"user-font\"\n{\nfont_name=\"" << font << "\"\n}\nwidget_class \"*\" style \"user-font\"";
+
+       //strstr << "\ngtk-menu-popup-delay = 10";
+
+//     std::cout << strstr.str() << "\n\n\n";
+       std::fstream f;
+       f.open(s_rc_file.c_str(), std::ios::out);
+               f << strstr.str();
+       f.close();
+
+
+       GtkSettings* settings = gtk_settings_get_default();
+
+       gtk_rc_reparse_all_for_settings (settings, true);
+//     gtk_rc_parse_string(strstr.str().c_str());
+//     gtk_rc_parse("/root/.gtk-tmp");
+//     gtk_rc_reset_styles(settings);
+
+       //unlink(s_rc_file.c_str());
+
+       while (gtk_events_pending())
+               gtk_main_iteration();
+
+
+}
+
+#endif // GARUX_DISABLE_GTKTHEME
diff --git a/radiant/gtktheme.h b/radiant/gtktheme.h
new file mode 100644 (file)
index 0000000..9aa723d
--- /dev/null
@@ -0,0 +1,19 @@
+/***************************************************************************
+                          main.cpp  -  description
+                             -------------------
+    begin                : Wed Jan  1 2003
+    copyright            : (C) 2003 - 2005 by Alex Shaduri
+    email                : ashaduri '@' gmail.com
+ ***************************************************************************/
+
+#define GARUX_DISABLE_GTKTHEME
+#ifndef GARUX_DISABLE_GTKTHEME
+
+#ifndef _GTKTHEME_H_
+#define _GTKTHEME_H_
+
+void gtkThemeDlg();
+
+#endif
+
+#endif // GARUX_DISABLE_GTKTHEME
index 3df10170085eddb32f17e53830fa55b3495ad5dc..1306de6ced97407633f403b96768ef26eb7baaf7 100644 (file)
@@ -490,18 +490,48 @@ void remove_local_pid(){
 }
 
 void user_shortcuts_init(){
-       StringOutputStream path( 256 );
-       path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
-       LoadCommandMap( path.c_str() );
-       SaveCommandMap( path.c_str() );
+       LoadCommandMap();
+       SaveCommandMap();
 }
 
 void user_shortcuts_save(){
-       StringOutputStream path( 256 );
-       path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
-       SaveCommandMap( path.c_str() );
+       SaveCommandMap();
 }
 
+void add_local_rc_files(){
+#define GARUX_DISABLE_GTKTHEME
+#ifndef GARUX_DISABLE_GTKTHEME
+/* FIXME: HACK: not GTK3 compatible
+ https://developer.gnome.org/gtk2/stable/gtk2-Resource-Files.html#gtk-rc-add-default-file
+ https://developer.gnome.org/gtk3/stable/gtk3-Resource-Files.html#gtk-rc-add-default-file
+ > gtk_rc_add_default_file has been deprecated since version 3.0 and should not be used in newly-written code.
+ > Use GtkStyleContext with a custom GtkStyleProvider instead
+*/
+
+       {
+               StringOutputStream path( 512 );
+               path << AppPath_get() << ".gtkrc-2.0.radiant";
+               gtk_rc_add_default_file( path.c_str() );
+       }
+#ifdef WIN32
+       {
+               StringOutputStream path( 512 );
+               path << AppPath_get() << ".gtkrc-2.0.win";
+               gtk_rc_add_default_file( path.c_str() );
+       }
+#endif
+#endif // GARUX_DISABLE_GTKTHEME
+}
+
+/* HACK: If ui::main is not called yet,
+gtk_main_quit will not quit, so tell main
+to not call ui::main. This happens when a
+map is loaded from command line and require
+a restart because of wrong format.
+Delete this when the code to not have to
+restart to load another format is merged. */
+bool g_dontStart = false;
+
 int main( int argc, char* argv[] ){
 #if GTK_TARGET == 3
        // HACK: force legacy GL backend as we don't support GL3 yet
@@ -520,10 +550,22 @@ int main( int argc, char* argv[] ){
        if ( lib != 0 ) {
                void ( WINAPI *qDwmEnableComposition )( bool bEnable ) = ( void (WINAPI *) ( bool bEnable ) )GetProcAddress( lib, "DwmEnableComposition" );
                if ( qDwmEnableComposition ) {
-                       qDwmEnableComposition( FALSE );
+                       bool Aero = false;
+                       for ( int i = 1; i < argc; ++i ){
+                               if ( !stricmp( argv[i], "-aero" ) ){
+                                       Aero = true;
+                                       qDwmEnableComposition( TRUE );
+                                       break;
+                               }
+                       }
+                       // disable Aero
+                       if ( !Aero ){
+                               qDwmEnableComposition( FALSE );
+                       }
                }
                FreeLibrary( lib );
        }
+       _setmaxstdio(2048);
 #endif
 
        const char* mapname = NULL;
@@ -540,9 +582,9 @@ int main( int argc, char* argv[] ){
        }
 
        // Gtk already removed parsed `--options`
-       if ( argc == 2 ) {
-               if ( strlen( argv[ 1 ] ) > 1 ) {
-                       mapname = argv[ 1 ];
+       if (argc == 2) {
+               if ( strlen( argv[1] ) > 1 ) {
+                                       mapname = argv[1];
 
                        if ( g_str_has_suffix( mapname, ".map" ) ) {
                                if ( !g_path_is_absolute( mapname ) ) {
@@ -560,7 +602,7 @@ int main( int argc, char* argv[] ){
                        }
                }
        }
-       else if ( argc > 2 ) {
+       else if (argc > 2) {
                g_print ( "%s\n", "too many arguments" );
                return -1;
        }
@@ -583,6 +625,8 @@ int main( int argc, char* argv[] ){
 
        paths_init();
 
+       add_local_rc_files();
+
        show_splash();
 
        create_global_pid();
@@ -618,7 +662,10 @@ int main( int argc, char* argv[] ){
 
        hide_splash();
 
-       if ( mapname != NULL ) {
+       if( openCmdMap && *openCmdMap ){
+               Map_LoadFile( openCmdMap );
+       }
+       else if ( mapname != NULL ) {
                Map_LoadFile( mapname );
        }
        else if ( g_bLoadLastMap && !g_strLastMap.empty() ) {
@@ -636,7 +683,17 @@ int main( int argc, char* argv[] ){
 
        remove_local_pid();
 
+       /* HACK: If ui::main is not called yet,
+       gtk_main_quit will not quit, so tell main
+       to not call ui::main. This happens when a
+       map is loaded from command line and require
+       a restart because of wrong format.
+       Delete this when the code to not have to
+       restart to load another format is merged. */
+       if ( !g_dontStart )
+       {
        ui::main();
+       }
 
        // avoid saving prefs when the app is minimized
        if ( g_pParentWnd->IsSleeping() ) {
index 4113072623c2d0dc0a6d5f224b38082040bbb3a1..bd030b45c8e2ff45032a85407441be9687761339 100644 (file)
 #include "feedback.h"
 #include "referencecache.h"
 #include "texwindow.h"
+#include "filterbar.h"
 
 #if GDEF_OS_WINDOWS
 #include <process.h>
 #define WORKAROUND_GOBJECT_SET_GLWIDGET(window, widget)
 #endif
 
+#define GARUX_DISABLE_GTKTHEME
+#ifndef GARUX_DISABLE_GTKTHEME
+#include "gtktheme.h"
+#endif
+
 struct layout_globals_t
 {
        WindowPosition m_position;
@@ -128,16 +134,16 @@ struct layout_globals_t
        layout_globals_t() :
                m_position( -1, -1, 640, 480 ),
 
-               nXYHeight( 300 ),
-               nXYWidth( 300 ),
-               nCamWidth( 200 ),
-               nCamHeight( 200 ),
-               nState( GDK_WINDOW_STATE_MAXIMIZED ){
+               nXYHeight( 350 ),
+               nXYWidth( 600 ),
+               nCamWidth( 300 ),
+               nCamHeight( 210 ),
+               nState( 0 ){
        }
 };
 
 layout_globals_t g_layout_globals;
-glwindow_globals_t g_glwindow_globals;
+//glwindow_globals_t g_glwindow_globals;
 
 
 // VFS
@@ -167,6 +173,8 @@ void VFS_Refresh(){
        RefreshReferences();
        // also refresh texture browser
        TextureBrowser_RefreshShaders();
+       // also show textures (all or common)
+       TextureBrowser_ShowStartupShaders( GlobalTextureBrowser() );
 }
 
 void VFS_Restart(){
@@ -179,7 +187,7 @@ class VFSModuleObserver : public ModuleObserver
 public:
 void realise(){
        VFS_Init();
-}
+       }
 
 void unrealise(){
        VFS_Shutdown();
@@ -253,7 +261,7 @@ void HomePaths_Realise(){
                        if ( shfolder ) {
                                FreeLibrary( shfolder );
                        }
-                       if ( SHGetFolderPath( NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir ) ) {
+                       if ( SUCCEEDED( SHGetFolderPath( NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir ) ) ) {
                                path.clear();
                                path << DirectoryCleaned( mydocsdir ) << "My Games/" << ( prefix + 1 ) << "/";
                                // win32: only add it if it already exists
@@ -270,10 +278,10 @@ void HomePaths_Realise(){
                                break;
                        }
                        else {
-                               path.clear();
-                               path << DirectoryCleaned( g_get_home_dir() ) << prefix << "/";
-                               g_qeglobals.m_userEnginePath = path.c_str();
-                               break;
+                       path.clear();
+                       path << DirectoryCleaned( g_get_home_dir() ) << prefix << "/";
+                       g_qeglobals.m_userEnginePath = path.c_str();
+                       break;
                        }
 #endif
                }
@@ -617,19 +625,37 @@ ui::Window BuildDialog(){
        auto vbox2 = create_dialog_vbox( 0, 4 );
        frame.add(vbox2);
 
+       const char* engine;
+#if defined( WIN32 )
+       engine = g_pGameDescription->getRequiredKeyValue( "engine_win32" );
+#elif defined( __linux__ ) || defined ( __FreeBSD__ )
+       engine = g_pGameDescription->getRequiredKeyValue( "engine_linux" );
+#elif defined( __APPLE__ )
+       engine = g_pGameDescription->getRequiredKeyValue( "engine_macos" );
+#else
+#error "unsupported platform"
+#endif
+       StringOutputStream text( 256 );
+       text << "Select directory, where game executable sits (typically \"" << engine << "\")\n";
+       GtkLabel* label = GTK_LABEL( gtk_label_new( text.c_str() ) );
+       gtk_widget_show( GTK_WIDGET( label ) );
+       gtk_container_add( GTK_CONTAINER( vbox2 ), GTK_WIDGET( label ) );
+
        {
                PreferencesPage page( *this, vbox2 );
                Paths_constructBasicPreferences( page );
        }
 
-       return ui::Window(create_simple_modal_dialog_window( "Engine Path Not Found", m_modal, frame ));
+       return ui::Window(create_simple_modal_dialog_window( "Engine Path Configuration", m_modal, frame ));
 }
 };
 
 PathsDialog g_PathsDialog;
 
+bool g_strEnginePath_was_empty_1st_start = false;
+
 void EnginePath_verify(){
-       if ( !file_exists( g_strEnginePath.c_str() ) ) {
+       if ( !file_exists( g_strEnginePath.c_str() ) || g_strEnginePath_was_empty_1st_start ) {
                g_PathsDialog.Create();
                g_PathsDialog.DoModal();
                g_PathsDialog.Destroy();
@@ -893,8 +919,6 @@ void ColorScheme_Original(){
        g_xywindow_globals.color_gridback = Vector3( 1.0f, 1.0f, 1.0f );
        g_xywindow_globals.color_gridminor = Vector3( 0.75f, 0.75f, 0.75f );
        g_xywindow_globals.color_gridmajor = Vector3( 0.5f, 0.5f, 0.5f );
-       g_xywindow_globals.color_gridminor_alt = Vector3( 0.5f, 0.0f, 0.0f );
-       g_xywindow_globals.color_gridmajor_alt = Vector3( 1.0f, 0.0f, 0.0f );
        g_xywindow_globals.color_gridblock = Vector3( 0.0f, 0.0f, 1.0f );
        g_xywindow_globals.color_gridtext = Vector3( 0.0f, 0.0f, 0.0f );
        g_xywindow_globals.color_selbrushes = Vector3( 1.0f, 0.0f, 0.0f );
@@ -1083,8 +1107,6 @@ ChooseColour m_textureback;
 ChooseColour m_xyback;
 ChooseColour m_gridmajor;
 ChooseColour m_gridminor;
-ChooseColour m_gridmajor_alt;
-ChooseColour m_gridminor_alt;
 ChooseColour m_gridtext;
 ChooseColour m_gridblock;
 ChooseColour m_cameraback;
@@ -1099,8 +1121,6 @@ ColoursMenu() :
        m_xyback( ColourGetCaller( g_xywindow_globals.color_gridback ), ColourSetCaller( g_xywindow_globals.color_gridback ) ),
        m_gridmajor( ColourGetCaller( g_xywindow_globals.color_gridmajor ), ColourSetCaller( g_xywindow_globals.color_gridmajor ) ),
        m_gridminor( ColourGetCaller( g_xywindow_globals.color_gridminor ), ColourSetCaller( g_xywindow_globals.color_gridminor ) ),
-       m_gridmajor_alt( ColourGetCaller( g_xywindow_globals.color_gridmajor_alt ), ColourSetCaller( g_xywindow_globals.color_gridmajor_alt ) ),
-       m_gridminor_alt( ColourGetCaller( g_xywindow_globals.color_gridminor_alt ), ColourSetCaller( g_xywindow_globals.color_gridminor_alt ) ),
        m_gridtext( ColourGetCaller( g_xywindow_globals.color_gridtext ), ColourSetCaller( g_xywindow_globals.color_gridtext ) ),
        m_gridblock( ColourGetCaller( g_xywindow_globals.color_gridblock ), ColourSetCaller( g_xywindow_globals.color_gridblock ) ),
        m_cameraback( ColourGetCaller( g_camwindow_globals.color_cameraback ), ColourSetCaller( g_camwindow_globals.color_cameraback ) ),
@@ -1132,22 +1152,24 @@ ui::MenuItem create_colours_menu(){
        create_menu_item_with_mnemonic( menu_3, "Maya/Max/Lightwave Emulation", "ColorSchemeYdnar" );
        create_menu_item_with_mnemonic(menu_3, "Adwaita Dark", "ColorSchemeAdwaitaDark");
 
+#ifndef GARUX_DISABLE_GTKTHEME
+       create_menu_item_with_mnemonic( menu_in_menu, "GTK Theme...", "gtkThemeDlg" );
+#endif
+
        menu_separator( menu_in_menu );
 
        create_menu_item_with_mnemonic( menu_in_menu, "_Texture Background...", "ChooseTextureBackgroundColor" );
+       create_menu_item_with_mnemonic( menu_in_menu, "Camera Background...", "ChooseCameraBackgroundColor" );
        create_menu_item_with_mnemonic( menu_in_menu, "Grid Background...", "ChooseGridBackgroundColor" );
        create_menu_item_with_mnemonic( menu_in_menu, "Grid Major...", "ChooseGridMajorColor" );
        create_menu_item_with_mnemonic( menu_in_menu, "Grid Minor...", "ChooseGridMinorColor" );
-       create_menu_item_with_mnemonic( menu_in_menu, "Grid Major Small...", "ChooseSmallGridMajorColor" );
-       create_menu_item_with_mnemonic( menu_in_menu, "Grid Minor Small...", "ChooseSmallGridMinorColor" );
        create_menu_item_with_mnemonic( menu_in_menu, "Grid Text...", "ChooseGridTextColor" );
        create_menu_item_with_mnemonic( menu_in_menu, "Grid Block...", "ChooseGridBlockColor" );
-       create_menu_item_with_mnemonic( menu_in_menu, "Default Brush...", "ChooseBrushColor" );
-       create_menu_item_with_mnemonic( menu_in_menu, "Camera Background...", "ChooseCameraBackgroundColor" );
-       create_menu_item_with_mnemonic( menu_in_menu, "Selected Brush...", "ChooseSelectedBrushColor" );
+       create_menu_item_with_mnemonic( menu_in_menu, "Default Brush (2D)...", "ChooseBrushColor" );
+       create_menu_item_with_mnemonic( menu_in_menu, "Selected Brush and Sizing (2D)...", "ChooseSelectedBrushColor" );
        create_menu_item_with_mnemonic( menu_in_menu, "Selected Brush (Camera)...", "ChooseCameraSelectedBrushColor" );
        create_menu_item_with_mnemonic( menu_in_menu, "Clipper...", "ChooseClipperColor" );
-       create_menu_item_with_mnemonic( menu_in_menu, "Active View name...", "ChooseOrthoViewNameColor" );
+       create_menu_item_with_mnemonic( menu_in_menu, "Active View Name and Outline...", "ChooseOrthoViewNameColor" );
 
        return colours_menu_item;
 }
@@ -1361,6 +1383,12 @@ bool pre( const scene::Path& path, scene::Instance& instance ) const {
                         && selectable->isSelected() ) {
                        return false;
                }
+               if( doMakeUnique && instance.childSelected() ){
+                       NodeSmartReference clone( Node_Clone_Selected( path.top() ) );
+                       Map_gatherNamespaced( clone );
+                       Node_getTraversable( path.parent().get() )->insert( clone );
+                       return false;
+               }
        }
 
        return true;
@@ -1448,6 +1476,12 @@ Vector3 AxisBase_axisForDirection( const AxisBase& axes, ENudgeDirection directi
        return Vector3( 0, 0, 0 );
 }
 
+bool g_bNudgeAfterClone = false;
+
+void Nudge_constructPreferences( PreferencesPage& page ){
+       page.appendCheckBox( "", "Nudge selected after duplication", g_bNudgeAfterClone );
+}
+
 void NudgeSelection( ENudgeDirection direction, float fAmount, VIEWTYPE viewtype ){
        AxisBase axes( AxisBase_forViewType( viewtype ) );
        Vector3 view_direction( vector3_negated( axes.z ) );
@@ -1461,8 +1495,10 @@ void Selection_Clone(){
 
                Scene_Clone_Selected( GlobalSceneGraph(), false );
 
-               //NudgeSelection(eNudgeRight, GetGridSize(), GlobalXYWnd_getCurrentViewType());
-               //NudgeSelection(eNudgeDown, GetGridSize(), GlobalXYWnd_getCurrentViewType());
+               if( g_bNudgeAfterClone ){
+                       NudgeSelection(eNudgeRight, GetGridSize(), GlobalXYWnd_getCurrentViewType());
+                       NudgeSelection(eNudgeDown, GetGridSize(), GlobalXYWnd_getCurrentViewType());
+               }
        }
 }
 
@@ -1472,8 +1508,10 @@ void Selection_Clone_MakeUnique(){
 
                Scene_Clone_Selected( GlobalSceneGraph(), true );
 
-               //NudgeSelection(eNudgeRight, GetGridSize(), GlobalXYWnd_getCurrentViewType());
-               //NudgeSelection(eNudgeDown, GetGridSize(), GlobalXYWnd_getCurrentViewType());
+               if( g_bNudgeAfterClone ){
+                       NudgeSelection(eNudgeRight, GetGridSize(), GlobalXYWnd_getCurrentViewType());
+                       NudgeSelection(eNudgeDown, GetGridSize(), GlobalXYWnd_getCurrentViewType());
+               }
        }
 }
 
@@ -1677,6 +1715,27 @@ void ClipperMode(){
 }
 
 
+void ToggleRotateScaleModes(){
+       if ( g_currentToolMode == RotateMode ) {
+               ScaleMode();
+       }
+       else
+       {
+               RotateMode();
+       }
+}
+
+void ToggleDragScaleModes(){
+       if ( g_currentToolMode == DragMode ) {
+               ScaleMode();
+       }
+       else
+       {
+               DragMode();
+       }
+}
+
+
 void Texdef_Rotate( float angle ){
        StringOutputStream command;
        command << "brushRotateTexture -angle " << angle;
@@ -2011,8 +2070,10 @@ void ClipperChangeNotify(){
 
 LatchedValue<int> g_Layout_viewStyle( 0, "Window Layout" );
 LatchedValue<bool> g_Layout_enableDetachableMenus( true, "Detachable Menus" );
+LatchedValue<bool> g_Layout_enableMainToolbar( true, "Main Toolbar" );
 LatchedValue<bool> g_Layout_enablePatchToolbar( true, "Patch Toolbar" );
 LatchedValue<bool> g_Layout_enablePluginToolbar( true, "Plugin Toolbar" );
+LatchedValue<bool> g_Layout_enableFilterToolbar( true, "Filter Toolbar" );
 
 
 ui::MenuItem create_file_menu(){
@@ -2034,22 +2095,23 @@ ui::MenuItem create_file_menu(){
 #endif
 
        create_menu_item_with_mnemonic( menu, "_Open...", "OpenMap" );
-
        create_menu_item_with_mnemonic( menu, "_Import...", "ImportMap" );
+       menu_separator( menu );
        create_menu_item_with_mnemonic( menu, "_Save", "SaveMap" );
        create_menu_item_with_mnemonic( menu, "Save _as...", "SaveMapAs" );
        create_menu_item_with_mnemonic( menu, "_Export selected...", "ExportSelected" );
-       menu_separator( menu );
        create_menu_item_with_mnemonic( menu, "Save re_gion...", "SaveRegion" );
        menu_separator( menu );
-       create_menu_item_with_mnemonic( menu, "_Refresh models", "RefreshReferences" );
-       menu_separator( menu );
+//     menu_separator( menu );
+//     create_menu_item_with_mnemonic( menu, "_Refresh models", "RefreshReferences" );
+//     menu_separator( menu );
        create_menu_item_with_mnemonic( menu, "Pro_ject settings...", "ProjectSettings" );
-       menu_separator( menu );
-       create_menu_item_with_mnemonic( menu, "_Pointfile...", "TogglePointfile" );
+       //menu_separator( menu );
+       create_menu_item_with_mnemonic( menu, "_Pointfile", "TogglePointfile" );
        menu_separator( menu );
        MRU_constructMenu( menu );
        menu_separator( menu );
+//     create_menu_item_with_mnemonic( menu, "Check for NetRadiant update (web)", "CheckForUpdate" ); // FIXME
        create_menu_item_with_mnemonic( menu, "E_xit", "Exit" );
 
        return file_menu_item;
@@ -2072,19 +2134,22 @@ ui::MenuItem create_edit_menu(){
        create_menu_item_with_mnemonic( menu, "_Duplicate", "CloneSelection" );
        create_menu_item_with_mnemonic( menu, "Duplicate, make uni_que", "CloneSelectionAndMakeUnique" );
        create_menu_item_with_mnemonic( menu, "D_elete", "DeleteSelection" );
-       menu_separator( menu );
-       create_menu_item_with_mnemonic( menu, "Pa_rent", "ParentSelection" );
+       //create_menu_item_with_mnemonic( menu, "Pa_rent", "ParentSelection" );
        menu_separator( menu );
        create_menu_item_with_mnemonic( menu, "C_lear Selection", "UnSelectSelection" );
        create_menu_item_with_mnemonic( menu, "_Invert Selection", "InvertSelection" );
        create_menu_item_with_mnemonic( menu, "Select i_nside", "SelectInside" );
        create_menu_item_with_mnemonic( menu, "Select _touching", "SelectTouching" );
 
-       auto convert_menu = create_sub_menu_with_mnemonic( menu, "E_xpand Selection" );
-       if ( g_Layout_enableDetachableMenus.m_value ) {
-               menu_tearoff( convert_menu );
-       }
-       create_menu_item_with_mnemonic( convert_menu, "To Whole _Entities", "ExpandSelectionToEntities" );
+       menu_separator( menu );
+
+//     auto convert_menu = create_sub_menu_with_mnemonic( menu, "E_xpand Selection" );
+//     if ( g_Layout_enableDetachableMenus.m_value ) {
+//             menu_tearoff( convert_menu );
+//     }
+       create_menu_item_with_mnemonic( menu, "Select All Of Type", "SelectAllOfType" );
+       create_menu_item_with_mnemonic( menu, "_Expand Selection To Entities", "ExpandSelectionToEntities" );
+       create_menu_item_with_mnemonic( menu, "Select Connected Entities", "SelectConnectedEntities" );
 
        menu_separator( menu );
        create_menu_item_with_mnemonic( menu, "Pre_ferences...", "Preferences" );
@@ -2092,20 +2157,6 @@ ui::MenuItem create_edit_menu(){
        return edit_menu_item;
 }
 
-void fill_view_xy_top_menu( ui::Menu menu ){
-       create_check_menu_item_with_mnemonic( menu, "XY (Top) View", "ToggleView" );
-}
-
-
-void fill_view_yz_side_menu( ui::Menu menu ){
-       create_check_menu_item_with_mnemonic( menu, "YZ (Side) View", "ToggleSideView" );
-}
-
-
-void fill_view_xz_front_menu( ui::Menu menu ){
-       create_check_menu_item_with_mnemonic( menu, "XZ (Front) View", "ToggleFrontView" );
-}
-
 
 ui::Widget g_toggle_z_item{ui::null};
 ui::Widget g_toggle_console_item{ui::null};
@@ -2121,13 +2172,13 @@ ui::MenuItem create_view_menu( MainFrame::EViewStyle style ){
        }
 
        if ( style == MainFrame::eFloating ) {
-               fill_view_camera_menu( menu );
-               fill_view_xy_top_menu( menu );
-               fill_view_yz_side_menu( menu );
-               fill_view_xz_front_menu( menu );
+               create_check_menu_item_with_mnemonic( menu, "Camera View", "ToggleCamera" );
+               create_check_menu_item_with_mnemonic( menu, "XY (Top) View", "ToggleView" );
+               create_check_menu_item_with_mnemonic( menu, "XZ (Front) View", "ToggleFrontView" );
+               create_check_menu_item_with_mnemonic( menu, "YZ (Side) View", "ToggleSideView" );
        }
        if ( style == MainFrame::eFloating || style == MainFrame::eSplit ) {
-               create_menu_item_with_mnemonic( menu, "Console View", "ToggleConsole" );
+               create_menu_item_with_mnemonic( menu, "Console", "ToggleConsole" );
                create_menu_item_with_mnemonic( menu, "Texture Browser", "ToggleTextures" );
                create_menu_item_with_mnemonic( menu, "Entity Inspector", "ToggleEntityInspector" );
        }
@@ -2136,6 +2187,7 @@ ui::MenuItem create_view_menu( MainFrame::EViewStyle style ){
                create_menu_item_with_mnemonic( menu, "Entity Inspector", "ViewEntityInfo" );
        }
        create_menu_item_with_mnemonic( menu, "_Surface Inspector", "SurfaceInspector" );
+       create_menu_item_with_mnemonic( menu, "_Patch Inspector", "PatchInspector" );
        create_menu_item_with_mnemonic( menu, "Entity List", "EntityList" );
 
        menu_separator( menu );
@@ -2144,6 +2196,7 @@ ui::MenuItem create_view_menu( MainFrame::EViewStyle style ){
                if ( g_Layout_enableDetachableMenus.m_value ) {
                        menu_tearoff( camera_menu );
                }
+               create_menu_item_with_mnemonic( camera_menu, "Focus on Selected", "CameraFocusOnSelected" );
                create_menu_item_with_mnemonic( camera_menu, "_Center", "CenterView" );
                create_menu_item_with_mnemonic( camera_menu, "_Up Floor", "UpFloor" );
                create_menu_item_with_mnemonic( camera_menu, "_Down Floor", "DownFloor" );
@@ -2151,11 +2204,15 @@ ui::MenuItem create_view_menu( MainFrame::EViewStyle style ){
                create_menu_item_with_mnemonic( camera_menu, "Far Clip Plane In", "CubicClipZoomIn" );
                create_menu_item_with_mnemonic( camera_menu, "Far Clip Plane Out", "CubicClipZoomOut" );
                menu_separator( camera_menu );
+               create_menu_item_with_mnemonic( camera_menu, "Decrease FOV", "FOVDec" );
+               create_menu_item_with_mnemonic( camera_menu, "Increase FOV", "FOVInc" );
+               menu_separator( camera_menu );
                create_menu_item_with_mnemonic( camera_menu, "Next leak spot", "NextLeakSpot" );
                create_menu_item_with_mnemonic( camera_menu, "Previous leak spot", "PrevLeakSpot" );
-               menu_separator( camera_menu );
-               create_menu_item_with_mnemonic( camera_menu, "Look Through Selected", "LookThroughSelected" );
-               create_menu_item_with_mnemonic( camera_menu, "Look Through Camera", "LookThroughCamera" );
+               //cameramodel is not implemented in instances, thus useless
+//             menu_separator( camera_menu );
+//             create_menu_item_with_mnemonic( camera_menu, "Look Through Selected", "LookThroughSelected" );
+//             create_menu_item_with_mnemonic( camera_menu, "Look Through Camera", "LookThroughCamera" );
        }
        menu_separator( menu );
        {
@@ -2166,11 +2223,17 @@ ui::MenuItem create_view_menu( MainFrame::EViewStyle style ){
                if ( style == MainFrame::eRegular || style == MainFrame::eRegularLeft || style == MainFrame::eFloating ) {
                        create_menu_item_with_mnemonic( orthographic_menu, "_Next (XY, YZ, XY)", "NextView" );
                        create_menu_item_with_mnemonic( orthographic_menu, "XY (Top)", "ViewTop" );
-                       create_menu_item_with_mnemonic( orthographic_menu, "YZ", "ViewSide" );
-                       create_menu_item_with_mnemonic( orthographic_menu, "XZ", "ViewFront" );
+                       create_menu_item_with_mnemonic( orthographic_menu, "XZ (Front)", "ViewFront" );
+                       create_menu_item_with_mnemonic( orthographic_menu, "YZ (Side)", "ViewSide" );
                        menu_separator( orthographic_menu );
                }
+               else{
+                       create_menu_item_with_mnemonic( orthographic_menu, "Center on Selected", "NextView" );
+               }
 
+               create_menu_item_with_mnemonic( orthographic_menu, "Focus on Selected", "XYFocusOnSelected" );
+               create_menu_item_with_mnemonic( orthographic_menu, "Center on Selected", "CenterXYView" );
+               menu_separator( orthographic_menu );
                create_menu_item_with_mnemonic( orthographic_menu, "_XY 100%", "Zoom100" );
                create_menu_item_with_mnemonic( orthographic_menu, "XY Zoom _In", "ZoomIn" );
                create_menu_item_with_mnemonic( orthographic_menu, "XY Zoom _Out", "ZoomOut" );
@@ -2183,14 +2246,22 @@ ui::MenuItem create_view_menu( MainFrame::EViewStyle style ){
                if ( g_Layout_enableDetachableMenus.m_value ) {
                        menu_tearoff( menu_in_menu );
                }
-               create_check_menu_item_with_mnemonic( menu_in_menu, "Show _Angles", "ShowAngles" );
-               create_check_menu_item_with_mnemonic( menu_in_menu, "Show _Names", "ShowNames" );
+               create_check_menu_item_with_mnemonic( menu_in_menu, "Show Entity _Angles", "ShowAngles" );
+               create_check_menu_item_with_mnemonic( menu_in_menu, "Show Entity _Names", "ShowNames" );
+               create_check_menu_item_with_mnemonic( menu_in_menu, "Entity Names = Targetnames", "ShowTargetNames" );
+               create_check_menu_item_with_mnemonic( menu_in_menu, "Show Light Radiuses", "ShowLightRadiuses" );
+
+               menu_separator( menu_in_menu );
+
+               create_check_menu_item_with_mnemonic( menu_in_menu, "Show Size Info", "ToggleSizePaint" );
+               create_check_menu_item_with_mnemonic( menu_in_menu, "Show Crosshair", "ToggleCrosshairs" );
+               create_check_menu_item_with_mnemonic( menu_in_menu, "Show Grid", "ToggleGrid" );
                create_check_menu_item_with_mnemonic( menu_in_menu, "Show Blocks", "ShowBlocks" );
                create_check_menu_item_with_mnemonic( menu_in_menu, "Show C_oordinates", "ShowCoordinates" );
                create_check_menu_item_with_mnemonic( menu_in_menu, "Show Window Outline", "ShowWindowOutline" );
                create_check_menu_item_with_mnemonic( menu_in_menu, "Show Axes", "ShowAxes" );
                create_check_menu_item_with_mnemonic( menu_in_menu, "Show Workzone", "ShowWorkzone" );
-               create_check_menu_item_with_mnemonic( menu_in_menu, "Show Stats", "ShowStats" );
+               create_check_menu_item_with_mnemonic( menu_in_menu, "Show Camera Stats", "ShowStats" );
        }
 
        {
@@ -2202,12 +2273,8 @@ ui::MenuItem create_view_menu( MainFrame::EViewStyle style ){
        }
        menu_separator( menu );
        {
-               auto menu_in_menu = create_sub_menu_with_mnemonic( menu, "Hide/Show" );
-               if ( g_Layout_enableDetachableMenus.m_value ) {
-                       menu_tearoff( menu_in_menu );
-               }
-               create_menu_item_with_mnemonic( menu_in_menu, "Hide Selected", "HideSelected" );
-               create_menu_item_with_mnemonic( menu_in_menu, "Show Hidden", "ShowHidden" );
+               create_check_menu_item_with_mnemonic( menu, "Hide Selected", "HideSelected" );
+               create_menu_item_with_mnemonic( menu, "Show Hidden", "ShowHidden" );
        }
        menu_separator( menu );
        {
@@ -2218,10 +2285,10 @@ ui::MenuItem create_view_menu( MainFrame::EViewStyle style ){
                create_menu_item_with_mnemonic( menu_in_menu, "_Off", "RegionOff" );
                create_menu_item_with_mnemonic( menu_in_menu, "_Set XY", "RegionSetXY" );
                create_menu_item_with_mnemonic( menu_in_menu, "Set _Brush", "RegionSetBrush" );
-               create_menu_item_with_mnemonic( menu_in_menu, "Set Se_lected Brushes", "RegionSetSelection" );
+               create_check_menu_item_with_mnemonic( menu_in_menu, "Set Se_lected Brushes", "RegionSetSelection" );
        }
 
-       command_connect_accelerator( "CenterXYView" );
+       //command_connect_accelerator( "CenterXYView" );
 
        return view_menu_item;
 }
@@ -2244,6 +2311,9 @@ ui::MenuItem create_selection_menu(){
                create_check_menu_item_with_mnemonic( menu_in_menu, "_Faces", "DragFaces" );
        }
 
+       menu_separator( menu );
+       create_menu_item_with_mnemonic( menu, "Snap To Grid", "SnapToGrid" );
+
        menu_separator( menu );
 
        {
@@ -2255,6 +2325,9 @@ ui::MenuItem create_selection_menu(){
                create_menu_item_with_mnemonic( menu_in_menu, "Nudge Right", "SelectNudgeRight" );
                create_menu_item_with_mnemonic( menu_in_menu, "Nudge Up", "SelectNudgeUp" );
                create_menu_item_with_mnemonic( menu_in_menu, "Nudge Down", "SelectNudgeDown" );
+               menu_separator( menu_in_menu );
+               create_menu_item_with_mnemonic( menu_in_menu, "Nudge +Z", "MoveSelectionUP" );
+               create_menu_item_with_mnemonic( menu_in_menu, "Nudge -Z", "MoveSelectionDOWN" );
        }
        {
                auto menu_in_menu = create_sub_menu_with_mnemonic( menu, "Rotate" );
@@ -2264,6 +2337,9 @@ ui::MenuItem create_selection_menu(){
                create_menu_item_with_mnemonic( menu_in_menu, "Rotate X", "RotateSelectionX" );
                create_menu_item_with_mnemonic( menu_in_menu, "Rotate Y", "RotateSelectionY" );
                create_menu_item_with_mnemonic( menu_in_menu, "Rotate Z", "RotateSelectionZ" );
+               menu_separator( menu_in_menu );
+               create_menu_item_with_mnemonic( menu_in_menu, "Rotate Clockwise", "RotateSelectionClockwise" );
+               create_menu_item_with_mnemonic( menu_in_menu, "Rotate Anticlockwise", "RotateSelectionAnticlockwise" );
        }
        {
                auto menu_in_menu = create_sub_menu_with_mnemonic( menu, "Flip" );
@@ -2273,6 +2349,9 @@ ui::MenuItem create_selection_menu(){
                create_menu_item_with_mnemonic( menu_in_menu, "Flip _X", "MirrorSelectionX" );
                create_menu_item_with_mnemonic( menu_in_menu, "Flip _Y", "MirrorSelectionY" );
                create_menu_item_with_mnemonic( menu_in_menu, "Flip _Z", "MirrorSelectionZ" );
+               menu_separator( menu_in_menu );
+               create_menu_item_with_mnemonic( menu_in_menu, "Flip Horizontally", "MirrorSelectionHorizontally" );
+               create_menu_item_with_mnemonic( menu_in_menu, "Flip Vertically", "MirrorSelectionVertically" );
        }
        menu_separator( menu );
        create_menu_item_with_mnemonic( menu, "Arbitrary rotation...", "ArbitraryRotation" );
@@ -2291,6 +2370,7 @@ ui::MenuItem create_bsp_menu(){
        }
 
        create_menu_item_with_mnemonic( menu, "Customize...", "BuildMenuCustomize" );
+       create_menu_item_with_mnemonic( menu, "Run recent build", "Build_runRecentExecutedBuild" );
 
        menu_separator( menu );
 
@@ -2330,8 +2410,10 @@ ui::MenuItem create_misc_menu(){
        create_menu_item_with_mnemonic( menu, "Find brush...", "FindBrush" );
        create_menu_item_with_mnemonic( menu, "Map Info...", "MapInfo" );
        // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=394
-//  create_menu_item_with_mnemonic(menu, "_Print XY View", FreeCaller<void(), WXY_Print>());
-       create_menu_item_with_mnemonic( menu, "_Background select", makeCallbackF(WXY_BackgroundSelect) );
+//  create_menu_item_with_mnemonic(menu, "_Print XY View", makeCallbackF( WXY_Print ));
+       create_menu_item_with_mnemonic( menu, "_Background image...", makeCallbackF(WXY_BackgroundSelect) );
+       create_menu_item_with_mnemonic( menu, "Fullscreen", "Fullscreen" );
+       create_menu_item_with_mnemonic( menu, "Maximize view", "MaximizeView" );
        return misc_menu_item;
 }
 
@@ -2382,14 +2464,14 @@ ui::MenuItem create_help_menu(){
                menu_tearoff( menu );
        }
 
-       create_menu_item_with_mnemonic( menu, "Manual", "OpenManual" );
+//     create_menu_item_with_mnemonic( menu, "Manual", "OpenManual" );
 
        // this creates all the per-game drop downs for the game pack helps
        // it will take care of hooking the Sys_OpenURL calls etc.
        create_game_help_menu( menu );
 
        create_menu_item_with_mnemonic( menu, "Bug report", makeCallbackF(OpenBugReportURL) );
-       create_menu_item_with_mnemonic( menu, "Shortcuts list", makeCallbackF(DoCommandListDlg) );
+       create_menu_item_with_mnemonic( menu, "Shortcuts", makeCallbackF(DoCommandListDlg) );
        create_menu_item_with_mnemonic( menu, "_About...", makeCallbackF(DoAbout) );
 
        return help_menu_item;
@@ -2428,7 +2510,7 @@ void Patch_registerShortcuts(){
        command_connect_accelerator( "PatchDeleteLastColumn" );
        command_connect_accelerator( "PatchDeleteLastRow" );
        command_connect_accelerator( "NaturalizePatch" );
-       //command_connect_accelerator("CapCurrentCurve");
+       command_connect_accelerator( "CapCurrentCurve");
 }
 
 void Manipulators_registerShortcuts(){
@@ -2453,12 +2535,14 @@ void TexdefNudge_registerShortcuts(){
 }
 
 void SelectNudge_registerShortcuts(){
-       command_connect_accelerator( "MoveSelectionDOWN" );
-       command_connect_accelerator( "MoveSelectionUP" );
+       //command_connect_accelerator( "MoveSelectionDOWN" );
+       //command_connect_accelerator( "MoveSelectionUP" );
        //command_connect_accelerator("SelectNudgeLeft");
        //command_connect_accelerator("SelectNudgeRight");
        //command_connect_accelerator("SelectNudgeUp");
        //command_connect_accelerator("SelectNudgeDown");
+       command_connect_accelerator( "UnSelectSelection2" );
+       command_connect_accelerator( "DeleteSelection2" );
 }
 
 void SnapToGrid_registerShortcuts(){
@@ -2473,19 +2557,33 @@ void SurfaceInspector_registerShortcuts(){
        command_connect_accelerator( "FitTexture" );
 }
 
+void TexBro_registerShortcuts(){
+       command_connect_accelerator( "FindReplaceTextures" );
+       command_connect_accelerator( "RefreshShaders" );
+}
+
+void Misc_registerShortcuts(){
+       //refresh models
+       command_connect_accelerator( "RefreshReferences" );
+       command_connect_accelerator( "MouseRotateOrScale" );
+       command_connect_accelerator( "MouseDragOrScale" );
+}
+
 
 void register_shortcuts(){
-       PatchInspector_registerShortcuts();
-       Patch_registerShortcuts();
+//     PatchInspector_registerShortcuts();
+       //Patch_registerShortcuts();
        Grid_registerShortcuts();
-       XYWnd_registerShortcuts();
+//     XYWnd_registerShortcuts();
        CamWnd_registerShortcuts();
        Manipulators_registerShortcuts();
        SurfaceInspector_registerShortcuts();
        TexdefNudge_registerShortcuts();
        SelectNudge_registerShortcuts();
-       SnapToGrid_registerShortcuts();
-       SelectByType_registerShortcuts();
+//     SnapToGrid_registerShortcuts();
+//     SelectByType_registerShortcuts();
+       TexBro_registerShortcuts();
+       Misc_registerShortcuts();
 }
 
 void File_constructToolbar( ui::Toolbar toolbar ){
@@ -2499,12 +2597,17 @@ void UndoRedo_constructToolbar( ui::Toolbar toolbar ){
 }
 
 void RotateFlip_constructToolbar( ui::Toolbar toolbar ){
-       toolbar_append_button( toolbar, "x-axis Flip", "brush_flipx.png", "MirrorSelectionX" );
-       toolbar_append_button( toolbar, "x-axis Rotate", "brush_rotatex.png", "RotateSelectionX" );
-       toolbar_append_button( toolbar, "y-axis Flip", "brush_flipy.png", "MirrorSelectionY" );
-       toolbar_append_button( toolbar, "y-axis Rotate", "brush_rotatey.png", "RotateSelectionY" );
-       toolbar_append_button( toolbar, "z-axis Flip", "brush_flipz.png", "MirrorSelectionZ" );
-       toolbar_append_button( toolbar, "z-axis Rotate", "brush_rotatez.png", "RotateSelectionZ" );
+//     toolbar_append_button( toolbar, "x-axis Flip", "brush_flipx.png", "MirrorSelectionX" );
+//     toolbar_append_button( toolbar, "x-axis Rotate", "brush_rotatex.png", "RotateSelectionX" );
+//     toolbar_append_button( toolbar, "y-axis Flip", "brush_flipy.png", "MirrorSelectionY" );
+//     toolbar_append_button( toolbar, "y-axis Rotate", "brush_rotatey.png", "RotateSelectionY" );
+//     toolbar_append_button( toolbar, "z-axis Flip", "brush_flipz.png", "MirrorSelectionZ" );
+//     toolbar_append_button( toolbar, "z-axis Rotate", "brush_rotatez.png", "RotateSelectionZ" );
+       toolbar_append_button( toolbar, "Flip Horizontally", "brush_flip_hor.png", "MirrorSelectionHorizontally" );
+       toolbar_append_button( toolbar, "Flip Vertically", "brush_flip_vert.png", "MirrorSelectionVertically" );
+
+       toolbar_append_button( toolbar, "Rotate Clockwise", "brush_rotate_clock.png", "RotateSelectionClockwise" );
+       toolbar_append_button( toolbar, "Rotate Anticlockwise", "brush_rotate_anti.png", "RotateSelectionAnticlockwise" );
 }
 
 void Select_constructToolbar( ui::Toolbar toolbar ){
@@ -2515,8 +2618,8 @@ void Select_constructToolbar( ui::Toolbar toolbar ){
 void CSG_constructToolbar( ui::Toolbar toolbar ){
        toolbar_append_button( toolbar, "CSG Subtract (SHIFT + U)", "selection_csgsubtract.png", "CSGSubtract" );
        toolbar_append_button( toolbar, "CSG Merge (CTRL + U)", "selection_csgmerge.png", "CSGMerge" );
-       toolbar_append_button( toolbar, "Make Hollow", "selection_makehollow.png", "CSGMakeHollow" );
-       toolbar_append_button( toolbar, "Make Room", "selection_makeroom.png", "CSGMakeRoom" );
+       toolbar_append_button( toolbar, "Make Room", "selection_makeroom.png", "CSGRoom" );
+       toolbar_append_button( toolbar, "CSG Tool", "ellipsis.png", "CSGTool" );
 }
 
 void ComponentModes_constructToolbar( ui::Toolbar toolbar ){
@@ -2531,13 +2634,13 @@ void Clipper_constructToolbar( ui::Toolbar toolbar ){
 }
 
 void XYWnd_constructToolbar( ui::Toolbar toolbar ){
-       toolbar_append_button( toolbar, "Change views", "view_change.png", "NextView" );
+       toolbar_append_button( toolbar, "Change views (CTRL + TAB)", "view_change.png", "NextView" );
 }
 
 void Manipulators_constructToolbar( ui::Toolbar toolbar ){
        toolbar_append_toggle_button( toolbar, "Translate (W)", "select_mousetranslate.png", "MouseTranslate" );
        toolbar_append_toggle_button( toolbar, "Rotate (R)", "select_mouserotate.png", "MouseRotate" );
-       toolbar_append_toggle_button( toolbar, "Scale", "select_mousescale.png", "MouseScale" );
+       toolbar_append_toggle_button( toolbar, "Scale (Q)", "select_mousescale.png", "MouseScale" );
        toolbar_append_toggle_button( toolbar, "Resize (Q)", "select_mouseresize.png", "MouseDrag" );
 
        Clipper_constructToolbar( toolbar );
@@ -2547,7 +2650,9 @@ ui::Toolbar create_main_toolbar( MainFrame::EViewStyle style ){
        auto toolbar = ui::Toolbar::from( gtk_toolbar_new() );
        gtk_orientable_set_orientation( GTK_ORIENTABLE(toolbar), GTK_ORIENTATION_HORIZONTAL );
        gtk_toolbar_set_style( toolbar, GTK_TOOLBAR_ICONS );
-
+//     gtk_toolbar_set_show_arrow( toolbar, TRUE );
+       //gtk_orientable_set_orientation( GTK_ORIENTABLE( toolbar ), GTK_ORIENTATION_HORIZONTAL );
+       //toolbar_append_space( toolbar );
        toolbar.show();
 
        auto space = [&]() {
@@ -2578,7 +2683,7 @@ ui::Toolbar create_main_toolbar( MainFrame::EViewStyle style ){
 
        ComponentModes_constructToolbar( toolbar );
 
-       if ( style == MainFrame::eRegular || style == MainFrame::eRegularLeft || style == MainFrame::eFloating ) {
+       if ( style != MainFrame::eSplit ) {
                space();
 
                XYWnd_constructToolbar( toolbar );
@@ -2600,25 +2705,22 @@ ui::Toolbar create_main_toolbar( MainFrame::EViewStyle style ){
 
        space();
 
-       toolbar_append_toggle_button( toolbar, "Texture Lock (SHIFT +T)", "texture_lock.png", "TogTexLock" );
+       toolbar_append_toggle_button( toolbar, "Texture Lock (SHIFT + T)", "texture_lock.png", "TogTexLock" );
 
        space();
 
-       /*auto g_view_entities_button =*/ toolbar_append_button( toolbar, "Entities (N)", "entities.png", "ToggleEntityInspector" );
-       auto g_view_console_button = toolbar_append_button( toolbar, "Console (O)", "console.png", "ToggleConsole" );
-       auto g_view_textures_button = toolbar_append_button( toolbar, "Texture Browser (T)", "texture_browser.png", "ToggleTextures" );
+       toolbar_append_button( toolbar, "Entities (N)", "entities.png", "ToggleEntityInspector" );
+       // disable the console and texture button in the regular layouts
+       if ( style != MainFrame::eRegular && style != MainFrame::eRegularLeft ) {
+               toolbar_append_button( toolbar, "Console (O)", "console.png", "ToggleConsole" );
+               toolbar_append_button( toolbar, "Texture Browser (T)", "texture_browser.png", "ToggleTextures" );
+       }
        // TODO: call light inspector
        //GtkButton* g_view_lightinspector_button = toolbar_append_button(toolbar, "Light Inspector", "lightinspector.png", "ToggleLightInspector");
 
        space();
-       /*auto g_refresh_models_button =*/ toolbar_append_button( toolbar, "Refresh Models", "refresh_models.png", "RefreshReferences" );
 
-
-       // disable the console and texture button in the regular layouts
-       if ( style == MainFrame::eRegular || style == MainFrame::eRegularLeft ) {
-               gtk_widget_set_sensitive( g_view_console_button , FALSE );
-               gtk_widget_set_sensitive( g_view_textures_button , FALSE );
-       }
+       toolbar_append_button( toolbar, "Refresh Models", "refresh_models.png", "RefreshReferences" );
 
        return toolbar;
 }
@@ -2644,7 +2746,11 @@ ui::Widget create_main_statusbar( ui::Widget pStatusLabel[c_count_status] ){
                gtk_frame_set_shadow_type( frame, GTK_SHADOW_IN );
 
                auto label = ui::Label( "Label" );
-               gtk_label_set_ellipsize( label, PANGO_ELLIPSIZE_END );
+               if( i == c_texture_status )
+                       gtk_label_set_ellipsize( label, PANGO_ELLIPSIZE_START );
+               else
+                       gtk_label_set_ellipsize( label, PANGO_ELLIPSIZE_END );
+
                gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
                gtk_misc_set_padding( GTK_MISC( label ), 4, 2 );
                label.show();
@@ -2909,6 +3015,15 @@ ui::Window create_splash(){
        image.show();
        window.add(image);
 
+#if GTK_TARGET == 2
+       if( gtk_image_get_storage_type( image ) == GTK_IMAGE_PIXBUF ){
+               GdkBitmap* mask;
+               GdkPixbuf* pix = gtk_image_get_pixbuf( image );
+               gdk_pixbuf_render_pixmap_and_mask( pix, NULL, &mask, 255 );
+               gtk_widget_shape_combine_mask ( GTK_WIDGET( window ), mask, 0, 0 );
+       }
+#endif
+
        window.dimensions(-1, -1);
        window.show();
 
@@ -2940,6 +3055,9 @@ static gint mainframe_delete( ui::Widget widget, GdkEvent *event, gpointer data
        return TRUE;
 }
 
+PanedState g_single_hpaned = { 0.75f, -1, };
+PanedState g_single_vpaned = { 0.75f, -1, };
+
 void MainFrame::Create(){
        ui::Window window = ui::Window( ui::window_type::TOP );
 
@@ -2974,6 +3092,7 @@ void MainFrame::Create(){
        auto vbox = ui::VBox( FALSE, 0 );
        window.add(vbox);
        vbox.show();
+       gtk_container_set_focus_chain( GTK_CONTAINER( vbox ), NULL );
 
        global_accel_connect_window( window );
 
@@ -2984,19 +3103,46 @@ void MainFrame::Create(){
     auto main_menu = create_main_menu( CurrentStyle() );
        vbox.pack_start( main_menu, FALSE, FALSE, 0 );
 
-    auto main_toolbar = create_main_toolbar( CurrentStyle() );
-       vbox.pack_start( main_toolbar, FALSE, FALSE, 0 );
+       if( g_Layout_enableMainToolbar.m_value ){
+               GtkToolbar* main_toolbar = create_main_toolbar( CurrentStyle() );
+               gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( main_toolbar ), FALSE, FALSE, 0 );
+       }
+
+       if ( g_Layout_enablePluginToolbar.m_value || g_Layout_enableFilterToolbar.m_value ){
+               auto PFbox = ui::HBox( FALSE, 3 );
+               vbox.pack_start( PFbox, FALSE, FALSE, 0 );
+               PFbox.show();
+               if ( g_Layout_enablePluginToolbar.m_value ){
+                       auto plugin_toolbar = create_plugin_toolbar();
+                       if ( g_Layout_enableFilterToolbar.m_value ){
+                               PFbox.pack_start( plugin_toolbar, FALSE, FALSE, 0 );
+                               // Force the toolbar to display all childrens
+                               // without collapsing them to a menu.
+                               gtk_toolbar_set_show_arrow( plugin_toolbar, FALSE );
+                       }
+                       else{
+                               PFbox.pack_start( plugin_toolbar, TRUE, TRUE, 0 );
+                       }
+               }
+               if ( g_Layout_enableFilterToolbar.m_value ){
+                       ui::Toolbar filter_toolbar = create_filter_toolbar();
+                       PFbox.pack_start( filter_toolbar, TRUE, TRUE, 0 );
+               }
+       }
 
-       auto plugin_toolbar = create_plugin_toolbar();
+       /*GtkToolbar* plugin_toolbar = create_plugin_toolbar();
        if ( !g_Layout_enablePluginToolbar.m_value ) {
-               plugin_toolbar.hide();
-       }
-       vbox.pack_start( plugin_toolbar, FALSE, FALSE, 0 );
+               gtk_widget_hide( GTK_WIDGET( plugin_toolbar ) );
+       }*/
 
        ui::Widget main_statusbar = create_main_statusbar(reinterpret_cast<ui::Widget *>(m_pStatusLabel));
        vbox.pack_end(main_statusbar, FALSE, TRUE, 2);
 
        GroupDialog_constructWindow( window );
+
+       /* want to realize it immediately; otherwise gtk paned splits positions wont be set correctly for floating group dlg */
+       gtk_widget_realize ( GTK_WIDGET( GroupDialog_getWindow() ) );
+
        g_page_entity = GroupDialog_addPage( "Entities", EntityInspector_constructWindow( GroupDialog_getWindow() ), RawStringExportCaller( "Entities" ) );
 
        if ( FloatingGroupDialog() ) {
@@ -3006,91 +3152,70 @@ void MainFrame::Create(){
 #if GDEF_OS_WINDOWS
        if ( g_multimon_globals.m_bStartOnPrimMon ) {
                PositionWindowOnPrimaryScreen( g_layout_globals.m_position );
-               window_set_position( window, g_layout_globals.m_position );
        }
-       else
 #endif
-       if ( g_layout_globals.nState & GDK_WINDOW_STATE_MAXIMIZED ) {
-               gtk_window_maximize( window );
-               WindowPosition default_position( -1, -1, 640, 480 );
-               window_set_position( window, default_position );
-       }
-       else
-       {
-               window_set_position( window, g_layout_globals.m_position );
-       }
+       window_set_position( window, g_layout_globals.m_position );
 
        m_window = window;
 
        window.show();
 
-       if ( CurrentStyle() == eRegular || CurrentStyle() == eRegularLeft ) {
+       if ( CurrentStyle() == eRegular || CurrentStyle() == eRegularLeft )
+       {
                {
-                       ui::Widget vsplit = ui::VPaned(ui::New);
-                       m_vSplit = vsplit;
-                       vbox.pack_start( vsplit, TRUE, TRUE, 0 );
-                       vsplit.show();
+                       ui::Widget hsplit = ui::HPaned(ui::New);
+                       m_hSplit = hsplit;
 
-                       // console
-                       ui::Widget console_window = Console_constructWindow( window );
-                       gtk_paned_pack2( GTK_PANED( vsplit ), console_window, FALSE, TRUE );
+                       vbox.pack_start( hsplit, TRUE, TRUE, 0 );
+                       hsplit.show();
 
                        {
-                               ui::Widget hsplit = ui::HPaned(ui::New);
-                               hsplit.show();
-                               m_hSplit = hsplit;
-                               gtk_paned_add1( GTK_PANED( vsplit ), hsplit );
+                               ui::Widget vsplit = ui::VPaned(ui::New);
+                               vsplit.show();
+                               m_vSplit = vsplit;
 
+                               ui::Widget vsplit2 = ui::VPaned(ui::New);
+                               vsplit2.show();
+                               m_vSplit2 = vsplit2;
+
+                               if ( CurrentStyle() == eRegular ){
+                                       gtk_paned_pack1( GTK_PANED( hsplit ), vsplit, TRUE, TRUE );
+                                       gtk_paned_pack2( GTK_PANED( hsplit ), vsplit2, TRUE, TRUE );
+                               }
+                               else{
+                                       gtk_paned_pack2( GTK_PANED( hsplit ), vsplit, TRUE, TRUE );
+                                       gtk_paned_pack1( GTK_PANED( hsplit ), vsplit2, TRUE, TRUE );
+                               }
+
+                               // console
+                               ui::Widget console_window = Console_constructWindow( window );
+                               gtk_paned_pack2( GTK_PANED( vsplit ), console_window, TRUE, TRUE );
+                               
                                // xy
                                m_pXYWnd = new XYWnd();
                                m_pXYWnd->SetViewType( XY );
                                ui::Widget xy_window = ui::Widget(create_framed_widget( m_pXYWnd->GetWidget( ) ));
+                               gtk_paned_pack1( GTK_PANED( vsplit ), xy_window, TRUE, TRUE );
 
                                {
-                                       ui::Widget vsplit2 = ui::VPaned(ui::New);
-                                       vsplit2.show();
-                                       m_vSplit2 = vsplit2;
-
-                                       if ( CurrentStyle() == eRegular ) {
-                                               gtk_paned_add1( GTK_PANED( hsplit ), xy_window );
-                                               gtk_paned_add2( GTK_PANED( hsplit ), vsplit2 );
-                                       }
-                                       else
-                                       {
-                                               gtk_paned_add1( GTK_PANED( hsplit ), vsplit2 );
-                                               gtk_paned_add2( GTK_PANED( hsplit ), xy_window );
-                                       }
-
-
                                        // camera
                                        m_pCamWnd = NewCamWnd();
                                        GlobalCamera_setCamWnd( *m_pCamWnd );
                                        CamWnd_setParent( *m_pCamWnd, window );
                                        auto camera_window = create_framed_widget( CamWnd_getWidget( *m_pCamWnd ) );
 
-                                       gtk_paned_add1( GTK_PANED( vsplit2 ), camera_window  );
+                                       gtk_paned_pack1( GTK_PANED( vsplit2 ), GTK_WIDGET( camera_window ) , TRUE, TRUE);
 
                                        // textures
                                        auto texture_window = create_framed_widget( TextureBrowser_constructWindow( window ) );
 
-                                       gtk_paned_add2( GTK_PANED( vsplit2 ), texture_window  );
+                                       gtk_paned_pack2( GTK_PANED( vsplit2 ), GTK_WIDGET( texture_window ), TRUE, TRUE );
                                }
                        }
                }
-
-               gtk_paned_set_position( GTK_PANED( m_vSplit ), g_layout_globals.nXYHeight );
-
-               if ( CurrentStyle() == eRegular ) {
-                       gtk_paned_set_position( GTK_PANED( m_hSplit ), g_layout_globals.nXYWidth );
-               }
-               else
-               {
-                       gtk_paned_set_position( GTK_PANED( m_hSplit ), g_layout_globals.nCamWidth );
-               }
-
-               gtk_paned_set_position( GTK_PANED( m_vSplit2 ), g_layout_globals.nCamHeight );
        }
-       else if ( CurrentStyle() == eFloating ) {
+       else if ( CurrentStyle() == eFloating )
+       {
                {
                        ui::Window window = ui::Window(create_persistent_floating_window( "Camera", m_window ));
                        global_accel_connect_window( window );
@@ -3178,13 +3303,17 @@ void MainFrame::Create(){
                {
                        auto frame = create_framed_widget( TextureBrowser_constructWindow( GroupDialog_getWindow() ) );
                        g_page_textures = GroupDialog_addPage( "Textures", frame, TextureBrowserExportTitleCaller() );
-
                        WORKAROUND_GOBJECT_SET_GLWIDGET( GroupDialog_getWindow(), TextureBrowser_getGLWidget() );
                }
 
+               // FIXME: find a way to do it with newer syntax
+               // m_vSplit = 0;
+               // m_hSplit = 0;
+               // m_vSplit2 = 0;
+
                GroupDialog_show();
        }
-       else // 4 way
+       else if ( CurrentStyle() == eSplit )
        {
                m_pCamWnd = NewCamWnd();
                GlobalCamera_setCamWnd( *m_pCamWnd );
@@ -3207,16 +3336,76 @@ void MainFrame::Create(){
 
                ui::Widget xz = m_pXZWnd->GetWidget();
 
-        auto split = create_split_views( camera, yz, xy, xz );
-               vbox.pack_start( split, TRUE, TRUE, 0 );
+               m_hSplit = create_split_views( camera, xy, yz, xz, m_vSplit, m_vSplit2 );
+               vbox.pack_start( m_hSplit, TRUE, TRUE, 0 );
 
                {
-            auto frame = create_framed_widget( TextureBrowser_constructWindow( window ) );
+            auto frame = create_framed_widget( TextureBrowser_constructWindow( GroupDialog_getWindow() ) );
                        g_page_textures = GroupDialog_addPage( "Textures", frame, TextureBrowserExportTitleCaller() );
 
                        WORKAROUND_GOBJECT_SET_GLWIDGET( window, TextureBrowser_getGLWidget() );
                }
        }
+       else // single window
+       {
+               m_pCamWnd = NewCamWnd();
+               GlobalCamera_setCamWnd( *m_pCamWnd );
+               CamWnd_setParent( *m_pCamWnd, window );
+
+               ui::Widget camera = CamWnd_getWidget( *m_pCamWnd );
+
+               m_pYZWnd = new XYWnd();
+               m_pYZWnd->SetViewType( YZ );
+
+               ui::Widget yz = m_pYZWnd->GetWidget();
+
+               m_pXYWnd = new XYWnd();
+               m_pXYWnd->SetViewType( XY );
+
+               ui::Widget xy = m_pXYWnd->GetWidget();
+
+               m_pXZWnd = new XYWnd();
+               m_pXZWnd->SetViewType( XZ );
+
+               ui::Widget xz = m_pXZWnd->GetWidget();
+
+               ui::Widget hsplit = ui::HPaned(ui::New);
+               vbox.pack_start( hsplit, TRUE, TRUE, 0 );
+               hsplit.show();
+
+               /* Before merging NetRadiantCustom:
+               ui::Widget split = create_split_views( camera, xy, yz, xz ); */
+               m_hSplit = create_split_views( camera, xy, yz, xz, m_vSplit, m_vSplit2 );
+
+               ui::Widget vsplit = ui::VPaned(ui::New);
+               vsplit.show();
+
+               // textures
+               ui::Widget texture_window = create_framed_widget( TextureBrowser_constructWindow( window ) );
+
+               // console
+               ui::Widget console_window = create_framed_widget( Console_constructWindow( window ) );
+
+               /* Before merging NetRadiantCustom:
+               gtk_paned_add1( GTK_PANED( hsplit ), m_hSplit );
+               gtk_paned_add2( GTK_PANED( hsplit ), vsplit );
+
+               gtk_paned_add1( GTK_PANED( vsplit ), texture_window  );
+               gtk_paned_add2( GTK_PANED( vsplit ), console_window  );
+               */
+
+               gtk_paned_pack1( GTK_PANED( hsplit ), m_hSplit, TRUE, TRUE );
+               gtk_paned_pack2( GTK_PANED( hsplit ), vsplit, TRUE, TRUE);
+
+               gtk_paned_pack1( GTK_PANED( vsplit ), texture_window, TRUE, TRUE );
+               gtk_paned_pack2( GTK_PANED( vsplit ), console_window, TRUE, TRUE );
+
+               hsplit.connect( "size_allocate", G_CALLBACK( hpaned_allocate ), &g_single_hpaned );
+               hsplit.connect( "notify::position", G_CALLBACK( paned_position ), &g_single_hpaned );
+
+               vsplit.connect( "size_allocate", G_CALLBACK( vpaned_allocate ), &g_single_vpaned );
+               vsplit.connect( "notify::position", G_CALLBACK( paned_position ), &g_single_vpaned );
+       }
 
        EntityList_constructWindow( window );
        PreferencesDialog_constructWindow( window );
@@ -3235,6 +3424,27 @@ void MainFrame::Create(){
 
        EverySecondTimer_enable();
 
+       if ( g_layout_globals.nState & GDK_WINDOW_STATE_MAXIMIZED ||
+               g_layout_globals.nState & GDK_WINDOW_STATE_ICONIFIED ) {
+               gtk_window_maximize( window );
+       }
+       if ( g_layout_globals.nState & GDK_WINDOW_STATE_FULLSCREEN ) {
+               gtk_window_fullscreen( window );
+       }
+
+       if ( !FloatingGroupDialog() ) {
+               gtk_paned_set_position( GTK_PANED( m_vSplit ), g_layout_globals.nXYHeight );
+
+               if ( CurrentStyle() == eRegular ) {
+                       gtk_paned_set_position( GTK_PANED( m_hSplit ), g_layout_globals.nXYWidth );
+               }
+               else
+               {
+                       gtk_paned_set_position( GTK_PANED( m_hSplit ), g_layout_globals.nCamWidth );
+               }
+
+               gtk_paned_set_position( GTK_PANED( m_vSplit2 ), g_layout_globals.nCamHeight );
+       }
        //GlobalShortcuts_reportUnregistered();
 }
 
@@ -3253,7 +3463,9 @@ void MainFrame::SaveWindowInfo(){
                g_layout_globals.nCamHeight = gtk_paned_get_position( GTK_PANED( m_vSplit2 ) );
        }
 
-       g_layout_globals.m_position = m_position_tracker.getPosition();
+       if( gdk_window_get_state( gtk_widget_get_window( GTK_WIDGET( m_window ) ) ) == 0 ){
+               g_layout_globals.m_position = m_position_tracker.getPosition();
+       }
 
        g_layout_globals.nState = gdk_window_get_state( gtk_widget_get_window(m_window ) );
 }
@@ -3381,7 +3593,7 @@ void GlobalGL_sharedContextDestroyed(){
 
 void Layout_constructPreferences( PreferencesPage& page ){
        {
-               const char* layouts[] = { "window1.png", "window2.png", "window3.png", "window4.png" };
+               const char* layouts[] = { "window1.png", "window2.png", "window3.png", "window4.png", "window5.png" };
                page.appendRadioIcons(
                        "Window Layout",
                        STRING_ARRAY_RANGE( layouts ),
@@ -3392,6 +3604,10 @@ void Layout_constructPreferences( PreferencesPage& page ){
                "", "Detachable Menus",
                make_property( g_Layout_enableDetachableMenus )
                );
+       page.appendCheckBox(
+               "", "Main Toolbar",
+               make_property( g_Layout_enableMainToolbar )
+               );
        if ( !string_empty( g_pGameDescription->getKeyValue( "no_patch" ) ) ) {
                page.appendCheckBox(
                        "", "Patch Toolbar",
@@ -3402,6 +3618,10 @@ void Layout_constructPreferences( PreferencesPage& page ){
                "", "Plugin Toolbar",
                make_property( g_Layout_enablePluginToolbar )
                );
+       page.appendCheckBox(
+               "", "Filter Toolbar",
+               make_property( g_Layout_enableFilterToolbar )
+               );
 }
 
 void Layout_constructPage( PreferenceGroup& group ){
@@ -3413,9 +3633,100 @@ void Layout_registerPreferencesPage(){
        PreferencesDialog_addInterfacePage( makeCallbackF(Layout_constructPage) );
 }
 
+void MainFrame_toggleFullscreen(){
+       GtkWindow* wnd = MainFrame_getWindow();
+       if( gdk_window_get_state( gtk_widget_get_window( GTK_WIDGET( wnd ) ) ) & GDK_WINDOW_STATE_FULLSCREEN ){
+               //some portion of buttsex, because gtk_window_unfullscreen doesn't work correctly after calling some modal window
+               bool maximize = ( gdk_window_get_state( gtk_widget_get_window( GTK_WIDGET( wnd ) ) ) & GDK_WINDOW_STATE_MAXIMIZED );
+               gtk_window_unfullscreen( wnd );
+               if( maximize ){
+                       gtk_window_unmaximize( wnd );
+                       gtk_window_maximize( wnd );
+               }
+               else{
+                       gtk_window_move( wnd, g_layout_globals.m_position.x, g_layout_globals.m_position.y );
+                       gtk_window_resize( wnd, g_layout_globals.m_position.w, g_layout_globals.m_position.h );
+               }
+       }
+       else{
+               gtk_window_fullscreen( wnd );
+       }
+}
+
+class MaximizeView
+{
+public:
+       MaximizeView(): m_maximized( false ){
+       }
+       void toggle(){
+               return m_maximized ? restore() : maximize();
+       }
+private:
+       bool m_maximized;
+       int m_vSplitPos;
+       int m_vSplit2Pos;
+       int m_hSplitPos;
+
+       void restore(){
+               m_maximized = false;
+               gtk_paned_set_position( GTK_PANED( g_pParentWnd->m_vSplit ), m_vSplitPos );
+               gtk_paned_set_position( GTK_PANED( g_pParentWnd->m_vSplit2 ), m_vSplit2Pos );
+               gtk_paned_set_position( GTK_PANED( g_pParentWnd->m_hSplit ), m_hSplitPos );
+       }
+
+       void maximize(){
+               m_maximized = true;
+               m_vSplitPos = gtk_paned_get_position( GTK_PANED( g_pParentWnd->m_vSplit ) );
+               m_vSplit2Pos = gtk_paned_get_position( GTK_PANED( g_pParentWnd->m_vSplit2 ) );
+               m_hSplitPos = gtk_paned_get_position( GTK_PANED( g_pParentWnd->m_hSplit ) );
 
+               int vSplitX, vSplitY, vSplit2X, vSplit2Y, hSplitX, hSplitY;
+               gdk_window_get_origin( gtk_widget_get_window( GTK_WIDGET( g_pParentWnd->m_vSplit ) ), &vSplitX, &vSplitY );
+               gdk_window_get_origin( gtk_widget_get_window( GTK_WIDGET( g_pParentWnd->m_vSplit2 ) ), &vSplit2X, &vSplit2Y );
+               gdk_window_get_origin( gtk_widget_get_window( GTK_WIDGET( g_pParentWnd->m_hSplit ) ), &hSplitX, &hSplitY );
+
+               vSplitY += m_vSplitPos;
+               vSplit2Y += m_vSplit2Pos;
+               hSplitX += m_hSplitPos;
+
+               int cur_x, cur_y;
+               Sys_GetCursorPos( MainFrame_getWindow(), &cur_x, &cur_y );
+
+               if( cur_x > hSplitX ){
+                       gtk_paned_set_position( GTK_PANED( g_pParentWnd->m_hSplit ), 0 );
+               }
+               else{
+                       gtk_paned_set_position( GTK_PANED( g_pParentWnd->m_hSplit ), 9999 );
+               }
+               if( cur_y > vSplitY ){
+                       gtk_paned_set_position( GTK_PANED( g_pParentWnd->m_vSplit ), 0 );
+               }
+               else{
+                       gtk_paned_set_position( GTK_PANED( g_pParentWnd->m_vSplit ), 9999 );
+               }
+               if( cur_y > vSplit2Y ){
+                       gtk_paned_set_position( GTK_PANED( g_pParentWnd->m_vSplit2 ), 0 );
+               }
+               else{
+                       gtk_paned_set_position( GTK_PANED( g_pParentWnd->m_vSplit2 ), 9999 );
+               }
+       }
+};
+
+MaximizeView g_maximizeview;
+
+void Maximize_View(){
+       if( g_pParentWnd != 0 && g_pParentWnd->m_vSplit != 0 && g_pParentWnd->m_vSplit2 != 0 && g_pParentWnd->m_hSplit != 0 )
+               g_maximizeview.toggle();
+}
+
+void FocusAllViews(){
+       XY_Centralize(); //using centralizing here, not focusing function
+       GlobalCamera_FocusOnSelected();
+}
 #include "preferencesystem.h"
 #include "stringio.h"
+#include "transformpath/transformpath.h"
 
 void MainFrame_Construct(){
        GlobalCommands_insert( "OpenManual", makeCallbackF(OpenHelpURL), Accelerator( GDK_KEY_F1 ) );
@@ -3439,37 +3750,38 @@ void MainFrame_Construct(){
        GlobalCommands_insert( "PasteToCamera", makeCallbackF(PasteToCamera), Accelerator( 'V', (GdkModifierType)GDK_MOD1_MASK ) );
        GlobalCommands_insert( "CloneSelection", makeCallbackF(Selection_Clone), Accelerator( GDK_KEY_space ) );
        GlobalCommands_insert( "CloneSelectionAndMakeUnique", makeCallbackF(Selection_Clone_MakeUnique), Accelerator( GDK_KEY_space, (GdkModifierType)GDK_SHIFT_MASK ) );
-       GlobalCommands_insert( "DeleteSelection", makeCallbackF(deleteSelection), Accelerator( GDK_KEY_BackSpace ) );
+//     GlobalCommands_insert( "DeleteSelection", makeCallbackF(deleteSelection), Accelerator( GDK_KEY_BackSpace ) );
+       GlobalCommands_insert( "DeleteSelection2", makeCallbackF(deleteSelection), Accelerator( GDK_KEY_BackSpace ) );
+       GlobalCommands_insert( "DeleteSelection", makeCallbackF(deleteSelection), Accelerator( 'Z' ) );
        GlobalCommands_insert( "ParentSelection", makeCallbackF(Scene_parentSelected) );
-       GlobalCommands_insert( "UnSelectSelection", makeCallbackF(Selection_Deselect), Accelerator( GDK_KEY_Escape ) );
+//     GlobalCommands_insert( "UnSelectSelection", makeCallbackF(Selection_Deselect), Accelerator( GDK_KEY_Escape ) );
+       GlobalCommands_insert( "UnSelectSelection2", makeCallbackF(Selection_Deselect), Accelerator( GDK_KEY_Escape ) );
+       GlobalCommands_insert( "UnSelectSelection", makeCallbackF(Selection_Deselect), Accelerator( 'C' ) );
        GlobalCommands_insert( "InvertSelection", makeCallbackF(Select_Invert), Accelerator( 'I' ) );
        GlobalCommands_insert( "SelectInside", makeCallbackF(Select_Inside) );
        GlobalCommands_insert( "SelectTouching", makeCallbackF(Select_Touching) );
        GlobalCommands_insert( "ExpandSelectionToEntities", makeCallbackF(Scene_ExpandSelectionToEntities), Accelerator( 'E', (GdkModifierType)( GDK_MOD1_MASK | GDK_CONTROL_MASK ) ) );
+       GlobalCommands_insert( "SelectConnectedEntities", makeCallbackF(SelectConnectedEntities), Accelerator( 'E', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
        GlobalCommands_insert( "Preferences", makeCallbackF(PreferencesDialog_showDialog), Accelerator( 'P' ) );
 
        GlobalCommands_insert( "ToggleConsole", makeCallbackF(Console_ToggleShow), Accelerator( 'O' ) );
        GlobalCommands_insert( "ToggleEntityInspector", makeCallbackF(EntityInspector_ToggleShow), Accelerator( 'N' ) );
        GlobalCommands_insert( "EntityList", makeCallbackF(EntityList_toggleShown), Accelerator( 'L' ) );
 
-       GlobalCommands_insert( "ShowHidden", makeCallbackF(Select_ShowAllHidden), Accelerator( 'H', (GdkModifierType)GDK_SHIFT_MASK ) );
-       GlobalCommands_insert( "HideSelected", makeCallbackF(HideSelected), Accelerator( 'H' ) );
+//     GlobalCommands_insert( "ShowHidden", makeCallbackF( Select_ShowAllHidden ), Accelerator( 'H', (GdkModifierType)GDK_SHIFT_MASK ) );
+//     GlobalCommands_insert( "HideSelected", makeCallbackF( HideSelected ), Accelerator( 'H' ) );
+
+       Select_registerCommands();
 
        GlobalToggles_insert( "DragVertices", makeCallbackF(SelectVertexMode), ToggleItem::AddCallbackCaller( g_vertexMode_button ), Accelerator( 'V' ) );
        GlobalToggles_insert( "DragEdges", makeCallbackF(SelectEdgeMode), ToggleItem::AddCallbackCaller( g_edgeMode_button ), Accelerator( 'E' ) );
        GlobalToggles_insert( "DragFaces", makeCallbackF(SelectFaceMode), ToggleItem::AddCallbackCaller( g_faceMode_button ), Accelerator( 'F' ) );
 
-       GlobalCommands_insert( "MirrorSelectionX", makeCallbackF(Selection_Flipx) );
-       GlobalCommands_insert( "RotateSelectionX", makeCallbackF(Selection_Rotatex) );
-       GlobalCommands_insert( "MirrorSelectionY", makeCallbackF(Selection_Flipy) );
-       GlobalCommands_insert( "RotateSelectionY", makeCallbackF(Selection_Rotatey) );
-       GlobalCommands_insert( "MirrorSelectionZ", makeCallbackF(Selection_Flipz) );
-       GlobalCommands_insert( "RotateSelectionZ", makeCallbackF(Selection_Rotatez) );
-
-       GlobalCommands_insert( "ArbitraryRotation", makeCallbackF(DoRotateDlg) );
-       GlobalCommands_insert( "ArbitraryScale", makeCallbackF(DoScaleDlg) );
+       GlobalCommands_insert( "ArbitraryRotation", makeCallbackF(DoRotateDlg), Accelerator( 'R', (GdkModifierType)GDK_SHIFT_MASK ) );
+       GlobalCommands_insert( "ArbitraryScale", makeCallbackF(DoScaleDlg), Accelerator( 'S', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
 
        GlobalCommands_insert( "BuildMenuCustomize", makeCallbackF(DoBuildMenu) );
+       GlobalCommands_insert( "Build_runRecentExecutedBuild", makeCallbackF(Build_runRecentExecutedBuild), Accelerator( GDK_KEY_F5 ) );
 
        GlobalCommands_insert( "FindBrush", makeCallbackF(DoFind) );
 
@@ -3481,19 +3793,22 @@ void MainFrame_Construct(){
        GlobalToggles_insert( "MouseTranslate", makeCallbackF(TranslateMode), ToggleItem::AddCallbackCaller( g_translatemode_button ), Accelerator( 'W' ) );
        GlobalToggles_insert( "MouseRotate", makeCallbackF(RotateMode), ToggleItem::AddCallbackCaller( g_rotatemode_button ), Accelerator( 'R' ) );
        GlobalToggles_insert( "MouseScale", makeCallbackF(ScaleMode), ToggleItem::AddCallbackCaller( g_scalemode_button ) );
-       GlobalToggles_insert( "MouseDrag", makeCallbackF(DragMode), ToggleItem::AddCallbackCaller( g_dragmode_button ), Accelerator( 'Q' ) );
+       GlobalToggles_insert( "MouseDrag", makeCallbackF(DragMode), ToggleItem::AddCallbackCaller( g_dragmode_button ) );
+       GlobalCommands_insert( "MouseRotateOrScale", makeCallbackF(ToggleRotateScaleModes) );
+       GlobalCommands_insert( "MouseDragOrScale", makeCallbackF(ToggleDragScaleModes), Accelerator( 'Q' ) );
 
+#ifndef GARUX_DISABLE_GTKTHEME
+       GlobalCommands_insert( "gtkThemeDlg", makeCallbackF(gtkThemeDlg) );
+#endif
        GlobalCommands_insert( "ColorSchemeOriginal", makeCallbackF(ColorScheme_Original) );
        GlobalCommands_insert( "ColorSchemeQER", makeCallbackF(ColorScheme_QER) );
        GlobalCommands_insert( "ColorSchemeBlackAndGreen", makeCallbackF(ColorScheme_Black) );
        GlobalCommands_insert( "ColorSchemeYdnar", makeCallbackF(ColorScheme_Ydnar) );
-       GlobalCommands_insert("ColorSchemeAdwaitaDark", makeCallbackF(ColorScheme_AdwaitaDark));
+       GlobalCommands_insert( "ColorSchemeAdwaitaDark", makeCallbackF(ColorScheme_AdwaitaDark));
        GlobalCommands_insert( "ChooseTextureBackgroundColor", makeCallback( g_ColoursMenu.m_textureback ) );
        GlobalCommands_insert( "ChooseGridBackgroundColor", makeCallback( g_ColoursMenu.m_xyback ) );
        GlobalCommands_insert( "ChooseGridMajorColor", makeCallback( g_ColoursMenu.m_gridmajor ) );
        GlobalCommands_insert( "ChooseGridMinorColor", makeCallback( g_ColoursMenu.m_gridminor ) );
-       GlobalCommands_insert( "ChooseSmallGridMajorColor", makeCallback( g_ColoursMenu.m_gridmajor_alt ) );
-       GlobalCommands_insert( "ChooseSmallGridMinorColor", makeCallback( g_ColoursMenu.m_gridminor_alt ) );
        GlobalCommands_insert( "ChooseGridTextColor", makeCallback( g_ColoursMenu.m_gridtext ) );
        GlobalCommands_insert( "ChooseGridBlockColor", makeCallback( g_ColoursMenu.m_gridblock ) );
        GlobalCommands_insert( "ChooseBrushColor", makeCallback( g_ColoursMenu.m_brush ) );
@@ -3503,11 +3818,14 @@ void MainFrame_Construct(){
        GlobalCommands_insert( "ChooseClipperColor", makeCallback( g_ColoursMenu.m_clipper ) );
        GlobalCommands_insert( "ChooseOrthoViewNameColor", makeCallback( g_ColoursMenu.m_viewname ) );
 
+       GlobalCommands_insert( "Fullscreen", makeCallbackF( MainFrame_toggleFullscreen ), Accelerator( GDK_KEY_F11 ) );
+       GlobalCommands_insert( "MaximizeView", makeCallbackF( Maximize_View ), Accelerator( GDK_KEY_F12 ) );
+
 
        GlobalCommands_insert( "CSGSubtract", makeCallbackF(CSG_Subtract), Accelerator( 'U', (GdkModifierType)GDK_SHIFT_MASK ) );
        GlobalCommands_insert( "CSGMerge", makeCallbackF(CSG_Merge), Accelerator( 'U', (GdkModifierType) GDK_CONTROL_MASK ) );
-       GlobalCommands_insert( "CSGMakeHollow", makeCallbackF(CSG_MakeHollow) );
-       GlobalCommands_insert( "CSGMakeRoom", makeCallbackF(CSG_MakeRoom) );
+       GlobalCommands_insert( "CSGRoom", makeCallbackF(CSG_MakeRoom) );
+       GlobalCommands_insert( "CSGTool", makeCallbackF(CSG_Tool) );
 
        Grid_registerCommands();
 
@@ -3541,8 +3859,10 @@ void MainFrame_Construct(){
        GlobalSelectionSystem().addSelectionChangeCallback( ComponentModeSelectionChangedCaller() );
 
        GlobalPreferenceSystem().registerPreference( "DetachableMenus", make_property_string( g_Layout_enableDetachableMenus.m_latched ) );
+       GlobalPreferenceSystem().registerPreference( "MainToolBar", make_property_string( g_Layout_enableMainToolbar.m_latched ) );
        GlobalPreferenceSystem().registerPreference( "PatchToolBar", make_property_string( g_Layout_enablePatchToolbar.m_latched ) );
        GlobalPreferenceSystem().registerPreference( "PluginToolBar", make_property_string( g_Layout_enablePluginToolbar.m_latched ) );
+       GlobalPreferenceSystem().registerPreference( "FilterToolBar", make_property_string( g_Layout_enableFilterToolbar.m_latched ) );
        GlobalPreferenceSystem().registerPreference( "QE4StyleWindows", make_property_string( g_Layout_viewStyle.m_latched ) );
        GlobalPreferenceSystem().registerPreference( "XYHeight", make_property_string( g_layout_globals.nXYHeight ) );
        GlobalPreferenceSystem().registerPreference( "XYWidth", make_property_string( g_layout_globals.nXYWidth ) );
@@ -3560,7 +3880,12 @@ void MainFrame_Construct(){
        GlobalPreferenceSystem().registerPreference( "YZWnd", make_property<WindowPositionTracker_String>(g_posYZWnd) );
        GlobalPreferenceSystem().registerPreference( "XZWnd", make_property<WindowPositionTracker_String>(g_posXZWnd) );
 
+       GlobalPreferenceSystem().registerPreference( "EnginePath", make_property_string( g_strEnginePath ) );
+
+       GlobalPreferenceSystem().registerPreference( "NudgeAfterClone", make_property_string( g_bNudgeAfterClone ) );
+       if ( g_strEnginePath.empty() )
        {
+               g_strEnginePath_was_empty_1st_start = true;
                const char* ENGINEPATH_ATTRIBUTE =
 #if GDEF_OS_WINDOWS
                        "enginepath_win32"
@@ -3572,12 +3897,13 @@ void MainFrame_Construct(){
 #error "unknown platform"
 #endif
                ;
+
                StringOutputStream path( 256 );
                path << DirectoryCleaned( g_pGameDescription->getRequiredKeyValue( ENGINEPATH_ATTRIBUTE ) );
-               g_strEnginePath = path.c_str();
-       }
 
-       GlobalPreferenceSystem().registerPreference( "EnginePath", make_property_string( g_strEnginePath ) );
+               g_strEnginePath = transformPath( path.c_str() ).c_str();
+               GlobalPreferenceSystem().registerPreference( "EnginePath", make_property_string( g_strEnginePath ) );
+       }
 
        GlobalPreferenceSystem().registerPreference( "DisableEnginePath", make_property_string( g_disableEnginePath ) );
        GlobalPreferenceSystem().registerPreference( "DisableHomePath", make_property_string( g_disableHomePath ) );
@@ -3589,11 +3915,14 @@ void MainFrame_Construct(){
 
        g_Layout_viewStyle.useLatched();
        g_Layout_enableDetachableMenus.useLatched();
+       g_Layout_enableMainToolbar.useLatched();
        g_Layout_enablePatchToolbar.useLatched();
        g_Layout_enablePluginToolbar.useLatched();
+       g_Layout_enableFilterToolbar.useLatched();
 
        Layout_registerPreferencesPage();
        Paths_registerPreferencesPage();
+       PreferencesDialog_addSettingsPreferences( FreeCaller<void(PreferencesPage&), Nudge_constructPreferences>() );
 
        g_brushCount.setCountChangedCallback( makeCallbackF(QE_brushCountChanged) );
        g_entityCount.setCountChangedCallback( makeCallbackF(QE_entityCountChanged) );
@@ -3614,12 +3943,21 @@ void MainFrame_Destroy(){
 
 
 void GLWindow_Construct(){
-       GlobalPreferenceSystem().registerPreference( "MouseButtons", make_property_string( g_glwindow_globals.m_nMouseType ) );
+//     GlobalPreferenceSystem().registerPreference( "MouseButtons", make_property_string( g_glwindow_globals.m_nMouseType ) );
 }
 
 void GLWindow_Destroy(){
 }
 
+/* HACK: If ui::main is not called yet,
+gtk_main_quit will not quit, so tell main
+to not call ui::main. This happens when a
+map is loaded from command line and require
+a restart because of wrong format.
+Delete this when the code to not have to
+restart to load another format is merged. */
+extern bool g_dontStart;
+
 void Radiant_Restart(){
        // preferences are expected to be already saved in any way
        // this is just to be sure and be future proof
@@ -3658,5 +3996,13 @@ void Radiant_Restart(){
        // quit if radiant successfully started
        if ( status == 0 ) {
                gtk_main_quit();
+               /* HACK: If ui::main is not called yet,
+               gtk_main_quit will not quit, so tell main
+               to not call ui::main. This happens when a
+               map is loaded from command line and require
+               a restart because of wrong format.
+               Delete this when the code to not have to
+               restart to load another format is merged. */
+               g_dontStart = true;
        }
 }
index ca0c4c97e95bd683826d5b60721e74790f88a156..cb5068efbd430d823e9cc736b28519a5b26f0d29 100644 (file)
@@ -54,6 +54,7 @@ enum EViewStyle
        eFloating = 1,
        eSplit = 2,
        eRegularLeft = 3,
+       eSingle = 4,
 };
 
 MainFrame();
@@ -72,10 +73,13 @@ void Create();
 void SaveWindowInfo();
 void Shutdown();
 
+public:
 ui::Widget m_vSplit{ui::null};
 ui::Widget m_hSplit{ui::null};
 ui::Widget m_vSplit2{ui::null};
 
+private:
+
 XYWnd* m_pXYWnd;
 XYWnd* m_pYZWnd;
 XYWnd* m_pXZWnd;
@@ -142,7 +146,7 @@ bool FloatingGroupDialog(){
 extern MainFrame* g_pParentWnd;
 
 ui::Window MainFrame_getWindow();
-
+/*
 enum EMouseButtonMode
 {
        ETwoButton = 0,
@@ -157,11 +161,11 @@ struct glwindow_globals_t
                m_nMouseType( EThreeButton ){
        }
 };
-
+*/
 void GLWindow_Construct();
 void GLWindow_Destroy();
 
-extern glwindow_globals_t g_glwindow_globals;
+//extern glwindow_globals_t g_glwindow_globals;
 template<typename Value>
 class LatchedValue;
 extern LatchedValue<bool> g_Layout_enableDetachableMenus;
@@ -244,6 +248,7 @@ void UpdateAllWindows();
 
 
 void ClipperChangeNotify();
+void ClipperMode();
 
 void DefaultMode();
 
@@ -287,4 +292,6 @@ void XYWindowMouseDown_disconnect( MouseEventHandlerId id );
 
 extern ui::Widget g_page_entity;
 
+void FocusAllViews();
+
 #endif
index 36b77ae1b828478767148f91738b156287139b5f..c783e035f869f6e63123ada368328c54ca4b6e6c 100644 (file)
@@ -86,6 +86,7 @@ MapModules& ReferenceAPI_getMapModules();
 #include "autosave.h"
 #include "brushmodule.h"
 #include "brush.h"
+#include "patch.h"
 
 bool g_writeMapComments = true;
 
@@ -764,6 +765,50 @@ scene::Node& Node_Clone( scene::Node& node ){
        return clone;
 }
 
+bool Node_instanceSelected( scene::Node& node );
+
+class CloneAllSelected : public scene::Traversable::Walker
+{
+mutable scene::Path m_path;
+public:
+CloneAllSelected( scene::Node& root )
+       : m_path( makeReference( root ) ){
+}
+bool pre( scene::Node& node ) const {
+       if ( node.isRoot() ) {
+               return false;
+       }
+
+       if( Node_instanceSelected( node ) ){
+               m_path.push( makeReference( node_clone( node ) ) );
+               m_path.top().get().IncRef();
+       }
+
+       return true;
+}
+void post( scene::Node& node ) const {
+       if ( node.isRoot() ) {
+               return;
+       }
+
+       if( Node_instanceSelected( node ) ){
+               Node_getTraversable( m_path.parent() )->insert( m_path.top() );
+
+               m_path.top().get().DecRef();
+               m_path.pop();
+       }
+}
+};
+
+scene::Node& Node_Clone_Selected( scene::Node& node ){
+       scene::Node& clone = node_clone( node );
+       scene::Traversable* traversable = Node_getTraversable( node );
+       if ( traversable != 0 ) {
+               traversable->traverse( CloneAllSelected( clone ) );
+       }
+       return clone;
+}
+
 
 typedef std::map<CopiedString, std::size_t> EntityBreakdown;
 
@@ -794,13 +839,56 @@ void Scene_EntityBreakdown( EntityBreakdown& entitymap ){
        GlobalSceneGraph().traverse( EntityBreakdownWalker( entitymap ) );
 }
 
+class CountStuffWalker : public scene::Graph::Walker
+{
+int& m_patches;
+int& m_ents_ingame;
+int& m_groupents;
+int& m_groupents_ingame;
+public:
+CountStuffWalker( int& patches, int& ents_ingame, int& groupents, int& groupents_ingame )
+       : m_patches( patches ), m_ents_ingame( ents_ingame ), m_groupents( groupents ), m_groupents_ingame( groupents_ingame ){
+}
+bool pre( const scene::Path& path, scene::Instance& instance ) const {
+       Patch* patch = Node_getPatch( path.top() );
+       if( patch != 0 ){
+               ++m_patches;
+       }
+       Entity* entity = Node_getEntity( path.top() );
+       if ( entity != 0 ){
+               if( entity->isContainer() ){
+                       ++m_groupents;
+                       if( !string_equal_nocase( "func_group", entity->getKeyValue( "classname" ) ) &&
+                               !string_equal_nocase( "_decal", entity->getKeyValue( "classname" ) ) ){
+                               ++m_groupents_ingame;
+                               ++m_ents_ingame;
+                       }
+                       return true;
+               }
+               if( !string_equal_nocase_n( "light", entity->getKeyValue( "classname" ), 5 ) &&
+                       !string_equal_nocase( "misc_model", entity->getKeyValue( "classname" ) ) ){
+                       ++m_ents_ingame;
+               }
+       }
+       return true;
+}
+};
+
+void Scene_CountStuff( int& patches, int& ents_ingame, int& groupents, int& groupents_ingame ){
+       GlobalSceneGraph().traverse( CountStuffWalker( patches, ents_ingame, groupents, groupents_ingame ) );
+}
 
 WindowPosition g_posMapInfoWnd( c_default_window_pos );
 
 void DoMapInfo(){
        ModalDialog dialog;
-       ui::Entry brushes_entry{ui::null};
-       ui::Entry entities_entry{ui::null};
+       ui::Widget w_brushes{ui::null};
+       ui::Widget w_patches{ui::null};
+       ui::Widget w_ents{ui::null};
+       ui::Widget w_ents_ingame{ui::null};
+       ui::Widget w_groupents{ui::null};
+       ui::Widget w_groupents_ingame{ui::null};
+
        ui::ListStore EntityBreakdownWalker{ui::null};
 
        ui::Window window = MainFrame_getWindow().create_dialog_window("Map Info", G_CALLBACK(dialog_delete_callback ), &dialog );
@@ -813,40 +901,90 @@ void DoMapInfo(){
 
                {
                        auto hbox = create_dialog_hbox( 4 );
-                       vbox.pack_start( hbox, FALSE, TRUE, 0 );
+                       vbox.pack_start( hbox, FALSE, FALSE, 0 );
 
                        {
-                               auto table = create_dialog_table( 2, 2, 4, 4 );
+                               auto table = create_dialog_table( 3, 4, 4, 4 );
                                hbox.pack_start( table, TRUE, TRUE, 0 );
 
                                {
-                                       auto entry = ui::Entry(ui::New);
-                                       entry.show();
-                    table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
-                                       gtk_editable_set_editable( GTK_EDITABLE(entry), FALSE );
-
-                                       brushes_entry = entry;
+                                       auto label = ui::Label( "Total Brushes:" );
+                                       label.show();
+                    table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
+                                       gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
                                }
                                {
-                                       auto entry = ui::Entry(ui::New);
-                                       entry.show();
-                    table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
-                                       gtk_editable_set_editable( GTK_EDITABLE(entry), FALSE );
-
-                                       entities_entry = entry;
+                                       auto label = ui::Label( "" );
+                                       label.show();
+                    table.attach(label, {1, 2, 0, 1}, {GTK_FILL | GTK_EXPAND, 0}, {3, 0});
+                                       w_brushes = label;
                                }
                                {
-                                       ui::Widget label = ui::Label( "Total Brushes" );
+                                       auto label = ui::Label( "Total Patches" );
                                        label.show();
-                    table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
+                    table.attach(label, {2, 3, 0, 1}, {GTK_FILL, 0});
                                        gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
                                }
                                {
-                                       ui::Widget label = ui::Label( "Total Entities" );
+                                       auto label = ui::Label( "" );
                                        label.show();
-                    table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
+                    table.attach(label, {3, 4, 0, 1}, {GTK_FILL, 0}, {3, 0});
                                        gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
+                                       w_patches = label;
+                               }
+                               {
+                                       auto label = ui::Label( "Total Entities:" );
+                                       label.show();
+                                       table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
+                                       gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
+                               }
+                               {
+                                       auto label = ui::Label( "" );
+                                       label.show();
+                                       table.attach(label, {1, 2, 1, 2}, {GTK_FILL | GTK_EXPAND, 0}, {3, 0});
+                                       gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
+                                       w_ents = label;
+                               }
+                               {
+                                       auto label = ui::Label( "Ingame Entities:" );
+                                       label.show();
+                                       table.attach(label, {2, 3, 1, 2}, {GTK_FILL, 0});
+                                       gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
+                               }
+                               {
+                                       auto label = ui::Label( "" );
+                                       label.show();
+                                       table.attach(label, {3, 4, 1, 2}, {GTK_FILL | GTK_EXPAND, 0 }, {3, 0});
+                                       gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
+                                       w_ents_ingame = label;
+                               }
+                               {
+                                       auto label = ui::Label( "Group Entities:" );
+                                       label.show();
+                                       table.attach(label, {0, 1, 2, 3}, {GTK_FILL, 0});
+                                       gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
+                               }
+                               {
+                                       auto label = ui::Label( "" );
+                                       label.show();
+                                       table.attach(label, {1, 2, 2, 3}, {GTK_FILL | GTK_EXPAND, 0}, {3, 0});
+                                       gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
+                                       w_groupents = label;
+                               }
+                               {
+                                       auto label = ui::Label( "Ingame Group Entities:" );
+                                       label.show();
+                                       table.attach(label, {2, 3, 2, 3}, {GTK_FILL, 0});
+                                       gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
+                               }
+                               {
+                                       auto label = ui::Label( "" );
+                                       label.show();
+                                       table.attach(label, {3, 4, 2, 3}, {GTK_FILL | GTK_EXPAND, 0}, {3, 0});
+                                       gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
+                                       w_groupents_ingame = label;
                                }
+
                        }
                        {
                                auto vbox2 = create_dialog_vbox( 4 );
@@ -859,7 +997,7 @@ void DoMapInfo(){
                        }
                }
                {
-                       ui::Widget label = ui::Label( "Entity breakdown" );
+                       ui::Widget label = ui::Label( "*** Entity breakdown ***" );
                        label.show();
                        vbox.pack_start( label, FALSE, TRUE, 0 );
                        gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
@@ -869,7 +1007,7 @@ void DoMapInfo(){
                        vbox.pack_start( scr, TRUE, TRUE, 0 );
 
                        {
-                               auto store = ui::ListStore::from(gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_STRING ));
+                               auto store = ui::ListStore::from(gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_UINT ));
 
                                auto view = ui::TreeView(ui::TreeModel::from(store._handle));
                                gtk_tree_view_set_headers_clickable(view, TRUE );
@@ -905,19 +1043,45 @@ void DoMapInfo(){
 
                for ( EntityBreakdown::iterator i = entitymap.begin(); i != entitymap.end(); ++i )
                {
-                       char tmp[16];
-                       sprintf( tmp, "%u", Unsigned( ( *i ).second ) );
-                       EntityBreakdownWalker.append(0, (*i).first.c_str(), 1, tmp);
+                       EntityBreakdownWalker.append(0, (*i).first.c_str(), 1, Unsigned( ( *i ).second ));
                }
        }
 
        EntityBreakdownWalker.unref();
 
-       char tmp[16];
-       sprintf( tmp, "%u", Unsigned( g_brushCount.get() ) );
-       brushes_entry.text(tmp);
-       sprintf( tmp, "%u", Unsigned( g_entityCount.get() ) );
-       entities_entry.text(tmp);
+       int n_patches = 0;
+       int n_ents_ingame = 0;
+       int n_groupents = 0;
+       int n_groupents_ingame = 0;
+       Scene_CountStuff( n_patches, n_ents_ingame, n_groupents, n_groupents_ingame );
+       //globalOutputStream() << n_patches << n_ents_ingame << n_groupents << n_groupents_ingame << "\n";
+
+       char *markup;
+
+       markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%u</b></span>  ", Unsigned( g_brushCount.get() ) );
+       gtk_label_set_markup( GTK_LABEL( w_brushes ), markup );
+       g_free( markup );
+
+       markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%i</b></span>  ", n_patches );
+       gtk_label_set_markup( GTK_LABEL( w_patches ), markup );
+       g_free( markup );
+
+       markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%u</b></span>  ", Unsigned( g_entityCount.get() ) );
+       gtk_label_set_markup( GTK_LABEL( w_ents ), markup );
+       g_free( markup );
+
+       markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%i</b></span>  ", n_ents_ingame );
+       gtk_label_set_markup( GTK_LABEL( w_ents_ingame ), markup );
+       g_free( markup );
+
+       markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%i</b></span>  ", n_groupents );
+       gtk_label_set_markup( GTK_LABEL( w_groupents ), markup );
+       g_free( markup );
+
+       markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%i</b></span>  ", n_groupents_ingame );
+       gtk_label_set_markup( GTK_LABEL( w_groupents_ingame ), markup );
+       g_free( markup );
+
 
        modal_dialog_show( window, dialog );
 
@@ -966,6 +1130,8 @@ void Map_LoadFile( const char *filename ){
        MRU_AddFile( filename );
        g_strLastMapFolder = g_path_get_dirname( filename );
 
+       bool switch_format = false;
+
        {
                ScopeTimer timer( "map load" );
 
@@ -992,6 +1158,7 @@ void Map_LoadFile( const char *filename ){
                                if ( !format->wrongFormat ) {
                                        break;
                                }
+                               switch_format = !switch_format;
                        }
                }
 
@@ -1012,6 +1179,8 @@ void Map_LoadFile( const char *filename ){
        Map_StartPosition();
 
        g_currentMap = &g_map;
+
+       Brush_switchFormat( switch_format );
 }
 
 class Excluder
@@ -1316,7 +1485,17 @@ void ConstructRegionStartpoint( scene::Node* startpoint, const Vector3& region_m
 
    ===========================================================
  */
-bool region_active;
+bool region_active = false;
+
+ConstReferenceCaller<bool, void(const Callback<void(bool)> &), PropertyImpl<bool>::Export> g_region_caller( region_active );
+
+ToggleItem g_region_item( g_region_caller );
+
+/*void Map_ToggleRegion(){
+       region_active = !region_active;
+       g_region_item.update();
+}*/
+
 Vector3 region_mins( g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord );
 Vector3 region_maxs( g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord );
 
@@ -1443,6 +1622,7 @@ void Scene_Exclude_Region( bool exclude ){
  */
 void Map_RegionOff(){
        region_active = false;
+       g_region_item.update();
 
        region_maxs[0] = g_MaxWorldCoord - 64;
        region_mins[0] = g_MinWorldCoord + 64;
@@ -1456,6 +1636,7 @@ void Map_RegionOff(){
 
 void Map_ApplyRegion( void ){
        region_active = true;
+       g_region_item.update();
 
        Scene_Exclude_Region( false );
 }
@@ -1472,6 +1653,7 @@ void Map_RegionSelectedBrushes( void ){
        if ( GlobalSelectionSystem().countSelected() != 0
                 && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
                region_active = true;
+               g_region_item.update();
                Select_GetBounds( region_mins, region_maxs );
 
                Scene_Exclude_Selected( false );
@@ -1676,21 +1858,19 @@ bool Map_SaveSelected( const char* filename ){
        return MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Selected, filename );
 }
 
-
 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker
 {
-scene::Node& m_parent;
+       scene::Node& m_parent;
+       mutable bool m_emptyOldParent;
+
 public:
-ParentSelectedBrushesToEntityWalker( scene::Node& parent ) : m_parent( parent ){
+ParentSelectedBrushesToEntityWalker( scene::Node& parent ) : m_parent( parent ), m_emptyOldParent( false ){
 }
 
 bool pre( const scene::Path& path, scene::Instance& instance ) const {
-       if ( path.top().get_pointer() != &m_parent
-                && Node_isPrimitive( path.top() ) ) {
+       if ( path.top().get_pointer() != &m_parent && ( Node_isPrimitive( path.top() ) || m_emptyOldParent ) ) {
                Selectable* selectable = Instance_getSelectable( instance );
-               if ( selectable != 0
-                        && selectable->isSelected()
-                        && path.size() > 1 ) {
+               if ( selectable && selectable->isSelected() && path.size() > 1 ) {
                        return false;
                }
        }
@@ -1698,20 +1878,33 @@ bool pre( const scene::Path& path, scene::Instance& instance ) const {
 }
 
 void post( const scene::Path& path, scene::Instance& instance ) const {
-       if ( path.top().get_pointer() != &m_parent
-                && Node_isPrimitive( path.top() ) ) {
+       if ( path.top().get_pointer() == &m_parent )
+               return;
+
+       if ( Node_isPrimitive( path.top() ) ){
+               m_emptyOldParent = false;
                Selectable* selectable = Instance_getSelectable( instance );
-               if ( selectable != 0
-                        && selectable->isSelected()
-                        && path.size() > 1 ) {
+
+               if ( selectable && selectable->isSelected() && path.size() > 1 ){
                        scene::Node& parent = path.parent();
-                       if ( &parent != &m_parent ) {
+                       if ( &parent != &m_parent ){
                                NodeSmartReference node( path.top().get() );
-                               Node_getTraversable( parent )->erase( node );
+                               scene::Traversable* traversable_parent = Node_getTraversable( parent );
+                               traversable_parent->erase( node );
                                Node_getTraversable( m_parent )->insert( node );
+                               if ( traversable_parent->empty() )
+                                       m_emptyOldParent = true;
                        }
                }
        }
+       else if ( m_emptyOldParent ){
+               m_emptyOldParent = false;
+               // delete empty entities
+               Entity* entity = Node_getEntity( path.top() );
+               if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map )     && Node_getTraversable( path.top() )->empty() ) {
+                       Path_deleteTop( path );
+               }
+       }
 }
 };
 
@@ -1933,6 +2126,7 @@ void SaveMap(){
        }
        else if ( Map_Modified( g_map ) ) {
                Map_Save();
+               MRU_AddFile( g_map.m_name.c_str() );    //add on saving, but not opening via cmd line: spoils the list
        }
 }
 
@@ -2050,7 +2244,7 @@ void SelectBrush( int entitynum, int brushnum ){
 }
 
 
-class BrushFindIndexWalker : public scene::Graph::Walker
+class BrushFindIndexWalker : public scene::Traversable::Walker
 {
 mutable const scene::Node* m_node;
 std::size_t& m_count;
@@ -2059,9 +2253,9 @@ BrushFindIndexWalker( const scene::Node& node, std::size_t& count )
        : m_node( &node ), m_count( count ){
 }
 
-bool pre( const scene::Path& path, scene::Instance& instance ) const {
-       if ( Node_isPrimitive( path.top() ) ) {
-               if ( m_node == path.top().get_pointer() ) {
+bool pre( scene::Node& node ) const {
+       if ( Node_isPrimitive( node ) ) {
+               if ( m_node == &node ) {
                        m_node = 0;
                }
                if ( m_node ) {
@@ -2072,7 +2266,7 @@ bool pre( const scene::Path& path, scene::Instance& instance ) const {
 }
 };
 
-class EntityFindIndexWalker : public scene::Graph::Walker
+class EntityFindIndexWalker : public scene::Traversable::Walker
 {
 mutable const scene::Node* m_node;
 std::size_t& m_count;
@@ -2081,9 +2275,9 @@ EntityFindIndexWalker( const scene::Node& node, std::size_t& count )
        : m_node( &node ), m_count( count ){
 }
 
-bool pre( const scene::Path& path, scene::Instance& instance ) const {
-       if ( Node_isEntity( path.top() ) ) {
-               if ( m_node == path.top().get_pointer() ) {
+bool pre( scene::Node& node ) const {
+       if ( Node_isEntity( node ) ) {
+               if ( m_node == &node ) {
                        m_node = 0;
                }
                if ( m_node ) {
@@ -2100,8 +2294,24 @@ static void GetSelectionIndex( int *ent, int *brush ){
        if ( GlobalSelectionSystem().countSelected() != 0 ) {
                const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
 
-               GlobalSceneGraph().traverse( BrushFindIndexWalker( path.top(), count_brush ) );
-               GlobalSceneGraph().traverse( EntityFindIndexWalker( path.parent(), count_entity ) );
+               {
+                       scene::Traversable* traversable = Node_getTraversable( path.parent() );
+                       if ( traversable != 0 && path.size() == 3 ) {
+                               traversable->traverse( BrushFindIndexWalker( path.top(), count_brush ) );
+                       }
+               }
+
+               {
+                       scene::Traversable* traversable = Node_getTraversable( GlobalSceneGraph().root() );
+                       if ( traversable != 0 ) {
+                               if( path.size() == 3 ){
+                                       traversable->traverse( EntityFindIndexWalker( path.parent(), count_entity ) );
+                               }
+                               else if ( path.size() == 2 ){
+                                       traversable->traverse( EntityFindIndexWalker( path.top(), count_entity ) );
+                               }
+                       }
+               }
        }
        *brush = int(count_brush);
        *ent = int(count_entity);
@@ -2252,7 +2462,8 @@ void Map_Construct(){
        GlobalCommands_insert( "RegionOff", makeCallbackF(RegionOff) );
        GlobalCommands_insert( "RegionSetXY", makeCallbackF(RegionXY) );
        GlobalCommands_insert( "RegionSetBrush", makeCallbackF(RegionBrush) );
-       GlobalCommands_insert( "RegionSetSelection", makeCallbackF(RegionSelected), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
+       //GlobalCommands_insert( "RegionSetSelection", makeCallbackF(RegionSelected), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
+       GlobalToggles_insert( "RegionSetSelection", makeCallbackF(RegionSelected), ToggleItem::AddCallbackCaller( g_region_item ), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
 
        GlobalPreferenceSystem().registerPreference( "LastMap", make_property_string( g_strLastMap ) );
        GlobalPreferenceSystem().registerPreference( "LoadLastMap", make_property_string( g_bLoadLastMap ) );
index c68b02751e270df0790012a283216e01dd98ccca..d23e3928500256c1603eb43233286e1269359775 100644 (file)
@@ -125,6 +125,7 @@ bool Map_Save();
 bool Map_SaveAs();
 
 scene::Node& Node_Clone( scene::Node& node );
+scene::Node& Node_Clone_Selected( scene::Node& node );
 
 void DoMapInfo();
 
index 640fd3914ca94e1f3f9ec7a77d3f9c5e9c1d66af..452feaf69f6c08e6242a4d55b76a69405e4f4032 100644 (file)
 #include "map.h"
 #include "qe3.h"
 
-const int MRU_MAX = 4;
+#define MRU_MAX 9
 namespace {
-       GtkMenuItem *MRU_items[MRU_MAX];
-       std::size_t MRU_used;
-       typedef CopiedString MRU_filename_t;
-       MRU_filename_t MRU_filenames[MRU_MAX];
-       typedef const char* MRU_key_t;
-       MRU_key_t MRU_keys[MRU_MAX] = { "File0", "File1", "File2", "File3" };
+GtkMenuItem *MRU_items[MRU_MAX];
+std::size_t MRU_used;
+typedef CopiedString MRU_filename_t;
+MRU_filename_t MRU_filenames[MRU_MAX];
+typedef const char* MRU_key_t;
+MRU_key_t MRU_keys[MRU_MAX] = { "File0", "File1", "File2", "File3", "File4", "File5", "File6", "File7", "File8" };
 }
 
 inline const char* MRU_GetText( std::size_t index ){
@@ -54,25 +54,25 @@ private:
 
 public:
        EscapedMnemonic() : m_buffer(){
-               m_buffer.push_back( '_' );
-       }
-       const char* c_str() const {
-               return m_buffer.c_str();
-       }
-       void push_back( char c ){ // not escaped
-               m_buffer.push_back( c );
-       }
-       std::size_t write( const char* buffer, std::size_t length ){
-               for ( const char* end = buffer + length; buffer != end; ++buffer )
-               {
-                       if ( *buffer == '_' ) {
-                               m_buffer.push_back( '_' );
-                       }
-
-                       m_buffer.push_back( *buffer );
+       m_buffer.push_back( '_' );
+}
+const char* c_str() const {
+       return m_buffer.c_str();
+}
+void push_back( char c ){ // not escaped
+       m_buffer.push_back( c );
+}
+std::size_t write( const char* buffer, std::size_t length ){
+       for ( const char* end = buffer + length; buffer != end; ++buffer )
+       {
+               if ( *buffer == '_' ) {
+                       m_buffer.push_back( '_' );
                }
-               return length;
+
+               m_buffer.push_back( *buffer );
        }
+       return length;
+}
 };
 
 template<typename T>
@@ -175,17 +175,17 @@ void MRU_Activate( std::size_t index ){
 class LoadMRU
 {
 private:
-       std::size_t m_number;
+std::size_t m_number;
 
 public:
-       LoadMRU( std::size_t number )
-               : m_number( number ){
-       }
-       void load(){
-               if ( ConfirmModified( "Open Map" ) ) {
-                       MRU_Activate( m_number - 1 );
-               }
+LoadMRU( std::size_t number )
+       : m_number( number ){
+}
+void load(){
+       if ( ConfirmModified( "Open Map" ) ) {
+               MRU_Activate( m_number - 1 );
        }
+}
 };
 
 typedef MemberCaller<LoadMRU, void(), &LoadMRU::load> LoadMRUCaller;
@@ -194,6 +194,11 @@ LoadMRU g_load_mru1( 1 );
 LoadMRU g_load_mru2( 2 );
 LoadMRU g_load_mru3( 3 );
 LoadMRU g_load_mru4( 4 );
+LoadMRU g_load_mru5( 5 );
+LoadMRU g_load_mru6( 6 );
+LoadMRU g_load_mru7( 7 );
+LoadMRU g_load_mru8( 8 );
+LoadMRU g_load_mru9( 9 );
 
 void MRU_constructMenu( ui::Menu menu ){
        {
@@ -216,6 +221,31 @@ void MRU_constructMenu( ui::Menu menu ){
                item.hide();
                MRU_AddWidget( item, 3 );
        }
+       {
+               auto item = create_menu_item_with_mnemonic( menu, "_5", LoadMRUCaller( g_load_mru5 ) );
+               item.hide();
+               MRU_AddWidget( item, 4 );
+       }
+       {
+               auto item = create_menu_item_with_mnemonic( menu, "_6", LoadMRUCaller( g_load_mru6 ) );
+               item.hide();
+               MRU_AddWidget( item, 5 );
+       }
+       {
+               auto item = create_menu_item_with_mnemonic( menu, "_7", LoadMRUCaller( g_load_mru7 ) );
+               item.hide();
+               MRU_AddWidget( item, 6 );
+       }
+       {
+               auto item = create_menu_item_with_mnemonic( menu, "_8", LoadMRUCaller( g_load_mru8 ) );
+               item.hide();
+               MRU_AddWidget( item, 7 );
+       }
+       {
+               auto item = create_menu_item_with_mnemonic( menu, "_9", LoadMRUCaller( g_load_mru9 ) );
+               item.hide();
+               MRU_AddWidget( item, 8 );
+       }
 }
 
 #include "preferencesystem.h"
index 869bc1d86690b28c1eb9d723fe0a9d8cdefaa6ab..f7c7006fcf14f42bb7b618a9d13ff7145791f477 100644 (file)
 
 multimon_globals_t g_multimon_globals;
 
-LatchedValue<bool> g_Multimon_enableSysMenuPopups( false, "Floating windows sysmenu icons" );
+//LatchedValue<bool> g_Multimon_enableSysMenuPopups( false, "Floating windows sysmenu icons" );
 
 void MultiMonitor_constructPreferences( PreferencesPage& page ){
        ui::CheckButton primary_monitor = page.appendCheckBox( "Multi Monitor", "Start on Primary Monitor", g_multimon_globals.m_bStartOnPrimMon );
-       ui::CheckButton popup = page.appendCheckBox(
-               "", "Disable system menu on popup windows",
-               make_property( g_Multimon_enableSysMenuPopups )
-               );
-       Widget_connectToggleDependency( popup, primary_monitor );
+//     ui::CheckButton popup = page.appendCheckBox(
+//             "", "Disable system menu on popup windows",
+//             make_property( g_Multimon_enableSysMenuPopups )
+//             );
+//     Widget_connectToggleDependency( popup, primary_monitor );
 }
 
 #include "preferencesystem.h"
@@ -87,9 +87,9 @@ void MultiMon_Construct(){
        }
 
        GlobalPreferenceSystem().registerPreference( "StartOnPrimMon", make_property_string( g_multimon_globals.m_bStartOnPrimMon ) );
-       GlobalPreferenceSystem().registerPreference( "NoSysMenuPopups", make_property_string( g_Multimon_enableSysMenuPopups.m_latched ) );
+//     GlobalPreferenceSystem().registerPreference( "NoSysMenuPopups", make_property_string( g_Multimon_enableSysMenuPopups.m_latched ) );
 
-       g_Multimon_enableSysMenuPopups.useLatched();
+//     g_Multimon_enableSysMenuPopups.useLatched();
 
        PreferencesDialog_addInterfacePreferences( makeCallbackF(MultiMonitor_constructPreferences) );
 }
index 3d53842f5568aa9d6e2158510960d7236d076ae9..a92597d4c95e461a9d1b5773b8fcea2b7bd3e267 100644 (file)
@@ -812,20 +812,20 @@ void Patch::InsertPoints( EMatrixMajor mt, bool bFirst ){
                return;
        }
        if ( bFirst ) {
-               pos = height - 1;
+               pos = 2;
        }
        else
        {
-               pos = 2;
+               pos = height - 1;
        }
 
        if ( pos >= height ) {
                if ( bFirst ) {
-                       pos = height - 1;
+                       pos = 2;
                }
                else
                {
-                       pos = 2;
+                       pos = height - 1;
                }
        }
        else if ( pos == 0 ) {
@@ -956,19 +956,19 @@ void Patch::RemovePoints( EMatrixMajor mt, bool bFirst ){
                return;
        }
        if ( bFirst ) {
-               pos = height - 3;
+               pos = 2;
        }
        else
        {
-               pos = 2;
+               pos = height - 3;
        }
        if ( pos >= height ) {
                if ( bFirst ) {
-                       pos = height - 3;
+                       pos = 2;
                }
                else
                {
-                       pos = 2;
+                       pos = height - 3;
                }
        }
        else if ( pos == 0 ) {
@@ -2776,6 +2776,417 @@ void Patch::BuildVertexArray(){
 }
 
 
+Vector3 getAverageNormal(const Vector3& normal1, const Vector3& normal2)
+{
+       // Beware of normals with 0 length
+       if ( vector3_length_squared( normal1 ) == 0 ) return normal2;
+       if ( vector3_length_squared( normal2 ) == 0 ) return normal1;
+
+       // Both normals have length > 0
+       //Vector3 n1 = vector3_normalised( normal1 );
+       //Vector3 n2 = vector3_normalised( normal2 );
+
+       // Get the angle bisector
+       if( vector3_length_squared( normal1 + normal2 ) == 0 ) return normal1;
+
+       Vector3 normal = vector3_normalised (normal1 + normal2);
+
+       // Now calculate the length correction out of the angle
+       // of the two normals
+               /* float factor = cos(n1.angle(n2) * 0.5); */
+       float factor = (float) vector3_dot( normal1, normal2 );
+       if ( factor > 1.0 ) factor = 1;
+       if ( factor < -1.0 ) factor = -1;
+       factor = acos( factor );
+
+       factor = cos( factor * 0.5 );
+
+       // Check for div by zero (if the normals are antiparallel)
+       // and stretch the resulting normal, if necessary
+       if (factor != 0)
+       {
+               normal /= factor;
+       }
+
+       return normal;
+}
+
+void Patch::createThickenedOpposite(const Patch& sourcePatch,
+                                                                       const float thickness,
+                                                                       const int axis,
+                                                                       bool& no12,
+                                                                       bool& no34)
+{
+       // Clone the dimensions from the other patch
+       setDims(sourcePatch.getWidth(), sourcePatch.getHeight());
+
+       // Also inherit the tesselation from the source patch
+               //setFixedSubdivisions(sourcePatch.subdivionsFixed(), sourcePatch.getSubdivisions());
+
+       // Copy the shader from the source patch
+       SetShader(sourcePatch.GetShader());
+
+       // if extrudeAxis == 0,0,0 the patch is extruded along its vertex normals
+       Vector3 extrudeAxis(0,0,0);
+
+       switch (axis) {
+               case 0: // X-Axis
+                       extrudeAxis = Vector3(1,0,0);
+                       break;
+               case 1: // Y-Axis
+                       extrudeAxis = Vector3(0,1,0);
+                       break;
+               case 2: // Z-Axis
+                       extrudeAxis = Vector3(0,0,1);
+                       break;
+               default:
+                       // Default value already set during initialisation
+                       break;
+       }
+
+       //check if certain seams are required + cycling in normals calculation is needed
+       //( endpoints != startpoints ) - not a cylinder or something
+       for (std::size_t col = 0; col < m_width; col++){
+               if( vector3_length_squared( sourcePatch.ctrlAt( 0, col ).m_vertex - sourcePatch.ctrlAt( m_height - 1, col ).m_vertex ) > 0.1f ){
+                       //globalOutputStream() << "yes12.\n";
+                       no12 = false;
+                       break;
+               }
+       }
+       for (std::size_t row = 0; row < m_height; row++){
+               if( vector3_length_squared( sourcePatch.ctrlAt( row, 0 ).m_vertex - sourcePatch.ctrlAt( row, m_width - 1 ).m_vertex ) > 0.1f ){
+                       no34 = false;
+                       //globalOutputStream() << "yes34.\n";
+                       break;
+               }
+       }
+
+       for (std::size_t col = 0; col < m_width; col++)
+       {
+               for (std::size_t row = 0; row < m_height; row++)
+               {
+                       // The current control vertex on the other patch
+                       const PatchControl& curCtrl = sourcePatch.ctrlAt(row, col);
+
+                       Vector3 normal;
+
+                       // Are we extruding along vertex normals (i.e. extrudeAxis == 0,0,0)?
+                       if (extrudeAxis == Vector3(0,0,0))
+                       {
+                               // The col tangents (empty if 0,0,0)
+                               Vector3 colTangent[2] = { Vector3(0,0,0), Vector3(0,0,0) };
+
+                               // Are we at the beginning/end of the row? + not cylinder
+                               if ( (col == 0 || col == m_width - 1) && !no34 )
+                               {
+                                       // Get the next col index
+                                       std::size_t nextCol = (col == m_width - 1) ? (col - 1) : (col + 1);
+
+                                       const PatchControl& colNeighbour = sourcePatch.ctrlAt(row, nextCol);
+
+                                       // One available tangent
+                                       colTangent[0] = colNeighbour.m_vertex - curCtrl.m_vertex;
+                                       // Reverse it if we're at the end of the column
+                                       colTangent[0] *= (col == m_width - 1) ? -1 : +1;
+                                       //normalize
+                                       if ( vector3_length_squared( colTangent[0] ) != 0 ) vector3_normalise( colTangent[0] );
+                               }
+                               // We are in between, two tangents can be calculated
+                               else
+                               {
+                                       // Take two neighbouring vertices that should form a line segment
+                                       std::size_t nextCol, prevCol;
+                                       if( col == 0 ){
+                                               nextCol = col+1;
+                                               prevCol = m_width-2;
+                                       }
+                                       else if( col == m_width - 1 ){
+                                               nextCol = 1;
+                                               prevCol = col-1;
+                                       }
+                                       else{
+                                               nextCol = col+1;
+                                               prevCol = col-1;
+                                       }
+                                       const PatchControl& neighbour1 = sourcePatch.ctrlAt(row, nextCol);
+                                       const PatchControl& neighbour2 = sourcePatch.ctrlAt(row, prevCol);
+
+
+                                       // Calculate both available tangents
+                                       colTangent[0] = neighbour1.m_vertex - curCtrl.m_vertex;
+                                       colTangent[1] = neighbour2.m_vertex - curCtrl.m_vertex;
+
+                                       // Reverse the second one
+                                       colTangent[1] *= -1;
+
+                                       //normalize b4 stuff
+                                       if ( vector3_length_squared( colTangent[0] ) != 0 ) vector3_normalise( colTangent[0] );
+                                       if ( vector3_length_squared( colTangent[1] ) != 0 ) vector3_normalise( colTangent[1] );
+
+                                       // Cull redundant tangents (parallel)
+                                       if ( vector3_length_squared( colTangent[1] + colTangent[0] ) == 0 ||
+                                               vector3_length_squared( colTangent[1] - colTangent[0] ) == 0 ){
+                                               colTangent[1] = Vector3(0,0,0);
+                                       }
+                               }
+
+                               // Calculate the tangent vectors to the next row
+                               Vector3 rowTangent[2] = { Vector3(0,0,0), Vector3(0,0,0) };
+
+                               // Are we at the beginning or the end?
+                               if ( (row == 0 || row == m_height - 1) && !no12 )
+                               {
+                                       // Yes, only calculate one row tangent
+                                       // Get the next row index
+                                       std::size_t nextRow = (row == m_height - 1) ? (row - 1) : (row + 1);
+
+                                       const PatchControl& rowNeighbour = sourcePatch.ctrlAt(nextRow, col);
+
+                                       // First tangent
+                                       rowTangent[0] = rowNeighbour.m_vertex - curCtrl.m_vertex;
+                                       // Reverse it accordingly
+                                       rowTangent[0] *= (row == m_height - 1) ? -1 : +1;
+                                       //normalize
+                                       if ( vector3_length_squared( rowTangent[0] ) != 0 ) vector3_normalise( rowTangent[0] );
+                               }
+                               else
+                               {
+                                       // Two tangents to calculate
+                                       std::size_t nextRow, prevRow;
+                                       if( row == 0 ){
+                                               nextRow = row+1;
+                                               prevRow = m_height-2;
+                                       }
+                                       else if( row == m_height - 1 ){
+                                               nextRow = 1;
+                                               prevRow = row-1;
+                                       }
+                                       else{
+                                               nextRow = row+1;
+                                               prevRow = row-1;
+                                       }
+                                       const PatchControl& rowNeighbour1 = sourcePatch.ctrlAt(nextRow, col);
+                                       const PatchControl& rowNeighbour2 = sourcePatch.ctrlAt(prevRow, col);
+
+                                       // First tangent
+                                       rowTangent[0] = rowNeighbour1.m_vertex - curCtrl.m_vertex;
+                                       rowTangent[1] = rowNeighbour2.m_vertex - curCtrl.m_vertex;
+
+                                       // Reverse the second one
+                                       rowTangent[1] *= -1;
+
+                                       //normalize b4 stuff
+                                       if ( vector3_length_squared( rowTangent[0] ) != 0 ) vector3_normalise( rowTangent[0] );
+                                       if ( vector3_length_squared( rowTangent[1] ) != 0 ) vector3_normalise( rowTangent[1] );
+
+                                       // Cull redundant tangents (parallel)
+                                       if ( vector3_length_squared( rowTangent[1] + rowTangent[0] ) == 0 ||
+                                               vector3_length_squared( rowTangent[1] - rowTangent[0] ) == 0 ){
+                                               rowTangent[1] = Vector3(0,0,0);
+                                       }
+                               }
+
+
+                               //clean parallel pairs...
+                               if ( vector3_length_squared( rowTangent[0] + colTangent[0] ) == 0 ||
+                                       vector3_length_squared( rowTangent[0] - colTangent[0] ) == 0 ){
+                                       rowTangent[0] = Vector3(0,0,0);
+                               }
+                               if ( vector3_length_squared( rowTangent[1] + colTangent[1] ) == 0 ||
+                                       vector3_length_squared( rowTangent[1] - colTangent[1] ) == 0 ){
+                                       rowTangent[1] = Vector3(0,0,0);
+                               }
+                               if ( vector3_length_squared( rowTangent[0] + colTangent[1] ) == 0 ||
+                                       vector3_length_squared( rowTangent[0] - colTangent[1] ) == 0 ){
+                                       colTangent[1] = Vector3(0,0,0);
+                               }
+                               if ( vector3_length_squared( rowTangent[1] + colTangent[0] ) == 0 ||
+                                       vector3_length_squared( rowTangent[1] - colTangent[0] ) == 0 ){
+                                       rowTangent[1] = Vector3(0,0,0);
+                               }
+
+                               //clean dummies
+                               if ( vector3_length_squared( colTangent[0] ) == 0 ){
+                                       colTangent[0] = colTangent[1];
+                                       colTangent[1] = Vector3(0,0,0);
+                               }
+                               if ( vector3_length_squared( rowTangent[0] ) == 0 ){
+                                       rowTangent[0] = rowTangent[1];
+                                       rowTangent[1] = Vector3(0,0,0);
+                               }
+                               if( vector3_length_squared( rowTangent[0] ) == 0 || vector3_length_squared( colTangent[0] ) == 0 ){
+                                       normal = extrudeAxis;
+
+                               }
+                               else{
+                                       // If two column + two row tangents are available, take the length-corrected average
+                                       if ( ( fabs( colTangent[1][0] ) + fabs( colTangent[1][1] ) + fabs( colTangent[1][2] ) ) > 0 &&
+                                                       ( fabs( rowTangent[1][0] ) + fabs( rowTangent[1][1] ) + fabs( rowTangent[1][2] ) ) > 0 )
+                                       {
+                                               // Two column normals to calculate
+                                               Vector3 normal1 = vector3_normalised( vector3_cross( rowTangent[0], colTangent[0] ) );
+                                               Vector3 normal2 = vector3_normalised( vector3_cross( rowTangent[1], colTangent[1] ) );
+
+                                               normal = getAverageNormal(normal1, normal2);
+                                               /*globalOutputStream() << "0\n";
+                                               globalOutputStream() << normal1 << "\n";
+                                               globalOutputStream() << normal2 << "\n";
+                                               globalOutputStream() << normal << "\n";*/
+
+                                       }
+                                       // If two column tangents are available, take the length-corrected average
+                                       else if ( ( fabs( colTangent[1][0] ) + fabs( colTangent[1][1] ) + fabs( colTangent[1][2] ) ) > 0)
+                                       {
+                                               // Two column normals to calculate
+                                               Vector3 normal1 = vector3_normalised( vector3_cross( rowTangent[0], colTangent[0] ) );
+                                               Vector3 normal2 = vector3_normalised( vector3_cross( rowTangent[0], colTangent[1] ) );
+
+                                               normal = getAverageNormal(normal1, normal2);
+                                               /*globalOutputStream() << "1\n";
+                                               globalOutputStream() << normal1 << "\n";
+                                               globalOutputStream() << normal2 << "\n";
+                                               globalOutputStream() << normal << "\n";*/
+
+                                       }
+                                       else
+                                       {
+                                               // One column tangent available, maybe we have a second rowtangent?
+                                               if ( ( fabs( rowTangent[1][0] ) + fabs( rowTangent[1][1] ) + fabs( rowTangent[1][2] ) ) > 0)
+                                               {
+                                                       // Two row normals to calculate
+                                                       Vector3 normal1 = vector3_normalised( vector3_cross( rowTangent[0], colTangent[0] ) );
+                                                       Vector3 normal2 = vector3_normalised( vector3_cross( rowTangent[1], colTangent[0] ) );
+
+                                                       normal = getAverageNormal(normal1, normal2);
+                                                       /*globalOutputStream() << "2\n";
+                                                       globalOutputStream() << rowTangent[0] << "\n";
+                                                       globalOutputStream() << colTangent[0] << "\n";
+                                                       globalOutputStream() << vector3_cross( rowTangent[0], colTangent[0]) << "\n";
+                                                       globalOutputStream() << normal1 << "\n";
+                                                       globalOutputStream() << normal2 << "\n";
+                                                       globalOutputStream() << normal << "\n";*/
+
+                                               }
+                                               else
+                                               {
+                                                       if ( vector3_length_squared( vector3_cross( rowTangent[0], colTangent[0] ) ) > 0 ){
+                                                               normal = vector3_normalised( vector3_cross( rowTangent[0], colTangent[0] ) );
+                                                               /*globalOutputStream() << "3\n";
+                                                               globalOutputStream() << (float)vector3_length_squared( vector3_cross( rowTangent[0], colTangent[0] ) ) << "\n";
+                                                               globalOutputStream() << normal << "\n";*/
+                                                       }
+                                                       else{
+                                                               normal = extrudeAxis;
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+                       else
+                       {
+                               // Take the predefined extrude direction instead
+                               normal = extrudeAxis;
+                       }
+
+                       // Store the new coordinates into this patch at the current coords
+                       ctrlAt(row, col).m_vertex = curCtrl.m_vertex + normal*thickness;
+
+                       // Clone the texture cooordinates of the source patch
+                       ctrlAt(row, col).m_texcoord = curCtrl.m_texcoord;
+               }
+       }
+
+       // Notify the patch about the change
+       controlPointsChanged();
+}
+
+void Patch::createThickenedWall(const Patch& sourcePatch,
+                                                               const Patch& targetPatch,
+                                                               const int wallIndex)
+{
+       // Copy the shader from the source patch
+       SetShader(sourcePatch.GetShader());
+
+       // The start and end control vertex indices
+       int start = 0;
+       int end = 0;
+       // The increment (incr = 1 for the "long" edge, incr = width for the "short" edge)
+       int incr = 1;
+
+       // These are the target dimensions of this wall
+       // The width is depending on which edge is "seamed".
+       int cols = 0;
+       int rows = 3;
+
+       int sourceWidth = static_cast<int>(sourcePatch.getWidth());
+       int sourceHeight = static_cast<int>(sourcePatch.getHeight());
+/*
+       bool sourceTesselationFixed = sourcePatch.subdivionsFixed();
+       Subdivisions sourceTesselationX(sourcePatch.getSubdivisions().x(), 1);
+       Subdivisions sourceTesselationY(sourcePatch.getSubdivisions().y(), 1);
+*/
+       // Determine which of the four edges have to be connected
+       // and calculate the start, end & stepsize for the following loop
+       switch (wallIndex) {
+               case 0:
+                       cols = sourceWidth;
+                       start = 0;
+                       end = sourceWidth - 1;
+                       incr = 1;
+                       //setFixedSubdivisions(sourceTesselationFixed, sourceTesselationX);
+                       break;
+               case 1:
+                       cols = sourceWidth;
+                       start = sourceWidth * (sourceHeight-1);
+                       end = sourceWidth*sourceHeight - 1;
+                       incr = 1;
+                       //setFixedSubdivisions(sourceTesselationFixed, sourceTesselationX);
+                       break;
+               case 2:
+                       cols = sourceHeight;
+                       start = 0;
+                       end = sourceWidth*(sourceHeight-1);
+                       incr = sourceWidth;
+                       //setFixedSubdivisions(sourceTesselationFixed, sourceTesselationY);
+                       break;
+               case 3:
+                       cols = sourceHeight;
+                       start = sourceWidth - 1;
+                       end = sourceWidth*sourceHeight - 1;
+                       incr = sourceWidth;
+                       //setFixedSubdivisions(sourceTesselationFixed, sourceTesselationY);
+                       break;
+       }
+
+       setDims(cols, rows);
+
+       const PatchControlArray& sourceCtrl = sourcePatch.getControlPoints();
+       const PatchControlArray& targetCtrl = targetPatch.getControlPoints();
+
+       int col = 0;
+       // Now go through the control vertices with these calculated stepsize
+       for (int idx = start; idx <= end; idx += incr, col++) {
+               Vector3 sourceCoord = sourceCtrl[idx].m_vertex;
+               Vector3 targetCoord = targetCtrl[idx].m_vertex;
+               Vector3 middleCoord = (sourceCoord + targetCoord) / 2;
+
+               // Now assign the vertex coordinates
+               ctrlAt(0, col).m_vertex = sourceCoord;
+               ctrlAt(1, col).m_vertex = middleCoord;
+               ctrlAt(2, col).m_vertex = targetCoord;
+       }
+
+       if (wallIndex == 0 || wallIndex == 3) {
+               InvertMatrix();
+       }
+
+       // Notify the patch about the change
+       controlPointsChanged();
+
+       // Texture the patch "naturally"
+       NaturalTexture();
+}
+
 
 class PatchFilterWrapper : public Filter
 {
index 6e8742b0daf0c588b7d12aaa8c0c9387841fc389..45d53fa845b85676ae2a6e9b1558713a13544127 100644 (file)
@@ -880,6 +880,12 @@ const_iterator end() const {
 PatchControlArray& getControlPoints(){
        return m_ctrl;
 }
+
+// Same as above, just for const arguments
+const PatchControlArray& getControlPoints() const {
+       return m_ctrl;
+}
+
 PatchControlArray& getControlPointsTransformed(){
        return m_ctrlTransformed;
 }
@@ -916,6 +922,8 @@ void SetTextureRepeat( float s, float t ); // call with s=1 t=1 for FIT
 void CapTexture();
 void NaturalTexture();
 void ProjectTexture( int nAxis );
+void createThickenedOpposite(const Patch& sourcePatch, const float thickness, const int axis, bool& no12, bool& no34 );
+void createThickenedWall(const Patch& sourcePatch, const Patch& targetPatch, const int wallIndex);
 
 void undoSave(){
        if ( m_map != 0 ) {
@@ -1414,6 +1422,11 @@ void allocate( std::size_t size ){
 
 void setSelected( bool select ){
        m_selectable.setSelected( select );
+       if ( !select && parent() ){
+               Selectable* sel_parent = Instance_getSelectable( *parent() );
+               if ( sel_parent && sel_parent->isSelected() )
+                       sel_parent->setSelected( false );
+       }
 }
 bool isSelected() const {
        return m_selectable.isSelected();
@@ -1572,6 +1585,13 @@ void transformComponents( const Matrix4& matrix ){
        }
 }
 
+void invertComponentSelection(){
+       for ( PatchControlInstances::iterator i = m_ctrl_instances.begin(); i != m_ctrl_instances.end(); ++i )
+       {
+               ( *i ).m_selectable.setSelected( !( *i ).m_selectable.isSelected() );
+       }
+}
+
 
 void selectPlanes( Selector& selector, SelectionTest& test, const PlaneCallback& selectedPlaneCallback ){
        test.BeginMesh( localToWorld() );
@@ -1802,6 +1822,9 @@ bool pre( const scene::Path& path, scene::Instance& instance ) const {
                        m_functor( *patch );
                }
        }
+       else{
+               return false;
+       }
        return true;
 }
 };
@@ -1826,6 +1849,9 @@ bool pre( const scene::Path& path, scene::Instance& instance ) const {
                        m_functor( *patch );
                }
        }
+       else{
+               return false;
+       }
        return true;
 }
 };
@@ -1849,6 +1875,9 @@ bool pre( const scene::Path& path, scene::Instance& instance ) const {
                        m_functor( *patch );
                }
        }
+       else{
+               return false;
+       }
        return true;
 }
 };
index 6a29298d592f8de1a599863a37669c572c66af22..ab228654a90147ac254ab987286aa977129d0440 100644 (file)
@@ -283,6 +283,8 @@ void PatchInspector_queueDraw(){
 void DoPatchInspector(){
        g_PatchInspector.GetPatchInfo();
        if ( !g_PatchInspector.visible() ) {
+               // workaround for strange gtk behaviour - modifying the contents of a window while it is not visible causes the window position to change without sending a configure_event
+               g_PatchInspector.m_position_tracker.sync( g_PatchInspector.GetWidget() );
                g_PatchInspector.ShowDlg();
        }
 }
@@ -800,7 +802,7 @@ ui::Window PatchInspector::BuildDialog(){
                                                        auto spin = ui::SpinButton( adj, 1, 0 );
                                                        spin.show();
                             table.attach(spin, {1, 2, 0, 1}, {0, 0});
-                                                       spin.dimensions(10, -1);
+                                                       spin.dimensions(16, -2);
                                                        gtk_widget_set_can_focus( spin, false );
                                                }
                                                {
@@ -817,7 +819,7 @@ ui::Window PatchInspector::BuildDialog(){
                                                        auto spin = ui::SpinButton( adj, 1, 0 );
                                                        spin.show();
                             table.attach(spin, {1, 2, 1, 2}, {0, 0});
-                                                       spin.dimensions(10, -1);
+                                                       spin.dimensions(16, -2);
                                                        gtk_widget_set_can_focus( spin, false );
                                                }
                                                {
@@ -834,7 +836,7 @@ ui::Window PatchInspector::BuildDialog(){
                                                        auto spin = ui::SpinButton( adj, 1, 0 );
                                                        spin.show();
                             table.attach(spin, {1, 2, 2, 3}, {0, 0});
-                                                       spin.dimensions(10, -1);
+                                                       spin.dimensions(16, -2);
                                                        gtk_widget_set_can_focus( spin, false );
                                                }
                                                {
@@ -851,7 +853,7 @@ ui::Window PatchInspector::BuildDialog(){
                                                        auto spin = ui::SpinButton( adj, 1, 0 );
                                                        spin.show();
                             table.attach(spin, {1, 2, 3, 4}, {0, 0});
-                                                       spin.dimensions(10, -1);
+                                                       spin.dimensions(16, -2);
                                                        gtk_widget_set_can_focus( spin, false );
                                                }
                                                {
@@ -868,7 +870,7 @@ ui::Window PatchInspector::BuildDialog(){
                                                        auto spin = ui::SpinButton( adj, 1, 0 );
                                                        spin.show();
                             table.attach(spin, {1, 2, 4, 5}, {0, 0});
-                                                       spin.dimensions(10, -1);
+                                                       spin.dimensions(16, -2);
                                                        gtk_widget_set_can_focus( spin, false );
                                                }
                                        }
index bd9d378beee07b6bda85c8b517278827648fb7cc..5dc313b4195ff978b509ddd8019a6d83c4dbb4b7 100644 (file)
@@ -49,7 +49,7 @@
 
 PatchCreator* g_patchCreator = 0;
 
-void Scene_PatchConstructPrefab( scene::Graph& graph, const AABB aabb, const char* shader, EPatchPrefab eType, int axis, std::size_t width = 3, std::size_t height = 3 ){
+void Scene_PatchConstructPrefab( scene::Graph& graph, const AABB aabb, const char* shader, EPatchPrefab eType, int axis, std::size_t width = 3, std::size_t height = 3, bool redisperse = false ){
        Select_Delete();
        GlobalSelectionSystem().setSelectedAll( false );
 
@@ -60,6 +60,10 @@ void Scene_PatchConstructPrefab( scene::Graph& graph, const AABB aabb, const cha
        patch->SetShader( shader );
 
        patch->ConstructPrefab( aabb, eType, axis, width, height );
+       if( redisperse ){
+               patch->Redisperse( COL );
+               patch->Redisperse( ROW );
+       }
        patch->controlPointsChanged();
 
        {
@@ -145,6 +149,7 @@ void Patch_makeCaps( Patch& patch, scene::Instance& instance, EPatchCap type, co
        }
 }
 
+
 typedef std::vector<scene::Instance*> InstanceVector;
 
 enum ECapDialog {
@@ -195,6 +200,110 @@ void Scene_PatchDoCap_Selected( scene::Graph& graph, const char* shader ){
        }
 }
 
+void Patch_deform( Patch& patch, scene::Instance& instance, const int deform, const int axis ){
+       patch.undoSave();
+
+       for ( PatchControlIter i = patch.begin(); i != patch.end(); ++i ){
+               PatchControl& control = *i;
+               int randomNumber = int( deform * ( float( std::rand() ) / float( RAND_MAX ) ) );
+               control.m_vertex[ axis ] += randomNumber;
+       }
+
+       patch.controlPointsChanged();
+}
+
+void Scene_PatchDeform( scene::Graph& graph, const int deform, const int axis )
+{
+       InstanceVector instances;
+       Scene_forEachVisibleSelectedPatchInstance([&](PatchInstance &patch) {
+                       instances.push_back(&patch);
+       });
+       for ( InstanceVector::const_iterator i = instances.begin(); i != instances.end(); ++i )
+       {
+               Patch_deform( *Node_getPatch( ( *i )->path().top() ), *( *i ), deform, axis );
+       }
+
+}
+
+void Patch_thicken( Patch& patch, scene::Instance& instance, const float thickness, bool seams, const int axis ){
+
+               // Create a new patch node
+               NodeSmartReference node( g_patchCreator->createPatch() );
+               // Insert the node into worldspawn
+               Node_getTraversable( Map_FindOrInsertWorldspawn( g_map ) )->insert( node );
+
+               // Retrieve the contained patch from the node
+               Patch* targetPatch = Node_getPatch( node );
+
+               // Create the opposite patch with the given thickness = distance
+               bool no12 = true;
+               bool no34 = true;
+               targetPatch->createThickenedOpposite( patch, thickness, axis, no12, no34 );
+
+               // Now select the newly created patches
+               {
+                       scene::Path patchpath( makeReference( GlobalSceneGraph().root() ) );
+                       patchpath.push( makeReference( *Map_GetWorldspawn( g_map ) ) );
+                       patchpath.push( makeReference( node.get() ) );
+                       Instance_getSelectable( *GlobalSceneGraph().find( patchpath ) )->setSelected( true );
+               }
+
+               if( seams && thickness != 0.0f){
+                       int i = 0;
+                       if ( no12 ){
+                               i = 2;
+                       }
+                       int iend = 4;
+                       if ( no34 ){
+                               iend = 2;
+                       }
+                       // Now create the four walls
+                       for ( ; i < iend; i++ ){
+                               // Allocate new patch
+                               NodeSmartReference node = NodeSmartReference( g_patchCreator->createPatch() );
+                               // Insert each node into worldspawn
+                               Node_getTraversable( Map_FindOrInsertWorldspawn( g_map ) )->insert( node );
+
+                               // Retrieve the contained patch from the node
+                               Patch* wallPatch = Node_getPatch( node );
+
+                               // Create the wall patch by passing i as wallIndex
+                               wallPatch->createThickenedWall( patch, *targetPatch, i );
+
+                               if( ( wallPatch->localAABB().extents[0] <= 0.00005 && wallPatch->localAABB().extents[1] <= 0.00005 ) ||
+                                       ( wallPatch->localAABB().extents[1] <= 0.00005 && wallPatch->localAABB().extents[2] <= 0.00005 ) ||
+                                       ( wallPatch->localAABB().extents[0] <= 0.00005 && wallPatch->localAABB().extents[2] <= 0.00005 ) ){
+                                       //globalOutputStream() << "Thicken: Discarding degenerate patch.\n";
+                                       Node_getTraversable( Map_FindOrInsertWorldspawn( g_map ) )->erase( node );
+                               }
+                               else
+                               // Now select the newly created patches
+                               {
+                                       scene::Path patchpath( makeReference( GlobalSceneGraph().root() ) );
+                                       patchpath.push( makeReference( *Map_GetWorldspawn(g_map) ) );
+                                       patchpath.push( makeReference( node.get() ) );
+                                       Instance_getSelectable( *GlobalSceneGraph().find( patchpath ) )->setSelected( true );
+                               }
+                       }
+               }
+
+               // Invert the target patch so that it faces the opposite direction
+               targetPatch->InvertMatrix();
+}
+
+void Scene_PatchThicken( scene::Graph& graph, const int thickness, bool seams, const int axis )
+{
+       InstanceVector instances;
+       Scene_forEachVisibleSelectedPatchInstance([&](PatchInstance &patch) {
+               instances.push_back(&patch);
+       });
+       for ( InstanceVector::const_iterator i = instances.begin(); i != instances.end(); ++i )
+       {
+               Patch_thicken( *Node_getPatch( ( *i )->path().top() ), *( *i ), thickness, seams, axis );
+       }
+
+}
+
 Patch* Scene_GetUltimateSelectedVisiblePatch(){
        if ( GlobalSelectionSystem().countSelected() != 0 ) {
                scene::Node& node = GlobalSelectionSystem().ultimateSelected().path().top();
@@ -272,6 +381,20 @@ void Scene_PatchGetShader_Selected( scene::Graph& graph, CopiedString& name ){
        }
 }
 
+class PatchSelectByShader
+{
+const char* m_name;
+public:
+inline PatchSelectByShader( const char* name )
+       : m_name( name ){
+}
+void operator()( PatchInstance& patch ) const {
+       if ( shader_equal( patch.getPatch().GetShader(), m_name ) ) {
+               patch.setSelected( true );
+       }
+}
+};
+
 void Scene_PatchSelectByShader( scene::Graph& graph, const char* name ){
        Scene_forEachVisiblePatchInstance([&](PatchInstance &patch) {
                if (shader_equal(patch.getPatch().GetShader(), name)) {
@@ -281,20 +404,42 @@ void Scene_PatchSelectByShader( scene::Graph& graph, const char* name ){
 }
 
 
+class PatchFindReplaceShader
+{
+const char* m_find;
+const char* m_replace;
+public:
+PatchFindReplaceShader( const char* find, const char* replace ) : m_find( find ), m_replace( replace ){
+}
+void operator()( Patch& patch ) const {
+       if ( shader_equal( patch.GetShader(), m_find ) ) {
+               patch.SetShader( m_replace );
+       }
+}
+};
+
+namespace{
+bool DoingSearch( const char *repl ){
+       return ( repl == NULL || ( strcmp( "textures/", repl ) == 0 ) );
+}
+}
 void Scene_PatchFindReplaceShader( scene::Graph& graph, const char* find, const char* replace ){
-       Scene_forEachVisiblePatch([&](Patch &patch) {
-               if (shader_equal(patch.GetShader(), find)) {
-                       patch.SetShader(replace);
-               }
-       });
+       if( DoingSearch( replace ) ){
+               Scene_forEachVisiblePatchInstance( PatchSelectByShader( find ) );
+       }
+       else{
+               Scene_forEachVisiblePatch( PatchFindReplaceShader( find, replace ) );
+       }
 }
 
 void Scene_PatchFindReplaceShader_Selected( scene::Graph& graph, const char* find, const char* replace ){
-       Scene_forEachVisibleSelectedPatch([&](Patch &patch) {
-               if (shader_equal(patch.GetShader(), find)) {
-                       patch.SetShader(replace);
-               }
-       });
+       if( DoingSearch( replace ) ){
+               //do nothing, because alternative is replacing to notex
+               //perhaps deselect ones with not matching shaders here?
+       }
+       else{
+               Scene_forEachVisibleSelectedPatch( PatchFindReplaceShader( find, replace ) );
+       }
 }
 
 
@@ -399,28 +544,28 @@ void Patch_Plane(){
        DoNewPatchDlg( ePlane, 3, 3, 3, 3, 0, 0 );
 }
 
-void Patch_InsertInsertColumn(){
-       UndoableCommand undo( "patchInsertColumns" );
+void Patch_InsertFirstColumn(){
+       UndoableCommand undo( "patchInsertFirstColumns" );
 
-       Scene_PatchInsertRemove_Selected( GlobalSceneGraph(), true, true, false );
+       Scene_PatchInsertRemove_Selected( GlobalSceneGraph(), true, true, true );
 }
 
-void Patch_InsertAddColumn(){
-       UndoableCommand undo( "patchAddColumns" );
+void Patch_InsertLastColumn(){
+       UndoableCommand undo( "patchInsertLastColumns" );
 
-       Scene_PatchInsertRemove_Selected( GlobalSceneGraph(), true, true, true );
+       Scene_PatchInsertRemove_Selected( GlobalSceneGraph(), true, true, false );
 }
 
-void Patch_InsertInsertRow(){
-       UndoableCommand undo( "patchInsertRows" );
+void Patch_InsertFirstRow(){
+       UndoableCommand undo( "patchInsertFirstRows" );
 
-       Scene_PatchInsertRemove_Selected( GlobalSceneGraph(), true, false, false );
+       Scene_PatchInsertRemove_Selected( GlobalSceneGraph(), true, false, true );
 }
 
-void Patch_InsertAddRow(){
-       UndoableCommand undo( "patchAddRows" );
+void Patch_InsertLastRow(){
+       UndoableCommand undo( "patchInsertLastRows" );
 
-       Scene_PatchInsertRemove_Selected( GlobalSceneGraph(), true, false, true );
+       Scene_PatchInsertRemove_Selected( GlobalSceneGraph(), true, false, false );
 }
 
 void Patch_DeleteFirstColumn(){
@@ -543,6 +688,23 @@ void Patch_FitTexture(){
        Scene_PatchTileTexture_Selected( GlobalSceneGraph(), 1, 1 );
 }
 
+void DoPatchDeformDlg();
+
+void Patch_Deform(){
+       UndoableCommand undo( "patchDeform" );
+
+       DoPatchDeformDlg();
+}
+
+void DoPatchThickenDlg();
+
+void Patch_Thicken(){
+       UndoableCommand undo( "patchThicken" );
+
+       DoPatchThickenDlg();
+}
+
+
 #include "ifilter.h"
 
 
@@ -578,13 +740,15 @@ bool filter( const Patch& patch ) const {
 
 
 filter_patch_all g_filter_patch_all;
-filter_patch_shader g_filter_patch_clip( "textures/common/clip" );
+filter_patch_flags g_filter_patch_clip( QER_CLIP );
+filter_patch_shader g_filter_patch_commonclip( "textures/common/clip" );
 filter_patch_shader g_filter_patch_weapclip( "textures/common/weapclip" );
-filter_patch_flags g_filter_patch_translucent( QER_TRANS );
+filter_patch_flags g_filter_patch_translucent( QER_TRANS | QER_ALPHATEST );
 
 void PatchFilters_construct(){
        add_patch_filter( g_filter_patch_all, EXCLUDE_CURVES );
        add_patch_filter( g_filter_patch_clip, EXCLUDE_CLIP );
+       add_patch_filter( g_filter_patch_commonclip, EXCLUDE_CLIP );
        add_patch_filter( g_filter_patch_weapclip, EXCLUDE_CLIP );
        add_patch_filter( g_filter_patch_translucent, EXCLUDE_TRANSLUCENT );
 }
@@ -618,37 +782,41 @@ void Patch_registerCommands(){
        GlobalCommands_insert( "InvertCurveTextureY", makeCallbackF(Patch_FlipTextureY), Accelerator( 'I', (GdkModifierType)GDK_SHIFT_MASK ) );
        GlobalCommands_insert( "NaturalizePatch", makeCallbackF(Patch_NaturalTexture), Accelerator( 'N', (GdkModifierType)GDK_CONTROL_MASK ) );
        GlobalCommands_insert( "PatchCylinder", makeCallbackF(Patch_Cylinder) );
-       GlobalCommands_insert( "PatchDenseCylinder", makeCallbackF(Patch_DenseCylinder) );
-       GlobalCommands_insert( "PatchVeryDenseCylinder", makeCallbackF(Patch_VeryDenseCylinder) );
+//     GlobalCommands_insert( "PatchDenseCylinder", makeCallbackF(Patch_DenseCylinder) );
+//     GlobalCommands_insert( "PatchVeryDenseCylinder", makeCallbackF(Patch_VeryDenseCylinder) );
        GlobalCommands_insert( "PatchSquareCylinder", makeCallbackF(Patch_SquareCylinder) );
        GlobalCommands_insert( "PatchXactCylinder", makeCallbackF(Patch_XactCylinder) );
        GlobalCommands_insert( "PatchXactSphere", makeCallbackF(Patch_XactSphere) );
        GlobalCommands_insert( "PatchXactCone", makeCallbackF(Patch_XactCone) );
        GlobalCommands_insert( "PatchEndCap", makeCallbackF(Patch_Endcap) );
        GlobalCommands_insert( "PatchBevel", makeCallbackF(Patch_Bevel) );
-       GlobalCommands_insert( "PatchSquareBevel", makeCallbackF(Patch_SquareBevel) );
-       GlobalCommands_insert( "PatchSquareEndcap", makeCallbackF(Patch_SquareEndcap) );
+//     GlobalCommands_insert( "PatchSquareBevel", makeCallbackF(Patch_SquareBevel) );
+//     GlobalCommands_insert( "PatchSquareEndcap", makeCallbackF(Patch_SquareEndcap) );
        GlobalCommands_insert( "PatchCone", makeCallbackF(Patch_Cone) );
        GlobalCommands_insert( "PatchSphere", makeCallbackF(Patch_Sphere) );
        GlobalCommands_insert( "SimplePatchMesh", makeCallbackF(Patch_Plane), Accelerator( 'P', (GdkModifierType)GDK_SHIFT_MASK ) );
-       GlobalCommands_insert( "PatchInsertInsertColumn", makeCallbackF(Patch_InsertInsertColumn), Accelerator( GDK_KEY_KP_Add, (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
-       GlobalCommands_insert( "PatchInsertAddColumn", makeCallbackF(Patch_InsertAddColumn) );
-       GlobalCommands_insert( "PatchInsertInsertRow", makeCallbackF(Patch_InsertInsertRow), Accelerator( GDK_KEY_KP_Add, (GdkModifierType)GDK_CONTROL_MASK ) );
-       GlobalCommands_insert( "PatchInsertAddRow", makeCallbackF(Patch_InsertAddRow) );
+       GlobalCommands_insert( "PatchInsertFirstColumn", makeCallbackF(Patch_InsertFirstColumn), Accelerator( GDK_KEY_KP_Add, (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
+       GlobalCommands_insert( "PatchInsertLastColumn", makeCallbackF(Patch_InsertLastColumn) );
+       GlobalCommands_insert( "PatchInsertFirstRow", makeCallbackF(Patch_InsertFirstRow), Accelerator( GDK_KEY_KP_Add, (GdkModifierType)GDK_CONTROL_MASK ) );
+       GlobalCommands_insert( "PatchInsertLastRow", makeCallbackF(Patch_InsertLastRow) );
        GlobalCommands_insert( "PatchDeleteFirstColumn", makeCallbackF(Patch_DeleteFirstColumn) );
        GlobalCommands_insert( "PatchDeleteLastColumn", makeCallbackF(Patch_DeleteLastColumn), Accelerator( GDK_KEY_KP_Subtract, (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
-       GlobalCommands_insert( "PatchDeleteFirstRow", makeCallbackF(Patch_DeleteFirstRow), Accelerator( GDK_KEY_KP_Subtract, (GdkModifierType)GDK_CONTROL_MASK ) );
+       GlobalCommands_insert( "PatchDeleteFirstRow", makeCallbackF(Patch_DeleteFirstRow) );
        GlobalCommands_insert( "PatchDeleteLastRow", makeCallbackF(Patch_DeleteLastRow) );
        GlobalCommands_insert( "InvertCurve", makeCallbackF(Patch_Invert), Accelerator( 'I', (GdkModifierType)GDK_CONTROL_MASK ) );
-       GlobalCommands_insert( "RedisperseRows", makeCallbackF(Patch_RedisperseRows), Accelerator( 'E', (GdkModifierType)GDK_CONTROL_MASK ) );
-       GlobalCommands_insert( "RedisperseCols", makeCallbackF(Patch_RedisperseCols), Accelerator( 'E', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
+       // GlobalCommands_insert( "RedisperseRows", makeCallbackF(Patch_RedisperseRows), Accelerator( 'E', (GdkModifierType)GDK_CONTROL_MASK ) );
+       GlobalCommands_insert( "RedisperseRows", makeCallbackF(Patch_RedisperseRows) );
+       // GlobalCommands_insert( "RedisperseCols", makeCallbackF(Patch_RedisperseCols), Accelerator( 'E', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
+       GlobalCommands_insert( "RedisperseCols", makeCallbackF(Patch_RedisperseCols) );
        GlobalCommands_insert( "SmoothRows", makeCallbackF(Patch_SmoothRows), Accelerator( 'W', (GdkModifierType)GDK_CONTROL_MASK ) );
        GlobalCommands_insert( "SmoothCols", makeCallbackF(Patch_SmoothCols), Accelerator( 'W', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
        GlobalCommands_insert( "MatrixTranspose", makeCallbackF(Patch_Transpose), Accelerator( 'M', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
        GlobalCommands_insert( "CapCurrentCurve", makeCallbackF(Patch_Cap), Accelerator( 'C', (GdkModifierType)GDK_SHIFT_MASK ) );
-       GlobalCommands_insert( "CycleCapTexturePatch", makeCallbackF(Patch_CycleProjection), Accelerator( 'N', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
-       GlobalCommands_insert( "MakeOverlayPatch", makeCallbackF(Patch_OverlayOn), Accelerator( 'Y' ) );
-       GlobalCommands_insert( "ClearPatchOverlays", makeCallbackF(Patch_OverlayOff), Accelerator( 'L', (GdkModifierType)GDK_CONTROL_MASK ) );
+       GlobalCommands_insert( "CycleCapTexturePatch", makeCallbackF(Patch_CycleProjection), Accelerator( 'N', (GdkModifierType)GDK_SHIFT_MASK ) );
+//     GlobalCommands_insert( "MakeOverlayPatch", makeCallbackF(Patch_OverlayOn), Accelerator( 'Y' ) );
+//     GlobalCommands_insert( "ClearPatchOverlays", makeCallbackF(Patch_OverlayOff), Accelerator( 'L', (GdkModifierType)GDK_CONTROL_MASK ) );
+       GlobalCommands_insert( "PatchDeform", makeCallbackF(Patch_Deform) );
+       GlobalCommands_insert( "PatchThicken", makeCallbackF(Patch_Thicken), Accelerator( 'T', (GdkModifierType)GDK_CONTROL_MASK ) );
 }
 
 void Patch_constructToolbar( ui::Toolbar toolbar ){
@@ -656,58 +824,49 @@ void Patch_constructToolbar( ui::Toolbar toolbar ){
 }
 
 void Patch_constructMenu( ui::Menu menu ){
-       create_menu_item_with_mnemonic( menu, "Cylinder", "PatchCylinder" );
-       {
-               auto menu_in_menu = create_sub_menu_with_mnemonic( menu, "More Cylinders" );
-               if ( g_Layout_enableDetachableMenus.m_value ) {
-                       menu_tearoff( menu_in_menu );
-               }
-               create_menu_item_with_mnemonic( menu_in_menu, "Dense Cylinder", "PatchDenseCylinder" );
-               create_menu_item_with_mnemonic( menu_in_menu, "Very Dense Cylinder", "PatchVeryDenseCylinder" );
-               create_menu_item_with_mnemonic( menu_in_menu, "Square Cylinder", "PatchSquareCylinder" );
-               create_menu_item_with_mnemonic( menu_in_menu, "Exact Cylinder...", "PatchXactCylinder" );
-       }
-       menu_separator( menu );
-       create_menu_item_with_mnemonic( menu, "End cap", "PatchEndCap" );
+       create_menu_item_with_mnemonic( menu, "Simple Patch Mesh...", "SimplePatchMesh" );
        create_menu_item_with_mnemonic( menu, "Bevel", "PatchBevel" );
-       {
-               auto menu_in_menu = create_sub_menu_with_mnemonic( menu, "More End caps, Bevels" );
-               if ( g_Layout_enableDetachableMenus.m_value ) {
-                       menu_tearoff( menu_in_menu );
-               }
-               create_menu_item_with_mnemonic( menu_in_menu, "Square Endcap", "PatchSquareBevel" );
-               create_menu_item_with_mnemonic( menu_in_menu, "Square Bevel", "PatchSquareEndcap" );
-       }
-       menu_separator( menu );
-       create_menu_item_with_mnemonic( menu, "Cone", "PatchCone" );
+       create_menu_item_with_mnemonic( menu, "End cap", "PatchEndCap" );
+       create_menu_item_with_mnemonic( menu, "Cylinder (9x3)", "PatchCylinder" );
+       create_menu_item_with_mnemonic( menu, "Square Cylinder (9x3)", "PatchSquareCylinder" );
+       create_menu_item_with_mnemonic( menu, "Exact Cylinder...", "PatchXactCylinder" );
+       create_menu_item_with_mnemonic( menu, "Cone (9x3)", "PatchCone" );
        create_menu_item_with_mnemonic( menu, "Exact Cone...", "PatchXactCone" );
-       menu_separator( menu );
-       create_menu_item_with_mnemonic( menu, "Sphere", "PatchSphere" );
+       create_menu_item_with_mnemonic( menu, "Sphere (9x5)", "PatchSphere" );
        create_menu_item_with_mnemonic( menu, "Exact Sphere...", "PatchXactSphere" );
+//     {
+//             ui::Menu menu_in_menu = create_sub_menu_with_mnemonic( menu, "More Cylinders" );
+//             if ( g_Layout_enableDetachableMenus.m_value ) {
+//                     menu_tearoff( menu_in_menu );
+//             }
+//             create_menu_item_with_mnemonic( menu_in_menu, "Dense Cylinder", "PatchDenseCylinder" );
+//             create_menu_item_with_mnemonic( menu_in_menu, "Very Dense Cylinder", "PatchVeryDenseCylinder" );
+//             create_menu_item_with_mnemonic( menu_in_menu, "Square Cylinder", "PatchSquareCylinder" );
+//     }
+//     {
+//             //not implemented
+//             create_menu_item_with_mnemonic( menu, "Square Endcap", "PatchSquareBevel" );
+//             create_menu_item_with_mnemonic( menu, "Square Bevel", "PatchSquareEndcap" );
+//     }
        menu_separator( menu );
-       create_menu_item_with_mnemonic( menu, "Simple Patch Mesh...", "SimplePatchMesh" );
+       create_menu_item_with_mnemonic( menu, "Cap Selection", "CapCurrentCurve" );
        menu_separator( menu );
        {
-               auto menu_in_menu = create_sub_menu_with_mnemonic( menu, "Insert" );
+               auto menu_in_menu = create_sub_menu_with_mnemonic( menu, "Insert/Delete" );
                if ( g_Layout_enableDetachableMenus.m_value ) {
                        menu_tearoff( menu_in_menu );
                }
-               create_menu_item_with_mnemonic( menu_in_menu, "Insert (2) Columns", "PatchInsertInsertColumn" );
-               create_menu_item_with_mnemonic( menu_in_menu, "Add (2) Columns", "PatchInsertAddColumn" );
+               create_menu_item_with_mnemonic( menu_in_menu, "Insert (2) First Columns", "PatchInsertFirstColumn" );
+               create_menu_item_with_mnemonic( menu_in_menu, "Insert (2) Last Columns", "PatchInsertLastColumn" );
                menu_separator( menu_in_menu );
-               create_menu_item_with_mnemonic( menu_in_menu, "Insert (2) Rows", "PatchInsertInsertRow" );
-               create_menu_item_with_mnemonic( menu_in_menu, "Add (2) Rows", "PatchInsertAddRow" );
-       }
-       {
-               auto menu_in_menu = create_sub_menu_with_mnemonic( menu, "Delete" );
-               if ( g_Layout_enableDetachableMenus.m_value ) {
-                       menu_tearoff( menu_in_menu );
-               }
-               create_menu_item_with_mnemonic( menu_in_menu, "First (2) Columns", "PatchDeleteFirstColumn" );
-               create_menu_item_with_mnemonic( menu_in_menu, "Last (2) Columns", "PatchDeleteLastColumn" );
+               create_menu_item_with_mnemonic( menu_in_menu, "Insert (2) First Rows", "PatchInsertFirstRow" );
+               create_menu_item_with_mnemonic( menu_in_menu, "Insert (2) Last Rows", "PatchInsertLastRow" );
+               menu_separator( menu_in_menu );
+               create_menu_item_with_mnemonic( menu_in_menu, "Del First (2) Columns", "PatchDeleteFirstColumn" );
+               create_menu_item_with_mnemonic( menu_in_menu, "Del Last (2) Columns", "PatchDeleteLastColumn" );
                menu_separator( menu_in_menu );
-               create_menu_item_with_mnemonic( menu_in_menu, "First (2) Rows", "PatchDeleteFirstRow" );
-               create_menu_item_with_mnemonic( menu_in_menu, "Last (2) Rows", "PatchDeleteLastRow" );
+               create_menu_item_with_mnemonic( menu_in_menu, "Del First (2) Rows", "PatchDeleteFirstRow" );
+               create_menu_item_with_mnemonic( menu_in_menu, "Del Last (2) Rows", "PatchDeleteLastRow" );
        }
        menu_separator( menu );
        {
@@ -716,32 +875,40 @@ void Patch_constructMenu( ui::Menu menu ){
                        menu_tearoff( menu_in_menu );
                }
                create_menu_item_with_mnemonic( menu_in_menu, "Invert", "InvertCurve" );
-               auto menu_3 = create_sub_menu_with_mnemonic( menu_in_menu, "Re-disperse" );
-               if ( g_Layout_enableDetachableMenus.m_value ) {
-                       menu_tearoff( menu_3 );
-               }
-               create_menu_item_with_mnemonic( menu_3, "Rows", "RedisperseRows" );
-               create_menu_item_with_mnemonic( menu_3, "Columns", "RedisperseCols" );
-               auto menu_4 = create_sub_menu_with_mnemonic( menu_in_menu, "Smooth" );
-               if ( g_Layout_enableDetachableMenus.m_value ) {
-                       menu_tearoff( menu_4 );
-               }
-               create_menu_item_with_mnemonic( menu_4, "Rows", "SmoothRows" );
-               create_menu_item_with_mnemonic( menu_4, "Columns", "SmoothCols" );
                create_menu_item_with_mnemonic( menu_in_menu, "Transpose", "MatrixTranspose" );
+
+               menu_separator( menu_in_menu );
+               create_menu_item_with_mnemonic( menu_in_menu, "Re-disperse Rows", "RedisperseRows" );
+               create_menu_item_with_mnemonic( menu_in_menu, "Re-disperse Columns", "RedisperseCols" );
+
+               menu_separator( menu_in_menu );
+               create_menu_item_with_mnemonic( menu_in_menu, "Smooth Rows", "SmoothRows" );
+               create_menu_item_with_mnemonic( menu_in_menu, "Smooth Columns", "SmoothCols" );
        }
        menu_separator( menu );
-       create_menu_item_with_mnemonic( menu, "Cap Selection", "CapCurrentCurve" );
-       create_menu_item_with_mnemonic( menu, "Cycle Cap Texture", "CycleCapTexturePatch" );
-       menu_separator( menu );
        {
-               auto menu_in_menu = create_sub_menu_with_mnemonic( menu, "Overlay" );
+               auto menu_in_menu = create_sub_menu_with_mnemonic( menu, "Texture" );
                if ( g_Layout_enableDetachableMenus.m_value ) {
                        menu_tearoff( menu_in_menu );
                }
-               create_menu_item_with_mnemonic( menu_in_menu, "Set", "MakeOverlayPatch" );
-               create_menu_item_with_mnemonic( menu_in_menu, "Clear", "ClearPatchOverlays" );
+               create_menu_item_with_mnemonic( menu_in_menu, "Cycle Projection", "CycleCapTexturePatch" );
+               create_menu_item_with_mnemonic( menu_in_menu, "Naturalize", "NaturalizePatch" );
+               create_menu_item_with_mnemonic( menu_in_menu, "Invert X", "InvertCurveTextureX" );
+               create_menu_item_with_mnemonic( menu_in_menu, "Invert Y", "InvertCurveTextureY" );
+
        }
+//     menu_separator( menu );
+//     {
+//             auto menu_in_menu = create_sub_menu_with_mnemonic( menu, "Overlay" );
+//             if ( g_Layout_enableDetachableMenus.m_value ) {
+//                     menu_tearoff( menu_in_menu );
+//             }
+//             create_menu_item_with_mnemonic( menu_in_menu, "Set", "MakeOverlayPatch" );
+//             create_menu_item_with_mnemonic( menu_in_menu, "Clear", "ClearPatchOverlays" );
+//     }
+       menu_separator( menu );
+       create_menu_item_with_mnemonic( menu, "Deform...", "PatchDeform" );
+       create_menu_item_with_mnemonic( menu, "Thicken...", "PatchThicken" );
 }
 
 
@@ -750,6 +917,7 @@ void Patch_constructMenu( ui::Menu menu ){
 
 void DoNewPatchDlg( EPatchPrefab prefab, int minrows, int mincols, int defrows, int defcols, int maxrows, int maxcols ){
        ModalDialog dialog;
+       GtkWidget* redisperseCheckBox;
 
        ui::Window window = MainFrame_getWindow().create_dialog_window("Patch density", G_CALLBACK(dialog_delete_callback ), &dialog );
 
@@ -761,7 +929,7 @@ void DoNewPatchDlg( EPatchPrefab prefab, int minrows, int mincols, int defrows,
                auto hbox = create_dialog_hbox( 4, 4 );
                window.add(hbox);
                {
-                       auto table = create_dialog_table( 2, 2, 4, 4 );
+                       auto table = create_dialog_table( 3, 2, 4, 4 );
                        hbox.pack_start( table, TRUE, TRUE, 0 );
                        {
                                auto label = ui::Label( "Width:" );
@@ -820,6 +988,18 @@ void DoNewPatchDlg( EPatchPrefab prefab, int minrows, int mincols, int defrows,
                                combo.show();
                 table.attach(combo, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
                        }
+
+                       if( prefab != ePlane ){
+                               GtkWidget* _redisperseCheckBox = gtk_check_button_new_with_label( "Square" );
+                               gtk_widget_set_tooltip_text( _redisperseCheckBox, "Redisperse columns & rows" );
+                               gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( _redisperseCheckBox ), FALSE );
+                               gtk_widget_show( _redisperseCheckBox );
+                               gtk_table_attach( table, _redisperseCheckBox, 0, 2, 2, 3,
+                                                               (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
+                                                               (GtkAttachOptions) ( 0 ), 0, 0 );
+                               redisperseCheckBox = _redisperseCheckBox;
+                       }
+
                }
 
                {
@@ -847,14 +1027,122 @@ void DoNewPatchDlg( EPatchPrefab prefab, int minrows, int mincols, int defrows,
        if ( modal_dialog_show( window, dialog ) == eIDOK ) {
                int w = gtk_combo_box_get_active( width ) * 2 + mincols;
                int h = gtk_combo_box_get_active( height ) * 2 + minrows;
-
-               Scene_PatchConstructPrefab( GlobalSceneGraph(), PatchCreator_getBounds(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ), prefab, GlobalXYWnd_getCurrentViewType(), w, h );
+               bool redisperse = false;
+               if( prefab != ePlane ){
+                       redisperse = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( redisperseCheckBox ) ) ? true : false;
+               }
+               Scene_PatchConstructPrefab( GlobalSceneGraph(), PatchCreator_getBounds(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ), prefab, GlobalXYWnd_getCurrentViewType(), w, h, redisperse );
        }
 
        window.destroy();
 }
 
 
+void DoPatchDeformDlg(){
+       ModalDialog dialog;
+       GtkWidget* deformW;
+
+    GtkWidget* rndY;
+       GtkWidget* rndX;
+
+       ui::Window window = create_dialog_window( MainFrame_getWindow(), "Patch deform", G_CALLBACK( dialog_delete_callback ), &dialog );
+
+       GtkAccelGroup* accel = gtk_accel_group_new();
+       gtk_window_add_accel_group( window, accel );
+
+       {
+               auto hbox = create_dialog_hbox( 4, 4 );
+               gtk_container_add( GTK_CONTAINER( window ), GTK_WIDGET( hbox ) );
+               {
+                       auto table = create_dialog_table( 2, 2, 4, 4 );
+                       gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
+                       {
+                               GtkLabel* label = GTK_LABEL( gtk_label_new( "Max deform:" ) );
+                               gtk_widget_show( GTK_WIDGET( label ) );
+                               gtk_table_attach( table, GTK_WIDGET( label ), 0, 1, 0, 1,
+                                                                 (GtkAttachOptions) ( GTK_FILL ),
+                                                                 (GtkAttachOptions) ( 0 ), 0, 0 );
+                               gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
+                       }
+//                     {
+//                             GtkWidget* entry = gtk_entry_new();
+//                             gtk_entry_set_text( GTK_ENTRY( entry ), "64" );
+//                             gtk_widget_show( entry );
+//                             gtk_table_attach( table, entry, 1, 2, 0, 1,
+//                                                               (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
+//                                                               (GtkAttachOptions) ( 0 ), 0, 0 );
+//
+//                             deformW = entry;
+//                     }
+                       {
+                               GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 64, -9999, 9999, 1, 10, 0 ) );
+                               //GtkSpinButton* spin = GTK_SPIN_BUTTON( gtk_spin_button_new( adj, 1, 0 ) );
+                               GtkWidget* spin = gtk_spin_button_new( adj, 1, 0 );
+                               gtk_widget_show( spin );
+                               gtk_table_attach( table, spin, 1, 2, 0, 1,
+                                                                 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
+                                                                 (GtkAttachOptions) ( 0 ), 0, 0 );
+                               gtk_widget_set_size_request( spin, 64, -1 );
+                               gtk_spin_button_set_numeric( GTK_SPIN_BUTTON( spin ), TRUE );
+
+                               deformW = spin;
+                       }
+                       {
+                               // Create the radio button group for choosing the axis
+                               GtkWidget* _rndZ = gtk_radio_button_new_with_label_from_widget( NULL, "Z" );
+                               GtkWidget* _rndY = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(_rndZ), "Y" );
+                               GtkWidget* _rndX = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(_rndZ), "X" );
+                               gtk_widget_show( _rndZ );
+                               gtk_widget_show( _rndY );
+                               gtk_widget_show( _rndX );
+
+
+                               GtkHBox* _hbox = create_dialog_hbox( 4, 4 );
+                               gtk_table_attach( table, GTK_WIDGET( _hbox ), 0, 2, 1, 2,
+                                                                 (GtkAttachOptions) ( GTK_FILL ),
+                                                                 (GtkAttachOptions) ( 0 ), 0, 0 );
+                               gtk_box_pack_start( GTK_BOX( _hbox ), GTK_WIDGET( _rndX ), TRUE, TRUE, 0 );
+                               gtk_box_pack_start( GTK_BOX( _hbox ), GTK_WIDGET( _rndY ), TRUE, TRUE, 0 );
+                               gtk_box_pack_start( GTK_BOX( _hbox ), GTK_WIDGET( _rndZ ), TRUE, TRUE, 0 );
+
+                               rndX = _rndX;
+                               rndY = _rndY;
+                       }
+               }
+               {
+                       auto vbox = create_dialog_vbox( 4 );
+                       hbox.pack_start( vbox, FALSE, FALSE, 0 );
+                       {
+                               auto button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &dialog );
+                               vbox.pack_start( button, FALSE, FALSE, 0 );
+                               widget_make_default( button );
+                               gtk_widget_grab_focus( button  );
+                               gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
+                       }
+                       {
+                               auto button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &dialog );
+                               vbox.pack_start( button, FALSE, FALSE, 0 );
+                               gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
+                       }
+               }
+       }
+
+       if ( modal_dialog_show( window, dialog ) == eIDOK ) {
+               //int deform = static_cast<int>( atoi( gtk_entry_get_text( GTK_ENTRY( deformW ) ) ) );
+               gtk_spin_button_update ( GTK_SPIN_BUTTON( deformW ) );
+               int deform = static_cast<int>( gtk_spin_button_get_value( GTK_SPIN_BUTTON( deformW ) ) );
+               int axis = 2; //Z
+               if( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( rndX ) ) ){
+                       axis = 0;
+               }
+               else if( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( rndY ) ) ){
+                       axis = 1;
+               }
+               Scene_PatchDeform( GlobalSceneGraph(), deform, axis );
+       }
+       gtk_widget_destroy( GTK_WIDGET( window ) );
+}
+
 
 
 EMessageBoxReturn DoCapDlg( ECapDialog* type ){
@@ -1001,3 +1289,137 @@ EMessageBoxReturn DoCapDlg( ECapDialog* type ){
 
        return ret;
 }
+
+
+void DoPatchThickenDlg(){
+       ModalDialog dialog;
+       GtkWidget* thicknessW;
+       GtkWidget* seamsW;
+       GtkWidget* radX;
+       GtkWidget* radY;
+       GtkWidget* radZ;
+
+       ui::Window window = create_dialog_window( MainFrame_getWindow(), "Patch thicken", G_CALLBACK( dialog_delete_callback ), &dialog );
+
+       GtkAccelGroup* accel = gtk_accel_group_new();
+       gtk_window_add_accel_group( window, accel );
+
+       {
+               auto hbox = create_dialog_hbox( 4, 4 );
+               gtk_container_add( GTK_CONTAINER( window ), GTK_WIDGET( hbox ) );
+               {
+                       auto table = create_dialog_table( 2, 4, 4, 4 );
+                       gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
+                       {
+                               GtkLabel* label = GTK_LABEL( gtk_label_new( "Thickness:" ) );
+                               gtk_widget_show( GTK_WIDGET( label ) );
+                               gtk_table_attach( table, GTK_WIDGET( label ), 0, 1, 0, 1,
+                                                                 (GtkAttachOptions) ( GTK_FILL ),
+                                                                 (GtkAttachOptions) ( 0 ), 0, 0 );
+                               gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
+                       }
+//                     {
+//                             GtkWidget* entry = gtk_entry_new();
+//                             gtk_entry_set_text( GTK_ENTRY( entry ), "16" );
+//                             gtk_widget_set_size_request( entry, 40, -1 );
+//                             gtk_widget_show( entry );
+//                             gtk_table_attach( table, entry, 1, 2, 0, 1,
+//                                                               (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
+//                                                               (GtkAttachOptions) ( 0 ), 0, 0 );
+//
+//                             thicknessW = entry;
+//                     }
+                       {
+                               GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( 16, -9999, 9999, 1, 10, 0 ) );
+                               GtkWidget* spin = gtk_spin_button_new( adj, 1, 0 );
+                               gtk_widget_show( spin );
+                               gtk_table_attach( table, spin, 1, 2, 0, 1,
+                                                                 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
+                                                                 (GtkAttachOptions) ( 0 ), 0, 0 );
+                               gtk_widget_set_size_request( spin, 48, -1 );
+                               gtk_spin_button_set_numeric( GTK_SPIN_BUTTON( spin ), TRUE );
+
+                               thicknessW = spin;
+                       }
+                       {
+                               // Create the "create seams" label
+                               GtkWidget* _seamsCheckBox = gtk_check_button_new_with_label( "Side walls" );
+                               gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( _seamsCheckBox ), TRUE );
+                               gtk_widget_show( _seamsCheckBox );
+                               gtk_table_attach( table, _seamsCheckBox, 2, 4, 0, 1,
+                                                                 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
+                                                                 (GtkAttachOptions) ( 0 ), 0, 0 );
+                               seamsW = _seamsCheckBox;
+
+                       }
+                       {
+                               // Create the radio button group for choosing the extrude axis
+                               GtkWidget* _radNormals = gtk_radio_button_new_with_label( NULL, "Normal" );
+                               GtkWidget* _radX = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(_radNormals), "X" );
+                               GtkWidget* _radY = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(_radNormals), "Y" );
+                               GtkWidget* _radZ = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(_radNormals), "Z" );
+                               gtk_widget_show( _radNormals );
+                               gtk_widget_show( _radX );
+                               gtk_widget_show( _radY );
+                               gtk_widget_show( _radZ );
+
+
+                               // Pack the buttons into the table
+                               gtk_table_attach( table, _radNormals, 0, 1, 1, 2,
+                                                                 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
+                                                                 (GtkAttachOptions) ( 0 ), 0, 0 );
+                               gtk_table_attach( table, _radX, 1, 2, 1, 2,
+                                                                 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
+                                                                 (GtkAttachOptions) ( 0 ), 0, 0 );
+                               gtk_table_attach( table, _radY, 2, 3, 1, 2,
+                                                                 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
+                                                                 (GtkAttachOptions) ( 0 ), 0, 0 );
+                               gtk_table_attach( table, _radZ, 3, 4, 1, 2,
+                                                                 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
+                                                                 (GtkAttachOptions) ( 0 ), 0, 0 );
+                               radX = _radX;
+                               radY = _radY;
+                               radZ = _radZ;
+                       }
+               }
+               {
+                       auto vbox = create_dialog_vbox( 4 );
+                       hbox.pack_start( vbox, FALSE, FALSE, 0 );
+                       {
+                               auto button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &dialog );
+                               vbox.pack_start( button, FALSE, FALSE, 0 );
+                               widget_make_default( button );
+                               gtk_widget_grab_focus( button  );
+                               gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
+                       }
+                       {
+                               auto button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &dialog );
+                               vbox.pack_start( button, FALSE, FALSE, 0 );
+                               gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
+                       }
+               }
+       }
+
+       if ( modal_dialog_show( window, dialog ) == eIDOK ) {
+               int axis = 3; // Extrude along normals
+               bool seams;
+               //float thickness = static_cast<float>( atoi( gtk_entry_get_text( GTK_ENTRY( thicknessW ) ) ) );
+               gtk_spin_button_update ( GTK_SPIN_BUTTON( thicknessW ) );
+               float thickness = static_cast<float>( gtk_spin_button_get_value( GTK_SPIN_BUTTON( thicknessW ) ) );
+               seams = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( seamsW )) ? true : false;
+
+               if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radX))) {
+                       axis = 0;
+               }
+               else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radY))) {
+                       axis = 1;
+               }
+               else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radZ))) {
+                       axis = 2;
+               }
+
+               Scene_PatchThicken( GlobalSceneGraph(), thickness, seams, axis );
+       }
+
+       gtk_widget_destroy( GTK_WIDGET( window ) );
+}
index 512a3f95c1132c05cd28f08f9c8d7930f3466e9a..773ceb965b9fb2a6f90e24879f150eb45f5414b0 100644 (file)
@@ -58,6 +58,13 @@ void Patch_FlipTextureX();
 void Patch_FlipTextureY();
 void Patch_AutoCapTexture();
 
+void Patch_NaturalTexture();
+void Patch_CapTexture();
+void Patch_ResetTexture();
+void Patch_FitTexture();
+void Patch_FlipTextureX();
+void Patch_FlipTextureY();
+
 class PatchCreator;
 extern PatchCreator* g_patchCreator;
 
index e06491d8f31f259690ec5f91025a4dc749e91880..d0745f4af9c3c381af27cca7c29273b8c47e4d07 100644 (file)
@@ -34,6 +34,7 @@
 #include "plugin.h"
 
 ui::Image new_plugin_image( const char* filename ){
+       // NetRadiantCustom look for AppPath (DataPath) plugins dir before GameToolsPath plugins dir.
        {
                StringOutputStream fullpath( 256 );
                fullpath << GameToolsPath_get() << g_pluginsDir << "bitmaps/" << filename;
@@ -62,6 +63,14 @@ void toolbar_insert( ui::Toolbar toolbar, const char* icon, const char* text, co
                toolbar.add(it);
                return;
        }
+       #define GARUX_DISABLE_SPACER_NOFOCUS
+       #ifndef GARUX_DISABLE_SPACER_NOFOCUS
+       else {
+               GTK_WIDGET_UNSET_FLAGS( widget, GTK_CAN_FOCUS );
+               GTK_WIDGET_UNSET_FLAGS( widget, GTK_CAN_DEFAULT );
+       }
+       #endif // GARUX_DISABLE_SPACER_NOFOCUS
+
        if (type == IToolbarButton::eButton) {
                auto button = ui::ToolButton::from(gtk_tool_button_new(new_plugin_image(icon), text));
                gtk_widget_set_tooltip_text(button, tooltip);
@@ -70,6 +79,7 @@ void toolbar_insert( ui::Toolbar toolbar, const char* icon, const char* text, co
                toolbar.add(button);
                return;
        }
+
        if (type == IToolbarButton::eToggleButton) {
                auto button = ui::ToolButton::from(gtk_toggle_tool_button_new());
                gtk_tool_button_set_icon_widget(button, new_plugin_image(icon));
@@ -80,6 +90,7 @@ void toolbar_insert( ui::Toolbar toolbar, const char* icon, const char* text, co
                toolbar.add(button);
                return;
        }
+
        ERROR_MESSAGE( "invalid toolbar button type" );
 }
 
@@ -123,6 +134,7 @@ ui::Toolbar create_plugin_toolbar(){
        auto toolbar = ui::Toolbar::from( gtk_toolbar_new() );
        gtk_orientable_set_orientation( GTK_ORIENTABLE(toolbar), GTK_ORIENTATION_HORIZONTAL );
        gtk_toolbar_set_style( toolbar, GTK_TOOLBAR_ICONS );
+//     gtk_toolbar_set_show_arrow( toolbar, TRUE );
        toolbar.show();
 
        g_plugin_toolbar = toolbar;
index ca35b1bc4266b2fb700429a6a07f0d20b244cebc..97bbffb2f087e5ab3016cff42e731564127e9592 100644 (file)
@@ -25,6 +25,7 @@
 #define INCLUDED_PLUGINTOOLBAR_H
 
 ui::Toolbar create_plugin_toolbar();
+
 void PluginToolbar_populate();
 void PluginToolbar_clear();
 
index c4a56492ee86ed73f0c2093d7f73ecfb08e91442..3e268168c5e167f41e5b83d0c7d0161fed3d888a 100644 (file)
@@ -193,7 +193,8 @@ void Pointfile_Next( void ){
 
        CamWnd& camwnd = *g_pParentWnd->GetCamWnd();
        Camera_setOrigin( camwnd, *i );
-       g_pParentWnd->GetXYWnd()->SetOrigin( *i );
+       g_pParentWnd->ActiveXY()->SetOrigin( *i );
+       g_pParentWnd->ActiveXY()->queueDraw();
        {
                Vector3 dir( vector3_normalised( vector3_subtracted( *( ++i ), Camera_getOrigin( camwnd ) ) ) );
                Vector3 angles( Camera_getAngles( camwnd ) );
@@ -218,7 +219,8 @@ void Pointfile_Prev( void ){
 
        CamWnd& camwnd = *g_pParentWnd->GetCamWnd();
        Camera_setOrigin( camwnd, *i );
-       g_pParentWnd->GetXYWnd()->SetOrigin( *i );
+       g_pParentWnd->ActiveXY()->SetOrigin( *i );
+       g_pParentWnd->ActiveXY()->queueDraw();
        {
                Vector3 dir( vector3_normalised( vector3_subtracted( *( ++i ), Camera_getOrigin( camwnd ) ) ) );
                Vector3 angles( Camera_getAngles( camwnd ) );
index b52fb73be5321c8cb99db21fe7d0667699b68bc0..53e3fa80c1f0d35a429d5e32f972cdd5f7de43e5 100644 (file)
@@ -1,4 +1,4 @@
-/*
+/*
    Copyright (C) 1999-2006 Id Software, Inc. and contributors.
    For a list of contributors, see the accompanying CONTRIBUTORS file.
 
@@ -58,23 +58,16 @@ void Global_constructPreferences( PreferencesPage& page ){
 }
 
 void Interface_constructPreferences( PreferencesPage& page ){
-#if GDEF_OS_WINDOWS
-       page.appendCheckBox( "", "Default Text Editor", g_TextEditor_useWin32Editor );
-#else
-       {
-               ui::CheckButton use_custom = page.appendCheckBox( "Text Editor", "Custom", g_TextEditor_useCustomEditor );
-               ui::Widget custom_editor = page.appendPathEntry( "Text Editor Command", g_TextEditor_editorCommand, true );
-               Widget_connectToggleDependency( custom_editor, use_custom );
-       }
-#endif
+       page.appendPathEntry( "Shader Editor Command", g_TextEditor_editorCommand, false );
 }
 
 void Mouse_constructPreferences( PreferencesPage& page ){
-       {
-               const char* buttons[] = { "2 button", "3 button", };
-               page.appendRadio( "Mouse Type",  g_glwindow_globals.m_nMouseType, STRING_ARRAY_RANGE( buttons ) );
-       }
-       page.appendCheckBox( "Right Button", "Activates Context Menu", g_xywindow_globals.m_bRightClick );
+//     {
+//             const char* buttons[] = { "2 button", "3 button", };
+//             page.appendRadio( "Mouse Type",  g_glwindow_globals.m_nMouseType, STRING_ARRAY_RANGE( buttons ) );
+//     }
+//     page.appendCheckBox( "Right Button", "Activates Context Menu", g_xywindow_globals.m_bRightClick );
+       page.appendCheckBox( "", "Zoom to mouse pointer", g_xywindow_globals.m_bZoomInToPointer );
 }
 void Mouse_constructPage( PreferenceGroup& group ){
        PreferencesPage page( group.createPage( "Mouse", "Mouse Preferences" ) );
@@ -106,7 +99,7 @@ CGameDescription::CGameDescription( xmlDocPtr pDoc, const CopiedString& gameFile
        // read the user-friendly game name
        xmlNodePtr pNode = pDoc->children;
 
-       while ( strcmp( (const char*)pNode->name, "game" ) && pNode != 0 )
+       while ( pNode != 0 && strcmp( (const char*)pNode->name, "game" ) )
        {
                pNode = pNode->next;
        }
@@ -281,7 +274,7 @@ void CGameDialog::GameFileImport( int value ){
        }
 
        if ( ( *iGame )->mGameFile != m_sGameFile ) {
-               m_sGameFile = ( *iGame )->mGameFile;
+       m_sGameFile = ( *iGame )->mGameFile;
 
                // do not trigger radiant restart when switching game on startup using Global Preferences dialog
                if ( !onStartup ) {
@@ -348,6 +341,30 @@ ui::Window CGameDialog::BuildDialog(){
        return create_simple_modal_dialog_window( "Global Preferences", m_modal, frame );
 }
 
+static void StringReplace( std::string& input, const std::string& first, const std::string& second )
+{
+       size_t found = 0;
+       while ( ( found = input.find(first, found) ) != std::string::npos )
+       {
+               input.replace( found, first.length(), second );
+       }
+}
+
+// FIXME, for some unknown reason it sorts “Quake 3” after “Quake 4”.
+static bool CompareGameName( CGameDescription *first, CGameDescription *second )
+{
+       std::string string1( first->getRequiredKeyValue( "name" ) );
+       std::string string2( second->getRequiredKeyValue( "name" ) );
+
+       // HACK: Replace some roman numerals.
+       StringReplace( string1, " III", " 3" );
+       StringReplace( string2, " III", " 3" );
+       StringReplace( string1, " II", " 2" );
+       StringReplace( string2, " II", " 2" );
+
+       return string1 < string2;
+}
+
 void CGameDialog::ScanForGames(){
        StringOutputStream strGamesPath( 256 );
        strGamesPath << DataPath_get() << "gamepacks/games/";
@@ -379,6 +396,8 @@ void CGameDialog::ScanForGames(){
                } else {
                        globalErrorStream() << "XML parser failed on '" << strPath.c_str() << "'\n";
                }
+
+               mGames.sort(CompareGameName);
        });
 }
 
@@ -700,7 +719,7 @@ PreferencesPage createPage( const char* treeName, const char* frameName ){
 
 ui::Window PrefsDlg::BuildDialog(){
        PreferencesDialog_addInterfacePreferences( makeCallbackF(Interface_constructPreferences) );
-       Mouse_registerPreferencesPage();
+       //Mouse_registerPreferencesPage();
 
        ui::Window dialog = ui::Window(create_floating_window( RADIANT_NAME " Preferences", m_parent ));
 
@@ -934,31 +953,39 @@ void PreferencesDialog_restartRequired( const char* staticName ){
        g_restart_required.push_back( staticName );
 }
 
-void PreferencesDialog_showDialog(){
-       if ( ConfirmModified( "Edit Preferences" ) && g_Preferences.DoModal() == eIDOK ) {
+bool PreferencesDialog_isRestartRequired(){
+       return !g_restart_required.empty();
+}
+
+void PreferencesDialog_restartIfRequired(){
                if ( !g_restart_required.empty() ) {
                        StringOutputStream message( 256 );
-                       message << "Preference changes require a restart:\n\n";
+               message << "Preference changes require a restart:\n\n";
 
                        for ( std::vector<const char*>::iterator i = g_restart_required.begin(); i != g_restart_required.end(); ++i )
                        {
                                message << ( *i ) << '\n';
                        }
 
-                       message << "\nRestart now?";
+               message << "\nRestart now?";
 
-                       auto ret = ui::alert( MainFrame_getWindow(), message.c_str(), "Restart " RADIANT_NAME "?", ui::alert_type::YESNO, ui::alert_icon::Question );
+               auto ret = ui::alert( MainFrame_getWindow(), message.c_str(), "Restart " RADIANT_NAME "?", ui::alert_type::YESNO, ui::alert_icon::Question );
 
                        g_restart_required.clear();
 
-                       if ( ret == ui::alert_response::YES ) {
-                               g_GamesDialog.m_bSkipGamePromptOnce = true;
-                               Radiant_Restart();
-                       }
+               if ( ret == ui::alert_response::YES ) {
+                       g_GamesDialog.m_bSkipGamePromptOnce = true;
+                       Radiant_Restart();
                }
        }
 }
 
+void PreferencesDialog_showDialog(){
+       if ( ConfirmModified( "Edit Preferences" ) && g_Preferences.DoModal() == eIDOK ) {
+               PreferencesDialog_restartIfRequired();
+       }
+}
+
 struct GameName {
        static void Export(const Callback<void(const char *)> &returnz) {
                returnz(gamename_get());
@@ -980,12 +1007,7 @@ struct GameMode {
 };
 
 void RegisterPreferences( PreferenceSystem& preferences ){
-#if GDEF_OS_WINDOWS
-       preferences.registerPreference( "UseCustomShaderEditor", make_property_string( g_TextEditor_useWin32Editor ) );
-#else
-       preferences.registerPreference( "UseCustomShaderEditor", make_property_string( g_TextEditor_useCustomEditor ) );
        preferences.registerPreference( "CustomShaderEditorCommand", make_property_string( g_TextEditor_editorCommand ) );
-#endif
 
        preferences.registerPreference( "GameName", make_property<GameName>() );
        preferences.registerPreference( "GameMode", make_property<GameMode>() );
index dbaae1354714a9f78f0752c52ed49a119f66de86..7ce4943ad26b3e46d5a528ff01b8f1cfe37208e1 100644 (file)
@@ -129,6 +129,7 @@ void PreferencesDialog_addDisplayPage( const PreferenceGroupCallback& callback )
 void PreferencesDialog_addSettingsPreferences( const PreferencesPageCallback& callback );
 void PreferencesDialog_addSettingsPage( const PreferenceGroupCallback& callback );
 
+bool PreferencesDialog_isRestartRequired();
 void PreferencesDialog_restartRequired( const char* staticName );
 
 template<typename Value>
@@ -269,7 +270,7 @@ std::list<CGameDescription*> mGames;
 
 CGameDialog() :
        m_sGameFile( "" ),
-       m_bGamePrompt( true ),
+       m_bGamePrompt( false ),
        m_bSkipGamePromptOnce( false ),
        m_bForceLogConsole( false ){
 }
@@ -357,9 +358,13 @@ public:
 ui::Widget m_notebook{ui::null};
 
 virtual ~PrefsDlg(){
+       if (m_rc_path) {
        g_string_free( m_rc_path, true );
+       }
+       if (m_inipath) {
        g_string_free( m_inipath, true );
 }
+}
 
 /*!
    path for global settings
@@ -410,6 +415,8 @@ extern preferences_globals_t g_preferences_globals;
 void PreferencesDialog_constructWindow( ui::Window main_window );
 void PreferencesDialog_destroyWindow();
 
+
+void PreferencesDialog_restartIfRequired();
 void PreferencesDialog_showDialog();
 
 void GlobalPreferences_Init();
index 3b015aa4aeb70564722bbc074304c08780438e6a..09d49d30dfabecc711eba8afc172e62ddc987cfb 100644 (file)
@@ -100,12 +100,12 @@ void QE_InitVFS(){
                // if we have a home dir
                if ( !string_equal( homepath, enginepath ) )
                {
-                       // ~/.<gameprefix>/<fs_game>
-                       if ( homepath && !g_disableHomePath ) {
-                               StringOutputStream userGamePath( 256 );
+               // ~/.<gameprefix>/<fs_game>
+               if ( homepath && !string_equal( enginepath, homepath ) && !g_disableHomePath ) {
+                       StringOutputStream userGamePath( 256 );
                                userGamePath << homepath << gamename << '/';
-                               GlobalFileSystem().initDirectory( userGamePath.c_str() );
-                       }
+                       GlobalFileSystem().initDirectory( userGamePath.c_str() );
+               }
                }
 
                // <fs_basepath>/<fs_game>
@@ -119,12 +119,12 @@ void QE_InitVFS(){
        // if we have a home dir
        if ( !string_equal( homepath, enginepath ) )
        {
-               // ~/.<gameprefix>/<fs_main>
-               if ( homepath && !g_disableHomePath ) {
-                       StringOutputStream userBasePath( 256 );
+       // ~/.<gameprefix>/<fs_main>
+       if ( homepath && !string_equal( enginepath, homepath ) && !g_disableHomePath ) {
+               StringOutputStream userBasePath( 256 );
                        userBasePath << homepath << basegame << '/';
-                       GlobalFileSystem().initDirectory( userBasePath.c_str() );
-               }
+               GlobalFileSystem().initDirectory( userBasePath.c_str() );
+       }
        }
 
        // <fs_basepath>/<fs_main>
@@ -227,7 +227,15 @@ void bsp_init(){
        name.append( mapname, path_get_filename_base_end( mapname ) - mapname );
        name += ".bsp";
 
-       build_set_variable( "MapFile", mapname );
+       if( region_active ){
+               StringOutputStream name( 256 );
+               name << StringRange( mapname, path_get_filename_base_end( mapname ) ) << ".reg";
+               build_set_variable( "MapFile", name.c_str() );
+       }
+       else{
+               build_set_variable( "MapFile", mapname );
+       }
+
        build_set_variable( "BspFile", name.c_str() );
 }
 
index 9ceeba1bc78fe0e0578d2d3ec97dca2459afa951..88f3fb3afbe0b4ac3f7375a76eeb068087ae9395 100644 (file)
@@ -124,8 +124,14 @@ bool file_saveBackup( const char* path ){
                StringOutputStream backup( 256 );
                backup << StringRange( path, path_get_extension( path ) ) << "bak";
 
+#if GDEF_OS_WINDOWS
+               // NT symlinks are not supported yet.
                return ( !file_exists( backup.c_str() ) || file_remove( backup.c_str() ) ) // remove backup
                           && file_move( path, backup.c_str() ); // rename current to backup
+#else
+               // POSIX symlinks are supported.
+               return file_move( path, backup.c_str() ); // rename current to backup
+#endif
        }
 
        globalErrorStream() << "map path is not writeable: " << makeQuoted( path ) << "\n";
@@ -137,7 +143,27 @@ bool MapResource_save( const MapFormat& format, scene::Node& root, const char* p
        fullpath << path << name;
 
        if ( path_is_absolute( fullpath.c_str() ) ) {
+#if GDEF_OS_WINDOWS
+               // NT symlinks are not supported yet.
                if ( !file_exists( fullpath.c_str() ) || file_saveBackup( fullpath.c_str() ) ) {
+#else
+               // POSIX symlinks are supported.
+               /* We don't want a backup + rename operation if the .map file is
+                * a symlink. Otherwise we'll break the user's careful symlink setup.
+                * Just overwrite the original file. Assume the user has versioning. */
+               bool make_backup;
+               struct stat st;
+               if ( lstat(fullpath.c_str(), &st) == 0 ) {
+                       make_backup = true;             // file exists
+                       if ( (st.st_mode & S_IFMT) == S_IFLNK ) {
+                               make_backup = false;    // .. but it is a symlink
+                       }
+               } else {
+                       make_backup = false;            // nothing to move
+               }
+
+               if ( !make_backup || file_saveBackup( fullpath.c_str() ) ) {
+#endif
                        return MapResource_saveFile( format, root, Map_Traverse, fullpath.c_str() );
                }
 
index c67cf0ea1cee2362e052af06d95a9316dfcf83df..d94afcadd3d28716ee75e686d193d5b3e3234c4d 100644 (file)
@@ -149,7 +149,10 @@ bool pre( const scene::Path& path, scene::Instance& instance, VolumeIntersection
                        else if ( renderable ) {
                                renderable->renderComponents( m_renderer, m_volume );
                        }
-                       m_renderer.Highlight( Renderer::ePrimitive );
+                       //if( !(GlobalSelectionSystem().Mode() == SelectionSystem::eComponent && path.size() == 2) )
+                       //if( !( GlobalSelectionSystem().Mode() == SelectionSystem::eComponent && node_is_group( path.top() ) ) )
+                       if( !( GlobalSelectionSystem().Mode() == SelectionSystem::eComponent && Node_isEntity( path.top() ) ) )
+                               m_renderer.Highlight( Renderer::ePrimitive );
                }
 
                if ( renderable ) {
index f714fc7eb2dd16712ae6486829ae7d81ea8965c0..cb7cde0610bde2b28b7957b966ec0b3cf4265606 100644 (file)
@@ -52,6 +52,7 @@
 #include "preferences.h"
 
 #include "xywindow.h"
+#include "camwindow.h"
 
 
 #define DEBUG_RENDER 0
@@ -2150,13 +2151,15 @@ void OpenGLShader::construct( const char* name ){
                break;
 
        case '$':
-       {
-               OpenGLStateMap::iterator i = g_openglStates->find( name );
-               if ( i != g_openglStates->end() ) {
-                       state = ( *i ).second;
-                       break;
+               {
+                       OpenGLStateMap::iterator i = g_openglStates->find( name );
+                       if ( i != g_openglStates->end() )
+                       {
+                               state = ( *i ).second;
+                               break;
+                       }
                }
-       }
+
                if ( string_equal( name + 1, "POINT" ) ) {
                        state.m_state = RENDER_COLOURARRAY | RENDER_COLOURWRITE | RENDER_DEPTHWRITE;
                        state.m_sort = OpenGLState::eSortControlFirst;
@@ -2197,9 +2200,9 @@ void OpenGLShader::construct( const char* name ){
                        state.m_sort = OpenGLState::eSortFullbright;
                }
                else if ( string_equal( name + 1, "CAM_HIGHLIGHT" ) ) {
-                       state.m_colour[0] = 1;
-                       state.m_colour[1] = 0;
-                       state.m_colour[2] = 0;
+                       state.m_colour[0] = g_camwindow_globals.color_selbrushes3d[0];
+                       state.m_colour[1] = g_camwindow_globals.color_selbrushes3d[1];
+                       state.m_colour[2] = g_camwindow_globals.color_selbrushes3d[2];
                        state.m_colour[3] = 0.3f;
                        state.m_state = RENDER_FILL | RENDER_DEPTHTEST | RENDER_CULLFACE | RENDER_BLEND | RENDER_COLOURWRITE | RENDER_DEPTHWRITE;
                        state.m_sort = OpenGLState::eSortHighlight;
@@ -2415,6 +2418,7 @@ void OpenGLShader::construct( const char* name ){
                                BlendFunc blendFunc = m_shader->getBlendFunc();
                                state.m_blend_src = convertBlendFactor( blendFunc.m_src );
                                state.m_blend_dst = convertBlendFactor( blendFunc.m_dst );
+                               state.m_depthfunc = GL_LEQUAL;
                                if ( state.m_blend_src == GL_SRC_ALPHA || state.m_blend_dst == GL_SRC_ALPHA ) {
                                        state.m_state |= RENDER_DEPTHWRITE;
                                }
index 7a40bb35eeab2035df480d91d5de2f9454ae423b..8f25906d83df876c9b02f5ec047680b79015aa25 100644 (file)
@@ -41,6 +41,7 @@
 #include "gtkutil/widget.h"
 #include "brushmanip.h"
 #include "brush.h"
+#include "patch.h"
 #include "patchmanip.h"
 #include "patchdialog.h"
 #include "selection.h"
@@ -112,27 +113,33 @@ SelectByBounds( AABB* aabbs, Unsigned count )
 }
 
 bool pre( const scene::Path& path, scene::Instance& instance ) const {
-       Selectable* selectable = Instance_getSelectable( instance );
+       if( path.top().get().visible() ){
+               Selectable* selectable = Instance_getSelectable( instance );
 
-       // ignore worldspawn
-       Entity* entity = Node_getEntity( path.top() );
-       if ( entity ) {
-               if ( string_equal( entity->getKeyValue( "classname" ), "worldspawn" ) ) {
-                       return true;
+               // ignore worldspawn
+               Entity* entity = Node_getEntity( path.top() );
+               if ( entity ) {
+                       if ( string_equal( entity->getKeyValue( "classname" ), "worldspawn" ) ) {
+                               return true;
+                       }
                }
-       }
 
-       if ( ( path.size() > 1 ) &&
-                ( !path.top().get().isRoot() ) &&
-                ( selectable != 0 )
-                ) {
-               for ( Unsigned i = 0; i < m_count; ++i )
-               {
-                       if ( policy.Evaluate( m_aabbs[i], instance ) ) {
-                               selectable->setSelected( true );
+               if ( ( path.size() > 1 ) &&
+                       ( !path.top().get().isRoot() ) &&
+                       ( selectable != 0 ) &&
+                       ( !node_is_group( path.top() ) )
+                       ) {
+                       for ( Unsigned i = 0; i < m_count; ++i )
+                       {
+                               if ( policy.Evaluate( m_aabbs[i], instance ) ) {
+                                       selectable->setSelected( true );
+                               }
                        }
                }
        }
+       else{
+               return false;
+       }
 
        return true;
 }
@@ -271,12 +278,17 @@ void Select_Delete( void ){
 class InvertSelectionWalker : public scene::Graph::Walker
 {
 SelectionSystem::EMode m_mode;
+SelectionSystem::EComponentMode m_compmode;
 mutable Selectable* m_selectable;
 public:
-InvertSelectionWalker( SelectionSystem::EMode mode )
-       : m_mode( mode ), m_selectable( 0 ){
+InvertSelectionWalker( SelectionSystem::EMode mode, SelectionSystem::EComponentMode compmode )
+       : m_mode( mode ), m_compmode( compmode ), m_selectable( 0 ){
 }
 bool pre( const scene::Path& path, scene::Instance& instance ) const {
+       if( !path.top().get().visible() ){
+               m_selectable = 0;
+               return false;
+       }
        Selectable* selectable = Instance_getSelectable( instance );
        if ( selectable ) {
                switch ( m_mode )
@@ -290,6 +302,18 @@ bool pre( const scene::Path& path, scene::Instance& instance ) const {
                        m_selectable = path.top().get().visible() ? selectable : 0;
                        break;
                case SelectionSystem::eComponent:
+                       BrushInstance* brushinstance = Instance_getBrush( instance );
+                       if( brushinstance != 0 ){
+                               if( brushinstance->isSelected() )
+                                       brushinstance->invertComponentSelection( m_compmode );
+                       }
+                       else{
+                               PatchInstance* patchinstance = Instance_getPatch( instance );
+                               if( patchinstance != 0 && m_compmode == SelectionSystem::eVertex ){
+                                       if( patchinstance->isSelected() )
+                                               patchinstance->invertComponentSelection();
+                               }
+                       }
                        break;
                }
        }
@@ -304,13 +328,64 @@ void post( const scene::Path& path, scene::Instance& instance ) const {
 };
 
 void Scene_Invert_Selection( scene::Graph& graph ){
-       graph.traverse( InvertSelectionWalker( GlobalSelectionSystem().Mode() ) );
+       graph.traverse( InvertSelectionWalker( GlobalSelectionSystem().Mode(), GlobalSelectionSystem().ComponentMode() ) );
 }
 
 void Select_Invert(){
        Scene_Invert_Selection( GlobalSceneGraph() );
 }
 
+//interesting printings
+class ExpandSelectionToEntitiesWalker_dbg : public scene::Graph::Walker
+{
+mutable std::size_t m_depth;
+NodeSmartReference worldspawn;
+public:
+ExpandSelectionToEntitiesWalker_dbg() : m_depth( 0 ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
+}
+bool pre( const scene::Path& path, scene::Instance& instance ) const {
+       ++m_depth;
+       globalOutputStream() << "pre depth_" << m_depth;
+       globalOutputStream() << " path.size()_" << path.size();
+       if ( path.top().get() == worldspawn )
+               globalOutputStream() << " worldspawn";
+       if( path.top().get().isRoot() )
+               globalOutputStream() << " path.top().get().isRoot()";
+       Entity* entity = Node_getEntity( path.top() );
+       if ( entity != 0 ){
+               globalOutputStream() << " entity!=0";
+               if( entity->isContainer() ){
+                       globalOutputStream() << " entity->isContainer()";
+               }
+               globalOutputStream() << " classname_" << entity->getKeyValue( "classname" );
+       }
+       globalOutputStream() << "\n";
+//     globalOutputStream() << "" <<  ;
+//     globalOutputStream() << "" <<  ;
+//     globalOutputStream() << "" <<  ;
+//     globalOutputStream() << "" <<  ;
+       return true;
+}
+void post( const scene::Path& path, scene::Instance& instance ) const {
+       globalOutputStream() << "post depth_" << m_depth;
+       globalOutputStream() << " path.size()_" << path.size();
+       if ( path.top().get() == worldspawn )
+               globalOutputStream() << " worldspawn";
+       if( path.top().get().isRoot() )
+               globalOutputStream() << " path.top().get().isRoot()";
+       Entity* entity = Node_getEntity( path.top() );
+       if ( entity != 0 ){
+               globalOutputStream() << " entity!=0";
+               if( entity->isContainer() ){
+                       globalOutputStream() << " entity->isContainer()";
+               }
+               globalOutputStream() << " classname_" << entity->getKeyValue( "classname" );
+       }
+       globalOutputStream() << "\n";
+       --m_depth;
+}
+};
+
 class ExpandSelectionToEntitiesWalker : public scene::Graph::Walker
 {
 mutable std::size_t m_depth;
@@ -322,17 +397,21 @@ bool pre( const scene::Path& path, scene::Instance& instance ) const {
        ++m_depth;
 
        // ignore worldspawn
-       NodeSmartReference me( path.top().get() );
-       if ( me == worldspawn ) {
-               return false;
-       }
+//     NodeSmartReference me( path.top().get() );
+//     if ( me == worldspawn ) {
+//             return false;
+//     }
 
        if ( m_depth == 2 ) { // entity depth
                // traverse and select children if any one is selected
-               if ( instance.childSelected() ) {
-                       Instance_setSelected( instance, true );
+               bool beselected = false;
+               if ( instance.childSelected() || instance.isSelected() ) {
+                       beselected = true;
+                       if( path.top().get() != worldspawn ){
+                               Instance_setSelected( instance, true );
+                       }
                }
-               return Node_getEntity( path.top() )->isContainer() && instance.isSelected();
+               return Node_getEntity( path.top() )->isContainer() && beselected;
        }
        else if ( m_depth == 3 ) { // primitive depth
                Instance_setSelected( instance, true );
@@ -373,9 +452,9 @@ void UpdateWorkzone_ForSelection(){
 
 // update the workzone to the current selection
 void UpdateWorkzone_ForSelectionChanged( const Selectable& selectable ){
-       if ( selectable.isSelected() ) {
+       //if ( selectable.isSelected() ) {
                UpdateWorkzone_ForSelection();
-       }
+       //}
 }
 
 void Select_SetShader( const char* shader ){
@@ -386,6 +465,13 @@ void Select_SetShader( const char* shader ){
        Scene_BrushSetShader_Component_Selected( GlobalSceneGraph(), shader );
 }
 
+void Select_SetShader_Undo( const char* shader ){
+       if ( GlobalSelectionSystem().countSelectedComponents() != 0 || GlobalSelectionSystem().countSelected() != 0 ) {
+               UndoableCommand undo( "textureNameSetSelected" );
+               Select_SetShader( shader );
+       }
+}
+
 void Select_SetTexdef( const TextureProjection& projection ){
        if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
                Scene_BrushSetTexdef_Selected( GlobalSceneGraph(), projection );
@@ -514,20 +600,20 @@ inline Quaternion quaternion_for_axis90( axis_t axis, sign_t sign ){
 
 void Select_RotateAxis( int axis, float deg ){
        if ( fabs( deg ) == 90.f ) {
-               GlobalSelectionSystem().rotateSelected( quaternion_for_axis90( (axis_t)axis, ( deg > 0 ) ? eSignPositive : eSignNegative ) );
+               GlobalSelectionSystem().rotateSelected( quaternion_for_axis90( (axis_t)axis, ( deg > 0 ) ? eSignPositive : eSignNegative ), true );
        }
        else
        {
                switch ( axis )
                {
                case 0:
-                       GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_x_degrees( deg ) ) );
+                       GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_x_degrees( deg ) ), false );
                        break;
                case 1:
-                       GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_y_degrees( deg ) ) );
+                       GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_y_degrees( deg ) ), false );
                        break;
                case 2:
-                       GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_z_degrees( deg ) ) );
+                       GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_z_degrees( deg ) ), false );
                        break;
                }
        }
@@ -605,15 +691,32 @@ class EntityFindByPropertyValueWalker : public scene::Graph::Walker
 {
 const PropertyValues& m_propertyvalues;
 const char *m_prop;
+const NodeSmartReference worldspawn;
 public:
 EntityFindByPropertyValueWalker( const char *prop, const PropertyValues& propertyvalues )
-       : m_propertyvalues( propertyvalues ), m_prop( prop ){
+       : m_propertyvalues( propertyvalues ), m_prop( prop ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
 }
 bool pre( const scene::Path& path, scene::Instance& instance ) const {
+       if( !path.top().get().visible() ){
+               return false;
+       }
+       // ignore worldspawn
+       if ( path.top().get() == worldspawn ) {
+               return false;
+       }
+
        Entity* entity = Node_getEntity( path.top() );
-       if ( entity != 0
-                && propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
-               Instance_getSelectable( instance )->setSelected( true );
+       if ( entity != 0 ){
+               if( propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
+                       Instance_getSelectable( instance )->setSelected( true );
+                       return true;
+               }
+               return false;
+       }
+       else if( path.size() > 2 && !path.top().get().isRoot() ){
+               Selectable* selectable = Instance_getSelectable( instance );
+               if( selectable != 0 )
+                       selectable->setSelected( true );
        }
        return true;
 }
@@ -627,9 +730,37 @@ class EntityGetSelectedPropertyValuesWalker : public scene::Graph::Walker
 {
 PropertyValues& m_propertyvalues;
 const char *m_prop;
+const NodeSmartReference worldspawn;
 public:
 EntityGetSelectedPropertyValuesWalker( const char *prop, PropertyValues& propertyvalues )
-       : m_propertyvalues( propertyvalues ), m_prop( prop ){
+       : m_propertyvalues( propertyvalues ), m_prop( prop ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
+}
+bool pre( const scene::Path& path, scene::Instance& instance ) const {
+       Entity* entity = Node_getEntity( path.top() );
+       if ( entity != 0 ){
+               if( path.top().get() != worldspawn ){
+                       Selectable* selectable = Instance_getSelectable( instance );
+                       if ( ( selectable != 0 && selectable->isSelected() ) || instance.childSelected() ) {
+                               if ( !propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
+                                       m_propertyvalues.push_back( entity->getKeyValue( m_prop ) );
+                               }
+                       }
+               }
+               return false;
+       }
+       return true;
+}
+};
+/*
+class EntityGetSelectedPropertyValuesWalker : public scene::Graph::Walker
+{
+PropertyValues& m_propertyvalues;
+const char *m_prop;
+mutable bool m_selected_children;
+const NodeSmartReference worldspawn;
+public:
+EntityGetSelectedPropertyValuesWalker( const char *prop, PropertyValues& propertyvalues )
+       : m_propertyvalues( propertyvalues ), m_prop( prop ), m_selected_children( false ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
 }
 bool pre( const scene::Path& path, scene::Instance& instance ) const {
        Selectable* selectable = Instance_getSelectable( instance );
@@ -640,12 +771,27 @@ bool pre( const scene::Path& path, scene::Instance& instance ) const {
                        if ( !propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
                                m_propertyvalues.push_back( entity->getKeyValue( m_prop ) );
                        }
+                       return false;
+               }
+               else{
+                       m_selected_children = true;
                }
        }
        return true;
 }
+void post( const scene::Path& path, scene::Instance& instance ) const {
+       Entity* entity = Node_getEntity( path.top() );
+       if( entity != 0 && m_selected_children ){
+               m_selected_children = false;
+               if( path.top().get() == worldspawn )
+                       return;
+               if ( !propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
+                       m_propertyvalues.push_back( entity->getKeyValue( m_prop ) );
+               }
+       }
+}
 };
-
+*/
 void Scene_EntityGetPropertyValues( scene::Graph& graph, const char *prop, PropertyValues& propertyvalues ){
        graph.traverse( EntityGetSelectedPropertyValuesWalker( prop, propertyvalues ) );
 }
@@ -694,12 +840,22 @@ void Select_FitTexture( float horizontal, float vertical ){
        SceneChangeNotify();
 }
 
+
+#include "commands.h"
+#include "dialog.h"
+
 inline void hide_node( scene::Node& node, bool hide ){
        hide
        ? node.enable( scene::Node::eHidden )
        : node.disable( scene::Node::eHidden );
 }
 
+bool g_nodes_be_hidden = false;
+
+ConstReferenceCaller<bool, void(const Callback<void(bool)> &), PropertyImpl<bool>::Export> g_hidden_caller( g_nodes_be_hidden );
+
+ToggleItem g_hidden_item( g_hidden_caller );
+
 class HideSelectedWalker : public scene::Graph::Walker
 {
 bool m_hide;
@@ -711,6 +867,7 @@ bool pre( const scene::Path& path, scene::Instance& instance ) const {
        Selectable* selectable = Instance_getSelectable( instance );
        if ( selectable != 0
                 && selectable->isSelected() ) {
+               g_nodes_be_hidden = m_hide;
                hide_node( path.top(), m_hide );
        }
        return true;
@@ -729,6 +886,7 @@ void Select_Hide(){
 void HideSelected(){
        Select_Hide();
        GlobalSelectionSystem().setSelectedAll( false );
+       g_hidden_item.update();
 }
 
 
@@ -752,10 +910,11 @@ void Scene_Hide_All( bool hide ){
 void Select_ShowAllHidden(){
        Scene_Hide_All( false );
        SceneChangeNotify();
+       g_nodes_be_hidden = false;
+       g_hidden_item.update();
 }
 
 
-
 void Selection_Flipx(){
        UndoableCommand undo( "mirrorSelected -axis x" );
        Select_FlipAxis( 0 );
@@ -785,7 +944,89 @@ void Selection_Rotatez(){
        UndoableCommand undo( "rotateSelected -axis z -angle -90" );
        Select_RotateAxis( 2,-90 );
 }
+#include "xywindow.h"
+void Selection_FlipHorizontally(){
+       VIEWTYPE viewtype = GlobalXYWnd_getCurrentViewType();
+       switch ( viewtype )
+       {
+       case XY:
+       case XZ:
+               Selection_Flipx();
+               break;
+       default:
+               Selection_Flipy();
+               break;
+       }
+}
 
+void Selection_FlipVertically(){
+       VIEWTYPE viewtype = GlobalXYWnd_getCurrentViewType();
+       switch ( viewtype )
+       {
+       case XZ:
+       case YZ:
+               Selection_Flipz();
+               break;
+       default:
+               Selection_Flipy();
+               break;
+       }
+}
+
+void Selection_RotateClockwise(){
+       UndoableCommand undo( "rotateSelected Clockwise 90" );
+       VIEWTYPE viewtype = GlobalXYWnd_getCurrentViewType();
+       switch ( viewtype )
+       {
+       case XY:
+               Select_RotateAxis( 2, -90 );
+               break;
+       case XZ:
+               Select_RotateAxis( 1, 90 );
+               break;
+       default:
+               Select_RotateAxis( 0, -90 );
+               break;
+       }
+}
+
+void Selection_RotateAnticlockwise(){
+       UndoableCommand undo( "rotateSelected Anticlockwise 90" );
+       VIEWTYPE viewtype = GlobalXYWnd_getCurrentViewType();
+       switch ( viewtype )
+       {
+       case XY:
+               Select_RotateAxis( 2, 90 );
+               break;
+       case XZ:
+               Select_RotateAxis( 1, -90 );
+               break;
+       default:
+               Select_RotateAxis( 0, 90 );
+               break;
+       }
+
+}
+
+
+
+void Select_registerCommands(){
+       GlobalCommands_insert( "ShowHidden", makeCallbackF( Select_ShowAllHidden ), Accelerator( 'H', (GdkModifierType)GDK_SHIFT_MASK ) );
+       GlobalToggles_insert( "HideSelected", makeCallbackF( HideSelected ), ToggleItem::AddCallbackCaller( g_hidden_item ), Accelerator( 'H' ) );
+
+       GlobalCommands_insert( "MirrorSelectionX", makeCallbackF( Selection_Flipx ) );
+       GlobalCommands_insert( "RotateSelectionX", makeCallbackF( Selection_Rotatex ) );
+       GlobalCommands_insert( "MirrorSelectionY", makeCallbackF( Selection_Flipy ) );
+       GlobalCommands_insert( "RotateSelectionY", makeCallbackF( Selection_Rotatey ) );
+       GlobalCommands_insert( "MirrorSelectionZ", makeCallbackF( Selection_Flipz ) );
+       GlobalCommands_insert( "RotateSelectionZ", makeCallbackF( Selection_Rotatez ) );
+
+       GlobalCommands_insert( "MirrorSelectionHorizontally", makeCallbackF( Selection_FlipHorizontally ) );
+       GlobalCommands_insert( "MirrorSelectionVertically", makeCallbackF( Selection_FlipVertically ) );
+
+       GlobalCommands_insert( "RotateSelectionClockwise", makeCallbackF( Selection_RotateClockwise ) );
+       GlobalCommands_insert( "RotateSelectionAnticlockwise", makeCallbackF( Selection_RotateAnticlockwise ) );
+}
 
 
 void Nudge( int nDim, float fNudge ){
@@ -874,6 +1115,9 @@ struct RotateDialog
 static gboolean rotatedlg_apply( ui::Widget widget, RotateDialog* rotateDialog ){
        Vector3 eulerXYZ;
 
+       gtk_spin_button_update ( rotateDialog->x );
+       gtk_spin_button_update ( rotateDialog->y );
+       gtk_spin_button_update ( rotateDialog->z );
        eulerXYZ[0] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->x ) );
        eulerXYZ[1] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->y ) );
        eulerXYZ[2] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->z ) );
@@ -882,7 +1126,7 @@ static gboolean rotatedlg_apply( ui::Widget widget, RotateDialog* rotateDialog )
        command << "rotateSelectedEulerXYZ -x " << eulerXYZ[0] << " -y " << eulerXYZ[1] << " -z " << eulerXYZ[2];
        UndoableCommand undo( command.c_str() );
 
-       GlobalSelectionSystem().rotateSelected( quaternion_for_euler_xyz_degrees( eulerXYZ ) );
+       GlobalSelectionSystem().rotateSelected( quaternion_for_euler_xyz_degrees( eulerXYZ ), false );
        return TRUE;
 }
 
@@ -898,6 +1142,7 @@ static gboolean rotatedlg_cancel( ui::Widget widget, RotateDialog* rotateDialog
 
 static gboolean rotatedlg_ok( ui::Widget widget, RotateDialog* rotateDialog ){
        rotatedlg_apply( widget, rotateDialog );
+//     rotatedlg_cancel( widget, rotateDialog );
        rotateDialog->window.hide();
        return TRUE;
 }
@@ -938,7 +1183,7 @@ void DoRotateDlg(){
                                }
                                {
                                        auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
-                                       auto spin = ui::SpinButton( adj, 1, 0 );
+                                       auto spin = ui::SpinButton( adj, 1, 1 );
                                        spin.show();
                     table.attach(spin, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
                     spin.dimensions(64, -1);
@@ -950,7 +1195,7 @@ void DoRotateDlg(){
                                }
                                {
                                        auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
-                                       auto spin = ui::SpinButton( adj, 1, 0 );
+                                       auto spin = ui::SpinButton( adj, 1, 1 );
                                        spin.show();
                     table.attach(spin, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
                     spin.dimensions(64, -1);
@@ -960,7 +1205,7 @@ void DoRotateDlg(){
                                }
                                {
                                        auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
-                                       auto spin = ui::SpinButton( adj, 1, 0 );
+                                       auto spin = ui::SpinButton( adj, 1, 1 );
                                        spin.show();
                     table.attach(spin, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
                     spin.dimensions(64, -1);
@@ -1038,6 +1283,7 @@ static gboolean scaledlg_cancel( ui::Widget widget, ScaleDialog* scaleDialog ){
 
 static gboolean scaledlg_ok( ui::Widget widget, ScaleDialog* scaleDialog ){
        scaledlg_apply( widget, scaleDialog );
+       //scaledlg_cancel( widget, scaleDialog );
        scaleDialog->window.hide();
        return TRUE;
 }
@@ -1126,3 +1372,79 @@ void DoScaleDlg(){
 
        g_scale_dialog.window.show();
 }
+
+
+class EntityGetSelectedPropertyValuesWalker_nonEmpty : public scene::Graph::Walker
+{
+PropertyValues& m_propertyvalues;
+const char *m_prop;
+const NodeSmartReference worldspawn;
+public:
+EntityGetSelectedPropertyValuesWalker_nonEmpty( const char *prop, PropertyValues& propertyvalues )
+       : m_propertyvalues( propertyvalues ), m_prop( prop ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
+}
+bool pre( const scene::Path& path, scene::Instance& instance ) const {
+       Entity* entity = Node_getEntity( path.top() );
+       if ( entity != 0 ){
+               if( path.top().get() != worldspawn ){
+                       Selectable* selectable = Instance_getSelectable( instance );
+                       if ( ( selectable != 0 && selectable->isSelected() ) || instance.childSelected() ) {
+                               const char* keyvalue = entity->getKeyValue( m_prop );
+                               if ( !string_empty( keyvalue ) && !propertyvalues_contain( m_propertyvalues, keyvalue ) ) {
+                                       m_propertyvalues.push_back( keyvalue );
+                               }
+                       }
+               }
+               return false;
+       }
+       return true;
+}
+};
+
+void Scene_EntityGetPropertyValues_nonEmpty( scene::Graph& graph, const char *prop, PropertyValues& propertyvalues ){
+       graph.traverse( EntityGetSelectedPropertyValuesWalker_nonEmpty( prop, propertyvalues ) );
+}
+
+#include "preferences.h"
+
+void Select_ConnectedEntities( bool targeting, bool targets, bool focus ){
+       PropertyValues target_propertyvalues;
+       PropertyValues targetname_propertyvalues;
+       const char *target_prop = "target";
+       const char *targetname_prop;
+       if ( g_pGameDescription->mGameType == "doom3" ) {
+               targetname_prop = "name";
+       }
+       else{
+               targetname_prop = "targetname";
+       }
+
+       if( targeting ){
+               Scene_EntityGetPropertyValues_nonEmpty( GlobalSceneGraph(), targetname_prop, targetname_propertyvalues );
+       }
+       if( targets ){
+               Scene_EntityGetPropertyValues_nonEmpty( GlobalSceneGraph(), target_prop, target_propertyvalues );
+       }
+
+       if( target_propertyvalues.empty() && targetname_propertyvalues.empty() ){
+               globalErrorStream() << "SelectConnectedEntities: nothing found\n";
+               return;
+       }
+
+       if( !targeting || !targets ){
+               GlobalSelectionSystem().setSelectedAll( false );
+       }
+       if ( targeting && !targetname_propertyvalues.empty() ) {
+               Scene_EntitySelectByPropertyValues( GlobalSceneGraph(), target_prop, targetname_propertyvalues );
+       }
+       if ( targets && !target_propertyvalues.empty() ) {
+               Scene_EntitySelectByPropertyValues( GlobalSceneGraph(), targetname_prop, target_propertyvalues );
+       }
+       if( focus ){
+               FocusAllViews();
+       }
+}
+
+void SelectConnectedEntities(){
+       Select_ConnectedEntities( true, true, false );
+}
index 4a158fd41d86994e629d507ccd15ba2f8a693741..88d25f68abef201c0e8fbba377882f4468d60c71 100644 (file)
@@ -33,12 +33,12 @@ void Select_Inside();
 void Select_Touching();
 void Scene_ExpandSelectionToEntities();
 
-void Selection_Flipx();
-void Selection_Flipy();
-void Selection_Flipz();
-void Selection_Rotatex();
-void Selection_Rotatey();
-void Selection_Rotatez();
+//void Selection_Flipx();
+//void Selection_Flipy();
+//void Selection_Flipz();
+//void Selection_Rotatex();
+//void Selection_Rotatey();
+//void Selection_Rotatez();
 
 
 void Selection_MoveDown();
@@ -46,11 +46,15 @@ void Selection_MoveUp();
 
 void Select_AllOfType();
 
+void Select_ConnectedEntities( bool targeting, bool targets, bool focus );
+void SelectConnectedEntities();
+
 void DoRotateDlg();
 void DoScaleDlg();
 
 
 void Select_SetShader( const char* shader );
+void Select_SetShader_Undo( const char* shader );
 
 class TextureProjection;
 void Select_SetTexdef( const TextureProjection& projection );
@@ -66,6 +70,7 @@ void FindReplaceTextures( const char* pFind, const char* pReplace, bool bSelecte
 
 void HideSelected();
 void Select_ShowAllHidden();
+void Select_registerCommands();
 
 // updating workzone to a given brush (depends on current view)
 
index df18daa9c5ef23a908cc40e83b84b62eb09617c9..53389df78bab57262e31bd82206df2bc2a9a4f84 100644 (file)
@@ -193,8 +193,8 @@ float distance_for_axis( const Vector3& a, const Vector3& b, const Vector3& axis
 class Manipulatable
 {
 public:
-virtual void Construct( const Matrix4& device2manip, const float x, const float y ) = 0;
-virtual void Transform( const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y ) = 0;
+virtual void Construct( const Matrix4& device2manip, const float x, const float y, const AABB bounds, const Vector3 transform_origin ) = 0;
+virtual void Transform( const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y, const bool snap, const bool snapbbox ) = 0;
 };
 
 void transform_local2object( Matrix4& object, const Matrix4& local, const Matrix4& local2object ){
@@ -219,16 +219,30 @@ public:
 RotateFree( Rotatable& rotatable )
        : m_rotatable( rotatable ){
 }
-void Construct( const Matrix4& device2manip, const float x, const float y ){
+void Construct( const Matrix4& device2manip, const float x, const float y, const AABB bounds, const Vector3 transform_origin ){
        point_on_sphere( m_start, device2manip, x, y );
        vector3_normalise( m_start );
 }
-void Transform( const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y ){
+void Transform( const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y, const bool snap, const bool snapbbox ){
        Vector3 current;
-
        point_on_sphere( current, device2manip, x, y );
-       vector3_normalise( current );
 
+       if( snap ){
+               Vector3 axis( 0, 0, 0 );
+               for( std::size_t i = 0; i < 3; ++i ){
+                       if( current[i] == 0.0f ){
+                               axis[i] = 1.0f;
+                               break;
+                       }
+               }
+               if( vector3_length_squared( axis ) != 0 ){
+                       constrain_to_axis( current, axis );
+                       m_rotatable.rotate( quaternion_for_axisangle( axis, float_snapped( angle_for_axis( m_start, current, axis ), static_cast<float>( c_pi / 12.0 ) ) ) );
+                       return;
+               }
+       }
+
+       vector3_normalise( current );
        m_rotatable.rotate( quaternion_for_unit_vectors( m_start, current ) );
 }
 };
@@ -242,17 +256,22 @@ public:
 RotateAxis( Rotatable& rotatable )
        : m_rotatable( rotatable ){
 }
-void Construct( const Matrix4& device2manip, const float x, const float y ){
+void Construct( const Matrix4& device2manip, const float x, const float y, const AABB bounds, const Vector3 transform_origin ){
        point_on_sphere( m_start, device2manip, x, y );
        constrain_to_axis( m_start, m_axis );
 }
 /// \brief Converts current position to a normalised vector orthogonal to axis.
-void Transform( const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y ){
+void Transform( const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y, const bool snap, const bool snapbbox ){
        Vector3 current;
        point_on_sphere( current, device2manip, x, y );
        constrain_to_axis( current, m_axis );
 
-       m_rotatable.rotate( quaternion_for_axisangle( m_axis, angle_for_axis( m_start, current, m_axis ) ) );
+       if( snap ){
+               m_rotatable.rotate( quaternion_for_axisangle( m_axis, float_snapped( angle_for_axis( m_start, current, m_axis ), static_cast<float>( c_pi / 12.0 ) ) ) );
+       }
+       else{
+               m_rotatable.rotate( quaternion_for_axisangle( m_axis, angle_for_axis( m_start, current, m_axis ) ) );
+       }
 }
 
 void SetAxis( const Vector3& axis ){
@@ -281,20 +300,43 @@ class TranslateAxis : public Manipulatable
 Vector3 m_start;
 Vector3 m_axis;
 Translatable& m_translatable;
+AABB m_bounds;
 public:
 TranslateAxis( Translatable& translatable )
        : m_translatable( translatable ){
 }
-void Construct( const Matrix4& device2manip, const float x, const float y ){
+void Construct( const Matrix4& device2manip, const float x, const float y, const AABB bounds, const Vector3 transform_origin ){
        point_on_axis( m_start, m_axis, device2manip, x, y );
+       m_bounds = bounds;
 }
-void Transform( const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y ){
+void Transform( const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y, const bool snap, const bool snapbbox ){
        Vector3 current;
        point_on_axis( current, m_axis, device2manip, x, y );
        current = vector3_scaled( m_axis, distance_for_axis( m_start, current, m_axis ) );
 
        translation_local2object( current, current, manip2object );
-       vector3_snap( current, GetSnapGridSize() );
+       if( snapbbox ){
+               float grid = GetSnapGridSize();
+               Vector3 maxs( m_bounds.origin + m_bounds.extents );
+               Vector3 mins( m_bounds.origin - m_bounds.extents );
+//             globalOutputStream() << "current: " << current << "\n";
+               for( std::size_t i = 0; i < 3; ++i ){
+                       if( m_axis[i] != 0.f ){
+                               float snapto1 = float_snapped( maxs[i] + current[i] , grid );
+                               float snapto2 = float_snapped( mins[i] + current[i] , grid );
+
+                               float dist1 = fabs( fabs( maxs[i] + current[i] ) - fabs( snapto1 ) );
+                               float dist2 = fabs( fabs( mins[i] + current[i] ) - fabs( snapto2 ) );
+
+//                             globalOutputStream() << "maxs[i] + current[i]: " << maxs[i] + current[i]  << "    snapto1: " << snapto1 << "   dist1: " << dist1 << "\n";
+//                             globalOutputStream() << "mins[i] + current[i]: " << mins[i] + current[i]  << "    snapto2: " << snapto2 << "   dist2: " << dist2 << "\n";
+                               current[i] = dist2 > dist1 ? snapto1 - maxs[i] : snapto2 - mins[i];
+                       }
+               }
+       }
+       else{
+               vector3_snap( current, GetSnapGridSize() );
+       }
 
        m_translatable.translate( current );
 }
@@ -309,26 +351,57 @@ class TranslateFree : public Manipulatable
 private:
 Vector3 m_start;
 Translatable& m_translatable;
+AABB m_bounds;
 public:
 TranslateFree( Translatable& translatable )
        : m_translatable( translatable ){
 }
-void Construct( const Matrix4& device2manip, const float x, const float y ){
+void Construct( const Matrix4& device2manip, const float x, const float y, const AABB bounds, const Vector3 transform_origin ){
        point_on_plane( m_start, device2manip, x, y );
+       m_bounds = bounds;
 }
-void Transform( const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y ){
+void Transform( const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y, const bool snap, const bool snapbbox ){
        Vector3 current;
        point_on_plane( current, device2manip, x, y );
        current = vector3_subtracted( current, m_start );
 
+       if( snap ){
+               for ( std::size_t i = 0; i < 3 ; ++i ){
+                       if( fabs( current[i] ) >= fabs( current[(i + 1) % 3] ) ){
+                               current[(i + 1) % 3] = 0.0f;
+                       }
+                       else{
+                               current[i] = 0.0f;
+                       }
+               }
+       }
+
        translation_local2object( current, current, manip2object );
-       vector3_snap( current, GetSnapGridSize() );
+       if( snapbbox ){
+               float grid = GetSnapGridSize();
+               Vector3 maxs( m_bounds.origin + m_bounds.extents );
+               Vector3 mins( m_bounds.origin - m_bounds.extents );
+               //globalOutputStream() << "current: " << current << "\n";
+               for( std::size_t i = 0; i < 3; ++i ){
+                       if( fabs( current[i] ) > 0.000001f ){
+                               float snapto1 = float_snapped( maxs[i] + current[i] , grid );
+                               float snapto2 = float_snapped( mins[i] + current[i] , grid );
+
+                               float dist1 = fabs( fabs( maxs[i] + current[i] ) - fabs( snapto1 ) );
+                               float dist2 = fabs( fabs( mins[i] + current[i] ) - fabs( snapto2 ) );
+
+                               current[i] = dist2 > dist1 ? snapto1 - maxs[i] : snapto2 - mins[i];
+                       }
+               }
+       }
+       else{
+               vector3_snap( current, GetSnapGridSize() );
+       }
 
        m_translatable.translate( current );
 }
 };
 
-
 class Scalable
 {
 public:
@@ -343,14 +416,26 @@ private:
 Vector3 m_start;
 Vector3 m_axis;
 Scalable& m_scalable;
+
+Vector3 m_choosen_extent;
+AABB m_bounds;
+
 public:
 ScaleAxis( Scalable& scalable )
        : m_scalable( scalable ){
 }
-void Construct( const Matrix4& device2manip, const float x, const float y ){
+void Construct( const Matrix4& device2manip, const float x, const float y, const AABB bounds, const Vector3 transform_origin ){
        point_on_axis( m_start, m_axis, device2manip, x, y );
+
+       m_choosen_extent = Vector3(
+                                       std::max( bounds.origin[0] + bounds.extents[0] - transform_origin[0], - bounds.origin[0] + bounds.extents[0] + transform_origin[0] ),
+                                       std::max( bounds.origin[1] + bounds.extents[1] - transform_origin[1], - bounds.origin[1] + bounds.extents[1] + transform_origin[1] ),
+                                       std::max( bounds.origin[2] + bounds.extents[2] - transform_origin[2], - bounds.origin[2] + bounds.extents[2] + transform_origin[2] )
+                                                       );
+       m_bounds = bounds;
 }
-void Transform( const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y ){
+void Transform( const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y, const bool snap, const bool snapbbox ){
+       //globalOutputStream() << "manip2object: " << manip2object << "  device2manip: " << device2manip << "  x: " << x << "  y:" << y <<"\n";
        Vector3 current;
        point_on_axis( current, m_axis, device2manip, x, y );
        Vector3 delta = vector3_subtracted( current, m_start );
@@ -358,12 +443,36 @@ void Transform( const Matrix4& manip2object, const Matrix4& device2manip, const
        translation_local2object( delta, delta, manip2object );
        vector3_snap( delta, GetSnapGridSize() );
 
-       Vector3 start( vector3_snapped( m_start, GetSnapGridSize() ) );
+       Vector3 start( vector3_snapped( m_start, GetSnapGridSize() != 0.0f ? GetSnapGridSize() : 0.001f ) );
+       for ( std::size_t i = 0; i < 3 ; ++i ){ //prevent snapping to 0 with big gridsize
+               if( float_snapped( m_start[i], 0.001f ) != 0.0f && start[i] == 0.0f ){
+                       start[i] = GetSnapGridSize();
+               }
+       }
+       //globalOutputStream() << "m_start: " << m_start << "   start: " << start << "   delta: " << delta <<"\n";
        Vector3 scale(
                start[0] == 0 ? 1 : 1 + delta[0] / start[0],
                start[1] == 0 ? 1 : 1 + delta[1] / start[1],
                start[2] == 0 ? 1 : 1 + delta[2] / start[2]
                );
+
+       for( std::size_t i = 0; i < 3; i++ ){
+               if( m_choosen_extent[i] > 0.0625f && m_axis[i] != 0.f ){ //epsilon to prevent super high scale for set of models, having really small extent, formed by origins
+                       scale[i] = ( m_choosen_extent[i] + delta[i] ) / m_choosen_extent[i];
+                       if( snapbbox ){
+                               float snappdwidth = float_snapped( scale[i] * m_bounds.extents[i] * 2.f, GetSnapGridSize() );
+                               scale[i] = snappdwidth / ( m_bounds.extents[i] * 2.f );
+                       }
+               }
+       }
+       if( snap ){
+               for( std::size_t i = 0; i < 3; i++ ){
+                       if( scale[i] == 1.0f ){
+                               scale[i] = vector3_dot( scale, m_axis );
+                       }
+               }
+       }
+       //globalOutputStream() << "scale: " << scale <<"\n";
        m_scalable.scale( scale );
 }
 
@@ -377,14 +486,25 @@ class ScaleFree : public Manipulatable
 private:
 Vector3 m_start;
 Scalable& m_scalable;
+
+Vector3 m_choosen_extent;
+AABB m_bounds;
+
 public:
 ScaleFree( Scalable& scalable )
        : m_scalable( scalable ){
 }
-void Construct( const Matrix4& device2manip, const float x, const float y ){
+void Construct( const Matrix4& device2manip, const float x, const float y, const AABB bounds, const Vector3 transform_origin ){
        point_on_plane( m_start, device2manip, x, y );
+
+       m_choosen_extent = Vector3(
+                                       std::max( bounds.origin[0] + bounds.extents[0] - transform_origin[0], - bounds.origin[0] + bounds.extents[0] + transform_origin[0] ),
+                                       std::max( bounds.origin[1] + bounds.extents[1] - transform_origin[1], - bounds.origin[1] + bounds.extents[1] + transform_origin[1] ),
+                                       std::max( bounds.origin[2] + bounds.extents[2] - transform_origin[2], - bounds.origin[2] + bounds.extents[2] + transform_origin[2] )
+                                                       );
+       m_bounds = bounds;
 }
-void Transform( const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y ){
+void Transform( const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y, const bool snap, const bool snapbbox ){
        Vector3 current;
        point_on_plane( current, device2manip, x, y );
        Vector3 delta = vector3_subtracted( current, m_start );
@@ -392,12 +512,45 @@ void Transform( const Matrix4& manip2object, const Matrix4& device2manip, const
        translation_local2object( delta, delta, manip2object );
        vector3_snap( delta, GetSnapGridSize() );
 
-       Vector3 start( vector3_snapped( m_start, GetSnapGridSize() ) );
+       Vector3 start( vector3_snapped( m_start, GetSnapGridSize() != 0.0f ? GetSnapGridSize() : 0.001f ) );
+       for ( std::size_t i = 0; i < 3 ; ++i ){ //prevent snapping to 0 with big gridsize
+               if( float_snapped( m_start[i], 0.001f ) != 0.0f && start[i] == 0.0f ){
+                       start[i] = GetSnapGridSize();
+               }
+       }
        Vector3 scale(
                start[0] == 0 ? 1 : 1 + delta[0] / start[0],
                start[1] == 0 ? 1 : 1 + delta[1] / start[1],
                start[2] == 0 ? 1 : 1 + delta[2] / start[2]
                );
+
+       //globalOutputStream() << "m_start: " << m_start << "   start: " << start << "   delta: " << delta <<"\n";
+       for( std::size_t i = 0; i < 3; i++ ){
+               if( m_choosen_extent[i] > 0.0625f ){
+                       scale[i] = ( m_choosen_extent[i] + delta[i] ) / m_choosen_extent[i];
+                       if( snapbbox && start[i] != 0.f ){
+                               float snappdwidth = float_snapped( scale[i] * m_bounds.extents[i] * 2.f, GetSnapGridSize() );
+                               scale[i] = snappdwidth / ( m_bounds.extents[i] * 2.f );
+                       }
+               }
+       }
+       //globalOutputStream() << "pre snap scale: " << scale <<"\n";
+       if( snap ){
+               float bestscale = scale[0];
+               for( std::size_t i = 1; i < 3; i++ ){
+                       //if( fabs( 1.0f - fabs( scale[i] ) ) > fabs( 1.0f - fabs( bestscale ) ) ){
+                       if( fabs( scale[i] ) > fabs( bestscale ) && scale[i] != 1.0f ){ //harder to scale down with this, but glitchier with upper one
+                               bestscale = scale[i];
+                       }
+                       //globalOutputStream() << "bestscale: " << bestscale <<"\n";
+               }
+               for( std::size_t i = 0; i < 3; i++ ){
+                       if( start[i] != 0.0f ){ // !!!!check grid == 0 case
+                               scale[i] = ( scale[i] < 0.0f ) ? -fabs( bestscale ) : fabs( bestscale );
+                       }
+               }
+       }
+       //globalOutputStream() << "scale: " << scale <<"\n";
        m_scalable.scale( scale );
 }
 };
@@ -903,6 +1056,8 @@ RotateManipulator( Rotatable& rotatable, std::size_t segments, float radius ) :
 
        draw_circle( segments, radius * 1.15f, m_circle_screen.m_vertices.data(), RemapXYZ() );
        draw_circle( segments, radius, m_circle_sphere.m_vertices.data(), RemapXYZ() );
+
+       m_selectable_sphere.setSelected( true );
 }
 
 
@@ -1736,6 +1891,59 @@ bool Scene_forEachPlaneSelectable_selectPlanes( scene::Graph& graph, Selector& s
        return !selectedPlanes.empty();
 }
 
+
+#include "brush.h"
+
+class TestedBrushFacesSelectVeritces : public scene::Graph::Walker
+{
+SelectionTest& m_test;
+public:
+TestedBrushFacesSelectVeritces( SelectionTest& test )
+       : m_test( test ){
+}
+bool pre( const scene::Path& path, scene::Instance& instance ) const {
+       if ( path.top().get().visible() ) {
+               Selectable* selectable = Instance_getSelectable( instance );
+               if ( selectable != 0 && selectable->isSelected() ) {
+                       BrushInstance* brushInstance = Instance_getBrush( instance );
+                       if ( brushInstance != 0 ) {
+                               brushInstance->selectVerticesOnTestedFaces( m_test );
+                       }
+               }
+       }
+       return true;
+}
+};
+
+void Scene_forEachTestedBrushFace_selectVertices( scene::Graph& graph, SelectionTest& test ){
+       graph.traverse( TestedBrushFacesSelectVeritces( test ) );
+}
+
+class BrushPlanesSelectVeritces : public scene::Graph::Walker
+{
+SelectionTest& m_test;
+public:
+BrushPlanesSelectVeritces( SelectionTest& test )
+       : m_test( test ){
+}
+bool pre( const scene::Path& path, scene::Instance& instance ) const {
+       if ( path.top().get().visible() ) {
+               Selectable* selectable = Instance_getSelectable( instance );
+               if ( selectable != 0 && selectable->isSelected() ) {
+                       BrushInstance* brushInstance = Instance_getBrush( instance );
+                       if ( brushInstance != 0 ) {
+                               brushInstance->selectVerticesOnPlanes( m_test );
+                       }
+               }
+       }
+       return true;
+}
+};
+
+void Scene_forEachBrushPlane_selectVertices( scene::Graph& graph, SelectionTest& test ){
+       graph.traverse( BrushPlanesSelectVeritces( test ) );
+}
+
 void Scene_Translate_Component_Selected( scene::Graph& graph, const Vector3& translation );
 void Scene_Translate_Selected( scene::Graph& graph, const Vector3& translation );
 void Scene_TestSelect_Primitive( Selector& selector, SelectionTest& test, const VolumeTest& volume );
@@ -2399,16 +2607,54 @@ std::list<Selectable*>& best(){
 }
 };
 
+class DeepBestSelector : public Selector
+{
+SelectionIntersection m_intersection;
+Selectable* m_selectable;
+SelectionIntersection m_bestIntersection;
+std::list<Selectable*> m_bestSelectable;
+public:
+DeepBestSelector() : m_bestIntersection( SelectionIntersection() ), m_bestSelectable( 0 ){
+}
+
+void pushSelectable( Selectable& selectable ){
+       m_intersection = SelectionIntersection();
+       m_selectable = &selectable;
+}
+void popSelectable(){
+       if ( m_intersection.equalEpsilon( m_bestIntersection, 0.25f, 2.f ) ) {
+               m_bestSelectable.push_back( m_selectable );
+               m_bestIntersection = m_intersection;
+       }
+       else if ( m_intersection < m_bestIntersection ) {
+               m_bestSelectable.clear();
+               m_bestSelectable.push_back( m_selectable );
+               m_bestIntersection = m_intersection;
+       }
+       m_intersection = SelectionIntersection();
+}
+void addIntersection( const SelectionIntersection& intersection ){
+       assign_if_closer( m_intersection, intersection );
+}
+
+std::list<Selectable*>& best(){
+       return m_bestSelectable;
+}
+};
+
+bool g_bAltDragManipulatorResize = false; //+select primitives in component modes
+bool g_bTmpComponentMode = false;
+
 class DragManipulator : public Manipulator
 {
 TranslateFree m_freeResize;
 TranslateFree m_freeDrag;
 ResizeTranslatable m_resize;
 DragTranslatable m_drag;
-SelectableBool m_dragSelectable;
+SelectableBool m_dragSelectable; //drag already selected stuff
 public:
 
-bool m_selected;
+bool m_selected; //selected temporally for drag
 
 DragManipulator() : m_freeResize( m_resize ), m_freeDrag( m_drag ), m_selected( false ){
 }
@@ -2428,12 +2674,38 @@ void testSelect( const View& view, const Matrix4& pivot2world ){
                Scene_TestSelect_Primitive( booleanSelector, test, view );
 
                if ( booleanSelector.isSelected() ) {
-                       selector.addSelectable( SelectionIntersection( 0, 0 ), &m_dragSelectable );
-                       m_selected = false;
+                       if( g_bAltDragManipulatorResize ){
+                               DeepBestSelector deepSelector;
+                               Scene_TestSelect_Component_Selected( deepSelector, test, view, SelectionSystem::eVertex );
+                               for ( std::list<Selectable*>::iterator i = deepSelector.best().begin(); i != deepSelector.best().end(); ++i )
+                               {
+                                       if ( !( *i )->isSelected() ) {
+                                               GlobalSelectionSystem().setSelectedAllComponents( false );
+                                       }
+                                       selector.addSelectable( SelectionIntersection( 0, 0 ), ( *i ) );
+                                       m_selected = true;
+                                       m_dragSelectable.setSelected( false );
+                               }
+                               if( deepSelector.best().empty() ){
+                                       Scene_forEachTestedBrushFace_selectVertices( GlobalSceneGraph(), test );        //drag clicked face
+                                       //Scene_forEachBrushPlane_selectVertices( GlobalSceneGraph(), test );
+                                       m_selected = true;
+                               }
+                       }
+                       else{
+                               selector.addSelectable( SelectionIntersection( 0, 0 ), &m_dragSelectable );
+                               m_selected = false;
+                       }
                }
                else
                {
-                       m_selected = Scene_forEachPlaneSelectable_selectPlanes( GlobalSceneGraph(), selector, test );
+                       if( g_bAltDragManipulatorResize ){
+                               Scene_forEachBrushPlane_selectVertices( GlobalSceneGraph(), test );
+                               m_selected = true;
+                       }
+                       else{
+                               m_selected = Scene_forEachPlaneSelectable_selectPlanes( GlobalSceneGraph(), selector, test );
+                       }
                }
        }
        else
@@ -2449,12 +2721,16 @@ void testSelect( const View& view, const Matrix4& pivot2world ){
                        selector.addSelectable( SelectionIntersection( 0, 0 ), ( *i ) );
                        m_dragSelectable.setSelected( true );
                }
+               if( GlobalSelectionSystem().countSelectedComponents() != 0 ){
+                       m_dragSelectable.setSelected( true );
+               }
        }
 
        for ( SelectionPool::iterator i = selector.begin(); i != selector.end(); ++i )
        {
                ( *i ).second->setSelected( true );
        }
+       g_bTmpComponentMode = m_selected;
 }
 
 void setSelected( bool select ){
@@ -2536,6 +2812,7 @@ Rotation m_rotation;
 Scale m_scale;
 public:
 static Shader* m_state;
+bool m_bPreferPointEntsIn2D;
 private:
 EManipulatorMode m_manipulator_mode;
 Manipulator* m_manipulator;
@@ -2561,8 +2838,13 @@ selection_t m_component_selection;
 Signal1<const Selectable&> m_selectionChanged_callbacks;
 
 void ConstructPivot() const;
+void setCustomPivotOrigin( Vector3& point ) const;
+public:
+AABB getSelectionAABB() const;
+private:
 mutable bool m_pivotChanged;
 bool m_pivot_moving;
+mutable bool m_pivotIsCustom;
 
 void Scene_TestSelect( Selector& selector, SelectionTest& test, const View& view, SelectionSystem::EMode mode, SelectionSystem::EComponentMode componentMode );
 
@@ -2579,9 +2861,12 @@ enum EModifier
        eToggle,
        eReplace,
        eCycle,
+       eSelect,
+       eDeselect,
 };
 
 RadiantSelectionSystem() :
+       m_bPreferPointEntsIn2D( true ),
        m_undo_begun( false ),
        m_mode( ePrimitive ),
        m_componentmode( eDefault ),
@@ -2591,7 +2876,8 @@ RadiantSelectionSystem() :
        m_rotate_manipulator( *this, 8, 64 ),
        m_scale_manipulator( *this, 0, 64 ),
        m_pivotChanged( false ),
-       m_pivot_moving( false ){
+       m_pivot_moving( false ),
+       m_pivotIsCustom( false ){
        SetManipulatorMode( eTranslate );
        pivotChanged();
        addSelectionChangeCallback( PivotChangedSelectionCaller( *this ) );
@@ -2623,6 +2909,7 @@ EComponentMode ComponentMode() const {
        return m_componentmode;
 }
 void SetManipulatorMode( EManipulatorMode mode ){
+       m_pivotIsCustom = false;
        m_manipulator_mode = mode;
        switch ( m_manipulator_mode )
        {
@@ -2750,7 +3037,7 @@ bool SelectManipulator( const View& view, const float device_point[2], const flo
 
                        Matrix4 device2manip;
                        ConstructDevice2Manip( device2manip, m_pivot2world_start, view.GetModelview(), view.GetProjection(), view.GetViewport() );
-                       m_manipulator->GetManipulatable()->Construct( device2manip, device_point[0], device_point[1] );
+                       m_manipulator->GetManipulatable()->Construct( device2manip, device_point[0], device_point[1], getSelectionAABB(), vector4_to_vector3( GetPivot2World().t() ) );
 
                        m_undo_begun = false;
                }
@@ -2771,18 +3058,29 @@ void deselectAll(){
        }
 }
 
+void deselectComponentsOrAll( bool components ){
+       if ( components ) {
+               setSelectedAllComponents( false );
+       }
+       else
+       {
+               deselectAll();
+       }
+}
+
 void SelectPoint( const View& view, const float device_point[2], const float device_epsilon[2], RadiantSelectionSystem::EModifier modifier, bool face ){
+       //globalOutputStream() << device_point[0] << "   " << device_point[1] << "\n";
        ASSERT_MESSAGE( fabs( device_point[0] ) <= 1.0f && fabs( device_point[1] ) <= 1.0f, "point-selection error" );
+
        if ( modifier == eReplace ) {
-               if ( face ) {
-                       setSelectedAllComponents( false );
-               }
-               else
-               {
-                       deselectAll();
-               }
+               deselectComponentsOrAll( face );
        }
-
+/*
+//nothingSelected() doesn't consider faces, selected in non-component mode, m
+       if ( modifier == eCycle && nothingSelected() ){
+               modifier = eReplace;
+       }
+*/
   #if defined ( DEBUG_SELECTION )
        g_render_clipped.destroy();
   #endif
@@ -2793,52 +3091,61 @@ void SelectPoint( const View& view, const float device_point[2], const float dev
 
                SelectionVolume volume( scissored );
                SelectionPool selector;
-               if ( face ) {
-                       Scene_TestSelect_Component( selector, volume, scissored, eFace );
-               }
-               else
-               {
-                       Scene_TestSelect( selector, volume, scissored, Mode(), ComponentMode() );
-               }
+               SelectionPool selector_point_ents;
+               const bool prefer_point_ents = m_bPreferPointEntsIn2D && Mode() == ePrimitive && !view.fill() && !face
+                       && ( modifier == RadiantSelectionSystem::eReplace || modifier == RadiantSelectionSystem::eSelect || modifier == RadiantSelectionSystem::eDeselect );
 
-               if ( !selector.failed() ) {
+               if( prefer_point_ents ){
+                       Scene_TestSelect( selector_point_ents, volume, scissored, eEntity, ComponentMode() );
+               }
+               if( prefer_point_ents && !selector_point_ents.failed() ){
                        switch ( modifier )
                        {
-                       case RadiantSelectionSystem::eToggle:
-                       {
-                               SelectableSortedSet::iterator best = selector.begin();
-                               // toggle selection of the object with least depth
-                               if ( ( *best ).second->isSelected() ) {
-                                       ( *best ).second->setSelected( false );
-                               }
-                               else{
-                                       ( *best ).second->setSelected( true );
-                               }
-                       }
-                       break;
                        // if cycle mode not enabled, enable it
                        case RadiantSelectionSystem::eReplace:
                        {
                                // select closest
-                               ( *selector.begin() ).second->setSelected( true );
+                               ( *selector_point_ents.begin() ).second->setSelected( true );
                        }
                        break;
-                       // select the next object in the list from the one already selected
-                       case RadiantSelectionSystem::eCycle:
+                       case RadiantSelectionSystem::eSelect:
                        {
-                               SelectionPool::iterator i = selector.begin();
-                               while ( i != selector.end() )
+                               SelectionPool::iterator best = selector_point_ents.begin();
+                               if( !( *best ).second->isSelected() ){
+                                       ( *best ).second->setSelected( true );
+                               }
+                               SelectionPool::iterator i = best;
+                               ++i;
+                               while ( i != selector_point_ents.end() )
                                {
-                                       if ( ( *i ).second->isSelected() ) {
-                                               ( *i ).second->setSelected( false );
-                                               ++i;
-                                               if ( i != selector.end() ) {
-                                                       i->second->setSelected( true );
+                                       if( ( *i ).first.equalEpsilon( ( *best ).first, 0.25f, 0.000001f ) ){
+                                               if( !( *i ).second->isSelected() ){
+                                                       ( *i ).second->setSelected( true );
                                                }
-                                               else
-                                               {
-                                                       selector.begin()->second->setSelected( true );
+                                       }
+                                       else{
+                                               break;
+                                       }
+                                       ++i;
+                               }
+                       }
+                       break;
+                       case RadiantSelectionSystem::eDeselect:
+                       {
+                               SelectionPool::iterator best = selector_point_ents.begin();
+                               if( ( *best ).second->isSelected() ){
+                                       ( *best ).second->setSelected( false );
+                               }
+                               SelectionPool::iterator i = best;
+                               ++i;
+                               while ( i != selector_point_ents.end() )
+                               {
+                                       if( ( *i ).first.equalEpsilon( ( *best ).first, 0.25f, 0.000001f ) ){
+                                               if( ( *i ).second->isSelected() ){
+                                                       ( *i ).second->setSelected( false );
                                                }
+                                       }
+                                       else{
                                                break;
                                        }
                                        ++i;
@@ -2849,19 +3156,191 @@ void SelectPoint( const View& view, const float device_point[2], const float dev
                                break;
                        }
                }
+               else{
+                       if ( face ){
+                               Scene_TestSelect_Component( selector, volume, scissored, eFace );
+                       }
+                       else{
+                               Scene_TestSelect( selector, volume, scissored, g_bAltDragManipulatorResize ? ePrimitive : Mode(), ComponentMode() );
+                       }
+
+                       if ( !selector.failed() ) {
+                               switch ( modifier )
+                               {
+                               case RadiantSelectionSystem::eToggle:
+                               {
+                                       SelectableSortedSet::iterator best = selector.begin();
+                                       // toggle selection of the object with least depth
+                                       if ( ( *best ).second->isSelected() ) {
+                                               ( *best ).second->setSelected( false );
+                                       }
+                                       else{
+                                               ( *best ).second->setSelected( true );
+                                       }
+                               }
+                               break;
+                               // if cycle mode not enabled, enable it
+                               case RadiantSelectionSystem::eReplace:
+                               {
+                                       // select closest
+                                       ( *selector.begin() ).second->setSelected( true );
+                               }
+                               break;
+                               // select the next object in the list from the one already selected
+                               case RadiantSelectionSystem::eCycle:
+                               {
+                                       bool CycleSelectionOccured = false;
+                                       SelectionPool::iterator i = selector.begin();
+                                       while ( i != selector.end() )
+                                       {
+                                               if ( ( *i ).second->isSelected() ) {
+                                                       deselectComponentsOrAll( face );
+                                                       ++i;
+                                                       if ( i != selector.end() ) {
+                                                               i->second->setSelected( true );
+                                                       }
+                                                       else
+                                                       {
+                                                               selector.begin()->second->setSelected( true );
+                                                       }
+                                                       CycleSelectionOccured = true;
+                                                       break;
+                                               }
+                                               ++i;
+                                       }
+                                       if( !CycleSelectionOccured ){
+                                               deselectComponentsOrAll( face );
+                                               ( *selector.begin() ).second->setSelected( true );
+                                       }
+                               }
+                               break;
+                               case RadiantSelectionSystem::eSelect:
+                               {
+                                       SelectionPool::iterator best = selector.begin();
+                                       if( !( *best ).second->isSelected() ){
+                                               ( *best ).second->setSelected( true );
+                                       }
+                                       SelectionPool::iterator i = best;
+                                       ++i;
+                                       while ( i != selector.end() )
+                                       {
+                                               if( ( *i ).first.equalEpsilon( ( *best ).first, 0.25f, 0.000001f ) ){
+                                                       if( !( *i ).second->isSelected() ){
+                                                               ( *i ).second->setSelected( true );
+                                                       }
+                                               }
+                                               else{
+                                                       break;
+                                               }
+                                               ++i;
+                                       }
+                               }
+                               break;
+                               case RadiantSelectionSystem::eDeselect:
+                               {
+                                       SelectionPool::iterator best = selector.begin();
+                                       if( ( *best ).second->isSelected() ){
+                                               ( *best ).second->setSelected( false );
+                                       }
+                                       SelectionPool::iterator i = best;
+                                       ++i;
+                                       while ( i != selector.end() )
+                                       {
+                                               if( ( *i ).first.equalEpsilon( ( *best ).first, 0.25f, 0.000001f ) ){
+                                                       if( ( *i ).second->isSelected() ){
+                                                               ( *i ).second->setSelected( false );
+                                                       }
+                                               }
+                                               else{
+                                                       break;
+                                               }
+                                               ++i;
+                                       }
+                               }
+                               break;
+                               default:
+                                       break;
+                               }
+                       }
+                       else if( modifier == eCycle ){
+                               deselectComponentsOrAll( face );
+                       }
+               }
        }
 }
 
-void SelectArea( const View& view, const float device_point[2], const float device_delta[2], RadiantSelectionSystem::EModifier modifier, bool face ){
-       if ( modifier == eReplace ) {
-               if ( face ) {
-                       setSelectedAllComponents( false );
+bool SelectPoint_InitPaint( const View& view, const float device_point[2], const float device_epsilon[2], bool face ){
+       ASSERT_MESSAGE( fabs( device_point[0] ) <= 1.0f && fabs( device_point[1] ) <= 1.0f, "point-selection error" );
+  #if defined ( DEBUG_SELECTION )
+       g_render_clipped.destroy();
+  #endif
+
+       {
+               View scissored( view );
+               ConstructSelectionTest( scissored, SelectionBoxForPoint( device_point, device_epsilon ) );
+
+               SelectionVolume volume( scissored );
+               SelectionPool selector;
+               SelectionPool selector_point_ents;
+               const bool prefer_point_ents = m_bPreferPointEntsIn2D && Mode() == ePrimitive && !view.fill() && !face;
+
+               if( prefer_point_ents ){
+                       Scene_TestSelect( selector_point_ents, volume, scissored, eEntity, ComponentMode() );
                }
-               else
-               {
-                       deselectAll();
+               if( prefer_point_ents && !selector_point_ents.failed() ){
+                       SelectableSortedSet::iterator best = selector_point_ents.begin();
+                       const bool wasSelected = ( *best ).second->isSelected();
+                       ( *best ).second->setSelected( !wasSelected );
+                       SelectableSortedSet::iterator i = best;
+                       ++i;
+                       while ( i != selector_point_ents.end() )
+                       {
+                               if( ( *i ).first.equalEpsilon( ( *best ).first, 0.25f, 0.000001f ) ){
+                                       ( *i ).second->setSelected( !wasSelected );
+                               }
+                               else{
+                                       break;
+                               }
+                               ++i;
+                       }
+                       return !wasSelected;
+               }
+               else{//do primitives, if ents failed
+                       if ( face ){
+                               Scene_TestSelect_Component( selector, volume, scissored, eFace );
+                       }
+                       else{
+                               Scene_TestSelect( selector, volume, scissored, g_bAltDragManipulatorResize ? ePrimitive : Mode(), ComponentMode() );
+                       }
+                       if ( !selector.failed() ){
+                               SelectableSortedSet::iterator best = selector.begin();
+                               const bool wasSelected = ( *best ).second->isSelected();
+                               ( *best ).second->setSelected( !wasSelected );
+                               SelectableSortedSet::iterator i = best;
+                               ++i;
+                               while ( i != selector.end() )
+                               {
+                                       if( ( *i ).first.equalEpsilon( ( *best ).first, 0.25f, 0.000001f ) ){
+                                               ( *i ).second->setSelected( !wasSelected );
+                                       }
+                                       else{
+                                               break;
+                                       }
+                                       ++i;
+                               }
+                               return !wasSelected;
+                       }
+                       else{
+                               return true;
+                       }
                }
        }
+}
+
+void SelectArea( const View& view, const float device_point[2], const float device_delta[2], RadiantSelectionSystem::EModifier modifier, bool face ){
+       if ( modifier == eReplace ) {
+               deselectComponentsOrAll( face );
+       }
 
   #if defined ( DEBUG_SELECTION )
        g_render_clipped.destroy();
@@ -2955,7 +3434,12 @@ void outputScale( TextOutputStream& ostream ){
        ostream << " -scale " << m_scale.x() << " " << m_scale.y() << " " << m_scale.z();
 }
 
-void rotateSelected( const Quaternion& rotation ){
+void rotateSelected( const Quaternion& rotation, bool snapOrigin ){
+       if( snapOrigin && !m_pivotIsCustom ){
+               m_pivot2world.tx() = float_snapped( m_pivot2world.tx(), GetSnapGridSize() );
+               m_pivot2world.ty() = float_snapped( m_pivot2world.ty(), GetSnapGridSize() );
+               m_pivot2world.tz() = float_snapped( m_pivot2world.tz(), GetSnapGridSize() );
+       }
        startMove();
        rotate( rotation );
        freezeTransforms();
@@ -2971,7 +3455,7 @@ void scaleSelected( const Vector3& scaling ){
        freezeTransforms();
 }
 
-void MoveSelected( const View& view, const float device_point[2] ){
+void MoveSelected( const View& view, const float device_point[2], bool snap, bool snapbbox ){
        if ( m_manipulator->isSelected() ) {
                if ( !m_undo_begun ) {
                        m_undo_begun = true;
@@ -2980,15 +3464,15 @@ void MoveSelected( const View& view, const float device_point[2] ){
 
                Matrix4 device2manip;
                ConstructDevice2Manip( device2manip, m_pivot2world_start, view.GetModelview(), view.GetProjection(), view.GetViewport() );
-               m_manipulator->GetManipulatable()->Transform( m_manip2pivot_start, device2manip, device_point[0], device_point[1] );
+               m_manipulator->GetManipulatable()->Transform( m_manip2pivot_start, device2manip, device_point[0], device_point[1], snap, snapbbox );
        }
 }
 
 /// \todo Support view-dependent nudge.
 void NudgeManipulator( const Vector3& nudge, const Vector3& view ){
-       if ( ManipulatorMode() == eTranslate || ManipulatorMode() == eDrag ) {
+//     if ( ManipulatorMode() == eTranslate || ManipulatorMode() == eDrag ) {
                translateSelected( nudge );
-       }
+//     }
 }
 
 void endMove();
@@ -3037,7 +3521,7 @@ inline RadiantSelectionSystem& getSelectionSystem(){
 }
 }
 
-
+#include "map.h"
 
 class testselect_entity_visible : public scene::Graph::Walker
 {
@@ -3048,6 +3532,10 @@ testselect_entity_visible( Selector& selector, SelectionTest& test )
        : m_selector( selector ), m_test( test ){
 }
 bool pre( const scene::Path& path, scene::Instance& instance ) const {
+       if( path.top().get_pointer() == Map_GetWorldspawn( g_map ) ||
+               node_is_group( path.top().get() ) ){
+               return false;
+       }
        Selectable* selectable = Instance_getSelectable( instance );
        if ( selectable != 0
                 && Node_isEntity( path.top() ) ) {
@@ -3195,7 +3683,13 @@ void RadiantSelectionSystem::endMove(){
 
        if ( Mode() == ePrimitive ) {
                if ( ManipulatorMode() == eDrag ) {
-                       Scene_SelectAll_Component( false, SelectionSystem::eFace );
+                       g_bTmpComponentMode = false;
+                       if( g_bAltDragManipulatorResize ){
+                               Scene_SelectAll_Component( false, SelectionSystem::eVertex );
+                       }
+                       else{
+                               Scene_SelectAll_Component( false, SelectionSystem::eFace );
+                       }
                }
        }
 
@@ -3315,7 +3809,7 @@ inline void pivot_for_node( Matrix4& pivot, scene::Node& node, scene::Instance&
 #endif
 
 void RadiantSelectionSystem::ConstructPivot() const {
-       if ( !m_pivotChanged || m_pivot_moving ) {
+       if ( !m_pivotChanged || m_pivot_moving || m_pivotIsCustom ) {
                return;
        }
        m_pivotChanged = false;
@@ -3335,7 +3829,8 @@ void RadiantSelectionSystem::ConstructPivot() const {
                        m_object_pivot = bounds.origin;
                }
 
-               vector3_snap( m_object_pivot, GetSnapGridSize() );
+               //vector3_snap( m_object_pivot, GetSnapGridSize() );
+               //globalOutputStream() << m_object_pivot << "\n";
                m_pivot2world = matrix4_translation_for_vec3( m_object_pivot );
 
                switch ( m_manipulator_mode )
@@ -3366,6 +3861,86 @@ void RadiantSelectionSystem::ConstructPivot() const {
        }
 }
 
+void RadiantSelectionSystem::setCustomPivotOrigin( Vector3& point ) const {
+       if ( !nothingSelected() && ( m_manipulator_mode == eTranslate || m_manipulator_mode == eRotate || m_manipulator_mode == eScale ) ) {
+               AABB bounds;
+               if ( Mode() == eComponent ) {
+                       Scene_BoundsSelectedComponent( GlobalSceneGraph(), bounds );
+               }
+               else
+               {
+                       Scene_BoundsSelected( GlobalSceneGraph(), bounds );
+               }
+               //globalOutputStream() << point << "\n";
+               for( std::size_t i = 0; i < 3; i++ ){
+                       if( point[i] < 900000.0f ){
+                               float bestsnapDist = fabs( bounds.origin[i] - point[i] );
+                               float bestsnapTo = bounds.origin[i];
+                               float othersnapDist = fabs( bounds.origin[i] + bounds.extents[i] - point[i] );
+                               if( othersnapDist < bestsnapDist ){
+                                       bestsnapDist = othersnapDist;
+                                       bestsnapTo = bounds.origin[i] + bounds.extents[i];
+                               }
+                               othersnapDist = fabs( bounds.origin[i] - bounds.extents[i] - point[i] );
+                               if( othersnapDist < bestsnapDist ){
+                                       bestsnapDist = othersnapDist;
+                                       bestsnapTo = bounds.origin[i] - bounds.extents[i];
+                               }
+                               othersnapDist = fabs( float_snapped( point[i], GetSnapGridSize() ) - point[i] );
+                               if( othersnapDist < bestsnapDist ){
+                                       bestsnapDist = othersnapDist;
+                                       bestsnapTo = float_snapped( point[i], GetSnapGridSize() );
+                               }
+                               point[i] = bestsnapTo;
+
+                               m_pivot2world[i + 12] = point[i]; //m_pivot2world.tx() .ty() .tz()
+                       }
+               }
+
+               switch ( m_manipulator_mode )
+               {
+               case eTranslate:
+                       break;
+               case eRotate:
+                       if ( Mode() == eComponent ) {
+                               matrix4_assign_rotation_for_pivot( m_pivot2world, m_component_selection.back() );
+                       }
+                       else
+                       {
+                               matrix4_assign_rotation_for_pivot( m_pivot2world, m_selection.back() );
+                       }
+                       break;
+               case eScale:
+                       if ( Mode() == eComponent ) {
+                               matrix4_assign_rotation_for_pivot( m_pivot2world, m_component_selection.back() );
+                       }
+                       else
+                       {
+                               matrix4_assign_rotation_for_pivot( m_pivot2world, m_selection.back() );
+                       }
+                       break;
+               default:
+                       break;
+               }
+
+               m_pivotIsCustom = true;
+       }
+}
+
+AABB RadiantSelectionSystem::getSelectionAABB() const {
+       AABB bounds;
+       if ( !nothingSelected() ) {
+               if ( Mode() == eComponent || g_bTmpComponentMode ) {
+                       Scene_BoundsSelectedComponent( GlobalSceneGraph(), bounds );
+               }
+               else
+               {
+                       Scene_BoundsSelected( GlobalSceneGraph(), bounds );
+               }
+       }
+       return bounds;
+}
+
 void RadiantSelectionSystem::renderSolid( Renderer& renderer, const VolumeTest& volume ) const {
        //if(view->TestPoint(m_object_pivot))
        if ( !nothingSelected() ) {
@@ -3385,12 +3960,29 @@ void RadiantSelectionSystem::renderSolid( Renderer& renderer, const VolumeTest&
 #endif
 }
 
+#include "preferencesystem.h"
+#include "preferences.h"
+
+bool g_bLeftMouseClickSelector = true;
+
+void SelectionSystem_constructPreferences( PreferencesPage& page ){
+       page.appendCheckBox( "", "Prefer point entities in 2D", getSelectionSystem().m_bPreferPointEntsIn2D );
+       page.appendCheckBox( "", "Left mouse click tunnel selector", g_bLeftMouseClickSelector );
+}
+void SelectionSystem_constructPage( PreferenceGroup& group ){
+       PreferencesPage page( group.createPage( "Selection", "Selection System Settings" ) );
+       SelectionSystem_constructPreferences( page );
+}
+void SelectionSystem_registerPreferencesPage(){
+       PreferencesDialog_addSettingsPage( FreeCaller<void(PreferenceGroup&), SelectionSystem_constructPage>() );
+}
+
+
 
 void SelectionSystem_OnBoundsChanged(){
        getSelectionSystem().pivotChanged();
 }
 
-
 SignalHandlerId SelectionSystem_boundsChanged;
 
 void SelectionSystem_Construct(){
@@ -3401,6 +3993,10 @@ void SelectionSystem_Construct(){
        SelectionSystem_boundsChanged = GlobalSceneGraph().addBoundsChangedCallback( FreeCaller<void(), SelectionSystem_OnBoundsChanged>() );
 
        GlobalShaderCache().attachRenderable( getSelectionSystem() );
+
+       GlobalPreferenceSystem().registerPreference( "PreferPointEntsIn2D", make_property_string( getSelectionSystem().m_bPreferPointEntsIn2D ) );
+       GlobalPreferenceSystem().registerPreference( "LeftMouseClickSelector", make_property_string( g_bLeftMouseClickSelector ) );
+       SelectionSystem_registerPreferencesPage();
 }
 
 void SelectionSystem_Destroy(){
@@ -3449,6 +4045,7 @@ Single<MouseEventCallback> g_mouseUpCallback;
 
 #if 1
 const ButtonIdentifier c_button_select = c_buttonLeft;
+const ButtonIdentifier c_button_select2 = c_buttonRight;
 const ModifierFlags c_modifier_manipulator = c_modifierNone;
 const ModifierFlags c_modifier_toggle = c_modifierShift;
 const ModifierFlags c_modifier_replace = c_modifierShift | c_modifierAlt;
@@ -3472,11 +4069,13 @@ const ModifierFlags c_modifier_copy_texture = c_modifierNone;
 class Selector_
 {
 RadiantSelectionSystem::EModifier modifier_for_state( ModifierFlags state ){
-       if ( state == c_modifier_toggle || state == c_modifier_toggle_face ) {
-               return RadiantSelectionSystem::eToggle;
-       }
-       if ( state == c_modifier_replace || state == c_modifier_replace_face ) {
-               return RadiantSelectionSystem::eReplace;
+       if ( ( state == c_modifier_toggle || state == c_modifier_toggle_face || state == c_modifier_face || state == c_modifierAlt ) ) {
+               if( m_mouse2 ){
+                       return RadiantSelectionSystem::eReplace;
+               }
+               else{
+                       return RadiantSelectionSystem::eToggle;
+               }
        }
        return RadiantSelectionSystem::eManipulator;
 }
@@ -3497,12 +4096,15 @@ public:
 DeviceVector m_start;
 DeviceVector m_current;
 DeviceVector m_epsilon;
-std::size_t m_unmoved_replaces;
 ModifierFlags m_state;
+bool m_mouse2;
+bool m_mouseMoved;
+bool m_mouseMovedWhilePressed;
+bool m_paintSelect;
 const View* m_view;
 RectangleCallback m_window_update;
 
-Selector_() : m_start( 0.0f, 0.0f ), m_current( 0.0f, 0.0f ), m_unmoved_replaces( 0 ), m_state( c_modifierNone ){
+Selector_() : m_start( 0.0f, 0.0f ), m_current( 0.0f, 0.0f ), m_state( c_modifierNone ), m_mouse2( false ), m_mouseMoved( false ), m_mouseMovedWhilePressed( false ){
 }
 
 void draw_area(){
@@ -3515,11 +4117,11 @@ void testSelect( DeviceVector position ){
                DeviceVector delta( position - m_start );
                if ( fabs( delta.x() ) > m_epsilon.x() && fabs( delta.y() ) > m_epsilon.y() ) {
                        DeviceVector delta( position - m_start );
-                       getSelectionSystem().SelectArea( *m_view, &m_start[0], &delta[0], modifier, ( m_state & c_modifier_face ) != c_modifierNone );
+                       //getSelectionSystem().SelectArea( *m_view, &m_start[0], &delta[0], modifier, ( m_state & c_modifier_face ) != c_modifierNone );
+                       getSelectionSystem().SelectArea( *m_view, &m_start[0], &delta[0], RadiantSelectionSystem::eToggle, ( m_state & c_modifier_face ) != c_modifierNone );
                }
-               else
-               {
-                       if ( modifier == RadiantSelectionSystem::eReplace && m_unmoved_replaces++ > 0 ) {
+               else if( !m_mouseMovedWhilePressed ){
+                       if ( modifier == RadiantSelectionSystem::eReplace && !m_mouseMoved ) {
                                modifier = RadiantSelectionSystem::eCycle;
                        }
                        getSelectionSystem().SelectPoint( *m_view, &position[0], &m_epsilon[0], modifier, ( m_state & c_modifier_face ) != c_modifierNone );
@@ -3530,8 +4132,22 @@ void testSelect( DeviceVector position ){
        draw_area();
 }
 
+void testSelect_simpleM1( DeviceVector position ){
+       /*RadiantSelectionSystem::EModifier modifier = RadiantSelectionSystem::eReplace;
+       DeviceVector delta( position - m_start );
+       if ( fabs( delta.x() ) < m_epsilon.x() && fabs( delta.y() ) < m_epsilon.y() ) {
+               modifier = RadiantSelectionSystem::eCycle;
+       }
+       getSelectionSystem().SelectPoint( *m_view, &position[0], &m_epsilon[0], modifier, false );*/
+       if( g_bLeftMouseClickSelector ){
+               getSelectionSystem().SelectPoint( *m_view, &position[0], &m_epsilon[0], m_mouseMoved ? RadiantSelectionSystem::eReplace : RadiantSelectionSystem::eCycle, false );
+       }
+       m_start = m_current = device_constrained( position );
+}
+
+
 bool selecting() const {
-       return m_state != c_modifier_manipulator;
+       return m_state != c_modifier_manipulator && m_mouse2;
 }
 
 void setState( ModifierFlags state ){
@@ -3555,16 +4171,32 @@ void modifierDisable( ModifierFlags type ){
 
 void mouseDown( DeviceVector position ){
        m_start = m_current = device_constrained( position );
+       if( !m_mouse2 && m_state != c_modifierNone ){
+               m_paintSelect = getSelectionSystem().SelectPoint_InitPaint( *m_view, &position[0], &m_epsilon[0], ( m_state & c_modifier_face ) != c_modifierNone );
+       }
 }
 
 void mouseMoved( DeviceVector position ){
        m_current = device_constrained( position );
-       draw_area();
+       m_mouseMovedWhilePressed = true;
+       if( m_mouse2 ){
+               draw_area();
+       }
+       else if( m_state != c_modifier_manipulator ){
+               getSelectionSystem().SelectPoint( *m_view, &m_current[0], &m_epsilon[0],
+                                                                               m_paintSelect ? RadiantSelectionSystem::eSelect : RadiantSelectionSystem::eDeselect,
+                                                                               ( m_state & c_modifier_face ) != c_modifierNone );
+       }
 }
 typedef MemberCaller<Selector_, void(DeviceVector), &Selector_::mouseMoved> MouseMovedCaller;
 
 void mouseUp( DeviceVector position ){
-       testSelect( device_constrained( position ) );
+       if( m_mouse2 ){
+               testSelect( device_constrained( position ) );
+       }
+       else{
+               m_start = m_current = DeviceVector( 0.0f, 0.0f );
+       }
 
        g_mouseMovedCallback.clear();
        g_mouseUpCallback.clear();
@@ -3578,13 +4210,17 @@ class Manipulator_
 public:
 DeviceVector m_epsilon;
 const View* m_view;
+ModifierFlags m_state;
+
+Manipulator_() : m_state( c_modifierNone ){
+}
 
 bool mouseDown( DeviceVector position ){
        return getSelectionSystem().SelectManipulator( *m_view, &position[0], &m_epsilon[0] );
 }
 
 void mouseMoved( DeviceVector position ){
-       getSelectionSystem().MoveSelected( *m_view, &position[0] );
+       getSelectionSystem().MoveSelected( *m_view, &position[0], ( m_state & c_modifierShift ) == c_modifierShift, ( m_state & c_modifierControl ) == c_modifierControl );
 }
 typedef MemberCaller<Manipulator_, void(DeviceVector), &Manipulator_::mouseMoved> MouseMovedCaller;
 
@@ -3594,6 +4230,21 @@ void mouseUp( DeviceVector position ){
        g_mouseUpCallback.clear();
 }
 typedef MemberCaller<Manipulator_, void(DeviceVector), &Manipulator_::mouseUp> MouseUpCaller;
+
+void setState( ModifierFlags state ){
+       m_state = state;
+}
+
+ModifierFlags getState() const {
+       return m_state;
+}
+
+void modifierEnable( ModifierFlags type ){
+       setState( bitfield_enable( getState(), type ) );
+}
+void modifierDisable( ModifierFlags type ){
+       setState( bitfield_disable( getState(), type ) );
+}
 };
 
 void Scene_copyClosestTexture( SelectionTest& test );
@@ -3634,16 +4285,19 @@ void onSizeChanged( int width, int height ){
        m_selector.m_epsilon = m_manipulator.m_epsilon = epsilon;
 }
 void onMouseDown( const WindowVector& position, ButtonIdentifier button, ModifierFlags modifiers ){
-       if ( button == c_button_select ) {
+       if ( button == c_button_select || ( button == c_button_select2 && modifiers != c_modifierNone ) ) {
                m_mouse_down = true;
+               //m_selector.m_mouseMoved = false;
 
                DeviceVector devicePosition( window_to_normalised_device( position, m_width, m_height ) );
-               if ( modifiers == c_modifier_manipulator && m_manipulator.mouseDown( devicePosition ) ) {
+               g_bAltDragManipulatorResize = ( modifiers == c_modifierAlt ) ? true : false;
+               if ( ( modifiers == c_modifier_manipulator || ( modifiers == c_modifierAlt && getSelectionSystem().Mode() != SelectionSystem::eComponent ) ) && m_manipulator.mouseDown( devicePosition ) ) {
                        g_mouseMovedCallback.insert( MouseEventCallback( Manipulator_::MouseMovedCaller( m_manipulator ) ) );
                        g_mouseUpCallback.insert( MouseEventCallback( Manipulator_::MouseUpCaller( m_manipulator ) ) );
                }
                else
                {
+                       m_selector.m_mouse2 = ( button == c_button_select ) ? false : true;
                        m_selector.mouseDown( devicePosition );
                        g_mouseMovedCallback.insert( MouseEventCallback( Selector_::MouseMovedCaller( m_selector ) ) );
                        g_mouseUpCallback.insert( MouseEventCallback( Selector_::MouseUpCaller( m_selector ) ) );
@@ -3665,24 +4319,37 @@ void onMouseDown( const WindowVector& position, ButtonIdentifier button, Modifie
        }
 }
 void onMouseMotion( const WindowVector& position, ModifierFlags modifiers ){
-       m_selector.m_unmoved_replaces = 0;
-
+       m_selector.m_mouseMoved = true;
        if ( m_mouse_down && !g_mouseMovedCallback.empty() ) {
+               m_selector.m_mouseMovedWhilePressed = true;
                g_mouseMovedCallback.get() ( window_to_normalised_device( position, m_width, m_height ) );
        }
 }
 void onMouseUp( const WindowVector& position, ButtonIdentifier button, ModifierFlags modifiers ){
-       if ( button == c_button_select && !g_mouseUpCallback.empty() ) {
+       if ( ( button == c_button_select || button == c_button_select2 ) && !g_mouseUpCallback.empty() ) {
                m_mouse_down = false;
 
                g_mouseUpCallback.get() ( window_to_normalised_device( position, m_width, m_height ) );
        }
+       //L button w/o scene changed = tunnel selection
+       if( // !getSelectionSystem().m_undo_begun &&
+               modifiers == c_modifierNone && button == c_button_select &&
+               //( !m_selector.m_mouseMoved || !m_mouse_down ) &&
+               !m_selector.m_mouseMovedWhilePressed &&
+               ( getSelectionSystem().Mode() != SelectionSystem::eComponent || getSelectionSystem().ManipulatorMode() != SelectionSystem::eDrag ) ){
+               m_selector.testSelect_simpleM1( device_constrained( window_to_normalised_device( position, m_width, m_height ) ) );
+       }
+       //getSelectionSystem().m_undo_begun = false;
+       m_selector.m_mouseMoved = false;
+       m_selector.m_mouseMovedWhilePressed = false;
 }
 void onModifierDown( ModifierFlags type ){
        m_selector.modifierEnable( type );
+       m_manipulator.modifierEnable( type );
 }
 void onModifierUp( ModifierFlags type ){
        m_selector.modifierDisable( type );
+       m_manipulator.modifierDisable( type );
 }
 };
 
index 879770c5e341f169e656f41d30d2a15ee199b43d..993472ae6234f0a415c1d684eac09e5bab97890b 100644 (file)
@@ -156,7 +156,7 @@ public:
 typedef int ( *FunctionPointer )();
 
 DynamicLibrary( const char* filename ){
-       m_library = dlopen( filename, RTLD_NOW );
+       m_library = dlopen( filename, RTLD_NOW | (RTLD_DEEPBIND + 0) );
 }
 ~DynamicLibrary(){
        if ( !failed() ) {
index d6748c0704f8f803fd8df7cb4b225845b07e4097..6612c335d6f6da83f3bcb60a4ae850552b89db02 100644 (file)
@@ -27,7 +27,7 @@
 
 #include "gtkdlgs.h"
 
-void ViewShader( const char *pFile, const char *pName ){
+void ViewShader( const char *pFile, const char *pName, bool external_editor ){
        char* pBuff = 0;
        //int nSize =
        vfsLoadFile( pFile, reinterpret_cast<void**>( &pBuff ) );
@@ -39,17 +39,66 @@ void ViewShader( const char *pFile, const char *pName ){
        StringOutputStream strFind( string_length( pName ) );
        strFind << LowerCase( pName );
        StringOutputStream strLook( string_length( pBuff ) );
-       strFind << LowerCase( pBuff );
+       strLook << LowerCase( pBuff );
        // offset used when jumping over commented out definitions
+       int length = string_length( pBuff );
        std::size_t nOffset = 0;
-       while ( true )
-       {
-               const char* substr = strstr( strFind.c_str() + nOffset, strFind.c_str() );
+       bool startOK = false;
+       bool endOK = false;
+       while ( !startOK || !endOK ){
+               const char* substr = strstr( strLook.c_str() + nOffset, strFind.c_str() );
                if ( substr == 0 ) {
                        break;
                }
                std::size_t nStart = substr - strLook.c_str();
-               // we have found something, maybe it's a commented out shader name?
+               startOK = endOK = false;
+               if ( nStart == 0 ){
+                       startOK = true;
+               }
+               //validate found one...
+               for ( const char* i = substr - 1; i > strLook.c_str(); i-- ){
+                       if( (strncmp( i, "\t", 1 ) == 0) || (strncmp( i, " ", 1 ) == 0) ){
+                               startOK = true;
+                               continue;
+                       }
+                       else if ( (strncmp( i, "\n", 1 ) == 0) || (strncmp( i, "\r", 1 ) == 0) ){
+                               startOK = true;
+                               break;
+                       }
+                       else{
+                               startOK = false;
+                               break;
+                       }
+               }
+               const char* b = strLook.c_str() + strlen( strLook.c_str() );
+               for ( const char* i = substr + strlen( strFind.c_str() ); i < b; i++ ){
+                       if( (strncmp( i, "\t", 1 ) == 0) || (strncmp( i, " ", 1 ) == 0) ){
+                               endOK = true;
+                               continue;
+                       }
+                       else if ( (strncmp( i, "\n", 1 ) == 0) || (strncmp( i, "\r", 1 ) == 0) || (strncmp( i, "{", 1 ) == 0) || (strncmp( i, ":q3map", 6 ) == 0) ){
+                               endOK = true;
+                               break;
+                       }
+                       else{
+                               endOK = false;
+                               break;
+                       }
+               }
+               if( !startOK || !endOK ){
+                       nOffset = nStart + 1;
+               }
+               else{
+                       //globalErrorStream() << "Validated successfully" << "\n";
+                       nOffset = nStart;
+                       //fix cr+lf
+                       for ( const char* i = strLook.c_str(); i < substr ; i++ ){
+                               if ( (strncmp( i, "\r\n", 2 ) == 0) ){
+                               nOffset--;
+                               }
+                       }
+               }
+               /*// we have found something, maybe it's a commented out shader name?
                char *strCheck = new char[string_length( strLook.c_str() ) + 1];
                strcpy( strCheck, strLook.c_str() );
                strCheck[nStart] = 0;
@@ -62,10 +111,17 @@ void ViewShader( const char *pFile, const char *pName ){
                }
                delete[] strCheck;
                nOffset = nStart;
-               break;
+               break;*/
+       }
+       //fix up length
+       const char* b = strLook.c_str() + strlen( strLook.c_str() ) - 1;
+       for ( const char* i = strLook.c_str(); i < b; i++ ){
+               if ( (strncmp( i, "\r\n", 2 ) == 0) ){
+               length--;
+               }
        }
        // now close the file
        vfsFreeFile( pBuff );
 
-       DoTextEditor( pFile, static_cast<int>( nOffset ) );
+       DoTextEditor( pFile, static_cast<int>( nOffset ), length, external_editor );
 }
index 6cf0ec853d9ab8e3bbee67c9e7840bafca4dd37b..7758853aef268c5bef754d2e517e5d1a9d8cd6c5 100644 (file)
@@ -22,6 +22,6 @@
 #if !defined( INCLUDED_SHADERS_H )
 #define INCLUDED_SHADERS_H
 
-void ViewShader( const char* file, const char* shader );
+void ViewShader( const char* file, const char* shader, bool external_editor );
 
 #endif
index 9ae43fd7e130fcbe6beb03047e6bacf29c4c5c72..51d497ad78e5def5cbfaeebdc50889faf021436c 100644 (file)
@@ -412,7 +412,7 @@ void DoSurface( void ){
 
        }
        getSurfaceInspector().Update();
-       getSurfaceInspector().importData();
+       //getSurfaceInspector().importData(); //happens in .ShowDlg() anyway
        getSurfaceInspector().ShowDlg();
 }
 
@@ -431,6 +431,16 @@ void SurfaceInspector_FitTexture(){
        Select_FitTexture( getSurfaceInspector().m_fitHorizontal, getSurfaceInspector().m_fitVertical );
 }
 
+void SurfaceInspector_FitTextureW(){
+       UndoableCommand undo( "textureAutoFitW" );
+       Select_FitTexture( getSurfaceInspector().m_fitHorizontal, 0 );
+}
+
+void SurfaceInspector_FitTextureH(){
+       UndoableCommand undo( "textureAutoFitH" );
+       Select_FitTexture( 0, getSurfaceInspector().m_fitVertical );
+}
+
 static void OnBtnPatchdetails( ui::Widget widget, gpointer data ){
        Patch_CapTexture();
 }
@@ -481,6 +491,18 @@ static void OnBtnFaceFit( ui::Widget widget, gpointer data ){
        SurfaceInspector_FitTexture();
 }
 
+static void OnBtnFaceFitW( GtkWidget *widget, gpointer data ){
+       getSurfaceInspector().exportData();
+       SurfaceInspector_FitTextureW();
+}
+
+static void OnBtnFaceFitH( GtkWidget *widget, gpointer data ){
+       getSurfaceInspector().exportData();
+       SurfaceInspector_FitTextureH();
+}
+
+
+
 typedef const char* FlagName;
 
 const FlagName surfaceflagNamesDefault[32] = {
@@ -1284,6 +1306,9 @@ bool pre( const scene::Path& path, scene::Instance& instance ) const {
                        }
                }
        }
+       else{
+               return false;
+       }
        return true;
 }
 };
index f741f0ceafafa77bff560b1177843f26b02bdf0f..22559b6adc6a2ac8ec5fec3fd7516465bd202fda 100644 (file)
@@ -113,7 +113,7 @@ bool g_TextureBrowser_shaderlistOnly = false;
 bool g_TextureBrowser_fixedSize = true;
 bool g_TextureBrowser_filterMissing = false;
 bool g_TextureBrowser_filterFallback = true;
-bool g_TextureBrowser_enableAlpha = true;
+bool g_TextureBrowser_enableAlpha = false;
 }
 
 CopiedString g_notex;
@@ -151,9 +151,6 @@ void TextureGroups_addShader( TextureGroups& groups, const char* shaderName ){
                if ( isNotex( shaderName ) ) {
                        return;
                }
-               if ( isNotex( texture ) ) {
-                       return;
-               }
        }
 
        if ( texture != shaderName ) {
@@ -169,6 +166,7 @@ typedef ReferenceCaller<TextureGroups, void(const char*), TextureGroups_addShade
 void TextureGroups_addDirectory( TextureGroups& groups, const char* directory ){
        groups.insert( directory );
 }
+
 typedef ReferenceCaller<TextureGroups, void(const char*), TextureGroups_addDirectory> TextureGroupsAddDirectoryCaller;
 
 class DeferredAdjustment
@@ -236,6 +234,10 @@ void TextureBrowser_showShadersExport( const Callback<void(bool)> & importer );
 
 typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_showShadersExport> TextureBrowserShowShadersExport;
 
+void TextureBrowser_showTexturesExport( const Callback<void(bool)> & importer );
+
+typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_showTexturesExport> TextureBrowserShowTexturesExport;
+
 void TextureBrowser_showShaderlistOnly( const Callback<void(bool)> & importer );
 
 typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_showShaderlistOnly> TextureBrowserShowShaderlistOnlyExport;
@@ -297,6 +299,7 @@ std::set<CopiedString> m_found_shaders;
 ToggleItem m_hideunused_item;
 ToggleItem m_hidenotex_item;
 ToggleItem m_showshaders_item;
+ToggleItem m_showtextures_item;
 ToggleItem m_showshaderlistonly_item;
 ToggleItem m_fixedsize_item;
 ToggleItem m_filternotex_item;
@@ -317,6 +320,7 @@ std::size_t m_mouseWheelScrollIncrement;
 std::size_t m_textureScale;
 // make the texture increments match the grid changes
 bool m_showShaders;
+bool m_showTextures;
 bool m_showTextureScrollbar;
 StartupShaders m_startupShaders;
 // if true, the texture window will only display in-use shaders
@@ -325,44 +329,44 @@ bool m_hideUnused;
 bool m_rmbSelected;
 bool m_searchedTags;
 bool m_tags;
+bool m_move_started;
 // The uniform size (in pixels) that textures are resized to when m_resizeTextures is true.
 int m_uniformTextureSize;
+int m_uniformTextureMinSize;
 
+bool m_hideNonShadersInCommon;
 // Return the display width of a texture in the texture browser
-int getTextureWidth( qtexture_t* tex ){
-       int width;
-       if ( !g_TextureBrowser_fixedSize ) {
-               // Don't use uniform size
-               width = (int)( tex->width * ( (float)m_textureScale / 100 ) );
-       }
-       else if
-       ( tex->width >= tex->height ) {
-               // Texture is square, or wider than it is tall
-               width = m_uniformTextureSize;
-       }
-       else {
-               // Otherwise, preserve the texture's aspect ratio
-               width = (int)( m_uniformTextureSize * ( (float)tex->width / tex->height ) );
-       }
-       return width;
-}
-
-// Return the display height of a texture in the texture browser
-int getTextureHeight( qtexture_t* tex ){
-       int height;
-       if ( !g_TextureBrowser_fixedSize ) {
+void getTextureWH( qtexture_t* tex, int &W, int &H ){
                // Don't use uniform size
-               height = (int)( tex->height * ( (float)m_textureScale / 100 ) );
-       }
-       else if ( tex->height >= tex->width ) {
-               // Texture is square, or taller than it is wide
-               height = m_uniformTextureSize;
-       }
-       else {
-               // Otherwise, preserve the texture's aspect ratio
-               height = (int)( m_uniformTextureSize * ( (float)tex->height / tex->width ) );
+               W = (int)( tex->width * ( (float)m_textureScale / 100 ) );
+               H = (int)( tex->height * ( (float)m_textureScale / 100 ) );
+               if ( W < 1 ) W = 1;
+               if ( H < 1 ) H = 1;
+
+       if ( g_TextureBrowser_fixedSize ){
+               if      ( W >= H ) {
+                       // Texture is square, or wider than it is tall
+                       if ( W >= m_uniformTextureSize ){
+                               H = m_uniformTextureSize * H / W;
+                               W = m_uniformTextureSize;
+                       }
+                       else if ( W <= m_uniformTextureMinSize ){
+                               H = m_uniformTextureMinSize * H / W;
+                               W = m_uniformTextureMinSize;
+                       }
+               }
+               else {
+                       // Texture taller than it is wide
+                       if ( H >= m_uniformTextureSize ){
+                               W = m_uniformTextureSize * W / H;
+                               H = m_uniformTextureSize;
+                       }
+                       else if ( H <= m_uniformTextureMinSize ){
+                               W = m_uniformTextureMinSize * W / H;
+                               H = m_uniformTextureMinSize;
+                       }
+               }
        }
-       return height;
 }
 
 TextureBrowser() :
@@ -370,6 +374,7 @@ TextureBrowser() :
        m_hideunused_item( TextureBrowserHideUnusedExport() ),
        m_hidenotex_item( TextureBrowserFilterFallbackExport() ),
        m_showshaders_item( TextureBrowserShowShadersExport() ),
+       m_showtextures_item( TextureBrowserShowTexturesExport() ),
        m_showshaderlistonly_item( TextureBrowserShowShaderlistOnlyExport() ),
        m_fixedsize_item( TextureBrowserFixedSizeExport() ),
        m_filternotex_item( TextureBrowserFilterMissingExport() ),
@@ -381,13 +386,17 @@ TextureBrowser() :
        m_mouseWheelScrollIncrement( 64 ),
        m_textureScale( 50 ),
        m_showShaders( true ),
+       m_showTextures( true ),
        m_showTextureScrollbar( true ),
        m_startupShaders( STARTUPSHADERS_NONE ),
        m_hideUnused( false ),
        m_rmbSelected( false ),
        m_searchedTags( false ),
        m_tags( false ),
-       m_uniformTextureSize( 96 ){
+       m_move_started( false ),
+       m_uniformTextureSize( 160 ),
+       m_uniformTextureMinSize( 48 ),
+       m_hideNonShadersInCommon( true ){
 }
 };
 
@@ -488,11 +497,11 @@ void Texture_StartPos( TextureLayout& layout ){
 void Texture_NextPos( TextureBrowser& textureBrowser, TextureLayout& layout, qtexture_t* current_texture, int *x, int *y ){
        qtexture_t* q = current_texture;
 
-       int nWidth = textureBrowser.getTextureWidth( q );
-       int nHeight = textureBrowser.getTextureHeight( q );
+       int nWidth, nHeight;
+       textureBrowser.getTextureWH( q, nWidth, nHeight );
        if ( layout.current_x + nWidth > textureBrowser.width - 8 && layout.current_row ) { // go to the next row unless the texture is the first on the row
                layout.current_x = 8;
-               layout.current_y -= layout.current_row + TextureBrowser_fontHeight( textureBrowser ) + 4;
+               layout.current_y -= layout.current_row + TextureBrowser_fontHeight( textureBrowser ) + 4;//+4
                layout.current_row = 0;
        }
 
@@ -525,7 +534,7 @@ bool TextureSearch_IsShown( const char* name ){
 }
 
 // if texture_showinuse jump over non in-use textures
-bool Texture_IsShown( IShader* shader, bool show_shaders, bool hideUnused ){
+bool Texture_IsShown( IShader* shader, bool show_shaders, bool show_textures, bool hideUnused, bool hideNonShadersInCommon ){
        // filter missing shaders
        // ugly: filter on built-in fallback name after substitution
        if ( g_TextureBrowser_filterMissing ) {
@@ -538,9 +547,6 @@ bool Texture_IsShown( IShader* shader, bool show_shaders, bool hideUnused ){
                if ( isNotex( shader->getName() ) ) {
                        return false;
                }
-               if ( isNotex( shader->getTexture()->name ) ) {
-                       return false;
-               }
        }
 
        if ( g_TextureBrowser_currentDirectory == "Untagged" ) {
@@ -564,10 +570,19 @@ bool Texture_IsShown( IShader* shader, bool show_shaders, bool hideUnused ){
                return false;
        }
 
+       if ( !show_textures && shader->IsDefault() ) {
+               return false;
+       }
+
        if ( hideUnused && !shader->IsInUse() ) {
                return false;
        }
 
+       if( hideNonShadersInCommon && shader->IsDefault() && !shader->IsInUse() //&& g_TextureBrowser_currentDirectory != ""
+               && shader_equal_prefix( shader_get_textureName( shader->getName() ), TextureBrowser_getCommonShadersDir() ) ){
+               return false;
+       }
+
        if ( GlobalTextureBrowser().m_searchedTags ) {
                if ( !TextureSearch_IsShown( shader->getName() ) ) {
                        return false;
@@ -612,13 +627,15 @@ void TextureBrowser_evaluateHeight( TextureBrowser& textureBrowser ){
                {
                        IShader* shader = QERApp_ActiveShaders_IteratorCurrent();
 
-                       if ( !Texture_IsShown( shader, textureBrowser.m_showShaders, textureBrowser.m_hideUnused ) ) {
+                       if ( !Texture_IsShown( shader, textureBrowser.m_showShaders, textureBrowser.m_showTextures, textureBrowser.m_hideUnused, textureBrowser.m_hideNonShadersInCommon ) ) {
                                continue;
                        }
 
                        int x, y;
                        Texture_NextPos( textureBrowser, layout, shader->getTexture(), &x, &y );
-                       textureBrowser.m_nTotalHeight = std::max( textureBrowser.m_nTotalHeight, abs( layout.current_y ) + TextureBrowser_fontHeight( textureBrowser ) + textureBrowser.getTextureHeight( shader->getTexture() ) + 4 );
+                       int nWidth, nHeight;
+                       textureBrowser.getTextureWH( shader->getTexture(), nWidth, nHeight );
+                       textureBrowser.m_nTotalHeight = std::max( textureBrowser.m_nTotalHeight, abs( layout.current_y ) + TextureBrowser_fontHeight( textureBrowser ) + nHeight + 4 );
                }
        }
 }
@@ -673,7 +690,8 @@ Signal0 m_realiseCallbacks;
 public:
 void realise(){
        m_realiseCallbacks();
-       TextureBrowser_constructTreeStore();
+       /* texturebrowser tree update on vfs restart */
+//     TextureBrowser_constructTreeStore();
 }
 
 void unrealise(){
@@ -823,6 +841,14 @@ void operator()( const char* name ) const {
 };
 
 void TextureDirectory_loadTexture( const char* directory, const char* texture ){
+       // Doom3-like dds/ prefix (used by DarkPlaces).
+       // When we list dds/textures/ folder,
+       // store the texture names without dds/ prefix.
+       if ( !strncmp( "dds/", directory, 4 ) )
+       {
+               directory = &directory[ 4 ];
+       }
+
        StringOutputStream name( 256 );
        name << directory << StringRange( texture, path_get_filename_base_end( texture ) );
 
@@ -856,6 +882,7 @@ void visit( const char* minor, const _QERPlugImageTable& table ) const {
 };
 
 void TextureBrowser_ShowDirectory( TextureBrowser& textureBrowser, const char* directory ){
+       textureBrowser.m_searchedTags = false;
        if ( TextureBrowser_showWads() ) {
                g_TextureBrowser_currentDirectory = directory;
                TextureBrowser_heightChanged( textureBrowser );
@@ -863,8 +890,11 @@ void TextureBrowser_ShowDirectory( TextureBrowser& textureBrowser, const char* d
                Archive* archive = GlobalFileSystem().getArchive( directory );
                if ( archive != nullptr )
                {
-                       LoadShaderVisitor visitor;
-                       archive->forEachFile( Archive::VisitorFunc( visitor, Archive::eFiles, 0 ), "textures/" );
+               LoadShaderVisitor visitor;
+               archive->forEachFile( Archive::VisitorFunc( visitor, Archive::eFiles, 0 ), "textures/" );
+
+                       // Doom3-like dds/ prefix (used by DarkPlaces).
+                       archive->forEachFile( Archive::VisitorFunc( visitor, Archive::eFiles, 0 ), "dds/textures/" );
                }
                else if ( extension_equal_i( path_get_extension( directory ), "wad" ) )
                {
@@ -886,7 +916,19 @@ void TextureBrowser_ShowDirectory( TextureBrowser& textureBrowser, const char* d
                        StringOutputStream dirstring( 64 );
                        dirstring << "textures/" << directory;
 
-                       Radiant_getImageModules().foreachModule( LoadTexturesByTypeVisitor( dirstring.c_str() ) );
+                       {
+                               LoadTexturesByTypeVisitor visitor( dirstring.c_str() );
+                               Radiant_getImageModules().foreachModule( visitor );
+                       }
+
+                       // Doom3-like dds/ prefix (used by DarkPlaces).
+                       dirstring.clear();
+                       dirstring << "dds/textures/" << directory;
+
+                       {
+                               LoadTexturesByTypeVisitor visitor( dirstring.c_str() );
+                               Radiant_getImageModules().foreachModule( visitor );
+                       }
                }
        }
 
@@ -913,6 +955,15 @@ void TextureBrowser_ShowTagSearchResult( TextureBrowser& textureBrowser, const c
                        LoadTexturesByTypeVisitor visitor( dirstring.c_str() );
                        Radiant_getImageModules().foreachModule( visitor );
                }
+
+               // Doom3-like dds/ prefix (used by DarkPlaces).
+               dirstring.clear();
+               dirstring << "dds/textures/" << directory;
+
+               {
+                       LoadTexturesByTypeVisitor visitor( dirstring.c_str() );
+                       Radiant_getImageModules().foreachModule( visitor );
+               }
        }
 
        // we'll display the newly loaded textures + all the ones already in use
@@ -934,6 +985,12 @@ void TextureBrowser_showShadersExport( const Callback<void(bool)> & importer ){
 
 typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_showShadersExport> TextureBrowserShowShadersExport;
 
+void TextureBrowser_showTexturesExport( const Callback<void(bool)> & importer ){
+       importer( GlobalTextureBrowser().m_showTextures );
+}
+
+typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_showTexturesExport> TextureBrowserShowTexturesExport;
+
 void TextureBrowser_showShaderlistOnly( const Callback<void(bool)> & importer ){
        importer( g_TextureBrowser_shaderlistOnly );
 }
@@ -965,13 +1022,7 @@ void TextureBrowser_enableAlpha( const Callback<void(bool)> & importer ){
 typedef FreeCaller<void(const Callback<void(bool)> &), TextureBrowser_enableAlpha> TextureBrowser_enableAlphaExport;
 
 void TextureBrowser_SetHideUnused( TextureBrowser& textureBrowser, bool hideUnused ){
-       if ( hideUnused ) {
-               textureBrowser.m_hideUnused = true;
-       }
-       else
-       {
-               textureBrowser.m_hideUnused = false;
-       }
+       textureBrowser.m_hideUnused = hideUnused;
 
        textureBrowser.m_hideunused_item.update();
 
@@ -999,7 +1050,7 @@ void TextureBrowser_Focus( TextureBrowser& textureBrowser, const char* name ){
        {
                IShader* shader = QERApp_ActiveShaders_IteratorCurrent();
 
-               if ( !Texture_IsShown( shader, textureBrowser.m_showShaders, textureBrowser.m_hideUnused ) ) {
+               if ( !Texture_IsShown( shader, textureBrowser.m_showShaders, textureBrowser.m_showTextures, textureBrowser.m_hideUnused, textureBrowser.m_hideNonShadersInCommon ) ) {
                        continue;
                }
 
@@ -1013,12 +1064,15 @@ void TextureBrowser_Focus( TextureBrowser& textureBrowser, const char* name ){
                // we have found when texdef->name and the shader name match
                // NOTE: as everywhere else for our comparisons, we are not case sensitive
                if ( shader_equal( name, shader->getName() ) ) {
-                       int textureHeight = (int)( q->height * ( (float)textureBrowser.m_textureScale / 100 ) )
-                                                               + 2 * TextureBrowser_fontHeight( textureBrowser );
+                       //int textureHeight = (int)( q->height * ( (float)textureBrowser.m_textureScale / 100 ) ) + 2 * TextureBrowser_fontHeight( textureBrowser );
+                       int textureWidth, textureHeight;
+                       textureBrowser.getTextureWH( q, textureWidth, textureHeight );
+                       textureHeight += 2 * TextureBrowser_fontHeight( textureBrowser );
+
 
                        int originy = TextureBrowser_getOriginY( textureBrowser );
                        if ( y > originy ) {
-                               originy = y;
+                               originy = y + 4;
                        }
 
                        if ( y - textureHeight < originy - textureBrowser.height ) {
@@ -1040,7 +1094,7 @@ IShader* Texture_At( TextureBrowser& textureBrowser, int mx, int my ){
        {
                IShader* shader = QERApp_ActiveShaders_IteratorCurrent();
 
-               if ( !Texture_IsShown( shader, textureBrowser.m_showShaders, textureBrowser.m_hideUnused ) ) {
+               if ( !Texture_IsShown( shader, textureBrowser.m_showShaders, textureBrowser.m_showTextures, textureBrowser.m_hideUnused, textureBrowser.m_hideNonShadersInCommon ) ) {
                        continue;
                }
 
@@ -1051,8 +1105,8 @@ IShader* Texture_At( TextureBrowser& textureBrowser, int mx, int my ){
                        break;
                }
 
-               int nWidth = textureBrowser.getTextureWidth( q );
-               int nHeight = textureBrowser.getTextureHeight( q );
+               int nWidth, nHeight;
+               textureBrowser.getTextureWH( q, nWidth, nHeight );
                if ( mx > x && mx - x < nWidth
                         && my < y && y - my < nHeight + TextureBrowser_fontHeight( textureBrowser ) ) {
                        return shader;
@@ -1069,25 +1123,15 @@ IShader* Texture_At( TextureBrowser& textureBrowser, int mx, int my ){
    By mouse click
    ==============
  */
-void SelectTexture( TextureBrowser& textureBrowser, int mx, int my, bool bShift ){
-       IShader* shader = Texture_At( textureBrowser, mx, my );
-       if ( shader != 0 ) {
-               if ( bShift ) {
-                       if ( shader->IsDefault() ) {
-                               globalOutputStream() << "ERROR: " << shader->getName() << " is not a shader, it's a texture.\n";
-                       }
-                       else{
-                               ViewShader( shader->getShaderFileName(), shader->getName() );
-                       }
-               }
-               else
-               {
+void SelectTexture( TextureBrowser& textureBrowser, int mx, int my, guint32 flags, bool texturizeSelection ){
+       if ( ( flags & GDK_SHIFT_MASK ) == 0 ) {
+               IShader* shader = Texture_At( textureBrowser, mx, my );
+               if ( shader != 0 ) {
                        TextureBrowser_SetSelectedShader( textureBrowser, shader->getName() );
                        TextureBrowser_textureSelected( shader->getName() );
 
-                       if ( !FindTextureDialog_isOpen() && !textureBrowser.m_rmbSelected ) {
-                               UndoableCommand undo( "textureNameSetSelected" );
-                               Select_SetShader( shader->getName() );
+                       if ( !FindTextureDialog_isOpen() && !textureBrowser.m_rmbSelected && !texturizeSelection ) {
+                               Select_SetShader_Undo( shader->getName() );
                        }
                }
        }
@@ -1116,16 +1160,39 @@ void TextureBrowser_trackingDelta( int x, int y, unsigned int state, void* data
        }
 }
 
+void TextureBrowser_Tracking_MouseUp( TextureBrowser& textureBrowser ){
+       textureBrowser.m_move_started = false;
+       /* NetRadiantCustom did this instead:
+       textureBrowser.m_freezePointer.unfreeze_pointer( textureBrowser.m_parent, false ); */
+       textureBrowser.m_freezePointer.unfreeze_pointer( textureBrowser.m_gl_widget, false );
+}
+
 void TextureBrowser_Tracking_MouseDown( TextureBrowser& textureBrowser ){
+       if( textureBrowser.m_move_started ){
+               TextureBrowser_Tracking_MouseUp( textureBrowser );
+       }
+       textureBrowser.m_move_started = true;
+       /* NetRadiantCustom did this instead:
+       textureBrowser.m_freezePointer.freeze_pointer( textureBrowser.m_parent, textureBrowser.m_gl_widget, TextureBrowser_trackingDelta, &textureBrowser ); */
        textureBrowser.m_freezePointer.freeze_pointer( textureBrowser.m_gl_widget, TextureBrowser_trackingDelta, &textureBrowser );
 }
 
-void TextureBrowser_Tracking_MouseUp( TextureBrowser& textureBrowser ){
-       textureBrowser.m_freezePointer.unfreeze_pointer( textureBrowser.m_gl_widget );
+void TextureBrowser_Selection_MouseDown( TextureBrowser& textureBrowser, guint32 flags, int pointx, int pointy, bool texturizeSelection ){
+       SelectTexture( textureBrowser, pointx, textureBrowser.height - 1 - pointy, flags, texturizeSelection );
 }
 
-void TextureBrowser_Selection_MouseDown( TextureBrowser& textureBrowser, guint32 flags, int pointx, int pointy ){
-       SelectTexture( textureBrowser, pointx, textureBrowser.height - 1 - pointy, ( flags & GDK_SHIFT_MASK ) != 0 );
+void TextureBrowser_Selection_MouseUp( TextureBrowser& textureBrowser, guint32 flags, int pointx, int pointy ){
+       if ( ( flags & GDK_SHIFT_MASK ) != 0 ) {
+               IShader* shader = Texture_At( textureBrowser, pointx, textureBrowser.height - 1 - pointy );
+               if ( shader != 0 ) {
+                       if ( shader->IsDefault() ) {
+                               globalOutputStream() << "ERROR: " << shader->getName() << " is not a shader, it's a texture.\n";
+                       }
+                       else{
+                               ViewShader( shader->getShaderFileName(), shader->getName(), ( flags & GDK_CONTROL_MASK ) != 0 );
+                       }
+               }
+       }
 }
 
 /*
@@ -1158,6 +1225,8 @@ void Texture_Draw( TextureBrowser& textureBrowser ){
 
        glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
        glDisable( GL_DEPTH_TEST );
+
+       //glDisable( GL_BLEND );
        if ( g_TextureBrowser_enableAlpha ) {
                glEnable( GL_BLEND );
                glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
@@ -1165,6 +1234,7 @@ void Texture_Draw( TextureBrowser& textureBrowser ){
        else {
                glDisable( GL_BLEND );
        }
+
        glOrtho( 0, textureBrowser.width, originy - textureBrowser.height, originy, -100, 100 );
        glEnable( GL_TEXTURE_2D );
 
@@ -1178,7 +1248,7 @@ void Texture_Draw( TextureBrowser& textureBrowser ){
        {
                IShader* shader = QERApp_ActiveShaders_IteratorCurrent();
 
-               if ( !Texture_IsShown( shader, textureBrowser.m_showShaders, textureBrowser.m_hideUnused ) ) {
+               if ( !Texture_IsShown( shader, textureBrowser.m_showShaders, textureBrowser.m_showTextures, textureBrowser.m_hideUnused, textureBrowser.m_hideNonShadersInCommon ) ) {
                        continue;
                }
 
@@ -1189,8 +1259,8 @@ void Texture_Draw( TextureBrowser& textureBrowser ){
                        break;
                }
 
-               int nWidth = textureBrowser.getTextureWidth( q );
-               int nHeight = textureBrowser.getTextureHeight( q );
+               int nWidth, nHeight;
+               textureBrowser.getTextureWH( q, nWidth, nHeight );
 
                if ( y != last_y ) {
                        last_y = y;
@@ -1206,65 +1276,84 @@ void Texture_Draw( TextureBrowser& textureBrowser ){
                        // shaders have a white border, simple textures don't
                        // if !texture_showinuse: (some textures displayed may not be in use)
                        // draw an additional square around with 0.5 1 0.5 color
+                       glLineWidth( 1 );
+                       const float xf = (float)x;
+                       const float yf = (float)( y - TextureBrowser_fontHeight( textureBrowser ) );
+                       float xfMax = xf + 1.5 + nWidth;
+                       float xfMin = xf - 1.5;
+                       float yfMax = yf + 1.5;
+                       float yfMin = yf - nHeight - 1.5;
+
+                       //selected texture
                        if ( shader_equal( TextureBrowser_GetSelectedShader( textureBrowser ), shader->getName() ) ) {
-                               glLineWidth( 3 );
+                               glLineWidth( 2 );
                                if ( textureBrowser.m_rmbSelected ) {
                                        glColor3f( 0,0,1 );
                                }
                                else {
                                        glColor3f( 1,0,0 );
                                }
+                               xfMax += .5;
+                               xfMin -= .5;
+                               yfMax += .5;
+                               yfMin -= .5;
                                glDisable( GL_TEXTURE_2D );
-
                                glBegin( GL_LINE_LOOP );
-                               glVertex2i( x - 4,y - TextureBrowser_fontHeight( textureBrowser ) + 4 );
-                               glVertex2i( x - 4,y - TextureBrowser_fontHeight( textureBrowser ) - nHeight - 4 );
-                               glVertex2i( x + 4 + nWidth,y - TextureBrowser_fontHeight( textureBrowser ) - nHeight - 4 );
-                               glVertex2i( x + 4 + nWidth,y - TextureBrowser_fontHeight( textureBrowser ) + 4 );
+                               glVertex2f( xfMin ,yfMax );
+                               glVertex2f( xfMin ,yfMin );
+                               glVertex2f( xfMax ,yfMin );
+                               glVertex2f( xfMax ,yfMax );
+                               glEnd();
+                               glEnable( GL_TEXTURE_2D );
+                       }
+                       // highlight in-use textures
+                       else if ( !textureBrowser.m_hideUnused && shader->IsInUse() ) {
+                               glColor3f( 0.5,1,0.5 );
+                               glDisable( GL_TEXTURE_2D );
+                               glBegin( GL_LINE_LOOP );
+                               glVertex2f( xfMin ,yfMax );
+                               glVertex2f( xfMin ,yfMin );
+                               glVertex2f( xfMax ,yfMin );
+                               glVertex2f( xfMax ,yfMax );
+                               glEnd();
+                               glEnable( GL_TEXTURE_2D );
+                       }
+                       // shader white border:
+                       else if ( !shader->IsDefault() ) {
+                               glColor3f( 1, 1, 1 );
+                               glDisable( GL_TEXTURE_2D );
+                               glBegin( GL_LINE_LOOP );
+                               glVertex2f( xfMin ,yfMax );
+                               glVertex2f( xfMin ,yfMin );
+                               glVertex2f( xfMax ,yfMin );
+                               glVertex2f( xfMax ,yfMax );
                                glEnd();
-
                                glEnable( GL_TEXTURE_2D );
-                               glLineWidth( 1 );
                        }
-                       else
-                       {
-                               glLineWidth( 1 );
-                               // shader border:
-                               if ( !shader->IsDefault() ) {
-                                       glColor3f( 1,1,1 );
-                                       glDisable( GL_TEXTURE_2D );
-
-                                       glBegin( GL_LINE_LOOP );
-                                       glVertex2i( x - 1,y + 1 - TextureBrowser_fontHeight( textureBrowser ) );
-                                       glVertex2i( x - 1,y - nHeight - 1 - TextureBrowser_fontHeight( textureBrowser ) );
-                                       glVertex2i( x + 1 + nWidth,y - nHeight - 1 - TextureBrowser_fontHeight( textureBrowser ) );
-                                       glVertex2i( x + 1 + nWidth,y + 1 - TextureBrowser_fontHeight( textureBrowser ) );
-                                       glEnd();
-                                       glEnable( GL_TEXTURE_2D );
-                               }
 
-                               // highlight in-use textures
-                               if ( !textureBrowser.m_hideUnused && shader->IsInUse() ) {
-                                       glColor3f( 0.5,1,0.5 );
-                                       glDisable( GL_TEXTURE_2D );
-                                       glBegin( GL_LINE_LOOP );
-                                       glVertex2i( x - 3,y + 3 - TextureBrowser_fontHeight( textureBrowser ) );
-                                       glVertex2i( x - 3,y - nHeight - 3 - TextureBrowser_fontHeight( textureBrowser ) );
-                                       glVertex2i( x + 3 + nWidth,y - nHeight - 3 - TextureBrowser_fontHeight( textureBrowser ) );
-                                       glVertex2i( x + 3 + nWidth,y + 3 - TextureBrowser_fontHeight( textureBrowser ) );
-                                       glEnd();
-                                       glEnable( GL_TEXTURE_2D );
-                               }
+                       // shader stipple:
+                       if ( !shader->IsDefault() ) {
+                               glEnable( GL_LINE_STIPPLE );
+                               glLineStipple( 1, 0xF000 );
+                               glDisable( GL_TEXTURE_2D );
+                               glBegin( GL_LINE_LOOP );
+                               glColor3f( 0, 0, 0 );
+                               glVertex2f( xfMin ,yfMax );
+                               glVertex2f( xfMin ,yfMin );
+                               glVertex2f( xfMax ,yfMin );
+                               glVertex2f( xfMax ,yfMax );
+                               glEnd();
+                               glDisable( GL_LINE_STIPPLE );
+                               glEnable( GL_TEXTURE_2D );
                        }
 
                        // draw checkerboard for transparent textures
-                       if ( g_TextureBrowser_enableAlpha )
+                       if ( g_TextureBrowser_enableAlpha )
                        {
                                glDisable( GL_TEXTURE_2D );
                                glBegin( GL_QUADS );
                                int font_height = TextureBrowser_fontHeight( textureBrowser );
                                for ( int i = 0; i < nHeight; i += 8 )
-                               {
                                        for ( int j = 0; j < nWidth; j += 8 )
                                        {
                                                unsigned char color = (i + j) / 8 % 2 ? 0x66 : 0x99;
@@ -1278,7 +1367,6 @@ void Texture_Draw( TextureBrowser& textureBrowser ){
                                                glVertex2i(x + left,  y - nHeight - font_height + bottom);
                                                glVertex2i(x + right, y - nHeight - font_height + bottom);
                                        }
-                               }
                                glEnd();
                                glEnable( GL_TEXTURE_2D );
                        }
@@ -1302,7 +1390,7 @@ void Texture_Draw( TextureBrowser& textureBrowser ){
                        glDisable( GL_TEXTURE_2D );
                        glColor3f( 1,1,1 );
 
-                       glRasterPos2i( x, y - TextureBrowser_fontHeight( textureBrowser ) + 5 );
+                       glRasterPos2i( x, y - TextureBrowser_fontHeight( textureBrowser ) + 2 );//+5
 
                        // don't draw the directory name
                        const char* name = shader->getName();
@@ -1333,15 +1421,32 @@ void TextureBrowser_queueDraw( TextureBrowser& textureBrowser ){
 void TextureBrowser_setScale( TextureBrowser& textureBrowser, std::size_t scale ){
        textureBrowser.m_textureScale = scale;
 
+       textureBrowser.m_heightChanged = true;
+       textureBrowser.m_originInvalid = true;
+       g_activeShadersChangedCallbacks();
+
        TextureBrowser_queueDraw( textureBrowser );
 }
 
 void TextureBrowser_setUniformSize( TextureBrowser& textureBrowser, std::size_t scale ){
        textureBrowser.m_uniformTextureSize = scale;
 
+       textureBrowser.m_heightChanged = true;
+       textureBrowser.m_originInvalid = true;
+       g_activeShadersChangedCallbacks();
+
        TextureBrowser_queueDraw( textureBrowser );
 }
 
+void TextureBrowser_setUniformMinSize( TextureBrowser& textureBrowser, std::size_t scale ){
+       textureBrowser.m_uniformTextureMinSize = scale;
+
+       textureBrowser.m_heightChanged = true;
+       textureBrowser.m_originInvalid = true;
+       g_activeShadersChangedCallbacks();
+
+       TextureBrowser_queueDraw( textureBrowser );
+}
 
 void TextureBrowser_MouseWheel( TextureBrowser& textureBrowser, bool bUp ){
        int originy = TextureBrowser_getOriginY( textureBrowser );
@@ -1424,10 +1529,11 @@ void BuildStoreAvailableTags(   ui::ListStore storeAvailable,
 
 gboolean TextureBrowser_button_press( ui::Widget widget, GdkEventButton* event, TextureBrowser* textureBrowser ){
        if ( event->type == GDK_BUTTON_PRESS ) {
+               gtk_widget_grab_focus( widget );
                if ( event->button == 3 ) {
                        if ( textureBrowser->m_tags ) {
                                textureBrowser->m_rmbSelected = true;
-                               TextureBrowser_Selection_MouseDown( *textureBrowser, event->state, static_cast<int>( event->x ), static_cast<int>( event->y ) );
+                               TextureBrowser_Selection_MouseDown( *textureBrowser, event->state, static_cast<int>( event->x ), static_cast<int>( event->y ), false );
 
                                BuildStoreAssignedTags( textureBrowser->m_assigned_store, textureBrowser->shader.c_str(), textureBrowser );
                                BuildStoreAvailableTags( textureBrowser->m_available_store, textureBrowser->m_assigned_store, textureBrowser->m_all_tags, textureBrowser );
@@ -1443,8 +1549,8 @@ gboolean TextureBrowser_button_press( ui::Widget widget, GdkEventButton* event,
                                TextureBrowser_Tracking_MouseDown( *textureBrowser );
                        }
                }
-               else if ( event->button == 1 ) {
-                       TextureBrowser_Selection_MouseDown( *textureBrowser, event->state, static_cast<int>( event->x ), static_cast<int>( event->y ) );
+               else if ( event->button == 1 || event->button == 2 ) {
+                       TextureBrowser_Selection_MouseDown( *textureBrowser, event->state, static_cast<int>( event->x ), static_cast<int>( event->y ), event->button == 2 );
 
                        if ( textureBrowser->m_tags ) {
                                textureBrowser->m_rmbSelected = false;
@@ -1452,6 +1558,32 @@ gboolean TextureBrowser_button_press( ui::Widget widget, GdkEventButton* event,
                        }
                }
        }
+       else if ( event->type == GDK_2BUTTON_PRESS && event->button == 1 ) {
+               #define GARUX_DISABLE_2BUTTON
+               #ifndef GARUX_DISABLE_2BUTTON
+               CopiedString texName = textureBrowser->shader;
+               char* sh = const_cast<char*>( texName.c_str() );
+               char* dir = strrchr( sh, '/' );
+               if( dir != NULL ){
+                       *(dir + 1) = '\0';
+                       dir = strchr( sh, '/' );
+                       if( dir != NULL ){
+                               dir++;
+                               if( *dir != '\0'){
+                                       ScopeDisableScreenUpdates disableScreenUpdates( dir, "Loading Textures" );
+                                       TextureBrowser_ShowDirectory( *textureBrowser, dir );
+                                       TextureBrowser_Focus( *textureBrowser, textureBrowser->shader.c_str() );
+                                       TextureBrowser_queueDraw( *textureBrowser );
+                               }
+                       }
+               }
+               #endif
+       }
+       else if ( event->type == GDK_2BUTTON_PRESS && event->button == 3 ) {
+               ScopeDisableScreenUpdates disableScreenUpdates( TextureBrowser_getCommonShadersDir(), "Loading Textures" );
+               TextureBrowser_ShowDirectory( *textureBrowser, TextureBrowser_getCommonShadersDir() );
+               TextureBrowser_queueDraw( *textureBrowser );
+       }
        return FALSE;
 }
 
@@ -1462,6 +1594,9 @@ gboolean TextureBrowser_button_release( ui::Widget widget, GdkEventButton* event
                                TextureBrowser_Tracking_MouseUp( *textureBrowser );
                        }
                }
+               if ( event->button == 1 ) {
+                       TextureBrowser_Selection_MouseUp( *textureBrowser, event->state, static_cast<int>( event->x ), static_cast<int>( event->y ) );
+               }
        }
        return FALSE;
 }
@@ -1471,6 +1606,10 @@ gboolean TextureBrowser_motion( ui::Widget widget, GdkEventMotion *event, Textur
 }
 
 gboolean TextureBrowser_scroll( ui::Widget widget, GdkEventScroll* event, TextureBrowser* textureBrowser ){
+       gtk_widget_grab_focus( widget );
+       if( !gtk_window_is_active( textureBrowser->m_parent ) )
+               gtk_window_present( textureBrowser->m_parent );
+
        if ( event->direction == GDK_SCROLL_UP ) {
                TextureBrowser_MouseWheel( *textureBrowser, true );
        }
@@ -1626,6 +1765,7 @@ void TextureBrowser_constructTreeStore(){
        TextureGroups groups = TextureGroups_constructTreeView();
        auto store = ui::TreeStore::from(gtk_tree_store_new( 1, G_TYPE_STRING ));
        TextureGroups_constructTreeModel( groups, store );
+       std::set<CopiedString>::iterator iter;
 
        gtk_tree_view_set_model(GlobalTextureBrowser().m_treeViewTree, store);
 
@@ -1633,7 +1773,7 @@ void TextureBrowser_constructTreeStore(){
 }
 
 void TextureBrowser_constructTreeStoreTags(){
-       TextureGroups groups;
+       //TextureGroups groups;
        TextureBrowser &textureBrowser = GlobalTextureBrowser();
        auto store = ui::TreeStore::from(gtk_tree_store_new( 1, G_TYPE_STRING ));
        auto model = GlobalTextureBrowser().m_all_tags_list;
@@ -1665,6 +1805,8 @@ void TreeView_onRowActivated( ui::TreeView treeview, ui::TreePath path, ui::Tree
                ScopeDisableScreenUpdates disableScreenUpdates( dirName, "Loading Textures" );
                TextureBrowser_ShowDirectory( GlobalTextureBrowser(), dirName );
                TextureBrowser_queueDraw( GlobalTextureBrowser() );
+               //deactivate, so SPACE and RETURN wont be broken for 2d
+               gtk_window_set_focus( GTK_WINDOW( gtk_widget_get_toplevel( GTK_WIDGET( treeview ) ) ), NULL );
        }
 }
 
@@ -1709,6 +1851,8 @@ void TextureBrowser_createContextMenu( ui::Widget treeview, GdkEventButton *even
                                        gdk_event_get_time( (GdkEvent*)event ) );
 }
 
+void TextureBrowser_searchTags();
+
 gboolean TreeViewTags_onButtonPressed( ui::TreeView treeview, GdkEventButton *event ){
        if ( event->type == GDK_BUTTON_PRESS && event->button == 3 ) {
                GtkTreePath *path;
@@ -1723,6 +1867,10 @@ gboolean TreeViewTags_onButtonPressed( ui::TreeView treeview, GdkEventButton *ev
                TextureBrowser_createContextMenu( treeview, event );
                return TRUE;
        }
+       if( event->type == GDK_2BUTTON_PRESS && event->button == 1 ){
+               TextureBrowser_searchTags();
+               return TRUE;
+       }
        return FALSE;
 }
 
@@ -1750,6 +1898,8 @@ ui::MenuItem TextureBrowser_constructViewMenu( ui::Menu menu ){
        }
 
        create_check_menu_item_with_mnemonic( menu, "Hide _Unused", "ShowInUse" );
+       create_menu_item_with_mnemonic( menu, "Show All", "ShowAllTextures" );
+
        if ( string_empty( g_pGameDescription->getKeyValue( "show_wads" ) ) ) {
                create_check_menu_item_with_mnemonic( menu, "Hide Image Missing", "FilterMissing" );
        }
@@ -1759,7 +1909,6 @@ ui::MenuItem TextureBrowser_constructViewMenu( ui::Menu menu ){
 
        menu_separator( menu );
 
-       create_menu_item_with_mnemonic( menu, "Show All", "ShowAllTextures" );
 
        // we always want to show shaders but don't want a "Show Shaders" menu for doom3 and .wad file games
        if ( g_pGameDescription->mGameType == "doom3" || TextureBrowser_showWads() ) {
@@ -1768,14 +1917,16 @@ ui::MenuItem TextureBrowser_constructViewMenu( ui::Menu menu ){
        else
        {
                create_check_menu_item_with_mnemonic( menu, "Show shaders", "ToggleShowShaders" );
+               create_check_menu_item_with_mnemonic( menu, "Show textures", "ToggleShowTextures" );
+               menu_separator( menu );
        }
 
-       if ( g_pGameDescription->mGameType != "doom3" && string_empty( g_pGameDescription->getKeyValue( "show_wads" ) ) ) {
-               create_check_menu_item_with_mnemonic( menu, "Shaders Only", "ToggleShowShaderlistOnly" );
-       }
        if ( textureBrowser.m_tags ) {
                create_menu_item_with_mnemonic( menu, "Show Untagged", "ShowUntagged" );
        }
+       if ( g_pGameDescription->mGameType != "doom3" && string_empty( g_pGameDescription->getKeyValue( "show_wads" ) ) ) {
+               create_check_menu_item_with_mnemonic( menu, "ShaderList Only", "ToggleShowShaderlistOnly" );
+       }
 
        menu_separator( menu );
        create_check_menu_item_with_mnemonic( menu, "Fixed Size", "FixedSize" );
@@ -1791,6 +1942,10 @@ ui::MenuItem TextureBrowser_constructViewMenu( ui::Menu menu ){
        return textures_menu_item;
 }
 
+void Popup_View_Menu( GtkWidget *widget, GtkMenu *menu ){
+       gtk_menu_popup( menu, NULL, NULL, NULL, NULL, 1, gtk_get_current_event_time() );
+}
+
 ui::MenuItem TextureBrowser_constructToolsMenu( ui::Menu menu ){
        ui::MenuItem textures_menu_item = ui::MenuItem(new_sub_menu_item_with_mnemonic( "_Tools" ));
 
@@ -1990,6 +2145,7 @@ void TextureBrowser_searchTags(){
                                TextureDirectory_loadTexture( path.c_str(), name.c_str() );
                        }
                }
+               TextureBrowser_SetHideUnused( textureBrowser, false );
                textureBrowser.m_searchedTags = true;
                g_TextureBrowser_currentDirectory = tags_searched;
 
@@ -2070,7 +2226,7 @@ void TextureBrowser_checkTagFile(){
                }
                else
                {
-                       globalErrorStream() << "Unable to find default tag file " << default_filename.c_str() << ". No tag support.\n";
+                       globalOutputStream() << "Unable to find default tag file " << default_filename.c_str() << ". No tag support. Plugins -> ShaderPlug -> Create tag file: to start using tags\n";
                }
        }
 }
@@ -2136,22 +2292,48 @@ ui::Widget TextureBrowser_constructWindow( ui::Window toplevel ){
        table.attach(vbox, {0, 1, 1, 3}, {GTK_FILL, GTK_FILL});
        vbox.show();
 
-       ui::Widget menu_bar{ui::null};
+       // ui::Widget menu_bar{ui::null};
+       auto toolbar = ui::Toolbar::from( gtk_toolbar_new() );
 
        { // menu bar
-               menu_bar = ui::Widget::from(gtk_menu_bar_new());
+               // menu_bar = ui::Widget::from(gtk_menu_bar_new());
                auto menu_view = ui::Menu(ui::New);
-               auto view_item = TextureBrowser_constructViewMenu( menu_view );
-               gtk_menu_item_set_submenu( GTK_MENU_ITEM( view_item ), menu_view );
-               gtk_menu_shell_append( GTK_MENU_SHELL( menu_bar ), view_item );
+               // auto view_item = TextureBrowser_constructViewMenu( menu_view );
+               TextureBrowser_constructViewMenu( menu_view );
+               gtk_menu_set_title( menu_view, "View" );
+               // gtk_menu_item_set_submenu( GTK_MENU_ITEM( view_item ), menu_view );
+               // gtk_menu_shell_append( GTK_MENU_SHELL( menu_bar ), view_item );
+
+               //gtk_table_attach( GTK_TABLE( table ), GTK_WIDGET( toolbar ), 0, 1, 0, 1, GTK_FILL, GTK_FILL, 0, 0 );
+               gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( toolbar ), FALSE, FALSE, 0 );
 
+               //view menu button
+               {
+                       auto button = toolbar_append_button( toolbar, "View", "texbro_view.png" );
+                       button.dimensions( 22, 22 );
+                       button.connect( "clicked", G_CALLBACK( Popup_View_Menu ), menu_view );
+
+                       //to show detached menu over floating tex bro
+                       gtk_menu_attach_to_widget( GTK_MENU( menu_view ), GTK_WIDGET( button ), NULL );
+               }
+               {
+                       auto button = toolbar_append_button( toolbar, "Find / Replace...", "texbro_gtk-find-and-replace.png", "FindReplaceTextures" );
+                       button.dimensions( 22, 22 );
+               }
+               {
+                       auto button = toolbar_append_button( toolbar, "Flush & Reload Shaders", "texbro_refresh.png", "RefreshShaders" );
+                       button.dimensions( 22, 22 );
+               }
+               toolbar.show();
+
+/*
                auto menu_tools = ui::Menu(ui::New);
                auto tools_item = TextureBrowser_constructToolsMenu( menu_tools );
                gtk_menu_item_set_submenu( GTK_MENU_ITEM( tools_item ), menu_tools );
                gtk_menu_shell_append( GTK_MENU_SHELL( menu_bar ), tools_item );
-
-               table.attach(menu_bar, {0, 3, 0, 1}, {GTK_FILL, GTK_SHRINK});
-               menu_bar.show();
+*/
+               // table.attach(menu_bar, {0, 3, 0, 1}, {GTK_FILL, GTK_SHRINK});
+               // menu_bar.show();
        }
        { // Texture TreeView
                textureBrowser.m_scr_win_tree = ui::ScrolledWindow(ui::New);
@@ -2164,7 +2346,9 @@ ui::Widget TextureBrowser_constructWindow( ui::Window toplevel ){
 
                TextureBrowser_createTreeViewTree();
 
-               gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW( textureBrowser.m_scr_win_tree ), textureBrowser.m_treeViewTree  );
+               //gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW( textureBrowser.m_scr_win_tree ), textureBrowser.m_treeViewTree  );
+               gtk_container_add( GTK_CONTAINER( textureBrowser.m_scr_win_tree ), GTK_WIDGET( textureBrowser.m_treeViewTree ) );
+
                textureBrowser.m_treeViewTree.show();
        }
        { // gl_widget scrollbar
@@ -2217,9 +2401,18 @@ ui::Widget TextureBrowser_constructWindow( ui::Window toplevel ){
                }
                { // tag menu bar
                        auto menu_tags = ui::Menu(ui::New);
-                       auto tags_item = TextureBrowser_constructTagsMenu( menu_tags );
-                       gtk_menu_item_set_submenu( GTK_MENU_ITEM( tags_item ), menu_tags );
-                       gtk_menu_shell_append( GTK_MENU_SHELL( menu_bar ), tags_item );
+                       gtk_menu_set_title( GTK_MENU( menu_tags ), "Tags" );
+                       // auto tags_item = TextureBrowser_constructTagsMenu( menu_tags );
+                       TextureBrowser_constructTagsMenu( menu_tags );
+                       // gtk_menu_item_set_submenu( GTK_MENU_ITEM( tags_item ), menu_tags );
+                       // gtk_menu_shell_append( GTK_MENU_SHELL( menu_bar ), tags_item );
+
+                       auto button = toolbar_append_button( toolbar, "Tags", "texbro_tags.png" );
+                       button.dimensions( 22, 22 );
+                       button.connect( "clicked", G_CALLBACK( Popup_View_Menu ), menu_tags );
+
+                       //to show detached menu over floating tex bro and main wnd...
+                       gtk_menu_attach_to_widget( GTK_MENU( menu_tags ), GTK_WIDGET( button ), NULL );
                }
                { // Tag TreeView
                        textureBrowser.m_scr_win_tags = ui::ScrolledWindow(ui::New);
@@ -2233,7 +2426,9 @@ ui::Widget TextureBrowser_constructWindow( ui::Window toplevel ){
             auto selection = gtk_tree_view_get_selection(textureBrowser.m_treeViewTags );
                        gtk_tree_selection_set_mode( selection, GTK_SELECTION_MULTIPLE );
 
-                       gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW( textureBrowser.m_scr_win_tags ), textureBrowser.m_treeViewTags  );
+                       //gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW( textureBrowser.m_scr_win_tags ), textureBrowser.m_treeViewTags  );
+                       gtk_container_add( GTK_CONTAINER( textureBrowser.m_scr_win_tags ), GTK_WIDGET( textureBrowser.m_treeViewTags ) );
+
                        textureBrowser.m_treeViewTags.show();
                }
                { // Texture/Tag notebook
@@ -2282,7 +2477,9 @@ ui::Widget TextureBrowser_constructWindow( ui::Window toplevel ){
                        textureBrowser.m_assigned_tree.show();
 
                        scrolled_win.show();
-                       gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW( scrolled_win ), textureBrowser.m_assigned_tree  );
+
+                       //gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW( scrolled_win ), textureBrowser.m_assigned_tree  );
+                       gtk_container_add( GTK_CONTAINER( scrolled_win ), GTK_WIDGET( textureBrowser.m_available_tree ) );
 
                        frame_table.attach(scrolled_win, {0, 1, 1, 3}, {GTK_FILL, GTK_FILL});
                }
@@ -2310,7 +2507,9 @@ ui::Widget TextureBrowser_constructWindow( ui::Window toplevel ){
                        textureBrowser.m_available_tree.show();
 
                        scrolled_win.show();
-                       gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW( scrolled_win ), textureBrowser.m_available_tree  );
+
+                       //gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW( scrolled_win ), textureBrowser.m_available_tree  );
+                       gtk_container_add( GTK_CONTAINER( scrolled_win ), GTK_WIDGET( textureBrowser.m_available_tree ) );
 
                        frame_table.attach(scrolled_win, {2, 3, 1, 3}, {GTK_FILL, GTK_FILL});
                }
@@ -2402,7 +2601,7 @@ void TextureBrowser_showGLWidget(){
                textureBrowser.m_vframe.set_child_packing( textureBrowser.m_gl_widget, TRUE, TRUE, 0, ui::Packing::START );
 
                textureBrowser.m_gl_widget.show();
-       }
+}
 }
 
 void TextureBrowser_hideGLWidget(){
@@ -2610,9 +2809,10 @@ void TextureBrowser_pasteTag(){
 
 void TextureBrowser_RefreshShaders(){
        TextureBrowser &textureBrowser = GlobalTextureBrowser();
-       ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Shaders" );
-       GlobalShaderSystem().refresh();
-       UpdateAllWindows();
+
+       /* When shaders are refreshed, forces reloading the textures as well.
+       Previously it would at best only display shaders, at worst mess up some textured objects. */
+
     auto selection = gtk_tree_view_get_selection(GlobalTextureBrowser().m_treeViewTree);
        GtkTreeModel* model = NULL;
        GtkTreeIter iter;
@@ -2627,14 +2827,45 @@ void TextureBrowser_RefreshShaders(){
                if ( !TextureBrowser_showWads() ) {
                        strcat( dirName, "/" );
                }
+
+               ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Shaders" );
+               GlobalShaderSystem().refresh();
+               /* texturebrowser tree update on vfs restart */
+               TextureBrowser_constructTreeStore();
+               UpdateAllWindows();
+
                TextureBrowser_ShowDirectory( GlobalTextureBrowser(), dirName );
                TextureBrowser_queueDraw( GlobalTextureBrowser() );
        }
+
+       else{
+               ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Shaders" );
+               GlobalShaderSystem().refresh();
+               /* texturebrowser tree update on vfs restart */
+               TextureBrowser_constructTreeStore();
+               UpdateAllWindows();
+       }
 }
 
 void TextureBrowser_ToggleShowShaders(){
        GlobalTextureBrowser().m_showShaders ^= 1;
        GlobalTextureBrowser().m_showshaders_item.update();
+
+       GlobalTextureBrowser().m_heightChanged = true;
+       GlobalTextureBrowser().m_originInvalid = true;
+       g_activeShadersChangedCallbacks();
+
+       TextureBrowser_queueDraw( GlobalTextureBrowser() );
+}
+
+void TextureBrowser_ToggleShowTextures(){
+       GlobalTextureBrowser().m_showTextures ^= 1;
+       GlobalTextureBrowser().m_showtextures_item.update();
+
+       GlobalTextureBrowser().m_heightChanged = true;
+       GlobalTextureBrowser().m_originInvalid = true;
+       g_activeShadersChangedCallbacks();
+
        TextureBrowser_queueDraw( GlobalTextureBrowser() );
 }
 
@@ -2648,7 +2879,9 @@ void TextureBrowser_ToggleShowShaderListOnly(){
 void TextureBrowser_showAll(){
        g_TextureBrowser_currentDirectory = "";
        GlobalTextureBrowser().m_searchedTags = false;
-       TextureBrowser_heightChanged( GlobalTextureBrowser() );
+//     TextureBrowser_SetHideUnused( GlobalTextureBrowser(), false );
+       TextureBrowser_ToggleHideUnused();
+       //TextureBrowser_heightChanged( GlobalTextureBrowser() );
        TextureBrowser_updateTitle();
 }
 
@@ -2772,6 +3005,17 @@ struct UniformTextureSize {
        }
 };
 
+struct UniformTextureMinSize {
+       static void Export(const TextureBrowser &self, const Callback<void(int)> &returnz) {
+               returnz(GlobalTextureBrowser().m_uniformTextureMinSize);
+       }
+
+       static void Import(TextureBrowser &self, int value) {
+               if (value > 16)
+                       TextureBrowser_setUniformSize(self, value);
+       }
+};
+
 void TextureBrowser_constructPreferences( PreferencesPage& page ){
        page.appendCheckBox(
                "", "Texture scrollbar",
@@ -2785,18 +3029,23 @@ void TextureBrowser_constructPreferences( PreferencesPage& page ){
                        make_property<TextureScale>(GlobalTextureBrowser())
                        );
        }
-       page.appendSpinner(
-               "Texture Thumbnail Size",
-               GlobalTextureBrowser().m_uniformTextureSize,
-               GlobalTextureBrowser().m_uniformTextureSize,
-               16, 8192
-       );
+       page.appendSpinner( "Thumbnails Max Size", GlobalTextureBrowser().m_uniformTextureSize, GlobalTextureBrowser().m_uniformTextureSize, 16, 8192 );
+       page.appendSpinner( "Thumbnails Min Size", GlobalTextureBrowser().m_uniformTextureMinSize, GlobalTextureBrowser().m_uniformTextureMinSize, 16, 8192 );
        page.appendEntry( "Mousewheel Increment", GlobalTextureBrowser().m_mouseWheelScrollIncrement );
        {
                const char* startup_shaders[] = { "None", TextureBrowser_getCommonShadersName() };
                page.appendCombo( "Load Shaders at Startup", reinterpret_cast<int&>( GlobalTextureBrowser().m_startupShaders ), STRING_ARRAY_RANGE( startup_shaders ) );
        }
+       {
+               StringOutputStream sstream( 256 );
+               sstream << "Hide nonShaders in " << TextureBrowser_getCommonShadersDir() << " folder";
+               page.appendCheckBox(
+                       "", sstream.c_str(),
+                       GlobalTextureBrowser().m_hideNonShadersInCommon
+                       );
+       }
 }
+
 void TextureBrowser_constructPage( PreferenceGroup& group ){
        PreferencesPage page( group.createPage( "Texture Browser", "Texture Browser Preferences" ) );
        TextureBrowser_constructPreferences( page );
@@ -2827,17 +3076,21 @@ void TextureBrowser_Construct(){
        GlobalToggles_insert( "ShowInUse", makeCallbackF(TextureBrowser_ToggleHideUnused), ToggleItem::AddCallbackCaller( textureBrowser.m_hideunused_item ), Accelerator( 'U' ) );
        GlobalCommands_insert( "ShowAllTextures", makeCallbackF(TextureBrowser_showAll), Accelerator( 'A', (GdkModifierType)GDK_CONTROL_MASK ) );
        GlobalCommands_insert( "ToggleTextures", makeCallbackF(TextureBrowser_toggleShow), Accelerator( 'T' ) );
-       GlobalToggles_insert( "ToggleShowShaders", makeCallbackF(TextureBrowser_ToggleShowShaders), ToggleItem::AddCallbackCaller( textureBrowser.m_showshaders_item ) );
-       GlobalToggles_insert( "ToggleShowShaderlistOnly", makeCallbackF(TextureBrowser_ToggleShowShaderListOnly), ToggleItem::AddCallbackCaller( textureBrowser.m_showshaderlistonly_item ) );
-       GlobalToggles_insert( "FixedSize", makeCallbackF(TextureBrowser_FixedSize), ToggleItem::AddCallbackCaller( textureBrowser.m_fixedsize_item ) );
-       GlobalToggles_insert( "FilterMissing", makeCallbackF(TextureBrowser_FilterMissing), ToggleItem::AddCallbackCaller( textureBrowser.m_filternotex_item ) );
-       GlobalToggles_insert( "FilterFallback", makeCallbackF(TextureBrowser_FilterFallback), ToggleItem::AddCallbackCaller( textureBrowser.m_hidenotex_item ) );
-       GlobalToggles_insert( "EnableAlpha", makeCallbackF(TextureBrowser_EnableAlpha), ToggleItem::AddCallbackCaller( textureBrowser.m_enablealpha_item ) );
+       GlobalToggles_insert( "ToggleShowShaders", makeCallbackF(TextureBrowser_ToggleShowShaders), ToggleItem::AddCallbackCaller( GlobalTextureBrowser().m_showshaders_item ) );
+       GlobalToggles_insert( "ToggleShowTextures", makeCallbackF(TextureBrowser_ToggleShowTextures), ToggleItem::AddCallbackCaller( GlobalTextureBrowser().m_showtextures_item ) );
+       GlobalToggles_insert( "ToggleShowShaderlistOnly", makeCallbackF(TextureBrowser_ToggleShowShaderListOnly),
+ ToggleItem::AddCallbackCaller( GlobalTextureBrowser().m_showshaderlistonly_item ) );
+       GlobalToggles_insert( "FixedSize", makeCallbackF(TextureBrowser_FixedSize), ToggleItem::AddCallbackCaller( GlobalTextureBrowser().m_fixedsize_item ) );
+       GlobalToggles_insert( "FilterMissing", makeCallbackF(TextureBrowser_FilterMissing), ToggleItem::AddCallbackCaller( GlobalTextureBrowser().m_filternotex_item ) );
+       GlobalToggles_insert( "FilterFallback", makeCallbackF(TextureBrowser_FilterFallback), ToggleItem::AddCallbackCaller( GlobalTextureBrowser().m_hidenotex_item ) );
+       GlobalToggles_insert( "EnableAlpha", makeCallbackF(TextureBrowser_EnableAlpha), ToggleItem::AddCallbackCaller( GlobalTextureBrowser().m_enablealpha_item ) );
 
        GlobalPreferenceSystem().registerPreference( "TextureScale", make_property_string<TextureScale>(textureBrowser) );
        GlobalPreferenceSystem().registerPreference( "UniformTextureSize", make_property_string<UniformTextureSize>(textureBrowser) );
+       GlobalPreferenceSystem().registerPreference( "UniformTextureMinSize", make_property_string<UniformTextureMinSize>(textureBrowser) );
        GlobalPreferenceSystem().registerPreference( "TextureScrollbar", make_property_string<TextureBrowser_ShowScrollbar>(textureBrowser));
        GlobalPreferenceSystem().registerPreference( "ShowShaders", make_property_string( textureBrowser.m_showShaders ) );
+       GlobalPreferenceSystem().registerPreference( "ShowTextures", make_property_string( GlobalTextureBrowser().m_showTextures ) );
        GlobalPreferenceSystem().registerPreference( "ShowShaderlistOnly", make_property_string( g_TextureBrowser_shaderlistOnly ) );
        GlobalPreferenceSystem().registerPreference( "FixedSize", make_property_string( g_TextureBrowser_fixedSize ) );
        GlobalPreferenceSystem().registerPreference( "FilterMissing", make_property_string( g_TextureBrowser_filterMissing ) );
@@ -2845,6 +3098,7 @@ void TextureBrowser_Construct(){
        GlobalPreferenceSystem().registerPreference( "LoadShaders", make_property_string( reinterpret_cast<int&>( textureBrowser.m_startupShaders ) ) );
        GlobalPreferenceSystem().registerPreference( "WheelMouseInc", make_property_string( textureBrowser.m_mouseWheelScrollIncrement ) );
        GlobalPreferenceSystem().registerPreference( "SI_Colors0", make_property_string( textureBrowser.color_textureback ) );
+       GlobalPreferenceSystem().registerPreference( "HideNonShadersInCommon", make_property_string( GlobalTextureBrowser().m_hideNonShadersInCommon ) );
 
        textureBrowser.shader = texdef_name_default();
 
index ad322d5d817c6e76da75e2723cdf0ab4a855e938..869b020dc2379718d131e4f966bcd04a073f5cc6 100644 (file)
@@ -187,6 +187,9 @@ bool fill() const {
 const Vector3& getViewer() const {
        return vector4_to_vector3( m_viewer );
 }
+const Frustum& getFrustum() const {
+       return m_frustum;
+}
 };
 
 #endif
index bb65e57337b331c5161a7f894e9c00111481899b..33fbdfd5e7f68ff108525b2dddd583841ae65032 100644 (file)
@@ -211,6 +211,7 @@ void BuildMonitor_Construct(){
 
 void BuildMonitor_Destroy(){
        delete g_pWatchBSP;
+       g_pWatchBSP = NULL;
 }
 
 CWatchBSP *GetWatchBSP(){
@@ -469,7 +470,7 @@ static xmlSAXHandler saxParser = {
 // ------------------------------------------------------------------------------------------------
 
 
-guint s_routine_id;
+guint s_routine_id = 0;
 static gint watchbsp_routine( gpointer data ){
        reinterpret_cast<CWatchBSP*>( data )->RoutineProcessing();
        return TRUE;
@@ -489,8 +490,9 @@ void CWatchBSP::Reset(){
                m_xmlInputBuffer = NULL;
        }
        m_eState = EIdle;
-       if ( s_routine_id ) {
+       if ( s_routine_id != 0 ) {
                g_source_remove( s_routine_id );
+               s_routine_id = 0;
        }
 }
 
index 9651af2c2c027dc665fbda6d9f91dfa7ddb721ec..8865db52aff206f4a0050d90eac9608c90b64f7d 100644 (file)
@@ -194,6 +194,10 @@ const double DEBUG_EPSILON_SQUARED = DEBUG_EPSILON * DEBUG_EPSILON;
 /// If \p winding is completely in back of the plane, \p clipped will be empty.
 /// If \p winding intersects the plane, the edge of \p clipped which lies on \p clipPlane will store the value of \p adjacent.
 void Winding_Clip( const FixedWinding& winding, const Plane3& plane, const Plane3& clipPlane, std::size_t adjacent, FixedWinding& clipped ){
+       // if there are no points, we're done
+       if ( winding.size() == 0 ) {
+               return;
+       }
        PlaneClassification classification = Winding_ClassifyDistance( plane3_distance_to_point( clipPlane, winding.back().vertex ), ON_EPSILON );
        PlaneClassification nextClassification;
        // for each edge
index 35d491701aa9a318ee3cfa822aa852a02b302f81..c06994c4fa90df0765d4ef1fae205952bd124c4a 100644 (file)
@@ -113,6 +113,7 @@ void Draw( const char *label, float scale );
 VIEWTYPE g_clip_viewtype;
 bool g_bSwitch = true;
 bool g_clip_useCaulk = false;
+bool g_quick_clipper = false;
 ClipPoint g_Clip1;
 ClipPoint g_Clip2;
 ClipPoint g_Clip3;
@@ -138,7 +139,9 @@ void ClipPoint::Draw( const char *label, float scale ){
 
        // draw label
        glRasterPos3f( m_ptClip[0] + offset, m_ptClip[1] + offset, m_ptClip[2] + offset );
-       glCallLists( GLsizei( strlen( label ) ), GL_UNSIGNED_BYTE, label );
+       //glCallLists( GLsizei( strlen( label ) ), GL_UNSIGNED_BYTE, label );   //fails with GCC
+       //glCallLists( GLsizei( strlen( label ) ), GL_UNSIGNED_BYTE, reinterpret_cast<const GLubyte*>( label ) );       //worx
+       GlobalOpenGL().drawString( label );
 }
 
 float fDiff( float f1, float f2 ){
@@ -261,6 +264,10 @@ void Clip(){
                g_Clip3.Reset();
                Clip_Update();
                ClipperChangeNotify();
+               if( g_quick_clipper ){
+                       g_quick_clipper = false;
+                       ClipperMode();
+               }
        }
 }
 
@@ -275,6 +282,10 @@ void SplitClip(){
                g_Clip3.Reset();
                Clip_Update();
                ClipperChangeNotify();
+               if( g_quick_clipper ){
+                       g_quick_clipper = false;
+                       ClipperMode();
+               }
        }
 }
 
@@ -345,26 +356,30 @@ struct xywindow_globals_private_t
        bool show_blocks;
        int blockSize;
 
-       bool m_bCamXYUpdate;
+//     bool m_bCamXYUpdate;
        bool m_bChaseMouse;
        bool m_bSizePaint;
 
+       bool g_bCrossHairs;
+
        xywindow_globals_private_t() :
                d_showgrid( true ),
 
                show_names( false ),
-               show_coordinates( true ),
+               show_coordinates( false ),
                show_angles( true ),
-               show_outline( false ),
+               show_outline( true ),
                show_axis( true ),
 
                d_show_work( false ),
 
                show_blocks( false ),
 
-               m_bCamXYUpdate( true ),
+//             m_bCamXYUpdate( true ),
                m_bChaseMouse( true ),
-               m_bSizePaint( true ){
+               m_bSizePaint( true ),
+
+               g_bCrossHairs( false ){
        }
 
 };
@@ -497,17 +512,17 @@ void XYWnd::SetScale( float f ){
        XYWnd_Update( *this );
 }
 
-void XYWnd_ZoomIn( XYWnd* xy ){
+void XYWnd::ZoomIn(){
        float max_scale = 64;
-       float scale = xy->Scale() * 5.0f / 4.0f;
+       float scale = Scale() * 5.0f / 4.0f;
        if ( scale > max_scale ) {
-               if ( xy->Scale() != max_scale ) {
-                       xy->SetScale( max_scale );
+               if ( Scale() != max_scale ) {
+                       SetScale( max_scale );
                }
        }
        else
        {
-               xy->SetScale( scale );
+               SetScale( scale );
        }
 }
 
@@ -515,17 +530,31 @@ void XYWnd_ZoomIn( XYWnd* xy ){
 // NOTE: the zoom out factor is 4/5, we could think about customizing it
 //  we don't go below a zoom factor corresponding to 10% of the max world size
 //  (this has to be computed against the window size)
-void XYWnd_ZoomOut( XYWnd* xy ){
-       float min_scale = MIN( xy->Width(),xy->Height() ) / ( 1.1f * ( g_MaxWorldCoord - g_MinWorldCoord ) );
-       float scale = xy->Scale() * 4.0f / 5.0f;
+void XYWnd::ZoomOut(){
+       float min_scale = MIN( Width(), Height() ) / ( 1.1f * ( g_MaxWorldCoord - g_MinWorldCoord ) );
+       float scale = Scale() * 4.0f / 5.0f;
        if ( scale < min_scale ) {
-               if ( xy->Scale() != min_scale ) {
-                       xy->SetScale( min_scale );
+               if ( Scale() != min_scale ) {
+                       SetScale( min_scale );
                }
        }
        else
        {
-               xy->SetScale( scale );
+               SetScale( scale );
+       }
+}
+
+void XYWnd::ZoomInWithMouse( int pointx, int pointy ){
+       float old_scale = Scale();
+       ZoomIn();
+       if ( g_xywindow_globals.m_bZoomInToPointer ) {
+               float scale_diff = 1.0 / old_scale - 1.0 / Scale();
+               int nDim1 = ( m_viewType == YZ ) ? 1 : 0;
+               int nDim2 = ( m_viewType == XY ) ? 1 : 2;
+               Vector3 origin = GetOrigin();
+               origin[nDim1] += scale_diff * (pointx - 0.5 * Width());
+               origin[nDim2] -= scale_diff * (pointy - 0.5 * Height());
+               SetOrigin( origin );
        }
 }
 
@@ -542,6 +571,20 @@ void XYWnd::Redraw() {
        }
 }
 
+void XYWnd::FocusOnBounds( AABB& bounds ){
+       SetOrigin( bounds.origin );
+       int nDim1 = ( m_viewType == YZ ) ? 1 : 0;
+       int nDim2 = ( m_viewType == XY ) ? 1 : 2;
+       if( bounds.extents[ nDim1 ] < 128.f )
+               bounds.extents[ nDim1 ] = 128.f;
+       if( bounds.extents[ nDim2 ] < 128.f )
+               bounds.extents[ nDim2 ] = 128.f;
+       float scale1 = Width() / ( 3.f * bounds.extents[ nDim1 ] );
+       float scale2 = Height() / ( 3.f * bounds.extents[ nDim2 ] );
+       SetScale( MIN( scale1, scale2 ) );
+
+}
+
 VIEWTYPE GlobalXYWnd_getCurrentViewType(){
        ASSERT_NOTNULL( g_pParentWnd );
        ASSERT_NOTNULL( g_pParentWnd->ActiveXY() );
@@ -551,8 +594,6 @@ VIEWTYPE GlobalXYWnd_getCurrentViewType(){
 // =============================================================================
 // variables
 
-bool g_bCrossHairs = false;
-
 ui::Menu XYWnd::m_mnuDrop(ui::null);
 
 // this is disabled, and broken
@@ -723,13 +764,26 @@ Shader* XYWnd::m_state_selected = 0;
 
 void xy_update_xor_rectangle( XYWnd& self, rect_t area ){
        if ( self.GetWidget().visible() ) {
-               self.m_XORRectangle.set( rectangle_from_area( area.min, area.max, self.Width(), self.Height() ) );
+               rectangle_t rect = rectangle_from_area( area.min, area.max, self.Width(), self.Height() );
+//             int nDim1 = ( self.GetViewType() == YZ ) ? 1 : 0;
+//             int nDim2 = ( self.GetViewType() == XY ) ? 1 : 2;
+//             rect.x /= self.Scale();
+//             rect.y /= self.Scale();
+//             rect.w /= self.Scale();
+//             rect.h /= self.Scale();
+//             rect.x += self.GetOrigin()[nDim1];
+//             rect.y += self.GetOrigin()[nDim2];
+               self.m_XORRectangle.set( rect );
        }
 }
 
 gboolean xywnd_button_press( ui::Widget widget, GdkEventButton* event, XYWnd* xywnd ){
        if ( event->type == GDK_BUTTON_PRESS ) {
-               g_pParentWnd->SetActiveXY( xywnd );
+               gtk_widget_grab_focus( xywnd->GetWidget() );
+
+               if( !xywnd->Active() ){
+                       g_pParentWnd->SetActiveXY( xywnd );
+               }
 
                xywnd->ButtonState_onMouseDown( buttons_for_event_button( event ) );
 
@@ -750,7 +804,9 @@ gboolean xywnd_button_release( ui::Widget widget, GdkEventButton* event, XYWnd*
 gboolean xywnd_focus_in( ui::Widget widget, GdkEventFocus* event, XYWnd* xywnd ){
        if ( event->type == GDK_FOCUS_CHANGE ) {
                if ( event->in ) {
-                       g_pParentWnd->SetActiveXY( xywnd );
+                       if( !xywnd->Active() ){
+                               g_pParentWnd->SetActiveXY( xywnd );
+                       }
                }
        }
        return FALSE;
@@ -764,11 +820,19 @@ void xywnd_motion( gdouble x, gdouble y, guint state, void* data ){
 }
 
 gboolean xywnd_wheel_scroll( ui::Widget widget, GdkEventScroll* event, XYWnd* xywnd ){
+       gtk_widget_grab_focus( xywnd->GetWidget() );
+       ui::Window window = xywnd->m_parent ? xywnd->m_parent : MainFrame_getWindow();
+       if( !gtk_window_is_active( window ) )
+               gtk_window_present( window );
+
+       if( !xywnd->Active() ){
+               g_pParentWnd->SetActiveXY( xywnd );
+       }
        if ( event->direction == GDK_SCROLL_UP ) {
-               XYWnd_ZoomIn( xywnd );
+               xywnd->ZoomInWithMouse( (int)event->x, (int)event->y );
        }
        else if ( event->direction == GDK_SCROLL_DOWN ) {
-               XYWnd_ZoomOut( xywnd );
+               xywnd->ZoomOut();
        }
        return FALSE;
 }
@@ -787,9 +851,10 @@ gboolean xywnd_expose( ui::Widget widget, GdkEventExpose* event, XYWnd* xywnd ){
 }
 
 void XYWnd_CameraMoved( XYWnd& xywnd ){
-       if ( g_xywindow_globals_private.m_bCamXYUpdate ) {
-               XYWnd_Update( xywnd );
-       }
+//     if ( g_xywindow_globals_private.m_bCamXYUpdate ) {
+               //XYWnd_Update( xywnd );
+               xywnd.UpdateCameraIcon();
+//     }
 }
 
 XYWnd::XYWnd() :
@@ -799,7 +864,7 @@ XYWnd::XYWnd() :
        m_parent( ui::null ),
        m_window_observer( NewWindowObserver() ),
        m_XORRectangle( m_gl_widget ),
-       m_chasemouse_handler( 0 ) {
+       m_chasemouse_handler( 0 ){
 
        m_bActive = false;
        m_buttonstate = 0;
@@ -844,7 +909,7 @@ XYWnd::XYWnd() :
 
        m_gl_widget.connect( "button_press_event", G_CALLBACK( xywnd_button_press ), this );
        m_gl_widget.connect( "button_release_event", G_CALLBACK( xywnd_button_release ), this );
-       m_gl_widget.connect( "focus_in_event", G_CALLBACK( xywnd_focus_in ), this );
+       m_gl_widget.connect( "focus_in_event", G_CALLBACK( xywnd_focus_in ), this );    //works only in floating views layout
        m_gl_widget.connect( "motion_notify_event", G_CALLBACK( DeferredMotion::gtk_motion ), &m_deferred_motion );
 
        m_gl_widget.connect( "scroll_event", G_CALLBACK( xywnd_wheel_scroll ), this );
@@ -911,6 +976,10 @@ unsigned int Clipper_buttons(){
        return RAD_LBUTTON;
 }
 
+unsigned int Clipper_quick_buttons(){
+       return RAD_LBUTTON | RAD_CONTROL;
+}
+
 void XYWnd::DropClipPoint( int pointx, int pointy ){
        Vector3 point;
 
@@ -949,20 +1018,58 @@ void XYWnd::Clipper_OnMouseMoved( int x, int y ){
        }
 }
 
+//#include "gtkutil/image.h"
+
+/* is called on every mouse move fraction; ain't good! */
 void XYWnd::Clipper_Crosshair_OnMouseMoved( int x, int y ){
        Vector3 mousePosition;
        XY_ToPoint( x, y, mousePosition );
+#if 0 // NetRadiantCustom
+       if ( ClipMode() ) {
+               if( GlobalClipPoints_Find( mousePosition, (VIEWTYPE)m_viewType, m_fScale ) != 0 ){
+                       GdkCursor *cursor;
+                       cursor = gdk_cursor_new( GDK_CROSSHAIR );
+                       //cursor = gdk_cursor_new( GDK_FLEUR );
+                       gdk_window_set_cursor( gtk_widget_get_window(m_gl_widget), cursor );
+                       gdk_cursor_unref( cursor );
+               }
+               else{
+                       GdkCursor *cursor;
+                       cursor = gdk_cursor_new( GDK_HAND2 );
+//                     GdkPixbuf* pixbuf = pixbuf_new_from_file_with_mask( "bitmaps/icon.png" );
+//                     cursor = gdk_cursor_new_from_pixbuf( gdk_display_get_default(), pixbuf, 0, 0 );
+//                     g_object_unref( pixbuf );
+                       gdk_window_set_cursor( gtk_widget_get_window(m_gl_widget), cursor );
+                       gdk_cursor_unref( cursor );
+
+               }
+       }
+#else
        if ( ClipMode() && GlobalClipPoints_Find( mousePosition, (VIEWTYPE)m_viewType, m_fScale ) != 0 ) {
                set_cursor ( m_gl_widget, GDK_CROSSHAIR );
        }
+#endif
        else
        {
                default_cursor( m_gl_widget );
        }
 }
 
+void XYWnd::SetCustomPivotOrigin( int pointx, int pointy ){
+       Vector3 point;
+       XY_ToPoint( pointx, pointy, point );
+       VIEWTYPE viewtype = static_cast<VIEWTYPE>( GetViewType() );
+       const int nDim = ( viewtype == YZ ) ? 0 : ( ( viewtype == XZ ) ? 1 : 2 );
+       //vector3_snap( point, GetSnapGridSize() );
+       point[nDim] = 999999;
+
+       GlobalSelectionSystem().setCustomPivotOrigin( point );
+       SceneChangeNotify();
+}
+
 unsigned int MoveCamera_buttons(){
-       return RAD_CONTROL | ( g_glwindow_globals.m_nMouseType == ETwoButton ? RAD_RBUTTON : RAD_MBUTTON );
+//     return RAD_CONTROL | ( g_glwindow_globals.m_nMouseType == ETwoButton ? RAD_RBUTTON : RAD_MBUTTON );
+       return RAD_CONTROL | RAD_MBUTTON;
 }
 
 void XYWnd_PositionCamera( XYWnd* xywnd, int x, int y, CamWnd& camwnd ){
@@ -973,16 +1080,17 @@ void XYWnd_PositionCamera( XYWnd* xywnd, int x, int y, CamWnd& camwnd ){
 }
 
 unsigned int OrientCamera_buttons(){
-       if ( g_glwindow_globals.m_nMouseType == ETwoButton ) {
-               return RAD_RBUTTON | RAD_SHIFT | RAD_CONTROL;
-       }
+//     if ( g_glwindow_globals.m_nMouseType == ETwoButton ) {
+//             return RAD_RBUTTON | RAD_SHIFT | RAD_CONTROL;
+//     }
        return RAD_MBUTTON;
 }
 
 void XYWnd_OrientCamera( XYWnd* xywnd, int x, int y, CamWnd& camwnd ){
+       //globalOutputStream() << Camera_getAngles( camwnd ) << "  b4\n";
        Vector3 point = g_vector3_identity;
        xywnd->XY_ToPoint( x, y, point );
-       xywnd->XY_SnapToGrid( point );
+       //xywnd->XY_SnapToGrid( point );
        vector3_subtract( point, Camera_getOrigin( camwnd ) );
 
        int n1 = ( xywnd->GetViewType() == XY ) ? 1 : 2;
@@ -991,8 +1099,38 @@ void XYWnd_OrientCamera( XYWnd* xywnd, int x, int y, CamWnd& camwnd ){
        if ( point[n1] || point[n2] ) {
                Vector3 angles( Camera_getAngles( camwnd ) );
                angles[nAngle] = static_cast<float>( radians_to_degrees( atan2( point[n1], point[n2] ) ) );
+               if( angles[CAMERA_YAW] < 0 )
+                       angles[CAMERA_YAW] = angles[CAMERA_YAW] + 360;
+               if ( nAngle == CAMERA_PITCH ){
+                       if( fabs( angles[CAMERA_PITCH] ) > 90 ){
+                               angles[CAMERA_PITCH] = ( angles[CAMERA_PITCH] > 0 ) ? ( -angles[CAMERA_PITCH] + 180 ) : ( -angles[CAMERA_PITCH] - 180 );
+                               if( xywnd->GetViewType() == YZ ){
+                                       if( angles[CAMERA_YAW] < 180 ){
+                                               angles[CAMERA_YAW] = 360 - angles[CAMERA_YAW];
+                                       }
+                               }
+                               else if( angles[CAMERA_YAW] < 90 || angles[CAMERA_YAW] > 270 ){
+                                       angles[CAMERA_YAW] = 180 - angles[CAMERA_YAW];
+                               }
+                       }
+                       else{
+                               if( xywnd->GetViewType() == YZ ){
+                                       if( angles[CAMERA_YAW] > 180 ){
+                                               angles[CAMERA_YAW] = 360 - angles[CAMERA_YAW];
+                                       }
+                               }
+                               else if( angles[CAMERA_YAW] > 90 && angles[CAMERA_YAW] < 270 ){
+                                       angles[CAMERA_YAW] = 180 - angles[CAMERA_YAW];
+                               }
+                       }
+               }
                Camera_setAngles( camwnd, angles );
        }
+       //globalOutputStream() << Camera_getAngles( camwnd ) << "\n";
+}
+
+unsigned int SetCustomPivotOrigin_buttons(){
+       return RAD_MBUTTON | RAD_SHIFT;
 }
 
 /*
@@ -1010,7 +1148,6 @@ void XYWnd::NewBrushDrag_Begin( int x, int y ){
        m_nNewBrushPressy = y;
 
        m_bNewBrushDrag = true;
-       GlobalUndoSystem().start();
 }
 
 void XYWnd::NewBrushDrag_End( int x, int y ){
@@ -1048,6 +1185,7 @@ void XYWnd::NewBrushDrag( int x, int y ){
        }
 
        if ( m_NewBrushDrag == 0 ) {
+               GlobalUndoSystem().start();
                NodeSmartReference node( GlobalBrushCreator().createBrush() );
                Node_getTraversable( Map_FindOrInsertWorldspawn( g_map ) )->insert( node );
 
@@ -1074,8 +1212,8 @@ void entitycreate_activated( ui::Widget item ){
                g_pParentWnd->ActiveXY()->OnEntityCreate( entity_name );
        }
        else {
-               GlobalRadiant().m_pfnMessageBox( MainFrame_getWindow(), "There's already a worldspawn in your map!"
-                                                                                                                                                         "",
+               GlobalRadiant().m_pfnMessageBox( MainFrame_getWindow(),
+                       "There's already a worldspawn in your map!",
                                                                                 "Info",
                                                                                 eMB_OK,
                                                                                 eMB_ICONDEFAULT );
@@ -1155,9 +1293,9 @@ void addItem( const char* name, const char* next ){
 };
 
 void XYWnd::OnContextMenu(){
-       if ( g_xywindow_globals.m_bRightClick == false ) {
-               return;
-       }
+//     if ( g_xywindow_globals.m_bRightClick == false ) {
+//             return;
+//     }
 
        if ( !m_mnuDrop ) { // first time, load it up
                auto menu = m_mnuDrop = ui::Menu(ui::New);
@@ -1190,35 +1328,45 @@ void XYWnd::Move_Begin(){
                Move_End();
        }
        m_move_started = true;
+       /* NetRadiantCustom did this instead:
+       g_xywnd_freezePointer.freeze_pointer( m_parent  ? m_parent : MainFrame_getWindow(), m_gl_widget, XYWnd_moveDelta, this ); */
        g_xywnd_freezePointer.freeze_pointer( m_gl_widget, XYWnd_moveDelta, this );
        m_move_focusOut = m_gl_widget.connect( "focus_out_event", G_CALLBACK( XYWnd_Move_focusOut ), this );
 }
 
 void XYWnd::Move_End(){
        m_move_started = false;
-       g_xywnd_freezePointer.unfreeze_pointer( m_gl_widget );
+       /* NetRadiant did this instead:
+       g_xywnd_freezePointer.unfreeze_pointer( m_parent ? m_parent : MainFrame_getWindow(), false ); */
+       g_xywnd_freezePointer.unfreeze_pointer( m_gl_widget, false );
        g_signal_handler_disconnect( G_OBJECT( m_gl_widget ), m_move_focusOut );
 }
 
 unsigned int Zoom_buttons(){
-       return RAD_RBUTTON | RAD_SHIFT;
+       return RAD_RBUTTON | RAD_ALT;
 }
 
 int g_dragZoom = 0;
+int g_zoom2x = 0;
+int g_zoom2y = 0;
 
 void XYWnd_zoomDelta( int x, int y, unsigned int state, void* data ){
        if ( y != 0 ) {
                g_dragZoom += y;
-
                while ( abs( g_dragZoom ) > 8 )
                {
                        if ( g_dragZoom > 0 ) {
-                               XYWnd_ZoomOut( reinterpret_cast<XYWnd*>( data ) );
+                               reinterpret_cast<XYWnd*>( data )->ZoomOut();
                                g_dragZoom -= 8;
                        }
                        else
                        {
-                               XYWnd_ZoomIn( reinterpret_cast<XYWnd*>( data ) );
+                               if ( g_xywindow_globals.m_bZoomInToPointer ) {
+                                       reinterpret_cast<XYWnd*>( data )->ZoomInWithMouse( g_zoom2x, g_zoom2y );
+                               }
+                               else{
+                                       reinterpret_cast<XYWnd*>( data )->ZoomIn();
+                               }
                                g_dragZoom += 8;
                        }
                }
@@ -1230,19 +1378,23 @@ gboolean XYWnd_Zoom_focusOut( ui::Widget widget, GdkEventFocus* event, XYWnd* xy
        return FALSE;
 }
 
-void XYWnd::Zoom_Begin(){
+void XYWnd::Zoom_Begin( int x, int y ){
        if ( m_zoom_started ) {
                Zoom_End();
        }
        m_zoom_started = true;
        g_dragZoom = 0;
+       g_zoom2x = x;
+       g_zoom2y = y;
+       /* NetRadiantCustom did this instead:
+       g_xywnd_freezePointer.freeze_pointer( m_parent ? m_parent : MainFrame_getWindow(), m_gl_widget, XYWnd_zoomDelta, this ); */
        g_xywnd_freezePointer.freeze_pointer( m_parent ? m_parent : MainFrame_getWindow(), XYWnd_zoomDelta, this );
        m_zoom_focusOut = m_gl_widget.connect( "focus_out_event", G_CALLBACK( XYWnd_Zoom_focusOut ), this );
 }
 
 void XYWnd::Zoom_End(){
        m_zoom_started = false;
-       g_xywnd_freezePointer.unfreeze_pointer( m_parent ? m_parent : MainFrame_getWindow() );
+       g_xywnd_freezePointer.unfreeze_pointer( m_parent ? m_parent : MainFrame_getWindow(), false );
        g_signal_handler_disconnect( G_OBJECT( m_gl_widget ), m_zoom_focusOut );
 }
 
@@ -1282,9 +1434,14 @@ void XYWnd::XY_MouseDown( int x, int y, unsigned int buttons ){
                EntityCreate_MouseDown( x, y );
        }
        else if ( buttons == Zoom_buttons() ) {
-               Zoom_Begin();
+               Zoom_Begin( x, y );
+       }
+       else if ( ClipMode() && ( buttons == Clipper_buttons() || buttons == Clipper_quick_buttons() ) ) {
+               Clipper_OnLButtonDown( x, y );
        }
-       else if ( ClipMode() && buttons == Clipper_buttons() ) {
+       else if ( !ClipMode() && buttons == Clipper_quick_buttons() ) {
+               ClipperMode();
+               g_quick_clipper = true;
                Clipper_OnLButtonDown( x, y );
        }
        else if ( buttons == NewBrushDrag_buttons() && GlobalSelectionSystem().countSelected() == 0 ) {
@@ -1298,6 +1455,9 @@ void XYWnd::XY_MouseDown( int x, int y, unsigned int buttons ){
        else if ( buttons == OrientCamera_buttons() ) {
                XYWnd_OrientCamera( this, x, y, *g_pParentWnd->GetCamWnd() );
        }
+       else if ( buttons == SetCustomPivotOrigin_buttons() ) {
+               SetCustomPivotOrigin( x, y );
+       }
        else
        {
                m_window_observer->onMouseDown( WindowVector_forInteger( x, y ), button_for_flags( buttons ), modifiers_for_flags( buttons ) );
@@ -1312,12 +1472,16 @@ void XYWnd::XY_MouseUp( int x, int y, unsigned int buttons ){
        else if ( m_zoom_started ) {
                Zoom_End();
        }
-       else if ( ClipMode() && buttons == Clipper_buttons() ) {
+       else if ( ClipMode() && ( buttons == Clipper_buttons() || buttons == Clipper_quick_buttons() ) ) {
                Clipper_OnLButtonUp( x, y );
        }
        else if ( m_bNewBrushDrag ) {
                m_bNewBrushDrag = false;
                NewBrushDrag_End( x, y );
+               if ( m_NewBrushDrag == 0 ) {
+                       //L button w/o created brush = tunnel selection
+                       m_window_observer->onMouseUp( WindowVector_forInteger( x, y ), button_for_flags( buttons ), modifiers_for_flags( buttons ) );
+               }
        }
        else
        {
@@ -1342,15 +1506,19 @@ void XYWnd::XY_MouseMoved( int x, int y, unsigned int buttons ){
        }
 
        // control mbutton = move camera
-       else if ( getButtonState() == MoveCamera_buttons() ) {
+       else if ( buttons == MoveCamera_buttons() ) {
                XYWnd_PositionCamera( this, x, y, *g_pParentWnd->GetCamWnd() );
        }
 
        // mbutton = angle camera
-       else if ( getButtonState() == OrientCamera_buttons() ) {
+       else if ( buttons == OrientCamera_buttons() ) {
                XYWnd_OrientCamera( this, x, y, *g_pParentWnd->GetCamWnd() );
        }
 
+       else if ( buttons == SetCustomPivotOrigin_buttons() ) {
+               SetCustomPivotOrigin( x, y );
+       }
+
        else
        {
                m_window_observer->onMouseMotion( WindowVector_forInteger( x, y ), modifiers_for_flags( buttons ) );
@@ -1365,7 +1533,7 @@ void XYWnd::XY_MouseMoved( int x, int y, unsigned int buttons ){
                           << "  z:: " << FloatFormat( m_mousePosition[2], 6, 1 );
                g_pParentWnd->SetStatusText( g_pParentWnd->m_position_status, status.c_str() );
 
-               if ( g_bCrossHairs ) {
+               if ( g_xywindow_globals_private.g_bCrossHairs ) {
                        XYWnd_Update( *this );
                }
 
@@ -1539,8 +1707,14 @@ void XYWnd::XY_DrawAxis( void ){
                const int w = ( m_nWidth / 2 / m_fScale );
                const int h = ( m_nHeight / 2 / m_fScale );
 
-               const Vector3& colourX = ( m_viewType == YZ ) ? g_xywindow_globals.AxisColorY : g_xywindow_globals.AxisColorX;
-               const Vector3& colourY = ( m_viewType == XY ) ? g_xywindow_globals.AxisColorY : g_xywindow_globals.AxisColorZ;
+               Vector3 colourX = ( m_viewType == YZ ) ? g_xywindow_globals.AxisColorY : g_xywindow_globals.AxisColorX;
+               Vector3 colourY = ( m_viewType == XY ) ? g_xywindow_globals.AxisColorY : g_xywindow_globals.AxisColorZ;
+               if( !Active() ){
+                       float grayX = vector3_dot( colourX, Vector3( 0.2989, 0.5870, 0.1140 ) );
+                       float grayY = vector3_dot( colourY, Vector3( 0.2989, 0.5870, 0.1140 ) );
+                       colourX[0] = colourX[1] = colourX[2] = grayX;
+                       colourY[0] = colourY[1] = colourY[2] = grayY;
+               }
 
                // draw two lines with corresponding axis colors to highlight current view
                // horizontal line: nDim1 color
@@ -1572,6 +1746,105 @@ void XYWnd::XY_DrawAxis( void ){
        }
 }
 
+void XYWnd::RenderActive( void ){
+       if ( glwidget_make_current( m_gl_widget ) != FALSE ) {
+               if ( Map_Valid( g_map ) && ScreenUpdates_Enabled() ) {
+                       GlobalOpenGL_debugAssertNoErrors();
+                       glDrawBuffer( GL_FRONT );
+
+                       if ( g_xywindow_globals_private.show_outline ) {
+                               glMatrixMode( GL_PROJECTION );
+                               glLoadIdentity();
+                               glOrtho( 0, m_nWidth, 0, m_nHeight, 0, 1 );
+
+                               glMatrixMode( GL_MODELVIEW );
+                               glLoadIdentity();
+
+                               if( !Active() ){ //sorta erase
+                                       glColor3fv( vector3_to_array( g_xywindow_globals.color_gridmajor ) );
+                               }
+                               // four view mode doesn't colorize
+                               else if ( g_pParentWnd->CurrentStyle() == MainFrame::eSplit ) {
+                                       glColor3fv( vector3_to_array( g_xywindow_globals.color_viewname ) );
+                               }
+                               else
+                               {
+                                       switch ( m_viewType )
+                                       {
+                                       case YZ:
+                                               glColor3fv( vector3_to_array( g_xywindow_globals.AxisColorX ) );
+                                               break;
+                                       case XZ:
+                                               glColor3fv( vector3_to_array( g_xywindow_globals.AxisColorY ) );
+                                               break;
+                                       case XY:
+                                               glColor3fv( vector3_to_array( g_xywindow_globals.AxisColorZ ) );
+                                               break;
+                                       }
+                               }
+                               glBegin( GL_LINE_LOOP );
+                               glVertex2f( 0.5, 0.5 );
+                               glVertex2f( m_nWidth - 0.5, 1 );
+                               glVertex2f( m_nWidth - 0.5, m_nHeight - 0.5 );
+                               glVertex2f( 0.5, m_nHeight - 0.5 );
+                               glEnd();
+                       }
+                       // we do this part (the old way) only if show_axis is disabled
+                       if ( !g_xywindow_globals_private.show_axis ) {
+                               glMatrixMode( GL_PROJECTION );
+                               glLoadIdentity();
+                               glOrtho( 0, m_nWidth, 0, m_nHeight, 0, 1 );
+
+                               glMatrixMode( GL_MODELVIEW );
+                               glLoadIdentity();
+
+                               if ( Active() ) {
+                                       glColor3fv( vector3_to_array( g_xywindow_globals.color_viewname ) );
+                               }
+                               else{
+                                       glColor4fv( vector4_to_array( Vector4( g_xywindow_globals.color_gridtext, 1.0f ) ) );
+                               }
+
+                               glDisable( GL_BLEND );
+                               glRasterPos2f( 35, m_nHeight - 20 );
+
+                               GlobalOpenGL().drawString( ViewType_getTitle( m_viewType ) );
+                       }
+                       else{
+                               // clear
+                               glViewport( 0, 0, m_nWidth, m_nHeight );
+                               // set up viewpoint
+                               glMatrixMode( GL_PROJECTION );
+                               glLoadMatrixf( reinterpret_cast<const float*>( &m_projection ) );
+
+                               glMatrixMode( GL_MODELVIEW );
+                               glLoadIdentity();
+                               glScalef( m_fScale, m_fScale, 1 );
+                               int nDim1 = ( m_viewType == YZ ) ? 1 : 0;
+                               int nDim2 = ( m_viewType == XY ) ? 1 : 2;
+                               glTranslatef( -m_vOrigin[nDim1], -m_vOrigin[nDim2], 0 );
+
+                               glDisable( GL_LINE_STIPPLE );
+                               glDisableClientState( GL_TEXTURE_COORD_ARRAY );
+                               glDisableClientState( GL_NORMAL_ARRAY );
+                               glDisableClientState( GL_COLOR_ARRAY );
+                               glDisable( GL_TEXTURE_2D );
+                               glDisable( GL_LIGHTING );
+                               glDisable( GL_COLOR_MATERIAL );
+                               glDisable( GL_DEPTH_TEST );
+                               glDisable( GL_TEXTURE_1D );
+                               glDisable( GL_BLEND );
+
+                               XYWnd::XY_DrawAxis();
+                       }
+
+                       glDrawBuffer( GL_BACK );
+                       GlobalOpenGL_debugAssertNoErrors();
+                       glwidget_make_current( m_gl_widget );
+               }
+       }
+}
+
 void XYWnd::XY_DrawBackground( void ){
        glPushAttrib( GL_ALL_ATTRIB_BITS );
 
@@ -1739,16 +2012,19 @@ void XYWnd::XY_DrawGrid( void ) {
                        GlobalOpenGL().drawString( text );
                }
 
+       }
+       // we do this part (the old way) only if show_axis is disabled
+       if ( !g_xywindow_globals_private.show_axis ) {
                if ( Active() ) {
                        glColor3fv( vector3_to_array( g_xywindow_globals.color_viewname ) );
                }
+               else{
+                       glColor4fv( vector4_to_array( Vector4( g_xywindow_globals.color_gridtext, 1.0f ) ) );
+               }
 
-               // we do this part (the old way) only if show_axis is disabled
-               if ( !g_xywindow_globals_private.show_axis ) {
-                       glRasterPos2f( m_vOrigin[nDim1] - w + 35 / m_fScale, m_vOrigin[nDim2] + h - 20 / m_fScale );
+               glRasterPos2f( m_vOrigin[nDim1] - w + 35 / m_fScale, m_vOrigin[nDim2] + h - 20 / m_fScale );
 
-                       GlobalOpenGL().drawString( ViewType_getTitle( m_viewType ) );
-               }
+               GlobalOpenGL().drawString( ViewType_getTitle( m_viewType ) );
        }
 
        XYWnd::XY_DrawAxis();
@@ -1868,47 +2144,108 @@ void XYWnd::XY_DrawBlockGrid(){
 }
 
 void XYWnd::DrawCameraIcon( const Vector3& origin, const Vector3& angles ){
-       float x, y, fov, box;
-       double a;
-
-       fov = 48 / m_fScale;
-       box = 16 / m_fScale;
+       Cam.fov = 48 / m_fScale;
+       Cam.box = 16 / m_fScale;
+//     globalOutputStream() << "pitch " << angles[CAMERA_PITCH] << "   yaw " << angles[CAMERA_YAW] << "\n";
 
        if ( m_viewType == XY ) {
-               x = origin[0];
-               y = origin[1];
-               a = degrees_to_radians( angles[CAMERA_YAW] );
+               Cam.x = origin[0];
+               Cam.y = origin[1];
+               Cam.a = degrees_to_radians( angles[CAMERA_YAW] );
        }
        else if ( m_viewType == YZ ) {
-               x = origin[1];
-               y = origin[2];
-               a = degrees_to_radians( angles[CAMERA_PITCH] );
+               Cam.x = origin[1];
+               Cam.y = origin[2];
+               Cam.a = degrees_to_radians( ( angles[CAMERA_YAW] > 180 ) ? ( 180.0f - angles[CAMERA_PITCH] ) : angles[CAMERA_PITCH] );
        }
        else
        {
-               x = origin[0];
-               y = origin[2];
-               a = degrees_to_radians( angles[CAMERA_PITCH] );
+               Cam.x = origin[0];
+               Cam.y = origin[2];
+               Cam.a = degrees_to_radians( ( angles[CAMERA_YAW] < 270 && angles[CAMERA_YAW] > 90 ) ? ( 180.0f - angles[CAMERA_PITCH] ) : angles[CAMERA_PITCH] );
        }
 
-       glColor3f( 0.0, 0.0, 1.0 );
+       //glColor3f( 0.0, 0.0, 1.0 );
+       glColor3f( 1.0, 1.0, 1.0 );
        glBegin( GL_LINE_STRIP );
-       glVertex3f( x - box,y,0 );
-       glVertex3f( x,y + ( box / 2 ),0 );
-       glVertex3f( x + box,y,0 );
-       glVertex3f( x,y - ( box / 2 ),0 );
-       glVertex3f( x - box,y,0 );
-       glVertex3f( x + box,y,0 );
+       glVertex3f( Cam.x - Cam.box,Cam.y,0 );
+       glVertex3f( Cam.x,Cam.y + ( Cam.box / 2 ),0 );
+       glVertex3f( Cam.x + Cam.box,Cam.y,0 );
+       glVertex3f( Cam.x,Cam.y - ( Cam.box / 2 ),0 );
+       glVertex3f( Cam.x - Cam.box,Cam.y,0 );
+       glVertex3f( Cam.x + Cam.box,Cam.y,0 );
        glEnd();
 
        glBegin( GL_LINE_STRIP );
-       glVertex3f( x + static_cast<float>( fov * cos( a + c_pi / 4 ) ), y + static_cast<float>( fov * sin( a + c_pi / 4 ) ), 0 );
-       glVertex3f( x, y, 0 );
-       glVertex3f( x + static_cast<float>( fov * cos( a - c_pi / 4 ) ), y + static_cast<float>( fov * sin( a - c_pi / 4 ) ), 0 );
+       glVertex3f( Cam.x + static_cast<float>( Cam.fov * cos( Cam.a + c_pi / 4 ) ), Cam.y + static_cast<float>( Cam.fov * sin( Cam.a + c_pi / 4 ) ), 0 );
+       glVertex3f( Cam.x, Cam.y, 0 );
+       glVertex3f( Cam.x + static_cast<float>( Cam.fov * cos( Cam.a - c_pi / 4 ) ), Cam.y + static_cast<float>( Cam.fov * sin( Cam.a - c_pi / 4 ) ), 0 );
        glEnd();
 
 }
 
+void XYWnd::UpdateCameraIcon( void ){
+       if ( glwidget_make_current( m_gl_widget ) != FALSE ) {
+               if ( Map_Valid( g_map ) && ScreenUpdates_Enabled() ) {
+                       GlobalOpenGL_debugAssertNoErrors();
+                       glDrawBuffer( GL_FRONT );
+                       {
+                               // clear
+                               glViewport( 0, 0, m_nWidth, m_nHeight );
+                               // set up viewpoint
+                               glMatrixMode( GL_PROJECTION );
+                               glLoadMatrixf( reinterpret_cast<const float*>( &m_projection ) );
+
+                               glMatrixMode( GL_MODELVIEW );
+                               glLoadIdentity();
+                               glScalef( m_fScale, m_fScale, 1 );
+                               int nDim1 = ( m_viewType == YZ ) ? 1 : 0;
+                               int nDim2 = ( m_viewType == XY ) ? 1 : 2;
+                               glTranslatef( -m_vOrigin[nDim1], -m_vOrigin[nDim2], 0 );
+
+                               glDisable( GL_LINE_STIPPLE );
+                               glDisableClientState( GL_TEXTURE_COORD_ARRAY );
+                               glDisableClientState( GL_NORMAL_ARRAY );
+                               glDisableClientState( GL_COLOR_ARRAY );
+                               glDisable( GL_TEXTURE_2D );
+                               glDisable( GL_LIGHTING );
+                               glDisable( GL_COLOR_MATERIAL );
+                               glDisable( GL_DEPTH_TEST );
+                               glDisable( GL_TEXTURE_1D );
+
+                               glEnable( GL_BLEND );
+                               glBlendFunc( GL_ONE_MINUS_DST_COLOR, GL_ZERO );
+
+                               //glColor3f( 0.0, 0.0, 1.0 );
+                               glColor3f( 1.0, 1.0, 1.0 );
+                               glBegin( GL_LINE_STRIP );
+                               glVertex3f( Cam.x - Cam.box,Cam.y,0 );
+                               glVertex3f( Cam.x,Cam.y + ( Cam.box / 2 ),0 );
+                               glVertex3f( Cam.x + Cam.box,Cam.y,0 );
+                               glVertex3f( Cam.x,Cam.y - ( Cam.box / 2 ),0 );
+                               glVertex3f( Cam.x - Cam.box,Cam.y,0 );
+                               glVertex3f( Cam.x + Cam.box,Cam.y,0 );
+                               glEnd();
+
+                               glBegin( GL_LINE_STRIP );
+                               glVertex3f( Cam.x + static_cast<float>( Cam.fov * cos( Cam.a + c_pi / 4 ) ), Cam.y + static_cast<float>( Cam.fov * sin( Cam.a + c_pi / 4 ) ), 0 );
+                               glVertex3f( Cam.x, Cam.y, 0 );
+                               glVertex3f( Cam.x + static_cast<float>( Cam.fov * cos( Cam.a - c_pi / 4 ) ), Cam.y + static_cast<float>( Cam.fov * sin( Cam.a - c_pi / 4 ) ), 0 );
+                               glEnd();
+
+                               XYWnd::DrawCameraIcon( Camera_getOrigin( *g_pParentWnd->GetCamWnd() ), Camera_getAngles( *g_pParentWnd->GetCamWnd() ) );
+
+                               glDisable( GL_BLEND );
+                               glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+                       }
+
+                       glDrawBuffer( GL_BACK );
+                       GlobalOpenGL_debugAssertNoErrors();
+                       glwidget_make_current( m_gl_widget );
+               }
+       }
+}
+
 
 float Betwixt( float f1, float f2 ){
        if ( f1 > f2 ) {
@@ -2138,8 +2475,8 @@ void XYWnd::updateProjection( bool reconstruct ){
        m_projection[15] = 1.0f;
 
        if (reconstruct) {
-               m_view.Construct( m_projection, m_modelview, m_nWidth, m_nHeight );
-       }
+       m_view.Construct( m_projection, m_modelview, m_nWidth, m_nHeight );
+}
 }
 
 // note: modelview matrix must have a uniform scale, otherwise strange things happen when rendering the rotation manipulator.
@@ -2200,7 +2537,7 @@ void XYWnd::updateModelview( bool reconstruct ){
        m_modelview[15] = 1;
 
        if (reconstruct) {
-               m_view.Construct( m_projection, m_modelview, m_nWidth, m_nHeight );
+       m_view.Construct( m_projection, m_modelview, m_nWidth, m_nHeight );
        }
 }
 
@@ -2311,7 +2648,7 @@ void XYWnd::XY_Draw(){
                PaintSizeInfo( nDim1, nDim2, min, max );
        }
 
-       if ( g_bCrossHairs ) {
+       if ( g_xywindow_globals_private.g_bCrossHairs ) {
                glColor4f( 0.2f, 0.9f, 0.2f, 0.8f );
                glBegin( GL_LINES );
                if ( m_viewType == XY ) {
@@ -2347,7 +2684,11 @@ void XYWnd::XY_Draw(){
        glScalef( m_fScale, m_fScale, 1 );
        glTranslatef( -m_vOrigin[nDim1], -m_vOrigin[nDim2], 0 );
 
+       glEnable( GL_BLEND );
+       glBlendFunc( GL_ONE_MINUS_DST_COLOR, GL_ZERO );
        DrawCameraIcon( Camera_getOrigin( *g_pParentWnd->GetCamWnd() ), Camera_getAngles( *g_pParentWnd->GetCamWnd() ) );
+       glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+       glDisable( GL_BLEND );
 
        Feedback_draw2D( m_viewType );
 
@@ -2413,7 +2754,7 @@ void XYWnd::OnEntityCreate( const char* item ){
 
 
 
-void GetFocusPosition( Vector3& position ){
+void GetCenterPosition( Vector3& position ){
        if ( GlobalSelectionSystem().countSelected() != 0 ) {
                Select_GetMid( position );
        }
@@ -2423,15 +2764,15 @@ void GetFocusPosition( Vector3& position ){
        }
 }
 
-void XYWnd_Focus( XYWnd* xywnd ){
+void XYWnd_Centralize( XYWnd* xywnd ){
        Vector3 position;
-       GetFocusPosition( position );
+       GetCenterPosition( position );
        xywnd->PositionView( position );
 }
 
-void XY_Split_Focus(){
+void XY_Split_Centralize(){
        Vector3 position;
-       GetFocusPosition( position );
+       GetCenterPosition( position );
        if ( g_pParentWnd->GetXYWnd() ) {
                g_pParentWnd->GetXYWnd()->PositionView( position );
        }
@@ -2443,66 +2784,87 @@ void XY_Split_Focus(){
        }
 }
 
-void XY_Focus(){
-       if ( g_pParentWnd->CurrentStyle() == MainFrame::eSplit ) {
-               // cannot do this in a split window
-               // do something else that the user may want here
-               XY_Split_Focus();
+void XY_Centralize(){
+       if ( g_pParentWnd->CurrentStyle() == MainFrame::eSplit || g_pParentWnd->CurrentStyle() == MainFrame::eFloating ) {
+               // centralize all
+               XY_Split_Centralize();
                return;
        }
 
        XYWnd* xywnd = g_pParentWnd->GetXYWnd();
-       XYWnd_Focus( xywnd );
+       XYWnd_Centralize( xywnd );
 }
 
-void XY_Top(){
-       if ( g_pParentWnd->CurrentStyle() == MainFrame::eSplit || g_pParentWnd->CurrentStyle() == MainFrame::eFloating ) {
-               // cannot do this in a split window
-               // do something else that the user may want here
-               XY_Split_Focus();
-               return;
+
+
+void GetSelectionBbox( AABB& bounds ){
+       if ( GlobalSelectionSystem().countSelected() != 0 ) {
+               Scene_BoundsSelected( GlobalSceneGraph(), bounds );
        }
+       else
+       {
+               bounds = AABB( Camera_getOrigin( *g_pParentWnd->GetCamWnd() ), Vector3( 128.f, 128.f, 128.f ) );
+       }
+}
 
-       XYWnd* xywnd = g_pParentWnd->GetXYWnd();
-       xywnd->SetViewType( XY );
-       XYWnd_Focus( xywnd );
+void XYWnd_Focus( XYWnd* xywnd ){
+       AABB bounds;
+       GetSelectionBbox( bounds );
+       xywnd->FocusOnBounds( bounds );
 }
 
-void XY_Side(){
-       if ( g_pParentWnd->CurrentStyle() == MainFrame::eSplit || g_pParentWnd->CurrentStyle() == MainFrame::eFloating ) {
-               // cannot do this in a split window
-               // do something else that the user may want here
-               XY_Split_Focus();
-               return;
+void XY_Split_Focus(){
+       AABB bounds;
+       GetSelectionBbox( bounds );
+       if ( g_pParentWnd->GetXYWnd() ) {
+               g_pParentWnd->GetXYWnd()->FocusOnBounds( bounds );
+       }
+       if ( g_pParentWnd->GetXZWnd() ) {
+               g_pParentWnd->GetXZWnd()->FocusOnBounds( bounds );
+       }
+       if ( g_pParentWnd->GetYZWnd() ) {
+               g_pParentWnd->GetYZWnd()->FocusOnBounds( bounds );
        }
-
-       XYWnd* xywnd = g_pParentWnd->GetXYWnd();
-       xywnd->SetViewType( XZ );
-       XYWnd_Focus( xywnd );
 }
 
-void XY_Front(){
+void XY_Focus(){
        if ( g_pParentWnd->CurrentStyle() == MainFrame::eSplit || g_pParentWnd->CurrentStyle() == MainFrame::eFloating ) {
-               // cannot do this in a split window
-               // do something else that the user may want here
+               // focus all
                XY_Split_Focus();
                return;
        }
 
        XYWnd* xywnd = g_pParentWnd->GetXYWnd();
-       xywnd->SetViewType( YZ );
        XYWnd_Focus( xywnd );
 }
 
-void XY_Next(){
-       if ( g_pParentWnd->CurrentStyle() == MainFrame::eSplit || g_pParentWnd->CurrentStyle() == MainFrame::eFloating ) {
+
+
+void XY_TopFrontSide( VIEWTYPE viewtype ){
+       if ( g_pParentWnd->CurrentStyle() == MainFrame::eSplit ) {
                // cannot do this in a split window
                // do something else that the user may want here
-               XY_Split_Focus();
+               XY_Split_Centralize();
                return;
        }
+       XYWnd* xywnd = g_pParentWnd->CurrentStyle() == MainFrame::eFloating ? g_pParentWnd->ActiveXY() : g_pParentWnd->GetXYWnd();
+       xywnd->SetViewType( viewtype );
+       XYWnd_Centralize( xywnd );
+}
 
-       XYWnd* xywnd = g_pParentWnd->GetXYWnd();
+void XY_Top(){
+       XY_TopFrontSide( XY );
+}
+
+void XY_Side(){
+       XY_TopFrontSide( XZ );
+}
+
+void XY_Front(){
+       XY_TopFrontSide( YZ );
+}
+
+void XY_NextView( XYWnd* xywnd ){
        if ( xywnd->GetViewType() == XY ) {
                xywnd->SetViewType( XZ );
        }
@@ -2512,7 +2874,18 @@ void XY_Next(){
        else{
                xywnd->SetViewType( XY );
        }
-       XYWnd_Focus( xywnd );
+       XYWnd_Centralize( xywnd );
+}
+
+void XY_Next(){
+       if ( g_pParentWnd->CurrentStyle() == MainFrame::eSplit ) {
+               // cannot do this in a split window
+               // do something else that the user may want here
+               XY_Split_Centralize();
+               return;
+       }
+       XYWnd* xywnd = g_pParentWnd->CurrentStyle() == MainFrame::eFloating ? g_pParentWnd->ActiveXY() : g_pParentWnd->GetXYWnd();
+       XY_NextView( xywnd );
 }
 
 void XY_Zoom100(){
@@ -2528,20 +2901,20 @@ void XY_Zoom100(){
 }
 
 void XY_ZoomIn(){
-       XYWnd_ZoomIn( g_pParentWnd->ActiveXY() );
+       g_pParentWnd->ActiveXY()->ZoomIn();
 }
 
 // NOTE: the zoom out factor is 4/5, we could think about customizing it
 //  we don't go below a zoom factor corresponding to 10% of the max world size
 //  (this has to be computed against the window size)
 void XY_ZoomOut(){
-       XYWnd_ZoomOut( g_pParentWnd->ActiveXY() );
+       g_pParentWnd->ActiveXY()->ZoomOut();
 }
 
 
 
 void ToggleShowCrosshair(){
-       g_bCrossHairs ^= 1;
+       g_xywindow_globals_private.g_bCrossHairs ^= 1;
        XY_UpdateAllWindows();
 }
 
@@ -2597,82 +2970,167 @@ void unrealise(){
 EntityClassMenu g_EntityClassMenu;
 
 
-
-
+// Names
 void ShowNamesToggle(){
        GlobalEntityCreator().setShowNames( !GlobalEntityCreator().getShowNames() );
        XY_UpdateAllWindows();
 }
+
 typedef FreeCaller<void(), ShowNamesToggle> ShowNamesToggleCaller;
+
 void ShowNamesExport( const Callback<void(bool)> & importer ){
        importer( GlobalEntityCreator().getShowNames() );
 }
+
 typedef FreeCaller<void(const Callback<void(bool)> &), ShowNamesExport> ShowNamesExportCaller;
 
+// TargetNames
+void ShowTargetNamesToggle(){
+       GlobalEntityCreator().setShowTargetNames( !GlobalEntityCreator().getShowTargetNames() );
+       XY_UpdateAllWindows();
+}
+
+typedef FreeCaller<void(), ShowTargetNamesToggle> ShowTargetNamesToggleCaller;
+
+void ShowTargetNamesExport( const Callback<void(bool)> & importer ){
+       importer( GlobalEntityCreator().getShowTargetNames() );
+}
+
+typedef FreeCaller<void(const Callback<void(bool)> &), ShowTargetNamesExport> ShowTargetNamesExportCaller;
+
+// Angles
 void ShowAnglesToggle(){
        GlobalEntityCreator().setShowAngles( !GlobalEntityCreator().getShowAngles() );
        XY_UpdateAllWindows();
 }
+
 typedef FreeCaller<void(), ShowAnglesToggle> ShowAnglesToggleCaller;
+
 void ShowAnglesExport( const Callback<void(bool)> & importer ){
        importer( GlobalEntityCreator().getShowAngles() );
 }
 typedef FreeCaller<void(const Callback<void(bool)> &), ShowAnglesExport> ShowAnglesExportCaller;
 
+// Blocks
 void ShowBlocksToggle(){
        g_xywindow_globals_private.show_blocks ^= 1;
        XY_UpdateAllWindows();
 }
+
 typedef FreeCaller<void(), ShowBlocksToggle> ShowBlocksToggleCaller;
+
 void ShowBlocksExport( const Callback<void(bool)> & importer ){
        importer( g_xywindow_globals_private.show_blocks );
 }
+
 typedef FreeCaller<void(const Callback<void(bool)> &), ShowBlocksExport> ShowBlocksExportCaller;
 
+// Coordinates
 void ShowCoordinatesToggle(){
        g_xywindow_globals_private.show_coordinates ^= 1;
        XY_UpdateAllWindows();
 }
+
 typedef FreeCaller<void(), ShowCoordinatesToggle> ShowCoordinatesToggleCaller;
+
 void ShowCoordinatesExport( const Callback<void(bool)> & importer ){
        importer( g_xywindow_globals_private.show_coordinates );
 }
+
 typedef FreeCaller<void(const Callback<void(bool)> &), ShowCoordinatesExport> ShowCoordinatesExportCaller;
 
+// Outlines
 void ShowOutlineToggle(){
        g_xywindow_globals_private.show_outline ^= 1;
        XY_UpdateAllWindows();
 }
+
 typedef FreeCaller<void(), ShowOutlineToggle> ShowOutlineToggleCaller;
+
 void ShowOutlineExport( const Callback<void(bool)> & importer ){
        importer( g_xywindow_globals_private.show_outline );
 }
+
 typedef FreeCaller<void(const Callback<void(bool)> &), ShowOutlineExport> ShowOutlineExportCaller;
 
+// Axes
 void ShowAxesToggle(){
        g_xywindow_globals_private.show_axis ^= 1;
        XY_UpdateAllWindows();
 }
 typedef FreeCaller<void(), ShowAxesToggle> ShowAxesToggleCaller;
+
 void ShowAxesExport( const Callback<void(bool)> & importer ){
        importer( g_xywindow_globals_private.show_axis );
 }
+
 typedef FreeCaller<void(const Callback<void(bool)> &), ShowAxesExport> ShowAxesExportCaller;
 
+// Workzone
 void ShowWorkzoneToggle(){
        g_xywindow_globals_private.d_show_work ^= 1;
        XY_UpdateAllWindows();
 }
 typedef FreeCaller<void(), ShowWorkzoneToggle> ShowWorkzoneToggleCaller;
+
 void ShowWorkzoneExport( const Callback<void(bool)> & importer ){
        importer( g_xywindow_globals_private.d_show_work );
 }
+
 typedef FreeCaller<void(const Callback<void(bool)> &), ShowWorkzoneExport> ShowWorkzoneExportCaller;
 
+/*
+BoolExportCaller g_texdef_movelock_caller( g_brush_texturelock_enabled );
+ToggleItem g_texdef_movelock_item( g_texdef_movelock_caller );
+
+void Texdef_ToggleMoveLock(){
+       g_brush_texturelock_enabled = !g_brush_texturelock_enabled;
+       g_texdef_movelock_item.update();
+}
+*/
+
+// Size
+void ShowSizeToggle(){
+       g_xywindow_globals_private.m_bSizePaint = !g_xywindow_globals_private.m_bSizePaint;
+       XY_UpdateAllWindows();
+}
+typedef FreeCaller<void(), ShowSizeToggle> ShowSizeToggleCaller;
+void ShowSizeExport( const Callback<void(bool)> & importer ){
+       importer( g_xywindow_globals_private.m_bSizePaint );
+}
+typedef FreeCaller<void(const Callback<void(bool)> &), ShowSizeExport> ShowSizeExportCaller;
+
+// Crosshair
+void ShowCrosshairToggle(){
+       g_xywindow_globals_private.g_bCrossHairs ^= 1;
+       XY_UpdateAllWindows();
+}
+typedef FreeCaller<void(), ShowCrosshairToggle> ShowCrosshairToggleCaller;
+void ShowCrosshairExport( const Callback<void(bool)> & importer ){
+       importer( g_xywindow_globals_private.g_bCrossHairs );
+}
+typedef FreeCaller<void(const Callback<void(bool)> &), ShowCrosshairExport> ShowCrosshairExportCaller;
+
+// Grid
+void ShowGridToggle(){
+       g_xywindow_globals_private.d_showgrid = !g_xywindow_globals_private.d_showgrid;
+       XY_UpdateAllWindows();
+}
+typedef FreeCaller<void(), ShowGridToggle> ShowGridToggleCaller;
+void ShowGridTExport( const Callback<void(bool)> & importer ){
+       importer( g_xywindow_globals_private.d_showgrid );
+}
+typedef FreeCaller<void(const Callback<void(bool)> &), ShowSizeExport> ShowGridExportCaller;
+
+
 ShowNamesExportCaller g_show_names_caller;
 Callback<void(const Callback<void(bool)> &)> g_show_names_callback( g_show_names_caller );
 ToggleItem g_show_names( g_show_names_callback );
 
+ShowTargetNamesExportCaller g_show_targetnames_caller;
+Callback<void(const Callback<void(bool)> &)> g_show_targetnames_callback( g_show_targetnames_caller );
+ToggleItem g_show_targetnames( g_show_targetnames_callback );
+
 ShowAnglesExportCaller g_show_angles_caller;
 Callback<void(const Callback<void(bool)> &)> g_show_angles_callback( g_show_angles_caller );
 ToggleItem g_show_angles( g_show_angles_callback );
@@ -2697,9 +3155,27 @@ ShowWorkzoneExportCaller g_show_workzone_caller;
 Callback<void(const Callback<void(bool)> &)> g_show_workzone_callback( g_show_workzone_caller );
 ToggleItem g_show_workzone( g_show_workzone_callback );
 
+ShowSizeExportCaller g_show_size_caller;
+Callback<void(const Callback<void(bool)> &)> g_show_size_callback( g_show_size_caller );
+ToggleItem g_show_size( g_show_size_callback );
+
+ShowCrosshairExportCaller g_show_crosshair_caller;
+Callback<void(const Callback<void(bool)> &)> g_show_crosshair_callback( g_show_crosshair_caller );
+ToggleItem g_show_crosshair( g_show_crosshair_callback );
+
+ShowGridExportCaller g_show_grid_caller;
+Callback<void(const Callback<void(bool)> &)> g_show_grid_callback( g_show_grid_caller );
+ToggleItem g_show_grid( g_show_grid_callback );
+
+
 void XYShow_registerCommands(){
+       GlobalToggles_insert( "ToggleSizePaint", ShowSizeToggleCaller(), ToggleItem::AddCallbackCaller( g_show_size ), Accelerator( 'J' ) );
+       GlobalToggles_insert( "ToggleCrosshairs", ShowCrosshairToggleCaller(), ToggleItem::AddCallbackCaller( g_show_crosshair ), Accelerator( 'X', (GdkModifierType)GDK_SHIFT_MASK ) );
+       GlobalToggles_insert( "ToggleGrid", ShowGridToggleCaller(), ToggleItem::AddCallbackCaller( g_show_grid ), Accelerator( '0' ) );
+
        GlobalToggles_insert( "ShowAngles", ShowAnglesToggleCaller(), ToggleItem::AddCallbackCaller( g_show_angles ) );
        GlobalToggles_insert( "ShowNames", ShowNamesToggleCaller(), ToggleItem::AddCallbackCaller( g_show_names ) );
+       GlobalToggles_insert( "ShowTargetNames", ShowTargetNamesToggleCaller(), ToggleItem::AddCallbackCaller( g_show_targetnames ) );
        GlobalToggles_insert( "ShowBlocks", ShowBlocksToggleCaller(), ToggleItem::AddCallbackCaller( g_show_blocks ) );
        GlobalToggles_insert( "ShowCoordinates", ShowCoordinatesToggleCaller(), ToggleItem::AddCallbackCaller( g_show_coordinates ) );
        GlobalToggles_insert( "ShowWindowOutline", ShowOutlineToggleCaller(), ToggleItem::AddCallbackCaller( g_show_outline ) );
@@ -2715,10 +3191,11 @@ void XYWnd_registerShortcuts(){
 
 
 void Orthographic_constructPreferences( PreferencesPage& page ){
-       page.appendCheckBox( "", "Solid selection boxes", g_xywindow_globals.m_bNoStipple );
-       page.appendCheckBox( "", "Display size info", g_xywindow_globals_private.m_bSizePaint );
+       page.appendCheckBox( "", "Solid selection boxes ( no stipple )", g_xywindow_globals.m_bNoStipple );
+       //page.appendCheckBox( "", "Display size info", g_xywindow_globals_private.m_bSizePaint );
        page.appendCheckBox( "", "Chase mouse during drags", g_xywindow_globals_private.m_bChaseMouse );
-       page.appendCheckBox( "", "Update views on camera move", g_xywindow_globals_private.m_bCamXYUpdate );
+//     page.appendCheckBox( "", "Update views on camera move", g_xywindow_globals_private.m_bCamXYUpdate );
+       page.appendCheckBox( "", "Zoom In to Mouse pointer", g_xywindow_globals.m_bZoomInToPointer );
 }
 void Orthographic_constructPage( PreferenceGroup& group ){
        PreferencesPage page( group.createPage( "Orthographic", "Orthographic View Preferences" ) );
@@ -2756,9 +3233,9 @@ struct ToggleShown_Bool {
 
 
 void XYWindow_Construct(){
-       GlobalCommands_insert( "ToggleCrosshairs", makeCallbackF(ToggleShowCrosshair), Accelerator( 'X', (GdkModifierType)GDK_SHIFT_MASK ) );
-       GlobalCommands_insert( "ToggleSizePaint", makeCallbackF(ToggleShowSizeInfo), Accelerator( 'J' ) );
-       GlobalCommands_insert( "ToggleGrid", makeCallbackF(ToggleShowGrid), Accelerator( '0' ) );
+//     GlobalCommands_insert( "ToggleCrosshairs", makeCallbackF(ToggleShowCrosshair), Accelerator( 'X', (GdkModifierType)GDK_SHIFT_MASK ) );
+//     GlobalCommands_insert( "ToggleSizePaint", makeCallbackF(ToggleShowSizeInfo), Accelerator( 'J' ) );
+//     GlobalCommands_insert( "ToggleGrid", makeCallbackF(ToggleShowGrid), Accelerator( '0' ) );
 
        GlobalToggles_insert( "ToggleView", ToggleShown::ToggleCaller( g_xy_top_shown ), ToggleItem::AddCallbackCaller( g_xy_top_shown.m_item ), Accelerator( 'V', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
        GlobalToggles_insert( "ToggleSideView", ToggleShown::ToggleCaller( g_yz_side_shown ), ToggleItem::AddCallbackCaller( g_yz_side_shown.m_item ) );
@@ -2770,18 +3247,21 @@ void XYWindow_Construct(){
        GlobalCommands_insert( "ViewSide", makeCallbackF(XY_Side), Accelerator( GDK_KEY_KP_Page_Down ) );
        GlobalCommands_insert( "ViewFront", makeCallbackF(XY_Front), Accelerator( GDK_KEY_KP_End ) );
        GlobalCommands_insert( "Zoom100", makeCallbackF(XY_Zoom100) );
-       GlobalCommands_insert( "CenterXYView", makeCallbackF(XY_Focus), Accelerator( GDK_KEY_Tab, (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
+       GlobalCommands_insert( "CenterXYView", makeCallbackF(XY_Centralize), Accelerator( GDK_KEY_Tab, (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
+       GlobalCommands_insert( "XYFocusOnSelected", makeCallbackF(XY_Focus), Accelerator( GDK_KEY_grave ) );
 
        GlobalPreferenceSystem().registerPreference( "ClipCaulk", make_property_string( g_clip_useCaulk ) );
 
-       GlobalPreferenceSystem().registerPreference( "NewRightClick", make_property_string( g_xywindow_globals.m_bRightClick ) );
+//     GlobalPreferenceSystem().registerPreference( "NewRightClick", make_property_string( g_xywindow_globals.m_bRightClick ) );
+       GlobalPreferenceSystem().registerPreference( "2DZoomInToPointer", make_property_string( g_xywindow_globals.m_bZoomInToPointer ) );
        GlobalPreferenceSystem().registerPreference( "ChaseMouse", make_property_string( g_xywindow_globals_private.m_bChaseMouse ) );
        GlobalPreferenceSystem().registerPreference( "SizePainting", make_property_string( g_xywindow_globals_private.m_bSizePaint ) );
+       GlobalPreferenceSystem().registerPreference( "ShowCrosshair", make_property_string( g_xywindow_globals_private.g_bCrossHairs ) );
        GlobalPreferenceSystem().registerPreference( "NoStipple", make_property_string( g_xywindow_globals.m_bNoStipple ) );
        GlobalPreferenceSystem().registerPreference( "SI_ShowCoords", make_property_string( g_xywindow_globals_private.show_coordinates ) );
        GlobalPreferenceSystem().registerPreference( "SI_ShowOutlines", make_property_string( g_xywindow_globals_private.show_outline ) );
        GlobalPreferenceSystem().registerPreference( "SI_ShowAxis", make_property_string( g_xywindow_globals_private.show_axis ) );
-       GlobalPreferenceSystem().registerPreference( "CamXYUpdate", make_property_string( g_xywindow_globals_private.m_bCamXYUpdate ) );
+//     GlobalPreferenceSystem().registerPreference( "CamXYUpdate", make_property_string( g_xywindow_globals_private.m_bCamXYUpdate ) );
        GlobalPreferenceSystem().registerPreference( "ShowWorkzone", make_property_string( g_xywindow_globals_private.d_show_work ) );
 
        GlobalPreferenceSystem().registerPreference( "SI_AxisColors0", make_property_string( g_xywindow_globals.AxisColorX ) );
@@ -2793,7 +3273,9 @@ void XYWindow_Construct(){
        GlobalPreferenceSystem().registerPreference( "SI_Colors6", make_property_string( g_xywindow_globals.color_gridblock ) );
        GlobalPreferenceSystem().registerPreference( "SI_Colors7", make_property_string( g_xywindow_globals.color_gridtext ) );
        GlobalPreferenceSystem().registerPreference( "SI_Colors8", make_property_string( g_xywindow_globals.color_brushes ) );
-       GlobalPreferenceSystem().registerPreference( "SI_Colors14", make_property_string( g_xywindow_globals.color_gridmajor_alt ) );
+       GlobalPreferenceSystem().registerPreference( "SI_Colors9", make_property_string( g_xywindow_globals.color_viewname ) );
+       GlobalPreferenceSystem().registerPreference( "SI_Colors10", make_property_string( g_xywindow_globals.color_clipper ) );
+       GlobalPreferenceSystem().registerPreference( "SI_Colors11", make_property_string( g_xywindow_globals.color_selbrushes ) );
 
 
        GlobalPreferenceSystem().registerPreference( "XZVIS", make_property_string<ToggleShown_Bool>( g_xz_front_shown ) );
index ac059329518178de2d63c32843579c56352f6be8..e23b4bd79280f24332ef74993cb0bbda61e205a4 100644 (file)
@@ -119,19 +119,35 @@ void Move_End();
 bool m_move_started;
 guint m_move_focusOut;
 
-void Zoom_Begin();
+void Zoom_Begin( int x, int y );
 void Zoom_End();
 bool m_zoom_started;
 guint m_zoom_focusOut;
 
+void ZoomIn();
+void ZoomOut();
+void ZoomInWithMouse( int pointx, int pointy );
+void FocusOnBounds( AABB& bounds );
+
 void Redraw();
 
+void RenderActive();
+
 void SetActive( bool b ){
        m_bActive = b;
+       RenderActive();
 };
 bool Active(){
        return m_bActive;
 };
+struct camera_icon_t
+{
+       float x, y, fov, box;
+       double a;
+};
+camera_icon_t Cam;
+void UpdateCameraIcon();
+
 
 void Clipper_OnLButtonDown( int x, int y );
 void Clipper_OnLButtonUp( int x, int y );
@@ -139,6 +155,8 @@ void Clipper_OnMouseMoved( int x, int y );
 void Clipper_Crosshair_OnMouseMoved( int x, int y );
 void DropClipPoint( int pointx, int pointy );
 
+void SetCustomPivotOrigin( int pointx, int pointy );
+
 void SetViewType( VIEWTYPE n );
 bool m_bActive;
 
@@ -196,10 +214,12 @@ bool m_entityCreate;
 
 public:
 void ButtonState_onMouseDown( unsigned int buttons ){
-       m_buttonstate |= buttons;
+       //m_buttonstate |= buttons;
+       m_buttonstate = buttons;
 }
 void ButtonState_onMouseUp( unsigned int buttons ){
-       m_buttonstate &= ~buttons;
+       //m_buttonstate &= ~buttons;
+       m_buttonstate = 0;
 }
 unsigned int getButtonState() const {
        return m_buttonstate;
@@ -233,6 +253,7 @@ inline void XYWnd_Update( XYWnd& xywnd ){
        xywnd.queueDraw();
 }
 
+void XY_Centralize();
 
 struct xywindow_globals_t
 {
@@ -251,14 +272,15 @@ struct xywindow_globals_t
        Vector3 AxisColorY;
        Vector3 AxisColorZ;
 
-       bool m_bRightClick;
+//     bool m_bRightClick;
        bool m_bNoStipple;
+       bool m_bZoomInToPointer;
 
        xywindow_globals_t() :
-               color_gridback( 1.f, 1.f, 1.f ),
-               color_gridminor( 0.75f, 0.75f, 0.75f ),
-               color_gridmajor( 0.5f, 0.5f, 0.5f ),
-               color_gridblock( 0.f, 0.f, 1.f ),
+               color_gridback( 0.77f, 0.77f, 0.77f ),
+               color_gridminor( 0.83f, 0.83f, 0.83f ),
+               color_gridmajor( 0.89f, 0.89f, 0.89f ),
+               color_gridblock( 1.0f, 1.0f, 1.0f ),
                color_gridtext( 0.f, 0.f, 0.f ),
                color_brushes( 0.f, 0.f, 0.f ),
                color_selbrushes( 1.f, 0.f, 0.f ),
@@ -270,8 +292,9 @@ struct xywindow_globals_t
                AxisColorX( 1.f, 0.f, 0.f ),
                AxisColorY( 0.f, 1.f, 0.f ),
                AxisColorZ( 0.f, 0.f, 1.f ),
-               m_bRightClick( true ),
-               m_bNoStipple( false ){
+//             m_bRightClick( true ),
+               m_bNoStipple( true ),
+               m_bZoomInToPointer( true ){
        }
 
 };
diff --git a/setup/data/tools-src/black.png b/setup/data/tools-src/black.png
new file mode 100644 (file)
index 0000000..6d3e454
Binary files /dev/null and b/setup/data/tools-src/black.png differ
diff --git a/setup/data/tools-src/brush_flipx.png b/setup/data/tools-src/brush_flipx.png
new file mode 100644 (file)
index 0000000..6207339
Binary files /dev/null and b/setup/data/tools-src/brush_flipx.png differ
diff --git a/setup/data/tools-src/brush_flipy.png b/setup/data/tools-src/brush_flipy.png
new file mode 100644 (file)
index 0000000..ce537f3
Binary files /dev/null and b/setup/data/tools-src/brush_flipy.png differ
diff --git a/setup/data/tools-src/brush_flipz.png b/setup/data/tools-src/brush_flipz.png
new file mode 100644 (file)
index 0000000..d2d0708
Binary files /dev/null and b/setup/data/tools-src/brush_flipz.png differ
diff --git a/setup/data/tools-src/brush_rotatex.png b/setup/data/tools-src/brush_rotatex.png
new file mode 100644 (file)
index 0000000..cd1e1a5
Binary files /dev/null and b/setup/data/tools-src/brush_rotatex.png differ
diff --git a/setup/data/tools-src/brush_rotatey.png b/setup/data/tools-src/brush_rotatey.png
new file mode 100644 (file)
index 0000000..907210e
Binary files /dev/null and b/setup/data/tools-src/brush_rotatey.png differ
diff --git a/setup/data/tools-src/brush_rotatez.png b/setup/data/tools-src/brush_rotatez.png
new file mode 100644 (file)
index 0000000..92edb04
Binary files /dev/null and b/setup/data/tools-src/brush_rotatez.png differ
diff --git a/setup/data/tools-src/dontselectcurve.png b/setup/data/tools-src/dontselectcurve.png
new file mode 100644 (file)
index 0000000..66254df
Binary files /dev/null and b/setup/data/tools-src/dontselectcurve.png differ
diff --git a/setup/data/tools-src/dontselectmodel.png b/setup/data/tools-src/dontselectmodel.png
new file mode 100644 (file)
index 0000000..82c472a
Binary files /dev/null and b/setup/data/tools-src/dontselectmodel.png differ
diff --git a/setup/data/tools-src/f-decals.png b/setup/data/tools-src/f-decals.png
new file mode 100644 (file)
index 0000000..a07fa5d
Binary files /dev/null and b/setup/data/tools-src/f-decals.png differ
diff --git a/setup/data/tools-src/garux/icon_garux.png b/setup/data/tools-src/garux/icon_garux.png
new file mode 100644 (file)
index 0000000..b0219e8
Binary files /dev/null and b/setup/data/tools-src/garux/icon_garux.png differ
diff --git a/setup/data/tools-src/garux/logo_garux.png b/setup/data/tools-src/garux/logo_garux.png
new file mode 100644 (file)
index 0000000..533933e
Binary files /dev/null and b/setup/data/tools-src/garux/logo_garux.png differ
diff --git a/setup/data/tools-src/garux/radiant_garux.ico b/setup/data/tools-src/garux/radiant_garux.ico
new file mode 100644 (file)
index 0000000..6492cb0
Binary files /dev/null and b/setup/data/tools-src/garux/radiant_garux.ico differ
diff --git a/setup/data/tools-src/garux/splash_garux.png b/setup/data/tools-src/garux/splash_garux.png
new file mode 100644 (file)
index 0000000..3e92376
Binary files /dev/null and b/setup/data/tools-src/garux/splash_garux.png differ
diff --git a/setup/data/tools-src/lightinspector.png b/setup/data/tools-src/lightinspector.png
new file mode 100644 (file)
index 0000000..93688b3
Binary files /dev/null and b/setup/data/tools-src/lightinspector.png differ
diff --git a/setup/data/tools-src/noFalloff.png b/setup/data/tools-src/noFalloff.png
new file mode 100644 (file)
index 0000000..55c6a80
Binary files /dev/null and b/setup/data/tools-src/noFalloff.png differ
diff --git a/setup/data/tools-src/patch_bend.png b/setup/data/tools-src/patch_bend.png
new file mode 100644 (file)
index 0000000..62eb756
Binary files /dev/null and b/setup/data/tools-src/patch_bend.png differ
diff --git a/setup/data/tools-src/patch_drilldown.png b/setup/data/tools-src/patch_drilldown.png
new file mode 100644 (file)
index 0000000..1794a13
Binary files /dev/null and b/setup/data/tools-src/patch_drilldown.png differ
diff --git a/setup/data/tools-src/patch_insdel.png b/setup/data/tools-src/patch_insdel.png
new file mode 100644 (file)
index 0000000..362285e
Binary files /dev/null and b/setup/data/tools-src/patch_insdel.png differ
diff --git a/setup/data/tools-src/patch_showboundingbox.png b/setup/data/tools-src/patch_showboundingbox.png
new file mode 100644 (file)
index 0000000..891f3c7
Binary files /dev/null and b/setup/data/tools-src/patch_showboundingbox.png differ
diff --git a/setup/data/tools-src/patch_weld.png b/setup/data/tools-src/patch_weld.png
new file mode 100644 (file)
index 0000000..3d53a6b
Binary files /dev/null and b/setup/data/tools-src/patch_weld.png differ
diff --git a/setup/data/tools-src/popup_selection.png b/setup/data/tools-src/popup_selection.png
new file mode 100644 (file)
index 0000000..217fa82
Binary files /dev/null and b/setup/data/tools-src/popup_selection.png differ
diff --git a/setup/data/tools-src/scalelockx.png b/setup/data/tools-src/scalelockx.png
new file mode 100644 (file)
index 0000000..9714120
Binary files /dev/null and b/setup/data/tools-src/scalelockx.png differ
diff --git a/setup/data/tools-src/scalelocky.png b/setup/data/tools-src/scalelocky.png
new file mode 100644 (file)
index 0000000..7836242
Binary files /dev/null and b/setup/data/tools-src/scalelocky.png differ
diff --git a/setup/data/tools-src/scalelockz.png b/setup/data/tools-src/scalelockz.png
new file mode 100644 (file)
index 0000000..f967680
Binary files /dev/null and b/setup/data/tools-src/scalelockz.png differ
diff --git a/setup/data/tools-src/selection_makehollow.png b/setup/data/tools-src/selection_makehollow.png
new file mode 100644 (file)
index 0000000..5842690
Binary files /dev/null and b/setup/data/tools-src/selection_makehollow.png differ
diff --git a/setup/data/tools-src/selection_selectcompletetall.png b/setup/data/tools-src/selection_selectcompletetall.png
new file mode 100644 (file)
index 0000000..3f5813e
Binary files /dev/null and b/setup/data/tools-src/selection_selectcompletetall.png differ
diff --git a/setup/data/tools-src/selection_selectcompletetall_old.png b/setup/data/tools-src/selection_selectcompletetall_old.png
new file mode 100644 (file)
index 0000000..3f55564
Binary files /dev/null and b/setup/data/tools-src/selection_selectcompletetall_old.png differ
diff --git a/setup/data/tools-src/selection_selectpartialtall.png b/setup/data/tools-src/selection_selectpartialtall.png
new file mode 100644 (file)
index 0000000..e378e47
Binary files /dev/null and b/setup/data/tools-src/selection_selectpartialtall.png differ
diff --git a/setup/data/tools-src/selection_selectpartialtall_old.png b/setup/data/tools-src/selection_selectpartialtall_old.png
new file mode 100644 (file)
index 0000000..ab1dfc7
Binary files /dev/null and b/setup/data/tools-src/selection_selectpartialtall_old.png differ
diff --git a/setup/data/tools-src/show_entities.png b/setup/data/tools-src/show_entities.png
new file mode 100644 (file)
index 0000000..97bc49b
Binary files /dev/null and b/setup/data/tools-src/show_entities.png differ
diff --git a/setup/data/tools-src/texbro_tags.xcf.bz2 b/setup/data/tools-src/texbro_tags.xcf.bz2
new file mode 100644 (file)
index 0000000..2719ad5
Binary files /dev/null and b/setup/data/tools-src/texbro_tags.xcf.bz2 differ
diff --git a/setup/data/tools-src/textures_popup.png b/setup/data/tools-src/textures_popup.png
new file mode 100644 (file)
index 0000000..b238679
Binary files /dev/null and b/setup/data/tools-src/textures_popup.png differ
diff --git a/setup/data/tools-src/view_cameratoggle.png b/setup/data/tools-src/view_cameratoggle.png
new file mode 100644 (file)
index 0000000..375e3de
Binary files /dev/null and b/setup/data/tools-src/view_cameratoggle.png differ
diff --git a/setup/data/tools-src/view_cameraupdate.png b/setup/data/tools-src/view_cameraupdate.png
new file mode 100644 (file)
index 0000000..62aa228
Binary files /dev/null and b/setup/data/tools-src/view_cameraupdate.png differ
diff --git a/setup/data/tools-src/white.png b/setup/data/tools-src/white.png
new file mode 100644 (file)
index 0000000..4af01bd
Binary files /dev/null and b/setup/data/tools-src/white.png differ
diff --git a/setup/data/tools/bitmaps/black.png b/setup/data/tools/bitmaps/black.png
deleted file mode 100644 (file)
index 6d3e454..0000000
Binary files a/setup/data/tools/bitmaps/black.png and /dev/null differ
diff --git a/setup/data/tools/bitmaps/brush_flip_hor.png b/setup/data/tools/bitmaps/brush_flip_hor.png
new file mode 100644 (file)
index 0000000..5d16a6f
Binary files /dev/null and b/setup/data/tools/bitmaps/brush_flip_hor.png differ
diff --git a/setup/data/tools/bitmaps/brush_flip_vert.png b/setup/data/tools/bitmaps/brush_flip_vert.png
new file mode 100644 (file)
index 0000000..a6d039b
Binary files /dev/null and b/setup/data/tools/bitmaps/brush_flip_vert.png differ
diff --git a/setup/data/tools/bitmaps/brush_flipx.png b/setup/data/tools/bitmaps/brush_flipx.png
deleted file mode 100644 (file)
index 6207339..0000000
Binary files a/setup/data/tools/bitmaps/brush_flipx.png and /dev/null differ
diff --git a/setup/data/tools/bitmaps/brush_flipy.png b/setup/data/tools/bitmaps/brush_flipy.png
deleted file mode 100644 (file)
index ce537f3..0000000
Binary files a/setup/data/tools/bitmaps/brush_flipy.png and /dev/null differ
diff --git a/setup/data/tools/bitmaps/brush_flipz.png b/setup/data/tools/bitmaps/brush_flipz.png
deleted file mode 100644 (file)
index d2d0708..0000000
Binary files a/setup/data/tools/bitmaps/brush_flipz.png and /dev/null differ
diff --git a/setup/data/tools/bitmaps/brush_rotate_anti.png b/setup/data/tools/bitmaps/brush_rotate_anti.png
new file mode 100644 (file)
index 0000000..4897620
Binary files /dev/null and b/setup/data/tools/bitmaps/brush_rotate_anti.png differ
diff --git a/setup/data/tools/bitmaps/brush_rotate_clock.png b/setup/data/tools/bitmaps/brush_rotate_clock.png
new file mode 100644 (file)
index 0000000..7c6b0ba
Binary files /dev/null and b/setup/data/tools/bitmaps/brush_rotate_clock.png differ
diff --git a/setup/data/tools/bitmaps/brush_rotatex.png b/setup/data/tools/bitmaps/brush_rotatex.png
deleted file mode 100644 (file)
index 37c46fc..0000000
Binary files a/setup/data/tools/bitmaps/brush_rotatex.png and /dev/null differ
diff --git a/setup/data/tools/bitmaps/brush_rotatey.png b/setup/data/tools/bitmaps/brush_rotatey.png
deleted file mode 100644 (file)
index c3b33e7..0000000
Binary files a/setup/data/tools/bitmaps/brush_rotatey.png and /dev/null differ
diff --git a/setup/data/tools/bitmaps/brush_rotatez.png b/setup/data/tools/bitmaps/brush_rotatez.png
deleted file mode 100644 (file)
index e56f1cb..0000000
Binary files a/setup/data/tools/bitmaps/brush_rotatez.png and /dev/null differ
index b39565df24acbbbe3047827242674d023af33970..92408cccab3f0328f7cc356f6322ce21c967af0a 100644 (file)
Binary files a/setup/data/tools/bitmaps/cap_bevel.png and b/setup/data/tools/bitmaps/cap_bevel.png differ
index 6ce1c0c16ccf248e9971ab7ad9c1d88e4e8aad2b..a46d1dd5df795d6ae3524c2522a682bff97d8a86 100644 (file)
Binary files a/setup/data/tools/bitmaps/cap_curve.png and b/setup/data/tools/bitmaps/cap_curve.png differ
index 96889301d11c7862252b8607721834a23ef0566c..9119be8abf3e3b146de19d630101e7f2f3ce78af 100644 (file)
Binary files a/setup/data/tools/bitmaps/cap_cylinder.png and b/setup/data/tools/bitmaps/cap_cylinder.png differ
index 049b2503d3e8cb1aa388ec763416ae5f5cb80b1d..5e841cc6d0091234612fc9fe097a210ee2871342 100644 (file)
Binary files a/setup/data/tools/bitmaps/cap_endcap.png and b/setup/data/tools/bitmaps/cap_endcap.png differ
index c284b66b213d1884e631f555b98a5f41336368fb..27a633ed5bd23207d7707595b52a96fe318c9695 100644 (file)
Binary files a/setup/data/tools/bitmaps/cap_ibevel.png and b/setup/data/tools/bitmaps/cap_ibevel.png differ
index 11a3f162110d49d08a5fbf04a930cca58e5bf709..283ea609d3e4cd265bfac6c34b0f30a38c349cec 100644 (file)
Binary files a/setup/data/tools/bitmaps/cap_iendcap.png and b/setup/data/tools/bitmaps/cap_iendcap.png differ
diff --git a/setup/data/tools/bitmaps/csgtool_diagonal.png b/setup/data/tools/bitmaps/csgtool_diagonal.png
new file mode 100644 (file)
index 0000000..6216152
Binary files /dev/null and b/setup/data/tools/bitmaps/csgtool_diagonal.png differ
diff --git a/setup/data/tools/bitmaps/csgtool_expand.png b/setup/data/tools/bitmaps/csgtool_expand.png
new file mode 100644 (file)
index 0000000..ee29803
Binary files /dev/null and b/setup/data/tools/bitmaps/csgtool_expand.png differ
diff --git a/setup/data/tools/bitmaps/csgtool_extrude.png b/setup/data/tools/bitmaps/csgtool_extrude.png
new file mode 100644 (file)
index 0000000..680338d
Binary files /dev/null and b/setup/data/tools/bitmaps/csgtool_extrude.png differ
diff --git a/setup/data/tools/bitmaps/csgtool_pull.png b/setup/data/tools/bitmaps/csgtool_pull.png
new file mode 100644 (file)
index 0000000..cabdd67
Binary files /dev/null and b/setup/data/tools/bitmaps/csgtool_pull.png differ
diff --git a/setup/data/tools/bitmaps/csgtool_removeinner.png b/setup/data/tools/bitmaps/csgtool_removeinner.png
new file mode 100644 (file)
index 0000000..5161b47
Binary files /dev/null and b/setup/data/tools/bitmaps/csgtool_removeinner.png differ
diff --git a/setup/data/tools/bitmaps/csgtool_shrink.png b/setup/data/tools/bitmaps/csgtool_shrink.png
new file mode 100644 (file)
index 0000000..2cb2458
Binary files /dev/null and b/setup/data/tools/bitmaps/csgtool_shrink.png differ
diff --git a/setup/data/tools/bitmaps/csgtool_wrap.png b/setup/data/tools/bitmaps/csgtool_wrap.png
new file mode 100644 (file)
index 0000000..569b8bc
Binary files /dev/null and b/setup/data/tools/bitmaps/csgtool_wrap.png differ
diff --git a/setup/data/tools/bitmaps/dontselectcurve.png b/setup/data/tools/bitmaps/dontselectcurve.png
deleted file mode 100644 (file)
index 66254df..0000000
Binary files a/setup/data/tools/bitmaps/dontselectcurve.png and /dev/null differ
diff --git a/setup/data/tools/bitmaps/dontselectmodel.png b/setup/data/tools/bitmaps/dontselectmodel.png
deleted file mode 100644 (file)
index 82c472a..0000000
Binary files a/setup/data/tools/bitmaps/dontselectmodel.png and /dev/null differ
diff --git a/setup/data/tools/bitmaps/f-areaportal.png b/setup/data/tools/bitmaps/f-areaportal.png
new file mode 100644 (file)
index 0000000..dd85eaf
Binary files /dev/null and b/setup/data/tools/bitmaps/f-areaportal.png differ
diff --git a/setup/data/tools/bitmaps/f-caulk.png b/setup/data/tools/bitmaps/f-caulk.png
new file mode 100644 (file)
index 0000000..3b6b899
Binary files /dev/null and b/setup/data/tools/bitmaps/f-caulk.png differ
diff --git a/setup/data/tools/bitmaps/f-clip.png b/setup/data/tools/bitmaps/f-clip.png
new file mode 100644 (file)
index 0000000..e69ae95
Binary files /dev/null and b/setup/data/tools/bitmaps/f-clip.png differ
diff --git a/setup/data/tools/bitmaps/f-details.png b/setup/data/tools/bitmaps/f-details.png
new file mode 100644 (file)
index 0000000..7748ba2
Binary files /dev/null and b/setup/data/tools/bitmaps/f-details.png differ
diff --git a/setup/data/tools/bitmaps/f-entities.png b/setup/data/tools/bitmaps/f-entities.png
new file mode 100644 (file)
index 0000000..7e90d43
Binary files /dev/null and b/setup/data/tools/bitmaps/f-entities.png differ
diff --git a/setup/data/tools/bitmaps/f-funcgroups.png b/setup/data/tools/bitmaps/f-funcgroups.png
new file mode 100644 (file)
index 0000000..aef9613
Binary files /dev/null and b/setup/data/tools/bitmaps/f-funcgroups.png differ
diff --git a/setup/data/tools/bitmaps/f-hide.png b/setup/data/tools/bitmaps/f-hide.png
new file mode 100644 (file)
index 0000000..f283417
Binary files /dev/null and b/setup/data/tools/bitmaps/f-hide.png differ
diff --git a/setup/data/tools/bitmaps/f-hint.png b/setup/data/tools/bitmaps/f-hint.png
new file mode 100644 (file)
index 0000000..0358c22
Binary files /dev/null and b/setup/data/tools/bitmaps/f-hint.png differ
diff --git a/setup/data/tools/bitmaps/f-invert.png b/setup/data/tools/bitmaps/f-invert.png
new file mode 100644 (file)
index 0000000..0b9f0f2
Binary files /dev/null and b/setup/data/tools/bitmaps/f-invert.png differ
diff --git a/setup/data/tools/bitmaps/f-lights.png b/setup/data/tools/bitmaps/f-lights.png
new file mode 100644 (file)
index 0000000..0c75e2e
Binary files /dev/null and b/setup/data/tools/bitmaps/f-lights.png differ
diff --git a/setup/data/tools/bitmaps/f-liquids.png b/setup/data/tools/bitmaps/f-liquids.png
new file mode 100644 (file)
index 0000000..1ecdaae
Binary files /dev/null and b/setup/data/tools/bitmaps/f-liquids.png differ
diff --git a/setup/data/tools/bitmaps/f-models.png b/setup/data/tools/bitmaps/f-models.png
new file mode 100644 (file)
index 0000000..42dc3aa
Binary files /dev/null and b/setup/data/tools/bitmaps/f-models.png differ
diff --git a/setup/data/tools/bitmaps/f-region.png b/setup/data/tools/bitmaps/f-region.png
new file mode 100644 (file)
index 0000000..a16c7ac
Binary files /dev/null and b/setup/data/tools/bitmaps/f-region.png differ
diff --git a/setup/data/tools/bitmaps/f-reset.png b/setup/data/tools/bitmaps/f-reset.png
new file mode 100644 (file)
index 0000000..fa3b37a
Binary files /dev/null and b/setup/data/tools/bitmaps/f-reset.png differ
diff --git a/setup/data/tools/bitmaps/f-structural.png b/setup/data/tools/bitmaps/f-structural.png
new file mode 100644 (file)
index 0000000..493cb10
Binary files /dev/null and b/setup/data/tools/bitmaps/f-structural.png differ
diff --git a/setup/data/tools/bitmaps/f-translucent.png b/setup/data/tools/bitmaps/f-translucent.png
new file mode 100644 (file)
index 0000000..98c2fe1
Binary files /dev/null and b/setup/data/tools/bitmaps/f-translucent.png differ
diff --git a/setup/data/tools/bitmaps/f-triggers.png b/setup/data/tools/bitmaps/f-triggers.png
new file mode 100644 (file)
index 0000000..4b2a8f9
Binary files /dev/null and b/setup/data/tools/bitmaps/f-triggers.png differ
diff --git a/setup/data/tools/bitmaps/f-world.png b/setup/data/tools/bitmaps/f-world.png
new file mode 100644 (file)
index 0000000..e96dd55
Binary files /dev/null and b/setup/data/tools/bitmaps/f-world.png differ
diff --git a/setup/data/tools/bitmaps/lightinspector.png b/setup/data/tools/bitmaps/lightinspector.png
deleted file mode 100644 (file)
index 93688b3..0000000
Binary files a/setup/data/tools/bitmaps/lightinspector.png and /dev/null differ
diff --git a/setup/data/tools/bitmaps/noFalloff.png b/setup/data/tools/bitmaps/noFalloff.png
deleted file mode 100644 (file)
index 55c6a80..0000000
Binary files a/setup/data/tools/bitmaps/noFalloff.png and /dev/null differ
diff --git a/setup/data/tools/bitmaps/patch_bend.png b/setup/data/tools/bitmaps/patch_bend.png
deleted file mode 100644 (file)
index 62eb756..0000000
Binary files a/setup/data/tools/bitmaps/patch_bend.png and /dev/null differ
diff --git a/setup/data/tools/bitmaps/patch_drilldown.png b/setup/data/tools/bitmaps/patch_drilldown.png
deleted file mode 100644 (file)
index 1794a13..0000000
Binary files a/setup/data/tools/bitmaps/patch_drilldown.png and /dev/null differ
diff --git a/setup/data/tools/bitmaps/patch_insdel.png b/setup/data/tools/bitmaps/patch_insdel.png
deleted file mode 100644 (file)
index 362285e..0000000
Binary files a/setup/data/tools/bitmaps/patch_insdel.png and /dev/null differ
diff --git a/setup/data/tools/bitmaps/patch_showboundingbox.png b/setup/data/tools/bitmaps/patch_showboundingbox.png
deleted file mode 100644 (file)
index 891f3c7..0000000
Binary files a/setup/data/tools/bitmaps/patch_showboundingbox.png and /dev/null differ
diff --git a/setup/data/tools/bitmaps/patch_weld.png b/setup/data/tools/bitmaps/patch_weld.png
deleted file mode 100644 (file)
index 3d53a6b..0000000
Binary files a/setup/data/tools/bitmaps/patch_weld.png and /dev/null differ
diff --git a/setup/data/tools/bitmaps/popup_selection.png b/setup/data/tools/bitmaps/popup_selection.png
deleted file mode 100644 (file)
index 217fa82..0000000
Binary files a/setup/data/tools/bitmaps/popup_selection.png and /dev/null differ
index befec6e3d1c66221320c4224a24bc48c6848a165..9483d42c76dc4e1a8fc06fceb4eae7cba815a8a6 100644 (file)
Binary files a/setup/data/tools/bitmaps/redo.png and b/setup/data/tools/bitmaps/redo.png differ
index ca77acb7e54259bdcb97ac74e3b5b2304e1f357d..4e53334acfb6d9e257e283b08fac65fd34082887 100644 (file)
Binary files a/setup/data/tools/bitmaps/refresh_models.png and b/setup/data/tools/bitmaps/refresh_models.png differ
diff --git a/setup/data/tools/bitmaps/scalelockx.png b/setup/data/tools/bitmaps/scalelockx.png
deleted file mode 100644 (file)
index 9714120..0000000
Binary files a/setup/data/tools/bitmaps/scalelockx.png and /dev/null differ
diff --git a/setup/data/tools/bitmaps/scalelocky.png b/setup/data/tools/bitmaps/scalelocky.png
deleted file mode 100644 (file)
index 7836242..0000000
Binary files a/setup/data/tools/bitmaps/scalelocky.png and /dev/null differ
diff --git a/setup/data/tools/bitmaps/scalelockz.png b/setup/data/tools/bitmaps/scalelockz.png
deleted file mode 100644 (file)
index f967680..0000000
Binary files a/setup/data/tools/bitmaps/scalelockz.png and /dev/null differ
index 6174568bae361acdbecbf3bd5f7823fd2cf98a94..a2b127cb16d524882b5486f369c6a2a3d63b6a56 100644 (file)
Binary files a/setup/data/tools/bitmaps/select_mouserotate.png and b/setup/data/tools/bitmaps/select_mouserotate.png differ
diff --git a/setup/data/tools/bitmaps/selection_makehollow.png b/setup/data/tools/bitmaps/selection_makehollow.png
deleted file mode 100644 (file)
index 44416e8..0000000
Binary files a/setup/data/tools/bitmaps/selection_makehollow.png and /dev/null differ
diff --git a/setup/data/tools/bitmaps/selection_selectcompletetall.png b/setup/data/tools/bitmaps/selection_selectcompletetall.png
deleted file mode 100644 (file)
index 3f5813e..0000000
Binary files a/setup/data/tools/bitmaps/selection_selectcompletetall.png and /dev/null differ
diff --git a/setup/data/tools/bitmaps/selection_selectpartialtall.png b/setup/data/tools/bitmaps/selection_selectpartialtall.png
deleted file mode 100644 (file)
index e378e47..0000000
Binary files a/setup/data/tools/bitmaps/selection_selectpartialtall.png and /dev/null differ
diff --git a/setup/data/tools/bitmaps/show_entities.png b/setup/data/tools/bitmaps/show_entities.png
deleted file mode 100644 (file)
index 97bc49b..0000000
Binary files a/setup/data/tools/bitmaps/show_entities.png and /dev/null differ
diff --git a/setup/data/tools/bitmaps/texbro_gtk-find-and-replace.png b/setup/data/tools/bitmaps/texbro_gtk-find-and-replace.png
new file mode 100644 (file)
index 0000000..fca34f5
Binary files /dev/null and b/setup/data/tools/bitmaps/texbro_gtk-find-and-replace.png differ
diff --git a/setup/data/tools/bitmaps/texbro_refresh.png b/setup/data/tools/bitmaps/texbro_refresh.png
new file mode 100644 (file)
index 0000000..ed2419a
Binary files /dev/null and b/setup/data/tools/bitmaps/texbro_refresh.png differ
diff --git a/setup/data/tools/bitmaps/texbro_tags.png b/setup/data/tools/bitmaps/texbro_tags.png
new file mode 100644 (file)
index 0000000..8b0da1e
Binary files /dev/null and b/setup/data/tools/bitmaps/texbro_tags.png differ
diff --git a/setup/data/tools/bitmaps/texbro_view.png b/setup/data/tools/bitmaps/texbro_view.png
new file mode 100644 (file)
index 0000000..cf2f4ac
Binary files /dev/null and b/setup/data/tools/bitmaps/texbro_view.png differ
diff --git a/setup/data/tools/bitmaps/textures_popup.png b/setup/data/tools/bitmaps/textures_popup.png
deleted file mode 100644 (file)
index b238679..0000000
Binary files a/setup/data/tools/bitmaps/textures_popup.png and /dev/null differ
index c0408ddd612dc6a30cf70cfee76527e28722966b..a9f9c042dac874776d53bfb5439ccf7588ca8cc0 100644 (file)
Binary files a/setup/data/tools/bitmaps/undo.png and b/setup/data/tools/bitmaps/undo.png differ
diff --git a/setup/data/tools/bitmaps/view_cameratoggle.png b/setup/data/tools/bitmaps/view_cameratoggle.png
deleted file mode 100644 (file)
index 375e3de..0000000
Binary files a/setup/data/tools/bitmaps/view_cameratoggle.png and /dev/null differ
diff --git a/setup/data/tools/bitmaps/view_cameraupdate.png b/setup/data/tools/bitmaps/view_cameraupdate.png
deleted file mode 100644 (file)
index 62aa228..0000000
Binary files a/setup/data/tools/bitmaps/view_cameraupdate.png and /dev/null differ
diff --git a/setup/data/tools/bitmaps/white.png b/setup/data/tools/bitmaps/white.png
deleted file mode 100644 (file)
index 4af01bd..0000000
Binary files a/setup/data/tools/bitmaps/white.png and /dev/null differ
diff --git a/setup/data/tools/bitmaps/window5.png b/setup/data/tools/bitmaps/window5.png
new file mode 100644 (file)
index 0000000..24335a5
Binary files /dev/null and b/setup/data/tools/bitmaps/window5.png differ
index c4de5d15783f6c249ae0199e0e2a96d285705e95..a109ec156ea09c527a32f81c5581ea5b91b6b522 100644 (file)
Binary files a/setup/data/tools/plugins/bitmaps/bobtoolz_cleanup.png and b/setup/data/tools/plugins/bitmaps/bobtoolz_cleanup.png differ
diff --git a/setup/data/tools/plugins/bitmaps/bobtoolz_cleanup_old.png b/setup/data/tools/plugins/bitmaps/bobtoolz_cleanup_old.png
new file mode 100644 (file)
index 0000000..c4de5d1
Binary files /dev/null and b/setup/data/tools/plugins/bitmaps/bobtoolz_cleanup_old.png differ
index e50d4b111ab70de3ea7c320aacc1b1c948bd60ef..f707ca8c45dabdbbf958bc585ff380824021c707 100644 (file)
Binary files a/setup/data/tools/plugins/bitmaps/bobtoolz_merge.png and b/setup/data/tools/plugins/bitmaps/bobtoolz_merge.png differ
diff --git a/setup/data/tools/plugins/bitmaps/bobtoolz_merge_old.png b/setup/data/tools/plugins/bitmaps/bobtoolz_merge_old.png
new file mode 100644 (file)
index 0000000..e50d4b1
Binary files /dev/null and b/setup/data/tools/plugins/bitmaps/bobtoolz_merge_old.png differ
index 897cbcbcd7ad9f2044e76a8cbfcd8d31f5be80d7..cfddeaa576c2009b688b7cd6f55f92456bce1a76 100644 (file)
Binary files a/setup/data/tools/plugins/bitmaps/bobtoolz_poly.png and b/setup/data/tools/plugins/bitmaps/bobtoolz_poly.png differ
diff --git a/setup/data/tools/plugins/bitmaps/bobtoolz_poly_old.png b/setup/data/tools/plugins/bitmaps/bobtoolz_poly_old.png
new file mode 100644 (file)
index 0000000..897cbcb
Binary files /dev/null and b/setup/data/tools/plugins/bitmaps/bobtoolz_poly_old.png differ
index 5387ec292cbb09e4bf4a2fa9f1af49bf46473eee..1b2d404314236b23ee8a0e0952a1b2a23ae7e126 100644 (file)
Binary files a/setup/data/tools/plugins/bitmaps/bobtoolz_split.png and b/setup/data/tools/plugins/bitmaps/bobtoolz_split.png differ
diff --git a/setup/data/tools/plugins/bitmaps/bobtoolz_split_old.png b/setup/data/tools/plugins/bitmaps/bobtoolz_split_old.png
new file mode 100644 (file)
index 0000000..5387ec2
Binary files /dev/null and b/setup/data/tools/plugins/bitmaps/bobtoolz_split_old.png differ
index 271c6128c134561ea300e427aa56b6fa80ba7aea..64649b9765d685e84eb16870bb9cbf31ad7b1ad9 100644 (file)
Binary files a/setup/data/tools/plugins/bitmaps/bobtoolz_splitcol.png and b/setup/data/tools/plugins/bitmaps/bobtoolz_splitcol.png differ
index c8d2863c212579cf50977b6aba28f6e4f476547f..ebd0b0783d52f470d8e27f2a25795d60ec9e8e98 100644 (file)
Binary files a/setup/data/tools/plugins/bitmaps/bobtoolz_splitrow.png and b/setup/data/tools/plugins/bitmaps/bobtoolz_splitrow.png differ
index 21933a1620ab26c37cbe9996442b9b99baecff31..9bfbf742039cfacb8736a22a4197b98215fd1eeb 100755 (executable)
@@ -33,12 +33,14 @@ then
        export GTK_PATH="${bundle_dir}"
 fi
 
-if [ "$(defaults read -g AppleInterfaceStyle 2>/dev/null)" = 'Dark' ]
-then
-       color='dark'
-else
-       color='light'
-fi
+color="$(defaults read -g AppleInterfaceStyle 2>/dev/null)"
+case "${color}" in
+       'Dark')
+               ;;
+       *)
+       color='Light'
+               ;;
+esac
 
 gtkrc_file="${bundle_dir}/share/themes/Mojave-${color}/gtk-2.0/gtkrc"
 
index 6e30389c35107adcb53c9e16d403b8fc51868aa2..b8f8565418b48968ecb7b4ecd67c1286be83fe1e 100644 (file)
@@ -6,5 +6,3 @@ add_subdirectory(quake3)
 
 add_custom_target(tools)
 add_dependencies(tools quake2 heretic2 quake3)
-
-add_subdirectory(unvanquished)
index 244452272c09a2e81c2ab024db56e89ef327db4a..9852623b02176d7e6ba0dca593febd90ec38bdac 100644 (file)
@@ -889,7 +889,7 @@ int ParseNum( const char *str ){
    // all output ends up through here
    void FPrintf (int flag, char *buf)
    {
-   printf(buf);
+   printf( "%s", buf );
 
    }
 
index 00f30e05701dd2389ed641576cb42f04d1e19c23..6a50cdb717537d116ad41b0b31ccdcdc67b2c40e 100644 (file)
@@ -1,4 +1,4 @@
-/*
+/*
    Copyright (C) 1999-2007 id Software, Inc. and contributors.
    For a list of contributors, see the accompanying CONTRIBUTORS file.
 
@@ -692,7 +692,7 @@ void Cmd_Mipdir( void ){
        GetToken( false );
        strcpy( mip_prefix, token );
        // create the directory if needed
-       sprintf( filename, "%stextures", gamedir, mip_prefix );
+       sprintf( filename, "%stextures", gamedir );
        Q_mkdir( filename );
        sprintf( filename, "%stextures/%s", gamedir, mip_prefix );
        Q_mkdir( filename );
index 1d9f6c78d48c650816a798d2581a674b81660ad1..7b551cbf31faf91185fe034d9b272967ddd90208 100644 (file)
@@ -245,7 +245,8 @@ void PackDirectory_r( char *dir ){
 #else
 
 #include <sys/types.h>
-#include <sys/dir.h>
+
+#include <dirent.h>
 
 void PackDirectory_r( char *dir ){
 #ifdef NeXT
index b6fe51173cb3a3121c8fd5c6a1cb472ddebd8be2..500ac1f960744b523d91f45e8678eea8aac66a10 100644 (file)
@@ -1194,7 +1194,7 @@ void Cmd_Video( void ){
        // build the dictionary
        for ( frame = startframe ;  ; frame++ )
        {
-               printf( "counting ", frame );
+               printf( "counting %i", frame );
                in = LoadFrame( base, frame, digits, &palette );
                if ( !in.data ) {
                        break;
@@ -1213,7 +1213,7 @@ void Cmd_Video( void ){
        // compress it with the dictionary
        for ( frame = startframe ;  ; frame++ )
        {
-               printf( "packing ", frame );
+               printf( "packing %i", frame );
                in = LoadFrame( base, frame, digits, &palette );
                if ( !in.data ) {
                        break;
index 5ade8802ca7eac347f2ebe14f81c85b36c89802c..1a7c6b8a0be3ff655c8437074dba4fde9d7280c9 100644 (file)
@@ -72,7 +72,7 @@ xmlNodePtr xml_NodeForVec( vec3_t v ){
 
        sprintf( buf, "%f %f %f", v[0], v[1], v[2] );
        ret = xmlNewNode( NULL, (xmlChar*)"point" );
-       xmlNodeSetContent( ret, (xmlChar*)buf );
+       xmlNodeAddContent( ret, (xmlChar*)buf );
        return ret;
 }
 
@@ -150,14 +150,14 @@ 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, (xmlChar*)"select" );
-       xmlNodeSetContent( node, (xmlChar*)buf );
+       xmlNodeAddContent( node, (xmlChar*)buf );
        level[0] = (int)'0' + ( bError ? SYS_ERR : SYS_WRN )  ;
        level[1] = 0;
        xmlSetProp( node, (xmlChar*)"level", (xmlChar*)&level );
        // a 'select' information
        sprintf( buf, "%i %i", entitynum, brushnum );
        select = xmlNewNode( NULL, (xmlChar*)"brush" );
-       xmlNodeSetContent( select, (xmlChar*)buf );
+       xmlNodeAddContent( select, (xmlChar*)buf );
        xmlAddChild( node, select );
        xml_SendNode( node );
 
@@ -177,14 +177,14 @@ void xml_Point( char *msg, vec3_t pt ){
        char level[2];
 
        node = xmlNewNode( NULL, (xmlChar*)"pointmsg" );
-       xmlNodeSetContent( node, (xmlChar*)msg );
+       xmlNodeAddContent( node, (xmlChar*)msg );
        level[0] = (int)'0' + SYS_ERR;
        level[1] = 0;
        xmlSetProp( node, (xmlChar*)"level", (xmlChar *)&level );
        // a 'point' node
        sprintf( buf, "%g %g %g", pt[0], pt[1], pt[2] );
        point = xmlNewNode( NULL, (xmlChar*)"point" );
-       xmlNodeSetContent( point, (xmlChar*)buf );
+       xmlNodeAddContent( point, (xmlChar*)buf );
        xmlAddChild( node, point );
        xml_SendNode( node );
 
@@ -201,7 +201,7 @@ void xml_Winding( char *msg, vec3_t p[], int numpoints, qboolean die ){
        int i;
 
        node = xmlNewNode( NULL, (xmlChar*)"windingmsg" );
-       xmlNodeSetContent( node, (xmlChar*)msg );
+       xmlNodeAddContent( node, (xmlChar*)msg );
        level[0] = (int)'0' + SYS_ERR;
        level[1] = 0;
        xmlSetProp( node, (xmlChar*)"level", (xmlChar *)&level );
@@ -218,7 +218,7 @@ void xml_Winding( char *msg, vec3_t p[], int numpoints, qboolean die ){
        }
 
        winding = xmlNewNode( NULL, (xmlChar*)"winding" );
-       xmlNodeSetContent( winding, (xmlChar*)buf );
+       xmlNodeAddContent( winding, (xmlChar*)buf );
        xmlAddChild( node, winding );
        xml_SendNode( node );
 
@@ -289,7 +289,7 @@ void FPrintf( int flag, char *buf ){
        node = xmlNewNode( NULL, (xmlChar*)"message" );
        {
                gchar* utf8 = g_locale_to_utf8( buf, -1, NULL, NULL, NULL );
-               xmlNodeSetContent( node, (xmlChar*)utf8 );
+               xmlNodeAddContent( node, (xmlChar*)utf8 );
                g_free( utf8 );
        }
        level[0] = (int)'0' + flag;
diff --git a/tools/quake3/common/miniz.c b/tools/quake3/common/miniz.c
new file mode 100644 (file)
index 0000000..50148dc
--- /dev/null
@@ -0,0 +1,4178 @@
+#include "miniz.h"
+
+#ifndef MINIZ_HEADER_FILE_ONLY
+
+typedef unsigned char mz_validate_uint16[sizeof(mz_uint16)==2 ? 1 : -1];
+typedef unsigned char mz_validate_uint32[sizeof(mz_uint32)==4 ? 1 : -1];
+typedef unsigned char mz_validate_uint64[sizeof(mz_uint64)==8 ? 1 : -1];
+
+#include <string.h>
+#include <assert.h>
+
+#define MZ_ASSERT(x) assert(x)
+
+#ifdef MINIZ_NO_MALLOC
+  #define MZ_MALLOC(x) NULL
+  #define MZ_FREE(x) (void)x, ((void)0)
+  #define MZ_REALLOC(p, x) NULL
+#else
+  #define MZ_MALLOC(x) malloc(x)
+  #define MZ_FREE(x) free(x)
+  #define MZ_REALLOC(p, x) realloc(p, x)
+#endif
+
+#define MZ_MAX(a,b) (((a)>(b))?(a):(b))
+#define MZ_MIN(a,b) (((a)<(b))?(a):(b))
+#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj))
+
+#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN
+  #define MZ_READ_LE16(p) *((const mz_uint16 *)(p))
+  #define MZ_READ_LE32(p) *((const mz_uint32 *)(p))
+#else
+  #define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U))
+  #define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U))
+#endif
+
+#ifdef _MSC_VER
+  #define MZ_FORCEINLINE __forceinline
+#elif defined(__GNUC__)
+  #define MZ_FORCEINLINE inline __attribute__((__always_inline__))
+#else
+  #define MZ_FORCEINLINE inline
+#endif
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+// ------------------- zlib-style API's
+
+mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len)
+{
+  mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); size_t block_len = buf_len % 5552;
+  if (!ptr) return MZ_ADLER32_INIT;
+  while (buf_len) {
+    for (i = 0; i + 7 < block_len; i += 8, ptr += 8) {
+      s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1;
+      s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1;
+    }
+    for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1;
+    s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552;
+  }
+  return (s2 << 16) + s1;
+}
+
+// Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/
+mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len)
+{
+  static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
+    0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c };
+  mz_uint32 crcu32 = (mz_uint32)crc;
+  if (!ptr) return MZ_CRC32_INIT;
+  crcu32 = ~crcu32; while (buf_len--) { mz_uint8 b = *ptr++; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; }
+  return ~crcu32;
+}
+
+void mz_free(void *p)
+{
+  MZ_FREE(p);
+}
+
+#ifndef MINIZ_NO_ZLIB_APIS
+
+static void *def_alloc_func(void *opaque, size_t items, size_t size) { (void)opaque, (void)items, (void)size; return MZ_MALLOC(items * size); }
+static void def_free_func(void *opaque, void *address) { (void)opaque, (void)address; MZ_FREE(address); }
+static void *def_realloc_func(void *opaque, void *address, size_t items, size_t size) { (void)opaque, (void)address, (void)items, (void)size; return MZ_REALLOC(address, items * size); }
+
+const char *mz_version(void)
+{
+  return MZ_VERSION;
+}
+
+int mz_deflateInit(mz_streamp pStream, int level)
+{
+  return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY);
+}
+
+int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy)
+{
+  tdefl_compressor *pComp;
+  mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy);
+
+  if (!pStream) return MZ_STREAM_ERROR;
+  if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))) return MZ_PARAM_ERROR;
+
+  pStream->data_type = 0;
+  pStream->adler = MZ_ADLER32_INIT;
+  pStream->msg = NULL;
+  pStream->reserved = 0;
+  pStream->total_in = 0;
+  pStream->total_out = 0;
+  if (!pStream->zalloc) pStream->zalloc = def_alloc_func;
+  if (!pStream->zfree) pStream->zfree = def_free_func;
+
+  pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor));
+  if (!pComp)
+    return MZ_MEM_ERROR;
+
+  pStream->state = (struct mz_internal_state *)pComp;
+
+  if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY)
+  {
+    mz_deflateEnd(pStream);
+    return MZ_PARAM_ERROR;
+  }
+
+  return MZ_OK;
+}
+
+int mz_deflateReset(mz_streamp pStream)
+{
+  if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree)) return MZ_STREAM_ERROR;
+  pStream->total_in = pStream->total_out = 0;
+  tdefl_init((tdefl_compressor*)pStream->state, NULL, NULL, ((tdefl_compressor*)pStream->state)->m_flags);
+  return MZ_OK;
+}
+
+int mz_deflate(mz_streamp pStream, int flush)
+{
+  size_t in_bytes, out_bytes;
+  mz_ulong orig_total_in, orig_total_out;
+  int mz_status = MZ_OK;
+
+  if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out)) return MZ_STREAM_ERROR;
+  if (!pStream->avail_out) return MZ_BUF_ERROR;
+
+  if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH;
+
+  if (((tdefl_compressor*)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE)
+    return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR;
+
+  orig_total_in = pStream->total_in; orig_total_out = pStream->total_out;
+  for ( ; ; )
+  {
+    tdefl_status defl_status;
+    in_bytes = pStream->avail_in; out_bytes = pStream->avail_out;
+
+    defl_status = tdefl_compress((tdefl_compressor*)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush);
+    pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes;
+    pStream->total_in += (mz_uint)in_bytes; pStream->adler = tdefl_get_adler32((tdefl_compressor*)pStream->state);
+
+    pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes;
+    pStream->total_out += (mz_uint)out_bytes;
+
+    if (defl_status < 0)
+    {
+      mz_status = MZ_STREAM_ERROR;
+      break;
+    }
+    else if (defl_status == TDEFL_STATUS_DONE)
+    {
+      mz_status = MZ_STREAM_END;
+      break;
+    }
+    else if (!pStream->avail_out)
+      break;
+    else if ((!pStream->avail_in) && (flush != MZ_FINISH))
+    {
+      if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out))
+        break;
+      return MZ_BUF_ERROR; // Can't make forward progress without some input.
+    }
+  }
+  return mz_status;
+}
+
+int mz_deflateEnd(mz_streamp pStream)
+{
+  if (!pStream) return MZ_STREAM_ERROR;
+  if (pStream->state)
+  {
+    pStream->zfree(pStream->opaque, pStream->state);
+    pStream->state = NULL;
+  }
+  return MZ_OK;
+}
+
+mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len)
+{
+  (void)pStream;
+  // This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.)
+  return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5);
+}
+
+int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level)
+{
+  int status;
+  mz_stream stream;
+  memset(&stream, 0, sizeof(stream));
+
+  // In case mz_ulong is 64-bits (argh I hate longs).
+  if ((source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR;
+
+  stream.next_in = pSource;
+  stream.avail_in = (mz_uint32)source_len;
+  stream.next_out = pDest;
+  stream.avail_out = (mz_uint32)*pDest_len;
+
+  status = mz_deflateInit(&stream, level);
+  if (status != MZ_OK) return status;
+
+  status = mz_deflate(&stream, MZ_FINISH);
+  if (status != MZ_STREAM_END)
+  {
+    mz_deflateEnd(&stream);
+    return (status == MZ_OK) ? MZ_BUF_ERROR : status;
+  }
+
+  *pDest_len = stream.total_out;
+  return mz_deflateEnd(&stream);
+}
+
+int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len)
+{
+  return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION);
+}
+
+mz_ulong mz_compressBound(mz_ulong source_len)
+{
+  return mz_deflateBound(NULL, source_len);
+}
+
+typedef struct
+{
+  tinfl_decompressor m_decomp;
+  mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; int m_window_bits;
+  mz_uint8 m_dict[TINFL_LZ_DICT_SIZE];
+  tinfl_status m_last_status;
+} inflate_state;
+
+int mz_inflateInit2(mz_streamp pStream, int window_bits)
+{
+  inflate_state *pDecomp;
+  if (!pStream) return MZ_STREAM_ERROR;
+  if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)) return MZ_PARAM_ERROR;
+
+  pStream->data_type = 0;
+  pStream->adler = 0;
+  pStream->msg = NULL;
+  pStream->total_in = 0;
+  pStream->total_out = 0;
+  pStream->reserved = 0;
+  if (!pStream->zalloc) pStream->zalloc = def_alloc_func;
+  if (!pStream->zfree) pStream->zfree = def_free_func;
+
+  pDecomp = (inflate_state*)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state));
+  if (!pDecomp) return MZ_MEM_ERROR;
+
+  pStream->state = (struct mz_internal_state *)pDecomp;
+
+  tinfl_init(&pDecomp->m_decomp);
+  pDecomp->m_dict_ofs = 0;
+  pDecomp->m_dict_avail = 0;
+  pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT;
+  pDecomp->m_first_call = 1;
+  pDecomp->m_has_flushed = 0;
+  pDecomp->m_window_bits = window_bits;
+
+  return MZ_OK;
+}
+
+int mz_inflateInit(mz_streamp pStream)
+{
+   return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS);
+}
+
+int mz_inflate(mz_streamp pStream, int flush)
+{
+  inflate_state* pState;
+  mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32;
+  size_t in_bytes, out_bytes, orig_avail_in;
+  tinfl_status status;
+
+  if ((!pStream) || (!pStream->state)) return MZ_STREAM_ERROR;
+  if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH;
+  if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) return MZ_STREAM_ERROR;
+
+  pState = (inflate_state*)pStream->state;
+  if (pState->m_window_bits > 0) decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER;
+  orig_avail_in = pStream->avail_in;
+
+  first_call = pState->m_first_call; pState->m_first_call = 0;
+  if (pState->m_last_status < 0) return MZ_DATA_ERROR;
+
+  if (pState->m_has_flushed && (flush != MZ_FINISH)) return MZ_STREAM_ERROR;
+  pState->m_has_flushed |= (flush == MZ_FINISH);
+
+  if ((flush == MZ_FINISH) && (first_call))
+  {
+    // MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file.
+    decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF;
+    in_bytes = pStream->avail_in; out_bytes = pStream->avail_out;
+    status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags);
+    pState->m_last_status = status;
+    pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; pStream->total_in += (mz_uint)in_bytes;
+    pStream->adler = tinfl_get_adler32(&pState->m_decomp);
+    pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; pStream->total_out += (mz_uint)out_bytes;
+
+    if (status < 0)
+      return MZ_DATA_ERROR;
+    else if (status != TINFL_STATUS_DONE)
+    {
+      pState->m_last_status = TINFL_STATUS_FAILED;
+      return MZ_BUF_ERROR;
+    }
+    return MZ_STREAM_END;
+  }
+  // flush != MZ_FINISH then we must assume there's more input.
+  if (flush != MZ_FINISH) decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT;
+
+  if (pState->m_dict_avail)
+  {
+    n = MZ_MIN(pState->m_dict_avail, pStream->avail_out);
+    memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n);
+    pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n;
+    pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1);
+    return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK;
+  }
+
+  for ( ; ; )
+  {
+    in_bytes = pStream->avail_in;
+    out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs;
+
+    status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags);
+    pState->m_last_status = status;
+
+    pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes;
+    pStream->total_in += (mz_uint)in_bytes; pStream->adler = tinfl_get_adler32(&pState->m_decomp);
+
+    pState->m_dict_avail = (mz_uint)out_bytes;
+
+    n = MZ_MIN(pState->m_dict_avail, pStream->avail_out);
+    memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n);
+    pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n;
+    pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1);
+
+    if (status < 0)
+       return MZ_DATA_ERROR; // Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well).
+    else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in))
+      return MZ_BUF_ERROR; // Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH.
+    else if (flush == MZ_FINISH)
+    {
+       // The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH.
+       if (status == TINFL_STATUS_DONE)
+          return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END;
+       // status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong.
+       else if (!pStream->avail_out)
+          return MZ_BUF_ERROR;
+    }
+    else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail))
+      break;
+  }
+
+  return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK;
+}
+
+int mz_inflateEnd(mz_streamp pStream)
+{
+  if (!pStream)
+    return MZ_STREAM_ERROR;
+  if (pStream->state)
+  {
+    pStream->zfree(pStream->opaque, pStream->state);
+    pStream->state = NULL;
+  }
+  return MZ_OK;
+}
+
+int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len)
+{
+  mz_stream stream;
+  int status;
+  memset(&stream, 0, sizeof(stream));
+
+  // In case mz_ulong is 64-bits (argh I hate longs).
+  if ((source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR;
+
+  stream.next_in = pSource;
+  stream.avail_in = (mz_uint32)source_len;
+  stream.next_out = pDest;
+  stream.avail_out = (mz_uint32)*pDest_len;
+
+  status = mz_inflateInit(&stream);
+  if (status != MZ_OK)
+    return status;
+
+  status = mz_inflate(&stream, MZ_FINISH);
+  if (status != MZ_STREAM_END)
+  {
+    mz_inflateEnd(&stream);
+    return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status;
+  }
+  *pDest_len = stream.total_out;
+
+  return mz_inflateEnd(&stream);
+}
+
+const char *mz_error(int err)
+{
+  static struct { int m_err; const char *m_pDesc; } s_error_descs[] =
+  {
+    { MZ_OK, "" }, { MZ_STREAM_END, "stream end" }, { MZ_NEED_DICT, "need dictionary" }, { MZ_ERRNO, "file error" }, { MZ_STREAM_ERROR, "stream error" },
+    { MZ_DATA_ERROR, "data error" }, { MZ_MEM_ERROR, "out of memory" }, { MZ_BUF_ERROR, "buf error" }, { MZ_VERSION_ERROR, "version error" }, { MZ_PARAM_ERROR, "parameter error" }
+  };
+  mz_uint i; for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) if (s_error_descs[i].m_err == err) return s_error_descs[i].m_pDesc;
+  return NULL;
+}
+
+#endif //MINIZ_NO_ZLIB_APIS
+
+// ------------------- Low-level Decompression (completely independent from all compression API's)
+
+#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l)
+#define TINFL_MEMSET(p, c, l) memset(p, c, l)
+
+#define TINFL_CR_BEGIN switch(r->m_state) { case 0:
+#define TINFL_CR_RETURN(state_index, result) do { status = result; r->m_state = state_index; goto common_exit; case state_index:; } MZ_MACRO_END
+#define TINFL_CR_RETURN_FOREVER(state_index, result) do { for ( ; ; ) { TINFL_CR_RETURN(state_index, result); } } MZ_MACRO_END
+#define TINFL_CR_FINISH }
+
+// TODO: If the caller has indicated that there's no more input, and we attempt to read beyond the input buf, then something is wrong with the input because the inflator never
+// reads ahead more than it needs to. Currently TINFL_GET_BYTE() pads the end of the stream with 0's in this scenario.
+#define TINFL_GET_BYTE(state_index, c) do { \
+  if (pIn_buf_cur >= pIn_buf_end) { \
+    for ( ; ; ) { \
+      if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { \
+        TINFL_CR_RETURN(state_index, TINFL_STATUS_NEEDS_MORE_INPUT); \
+        if (pIn_buf_cur < pIn_buf_end) { \
+          c = *pIn_buf_cur++; \
+          break; \
+        } \
+      } else { \
+        c = 0; \
+        break; \
+      } \
+    } \
+  } else c = *pIn_buf_cur++; } MZ_MACRO_END
+
+#define TINFL_NEED_BITS(state_index, n) do { mz_uint c; TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; } while (num_bits < (mz_uint)(n))
+#define TINFL_SKIP_BITS(state_index, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END
+#define TINFL_GET_BITS(state_index, b, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } b = bit_buf & ((1 << (n)) - 1); bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END
+
+// TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2.
+// It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a
+// Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the
+// bit buffer contains >=15 bits (deflate's max. Huffman code size).
+#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \
+  do { \
+    temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \
+    if (temp >= 0) { \
+      code_len = temp >> 9; \
+      if ((code_len) && (num_bits >= code_len)) \
+      break; \
+    } else if (num_bits > TINFL_FAST_LOOKUP_BITS) { \
+       code_len = TINFL_FAST_LOOKUP_BITS; \
+       do { \
+          temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \
+       } while ((temp < 0) && (num_bits >= (code_len + 1))); if (temp >= 0) break; \
+    } TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; \
+  } while (num_bits < 15);
+
+// TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read
+// beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully
+// decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32.
+// The slow path is only executed at the very end of the input buffer.
+#define TINFL_HUFF_DECODE(state_index, sym, pHuff) do { \
+  int temp; mz_uint code_len, c; \
+  if (num_bits < 15) { \
+    if ((pIn_buf_end - pIn_buf_cur) < 2) { \
+       TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \
+    } else { \
+       bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); pIn_buf_cur += 2; num_bits += 16; \
+    } \
+  } \
+  if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \
+    code_len = temp >> 9, temp &= 511; \
+  else { \
+    code_len = TINFL_FAST_LOOKUP_BITS; do { temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; } while (temp < 0); \
+  } sym = temp; bit_buf >>= code_len; num_bits -= code_len; } MZ_MACRO_END
+
+tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags)
+{
+  static const int s_length_base[31] = { 3,4,5,6,7,8,9,10,11,13, 15,17,19,23,27,31,35,43,51,59, 67,83,99,115,131,163,195,227,258,0,0 };
+  static const int s_length_extra[31]= { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 };
+  static const int s_dist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0};
+  static const int s_dist_extra[32] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13};
+  static const mz_uint8 s_length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 };
+  static const int s_min_table_sizes[3] = { 257, 1, 4 };
+
+  tinfl_status status = TINFL_STATUS_FAILED; mz_uint32 num_bits, dist, counter, num_extra; tinfl_bit_buf_t bit_buf;
+  const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size;
+  mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size;
+  size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start;
+
+  // Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter).
+  if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) { *pIn_buf_size = *pOut_buf_size = 0; return TINFL_STATUS_BAD_PARAM; }
+
+  num_bits = r->m_num_bits; bit_buf = r->m_bit_buf; dist = r->m_dist; counter = r->m_counter; num_extra = r->m_num_extra; dist_from_out_buf_start = r->m_dist_from_out_buf_start;
+  TINFL_CR_BEGIN
+
+  bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; r->m_z_adler32 = r->m_check_adler32 = 1;
+  if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER)
+  {
+    TINFL_GET_BYTE(1, r->m_zhdr0); TINFL_GET_BYTE(2, r->m_zhdr1);
+    counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8));
+    if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4)))));
+    if (counter) { TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); }
+  }
+
+  do
+  {
+    TINFL_GET_BITS(3, r->m_final, 3); r->m_type = r->m_final >> 1;
+    if (r->m_type == 0)
+    {
+      TINFL_SKIP_BITS(5, num_bits & 7);
+      for (counter = 0; counter < 4; ++counter) { if (num_bits) TINFL_GET_BITS(6, r->m_raw_header[counter], 8); else TINFL_GET_BYTE(7, r->m_raw_header[counter]); }
+      if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) { TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); }
+      while ((counter) && (num_bits))
+      {
+        TINFL_GET_BITS(51, dist, 8);
+        while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); }
+        *pOut_buf_cur++ = (mz_uint8)dist;
+        counter--;
+      }
+      while (counter)
+      {
+        size_t n; while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); }
+        while (pIn_buf_cur >= pIn_buf_end)
+        {
+          if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT)
+          {
+            TINFL_CR_RETURN(38, TINFL_STATUS_NEEDS_MORE_INPUT);
+          }
+          else
+          {
+            TINFL_CR_RETURN_FOREVER(40, TINFL_STATUS_FAILED);
+          }
+        }
+        n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter);
+        TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); pIn_buf_cur += n; pOut_buf_cur += n; counter -= (mz_uint)n;
+      }
+    }
+    else if (r->m_type == 3)
+    {
+      TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED);
+    }
+    else
+    {
+      if (r->m_type == 1)
+      {
+        mz_uint8 *p = r->m_tables[0].m_code_size; mz_uint i;
+        r->m_table_sizes[0] = 288; r->m_table_sizes[1] = 32; TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32);
+        for ( i = 0; i <= 143; ++i) *p++ = 8; for ( ; i <= 255; ++i) *p++ = 9; for ( ; i <= 279; ++i) *p++ = 7; for ( ; i <= 287; ++i) *p++ = 8;
+      }
+      else
+      {
+        for (counter = 0; counter < 3; counter++) { TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); r->m_table_sizes[counter] += s_min_table_sizes[counter]; }
+        MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); for (counter = 0; counter < r->m_table_sizes[2]; counter++) { mz_uint s; TINFL_GET_BITS(14, s, 3); r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; }
+        r->m_table_sizes[2] = 19;
+      }
+      for ( ; (int)r->m_type >= 0; r->m_type--)
+      {
+        int tree_next, tree_cur; tinfl_huff_table *pTable;
+        mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; pTable = &r->m_tables[r->m_type]; MZ_CLEAR_OBJ(total_syms); MZ_CLEAR_OBJ(pTable->m_look_up); MZ_CLEAR_OBJ(pTable->m_tree);
+        for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) total_syms[pTable->m_code_size[i]]++;
+        used_syms = 0, total = 0; next_code[0] = next_code[1] = 0;
+        for (i = 1; i <= 15; ++i) { used_syms += total_syms[i]; next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); }
+        if ((65536 != total) && (used_syms > 1))
+        {
+          TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED);
+        }
+        for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index)
+        {
+          mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; if (!code_size) continue;
+          cur_code = next_code[code_size]++; for (l = code_size; l > 0; l--, cur_code >>= 1) rev_code = (rev_code << 1) | (cur_code & 1);
+          if (code_size <= TINFL_FAST_LOOKUP_BITS) { mz_int16 k = (mz_int16)((code_size << 9) | sym_index); while (rev_code < TINFL_FAST_LOOKUP_SIZE) { pTable->m_look_up[rev_code] = k; rev_code += (1 << code_size); } continue; }
+          if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) { pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; }
+          rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1);
+          for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--)
+          {
+            tree_cur -= ((rev_code >>= 1) & 1);
+            if (!pTable->m_tree[-tree_cur - 1]) { pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } else tree_cur = pTable->m_tree[-tree_cur - 1];
+          }
+          tree_cur -= ((rev_code >>= 1) & 1); pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index;
+        }
+        if (r->m_type == 2)
+        {
+          for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]); )
+          {
+            mz_uint s; TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); if (dist < 16) { r->m_len_codes[counter++] = (mz_uint8)dist; continue; }
+            if ((dist == 16) && (!counter))
+            {
+              TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED);
+            }
+            num_extra = "\02\03\07"[dist - 16]; TINFL_GET_BITS(18, s, num_extra); s += "\03\03\013"[dist - 16];
+            TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); counter += s;
+          }
+          if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter)
+          {
+            TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED);
+          }
+          TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]);
+        }
+      }
+      for ( ; ; )
+      {
+        mz_uint8 *pSrc;
+        for ( ; ; )
+        {
+          if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2))
+          {
+            TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]);
+            if (counter >= 256)
+              break;
+            while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); }
+            *pOut_buf_cur++ = (mz_uint8)counter;
+          }
+          else
+          {
+            int sym2; mz_uint code_len;
+#if TINFL_USE_64BIT_BITBUF
+            if (num_bits < 30) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); pIn_buf_cur += 4; num_bits += 32; }
+#else
+            if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; }
+#endif
+            if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0)
+              code_len = sym2 >> 9;
+            else
+            {
+              code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0);
+            }
+            counter = sym2; bit_buf >>= code_len; num_bits -= code_len;
+            if (counter & 256)
+              break;
+
+#if !TINFL_USE_64BIT_BITBUF
+            if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; }
+#endif
+            if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0)
+              code_len = sym2 >> 9;
+            else
+            {
+              code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0);
+            }
+            bit_buf >>= code_len; num_bits -= code_len;
+
+            pOut_buf_cur[0] = (mz_uint8)counter;
+            if (sym2 & 256)
+            {
+              pOut_buf_cur++;
+              counter = sym2;
+              break;
+            }
+            pOut_buf_cur[1] = (mz_uint8)sym2;
+            pOut_buf_cur += 2;
+          }
+        }
+        if ((counter &= 511) == 256) break;
+
+        num_extra = s_length_extra[counter - 257]; counter = s_length_base[counter - 257];
+        if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(25, extra_bits, num_extra); counter += extra_bits; }
+
+        TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]);
+        num_extra = s_dist_extra[dist]; dist = s_dist_base[dist];
+        if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(27, extra_bits, num_extra); dist += extra_bits; }
+
+        dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start;
+        if ((dist > dist_from_out_buf_start) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))
+        {
+          TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED);
+        }
+
+        pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask);
+
+        if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end)
+        {
+          while (counter--)
+          {
+            while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); }
+            *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask];
+          }
+          continue;
+        }
+#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES
+        else if ((counter >= 9) && (counter <= dist))
+        {
+          const mz_uint8 *pSrc_end = pSrc + (counter & ~7);
+          do
+          {
+            ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0];
+            ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1];
+            pOut_buf_cur += 8;
+          } while ((pSrc += 8) < pSrc_end);
+          if ((counter &= 7) < 3)
+          {
+            if (counter)
+            {
+              pOut_buf_cur[0] = pSrc[0];
+              if (counter > 1)
+                pOut_buf_cur[1] = pSrc[1];
+              pOut_buf_cur += counter;
+            }
+            continue;
+          }
+        }
+#endif
+        do
+        {
+          pOut_buf_cur[0] = pSrc[0];
+          pOut_buf_cur[1] = pSrc[1];
+          pOut_buf_cur[2] = pSrc[2];
+          pOut_buf_cur += 3; pSrc += 3;
+        } while ((int)(counter -= 3) > 2);
+        if ((int)counter > 0)
+        {
+          pOut_buf_cur[0] = pSrc[0];
+          if ((int)counter > 1)
+            pOut_buf_cur[1] = pSrc[1];
+          pOut_buf_cur += counter;
+        }
+      }
+    }
+  } while (!(r->m_final & 1));
+  if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER)
+  {
+    TINFL_SKIP_BITS(32, num_bits & 7); for (counter = 0; counter < 4; ++counter) { mz_uint s; if (num_bits) TINFL_GET_BITS(41, s, 8); else TINFL_GET_BYTE(42, s); r->m_z_adler32 = (r->m_z_adler32 << 8) | s; }
+  }
+  TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE);
+  TINFL_CR_FINISH
+
+common_exit:
+  r->m_num_bits = num_bits; r->m_bit_buf = bit_buf; r->m_dist = dist; r->m_counter = counter; r->m_num_extra = num_extra; r->m_dist_from_out_buf_start = dist_from_out_buf_start;
+  *pIn_buf_size = pIn_buf_cur - pIn_buf_next; *pOut_buf_size = pOut_buf_cur - pOut_buf_next;
+  if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0))
+  {
+    const mz_uint8 *ptr = pOut_buf_next; size_t buf_len = *pOut_buf_size;
+    mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; size_t block_len = buf_len % 5552;
+    while (buf_len)
+    {
+      for (i = 0; i + 7 < block_len; i += 8, ptr += 8)
+      {
+        s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1;
+        s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1;
+      }
+      for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1;
+      s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552;
+    }
+    r->m_check_adler32 = (s2 << 16) + s1; if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) status = TINFL_STATUS_ADLER32_MISMATCH;
+  }
+  return status;
+}
+
+// Higher level helper functions.
+void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags)
+{
+  tinfl_decompressor decomp; void *pBuf = NULL, *pNew_buf; size_t src_buf_ofs = 0, out_buf_capacity = 0;
+  *pOut_len = 0;
+  tinfl_init(&decomp);
+  for ( ; ; )
+  {
+    size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity;
+    tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8*)pBuf, pBuf ? (mz_uint8*)pBuf + *pOut_len : NULL, &dst_buf_size,
+      (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
+    if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT))
+    {
+      MZ_FREE(pBuf); *pOut_len = 0; return NULL;
+    }
+    src_buf_ofs += src_buf_size;
+    *pOut_len += dst_buf_size;
+    if (status == TINFL_STATUS_DONE) break;
+    new_out_buf_capacity = out_buf_capacity * 2; if (new_out_buf_capacity < 128) new_out_buf_capacity = 128;
+    pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity);
+    if (!pNew_buf)
+    {
+      MZ_FREE(pBuf); *pOut_len = 0; return NULL;
+    }
+    pBuf = pNew_buf; out_buf_capacity = new_out_buf_capacity;
+  }
+  return pBuf;
+}
+
+size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags)
+{
+  tinfl_decompressor decomp; tinfl_status status; tinfl_init(&decomp);
+  status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf, &src_buf_len, (mz_uint8*)pOut_buf, (mz_uint8*)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
+  return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len;
+}
+
+int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags)
+{
+  int result = 0;
+  tinfl_decompressor decomp;
+  mz_uint8 *pDict = (mz_uint8*)MZ_MALLOC(TINFL_LZ_DICT_SIZE); size_t in_buf_ofs = 0, dict_ofs = 0;
+  if (!pDict)
+    return TINFL_STATUS_FAILED;
+  tinfl_init(&decomp);
+  for ( ; ; )
+  {
+    size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs;
+    tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size,
+      (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)));
+    in_buf_ofs += in_buf_size;
+    if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user)))
+      break;
+    if (status != TINFL_STATUS_HAS_MORE_OUTPUT)
+    {
+      result = (status == TINFL_STATUS_DONE);
+      break;
+    }
+    dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1);
+  }
+  MZ_FREE(pDict);
+  *pIn_buf_size = in_buf_ofs;
+  return result;
+}
+
+// ------------------- Low-level Compression (independent from all decompression API's)
+
+// Purposely making these tables static for faster init and thread safety.
+static const mz_uint16 s_tdefl_len_sym[256] = {
+  257,258,259,260,261,262,263,264,265,265,266,266,267,267,268,268,269,269,269,269,270,270,270,270,271,271,271,271,272,272,272,272,
+  273,273,273,273,273,273,273,273,274,274,274,274,274,274,274,274,275,275,275,275,275,275,275,275,276,276,276,276,276,276,276,276,
+  277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,
+  279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,
+  281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,
+  282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,
+  283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,
+  284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,285 };
+
+static const mz_uint8 s_tdefl_len_extra[256] = {
+  0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
+  4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
+  5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
+  5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,0 };
+
+static const mz_uint8 s_tdefl_small_dist_sym[512] = {
+  0,1,2,3,4,4,5,5,6,6,6,6,7,7,7,7,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,11,11,11,11,11,
+  11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13,
+  13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,14,14,14,14,14,14,14,14,14,14,14,14,
+  14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
+  14,14,14,14,14,14,14,14,14,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
+  15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,16,16,16,16,16,16,16,16,16,16,16,16,16,
+  16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+  16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+  16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
+  17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
+  17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
+  17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17 };
+
+static const mz_uint8 s_tdefl_small_dist_extra[512] = {
+  0,0,0,0,1,1,1,1,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,
+  5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
+  6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
+  6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+  7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+  7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+  7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+  7,7,7,7,7,7,7,7 };
+
+static const mz_uint8 s_tdefl_large_dist_sym[128] = {
+  0,0,18,19,20,20,21,21,22,22,22,22,23,23,23,23,24,24,24,24,24,24,24,24,25,25,25,25,25,25,25,25,26,26,26,26,26,26,26,26,26,26,26,26,
+  26,26,26,26,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,
+  28,28,28,28,28,28,28,28,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29 };
+
+static const mz_uint8 s_tdefl_large_dist_extra[128] = {
+  0,0,8,8,9,9,9,9,10,10,10,10,10,10,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,
+  12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
+  13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13 };
+
+// Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values.
+typedef struct { mz_uint16 m_key, m_sym_index; } tdefl_sym_freq;
+static tdefl_sym_freq* tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq* pSyms0, tdefl_sym_freq* pSyms1)
+{
+  mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; tdefl_sym_freq* pCur_syms = pSyms0, *pNew_syms = pSyms1; MZ_CLEAR_OBJ(hist);
+  for (i = 0; i < num_syms; i++) { mz_uint freq = pSyms0[i].m_key; hist[freq & 0xFF]++; hist[256 + ((freq >> 8) & 0xFF)]++; }
+  while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) total_passes--;
+  for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8)
+  {
+    const mz_uint32* pHist = &hist[pass << 8];
+    mz_uint offsets[256], cur_ofs = 0;
+    for (i = 0; i < 256; i++) { offsets[i] = cur_ofs; cur_ofs += pHist[i]; }
+    for (i = 0; i < num_syms; i++) pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i];
+    { tdefl_sym_freq* t = pCur_syms; pCur_syms = pNew_syms; pNew_syms = t; }
+  }
+  return pCur_syms;
+}
+
+// tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996.
+static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n)
+{
+  int root, leaf, next, avbl, used, dpth;
+  if (n==0) return; else if (n==1) { A[0].m_key = 1; return; }
+  A[0].m_key += A[1].m_key; root = 0; leaf = 2;
+  for (next=1; next < n-1; next++)
+  {
+    if (leaf>=n || A[root].m_key<A[leaf].m_key) { A[next].m_key = A[root].m_key; A[root++].m_key = (mz_uint16)next; } else A[next].m_key = A[leaf++].m_key;
+    if (leaf>=n || (root<next && A[root].m_key<A[leaf].m_key)) { A[next].m_key = (mz_uint16)(A[next].m_key + A[root].m_key); A[root++].m_key = (mz_uint16)next; } else A[next].m_key = (mz_uint16)(A[next].m_key + A[leaf++].m_key);
+  }
+  A[n-2].m_key = 0; for (next=n-3; next>=0; next--) A[next].m_key = A[A[next].m_key].m_key+1;
+  avbl = 1; used = dpth = 0; root = n-2; next = n-1;
+  while (avbl>0)
+  {
+    while (root>=0 && (int)A[root].m_key==dpth) { used++; root--; }
+    while (avbl>used) { A[next--].m_key = (mz_uint16)(dpth); avbl--; }
+    avbl = 2*used; dpth++; used = 0;
+  }
+}
+
+// Limits canonical Huffman code table's max code size.
+enum { TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 };
+static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size)
+{
+  int i; mz_uint32 total = 0; if (code_list_len <= 1) return;
+  for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) pNum_codes[max_code_size] += pNum_codes[i];
+  for (i = max_code_size; i > 0; i--) total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i));
+  while (total != (1UL << max_code_size))
+  {
+    pNum_codes[max_code_size]--;
+    for (i = max_code_size - 1; i > 0; i--) if (pNum_codes[i]) { pNum_codes[i]--; pNum_codes[i + 1] += 2; break; }
+    total--;
+  }
+}
+
+static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table)
+{
+  int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; MZ_CLEAR_OBJ(num_codes);
+  if (static_table)
+  {
+    for (i = 0; i < table_len; i++) num_codes[d->m_huff_code_sizes[table_num][i]]++;
+  }
+  else
+  {
+    tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms;
+    int num_used_syms = 0;
+    const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0];
+    for (i = 0; i < table_len; i++) if (pSym_count[i]) { syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; syms0[num_used_syms++].m_sym_index = (mz_uint16)i; }
+
+    pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); tdefl_calculate_minimum_redundancy(pSyms, num_used_syms);
+
+    for (i = 0; i < num_used_syms; i++) num_codes[pSyms[i].m_key]++;
+
+    tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit);
+
+    MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); MZ_CLEAR_OBJ(d->m_huff_codes[table_num]);
+    for (i = 1, j = num_used_syms; i <= code_size_limit; i++)
+      for (l = num_codes[i]; l > 0; l--) d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i);
+  }
+
+  next_code[1] = 0; for (j = 0, i = 2; i <= code_size_limit; i++) next_code[i] = j = ((j + num_codes[i - 1]) << 1);
+
+  for (i = 0; i < table_len; i++)
+  {
+    mz_uint rev_code = 0, code, code_size; if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) continue;
+    code = next_code[code_size]++; for (l = code_size; l > 0; l--, code >>= 1) rev_code = (rev_code << 1) | (code & 1);
+    d->m_huff_codes[table_num][i] = (mz_uint16)rev_code;
+  }
+}
+
+#define TDEFL_PUT_BITS(b, l) do { \
+  mz_uint bits = b; mz_uint len = l; MZ_ASSERT(bits <= ((1U << len) - 1U)); \
+  d->m_bit_buffer |= (bits << d->m_bits_in); d->m_bits_in += len; \
+  while (d->m_bits_in >= 8) { \
+    if (d->m_pOutput_buf < d->m_pOutput_buf_end) \
+      *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \
+      d->m_bit_buffer >>= 8; \
+      d->m_bits_in -= 8; \
+  } \
+} MZ_MACRO_END
+
+#define TDEFL_RLE_PREV_CODE_SIZE() { if (rle_repeat_count) { \
+  if (rle_repeat_count < 3) { \
+    d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \
+    while (rle_repeat_count--) packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \
+  } else { \
+    d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); packed_code_sizes[num_packed_code_sizes++] = 16; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3); \
+} rle_repeat_count = 0; } }
+
+#define TDEFL_RLE_ZERO_CODE_SIZE() { if (rle_z_count) { \
+  if (rle_z_count < 3) { \
+    d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); while (rle_z_count--) packed_code_sizes[num_packed_code_sizes++] = 0; \
+  } else if (rle_z_count <= 10) { \
+    d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); packed_code_sizes[num_packed_code_sizes++] = 17; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3); \
+  } else { \
+    d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); packed_code_sizes[num_packed_code_sizes++] = 18; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \
+} rle_z_count = 0; } }
+
+static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
+
+static void tdefl_start_dynamic_block(tdefl_compressor *d)
+{
+  int num_lit_codes, num_dist_codes, num_bit_lengths; mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index;
+  mz_uint8 code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF;
+
+  d->m_huff_count[0][256] = 1;
+
+  tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE);
+  tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE);
+
+  for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) if (d->m_huff_code_sizes[0][num_lit_codes - 1]) break;
+  for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) if (d->m_huff_code_sizes[1][num_dist_codes - 1]) break;
+
+  memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes);
+  memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes);
+  total_code_sizes_to_pack = num_lit_codes + num_dist_codes; num_packed_code_sizes = 0; rle_z_count = 0; rle_repeat_count = 0;
+
+  memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2);
+  for (i = 0; i < total_code_sizes_to_pack; i++)
+  {
+    mz_uint8 code_size = code_sizes_to_pack[i];
+    if (!code_size)
+    {
+      TDEFL_RLE_PREV_CODE_SIZE();
+      if (++rle_z_count == 138) { TDEFL_RLE_ZERO_CODE_SIZE(); }
+    }
+    else
+    {
+      TDEFL_RLE_ZERO_CODE_SIZE();
+      if (code_size != prev_code_size)
+      {
+        TDEFL_RLE_PREV_CODE_SIZE();
+        d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1); packed_code_sizes[num_packed_code_sizes++] = code_size;
+      }
+      else if (++rle_repeat_count == 6)
+      {
+        TDEFL_RLE_PREV_CODE_SIZE();
+      }
+    }
+    prev_code_size = code_size;
+  }
+  if (rle_repeat_count) { TDEFL_RLE_PREV_CODE_SIZE(); } else { TDEFL_RLE_ZERO_CODE_SIZE(); }
+
+  tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE);
+
+  TDEFL_PUT_BITS(2, 2);
+
+  TDEFL_PUT_BITS(num_lit_codes - 257, 5);
+  TDEFL_PUT_BITS(num_dist_codes - 1, 5);
+
+  for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) break;
+  num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); TDEFL_PUT_BITS(num_bit_lengths - 4, 4);
+  for (i = 0; (int)i < num_bit_lengths; i++) TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3);
+
+  for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes; )
+  {
+    mz_uint code = packed_code_sizes[packed_code_sizes_index++]; MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2);
+    TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]);
+    if (code >= 16) TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]);
+  }
+}
+
+static void tdefl_start_static_block(tdefl_compressor *d)
+{
+  mz_uint i;
+  mz_uint8 *p = &d->m_huff_code_sizes[0][0];
+
+  for (i = 0; i <= 143; ++i) *p++ = 8;
+  for ( ; i <= 255; ++i) *p++ = 9;
+  for ( ; i <= 279; ++i) *p++ = 7;
+  for ( ; i <= 287; ++i) *p++ = 8;
+
+  memset(d->m_huff_code_sizes[1], 5, 32);
+
+  tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE);
+  tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE);
+
+  TDEFL_PUT_BITS(1, 2);
+}
+
+static const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF };
+
+#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS
+static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d)
+{
+  mz_uint flags;
+  mz_uint8 *pLZ_codes;
+  mz_uint8 *pOutput_buf = d->m_pOutput_buf;
+  mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf;
+  mz_uint64 bit_buffer = d->m_bit_buffer;
+  mz_uint bits_in = d->m_bits_in;
+
+#define TDEFL_PUT_BITS_FAST(b, l) { bit_buffer |= (((mz_uint64)(b)) << bits_in); bits_in += (l); }
+
+  flags = 1;
+  for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1)
+  {
+    if (flags == 1)
+      flags = *pLZ_codes++ | 0x100;
+
+    if (flags & 1)
+    {
+      mz_uint s0, s1, n0, n1, sym, num_extra_bits;
+      mz_uint match_len = pLZ_codes[0], match_dist = *(const mz_uint16 *)(pLZ_codes + 1); pLZ_codes += 3;
+
+      MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]);
+      TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]);
+      TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]);
+
+      // This sequence coaxes MSVC into using cmov's vs. jmp's.
+      s0 = s_tdefl_small_dist_sym[match_dist & 511];
+      n0 = s_tdefl_small_dist_extra[match_dist & 511];
+      s1 = s_tdefl_large_dist_sym[match_dist >> 8];
+      n1 = s_tdefl_large_dist_extra[match_dist >> 8];
+      sym = (match_dist < 512) ? s0 : s1;
+      num_extra_bits = (match_dist < 512) ? n0 : n1;
+
+      MZ_ASSERT(d->m_huff_code_sizes[1][sym]);
+      TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]);
+      TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits);
+    }
+    else
+    {
+      mz_uint lit = *pLZ_codes++;
+      MZ_ASSERT(d->m_huff_code_sizes[0][lit]);
+      TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]);
+
+      if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end))
+      {
+        flags >>= 1;
+        lit = *pLZ_codes++;
+        MZ_ASSERT(d->m_huff_code_sizes[0][lit]);
+        TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]);
+
+        if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end))
+        {
+          flags >>= 1;
+          lit = *pLZ_codes++;
+          MZ_ASSERT(d->m_huff_code_sizes[0][lit]);
+          TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]);
+        }
+      }
+    }
+
+    if (pOutput_buf >= d->m_pOutput_buf_end)
+      return MZ_FALSE;
+
+    *(mz_uint64*)pOutput_buf = bit_buffer;
+    pOutput_buf += (bits_in >> 3);
+    bit_buffer >>= (bits_in & ~7);
+    bits_in &= 7;
+  }
+
+#undef TDEFL_PUT_BITS_FAST
+
+  d->m_pOutput_buf = pOutput_buf;
+  d->m_bits_in = 0;
+  d->m_bit_buffer = 0;
+
+  while (bits_in)
+  {
+    mz_uint32 n = MZ_MIN(bits_in, 16);
+    TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n);
+    bit_buffer >>= n;
+    bits_in -= n;
+  }
+
+  TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]);
+
+  return (d->m_pOutput_buf < d->m_pOutput_buf_end);
+}
+#else
+static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d)
+{
+  mz_uint flags;
+  mz_uint8 *pLZ_codes;
+
+  flags = 1;
+  for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1)
+  {
+    if (flags == 1)
+      flags = *pLZ_codes++ | 0x100;
+    if (flags & 1)
+    {
+      mz_uint sym, num_extra_bits;
+      mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); pLZ_codes += 3;
+
+      MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]);
+      TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]);
+      TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]);
+
+      if (match_dist < 512)
+      {
+        sym = s_tdefl_small_dist_sym[match_dist]; num_extra_bits = s_tdefl_small_dist_extra[match_dist];
+      }
+      else
+      {
+        sym = s_tdefl_large_dist_sym[match_dist >> 8]; num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8];
+      }
+      MZ_ASSERT(d->m_huff_code_sizes[1][sym]);
+      TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]);
+      TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits);
+    }
+    else
+    {
+      mz_uint lit = *pLZ_codes++;
+      MZ_ASSERT(d->m_huff_code_sizes[0][lit]);
+      TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]);
+    }
+  }
+
+  TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]);
+
+  return (d->m_pOutput_buf < d->m_pOutput_buf_end);
+}
+#endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS
+
+static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block)
+{
+  if (static_block)
+    tdefl_start_static_block(d);
+  else
+    tdefl_start_dynamic_block(d);
+  return tdefl_compress_lz_codes(d);
+}
+
+static int tdefl_flush_block(tdefl_compressor *d, int flush)
+{
+  mz_uint saved_bit_buf, saved_bits_in;
+  mz_uint8 *pSaved_output_buf;
+  mz_bool comp_block_succeeded = MZ_FALSE;
+  int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size;
+  mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) : d->m_output_buf;
+
+  d->m_pOutput_buf = pOutput_buf_start;
+  d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16;
+
+  MZ_ASSERT(!d->m_output_flush_remaining);
+  d->m_output_flush_ofs = 0;
+  d->m_output_flush_remaining = 0;
+
+  *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left);
+  d->m_pLZ_code_buf -= (d->m_num_flags_left == 8);
+
+  if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index))
+  {
+    TDEFL_PUT_BITS(0x78, 8); TDEFL_PUT_BITS(0x01, 8);
+  }
+
+  TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1);
+
+  pSaved_output_buf = d->m_pOutput_buf; saved_bit_buf = d->m_bit_buffer; saved_bits_in = d->m_bits_in;
+
+  if (!use_raw_block)
+    comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48));
+
+  // If the block gets expanded, forget the current contents of the output buffer and send a raw block instead.
+  if ( ((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) &&
+       ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size) )
+  {
+    mz_uint i; d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in;
+    TDEFL_PUT_BITS(0, 2);
+    if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); }
+    for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF)
+    {
+      TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16);
+    }
+    for (i = 0; i < d->m_total_lz_bytes; ++i)
+    {
+      TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8);
+    }
+  }
+  // Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes.
+  else if (!comp_block_succeeded)
+  {
+    d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in;
+    tdefl_compress_block(d, MZ_TRUE);
+  }
+
+  if (flush)
+  {
+    if (flush == TDEFL_FINISH)
+    {
+      if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); }
+      if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) { mz_uint i, a = d->m_adler32; for (i = 0; i < 4; i++) { TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); a <<= 8; } }
+    }
+    else
+    {
+      mz_uint i, z = 0; TDEFL_PUT_BITS(0, 3); if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } for (i = 2; i; --i, z ^= 0xFFFF) { TDEFL_PUT_BITS(z & 0xFFFF, 16); }
+    }
+  }
+
+  MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end);
+
+  memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0);
+  memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1);
+
+  d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8; d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; d->m_total_lz_bytes = 0; d->m_block_index++;
+
+  if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0)
+  {
+    if (d->m_pPut_buf_func)
+    {
+      *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf;
+      if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user))
+        return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED);
+    }
+    else if (pOutput_buf_start == d->m_output_buf)
+    {
+      int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs));
+      memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy);
+      d->m_out_buf_ofs += bytes_to_copy;
+      if ((n -= bytes_to_copy) != 0)
+      {
+        d->m_output_flush_ofs = bytes_to_copy;
+        d->m_output_flush_remaining = n;
+      }
+    }
+    else
+    {
+      d->m_out_buf_ofs += n;
+    }
+  }
+
+  return d->m_output_flush_remaining;
+}
+
+#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES
+#define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16*)(p)
+static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len)
+{
+  mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len;
+  mz_uint num_probes_left = d->m_max_probes[match_len >= 32];
+  const mz_uint16 *s = (const mz_uint16*)(d->m_dict + pos), *p, *q;
+  mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD(s);
+  MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return;
+  for ( ; ; )
+  {
+    for ( ; ; )
+    {
+      if (--num_probes_left == 0) return;
+      #define TDEFL_PROBE \
+        next_probe_pos = d->m_next[probe_pos]; \
+        if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) return; \
+        probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \
+        if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) break;
+      TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE;
+    }
+    if (!dist) break; q = (const mz_uint16*)(d->m_dict + probe_pos); if (TDEFL_READ_UNALIGNED_WORD(q) != s01) continue; p = s; probe_len = 32;
+    do { } while ( (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) &&
+                   (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (--probe_len > 0) );
+    if (!probe_len)
+    {
+      *pMatch_dist = dist; *pMatch_len = MZ_MIN(max_match_len, TDEFL_MAX_MATCH_LEN); break;
+    }
+    else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8*)p == *(const mz_uint8*)q)) > match_len)
+    {
+      *pMatch_dist = dist; if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len) break;
+      c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]);
+    }
+  }
+}
+#else
+static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len)
+{
+  mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len;
+  mz_uint num_probes_left = d->m_max_probes[match_len >= 32];
+  const mz_uint8 *s = d->m_dict + pos, *p, *q;
+  mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1];
+  MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return;
+  for ( ; ; )
+  {
+    for ( ; ; )
+    {
+      if (--num_probes_left == 0) return;
+      #define TDEFL_PROBE \
+        next_probe_pos = d->m_next[probe_pos]; \
+        if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) return; \
+        probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \
+        if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) break;
+      TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE;
+    }
+    if (!dist) break; p = s; q = d->m_dict + probe_pos; for (probe_len = 0; probe_len < max_match_len; probe_len++) if (*p++ != *q++) break;
+    if (probe_len > match_len)
+    {
+      *pMatch_dist = dist; if ((*pMatch_len = match_len = probe_len) == max_match_len) return;
+      c0 = d->m_dict[pos + match_len]; c1 = d->m_dict[pos + match_len - 1];
+    }
+  }
+}
+#endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES
+
+#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN
+static mz_bool tdefl_compress_fast(tdefl_compressor *d)
+{
+  // Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio.
+  mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, num_flags_left = d->m_num_flags_left;
+  mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags;
+  mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK;
+
+  while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size)))
+  {
+    const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096;
+    mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK;
+    mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size);
+    d->m_src_buf_left -= num_bytes_to_process;
+    lookahead_size += num_bytes_to_process;
+
+    while (num_bytes_to_process)
+    {
+      mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process);
+      memcpy(d->m_dict + dst_pos, d->m_pSrc, n);
+      if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1))
+        memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos));
+      d->m_pSrc += n;
+      dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK;
+      num_bytes_to_process -= n;
+    }
+
+    dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size);
+    if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) break;
+
+    while (lookahead_size >= 4)
+    {
+      mz_uint cur_match_dist, cur_match_len = 1;
+      mz_uint8 *pCur_dict = d->m_dict + cur_pos;
+      mz_uint first_trigram = (*(const mz_uint32 *)pCur_dict) & 0xFFFFFF;
+      mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK;
+      mz_uint probe_pos = d->m_hash[hash];
+      d->m_hash[hash] = (mz_uint16)lookahead_pos;
+
+      if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && ((*(const mz_uint32 *)(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & 0xFFFFFF) == first_trigram))
+      {
+        const mz_uint16 *p = (const mz_uint16 *)pCur_dict;
+        const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos);
+        mz_uint32 probe_len = 32;
+        do { } while ( (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) &&
+          (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (--probe_len > 0) );
+        cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q);
+        if (!probe_len)
+          cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0;
+
+        if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U*1024U)))
+        {
+          cur_match_len = 1;
+          *pLZ_code_buf++ = (mz_uint8)first_trigram;
+          *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1);
+          d->m_huff_count[0][(mz_uint8)first_trigram]++;
+        }
+        else
+        {
+          mz_uint32 s0, s1;
+          cur_match_len = MZ_MIN(cur_match_len, lookahead_size);
+
+          MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE));
+
+          cur_match_dist--;
+
+          pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN);
+          *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist;
+          pLZ_code_buf += 3;
+          *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80);
+
+          s0 = s_tdefl_small_dist_sym[cur_match_dist & 511];
+          s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8];
+          d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++;
+
+          d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++;
+        }
+      }
+      else
+      {
+        *pLZ_code_buf++ = (mz_uint8)first_trigram;
+        *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1);
+        d->m_huff_count[0][(mz_uint8)first_trigram]++;
+      }
+
+      if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; }
+
+      total_lz_bytes += cur_match_len;
+      lookahead_pos += cur_match_len;
+      dict_size = MZ_MIN(dict_size + cur_match_len, TDEFL_LZ_DICT_SIZE);
+      cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK;
+      MZ_ASSERT(lookahead_size >= cur_match_len);
+      lookahead_size -= cur_match_len;
+
+      if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8])
+      {
+        int n;
+        d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size;
+        d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left;
+        if ((n = tdefl_flush_block(d, 0)) != 0)
+          return (n < 0) ? MZ_FALSE : MZ_TRUE;
+        total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left;
+      }
+    }
+
+    while (lookahead_size)
+    {
+      mz_uint8 lit = d->m_dict[cur_pos];
+
+      total_lz_bytes++;
+      *pLZ_code_buf++ = lit;
+      *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1);
+      if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; }
+
+      d->m_huff_count[0][lit]++;
+
+      lookahead_pos++;
+      dict_size = MZ_MIN(dict_size + 1, TDEFL_LZ_DICT_SIZE);
+      cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK;
+      lookahead_size--;
+
+      if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8])
+      {
+        int n;
+        d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size;
+        d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left;
+        if ((n = tdefl_flush_block(d, 0)) != 0)
+          return (n < 0) ? MZ_FALSE : MZ_TRUE;
+        total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left;
+      }
+    }
+  }
+
+  d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size;
+  d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left;
+  return MZ_TRUE;
+}
+#endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN
+
+static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit)
+{
+  d->m_total_lz_bytes++;
+  *d->m_pLZ_code_buf++ = lit;
+  *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; }
+  d->m_huff_count[0][lit]++;
+}
+
+static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist)
+{
+  mz_uint32 s0, s1;
+
+  MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE));
+
+  d->m_total_lz_bytes += match_len;
+
+  d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN);
+
+  match_dist -= 1;
+  d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF);
+  d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); d->m_pLZ_code_buf += 3;
+
+  *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; }
+
+  s0 = s_tdefl_small_dist_sym[match_dist & 511]; s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127];
+  d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++;
+
+  if (match_len >= TDEFL_MIN_MATCH_LEN) d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++;
+}
+
+static mz_bool tdefl_compress_normal(tdefl_compressor *d)
+{
+  const mz_uint8 *pSrc = d->m_pSrc; size_t src_buf_left = d->m_src_buf_left;
+  tdefl_flush flush = d->m_flush;
+
+  while ((src_buf_left) || ((flush) && (d->m_lookahead_size)))
+  {
+    mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos;
+    // Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN.
+    if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1))
+    {
+      mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2;
+      mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK];
+      mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size);
+      const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process;
+      src_buf_left -= num_bytes_to_process;
+      d->m_lookahead_size += num_bytes_to_process;
+      while (pSrc != pSrc_end)
+      {
+        mz_uint8 c = *pSrc++; d->m_dict[dst_pos] = c; if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c;
+        hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1);
+        d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos);
+        dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; ins_pos++;
+      }
+    }
+    else
+    {
+      while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN))
+      {
+        mz_uint8 c = *pSrc++;
+        mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK;
+        src_buf_left--;
+        d->m_dict[dst_pos] = c;
+        if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1))
+          d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c;
+        if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN)
+        {
+          mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2;
+          mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << (TDEFL_LZ_HASH_SHIFT * 2)) ^ (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1);
+          d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos);
+        }
+      }
+    }
+    d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size);
+    if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN))
+      break;
+
+    // Simple lazy/greedy parsing state machine.
+    len_to_move = 1; cur_match_dist = 0; cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK;
+    if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS))
+    {
+      if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS)))
+      {
+        mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK];
+        cur_match_len = 0; while (cur_match_len < d->m_lookahead_size) { if (d->m_dict[cur_pos + cur_match_len] != c) break; cur_match_len++; }
+        if (cur_match_len < TDEFL_MIN_MATCH_LEN) cur_match_len = 0; else cur_match_dist = 1;
+      }
+    }
+    else
+    {
+      tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len);
+    }
+    if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U*1024U)) || (cur_pos == cur_match_dist) || ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5)))
+    {
+      cur_match_dist = cur_match_len = 0;
+    }
+    if (d->m_saved_match_len)
+    {
+      if (cur_match_len > d->m_saved_match_len)
+      {
+        tdefl_record_literal(d, (mz_uint8)d->m_saved_lit);
+        if (cur_match_len >= 128)
+        {
+          tdefl_record_match(d, cur_match_len, cur_match_dist);
+          d->m_saved_match_len = 0; len_to_move = cur_match_len;
+        }
+        else
+        {
+          d->m_saved_lit = d->m_dict[cur_pos]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len;
+        }
+      }
+      else
+      {
+        tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist);
+        len_to_move = d->m_saved_match_len - 1; d->m_saved_match_len = 0;
+      }
+    }
+    else if (!cur_match_dist)
+      tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]);
+    else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128))
+    {
+      tdefl_record_match(d, cur_match_len, cur_match_dist);
+      len_to_move = cur_match_len;
+    }
+    else
+    {
+      d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len;
+    }
+    // Move the lookahead forward by len_to_move bytes.
+    d->m_lookahead_pos += len_to_move;
+    MZ_ASSERT(d->m_lookahead_size >= len_to_move);
+    d->m_lookahead_size -= len_to_move;
+    d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, TDEFL_LZ_DICT_SIZE);
+    // Check if it's time to flush the current LZ codes to the internal output buffer.
+    if ( (d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) ||
+         ( (d->m_total_lz_bytes > 31*1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) )
+    {
+      int n;
+      d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left;
+      if ((n = tdefl_flush_block(d, 0)) != 0)
+        return (n < 0) ? MZ_FALSE : MZ_TRUE;
+    }
+  }
+
+  d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left;
+  return MZ_TRUE;
+}
+
+static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d)
+{
+  if (d->m_pIn_buf_size)
+  {
+    *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf;
+  }
+
+  if (d->m_pOut_buf_size)
+  {
+    size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining);
+    memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n);
+    d->m_output_flush_ofs += (mz_uint)n;
+    d->m_output_flush_remaining -= (mz_uint)n;
+    d->m_out_buf_ofs += n;
+
+    *d->m_pOut_buf_size = d->m_out_buf_ofs;
+  }
+
+  return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY;
+}
+
+tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush)
+{
+  if (!d)
+  {
+    if (pIn_buf_size) *pIn_buf_size = 0;
+    if (pOut_buf_size) *pOut_buf_size = 0;
+    return TDEFL_STATUS_BAD_PARAM;
+  }
+
+  d->m_pIn_buf = pIn_buf; d->m_pIn_buf_size = pIn_buf_size;
+  d->m_pOut_buf = pOut_buf; d->m_pOut_buf_size = pOut_buf_size;
+  d->m_pSrc = (const mz_uint8 *)(pIn_buf); d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0;
+  d->m_out_buf_ofs = 0;
+  d->m_flush = flush;
+
+  if ( ((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) ||
+        (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf) )
+  {
+    if (pIn_buf_size) *pIn_buf_size = 0;
+    if (pOut_buf_size) *pOut_buf_size = 0;
+    return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM);
+  }
+  d->m_wants_to_finish |= (flush == TDEFL_FINISH);
+
+  if ((d->m_output_flush_remaining) || (d->m_finished))
+    return (d->m_prev_return_status = tdefl_flush_output_buffer(d));
+
+#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN
+  if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) &&
+      ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) &&
+      ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0))
+  {
+    if (!tdefl_compress_fast(d))
+      return d->m_prev_return_status;
+  }
+  else
+#endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN
+  {
+    if (!tdefl_compress_normal(d))
+      return d->m_prev_return_status;
+  }
+
+  if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf))
+    d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf);
+
+  if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining))
+  {
+    if (tdefl_flush_block(d, flush) < 0)
+      return d->m_prev_return_status;
+    d->m_finished = (flush == TDEFL_FINISH);
+    if (flush == TDEFL_FULL_FLUSH) { MZ_CLEAR_OBJ(d->m_hash); MZ_CLEAR_OBJ(d->m_next); d->m_dict_size = 0; }
+  }
+
+  return (d->m_prev_return_status = tdefl_flush_output_buffer(d));
+}
+
+tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush)
+{
+  MZ_ASSERT(d->m_pPut_buf_func); return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush);
+}
+
+tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags)
+{
+  d->m_pPut_buf_func = pPut_buf_func; d->m_pPut_buf_user = pPut_buf_user;
+  d->m_flags = (mz_uint)(flags); d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0;
+  d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3;
+  if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) MZ_CLEAR_OBJ(d->m_hash);
+  d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0;
+  d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0;
+  d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8;
+  d->m_pOutput_buf = d->m_output_buf; d->m_pOutput_buf_end = d->m_output_buf; d->m_prev_return_status = TDEFL_STATUS_OKAY;
+  d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; d->m_adler32 = 1;
+  d->m_pIn_buf = NULL; d->m_pOut_buf = NULL;
+  d->m_pIn_buf_size = NULL; d->m_pOut_buf_size = NULL;
+  d->m_flush = TDEFL_NO_FLUSH; d->m_pSrc = NULL; d->m_src_buf_left = 0; d->m_out_buf_ofs = 0;
+  memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0);
+  memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1);
+  return TDEFL_STATUS_OKAY;
+}
+
+tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d)
+{
+  return d->m_prev_return_status;
+}
+
+mz_uint32 tdefl_get_adler32(tdefl_compressor *d)
+{
+  return d->m_adler32;
+}
+
+mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags)
+{
+  tdefl_compressor *pComp; mz_bool succeeded; if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) return MZ_FALSE;
+  pComp = (tdefl_compressor*)MZ_MALLOC(sizeof(tdefl_compressor)); if (!pComp) return MZ_FALSE;
+  succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY);
+  succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE);
+  MZ_FREE(pComp); return succeeded;
+}
+
+typedef struct
+{
+  size_t m_size, m_capacity;
+  mz_uint8 *m_pBuf;
+  mz_bool m_expandable;
+} tdefl_output_buffer;
+
+static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser)
+{
+  tdefl_output_buffer *p = (tdefl_output_buffer *)pUser;
+  size_t new_size = p->m_size + len;
+  if (new_size > p->m_capacity)
+  {
+    size_t new_capacity = p->m_capacity; mz_uint8 *pNew_buf; if (!p->m_expandable) return MZ_FALSE;
+    do { new_capacity = MZ_MAX(128U, new_capacity << 1U); } while (new_size > new_capacity);
+    pNew_buf = (mz_uint8*)MZ_REALLOC(p->m_pBuf, new_capacity); if (!pNew_buf) return MZ_FALSE;
+    p->m_pBuf = pNew_buf; p->m_capacity = new_capacity;
+  }
+  memcpy((mz_uint8*)p->m_pBuf + p->m_size, pBuf, len); p->m_size = new_size;
+  return MZ_TRUE;
+}
+
+void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags)
+{
+  tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf);
+  if (!pOut_len) return MZ_FALSE; else *pOut_len = 0;
+  out_buf.m_expandable = MZ_TRUE;
+  if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return NULL;
+  *pOut_len = out_buf.m_size; return out_buf.m_pBuf;
+}
+
+size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags)
+{
+  tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf);
+  if (!pOut_buf) return 0;
+  out_buf.m_pBuf = (mz_uint8*)pOut_buf; out_buf.m_capacity = out_buf_len;
+  if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return 0;
+  return out_buf.m_size;
+}
+
+#ifndef MINIZ_NO_ZLIB_APIS
+static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32,  16, 32, 128, 256,  512, 768, 1500 };
+
+// level may actually range from [0,10] (10 is a "hidden" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files).
+mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy)
+{
+  mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0);
+  if (window_bits > 0) comp_flags |= TDEFL_WRITE_ZLIB_HEADER;
+
+  if (!level) comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS;
+  else if (strategy == MZ_FILTERED) comp_flags |= TDEFL_FILTER_MATCHES;
+  else if (strategy == MZ_HUFFMAN_ONLY) comp_flags &= ~TDEFL_MAX_PROBES_MASK;
+  else if (strategy == MZ_FIXED) comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS;
+  else if (strategy == MZ_RLE) comp_flags |= TDEFL_RLE_MATCHES;
+
+  return comp_flags;
+}
+#endif //MINIZ_NO_ZLIB_APIS
+
+#ifdef _MSC_VER
+#pragma warning (push)
+#pragma warning (disable:4204) // nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal)
+#endif
+
+// Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at
+// http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/.
+// This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck.
+void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip)
+{
+  // Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined.
+  static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32,  16, 32, 128, 256,  512, 768, 1500 };
+  tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); tdefl_output_buffer out_buf; int i, bpl = w * num_chans, y, z; mz_uint32 c; *pLen_out = 0;
+  if (!pComp) return NULL;
+  MZ_CLEAR_OBJ(out_buf); out_buf.m_expandable = MZ_TRUE; out_buf.m_capacity = 57+MZ_MAX(64, (1+bpl)*h); if (NULL == (out_buf.m_pBuf = (mz_uint8*)MZ_MALLOC(out_buf.m_capacity))) { MZ_FREE(pComp); return NULL; }
+  // write dummy header
+  for (z = 41; z; --z) tdefl_output_buffer_putter(&z, 1, &out_buf);
+  // compress image data
+  tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER);
+  for (y = 0; y < h; ++y) { tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); tdefl_compress_buffer(pComp, (mz_uint8*)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH); }
+  if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) { MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; }
+  // write real header
+  *pLen_out = out_buf.m_size-41;
+  {
+    static const mz_uint8 chans[] = {0x00, 0x00, 0x04, 0x02, 0x06};
+    mz_uint8 pnghdr[41]={0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48,0x44,0x52,
+      0,0,(mz_uint8)(w>>8),(mz_uint8)w,0,0,(mz_uint8)(h>>8),(mz_uint8)h,8,chans[num_chans],0,0,0,0,0,0,0,
+      (mz_uint8)(*pLen_out>>24),(mz_uint8)(*pLen_out>>16),(mz_uint8)(*pLen_out>>8),(mz_uint8)*pLen_out,0x49,0x44,0x41,0x54};
+    c=(mz_uint32)mz_crc32(MZ_CRC32_INIT,pnghdr+12,17); for (i=0; i<4; ++i, c<<=8) ((mz_uint8*)(pnghdr+29))[i]=(mz_uint8)(c>>24);
+    memcpy(out_buf.m_pBuf, pnghdr, 41);
+  }
+  // write footer (IDAT CRC-32, followed by IEND chunk)
+  if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) { *pLen_out = 0; MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; }
+  c = (mz_uint32)mz_crc32(MZ_CRC32_INIT,out_buf.m_pBuf+41-4, *pLen_out+4); for (i=0; i<4; ++i, c<<=8) (out_buf.m_pBuf+out_buf.m_size-16)[i] = (mz_uint8)(c >> 24);
+  // compute final size of file, grab compressed data buffer and return
+  *pLen_out += 57; MZ_FREE(pComp); return out_buf.m_pBuf;
+}
+void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out)
+{
+  // Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out)
+  return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE);
+}
+
+#ifdef _MSC_VER
+#pragma warning (pop)
+#endif
+
+// ------------------- .ZIP archive reading
+
+#ifndef MINIZ_NO_ARCHIVE_APIS
+
+#ifdef MINIZ_NO_STDIO
+  #define MZ_FILE void *
+#else
+  #include <stdio.h>
+  #include <sys/stat.h>
+
+  #if defined(_MSC_VER) || defined(__MINGW64__)
+    static FILE *mz_fopen(const char *pFilename, const char *pMode)
+    {
+      FILE* pFile = NULL;
+      fopen_s(&pFile, pFilename, pMode);
+      return pFile;
+    }
+    static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream)
+    {
+      FILE* pFile = NULL;
+      if (freopen_s(&pFile, pPath, pMode, pStream))
+        return NULL;
+      return pFile;
+    }
+    #ifndef MINIZ_NO_TIME
+      #include <sys/utime.h>
+    #endif
+    #define MZ_FILE FILE
+    #define MZ_FOPEN mz_fopen
+    #define MZ_FCLOSE fclose
+    #define MZ_FREAD fread
+    #define MZ_FWRITE fwrite
+    #define MZ_FTELL64 _ftelli64
+    #define MZ_FSEEK64 _fseeki64
+    #define MZ_FILE_STAT_STRUCT _stat
+    #define MZ_FILE_STAT _stat
+    #define MZ_FFLUSH fflush
+    #define MZ_FREOPEN mz_freopen
+    #define MZ_DELETE_FILE remove
+  #elif defined(__MINGW32__)
+    #ifndef MINIZ_NO_TIME
+      #include <sys/utime.h>
+    #endif
+    #define MZ_FILE FILE
+    #define MZ_FOPEN(f, m) fopen(f, m)
+    #define MZ_FCLOSE fclose
+    #define MZ_FREAD fread
+    #define MZ_FWRITE fwrite
+    #define MZ_FTELL64 ftello64
+    #define MZ_FSEEK64 fseeko64
+    #define MZ_FILE_STAT_STRUCT _stat
+    #define MZ_FILE_STAT _stat
+    #define MZ_FFLUSH fflush
+    #define MZ_FREOPEN(f, m, s) freopen(f, m, s)
+    #define MZ_DELETE_FILE remove
+  #elif defined(__TINYC__)
+    #ifndef MINIZ_NO_TIME
+      #include <sys/utime.h>
+    #endif
+    #define MZ_FILE FILE
+    #define MZ_FOPEN(f, m) fopen(f, m)
+    #define MZ_FCLOSE fclose
+    #define MZ_FREAD fread
+    #define MZ_FWRITE fwrite
+    #define MZ_FTELL64 ftell
+    #define MZ_FSEEK64 fseek
+    #define MZ_FILE_STAT_STRUCT stat
+    #define MZ_FILE_STAT stat
+    #define MZ_FFLUSH fflush
+    #define MZ_FREOPEN(f, m, s) freopen(f, m, s)
+    #define MZ_DELETE_FILE remove
+  #elif defined(__GNUC__) && _LARGEFILE64_SOURCE
+    #ifndef MINIZ_NO_TIME
+      #include <utime.h>
+    #endif
+    #define MZ_FILE FILE
+    #define MZ_FOPEN(f, m) fopen64(f, m)
+    #define MZ_FCLOSE fclose
+    #define MZ_FREAD fread
+    #define MZ_FWRITE fwrite
+    #define MZ_FTELL64 ftello64
+    #define MZ_FSEEK64 fseeko64
+    #define MZ_FILE_STAT_STRUCT stat64
+    #define MZ_FILE_STAT stat64
+    #define MZ_FFLUSH fflush
+    #define MZ_FREOPEN(p, m, s) freopen64(p, m, s)
+    #define MZ_DELETE_FILE remove
+  #else
+    #ifndef MINIZ_NO_TIME
+      #include <utime.h>
+    #endif
+    #define MZ_FILE FILE
+    #define MZ_FOPEN(f, m) fopen(f, m)
+    #define MZ_FCLOSE fclose
+    #define MZ_FREAD fread
+    #define MZ_FWRITE fwrite
+    #define MZ_FTELL64 ftello
+    #define MZ_FSEEK64 fseeko
+    #define MZ_FILE_STAT_STRUCT stat
+    #define MZ_FILE_STAT stat
+    #define MZ_FFLUSH fflush
+    #define MZ_FREOPEN(f, m, s) freopen(f, m, s)
+    #define MZ_DELETE_FILE remove
+  #endif // #ifdef _MSC_VER
+#endif // #ifdef MINIZ_NO_STDIO
+
+#define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c))
+
+// Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff.
+enum
+{
+  // ZIP archive identifiers and record sizes
+  MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50,
+  MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22,
+  // Central directory header record offsets
+  MZ_ZIP_CDH_SIG_OFS = 0, MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, MZ_ZIP_CDH_BIT_FLAG_OFS = 8,
+  MZ_ZIP_CDH_METHOD_OFS = 10, MZ_ZIP_CDH_FILE_TIME_OFS = 12, MZ_ZIP_CDH_FILE_DATE_OFS = 14, MZ_ZIP_CDH_CRC32_OFS = 16,
+  MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, MZ_ZIP_CDH_EXTRA_LEN_OFS = 30,
+  MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, MZ_ZIP_CDH_DISK_START_OFS = 34, MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42,
+  // Local directory header offsets
+  MZ_ZIP_LDH_SIG_OFS = 0, MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, MZ_ZIP_LDH_BIT_FLAG_OFS = 6, MZ_ZIP_LDH_METHOD_OFS = 8, MZ_ZIP_LDH_FILE_TIME_OFS = 10,
+  MZ_ZIP_LDH_FILE_DATE_OFS = 12, MZ_ZIP_LDH_CRC32_OFS = 14, MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22,
+  MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, MZ_ZIP_LDH_EXTRA_LEN_OFS = 28,
+  // End of central directory offsets
+  MZ_ZIP_ECDH_SIG_OFS = 0, MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8,
+  MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20,
+};
+
+typedef struct
+{
+  void *m_p;
+  size_t m_size, m_capacity;
+  mz_uint m_element_size;
+} mz_zip_array;
+
+struct mz_zip_internal_state_tag
+{
+  mz_zip_array m_central_dir;
+  mz_zip_array m_central_dir_offsets;
+  mz_zip_array m_sorted_central_dir_offsets;
+  MZ_FILE *m_pFile;
+  void *m_pMem;
+  size_t m_mem_size;
+  size_t m_mem_capacity;
+};
+
+#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size
+#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index]
+
+static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray)
+{
+  pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p);
+  memset(pArray, 0, sizeof(mz_zip_array));
+}
+
+static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing)
+{
+  void *pNew_p; size_t new_capacity = min_new_capacity; MZ_ASSERT(pArray->m_element_size); if (pArray->m_capacity >= min_new_capacity) return MZ_TRUE;
+  if (growing) { new_capacity = MZ_MAX(1, pArray->m_capacity); while (new_capacity < min_new_capacity) new_capacity *= 2; }
+  if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity))) return MZ_FALSE;
+  pArray->m_p = pNew_p; pArray->m_capacity = new_capacity;
+  return MZ_TRUE;
+}
+
+static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing)
+{
+  if (new_capacity > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) return MZ_FALSE; }
+  return MZ_TRUE;
+}
+
+static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing)
+{
+  if (new_size > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) return MZ_FALSE; }
+  pArray->m_size = new_size;
+  return MZ_TRUE;
+}
+
+static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n)
+{
+  return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE);
+}
+
+static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n)
+{
+  size_t orig_size = pArray->m_size; if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) return MZ_FALSE;
+  memcpy((mz_uint8*)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size);
+  return MZ_TRUE;
+}
+
+#ifndef MINIZ_NO_TIME
+static time_t mz_zip_dos_to_time_t(int dos_time, int dos_date)
+{
+  struct tm tm;
+  memset(&tm, 0, sizeof(tm)); tm.tm_isdst = -1;
+  tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; tm.tm_mon = ((dos_date >> 5) & 15) - 1; tm.tm_mday = dos_date & 31;
+  tm.tm_hour = (dos_time >> 11) & 31; tm.tm_min = (dos_time >> 5) & 63; tm.tm_sec = (dos_time << 1) & 62;
+  return mktime(&tm);
+}
+
+static void mz_zip_time_to_dos_time(time_t time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date)
+{
+#ifdef _MSC_VER
+  struct tm tm_struct;
+  struct tm *tm = &tm_struct;
+  errno_t err = localtime_s(tm, &time);
+  if (err)
+  {
+    *pDOS_date = 0; *pDOS_time = 0;
+    return;
+  }
+#else
+  struct tm *tm = localtime(&time);
+#endif
+  *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1));
+  *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday);
+}
+#endif
+
+#ifndef MINIZ_NO_STDIO
+static mz_bool mz_zip_get_file_modified_time(const char *pFilename, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date)
+{
+#ifdef MINIZ_NO_TIME
+  (void)pFilename; *pDOS_date = *pDOS_time = 0;
+#else
+  struct MZ_FILE_STAT_STRUCT file_stat;
+  // On Linux with x86 glibc, this call will fail on large files (>= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh.
+  if (MZ_FILE_STAT(pFilename, &file_stat) != 0)
+    return MZ_FALSE;
+  mz_zip_time_to_dos_time(file_stat.st_mtime, pDOS_time, pDOS_date);
+#endif // #ifdef MINIZ_NO_TIME
+  return MZ_TRUE;
+}
+
+#ifndef MINIZ_NO_TIME
+static mz_bool mz_zip_set_file_times(const char *pFilename, time_t access_time, time_t modified_time)
+{
+  struct utimbuf t; t.actime = access_time; t.modtime = modified_time;
+  return !utime(pFilename, &t);
+}
+#endif // #ifndef MINIZ_NO_TIME
+#endif // #ifndef MINIZ_NO_STDIO
+
+static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint32 flags)
+{
+  (void)flags;
+  if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID))
+    return MZ_FALSE;
+
+  if (!pZip->m_pAlloc) pZip->m_pAlloc = def_alloc_func;
+  if (!pZip->m_pFree) pZip->m_pFree = def_free_func;
+  if (!pZip->m_pRealloc) pZip->m_pRealloc = def_realloc_func;
+
+  pZip->m_zip_mode = MZ_ZIP_MODE_READING;
+  pZip->m_archive_size = 0;
+  pZip->m_central_directory_file_ofs = 0;
+  pZip->m_total_files = 0;
+
+  if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state))))
+    return MZ_FALSE;
+  memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state));
+  MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8));
+  MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32));
+  MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32));
+  return MZ_TRUE;
+}
+
+static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index)
+{
+  const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE;
+  const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index));
+  mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS);
+  mz_uint8 l = 0, r = 0;
+  pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;
+  pE = pL + MZ_MIN(l_len, r_len);
+  while (pL < pE)
+  {
+    if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR)))
+      break;
+    pL++; pR++;
+  }
+  return (pL == pE) ? (l_len < r_len) : (l < r);
+}
+
+#define MZ_SWAP_UINT32(a, b) do { mz_uint32 t = a; a = b; b = t; } MZ_MACRO_END
+
+// Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.)
+static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip)
+{
+  mz_zip_internal_state *pState = pZip->m_pState;
+  const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets;
+  const mz_zip_array *pCentral_dir = &pState->m_central_dir;
+  mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0);
+  const int size = pZip->m_total_files;
+  int start = (size - 2) >> 1, end;
+  while (start >= 0)
+  {
+    int child, root = start;
+    for ( ; ; )
+    {
+      if ((child = (root << 1) + 1) >= size)
+        break;
+      child += (((child + 1) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1])));
+      if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child]))
+        break;
+      MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child;
+    }
+    start--;
+  }
+
+  end = size - 1;
+  while (end > 0)
+  {
+    int child, root = 0;
+    MZ_SWAP_UINT32(pIndices[end], pIndices[0]);
+    for ( ; ; )
+    {
+      if ((child = (root << 1) + 1) >= end)
+        break;
+      child += (((child + 1) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1]));
+      if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child]))
+        break;
+      MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child;
+    }
+    end--;
+  }
+}
+
+static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint32 flags)
+{
+  mz_uint cdir_size, num_this_disk, cdir_disk_index;
+  mz_uint64 cdir_ofs;
+  mz_int64 cur_file_ofs;
+  const mz_uint8 *p;
+  mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; mz_uint8 *pBuf = (mz_uint8 *)buf_u32;
+  mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0);
+  // Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there.
+  if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
+    return MZ_FALSE;
+  // Find the end of central directory record by scanning the file from the end towards the beginning.
+  cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0);
+  for ( ; ; )
+  {
+    int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs);
+    if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n)
+      return MZ_FALSE;
+    for (i = n - 4; i >= 0; --i)
+      if (MZ_READ_LE32(pBuf + i) == MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG)
+        break;
+    if (i >= 0)
+    {
+      cur_file_ofs += i;
+      break;
+    }
+    if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (0xFFFF + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)))
+      return MZ_FALSE;
+    cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0);
+  }
+  // Read and verify the end of central directory record.
+  if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
+    return MZ_FALSE;
+  if ((MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) ||
+      ((pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS)) != MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS)))
+    return MZ_FALSE;
+
+  num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS);
+  cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS);
+  if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1)))
+    return MZ_FALSE;
+
+  if ((cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS)) < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)
+    return MZ_FALSE;
+
+  cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS);
+  if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size)
+    return MZ_FALSE;
+
+  pZip->m_central_directory_file_ofs = cdir_ofs;
+
+  if (pZip->m_total_files)
+  {
+     mz_uint i, n;
+
+    // Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and another to hold the sorted indices.
+    if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) ||
+        (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE)))
+      return MZ_FALSE;
+
+    if (sort_central_dir)
+    {
+      if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE))
+        return MZ_FALSE;
+    }
+
+    if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size)
+      return MZ_FALSE;
+
+    // Now create an index into the central directory file records, do some basic sanity checking on each record, and check for zip64 entries (which are not yet supported).
+    p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p;
+    for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i)
+    {
+      mz_uint total_header_size, comp_size, decomp_size, disk_index;
+      if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG))
+        return MZ_FALSE;
+      MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p);
+      if (sort_central_dir)
+        MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i;
+      comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS);
+      decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS);
+      if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size) || (decomp_size == 0xFFFFFFFF) || (comp_size == 0xFFFFFFFF))
+        return MZ_FALSE;
+      disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS);
+      if ((disk_index != num_this_disk) && (disk_index != 1))
+        return MZ_FALSE;
+      if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size)
+        return MZ_FALSE;
+      if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n)
+        return MZ_FALSE;
+      n -= total_header_size; p += total_header_size;
+    }
+  }
+
+  if (sort_central_dir)
+    mz_zip_reader_sort_central_dir_offsets_by_filename(pZip);
+
+  return MZ_TRUE;
+}
+
+mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint32 flags)
+{
+  if ((!pZip) || (!pZip->m_pRead))
+    return MZ_FALSE;
+  if (!mz_zip_reader_init_internal(pZip, flags))
+    return MZ_FALSE;
+  pZip->m_archive_size = size;
+  if (!mz_zip_reader_read_central_dir(pZip, flags))
+  {
+    mz_zip_reader_end(pZip);
+    return MZ_FALSE;
+  }
+  return MZ_TRUE;
+}
+
+static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n)
+{
+  mz_zip_archive *pZip = (mz_zip_archive *)pOpaque;
+  size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n);
+  memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s);
+  return s;
+}
+
+mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint32 flags)
+{
+  if (!mz_zip_reader_init_internal(pZip, flags))
+    return MZ_FALSE;
+  pZip->m_archive_size = size;
+  pZip->m_pRead = mz_zip_mem_read_func;
+  pZip->m_pIO_opaque = pZip;
+#ifdef __cplusplus
+  pZip->m_pState->m_pMem = const_cast<void *>(pMem);
+#else
+  pZip->m_pState->m_pMem = (void *)pMem;
+#endif
+  pZip->m_pState->m_mem_size = size;
+  if (!mz_zip_reader_read_central_dir(pZip, flags))
+  {
+    mz_zip_reader_end(pZip);
+    return MZ_FALSE;
+  }
+  return MZ_TRUE;
+}
+
+#ifndef MINIZ_NO_STDIO
+static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n)
+{
+  mz_zip_archive *pZip = (mz_zip_archive *)pOpaque;
+  mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile);
+  if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET))))
+    return 0;
+  return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile);
+}
+
+mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags)
+{
+  mz_uint64 file_size;
+  MZ_FILE *pFile = MZ_FOPEN(pFilename, "rb");
+  if (!pFile)
+    return MZ_FALSE;
+  if (MZ_FSEEK64(pFile, 0, SEEK_END))
+  {
+    MZ_FCLOSE(pFile);
+    return MZ_FALSE;
+  }
+  file_size = MZ_FTELL64(pFile);
+  if (!mz_zip_reader_init_internal(pZip, flags))
+  {
+    MZ_FCLOSE(pFile);
+    return MZ_FALSE;
+  }
+  pZip->m_pRead = mz_zip_file_read_func;
+  pZip->m_pIO_opaque = pZip;
+  pZip->m_pState->m_pFile = pFile;
+  pZip->m_archive_size = file_size;
+  if (!mz_zip_reader_read_central_dir(pZip, flags))
+  {
+    mz_zip_reader_end(pZip);
+    return MZ_FALSE;
+  }
+  return MZ_TRUE;
+}
+#endif // #ifndef MINIZ_NO_STDIO
+
+mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip)
+{
+  return pZip ? pZip->m_total_files : 0;
+}
+
+static MZ_FORCEINLINE const mz_uint8 *mz_zip_reader_get_cdh(mz_zip_archive *pZip, mz_uint file_index)
+{
+  if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING))
+    return NULL;
+  return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index));
+}
+
+mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index)
+{
+  mz_uint m_bit_flag;
+  const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index);
+  if (!p)
+    return MZ_FALSE;
+  m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS);
+  return (m_bit_flag & 1);
+}
+
+mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index)
+{
+  mz_uint filename_len, external_attr;
+  const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index);
+  if (!p)
+    return MZ_FALSE;
+
+  // First see if the filename ends with a '/' character.
+  filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);
+  if (filename_len)
+  {
+    if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/')
+      return MZ_TRUE;
+  }
+
+  // Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct.
+  // Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field.
+  // FIXME: Remove this check? Is it necessary - we already check the filename.
+  external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS);
+  if ((external_attr & 0x10) != 0)
+    return MZ_TRUE;
+
+  return MZ_FALSE;
+}
+
+mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat)
+{
+  mz_uint n;
+  const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index);
+  if ((!p) || (!pStat))
+    return MZ_FALSE;
+
+  // Unpack the central directory record.
+  pStat->m_file_index = file_index;
+  pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index);
+  pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS);
+  pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS);
+  pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS);
+  pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS);
+#ifndef MINIZ_NO_TIME
+  pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS));
+#endif
+  pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS);
+  pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS);
+  pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS);
+  pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS);
+  pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS);
+  pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS);
+
+  // Copy as much of the filename and comment as possible.
+  n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1);
+  memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); pStat->m_filename[n] = '\0';
+
+  n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1);
+  pStat->m_comment_size = n;
+  memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n); pStat->m_comment[n] = '\0';
+
+  return MZ_TRUE;
+}
+
+mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size)
+{
+  mz_uint n;
+  const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index);
+  if (!p) { if (filename_buf_size) pFilename[0] = '\0'; return 0; }
+  n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);
+  if (filename_buf_size)
+  {
+    n = MZ_MIN(n, filename_buf_size - 1);
+    memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n);
+    pFilename[n] = '\0';
+  }
+  return n + 1;
+}
+
+static MZ_FORCEINLINE mz_bool mz_zip_reader_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags)
+{
+  mz_uint i;
+  if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE)
+    return 0 == memcmp(pA, pB, len);
+  for (i = 0; i < len; ++i)
+    if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i]))
+      return MZ_FALSE;
+  return MZ_TRUE;
+}
+
+static MZ_FORCEINLINE int mz_zip_reader_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len)
+{
+  const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE;
+  mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS);
+  mz_uint8 l = 0, r = 0;
+  pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;
+  pE = pL + MZ_MIN(l_len, r_len);
+  while (pL < pE)
+  {
+    if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR)))
+      break;
+    pL++; pR++;
+  }
+  return (pL == pE) ? (int)(l_len - r_len) : (l - r);
+}
+
+static int mz_zip_reader_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename)
+{
+  mz_zip_internal_state *pState = pZip->m_pState;
+  const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets;
+  const mz_zip_array *pCentral_dir = &pState->m_central_dir;
+  mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0);
+  const int size = pZip->m_total_files;
+  const mz_uint filename_len = (mz_uint)strlen(pFilename);
+  int l = 0, h = size - 1;
+  while (l <= h)
+  {
+    int m = (l + h) >> 1, file_index = pIndices[m], comp = mz_zip_reader_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len);
+    if (!comp)
+      return file_index;
+    else if (comp < 0)
+      l = m + 1;
+    else
+      h = m - 1;
+  }
+  return -1;
+}
+
+int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags)
+{
+  mz_uint file_index; size_t name_len, comment_len;
+  if ((!pZip) || (!pZip->m_pState) || (!pName) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING))
+    return -1;
+  if (((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size))
+    return mz_zip_reader_locate_file_binary_search(pZip, pName);
+  name_len = strlen(pName); if (name_len > 0xFFFF) return -1;
+  comment_len = pComment ? strlen(pComment) : 0; if (comment_len > 0xFFFF) return -1;
+  for (file_index = 0; file_index < pZip->m_total_files; file_index++)
+  {
+    const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index));
+    mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS);
+    const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;
+    if (filename_len < name_len)
+      continue;
+    if (comment_len)
+    {
+      mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS);
+      const char *pFile_comment = pFilename + filename_len + file_extra_len;
+      if ((file_comment_len != comment_len) || (!mz_zip_reader_string_equal(pComment, pFile_comment, file_comment_len, flags)))
+        continue;
+    }
+    if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len))
+    {
+      int ofs = filename_len - 1;
+      do
+      {
+        if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':'))
+          break;
+      } while (--ofs >= 0);
+      ofs++;
+      pFilename += ofs; filename_len -= ofs;
+    }
+    if ((filename_len == name_len) && (mz_zip_reader_string_equal(pName, pFilename, filename_len, flags)))
+      return file_index;
+  }
+  return -1;
+}
+
+mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size)
+{
+  int status = TINFL_STATUS_DONE;
+  mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail;
+  mz_zip_archive_file_stat file_stat;
+  void *pRead_buf;
+  mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;
+  tinfl_decompressor inflator;
+
+  if ((buf_size) && (!pBuf))
+    return MZ_FALSE;
+
+  if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat))
+    return MZ_FALSE;
+
+  // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes)
+  if (!file_stat.m_comp_size)
+    return MZ_TRUE;
+
+  // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers).
+  // I'm torn how to handle this case - should it fail instead?
+  if (mz_zip_reader_is_file_a_directory(pZip, file_index))
+    return MZ_TRUE;
+
+  // Encryption and patch files are not supported.
+  if (file_stat.m_bit_flag & (1 | 32))
+    return MZ_FALSE;
+
+  // This function only supports stored and deflate.
+  if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED))
+    return MZ_FALSE;
+
+  // Ensure supplied output buffer is large enough.
+  needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size;
+  if (buf_size < needed_size)
+    return MZ_FALSE;
+
+  // Read and parse the local directory entry.
+  cur_file_ofs = file_stat.m_local_header_ofs;
+  if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
+    return MZ_FALSE;
+  if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)
+    return MZ_FALSE;
+
+  cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);
+  if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size)
+    return MZ_FALSE;
+
+  if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method))
+  {
+    // The file is stored or the caller has requested the compressed data.
+    if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size)
+      return MZ_FALSE;
+    return ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) != 0) || (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) == file_stat.m_crc32);
+  }
+
+  // Decompress the file either directly from memory or from a file input buffer.
+  tinfl_init(&inflator);
+
+  if (pZip->m_pState->m_pMem)
+  {
+    // Read directly from the archive in memory.
+    pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs;
+    read_buf_size = read_buf_avail = file_stat.m_comp_size;
+    comp_remaining = 0;
+  }
+  else if (pUser_read_buf)
+  {
+    // Use a user provided read buffer.
+    if (!user_read_buf_size)
+      return MZ_FALSE;
+    pRead_buf = (mz_uint8 *)pUser_read_buf;
+    read_buf_size = user_read_buf_size;
+    read_buf_avail = 0;
+    comp_remaining = file_stat.m_comp_size;
+  }
+  else
+  {
+    // Temporarily allocate a read buffer.
+    read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE);
+#ifdef _MSC_VER
+    if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF))
+#else
+    if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF))
+#endif
+      return MZ_FALSE;
+    if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size)))
+      return MZ_FALSE;
+    read_buf_avail = 0;
+    comp_remaining = file_stat.m_comp_size;
+  }
+
+  do
+  {
+    size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs);
+    if ((!read_buf_avail) && (!pZip->m_pState->m_pMem))
+    {
+      read_buf_avail = MZ_MIN(read_buf_size, comp_remaining);
+      if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)
+      {
+        status = TINFL_STATUS_FAILED;
+        break;
+      }
+      cur_file_ofs += read_buf_avail;
+      comp_remaining -= read_buf_avail;
+      read_buf_ofs = 0;
+    }
+    in_buf_size = (size_t)read_buf_avail;
+    status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0));
+    read_buf_avail -= in_buf_size;
+    read_buf_ofs += in_buf_size;
+    out_buf_ofs += out_buf_size;
+  } while (status == TINFL_STATUS_NEEDS_MORE_INPUT);
+
+  if (status == TINFL_STATUS_DONE)
+  {
+    // Make sure the entire file was decompressed, and check its CRC.
+    if ((out_buf_ofs != file_stat.m_uncomp_size) || (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32))
+      status = TINFL_STATUS_FAILED;
+  }
+
+  if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf))
+    pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
+
+  return status == TINFL_STATUS_DONE;
+}
+
+mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size)
+{
+  int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags);
+  if (file_index < 0)
+    return MZ_FALSE;
+  return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size);
+}
+
+mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags)
+{
+  return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, NULL, 0);
+}
+
+mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags)
+{
+  return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0);
+}
+
+void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags)
+{
+  mz_uint64 comp_size, uncomp_size, alloc_size;
+  const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index);
+  void *pBuf;
+
+  if (pSize)
+    *pSize = 0;
+  if (!p)
+    return NULL;
+
+  comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS);
+  uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS);
+
+  alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size;
+#ifdef _MSC_VER
+  if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF))
+#else
+  if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF))
+#endif
+    return NULL;
+  if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size)))
+    return NULL;
+
+  if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, flags))
+  {
+    pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
+    return NULL;
+  }
+
+  if (pSize) *pSize = (size_t)alloc_size;
+  return pBuf;
+}
+
+void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags)
+{
+  int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags);
+  if (file_index < 0)
+  {
+    if (pSize) *pSize = 0;
+    return MZ_FALSE;
+  }
+  return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags);
+}
+
+mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags)
+{
+  int status = TINFL_STATUS_DONE; mz_uint file_crc32 = MZ_CRC32_INIT;
+  mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs;
+  mz_zip_archive_file_stat file_stat;
+  void *pRead_buf = NULL; void *pWrite_buf = NULL;
+  mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;
+
+  if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat))
+    return MZ_FALSE;
+
+  // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes)
+  if (!file_stat.m_comp_size)
+    return MZ_TRUE;
+
+  // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers).
+  // I'm torn how to handle this case - should it fail instead?
+  if (mz_zip_reader_is_file_a_directory(pZip, file_index))
+    return MZ_TRUE;
+
+  // Encryption and patch files are not supported.
+  if (file_stat.m_bit_flag & (1 | 32))
+    return MZ_FALSE;
+
+  // This function only supports stored and deflate.
+  if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED))
+    return MZ_FALSE;
+
+  // Read and parse the local directory entry.
+  cur_file_ofs = file_stat.m_local_header_ofs;
+  if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
+    return MZ_FALSE;
+  if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)
+    return MZ_FALSE;
+
+  cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);
+  if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size)
+    return MZ_FALSE;
+
+  // Decompress the file either directly from memory or from a file input buffer.
+  if (pZip->m_pState->m_pMem)
+  {
+    pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs;
+    read_buf_size = read_buf_avail = file_stat.m_comp_size;
+    comp_remaining = 0;
+  }
+  else
+  {
+    read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE);
+    if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size)))
+      return MZ_FALSE;
+    read_buf_avail = 0;
+    comp_remaining = file_stat.m_comp_size;
+  }
+
+  if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method))
+  {
+    // The file is stored or the caller has requested the compressed data.
+    if (pZip->m_pState->m_pMem)
+    {
+#ifdef _MSC_VER
+      if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > 0xFFFFFFFF))
+#else
+      if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > 0xFFFFFFFF))
+#endif
+        return MZ_FALSE;
+      if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size)
+        status = TINFL_STATUS_FAILED;
+      else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))
+        file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size);
+      cur_file_ofs += file_stat.m_comp_size;
+      out_buf_ofs += file_stat.m_comp_size;
+      comp_remaining = 0;
+    }
+    else
+    {
+      while (comp_remaining)
+      {
+        read_buf_avail = MZ_MIN(read_buf_size, comp_remaining);
+        if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)
+        {
+          status = TINFL_STATUS_FAILED;
+          break;
+        }
+
+        if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))
+          file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail);
+
+        if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)
+        {
+          status = TINFL_STATUS_FAILED;
+          break;
+        }
+        cur_file_ofs += read_buf_avail;
+        out_buf_ofs += read_buf_avail;
+        comp_remaining -= read_buf_avail;
+      }
+    }
+  }
+  else
+  {
+    tinfl_decompressor inflator;
+    tinfl_init(&inflator);
+
+    if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE)))
+      status = TINFL_STATUS_FAILED;
+    else
+    {
+      do
+      {
+        mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1));
+        size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1));
+        if ((!read_buf_avail) && (!pZip->m_pState->m_pMem))
+        {
+          read_buf_avail = MZ_MIN(read_buf_size, comp_remaining);
+          if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)
+          {
+            status = TINFL_STATUS_FAILED;
+            break;
+          }
+          cur_file_ofs += read_buf_avail;
+          comp_remaining -= read_buf_avail;
+          read_buf_ofs = 0;
+        }
+
+        in_buf_size = (size_t)read_buf_avail;
+        status = tinfl_decompress(&inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0);
+        read_buf_avail -= in_buf_size;
+        read_buf_ofs += in_buf_size;
+
+        if (out_buf_size)
+        {
+          if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size)
+          {
+            status = TINFL_STATUS_FAILED;
+            break;
+          }
+          file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size);
+          if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size)
+          {
+            status = TINFL_STATUS_FAILED;
+            break;
+          }
+        }
+      } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT));
+    }
+  }
+
+  if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)))
+  {
+    // Make sure the entire file was decompressed, and check its CRC.
+    if ((out_buf_ofs != file_stat.m_uncomp_size) || (file_crc32 != file_stat.m_crc32))
+      status = TINFL_STATUS_FAILED;
+  }
+
+  if (!pZip->m_pState->m_pMem)
+    pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
+  if (pWrite_buf)
+    pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf);
+
+  return status == TINFL_STATUS_DONE;
+}
+
+mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags)
+{
+  int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags);
+  if (file_index < 0)
+    return MZ_FALSE;
+  return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags);
+}
+
+#ifndef MINIZ_NO_STDIO
+static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n)
+{
+  (void)ofs; return MZ_FWRITE(pBuf, 1, n, (MZ_FILE*)pOpaque);
+}
+
+mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags)
+{
+  mz_bool status;
+  mz_zip_archive_file_stat file_stat;
+  MZ_FILE *pFile;
+  if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat))
+    return MZ_FALSE;
+  pFile = MZ_FOPEN(pDst_filename, "wb");
+  if (!pFile)
+    return MZ_FALSE;
+  status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags);
+  if (MZ_FCLOSE(pFile) == EOF)
+    return MZ_FALSE;
+#ifndef MINIZ_NO_TIME
+  if (status)
+    mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time);
+#endif
+  return status;
+}
+#endif // #ifndef MINIZ_NO_STDIO
+
+mz_bool mz_zip_reader_end(mz_zip_archive *pZip)
+{
+  if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING))
+    return MZ_FALSE;
+
+  if (pZip->m_pState)
+  {
+    mz_zip_internal_state *pState = pZip->m_pState; pZip->m_pState = NULL;
+    mz_zip_array_clear(pZip, &pState->m_central_dir);
+    mz_zip_array_clear(pZip, &pState->m_central_dir_offsets);
+    mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets);
+
+#ifndef MINIZ_NO_STDIO
+    if (pState->m_pFile)
+    {
+      MZ_FCLOSE(pState->m_pFile);
+      pState->m_pFile = NULL;
+    }
+#endif // #ifndef MINIZ_NO_STDIO
+
+    pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
+  }
+  pZip->m_zip_mode = MZ_ZIP_MODE_INVALID;
+
+  return MZ_TRUE;
+}
+
+#ifndef MINIZ_NO_STDIO
+mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags)
+{
+  int file_index = mz_zip_reader_locate_file(pZip, pArchive_filename, NULL, flags);
+  if (file_index < 0)
+    return MZ_FALSE;
+  return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags);
+}
+#endif
+
+// ------------------- .ZIP archive writing
+
+#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS
+
+static void mz_write_le16(mz_uint8 *p, mz_uint16 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); }
+static void mz_write_le32(mz_uint8 *p, mz_uint32 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); p[2] = (mz_uint8)(v >> 16); p[3] = (mz_uint8)(v >> 24); }
+#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v))
+#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v))
+
+mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size)
+{
+  if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID))
+    return MZ_FALSE;
+
+  if (pZip->m_file_offset_alignment)
+  {
+    // Ensure user specified file offset alignment is a power of 2.
+    if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1))
+      return MZ_FALSE;
+  }
+
+  if (!pZip->m_pAlloc) pZip->m_pAlloc = def_alloc_func;
+  if (!pZip->m_pFree) pZip->m_pFree = def_free_func;
+  if (!pZip->m_pRealloc) pZip->m_pRealloc = def_realloc_func;
+
+  pZip->m_zip_mode = MZ_ZIP_MODE_WRITING;
+  pZip->m_archive_size = existing_size;
+  pZip->m_central_directory_file_ofs = 0;
+  pZip->m_total_files = 0;
+
+  if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state))))
+    return MZ_FALSE;
+  memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state));
+  MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8));
+  MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32));
+  MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32));
+  return MZ_TRUE;
+}
+
+static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n)
+{
+  mz_zip_archive *pZip = (mz_zip_archive *)pOpaque;
+  mz_zip_internal_state *pState = pZip->m_pState;
+  mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size);
+#ifdef _MSC_VER
+  if ((!n) || ((0, sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF)))
+#else
+  if ((!n) || ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF)))
+#endif
+    return 0;
+  if (new_size > pState->m_mem_capacity)
+  {
+    void *pNew_block;
+    size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); while (new_capacity < new_size) new_capacity *= 2;
+    if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity)))
+      return 0;
+    pState->m_pMem = pNew_block; pState->m_mem_capacity = new_capacity;
+  }
+  memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n);
+  pState->m_mem_size = (size_t)new_size;
+  return n;
+}
+
+mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size)
+{
+  pZip->m_pWrite = mz_zip_heap_write_func;
+  pZip->m_pIO_opaque = pZip;
+  if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning))
+    return MZ_FALSE;
+  if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning)))
+  {
+    if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size)))
+    {
+      mz_zip_writer_end(pZip);
+      return MZ_FALSE;
+    }
+    pZip->m_pState->m_mem_capacity = initial_allocation_size;
+  }
+  return MZ_TRUE;
+}
+
+#ifndef MINIZ_NO_STDIO
+static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n)
+{
+  mz_zip_archive *pZip = (mz_zip_archive *)pOpaque;
+  mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile);
+  if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET))))
+    return 0;
+  return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile);
+}
+
+mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning)
+{
+  MZ_FILE *pFile;
+  pZip->m_pWrite = mz_zip_file_write_func;
+  pZip->m_pIO_opaque = pZip;
+  if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning))
+    return MZ_FALSE;
+  if (NULL == (pFile = MZ_FOPEN(pFilename, "wb")))
+  {
+    mz_zip_writer_end(pZip);
+    return MZ_FALSE;
+  }
+  pZip->m_pState->m_pFile = pFile;
+  if (size_to_reserve_at_beginning)
+  {
+    mz_uint64 cur_ofs = 0; char buf[4096]; MZ_CLEAR_OBJ(buf);
+    do
+    {
+      size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning);
+      if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n)
+      {
+        mz_zip_writer_end(pZip);
+        return MZ_FALSE;
+      }
+      cur_ofs += n; size_to_reserve_at_beginning -= n;
+    } while (size_to_reserve_at_beginning);
+  }
+  return MZ_TRUE;
+}
+#endif // #ifndef MINIZ_NO_STDIO
+
+mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename)
+{
+  mz_zip_internal_state *pState;
+  if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING))
+    return MZ_FALSE;
+  // No sense in trying to write to an archive that's already at the support max size
+  if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > 0xFFFFFFFF))
+    return MZ_FALSE;
+
+  pState = pZip->m_pState;
+
+  if (pState->m_pFile)
+  {
+#ifdef MINIZ_NO_STDIO
+    pFilename; return MZ_FALSE;
+#else
+    // Archive is being read from stdio - try to reopen as writable.
+    if (pZip->m_pIO_opaque != pZip)
+      return MZ_FALSE;
+    if (!pFilename)
+      return MZ_FALSE;
+    pZip->m_pWrite = mz_zip_file_write_func;
+    if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile)))
+    {
+      // The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it.
+      mz_zip_reader_end(pZip);
+      return MZ_FALSE;
+    }
+#endif // #ifdef MINIZ_NO_STDIO
+  }
+  else if (pState->m_pMem)
+  {
+    // Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback.
+    if (pZip->m_pIO_opaque != pZip)
+      return MZ_FALSE;
+    pState->m_mem_capacity = pState->m_mem_size;
+    pZip->m_pWrite = mz_zip_heap_write_func;
+  }
+  // Archive is being read via a user provided read function - make sure the user has specified a write function too.
+  else if (!pZip->m_pWrite)
+    return MZ_FALSE;
+
+  // Start writing new files at the archive's current central directory location.
+  pZip->m_archive_size = pZip->m_central_directory_file_ofs;
+  pZip->m_zip_mode = MZ_ZIP_MODE_WRITING;
+  pZip->m_central_directory_file_ofs = 0;
+
+  return MZ_TRUE;
+}
+
+mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags)
+{
+  return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0);
+}
+
+typedef struct
+{
+  mz_zip_archive *m_pZip;
+  mz_uint64 m_cur_archive_file_ofs;
+  mz_uint64 m_comp_size;
+} mz_zip_writer_add_state;
+
+static mz_bool mz_zip_writer_add_put_buf_callback(const void* pBuf, int len, void *pUser)
+{
+  mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser;
+  if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len)
+    return MZ_FALSE;
+  pState->m_cur_archive_file_ofs += len;
+  pState->m_comp_size += len;
+  return MZ_TRUE;
+}
+
+static mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date)
+{
+  (void)pZip;
+  memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE);
+  MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG);
+  MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0);
+  MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags);
+  MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method);
+  MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time);
+  MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date);
+  MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32);
+  MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, comp_size);
+  MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, uncomp_size);
+  MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size);
+  MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size);
+  return MZ_TRUE;
+}
+
+static mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes)
+{
+  (void)pZip;
+  memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE);
+  MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG);
+  MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0);
+  MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags);
+  MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method);
+  MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time);
+  MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date);
+  MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32);
+  MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, comp_size);
+  MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, uncomp_size);
+  MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size);
+  MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size);
+  MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size);
+  MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes);
+  MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_header_ofs);
+  return MZ_TRUE;
+}
+
+static mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size, const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes)
+{
+  mz_zip_internal_state *pState = pZip->m_pState;
+  mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size;
+  size_t orig_central_dir_size = pState->m_central_dir.m_size;
+  mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE];
+
+  // No zip64 support yet
+  if ((local_header_ofs > 0xFFFFFFFF) || (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + comment_size) > 0xFFFFFFFF))
+    return MZ_FALSE;
+
+  if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, extra_size, comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_header_ofs, ext_attributes))
+    return MZ_FALSE;
+
+  if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) ||
+      (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) ||
+      (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) ||
+      (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) ||
+      (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &central_dir_ofs, 1)))
+  {
+    // Try to push the central directory array back into its original state.
+    mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
+    return MZ_FALSE;
+  }
+
+  return MZ_TRUE;
+}
+
+static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name)
+{
+  // Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes.
+  if (*pArchive_name == '/')
+    return MZ_FALSE;
+  while (*pArchive_name)
+  {
+    if ((*pArchive_name == '\\') || (*pArchive_name == ':'))
+      return MZ_FALSE;
+    pArchive_name++;
+  }
+  return MZ_TRUE;
+}
+
+static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip)
+{
+  mz_uint32 n;
+  if (!pZip->m_file_offset_alignment)
+    return 0;
+  n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1));
+  return (pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1);
+}
+
+static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n)
+{
+  char buf[4096];
+  memset(buf, 0, MZ_MIN(sizeof(buf), n));
+  while (n)
+  {
+    mz_uint32 s = MZ_MIN(sizeof(buf), n);
+    if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s)
+      return MZ_FALSE;
+    cur_file_ofs += s; n -= s;
+  }
+  return MZ_TRUE;
+}
+
+mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32)
+{
+  mz_uint16 method = 0, dos_time = 0, dos_date = 0;
+  mz_uint level, ext_attributes = 0, num_alignment_padding_bytes;
+  mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0;
+  size_t archive_name_size;
+  mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE];
+  tdefl_compressor *pComp = NULL;
+  mz_bool store_data_uncompressed;
+  mz_zip_internal_state *pState;
+
+  if ((int)level_and_flags < 0)
+    level_and_flags = MZ_DEFAULT_LEVEL;
+  level = level_and_flags & 0xF;
+  store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA));
+
+  if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (pZip->m_total_files == 0xFFFF) || (level > MZ_UBER_COMPRESSION))
+    return MZ_FALSE;
+
+  pState = pZip->m_pState;
+
+  if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size))
+    return MZ_FALSE;
+  // No zip64 support yet
+  if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF))
+    return MZ_FALSE;
+  if (!mz_zip_writer_validate_archive_name(pArchive_name))
+    return MZ_FALSE;
+
+#ifndef MINIZ_NO_TIME
+  {
+    time_t cur_time; time(&cur_time);
+    mz_zip_time_to_dos_time(cur_time, &dos_time, &dos_date);
+  }
+#endif // #ifndef MINIZ_NO_TIME
+
+  archive_name_size = strlen(pArchive_name);
+  if (archive_name_size > 0xFFFF)
+    return MZ_FALSE;
+
+  num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip);
+
+  // no zip64 support yet
+  if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF))
+    return MZ_FALSE;
+
+  if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/'))
+  {
+    // Set DOS Subdirectory attribute bit.
+    ext_attributes |= 0x10;
+    // Subdirectories cannot contain data.
+    if ((buf_size) || (uncomp_size))
+      return MZ_FALSE;
+  }
+
+  // Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.)
+  if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size)) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1)))
+    return MZ_FALSE;
+
+  if ((!store_data_uncompressed) && (buf_size))
+  {
+    if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor))))
+      return MZ_FALSE;
+  }
+
+  if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes + sizeof(local_dir_header)))
+  {
+    pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
+    return MZ_FALSE;
+  }
+  local_dir_header_ofs += num_alignment_padding_bytes;
+  if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); }
+  cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header);
+
+  MZ_CLEAR_OBJ(local_dir_header);
+  if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size)
+  {
+    pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
+    return MZ_FALSE;
+  }
+  cur_archive_file_ofs += archive_name_size;
+
+  if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA))
+  {
+    uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8*)pBuf, buf_size);
+    uncomp_size = buf_size;
+    if (uncomp_size <= 3)
+    {
+      level = 0;
+      store_data_uncompressed = MZ_TRUE;
+    }
+  }
+
+  if (store_data_uncompressed)
+  {
+    if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size)
+    {
+      pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
+      return MZ_FALSE;
+    }
+
+    cur_archive_file_ofs += buf_size;
+    comp_size = buf_size;
+
+    if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)
+      method = MZ_DEFLATED;
+  }
+  else if (buf_size)
+  {
+    mz_zip_writer_add_state state;
+
+    state.m_pZip = pZip;
+    state.m_cur_archive_file_ofs = cur_archive_file_ofs;
+    state.m_comp_size = 0;
+
+    if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) ||
+        (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE))
+    {
+      pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
+      return MZ_FALSE;
+    }
+
+    comp_size = state.m_comp_size;
+    cur_archive_file_ofs = state.m_cur_archive_file_ofs;
+
+    method = MZ_DEFLATED;
+  }
+
+  pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
+  pComp = NULL;
+
+  // no zip64 support yet
+  if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF))
+    return MZ_FALSE;
+
+  if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date))
+    return MZ_FALSE;
+
+  if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header))
+    return MZ_FALSE;
+
+  if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date, local_dir_header_ofs, ext_attributes))
+    return MZ_FALSE;
+
+  pZip->m_total_files++;
+  pZip->m_archive_size = cur_archive_file_ofs;
+
+  return MZ_TRUE;
+}
+
+mz_bool mz_zip_writer_add_mem_ex_with_time(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, mz_uint16 dos_time, mz_uint16 dos_date)
+{
+  mz_uint16 method = 0;
+  mz_uint level, ext_attributes = 0, num_alignment_padding_bytes;
+  mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0;
+  size_t archive_name_size;
+  mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE];
+  tdefl_compressor *pComp = NULL;
+  mz_bool store_data_uncompressed;
+  mz_zip_internal_state *pState;
+
+  if ((int)level_and_flags < 0)
+    level_and_flags = MZ_DEFAULT_LEVEL;
+  level = level_and_flags & 0xF;
+  store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA));
+
+  if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (pZip->m_total_files == 0xFFFF) || (level > MZ_UBER_COMPRESSION))
+    return MZ_FALSE;
+
+  pState = pZip->m_pState;
+
+  if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size))
+    return MZ_FALSE;
+  // No zip64 support yet
+  if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF))
+    return MZ_FALSE;
+  if (!mz_zip_writer_validate_archive_name(pArchive_name))
+    return MZ_FALSE;
+
+  archive_name_size = strlen(pArchive_name);
+  if (archive_name_size > 0xFFFF)
+    return MZ_FALSE;
+
+  num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip);
+
+  // no zip64 support yet
+  if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF))
+    return MZ_FALSE;
+
+  if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/'))
+  {
+    // Set DOS Subdirectory attribute bit.
+    ext_attributes |= 0x10;
+    // Subdirectories cannot contain data.
+    if ((buf_size) || (uncomp_size))
+      return MZ_FALSE;
+  }
+
+  // Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.)
+  if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size)) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1)))
+    return MZ_FALSE;
+
+  if ((!store_data_uncompressed) && (buf_size))
+  {
+    if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor))))
+      return MZ_FALSE;
+  }
+
+  if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes + sizeof(local_dir_header)))
+  {
+    pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
+    return MZ_FALSE;
+  }
+  local_dir_header_ofs += num_alignment_padding_bytes;
+  if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); }
+  cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header);
+
+  MZ_CLEAR_OBJ(local_dir_header);
+  if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size)
+  {
+    pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
+    return MZ_FALSE;
+  }
+  cur_archive_file_ofs += archive_name_size;
+
+  if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA))
+  {
+    uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8*)pBuf, buf_size);
+    uncomp_size = buf_size;
+    if (uncomp_size <= 3)
+    {
+      level = 0;
+      store_data_uncompressed = MZ_TRUE;
+    }
+  }
+
+  if (store_data_uncompressed)
+  {
+    if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size)
+    {
+      pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
+      return MZ_FALSE;
+    }
+
+    cur_archive_file_ofs += buf_size;
+    comp_size = buf_size;
+
+    if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)
+      method = MZ_DEFLATED;
+  }
+  else if (buf_size)
+  {
+    mz_zip_writer_add_state state;
+
+    state.m_pZip = pZip;
+    state.m_cur_archive_file_ofs = cur_archive_file_ofs;
+    state.m_comp_size = 0;
+
+    if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) ||
+        (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE))
+    {
+      pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
+      return MZ_FALSE;
+    }
+
+    comp_size = state.m_comp_size;
+    cur_archive_file_ofs = state.m_cur_archive_file_ofs;
+
+    method = MZ_DEFLATED;
+  }
+
+  pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
+  pComp = NULL;
+
+  // no zip64 support yet
+  if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF))
+    return MZ_FALSE;
+
+  if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date))
+    return MZ_FALSE;
+
+  if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header))
+    return MZ_FALSE;
+
+  if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date, local_dir_header_ofs, ext_attributes))
+    return MZ_FALSE;
+
+  pZip->m_total_files++;
+  pZip->m_archive_size = cur_archive_file_ofs;
+
+  return MZ_TRUE;
+}
+
+#ifndef MINIZ_NO_STDIO
+mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags)
+{
+  mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes;
+  mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0;
+  mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = 0, comp_size = 0;
+  size_t archive_name_size;
+  mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE];
+  MZ_FILE *pSrc_file = NULL;
+
+  if ((int)level_and_flags < 0)
+    level_and_flags = MZ_DEFAULT_LEVEL;
+  level = level_and_flags & 0xF;
+
+  if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION))
+    return MZ_FALSE;
+  if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)
+    return MZ_FALSE;
+  if (!mz_zip_writer_validate_archive_name(pArchive_name))
+    return MZ_FALSE;
+
+  archive_name_size = strlen(pArchive_name);
+  if (archive_name_size > 0xFFFF)
+    return MZ_FALSE;
+
+  num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip);
+
+  // no zip64 support yet
+  if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF))
+    return MZ_FALSE;
+
+  if (!mz_zip_get_file_modified_time(pSrc_filename, &dos_time, &dos_date))
+    return MZ_FALSE;
+
+  pSrc_file = MZ_FOPEN(pSrc_filename, "rb");
+  if (!pSrc_file)
+    return MZ_FALSE;
+  MZ_FSEEK64(pSrc_file, 0, SEEK_END);
+  uncomp_size = MZ_FTELL64(pSrc_file);
+  MZ_FSEEK64(pSrc_file, 0, SEEK_SET);
+
+  if (uncomp_size > 0xFFFFFFFF)
+  {
+    // No zip64 support yet
+    MZ_FCLOSE(pSrc_file);
+    return MZ_FALSE;
+  }
+  if (uncomp_size <= 3)
+    level = 0;
+
+  if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes + sizeof(local_dir_header)))
+  {
+    MZ_FCLOSE(pSrc_file);
+    return MZ_FALSE;
+  }
+  local_dir_header_ofs += num_alignment_padding_bytes;
+  if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); }
+  cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header);
+
+  MZ_CLEAR_OBJ(local_dir_header);
+  if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size)
+  {
+    MZ_FCLOSE(pSrc_file);
+    return MZ_FALSE;
+  }
+  cur_archive_file_ofs += archive_name_size;
+
+  if (uncomp_size)
+  {
+    mz_uint64 uncomp_remaining = uncomp_size;
+    void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE);
+    if (!pRead_buf)
+    {
+      MZ_FCLOSE(pSrc_file);
+      return MZ_FALSE;
+    }
+
+    if (!level)
+    {
+      while (uncomp_remaining)
+      {
+        mz_uint n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, uncomp_remaining);
+        if ((MZ_FREAD(pRead_buf, 1, n, pSrc_file) != n) || (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n))
+        {
+          pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
+          MZ_FCLOSE(pSrc_file);
+          return MZ_FALSE;
+        }
+        uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n);
+        uncomp_remaining -= n;
+        cur_archive_file_ofs += n;
+      }
+      comp_size = uncomp_size;
+    }
+    else
+    {
+      mz_bool result = MZ_FALSE;
+      mz_zip_writer_add_state state;
+      tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor));
+      if (!pComp)
+      {
+        pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
+        MZ_FCLOSE(pSrc_file);
+        return MZ_FALSE;
+      }
+
+      state.m_pZip = pZip;
+      state.m_cur_archive_file_ofs = cur_archive_file_ofs;
+      state.m_comp_size = 0;
+
+      if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY)
+      {
+        pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
+        pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
+        MZ_FCLOSE(pSrc_file);
+        return MZ_FALSE;
+      }
+
+      for ( ; ; )
+      {
+        size_t in_buf_size = (mz_uint32)MZ_MIN(uncomp_remaining, MZ_ZIP_MAX_IO_BUF_SIZE);
+        tdefl_status status;
+
+        if (MZ_FREAD(pRead_buf, 1, in_buf_size, pSrc_file) != in_buf_size)
+          break;
+
+        uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, in_buf_size);
+        uncomp_remaining -= in_buf_size;
+
+        status = tdefl_compress_buffer(pComp, pRead_buf, in_buf_size, uncomp_remaining ? TDEFL_NO_FLUSH : TDEFL_FINISH);
+        if (status == TDEFL_STATUS_DONE)
+        {
+          result = MZ_TRUE;
+          break;
+        }
+        else if (status != TDEFL_STATUS_OKAY)
+          break;
+      }
+
+      pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
+
+      if (!result)
+      {
+        pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
+        MZ_FCLOSE(pSrc_file);
+        return MZ_FALSE;
+      }
+
+      comp_size = state.m_comp_size;
+      cur_archive_file_ofs = state.m_cur_archive_file_ofs;
+
+      method = MZ_DEFLATED;
+    }
+
+    pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
+  }
+
+  MZ_FCLOSE(pSrc_file); pSrc_file = NULL;
+
+  // no zip64 support yet
+  if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF))
+    return MZ_FALSE;
+
+  if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date))
+    return MZ_FALSE;
+
+  if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header))
+    return MZ_FALSE;
+
+  if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date, local_dir_header_ofs, ext_attributes))
+    return MZ_FALSE;
+
+  pZip->m_total_files++;
+  pZip->m_archive_size = cur_archive_file_ofs;
+
+  return MZ_TRUE;
+}
+#endif // #ifndef MINIZ_NO_STDIO
+
+mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint file_index)
+{
+  mz_uint n, bit_flags, num_alignment_padding_bytes;
+  mz_uint64 comp_bytes_remaining, local_dir_header_ofs;
+  mz_uint64 cur_src_file_ofs, cur_dst_file_ofs;
+  mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;
+  mz_uint8 central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE];
+  size_t orig_central_dir_size;
+  mz_zip_internal_state *pState;
+  void *pBuf; const mz_uint8 *pSrc_central_header;
+
+  if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING))
+    return MZ_FALSE;
+  if (NULL == (pSrc_central_header = mz_zip_reader_get_cdh(pSource_zip, file_index)))
+    return MZ_FALSE;
+  pState = pZip->m_pState;
+
+  num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip);
+
+  // no zip64 support yet
+  if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF))
+    return MZ_FALSE;
+
+  cur_src_file_ofs = MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS);
+  cur_dst_file_ofs = pZip->m_archive_size;
+
+  if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
+    return MZ_FALSE;
+  if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)
+    return MZ_FALSE;
+  cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE;
+
+  if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes))
+    return MZ_FALSE;
+  cur_dst_file_ofs += num_alignment_padding_bytes;
+  local_dir_header_ofs = cur_dst_file_ofs;
+  if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); }
+
+  if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
+    return MZ_FALSE;
+  cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE;
+
+  n = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);
+  comp_bytes_remaining = n + MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS);
+
+  if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)MZ_MAX(sizeof(mz_uint32) * 4, MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining)))))
+    return MZ_FALSE;
+
+  while (comp_bytes_remaining)
+  {
+    n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining);
+    if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n)
+    {
+      pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
+      return MZ_FALSE;
+    }
+    cur_src_file_ofs += n;
+
+    if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n)
+    {
+      pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
+      return MZ_FALSE;
+    }
+    cur_dst_file_ofs += n;
+
+    comp_bytes_remaining -= n;
+  }
+
+  bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS);
+  if (bit_flags & 8)
+  {
+    // Copy data descriptor
+    if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4)
+    {
+      pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
+      return MZ_FALSE;
+    }
+
+    n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == 0x08074b50) ? 4 : 3);
+    if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n)
+    {
+      pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
+      return MZ_FALSE;
+    }
+
+    cur_src_file_ofs += n;
+    cur_dst_file_ofs += n;
+  }
+  pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
+
+  // no zip64 support yet
+  if (cur_dst_file_ofs > 0xFFFFFFFF)
+    return MZ_FALSE;
+
+  orig_central_dir_size = pState->m_central_dir.m_size;
+
+  memcpy(central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE);
+  MZ_WRITE_LE32(central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs);
+  if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE))
+    return MZ_FALSE;
+
+  n = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS);
+  if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n))
+  {
+    mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
+    return MZ_FALSE;
+  }
+
+  if (pState->m_central_dir.m_size > 0xFFFFFFFF)
+    return MZ_FALSE;
+  n = (mz_uint32)orig_central_dir_size;
+  if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1))
+  {
+    mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
+    return MZ_FALSE;
+  }
+
+  pZip->m_total_files++;
+  pZip->m_archive_size = cur_dst_file_ofs;
+
+  return MZ_TRUE;
+}
+
+mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip)
+{
+  mz_zip_internal_state *pState;
+  mz_uint64 central_dir_ofs, central_dir_size;
+  mz_uint8 hdr[MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE];
+
+  if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING))
+    return MZ_FALSE;
+
+  pState = pZip->m_pState;
+
+  // no zip64 support yet
+  if ((pZip->m_total_files > 0xFFFF) || ((pZip->m_archive_size + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF))
+    return MZ_FALSE;
+
+  central_dir_ofs = 0;
+  central_dir_size = 0;
+  if (pZip->m_total_files)
+  {
+    // Write central directory
+    central_dir_ofs = pZip->m_archive_size;
+    central_dir_size = pState->m_central_dir.m_size;
+    pZip->m_central_directory_file_ofs = central_dir_ofs;
+    if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, (size_t)central_dir_size) != central_dir_size)
+      return MZ_FALSE;
+    pZip->m_archive_size += central_dir_size;
+  }
+
+  // Write end of central directory record
+  MZ_CLEAR_OBJ(hdr);
+  MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG);
+  MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files);
+  MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files);
+  MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, central_dir_size);
+  MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, central_dir_ofs);
+
+  if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, sizeof(hdr)) != sizeof(hdr))
+    return MZ_FALSE;
+#ifndef MINIZ_NO_STDIO
+  if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF))
+    return MZ_FALSE;
+#endif // #ifndef MINIZ_NO_STDIO
+
+  pZip->m_archive_size += sizeof(hdr);
+
+  pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED;
+  return MZ_TRUE;
+}
+
+mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, size_t *pSize)
+{
+  if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pSize))
+    return MZ_FALSE;
+  if (pZip->m_pWrite != mz_zip_heap_write_func)
+    return MZ_FALSE;
+  if (!mz_zip_writer_finalize_archive(pZip))
+    return MZ_FALSE;
+
+  *pBuf = pZip->m_pState->m_pMem;
+  *pSize = pZip->m_pState->m_mem_size;
+  pZip->m_pState->m_pMem = NULL;
+  pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0;
+  return MZ_TRUE;
+}
+
+mz_bool mz_zip_writer_end(mz_zip_archive *pZip)
+{
+  mz_zip_internal_state *pState;
+  mz_bool status = MZ_TRUE;
+  if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED)))
+    return MZ_FALSE;
+
+  pState = pZip->m_pState;
+  pZip->m_pState = NULL;
+  mz_zip_array_clear(pZip, &pState->m_central_dir);
+  mz_zip_array_clear(pZip, &pState->m_central_dir_offsets);
+  mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets);
+
+#ifndef MINIZ_NO_STDIO
+  if (pState->m_pFile)
+  {
+    MZ_FCLOSE(pState->m_pFile);
+    pState->m_pFile = NULL;
+  }
+#endif // #ifndef MINIZ_NO_STDIO
+
+  if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem))
+  {
+    pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem);
+    pState->m_pMem = NULL;
+  }
+
+  pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
+  pZip->m_zip_mode = MZ_ZIP_MODE_INVALID;
+  return status;
+}
+
+#ifndef MINIZ_NO_STDIO
+mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags)
+{
+  mz_bool status, created_new_archive = MZ_FALSE;
+  mz_zip_archive zip_archive;
+  struct MZ_FILE_STAT_STRUCT file_stat;
+  MZ_CLEAR_OBJ(zip_archive);
+  if ((int)level_and_flags < 0)
+     level_and_flags = MZ_DEFAULT_LEVEL;
+  if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION))
+    return MZ_FALSE;
+  if (!mz_zip_writer_validate_archive_name(pArchive_name))
+    return MZ_FALSE;
+  if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0)
+  {
+    // Create a new archive.
+    if (!mz_zip_writer_init_file(&zip_archive, pZip_filename, 0))
+      return MZ_FALSE;
+    created_new_archive = MZ_TRUE;
+  }
+  else
+  {
+    // Append to an existing archive.
+    if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY))
+      return MZ_FALSE;
+    if (!mz_zip_writer_init_from_reader(&zip_archive, pZip_filename))
+    {
+      mz_zip_reader_end(&zip_archive);
+      return MZ_FALSE;
+    }
+  }
+  status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0);
+  // Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.)
+  if (!mz_zip_writer_finalize_archive(&zip_archive))
+    status = MZ_FALSE;
+  if (!mz_zip_writer_end(&zip_archive))
+    status = MZ_FALSE;
+  if ((!status) && (created_new_archive))
+  {
+    // It's a new archive and something went wrong, so just delete it.
+    int ignoredStatus = MZ_DELETE_FILE(pZip_filename);
+    (void)ignoredStatus;
+  }
+  return status;
+}
+
+mz_bool mz_zip_add_mem_to_archive_file_in_place_with_time(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint16 dos_time, mz_uint16 dos_date)
+{
+  mz_bool status, created_new_archive = MZ_FALSE;
+  mz_zip_archive zip_archive;
+  struct MZ_FILE_STAT_STRUCT file_stat;
+  MZ_CLEAR_OBJ(zip_archive);
+  if ((int)level_and_flags < 0)
+     level_and_flags = MZ_DEFAULT_LEVEL;
+  if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION))
+    return MZ_FALSE;
+  if (!mz_zip_writer_validate_archive_name(pArchive_name))
+    return MZ_FALSE;
+  if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0)
+  {
+    // Create a new archive.
+    if (!mz_zip_writer_init_file(&zip_archive, pZip_filename, 0))
+      return MZ_FALSE;
+    created_new_archive = MZ_TRUE;
+  }
+  else
+  {
+    // Append to an existing archive.
+    if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY))
+      return MZ_FALSE;
+    if (!mz_zip_writer_init_from_reader(&zip_archive, pZip_filename))
+    {
+      mz_zip_reader_end(&zip_archive);
+      return MZ_FALSE;
+    }
+  }
+  status = mz_zip_writer_add_mem_ex_with_time(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0, dos_time, dos_date);
+  // Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.)
+  if (!mz_zip_writer_finalize_archive(&zip_archive))
+    status = MZ_FALSE;
+  if (!mz_zip_writer_end(&zip_archive))
+    status = MZ_FALSE;
+  if ((!status) && (created_new_archive))
+  {
+    // It's a new archive and something went wrong, so just delete it.
+    int ignoredStatus = MZ_DELETE_FILE(pZip_filename);
+    (void)ignoredStatus;
+  }
+  return status;
+}
+
+
+void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags)
+{
+  int file_index;
+  mz_zip_archive zip_archive;
+  void *p = NULL;
+
+  if (pSize)
+    *pSize = 0;
+
+  if ((!pZip_filename) || (!pArchive_name))
+    return NULL;
+
+  MZ_CLEAR_OBJ(zip_archive);
+  if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY))
+    return NULL;
+
+  if ((file_index = mz_zip_reader_locate_file(&zip_archive, pArchive_name, NULL, flags)) >= 0)
+    p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags);
+
+  mz_zip_reader_end(&zip_archive);
+  return p;
+}
+
+#endif // #ifndef MINIZ_NO_STDIO
+
+#endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS
+
+#endif // #ifndef MINIZ_NO_ARCHIVE_APIS
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // MINIZ_HEADER_FILE_ONLY
+
+/*
+  This is free and unencumbered software released into the public domain.
+
+  Anyone is free to copy, modify, publish, use, compile, sell, or
+  distribute this software, either in source code form or as a compiled
+  binary, for any purpose, commercial or non-commercial, and by any
+  means.
+
+  In jurisdictions that recognize copyright laws, the author or authors
+  of this software dedicate any and all copyright interest in the
+  software to the public domain. We make this dedication for the benefit
+  of the public at large and to the detriment of our heirs and
+  successors. We intend this dedication to be an overt act of
+  relinquishment in perpetuity of all present and future rights to this
+  software under copyright law.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+  OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+  OTHER DEALINGS IN THE SOFTWARE.
+
+  For more information, please refer to <http://unlicense.org/>
+*/
diff --git a/tools/quake3/common/miniz.h b/tools/quake3/common/miniz.h
new file mode 100644 (file)
index 0000000..7e1e04c
--- /dev/null
@@ -0,0 +1,929 @@
+/* miniz.c v1.15 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing
+   See "unlicense" statement at the end of this file.
+   Rich Geldreich <richgel99@gmail.com>, last updated Oct. 13, 2013
+   Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt
+
+   Most API's defined in miniz.c are optional. For example, to disable the archive related functions just define
+   MINIZ_NO_ARCHIVE_APIS, or to get rid of all stdio usage define MINIZ_NO_STDIO (see the list below for more macros).
+
+   * Change History
+     10/13/13 v1.15 r4 - Interim bugfix release while I work on the next major release with Zip64 support (almost there!):
+       - Critical fix for the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY bug (thanks kahmyong.moon@hp.com) which could cause locate files to not find files. This bug
+        would only have occured in earlier versions if you explicitly used this flag, OR if you used mz_zip_extract_archive_file_to_heap() or mz_zip_add_mem_to_archive_file_in_place()
+        (which used this flag). If you can't switch to v1.15 but want to fix this bug, just remove the uses of this flag from both helper funcs (and of course don't use the flag).
+       - Bugfix in mz_zip_reader_extract_to_mem_no_alloc() from kymoon when pUser_read_buf is not NULL and compressed size is > uncompressed size
+       - Fixing mz_zip_reader_extract_*() funcs so they don't try to extract compressed data from directory entries, to account for weird zipfiles which contain zero-size compressed data on dir entries.
+         Hopefully this fix won't cause any issues on weird zip archives, because it assumes the low 16-bits of zip external attributes are DOS attributes (which I believe they always are in practice).
+       - Fixing mz_zip_reader_is_file_a_directory() so it doesn't check the internal attributes, just the filename and external attributes
+       - mz_zip_reader_init_file() - missing MZ_FCLOSE() call if the seek failed
+       - Added cmake support for Linux builds which builds all the examples, tested with clang v3.3 and gcc v4.6.
+       - Clang fix for tdefl_write_image_to_png_file_in_memory() from toffaletti
+       - Merged MZ_FORCEINLINE fix from hdeanclark
+       - Fix <time.h> include before config #ifdef, thanks emil.brink
+       - Added tdefl_write_image_to_png_file_in_memory_ex(): supports Y flipping (super useful for OpenGL apps), and explicit control over the compression level (so you can
+        set it to 1 for real-time compression).
+       - Merged in some compiler fixes from paulharris's github repro.
+       - Retested this build under Windows (VS 2010, including static analysis), tcc  0.9.26, gcc v4.6 and clang v3.3.
+       - Added example6.c, which dumps an image of the mandelbrot set to a PNG file.
+       - Modified example2 to help test the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY flag more.
+       - In r3: Bugfix to mz_zip_writer_add_file() found during merge: Fix possible src file fclose() leak if alignment bytes+local header file write faiiled
+       - In r4: Minor bugfix to mz_zip_writer_add_from_zip_reader(): Was pushing the wrong central dir header offset, appears harmless in this release, but it became a problem in the zip64 branch
+     5/20/12 v1.14 - MinGW32/64 GCC 4.6.1 compiler fixes: added MZ_FORCEINLINE, #include <time.h> (thanks fermtect).
+     5/19/12 v1.13 - From jason@cornsyrup.org and kelwert@mtu.edu - Fix mz_crc32() so it doesn't compute the wrong CRC-32's when mz_ulong is 64-bit.
+       - Temporarily/locally slammed in "typedef unsigned long mz_ulong" and re-ran a randomized regression test on ~500k files.
+       - Eliminated a bunch of warnings when compiling with GCC 32-bit/64.
+       - Ran all examples, miniz.c, and tinfl.c through MSVC 2008's /analyze (static analysis) option and fixed all warnings (except for the silly
+        "Use of the comma-operator in a tested expression.." analysis warning, which I purposely use to work around a MSVC compiler warning).
+       - Created 32-bit and 64-bit Codeblocks projects/workspace. Built and tested Linux executables. The codeblocks workspace is compatible with Linux+Win32/x64.
+       - Added miniz_tester solution/project, which is a useful little app derived from LZHAM's tester app that I use as part of the regression test.
+       - Ran miniz.c and tinfl.c through another series of regression testing on ~500,000 files and archives.
+       - Modified example5.c so it purposely disables a bunch of high-level functionality (MINIZ_NO_STDIO, etc.). (Thanks to corysama for the MINIZ_NO_STDIO bug report.)
+       - Fix ftell() usage in examples so they exit with an error on files which are too large (a limitation of the examples, not miniz itself).
+     4/12/12 v1.12 - More comments, added low-level example5.c, fixed a couple minor level_and_flags issues in the archive API's.
+      level_and_flags can now be set to MZ_DEFAULT_COMPRESSION. Thanks to Bruce Dawson <bruced@valvesoftware.com> for the feedback/bug report.
+     5/28/11 v1.11 - Added statement from unlicense.org
+     5/27/11 v1.10 - Substantial compressor optimizations:
+      - Level 1 is now ~4x faster than before. The L1 compressor's throughput now varies between 70-110MB/sec. on a
+      - Core i7 (actual throughput varies depending on the type of data, and x64 vs. x86).
+      - Improved baseline L2-L9 compression perf. Also, greatly improved compression perf. issues on some file types.
+      - Refactored the compression code for better readability and maintainability.
+      - Added level 10 compression level (L10 has slightly better ratio than level 9, but could have a potentially large
+       drop in throughput on some files).
+     5/15/11 v1.09 - Initial stable release.
+
+   * Low-level Deflate/Inflate implementation notes:
+
+     Compression: Use the "tdefl" API's. The compressor supports raw, static, and dynamic blocks, lazy or
+     greedy parsing, match length filtering, RLE-only, and Huffman-only streams. It performs and compresses
+     approximately as well as zlib.
+
+     Decompression: Use the "tinfl" API's. The entire decompressor is implemented as a single function
+     coroutine: see tinfl_decompress(). It supports decompression into a 32KB (or larger power of 2) wrapping buffer, or into a memory
+     block large enough to hold the entire file.
+
+     The low-level tdefl/tinfl API's do not make any use of dynamic memory allocation.
+
+   * zlib-style API notes:
+
+     miniz.c implements a fairly large subset of zlib. There's enough functionality present for it to be a drop-in
+     zlib replacement in many apps:
+        The z_stream struct, optional memory allocation callbacks
+        deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound
+        inflateInit/inflateInit2/inflate/inflateEnd
+        compress, compress2, compressBound, uncompress
+        CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly routines.
+        Supports raw deflate streams or standard zlib streams with adler-32 checking.
+
+     Limitations:
+      The callback API's are not implemented yet. No support for gzip headers or zlib static dictionaries.
+      I've tried to closely emulate zlib's various flavors of stream flushing and return status codes, but
+      there are no guarantees that miniz.c pulls this off perfectly.
+
+   * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, originally written by
+     Alex Evans. Supports 1-4 bytes/pixel images.
+
+   * ZIP archive API notes:
+
+     The ZIP archive API's where designed with simplicity and efficiency in mind, with just enough abstraction to
+     get the job done with minimal fuss. There are simple API's to retrieve file information, read files from
+     existing archives, create new archives, append new files to existing archives, or clone archive data from
+     one archive to another. It supports archives located in memory or the heap, on disk (using stdio.h),
+     or you can specify custom file read/write callbacks.
+
+     - Archive reading: Just call this function to read a single file from a disk archive:
+
+      void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name,
+        size_t *pSize, mz_uint zip_flags);
+
+     For more complex cases, use the "mz_zip_reader" functions. Upon opening an archive, the entire central
+     directory is located and read as-is into memory, and subsequent file access only occurs when reading individual files.
+
+     - Archives file scanning: The simple way is to use this function to scan a loaded archive for a specific file:
+
+     int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags);
+
+     The locate operation can optionally check file comments too, which (as one example) can be used to identify
+     multiple versions of the same file in an archive. This function uses a simple linear search through the central
+     directory, so it's not very fast.
+
+     Alternately, you can iterate through all the files in an archive (using mz_zip_reader_get_num_files()) and
+     retrieve detailed info on each file by calling mz_zip_reader_file_stat().
+
+     - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer immediately writes compressed file data
+     to disk and builds an exact image of the central directory in memory. The central directory image is written
+     all at once at the end of the archive file when the archive is finalized.
+
+     The archive writer can optionally align each file's local header and file data to any power of 2 alignment,
+     which can be useful when the archive will be read from optical media. Also, the writer supports placing
+     arbitrary data blobs at the very beginning of ZIP archives. Archives written using either feature are still
+     readable by any ZIP tool.
+
+     - Archive appending: The simple way to add a single file to an archive is to call this function:
+
+      mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name,
+        const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags);
+
+     The archive will be created if it doesn't already exist, otherwise it'll be appended to.
+     Note the appending is done in-place and is not an atomic operation, so if something goes wrong
+     during the operation it's possible the archive could be left without a central directory (although the local
+     file headers and file data will be fine, so the archive will be recoverable).
+
+     For more complex archive modification scenarios:
+     1. The safest way is to use a mz_zip_reader to read the existing archive, cloning only those bits you want to
+     preserve into a new archive using using the mz_zip_writer_add_from_zip_reader() function (which compiles the
+     compressed file data as-is). When you're done, delete the old archive and rename the newly written archive, and
+     you're done. This is safe but requires a bunch of temporary disk space or heap memory.
+
+     2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using mz_zip_writer_init_from_reader(),
+     append new files as needed, then finalize the archive which will write an updated central directory to the
+     original archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() does.) There's a
+     possibility that the archive's central directory could be lost with this method if anything goes wrong, though.
+
+     - ZIP archive support limitations:
+     No zip64 or spanning support. Extraction functions can only handle unencrypted, stored or deflated files.
+     Requires streams capable of seeking.
+
+   * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the
+     below header, or create miniz.h, #define MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it.
+
+   * Important: For best perf. be sure to customize the below macros for your target platform:
+     #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1
+     #define MINIZ_LITTLE_ENDIAN 1
+     #define MINIZ_HAS_64BIT_REGISTERS 1
+
+   * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before including miniz.c to ensure miniz
+     uses the 64-bit variants: fopen64(), stat64(), etc. Otherwise you won't be able to process large files
+     (i.e. 32-bit stat() fails for me on files > 0x7FFFFFFF bytes).
+*/
+
+#ifndef MINIZ_HEADER_INCLUDED
+#define MINIZ_HEADER_INCLUDED
+
+#include <stdlib.h>
+
+// Defines to completely disable specific portions of miniz.c:
+// If all macros here are defined the only functionality remaining will be CRC-32, adler-32, tinfl, and tdefl.
+
+// Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O.
+//#define MINIZ_NO_STDIO
+
+// If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or
+// get/set file times, and the C run-time funcs that get/set times won't be called.
+// The current downside is the times written to your archives will be from 1979.
+//#define MINIZ_NO_TIME
+
+// Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's.
+//#define MINIZ_NO_ARCHIVE_APIS
+
+// Define MINIZ_NO_ARCHIVE_APIS to disable all writing related ZIP archive API's.
+//#define MINIZ_NO_ARCHIVE_WRITING_APIS
+
+// Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression API's.
+//#define MINIZ_NO_ZLIB_APIS
+
+// Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib.
+//#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES
+
+// Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc.
+// Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc
+// callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user
+// functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work.
+//#define MINIZ_NO_MALLOC
+
+#if defined(__TINYC__) && (defined(__linux) || defined(__linux__))
+  // TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux
+  #define MINIZ_NO_TIME
+#endif
+
+#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS)
+  #include <time.h>
+#endif
+
+#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__)
+// MINIZ_X86_OR_X64_CPU is only used to help set the below macros.
+#define MINIZ_X86_OR_X64_CPU 1
+#endif
+
+#if (__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU
+// Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian.
+#define MINIZ_LITTLE_ENDIAN 1
+#endif
+
+#if MINIZ_X86_OR_X64_CPU
+// Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses.
+#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1
+#endif
+
+#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__)
+// Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions).
+#define MINIZ_HAS_64BIT_REGISTERS 1
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// ------------------- zlib-style API Definitions.
+
+// For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits!
+typedef unsigned long mz_ulong;
+
+// mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap.
+void mz_free(void *p);
+
+#define MZ_ADLER32_INIT (1)
+// mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL.
+mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len);
+
+#define MZ_CRC32_INIT (0)
+// mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL.
+mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len);
+
+// Compression strategies.
+enum { MZ_DEFAULT_STRATEGY = 0, MZ_FILTERED = 1, MZ_HUFFMAN_ONLY = 2, MZ_RLE = 3, MZ_FIXED = 4 };
+
+// Method
+#define MZ_DEFLATED 8
+
+#ifndef MINIZ_NO_ZLIB_APIS
+
+// Heap allocation callbacks.
+// Note that mz_alloc_func parameter types purpsosely differ from zlib's: items/size is size_t, not unsigned long.
+typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size);
+typedef void (*mz_free_func)(void *opaque, void *address);
+typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size);
+
+#define MZ_VERSION          "9.1.15"
+#define MZ_VERNUM           0x91F0
+#define MZ_VER_MAJOR        9
+#define MZ_VER_MINOR        1
+#define MZ_VER_REVISION     15
+#define MZ_VER_SUBREVISION  0
+
+// Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs).
+enum { MZ_NO_FLUSH = 0, MZ_PARTIAL_FLUSH = 1, MZ_SYNC_FLUSH = 2, MZ_FULL_FLUSH = 3, MZ_FINISH = 4, MZ_BLOCK = 5 };
+
+// Return status codes. MZ_PARAM_ERROR is non-standard.
+enum { MZ_OK = 0, MZ_STREAM_END = 1, MZ_NEED_DICT = 2, MZ_ERRNO = -1, MZ_STREAM_ERROR = -2, MZ_DATA_ERROR = -3, MZ_MEM_ERROR = -4, MZ_BUF_ERROR = -5, MZ_VERSION_ERROR = -6, MZ_PARAM_ERROR = -10000 };
+
+// Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL.
+enum { MZ_NO_COMPRESSION = 0, MZ_BEST_SPEED = 1, MZ_BEST_COMPRESSION = 9, MZ_UBER_COMPRESSION = 10, MZ_DEFAULT_LEVEL = 6, MZ_DEFAULT_COMPRESSION = -1 };
+
+// Window bits
+#define MZ_DEFAULT_WINDOW_BITS 15
+
+struct mz_internal_state;
+
+// Compression/decompression stream struct.
+typedef struct mz_stream_s
+{
+  const unsigned char *next_in;     // pointer to next byte to read
+  unsigned int avail_in;            // number of bytes available at next_in
+  mz_ulong total_in;                // total number of bytes consumed so far
+
+  unsigned char *next_out;          // pointer to next byte to write
+  unsigned int avail_out;           // number of bytes that can be written to next_out
+  mz_ulong total_out;               // total number of bytes produced so far
+
+  char *msg;                        // error msg (unused)
+  struct mz_internal_state *state;  // internal state, allocated by zalloc/zfree
+
+  mz_alloc_func zalloc;             // optional heap allocation function (defaults to malloc)
+  mz_free_func zfree;               // optional heap free function (defaults to free)
+  void *opaque;                     // heap alloc function user pointer
+
+  int data_type;                    // data_type (unused)
+  mz_ulong adler;                   // adler32 of the source or uncompressed data
+  mz_ulong reserved;                // not used
+} mz_stream;
+
+typedef mz_stream *mz_streamp;
+
+// Returns the version string of miniz.c.
+const char *mz_version(void);
+
+// mz_deflateInit() initializes a compressor with default options:
+// Parameters:
+//  pStream must point to an initialized mz_stream struct.
+//  level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION].
+//  level 1 enables a specially optimized compression function that's been optimized purely for performance, not ratio.
+//  (This special func. is currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and MINIZ_LITTLE_ENDIAN are defined.)
+// Return values:
+//  MZ_OK on success.
+//  MZ_STREAM_ERROR if the stream is bogus.
+//  MZ_PARAM_ERROR if the input parameters are bogus.
+//  MZ_MEM_ERROR on out of memory.
+int mz_deflateInit(mz_streamp pStream, int level);
+
+// mz_deflateInit2() is like mz_deflate(), except with more control:
+// Additional parameters:
+//   method must be MZ_DEFLATED
+//   window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer)
+//   mem_level must be between [1, 9] (it's checked but ignored by miniz.c)
+int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy);
+
+// Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2().
+int mz_deflateReset(mz_streamp pStream);
+
+// mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible.
+// Parameters:
+//   pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members.
+//   flush may be MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH.
+// Return values:
+//   MZ_OK on success (when flushing, or if more input is needed but not available, and/or there's more output to be written but the output buffer is full).
+//   MZ_STREAM_END if all input has been consumed and all output bytes have been written. Don't call mz_deflate() on the stream anymore.
+//   MZ_STREAM_ERROR if the stream is bogus.
+//   MZ_PARAM_ERROR if one of the parameters is invalid.
+//   MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.)
+int mz_deflate(mz_streamp pStream, int flush);
+
+// mz_deflateEnd() deinitializes a compressor:
+// Return values:
+//  MZ_OK on success.
+//  MZ_STREAM_ERROR if the stream is bogus.
+int mz_deflateEnd(mz_streamp pStream);
+
+// mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH.
+mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len);
+
+// Single-call compression functions mz_compress() and mz_compress2():
+// Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure.
+int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len);
+int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level);
+
+// mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress().
+mz_ulong mz_compressBound(mz_ulong source_len);
+
+// Initializes a decompressor.
+int mz_inflateInit(mz_streamp pStream);
+
+// mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer:
+// window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate).
+int mz_inflateInit2(mz_streamp pStream, int window_bits);
+
+// Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible.
+// Parameters:
+//   pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members.
+//   flush may be MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH.
+//   On the first call, if flush is MZ_FINISH it's assumed the input and output buffers are both sized large enough to decompress the entire stream in a single call (this is slightly faster).
+//   MZ_FINISH implies that there are no more source bytes available beside what's already in the input buffer, and that the output buffer is large enough to hold the rest of the decompressed data.
+// Return values:
+//   MZ_OK on success. Either more input is needed but not available, and/or there's more output to be written but the output buffer is full.
+//   MZ_STREAM_END if all needed input has been consumed and all output bytes have been written. For zlib streams, the adler-32 of the decompressed data has also been verified.
+//   MZ_STREAM_ERROR if the stream is bogus.
+//   MZ_DATA_ERROR if the deflate stream is invalid.
+//   MZ_PARAM_ERROR if one of the parameters is invalid.
+//   MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again
+//   with more input data, or with more room in the output buffer (except when using single call decompression, described above).
+int mz_inflate(mz_streamp pStream, int flush);
+
+// Deinitializes a decompressor.
+int mz_inflateEnd(mz_streamp pStream);
+
+// Single-call decompression.
+// Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure.
+int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len);
+
+// Returns a string description of the specified error code, or NULL if the error code is invalid.
+const char *mz_error(int err);
+
+// Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports.
+// Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project.
+#ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES
+  typedef unsigned char Byte;
+  typedef unsigned int uInt;
+  typedef mz_ulong uLong;
+  typedef Byte Bytef;
+  typedef uInt uIntf;
+  typedef char charf;
+  typedef int intf;
+  typedef void *voidpf;
+  typedef uLong uLongf;
+  typedef void *voidp;
+  typedef void *const voidpc;
+  #define Z_NULL                0
+  #define Z_NO_FLUSH            MZ_NO_FLUSH
+  #define Z_PARTIAL_FLUSH       MZ_PARTIAL_FLUSH
+  #define Z_SYNC_FLUSH          MZ_SYNC_FLUSH
+  #define Z_FULL_FLUSH          MZ_FULL_FLUSH
+  #define Z_FINISH              MZ_FINISH
+  #define Z_BLOCK               MZ_BLOCK
+  #define Z_OK                  MZ_OK
+  #define Z_STREAM_END          MZ_STREAM_END
+  #define Z_NEED_DICT           MZ_NEED_DICT
+  #define Z_ERRNO               MZ_ERRNO
+  #define Z_STREAM_ERROR        MZ_STREAM_ERROR
+  #define Z_DATA_ERROR          MZ_DATA_ERROR
+  #define Z_MEM_ERROR           MZ_MEM_ERROR
+  #define Z_BUF_ERROR           MZ_BUF_ERROR
+  #define Z_VERSION_ERROR       MZ_VERSION_ERROR
+  #define Z_PARAM_ERROR         MZ_PARAM_ERROR
+  #define Z_NO_COMPRESSION      MZ_NO_COMPRESSION
+  #define Z_BEST_SPEED          MZ_BEST_SPEED
+  #define Z_BEST_COMPRESSION    MZ_BEST_COMPRESSION
+  #define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION
+  #define Z_DEFAULT_STRATEGY    MZ_DEFAULT_STRATEGY
+  #define Z_FILTERED            MZ_FILTERED
+  #define Z_HUFFMAN_ONLY        MZ_HUFFMAN_ONLY
+  #define Z_RLE                 MZ_RLE
+  #define Z_FIXED               MZ_FIXED
+  #define Z_DEFLATED            MZ_DEFLATED
+  #define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS
+  #define alloc_func            mz_alloc_func
+  #define free_func             mz_free_func
+  #define internal_state        mz_internal_state
+  #define z_stream              mz_stream
+  #define deflateInit           mz_deflateInit
+  #define deflateInit2          mz_deflateInit2
+  #define deflateReset          mz_deflateReset
+  #define deflate               mz_deflate
+  #define deflateEnd            mz_deflateEnd
+  #define deflateBound          mz_deflateBound
+  #define compress              mz_compress
+  #define compress2             mz_compress2
+  #define compressBound         mz_compressBound
+  #define inflateInit           mz_inflateInit
+  #define inflateInit2          mz_inflateInit2
+  #define inflate               mz_inflate
+  #define inflateEnd            mz_inflateEnd
+  #define uncompress            mz_uncompress
+  #define crc32                 mz_crc32
+  #define adler32               mz_adler32
+  #define MAX_WBITS             15
+  #define MAX_MEM_LEVEL         9
+  #define zError                mz_error
+  #define ZLIB_VERSION          MZ_VERSION
+  #define ZLIB_VERNUM           MZ_VERNUM
+  #define ZLIB_VER_MAJOR        MZ_VER_MAJOR
+  #define ZLIB_VER_MINOR        MZ_VER_MINOR
+  #define ZLIB_VER_REVISION     MZ_VER_REVISION
+  #define ZLIB_VER_SUBREVISION  MZ_VER_SUBREVISION
+  #define zlibVersion           mz_version
+  #define zlib_version          mz_version()
+#endif // #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES
+
+#endif // MINIZ_NO_ZLIB_APIS
+
+// ------------------- Types and macros
+
+typedef unsigned char mz_uint8;
+typedef signed short mz_int16;
+typedef unsigned short mz_uint16;
+typedef unsigned int mz_uint32;
+typedef unsigned int mz_uint;
+typedef long long mz_int64;
+typedef unsigned long long mz_uint64;
+typedef int mz_bool;
+
+#define MZ_FALSE (0)
+#define MZ_TRUE (1)
+
+// An attempt to work around MSVC's spammy "warning C4127: conditional expression is constant" message.
+#ifdef _MSC_VER
+   #define MZ_MACRO_END while (0, 0)
+#else
+   #define MZ_MACRO_END while (0)
+#endif
+
+// ------------------- ZIP archive reading/writing
+
+#ifndef MINIZ_NO_ARCHIVE_APIS
+
+enum
+{
+  MZ_ZIP_MAX_IO_BUF_SIZE = 64*1024,
+  MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 260,
+  MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 256
+};
+
+typedef struct
+{
+  mz_uint32 m_file_index;
+  mz_uint32 m_central_dir_ofs;
+  mz_uint16 m_version_made_by;
+  mz_uint16 m_version_needed;
+  mz_uint16 m_bit_flag;
+  mz_uint16 m_method;
+#ifndef MINIZ_NO_TIME
+  time_t m_time;
+#endif
+  mz_uint32 m_crc32;
+  mz_uint64 m_comp_size;
+  mz_uint64 m_uncomp_size;
+  mz_uint16 m_internal_attr;
+  mz_uint32 m_external_attr;
+  mz_uint64 m_local_header_ofs;
+  mz_uint32 m_comment_size;
+  char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE];
+  char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE];
+} mz_zip_archive_file_stat;
+
+typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n);
+typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n);
+
+struct mz_zip_internal_state_tag;
+typedef struct mz_zip_internal_state_tag mz_zip_internal_state;
+
+typedef enum
+{
+  MZ_ZIP_MODE_INVALID = 0,
+  MZ_ZIP_MODE_READING = 1,
+  MZ_ZIP_MODE_WRITING = 2,
+  MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3
+} mz_zip_mode;
+
+typedef struct mz_zip_archive_tag
+{
+  mz_uint64 m_archive_size;
+  mz_uint64 m_central_directory_file_ofs;
+  mz_uint m_total_files;
+  mz_zip_mode m_zip_mode;
+
+  mz_uint m_file_offset_alignment;
+
+  mz_alloc_func m_pAlloc;
+  mz_free_func m_pFree;
+  mz_realloc_func m_pRealloc;
+  void *m_pAlloc_opaque;
+
+  mz_file_read_func m_pRead;
+  mz_file_write_func m_pWrite;
+  void *m_pIO_opaque;
+
+  mz_zip_internal_state *m_pState;
+
+} mz_zip_archive;
+
+typedef enum
+{
+  MZ_ZIP_FLAG_CASE_SENSITIVE                = 0x0100,
+  MZ_ZIP_FLAG_IGNORE_PATH                   = 0x0200,
+  MZ_ZIP_FLAG_COMPRESSED_DATA               = 0x0400,
+  MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800
+} mz_zip_flags;
+
+// ZIP archive reading
+
+// Inits a ZIP archive reader.
+// These functions read and validate the archive's central directory.
+mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint32 flags);
+mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint32 flags);
+
+#ifndef MINIZ_NO_STDIO
+mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags);
+#endif
+
+// Returns the total number of files in the archive.
+mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip);
+
+// Returns detailed information about an archive file entry.
+mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat);
+
+// Determines if an archive file entry is a directory entry.
+mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index);
+mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index);
+
+// Retrieves the filename of an archive file entry.
+// Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename.
+mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size);
+
+// Attempts to locates a file in the archive's central directory.
+// Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH
+// Returns -1 if the file cannot be found.
+int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags);
+
+// Extracts a archive file to a memory buffer using no memory allocation.
+mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size);
+mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size);
+
+// Extracts a archive file to a memory buffer.
+mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags);
+mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags);
+
+// Extracts a archive file to a dynamically allocated heap buffer.
+void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags);
+void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags);
+
+// Extracts a archive file using a callback function to output the file's data.
+mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags);
+mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags);
+
+#ifndef MINIZ_NO_STDIO
+// Extracts a archive file to a disk file and sets its last accessed and modified times.
+// This function only extracts files, not archive directory records.
+mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags);
+mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags);
+#endif
+
+// Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used.
+mz_bool mz_zip_reader_end(mz_zip_archive *pZip);
+
+// ZIP archive writing
+
+#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS
+
+// Inits a ZIP archive writer.
+mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size);
+mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size);
+
+#ifndef MINIZ_NO_STDIO
+mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning);
+#endif
+
+// Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive.
+// For archives opened using mz_zip_reader_init_file, pFilename must be the archive's filename so it can be reopened for writing. If the file can't be reopened, mz_zip_reader_end() will be called.
+// For archives opened using mz_zip_reader_init_mem, the memory block must be growable using the realloc callback (which defaults to realloc unless you've overridden it).
+// Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL.
+// Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before
+// the archive is finalized the file's central directory will be hosed.
+mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename);
+
+// Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive.
+// To add a directory entry, call this method with an archive name ending in a forwardslash with empty buffer.
+// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION.
+mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags);
+mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32);
+
+#ifndef MINIZ_NO_STDIO
+// Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive.
+// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION.
+mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags);
+#endif
+
+// Adds a file to an archive by fully cloning the data from another archive.
+// This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data, and comment fields.
+mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint file_index);
+
+// Finalizes the archive by writing the central directory records followed by the end of central directory record.
+// After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end().
+// An archive must be manually finalized by calling this function for it to be valid.
+mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip);
+mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, size_t *pSize);
+
+// Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used.
+// Note for the archive to be valid, it must have been finalized before ending.
+mz_bool mz_zip_writer_end(mz_zip_archive *pZip);
+
+// Misc. high-level helper functions:
+
+// mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive.
+// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION.
+mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags);
+mz_bool mz_zip_add_mem_to_archive_file_in_place_with_time(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint16 dos_time, mz_uint16 dos_date);
+
+// Reads a single file from an archive into a heap block.
+// Returns NULL on failure.
+void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint zip_flags);
+
+#endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS
+
+#endif // #ifndef MINIZ_NO_ARCHIVE_APIS
+
+// ------------------- Low-level Decompression API Definitions
+
+// Decompression flags used by tinfl_decompress().
+// TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream.
+// TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input.
+// TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB).
+// TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes.
+enum
+{
+  TINFL_FLAG_PARSE_ZLIB_HEADER = 1,
+  TINFL_FLAG_HAS_MORE_INPUT = 2,
+  TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4,
+  TINFL_FLAG_COMPUTE_ADLER32 = 8
+};
+
+// High level decompression functions:
+// tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc().
+// On entry:
+//  pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress.
+// On return:
+//  Function returns a pointer to the decompressed data, or NULL on failure.
+//  *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data.
+//  The caller must call mz_free() on the returned block when it's no longer needed.
+void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags);
+
+// tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory.
+// Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success.
+#define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1))
+size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags);
+
+// tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer.
+// Returns 1 on success or 0 on failure.
+typedef int (*tinfl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser);
+int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags);
+
+struct tinfl_decompressor_tag; typedef struct tinfl_decompressor_tag tinfl_decompressor;
+
+// Max size of LZ dictionary.
+#define TINFL_LZ_DICT_SIZE 32768
+
+// Return status.
+typedef enum
+{
+  TINFL_STATUS_BAD_PARAM = -3,
+  TINFL_STATUS_ADLER32_MISMATCH = -2,
+  TINFL_STATUS_FAILED = -1,
+  TINFL_STATUS_DONE = 0,
+  TINFL_STATUS_NEEDS_MORE_INPUT = 1,
+  TINFL_STATUS_HAS_MORE_OUTPUT = 2
+} tinfl_status;
+
+// Initializes the decompressor to its initial state.
+#define tinfl_init(r) do { (r)->m_state = 0; } MZ_MACRO_END
+#define tinfl_get_adler32(r) (r)->m_check_adler32
+
+// Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability.
+// This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output.
+tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags);
+
+// Internal/private bits follow.
+enum
+{
+  TINFL_MAX_HUFF_TABLES = 3, TINFL_MAX_HUFF_SYMBOLS_0 = 288, TINFL_MAX_HUFF_SYMBOLS_1 = 32, TINFL_MAX_HUFF_SYMBOLS_2 = 19,
+  TINFL_FAST_LOOKUP_BITS = 10, TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS
+};
+
+typedef struct
+{
+  mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0];
+  mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2];
+} tinfl_huff_table;
+
+#if MINIZ_HAS_64BIT_REGISTERS
+  #define TINFL_USE_64BIT_BITBUF 1
+#endif
+
+#if TINFL_USE_64BIT_BITBUF
+  typedef mz_uint64 tinfl_bit_buf_t;
+  #define TINFL_BITBUF_SIZE (64)
+#else
+  typedef mz_uint32 tinfl_bit_buf_t;
+  #define TINFL_BITBUF_SIZE (32)
+#endif
+
+struct tinfl_decompressor_tag
+{
+  mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES];
+  tinfl_bit_buf_t m_bit_buf;
+  size_t m_dist_from_out_buf_start;
+  tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES];
+  mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137];
+};
+
+// ------------------- Low-level Compression API Definitions
+
+// Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly slower, and raw/dynamic blocks will be output more frequently).
+#define TDEFL_LESS_MEMORY 0
+
+// tdefl_init() compression flags logically OR'd together (low 12 bits contain the max. number of probes per dictionary search):
+// TDEFL_DEFAULT_MAX_PROBES: The compressor defaults to 128 dictionary probes per dictionary search. 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ (slowest/best compression).
+enum
+{
+  TDEFL_HUFFMAN_ONLY = 0, TDEFL_DEFAULT_MAX_PROBES = 128, TDEFL_MAX_PROBES_MASK = 0xFFF
+};
+
+// TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before the deflate data, and the Adler-32 of the source data at the end. Otherwise, you'll get raw deflate data.
+// TDEFL_COMPUTE_ADLER32: Always compute the adler-32 of the input data (even when not writing zlib headers).
+// TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more efficient lazy parsing.
+// TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to decrease the compressor's initialization time to the minimum, but the output may vary from run to run given the same input (depending on the contents of memory).
+// TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a distance of 1)
+// TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled.
+// TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables.
+// TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks.
+// The low 12 bits are reserved to control the max # of hash probes per dictionary lookup (see TDEFL_MAX_PROBES_MASK).
+enum
+{
+  TDEFL_WRITE_ZLIB_HEADER             = 0x01000,
+  TDEFL_COMPUTE_ADLER32               = 0x02000,
+  TDEFL_GREEDY_PARSING_FLAG           = 0x04000,
+  TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000,
+  TDEFL_RLE_MATCHES                   = 0x10000,
+  TDEFL_FILTER_MATCHES                = 0x20000,
+  TDEFL_FORCE_ALL_STATIC_BLOCKS       = 0x40000,
+  TDEFL_FORCE_ALL_RAW_BLOCKS          = 0x80000
+};
+
+// High level compression functions:
+// tdefl_compress_mem_to_heap() compresses a block in memory to a heap block allocated via malloc().
+// On entry:
+//  pSrc_buf, src_buf_len: Pointer and size of source block to compress.
+//  flags: The max match finder probes (default is 128) logically OR'd against the above flags. Higher probes are slower but improve compression.
+// On return:
+//  Function returns a pointer to the compressed data, or NULL on failure.
+//  *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data.
+//  The caller must free() the returned block when it's no longer needed.
+void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags);
+
+// tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory.
+// Returns 0 on failure.
+size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags);
+
+// Compresses an image to a compressed PNG file in memory.
+// On entry:
+//  pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4.
+//  The image pitch in bytes per scanline will be w*num_chans. The leftmost pixel on the top scanline is stored first in memory.
+//  level may range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL
+//  If flip is true, the image will be flipped on the Y axis (useful for OpenGL apps).
+// On return:
+//  Function returns a pointer to the compressed data, or NULL on failure.
+//  *pLen_out will be set to the size of the PNG image file.
+//  The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed.
+void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip);
+void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out);
+
+// Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time.
+typedef mz_bool (*tdefl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser);
+
+// tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally.
+mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags);
+
+enum { TDEFL_MAX_HUFF_TABLES = 3, TDEFL_MAX_HUFF_SYMBOLS_0 = 288, TDEFL_MAX_HUFF_SYMBOLS_1 = 32, TDEFL_MAX_HUFF_SYMBOLS_2 = 19, TDEFL_LZ_DICT_SIZE = 32768, TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1, TDEFL_MIN_MATCH_LEN = 3, TDEFL_MAX_MATCH_LEN = 258 };
+
+// TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed output block (using static/fixed Huffman codes).
+#if TDEFL_LESS_MEMORY
+enum { TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13 ) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 12, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS };
+#else
+enum { TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13 ) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 15, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS };
+#endif
+
+// The low-level tdefl functions below may be used directly if the above helper functions aren't flexible enough. The low-level functions don't make any heap allocations, unlike the above helper functions.
+typedef enum
+{
+  TDEFL_STATUS_BAD_PARAM = -2,
+  TDEFL_STATUS_PUT_BUF_FAILED = -1,
+  TDEFL_STATUS_OKAY = 0,
+  TDEFL_STATUS_DONE = 1,
+} tdefl_status;
+
+// Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums
+typedef enum
+{
+  TDEFL_NO_FLUSH = 0,
+  TDEFL_SYNC_FLUSH = 2,
+  TDEFL_FULL_FLUSH = 3,
+  TDEFL_FINISH = 4
+} tdefl_flush;
+
+// tdefl's compression state structure.
+typedef struct
+{
+  tdefl_put_buf_func_ptr m_pPut_buf_func;
+  void *m_pPut_buf_user;
+  mz_uint m_flags, m_max_probes[2];
+  int m_greedy_parsing;
+  mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size;
+  mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end;
+  mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer;
+  mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish;
+  tdefl_status m_prev_return_status;
+  const void *m_pIn_buf;
+  void *m_pOut_buf;
+  size_t *m_pIn_buf_size, *m_pOut_buf_size;
+  tdefl_flush m_flush;
+  const mz_uint8 *m_pSrc;
+  size_t m_src_buf_left, m_out_buf_ofs;
+  mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1];
+  mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS];
+  mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS];
+  mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS];
+  mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE];
+  mz_uint16 m_next[TDEFL_LZ_DICT_SIZE];
+  mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE];
+  mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE];
+} tdefl_compressor;
+
+// Initializes the compressor.
+// There is no corresponding deinit() function because the tdefl API's do not dynamically allocate memory.
+// pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression.
+// If pBut_buf_func is NULL the user should always call the tdefl_compress() API.
+// flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.)
+tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags);
+
+// Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible.
+tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush);
+
+// tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr.
+// tdefl_compress_buffer() always consumes the entire input buffer.
+tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush);
+
+tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d);
+mz_uint32 tdefl_get_adler32(tdefl_compressor *d);
+
+// Can't use tdefl_create_comp_flags_from_zip_params if MINIZ_NO_ZLIB_APIS isn't defined, because it uses some of its macros.
+#ifndef MINIZ_NO_ZLIB_APIS
+// Create tdefl_compress() flags given zlib-style compression parameters.
+// level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files)
+// window_bits may be -15 (raw deflate) or 15 (zlib)
+// strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED
+mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy);
+#endif // #ifndef MINIZ_NO_ZLIB_APIS
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // MINIZ_HEADER_INCLUDED
+
+// ------------------- End of Header: Implementation follows. (If you only want the header, define MINIZ_HEADER_FILE_ONLY.)
index e17124eb6506a59f077227bbd485e22c9100912d..5d24b3db674e2089b8c73e5b57517d5a91e2f99b 100644 (file)
@@ -101,7 +101,35 @@ void LoadScriptFile( const char *filename, int index ){
        endofscript = qfalse;
        tokenready = qfalse;
 }
+/* &unload current; for autopacker */
+void SilentLoadScriptFile( const char *filename, int index ){
+       int size;
+
+       if ( script->buffer != NULL && !endofscript ) {
+               free( script->buffer );
+               script->buffer = NULL;
+       }
+
+       script = scriptstack;
+
+       script++;
+       if ( script == &scriptstack[MAX_INCLUDES] ) {
+               Error( "script file exceeded MAX_INCLUDES" );
+       }
+       strcpy( script->filename, ExpandPath( filename ) );
 
+       size = vfsLoadFile( script->filename, (void **)&script->buffer, index );
+
+       if ( size == -1 ) {
+               Sys_Printf( "Script file %s was not found\n", script->filename );
+       }
+       script->line = 1;
+       script->script_p = script->buffer;
+       script->end_p = script->buffer + size;
+
+       endofscript = qfalse;
+       tokenready = qfalse;
+}
 
 /*
    ==============
@@ -147,7 +175,7 @@ void UnGetToken( void ){
 
 qboolean EndOfScript( qboolean crossline ){
        if ( !crossline ) {
-               Error( "Line %i is incomplete\n",scriptline );
+               Error( "Line %i is incomplete\nFile location be: %s\n", scriptline, g_strLoadedFileLocation );
        }
 
        if ( !strcmp( script->filename, "memory buffer" ) ) {
@@ -206,7 +234,7 @@ skipspace:
                }
                if ( *script->script_p++ == '\n' ) {
                        if ( !crossline ) {
-                               Error( "Line %i is incomplete\n",scriptline );
+                               Error( "Line %i is incomplete\nFile location be: %s\n", scriptline, g_strLoadedFileLocation );
                        }
                        script->line++;
                        scriptline = script->line;
@@ -221,7 +249,7 @@ skipspace:
        if ( *script->script_p == ';' || *script->script_p == '#'
                 || ( script->script_p[0] == '/' && script->script_p[1] == '/' ) ) {
                if ( !crossline ) {
-                       Error( "Line %i is incomplete\n",scriptline );
+                       Error( "Line %i is incomplete\nFile location be: %s\n", scriptline, g_strLoadedFileLocation );
                }
                while ( *script->script_p++ != '\n' )
                        if ( script->script_p >= script->end_p ) {
@@ -235,10 +263,10 @@ skipspace:
        // /* */ comments
        if ( script->script_p[0] == '/' && script->script_p[1] == '*' ) {
                if ( !crossline ) {
-                       Error( "Line %i is incomplete\n",scriptline );
+                       Error( "Line %i is incomplete\nFile location be: %s\n", scriptline, g_strLoadedFileLocation );
                }
                script->script_p += 2;
-               while ( script->script_p[0] != '*' && script->script_p[1] != '/' )
+               while ( script->script_p[0] != '*' || script->script_p[1] != '/' )
                {
                        if ( *script->script_p == '\n' ) {
                                script->line++;
@@ -268,7 +296,7 @@ skipspace:
                                break;
                        }
                        if ( token_p == &token[MAXTOKEN] ) {
-                               Error( "Token too large on line %i\n",scriptline );
+                               Error( "Token too large on line %i\nFile location be: %s\n", scriptline, g_strLoadedFileLocation );
                        }
                }
                script->script_p++;
@@ -281,7 +309,7 @@ skipspace:
                                break;
                        }
                        if ( token_p == &token[MAXTOKEN] ) {
-                               Error( "Token too large on line %i\n",scriptline );
+                               Error( "Token too large on line %i\nFile location be: %s\n", scriptline, g_strLoadedFileLocation );
                        }
                }
        }
index dc9b2348aac1d9d11ac40703ad27c1af2d634d04..0ee62bde3d199e9eb35ca40a7ba449b9c4c4c162 100644 (file)
@@ -38,6 +38,7 @@ extern qboolean endofscript;
 
 
 void LoadScriptFile( const char *filename, int index );
+void SilentLoadScriptFile( const char *filename, int index );
 void ParseFromMemory( char *buffer, int size );
 
 qboolean GetToken( qboolean crossline );
index 0bd6ed1e2f6284d8e9680c343dddd1a0f7ae1eaa..f741da7c80ed9b6b01acdd086ca7d01545a84bda 100644 (file)
 #include "vfs.h"
 #include <unzip.h>
 #include <glib.h>
+#define GARUX_DISABLE_BAD_MINIZ
+#ifndef GARUX_DISABLE_BAD_MINIZ
+#include "miniz.h"
+#endif
 
 typedef struct
 {
+       char* unzFilePath;
        char*   name;
        unzFile zipfile;
        unz_file_pos zippos;
@@ -71,6 +76,7 @@ static int g_numDirs;
 char g_strForbiddenDirs[VFS_MAXDIRS][PATH_MAX + 1];
 int g_numForbiddenDirs = 0;
 static gboolean g_bUsePak = TRUE;
+char g_strLoadedFileLocation[1024];
 
 // =============================================================================
 // Static functions
@@ -120,6 +126,8 @@ static void vfsInitPakFile( const char *filename ){
        }
        unzGoToFirstFile( uf );
 
+       char* unzFilePath = strdup( filename );
+
        for ( i = 0; i < gi.number_entry; i++ )
        {
                char filename_inzip[NAME_MAX];
@@ -147,6 +155,7 @@ static void vfsInitPakFile( const char *filename ){
                file->name = strdup( filename_lower );
                file->size = file_info.uncompressed_size;
                file->zipfile = uf;
+               file->unzFilePath = unzFilePath;
                file->zippos = pos;
 
                if ( ( i + 1 ) < gi.number_entry ) {
@@ -188,6 +197,7 @@ void vfsInitDirectory( const char *path ){
        }
 
        if ( g_numDirs == VFS_MAXDIRS ) {
+               Sys_FPrintf( SYS_WRN, "WARNING: too many VFS directories, can't init %s\n", path );
                return;
        }
 
@@ -255,6 +265,72 @@ void vfsInitDirectory( const char *path ){
        }
 }
 
+
+// lists all .shader files
+void vfsListShaderFiles( char* list, int *num ){
+       //char filename[PATH_MAX];
+       char *dirlist;
+       GDir *dir;
+       int i, k;
+       char path[NAME_MAX];
+/* search in dirs */
+       for ( i = 0; i < g_numDirs; i++ ){
+               strncpy( path, g_strDirs[ i ], NAME_MAX );
+               strcat( path, "scripts/" );
+
+               dir = g_dir_open( path, 0, NULL );
+
+               if ( dir != NULL ) {
+                       while ( 1 )
+                       {
+                               const char* name = g_dir_read_name( dir );
+                               if ( name == NULL ) {
+                                       break;
+                               }
+                               dirlist = g_strdup( name );
+                               char *ext = strrchr( dirlist, '.' );
+
+                               if ( ( ext == NULL ) || ( Q_stricmp( ext, ".shader" ) != 0 ) ) {
+                                       continue;
+                               }
+
+                               for ( k = 0; k < *num; k++ ){
+                                       if ( !Q_stricmp( list + k*65, dirlist ) ) goto shISdouplicate;
+                               }
+                               strcpy( list + (*num)*65, dirlist );
+                               (*num)++;
+shISdouplicate:
+                               g_free( dirlist );
+                       }
+                       g_dir_close( dir );
+               }
+       }
+       /* search in packs */
+       GSList *lst;
+
+       for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
+       {
+               VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
+
+               char *ext = strrchr( file->name, '.' );
+
+               if ( ( ext == NULL ) || ( Q_stricmp( ext, ".shader" ) != 0 ) ) {
+                       continue;
+               }
+               //name + ext this time
+               ext = strrchr( file->name, '/' );
+               ext++;
+
+               for ( k = 0; k < *num; k++ ){
+                       if ( !Q_stricmp( list + k*65, ext ) ) goto shISdouplicate2;
+               }
+               strcpy( list + (*num)*65, ext );
+               (*num)++;
+shISdouplicate2:
+               continue;
+       }
+}
+
 // frees all memory that we allocated
 void vfsShutdown(){
        while ( g_unzFiles )
@@ -266,6 +342,7 @@ void vfsShutdown(){
        while ( g_pakFiles )
        {
                VFS_PAKFILE* file = (VFS_PAKFILE*)g_pakFiles->data;
+               free( file->unzFilePath );
                free( file->name );
                free( file );
                g_pakFiles = g_slist_remove( g_pakFiles, file );
@@ -363,6 +440,7 @@ int vfsLoadFile( const char *filename, void **bufferptr, int index ){
 
        // filename is a full path
        if ( index == -1 ) {
+               strcpy( g_strLoadedFileLocation, filename );
                long len;
                FILE *f;
 
@@ -404,6 +482,8 @@ int vfsLoadFile( const char *filename, void **bufferptr, int index ){
                strcat( tmp, filename );
                if ( access( tmp, R_OK ) == 0 ) {
                        if ( count == index ) {
+                               strcpy( g_strLoadedFileLocation, tmp );
+
                                long len;
                                FILE *f;
 
@@ -452,6 +532,10 @@ int vfsLoadFile( const char *filename, void **bufferptr, int index ){
                }
 
                if ( count == index ) {
+                       strcpy( g_strLoadedFileLocation, file->unzFilePath );
+                       strcat( g_strLoadedFileLocation, " :: " );
+                       strcat( g_strLoadedFileLocation, filename );
+
 
                if ( unzGoToFilePos( file->zipfile, &file->zippos ) != UNZ_OK ) {
                        return -1;
@@ -515,3 +599,137 @@ int vfsLoadFile( const char *filename, void **bufferptr, int index ){
        g_free( lower );
        return -1;
 }
+
+
+qboolean vfsPackFile( const char *filename, const char *packname, const int compLevel ){
+#ifndef GARUX_DISABLE_BAD_MINIZ
+       int i;
+       char tmp[NAME_MAX], fixed[NAME_MAX];
+       GSList *lst;
+
+       byte *bufferptr = NULL;
+       strcpy( fixed, filename );
+       vfsFixDOSName( fixed );
+       g_strdown( fixed );
+
+       for ( i = 0; i < g_numDirs; i++ )
+       {
+               strcpy( tmp, g_strDirs[i] );
+               strcat( tmp, filename );
+               if ( access( tmp, R_OK ) == 0 ) {
+                       if ( access( packname, R_OK ) == 0 ) {
+                               mz_zip_archive zip;
+                               memset( &zip, 0, sizeof(zip) );
+                               mz_zip_reader_init_file( &zip, packname, 0 );
+                               mz_zip_writer_init_from_reader( &zip, packname );
+
+                               mz_bool success = MZ_TRUE;
+                               success &= mz_zip_writer_add_file( &zip, filename, tmp, 0, 0, compLevel );
+                               if ( !success || !mz_zip_writer_finalize_archive( &zip ) ){
+                                       Error( "Failed creating zip archive \"%s\"!\n", packname );
+                               }
+                               mz_zip_reader_end( &zip);
+                               mz_zip_writer_end( &zip );
+                       }
+                       else{
+                               mz_zip_archive zip;
+                               memset( &zip, 0, sizeof(zip) );
+                               if( !mz_zip_writer_init_file( &zip, packname, 0 ) ){
+                                       Error( "Failed creating zip archive \"%s\"!\n", packname );
+                               }
+                               mz_bool success = MZ_TRUE;
+                               success &= mz_zip_writer_add_file( &zip, filename, tmp, 0, 0, compLevel );
+                               if ( !success || !mz_zip_writer_finalize_archive( &zip ) ){
+                                       Error( "Failed creating zip archive \"%s\"!\n", packname );
+                               }
+                               mz_zip_writer_end( &zip );
+                       }
+
+                       return qtrue;
+               }
+       }
+
+       for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
+       {
+               VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
+
+               if ( strcmp( file->name, fixed ) != 0 ) {
+                       continue;
+               }
+
+               memcpy( file->zipfile, &file->zipinfo, sizeof( unz_s ) );
+
+               if ( unzOpenCurrentFile( file->zipfile ) != UNZ_OK ) {
+                       return qfalse;
+               }
+
+               bufferptr = safe_malloc( file->size + 1 );
+               // we need to end the buffer with a 0
+               ( (char*) ( bufferptr ) )[file->size] = 0;
+
+               mz_uint16 DOS_time = (mz_uint16)(((file->zipinfo.cur_file_info.tmu_date.tm_hour) << 11) + ((file->zipinfo.cur_file_info.tmu_date.tm_min) << 5) + ((file->zipinfo.cur_file_info.tmu_date.tm_sec) >> 1));
+               mz_uint16 DOS_date = (mz_uint16)(((file->zipinfo.cur_file_info.tmu_date.tm_year - 1980) << 9) + ((file->zipinfo.cur_file_info.tmu_date.tm_mon + 1) << 5) + file->zipinfo.cur_file_info.tmu_date.tm_mday);
+
+               i = unzReadCurrentFile( file->zipfile, bufferptr, file->size );
+               unzCloseCurrentFile( file->zipfile );
+               if ( i < 0 ) {
+                       return qfalse;
+               }
+               else{
+                       mz_bool success = MZ_TRUE;
+                       success &= mz_zip_add_mem_to_archive_file_in_place_with_time( packname, filename, bufferptr, i, 0, 0, compLevel, DOS_time, DOS_date );
+                               if ( !success ){
+                                       Error( "Failed creating zip archive \"%s\"!\n", packname );
+                               }
+                       free( bufferptr );
+                       return qtrue;
+               }
+       }
+
+       return qfalse;
+#else
+               Error( "Disabled because of miniz issue" );
+#endif
+}
+
+qboolean vfsPackFile_Absolute_Path( const char *filepath, const char *filename, const char *packname, const int compLevel ){
+#ifndef GARUX_DISABLE_BAD_MINIZ
+       char tmp[NAME_MAX];
+       strcpy( tmp, filepath );
+       if ( access( tmp, R_OK ) == 0 ) {
+               if ( access( packname, R_OK ) == 0 ) {
+                       mz_zip_archive zip;
+                       memset( &zip, 0, sizeof(zip) );
+                       mz_zip_reader_init_file( &zip, packname, 0 );
+                       mz_zip_writer_init_from_reader( &zip, packname );
+
+                       mz_bool success = MZ_TRUE;
+                       success &= mz_zip_writer_add_file( &zip, filename, tmp, 0, 0, compLevel );
+                       if ( !success || !mz_zip_writer_finalize_archive( &zip ) ){
+                               Error( "Failed creating zip archive \"%s\"!\n", packname );
+                       }
+                       mz_zip_reader_end( &zip);
+                       mz_zip_writer_end( &zip );
+               }
+               else{
+                       mz_zip_archive zip;
+                       memset( &zip, 0, sizeof(zip) );
+                       if( !mz_zip_writer_init_file( &zip, packname, 0 ) ){
+                               Error( "Failed creating zip archive \"%s\"!\n", packname );
+                       }
+                       mz_bool success = MZ_TRUE;
+                       success &= mz_zip_writer_add_file( &zip, filename, tmp, 0, 0, compLevel );
+                       if ( !success || !mz_zip_writer_finalize_archive( &zip ) ){
+                               Error( "Failed creating zip archive \"%s\"!\n", packname );
+                       }
+                       mz_zip_writer_end( &zip );
+               }
+
+               return qtrue;
+       }
+
+       return qfalse;
+#else
+               Error( "Disabled because of miniz issue" );
+#endif
+}
index 227a2f29f76fe05f4800c163e6b7620615a0e146..706314d42d1d5c747cdc56258a53c078d5ae26bc 100644 (file)
 #if GDEF_OS_WINDOWS
 #include <wtypes.h>
 #include <io.h>
+
+#ifndef R_OK
+#define R_OK 04
+#endif
+
 #define S_ISDIR( mode ) ( mode & _S_IFDIR )
 #else // !GDEF_OS_WINDOWS
 #include <dirent.h>
 #include <sys/syslimits.h>
 #endif
 
-#define VFS_MAXDIRS 64
+// Multiple pakpaths with many pk3dirs can lead
+// to high list of VFS directories
+#define VFS_MAXDIRS 256
 
 void vfsInitDirectory( const char *path );
 void vfsShutdown();
 int vfsGetFileCount( const char *filename );
 int vfsLoadFile( const char *filename, void **buffer, int index );
+void vfsListShaderFiles( char* list, int *num );
+qboolean vfsPackFile( const char *filename, const char *packname, const int compLevel );
+qboolean vfsPackFile_Absolute_Path( const char *filepath, const char *filename, const char *packname, const int compLevel );
 
 extern char g_strForbiddenDirs[VFS_MAXDIRS][PATH_MAX + 1];
 extern int g_numForbiddenDirs;
+extern char g_strLoadedFileLocation[1024];
 
 #endif // _VFS_H_
index 3f74d27f44a96ec98070f293a42069bcf4740b45..39b2e49765e77416a5e6ae5b4a32e09ae5aa05a4 100644 (file)
@@ -335,11 +335,8 @@ void PackDirectory_r( char *dir ){
 #else
 
 #include <sys/types.h>
-#if !GDEF_OS_WINDOWS
-#include <sys/dir.h>
-#else
-#include <sys/dirent.h>
-#endif
+
+#include <dirent.h>
 
 void PackDirectory_r( char *dir ){
 #ifdef NeXT
index db04b8805e2faf08bc61ec8993df13e5c09964d8..d48089540546de331b1ccfe57f76223445415f8f 100644 (file)
@@ -94,10 +94,11 @@ brush_t *AllocBrush( int numSides ){
        size_t c;
 
        /* allocate and clear */
-       if ( numSides <= 0 ) {
+       /*if ( numSides <= 0 ) {
                Error( "AllocBrush called with numsides = %d", numSides );
        }
-       c = (size_t)&( ( (brush_t*) 0 )->sides[ numSides ] );
+       c = (size_t)&( ( (brush_t*) 0 )->sides[ numSides ] );*/
+       c = sizeof(*bb) + (numSides > 6 ? sizeof(side_t)*(numSides - 6) : 0);
        bb = safe_malloc0( c );
        if ( numthreads == 1 ) {
                numActiveBrushes++;
index bd0abc207b9bedcfa1437c196a74772006558a40..7e504a6a2f8fee3bba0f43d6a84622989716a2fc 100644 (file)
@@ -44,7 +44,6 @@
 
    ------------------------------------------------------------------------------- */
 
-
 /*
    ProcessAdvertisements()
    copies advertisement info into the BSP structures
@@ -320,6 +319,10 @@ void ProcessWorldModel( const char *portalFilePath, const char *lineFilePath ){
        /* check for patches with adjacent edges that need to lod together */
        PatchMapDrawSurfs( e );
 
+       if ( debugClip ) {
+               AddTriangleModels( e );
+       }
+
        /* build an initial bsp tree using all of the sides of all of the structural brushes */
        faces = MakeStructuralBSPFaceList( entities[ 0 ].brushes );
        tree = FaceBSP( faces );
@@ -388,7 +391,9 @@ void ProcessWorldModel( const char *portalFilePath, const char *lineFilePath ){
        FloodAreas( tree );
 
        /* create drawsurfs for triangle models */
-       AddTriangleModels( e );
+       if ( !debugClip ) {
+               AddTriangleModels( e );
+       }
 
        /* create drawsurfs for surface models */
        AddEntitySurfaceModels( e );
@@ -632,6 +637,8 @@ void ProcessModels( const char *portalFilePath, const char *lineFilePath ){
        /* restore -v setting */
        verbose = oldVerbose;
 
+       Sys_FPrintf( SYS_VRB, "%9i bspModels in total\n", numBSPModels );
+
        /* write fogs */
        EmitFogs();
 
@@ -717,6 +724,7 @@ int BSPMain( int argc, char **argv ){
        /* note it */
        Sys_Printf( "--- BSP ---\n" );
 
+       doingBSP = qtrue;
        SetDrawSurfacesBuffer();
        mapDrawSurfs = safe_malloc0( sizeof( mapDrawSurface_t ) * MAX_MAP_DRAW_SURFS );
        numMapDrawSurfs = 0;
@@ -870,8 +878,8 @@ int BSPMain( int argc, char **argv ){
                                Sys_Printf( "The -mv argument is deprecated, use \"-maxlightmapvertices\" instead\n" );
                        }
                        else {
-                               Sys_Printf( "Maximum lightmapped surface vertex count set to %d\n", maxLMSurfaceVerts );
-                       }
+                       Sys_Printf( "Maximum lightmapped surface vertex count set to %d\n", maxLMSurfaceVerts );
+               }
                }
                else if ( !strcmp( argv[ i ], "-np" ) ) {
                        npDegrees = atof( argv[ i + 1 ] );
@@ -981,6 +989,15 @@ int BSPMain( int argc, char **argv ){
                        Sys_Printf( "Debug portal surfaces enabled\n" );
                        debugPortals = qtrue;
                }
+               else if ( !strcmp( argv[ i ], "-debugclip" ) ) {
+                       Sys_Printf( "Debug model clip enabled\n" );
+                       debugClip = qtrue;
+               }
+               else if ( !strcmp( argv[ i ],  "-clipdepth" ) ) {
+                       clipDepthGlobal = atof( argv[ i + 1 ] );
+                       i++;
+                       Sys_Printf( "Model autoclip thickness set to %.3f\n", clipDepthGlobal );
+               }
                else if ( !strcmp( argv[ i ], "-sRGBtex" ) ) {
                        texturesRGB = qtrue;
                        Sys_Printf( "Textures are in sRGB\n" );
@@ -1003,13 +1020,11 @@ int BSPMain( int argc, char **argv ){
                        colorsRGB = qfalse;
                        Sys_Printf( "Colors are linear\n" );
                }
-               else if ( !strcmp( argv[ i ], "-altsplit" ) )
-               {
+               else if ( !strcmp( argv[ i ], "-altsplit" ) ) {
                        Sys_Printf( "Alternate BSP splitting (by 27) enabled\n" );
                        bspAlternateSplitWeights = qtrue;
                }
-               else if ( !strcmp( argv[ i ], "-deep" ) )
-               {
+               else if ( !strcmp( argv[ i ], "-deep" ) ) {
                        Sys_Printf( "Deep BSP tree generation enabled\n" );
                        deepBSP = qtrue;
                }
@@ -1049,8 +1064,13 @@ int BSPMain( int argc, char **argv ){
                        argv[ i ] = NULL;
                        Sys_Printf( "Use %s as surface file\n", surfaceFilePath );
                }
-               else{
-                       Sys_FPrintf( SYS_WRN, "WARNING: Unknown option \"%s\"\n", argv[ i ] );
+               else if ( !strcmp( argv[ i ], "-noob" ) ) {
+                       Sys_Printf( "No oBs!\n" );
+                       noob = qtrue;
+               }
+               else
+               {
+                       Sys_Printf( "WARNING: Unknown option \"%s\"\n", argv[ i ] );
                }
        }
 
index db14ea452230674f9a835ad359e43f505022726d..0530fc1f4b1cf0afe92f64c7ed400825d8f982e9 100644 (file)
@@ -68,14 +68,19 @@ void IncDrawVerts(){
 
        }
        else if ( numBSPDrawVerts > numBSPDrawVertsBuffer ) {
+               bspDrawVert_t *newBspDrawVerts;
+
                numBSPDrawVertsBuffer *= 3; // multiply by 1.5
                numBSPDrawVertsBuffer /= 2;
 
-               bspDrawVerts = realloc( bspDrawVerts, sizeof( bspDrawVert_t ) * numBSPDrawVertsBuffer );
+               newBspDrawVerts = realloc( bspDrawVerts, sizeof( bspDrawVert_t ) * numBSPDrawVertsBuffer );
 
-               if ( !bspDrawVerts ) {
+               if ( !newBspDrawVerts ) {
+                       free (bspDrawVerts);
                        Error( "realloc() failed (IncDrawVerts)" );
                }
+
+               bspDrawVerts = newBspDrawVerts;
        }
 
        memset( bspDrawVerts + ( numBSPDrawVerts - 1 ), 0, sizeof( bspDrawVert_t ) );
@@ -163,7 +168,7 @@ void SwapBlock( int *block, int size ){
 
 void SwapBSPFile( void ){
        int i, j;
-
+       shaderInfo_t    *si;
 
        /* models */
        SwapBlock( (int*) bspModels, numBSPModels * sizeof( bspModels[ 0 ] ) );
@@ -171,6 +176,12 @@ void SwapBSPFile( void ){
        /* shaders (don't swap the name) */
        for ( i = 0; i < numBSPShaders ; i++ )
        {
+       if ( doingBSP ){
+               si = ShaderInfoForShader( bspShaders[ i ].shader );
+               if ( si->remapShader && si->remapShader[ 0 ] ) {
+                       strcpy( bspShaders[ i ].shader, si->remapShader );
+               }
+       }
                bspShaders[ i ].contentFlags = LittleLong( bspShaders[ i ].contentFlags );
                bspShaders[ i ].surfaceFlags = LittleLong( bspShaders[ i ].surfaceFlags );
        }
@@ -251,8 +262,6 @@ void SwapBSPFile( void ){
        }
 }
 
-
-
 /*
    GetLumpElements()
    gets the number of elements in a bsp lump
@@ -365,7 +374,36 @@ void LoadBSPFile( const char *filename ){
        SwapBSPFile();
 }
 
+/*
+   PartialLoadBSPFile()
+   partially loads a bsp file into memory
+   for autopacker
+ */
 
+void PartialLoadBSPFile( const char *filename ){
+       /* dummy check */
+       if ( game == NULL || game->load == NULL ) {
+               Error( "LoadBSPFile: unsupported BSP file format" );
+       }
+
+       /* load it, then byte swap the in-memory version */
+       //game->load( filename );
+       PartialLoadIBSPFile( filename );
+
+       /* PartialSwapBSPFile() */
+       int i;
+
+       /* shaders (don't swap the name) */
+       for ( i = 0; i < numBSPShaders ; i++ )
+       {
+               bspShaders[ i ].contentFlags = LittleLong( bspShaders[ i ].contentFlags );
+               bspShaders[ i ].surfaceFlags = LittleLong( bspShaders[ i ].surfaceFlags );
+       }
+
+       /* drawsurfs */
+       /* note: rbsp files (and hence q3map2 abstract bsp) have byte lightstyles index arrays, this follows sof2map convention */
+       SwapBlock( (int*) bspDrawSurfaces, numBSPDrawSurfaces * sizeof( bspDrawSurfaces[ 0 ] ) );
+}
 
 /*
    WriteBSPFile()
@@ -408,7 +446,12 @@ void PrintBSPFileSizes( void ){
        if ( numEntities <= 0 ) {
                ParseEntities();
        }
-
+       int patchCount = 0;
+       bspDrawSurface_t *s;
+       for ( s = bspDrawSurfaces; s != bspDrawSurfaces + numBSPDrawSurfaces; ++s ){
+               if ( s->surfaceType == MST_PATCH )
+                       ++patchCount;
+       }
        /* note that this is abstracted */
        Sys_Printf( "Abstracted BSP file components (*actual sizes may differ)\n" );
 
@@ -441,6 +484,8 @@ void PrintBSPFileSizes( void ){
 
        Sys_Printf( "%9d drawsurfaces  %9d *\n",
                                numBSPDrawSurfaces, (int) ( numBSPDrawSurfaces * sizeof( *bspDrawSurfaces ) ) );
+       Sys_Printf( "%9d patchsurfaces       \n",
+                               patchCount );
        Sys_Printf( "%9d drawverts     %9d *\n",
                                numBSPDrawVerts, (int) ( numBSPDrawVerts * sizeof( *bspDrawVerts ) ) );
        Sys_Printf( "%9d drawindexes   %9d\n",
@@ -589,6 +634,9 @@ void InjectCommandLine( char **argv, int beginArgs, int endArgs ){
        char *sentinel = newCommandLine + sizeof( newCommandLine ) - 1;
        int i;
 
+       if ( nocmdline ){
+               return;
+       }
        previousCommandLine = ValueForKey( &entities[0], "_q3map2_cmdline" );
        if ( previousCommandLine && *previousCommandLine ) {
                inpos = previousCommandLine;
@@ -926,7 +974,7 @@ void GetEntityShadowFlags( const entity_t *ent, const entity_t *ent2, int *castS
                }
        }
 
-       /* vortex: game-specific default eneity keys */
+       /* vortex: game-specific default entity keys */
        value = ValueForKey( ent, "classname" );
        if ( !Q_stricmp( game->magic, "dq" ) || !Q_stricmp( game->magic, "prophecy" ) ) {
                /* vortex: deluxe quake default shadow flags */
index 1c4c994a894fb5fad23ea32e5b6cc472b1aa503f..d4c7e1cf3d09461af30f4a65908ebdea30a3fcbf 100644 (file)
@@ -459,7 +459,7 @@ void LoadIBSPFile( const char *filename ){
        SwapBlock( (int*) ( (byte*) header + sizeof( int ) ), sizeof( *header ) - sizeof( int ) );
 
        /* make sure it matches the format we're trying to load */
-       if ( force == qfalse && *( (int*) header->ident ) != *( (int*) game->bspIdent ) ) {
+       if ( force == qfalse && memcmp( header->ident, game->bspIdent, 4 ) != 0 ) {
                Error( "%s is not a %s file", filename, game->bspIdent );
        }
        if ( force == qfalse && header->version != game->bspVersion ) {
@@ -515,7 +515,39 @@ void LoadIBSPFile( const char *filename ){
        free( header );
 }
 
+/*
+   PartialLoadIBSPFile()
+   loads a part of quake 3 bsp file, required by packer, into memory
+ */
+
+void PartialLoadIBSPFile( const char *filename ){
+       ibspHeader_t    *header;
+
+
+       /* load the file header */
+       LoadFile( filename, (void**) &header );
 
+       /* swap the header (except the first 4 bytes) */
+       SwapBlock( (int*) ( (byte*) header + sizeof( int ) ), sizeof( *header ) - sizeof( int ) );
+
+       /* make sure it matches the format we're trying to load */
+       if ( force == qfalse && *( (int*) header->ident ) != *( (int*) game->bspIdent ) ) {
+               Error( "%s is not a %s file", filename, game->bspIdent );
+       }
+       if ( force == qfalse && header->version != game->bspVersion ) {
+               Error( "%s is version %d, not %d", filename, header->version, game->bspVersion );
+       }
+
+       /* load/convert lumps */
+       numBSPShaders = CopyLump_Allocate( (bspHeader_t*) header, LUMP_SHADERS, (void **) &bspShaders, sizeof( bspShader_t ), &allocatedBSPShaders );
+
+       CopyDrawSurfacesLump( header );
+
+       bspEntDataSize = CopyLump_Allocate( (bspHeader_t*) header, LUMP_ENTITIES, (void **) &bspEntData, 1, &allocatedBSPEntData );
+
+       /* free the file buffer */
+       free( header );
+}
 
 /*
    WriteIBSPFile()
@@ -526,7 +558,6 @@ void WriteIBSPFile( const char *filename ){
        ibspHeader_t outheader, *header;
        FILE            *file;
        time_t t;
-       char marker[ 1024 ];
        int size;
 
 
@@ -537,7 +568,7 @@ void WriteIBSPFile( const char *filename ){
        //%     Swapfile();
 
        /* set up header */
-       *( (int*) (bspHeader_t*) header->ident ) = *( (int*) game->bspIdent );
+       memcpy(header->ident, game->bspIdent, 4);
        header->version = LittleLong( game->bspVersion );
 
        /* write initial header */
@@ -545,10 +576,7 @@ void WriteIBSPFile( const char *filename ){
        SafeWrite( file, (bspHeader_t*) header, sizeof( *header ) );    /* overwritten later */
 
        /* add marker lump */
-       time( &t );
-
-       /* asctime adds an implicit trailing \n */
-       sprintf( marker, "I LOVE MY Q3MAP2 %s on %s", Q3MAP_VERSION, asctime( localtime( &t ) ) );
+       const char marker[] = "I LOVE MY Q3MAP2";
        AddLump( file, (bspHeader_t*) header, 0, marker, strlen( marker ) + 1 );
 
        /* add lumps */
index 88ebcbf4e8dc5705ba9363c5bd1af7375582bbfa..598cf2de9dd75c44d30903de697095d60ed8f770 100644 (file)
@@ -284,7 +284,6 @@ void WriteRBSPFile( const char *filename ){
        rbspHeader_t outheader, *header;
        FILE            *file;
        time_t t;
-       char marker[ 1024 ];
        int size;
 
 
@@ -303,9 +302,8 @@ void WriteRBSPFile( 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 ) ) );
-       AddLump( file, (bspHeader_t*) header, 0, marker, strlen( marker ) + 1 );
+       const char marker[] = "I LOVE MY Q3MAP2";
+       AddLump( file, header, 0, marker, strlen( marker ) + 1 );
 
        /* add lumps */
        AddLump( file, (bspHeader_t*) header, LUMP_SHADERS, bspShaders, numBSPShaders * sizeof( bspShader_t ) );
index 04c4a4d0872f0a7047a33205ecb5507304fcadfa..3081ef779fcb7cf084558cd448aeaaf30a590f1d 100644 (file)
@@ -88,13 +88,16 @@ void GetBestSurfaceTriangleMatchForBrushside( side_t *buildSide, bspDrawVert_t *
                        vert[1] = &bspDrawVerts[s->firstVert + bspDrawIndexes[s->firstIndex + t + 1]];
                        vert[2] = &bspDrawVerts[s->firstVert + bspDrawIndexes[s->firstIndex + t + 2]];
                        if ( s->surfaceType == MST_PLANAR && VectorCompare( vert[0]->normal, vert[1]->normal ) && VectorCompare( vert[1]->normal, vert[2]->normal ) ) {
-                               VectorSubtract( vert[0]->normal, buildPlane->normal, normdiff ); if ( VectorLength( normdiff ) >= normalEpsilon ) {
+                               VectorSubtract( vert[0]->normal, buildPlane->normal, normdiff );
+                               if ( VectorLength( normdiff ) >= normalEpsilon ) {
                                        continue;
                                }
-                               VectorSubtract( vert[1]->normal, buildPlane->normal, normdiff ); if ( VectorLength( normdiff ) >= normalEpsilon ) {
+                               VectorSubtract( vert[1]->normal, buildPlane->normal, normdiff );
+                               if ( VectorLength( normdiff ) >= normalEpsilon ) {
                                        continue;
                                }
-                               VectorSubtract( vert[2]->normal, buildPlane->normal, normdiff ); if ( VectorLength( normdiff ) >= normalEpsilon ) {
+                               VectorSubtract( vert[2]->normal, buildPlane->normal, normdiff );
+                               if ( VectorLength( normdiff ) >= normalEpsilon ) {
                                        continue;
                                }
                        }
@@ -106,7 +109,8 @@ void GetBestSurfaceTriangleMatchForBrushside( side_t *buildSide, bspDrawVert_t *
                                VectorSubtract( vert[2]->xyz, vert[0]->xyz, v2v0 );
                                CrossProduct( v2v0, v1v0, norm );
                                VectorNormalize( norm, norm );
-                               VectorSubtract( norm, buildPlane->normal, normdiff ); if ( VectorLength( normdiff ) >= normalEpsilon ) {
+                               VectorSubtract( norm, buildPlane->normal, normdiff );
+                               if ( VectorLength( normdiff ) >= normalEpsilon ) {
                                        continue;
                                }
                        }
@@ -220,17 +224,83 @@ static void ConvertOriginBrush( FILE *f, int num, vec3_t origin, qboolean brushP
        fprintf( f, "\t}\n\n" );
 }
 
-static void ConvertBrush( FILE *f, int num, bspBrush_t *brush, vec3_t origin, qboolean brushPrimitives ){
-       int i, j;
+static void ConvertBrushFast( FILE *f, int num, bspBrush_t *brush, vec3_t origin, qboolean brushPrimitives ){
+       int i;
        bspBrushSide_t  *side;
        side_t          *buildSide;
        bspShader_t     *shader;
        char            *texture;
        plane_t         *buildPlane;
        vec3_t pts[ 3 ];
-       bspDrawVert_t   *vert[3];
 
 
+       /* clear out build brush */
+       for ( i = 0; i < buildBrush->numsides; i++ )
+       {
+               buildSide = &buildBrush->sides[ i ];
+               if ( buildSide->winding != NULL ) {
+                       FreeWinding( buildSide->winding );
+                       buildSide->winding = NULL;
+               }
+       }
+       buildBrush->numsides = 0;
+
+       qboolean modelclip = qfalse;
+       /* try to guess if thats model clip */
+       if ( force ){
+               int notNoShader = 0;
+               modelclip = qtrue;
+               for ( i = 0; i < brush->numSides; i++ )
+               {
+                       /* get side */
+                       side = &bspBrushSides[ brush->firstSide + i ];
+
+                       /* get shader */
+                       if ( side->shaderNum < 0 || side->shaderNum >= numBSPShaders ) {
+                               continue;
+                       }
+                       shader = &bspShaders[ side->shaderNum ];
+                       //"noshader" happens on modelclip and unwanted sides ( usually breaking complex brushes )
+                       if( Q_stricmp( shader->shader, "noshader" ) ){
+                               notNoShader++;
+                       }
+                       if( notNoShader > 1 ){
+                               modelclip = qfalse;
+                               break;
+                       }
+               }
+       }
+
+       /* iterate through bsp brush sides */
+       for ( i = 0; i < brush->numSides; i++ )
+       {
+               /* get side */
+               side = &bspBrushSides[ brush->firstSide + i ];
+
+               /* get shader */
+               if ( side->shaderNum < 0 || side->shaderNum >= numBSPShaders ) {
+                       continue;
+               }
+               shader = &bspShaders[ side->shaderNum ];
+               //"noshader" happens on modelclip and unwanted sides ( usually breaking complex brushes )
+               if( !Q_stricmp( shader->shader, "default" ) || ( !Q_stricmp( shader->shader, "noshader" ) && !modelclip ) )
+                       continue;
+
+               /* add build side */
+               buildSide = &buildBrush->sides[ buildBrush->numsides ];
+               buildBrush->numsides++;
+
+               /* tag it */
+               buildSide->shaderInfo = ShaderInfoForShader( shader->shader );
+               buildSide->planenum = side->planeNum;
+               buildSide->winding = NULL;
+       }
+
+       if ( !CreateBrushWindings( buildBrush ) ) {
+               //Sys_Printf( "CreateBrushWindings failed\n" );
+               return;
+       }
+
        /* start brush */
        fprintf( f, "\t// brush %d\n", num );
        fprintf( f, "\t{\n" );
@@ -239,6 +309,81 @@ static void ConvertBrush( FILE *f, int num, bspBrush_t *brush, vec3_t origin, qb
                fprintf( f, "\t{\n" );
        }
 
+       /* iterate through build brush sides */
+       for ( i = 0; i < buildBrush->numsides; i++ )
+       {
+               /* get build side */
+               buildSide = &buildBrush->sides[ i ];
+
+               /* get plane */
+               buildPlane = &mapplanes[ buildSide->planenum ];
+
+               /* dummy check */
+               if ( buildSide->shaderInfo == NULL || buildSide->winding == NULL ) {
+                       continue;
+               }
+
+               /* get texture name */
+               if ( !Q_strncasecmp( buildSide->shaderInfo->shader, "textures/", 9 ) ) {
+                       texture = buildSide->shaderInfo->shader + 9;
+               }
+               else{
+                       texture = buildSide->shaderInfo->shader;
+               }
+
+               {
+                       vec3_t vecs[ 2 ];
+                       MakeNormalVectors( buildPlane->normal, vecs[ 0 ], vecs[ 1 ] );
+                       VectorMA( vec3_origin, buildPlane->dist, buildPlane->normal, pts[ 0 ] );
+                       VectorAdd( pts[ 0 ], origin, pts[ 0 ] );
+                       VectorMA( pts[ 0 ], 256.0f, vecs[ 0 ], pts[ 1 ] );
+                       VectorMA( pts[ 0 ], 256.0f, vecs[ 1 ], pts[ 2 ] );
+               }
+
+               {
+                       if ( brushPrimitives ) {
+                               fprintf( f, "\t\t( %.3f %.3f %.3f ) ( %.3f %.3f %.3f ) ( %.3f %.3f %.3f ) ( ( %.8f %.8f %.8f ) ( %.8f %.8f %.8f ) ) %s %d 0 0\n",
+                                                pts[ 0 ][ 0 ], pts[ 0 ][ 1 ], pts[ 0 ][ 2 ],
+                                                pts[ 1 ][ 0 ], pts[ 1 ][ 1 ], pts[ 1 ][ 2 ],
+                                                pts[ 2 ][ 0 ], pts[ 2 ][ 1 ], pts[ 2 ][ 2 ],
+                                                1.0f / 32.0f, 0.0f, 0.0f,
+                                                0.0f, 1.0f / 32.0f, 0.0f,
+                                                texture,
+                                                0
+                                                );
+                       }
+                       else
+                       {
+                               fprintf( f, "\t\t( %.3f %.3f %.3f ) ( %.3f %.3f %.3f ) ( %.3f %.3f %.3f ) %s %.8f %.8f %.8f %.8f %.8f %d 0 0\n",
+                                                pts[ 0 ][ 0 ], pts[ 0 ][ 1 ], pts[ 0 ][ 2 ],
+                                                pts[ 1 ][ 0 ], pts[ 1 ][ 1 ], pts[ 1 ][ 2 ],
+                                                pts[ 2 ][ 0 ], pts[ 2 ][ 1 ], pts[ 2 ][ 2 ],
+                                                texture,
+                                                0.0f, 0.0f, 0.0f, 0.5f, 0.5f,
+                                                0
+                                                );
+                       }
+               }
+       }
+
+       /* end brush */
+       if ( brushPrimitives ) {
+               fprintf( f, "\t}\n" );
+       }
+       fprintf( f, "\t}\n\n" );
+}
+
+static void ConvertBrush( FILE *f, int num, bspBrush_t *brush, vec3_t origin, qboolean brushPrimitives ){
+       int i, j;
+       bspBrushSide_t  *side;
+       side_t          *buildSide;
+       bspShader_t     *shader;
+       char            *texture;
+       plane_t         *buildPlane;
+       vec3_t pts[ 3 ];
+       bspDrawVert_t   *vert[3];
+
+
        /* clear out build brush */
        for ( i = 0; i < buildBrush->numsides; i++ )
        {
@@ -250,6 +395,32 @@ static void ConvertBrush( FILE *f, int num, bspBrush_t *brush, vec3_t origin, qb
        }
        buildBrush->numsides = 0;
 
+       qboolean modelclip = qfalse;
+       /* try to guess if thats model clip */
+       if ( force ){
+               int notNoShader = 0;
+               modelclip = qtrue;
+               for ( i = 0; i < brush->numSides; i++ )
+               {
+                       /* get side */
+                       side = &bspBrushSides[ brush->firstSide + i ];
+
+                       /* get shader */
+                       if ( side->shaderNum < 0 || side->shaderNum >= numBSPShaders ) {
+                               continue;
+                       }
+                       shader = &bspShaders[ side->shaderNum ];
+                       //"noshader" happens on modelclip and unwanted sides ( usually breaking complex brushes )
+                       if( Q_stricmp( shader->shader, "noshader" ) ){
+                               notNoShader++;
+                       }
+                       if( notNoShader > 1 ){
+                               modelclip = qfalse;
+                               break;
+                       }
+               }
+       }
+
        /* iterate through bsp brush sides */
        for ( i = 0; i < brush->numSides; i++ )
        {
@@ -261,8 +432,9 @@ static void ConvertBrush( FILE *f, int num, bspBrush_t *brush, vec3_t origin, qb
                        continue;
                }
                shader = &bspShaders[ side->shaderNum ];
-               //if( !Q_stricmp( shader->shader, "default" ) || !Q_stricmp( shader->shader, "noshader" ) )
-               //      continue;
+               //"noshader" happens on modelclip and unwanted sides ( usually breaking complex brushes )
+               if( !Q_stricmp( shader->shader, "default" ) || ( !Q_stricmp( shader->shader, "noshader" ) && !modelclip ) )
+                       continue;
 
                /* add build side */
                buildSide = &buildBrush->sides[ buildBrush->numsides ];
@@ -276,10 +448,18 @@ static void ConvertBrush( FILE *f, int num, bspBrush_t *brush, vec3_t origin, qb
 
        /* make brush windings */
        if ( !CreateBrushWindings( buildBrush ) ) {
-               Sys_Printf( "CreateBrushWindings failed\n" );
+               //Sys_Printf( "CreateBrushWindings failed\n" );
                return;
        }
 
+       /* start brush */
+       fprintf( f, "\t// brush %d\n", num );
+       fprintf( f, "\t{\n" );
+       if ( brushPrimitives ) {
+               fprintf( f, "\tbrushDef\n" );
+               fprintf( f, "\t{\n" );
+       }
+
        /* iterate through build brush sides */
        for ( i = 0; i < buildBrush->numsides; i++ )
        {
@@ -314,13 +494,51 @@ static void ConvertBrush( FILE *f, int num, bspBrush_t *brush, vec3_t origin, qb
                        texture = buildSide->shaderInfo->shader;
                }
 
-               /* get plane points and offset by origin */
-               for ( j = 0; j < 3; j++ )
-               {
-                       VectorAdd( buildSide->winding->p[ j ], origin, pts[ j ] );
-                       //%     pts[ j ][ 0 ] = SNAP_INT_TO_FLOAT * floor( pts[ j ][ 0 ] * SNAP_FLOAT_TO_INT + 0.5f );
-                       //%     pts[ j ][ 1 ] = SNAP_INT_TO_FLOAT * floor( pts[ j ][ 1 ] * SNAP_FLOAT_TO_INT + 0.5f );
-                       //%     pts[ j ][ 2 ] = SNAP_INT_TO_FLOAT * floor( pts[ j ][ 2 ] * SNAP_FLOAT_TO_INT + 0.5f );
+               /* recheck and fix winding points, fails occur somehow */
+               int match = 0;
+               for ( j = 0; j < buildSide->winding->numpoints; j++ ){
+                       if ( fabs( DotProduct( buildSide->winding->p[ j ], buildPlane->normal ) - buildPlane->dist ) >= distanceEpsilon ) {
+                               continue;
+                       }
+                       else{
+                               VectorCopy( buildSide->winding->p[ j ], pts[ match ] );
+                               match++;
+                               /* got 3 fine points? */
+                               if( match > 2 )
+                                       break;
+                       }
+               }
+
+               if( match > 2 ){
+                       //Sys_Printf( "pointsKK " );
+                       vec4_t testplane;
+                       if ( PlaneFromPoints( testplane, pts[0], pts[1], pts[2] ) ){
+                               if( !PlaneEqual( buildPlane, testplane, testplane[3] ) ){
+                                       //Sys_Printf( "1: %f %f %f %f\n2: %f %f %f %f\n", buildPlane->normal[0], buildPlane->normal[1], buildPlane->normal[2], buildPlane->dist, testplane[0], testplane[1], testplane[2], testplane[3] );
+                                       match--;
+                                       //Sys_Printf( "planentEQ " );
+                               }
+                       }
+                       else{
+                               match--;
+                       }
+               }
+
+
+               if( match > 2 ){
+                       //Sys_Printf( "ok " );
+                       /* offset by origin */
+                       for ( j = 0; j < 3; j++ )
+                               VectorAdd( pts[ j ], origin, pts[ j ] );
+               }
+               else{
+                       vec3_t vecs[ 2 ];
+                       MakeNormalVectors( buildPlane->normal, vecs[ 0 ], vecs[ 1 ] );
+                       VectorMA( vec3_origin, buildPlane->dist, buildPlane->normal, pts[ 0 ] );
+                       VectorAdd( pts[ 0 ], origin, pts[ 0 ] );
+                       VectorMA( pts[ 0 ], 256.0f, vecs[ 0 ], pts[ 1 ] );
+                       VectorMA( pts[ 0 ], 256.0f, vecs[ 1 ], pts[ 2 ] );
+                       //Sys_Printf( "not\n" );
                }
 
                if ( vert[0] && vert[1] && vert[2] ) {
@@ -495,20 +713,21 @@ static void ConvertBrush( FILE *f, int num, bspBrush_t *brush, vec3_t origin, qb
                }
                else
                {
-                       vec3_t vecs[ 2 ];
+                       //vec3_t vecs[ 2 ];
                        if ( strncmp( buildSide->shaderInfo->shader, "textures/common/", 16 ) ) {
                                if ( strcmp( buildSide->shaderInfo->shader, "noshader" ) ) {
                                        if ( strcmp( buildSide->shaderInfo->shader, "default" ) ) {
-                                               fprintf( stderr, "no matching triangle for brushside using %s (hopefully nobody can see this side anyway)\n", buildSide->shaderInfo->shader );
+                                               //fprintf( stderr, "no matching triangle for brushside using %s (hopefully nobody can see this side anyway)\n", buildSide->shaderInfo->shader );
                                                texture = "common/WTF";
                                        }
                                }
                        }
-
+/*
                        MakeNormalVectors( buildPlane->normal, vecs[ 0 ], vecs[ 1 ] );
                        VectorMA( vec3_origin, buildPlane->dist, buildPlane->normal, pts[ 0 ] );
-                       VectorMA( pts[ 0 ], 256.0f, vecs[ 0 ], pts[ 1 ] );
                        VectorMA( pts[ 0 ], 256.0f, vecs[ 1 ], pts[ 2 ] );
+                       VectorMA( pts[ 0 ], 256.0f, vecs[ 0 ], pts[ 1 ] );
+*/
                        if ( brushPrimitives ) {
                                fprintf( f, "\t\t( %.3f %.3f %.3f ) ( %.3f %.3f %.3f ) ( %.3f %.3f %.3f ) ( ( %.8f %.8f %.8f ) ( %.8f %.8f %.8f ) ) %s %d 0 0\n",
                                                 pts[ 0 ][ 0 ], pts[ 0 ][ 1 ], pts[ 0 ][ 2 ],
@@ -717,7 +936,12 @@ static void ConvertModel( FILE *f, bspModel_t *model, int modelNum, vec3_t origi
        {
                num = i + model->firstBSPBrush;
                brush = &bspBrushes[ num ];
-               ConvertBrush( f, num, brush, origin, brushPrimitives );
+               if( fast ){
+                       ConvertBrushFast( f, num, brush, origin, brushPrimitives );
+               }
+               else{
+                       ConvertBrush( f, num, brush, origin, brushPrimitives );
+               }
        }
 
        /* free the build brush */
index 900e0cb8eb568b01819829942731cf2d2c85c2bc..5b2ca6f79483de33db476d6986f057ed07fffa3d 100644 (file)
@@ -712,7 +712,7 @@ int FogForBounds( vec3_t mins, vec3_t maxs, float epsilon ){
  */
 
 void CreateMapFogs( void ){
-       int i;
+       int i, j;
        entity_t    *entity;
        brush_t     *brush;
        fog_t       *fog;
@@ -759,10 +759,10 @@ void CreateMapFogs( void ){
                                VectorScale( fog->si->fogDir, -1.0f, invFogDir );
 
                                /* find the brush side */
-                               for ( i = 0; i < brush->numsides; i++ )
+                               for ( j = 0; j < brush->numsides; j++ )
                                {
-                                       if ( VectorCompare( invFogDir, mapplanes[ brush->sides[ i ].planenum ].normal ) ) {
-                                               fog->visibleSide = i;
+                                       if ( VectorCompare( invFogDir, mapplanes[ brush->sides[ j ].planenum ].normal ) ) {
+                                               fog->visibleSide = j;
                                                //%     Sys_Printf( "Brush num: %d Side num: %d\n", fog->brushNum, fog->visibleSide );
                                                break;
                                        }
index e20493a5928c86f99bd226a87c3fee552a8f2990..6362369ae085e42e09e6807ba688ec036a0af21c 100644 (file)
@@ -68,7 +68,7 @@
 #define Q_CONT_NODROP               0x80000000  /* don't leave bodies or items (death fog, lava) */
 
 #define Q_SURF_NODAMAGE             0x1         /* never give falling damage */
-#define Q_SURF_SLICK                0x2         /* effects game physics */
+#define Q_SURF_SLICK                0x2         /* effects game physics: zero friction on this */
 #define Q_SURF_SKY                  0x4         /* lighting from environment map */
 #define Q_SURF_LADDER               0x8
 #define Q_SURF_NOIMPACT             0x10        /* don't make missile explosions */
@@ -86,6 +86,7 @@
 #define Q_SURF_ALPHASHADOW          0x10000     /* do per-pixel light shadow casting in q3map */
 #define Q_SURF_NODLIGHT             0x20000     /* don't dlight even if solid (solid lava, skies) */
 #define Q_SURF_DUST                 0x40000     /* leave a dust trail when walking on this surface */
+#define Q_SURF_NOOB                 0x80000     /* no overbounces on this surface */
 
 /* ydnar flags */
 #define Q_SURF_VERTEXLIT            ( Q_SURF_POINTLIGHT | Q_SURF_NOLIGHTMAP )
                { "nosteps",        0,                          0,                          Q_SURF_NOSTEPS,             0,                          0,                          0 },
                { "nodlight",       0,                          0,                          Q_SURF_NODLIGHT,            0,                          0,                          0 },
                { "dust",           0,                          0,                          Q_SURF_DUST,                0,                          0,                          0 },
+               { "noob",           0,                          0,                          Q_SURF_NOOB,                0,                          0,                          0 },
+               { "ob",             0,                          0,                          0,                          0,                          C_OB,                       0 },
+
 
                /* null */
                { NULL, 0, 0, 0, 0, 0, 0 }
index 60631e3d72f175a38c581b28a50df232ea8289ab..4f5b2b5f39e3e559e5cbf9b1bd009f020a8b69ab 100644 (file)
@@ -215,6 +215,7 @@ void HelpLight()
                {"-export", "Export lightmaps when compile finished (like `-export` mode)"},
                {"-exposure <F>", "Lightmap exposure to better support overbright spots"},
                {"-external", "Force external lightmaps even if at size of internal lightmaps"},
+               {"-externalnames", "Write lightstyle shader using external lightmap names"},
                {"-extradist <F>", "Extra distance for lights in map units"},
                {"-extravisnudge", "Broken feature to nudge the luxel origin to a better vis cluster"},
                {"-extrawide", "Deprecated alias for `-super 2 -filter`"},
@@ -533,7 +534,7 @@ void HelpMain(const char* arg)
                                help_funcs[i]();
                                return;
                        }
-               }
+       }
        }
 
        HelpOptions("Stages", 0, terminalColumns, stages, sizeof(stages)/sizeof(struct HelpOption));
index 851499d52accc4f20c4477a753cd1f0e4a3f4d16..f5c0b1e90f7c4082bad7b6e41785e545087c282a 100644 (file)
@@ -479,11 +479,12 @@ image_t *ImageLoad( const char *filename ){
 
                /* also look for .dds image in dds/ prefix like Doom3 or DarkPlaces */
                if ( size <= 0 ) {
-                       strcpy( name, "dds/" );
-                       strcat( name, image->name );
-                       StripExtension( name );
-                       strcat( name, ".dds" );
-                       size = vfsLoadFile( (const char*) name, (void**) &buffer, 0 );
+                       char ddsname[ 1024 ];
+                       strcpy( ddsname, "dds/" );
+                       strcat( ddsname, image->name );
+                       StripExtension( ddsname );
+                       strcat( ddsname, ".dds" );
+                       size = vfsLoadFile( (const char*) ddsname, (void**) &buffer, 0 );
                }
 
                if ( size > 0 ) {
@@ -504,7 +505,7 @@ image_t *ImageLoad( const char *filename ){
                /* attempt to load crn */
                StripExtension( name );
                strcat( name, ".crn" );
-               size = vfsLoadFile( ( const char* ) name, ( void** ) &buffer, 0 );
+               size = vfsLoadFile( (const char*) name, (void**) &buffer, 0 );
                if ( size > 0 ) {
                        LoadCRNBuffer( buffer, size, &image->pixels, &image->width, &image->height );
                        break;
index 99d794840f6787fa836f2450ea2060afc44a1b37..26b2a347bfc9d335acd3a6dc50bfbdb495ca46c4 100644 (file)
@@ -717,7 +717,7 @@ float PointToPolygonFormFactor( const vec3_t point, const vec3_t normal, const w
        for ( i = 0; i < w->numpoints; i++ )
        {
                VectorSubtract( w->p[ i ], point, dirs[ i ] );
-               VectorNormalize( dirs[ i ], dirs[ i ] );
+               VectorFastNormalize( dirs[ i ], dirs[ i ] );
        }
 
        /* duplicate first vertex to avoid mod operation */
@@ -743,7 +743,7 @@ float PointToPolygonFormFactor( const vec3_t point, const vec3_t normal, const w
                angle = acos( dot );
 
                CrossProduct( dirs[ i ], dirs[ j ], triVector );
-               if ( VectorNormalize( triVector, triNormal ) < 0.0001f ) {
+               if ( VectorFastNormalize( triVector, triNormal ) < 0.0001f ) {
                        continue;
                }
 
@@ -1780,7 +1780,7 @@ void TraceGrid( int num ){
                         */
                        ColorToBytesNonZero(color, bgp->ambient[i], gridScale * gridAmbientScale);
                } else {
-                       ColorToBytes(color, bgp->ambient[i], gridScale * gridAmbientScale);
+               ColorToBytes( color, bgp->ambient[ i ], gridScale * gridAmbientScale );
                }
                ColorToBytes( gp->directed[ i ], bgp->directed[ i ], gridScale );
        }
@@ -1874,11 +1874,12 @@ void SetupGrid( void ){
        /* clear lightgrid */
        for ( i = 0; i < numRawGridPoints; i++ )
        {
-               VectorCopy( ambientColor, rawGridPoints[ i ].ambient[ j ] );
+               VectorCopy( ambientColor, rawGridPoints[ i ].ambient[ 0 ] );
                rawGridPoints[ i ].styles[ 0 ] = LS_NORMAL;
                bspGridPoints[ i ].styles[ 0 ] = LS_NORMAL;
                for ( j = 1; j < MAX_LIGHTMAPS; j++ )
                {
+                       VectorCopy( ambientColor, rawGridPoints[ i ].ambient[ j ] );
                        rawGridPoints[ i ].styles[ j ] = LS_NONE;
                        bspGridPoints[ i ].styles[ j ] = LS_NONE;
                }
@@ -2045,8 +2046,8 @@ void LightWorld( const char *BSPFilePath, qboolean fastAllocate, qboolean noBoun
                UnparseEntities();
 
                if ( storeForReal ) {
-                       Sys_Printf( "Writing %s\n", BSPFilePath );
-                       WriteBSPFile( BSPFilePath );
+               Sys_Printf( "Writing %s\n", BSPFilePath );
+               WriteBSPFile( BSPFilePath );
                }
 
                /* note it */
@@ -2153,7 +2154,7 @@ int LightMain( int argc, char **argv ){
        const char  *value;
        int lightmapMergeSize = 0;
        qboolean lightSamplesInsist = qfalse;
-       qboolean fastAllocate = qfalse;
+       qboolean fastAllocate = qtrue;
        qboolean noBounceStore = qfalse;
 
        /* note it */
@@ -2289,6 +2290,43 @@ int LightMain( int argc, char **argv ){
                        i++;
                }
 
+               else if ( !strcmp( argv[ i ], "-vertexscale" ) ) {
+                       f = atof( argv[ i + 1 ] );
+                       vertexglobalscale *= f;
+                       Sys_Printf( "Vertexlight scaled by %f to %f\n", f, vertexglobalscale );
+                       i++;
+               }
+
+               else if ( !strcmp( argv[ i ], "-backsplash" ) && i < ( argc - 3 ) ) {
+                       f = atof( argv[ i + 1 ] );
+                       g_backsplashFractionScale = f;
+                       Sys_Printf( "Area lights backsplash fraction scaled by %f\n", f, g_backsplashFractionScale );
+                       f = atof( argv[ i + 2 ] );
+                       if ( f >= -900.0f ){
+                               g_backsplashDistance = f;
+                               Sys_Printf( "Area lights backsplash distance set globally to %f\n", f, g_backsplashDistance );
+                       }
+                       i+=2;
+               }
+
+               else if ( !strcmp( argv[ i ], "-nolm" ) ) {
+                       nolm = qtrue;
+                       Sys_Printf( "No lightmaps yo\n" );
+               }
+
+               else if ( !strcmp( argv[ i ], "-bouncecolorratio" ) ) {
+                       f = atof( argv[ i + 1 ] );
+                       bounceColorRatio *= f;
+                       if ( bounceColorRatio > 1 ) {
+                               bounceColorRatio = 1;
+                       }
+                       if ( bounceColorRatio < 0 ) {
+                               bounceColorRatio = 0;
+                       }
+                       Sys_Printf( "Bounce color ratio set to %f\n", bounceColorRatio );
+                       i++;
+               }
+
                else if ( !strcmp( argv[ i ], "-bouncescale" ) ) {
                        f = atof( argv[ i + 1 ] );
                        bounceScale *= f;
@@ -2419,6 +2457,30 @@ int LightMain( int argc, char **argv ){
                        i++;
                }
 
+               /* Lighting brightness */
+               else if( !strcmp( argv[ i ], "-brightness" ) ){
+                       f = atof( argv[ i + 1 ] );
+                       lightmapBrightness = f;
+                       Sys_Printf( "Lighting brightness set to %f\n", lightmapBrightness );
+                       i++;
+               }
+
+               /* Lighting contrast */
+               else if( !strcmp( argv[ i ], "-contrast" ) ){
+                       f = atof( argv[ i + 1 ] );
+                       lightmapContrast = f;
+                       if( lightmapContrast > 255 ){
+                               lightmapContrast = 255;
+                       }
+                       else if( lightmapContrast < -255 ){
+                               lightmapContrast = -255;
+                       }
+                       Sys_Printf( "Lighting contrast set to %f\n", lightmapContrast );
+                       i++;
+                       /* change to factor in range of 0 to 129.5 */
+                       lightmapContrast = ( 259 * ( lightmapContrast + 255 ) ) / ( 255 * ( 259 - lightmapContrast ) );
+               }
+
                /* ydnar switches */
                else if ( !strcmp( argv[ i ], "-bounce" ) ) {
                        bounce = atoi( argv[ i + 1 ] );
@@ -2546,6 +2608,12 @@ int LightMain( int argc, char **argv ){
                        Sys_Printf( "Storing all lightmaps externally\n" );
                }
 
+               else if ( !strcmp( argv[ i ], "-externalnames" ) ) {
+                       externalLightmaps = qtrue;
+                       externalLightmapNames = qtrue;
+                       Sys_Printf( "Writing lightstyle shader using external lightmap names\n" );
+               }
+
                else if ( !strcmp( argv[ i ], "-lightmapsize" ) ) {
                        lmCustomSize = atoi( argv[ i + 1 ] );
 
@@ -2698,6 +2766,11 @@ int LightMain( int argc, char **argv ){
                        Sys_Printf( "Slow lightmap allocation mode enabled (default)\n" );
                }
 
+               else if ( !strcmp( argv[ i ], "-slowallocate" ) ) {
+                       fastAllocate = qfalse;
+                       Sys_Printf( "Slow allocation mode enabled\n" );
+               }
+
                else if ( !strcmp( argv[ i ], "-fastgrid" ) ) {
                        fastgrid = qtrue;
                        Sys_Printf( "Fast grid lighting enabled\n" );
@@ -2808,9 +2881,20 @@ int LightMain( int argc, char **argv ){
                        i++;
                        Sys_Printf( "Lightmaps sample scale set to %d\n", sampleScale );
                }
+               else if ( !strcmp( argv[ i ],  "-debugsamplesize" ) ) {
+                       debugSampleSize = 1;
+                       Sys_Printf( "debugging Lightmaps SampleSize\n" );
+               }
                else if ( !strcmp( argv[ i ], "-novertex" ) ) {
-                       noVertexLighting = qtrue;
-                       Sys_Printf( "Disabling vertex lighting\n" );
+                       noVertexLighting = 1;
+                       if ( ( atof( argv[ i + 1 ] ) != 0 ) && ( atof( argv[ i + 1 ] )) < 1 ) {
+                               noVertexLighting = ( atof( argv[ i + 1 ] ) );
+                               i++;
+                               Sys_Printf( "Setting vertex lighting globally to %f\n", noVertexLighting );
+                       }
+                       else{
+                               Sys_Printf( "Disabling vertex lighting\n" );
+                       }
                }
                else if ( !strcmp( argv[ i ], "-nogrid" ) ) {
                        noGridLighting = qtrue;
@@ -2849,8 +2933,8 @@ int LightMain( int argc, char **argv ){
                                else{
                                        Sys_Printf( "Disabling half lambert light angle attenuation\n" );
                                }
+                               i++;
                        }
-                       i++;
                }
                else if ( !strcmp( argv[ i ], "-nostyle" ) || !strcmp( argv[ i ], "-nostyles" ) ) {
                        noStyles = qtrue;
@@ -2895,7 +2979,7 @@ int LightMain( int argc, char **argv ){
                                Sys_Printf( "Enabling randomized dirtmapping\n" );
                        }
                        else{
-                               Sys_Printf( "Enabling ordered dir mapping\n" );
+                               Sys_Printf( "Enabling ordered dirtmapping\n" );
                        }
                        i++;
                }
index 3245ad5380a473f465ef94da5e42143b00afc9cb..b0b838b8ccea11094e34b50f3dc1745e0dc64384 100644 (file)
@@ -185,10 +185,10 @@ static void RadClipWindingEpsilon( radWinding_t *in, vec3_t normal, vec_t dist,
        }
 
        /* error check */
-       if ( front->numVerts > maxPoints || front->numVerts > maxPoints ) {
+       if ( front->numVerts > maxPoints ) {
                Error( "RadClipWindingEpsilon: points exceeded estimate" );
        }
-       if ( front->numVerts > MAX_POINTS_ON_WINDING || front->numVerts > MAX_POINTS_ON_WINDING ) {
+       if ( front->numVerts > MAX_POINTS_ON_WINDING ) {
                Error( "RadClipWindingEpsilon: MAX_POINTS_ON_WINDING" );
        }
 }
@@ -263,7 +263,7 @@ qboolean RadSampleImage( byte *pixels, int width, int height, float st[ 2 ], flo
 #define SAMPLE_GRANULARITY  6
 
 static void RadSample( int lightmapNum, bspDrawSurface_t *ds, rawLightmap_t *lm, shaderInfo_t *si, radWinding_t *rw, vec3_t average, vec3_t gradient, int *style ){
-       int i, j, k, l, v, x, y, samples;
+       int i, j, k, l, v, x, y, samples, avgcolor, f_superSample;
        vec3_t color, mins, maxs;
        vec4_t textureColor;
        float alpha, alphaI, bf;
@@ -295,10 +295,12 @@ static void RadSample( int lightmapNum, bspDrawSurface_t *ds, rawLightmap_t *lm,
                        /* multiply by texture color */
                        if ( !RadSampleImage( si->lightImage->pixels, si->lightImage->width, si->lightImage->height, rw->verts[ samples ].st, textureColor ) ) {
                                VectorCopy( si->averageColor, textureColor );
-                               textureColor[ 4 ] = 255.0f;
+                               textureColor[ 3 ] = 255.0f;
                        }
+                       avgcolor = ( textureColor[ 0 ] + textureColor[ 1 ] + textureColor[ 2 ] ) / 3;
                        for ( i = 0; i < 3; i++ )
-                               color[ i ] = ( textureColor[ i ] / 255 ) * ( rw->verts[ samples ].color[ lightmapNum ][ i ] / 255.0f );
+                               color[ i ] = ( ( textureColor[ i ] * bounceColorRatio + ( avgcolor * ( 1 - bounceColorRatio ) ) ) / 255 ) * ( rw->verts[ samples ].color[ lightmapNum ][ i ] / 255.0f );
+//                             color[ i ] = ( textureColor[ i ] / 255 ) * ( rw->verts[ samples ].color[ lightmapNum ][ i ] / 255.0f );
 
                        AddPointToBounds( color, mins, maxs );
                        VectorAdd( average, color, average );
@@ -314,6 +316,7 @@ static void RadSample( int lightmapNum, bspDrawSurface_t *ds, rawLightmap_t *lm,
        /* sample lightmap */
        else
        {
+               f_superSample = (float)superSample;
                /* fracture the winding into a fan (including degenerate tris) */
                for ( v = 1; v < ( rw->numVerts - 1 ) && samples < MAX_SAMPLES; v++ )
                {
@@ -333,7 +336,7 @@ static void RadSample( int lightmapNum, bspDrawSurface_t *ds, rawLightmap_t *lm,
                                                blend[ 0 ] = i;
                                                blend[ 1 ] = j;
                                                blend[ 2 ] = k;
-                                               bf = ( 1.0 / ( blend[ 0 ] + blend[ 1 ] + blend[ 2 ] ) );
+                                               bf = ( 1.0f / ( blend[ 0 ] + blend[ 1 ] + blend[ 2 ] ) );
                                                VectorScale( blend, bf, blend );
 
                                                /* create a blended sample */
@@ -350,8 +353,10 @@ static void RadSample( int lightmapNum, bspDrawSurface_t *ds, rawLightmap_t *lm,
                                                }
 
                                                /* get lightmap xy coords */
-                                               x = lightmap[ 0 ] / (float) superSample;
-                                               y = lightmap[ 1 ] / (float) superSample;
+                                               /* xy = clamp(lightmap/superSample, 0, lm - 1)*/
+                                               x = lightmap[ 0 ] / f_superSample;
+                                               y = lightmap[ 1 ] / f_superSample;
+
                                                if ( x < 0 ) {
                                                        x = 0;
                                                }
@@ -379,11 +384,20 @@ static void RadSample( int lightmapNum, bspDrawSurface_t *ds, rawLightmap_t *lm,
                                                /* multiply by texture color */
                                                if ( !RadSampleImage( si->lightImage->pixels, si->lightImage->width, si->lightImage->height, st, textureColor ) ) {
                                                        VectorCopy( si->averageColor, textureColor );
-                                                       textureColor[ 4 ] = 255;
+                                                       textureColor[ 3 ] = 255;
+                                               }
+                                               avgcolor = ( textureColor[ 0 ] + textureColor[ 1 ] + textureColor[ 2 ] ) / 3;
+                                               for ( i = 0; i < 3; i++ ){
+                                                       color[ i ] = ( ( textureColor[ i ] * bounceColorRatio + ( avgcolor * ( 1 - bounceColorRatio ) ) ) / 255 ) * ( radLuxel[ i ] / 255 );
+                                               /*
+                                                  Workaround for https://gitlab.com/xonotic/netradiant/-/issues/182
+                                                  This loop normally uses the l iterator instead of i:
+                                                  for ( l = 0; l < 3; l++ ){
+                                                       color[ l ] = ( ( textureColor[ l ] * bounceColorRatio + ( avgcolor * ( 1 - bounceColorRatio ) ) ) / 255 ) * ( radLuxel[ l ] / 255 );
+                                                       }
+                                               */
+                                               //Sys_Printf( "%i %i %i %i %i \n", (int) textureColor[ 0 ], (int) textureColor[ 1 ], (int) textureColor[ 2 ], (int) avgcolor, (int) color[ i ] );
                                                }
-                                               for ( i = 0; i < 3; i++ )
-                                                       color[ i ] = ( textureColor[ i ] / 255 ) * ( radLuxel[ i ] / 255 );
-
                                                AddPointToBounds( color, mins, maxs );
                                                VectorAdd( average, color, average );
 
@@ -431,13 +445,15 @@ static void RadSample( int lightmapNum, bspDrawSurface_t *ds, rawLightmap_t *lm,
 #define RADIOSITY_MIN               0.0001f
 #define RADIOSITY_CLIP_EPSILON      0.125f
 
+
+
 static void RadSubdivideDiffuseLight( int lightmapNum, bspDrawSurface_t *ds, rawLightmap_t *lm, shaderInfo_t *si,
-                                                                         float scale, float subdivide, qboolean original, radWinding_t *rw, clipWork_t *cw ){
+                                                                         float scale, float subdivide, radWinding_t *rw, clipWork_t *cw ){
        int i, style = 0;
        float dist, area, value;
        vec3_t mins, maxs, normal, d1, d2, cross, color, gradient;
        light_t         *light, *splash;
-       winding_t       *w;
+       winding_t       *w, *splash_w;
 
 
        /* dummy check */
@@ -466,8 +482,8 @@ static void RadSubdivideDiffuseLight( int lightmapNum, bspDrawSurface_t *ds, raw
                        RadClipWindingEpsilon( rw, normal, dist, RADIOSITY_CLIP_EPSILON, &front, &back, cw );
 
                        /* recurse */
-                       RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qfalse, &front, cw );
-                       RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qfalse, &back, cw );
+                       RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, &front, cw );
+                       RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, &back, cw );
                        return;
                }
        }
@@ -493,7 +509,7 @@ static void RadSubdivideDiffuseLight( int lightmapNum, bspDrawSurface_t *ds, raw
                /* if color gradient is too high, subdivide again */
                if ( subdivide > minDiffuseSubdivide &&
                         ( gradient[ 0 ] > RADIOSITY_MAX_GRADIENT || gradient[ 1 ] > RADIOSITY_MAX_GRADIENT || gradient[ 2 ] > RADIOSITY_MAX_GRADIENT ) ) {
-                       RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, ( subdivide / 2.0f ), qfalse, rw, cw );
+                       RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, ( subdivide / 2.0f ), rw, cw );
                        return;
                }
        }
@@ -582,27 +598,77 @@ static void RadSubdivideDiffuseLight( int lightmapNum, bspDrawSurface_t *ds, raw
                VectorMA( light->origin, 1.0f, light->normal, light->origin );
                light->dist = DotProduct( light->origin, normal );
 
-               /* optionally create a point splashsplash light for first pass */
-               if ( original && si->backsplashFraction > 0 ) {
+#if 0
+               /* optionally create a point backsplash light */
+               if ( si->backsplashFraction > 0 ) {
+
                        /* allocate a new point light */
                        splash = safe_malloc0( sizeof( *splash ) );
+
                        splash->next = lights;
                        lights = splash;
 
+
                        /* set it up */
                        splash->flags = LIGHT_Q3A_DEFAULT;
                        splash->type = EMIT_POINT;
                        splash->photons = light->photons * si->backsplashFraction;
+
                        splash->fade = 1.0f;
                        splash->si = si;
                        VectorMA( light->origin, si->backsplashDistance, normal, splash->origin );
                        VectorCopy( si->color, splash->color );
+
                        splash->falloffTolerance = falloffTolerance;
                        splash->style = noStyles ? LS_NORMAL : light->style;
 
                        /* add to counts */
                        numPointLights++;
                }
+#endif
+
+#if 1
+               /* optionally create area backsplash light */
+               //if ( original && si->backsplashFraction > 0 ) {
+               if ( si->backsplashFraction > 0 && !( si->compileFlags & C_SKY ) ) {
+                       /* allocate a new area light */
+                       splash = safe_malloc( sizeof( *splash ) );
+                       memset( splash, 0, sizeof( *splash ) );
+                       ThreadLock();
+                       splash->next = lights;
+                       lights = splash;
+                       ThreadUnlock();
+
+                       /* set it up */
+                       splash->flags = LIGHT_AREA_DEFAULT;
+                       splash->type = EMIT_AREA;
+                       splash->photons = light->photons * 7.0f * si->backsplashFraction;
+                       splash->add = light->add * 7.0f * si->backsplashFraction;
+                       splash->fade = 1.0f;
+                       splash->si = si;
+                       VectorCopy( si->color, splash->color );
+                       VectorScale( splash->color, splash->add, splash->emitColor );
+                       splash->falloffTolerance = falloffTolerance;
+                       splash->style = noStyles ? LS_NORMAL : si->lightStyle;
+                       if ( splash->style < LS_NORMAL || splash->style >= LS_NONE ) {
+                               splash->style = LS_NORMAL;
+                       }
+
+                       /* create a regular winding */
+                       splash_w = AllocWinding( rw->numVerts );
+                       splash_w->numpoints = rw->numVerts;
+                       for ( i = 0; i < rw->numVerts; i++ )
+                               VectorMA( rw->verts[rw->numVerts - 1 - i].xyz, si->backsplashDistance, normal, splash_w->p[ i ] );
+                       splash->w = splash_w;
+
+                       VectorMA( light->origin, si->backsplashDistance, normal, splash->origin );
+                       VectorNegate( normal, splash->normal );
+            splash->dist = DotProduct( splash->origin, splash->normal );
+
+//                     splash->flags |= LIGHT_TWOSIDED;
+               }
+#endif
+
        }
        else
        {
@@ -641,7 +707,6 @@ static void RadSubdivideDiffuseLight( int lightmapNum, bspDrawSurface_t *ds, raw
 }
 
 
-
 /*
    RadLightForTriangles()
    creates unbounced diffuse lights for triangle soup (misc_models, etc)
@@ -680,7 +745,7 @@ void RadLightForTriangles( int num, int lightmapNum, rawLightmap_t *lm, shaderIn
                }
 
                /* subdivide into area lights */
-               RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qtrue, &rw, cw );
+               RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, &rw, cw );
        }
 }
 
@@ -789,7 +854,7 @@ void RadLightForPatch( int num, int lightmapNum, rawLightmap_t *lm, shaderInfo_t
                                }
 
                                /* subdivide into area lights */
-                               RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qtrue, &rw, cw );
+                               RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, &rw, cw );
                        }
 
                        /* generate 2 tris */
@@ -818,7 +883,7 @@ void RadLightForPatch( int num, int lightmapNum, rawLightmap_t *lm, shaderInfo_t
                                        }
 
                                        /* subdivide into area lights */
-                                       RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qtrue, &rw, cw );
+                                       RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, &rw, cw );
                                }
                        }
                }
index f58bd4ac4a9db93a1a3183d507b4f56d74c35091..a8b8bb56d30edd46c2a36cddba444d47c12b11ee 100644 (file)
@@ -1614,103 +1614,132 @@ qboolean TraceWinding( traceWinding_t *tw, trace_t *trace ){
 /*
    TraceLine_r()
    returns qtrue if something is hit and tracing can stop
+
+   SmileTheory: made half-iterative
  */
 
-static qboolean TraceLine_r( int nodeNum, vec3_t origin, vec3_t end, trace_t *trace ){
+#define TRACELINE_R_HALF_ITERATIVE 1
+
+#if TRACELINE_R_HALF_ITERATIVE
+static qboolean TraceLine_r( int nodeNum, const vec3_t start, const vec3_t end, trace_t *trace )
+#else
+static qboolean TraceLine_r( int nodeNum, const vec3_t origin, const vec3_t end, trace_t *trace )
+#endif
+{
        traceNode_t     *node;
        int side;
        float front, back, frac;
        vec3_t mid;
-       qboolean r;
 
+#if TRACELINE_R_HALF_ITERATIVE
+       vec3_t origin;
+       VectorCopy( start, origin );
 
-       /* bogus node number means solid, end tracing unless testing all */
-       if ( nodeNum < 0 ) {
-               VectorCopy( origin, trace->hit );
-               trace->passSolid = qtrue;
-               return qtrue;
-       }
+       while ( 1 )
+#endif
+       {
+               /* bogus node number means solid, end tracing unless testing all */
+               if ( nodeNum < 0 ) {
+                       VectorCopy( origin, trace->hit );
+                       trace->passSolid = qtrue;
+                       return qtrue;
+               }
 
-       /* get node */
-       node = &traceNodes[ nodeNum ];
+               /* get node */
+               node = &traceNodes[ nodeNum ];
 
-       /* solid? */
-       if ( node->type == TRACE_LEAF_SOLID ) {
-               VectorCopy( origin, trace->hit );
-               trace->passSolid = qtrue;
-               return qtrue;
-       }
+               /* solid? */
+               if ( node->type == TRACE_LEAF_SOLID ) {
+                       VectorCopy( origin, trace->hit );
+                       trace->passSolid = qtrue;
+                       return qtrue;
+               }
 
-       /* leafnode? */
-       if ( node->type < 0 ) {
-               /* note leaf and return */
-               if ( node->numItems > 0 && trace->numTestNodes < MAX_TRACE_TEST_NODES ) {
-                       trace->testNodes[ trace->numTestNodes++ ] = nodeNum;
+               /* leafnode? */
+               if ( node->type < 0 ) {
+                       /* note leaf and return */
+                       if ( node->numItems > 0 && trace->numTestNodes < MAX_TRACE_TEST_NODES ) {
+                               trace->testNodes[ trace->numTestNodes++ ] = nodeNum;
+                       }
+                       return qfalse;
                }
-               return qfalse;
-       }
 
-       /* ydnar 2003-09-07: don't test branches of the bsp with nothing in them when testall is enabled */
-       if ( trace->testAll && node->numItems == 0 ) {
-               return qfalse;
-       }
+               /* ydnar 2003-09-07: don't test branches of the bsp with nothing in them when testall is enabled */
+               if ( trace->testAll && node->numItems == 0 ) {
+                       return qfalse;
+               }
 
-       /* classify beginning and end points */
-       switch ( node->type )
-       {
-       case PLANE_X:
-               front = origin[ 0 ] - node->plane[ 3 ];
-               back = end[ 0 ] - node->plane[ 3 ];
-               break;
+               /* classify beginning and end points */
+               switch ( node->type )
+               {
+               case PLANE_X:
+                       front = origin[ 0 ] - node->plane[ 3 ];
+                       back = end[ 0 ] - node->plane[ 3 ];
+                       break;
 
-       case PLANE_Y:
-               front = origin[ 1 ] - node->plane[ 3 ];
-               back = end[ 1 ] - node->plane[ 3 ];
-               break;
+               case PLANE_Y:
+                       front = origin[ 1 ] - node->plane[ 3 ];
+                       back = end[ 1 ] - node->plane[ 3 ];
+                       break;
 
-       case PLANE_Z:
-               front = origin[ 2 ] - node->plane[ 3 ];
-               back = end[ 2 ] - node->plane[ 3 ];
-               break;
+               case PLANE_Z:
+                       front = origin[ 2 ] - node->plane[ 3 ];
+                       back = end[ 2 ] - node->plane[ 3 ];
+                       break;
 
-       default:
-               front = DotProduct( origin, node->plane ) - node->plane[ 3 ];
-               back = DotProduct( end, node->plane ) - node->plane[ 3 ];
-               break;
-       }
+               default:
+                       front = DotProduct( origin, node->plane ) - node->plane[ 3 ];
+                       back = DotProduct( end, node->plane ) - node->plane[ 3 ];
+                       break;
+               }
 
-       /* entirely in front side? */
-       if ( front >= -TRACE_ON_EPSILON && back >= -TRACE_ON_EPSILON ) {
-               return TraceLine_r( node->children[ 0 ], origin, end, trace );
-       }
+               /* entirely in front side? */
+               if ( front >= -TRACE_ON_EPSILON && back >= -TRACE_ON_EPSILON ) {
+#if TRACELINE_R_HALF_ITERATIVE
+                       nodeNum = node->children[ 0 ];
+                       continue;
+#else
+                       return TraceLine_r( node->children[ 0 ], origin, end, trace );
+#endif
+               }
 
-       /* entirely on back side? */
-       if ( front < TRACE_ON_EPSILON && back < TRACE_ON_EPSILON ) {
-               return TraceLine_r( node->children[ 1 ], origin, end, trace );
-       }
+               /* entirely on back side? */
+               if ( front < TRACE_ON_EPSILON && back < TRACE_ON_EPSILON ) {
+#if TRACELINE_R_HALF_ITERATIVE
+                       nodeNum = node->children[ 1 ];
+                       continue;
+#else
+                       return TraceLine_r( node->children[ 1 ], origin, end, trace );
+#endif
+               }
 
-       /* select side */
-       side = front < 0;
+               /* select side */
+               side = front < 0;
 
-       /* calculate intercept point */
-       frac = front / ( front - back );
-       mid[ 0 ] = origin[ 0 ] + ( end[ 0 ] - origin[ 0 ] ) * frac;
-       mid[ 1 ] = origin[ 1 ] + ( end[ 1 ] - origin[ 1 ] ) * frac;
-       mid[ 2 ] = origin[ 2 ] + ( end[ 2 ] - origin[ 2 ] ) * frac;
+               /* calculate intercept point */
+               frac = front / ( front - back );
+               mid[ 0 ] = origin[ 0 ] + ( end[ 0 ] - origin[ 0 ] ) * frac;
+               mid[ 1 ] = origin[ 1 ] + ( end[ 1 ] - origin[ 1 ] ) * frac;
+               mid[ 2 ] = origin[ 2 ] + ( end[ 2 ] - origin[ 2 ] ) * frac;
 
-       /* fixme: check inhibit radius, then solid nodes and ignore */
+               /* fixme: check inhibit radius, then solid nodes and ignore */
 
-       /* set trace hit here */
-       //%     VectorCopy( mid, trace->hit );
+               /* set trace hit here */
+               //%     VectorCopy( mid, trace->hit );
 
-       /* trace first side */
-       r = TraceLine_r( node->children[ side ], origin, mid, trace );
-       if ( r ) {
-               return r;
-       }
+               /* trace first side */
+               if ( TraceLine_r( node->children[ side ], origin, mid, trace ) ) {
+                       return qtrue;
+               }
 
-       /* trace other side */
-       return TraceLine_r( node->children[ !side ], mid, end, trace );
+               /* trace other side */
+#if TRACELINE_R_HALF_ITERATIVE
+               nodeNum = node->children[ !side ];
+               VectorCopy( mid, origin );
+#else
+               return TraceLine_r( node->children[ !side ], mid, end, trace );
+#endif
+       }
 }
 
 
@@ -1787,7 +1816,7 @@ void TraceLine( trace_t *trace ){
 
 float SetupTrace( trace_t *trace ){
        VectorSubtract( trace->end, trace->origin, trace->displacement );
-       trace->distance = VectorNormalize( trace->displacement, trace->direction );
+       trace->distance = VectorFastNormalize( trace->displacement, trace->direction );
        VectorCopy( trace->origin, trace->hit );
        return trace->distance;
 }
index ce9cf861fed4afd8f1e57fd967748855d169bda5..eee4a17362c7ddf71d218aca2de8a4a9be6c73d4 100644 (file)
@@ -55,6 +55,8 @@ void ColorToBytes( const float *color, byte *colorBytes, float scale ){
        if ( scale <= 0.0f ) {
                scale = 1.0f;
        }
+       /* globally */
+       scale *= lightmapBrightness;
 
        /* make a local copy */
        VectorScale( color, scale, sample );
@@ -119,6 +121,23 @@ void ColorToBytes( const float *color, byte *colorBytes, float scale ){
        /* compensate for ingame overbrighting/bitshifting */
        VectorScale( sample, ( 1.0f / lightmapCompensate ), sample );
 
+       /* contrast */
+       if ( lightmapContrast != 1.0f ){
+               for ( i = 0; i < 3; i++ ){
+                       sample[i] = lightmapContrast * ( sample[i] - 128 ) + 128;
+                       if ( sample[i] < 0 ){
+                               sample[i] = 0;
+                       }
+               }
+               if ( ( sample[0] > 255 ) || ( sample[1] > 255 ) || ( sample[2] > 255 ) ) {
+                       max = sample[0] > sample[1] ? sample[0] : sample[1];
+                       max = max > sample[2] ? max : sample[2];
+                       sample[0] = sample[0] * 255 / max;
+                       sample[1] = sample[1] * 255 / max;
+                       sample[2] = sample[2] * 255 / max;
+               }
+       }
+
        /* sRGB lightmaps */
        if ( lightmapsRGB ) {
                sample[0] = floor( Image_sRGBFloatFromLinearFloat( sample[0] * ( 1.0 / 255.0 ) ) * 255.0 + 0.5 );
@@ -1239,6 +1258,7 @@ void MapRawLightmap( int rawLightmapNum ){
           ----------------------------------------------------------------- */
 
        /* walk the luxels */
+       /* FIXME: superSample is int, no need in floor() */
        radius = floor( superSample / 2 );
        radius = radius > 0 ? radius : 1.0f;
        radius += 1.0f;
@@ -1775,7 +1795,7 @@ static qboolean SubmapRawLuxel( rawLightmap_t *lm, int x, int y, float bx, float
                origin2 = SUPER_ORIGIN( x, y );
                //%     normal2 = SUPER_NORMAL( x, y );
        }
-       else {
+       else{
                Sys_FPrintf( SYS_WRN, "WARNING: Spurious lightmap T vector\n" );
        }
 
@@ -1809,7 +1829,7 @@ static void SubsampleRawLuxel_r( rawLightmap_t *lm, trace_t *trace, vec3_t sampl
        int b, samples, mapped, lighted;
        int cluster[ 4 ];
        vec4_t luxel[ 4 ];
-       vec3_t deluxel[ 3 ];
+       vec3_t deluxel[ 4 ];
        vec3_t origin[ 4 ], normal[ 4 ];
        float biasDirs[ 4 ][ 2 ] = { { -1.0f, -1.0f }, { 1.0f, -1.0f }, { -1.0f, 1.0f }, { 1.0f, 1.0f } };
        vec3_t color, direction = { 0, 0, 0 }, total;
@@ -2225,7 +2245,7 @@ void IlluminateRawLightmap( int rawLightmapNum ){
                        }
 
                        /* allocate sampling flags storage */
-                       if ( ( lightSamples > 1 || lightRandomSamples ) && luxelFilterRadius == 0 ) {
+                       if ( lightSamples > 1 || lightRandomSamples ) {
                                size = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof( unsigned char );
                                if ( lm->superFlags == NULL ) {
                                        lm->superFlags = safe_malloc( size );
@@ -2269,8 +2289,8 @@ void IlluminateRawLightmap( int rawLightmapNum ){
                                        }
 
                                        /* check for evilness */
-                                       if ( trace.forceSubsampling > 1.0f && ( lightSamples > 1 || lightRandomSamples ) && luxelFilterRadius == 0 ) {
-                                                       totalLighted++;
+                                       if ( trace.forceSubsampling > 1.0f && ( lightSamples > 1 || lightRandomSamples ) ) {
+                                               totalLighted++;
                                                *flag |= FLAG_FORCE_SUBSAMPLING; /* force */
                                        }
                                        /* add to count */
@@ -2287,7 +2307,7 @@ void IlluminateRawLightmap( int rawLightmapNum ){
 
                        /* secondary pass, adaptive supersampling (fixme: use a contrast function to determine if subsampling is necessary) */
                        /* 2003-09-27: changed it so filtering disamples supersampling, as it would waste time */
-                       if ( ( lightSamples > 1 || lightRandomSamples ) && luxelFilterRadius == 0 ) {
+                       if ( lightSamples > 1 || lightRandomSamples ) {
                                /* walk luxels */
                                for ( y = 0; y < ( lm->sh - 1 ); y++ )
                                {
@@ -2740,7 +2760,7 @@ void IlluminateVertexes( int num ){
        int i, x, y, z, x1, y1, z1, sx, sy, radius, maxRadius, *cluster;
        int lightmapNum, numAvg;
        float samples, *vertLuxel, *radVertLuxel, *luxel, dirt;
-       vec3_t origin, temp, temp2, colors[ MAX_LIGHTMAPS ], avgColors[ MAX_LIGHTMAPS ];
+       vec3_t temp, temp2, colors[ MAX_LIGHTMAPS ], avgColors[ MAX_LIGHTMAPS ];
        bspDrawSurface_t    *ds;
        surfaceInfo_t       *info;
        rawLightmap_t       *lm;
@@ -2795,7 +2815,7 @@ void IlluminateVertexes( int num ){
                        else if ( debugOrigin ) {
                                VectorSubtract( info->maxs, info->mins, temp );
                                VectorScale( temp, ( 1.0f / 255.0f ), temp );
-                               VectorSubtract( origin, lm->mins, temp2 );
+                               VectorSubtract( verts[ i ].xyz, info->mins, temp2 );
                                radVertLuxel[ 0 ] = info->mins[ 0 ] + ( temp[ 0 ] * temp2[ 0 ] );
                                radVertLuxel[ 1 ] = info->mins[ 1 ] + ( temp[ 1 ] * temp2[ 1 ] );
                                radVertLuxel[ 2 ] = info->mins[ 2 ] + ( temp[ 2 ] * temp2[ 2 ] );
@@ -2808,6 +2828,14 @@ void IlluminateVertexes( int num ){
                                radVertLuxel[ 2 ] = ( verts[ i ].normal[ 2 ] + 1.0f ) * 127.5f;
                        }
 
+                       else if ( info->si->noVertexLight ) {
+                               VectorSet( radVertLuxel, 127.5f, 127.5f, 127.5f );
+                       }
+
+                       else if ( noVertexLighting > 0 ) {
+                               VectorSet( radVertLuxel, 127.5f * noVertexLighting, 127.5f * noVertexLighting, 127.5f * noVertexLighting );
+                       }
+
                        /* illuminate the vertex */
                        else
                        {
@@ -2881,7 +2909,7 @@ void IlluminateVertexes( int num ){
                                                                trace.origin[ 2 ] = verts[ i ].xyz[ 2 ] + ( VERTEX_NUDGE * z1 );
 
                                                                /* try at nudged origin */
-                                                               trace.cluster = ClusterForPointExtFilter( origin, VERTEX_EPSILON, info->numSurfaceClusters, &surfaceClusters[ info->firstSurfaceCluster ] );
+                                                               trace.cluster = ClusterForPointExtFilter( trace.origin, VERTEX_EPSILON, info->numSurfaceClusters, &surfaceClusters[ info->firstSurfaceCluster ] );
                                                                if ( trace.cluster < 0 ) {
                                                                        continue;
                                                                }
@@ -3057,6 +3085,14 @@ void IlluminateVertexes( int num ){
                                VectorCopy( debugColors[ num % 12 ], radVertLuxel );
                        }
 
+                       else if ( info->si->noVertexLight ) {
+                               VectorSet( radVertLuxel, 127.5f, 127.5f, 127.5f );
+                       }
+
+                       else if ( noVertexLighting > 0 ) {
+                               VectorSet( radVertLuxel, 127.5f * noVertexLighting, 127.5f * noVertexLighting, 127.5f * noVertexLighting );
+                       }
+
                        /* divine color from the superluxels */
                        else
                        {
@@ -3137,7 +3173,6 @@ void IlluminateVertexes( int num ){
 void SetupBrushesFlags( unsigned int mask_any, unsigned int test_any, unsigned int mask_all, unsigned int test_all ){
        int i, j, b;
        unsigned int compileFlags, allCompileFlags;
-       qboolean inside;
        bspBrush_t      *brush;
        bspBrushSide_t  *side;
        bspShader_t     *shader;
@@ -3164,10 +3199,9 @@ void SetupBrushesFlags( unsigned int mask_any, unsigned int test_any, unsigned i
                brush = &bspBrushes[ b ];
 
                /* check all sides */
-               inside = qtrue;
                compileFlags = 0;
                allCompileFlags = ~( 0u );
-               for ( j = 0; j < brush->numSides && inside; j++ )
+               for ( j = 0; j < brush->numSides; j++ )
                {
                        /* do bsp shader calculations */
                        side = &bspBrushSides[ brush->firstSide + j ];
index 37bb6da4fc5c394048ed002089a21ec9b4493982..ee9902460b505c2912fc182f571986a4308a44b3 100644 (file)
@@ -695,16 +695,33 @@ qboolean AddSurfaceToRawLightmap( int num, rawLightmap_t *lm ){
                }
        }
 
-       if ( sampleSize != lm->sampleSize && lmLimitSize == 0 ) {
-               Sys_FPrintf( SYS_VRB,"WARNING: surface at (%6.0f %6.0f %6.0f) (%6.0f %6.0f %6.0f) too large for desired samplesize/lightmapsize/lightmapscale combination, increased samplesize from %d to %d\n",
-                                        info->mins[0],
-                                        info->mins[1],
-                                        info->mins[2],
-                                        info->maxs[0],
-                                        info->maxs[1],
-                                        info->maxs[2],
-                                        lm->sampleSize,
-                                        (int) sampleSize );
+       if ( sampleSize != lm->sampleSize && lmLimitSize == 0 ){
+               if ( debugSampleSize == 1 || lm->customWidth > 128 ){
+                       Sys_FPrintf( SYS_VRB,"WARNING: surface at (%6.0f %6.0f %6.0f) (%6.0f %6.0f %6.0f) too large for desired samplesize/lightmapsize/lightmapscale combination, increased samplesize from %d to %d\n",
+                                               info->mins[0],
+                                               info->mins[1],
+                                               info->mins[2],
+                                               info->maxs[0],
+                                               info->maxs[1],
+                                               info->maxs[2],
+                                               lm->sampleSize,
+                                               (int) sampleSize );
+               }
+               else if ( debugSampleSize == 0 ){
+                       Sys_FPrintf( SYS_VRB,"WARNING: surface at (%6.0f %6.0f %6.0f) (%6.0f %6.0f %6.0f) too large for desired samplesize/lightmapsize/lightmapscale combination, increased samplesize from %d to %d\n",
+                                               info->mins[0],
+                                               info->mins[1],
+                                               info->mins[2],
+                                               info->maxs[0],
+                                               info->maxs[1],
+                                               info->maxs[2],
+                                               lm->sampleSize,
+                                               (int) sampleSize );
+                       debugSampleSize--;
+               }
+               else{
+                       debugSampleSize--;
+               }
        }
 
        /* set actual sample size */
@@ -1089,7 +1106,8 @@ void SetupSurfaceLightmaps( void ){
                        /* determine if surface requires a lightmap */
                        if ( ds->surfaceType == MST_TRIANGLE_SOUP ||
                                 ds->surfaceType == MST_FOLIAGE ||
-                                ( info->si->compileFlags & C_VERTEXLIT ) ) {
+                               ( info->si->compileFlags & C_VERTEXLIT ) ||
+                               nolm == qtrue ) {
                                numSurfsVertexLit++;
                        }
                        else
@@ -1196,6 +1214,10 @@ void SetupSurfaceLightmaps( void ){
                FinishRawLightmap( lm );
        }
 
+       if ( debugSampleSize < -1 ){
+               Sys_FPrintf( SYS_VRB, "+%d similar occurrences;\t-debugSampleSize to show ones\n", -debugSampleSize - 1 );
+       }
+
        /* allocate vertex luxel storage */
        for ( k = 0; k < MAX_LIGHTMAPS; k++ )
        {
@@ -2151,6 +2173,10 @@ static void FindOutLightmaps( rawLightmap_t *lm, qboolean fastAllocate ){
                        /* allocate LIGHTMAP_RESERVE_COUNT new output lightmaps */
                        numOutLightmaps += LIGHTMAP_RESERVE_COUNT;
                        olm = safe_malloc( numOutLightmaps * sizeof( outLightmap_t ) );
+                       if ( !olm ){
+                               Error( "FindOutLightmaps: Failed to allocate memory.\n" );
+                       }
+
                        if ( outLightmaps != NULL && numOutLightmaps > LIGHTMAP_RESERVE_COUNT ) {
                                memcpy( olm, outLightmaps, ( numOutLightmaps - LIGHTMAP_RESERVE_COUNT ) * sizeof( outLightmap_t ) );
                                free( outLightmaps );
@@ -2299,6 +2325,16 @@ static int CompareRawLightmap( const void *a, const void *b ){
        /* get min number of surfaces */
        min = ( alm->numLightSurfaces < blm->numLightSurfaces ? alm->numLightSurfaces : blm->numLightSurfaces );
 
+//#define allocate_bigger_first
+#ifdef allocate_bigger_first
+       /* compare size, allocate bigger first */
+       // fastAllocate commit part: can kick fps by unique lightmap/shader combinations*=~2 + bigger compile time
+       //return -diff; makes packing faster and rough
+       diff = ( blm->w * blm->h ) - ( alm->w * alm->h );
+       if ( diff != 0 ) {
+               return diff;
+       }
+#endif
        /* iterate */
        for ( i = 0; i < min; i++ )
        {
@@ -2320,13 +2356,13 @@ static int CompareRawLightmap( const void *a, const void *b ){
        if ( diff ) {
                return diff;
        }
-
+#ifndef allocate_bigger_first
        /* compare size */
        diff = ( blm->w * blm->h ) - ( alm->w * alm->h );
        if ( diff != 0 ) {
                return diff;
        }
-
+#endif
        /* must be equivalent */
        return 0;
 }
@@ -2445,15 +2481,13 @@ void FillOutLightmap( outLightmap_t *olm ){
        }
 }
 
-
-
 /*
    StoreSurfaceLightmaps()
    stores the surface lightmaps into the bsp as byte rgb triplets
  */
 
 void StoreSurfaceLightmaps( qboolean fastAllocate, qboolean storeForReal ){
-       int i, j, k, x, y, lx, ly, sx, sy, *cluster, mappedSamples;
+       int i, j, k, x, y, lx, ly, sx, sy, *cluster, mappedSamples, timer_start;
        int style, size, lightmapNum, lightmapNum2;
        float               *normal, *luxel, *bspLuxel, *bspLuxel2, *radLuxel, samples, occludedSamples;
        vec3_t sample, occludedSample, dirSample, colorMins, colorMaxs;
@@ -2496,6 +2530,8 @@ void StoreSurfaceLightmaps( qboolean fastAllocate, qboolean storeForReal ){
        /* note it */
        Sys_FPrintf( SYS_VRB, "Subsampling..." );
 
+       timer_start = I_FloatTime();
+
        /* walk the list of raw lightmaps */
        numUsed = 0;
        numTwins = 0;
@@ -2829,6 +2865,8 @@ void StoreSurfaceLightmaps( qboolean fastAllocate, qboolean storeForReal ){
                }
        }
 
+       Sys_FPrintf( SYS_VRB, "%d.", (int) ( I_FloatTime() - timer_start ) );
+
        /* -----------------------------------------------------------------
           convert modelspace deluxemaps to tangentspace
           ----------------------------------------------------------------- */
@@ -2838,6 +2876,8 @@ void StoreSurfaceLightmaps( qboolean fastAllocate, qboolean storeForReal ){
                        vec3_t worldUp, myNormal, myTangent, myBinormal;
                        float dist;
 
+                       timer_start = I_FloatTime();
+
                        Sys_Printf( "converting..." );
 
                        for ( i = 0; i < numRawLightmaps; i++ )
@@ -2907,6 +2947,8 @@ void StoreSurfaceLightmaps( qboolean fastAllocate, qboolean storeForReal ){
                                        }
                                }
                        }
+
+                       Sys_FPrintf( SYS_VRB, "%d.", (int) ( I_FloatTime() - timer_start ) );
                }
        }
 
@@ -2963,6 +3005,8 @@ void StoreSurfaceLightmaps( qboolean fastAllocate, qboolean storeForReal ){
                /* note it */
                Sys_FPrintf( SYS_VRB, "collapsing..." );
 
+               timer_start = I_FloatTime();
+
                /* set all twin refs to null */
                for ( i = 0; i < numRawLightmaps; i++ )
                {
@@ -3023,6 +3067,8 @@ void StoreSurfaceLightmaps( qboolean fastAllocate, qboolean storeForReal ){
                                }
                        }
                }
+
+               Sys_FPrintf( SYS_VRB, "%d.", (int) ( I_FloatTime() - timer_start ) );
        }
 
        /* -----------------------------------------------------------------
@@ -3032,6 +3078,8 @@ void StoreSurfaceLightmaps( qboolean fastAllocate, qboolean storeForReal ){
        /* note it */
        Sys_FPrintf( SYS_VRB, "sorting..." );
 
+       timer_start = I_FloatTime();
+
        /* allocate a new sorted list */
        if ( sortLightmaps == NULL ) {
                sortLightmaps = safe_malloc( numRawLightmaps * sizeof( int ) );
@@ -3042,6 +3090,8 @@ void StoreSurfaceLightmaps( qboolean fastAllocate, qboolean storeForReal ){
                sortLightmaps[ i ] = i;
        qsort( sortLightmaps, numRawLightmaps, sizeof( int ), CompareRawLightmap );
 
+       Sys_FPrintf( SYS_VRB, "%d.", (int) ( I_FloatTime() - timer_start ) );
+
        /* -----------------------------------------------------------------
           allocate output lightmaps
           ----------------------------------------------------------------- */
@@ -3050,6 +3100,8 @@ void StoreSurfaceLightmaps( qboolean fastAllocate, qboolean storeForReal ){
                /* note it */
                Sys_FPrintf( SYS_VRB, "allocating..." );
 
+       timer_start = I_FloatTime();
+
                /* kill all existing output lightmaps */
                if ( outLightmaps != NULL ) {
                        for ( i = 0; i < numOutLightmaps; i++ )
@@ -3097,6 +3149,8 @@ void StoreSurfaceLightmaps( qboolean fastAllocate, qboolean storeForReal ){
                }
        }
 
+       Sys_FPrintf( SYS_VRB, "%d.", (int) ( I_FloatTime() - timer_start ) );
+
        /* -----------------------------------------------------------------
           store output lightmaps
           ----------------------------------------------------------------- */
@@ -3105,6 +3159,8 @@ void StoreSurfaceLightmaps( qboolean fastAllocate, qboolean storeForReal ){
                /* note it */
                Sys_FPrintf( SYS_VRB, "storing..." );
 
+       timer_start = I_FloatTime();
+
                /* count the bsp lightmaps and allocate space */
                if ( bspLightBytes != NULL ) {
                        free( bspLightBytes );
@@ -3189,6 +3245,8 @@ void StoreSurfaceLightmaps( qboolean fastAllocate, qboolean storeForReal ){
                }
        }
 
+       Sys_FPrintf( SYS_VRB, "%d.", (int) ( I_FloatTime() - timer_start ) );
+
        /* -----------------------------------------------------------------
           project the lightmaps onto the bsp surfaces
           ----------------------------------------------------------------- */
@@ -3197,6 +3255,8 @@ void StoreSurfaceLightmaps( qboolean fastAllocate, qboolean storeForReal ){
                /* note it */
                Sys_FPrintf( SYS_VRB, "projecting..." );
 
+       timer_start = I_FloatTime();
+
                /* walk the list of surfaces */
                for ( i = 0; i < numBSPDrawSurfaces; i++ )
                {
@@ -3355,7 +3415,8 @@ void StoreSurfaceLightmaps( qboolean fastAllocate, qboolean storeForReal ){
                                        olm = &outLightmaps[ lm->outLightmapNums[ lightmapNum ] ];
 
                                        /* lightmap name */
-                                       if ( lm->outLightmapNums[ lightmapNum ] == lm->outLightmapNums[ 0 ] ) {
+                                       if ( !externalLightmapNames
+                                               && lm->outLightmapNums[ lightmapNum ] == lm->outLightmapNums[ 0 ] ) {
                                                strcpy( lightmapName, "$lightmap" );
                                        }
                                        else{
@@ -3474,6 +3535,8 @@ void StoreSurfaceLightmaps( qboolean fastAllocate, qboolean storeForReal ){
                }
        }
 
+       Sys_FPrintf( SYS_VRB, "%d.", (int) ( I_FloatTime() - timer_start ) );
+
        /* finish */
        Sys_FPrintf( SYS_VRB, "done.\n" );
 
index e6b4a5c96e25e8507c4f7f0895eaf32d788fec58..c5ed2c2b33087fe187e6f254b1bbd834531913a1 100644 (file)
@@ -59,7 +59,7 @@ char *Q_strncpyz( char *dst, const char *src, size_t len ) {
 
 
 char *Q_strcat( char *dst, size_t dlen, const char *src ) {
-       size_t n = strlen( dst  );
+       size_t n = strlen( dst );
 
        if ( n > dlen ) {
                abort(); /* buffer overflow */
@@ -93,6 +93,1990 @@ static void ExitQ3Map( void ){
 }
 
 
+/*
+   ShiftBSPMain()
+   shifts a map: for testing physics with huge coordinates
+ */
+
+int ShiftBSPMain( int argc, char **argv ){
+       int i, j;
+       vec3_t scale;
+       vec3_t vec;
+       char str[ 1024 ];
+       float spawn_ref = 0;
+
+
+       /* arg checking */
+       if ( argc < 3 ) {
+               Sys_Printf( "Usage: q3map [-v] -shift [-tex] [-spawn_ref <value>] <value> <mapname>\n" );
+               return 0;
+       }
+
+       for ( i = 1; i < argc - 2; ++i )
+       {
+               if ( !strcmp( argv[i], "-tex" ) ) {
+               }
+               else if ( !strcmp( argv[i], "-spawn_ref" ) ) {
+                       spawn_ref = atof( argv[i + 1] );
+                       ++i;
+               }
+               else{
+                       break;
+               }
+       }
+
+       /* get shift */
+       // if(argc-2 >= i) // always true
+       scale[2] = scale[1] = scale[0] = atof( argv[ argc - 2 ] );
+       if ( argc - 3 >= i ) {
+               scale[1] = scale[0] = atof( argv[ argc - 3 ] );
+       }
+       if ( argc - 4 >= i ) {
+               scale[0] = atof( argv[ argc - 4 ] );
+       }
+
+
+       /* do some path mangling */
+       strcpy( source, ExpandArg( argv[ argc - 1 ] ) );
+       StripExtension( source );
+       DefaultExtension( source, ".bsp" );
+
+       /* load the bsp */
+       Sys_Printf( "Loading %s\n", source );
+       LoadBSPFile( source );
+       ParseEntities();
+
+       /* note it */
+       Sys_Printf( "--- ShiftBSP ---\n" );
+       Sys_FPrintf( SYS_VRB, "%9d entities\n", numEntities );
+
+       /* shift entity keys */
+       for ( i = 0; i < numBSPEntities && i < numEntities; i++ )
+       {
+               /* shift origin */
+               GetVectorForKey( &entities[ i ], "origin", vec );
+               if ( ( vec[ 0 ] || vec[ 1 ] || vec[ 2 ] ) ) {
+                       if ( !!strncmp( ValueForKey( &entities[i], "classname" ), "info_player_", 12 ) ) {
+                               vec[2] += spawn_ref;
+                       }
+                       vec[0] += scale[0];
+                       vec[1] += scale[1];
+                       vec[2] += scale[2];
+                       if ( !!strncmp( ValueForKey( &entities[i], "classname" ), "info_player_", 12 ) ) {
+                               vec[2] -= spawn_ref;
+                       }
+                       sprintf( str, "%f %f %f", vec[ 0 ], vec[ 1 ], vec[ 2 ] );
+                       SetKeyValue( &entities[ i ], "origin", str );
+               }
+
+       }
+
+       /* shift models */
+       for ( i = 0; i < numBSPModels; i++ )
+       {
+               bspModels[ i ].mins[0] += scale[0];
+               bspModels[ i ].mins[1] += scale[1];
+               bspModels[ i ].mins[2] += scale[2];
+               bspModels[ i ].maxs[0] += scale[0];
+               bspModels[ i ].maxs[1] += scale[1];
+               bspModels[ i ].maxs[2] += scale[2];
+       }
+
+       /* shift nodes */
+       for ( i = 0; i < numBSPNodes; i++ )
+       {
+               bspNodes[ i ].mins[0] += scale[0];
+               bspNodes[ i ].mins[1] += scale[1];
+               bspNodes[ i ].mins[2] += scale[2];
+               bspNodes[ i ].maxs[0] += scale[0];
+               bspNodes[ i ].maxs[1] += scale[1];
+               bspNodes[ i ].maxs[2] += scale[2];
+       }
+
+       /* shift leafs */
+       for ( i = 0; i < numBSPLeafs; i++ )
+       {
+               bspLeafs[ i ].mins[0] += scale[0];
+               bspLeafs[ i ].mins[1] += scale[1];
+               bspLeafs[ i ].mins[2] += scale[2];
+               bspLeafs[ i ].maxs[0] += scale[0];
+               bspLeafs[ i ].maxs[1] += scale[1];
+               bspLeafs[ i ].maxs[2] += scale[2];
+       }
+
+       /* shift drawverts */
+       for ( i = 0; i < numBSPDrawVerts; i++ )
+       {
+               bspDrawVerts[i].xyz[0] += scale[0];
+               bspDrawVerts[i].xyz[1] += scale[1];
+               bspDrawVerts[i].xyz[2] += scale[2];
+       }
+
+       /* shift planes */
+
+       vec3_t point;
+
+       for ( i = 0; i < numBSPPlanes; i++ )
+       {
+               //find point on plane
+               for ( j=0; j<3; j++ ){
+                       // This line being commented out is an unexplained change by Garux
+                       // see dd7f4f1689e8ede7580bdfa8e08d0b78d08f5253
+                       //point[j] = bspPlanes[ i ].dist * bspPlanes[ i ].normal[j];
+                       if ( fabs( bspPlanes[ i ].normal[j] ) > 0.5 ){
+                               point[j] = bspPlanes[ i ].dist / bspPlanes[ i ].normal[j];
+                               point[(j+1)%3] = point[(j+2)%3] = 0;
+                               break;
+                       }
+               }
+               //shift point
+               for ( j=0; j<3; j++ ){
+                       point[j] += scale[j];
+               }
+               //calc new plane dist
+               bspPlanes[ i ].dist = DotProduct( point, bspPlanes[ i ].normal );
+       }
+
+       /* scale gridsize */
+       /*
+       GetVectorForKey( &entities[ 0 ], "gridsize", vec );
+       if ( ( vec[ 0 ] + vec[ 1 ] + vec[ 2 ] ) == 0.0f ) {
+               VectorCopy( gridSize, vec );
+       }
+       vec[0] *= scale[0];
+       vec[1] *= scale[1];
+       vec[2] *= scale[2];
+       sprintf( str, "%f %f %f", vec[ 0 ], vec[ 1 ], vec[ 2 ] );
+       SetKeyValue( &entities[ 0 ], "gridsize", str );
+*/
+       /* inject command line parameters */
+       InjectCommandLine( argv, 0, argc - 1 );
+
+       /* write the bsp */
+       UnparseEntities();
+       StripExtension( source );
+       DefaultExtension( source, "_sh.bsp" );
+       Sys_Printf( "Writing %s\n", source );
+       WriteBSPFile( source );
+
+       /* return to sender */
+       return 0;
+}
+
+
+void FixDOSName( char *src ){
+       if ( src == NULL ) {
+               return;
+       }
+
+       while ( *src )
+       {
+               if ( *src == '\\' ) {
+                       *src = '/';
+               }
+               src++;
+       }
+}
+
+/*
+       Check if newcoming texture is unique and not excluded
+*/
+void tex2list( char* texlist, int *texnum, char* EXtex, int *EXtexnum ){
+       int i;
+       if ( token[0] == '\0') return;
+       StripExtension( token );
+       FixDOSName( token );
+       for ( i = 0; i < *texnum; i++ ){
+               if ( !Q_stricmp( texlist + i*65, token ) ) return;
+       }
+       for ( i = 0; i < *EXtexnum; i++ ){
+               if ( !Q_stricmp( EXtex + i*65, token ) ) return;
+       }
+       strcpy ( texlist + (*texnum)*65, token );
+       (*texnum)++;
+       return;
+}
+
+/* 4 repack */
+void tex2list2( char* texlist, int *texnum, char* EXtex, int *EXtexnum, char* rEXtex, int *rEXtexnum ){
+       int i;
+       if ( token[0] == '\0') return;
+       //StripExtension( token );
+       char* dot = strrchr( token, '.' );
+       if ( dot != NULL){
+               if ( Q_stricmp( dot, ".tga" ) && Q_stricmp( dot, ".jpg" ) && Q_stricmp( dot, ".png" ) ){
+                       Sys_Printf( "WARNING4: %s : weird or missing extension in shader\n", token );
+               }
+               else{
+                       *dot = '\0';
+               }
+       }
+       FixDOSName( token );
+       strcpy ( texlist + (*texnum)*65, token );
+       strcat( token, ".tga" );
+
+       /* exclude */
+       for ( i = 0; i < *texnum; i++ ){
+               if ( !Q_stricmp( texlist + i*65, texlist + (*texnum)*65 ) ) return;
+       }
+       for ( i = 0; i < *EXtexnum; i++ ){
+               if ( !Q_stricmp( EXtex + i*65, texlist + (*texnum)*65 ) ) return;
+       }
+       for ( i = 0; i < *rEXtexnum; i++ ){
+               if ( !Q_stricmp( rEXtex + i*65, texlist + (*texnum)*65 ) ) return;
+       }
+       (*texnum)++;
+       return;
+}
+
+
+/*
+       Check if newcoming res is unique
+*/
+void res2list( char* data, int *num ){
+       int i;
+       if ( strlen( data + (*num)*65 ) > 64 ){
+               Sys_Printf( "WARNING6: %s : path too long.\n", data + (*num)*65 );
+       }
+       while ( *( data + (*num)*65 ) == '\\' || *( data + (*num)*65 ) == '/' ){
+               char* cut = data + (*num)*65 + 1;
+               strcpy( data + (*num)*65, cut );
+       }
+       if ( *( data + (*num)*65 ) == '\0') return;
+       for ( i = 0; i < *num; i++ ){
+               if ( !Q_stricmp( data + i*65, data + (*num)*65 ) ) return;
+       }
+       (*num)++;
+       return;
+}
+
+void parseEXblock ( char* data, int *num, const char *exName ){
+       if ( !GetToken( qtrue ) || strcmp( token, "{" ) ) {
+               Error( "ReadExclusionsFile: %s, line %d: { not found", exName, scriptline );
+       }
+       while ( 1 )
+       {
+               if ( !GetToken( qtrue ) ) {
+                       break;
+               }
+               if ( !strcmp( token, "}" ) ) {
+                       break;
+               }
+               if ( token[0] == '{' ) {
+                       Error( "ReadExclusionsFile: %s, line %d: brace, opening twice in a row.", exName, scriptline );
+               }
+
+               /* add to list */
+               strcpy( data + (*num)*65, token );
+               (*num)++;
+       }
+       return;
+}
+
+char q3map2path[1024];
+/*
+   pk3BSPMain()
+   map autopackager, works for Q3 type of shaders and ents
+ */
+
+int pk3BSPMain( int argc, char **argv ){
+       int i, j, len;
+       qboolean dbg = qfalse, png = qfalse, packFAIL = qfalse;
+
+       /* process arguments */
+       for ( i = 1; i < ( argc - 1 ); i++ ){
+               if ( !strcmp( argv[ i ],  "-dbg" ) ) {
+                       dbg = qtrue;
+               }
+               else if ( !strcmp( argv[ i ],  "-png" ) ) {
+                       png = qtrue;
+               }
+       }
+
+       /* do some path mangling */
+       strcpy( source, ExpandArg( argv[ argc - 1 ] ) );
+       StripExtension( source );
+       DefaultExtension( source, ".bsp" );
+
+       /* load the bsp */
+       Sys_Printf( "Loading %s\n", source );
+       LoadBSPFile( source );
+       ParseEntities();
+
+
+       char packname[ 1024 ], packFailName[ 1024 ], base[ 1024 ], nameOFmap[ 1024 ], temp[ 1024 ];
+
+       /* copy map name */
+       strcpy( base, source );
+       StripExtension( base );
+
+       /* extract map name */
+       len = strlen( base ) - 1;
+       while ( len > 0 && base[ len ] != '/' && base[ len ] != '\\' )
+               len--;
+       strcpy( nameOFmap, &base[ len + 1 ] );
+
+
+       qboolean drawsurfSHs[1024] = { qfalse };
+
+       for ( i = 0; i < numBSPDrawSurfaces; i++ ){
+               /* can't exclude nodraw patches here (they want shaders :0!) */
+               //if ( !( bspDrawSurfaces[i].surfaceType == 2 && bspDrawSurfaces[i].numIndexes == 0 ) ) drawsurfSHs[bspDrawSurfaces[i].shaderNum] = qtrue;
+               drawsurfSHs[ bspDrawSurfaces[i].shaderNum ] = qtrue;
+               //Sys_Printf( "%s\n", bspShaders[bspDrawSurfaces[i].shaderNum].shader );
+       }
+
+       int pk3ShadersN = 0;
+       char* pk3Shaders = (char *)calloc( 1024*65, sizeof( char ) );
+       int pk3SoundsN = 0;
+       char* pk3Sounds = (char *)calloc( 1024*65, sizeof( char ) );
+       int pk3ShaderfilesN = 0;
+       char* pk3Shaderfiles = (char *)calloc( 1024*65, sizeof( char ) );
+       int pk3TexturesN = 0;
+       char* pk3Textures = (char *)calloc( 1024*65, sizeof( char ) );
+       int pk3VideosN = 0;
+       char* pk3Videos = (char *)calloc( 1024*65, sizeof( char ) );
+
+
+
+       for ( i = 0; i < numBSPShaders; i++ ){
+               if ( drawsurfSHs[i] ){
+                       strcpy( pk3Shaders + pk3ShadersN*65, bspShaders[i].shader );
+                       res2list( pk3Shaders, &pk3ShadersN );
+                       //pk3ShadersN++;
+                       //Sys_Printf( "%s\n", bspShaders[i].shader );
+               }
+       }
+
+       /* Ent keys */
+       epair_t *ep;
+       for ( ep = entities[0].epairs; ep != NULL; ep = ep->next )
+       {
+               if ( !Q_strncasecmp( ep->key, "vertexremapshader", 17 ) ) {
+                       sscanf( ep->value, "%*[^;] %*[;] %s", pk3Shaders + pk3ShadersN*65 );
+                       res2list( pk3Shaders, &pk3ShadersN );
+               }
+       }
+       strcpy( pk3Sounds + pk3SoundsN*65, ValueForKey( &entities[0], "music" ) );
+       if ( *( pk3Sounds + pk3SoundsN*65 ) != '\0' ){
+               FixDOSName( pk3Sounds + pk3SoundsN*65 );
+               DefaultExtension( pk3Sounds + pk3SoundsN*65, ".wav" );
+               res2list( pk3Sounds, &pk3SoundsN );
+       }
+
+       for ( i = 0; i < numBSPEntities && i < numEntities; i++ )
+       {
+               strcpy( pk3Sounds + pk3SoundsN*65, ValueForKey( &entities[i], "noise" ) );
+               if ( *( pk3Sounds + pk3SoundsN*65 ) != '\0' && *( pk3Sounds + pk3SoundsN*65 ) != '*' ){
+                       FixDOSName( pk3Sounds + pk3SoundsN*65 );
+                       DefaultExtension( pk3Sounds + pk3SoundsN*65, ".wav" );
+                       res2list( pk3Sounds, &pk3SoundsN );
+               }
+
+               if ( !Q_stricmp( ValueForKey( &entities[i], "classname" ), "func_plat" ) ){
+                       strcpy( pk3Sounds + pk3SoundsN*65, "sound/movers/plats/pt1_strt.wav");
+                       res2list( pk3Sounds, &pk3SoundsN );
+                       strcpy( pk3Sounds + pk3SoundsN*65, "sound/movers/plats/pt1_end.wav");
+                       res2list( pk3Sounds, &pk3SoundsN );
+               }
+               if ( !Q_stricmp( ValueForKey( &entities[i], "classname" ), "target_push" ) ){
+                       if ( !(IntForKey( &entities[i], "spawnflags") & 1) ){
+                               strcpy( pk3Sounds + pk3SoundsN*65, "sound/misc/windfly.wav");
+                               res2list( pk3Sounds, &pk3SoundsN );
+                       }
+               }
+               strcpy( pk3Shaders + pk3ShadersN*65, ValueForKey( &entities[i], "targetShaderNewName" ) );
+               res2list( pk3Shaders, &pk3ShadersN );
+       }
+
+       //levelshot
+       sprintf( pk3Shaders + pk3ShadersN*65, "levelshots/%s", nameOFmap );
+       res2list( pk3Shaders, &pk3ShadersN );
+
+
+       if( dbg ){
+               Sys_Printf( "\n\tDrawsurface+ent calls....%i\n", pk3ShadersN );
+               for ( i = 0; i < pk3ShadersN; i++ ){
+                       Sys_Printf( "%s\n", pk3Shaders + i*65 );
+               }
+               Sys_Printf( "\n\tSounds....%i\n", pk3SoundsN );
+               for ( i = 0; i < pk3SoundsN; i++ ){
+                       Sys_Printf( "%s\n", pk3Sounds + i*65 );
+               }
+       }
+
+       vfsListShaderFiles( pk3Shaderfiles, &pk3ShaderfilesN );
+
+       if( dbg ){
+               Sys_Printf( "\n\tSchroider fileses.....%i\n", pk3ShaderfilesN );
+               for ( i = 0; i < pk3ShaderfilesN; i++ ){
+                       Sys_Printf( "%s\n", pk3Shaderfiles + i*65 );
+               }
+       }
+
+
+       /* load exclusions file */
+       int ExTexturesN = 0;
+       char* ExTextures = (char *)calloc( 4096*65, sizeof( char ) );
+       int ExShadersN = 0;
+       char* ExShaders = (char *)calloc( 4096*65, sizeof( char ) );
+       int ExSoundsN = 0;
+       char* ExSounds = (char *)calloc( 4096*65, sizeof( char ) );
+       int ExShaderfilesN = 0;
+       char* ExShaderfiles = (char *)calloc( 4096*65, sizeof( char ) );
+       int ExVideosN = 0;
+       char* ExVideos = (char *)calloc( 4096*65, sizeof( char ) );
+       int ExPureTexturesN = 0;
+       char* ExPureTextures = (char *)calloc( 4096*65, sizeof( char ) );
+
+       char* ExReasonShader[4096] = { NULL };
+       char* ExReasonShaderFile[4096] = { NULL };
+
+       char exName[ 1024 ];
+       byte *buffer;
+       int size;
+
+       strcpy( exName, q3map2path );
+       char *cut = strrchr( exName, '\\' );
+       char *cut2 = strrchr( exName, '/' );
+       if ( cut == NULL && cut2 == NULL ){
+               Sys_Printf( "WARNING: Unable to load exclusions file.\n" );
+               goto skipEXfile;
+       }
+       if ( cut2 > cut ) cut = cut2;
+       cut[1] = '\0';
+       strcat( exName, game->arg );
+       strcat( exName, ".exclude" );
+
+       Sys_Printf( "Loading %s\n", exName );
+       size = TryLoadFile( exName, (void**) &buffer );
+       if ( size <= 0 ) {
+               Sys_Printf( "WARNING: Unable to find exclusions file %s.\n", exName );
+               goto skipEXfile;
+       }
+
+       /* parse the file */
+       ParseFromMemory( (char *) buffer, size );
+
+       /* tokenize it */
+       while ( 1 )
+       {
+               /* test for end of file */
+               if ( !GetToken( qtrue ) ) {
+                       break;
+               }
+
+               /* blocks */
+               if ( !Q_stricmp( token, "textures" ) ){
+                       parseEXblock ( ExTextures, &ExTexturesN, exName );
+               }
+               else if ( !Q_stricmp( token, "shaders" ) ){
+                       parseEXblock ( ExShaders, &ExShadersN, exName );
+               }
+               else if ( !Q_stricmp( token, "shaderfiles" ) ){
+                       parseEXblock ( ExShaderfiles, &ExShaderfilesN, exName );
+               }
+               else if ( !Q_stricmp( token, "sounds" ) ){
+                       parseEXblock ( ExSounds, &ExSoundsN, exName );
+               }
+               else if ( !Q_stricmp( token, "videos" ) ){
+                       parseEXblock ( ExVideos, &ExVideosN, exName );
+               }
+               else{
+                       Error( "ReadExclusionsFile: %s, line %d: unknown block name!\nValid ones are: textures, shaders, shaderfiles, sounds, videos.", exName, scriptline );
+               }
+       }
+
+       /* free the buffer */
+       free( buffer );
+
+       for ( i = 0; i < ExTexturesN; i++ ){
+               for ( j = 0; j < ExShadersN; j++ ){
+                       if ( !Q_stricmp( ExTextures + i*65, ExShaders + j*65 ) ){
+                               break;
+                       }
+               }
+               if ( j == ExShadersN ){
+                       strcpy ( ExPureTextures + ExPureTexturesN*65, ExTextures + i*65 );
+                       ExPureTexturesN++;
+               }
+       }
+
+skipEXfile:
+
+       if( dbg ){
+               Sys_Printf( "\n\tExTextures....%i\n", ExTexturesN );
+               for ( i = 0; i < ExTexturesN; i++ ) Sys_Printf( "%s\n", ExTextures + i*65 );
+               Sys_Printf( "\n\tExPureTextures....%i\n", ExPureTexturesN );
+               for ( i = 0; i < ExPureTexturesN; i++ ) Sys_Printf( "%s\n", ExPureTextures + i*65 );
+               Sys_Printf( "\n\tExShaders....%i\n", ExShadersN );
+               for ( i = 0; i < ExShadersN; i++ ) Sys_Printf( "%s\n", ExShaders + i*65 );
+               Sys_Printf( "\n\tExShaderfiles....%i\n", ExShaderfilesN );
+               for ( i = 0; i < ExShaderfilesN; i++ ) Sys_Printf( "%s\n", ExShaderfiles + i*65 );
+               Sys_Printf( "\n\tExSounds....%i\n", ExSoundsN );
+               for ( i = 0; i < ExSoundsN; i++ ) Sys_Printf( "%s\n", ExSounds + i*65 );
+               Sys_Printf( "\n\tExVideos....%i\n", ExVideosN );
+               for ( i = 0; i < ExVideosN; i++ ) Sys_Printf( "%s\n", ExVideos + i*65 );
+       }
+
+       /* can exclude pure textures right now, shouldn't create shaders for them anyway */
+       for ( i = 0; i < pk3ShadersN ; i++ ){
+               for ( j = 0; j < ExPureTexturesN ; j++ ){
+                       if ( !Q_stricmp( pk3Shaders + i*65, ExPureTextures + j*65 ) ){
+                               *( pk3Shaders + i*65 ) = '\0';
+                               break;
+                       }
+               }
+       }
+
+       //Parse Shader Files
+        /* hack */
+       endofscript = qtrue;
+
+       for ( i = 0; i < pk3ShaderfilesN; i++ ){
+               qboolean wantShader = qfalse, wantShaderFile = qfalse, ShaderFileExcluded = qfalse;
+               int shader;
+               char* reasonShader = NULL;
+               char* reasonShaderFile = NULL;
+
+               /* load the shader */
+               sprintf( temp, "%s/%s", game->shaderPath, pk3Shaderfiles + i*65 );
+               SilentLoadScriptFile( temp, 0 );
+               if( dbg ) Sys_Printf( "\n\tentering %s\n", pk3Shaderfiles + i*65 );
+
+               /* do wanna le shader file? */
+               for ( j = 0; j < ExShaderfilesN; j++ ){
+                       if ( !Q_stricmp( ExShaderfiles + j*65, pk3Shaderfiles + i*65 ) ){
+                               ShaderFileExcluded = qtrue;
+                               reasonShaderFile = ExShaderfiles + j*65;
+                               break;
+                       }
+               }
+               /* tokenize it */
+               /* check if shader file has to be excluded */
+               while ( !ShaderFileExcluded )
+               {
+                       /* test for end of file */
+                       if ( !GetToken( qtrue ) ) {
+                               break;
+                       }
+
+                       /* does it contain restricted shaders/textures? */
+                       for ( j = 0; j < ExShadersN; j++ ){
+                               if ( !Q_stricmp( ExShaders + j*65, token ) ){
+                                       ShaderFileExcluded = qtrue;
+                                       reasonShader = ExShaders + j*65;
+                                       break;
+                               }
+                       }
+                       if ( ShaderFileExcluded )
+                               break;
+                       for ( j = 0; j < ExPureTexturesN; j++ ){
+                               if ( !Q_stricmp( ExPureTextures + j*65, token ) ){
+                                       ShaderFileExcluded = qtrue;
+                                       reasonShader = ExPureTextures + j*65;
+                                       break;
+                               }
+                       }
+                       if ( ShaderFileExcluded )
+                               break;
+
+                       /* handle { } section */
+                       if ( !GetToken( qtrue ) ) {
+                               break;
+                       }
+                       if ( strcmp( token, "{" ) ) {
+                                       Error( "ParseShaderFile: %s, line %d: { not found!\nFound instead: %s\nFile location be: %s",
+                                               temp, scriptline, token, g_strLoadedFileLocation );
+                       }
+
+                       while ( 1 )
+                       {
+                               /* get the next token */
+                               if ( !GetToken( qtrue ) ) {
+                                       break;
+                               }
+                               if ( !strcmp( token, "}" ) ) {
+                                       break;
+                               }
+                               /* parse stage directives */
+                               if ( !strcmp( token, "{" ) ) {
+                                       while ( 1 )
+                                       {
+                                               if ( !GetToken( qtrue ) ) {
+                                                       break;
+                                               }
+                                               if ( !strcmp( token, "}" ) ) {
+                                                       break;
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               /* tokenize it again */
+               SilentLoadScriptFile( temp, 0 );
+               while ( 1 )
+               {
+                       /* test for end of file */
+                       if ( !GetToken( qtrue ) ) {
+                               break;
+                       }
+                       //dump shader names
+                       if( dbg ) Sys_Printf( "%s\n", token );
+
+                       /* do wanna le shader? */
+                       wantShader = qfalse;
+                       for ( j = 0; j < pk3ShadersN; j++ ){
+                               if ( !Q_stricmp( pk3Shaders + j*65, token) ){
+                                       shader = j;
+                                       wantShader = qtrue;
+                                       break;
+                               }
+                       }
+
+                       /* handle { } section */
+                       if ( !GetToken( qtrue ) ) {
+                               break;
+                       }
+                       if ( strcmp( token, "{" ) ) {
+                                       Error( "ParseShaderFile: %s, line %d: { not found!\nFound instead: %s\nFile location be: %s",
+                                               temp, scriptline, token, g_strLoadedFileLocation );
+                       }
+
+                       qboolean hasmap = qfalse;
+                       while ( 1 )
+                       {
+                               /* get the next token */
+                               if ( !GetToken( qtrue ) ) {
+                                       break;
+                               }
+                               if ( !strcmp( token, "}" ) ) {
+                                       break;
+                               }
+
+
+                               /* -----------------------------------------------------------------
+                               shader stages (passes)
+                               ----------------------------------------------------------------- */
+
+                               /* parse stage directives */
+                               if ( !strcmp( token, "{" ) ) {
+                                       while ( 1 )
+                                       {
+                                               if ( !GetToken( qtrue ) ) {
+                                                       break;
+                                               }
+                                               if ( !strcmp( token, "}" ) ) {
+                                                       break;
+                                               }
+                                               if ( !strcmp( token, "{" ) ) {
+                                                       Sys_Printf( "WARNING9: %s : line %d : opening brace inside shader stage\n", temp, scriptline );
+                                               }
+                                               if ( !Q_stricmp( token, "mapComp" ) || !Q_stricmp( token, "mapNoComp" ) || !Q_stricmp( token, "animmapcomp" ) || !Q_stricmp( token, "animmapnocomp" ) ){
+                                                       Sys_Printf( "WARNING7: %s : line %d : unsupported '%s' map directive\n", temp, scriptline, token );
+                                               }
+                                               /* skip the shader */
+                                               if ( !wantShader ) continue;
+
+                                               /* digest any images */
+                                               if ( !Q_stricmp( token, "map" ) ||
+                                                       !Q_stricmp( token, "clampMap" ) ) {
+                                                       hasmap = qtrue;
+                                                       /* get an image */
+                                                       GetToken( qfalse );
+                                                       if ( token[ 0 ] != '*' && token[ 0 ] != '$' ) {
+                                                               tex2list( pk3Textures, &pk3TexturesN, ExTextures, &ExTexturesN );
+                                                       }
+                                               }
+                                               else if ( !Q_stricmp( token, "animMap" ) ||
+                                                       !Q_stricmp( token, "clampAnimMap" ) ) {
+                                                       hasmap = qtrue;
+                                                       GetToken( qfalse );// skip num
+                                                       while ( TokenAvailable() ){
+                                                               GetToken( qfalse );
+                                                               tex2list( pk3Textures, &pk3TexturesN, ExTextures, &ExTexturesN );
+                                                       }
+                                               }
+                                               else if ( !Q_stricmp( token, "videoMap" ) ){
+                                                       hasmap = qtrue;
+                                                       GetToken( qfalse );
+                                                       FixDOSName( token );
+                                                       if ( strchr( token, '/' ) == NULL && strchr( token, '\\' ) == NULL ){
+                                                               sprintf( temp, "video/%s", token );
+                                                               strcpy( token, temp );
+                                                       }
+                                                       FixDOSName( token );
+                                                       for ( j = 0; j < pk3VideosN; j++ ){
+                                                               if ( !Q_stricmp( pk3Videos + j*65, token ) ){
+                                                                       goto away;
+                                                               }
+                                                       }
+                                                       for ( j = 0; j < ExVideosN; j++ ){
+                                                               if ( !Q_stricmp( ExVideos + j*65, token ) ){
+                                                                       goto away;
+                                                               }
+                                                       }
+                                                       strcpy ( pk3Videos + pk3VideosN*65, token );
+                                                       pk3VideosN++;
+                                                       away:
+                                                       j = 0;
+                                               }
+                                       }
+                               }
+                               else if ( !Q_strncasecmp( token, "implicit", 8 ) ){
+                                       Sys_Printf( "WARNING5: %s : line %d : unsupported %s shader\n", temp, scriptline, token );
+                               }
+                               /* skip the shader */
+                               else if ( !wantShader ) continue;
+
+                               /* -----------------------------------------------------------------
+                               surfaceparm * directives
+                               ----------------------------------------------------------------- */
+
+                               /* match surfaceparm */
+                               else if ( !Q_stricmp( token, "surfaceparm" ) ) {
+                                       GetToken( qfalse );
+                                       if ( !Q_stricmp( token, "nodraw" ) ) {
+                                               wantShader = qfalse;
+                                               *( pk3Shaders + shader*65 ) = '\0';
+                                       }
+                               }
+
+                               /* skyparms <outer image> <cloud height> <inner image> */
+                               else if ( !Q_stricmp( token, "skyParms" ) ) {
+                                       hasmap = qtrue;
+                                       /* get image base */
+                                       GetToken( qfalse );
+
+                                       /* ignore bogus paths */
+                                       if ( Q_stricmp( token, "-" ) && Q_stricmp( token, "full" ) ) {
+                                               strcpy ( temp, token );
+                                               sprintf( token, "%s_up", temp );
+                                               tex2list( pk3Textures, &pk3TexturesN, ExTextures, &ExTexturesN );
+                                               sprintf( token, "%s_dn", temp );
+                                               tex2list( pk3Textures, &pk3TexturesN, ExTextures, &ExTexturesN );
+                                               sprintf( token, "%s_lf", temp );
+                                               tex2list( pk3Textures, &pk3TexturesN, ExTextures, &ExTexturesN );
+                                               sprintf( token, "%s_rt", temp );
+                                               tex2list( pk3Textures, &pk3TexturesN, ExTextures, &ExTexturesN );
+                                               sprintf( token, "%s_bk", temp );
+                                               tex2list( pk3Textures, &pk3TexturesN, ExTextures, &ExTexturesN );
+                                               sprintf( token, "%s_ft", temp );
+                                               tex2list( pk3Textures, &pk3TexturesN, ExTextures, &ExTexturesN );
+                                       }
+                                       /* skip rest of line */
+                                       GetToken( qfalse );
+                                       GetToken( qfalse );
+                               }
+                               else if ( !Q_stricmp( token, "fogparms" ) ){
+                                       hasmap = qtrue;
+                               }
+                       }
+
+                       //exclude shader
+                       if ( wantShader ){
+                               for ( j = 0; j < ExShadersN; j++ ){
+                                       if ( !Q_stricmp( ExShaders + j*65, pk3Shaders + shader*65 ) ){
+                                               wantShader = qfalse;
+                                               *( pk3Shaders + shader*65 ) = '\0';
+                                               break;
+                                       }
+                               }
+                               if ( !hasmap ){
+                                       wantShader = qfalse;
+                               }
+                               if ( wantShader ){
+                                       if ( ShaderFileExcluded ){
+                                               if ( reasonShaderFile != NULL ){
+                                                       ExReasonShaderFile[ shader ] = reasonShaderFile;
+                                               }
+                                               else{
+                                                       ExReasonShaderFile[ shader ] = ( char* ) calloc( 65, sizeof( char ) );
+                                                       strcpy( ExReasonShaderFile[ shader ], pk3Shaderfiles + i*65 );
+                                               }
+                                               ExReasonShader[ shader ] = reasonShader;
+                                       }
+                                       else{
+                                               wantShaderFile = qtrue;
+                                               *( pk3Shaders + shader*65 ) = '\0';
+                                       }
+                               }
+                       }
+               }
+               if ( !wantShaderFile ){
+                       *( pk3Shaderfiles + i*65 ) = '\0';
+               }
+       }
+
+
+
+/* exclude stuff */
+//wanted shaders from excluded .shaders
+       Sys_Printf( "\n" );
+       for ( i = 0; i < pk3ShadersN; i++ ){
+               if ( *( pk3Shaders + i*65 ) != '\0' && ( ExReasonShader[i] != NULL || ExReasonShaderFile[i] != NULL ) ){
+                       Sys_Printf( "  !FAIL! %s\n", pk3Shaders + i*65 );
+                       packFAIL = qtrue;
+                       if ( ExReasonShader[i] != NULL ){
+                               Sys_Printf( "     reason: is located in %s,\n     containing restricted shader %s\n", ExReasonShaderFile[i], ExReasonShader[i] );
+                       }
+                       else{
+                               Sys_Printf( "     reason: is located in restricted %s\n", ExReasonShaderFile[i] );
+                       }
+                       *( pk3Shaders + i*65 ) = '\0';
+               }
+       }
+//pure textures (shader ones are done)
+       for ( i = 0; i < pk3ShadersN; i++ ){
+               if ( *( pk3Shaders + i*65 ) != '\0' ){
+                       FixDOSName( pk3Shaders + i*65 );
+                       for ( j = 0; j < pk3TexturesN; j++ ){
+                               if ( !Q_stricmp( pk3Shaders + i*65, pk3Textures + j*65 ) ){
+                                       *( pk3Shaders + i*65 ) = '\0';
+                                       break;
+                               }
+                       }
+                       if ( *( pk3Shaders + i*65 ) == '\0' ) continue;
+                       for ( j = 0; j < ExTexturesN; j++ ){
+                               if ( !Q_stricmp( pk3Shaders + i*65, ExTextures + j*65 ) ){
+                                       *( pk3Shaders + i*65 ) = '\0';
+                                       break;
+                               }
+                       }
+               }
+       }
+
+//snds
+       for ( i = 0; i < pk3SoundsN; i++ ){
+               for ( j = 0; j < ExSoundsN; j++ ){
+                       if ( !Q_stricmp( pk3Sounds + i*65, ExSounds + j*65 ) ){
+                               *( pk3Sounds + i*65 ) = '\0';
+                               break;
+                       }
+               }
+       }
+
+       /* make a pack */
+       sprintf( packname, "%s/%s_autopacked.pk3", EnginePath, nameOFmap );
+       remove( packname );
+       sprintf( packFailName, "%s/%s_FAILEDpack.pk3", EnginePath, nameOFmap );
+       remove( packFailName );
+
+       Sys_Printf( "\n--- ZipZip ---\n" );
+
+       Sys_Printf( "\n\tShader referenced textures....\n" );
+
+       for ( i = 0; i < pk3TexturesN; i++ ){
+               if ( png ){
+                       sprintf( temp, "%s.png", pk3Textures + i*65 );
+                       if ( vfsPackFile( temp, packname, 10 ) ){
+                               Sys_Printf( "++%s\n", temp );
+                               continue;
+                       }
+               }
+               sprintf( temp, "%s.tga", pk3Textures + i*65 );
+               if ( vfsPackFile( temp, packname, 10 ) ){
+                       Sys_Printf( "++%s\n", temp );
+                       continue;
+               }
+               sprintf( temp, "%s.jpg", pk3Textures + i*65 );
+               if ( vfsPackFile( temp, packname, 10 ) ){
+                       Sys_Printf( "++%s\n", temp );
+                       continue;
+               }
+               Sys_Printf( "  !FAIL! %s\n", pk3Textures + i*65 );
+               packFAIL = qtrue;
+       }
+
+       Sys_Printf( "\n\tPure textures....\n" );
+
+       for ( i = 0; i < pk3ShadersN; i++ ){
+               if ( *( pk3Shaders + i*65 ) != '\0' ){
+                       if ( png ){
+                               sprintf( temp, "%s.png", pk3Shaders + i*65 );
+                               if ( vfsPackFile( temp, packname, 10 ) ){
+                                       Sys_Printf( "++%s\n", temp );
+                                       continue;
+                               }
+                       }
+                       sprintf( temp, "%s.tga", pk3Shaders + i*65 );
+                       if ( vfsPackFile( temp, packname, 10 ) ){
+                               Sys_Printf( "++%s\n", temp );
+                               continue;
+                       }
+                       sprintf( temp, "%s.jpg", pk3Shaders + i*65 );
+                       if ( vfsPackFile( temp, packname, 10 ) ){
+                               Sys_Printf( "++%s\n", temp );
+                               continue;
+                       }
+
+                       if ( i == pk3ShadersN - 1 ){ //levelshot typically
+                               Sys_Printf( "  ~fail  %s\n", pk3Shaders + i*65 );
+                       }
+                       else{
+                               Sys_Printf( "  !FAIL! %s\n", pk3Shaders + i*65 );
+                               packFAIL = qtrue;
+                       }
+               }
+       }
+
+       Sys_Printf( "\n\tShaizers....\n" );
+
+       for ( i = 0; i < pk3ShaderfilesN; i++ ){
+               if ( *( pk3Shaderfiles + i*65 ) != '\0' ){
+                       sprintf( temp, "%s/%s", game->shaderPath, pk3Shaderfiles + i*65 );
+                       if ( vfsPackFile( temp, packname, 10 ) ){
+                               Sys_Printf( "++%s\n", temp );
+                               continue;
+                       }
+                       Sys_Printf( "  !FAIL! %s\n", pk3Shaders + i*65 );
+                       packFAIL = qtrue;
+               }
+       }
+
+       Sys_Printf( "\n\tSounds....\n" );
+
+       for ( i = 0; i < pk3SoundsN; i++ ){
+               if ( *( pk3Sounds + i*65 ) != '\0' ){
+                       if ( vfsPackFile( pk3Sounds + i*65, packname, 10 ) ){
+                               Sys_Printf( "++%s\n", pk3Sounds + i*65 );
+                               continue;
+                       }
+                       Sys_Printf( "  !FAIL! %s\n", pk3Sounds + i*65 );
+                       packFAIL = qtrue;
+               }
+       }
+
+       Sys_Printf( "\n\tVideos....\n" );
+
+       for ( i = 0; i < pk3VideosN; i++ ){
+               if ( vfsPackFile( pk3Videos + i*65, packname, 10 ) ){
+                       Sys_Printf( "++%s\n", pk3Videos + i*65 );
+                       continue;
+               }
+               Sys_Printf( "  !FAIL! %s\n", pk3Videos + i*65 );
+               packFAIL = qtrue;
+       }
+
+       Sys_Printf( "\n\t.bsp and stuff\n" );
+
+       sprintf( temp, "maps/%s.bsp", nameOFmap );
+       //if ( vfsPackFile( temp, packname, 10 ) ){
+       if ( vfsPackFile_Absolute_Path( source, temp, packname, 10 ) ){
+                       Sys_Printf( "++%s\n", temp );
+               }
+       else{
+               Sys_Printf( "  !FAIL! %s\n", temp );
+               packFAIL = qtrue;
+       }
+
+       sprintf( temp, "maps/%s.aas", nameOFmap );
+       if ( vfsPackFile( temp, packname, 10 ) ){
+                       Sys_Printf( "++%s\n", temp );
+               }
+       else{
+               Sys_Printf( "  ~fail  %s\n", temp );
+       }
+
+       sprintf( temp, "scripts/%s.arena", nameOFmap );
+       if ( vfsPackFile( temp, packname, 10 ) ){
+                       Sys_Printf( "++%s\n", temp );
+               }
+       else{
+               Sys_Printf( "  ~fail  %s\n", temp );
+       }
+
+       sprintf( temp, "scripts/%s.defi", nameOFmap );
+       if ( vfsPackFile( temp, packname, 10 ) ){
+                       Sys_Printf( "++%s\n", temp );
+               }
+       else{
+               Sys_Printf( "  ~fail  %s\n", temp );
+       }
+
+       if ( !packFAIL ){
+       Sys_Printf( "\nSaved to %s\n", packname );
+       }
+       else{
+               rename( packname, packFailName );
+               Sys_Printf( "\nSaved to %s\n", packFailName );
+       }
+       /* return to sender */
+       return 0;
+}
+
+
+/*
+   repackBSPMain()
+   repack multiple maps, strip out only required shaders
+   works for Q3 type of shaders and ents
+ */
+
+int repackBSPMain( int argc, char **argv ){
+       int i, j, len, compLevel = 0;
+       qboolean dbg = qfalse, png = qfalse;
+
+       /* process arguments */
+       for ( i = 1; i < ( argc - 1 ); i++ ){
+               if ( !strcmp( argv[ i ],  "-dbg" ) ) {
+                       dbg = qtrue;
+               }
+               else if ( !strcmp( argv[ i ],  "-png" ) ) {
+                       png = qtrue;
+               }
+               else if ( !strcmp( argv[ i ],  "-complevel" ) ) {
+                       compLevel = atoi( argv[ i + 1 ] );
+                       i++;
+                       if ( compLevel < -1 ) compLevel = -1;
+                       if ( compLevel > 10 ) compLevel = 10;
+                       Sys_Printf( "Compression level set to %i\n", compLevel );
+               }
+       }
+
+/* load exclusions file */
+       int ExTexturesN = 0;
+       char* ExTextures = (char *)calloc( 4096*65, sizeof( char ) );
+       int ExShadersN = 0;
+       char* ExShaders = (char *)calloc( 4096*65, sizeof( char ) );
+       int ExSoundsN = 0;
+       char* ExSounds = (char *)calloc( 4096*65, sizeof( char ) );
+       int ExShaderfilesN = 0;
+       char* ExShaderfiles = (char *)calloc( 4096*65, sizeof( char ) );
+       int ExVideosN = 0;
+       char* ExVideos = (char *)calloc( 4096*65, sizeof( char ) );
+       int ExPureTexturesN = 0;
+       char* ExPureTextures = (char *)calloc( 4096*65, sizeof( char ) );
+
+
+       char exName[ 1024 ];
+       byte *buffer;
+       int size;
+
+       strcpy( exName, q3map2path );
+       char *cut = strrchr( exName, '\\' );
+       char *cut2 = strrchr( exName, '/' );
+       if ( cut == NULL && cut2 == NULL ){
+               Sys_Printf( "WARNING: Unable to load exclusions file.\n" );
+               goto skipEXfile;
+       }
+       if ( cut2 > cut ) cut = cut2;
+       cut[1] = '\0';
+       strcat( exName, game->arg );
+       strcat( exName, ".exclude" );
+
+       Sys_Printf( "Loading %s\n", exName );
+       size = TryLoadFile( exName, (void**) &buffer );
+       if ( size <= 0 ) {
+               Sys_Printf( "WARNING: Unable to find exclusions file %s.\n", exName );
+               goto skipEXfile;
+       }
+
+       /* parse the file */
+       ParseFromMemory( (char *) buffer, size );
+
+       /* tokenize it */
+       while ( 1 )
+       {
+               /* test for end of file */
+               if ( !GetToken( qtrue ) ) {
+                       break;
+               }
+
+               /* blocks */
+               if ( !Q_stricmp( token, "textures" ) ){
+                       parseEXblock ( ExTextures, &ExTexturesN, exName );
+               }
+               else if ( !Q_stricmp( token, "shaders" ) ){
+                       parseEXblock ( ExShaders, &ExShadersN, exName );
+               }
+               else if ( !Q_stricmp( token, "shaderfiles" ) ){
+                       parseEXblock ( ExShaderfiles, &ExShaderfilesN, exName );
+               }
+               else if ( !Q_stricmp( token, "sounds" ) ){
+                       parseEXblock ( ExSounds, &ExSoundsN, exName );
+               }
+               else if ( !Q_stricmp( token, "videos" ) ){
+                       parseEXblock ( ExVideos, &ExVideosN, exName );
+               }
+               else{
+                       Error( "ReadExclusionsFile: %s, line %d: unknown block name!\nValid ones are: textures, shaders, shaderfiles, sounds, videos.", exName, scriptline );
+               }
+       }
+
+       /* free the buffer */
+       free( buffer );
+
+       for ( i = 0; i < ExTexturesN; i++ ){
+               for ( j = 0; j < ExShadersN; j++ ){
+                       if ( !Q_stricmp( ExTextures + i*65, ExShaders + j*65 ) ){
+                               break;
+                       }
+               }
+               if ( j == ExShadersN ){
+                       strcpy ( ExPureTextures + ExPureTexturesN*65, ExTextures + i*65 );
+                       ExPureTexturesN++;
+               }
+       }
+
+skipEXfile:
+
+       if( dbg ){
+               Sys_Printf( "\n\tExTextures....%i\n", ExTexturesN );
+               for ( i = 0; i < ExTexturesN; i++ ) Sys_Printf( "%s\n", ExTextures + i*65 );
+               Sys_Printf( "\n\tExPureTextures....%i\n", ExPureTexturesN );
+               for ( i = 0; i < ExPureTexturesN; i++ ) Sys_Printf( "%s\n", ExPureTextures + i*65 );
+               Sys_Printf( "\n\tExShaders....%i\n", ExShadersN );
+               for ( i = 0; i < ExShadersN; i++ ) Sys_Printf( "%s\n", ExShaders + i*65 );
+               Sys_Printf( "\n\tExShaderfiles....%i\n", ExShaderfilesN );
+               for ( i = 0; i < ExShaderfilesN; i++ ) Sys_Printf( "%s\n", ExShaderfiles + i*65 );
+               Sys_Printf( "\n\tExSounds....%i\n", ExSoundsN );
+               for ( i = 0; i < ExSoundsN; i++ ) Sys_Printf( "%s\n", ExSounds + i*65 );
+               Sys_Printf( "\n\tExVideos....%i\n", ExVideosN );
+               for ( i = 0; i < ExVideosN; i++ ) Sys_Printf( "%s\n", ExVideos + i*65 );
+       }
+
+
+
+
+/* load repack.exclude */
+       int rExTexturesN = 0;
+       char* rExTextures = (char *)calloc( 65536*65, sizeof( char ) );
+       int rExShadersN = 0;
+       char* rExShaders = (char *)calloc( 32768*65, sizeof( char ) );
+       int rExSoundsN = 0;
+       char* rExSounds = (char *)calloc( 8192*65, sizeof( char ) );
+       int rExShaderfilesN = 0;
+       char* rExShaderfiles = (char *)calloc( 4096*65, sizeof( char ) );
+       int rExVideosN = 0;
+       char* rExVideos = (char *)calloc( 4096*65, sizeof( char ) );
+
+       strcpy( exName, q3map2path );
+       cut = strrchr( exName, '\\' );
+       cut2 = strrchr( exName, '/' );
+       if ( cut == NULL && cut2 == NULL ){
+               Sys_Printf( "WARNING: Unable to load repack exclusions file.\n" );
+               goto skipEXrefile;
+       }
+       if ( cut2 > cut ) cut = cut2;
+       cut[1] = '\0';
+       strcat( exName, "repack.exclude" );
+
+       Sys_Printf( "Loading %s\n", exName );
+       size = TryLoadFile( exName, (void**) &buffer );
+       if ( size <= 0 ) {
+               Sys_Printf( "WARNING: Unable to find repack exclusions file %s.\n", exName );
+               goto skipEXrefile;
+       }
+
+       /* parse the file */
+       ParseFromMemory( (char *) buffer, size );
+
+       /* tokenize it */
+       while ( 1 )
+       {
+               /* test for end of file */
+               if ( !GetToken( qtrue ) ) {
+                       break;
+               }
+
+               /* blocks */
+               if ( !Q_stricmp( token, "textures" ) ){
+                       parseEXblock ( rExTextures, &rExTexturesN, exName );
+               }
+               else if ( !Q_stricmp( token, "shaders" ) ){
+                       parseEXblock ( rExShaders, &rExShadersN, exName );
+               }
+               else if ( !Q_stricmp( token, "shaderfiles" ) ){
+                       parseEXblock ( rExShaderfiles, &rExShaderfilesN, exName );
+               }
+               else if ( !Q_stricmp( token, "sounds" ) ){
+                       parseEXblock ( rExSounds, &rExSoundsN, exName );
+               }
+               else if ( !Q_stricmp( token, "videos" ) ){
+                       parseEXblock ( rExVideos, &rExVideosN, exName );
+               }
+               else{
+                       Error( "ReadExclusionsFile: %s, line %d: unknown block name!\nValid ones are: textures, shaders, shaderfiles, sounds, videos.", exName, scriptline );
+               }
+       }
+
+       /* free the buffer */
+       free( buffer );
+
+skipEXrefile:
+
+       if( dbg ){
+               Sys_Printf( "\n\trExTextures....%i\n", rExTexturesN );
+               for ( i = 0; i < rExTexturesN; i++ ) Sys_Printf( "%s\n", rExTextures + i*65 );
+               Sys_Printf( "\n\trExShaders....%i\n", rExShadersN );
+               for ( i = 0; i < rExShadersN; i++ ) Sys_Printf( "%s\n", rExShaders + i*65 );
+               Sys_Printf( "\n\trExShaderfiles....%i\n", rExShaderfilesN );
+               for ( i = 0; i < rExShaderfilesN; i++ ) Sys_Printf( "%s\n", rExShaderfiles + i*65 );
+               Sys_Printf( "\n\trExSounds....%i\n", rExSoundsN );
+               for ( i = 0; i < rExSoundsN; i++ ) Sys_Printf( "%s\n", rExSounds + i*65 );
+               Sys_Printf( "\n\trExVideos....%i\n", rExVideosN );
+               for ( i = 0; i < rExVideosN; i++ ) Sys_Printf( "%s\n", rExVideos + i*65 );
+       }
+
+
+
+
+       int bspListN = 0;
+       char* bspList = (char *)calloc( 8192*1024, sizeof( char ) );
+
+       /* do some path mangling */
+       strcpy( source, ExpandArg( argv[ argc - 1 ] ) );
+       if ( !Q_stricmp( strrchr( source, '.' ), ".bsp" ) ){
+               strcpy( bspList, source );
+               bspListN++;
+       }
+       else{
+               /* load bsps paths list */
+               Sys_Printf( "Loading %s\n", source );
+               size = TryLoadFile( source, (void**) &buffer );
+               if ( size <= 0 ) {
+                       Sys_Printf( "WARNING: Unable to open bsps paths list file %s.\n", source );
+               }
+
+               /* parse the file */
+               ParseFromMemory( (char *) buffer, size );
+
+               /* tokenize it */
+               while ( 1 )
+               {
+                       /* test for end of file */
+                       if ( !GetToken( qtrue ) ) {
+                               break;
+                       }
+                       strcpy( bspList + bspListN * 1024 , token );
+                       bspListN++;
+               }
+
+               /* free the buffer */
+               free( buffer );
+       }
+
+       char packname[ 1024 ], nameOFrepack[ 1024 ], nameOFmap[ 1024 ], temp[ 1024 ];
+
+       /* copy input file name */
+       strcpy( temp, source );
+       StripExtension( temp );
+
+       /* extract input file name */
+       len = strlen( temp ) - 1;
+       while ( len > 0 && temp[ len ] != '/' && temp[ len ] != '\\' )
+               len--;
+       strcpy( nameOFrepack, &temp[ len + 1 ] );
+
+
+/* load bsps */
+       int pk3ShadersN = 0;
+       char* pk3Shaders = (char *)calloc( 65536*65, sizeof( char ) );
+       int pk3SoundsN = 0;
+       char* pk3Sounds = (char *)calloc( 4096*65, sizeof( char ) );
+       int pk3ShaderfilesN = 0;
+       char* pk3Shaderfiles = (char *)calloc( 4096*65, sizeof( char ) );
+       int pk3TexturesN = 0;
+       char* pk3Textures = (char *)calloc( 65536*65, sizeof( char ) );
+       int pk3VideosN = 0;
+       char* pk3Videos = (char *)calloc( 1024*65, sizeof( char ) );
+
+       for( j = 0; j < bspListN; j++ ){
+
+               int pk3SoundsNold = pk3SoundsN;
+               int pk3ShadersNold = pk3ShadersN;
+
+               strcpy( source, bspList + j*1024 );
+               StripExtension( source );
+               DefaultExtension( source, ".bsp" );
+
+               /* load the bsp */
+               Sys_Printf( "\nLoading %s\n", source );
+               PartialLoadBSPFile( source );
+               ParseEntities();
+
+               /* copy map name */
+               strcpy( temp, source );
+               StripExtension( temp );
+
+               /* extract map name */
+               len = strlen( temp ) - 1;
+               while ( len > 0 && temp[ len ] != '/' && temp[ len ] != '\\' )
+                       len--;
+               strcpy( nameOFmap, &temp[ len + 1 ] );
+
+
+               qboolean drawsurfSHs[1024] = { qfalse };
+
+               for ( i = 0; i < numBSPDrawSurfaces; i++ ){
+                       drawsurfSHs[ bspDrawSurfaces[i].shaderNum ] = qtrue;
+               }
+
+               for ( i = 0; i < numBSPShaders; i++ ){
+                       if ( drawsurfSHs[i] ){
+                               strcpy( pk3Shaders + pk3ShadersN*65, bspShaders[i].shader );
+                               res2list( pk3Shaders, &pk3ShadersN );
+                       }
+               }
+
+               /* Ent keys */
+               epair_t *ep;
+               for ( ep = entities[0].epairs; ep != NULL; ep = ep->next )
+               {
+                       if ( !Q_strncasecmp( ep->key, "vertexremapshader", 17 ) ) {
+                               sscanf( ep->value, "%*[^;] %*[;] %s", pk3Shaders + pk3ShadersN*65 );
+                               res2list( pk3Shaders, &pk3ShadersN );
+                       }
+               }
+               strcpy( pk3Sounds + pk3SoundsN*65, ValueForKey( &entities[0], "music" ) );
+               if ( *( pk3Sounds + pk3SoundsN*65 ) != '\0' ){
+                       FixDOSName( pk3Sounds + pk3SoundsN*65 );
+                       DefaultExtension( pk3Sounds + pk3SoundsN*65, ".wav" );
+                       res2list( pk3Sounds, &pk3SoundsN );
+               }
+
+               for ( i = 0; i < numBSPEntities && i < numEntities; i++ )
+               {
+                       strcpy( pk3Sounds + pk3SoundsN*65, ValueForKey( &entities[i], "noise" ) );
+                       if ( *( pk3Sounds + pk3SoundsN*65 ) != '\0' && *( pk3Sounds + pk3SoundsN*65 ) != '*' ){
+                               FixDOSName( pk3Sounds + pk3SoundsN*65 );
+                               DefaultExtension( pk3Sounds + pk3SoundsN*65, ".wav" );
+                               res2list( pk3Sounds, &pk3SoundsN );
+                       }
+
+                       if ( !Q_stricmp( ValueForKey( &entities[i], "classname" ), "func_plat" ) ){
+                               strcpy( pk3Sounds + pk3SoundsN*65, "sound/movers/plats/pt1_strt.wav");
+                               res2list( pk3Sounds, &pk3SoundsN );
+                               strcpy( pk3Sounds + pk3SoundsN*65, "sound/movers/plats/pt1_end.wav");
+                               res2list( pk3Sounds, &pk3SoundsN );
+                       }
+                       if ( !Q_stricmp( ValueForKey( &entities[i], "classname" ), "target_push" ) ){
+                               if ( !(IntForKey( &entities[i], "spawnflags") & 1) ){
+                                       strcpy( pk3Sounds + pk3SoundsN*65, "sound/misc/windfly.wav");
+                                       res2list( pk3Sounds, &pk3SoundsN );
+                               }
+                       }
+                       strcpy( pk3Shaders + pk3ShadersN*65, ValueForKey( &entities[i], "targetShaderNewName" ) );
+                       res2list( pk3Shaders, &pk3ShadersN );
+               }
+
+               //levelshot
+               sprintf( pk3Shaders + pk3ShadersN*65, "levelshots/%s", nameOFmap );
+               res2list( pk3Shaders, &pk3ShadersN );
+
+
+
+               Sys_Printf( "\n\t+Drawsurface+ent calls....%i\n", pk3ShadersN - pk3ShadersNold );
+               for ( i = pk3ShadersNold; i < pk3ShadersN; i++ ){
+                       Sys_Printf( "%s\n", pk3Shaders + i*65 );
+               }
+               Sys_Printf( "\n\t+Sounds....%i\n", pk3SoundsN - pk3SoundsNold );
+               for ( i = pk3SoundsNold; i < pk3SoundsN; i++ ){
+                       Sys_Printf( "%s\n", pk3Sounds + i*65 );
+               }
+               /* free bsp data */
+/*
+               if ( bspDrawVerts != 0 ) {
+                       free( bspDrawVerts );
+                       bspDrawVerts = NULL;
+                       //numBSPDrawVerts = 0;
+                       Sys_Printf( "freed BSPDrawVerts\n" );
+               }
+*/             if ( bspDrawSurfaces != 0 ) {
+                       free( bspDrawSurfaces );
+                       bspDrawSurfaces = NULL;
+                       //numBSPDrawSurfaces = 0;
+                       //Sys_Printf( "freed bspDrawSurfaces\n" );
+               }
+/*             if ( bspLightBytes != 0 ) {
+                       free( bspLightBytes );
+                       bspLightBytes = NULL;
+                       //numBSPLightBytes = 0;
+                       Sys_Printf( "freed BSPLightBytes\n" );
+               }
+               if ( bspGridPoints != 0 ) {
+                       free( bspGridPoints );
+                       bspGridPoints = NULL;
+                       //numBSPGridPoints = 0;
+                       Sys_Printf( "freed BSPGridPoints\n" );
+               }
+               if ( bspPlanes != 0 ) {
+                       free( bspPlanes );
+                       bspPlanes = NULL;
+                       Sys_Printf( "freed bspPlanes\n" );
+                       //numBSPPlanes = 0;
+                       //allocatedBSPPlanes = 0;
+               }
+               if ( bspBrushes != 0 ) {
+                       free( bspBrushes );
+                       bspBrushes = NULL;
+                       Sys_Printf( "freed bspBrushes\n" );
+                       //numBSPBrushes = 0;
+                       //allocatedBSPBrushes = 0;
+               }
+*/             if ( entities != 0 ) {
+                       epair_t *ep2free;
+                       for ( i = 0; i < numBSPEntities && i < numEntities; i++ ){
+                               ep = entities[i].epairs;
+                               while( ep != NULL){
+                                       ep2free = ep;
+                                       ep = ep->next;
+                                       free( ep2free );
+                               }
+                       }
+                       free( entities );
+                       entities = NULL;
+                       //Sys_Printf( "freed entities\n" );
+                       numEntities = 0;
+                       numBSPEntities = 0;
+                       allocatedEntities = 0;
+               }
+/*             if ( bspModels != 0 ) {
+                       free( bspModels );
+                       bspModels = NULL;
+                       Sys_Printf( "freed bspModels\n" );
+                       //numBSPModels = 0;
+                       //allocatedBSPModels = 0;
+               }
+*/             if ( bspShaders != 0 ) {
+                       free( bspShaders );
+                       bspShaders = NULL;
+                       //Sys_Printf( "freed bspShaders\n" );
+                       //numBSPShaders = 0;
+                       //allocatedBSPShaders = 0;
+               }
+               if ( bspEntData != 0 ) {
+                       free( bspEntData );
+                       bspEntData = NULL;
+                       //Sys_Printf( "freed bspEntData\n" );
+                       //bspEntDataSize = 0;
+                       //allocatedBSPEntData = 0;
+               }
+/*             if ( bspNodes != 0 ) {
+                       free( bspNodes );
+                       bspNodes = NULL;
+                       Sys_Printf( "freed bspNodes\n" );
+                       //numBSPNodes = 0;
+                       //allocatedBSPNodes = 0;
+               }
+               if ( bspDrawIndexes != 0 ) {
+                       free( bspDrawIndexes );
+                       bspDrawIndexes = NULL;
+                       Sys_Printf( "freed bspDrawIndexes\n" );
+                       //numBSPDrawIndexes = 0;
+                       //allocatedBSPDrawIndexes = 0;
+               }
+               if ( bspLeafSurfaces != 0 ) {
+                       free( bspLeafSurfaces );
+                       bspLeafSurfaces = NULL;
+                       Sys_Printf( "freed bspLeafSurfaces\n" );
+                       //numBSPLeafSurfaces = 0;
+                       //allocatedBSPLeafSurfaces = 0;
+               }
+               if ( bspLeafBrushes != 0 ) {
+                       free( bspLeafBrushes );
+                       bspLeafBrushes = NULL;
+                       Sys_Printf( "freed bspLeafBrushes\n" );
+                       //numBSPLeafBrushes = 0;
+                       //allocatedBSPLeafBrushes = 0;
+               }
+               if ( bspBrushSides != 0 ) {
+                       free( bspBrushSides );
+                       bspBrushSides = NULL;
+                       Sys_Printf( "freed bspBrushSides\n" );
+                       numBSPBrushSides = 0;
+                       allocatedBSPBrushSides = 0;
+               }
+               if ( numBSPFogs != 0 ) {
+                       Sys_Printf( "freed numBSPFogs\n" );
+                       numBSPFogs = 0;
+               }
+               if ( numBSPAds != 0 ) {
+                       Sys_Printf( "freed numBSPAds\n" );
+                       numBSPAds = 0;
+               }
+               if ( numBSPLeafs != 0 ) {
+                       Sys_Printf( "freed numBSPLeafs\n" );
+                       numBSPLeafs = 0;
+               }
+               if ( numBSPVisBytes != 0 ) {
+                       Sys_Printf( "freed numBSPVisBytes\n" );
+                       numBSPVisBytes = 0;
+               }
+*/     }
+
+
+
+       vfsListShaderFiles( pk3Shaderfiles, &pk3ShaderfilesN );
+
+       if( dbg ){
+               Sys_Printf( "\n\tSchroider fileses.....%i\n", pk3ShaderfilesN );
+               for ( i = 0; i < pk3ShaderfilesN; i++ ){
+                       Sys_Printf( "%s\n", pk3Shaderfiles + i*65 );
+               }
+       }
+
+
+
+       /* can exclude pure *base* textures right now, shouldn't create shaders for them anyway */
+       for ( i = 0; i < pk3ShadersN ; i++ ){
+               for ( j = 0; j < ExPureTexturesN ; j++ ){
+                       if ( !Q_stricmp( pk3Shaders + i*65, ExPureTextures + j*65 ) ){
+                               *( pk3Shaders + i*65 ) = '\0';
+                               break;
+                       }
+               }
+       }
+       /* can exclude repack.exclude shaders, assuming they got all their images */
+       for ( i = 0; i < pk3ShadersN ; i++ ){
+               for ( j = 0; j < rExShadersN ; j++ ){
+                       if ( !Q_stricmp( pk3Shaders + i*65, rExShaders + j*65 ) ){
+                               *( pk3Shaders + i*65 ) = '\0';
+                               break;
+                       }
+               }
+       }
+
+       //Parse Shader Files
+       Sys_Printf( "\t\nParsing shaders....\n\n" );
+       char shaderText[ 8192 ];
+       char* allShaders = (char *)calloc( 16777216, sizeof( char ) );
+        /* hack */
+       endofscript = qtrue;
+
+       for ( i = 0; i < pk3ShaderfilesN; i++ ){
+               qboolean wantShader = qfalse;
+               int shader;
+
+               /* load the shader */
+               sprintf( temp, "%s/%s", game->shaderPath, pk3Shaderfiles + i*65 );
+               if ( dbg ) Sys_Printf( "\n\tentering %s\n", pk3Shaderfiles + i*65 );
+               SilentLoadScriptFile( temp, 0 );
+
+               /* tokenize it */
+               while ( 1 )
+               {
+                       int line = scriptline;
+                       /* test for end of file */
+                       if ( !GetToken( qtrue ) ) {
+                               break;
+                       }
+                       //dump shader names
+                       if( dbg ) Sys_Printf( "%s\n", token );
+
+                       strcpy( shaderText, token );
+
+                       if ( strchr( token, '\\') != NULL  ){
+                               Sys_Printf( "WARNING1: %s : %s : shader name with backslash\n", pk3Shaderfiles + i*65, token );
+                       }
+
+                       /* do wanna le shader? */
+                       wantShader = qfalse;
+                       for ( j = 0; j < pk3ShadersN; j++ ){
+                               if ( !Q_stricmp( pk3Shaders + j*65, token) ){
+                                       shader = j;
+                                       wantShader = qtrue;
+                                       break;
+                               }
+                       }
+                       if ( wantShader ){
+                               for ( j = 0; j < rExTexturesN ; j++ ){
+                                       if ( !Q_stricmp( pk3Shaders + shader*65, rExTextures + j*65 ) ){
+                                               Sys_Printf( "WARNING3: %s : about to include shader for excluded texture\n", pk3Shaders + shader*65 );
+                                               break;
+                                       }
+                               }
+                       }
+
+                       /* handle { } section */
+                       if ( !GetToken( qtrue ) ) {
+                               break;
+                       }
+                       if ( strcmp( token, "{" ) ) {
+                                       Error( "ParseShaderFile: %s, line %d: { not found!\nFound instead: %s\nFile location be: %s",
+                                               temp, scriptline, token, g_strLoadedFileLocation );
+                       }
+                       strcat( shaderText, "\n{" );
+                       qboolean hasmap = qfalse;
+
+                       while ( 1 )
+                       {
+                               line = scriptline;
+                               /* get the next token */
+                               if ( !GetToken( qtrue ) ) {
+                                       break;
+                               }
+                               if ( !strcmp( token, "}" ) ) {
+                                       strcat( shaderText, "\n}\n\n" );
+                                       break;
+                               }
+                               /* parse stage directives */
+                               if ( !strcmp( token, "{" ) ) {
+                                       qboolean tokenready = qfalse;
+                                       strcat( shaderText, "\n\t{" );
+                                       while ( 1 )
+                                       {
+                                               /* detour of TokenAvailable() '~' */
+                                               if ( tokenready ) tokenready = qfalse;
+                                               else line = scriptline;
+                                               if ( !GetToken( qtrue ) ) {
+                                                       break;
+                                               }
+                                               if ( !strcmp( token, "}" ) ) {
+                                                       strcat( shaderText, "\n\t}" );
+                                                       break;
+                                               }
+                                               if ( !strcmp( token, "{" ) ) {
+                                                       strcat( shaderText, "\n\t{" );
+                                                       Sys_Printf( "WARNING9: %s : line %d : opening brace inside shader stage\n", temp, scriptline );
+                                               }
+                                               /* skip the shader */
+                                               if ( !wantShader ) continue;
+
+                                               /* digest any images */
+                                               if ( !Q_stricmp( token, "map" ) ||
+                                                       !Q_stricmp( token, "clampMap" ) ) {
+                                                       strcat( shaderText, "\n\t\t" );
+                                                       strcat( shaderText, token );
+                                                       hasmap = qtrue;
+
+                                                       /* get an image */
+                                                       GetToken( qfalse );
+                                                       if ( token[ 0 ] != '*' && token[ 0 ] != '$' ) {
+                                                               tex2list2( pk3Textures, &pk3TexturesN, ExTextures, &ExTexturesN, rExTextures, &rExTexturesN );
+                                                       }
+                                                       strcat( shaderText, " " );
+                                                       strcat( shaderText, token );
+                                               }
+                                               else if ( !Q_stricmp( token, "animMap" ) ||
+                                                       !Q_stricmp( token, "clampAnimMap" ) ) {
+                                                       strcat( shaderText, "\n\t\t" );
+                                                       strcat( shaderText, token );
+                                                       hasmap = qtrue;
+
+                                                       GetToken( qfalse );// skip num
+                                                       strcat( shaderText, " " );
+                                                       strcat( shaderText, token );
+                                                       while ( TokenAvailable() ){
+                                                               GetToken( qfalse );
+                                                               tex2list2( pk3Textures, &pk3TexturesN, ExTextures, &ExTexturesN, rExTextures, &rExTexturesN );
+                                                               strcat( shaderText, " " );
+                                                               strcat( shaderText, token );
+                                                       }
+                                                       tokenready = qtrue;
+                                               }
+                                               else if ( !Q_stricmp( token, "videoMap" ) ){
+                                                       strcat( shaderText, "\n\t\t" );
+                                                       strcat( shaderText, token );
+                                                       hasmap = qtrue;
+                                                       GetToken( qfalse );
+                                                       strcat( shaderText, " " );
+                                                       strcat( shaderText, token );
+                                                       FixDOSName( token );
+                                                       if ( strchr( token, '/' ) == NULL && strchr( token, '\\' ) == NULL ){
+                                                               sprintf( temp, "video/%s", token );
+                                                               strcpy( token, temp );
+                                                       }
+                                                       FixDOSName( token );
+                                                       for ( j = 0; j < pk3VideosN; j++ ){
+                                                               if ( !Q_stricmp( pk3Videos + j*65, token ) ){
+                                                                       goto away;
+                                                               }
+                                                       }
+                                                       for ( j = 0; j < ExVideosN; j++ ){
+                                                               if ( !Q_stricmp( ExVideos + j*65, token ) ){
+                                                                       goto away;
+                                                               }
+                                                       }
+                                                       for ( j = 0; j < rExVideosN; j++ ){
+                                                               if ( !Q_stricmp( rExVideos + j*65, token ) ){
+                                                                       goto away;
+                                                               }
+                                                       }
+                                                       strcpy ( pk3Videos + pk3VideosN*65, token );
+                                                       pk3VideosN++;
+                                                       away:
+                                                       j = 0;
+                                               }
+                                               else if ( !Q_stricmp( token, "mapComp" ) || !Q_stricmp( token, "mapNoComp" ) || !Q_stricmp( token, "animmapcomp" ) || !Q_stricmp( token, "animmapnocomp" ) ){
+                                                       Sys_Printf( "WARNING7: %s : %s shader\n", pk3Shaders + shader*65, token );
+                                                       hasmap = qtrue;
+                                                       if ( line == scriptline ){
+                                                               strcat( shaderText, " " );
+                                                               strcat( shaderText, token );
+                                                       }
+                                                       else{
+                                                               strcat( shaderText, "\n\t\t" );
+                                                               strcat( shaderText, token );
+                                                       }
+                                               }
+                                               else if ( line == scriptline ){
+                                                       strcat( shaderText, " " );
+                                                       strcat( shaderText, token );
+                                               }
+                                               else{
+                                                       strcat( shaderText, "\n\t\t" );
+                                                       strcat( shaderText, token );
+                                               }
+                                       }
+                               }
+                               /* skip the shader */
+                               else if ( !wantShader ) continue;
+
+                               /* -----------------------------------------------------------------
+                               surfaceparm * directives
+                               ----------------------------------------------------------------- */
+
+                               /* match surfaceparm */
+                               else if ( !Q_stricmp( token, "surfaceparm" ) ) {
+                                       strcat( shaderText, "\n\tsurfaceparm " );
+                                       GetToken( qfalse );
+                                       strcat( shaderText, token );
+                                       if ( !Q_stricmp( token, "nodraw" ) ) {
+                                               wantShader = qfalse;
+                                               *( pk3Shaders + shader*65 ) = '\0';
+                                       }
+                               }
+
+                               /* skyparms <outer image> <cloud height> <inner image> */
+                               else if ( !Q_stricmp( token, "skyParms" ) ) {
+                                       strcat( shaderText, "\n\tskyParms " );
+                                       hasmap = qtrue;
+                                       /* get image base */
+                                       GetToken( qfalse );
+                                       strcat( shaderText, token );
+
+                                       /* ignore bogus paths */
+                                       if ( Q_stricmp( token, "-" ) && Q_stricmp( token, "full" ) ) {
+                                               strcpy ( temp, token );
+                                               sprintf( token, "%s_up", temp );
+                                               tex2list2( pk3Textures, &pk3TexturesN, ExTextures, &ExTexturesN, rExTextures, &rExTexturesN );
+                                               sprintf( token, "%s_dn", temp );
+                                               tex2list2( pk3Textures, &pk3TexturesN, ExTextures, &ExTexturesN, rExTextures, &rExTexturesN );
+                                               sprintf( token, "%s_lf", temp );
+                                               tex2list2( pk3Textures, &pk3TexturesN, ExTextures, &ExTexturesN, rExTextures, &rExTexturesN );
+                                               sprintf( token, "%s_rt", temp );
+                                               tex2list2( pk3Textures, &pk3TexturesN, ExTextures, &ExTexturesN, rExTextures, &rExTexturesN );
+                                               sprintf( token, "%s_bk", temp );
+                                               tex2list2( pk3Textures, &pk3TexturesN, ExTextures, &ExTexturesN, rExTextures, &rExTexturesN );
+                                               sprintf( token, "%s_ft", temp );
+                                               tex2list2( pk3Textures, &pk3TexturesN, ExTextures, &ExTexturesN, rExTextures, &rExTexturesN );
+                                       }
+                                       /* skip rest of line */
+                                       GetToken( qfalse );
+                                       strcat( shaderText, " " );
+                                       strcat( shaderText, token );
+                                       GetToken( qfalse );
+                                       strcat( shaderText, " " );
+                                       strcat( shaderText, token );
+                               }
+                               else if ( !Q_strncasecmp( token, "implicit", 8 ) ){
+                                       Sys_Printf( "WARNING5: %s : %s shader\n", pk3Shaders + shader*65, token );
+                                       hasmap = qtrue;
+                                       if ( line == scriptline ){
+                                               strcat( shaderText, " " );
+                                               strcat( shaderText, token );
+                                       }
+                                       else{
+                                               strcat( shaderText, "\n\t" );
+                                               strcat( shaderText, token );
+                                       }
+                               }
+                               else if ( !Q_stricmp( token, "fogparms" ) ){
+                                       hasmap = qtrue;
+                                       if ( line == scriptline ){
+                                               strcat( shaderText, " " );
+                                               strcat( shaderText, token );
+                                       }
+                                       else{
+                                               strcat( shaderText, "\n\t" );
+                                               strcat( shaderText, token );
+                                       }
+                               }
+                               else if ( line == scriptline ){
+                                       strcat( shaderText, " " );
+                                       strcat( shaderText, token );
+                               }
+                               else{
+                                       strcat( shaderText, "\n\t" );
+                                       strcat( shaderText, token );
+                               }
+                       }
+
+                       //exclude shader
+                       if ( wantShader ){
+                               for ( j = 0; j < ExShadersN; j++ ){
+                                       if ( !Q_stricmp( ExShaders + j*65, pk3Shaders + shader*65 ) ){
+                                               wantShader = qfalse;
+                                               *( pk3Shaders + shader*65 ) = '\0';
+                                               break;
+                                       }
+                               }
+                               if ( wantShader && !hasmap ){
+                                       Sys_Printf( "WARNING8: %s : shader has no known maps\n", pk3Shaders + shader*65 );
+                                       wantShader = qfalse;
+                                       *( pk3Shaders + shader*65 ) = '\0';
+                               }
+                               if ( wantShader ){
+                                       strcat( allShaders, shaderText );
+                                       *( pk3Shaders + shader*65 ) = '\0';
+                               }
+                       }
+               }
+       }
+/* TODO: RTCW's mapComp, mapNoComp, animmapcomp, animmapnocomp; nocompress?; ET's implicitmap, implicitblend, implicitmask */
+
+
+/* exclude stuff */
+
+//pure textures (shader ones are done)
+       for ( i = 0; i < pk3ShadersN; i++ ){
+               if ( *( pk3Shaders + i*65 ) != '\0' ){
+                       if ( strchr( pk3Shaders + i*65, '\\') != NULL  ){
+                               Sys_Printf( "WARNING2: %s : bsp shader path with backslash\n", pk3Shaders + i*65 );
+                               FixDOSName( pk3Shaders + i*65 );
+                               //what if theres properly slashed one in the list?
+                               for ( j = 0; j < pk3ShadersN; j++ ){
+                                       if ( !Q_stricmp( pk3Shaders + i*65, pk3Shaders + j*65 ) && (i != j) ){
+                                               *( pk3Shaders + i*65 ) = '\0';
+                                               break;
+                                       }
+                               }
+                       }
+                       if ( *( pk3Shaders + i*65 ) == '\0' ) continue;
+                       for ( j = 0; j < pk3TexturesN; j++ ){
+                               if ( !Q_stricmp( pk3Shaders + i*65, pk3Textures + j*65 ) ){
+                                       *( pk3Shaders + i*65 ) = '\0';
+                                       break;
+                               }
+                       }
+                       if ( *( pk3Shaders + i*65 ) == '\0' ) continue;
+                       for ( j = 0; j < ExTexturesN; j++ ){
+                               if ( !Q_stricmp( pk3Shaders + i*65, ExTextures + j*65 ) ){
+                                       *( pk3Shaders + i*65 ) = '\0';
+                                       break;
+                               }
+                       }
+                       if ( *( pk3Shaders + i*65 ) == '\0' ) continue;
+                       for ( j = 0; j < rExTexturesN; j++ ){
+                               if ( !Q_stricmp( pk3Shaders + i*65, rExTextures + j*65 ) ){
+                                       *( pk3Shaders + i*65 ) = '\0';
+                                       break;
+                               }
+                       }
+               }
+       }
+
+//snds
+       for ( i = 0; i < pk3SoundsN; i++ ){
+               for ( j = 0; j < ExSoundsN; j++ ){
+                       if ( !Q_stricmp( pk3Sounds + i*65, ExSounds + j*65 ) ){
+                               *( pk3Sounds + i*65 ) = '\0';
+                               break;
+                       }
+               }
+               if ( *( pk3Sounds + i*65 ) == '\0' ) continue;
+               for ( j = 0; j < rExSoundsN; j++ ){
+                       if ( !Q_stricmp( pk3Sounds + i*65, rExSounds + j*65 ) ){
+                               *( pk3Sounds + i*65 ) = '\0';
+                               break;
+                       }
+               }
+       }
+
+       /* write shader */
+       sprintf( temp, "%s/%s_strippedBYrepacker.shader", EnginePath, nameOFrepack );
+       FILE *f;
+       f = fopen( temp, "wb" );
+       fwrite( allShaders, sizeof( char ), strlen( allShaders ), f );
+       fclose( f );
+       Sys_Printf( "Shaders saved to %s\n", temp );
+
+       /* make a pack */
+       sprintf( packname, "%s/%s_repacked.pk3", EnginePath, nameOFrepack );
+       remove( packname );
+
+       Sys_Printf( "\n--- ZipZip ---\n" );
+
+       Sys_Printf( "\n\tShader referenced textures....\n" );
+
+       for ( i = 0; i < pk3TexturesN; i++ ){
+               if ( png ){
+                       sprintf( temp, "%s.png", pk3Textures + i*65 );
+                       if ( vfsPackFile( temp, packname, compLevel ) ){
+                               Sys_Printf( "++%s\n", temp );
+                               continue;
+                       }
+               }
+               sprintf( temp, "%s.tga", pk3Textures + i*65 );
+               if ( vfsPackFile( temp, packname, compLevel ) ){
+                       Sys_Printf( "++%s\n", temp );
+                       continue;
+               }
+               sprintf( temp, "%s.jpg", pk3Textures + i*65 );
+               if ( vfsPackFile( temp, packname, compLevel ) ){
+                       Sys_Printf( "++%s\n", temp );
+                       continue;
+               }
+               Sys_Printf( "  !FAIL! %s\n", pk3Textures + i*65 );
+       }
+
+       Sys_Printf( "\n\tPure textures....\n" );
+
+       for ( i = 0; i < pk3ShadersN; i++ ){
+               if ( *( pk3Shaders + i*65 ) != '\0' ){
+                       if ( png ){
+                               sprintf( temp, "%s.png", pk3Shaders + i*65 );
+                               if ( vfsPackFile( temp, packname, compLevel ) ){
+                                       Sys_Printf( "++%s\n", temp );
+                                       continue;
+                               }
+                       }
+                       sprintf( temp, "%s.tga", pk3Shaders + i*65 );
+                       if ( vfsPackFile( temp, packname, compLevel ) ){
+                               Sys_Printf( "++%s\n", temp );
+                               continue;
+                       }
+                       sprintf( temp, "%s.jpg", pk3Shaders + i*65 );
+                       if ( vfsPackFile( temp, packname, compLevel ) ){
+                               Sys_Printf( "++%s\n", temp );
+                               continue;
+                       }
+                       Sys_Printf( "  !FAIL! %s\n", pk3Shaders + i*65 );
+               }
+       }
+
+       Sys_Printf( "\n\tSounds....\n" );
+
+       for ( i = 0; i < pk3SoundsN; i++ ){
+               if ( *( pk3Sounds + i*65 ) != '\0' ){
+                       if ( vfsPackFile( pk3Sounds + i*65, packname, compLevel ) ){
+                               Sys_Printf( "++%s\n", pk3Sounds + i*65 );
+                               continue;
+                       }
+                       Sys_Printf( "  !FAIL! %s\n", pk3Sounds + i*65 );
+               }
+       }
+
+       Sys_Printf( "\n\tVideos....\n" );
+
+       for ( i = 0; i < pk3VideosN; i++ ){
+               if ( vfsPackFile( pk3Videos + i*65, packname, compLevel ) ){
+                       Sys_Printf( "++%s\n", pk3Videos + i*65 );
+                       continue;
+               }
+               Sys_Printf( "  !FAIL! %s\n", pk3Videos + i*65 );
+       }
+
+       Sys_Printf( "\nSaved to %s\n", packname );
+
+       /* return to sender */
+       return 0;
+}
+
+
+
 /*
    main()
    q3map mojo...
@@ -103,6 +2087,9 @@ int main( int argc, char **argv ){
        double start, end;
        extern qboolean werror;
 
+#ifdef WIN32
+       _setmaxstdio(2048);
+#endif
 
        /* we want consistent 'randomness' */
        srand( 0 );
@@ -178,6 +2165,14 @@ int main( int argc, char **argv ){
                        numthreads = atoi( argv[ i ] );
                        argv[ i ] = NULL;
                }
+
+               else if( !strcmp( argv[ i ], "-nocmdline" ) )
+               {
+                       Sys_Printf( "noCmdLine\n" );
+                       nocmdline = qtrue;
+                       argv[ i ] = NULL;
+               }
+
        }
 
        /* init model library */
@@ -205,6 +2200,9 @@ int main( int argc, char **argv ){
        Sys_Printf( "Q3Map (ydnar) - v" Q3MAP_VERSION "\n" );
        Sys_Printf( RADIANT_NAME "    - v" RADIANT_VERSION " " __DATE__ " " __TIME__ "\n" );
        Sys_Printf( "%s\n", Q3MAP_MOTD );
+       Sys_Printf( "%s\n", argv[0] );
+
+       strcpy( q3map2path, argv[0] );//fuer autoPack func
 
        /* ydnar: new path initialization */
        InitPaths( &argc, argv );
@@ -216,7 +2214,7 @@ int main( int argc, char **argv ){
 
        /* check if we have enough options left to attempt something */
        if ( argc < 2 ) {
-               Error( "Usage: %s [general options] [options] mapfile", argv[ 0 ] );
+               Error( "Usage: %s [general options] [options] mapfile\n%s -help for help", argv[ 0 ] , argv[ 0 ] );
        }
 
        /* fixaas */
@@ -271,6 +2269,21 @@ int main( int argc, char **argv ){
                r = ScaleBSPMain( argc - 1, argv + 1 );
        }
 
+       /* bsp shifting */
+       else if ( !strcmp( argv[ 1 ], "-shift" ) ) {
+               r = ShiftBSPMain( argc - 1, argv + 1 );
+       }
+
+       /* autopacking */
+       else if ( !strcmp( argv[ 1 ], "-pk3" ) ) {
+               r = pk3BSPMain( argc - 1, argv + 1 );
+       }
+
+       /* repacker */
+       else if ( !strcmp( argv[ 1 ], "-repack" ) ) {
+               r = repackBSPMain( argc - 1, argv + 1 );
+       }
+
        /* ydnar: bsp conversion */
        else if ( !strcmp( argv[ 1 ], "-convert" ) ) {
                r = ConvertBSPMain( argc - 1, argv + 1 );
@@ -282,7 +2295,7 @@ int main( int argc, char **argv ){
        }
 
        /* ydnar: otherwise create a bsp */
-       else {
+       else{
                /* used to write Smokin'Guns like tex file */
                compile_map = qtrue;
 
index ff6eb98f62d1da84cef6d526911b91df016aa878..eba484198bb43349f80b00043d9dc4e301f37921 100644 (file)
@@ -69,7 +69,7 @@ qboolean PlaneEqual( plane_t *p, vec3_t normal, vec_t dist ){
 
        /* compare */
        // We check equality of each component since we're using '<', not '<='
-       // (the epsilons may be zero).  We want to use '<' intead of '<=' to be
+       // (the epsilons may be zero).  We want to use '<' instead of '<=' to be
        // consistent with the true meaning of "epsilon", and also because other
        // parts of the code uses this inequality.
        if ( ( p->dist == dist || fabs( p->dist - dist ) < de ) &&
@@ -164,6 +164,44 @@ qboolean SnapNormal( vec3_t normal ){
        // normalized).  The original SnapNormal() didn't snap such vectors - it
        // only snapped vectors that were near a perfect axis.
 
+       //adjusting vectors, that are near perfect axis, with bigger epsilon
+       //they cause precision errors
+
+
+       if ( ( normal[0] != 0.0 || normal[1] != 0.0 ) && fabs(normal[0]) < 0.00025 && fabs(normal[1]) < 0.00025){
+               normal[0] = normal[1] = 0.0;
+               adjusted = qtrue;
+       }
+       else if ( ( normal[0] != 0.0 || normal[2] != 0.0 ) && fabs(normal[0]) < 0.00025 && fabs(normal[2]) < 0.00025){
+               normal[0] = normal[2] = 0.0;
+               adjusted = qtrue;
+       }
+       else if ( ( normal[2] != 0.0 || normal[1] != 0.0 ) && fabs(normal[2]) < 0.00025 && fabs(normal[1]) < 0.00025){
+               normal[2] = normal[1] = 0.0;
+               adjusted = qtrue;
+       }
+
+
+       /*
+       for ( i=0; i<30; i++ )
+       {
+               double x, y, z, length;
+               x=(double) 1.0;
+               y=(double) ( 0.00001 * i );
+               z=(double) 0.0;
+
+               Sys_Printf("(%6.18f %6.18f %6.18f)inNormal\n", x,y,z );
+
+               length = sqrt( ( x * x ) + ( y * y ) + ( z * z ) );
+               Sys_Printf("(%6.18f)length\n", length);
+               x = (vec_t) ( x / length );
+               y = (vec_t) ( y / length );
+               z = (vec_t) ( z / length );
+               Sys_Printf("(%6.18f %6.18f %6.18f)outNormal\n\n", x,y,z );
+       }
+       Error("vectorNormalize test completed");
+       */
+
        for ( i = 0; i < 3; i++ )
        {
                if ( normal[i] != 0.0 && -normalEpsilon < normal[i] && normal[i] < normalEpsilon ) {
@@ -237,7 +275,7 @@ qboolean SnapNormal( vec3_t normal ){
    snaps a plane to normal/distance epsilons
  */
 
-void SnapPlane( vec3_t normal, vec_t *dist, vec3_t center ){
+void SnapPlane( vec3_t normal, vec_t *dist ){
 // SnapPlane disabled by LordHavoc because it often messes up collision
 // brushes made from triangles of embedded models, and it has little effect
 // on anything else (axial planes are usually derived from snapped points)
@@ -490,7 +528,8 @@ void SetBrushContents( brush_t *b ){
        //%     mixed = qfalse;
 
        /* get the content/compile flags for every side in the brush */
-       for ( i = 1; i < b->numsides; i++, s++ )
+       //for ( i = 1; i < b->numsides; i++, s++ )
+       for ( i = 1; i < b->numsides; i++ )
        {
                s = &b->sides[ i ];
                if ( s->shaderInfo == NULL ) {
@@ -501,6 +540,33 @@ void SetBrushContents( brush_t *b ){
 
                contentFlags |= s->contentFlags;
                compileFlags |= s->compileFlags;
+
+               /* resolve inconsistency, when brush content was determined by 1st face */
+               if ( b->contentShader->compileFlags & C_LIQUID ){
+                       continue;
+               }
+               else if ( s->compileFlags & C_LIQUID ){
+                       b->contentShader = s->shaderInfo;
+               }
+               else if ( b->contentShader->compileFlags & C_FOG ){
+                       continue;
+               }
+               else if ( s->compileFlags & C_FOG ){
+                       b->contentShader = s->shaderInfo;
+               }
+               //playerclip
+               else if ( b->contentShader->contentFlags & 0x10000 ){
+                       continue;
+               }
+               else if ( s->contentFlags & 0x10000 ){
+                       b->contentShader = s->shaderInfo;
+               }
+               else if (!( b->contentShader->compileFlags & C_SOLID )){
+                       continue;
+               }
+               else if (!( s->compileFlags & C_SOLID )){
+                       b->contentShader = s->shaderInfo;
+               }
        }
 
        /* ydnar: getting rid of this stupid warning */
@@ -1768,6 +1834,9 @@ static qboolean ParseMapEntity( qboolean onlyLights, qboolean noCollapseGroups )
        else if ( strcmp( "", ValueForKey( mapEnt, "_sn" ) ) ) {
                shadeAngle = FloatForKey( mapEnt, "_sn" );
        }
+       else if ( strcmp( "", ValueForKey( mapEnt, "_sa" ) ) ) {
+               shadeAngle = FloatForKey( mapEnt, "_sa" );
+       }
        else if ( strcmp( "", ValueForKey( mapEnt, "_smooth" ) ) ) {
                shadeAngle = FloatForKey( mapEnt, "_smooth" );
        }
@@ -1788,6 +1857,9 @@ static qboolean ParseMapEntity( qboolean onlyLights, qboolean noCollapseGroups )
        else if ( strcmp( "", ValueForKey( mapEnt, "_samplesize" ) ) ) {
                lightmapSampleSize = IntForKey( mapEnt, "_samplesize" );
        }
+       else if ( strcmp( "", ValueForKey( mapEnt, "_ss" ) ) ) {
+               lightmapSampleSize = IntForKey( mapEnt, "_ss" );
+       }
 
        if ( lightmapSampleSize < 0 ) {
                lightmapSampleSize = 0;
index effaf17d36523df9b05bfa37c7562605912689e5..b9760e2e39c0b3f23869cf40be5fe495dc436c7b 100644 (file)
@@ -210,8 +210,8 @@ picoModel_t *LoadModel( const char *name, int frame ){
    adds a picomodel into the bsp
  */
 
-void InsertModel( const char *name, int skin, int frame, m4x4_t transform, remap_t *remap, shaderInfo_t *celShader, int eNum, int castShadows, int recvShadows, int spawnFlags, float lightmapScale, int lightmapSampleSize, float shadeAngle ){
-       int i, j, s, numSurfaces;
+void InsertModel( const char *name, int skin, int frame, m4x4_t transform, remap_t *remap, shaderInfo_t *celShader, int eNum, int castShadows, int recvShadows, int spawnFlags, float lightmapScale, int lightmapSampleSize, float shadeAngle, float clipDepth ){
+       int i, j, s, k, numSurfaces;
        m4x4_t identity, nTransform;
        picoModel_t         *model;
        picoShader_t        *shader;
@@ -226,12 +226,19 @@ void InsertModel( const char *name, int skin, int frame, m4x4_t transform, remap
        picoIndex_t         *indexes;
        remap_t             *rm, *glob;
        skinfile_t          *sf, *sf2;
-       double normalEpsilon_save;
-       double distanceEpsilon_save;
        char skinfilename[ MAX_QPATH ];
        char                *skinfilecontent;
        int skinfilesize;
        char                *skinfileptr, *skinfilenextptr;
+       //int ok=0, notok=0;
+       int spf = ( spawnFlags & 8088 );
+       float limDepth=0;
+
+
+       if ( clipDepth < 0 ){
+               limDepth = -clipDepth;
+               clipDepth = 2.0;
+       }
 
 
        /* get model */
@@ -514,23 +521,99 @@ void InsertModel( const char *name, int skin, int frame, m4x4_t transform, remap
                ds->celShader = celShader;
 
                /* ydnar: giant hack land: generate clipping brushes for model triangles */
-               if ( si->clipModel || ( spawnFlags & 2 ) ) { /* 2nd bit */
-                       vec3_t points[ 4 ], backs[ 3 ];
-                       vec4_t plane, reverse, pa, pb, pc;
-
+               if ( ( si->clipModel && !( spf ) ) ||   //default CLIPMODEL
+                       ( ( spawnFlags & 8090 ) == 2 ) ||       //default CLIPMODEL
+                       ( spf == 8 ) ||         //EXTRUDE_FACE_NORMALS
+                       ( spf == 16 )   ||      //EXTRUDE_TERRAIN
+                       ( spf == 128 )  ||      //EXTRUDE_VERTEX_NORMALS
+                       ( spf == 256 )  ||      //PYRAMIDAL_CLIP
+                       ( spf == 512 )  ||      //EXTRUDE_DOWNWARDS
+                       ( spf == 1024 ) ||      //EXTRUDE_UPWARDS
+                       ( spf == 4096 ) ||      //default CLIPMODEL + AXIAL_BACKPLANE
+                       ( spf == 264 )  ||      //EXTRUDE_FACE_NORMALS+PYRAMIDAL_CLIP (extrude 45)
+                       ( spf == 2064 ) ||      //EXTRUDE_TERRAIN+MAX_EXTRUDE
+                       ( spf == 4112 ) ||      //EXTRUDE_TERRAIN+AXIAL_BACKPLANE
+                       ( spf == 384 )  ||      //EXTRUDE_VERTEX_NORMALS + PYRAMIDAL_CLIP - vertex normals + don't check for sides, sticking outwards
+                       ( spf == 4352 ) ||      //PYRAMIDAL_CLIP+AXIAL_BACKPLANE
+                       ( spf == 1536 ) ||      //EXTRUDE_DOWNWARDS+EXTRUDE_UPWARDS
+                       ( spf == 2560 ) ||      //EXTRUDE_DOWNWARDS+MAX_EXTRUDE
+                       ( spf == 4608 ) ||      //EXTRUDE_DOWNWARDS+AXIAL_BACKPLANE
+                       ( spf == 3584 ) ||      //EXTRUDE_DOWNWARDS+EXTRUDE_UPWARDS+MAX_EXTRUDE
+                       ( spf == 5632 ) ||      //EXTRUDE_DOWNWARDS+EXTRUDE_UPWARDS+AXIAL_BACKPLANE
+                       ( spf == 3072 ) ||      //EXTRUDE_UPWARDS+MAX_EXTRUDE
+                       ( spf == 5120 ) ){      //EXTRUDE_UPWARDS+AXIAL_BACKPLANE
+                       vec3_t points[ 4 ], cnt, bestNormal, nrm, Vnorm[3], Enorm[3];
+                       vec4_t plane, reverse, p[3];
+                       double normalEpsilon_save;
+                       qboolean snpd;
+                       vec3_t min = { 999999, 999999, 999999 }, max = { -999999, -999999, -999999 };
+                       vec3_t avgDirection = { 0, 0, 0 };
+                       int axis;
+                       #define nonax_clip_dbg 0
 
                        /* temp hack */
                        if ( !si->clipModel && !( si->compileFlags & C_SOLID ) ) {
                                continue;
                        }
 
+                       //wont snap these in normal way, or will explode
+                       normalEpsilon_save = normalEpsilon;
+                       //normalEpsilon = 0.000001;
+
+
+                       //MAX_EXTRUDE or EXTRUDE_TERRAIN
+                       if ( ( spawnFlags & 2048 ) || ( spawnFlags & 16 ) ){
+
+                               for ( i = 0; i < ds->numIndexes; i += 3 )
+                               {
+                                       for ( j = 0; j < 3; j++ )
+                                       {
+                                               dv = &ds->verts[ ds->indexes[ i + j ] ];
+                                               VectorCopy( dv->xyz, points[ j ] );
+                                       }
+                                       if ( PlaneFromPoints( plane, points[ 0 ], points[ 1 ], points[ 2 ] ) ){
+                                               if ( spawnFlags & 16 ) VectorAdd( avgDirection, plane, avgDirection );  //calculate average mesh facing direction
+
+                                               //get min/max
+                                               for ( k = 2; k > -1; k-- ){
+                                                       if ( plane[k] > 0 ){
+                                                               for ( j = 0; j < 3; j++ ){ if ( points[j][k] < min[k] ) min[k] = points[j][k]; }
+                                                       }
+                                                       else if ( plane[k] < 0 ){
+                                                               for ( j = 0; j < 3; j++ ){ if ( points[j][k] > max[k] ) max[k] = points[j][k]; }
+                                                       }
+                                                       //if EXTRUDE_DOWNWARDS or EXTRUDE_UPWARDS
+                                                       if ( ( spawnFlags & 512 ) || ( spawnFlags & 1024 ) ){
+                                                               break;
+                                                       }
+                                               }
+                                       }
+                               }
+                               //unify avg direction
+                               if ( spawnFlags & 16 ){
+                                       for ( j = 0; j < 3; j++ ){
+                                               if ( fabs(avgDirection[j]) > fabs(avgDirection[(j+1)%3]) ){
+                                                       avgDirection[(j+1)%3] = 0.0;
+                                                       axis = j;
+                                               }
+                                               else {
+                                                       avgDirection[j] = 0.0;
+                                               }
+                                       }
+                                       if ( VectorNormalize( avgDirection, avgDirection ) == 0 ){
+                                               axis = 2;
+                                               VectorSet( avgDirection, 0, 0, 1 );
+                                       }
+                               }
+                       }
+
                        /* walk triangle list */
                        for ( i = 0; i < ds->numIndexes; i += 3 )
                        {
                                /* overflow hack */
                                AUTOEXPAND_BY_REALLOC( mapplanes, ( nummapplanes + 64 ) << 1, allocatedmapplanes, 1024 );
 
-                               /* make points and back points */
+                               /* make points */
                                for ( j = 0; j < 3; j++ )
                                {
                                        /* get vertex */
@@ -540,125 +623,688 @@ void InsertModel( const char *name, int skin, int frame, m4x4_t transform, remap
                                        VectorCopy( dv->xyz, points[ j ] );
                                }
 
-                               VectorCopy( points[0], points[3] ); // for cyclic usage
-
                                /* make plane for triangle */
-                               // div0: add some extra spawnflags:
-                               //   0: snap normals to axial planes for extrusion
-                               //   8: extrude with the original normals
-                               //  16: extrude only with up/down normals (ideal for terrain)
-                               //  24: extrude by distance zero (may need engine changes)
                                if ( PlaneFromPoints( plane, points[ 0 ], points[ 1 ], points[ 2 ] ) ) {
-                                       vec3_t bestNormal;
-                                       float backPlaneDistance = 2;
 
-                                       if ( spawnFlags & 8 ) { // use a DOWN normal
-                                               if ( spawnFlags & 16 ) {
-                                                       // 24: normal as is, and zero width (broken)
-                                                       VectorCopy( plane, bestNormal );
+                                       /* build a brush */
+                                       buildBrush = AllocBrush( 48 );
+                                       buildBrush->entityNum = mapEntityNum;
+                                       buildBrush->original = buildBrush;
+                                       buildBrush->contentShader = si;
+                                       buildBrush->compileFlags = si->compileFlags;
+                                       buildBrush->contentFlags = si->contentFlags;
+                                       buildBrush->detail = qtrue;
+
+                                       //snap points before using them for further calculations
+                                       //precision suffers a lot, when two of normal values are under .00025 (often no collision, knocking up effect in ioq3)
+                                       //also broken drawsurfs in case of normal brushes
+                                       snpd = qfalse;
+                                       for ( j=0; j<3; j++ )
+                                       {
+                                               if ( fabs(plane[j]) < 0.00025 && fabs(plane[(j+1)%3]) < 0.00025 && ( plane[j] != 0.0 || plane[(j+1)%3] != 0.0 ) ){
+                                                       VectorAdd( points[ 0 ], points[ 1 ], cnt );
+                                                       VectorAdd( cnt, points[ 2 ], cnt );
+                                                       VectorScale( cnt, 0.3333333333333f, cnt );
+                                                       points[0][(j+2)%3]=points[1][(j+2)%3]=points[2][(j+2)%3]=cnt[(j+2)%3];
+                                                       snpd = qtrue;
+                                                       break;
                                                }
-                                               else
+                                       }
+
+                                       //snap pairs of points to prevent bad side planes
+                                       for ( j=0; j<3; j++ )
+                                       {
+                                               VectorSubtract( points[j], points[(j+1)%3], nrm );
+                                               VectorNormalize( nrm, nrm );
+                                               for ( k=0; k<3; k++ )
                                                {
-                                                       // 8: normal as is
-                                                       VectorCopy( plane, bestNormal );
+                                                       if ( nrm[k] != 0.0 && fabs(nrm[k]) < 0.00025 ){
+                                                               //Sys_Printf( "b4(%6.6f %6.6f %6.6f)(%6.6f %6.6f %6.6f)\n", points[j][0], points[j][1], points[j][2], points[(j+1)%3][0], points[(j+1)%3][1], points[(j+1)%3][2] );
+                                                               points[j][k]=points[(j+1)%3][k] = ( points[j][k] + points[(j+1)%3][k] ) / 2.0;
+                                                               //Sys_Printf( "sn(%6.6f %6.6f %6.6f)(%6.6f %6.6f %6.6f)\n", points[j][0], points[j][1], points[j][2], points[(j+1)%3][0], points[(j+1)%3][1], points[(j+1)%3][2] );
+                                                               snpd = qtrue;
+                                                       }
                                                }
                                        }
-                                       else
+
+                                       if ( snpd ) {
+                                               PlaneFromPoints( plane, points[ 0 ], points[ 1 ], points[ 2 ] );
+                                               snpd = qfalse;
+                                       }
+
+                                       //vector-is-close-to-be-on-axis check again, happens after previous code sometimes
+                                       for ( j=0; j<3; j++ )
                                        {
-                                               if ( spawnFlags & 16 ) {
-                                                       // 16: UP/DOWN normal
-                                                       VectorSet( bestNormal, 0, 0, ( plane[2] >= 0 ? 1 : -1 ) );
+                                               if ( fabs(plane[j]) < 0.00025 && fabs(plane[(j+1)%3]) < 0.00025 && ( plane[j] != 0.0 || plane[(j+1)%3] != 0.0 ) ){
+                                                       VectorAdd( points[ 0 ], points[ 1 ], cnt );
+                                                       VectorAdd( cnt, points[ 2 ], cnt );
+                                                       VectorScale( cnt, 0.3333333333333f, cnt );
+                                                       points[0][(j+2)%3]=points[1][(j+2)%3]=points[2][(j+2)%3]=cnt[(j+2)%3];
+                                                       PlaneFromPoints( plane, points[ 0 ], points[ 1 ], points[ 2 ] );
+                                                       break;
                                                }
-                                               else
+                                       }
+
+                                       //snap single snappable normal components
+                                       for ( j=0; j<3; j++ )
+                                       {
+                                               if ( plane[j] != 0.0 && fabs(plane[j]) < 0.00005 ){
+                                                       plane[j]=0.0;
+                                                       snpd = qtrue;
+                                               }
+                                       }
+
+                                       //adjust plane dist
+                                       if ( snpd ) {
+                                               VectorAdd( points[ 0 ], points[ 1 ], cnt );
+                                               VectorAdd( cnt, points[ 2 ], cnt );
+                                               VectorScale( cnt, 0.3333333333333f, cnt );
+                                               VectorNormalize( plane, plane );
+                                               plane[3] = DotProduct( plane, cnt );
+
+                                               //project points to resulting plane to keep intersections precision
+                                               for ( j=0; j<3; j++ )
+                                               {
+                                                       //Sys_Printf( "b4 %i (%6.7f %6.7f %6.7f)\n", j, points[j][0], points[j][1], points[j][2] );
+                                                       VectorMA( points[j], plane[3] - DotProduct( plane, points[j]), plane, points[j] );
+                                                       //Sys_Printf( "sn %i (%6.7f %6.7f %6.7f)\n", j, points[j][0], points[j][1], points[j][2] );
+                                               }
+                                               //Sys_Printf( "sn pln (%6.7f %6.7f %6.7f %6.7f)\n", plane[0], plane[1], plane[2], plane[3] );
+                                               //PlaneFromPoints( plane, points[ 0 ], points[ 1 ], points[ 2 ] );
+                                               //Sys_Printf( "pts pln (%6.7f %6.7f %6.7f %6.7f)\n", plane[0], plane[1], plane[2], plane[3] );
+                                       }
+
+
+                                       if ( spf == 4352 ){     //PYRAMIDAL_CLIP+AXIAL_BACKPLANE
+
+                                               for ( j=0; j<3; j++ )
                                                {
-                                                       // 0: axial normal
-                                                       if ( fabs( plane[0] ) > fabs( plane[1] ) ) { // x>y
-                                                               if ( fabs( plane[1] ) > fabs( plane[2] ) ) { // x>y, y>z
-                                                                       VectorSet( bestNormal, ( plane[0] >= 0 ? 1 : -1 ), 0, 0 );
+                                                       if ( fabs(plane[j]) < 0.05 && fabs(plane[(j+1)%3]) < 0.05 ){ //no way, close to lay on two axises
+                                                               goto default_CLIPMODEL;
+                                                       }
+                                               }
+
+                                               // best axial normal
+                                               VectorCopy( plane, bestNormal );
+                                               for ( j = 0; j < 3; j++ ){
+                                                       if ( fabs(bestNormal[j]) > fabs(bestNormal[(j+1)%3]) ){
+                                                               bestNormal[(j+1)%3] = 0.0;
+                                                               axis = j;
+                                                       }
+                                                       else {
+                                                               bestNormal[j] = 0.0;
+                                                       }
+                                               }
+                                               VectorNormalize( bestNormal, bestNormal );
+
+
+                                               float bestdist, currdist, bestangle, currangle, mindist = 999999;
+
+                                               for ( j = 0; j < 3; j++ ){//planes
+                                                       bestdist = 999999;
+                                                       bestangle = 1;
+                                                       for ( k = 0; k < 3; k++ ){//axises
+                                                               VectorSubtract( points[ (j+1)%3 ], points[ j ], nrm );
+                                                               if ( k == axis ){
+                                                                       CrossProduct( bestNormal, nrm, reverse );
                                                                }
-                                                               else // x>y, z>=y
-                                                               if ( fabs( plane[0] ) > fabs( plane[2] ) ) { // x>z, z>=y
-                                                                       VectorSet( bestNormal, ( plane[0] >= 0 ? 1 : -1 ), 0, 0 );
+                                                               else{
+                                                                       VectorClear( Vnorm[0] );
+                                                                       if ( (k+1)%3 == axis ){
+                                                                               if ( nrm[ (k+2)%3 ] == 0 ) continue;
+                                                                               Vnorm[0][ (k+2)%3 ] = nrm[ (k+2)%3 ];
+                                                                       }
+                                                                       else{
+                                                                               if ( nrm[ (k+1)%3 ] == 0 ) continue;
+                                                                               Vnorm[0][ (k+1)%3 ] = nrm[ (k+1)%3 ];
+                                                                       }
+                                                                       CrossProduct( bestNormal, Vnorm[0], Enorm[0] );
+                                                                       CrossProduct( Enorm[0], nrm, reverse );
                                                                }
-                                                               else{    // z>=x, x>y
-                                                                       VectorSet( bestNormal, 0, 0, ( plane[2] >= 0 ? 1 : -1 ) );
+                                                               VectorNormalize( reverse, reverse );
+                                                               reverse[3] = DotProduct( points[ j ], reverse );
+                                                               //check facing, thickness
+                                                               currdist = reverse[3] - DotProduct( reverse, points[ (j+2)%3 ] );
+                                                               currangle = DotProduct( reverse, plane );
+                                                               if ( ( ( currdist > 0.1 ) && ( currdist < bestdist ) && ( currangle < 0 ) ) ||
+                                                                       ( ( currangle >= 0 ) && ( currangle <= bestangle ) ) ){
+                                                                       bestangle = currangle;
+                                                                       if ( currangle < 0 ) bestdist = currdist;
+                                                                       VectorCopy( reverse, p[j] );
+                                                                       p[j][3] = reverse[3];
                                                                }
                                                        }
-                                                       else // y>=x
-                                                       if ( fabs( plane[1] ) > fabs( plane[2] ) ) { // y>z, y>=x
-                                                               VectorSet( bestNormal, 0, ( plane[1] >= 0 ? 1 : -1 ), 0 );
+                                                       //if ( bestdist == 999999 && bestangle == 1 ) Sys_Printf("default_CLIPMODEL\n");
+                                                       if ( bestdist == 999999 && bestangle == 1 ) goto default_CLIPMODEL;
+                                                       if ( bestdist < mindist ) mindist = bestdist;
+                                               }
+                                               if ( (limDepth != 0.0) && (mindist > limDepth) ) goto default_CLIPMODEL;
+
+
+#if nonax_clip_dbg
+                                               for ( j = 0; j < 3; j++ )
+                                               {
+                                                       for ( k = 0; k < 3; k++ )
+                                                       {
+                                                               if ( fabs(p[j][k]) < 0.00025 && p[j][k] != 0.0 ){
+                                                                       Sys_Printf( "nonax nrm %6.17f %6.17f %6.17f\n", p[j][0], p[j][1], p[j][2] );
+                                                               }
                                                        }
-                                                       else{    // z>=y, y>=x
+                                               }
+#endif
+                                               /* set up brush sides */
+                                               buildBrush->numsides = 4;
+                                               buildBrush->sides[ 0 ].shaderInfo = si;
+                                               for ( j = 1; j < buildBrush->numsides; j++ ) {
+                                                       if ( debugClip ) {
+                                                               buildBrush->sides[ 0 ].shaderInfo = ShaderInfoForShader( "debugclip2" );
+                                                               buildBrush->sides[ j ].shaderInfo = ShaderInfoForShader( "debugclip" );
+                                                       }
+                                                       else {
+                                                               buildBrush->sides[ j ].shaderInfo = NULL;  // don't emit these faces as draw surfaces, should make smaller BSPs; hope this works
+                                                       }
+                                               }
+                                               VectorCopy( points[0], points[3] ); // for cyclic usage
+
+                                               buildBrush->sides[ 0 ].planenum = FindFloatPlane( plane, plane[ 3 ], 3, points );
+                                               buildBrush->sides[ 1 ].planenum = FindFloatPlane( p[0], p[0][ 3 ], 2, &points[ 0 ] ); // p[0] contains points[0] and points[1]
+                                               buildBrush->sides[ 2 ].planenum = FindFloatPlane( p[1], p[1][ 3 ], 2, &points[ 1 ] ); // p[1] contains points[1] and points[2]
+                                               buildBrush->sides[ 3 ].planenum = FindFloatPlane( p[2], p[2][ 3 ], 2, &points[ 2 ] ); // p[2] contains points[2] and points[0] (copied to points[3]
+                                       }
+
+
+                                       else if ( ( spf == 16 ) ||      //EXTRUDE_TERRAIN
+                                               ( spf == 512 ) ||       //EXTRUDE_DOWNWARDS
+                                               ( spf == 1024 ) ||      //EXTRUDE_UPWARDS
+                                               ( spf == 4096 ) ||      //default CLIPMODEL + AXIAL_BACKPLANE
+                                               ( spf == 2064 ) ||      //EXTRUDE_TERRAIN+MAX_EXTRUDE
+                                               ( spf == 4112 ) ||      //EXTRUDE_TERRAIN+AXIAL_BACKPLANE
+                                               ( spf == 1536 ) ||      //EXTRUDE_DOWNWARDS+EXTRUDE_UPWARDS
+                                               ( spf == 2560 ) ||      //EXTRUDE_DOWNWARDS+MAX_EXTRUDE
+                                               ( spf == 4608 ) ||      //EXTRUDE_DOWNWARDS+AXIAL_BACKPLANE
+                                               ( spf == 3584 ) ||      //EXTRUDE_DOWNWARDS+EXTRUDE_UPWARDS+MAX_EXTRUDE
+                                               ( spf == 5632 ) ||      //EXTRUDE_DOWNWARDS+EXTRUDE_UPWARDS+AXIAL_BACKPLANE
+                                               ( spf == 3072 ) ||      //EXTRUDE_UPWARDS+MAX_EXTRUDE
+                                               ( spf == 5120 ) ){      //EXTRUDE_UPWARDS+AXIAL_BACKPLANE
+
+                                               if ( spawnFlags & 16 ){ //autodirection
+                                                       VectorCopy( avgDirection, bestNormal );
+                                               }
+                                               else{
+                                                       axis = 2;
+                                                       if ( ( spawnFlags & 1536 ) == 1536 ){ //up+down
                                                                VectorSet( bestNormal, 0, 0, ( plane[2] >= 0 ? 1 : -1 ) );
                                                        }
+                                                       else if ( spawnFlags & 512 ){ //down
+                                                               VectorSet( bestNormal, 0, 0, 1 );
+
+                                                       }
+                                                       else if ( spawnFlags & 1024 ){ //up
+                                                               VectorSet( bestNormal, 0, 0, -1 );
+                                                       }
+                                                       else{ // best axial normal
+                                                               VectorCopy( plane, bestNormal );
+                                                               for ( j = 0; j < 3; j++ ){
+                                                                       if ( fabs(bestNormal[j]) > fabs(bestNormal[(j+1)%3]) ){
+                                                                               bestNormal[(j+1)%3] = 0.0;
+                                                                               axis = j;
+                                                                       }
+                                                                       else {
+                                                                               bestNormal[j] = 0.0;
+                                                                       }
+                                                               }
+                                                               VectorNormalize( bestNormal, bestNormal );
+                                                       }
+                                               }
+
+                                               if ( DotProduct( plane, bestNormal ) < 0.05 ){
+                                                       goto default_CLIPMODEL;
+                                               }
+
+
+                                               /* make side planes */
+                                               for ( j = 0; j < 3; j++ )
+                                               {
+                                                       VectorSubtract( points[(j+1)%3], points[ j ], nrm );
+                                                       CrossProduct( bestNormal, nrm, p[ j ] );
+                                                       VectorNormalize( p[ j ], p[ j ] );
+                                                       p[j][3] = DotProduct( points[j], p[j] );
+                                               }
+
+                                               /* make back plane */
+                                               if ( spawnFlags & 2048 ){ //max extrude
+                                                       VectorScale( bestNormal, -1.0f, reverse );
+                                                       if ( bestNormal[axis] > 0 ){
+                                                               reverse[3] = -min[axis] + clipDepth;
+                                                       }
+                                                       else{
+                                                               reverse[3] = max[axis] + clipDepth;
+                                                       }
+                                               }
+                                               else if ( spawnFlags & 4096 ){ //axial backplane
+                                                       VectorScale( bestNormal, -1.0f, reverse );
+                                                       reverse[3] = points[0][axis];
+                                                       if ( bestNormal[axis] > 0 ){
+                                                               for ( j = 1; j < 3; j++ ){
+                                                                       if ( points[j][axis] < reverse[3] ){
+                                                                               reverse[3] = points[j][axis];
+                                                                       }
+                                                               }
+                                                               reverse[3] = -reverse[3] + clipDepth;
+                                                       }
+                                                       else{
+                                                               for ( j = 1; j < 3; j++ ){
+                                                                       if ( points[j][axis] > reverse[3] ){
+                                                                               reverse[3] = points[j][axis];
+                                                                       }
+                                                               }
+                                                               reverse[3] += clipDepth;
+                                                       }
+                                                       if (limDepth != 0.0){
+                                                               VectorCopy( points[0], cnt );
+                                                               if ( bestNormal[axis] > 0 ){
+                                                                       for ( j = 1; j < 3; j++ ){
+                                                                               if ( points[j][axis] > cnt[axis] ){
+                                                                                       VectorCopy( points[j], cnt );
+                                                                               }
+                                                                       }
+                                                               }
+                                                               else {
+                                                                       for ( j = 1; j < 3; j++ ){
+                                                                               if ( points[j][axis] < cnt[axis] ){
+                                                                                       VectorCopy( points[j], cnt );
+                                                                               }
+                                                                       }
+                                                               }
+                                                               VectorMA( cnt, reverse[3] - DotProduct( reverse, cnt ), reverse, cnt );
+                                                               if ( ( plane[3] - DotProduct( plane, cnt ) ) > limDepth ){
+                                                                       VectorScale( plane, -1.0f, reverse );
+                                                                       reverse[ 3 ] = -plane[ 3 ];
+                                                                       reverse[3] += clipDepth;
+                                                               }
+                                                       }
                                                }
+                                               else{   //normal backplane
+                                                       VectorScale( plane, -1.0f, reverse );
+                                                       reverse[ 3 ] = -plane[ 3 ];
+                                                       reverse[3] += clipDepth;
+                                               }
+#if nonax_clip_dbg
+                                               for ( j = 0; j < 3; j++ )
+                                               {
+                                                       for ( k = 0; k < 3; k++ )
+                                                       {
+                                                               if ( fabs(p[j][k]) < 0.00025 && p[j][k] != 0.0 ){
+                                                                       Sys_Printf( "nonax nrm %6.17f %6.17f %6.17f\n", p[j][0], p[j][1], p[j][2] );
+                                                               }
+                                                       }
+                                               }
+#endif
+                                               /* set up brush sides */
+                                               buildBrush->numsides = 5;
+                                               buildBrush->sides[ 0 ].shaderInfo = si;
+                                               for ( j = 1; j < buildBrush->numsides; j++ ) {
+                                                       if ( debugClip ) {
+                                                               buildBrush->sides[ 0 ].shaderInfo = ShaderInfoForShader( "debugclip2" );
+                                                               buildBrush->sides[ j ].shaderInfo = ShaderInfoForShader( "debugclip" );
+                                                       }
+                                                       else {
+                                                               buildBrush->sides[ j ].shaderInfo = NULL;  // don't emit these faces as draw surfaces, should make smaller BSPs; hope this works
+                                                       }
+                                               }
+                                               VectorCopy( points[0], points[3] ); // for cyclic usage
+
+                                               buildBrush->sides[ 0 ].planenum = FindFloatPlane( plane, plane[ 3 ], 3, points );
+                                               buildBrush->sides[ 1 ].planenum = FindFloatPlane( p[0], p[0][ 3 ], 2, &points[ 0 ] ); // p[0] contains points[0] and points[1]
+                                               buildBrush->sides[ 2 ].planenum = FindFloatPlane( p[1], p[1][ 3 ], 2, &points[ 1 ] ); // p[1] contains points[1] and points[2]
+                                               buildBrush->sides[ 3 ].planenum = FindFloatPlane( p[2], p[2][ 3 ], 2, &points[ 2 ] ); // p[2] contains points[2] and points[0] (copied to points[3]
+                                               buildBrush->sides[ 4 ].planenum = FindFloatPlane( reverse, reverse[ 3 ], 0, NULL );
                                        }
 
-                                       /* build a brush */
-                                       buildBrush = AllocBrush( 48 );
-                                       buildBrush->entityNum = mapEntityNum;
-                                       buildBrush->original = buildBrush;
-                                       buildBrush->contentShader = si;
-                                       buildBrush->compileFlags = si->compileFlags;
-                                       buildBrush->contentFlags = si->contentFlags;
-                                       normalEpsilon_save = normalEpsilon;
-                                       distanceEpsilon_save = distanceEpsilon;
-                                       if ( si->compileFlags & C_STRUCTURAL ) { // allow forced structural brushes here
-                                               buildBrush->detail = qfalse;
 
-                                               // only allow EXACT matches when snapping for these (this is mostly for caulk brushes inside a model)
-                                               if ( normalEpsilon > 0 ) {
-                                                       normalEpsilon = 0;
+                                       else if ( spf == 264 ){ //EXTRUDE_FACE_NORMALS+PYRAMIDAL_CLIP (extrude 45)
+
+                                               //45 degrees normals for side planes
+                                               for ( j = 0; j < 3; j++ )
+                                               {
+                                                       VectorSubtract( points[(j+1)%3], points[ j ], nrm );
+                                                       CrossProduct( plane, nrm, Enorm[ j ] );
+                                                       VectorNormalize( Enorm[ j ], Enorm[ j ] );
+                                                       VectorAdd( plane, Enorm[ j ], Enorm[ j ] );
+                                                       VectorNormalize( Enorm[ j ], Enorm[ j ] );
+                                                       /* make side planes */
+                                                       CrossProduct( Enorm[ j ], nrm, p[ j ] );
+                                                       VectorNormalize( p[ j ], p[ j ] );
+                                                       p[j][3] = DotProduct( points[j], p[j] );
+                                                       //snap nearly axial side planes
+                                                       snpd = qfalse;
+                                                       for ( k = 0; k < 3; k++ )
+                                                       {
+                                                               if ( fabs(p[j][k]) < 0.00025 && p[j][k] != 0.0 ){
+                                                                       p[j][k] = 0.0;
+                                                                       snpd = qtrue;
+                                                               }
+                                                       }
+                                                       if ( snpd ){
+                                                               VectorNormalize( p[j], p[j] );
+                                                               VectorAdd( points[j], points[(j+1)%3], cnt );
+                                                               VectorScale( cnt, 0.5f, cnt );
+                                                               p[j][3] = DotProduct( cnt, p[j] );
+                                                       }
                                                }
-                                               if ( distanceEpsilon > 0 ) {
-                                                       distanceEpsilon = 0;
+
+                                               /* make back plane */
+                                               VectorScale( plane, -1.0f, reverse );
+                                               reverse[ 3 ] = -plane[ 3 ];
+                                               reverse[3] += clipDepth;
+
+                                               /* set up brush sides */
+                                               buildBrush->numsides = 5;
+                                               buildBrush->sides[ 0 ].shaderInfo = si;
+                                               for ( j = 1; j < buildBrush->numsides; j++ ) {
+                                                       if ( debugClip ) {
+                                                               buildBrush->sides[ 0 ].shaderInfo = ShaderInfoForShader( "debugclip2" );
+                                                               buildBrush->sides[ j ].shaderInfo = ShaderInfoForShader( "debugclip" );
+                                                       }
+                                                       else {
+                                                               buildBrush->sides[ j ].shaderInfo = NULL;  // don't emit these faces as draw surfaces, should make smaller BSPs; hope this works
+                                                       }
                                                }
+                                               VectorCopy( points[0], points[3] ); // for cyclic usage
+
+                                               buildBrush->sides[ 0 ].planenum = FindFloatPlane( plane, plane[ 3 ], 3, points );
+                                               buildBrush->sides[ 1 ].planenum = FindFloatPlane( p[0], p[0][ 3 ], 2, &points[ 0 ] ); // p[0] contains points[0] and points[1]
+                                               buildBrush->sides[ 2 ].planenum = FindFloatPlane( p[1], p[1][ 3 ], 2, &points[ 1 ] ); // p[1] contains points[1] and points[2]
+                                               buildBrush->sides[ 3 ].planenum = FindFloatPlane( p[2], p[2][ 3 ], 2, &points[ 2 ] ); // p[2] contains points[2] and points[0] (copied to points[3]
+                                               buildBrush->sides[ 4 ].planenum = FindFloatPlane( reverse, reverse[ 3 ], 0, NULL );
                                        }
-                                       else{
-                                               buildBrush->detail = qtrue;
+
+
+                                       else if ( ( spf == 128 ) ||     //EXTRUDE_VERTEX_NORMALS
+                                               ( spf == 384 ) ){               //EXTRUDE_VERTEX_NORMALS + PYRAMIDAL_CLIP - vertex normals + don't check for sides, sticking outwards
+                                               /* get vertex normals */
+                                               for ( j = 0; j < 3; j++ )
+                                               {
+                                                       /* get vertex */
+                                                       dv = &ds->verts[ ds->indexes[ i + j ] ];
+                                                       /* copy normal */
+                                                       VectorCopy( dv->normal, Vnorm[ j ] );
+                                               }
+
+                                               //avg normals for side planes
+                                               for ( j = 0; j < 3; j++ )
+                                               {
+                                                       VectorAdd( Vnorm[ j ], Vnorm[ (j+1)%3 ], Enorm[ j ] );
+                                                       VectorNormalize( Enorm[ j ], Enorm[ j ] );
+                                                       //check fuer bad ones
+                                                       VectorSubtract( points[(j+1)%3], points[ j ], cnt );
+                                                       CrossProduct( plane, cnt, nrm );
+                                                       VectorNormalize( nrm, nrm );
+                                                       //check for negative or outside direction
+                                                       if ( DotProduct( Enorm[ j ], plane ) > 0.1 ){
+                                                               if ( ( DotProduct( Enorm[ j ], nrm ) > -0.2 ) || ( spawnFlags & 256 ) ){
+                                                                       //ok++;
+                                                                       continue;
+                                                               }
+                                                       }
+                                                       //notok++;
+                                                       //Sys_Printf( "faulty Enormal %i/%i\n", notok, ok );
+                                                       //use 45 normal
+                                                       VectorAdd( plane, nrm, Enorm[ j ] );
+                                                       VectorNormalize( Enorm[ j ], Enorm[ j ] );
+                                               }
+
+                                               /* make side planes */
+                                               for ( j = 0; j < 3; j++ )
+                                               {
+                                                       VectorSubtract( points[(j+1)%3], points[ j ], nrm );
+                                                       CrossProduct( Enorm[ j ], nrm, p[ j ] );
+                                                       VectorNormalize( p[ j ], p[ j ] );
+                                                       p[j][3] = DotProduct( points[j], p[j] );
+                                                       //snap nearly axial side planes
+                                                       snpd = qfalse;
+                                                       for ( k = 0; k < 3; k++ )
+                                                       {
+                                                               if ( fabs(p[j][k]) < 0.00025 && p[j][k] != 0.0 ){
+                                                                       //Sys_Printf( "init plane %6.8f %6.8f %6.8f %6.8f\n", p[j][0], p[j][1], p[j][2], p[j][3]);
+                                                                       p[j][k] = 0.0;
+                                                                       snpd = qtrue;
+                                                               }
+                                                       }
+                                                       if ( snpd ){
+                                                               VectorNormalize( p[j], p[j] );
+                                                               //Sys_Printf( "nrm plane %6.8f %6.8f %6.8f %6.8f\n", p[j][0], p[j][1], p[j][2], p[j][3]);
+                                                               VectorAdd( points[j], points[(j+1)%3], cnt );
+                                                               VectorScale( cnt, 0.5f, cnt );
+                                                               p[j][3] = DotProduct( cnt, p[j] );
+                                                               //Sys_Printf( "dst plane %6.8f %6.8f %6.8f %6.8f\n", p[j][0], p[j][1], p[j][2], p[j][3]);
+                                                       }
+                                               }
+
+                                               /* make back plane */
+                                               VectorScale( plane, -1.0f, reverse );
+                                               reverse[ 3 ] = -plane[ 3 ];
+                                               reverse[3] += clipDepth;
+
+                                               /* set up brush sides */
+                                               buildBrush->numsides = 5;
+                                               buildBrush->sides[ 0 ].shaderInfo = si;
+                                               for ( j = 1; j < buildBrush->numsides; j++ ) {
+                                                       if ( debugClip ) {
+                                                               buildBrush->sides[ 0 ].shaderInfo = ShaderInfoForShader( "debugclip2" );
+                                                               buildBrush->sides[ j ].shaderInfo = ShaderInfoForShader( "debugclip" );
+                                                       }
+                                                       else {
+                                                               buildBrush->sides[ j ].shaderInfo = NULL;  // don't emit these faces as draw surfaces, should make smaller BSPs; hope this works
+                                                       }
+                                               }
+                                               VectorCopy( points[0], points[3] ); // for cyclic usage
+
+                                               buildBrush->sides[ 0 ].planenum = FindFloatPlane( plane, plane[ 3 ], 3, points );
+                                               buildBrush->sides[ 1 ].planenum = FindFloatPlane( p[0], p[0][ 3 ], 2, &points[ 0 ] ); // p[0] contains points[0] and points[1]
+                                               buildBrush->sides[ 2 ].planenum = FindFloatPlane( p[1], p[1][ 3 ], 2, &points[ 1 ] ); // p[1] contains points[1] and points[2]
+                                               buildBrush->sides[ 3 ].planenum = FindFloatPlane( p[2], p[2][ 3 ], 2, &points[ 2 ] ); // p[2] contains points[2] and points[0] (copied to points[3]
+                                               buildBrush->sides[ 4 ].planenum = FindFloatPlane( reverse, reverse[ 3 ], 0, NULL );
                                        }
 
-                                       /* regenerate back points */
-                                       for ( j = 0; j < 3; j++ )
-                                       {
-                                               /* get vertex */
-                                               dv = &ds->verts[ ds->indexes[ i + j ] ];
 
-                                               // shift by some units
-                                               VectorMA( dv->xyz, -64.0f, bestNormal, backs[j] ); // 64 prevents roundoff errors a bit
+                                       else if ( spf == 8 ){   //EXTRUDE_FACE_NORMALS
+
+                                               /* make side planes */
+                                               for ( j = 0; j < 3; j++ )
+                                               {
+                                                       VectorSubtract( points[(j+1)%3], points[ j ], nrm );
+                                                       CrossProduct( plane, nrm, p[ j ] );
+                                                       VectorNormalize( p[ j ], p[ j ] );
+                                                       p[j][3] = DotProduct( points[j], p[j] );
+                                                       //snap nearly axial side planes
+                                                       snpd = qfalse;
+                                                       for ( k = 0; k < 3; k++ )
+                                                       {
+                                                               if ( fabs(p[j][k]) < 0.00025 && p[j][k] != 0.0 ){
+                                                                       //Sys_Printf( "init plane %6.8f %6.8f %6.8f %6.8f\n", p[j][0], p[j][1], p[j][2], p[j][3]);
+                                                                       p[j][k] = 0.0;
+                                                                       snpd = qtrue;
+                                                               }
+                                                       }
+                                                       if ( snpd ){
+                                                               VectorNormalize( p[j], p[j] );
+                                                               //Sys_Printf( "nrm plane %6.8f %6.8f %6.8f %6.8f\n", p[j][0], p[j][1], p[j][2], p[j][3]);
+                                                               VectorAdd( points[j], points[(j+1)%3], cnt );
+                                                               VectorScale( cnt, 0.5f, cnt );
+                                                               p[j][3] = DotProduct( cnt, p[j] );
+                                                               //Sys_Printf( "dst plane %6.8f %6.8f %6.8f %6.8f\n", p[j][0], p[j][1], p[j][2], p[j][3]);
+                                                       }
+                                               }
+
+                                               /* make back plane */
+                                               VectorScale( plane, -1.0f, reverse );
+                                               reverse[ 3 ] = -plane[ 3 ];
+                                               reverse[3] += clipDepth;
+#if nonax_clip_dbg
+                                               for ( j = 0; j < 3; j++ )
+                                               {
+                                                       for ( k = 0; k < 3; k++ )
+                                                       {
+                                                               if ( fabs(p[j][k]) < 0.00005 && p[j][k] != 0.0 ){
+                                                                       Sys_Printf( "nonax nrm %6.17f %6.17f %6.17f\n", p[j][0], p[j][1], p[j][2] );
+                                                                       Sys_Printf( "frm src nrm %6.17f %6.17f %6.17f\n", plane[0], plane[1], plane[2]);
+                                                               }
+                                                       }
+                                               }
+#endif
+                                               /* set up brush sides */
+                                               buildBrush->numsides = 5;
+                                               buildBrush->sides[ 0 ].shaderInfo = si;
+                                               for ( j = 1; j < buildBrush->numsides; j++ ) {
+                                                       if ( debugClip ) {
+                                                               buildBrush->sides[ 0 ].shaderInfo = ShaderInfoForShader( "debugclip2" );
+                                                               buildBrush->sides[ j ].shaderInfo = ShaderInfoForShader( "debugclip" );
+                                                       }
+                                                       else {
+                                                               buildBrush->sides[ j ].shaderInfo = NULL;  // don't emit these faces as draw surfaces, should make smaller BSPs; hope this works
+                                                       }
+                                               }
+                                               VectorCopy( points[0], points[3] ); // for cyclic usage
+
+                                               buildBrush->sides[ 0 ].planenum = FindFloatPlane( plane, plane[ 3 ], 3, points );
+                                               buildBrush->sides[ 1 ].planenum = FindFloatPlane( p[0], p[0][ 3 ], 2, &points[ 0 ] ); // p[0] contains points[0] and points[1]
+                                               buildBrush->sides[ 2 ].planenum = FindFloatPlane( p[1], p[1][ 3 ], 2, &points[ 1 ] ); // p[1] contains points[1] and points[2]
+                                               buildBrush->sides[ 3 ].planenum = FindFloatPlane( p[2], p[2][ 3 ], 2, &points[ 2 ] ); // p[2] contains points[2] and points[0] (copied to points[3]
+                                               buildBrush->sides[ 4 ].planenum = FindFloatPlane( reverse, reverse[ 3 ], 0, NULL );
                                        }
 
-                                       /* make back plane */
-                                       VectorScale( plane, -1.0f, reverse );
-                                       reverse[ 3 ] = -plane[ 3 ];
-                                       if ( ( spawnFlags & 24 ) != 24 ) {
-                                               reverse[3] += DotProduct( bestNormal, plane ) * backPlaneDistance;
+
+                                       else if ( spf == 256 ){ //PYRAMIDAL_CLIP
+
+                                               /* calculate center */
+                                               VectorAdd( points[ 0 ], points[ 1 ], cnt );
+                                               VectorAdd( cnt, points[ 2 ], cnt );
+                                               VectorScale( cnt, 0.3333333333333f, cnt );
+
+                                               /* make back pyramid point */
+                                               VectorMA( cnt, -clipDepth, plane, cnt );
+
+                                               /* make 3 more planes */
+                                               if( PlaneFromPoints( p[0], points[ 2 ], points[ 1 ], cnt ) &&
+                                                       PlaneFromPoints( p[1], points[ 1 ], points[ 0 ], cnt ) &&
+                                                       PlaneFromPoints( p[2], points[ 0 ], points[ 2 ], cnt ) ) {
+
+                                                       //check for dangerous planes
+                                                       while( (( p[0][0] != 0.0 || p[0][1] != 0.0 ) && fabs(p[0][0]) < 0.00025 && fabs(p[0][1]) < 0.00025) ||
+                                                               (( p[0][0] != 0.0 || p[0][2] != 0.0 ) && fabs(p[0][0]) < 0.00025 && fabs(p[0][2]) < 0.00025) ||
+                                                               (( p[0][2] != 0.0 || p[0][1] != 0.0 ) && fabs(p[0][2]) < 0.00025 && fabs(p[0][1]) < 0.00025) ||
+                                                               (( p[1][0] != 0.0 || p[1][1] != 0.0 ) && fabs(p[1][0]) < 0.00025 && fabs(p[1][1]) < 0.00025) ||
+                                                               (( p[1][0] != 0.0 || p[1][2] != 0.0 ) && fabs(p[1][0]) < 0.00025 && fabs(p[1][2]) < 0.00025) ||
+                                                               (( p[1][2] != 0.0 || p[1][1] != 0.0 ) && fabs(p[1][2]) < 0.00025 && fabs(p[1][1]) < 0.00025) ||
+                                                               (( p[2][0] != 0.0 || p[2][1] != 0.0 ) && fabs(p[2][0]) < 0.00025 && fabs(p[2][1]) < 0.00025) ||
+                                                               (( p[2][0] != 0.0 || p[2][2] != 0.0 ) && fabs(p[2][0]) < 0.00025 && fabs(p[2][2]) < 0.00025) ||
+                                                               (( p[2][2] != 0.0 || p[2][1] != 0.0 ) && fabs(p[2][2]) < 0.00025 && fabs(p[2][1]) < 0.00025) ) {
+                                                               VectorMA( cnt, -0.1f, plane, cnt );
+                                                               //      Sys_Printf( "shifting pyramid point\n" );
+                                                               PlaneFromPoints( p[0], points[ 2 ], points[ 1 ], cnt );
+                                                               PlaneFromPoints( p[1], points[ 1 ], points[ 0 ], cnt );
+                                                               PlaneFromPoints( p[2], points[ 0 ], points[ 2 ], cnt );
+                                                       }
+#if nonax_clip_dbg
+                                                       for ( j = 0; j < 3; j++ )
+                                                       {
+                                                               for ( k = 0; k < 3; k++ )
+                                                               {
+                                                                       if ( fabs(p[j][k]) < 0.00005 && p[j][k] != 0.0 ){
+                                                                               Sys_Printf( "nonax nrm %6.17f %6.17f %6.17f\n (%6.8f %6.8f %6.8f)\n (%6.8f %6.8f %6.8f)\n (%6.8f %6.8f %6.8f)\n", p[j][0], p[j][1], p[j][2], points[j][0], points[j][1], points[j][2], points[(j+1)%3][0], points[(j+1)%3][1], points[(j+1)%3][2], cnt[0], cnt[1], cnt[2] );
+                                                                       }
+                                                               }
+                                                       }
+#endif
+                                                       /* set up brush sides */
+                                                       buildBrush->numsides = 4;
+                                                       buildBrush->sides[ 0 ].shaderInfo = si;
+                                                       for ( j = 1; j < buildBrush->numsides; j++ ) {
+                                                               if ( debugClip ) {
+                                                                       buildBrush->sides[ 0 ].shaderInfo = ShaderInfoForShader( "debugclip2" );
+                                                                       buildBrush->sides[ j ].shaderInfo = ShaderInfoForShader( "debugclip" );
+                                                               }
+                                                               else {
+                                                                       buildBrush->sides[ j ].shaderInfo = NULL;  // don't emit these faces as draw surfaces, should make smaller BSPs; hope this works
+                                                               }
+                                                       }
+                                                       VectorCopy( points[0], points[3] ); // for cyclic usage
+
+                                                       buildBrush->sides[ 0 ].planenum = FindFloatPlane( plane, plane[ 3 ], 3, points );
+                                                       buildBrush->sides[ 1 ].planenum = FindFloatPlane( p[0], p[0][ 3 ], 2, &points[ 1 ] ); // p[0] contains points[1] and points[2]
+                                                       buildBrush->sides[ 2 ].planenum = FindFloatPlane( p[1], p[1][ 3 ], 2, &points[ 0 ] ); // p[1] contains points[0] and points[1]
+                                                       buildBrush->sides[ 3 ].planenum = FindFloatPlane( p[2], p[2][ 3 ], 2, &points[ 2 ] ); // p[2] contains points[2] and points[0] (copied to points[3]
+                                               }
+                                               else
+                                               {
+                                                       Sys_Printf( "WARNING: triangle (%6.0f %6.0f %6.0f) (%6.0f %6.0f %6.0f) (%6.0f %6.0f %6.0f) of %s was not autoclipped\n", points[0][0], points[0][1], points[0][2], points[1][0], points[1][1], points[1][2], points[2][0], points[2][1], points[2][2], name );
+                                                       free( buildBrush );
+                                                       continue;
+                                               }
                                        }
-                                       // that's at least sqrt(1/3) backPlaneDistance, unless in DOWN mode; in DOWN mode, we are screwed anyway if we encounter a plane that's perpendicular to the xy plane)
 
-                                       if ( PlaneFromPoints( pa, points[ 2 ], points[ 1 ], backs[ 1 ] ) &&
-                                                PlaneFromPoints( pb, points[ 1 ], points[ 0 ], backs[ 0 ] ) &&
-                                                PlaneFromPoints( pc, points[ 0 ], points[ 2 ], backs[ 2 ] ) ) {
+
+                                       else if ( ( si->clipModel && !( spf ) ) || ( ( spawnFlags & 8090 ) == 2 ) ){    //default CLIPMODEL
+
+                                               default_CLIPMODEL:
+                                               // axial normal
+                                               VectorCopy( plane, bestNormal );
+                                               for ( j = 0; j < 3; j++ ){
+                                                       if ( fabs(bestNormal[j]) > fabs(bestNormal[(j+1)%3]) ){
+                                                               bestNormal[(j+1)%3] = 0.0;
+                                                       }
+                                                       else {
+                                                               bestNormal[j] = 0.0;
+                                                       }
+                                               }
+                                               VectorNormalize( bestNormal, bestNormal );
+
+                                               /* make side planes */
+                                               for ( j = 0; j < 3; j++ )
+                                               {
+                                                       VectorSubtract( points[(j+1)%3], points[ j ], nrm );
+                                                       CrossProduct( bestNormal, nrm, p[ j ] );
+                                                       VectorNormalize( p[ j ], p[ j ] );
+                                                       p[j][3] = DotProduct( points[j], p[j] );
+                                               }
+
+                                               /* make back plane */
+                                               VectorScale( plane, -1.0f, reverse );
+                                               reverse[ 3 ] = -plane[ 3 ];
+                                               reverse[3] += DotProduct( bestNormal, plane ) * clipDepth;
+#if nonax_clip_dbg
+                                               for ( j = 0; j < 3; j++ )
+                                               {
+                                                       for ( k = 0; k < 3; k++ )
+                                                       {
+                                                               if ( fabs(p[j][k]) < 0.00025 && p[j][k] != 0.0 ){
+                                                                       Sys_Printf( "nonax nrm %6.17f %6.17f %6.17f\n", p[j][0], p[j][1], p[j][2] );
+                                                               }
+                                                       }
+                                               }
+#endif
                                                /* set up brush sides */
                                                buildBrush->numsides = 5;
                                                buildBrush->sides[ 0 ].shaderInfo = si;
-                                               for ( j = 1; j < buildBrush->numsides; j++ )
-                                                       buildBrush->sides[ j ].shaderInfo = NULL;  // don't emit these faces as draw surfaces, should make smaller BSPs; hope this works
+                                               for ( j = 1; j < buildBrush->numsides; j++ ) {
+                                                       if ( debugClip ) {
+                                                               buildBrush->sides[ 0 ].shaderInfo = ShaderInfoForShader( "debugclip2" );
+                                                               buildBrush->sides[ j ].shaderInfo = ShaderInfoForShader( "debugclip" );
+                                                       }
+                                                       else {
+                                                               buildBrush->sides[ j ].shaderInfo = NULL;  // don't emit these faces as draw surfaces, should make smaller BSPs; hope this works
+                                                       }
+                                               }
+                                               VectorCopy( points[0], points[3] ); // for cyclic usage
 
                                                buildBrush->sides[ 0 ].planenum = FindFloatPlane( plane, plane[ 3 ], 3, points );
-                                               buildBrush->sides[ 1 ].planenum = FindFloatPlane( pa, pa[ 3 ], 2, &points[ 1 ] ); // pa contains points[1] and points[2]
-                                               buildBrush->sides[ 2 ].planenum = FindFloatPlane( pb, pb[ 3 ], 2, &points[ 0 ] ); // pb contains points[0] and points[1]
-                                               buildBrush->sides[ 3 ].planenum = FindFloatPlane( pc, pc[ 3 ], 2, &points[ 2 ] ); // pc contains points[2] and points[0] (copied to points[3]
-                                               buildBrush->sides[ 4 ].planenum = FindFloatPlane( reverse, reverse[ 3 ], 3, backs );
-                                       }
-                                       else
-                                       {
-                                               free( buildBrush );
-                                               continue;
+                                               buildBrush->sides[ 1 ].planenum = FindFloatPlane( p[0], p[0][ 3 ], 2, &points[ 0 ] ); // p[0] contains points[0] and points[1]
+                                               buildBrush->sides[ 2 ].planenum = FindFloatPlane( p[1], p[1][ 3 ], 2, &points[ 1 ] ); // p[1] contains points[1] and points[2]
+                                               buildBrush->sides[ 3 ].planenum = FindFloatPlane( p[2], p[2][ 3 ], 2, &points[ 2 ] ); // p[2] contains points[2] and points[0] (copied to points[3]
+                                               buildBrush->sides[ 4 ].planenum = FindFloatPlane( reverse, reverse[ 3 ], 0, NULL );
                                        }
 
-                                       normalEpsilon = normalEpsilon_save;
-                                       distanceEpsilon = distanceEpsilon_save;
 
                                        /* add to entity */
                                        if ( CreateBrushWindings( buildBrush ) ) {
@@ -669,10 +1315,15 @@ void InsertModel( const char *name, int skin, int frame, m4x4_t transform, remap
                                                entities[ mapEntityNum ].numBrushes++;
                                        }
                                        else{
+                                               Sys_Printf( "WARNING: triangle (%6.0f %6.0f %6.0f) (%6.0f %6.0f %6.0f) (%6.0f %6.0f %6.0f) of %s was not autoclipped\n", points[0][0], points[0][1], points[0][2], points[1][0], points[1][1], points[1][2], points[2][0], points[2][1], points[2][2], name );
                                                free( buildBrush );
                                        }
                                }
                        }
+                       normalEpsilon = normalEpsilon_save;
+               }
+               else if ( spawnFlags & 8090 ){
+                       Sys_Printf( "WARNING: nonexistent clipping mode selected\n" );
                }
        }
 }
@@ -691,7 +1342,7 @@ void AddTriangleModels( entity_t *e ){
        const char      *target, *model, *value;
        char shader[ MAX_QPATH ];
        shaderInfo_t    *celShader;
-       float temp, baseLightmapScale, lightmapScale;
+       float temp, baseLightmapScale, lightmapScale, clipDepth;
        float shadeAngle;
        int lightmapSampleSize;
        vec3_t origin, scale, angles;
@@ -704,6 +1355,7 @@ void AddTriangleModels( entity_t *e ){
        /* note it */
        Sys_FPrintf( SYS_VRB, "--- AddTriangleModels ---\n" );
 
+
        /* get current brush entity targetname */
        if ( e == entities ) {
                targetName = "";
@@ -872,6 +1524,9 @@ void AddTriangleModels( entity_t *e ){
                else if ( strcmp( "", ValueForKey( e2, "_samplesize" ) ) ) {
                        lightmapSampleSize = IntForKey( e2, "_samplesize" );
                }
+               else if ( strcmp( "", ValueForKey( e2, "_ss" ) ) ) {
+                       lightmapSampleSize = IntForKey( e2, "_ss" );
+               }
 
                if ( lightmapSampleSize < 0 ) {
                        lightmapSampleSize = 0;
@@ -914,6 +1569,9 @@ void AddTriangleModels( entity_t *e ){
                else if ( strcmp( "", ValueForKey( e2, "_sn" ) ) ) {
                        shadeAngle = FloatForKey( e2, "_sn" );
                }
+               else if ( strcmp( "", ValueForKey( e2, "_sa" ) ) ) {
+                       shadeAngle = FloatForKey( e2, "_sa" );
+               }
                else if ( strcmp( "", ValueForKey( e2, "_smooth" ) ) ) {
                        shadeAngle = FloatForKey( e2, "_smooth" );
                }
@@ -934,8 +1592,15 @@ void AddTriangleModels( entity_t *e ){
                        skin = IntForKey( e2, "skin" );
                }
 
+               clipDepth = clipDepthGlobal;
+               if ( strcmp( "", ValueForKey( e2, "_clipdepth" ) ) ) {
+                       clipDepth = FloatForKey( e2, "_clipdepth" );
+                       Sys_Printf( "misc_model has autoclip depth of %.3f\n", clipDepth );
+               }
+
+
                /* insert the model */
-               InsertModel( model, skin, frame, transform, remap, celShader, mapEntityNum, castShadows, recvShadows, spawnFlags, lightmapScale, lightmapSampleSize, shadeAngle );
+               InsertModel( model, skin, frame, transform, remap, celShader, mapEntityNum, castShadows, recvShadows, spawnFlags, lightmapScale, lightmapSampleSize, shadeAngle, clipDepth );
 
                /* free shader remappings */
                while ( remap != NULL )
@@ -945,4 +1610,5 @@ void AddTriangleModels( entity_t *e ){
                        remap = remap2;
                }
        }
+
 }
index 322173c95509f9ea009959a36e3ecb4838414216..c9f26b32c706180c50accfc5c37a567ff9baaa60 100644 (file)
@@ -111,7 +111,7 @@ void LokiInitPaths( char *argv0 ){
                /* set home path */
                homePath = home;
        }
-       else {
+       else{
                home = homePath;
        }
 
@@ -188,6 +188,7 @@ void LokiInitPaths( char *argv0 ){
                                path++;
                        }
 
+
                        /* concatenate */
                        if ( last > ( path + 1 ) ) {
                                // +1 hack: Q_strncat calls Q_strncpyz that expects a len including '\0'
@@ -295,6 +296,7 @@ void AddBasePath( char *path ){
        basePaths[ numBasePaths ] = safe_malloc( strlen( path ) + 1 );
        strcpy( basePaths[ numBasePaths ], path );
        CleanPath( basePaths[ numBasePaths ] );
+       if ( EnginePath[0] == '\0' ) strcpy( EnginePath, basePaths[ numBasePaths ] );
        numBasePaths++;
 }
 
@@ -360,7 +362,7 @@ void AddHomeBasePath( char *path ){
                if ( access( temp, X_OK ) == 0 ) {
                        if ( customHomePath == qfalse ) {
                                tempHomePath = xdgDataHomePath;
-                       }
+       }
                        path = path + 1;
                }
                #endif // GDEF_OS_XDG
@@ -460,6 +462,8 @@ void InitPaths( int *argc, char **argv ){
        numBasePaths = 0;
        numGamePaths = 0;
 
+       EnginePath[0] = '\0';
+
        /* parse through the arguments and extract those relevant to paths */
        for ( i = 0; i < *argc; i++ )
        {
index 27ae8c31f31d8705d0eb06109ba3323386c69b6d..5aab9d053955c2771af2b8525f0edbf16eeb4405 100644 (file)
@@ -668,7 +668,7 @@ int FloodEntities( tree_t *tree ){
        node_t      *headnode;
        entity_t    *e, *tripped;
        const char  *value;
-       int tripcount;
+       int tripcount = INT_MIN;
 
 
        headnode = tree->headnode;
index 5721ae255ef06c70ce98c9ce4b93de95dcea28ab..40b13c1d7f1b1d37f7f058491f96946e5ec51b4d 100644 (file)
@@ -41,6 +41,8 @@
 #define Q3MAP_MOTD      "Your map saw the pretty lights from q3map2's BFG"
 
 
+
+
 /* -------------------------------------------------------------------------------
 
    dependencies
 
    ------------------------------------------------------------------------------- */
 
-#if GDEF_OS_WINDOWS
-       #define Q_stricmp           stricmp
-       #define Q_strncasecmp       strnicmp
-#else
-       #define Q_stricmp           strcasecmp
-       #define Q_strncasecmp       strncasecmp
-#endif
+       #if GDEF_OS_WINDOWS
+               #define Q_stricmp           stricmp
+               #define Q_strncasecmp       strnicmp
+       #else
+               #define Q_stricmp           strcasecmp
+               #define Q_strncasecmp       strncasecmp
+       #endif
 
 // hack to declare and define in the same file
 #ifdef MAIN_C
 #define C_ANTIPORTAL            0x00004000  /* like hint, but doesn't generate portals */
 #define C_SKIP                  0x00008000  /* like hint, but skips this face (doesn't split bsp) */
 #define C_NOMARKS               0x00010000  /* no decals */
+#define C_OB                    0x00020000  /* skip -noob for this */
 #define C_DETAIL                0x08000000  /* THIS MUST BE THE SAME AS IN RADIANT! */
 
 
 #define HINT_PRIORITY           1000        /* ydnar: force hint splits first and antiportal/areaportal splits last */
 #define ANTIPORTAL_PRIORITY     -1000
 #define AREAPORTAL_PRIORITY     -1000
-#define DETAIL_PRIORITY         -3000
+#define DETAIL_PRIORITY     -3000
 
 #define PSIDE_FRONT             1
 #define PSIDE_BACK              2
 #define RAD_LUXEL_SIZE          3
 #define SUPER_LUXEL_SIZE        4
 #define SUPER_FLAG_SIZE         4
-#define FLAG_FORCE_SUBSAMPLING  1
+#define FLAG_FORCE_SUBSAMPLING 1
 #define FLAG_ALREADY_SUBSAMPLED 2
 #define SUPER_ORIGIN_SIZE       3
 #define SUPER_NORMAL_SIZE       4
@@ -392,7 +395,7 @@ typedef struct
 bspShader_t;
 
 
-/* planes x^1 is allways the opposite of plane x */
+/* planes x^1 is always the opposite of plane x */
 
 typedef struct
 {
@@ -406,7 +409,7 @@ typedef struct
 {
        int planeNum;
        int children[ 2 ];              /* negative numbers are -(leafs+1), not nodes */
-       int mins[ 3 ];                  /* for frustom culling */
+       int mins[ 3 ];                  /* for frustum culling */
        int maxs[ 3 ];
 }
 bspNode_t;
@@ -807,7 +810,7 @@ typedef struct shaderInfo_s
        sun_t               *sun;                           /* ydnar */
 
        vec3_t color;                                       /* normalized color */
-       vec3_t averageColor;
+       vec4_t averageColor;
        byte lightStyle;
 
        /* vortex: per-surface floodlight */
@@ -845,7 +848,7 @@ typedef struct face_s
        struct face_s       *next;
        int planenum;
        int priority;
-       //qboolean checked;
+       //qboolean                      checked;
        int compileFlags;
        winding_t           *w;
 }
@@ -1487,7 +1490,7 @@ typedef struct rawLightmap_s
        float                   *bspLuxels[ MAX_LIGHTMAPS ];
        float                   *radLuxels[ MAX_LIGHTMAPS ];
        float                   *superLuxels[ MAX_LIGHTMAPS ];
-       unsigned char           *superFlags;
+       unsigned char               *superFlags;
        float                   *superOrigins;
        float                   *superNormals;
        int                     *superClusters;
@@ -1535,6 +1538,7 @@ vec_t                       Random( void );
 char                        *Q_strncpyz( char *dst, const char *src, size_t len );
 char                        *Q_strcat( char *dst, size_t dlen, const char *src );
 char                        *Q_strncat( char *dst, size_t dlen, const char *src, size_t slen );
+int                         ShiftBSPMain( int argc, char **argv );
 
 /* help.c */
 void                        HelpMain(const char* arg);
@@ -1626,6 +1630,7 @@ void                        MakeNormalVectors( vec3_t forward, vec3_t right, vec
 /* map.c */
 void                        LoadMapFile( char *filename, qboolean onlyLights, qboolean noCollapseGroups );
 int                         FindFloatPlane( vec3_t normal, vec_t dist, int numPoints, vec3_t *points );
+qboolean                                       PlaneEqual( plane_t *p, vec3_t normal, vec_t dist );
 int                         PlaneTypeForNormal( vec3_t normal );
 void                        AddBrushBevels( void );
 brush_t                     *FinishBrush( qboolean noCollapseGroups );
@@ -1711,7 +1716,7 @@ void                        PicoPrintFunc( int level, const char *str );
 void                        PicoLoadFileFunc( const char *name, byte **buffer, int *bufSize );
 picoModel_t                 *FindModel( const char *name, int frame );
 picoModel_t                 *LoadModel( const char *name, int frame );
-void                        InsertModel( const char *name, int skin, int frame, m4x4_t transform, remap_t *remap, shaderInfo_t *celShader, int eNum, int castShadows, int recvShadows, int spawnFlags, float lightmapScale, int lightmapSampleSize, float shadeAngle );
+void                        InsertModel( const char *name, int skin, int frame, m4x4_t transform, remap_t *remap, shaderInfo_t *celShader, int eNum, int castShadows, int recvShadows, int spawnFlags, float lightmapScale, int lightmapSampleSize, float shadeAngle, float clipDepth );
 void                        AddTriangleModels( entity_t *e );
 
 
@@ -1927,6 +1932,7 @@ int                         CopyLump_Allocate( bspHeader_t *header, int lump, vo
 void                        AddLump( FILE *file, bspHeader_t *header, int lumpNum, const void *data, int length );
 
 void                        LoadBSPFile( const char *filename );
+void                        PartialLoadBSPFile( const char *filename );
 void                        WriteBSPFile( const char *filename );
 void                        PrintBSPFileSizes( void );
 
@@ -1954,6 +1960,8 @@ void InjectCommandLine( char **argv, int beginArgs, int endArgs );
 /* bspfile_ibsp.c */
 void                        LoadIBSPFile( const char *filename );
 void                        WriteIBSPFile( const char *filename );
+void                                           PartialLoadIBSPFile( const char *filename );
+
 
 
 /* bspfile_rbsp.c */
@@ -1989,7 +1997,7 @@ Q_EXTERN game_t games[]
        ,
                                                                #include "game_smokinguns.h" /* must be after game_quake3.h */
        ,
-                                                               #include "game_tremulous.h" /* LinuxManMikeC: must be after game_quake3.h, depends on #define's set in it */
+                                                               #include "game_tremulous.h" /*LinuxManMikeC: must be after game_quake3.h, depends on #define's set in it */
        ,
                                                                #include "game_unvanquished.h" /* must be after game_tremulous.h as they share defines! */
        ,
@@ -2009,11 +2017,11 @@ Q_EXTERN game_t games[]
        ,
                                                                #include "game_sof2.h"
        ,
-                                                               #include "game_jk2.h" /* must be after game_sof2.h as they share defines! */
+                                                               #include "game_jk2.h"   /* must be after game_sof2.h as they share defines! */
        ,
-                                                               #include "game_ja.h" /* must be after game_jk2.h as they share defines! */
+                                                               #include "game_ja.h"    /* must be after game_jk2.h as they share defines! */
        ,
-                                                               #include "game_qfusion.h" /* qfusion game */
+                                                               #include "game_qfusion.h"   /* qfusion game */
        ,
                                                                #include "game_warsow.h" /* must be after game_qfusion.h as they share defines! */
        ,
@@ -2052,6 +2060,8 @@ Q_EXTERN qboolean warnImage Q_ASSIGN( qtrue );
 /* ydnar: sinusoid samples */
 Q_EXTERN float jitters[ MAX_JITTERS ];
 
+/*can't code*/
+Q_EXTERN qboolean doingBSP Q_ASSIGN( qfalse );
 
 /* commandline arguments */
 Q_EXTERN qboolean verboseEntities Q_ASSIGN( qfalse );
@@ -2072,9 +2082,10 @@ Q_EXTERN qboolean nofog Q_ASSIGN( qfalse );
 Q_EXTERN qboolean noHint Q_ASSIGN( qfalse );                        /* ydnar */
 Q_EXTERN qboolean renameModelShaders Q_ASSIGN( qfalse );            /* ydnar */
 Q_EXTERN qboolean skyFixHack Q_ASSIGN( qfalse );                    /* ydnar */
-Q_EXTERN qboolean bspAlternateSplitWeights Q_ASSIGN( qfalse );      /* 27 */
-Q_EXTERN qboolean deepBSP Q_ASSIGN( qfalse );                       /* div0 */
+Q_EXTERN qboolean bspAlternateSplitWeights Q_ASSIGN( qfalse );                      /* 27 */
+Q_EXTERN qboolean deepBSP Q_ASSIGN( qfalse );                   /* div0 */
 Q_EXTERN qboolean maxAreaFaceSurface Q_ASSIGN( qfalse );                    /* divVerent */
+Q_EXTERN qboolean nocmdline Q_ASSIGN( qfalse );
 
 Q_EXTERN int patchSubdivisions Q_ASSIGN( 8 );                       /* ydnar: -patchmeta subdivisions */
 
@@ -2091,12 +2102,15 @@ Q_EXTERN qboolean emitFlares Q_ASSIGN( qfalse );
 Q_EXTERN qboolean debugSurfaces Q_ASSIGN( qfalse );
 Q_EXTERN qboolean debugInset Q_ASSIGN( qfalse );
 Q_EXTERN qboolean debugPortals Q_ASSIGN( qfalse );
+Q_EXTERN qboolean debugClip Q_ASSIGN( qfalse );                        /* debug model autoclipping */
+Q_EXTERN float clipDepthGlobal Q_ASSIGN( 2.0f );
 Q_EXTERN qboolean lightmapTriangleCheck Q_ASSIGN( qfalse );
 Q_EXTERN qboolean lightmapExtraVisClusterNudge Q_ASSIGN( qfalse );
 Q_EXTERN qboolean lightmapFill Q_ASSIGN( qfalse );
 Q_EXTERN int metaAdequateScore Q_ASSIGN( -1 );
 Q_EXTERN int metaGoodScore Q_ASSIGN( -1 );
 Q_EXTERN float metaMaxBBoxDistance Q_ASSIGN( -1 );
+Q_EXTERN qboolean noob Q_ASSIGN( qfalse );
 
 #if Q3MAP2_EXPERIMENTAL_SNAP_NORMAL_FIX
 // Increasing the normalEpsilon to compensate for new logic in SnapNormal(), where
@@ -2134,6 +2148,8 @@ Q_EXTERN int blockSize[ 3 ]                                 /* should be the sam
        = { 1024, 1024, 1024 };
 #endif
 
+Q_EXTERN char EnginePath[ 1024 ];
+
 Q_EXTERN char name[ 1024 ];
 Q_EXTERN char source[ 1024 ];
 Q_EXTERN char outbase[ 32 ];
@@ -2226,6 +2242,7 @@ Q_EXTERN char inbase[ MAX_QPATH ];
 Q_EXTERN char globalCelShader[ MAX_QPATH ];
 
 Q_EXTERN float farPlaneDist;                /* rr2do2, rf, mre, ydnar all contributed to this one... */
+Q_EXTERN int farPlaneDistMode;
 
 Q_EXTERN int numportals;
 Q_EXTERN int portalclusters;
@@ -2272,6 +2289,7 @@ Q_EXTERN qboolean keepLights Q_ASSIGN( qfalse );
 Q_EXTERN int sampleSize Q_ASSIGN( DEFAULT_LIGHTMAP_SAMPLE_SIZE );
 Q_EXTERN int minSampleSize Q_ASSIGN( DEFAULT_LIGHTMAP_MIN_SAMPLE_SIZE );
 Q_EXTERN qboolean noVertexLighting Q_ASSIGN( qfalse );
+Q_EXTERN qboolean nolm Q_ASSIGN( qfalse );
 Q_EXTERN qboolean noGridLighting Q_ASSIGN( qfalse );
 
 Q_EXTERN qboolean noTrace Q_ASSIGN( qfalse );
@@ -2310,6 +2328,7 @@ Q_EXTERN qboolean noCollapse Q_ASSIGN( qfalse );
 Q_EXTERN int lightmapSearchBlockSize Q_ASSIGN( 0 );
 Q_EXTERN qboolean exportLightmaps Q_ASSIGN( qfalse );
 Q_EXTERN qboolean externalLightmaps Q_ASSIGN( qfalse );
+Q_EXTERN qboolean externalLightmapNames Q_ASSIGN( qfalse );
 Q_EXTERN int lmCustomSize Q_ASSIGN( LIGHTMAP_WIDTH );
 Q_EXTERN char *             lmCustomDir Q_ASSIGN( NULL );
 Q_EXTERN int lmLimitSize Q_ASSIGN( 0 );
@@ -2337,6 +2356,8 @@ Q_EXTERN qboolean debugAxis Q_ASSIGN( qfalse );
 Q_EXTERN qboolean debugCluster Q_ASSIGN( qfalse );
 Q_EXTERN qboolean debugOrigin Q_ASSIGN( qfalse );
 Q_EXTERN qboolean lightmapBorder Q_ASSIGN( qfalse );
+//1=warn; 0=warn if lmsize>128
+Q_EXTERN int debugSampleSize Q_ASSIGN( 0 );
 
 /* longest distance across the map */
 Q_EXTERN float maxMapDistance Q_ASSIGN( 0 );
@@ -2347,6 +2368,10 @@ Q_EXTERN float spotScale Q_ASSIGN( 7500.0f );
 Q_EXTERN float areaScale Q_ASSIGN( 0.25f );
 Q_EXTERN float skyScale Q_ASSIGN( 1.0f );
 Q_EXTERN float bounceScale Q_ASSIGN( 0.25f );
+Q_EXTERN float bounceColorRatio Q_ASSIGN( 1.0f );
+Q_EXTERN float vertexglobalscale Q_ASSIGN( 1.0f );
+Q_EXTERN float g_backsplashFractionScale Q_ASSIGN( 1.0f );
+Q_EXTERN float g_backsplashDistance Q_ASSIGN( -999.0f );
 
 /* jal: alternative angle attenuation curve */
 Q_EXTERN qboolean lightAngleHL Q_ASSIGN( qfalse );
@@ -2365,6 +2390,8 @@ Q_EXTERN float texturesRGB Q_ASSIGN( qfalse );
 Q_EXTERN float colorsRGB Q_ASSIGN( qfalse );
 Q_EXTERN float lightmapExposure Q_ASSIGN( 0.0f );
 Q_EXTERN float lightmapCompensate Q_ASSIGN( 1.0f );
+Q_EXTERN float lightmapBrightness Q_ASSIGN( 1.0f );
+Q_EXTERN float lightmapContrast Q_ASSIGN( 1.0f );
 
 /* ydnar: for runtime tweaking of falloff tolerance */
 Q_EXTERN float falloffTolerance Q_ASSIGN( 1.0f );
@@ -2557,7 +2584,7 @@ Q_EXTERN int allocatedBSPBrushSides Q_ASSIGN( 0 );
 Q_EXTERN bspBrushSide_t*    bspBrushSides Q_ASSIGN( NULL );
 
 Q_EXTERN int numBSPLightBytes Q_ASSIGN( 0 );
-Q_EXTERN byte *bspLightBytes Q_ASSIGN( NULL );
+Q_EXTERN byte               *bspLightBytes Q_ASSIGN( NULL );
 
 //%    Q_EXTERN int                            numBSPGridPoints Q_ASSIGN( 0 );
 //%    Q_EXTERN byte                           *bspGridPoints Q_ASSIGN( NULL );
@@ -2569,11 +2596,11 @@ Q_EXTERN int numBSPVisBytes Q_ASSIGN( 0 );
 Q_EXTERN byte bspVisBytes[ MAX_MAP_VISIBILITY ];
 
 Q_EXTERN int numBSPDrawVerts Q_ASSIGN( 0 );
-Q_EXTERN bspDrawVert_t *bspDrawVerts Q_ASSIGN( NULL );
+Q_EXTERN bspDrawVert_t          *bspDrawVerts Q_ASSIGN( NULL );
 
 Q_EXTERN int numBSPDrawIndexes Q_ASSIGN( 0 );
 Q_EXTERN int allocatedBSPDrawIndexes Q_ASSIGN( 0 );
-Q_EXTERN int *bspDrawIndexes Q_ASSIGN( NULL );
+Q_EXTERN int                *bspDrawIndexes Q_ASSIGN( NULL );
 
 Q_EXTERN int numBSPDrawSurfaces Q_ASSIGN( 0 );
 Q_EXTERN bspDrawSurface_t   *bspDrawSurfaces Q_ASSIGN( NULL );
@@ -2602,7 +2629,7 @@ Q_EXTERN qboolean compile_map;
                        } \
                        if ( !allocated || allocated > 2147483647 / (int)sizeof( *ptr ) ) \
                        { \
-                               Error( #ptr " over 2 GB" ); \
+                               Error( # ptr " over 2 GB" ); \
                        } \
                        ptr = realloc( ptr, sizeof( *ptr ) * allocated ); \
                        if ( !ptr ) { \
@@ -2620,7 +2647,7 @@ Q_EXTERN qboolean compile_map;
 
 #define AUTOEXPAND_BY_REALLOC0( ptr, reqitem, allocated, def ) _AUTOEXPAND_BY_REALLOC( ptr, reqitem, allocated, def, qtrue )
 
-#define AUTOEXPAND_BY_REALLOC_BSP( suffix, def ) AUTOEXPAND_BY_REALLOC( bsp##suffix, numBSP##suffix, allocatedBSP##suffix, def )
+#define AUTOEXPAND_BY_REALLOC_BSP( suffix, def ) AUTOEXPAND_BY_REALLOC( bsp ## suffix, numBSP ## suffix, allocatedBSP ## suffix, def )
 
 #define AUTOEXPAND_BY_REALLOC0_BSP( suffix, def ) AUTOEXPAND_BY_REALLOC0( bsp##suffix, numBSP##suffix, allocatedBSP##suffix, def )
 
index b3697e4e2afdb40dd0a449fdc53ca4a5734cc601..f075a3ea623d2ac723a57749358c0ec54ed04928 100644 (file)
@@ -70,7 +70,7 @@ void ColorMod( colorMod_t *cm, int numVerts, bspDrawVert_t *drawVerts ){
                        VectorSet( mult, 1.0f, 1.0f, 1.0f );
                        mult[ 3 ] = 1.0f;
                        VectorSet( add, 0.0f, 0.0f, 0.0f );
-                       mult[ 3 ] = 0.0f;
+                       add[ 3 ] = 0.0f;
 
                        /* switch on type */
                        switch ( cm2->type )
@@ -645,8 +645,8 @@ static shaderInfo_t *AllocShaderInfo( void ){
        /* set defaults */
        ApplySurfaceParm( "default", &si->contentFlags, &si->surfaceFlags, &si->compileFlags );
 
-       si->backsplashFraction = DEF_BACKSPLASH_FRACTION;
-       si->backsplashDistance = DEF_BACKSPLASH_DISTANCE;
+       si->backsplashFraction = DEF_BACKSPLASH_FRACTION * g_backsplashFractionScale;
+       si->backsplashDistance = g_backsplashDistance < -900.0f ? DEF_BACKSPLASH_DISTANCE : g_backsplashDistance;
 
        si->bounceScale = DEF_RADIOSITY_BOUNCE;
 
@@ -660,7 +660,7 @@ static shaderInfo_t *AllocShaderInfo( void ){
        si->patchShadows = qfalse;
        si->vertexShadows = qtrue;  /* ydnar: changed default behavior */
        si->forceSunlight = qfalse;
-       si->vertexScale = 1.0;
+       si->vertexScale = vertexglobalscale;
        si->notjunc = qfalse;
 
        /* ydnar: set texture coordinate transform matrix to identity */
@@ -727,6 +727,9 @@ void FinishShader( shaderInfo_t *si ){
                        }
                }
        }
+               if (noob && !(si->compileFlags & C_OB)){
+                       ApplySurfaceParm( "noob", &si->contentFlags, &si->surfaceFlags, &si->compileFlags );
+               }
 
        /* set to finished */
        si->finished = qtrue;
@@ -808,10 +811,12 @@ static void LoadShaderImages( shaderInfo_t *si ){
        if ( VectorLength( si->color ) <= 0.0f ) {
                ColorNormalize( color, si->color );
                VectorScale( color, ( 1.0f / count ), si->averageColor );
+               si->averageColor[ 3 ] = color[ 3 ] / count;
        }
        else
        {
                VectorCopy( si->color, si->averageColor );
+               si->averageColor[ 3 ] = 1.0f;
        }
 }
 
@@ -942,17 +947,17 @@ void Parse1DMatrixAppend( char *buffer, int x, vec_t *m ){
 
 
        if ( !GetTokenAppend( buffer, qtrue ) || strcmp( token, "(" ) ) {
-               Error( "Parse1DMatrixAppend(): line %d: ( not found!", scriptline );
+               Error( "Parse1DMatrixAppend(): line %d: ( not found!\nFile location be: %s\n", scriptline, g_strLoadedFileLocation );
        }
        for ( i = 0; i < x; i++ )
        {
                if ( !GetTokenAppend( buffer, qfalse ) ) {
-                       Error( "Parse1DMatrixAppend(): line %d: Number not found!", scriptline );
+                       Error( "Parse1DMatrixAppend(): line %d: Number not found!\nFile location be: %s\n", scriptline, g_strLoadedFileLocation );
                }
                m[ i ] = atof( token );
        }
        if ( !GetTokenAppend( buffer, qtrue ) || strcmp( token, ")" ) ) {
-               Error( "Parse1DMatrixAppend(): line %d: ) not found!", scriptline );
+               Error( "Parse1DMatrixAppend(): line %d: ) not found!\nFile location be: %s\n", scriptline, g_strLoadedFileLocation );
        }
 }
 
@@ -1014,12 +1019,12 @@ static void ParseShaderFile( const char *filename ){
                }
                if ( strcmp( token, "{" ) ) {
                        if ( si != NULL ) {
-                               Error( "ParseShaderFile(): %s, line %d: { not found!\nFound instead: %s\nLast known shader: %s",
-                                          filename, scriptline, token, si->shader );
+                               Error( "ParseShaderFile(): %s, line %d: { not found!\nFound instead: %s\nLast known shader: %s\nFile location be: %s\n",
+                                          filename, scriptline, token, si->shader, g_strLoadedFileLocation );
                        }
                        else{
-                               Error( "ParseShaderFile(): %s, line %d: { not found!\nFound instead: %s",
-                                          filename, scriptline, token );
+                               Error( "ParseShaderFile(): %s, line %d: { not found!\nFound instead: %s\nFile location be: %s\n",
+                                          filename, scriptline, token, g_strLoadedFileLocation );
                        }
                }
 
@@ -1057,7 +1062,6 @@ static void ParseShaderFile( const char *filename ){
                                                         !Q_stricmp( token, "clampMap" ) ||
                                                         !Q_stricmp( token, "animMap" ) ||
                                                         !Q_stricmp( token, "clampAnimMap" ) ||
-                                                        !Q_stricmp( token, "clampMap" ) ||
                                                         !Q_stricmp( token, "mapComp" ) ||
                                                         !Q_stricmp( token, "mapNoComp" ) ) {
                                                        /* skip one token for animated stages */
@@ -1599,11 +1603,11 @@ static void ParseShaderFile( const char *filename ){
                                /* q3map_vertexScale (scale vertex lighting by this fraction) */
                                else if ( !Q_stricmp( token, "q3map_vertexScale" ) ) {
                                        GetTokenAppend( shaderText, qfalse );
-                                       si->vertexScale = atof( token );
+                                       si->vertexScale *= atof( token );
                                }
 
                                /* q3map_noVertexLight */
-                               else if ( !Q_stricmp( token, "q3map_noVertexLight" )  ) {
+                               else if ( !Q_stricmp( token, "q3map_noVertexLight" ) ) {
                                        si->noVertexLight = qtrue;
                                }
 
index 4f69a2f165a6a8be084b759168c0d5cf2324ba67..9b6f706b51e0c55fec49ce60c2bb84cbc4378055 100644 (file)
@@ -82,7 +82,7 @@ void FinishSurface( mapDrawSurface_t *ds ){
 
 
        /* dummy check */
-       if ( ds->type <= SURFACE_BAD || ds->type >= NUM_SURFACE_TYPES || ds == NULL || ds->shaderInfo == NULL ) {
+       if ( ds == NULL || ds->shaderInfo == NULL || ds->type <= SURFACE_BAD || ds->type >= NUM_SURFACE_TYPES ) {
                return;
        }
 
@@ -598,7 +598,7 @@ void ClassifySurfaces( int numSurfs, mapDrawSurface_t *ds ){
                   ----------------------------------------------------------------- */
 
                /* vertex lit surfaces don't need this information */
-               if ( si->compileFlags & C_VERTEXLIT || ds->type == SURFACE_TRIANGLES ) {
+               if ( si->compileFlags & C_VERTEXLIT || ds->type == SURFACE_TRIANGLES || nolm == qtrue ) {
                        VectorClear( ds->lightmapAxis );
                        //%     VectorClear( ds->lightmapVecs[ 2 ] );
                        ds->sampleSize = 0;
@@ -2114,8 +2114,8 @@ int FilterPointConvexHullIntoTree_r( vec3_t **points, int npoints, mapDrawSurfac
 
 int FilterWindingIntoTree_r( winding_t *w, mapDrawSurface_t *ds, node_t *node ){
        int i, refs = 0;
-       plane_t         *p1, *p2;
-       vec4_t plane1, plane2;
+       plane_t         *p1;
+       vec4_t plane1;
        winding_t       *fat, *front, *back;
        shaderInfo_t    *si;
 
@@ -2169,12 +2169,15 @@ int FilterWindingIntoTree_r( winding_t *w, mapDrawSurface_t *ds, node_t *node ){
 
                /* check if surface is planar */
                if ( ds->planeNum >= 0 ) {
+                       #if 0
+                       plane_t *p2;
+                       vec4_t plane2;
+
                        /* get surface plane */
                        p2 = &mapplanes[ ds->planeNum ];
                        VectorCopy( p2->normal, plane2 );
                        plane2[ 3 ] = p2->dist;
 
-                       #if 0
                        /* div0: this is the plague (inaccurate) */
                        vec4_t reverse;
 
@@ -2190,7 +2193,6 @@ int FilterWindingIntoTree_r( winding_t *w, mapDrawSurface_t *ds, node_t *node ){
                                return FilterWindingIntoTree_r( w, ds, node->children[ 1 ] );
                        }
                        #else
-                       (void) plane2;
                        /* div0: this is the cholera (doesn't hit enough) */
 
                        /* the drawsurf might have an associated plane, if so, force a filter here */
@@ -2313,10 +2315,6 @@ static int FilterTrianglesIntoTree( mapDrawSurface_t *ds, tree_t *tree ){
                refs += FilterWindingIntoTree_r( w, ds, tree->headnode );
        }
 
-       /* use point filtering as well */
-       for ( i = 0; i < ds->numVerts; i++ )
-               refs += FilterPointIntoTree_r( ds->verts[ i ].xyz, ds, tree->headnode );
-
        return refs;
 }
 
@@ -2359,13 +2357,6 @@ static int FilterFoliageIntoTree( mapDrawSurface_t *ds, tree_t *tree ){
                        VectorAdd( instance->xyz, ds->verts[ ds->indexes[ i + 2 ] ].xyz, w->p[ 2 ] );
                        refs += FilterWindingIntoTree_r( w, ds, tree->headnode );
                }
-
-               /* use point filtering as well */
-               for ( i = 0; i < ( ds->numVerts - ds->numFoliageInstances ); i++ )
-               {
-                       VectorAdd( instance->xyz, ds->verts[ i ].xyz, xyz );
-                       refs += FilterPointIntoTree_r( xyz, ds, tree->headnode );
-               }
        }
 
        return refs;
@@ -3287,7 +3278,7 @@ int AddSurfaceModelsToTriangle_r( mapDrawSurface_t *ds, surfaceModel_t *model, b
                        }
 
                        /* insert the model */
-                       InsertModel( (char *) model->model, 0, 0, transform, NULL, ds->celShader, ds->entityNum, ds->castShadows, ds->recvShadows, 0, ds->lightmapScale, 0, 0 );
+                       InsertModel( (char *) model->model, 0, 0, transform, NULL, ds->celShader, ds->entityNum, ds->castShadows, ds->recvShadows, 0, ds->lightmapScale, 0, 0, clipDepthGlobal );
 
                        /* return to sender */
                        return 1;
@@ -3377,7 +3368,7 @@ int AddSurfaceModels( mapDrawSurface_t *ds ){
                        alpha /= ds->numVerts;
                        centroid.color[ 0 ][ 0 ] = 0xFF;
                        centroid.color[ 0 ][ 1 ] = 0xFF;
-                       centroid.color[ 0 ][ 2 ] = 0xFF;
+                       //centroid.color[ 0 ][ 2 ] = 0xFF;
                        centroid.color[ 0 ][ 2 ] = ( alpha > 255.0f ? 0xFF : alpha );
 
                        /* head vert is centroid */
@@ -3657,10 +3648,10 @@ void FilterDrawsurfsIntoTree( entity_t *e, tree_t *tree ){
                }
 
                /* ydnar: remap shader */
-               if ( ds->shaderInfo->remapShader && ds->shaderInfo->remapShader[ 0 ] ) {
+/*             if ( ds->shaderInfo->remapShader && ds->shaderInfo->remapShader[ 0 ] ) {
                        ds->shaderInfo = ShaderInfoForShader( ds->shaderInfo->remapShader );
                }
-
+*/
                /* ydnar: gs mods: handle the various types of surfaces */
                switch ( ds->type )
                {
index da8e71f7b3454c4c7d59223711706edb535c7c51..263e03bfe19e5cb5741428f40866bc8f1b537b7d 100644 (file)
@@ -281,7 +281,7 @@ void Foliage( mapDrawSurface_t *src ){
                m4x4_scale_for_vec3( transform, scale );
 
                /* add the model to the bsp */
-               InsertModel( foliage->model, 0, 0, transform, NULL, NULL, src->entityNum, src->castShadows, src->recvShadows, 0, src->lightmapScale, 0, 0 );
+               InsertModel( foliage->model, 0, 0, transform, NULL, NULL, src->entityNum, src->castShadows, src->recvShadows, 0, src->lightmapScale, 0, 0, clipDepthGlobal );
 
                /* walk each new surface */
                for ( i = oldNumMapDrawSurfs; i < numMapDrawSurfs; i++ )
index 2cd6f0374e8b2aea6189facca07a1dcaf7ae7e7b..e569e070d8c0cd41e77bf5868e3b8af6450aa7e9 100644 (file)
@@ -962,7 +962,8 @@ void MakeEntityMetaTriangles( entity_t *e ){
 
 typedef struct edge_s
 {
-       vec3_t origin, edge;
+       vec3_t origin;
+       vec4_t edge;
        vec_t length, kingpinLength;
        int kingpin;
        vec4_t plane;
@@ -1710,6 +1711,9 @@ static void MetaTrianglesToSurface( int numPossibles, metaTriangle_t *possibles,
                ClearBounds( ds->mins, ds->maxs );
 
                /* clear verts/indexes */
+//             memset( verts, 0, sizeof( verts ) );
+//             memset( indexes, 0, sizeof( indexes ) );
+               //is more correct, but upper works ok too
                memset( verts, 0, sizeof( *verts ) * maxSurfaceVerts );
                memset( indexes, 0, sizeof( *indexes ) * maxSurfaceIndexes );
 
index f62ad047b8af9ea80bac94d9a27ad415c0a8f983..fe180508ee3ee8ab3df9df58d04eb64b0875656b 100644 (file)
@@ -605,7 +605,7 @@ int EdgeCompare( const void *elem1, const void *elem2 ) {
        if ( d1 < d2 ) {
                return -1;
        }
-       if ( d2 > d1 ) {
+       if ( d1 > d2 ) {
                return 1;
        }
        return 0;
index 588765ee99e4e15b9d55b68f6c2fb63266dfd65d..5e3bf7efeb64b320f6d31b0be1fde93c94b63c3a 100644 (file)
@@ -319,16 +319,25 @@ void CalcVis( void ){
        }
        if ( value[ 0 ] != '\0' ) {
                farPlaneDist = atof( value );
-               if ( farPlaneDist > 0.0f ) {
+               farPlaneDistMode = value[strlen(value) - 1 ];
+               if ( farPlaneDist != 0.0f ) {
                        Sys_Printf( "farplane distance = %.1f\n", farPlaneDist );
                }
-               else{
-                       farPlaneDist = 0.0f;
+                       if ( farPlaneDist != 0.0f && farPlaneDistMode == 'o' ) {
+                       Sys_Printf( "farplane Origin2Origin mode on\n" );
+               }
+                       if ( farPlaneDist != 0.0f && farPlaneDistMode == 'r' ) {
+                       Sys_Printf( "farplane Radius+Radius mode on\n" );
+               }
+                       if ( farPlaneDist != 0.0f && farPlaneDistMode == 'e' ) {
+                       Sys_Printf( "farplane Exact distance mode on\n" );
                }
+
        }
 
 
 
+
        Sys_Printf( "\n--- BasePortalVis (%d) ---\n", numportals * 2 );
        RunThreadsOnIndividual( numportals * 2, qtrue, BasePortalVis );
 
index 547a7291c5be32ab21ae338f74b6f695d2144aac..adc0676110c942e492a094d17d6ddc5592d5bc36 100644 (file)
@@ -1608,8 +1608,28 @@ void BasePortalVis( int portalnum ){
                   }
                 */
 
+               if( !p->sky && !tp->sky && farPlaneDist != 0.0f && farPlaneDistMode == 'o' )
+               {
+                       VectorSubtract( p->origin, tp->origin, dir );
+                       if( VectorLength( dir ) > farPlaneDist )
+                               continue;
+               }
+
+               if( !p->sky && !tp->sky && farPlaneDist != 0.0f && farPlaneDistMode == 'e' )
+               {
+                       VectorSubtract( p->origin, tp->origin, dir );
+                       if( VectorLength( dir ) + p->radius + tp->radius > 2.0f * farPlaneDist )
+                               continue;
+               }
+
+               if( !p->sky && !tp->sky && farPlaneDist != 0.0f && farPlaneDistMode == 'r' )
+               {
+                       if( p->radius + tp->radius > farPlaneDist )
+                               continue;
+               }
+
                /* ydnar: this is known-to-be-working farplane code */
-               if ( !p->sky && !tp->sky && farPlaneDist > 0.0f ) {
+               if ( !p->sky && !tp->sky && farPlaneDist != 0.0f ) {
                        VectorSubtract( p->origin, tp->origin, dir );
                        if ( VectorLength( dir ) - p->radius - tp->radius > farPlaneDist ) {
                                continue;
index cc1b352ddc10e7f1d6e0916b7e9e789ce8de54ff..3e043a40f192e47da6911d31743276cc633e6474 100644 (file)
@@ -114,15 +114,20 @@ int EmitShader( const char *shader, int *contentFlags, int *surfaceFlags ){
                /* if not Smokin'Guns like tex file */
                if ( !game->texFile )
                {
-                       /* ydnar: handle custom surface/content flags */
-                       if ( surfaceFlags != NULL && bspShaders[ i ].surfaceFlags != *surfaceFlags ) {
-                               continue;
-                       }
-                       if ( contentFlags != NULL && bspShaders[ i ].contentFlags != *contentFlags ) {
-                               continue;
+               /* ydnar: handle custom surface/content flags */
+               if ( surfaceFlags != NULL && bspShaders[ i ].surfaceFlags != *surfaceFlags ) {
+                       continue;
+               }
+               if ( contentFlags != NULL && bspShaders[ i ].contentFlags != *contentFlags ) {
+                       continue;
+               }
+               }
+               if ( !doingBSP ){
+                       si = ShaderInfoForShader( shader );
+                       if ( si->remapShader && si->remapShader[ 0 ] ) {
+                               shader = si->remapShader;
                        }
                }
-
                /* compare name */
                if ( !Q_stricmp( shader, bspShaders[ i ].shader ) ) {
                        return i;
@@ -150,13 +155,13 @@ int EmitShader( const char *shader, int *contentFlags, int *surfaceFlags ){
        /* if not Smokin'Guns like tex file */
        if ( !game->texFile )
        {
-               /* handle custom content/surface flags */
-               if ( surfaceFlags != NULL ) {
-                       bspShaders[ i ].surfaceFlags = *surfaceFlags;
-               }
-               if ( contentFlags != NULL ) {
-                       bspShaders[ i ].contentFlags = *contentFlags;
-               }
+       /* handle custom content/surface flags */
+       if ( surfaceFlags != NULL ) {
+               bspShaders[ i ].surfaceFlags = *surfaceFlags;
+       }
+       if ( contentFlags != NULL ) {
+               bspShaders[ i ].contentFlags = *contentFlags;
+       }
        }
 
        /* recursively emit any damage shaders */
diff --git a/tools/unvanquished/CMakeLists.txt b/tools/unvanquished/CMakeLists.txt
deleted file mode 100644 (file)
index ed9ed82..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-if (BUILD_DAEMONMAP)
-    # Always keep daemonmap libs/
-    # up-to-date with NetRadiant libs/
-    include_directories(daemonmap/libs)
-    include_directories(daemonmap/tools)
-
-    add_subdirectory(daemonmap/libs)
-    add_subdirectory(daemonmap/tools)
-endif()
diff --git a/tools/unvanquished/daemonmap b/tools/unvanquished/daemonmap
deleted file mode 160000 (submodule)
index 7834097..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 7834097b7080e96e28cc84f4c4cce6633b8683de