From d0b0a7a9dd089efe244039aebd2fb9e3496b007d Mon Sep 17 00:00:00 2001 From: Scott Ehlert Date: Mon, 21 May 2012 02:48:36 -0500 Subject: [PATCH] Imported tier1 and mathlib code from L4D2 SDK. --- linux_sdk/Makefile | 150 + linux_sdk/Makefile.choreo | 57 + linux_sdk/Makefile.mathlib | 71 + linux_sdk/Makefile.plugin | 57 + linux_sdk/Makefile.server | 28 + linux_sdk/Makefile.tier1 | 78 + linux_sdk/Makefile.vcpm | 50 + mathlib/3dnow.cpp | 193 ++ mathlib/3dnow.h | 16 + mathlib/IceKey.cpp | 394 +++ mathlib/anorms.cpp | 181 ++ mathlib/bumpvects.cpp | 69 + mathlib/color_conversion.cpp | 645 ++++ mathlib/datagen.pl | 63 + mathlib/halton.cpp | 30 + mathlib/imagequant.cpp | 95 + mathlib/lightdesc.cpp | 320 ++ mathlib/mathlib-2005.vcproj | 407 +++ mathlib/mathlib_base.cpp | 4090 ++++++++++++++++++++++++++ mathlib/noisedata.h | 180 ++ mathlib/polyhedron.cpp | 2294 +++++++++++++++ mathlib/powsse.cpp | 39 + mathlib/quantize.cpp | 678 +++++ mathlib/randsse.cpp | 109 + mathlib/simdvectormatrix.cpp | 112 + mathlib/sparse_convolution_noise.cpp | 218 ++ mathlib/sse.cpp | 845 ++++++ mathlib/sse.h | 23 + mathlib/sseconst.cpp | 1164 ++++++++ mathlib/ssenoise.cpp | 105 + mathlib/vector.cpp | 12 + mathlib/vmatrix.cpp | 1263 ++++++++ tier1/KeyValues.cpp | 2521 ++++++++++++++++ tier1/NetAdr.cpp | 331 +++ tier1/bitbuf.cpp | 1269 ++++++++ tier1/byteswap.cpp | 90 + tier1/characterset.cpp | 41 + tier1/checksum_crc.cpp | 180 ++ tier1/checksum_md5.cpp | 271 ++ tier1/commandbuffer.cpp | 636 ++++ tier1/convar.cpp | 1287 ++++++++ tier1/datamanager.cpp | 410 +++ tier1/diff.cpp | 547 ++++ tier1/generichash.cpp | 303 ++ tier1/interface.cpp | 465 +++ tier1/mempool.cpp | 316 ++ tier1/memstack.cpp | 300 ++ tier1/newbitbuf.cpp | 713 +++++ tier1/processor_detect.cpp | 278 ++ tier1/processor_detect_linux.cpp | 47 + tier1/rangecheckedvar.cpp | 41 + tier1/stringpool.cpp | 334 +++ tier1/strtools.cpp | 2015 +++++++++++++ tier1/tier1-2005.vcproj | 568 ++++ tier1/tier1.cpp | 63 + tier1/tokenreader.cpp | 480 +++ tier1/undiff.cpp | 94 + tier1/uniqueid.cpp | 177 ++ tier1/utlbuffer.cpp | 1753 +++++++++++ tier1/utlbufferutil.cpp | 561 ++++ tier1/utlstring.cpp | 334 +++ tier1/utlsymbol.cpp | 397 +++ 62 files changed, 30858 insertions(+) create mode 100644 linux_sdk/Makefile create mode 100644 linux_sdk/Makefile.choreo create mode 100644 linux_sdk/Makefile.mathlib create mode 100644 linux_sdk/Makefile.plugin create mode 100644 linux_sdk/Makefile.server create mode 100644 linux_sdk/Makefile.tier1 create mode 100644 linux_sdk/Makefile.vcpm create mode 100644 mathlib/3dnow.cpp create mode 100644 mathlib/3dnow.h create mode 100644 mathlib/IceKey.cpp create mode 100644 mathlib/anorms.cpp create mode 100644 mathlib/bumpvects.cpp create mode 100644 mathlib/color_conversion.cpp create mode 100644 mathlib/datagen.pl create mode 100644 mathlib/halton.cpp create mode 100644 mathlib/imagequant.cpp create mode 100644 mathlib/lightdesc.cpp create mode 100644 mathlib/mathlib-2005.vcproj create mode 100644 mathlib/mathlib_base.cpp create mode 100644 mathlib/noisedata.h create mode 100644 mathlib/polyhedron.cpp create mode 100644 mathlib/powsse.cpp create mode 100644 mathlib/quantize.cpp create mode 100644 mathlib/randsse.cpp create mode 100644 mathlib/simdvectormatrix.cpp create mode 100644 mathlib/sparse_convolution_noise.cpp create mode 100644 mathlib/sse.cpp create mode 100644 mathlib/sse.h create mode 100644 mathlib/sseconst.cpp create mode 100644 mathlib/ssenoise.cpp create mode 100644 mathlib/vector.cpp create mode 100644 mathlib/vmatrix.cpp create mode 100644 tier1/KeyValues.cpp create mode 100644 tier1/NetAdr.cpp create mode 100644 tier1/bitbuf.cpp create mode 100644 tier1/byteswap.cpp create mode 100644 tier1/characterset.cpp create mode 100644 tier1/checksum_crc.cpp create mode 100644 tier1/checksum_md5.cpp create mode 100644 tier1/commandbuffer.cpp create mode 100644 tier1/convar.cpp create mode 100644 tier1/datamanager.cpp create mode 100644 tier1/diff.cpp create mode 100644 tier1/generichash.cpp create mode 100644 tier1/interface.cpp create mode 100644 tier1/mempool.cpp create mode 100644 tier1/memstack.cpp create mode 100644 tier1/newbitbuf.cpp create mode 100644 tier1/processor_detect.cpp create mode 100644 tier1/processor_detect_linux.cpp create mode 100644 tier1/rangecheckedvar.cpp create mode 100644 tier1/stringpool.cpp create mode 100644 tier1/strtools.cpp create mode 100644 tier1/tier1-2005.vcproj create mode 100644 tier1/tier1.cpp create mode 100644 tier1/tokenreader.cpp create mode 100644 tier1/undiff.cpp create mode 100644 tier1/uniqueid.cpp create mode 100644 tier1/utlbuffer.cpp create mode 100644 tier1/utlbufferutil.cpp create mode 100644 tier1/utlstring.cpp create mode 100644 tier1/utlsymbol.cpp diff --git a/linux_sdk/Makefile b/linux_sdk/Makefile new file mode 100644 index 00000000..b39b926e --- /dev/null +++ b/linux_sdk/Makefile @@ -0,0 +1,150 @@ +# +# SDK Makefile for x86 Linux +# +# + +############################################################################# +# Developer configurable items +############################################################################# + +# the name of the mod binary (_i486.so is appended to the end) +NAME = server + +# the location of the vcproj that builds the mod +MOD_PROJ = ../game/server/server_scratch-2005.vcproj +# the name of the mod configuration (typically _) +MOD_CONFIG = Server\(SDK\)_ReleaseWin32 + +# the directory the base binaries (tier0_i486.so, etc) are located +# this should point to your orange box subfolder of where you have srcds installed. +SRCDS_DIR = ~/srcds/orangebox + +# the path to your mods directory +# set this so that 'make install' or 'make installrelease' will copy your binary over automatically. +GAME_DIR = $(SRCDS_DIR)/scratchmod + +# compiler options (gcc 3.4.1 or above is required - 4.1.2+ recommended) +CC = /usr/bin/gcc +CPLUS = /usr/bin/g++ +CLINK = /usr/bin/gcc +CPP_LIB = "libstdc++.a libgcc_eh.a" + +# put any compiler flags you want passed here +USER_CFLAGS = + +# link flags for your mod, make sure to include any special libraries here +LDFLAGS = "-lm -ldl $(LIB_DIR)/particles_i486.a $(LIB_DIR)/dmxloader_i486.a $(LIB_DIR)/mathlib_i486.a tier0_i486.so vstdlib_i486.so $(LIB_DIR)/tier1_i486.a $(LIB_DIR)/tier2_i486.a $(LIB_DIR)/tier3_i486.a $(LIB_DIR)/choreoobjects_i486.a steam_api_i486.so" + +# XERCES 2.6.0 or above ( http://xml.apache.org/xerces-c/ ) is used by the vcproj to makefile converter +# it must be installed before being able to run this makefile +# if you have xerces installed already you should be able to use the two lines below +XERCES_INC_DIR = /usr/include +XERCES_LIB_DIR = /usr/lib + +# Change this to true if you want to build debug binaries for everything +# The only exception is the mod/game as MOD_CONFIG determines if it's a debug build or not +DEBUG = false + +############################################################################# +# Things below here shouldn't need to be altered +############################################################################# +MAKE = make +AR = "ar rvs" + +# the dir we want to put binaries we build into +BUILD_DIR = . +# the place to put object files +BUILD_OBJ_DIR = $(BUILD_DIR)/obj + +# the location of the source code +SRC_DIR = .. +# the location of the Linux static libraries +LIB_DIR = $(SRC_DIR)/lib/linux + +# the CPU target for the build, must be i486 for now +ARCH = i486 +ARCH_CFLAGS = -mtune=i686 -march=pentium3 -mmmx -m32 + +DEFINES = -D_LINUX -DLINUX -DVPROF_LEVEL=1 -DSWDS -D_finite=finite -Dstricmp=strcasecmp -D_stricmp=strcasecmp \ + -D_strnicmp=strncasecmp -Dstrnicmp=strncasecmp -D_vsnprintf=vsnprintf -D_alloca=alloca -Dstrcmpi=strcasecmp +UNDEF = -Usprintf -Ustrncpy -UPROTECTED_THINGS_ENABLE + +BASE_CFLAGS = -fno-strict-aliasing -Wall -Werror -Wconversion -Wno-non-virtual-dtor -Wno-invalid-offsetof +SHLIBEXT = so +SHLIBCFLAGS = -fPIC +SHLIBLDFLAGS = -shared -Wl,-Map,$@_map.txt -Wl + +# Flags passed to the c compiler +CFLAGS = $(DEFINES) $(ARCH_CFLAGS) -O3 $(BASE_CFLAGS) +ifdef USER_CFLAGS + CFLAGS += $(USER_CFLAGS) +endif +CFLAGS += $(UNDEF) + +# Debug flags +DBG_DEFINES = "-D_DEBUG -DDEBUG" +DBG_CFLAGS = "$(DEFINES) $(ARCH_CFLAGS) -g -ggdb $(BASE_CFLAGS) $(UNDEF)" + +# define list passed to make for the sub makefile +BASE_DEFINES = CC=$(CC) AR=$(AR) CPLUS=$(CPLUS) CPP_LIB=$(CPP_LIB) DEBUG=$(DEBUG) \ + BUILD_DIR=$(BUILD_DIR) BUILD_OBJ_DIR=$(BUILD_OBJ_DIR) SRC_DIR=$(SRC_DIR) \ + LIB_DIR=$(LIB_DIR) SHLIBLDFLAGS=$(SHLIBLDFLAGS) SHLIBEXT=$(SHLIBEXT) \ + CLINK=$(CLINK) CFLAGS="$(CFLAGS)" DBG_CFLAGS=$(DBG_CFLAGS) LDFLAGS=$(LDFLAGS) \ + DEFINES="$(DEFINES)" DBG_DEFINES=$(DBG_DEFINES) \ + ARCH=$(ARCH) SRCDS_DIR=$(SRCDS_DIR) MOD_CONFIG=$(MOD_CONFIG) NAME=$(NAME) \ + XERCES_INC_DIR=$(XERCES_INC_DIR) XERCES_LIB_DIR=$(XERCES_LIB_DIR) + +# Project Makefile +MAKE_SERVER = Makefile.server +MAKE_VCPM = Makefile.vcpm +MAKE_PLUGIN = Makefile.plugin +MAKE_TIER1 = Makefile.tier1 +MAKE_MATH = Makefile.mathlib +MAKE_CHOREO = Makefile.choreo + +all: check vcpm mod + +check: + if [ -z "$(CC)" ]; then echo "Compiler not defined."; exit; fi + if [ ! -d $(BUILD_DIR) ];then mkdir -p $(BUILD_DIR);fi + cd $(BUILD_DIR) + if [ ! -e "$(LIB_DIR)/tier1_i486.a" ]; then $(MAKE) tier1;fi + if [ ! -e "$(LIB_DIR)/mathlib_i486.a" ]; then $(MAKE) mathlib;fi + if [ ! -e "$(LIB_DIR)/choreoobjects_i486.a" ]; then $(MAKE) choreo;fi + if [ ! -f "tier0_i486.so" ]; then ln -s $(SRCDS_DIR)/bin/tier0_i486.so .; fi + if [ ! -f "vstdlib_i486.so" ]; then ln -s $(SRCDS_DIR)/bin/vstdlib_i486.so .; fi + if [ ! -f "steam_api_i486.so" ]; then ln -s $(SRCDS_DIR)/bin/steam_api_i486.so .; fi + +vcpm: check + if [ ! -e "vcpm" ]; then $(MAKE) -f $(MAKE_VCPM) $(BASE_DEFINES);fi + +mod: check vcpm + ./vcpm $(MOD_PROJ) + $(MAKE) -f $(MAKE_SERVER) $(BASE_DEFINES) + +plugin: check + $(MAKE) -f $(MAKE_PLUGIN) $(BASE_DEFINES) + +tier1: + $(MAKE) -f $(MAKE_TIER1) $(BASE_DEFINES) + +mathlib: + $(MAKE) -f $(MAKE_MATH) $(BASE_DEFINES) + +choreo: + $(MAKE) -f $(MAKE_CHOREO) $(BASE_DEFINES) + +install: + cp -f $(NAME)_$(ARCH).$(SHLIBEXT) $(GAME_DIR)/bin/$(NAME)_$(ARCH).$(SHLIBEXT) + +installrelease: + cp -f $(NAME)_$(ARCH).$(SHLIBEXT) $(GAME_DIR)/bin/$(NAME)_$(ARCH).$(SHLIBEXT) + strip $(GAME_DIR)/bin/$(NAME)_$(ARCH).$(SHLIBEXT) + +clean: + $(MAKE) -f $(MAKE_VCPM) $(BASE_DEFINES) clean + $(MAKE) -f $(MAKE_PLUGIN) $(BASE_DEFINES) clean + $(MAKE) -f $(MAKE_SERVER) $(BASE_DEFINES) clean + $(MAKE) -f $(MAKE_TIER1) $(BASE_DEFINES) clean + $(MAKE) -f $(MAKE_MATH) $(BASE_DEFINES) clean + $(MAKE) -f $(MAKE_CHOREO) $(BASE_DEFINES) clean diff --git a/linux_sdk/Makefile.choreo b/linux_sdk/Makefile.choreo new file mode 100644 index 00000000..4182e60a --- /dev/null +++ b/linux_sdk/Makefile.choreo @@ -0,0 +1,57 @@ +# +# Choreoobjects Static Library Makefile +# + +override NAME = choreoobjects + +LIB_SRC_DIR = $(SRC_DIR)/game/shared +PUBLIC_SRC_DIR = $(SRC_DIR)/public +TIER0_PUBLIC_SRC_DIR = $(SRC_DIR)/public/tier0 +TIER1_PUBLIC_SRC_DIR = $(SRC_DIR)/public/tier1 +UTIL_COMMON_SRC_DIR = $(SRC_DIR)/utils/common + +LIB_OBJ_DIR = $(BUILD_OBJ_DIR)/$(NAME)_$(ARCH) + +# Extension of linux static library +override SHLIBEXT = a + +INCLUDEDIRS = -I$(LIB_SRC_DIR) -I$(PUBLIC_SRC_DIR) -I$(TIER0_PUBLIC_SRC_DIR) -I$(TIER1_PUBLIC_SRC_DIR) -I$(UTIL_COMMON_SRC_DIR) -D_LIB -DCHOREOOBJECTS_STATIC_LIB + +DO_CC = $(CPLUS) $(INCLUDEDIRS) -DARCH=$(ARCH) + +ifeq "$(DEBUG)" "true" + DO_CC += $(DBG_DEFINES) $(DBG_CFLAGS) +else + DO_CC += -DNDEBUG $(CFLAGS) +endif + +DO_CC += -o $@ -c $< + +##################################################################### + +LIB_OBJS= \ + $(LIB_OBJ_DIR)/choreoactor.o \ + $(LIB_OBJ_DIR)/choreochannel.o \ + $(LIB_OBJ_DIR)/choreoevent.o \ + $(LIB_OBJ_DIR)/choreoscene.o \ + $(LIB_OBJ_DIR)/sceneimage.o \ + +all: dirs $(NAME)_$(ARCH).$(SHLIBEXT) + +dirs: + -mkdir -p $(BUILD_OBJ_DIR) + -mkdir -p $(LIB_OBJ_DIR) + +$(NAME)_$(ARCH).$(SHLIBEXT): $(LIB_OBJS) + $(AR) $(LIB_DIR)/$@ $(LIB_OBJS) + +$(LIB_OBJ_DIR)/%.o: $(LIB_SRC_DIR)/%.cpp + $(DO_CC) + +install: + cp -f $(NAME)_$(ARCH).$(SHLIBEXT) $(LIB_DIR)/$(NAME)_$(ARCH).$(SHLIBEXT) + +clean: + -rm -rf $(LIB_OBJ_DIR) + + diff --git a/linux_sdk/Makefile.mathlib b/linux_sdk/Makefile.mathlib new file mode 100644 index 00000000..c2a55e6a --- /dev/null +++ b/linux_sdk/Makefile.mathlib @@ -0,0 +1,71 @@ +# +# Mathlin Static Library Makefile +# + +override NAME = mathlib + +LIB_SRC_DIR = $(SRC_DIR)/$(NAME) +PUBLIC_SRC_DIR = $(SRC_DIR)/public +TIER0_PUBLIC_SRC_DIR = $(SRC_DIR)/public/tier0 +TIER1_PUBLIC_SRC_DIR = $(SRC_DIR)/public/tier1 +MATHLIB_PUBLIC_SRC_DIR = $(SRC_DIR)/public/mathlib + +LIB_OBJ_DIR = $(BUILD_OBJ_DIR)/$(NAME)_$(ARCH) + +# Extension of linux static library +override SHLIBEXT = a + +INCLUDEDIRS = -I$(PUBLIC_SRC_DIR) -I$(TIER0_PUBLIC_SRC_DIR) -I$(TIER1_PUBLIC_SRC_DIR) -I$(MATHLIB_PUBLIC_SRC_DIR) -D_LIB + +DO_CC = $(CPLUS) $(INCLUDEDIRS) -DARCH=$(ARCH) + +ifeq "$(DEBUG)" "true" + DO_CC += $(DBG_DEFINES) $(DBG_CFLAGS) +else + DO_CC += -DNDEBUG $(CFLAGS) +endif + +DO_CC += -o $@ -c $< + +##################################################################### + +LIB_OBJS= \ + $(LIB_OBJ_DIR)/3dnow.o \ + $(LIB_OBJ_DIR)/anorms.o \ + $(LIB_OBJ_DIR)/bumpvects.o \ + $(LIB_OBJ_DIR)/color_conversion.o \ + $(LIB_OBJ_DIR)/halton.o \ + $(LIB_OBJ_DIR)/IceKey.o \ + $(LIB_OBJ_DIR)/imagequant.o \ + $(LIB_OBJ_DIR)/lightdesc.o \ + $(LIB_OBJ_DIR)/mathlib_base.o \ + $(LIB_OBJ_DIR)/polyhedron.o \ + $(LIB_OBJ_DIR)/powsse.o \ + $(LIB_OBJ_DIR)/quantize.o \ + $(LIB_OBJ_DIR)/randsse.o \ + $(LIB_OBJ_DIR)/simdvectormatrix.o \ + $(LIB_OBJ_DIR)/sparse_convolution_noise.o \ + $(LIB_OBJ_DIR)/sse.o \ + $(LIB_OBJ_DIR)/sseconst.o \ + $(LIB_OBJ_DIR)/ssenoise.o \ + $(LIB_OBJ_DIR)/vector.o \ + $(LIB_OBJ_DIR)/vmatrix.o \ + +all: dirs $(NAME)_$(ARCH).$(SHLIBEXT) + +dirs: + -mkdir -p $(BUILD_OBJ_DIR) + -mkdir -p $(LIB_OBJ_DIR) + +$(NAME)_$(ARCH).$(SHLIBEXT): $(LIB_OBJS) + $(AR) $(LIB_DIR)/$@ $(LIB_OBJS) + +$(LIB_OBJ_DIR)/%.o: $(LIB_SRC_DIR)/%.cpp + $(DO_CC) + +install: + cp -f $(NAME)_$(ARCH).$(SHLIBEXT) $(LIB_DIR)/$(NAME)_$(ARCH).$(SHLIBEXT) + +clean: + -rm -rf $(LIB_OBJ_DIR) + diff --git a/linux_sdk/Makefile.plugin b/linux_sdk/Makefile.plugin new file mode 100644 index 00000000..9586b03c --- /dev/null +++ b/linux_sdk/Makefile.plugin @@ -0,0 +1,57 @@ +# +# Sample server plugin for SRC engine +# +# October 2004, alfred@valvesoftware.com +# + +override NAME = serverplugin_empty + +PLUGIN_SRC_DIR = $(SRC_DIR)/utils/serverplugin_sample +PUBLIC_SRC_DIR = $(SRC_DIR)/public +TIER0_PUBLIC_SRC_DIR = $(SRC_DIR)/public/tier0 +TIER1_PUBLIC_SRC_DIR = $(SRC_DIR)/public/tier1 + +PLUGIN_OBJ_DIR = $(BUILD_OBJ_DIR)/serverplugin_empty_$(ARCH) +TIER0_OBJ_DIR = $(PLUGIN_OBJ_DIR)/tier0 + +INCLUDEDIRS = -I$(PUBLIC_SRC_DIR) -I$(TIER0_PUBLIC_SRC_DIR) -I$(TIER1_PUBLIC_SRC_DIR) +LDFLAGS_PLG = -lm -ldl tier0_i486.so vstdlib_i486.so $(LIB_DIR)/mathlib_i486.a $(LIB_DIR)/tier1_i486.a $(LIB_DIR)/tier2_i486.a + +DO_CC = $(CPLUS) $(INCLUDEDIRS) -DARCH=$(ARCH) + +ifeq "$(DEBUG)" "true" + DO_CC += $(DBG_DEFINES) $(DBG_CFLAGS) +else + DO_CC += -DNDEBUG $(CFLAGS) +endif + +DO_CC += -o $@ -c $< + +##################################################################### + +PLUGIN_OBJS = \ + $(PLUGIN_OBJ_DIR)/serverplugin_bot.o \ + $(PLUGIN_OBJ_DIR)/serverplugin_empty.o \ + +TIER0_OBJS = \ + $(TIER0_OBJ_DIR)/memoverride.o \ + +all: dirs $(NAME)_$(ARCH).$(SHLIBEXT) + +dirs: + -mkdir -p $(BUILD_OBJ_DIR) + -mkdir -p $(PLUGIN_OBJ_DIR) + -mkdir -p $(TIER0_OBJ_DIR) + +$(NAME)_$(ARCH).$(SHLIBEXT): $(PLUGIN_OBJS) $(TIER0_OBJS) + $(CLINK) -o $(BUILD_DIR)/$@ -m32 $(SHLIBLDFLAGS) $(PLUGIN_OBJS) $(TIER0_OBJS) $(PUBLIC_OBJS) $(CPP_LIB) $(LDFLAGS_PLG) $(CPP_LIB) + +$(PLUGIN_OBJ_DIR)/%.o: $(PLUGIN_SRC_DIR)/%.cpp + $(DO_CC) + +$(TIER0_OBJ_DIR)/%.o: $(TIER0_PUBLIC_SRC_DIR)/%.cpp + $(DO_CC) + +clean: + -rm -rf $(PLUGIN_OBJ_DIR) + -rm -f $(NAME)_$(ARCH).$(SHLIBEXT) diff --git a/linux_sdk/Makefile.server b/linux_sdk/Makefile.server new file mode 100644 index 00000000..fe613f38 --- /dev/null +++ b/linux_sdk/Makefile.server @@ -0,0 +1,28 @@ +# +# wrapper Makefile for auto-generated make files +# +# + +############################################################################# +# PROJECT MAKEFILES +############################################################################# +MAKE_FILE = Makefile.$(MOD_CONFIG) +-include $(MAKE_FILE) + +############################################################################# +# The compiler command line for each src code file to compile +############################################################################# +DO_CC = $(CPLUS) $(INCLUDES) -DARCH=$(ARCH) + +ifeq (_DEBUG,$(findstring _DEBUG,$(CFLAGS))) + DO_CC += $(DEFINES) $(DBG_CFLAGS) +else + DO_CC += $(CFLAGS) +endif + +DO_CC += -o $@ -c $< + +clean: + rm -rf obj/$(NAME)_$(ARCH) + rm -f $(NAME)_$(ARCH).$(SHLIBEXT) + diff --git a/linux_sdk/Makefile.tier1 b/linux_sdk/Makefile.tier1 new file mode 100644 index 00000000..fac93119 --- /dev/null +++ b/linux_sdk/Makefile.tier1 @@ -0,0 +1,78 @@ +# +# Tier1 Static Library Makefile +# + +override NAME = tier1 + +LIB_SRC_DIR = $(SRC_DIR)/$(NAME) +PUBLIC_SRC_DIR = $(SRC_DIR)/public +TIER0_PUBLIC_SRC_DIR = $(SRC_DIR)/public/tier0 +TIER1_PUBLIC_SRC_DIR = $(SRC_DIR)/public/tier1 + +LIB_OBJ_DIR=$(BUILD_OBJ_DIR)/$(NAME)_$(ARCH) + +# Extension of linux static library +override SHLIBEXT = a + +INCLUDEDIRS = -I$(PUBLIC_SRC_DIR) -I$(TIER0_PUBLIC_SRC_DIR) -I$(TIER1_PUBLIC_SRC_DIR) -D_LIB -DTIER1_STATIC_LIB + +DO_CC = $(CPLUS) $(INCLUDEDIRS) -DARCH=$(ARCH) + +ifeq "$(DEBUG)" "true" + DO_CC += $(DBG_DEFINES) $(DBG_CFLAGS) +else + DO_CC += -DNDEBUG $(CFLAGS) +endif + +DO_CC += -o $@ -c $< + +##################################################################### + +LIB_OBJS= \ + $(LIB_OBJ_DIR)/bitbuf.o \ + $(LIB_OBJ_DIR)/byteswap.o \ + $(LIB_OBJ_DIR)/characterset.o \ + $(LIB_OBJ_DIR)/checksum_crc.o \ + $(LIB_OBJ_DIR)/checksum_md5.o \ + $(LIB_OBJ_DIR)/commandbuffer.o \ + $(LIB_OBJ_DIR)/convar.o \ + $(LIB_OBJ_DIR)/datamanager.o \ + $(LIB_OBJ_DIR)/diff.o \ + $(LIB_OBJ_DIR)/generichash.o \ + $(LIB_OBJ_DIR)/interface.o \ + $(LIB_OBJ_DIR)/KeyValues.o \ + $(LIB_OBJ_DIR)/mempool.o \ + $(LIB_OBJ_DIR)/memstack.o \ + $(LIB_OBJ_DIR)/NetAdr.o \ + $(LIB_OBJ_DIR)/newbitbuf.o \ + $(LIB_OBJ_DIR)/processor_detect.o \ + $(LIB_OBJ_DIR)/rangecheckedvar.o \ + $(LIB_OBJ_DIR)/stringpool.o \ + $(LIB_OBJ_DIR)/strtools.o \ + $(LIB_OBJ_DIR)/tier1.o \ + $(LIB_OBJ_DIR)/tokenreader.o \ + $(LIB_OBJ_DIR)/undiff.o \ + $(LIB_OBJ_DIR)/uniqueid.o \ + $(LIB_OBJ_DIR)/utlbuffer.o \ + $(LIB_OBJ_DIR)/utlbufferutil.o \ + $(LIB_OBJ_DIR)/utlstring.o \ + $(LIB_OBJ_DIR)/utlsymbol.o \ + +all: dirs $(NAME)_$(ARCH).$(SHLIBEXT) + +dirs: + -mkdir -p $(BUILD_OBJ_DIR) + -mkdir -p $(LIB_OBJ_DIR) + +$(NAME)_$(ARCH).$(SHLIBEXT): $(LIB_OBJS) + $(AR) $(LIB_DIR)/$@ $(LIB_OBJS) + +$(LIB_OBJ_DIR)/%.o: $(LIB_SRC_DIR)/%.cpp + $(DO_CC) + +install: + cp -f $(NAME)_$(ARCH).$(SHLIBEXT) $(LIB_DIR)/$(NAME)_$(ARCH).$(SHLIBEXT) + +clean: + -rm -rf $(LIB_OBJ_DIR) + diff --git a/linux_sdk/Makefile.vcpm b/linux_sdk/Makefile.vcpm new file mode 100644 index 00000000..eb8ecff9 --- /dev/null +++ b/linux_sdk/Makefile.vcpm @@ -0,0 +1,50 @@ +# +# VCProject file to Makefile converter +# +# November 2004, alfred@valvesoftware.com +# + +VCPM_SRC_DIR = $(SRC_DIR)/utils/vprojtomake +PUBLIC_SRC_DIR = $(SRC_DIR)/public +TIER0_PUBLIC_SRC_DIR = $(SRC_DIR)/public/tier0 +TIER1_PUBLIC_SRC_DIR = $(SRC_DIR)/public/tier1 +UTIL_COMMON_SRC_DIR = $(SRC_DIR)/utils/common + +VCPM_OBJ_DIR = $(BUILD_OBJ_DIR)/vcpm + +INCLUDEDIRS = -I$(PUBLIC_SRC_DIR) -I$(XERCES_INC_DIR) -I$(TIER0_PUBLIC_SRC_DIR) -I$(TIER1_PUBLIC_SRC_DIR) -I$(UTIL_COMMON_SRC_DIR) +LDFLAGS_VC = -lm -ldl -L$(XERCES_LIB_DIR) -lxerces-c tier0_i486.so vstdlib_i486.so $(LIB_DIR)/tier1_i486.a + +DO_CC = $(CPLUS) $(INCLUDEDIRS) -DARCH=$(ARCH) + +ifeq "$(DEBUG)" "true" + DO_CC += $(DBG_DEFINES) $(DBG_CFLAGS) +else + DO_CC += -DNDEBUG $(CFLAGS) +endif + +DO_CC += -o $@ -c $< + +##################################################################### + +VCPM_OBJS = \ + $(VCPM_OBJ_DIR)/makefilecreator.o \ + $(VCPM_OBJ_DIR)/vprojtomake.o \ + $(VCPM_OBJ_DIR)/vcprojconvert.o \ + +all: dirs vcpm + +dirs: + -mkdir -p $(BUILD_OBJ_DIR) + -mkdir -p $(VCPM_OBJ_DIR) + +vcpm: $(VCPM_OBJS) + $(CLINK) -m32 -o $(BUILD_DIR)/$@ $(VCPM_OBJS) $(CPP_LIB) $(LDFLAGS_VC) + +$(VCPM_OBJ_DIR)/%.o: $(VCPM_SRC_DIR)/%.cpp + $(DO_CC) + +clean: + -rm -rf $(VCPM_OBJ_DIR) + -rm -f vcpm + diff --git a/mathlib/3dnow.cpp b/mathlib/3dnow.cpp new file mode 100644 index 00000000..53a23736 --- /dev/null +++ b/mathlib/3dnow.cpp @@ -0,0 +1,193 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: 3DNow Math primitives. +// +//=====================================================================================// + +#include +#include // Needed for FLT_EPSILON +#include "basetypes.h" +#include +#include "tier0/dbg.h" +#include "mathlib/mathlib.h" +#include "mathlib/amd3dx.h" +#include "mathlib/vector.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#ifdef _MSC_VER +#pragma warning(disable:4244) // "conversion from 'const int' to 'float', possible loss of data" +#pragma warning(disable:4730) // "mixing _m64 and floating point expressions may result in incorrect code" +#endif + +//----------------------------------------------------------------------------- +// 3D Now Implementations of optimized routines: +//----------------------------------------------------------------------------- +float _3DNow_Sqrt(float x) +{ + Assert( s_bMathlibInitialized ); + float root = 0.f; +#ifdef _WIN32 + _asm + { + femms + movd mm0, x + PFRSQRT (mm1,mm0) + punpckldq mm0, mm0 + PFMUL (mm0, mm1) + movd root, mm0 + femms + } +#elif defined _LINUX || defined __APPLE__ + __asm __volatile__( "femms" ); + __asm __volatile__ + ( + "pfrsqrt %y0, %y1 \n\t" + "punpckldq %y1, %y1 \n\t" + "pfmul %y1, %y0 \n\t" + : "=y" (root), "=y" (x) + :"0" (x) + ); + __asm __volatile__( "femms" ); +#else +#error +#endif + + return root; +} + +// NJS FIXME: Need to test Recripricol squareroot performance and accuraccy +// on AMD's before using the specialized instruction. +float _3DNow_RSqrt(float x) +{ + Assert( s_bMathlibInitialized ); + + return 1.f / _3DNow_Sqrt(x); +} + + +float FASTCALL _3DNow_VectorNormalize (Vector& vec) +{ + Assert( s_bMathlibInitialized ); + float *v = &vec[0]; + float radius = 0.f; + + if ( v[0] || v[1] || v[2] ) + { +#ifdef _WIN32 + _asm + { + mov eax, v + femms + movq mm0, QWORD PTR [eax] + movd mm1, DWORD PTR [eax+8] + movq mm2, mm0 + movq mm3, mm1 + PFMUL (mm0, mm0) + PFMUL (mm1, mm1) + PFACC (mm0, mm0) + PFADD (mm1, mm0) + PFRSQRT (mm0, mm1) + punpckldq mm1, mm1 + PFMUL (mm1, mm0) + PFMUL (mm2, mm0) + PFMUL (mm3, mm0) + movq QWORD PTR [eax], mm2 + movd DWORD PTR [eax+8], mm3 + movd radius, mm1 + femms + } +#elif defined _LINUX || defined __APPLE__ + long long a,c; + int b,d; + memcpy(&a,&vec[0],sizeof(a)); + memcpy(&b,&vec[2],sizeof(b)); + memcpy(&c,&vec[0],sizeof(c)); + memcpy(&d,&vec[2],sizeof(d)); + + __asm __volatile__( "femms" ); + __asm __volatile__ + ( + "pfmul %y3, %y3\n\t" + "pfmul %y0, %y0 \n\t" + "pfacc %y3, %y3 \n\t" + "pfadd %y3, %y0 \n\t" + "pfrsqrt %y0, %y3 \n\t" + "punpckldq %y0, %y0 \n\t" + "pfmul %y3, %y0 \n\t" + "pfmul %y3, %y2 \n\t" + "pfmul %y3, %y1 \n\t" + : "=y" (radius), "=y" (c), "=y" (d) + : "y" (a), "0" (b), "1" (c), "2" (d) + ); + memcpy(&vec[0],&c,sizeof(c)); + memcpy(&vec[2],&d,sizeof(d)); + __asm __volatile__( "femms" ); + +#else +#error +#endif + } + return radius; +} + + +void FASTCALL _3DNow_VectorNormalizeFast (Vector& vec) +{ + _3DNow_VectorNormalize( vec ); +} + + +// JAY: This complains with the latest processor pack +#ifdef _MSC_VER +#pragma warning(disable: 4730) +#endif + +float _3DNow_InvRSquared(const float* v) +{ + Assert( s_bMathlibInitialized ); + float r2 = 1.f; +#ifdef _WIN32 + _asm { // AMD 3DNow only routine + mov eax, v + femms + movq mm0, QWORD PTR [eax] + movd mm1, DWORD PTR [eax+8] + movd mm2, [r2] + PFMUL (mm0, mm0) + PFMUL (mm1, mm1) + PFACC (mm0, mm0) + PFADD (mm1, mm0) + PFMAX (mm1, mm2) + PFRCP (mm0, mm1) + movd [r2], mm0 + femms + } +#elif defined _LINUX || defined __APPLE__ + long long a,c; + int b; + memcpy(&a,&v[0],sizeof(a)); + memcpy(&b,&v[2],sizeof(b)); + memcpy(&c,&v[0],sizeof(c)); + + __asm __volatile__( "femms" ); + __asm __volatile__ + ( + "PFMUL %y2, %y2 \n\t" + "PFMUL %y3, %y3 \n\t" + "PFACC %y2, %y2 \n\t" + "PFADD %y2, %y3 \n\t" + "PFMAX %y3, %y4 \n\t" + "PFRCP %y3, %y2 \n\t" + "movq %y2, %y0 \n\t" + : "=y" (r2) + : "0" (r2), "y" (a), "y" (b), "y" (c) + ); + __asm __volatile__( "femms" ); +#else +#error +#endif + + return r2; +} diff --git a/mathlib/3dnow.h b/mathlib/3dnow.h new file mode 100644 index 00000000..3e6e108b --- /dev/null +++ b/mathlib/3dnow.h @@ -0,0 +1,16 @@ +//========= Copyright © 1996-2006, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=====================================================================================// + +#ifndef _3DNOW_H +#define _3DNOW_H + +float _3DNow_Sqrt(float x); +float _3DNow_RSqrt(float x); +float FASTCALL _3DNow_VectorNormalize (Vector& vec); +void FASTCALL _3DNow_VectorNormalizeFast (Vector& vec); +float _3DNow_InvRSquared(const float* v); + +#endif // _3DNOW_H diff --git a/mathlib/IceKey.cpp b/mathlib/IceKey.cpp new file mode 100644 index 00000000..3d1566a8 --- /dev/null +++ b/mathlib/IceKey.cpp @@ -0,0 +1,394 @@ +// Purpose: C++ implementation of the ICE encryption algorithm. +// Taken from public domain code, as written by Matthew Kwan - July 1996 +// http://www.darkside.com.au/ice/ + +#if !defined(_STATIC_LINKED) || defined(_SHARED_LIB) + +#include "mathlib/IceKey.H" + +#ifdef _MSC_VER +#pragma warning(disable: 4244) +#endif + + /* Structure of a single round subkey */ +class IceSubkey { + public: + unsigned long val[3]; +}; + + + /* The S-boxes */ +static unsigned long ice_sbox[4][1024]; +static int ice_sboxes_initialised = 0; + + + /* Modulo values for the S-boxes */ +static const int ice_smod[4][4] = { + {333, 313, 505, 369}, + {379, 375, 319, 391}, + {361, 445, 451, 397}, + {397, 425, 395, 505}}; + + /* XOR values for the S-boxes */ +static const int ice_sxor[4][4] = { + {0x83, 0x85, 0x9b, 0xcd}, + {0xcc, 0xa7, 0xad, 0x41}, + {0x4b, 0x2e, 0xd4, 0x33}, + {0xea, 0xcb, 0x2e, 0x04}}; + + /* Permutation values for the P-box */ +static const unsigned long ice_pbox[32] = { + 0x00000001, 0x00000080, 0x00000400, 0x00002000, + 0x00080000, 0x00200000, 0x01000000, 0x40000000, + 0x00000008, 0x00000020, 0x00000100, 0x00004000, + 0x00010000, 0x00800000, 0x04000000, 0x20000000, + 0x00000004, 0x00000010, 0x00000200, 0x00008000, + 0x00020000, 0x00400000, 0x08000000, 0x10000000, + 0x00000002, 0x00000040, 0x00000800, 0x00001000, + 0x00040000, 0x00100000, 0x02000000, 0x80000000}; + + /* The key rotation schedule */ +static const int ice_keyrot[16] = { + 0, 1, 2, 3, 2, 1, 3, 0, + 1, 3, 2, 0, 3, 1, 0, 2}; + + +/* + * 8-bit Galois Field multiplication of a by b, modulo m. + * Just like arithmetic multiplication, except that additions and + * subtractions are replaced by XOR. + */ + +static unsigned int +gf_mult ( + register unsigned int a, + register unsigned int b, + register unsigned int m +) { + register unsigned int res = 0; + + while (b) { + if (b & 1) + res ^= a; + + a <<= 1; + b >>= 1; + + if (a >= 256) + a ^= m; + } + + return (res); +} + + +/* + * Galois Field exponentiation. + * Raise the base to the power of 7, modulo m. + */ + +static unsigned long +gf_exp7 ( + register unsigned int b, + unsigned int m +) { + register unsigned int x; + + if (b == 0) + return (0); + + x = gf_mult (b, b, m); + x = gf_mult (b, x, m); + x = gf_mult (x, x, m); + return (gf_mult (b, x, m)); +} + + +/* + * Carry out the ICE 32-bit P-box permutation. + */ + +static unsigned long +ice_perm32 ( + register unsigned long x +) { + register unsigned long res = 0; + register const unsigned long *pbox = ice_pbox; + + while (x) { + if (x & 1) + res |= *pbox; + pbox++; + x >>= 1; + } + + return (res); +} + + +/* + * Initialise the ICE S-boxes. + * This only has to be done once. + */ + +static void +ice_sboxes_init (void) +{ + register int i; + + for (i=0; i<1024; i++) { + int col = (i >> 1) & 0xff; + int row = (i & 0x1) | ((i & 0x200) >> 8); + unsigned long x; + + x = gf_exp7 (col ^ ice_sxor[0][row], ice_smod[0][row]) << 24; + ice_sbox[0][i] = ice_perm32 (x); + + x = gf_exp7 (col ^ ice_sxor[1][row], ice_smod[1][row]) << 16; + ice_sbox[1][i] = ice_perm32 (x); + + x = gf_exp7 (col ^ ice_sxor[2][row], ice_smod[2][row]) << 8; + ice_sbox[2][i] = ice_perm32 (x); + + x = gf_exp7 (col ^ ice_sxor[3][row], ice_smod[3][row]); + ice_sbox[3][i] = ice_perm32 (x); + } +} + + +/* + * Create a new ICE key. + */ + +IceKey::IceKey (int n) +{ + if (!ice_sboxes_initialised) { + ice_sboxes_init (); + ice_sboxes_initialised = 1; + } + + if (n < 1) { + _size = 1; + _rounds = 8; + } else { + _size = n; + _rounds = n * 16; + } + + _keysched = new IceSubkey[_rounds]; +} + + +/* + * Destroy an ICE key. + */ + +IceKey::~IceKey () +{ + int i, j; + + for (i=0; i<_rounds; i++) + for (j=0; j<3; j++) + _keysched[i].val[j] = 0; + + _rounds = _size = 0; + + delete[] _keysched; +} + + +/* + * The single round ICE f function. + */ + +static unsigned long +ice_f ( + register unsigned long p, + const IceSubkey *sk +) { + unsigned long tl, tr; /* Expanded 40-bit values */ + unsigned long al, ar; /* Salted expanded 40-bit values */ + + /* Left half expansion */ + tl = ((p >> 16) & 0x3ff) | (((p >> 14) | (p << 18)) & 0xffc00); + + /* Right half expansion */ + tr = (p & 0x3ff) | ((p << 2) & 0xffc00); + + /* Perform the salt permutation */ + // al = (tr & sk->val[2]) | (tl & ~sk->val[2]); + // ar = (tl & sk->val[2]) | (tr & ~sk->val[2]); + al = sk->val[2] & (tl ^ tr); + ar = al ^ tr; + al ^= tl; + + al ^= sk->val[0]; /* XOR with the subkey */ + ar ^= sk->val[1]; + + /* S-box lookup and permutation */ + return (ice_sbox[0][al >> 10] | ice_sbox[1][al & 0x3ff] + | ice_sbox[2][ar >> 10] | ice_sbox[3][ar & 0x3ff]); +} + + +/* + * Encrypt a block of 8 bytes of data with the given ICE key. + */ + +void +IceKey::encrypt ( + const unsigned char *ptext, + unsigned char *ctext +) const +{ + register int i; + register unsigned long l, r; + + l = (((unsigned long) ptext[0]) << 24) + | (((unsigned long) ptext[1]) << 16) + | (((unsigned long) ptext[2]) << 8) | ptext[3]; + r = (((unsigned long) ptext[4]) << 24) + | (((unsigned long) ptext[5]) << 16) + | (((unsigned long) ptext[6]) << 8) | ptext[7]; + + for (i = 0; i < _rounds; i += 2) { + l ^= ice_f (r, &_keysched[i]); + r ^= ice_f (l, &_keysched[i + 1]); + } + + for (i = 0; i < 4; i++) { + ctext[3 - i] = r & 0xff; + ctext[7 - i] = l & 0xff; + + r >>= 8; + l >>= 8; + } +} + + +/* + * Decrypt a block of 8 bytes of data with the given ICE key. + */ + +void +IceKey::decrypt ( + const unsigned char *ctext, + unsigned char *ptext +) const +{ + register int i; + register unsigned long l, r; + + l = (((unsigned long) ctext[0]) << 24) + | (((unsigned long) ctext[1]) << 16) + | (((unsigned long) ctext[2]) << 8) | ctext[3]; + r = (((unsigned long) ctext[4]) << 24) + | (((unsigned long) ctext[5]) << 16) + | (((unsigned long) ctext[6]) << 8) | ctext[7]; + + for (i = _rounds - 1; i > 0; i -= 2) { + l ^= ice_f (r, &_keysched[i]); + r ^= ice_f (l, &_keysched[i - 1]); + } + + for (i = 0; i < 4; i++) { + ptext[3 - i] = r & 0xff; + ptext[7 - i] = l & 0xff; + + r >>= 8; + l >>= 8; + } +} + + +/* + * Set 8 rounds [n, n+7] of the key schedule of an ICE key. + */ + +void +IceKey::scheduleBuild ( + unsigned short *kb, + int n, + const int *keyrot +) { + int i; + + for (i=0; i<8; i++) { + register int j; + register int kr = keyrot[i]; + IceSubkey *isk = &_keysched[n + i]; + + for (j=0; j<3; j++) + isk->val[j] = 0; + + for (j=0; j<15; j++) { + register int k; + unsigned long *curr_sk = &isk->val[j % 3]; + + for (k=0; k<4; k++) { + unsigned short *curr_kb = &kb[(kr + k) & 3]; + register int bit = *curr_kb & 1; + + *curr_sk = (*curr_sk << 1) | bit; + *curr_kb = (*curr_kb >> 1) | ((bit ^ 1) << 15); + } + } + } +} + + +/* + * Set the key schedule of an ICE key. + */ + +void +IceKey::set ( + const unsigned char *key +) { + int i; + + if (_rounds == 8) { + unsigned short kb[4]; + + for (i=0; i<4; i++) + kb[3 - i] = (key[i*2] << 8) | key[i*2 + 1]; + + scheduleBuild (kb, 0, ice_keyrot); + return; + } + + for (i=0; i<_size; i++) { + int j; + unsigned short kb[4]; + + for (j=0; j<4; j++) + kb[3 - j] = (key[i*8 + j*2] << 8) | key[i*8 + j*2 + 1]; + + scheduleBuild (kb, i*8, ice_keyrot); + scheduleBuild (kb, _rounds - 8 - i*8, &ice_keyrot[8]); + } +} + + +/* + * Return the key size, in bytes. + */ + +int +IceKey::keySize () const +{ + return (_size * 8); +} + + +/* + * Return the block size, in bytes. + */ + +int +IceKey::blockSize () const +{ + return (8); +} + +#endif // !_STATIC_LINKED || _SHARED_LIB diff --git a/mathlib/anorms.cpp b/mathlib/anorms.cpp new file mode 100644 index 00000000..443bd58f --- /dev/null +++ b/mathlib/anorms.cpp @@ -0,0 +1,181 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#if !defined(_STATIC_LINKED) || defined(_SHARED_LIB) + + +#include "mathlib/vector.h" +#include "mathlib/anorms.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +Vector g_anorms[NUMVERTEXNORMALS] = +{ + Vector(-0.525731, 0.000000, 0.850651), + Vector(-0.442863, 0.238856, 0.864188), + Vector(-0.295242, 0.000000, 0.955423), + Vector(-0.309017, 0.500000, 0.809017), + Vector(-0.162460, 0.262866, 0.951056), + Vector(0.000000, 0.000000, 1.000000), + Vector(0.000000, 0.850651, 0.525731), + Vector(-0.147621, 0.716567, 0.681718), + Vector(0.147621, 0.716567, 0.681718), + Vector(0.000000, 0.525731, 0.850651), + Vector(0.309017, 0.500000, 0.809017), + Vector(0.525731, 0.000000, 0.850651), + Vector(0.295242, 0.000000, 0.955423), + Vector(0.442863, 0.238856, 0.864188), + Vector(0.162460, 0.262866, 0.951056), + Vector(-0.681718, 0.147621, 0.716567), + Vector(-0.809017, 0.309017, 0.500000), + Vector(-0.587785, 0.425325, 0.688191), + Vector(-0.850651, 0.525731, 0.000000), + Vector(-0.864188, 0.442863, 0.238856), + Vector(-0.716567, 0.681718, 0.147621), + Vector(-0.688191, 0.587785, 0.425325), + Vector(-0.500000, 0.809017, 0.309017), + Vector(-0.238856, 0.864188, 0.442863), + Vector(-0.425325, 0.688191, 0.587785), + Vector(-0.716567, 0.681718, -0.147621), + Vector(-0.500000, 0.809017, -0.309017), + Vector(-0.525731, 0.850651, 0.000000), + Vector(0.000000, 0.850651, -0.525731), + Vector(-0.238856, 0.864188, -0.442863), + Vector(0.000000, 0.955423, -0.295242), + Vector(-0.262866, 0.951056, -0.162460), + Vector(0.000000, 1.000000, 0.000000), + Vector(0.000000, 0.955423, 0.295242), + Vector(-0.262866, 0.951056, 0.162460), + Vector(0.238856, 0.864188, 0.442863), + Vector(0.262866, 0.951056, 0.162460), + Vector(0.500000, 0.809017, 0.309017), + Vector(0.238856, 0.864188, -0.442863), + Vector(0.262866, 0.951056, -0.162460), + Vector(0.500000, 0.809017, -0.309017), + Vector(0.850651, 0.525731, 0.000000), + Vector(0.716567, 0.681718, 0.147621), + Vector(0.716567, 0.681718, -0.147621), + Vector(0.525731, 0.850651, 0.000000), + Vector(0.425325, 0.688191, 0.587785), + Vector(0.864188, 0.442863, 0.238856), + Vector(0.688191, 0.587785, 0.425325), + Vector(0.809017, 0.309017, 0.500000), + Vector(0.681718, 0.147621, 0.716567), + Vector(0.587785, 0.425325, 0.688191), + Vector(0.955423, 0.295242, 0.000000), + Vector(1.000000, 0.000000, 0.000000), + Vector(0.951056, 0.162460, 0.262866), + Vector(0.850651, -0.525731, 0.000000), + Vector(0.955423, -0.295242, 0.000000), + Vector(0.864188, -0.442863, 0.238856), + Vector(0.951056, -0.162460, 0.262866), + Vector(0.809017, -0.309017, 0.500000), + Vector(0.681718, -0.147621, 0.716567), + Vector(0.850651, 0.000000, 0.525731), + Vector(0.864188, 0.442863, -0.238856), + Vector(0.809017, 0.309017, -0.500000), + Vector(0.951056, 0.162460, -0.262866), + Vector(0.525731, 0.000000, -0.850651), + Vector(0.681718, 0.147621, -0.716567), + Vector(0.681718, -0.147621, -0.716567), + Vector(0.850651, 0.000000, -0.525731), + Vector(0.809017, -0.309017, -0.500000), + Vector(0.864188, -0.442863, -0.238856), + Vector(0.951056, -0.162460, -0.262866), + Vector(0.147621, 0.716567, -0.681718), + Vector(0.309017, 0.500000, -0.809017), + Vector(0.425325, 0.688191, -0.587785), + Vector(0.442863, 0.238856, -0.864188), + Vector(0.587785, 0.425325, -0.688191), + Vector(0.688191, 0.587785, -0.425325), + Vector(-0.147621, 0.716567, -0.681718), + Vector(-0.309017, 0.500000, -0.809017), + Vector(0.000000, 0.525731, -0.850651), + Vector(-0.525731, 0.000000, -0.850651), + Vector(-0.442863, 0.238856, -0.864188), + Vector(-0.295242, 0.000000, -0.955423), + Vector(-0.162460, 0.262866, -0.951056), + Vector(0.000000, 0.000000, -1.000000), + Vector(0.295242, 0.000000, -0.955423), + Vector(0.162460, 0.262866, -0.951056), + Vector(-0.442863, -0.238856, -0.864188), + Vector(-0.309017, -0.500000, -0.809017), + Vector(-0.162460, -0.262866, -0.951056), + Vector(0.000000, -0.850651, -0.525731), + Vector(-0.147621, -0.716567, -0.681718), + Vector(0.147621, -0.716567, -0.681718), + Vector(0.000000, -0.525731, -0.850651), + Vector(0.309017, -0.500000, -0.809017), + Vector(0.442863, -0.238856, -0.864188), + Vector(0.162460, -0.262866, -0.951056), + Vector(0.238856, -0.864188, -0.442863), + Vector(0.500000, -0.809017, -0.309017), + Vector(0.425325, -0.688191, -0.587785), + Vector(0.716567, -0.681718, -0.147621), + Vector(0.688191, -0.587785, -0.425325), + Vector(0.587785, -0.425325, -0.688191), + Vector(0.000000, -0.955423, -0.295242), + Vector(0.000000, -1.000000, 0.000000), + Vector(0.262866, -0.951056, -0.162460), + Vector(0.000000, -0.850651, 0.525731), + Vector(0.000000, -0.955423, 0.295242), + Vector(0.238856, -0.864188, 0.442863), + Vector(0.262866, -0.951056, 0.162460), + Vector(0.500000, -0.809017, 0.309017), + Vector(0.716567, -0.681718, 0.147621), + Vector(0.525731, -0.850651, 0.000000), + Vector(-0.238856, -0.864188, -0.442863), + Vector(-0.500000, -0.809017, -0.309017), + Vector(-0.262866, -0.951056, -0.162460), + Vector(-0.850651, -0.525731, 0.000000), + Vector(-0.716567, -0.681718, -0.147621), + Vector(-0.716567, -0.681718, 0.147621), + Vector(-0.525731, -0.850651, 0.000000), + Vector(-0.500000, -0.809017, 0.309017), + Vector(-0.238856, -0.864188, 0.442863), + Vector(-0.262866, -0.951056, 0.162460), + Vector(-0.864188, -0.442863, 0.238856), + Vector(-0.809017, -0.309017, 0.500000), + Vector(-0.688191, -0.587785, 0.425325), + Vector(-0.681718, -0.147621, 0.716567), + Vector(-0.442863, -0.238856, 0.864188), + Vector(-0.587785, -0.425325, 0.688191), + Vector(-0.309017, -0.500000, 0.809017), + Vector(-0.147621, -0.716567, 0.681718), + Vector(-0.425325, -0.688191, 0.587785), + Vector(-0.162460, -0.262866, 0.951056), + Vector(0.442863, -0.238856, 0.864188), + Vector(0.162460, -0.262866, 0.951056), + Vector(0.309017, -0.500000, 0.809017), + Vector(0.147621, -0.716567, 0.681718), + Vector(0.000000, -0.525731, 0.850651), + Vector(0.425325, -0.688191, 0.587785), + Vector(0.587785, -0.425325, 0.688191), + Vector(0.688191, -0.587785, 0.425325), + Vector(-0.955423, 0.295242, 0.000000), + Vector(-0.951056, 0.162460, 0.262866), + Vector(-1.000000, 0.000000, 0.000000), + Vector(-0.850651, 0.000000, 0.525731), + Vector(-0.955423, -0.295242, 0.000000), + Vector(-0.951056, -0.162460, 0.262866), + Vector(-0.864188, 0.442863, -0.238856), + Vector(-0.951056, 0.162460, -0.262866), + Vector(-0.809017, 0.309017, -0.500000), + Vector(-0.864188, -0.442863, -0.238856), + Vector(-0.951056, -0.162460, -0.262866), + Vector(-0.809017, -0.309017, -0.500000), + Vector(-0.681718, 0.147621, -0.716567), + Vector(-0.681718, -0.147621, -0.716567), + Vector(-0.850651, 0.000000, -0.525731), + Vector(-0.688191, 0.587785, -0.425325), + Vector(-0.587785, 0.425325, -0.688191), + Vector(-0.425325, 0.688191, -0.587785), + Vector(-0.425325, -0.688191, -0.587785), + Vector(-0.587785, -0.425325, -0.688191), + Vector(-0.688191, -0.587785, -0.425325) +}; + +#endif // !_STATIC_LINKED || _SHARED_LIB diff --git a/mathlib/bumpvects.cpp b/mathlib/bumpvects.cpp new file mode 100644 index 00000000..e5099c6f --- /dev/null +++ b/mathlib/bumpvects.cpp @@ -0,0 +1,69 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#if !defined(_STATIC_LINKED) || defined(_SHARED_LIB) + + +#ifdef QUIVER +#include "r_local.h" +#endif +#include "mathlib/bumpvects.h" +#include "mathlib/vector.h" +#include + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// z is coming out of the face. + +void GetBumpNormals( const Vector& sVect, const Vector& tVect, const Vector& flatNormal, + const Vector& phongNormal, Vector bumpNormals[NUM_BUMP_VECTS] ) +{ + Vector tmpNormal; + bool leftHanded; + int i; + + assert( NUM_BUMP_VECTS == 3 ); + + // Are we left or right handed? + CrossProduct( sVect, tVect, tmpNormal ); + if( DotProduct( flatNormal, tmpNormal ) < 0.0f ) + { + leftHanded = true; + } + else + { + leftHanded = false; + } + + // Build a basis for the face around the phong normal + matrix3x4_t smoothBasis; + CrossProduct( phongNormal.Base(), sVect.Base(), smoothBasis[1] ); + VectorNormalize( smoothBasis[1] ); + CrossProduct( smoothBasis[1], phongNormal.Base(), smoothBasis[0] ); + VectorNormalize( smoothBasis[0] ); + VectorCopy( phongNormal.Base(), smoothBasis[2] ); + + if( leftHanded ) + { + VectorNegate( smoothBasis[1] ); + } + + // move the g_localBumpBasis into world space to create bumpNormals + for( i = 0; i < 3; i++ ) + { + VectorIRotate( g_localBumpBasis[i], smoothBasis, bumpNormals[i] ); + } +} + +#endif // !_STATIC_LINKED || _SHARED_LIB diff --git a/mathlib/color_conversion.cpp b/mathlib/color_conversion.cpp new file mode 100644 index 00000000..c29d59ca --- /dev/null +++ b/mathlib/color_conversion.cpp @@ -0,0 +1,645 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Color conversion routines. +// +//=====================================================================================// + +#include +#include // Needed for FLT_EPSILON +#include "basetypes.h" +#include +#include "tier0/dbg.h" +#include "mathlib/mathlib.h" +#include "mathlib/vector.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Gamma conversion support +//----------------------------------------------------------------------------- +static byte texgammatable[256]; // palette is sent through this to convert to screen gamma + +static float texturetolinear[256]; // texture (0..255) to linear (0..1) +static int lineartotexture[1024]; // linear (0..1) to texture (0..255) +static int lineartoscreen[1024]; // linear (0..1) to gamma corrected vertex light (0..255) + +// build a lightmap texture to combine with surface texture, adjust for src*dst+dst*src, ramp reprogramming, etc +float lineartovertex[4096]; // linear (0..4) to screen corrected vertex space (0..1?) +unsigned char lineartolightmap[4096]; // linear (0..4) to screen corrected texture value (0..255) + +static float g_Mathlib_GammaToLinear[256]; // gamma (0..1) to linear (0..1) +static float g_Mathlib_LinearToGamma[256]; // linear (0..1) to gamma (0..1) + +// This is aligned to 16-byte boundaries so that we can load it +// onto SIMD registers easily if needed (used by SSE version of lightmaps) +// TODO: move this into the one DLL that actually uses it, instead of statically +// linking it everywhere via mathlib. +ALIGN128 float power2_n[256] = // 2**(index - 128) / 255 +{ + 1.152445441982634800E-041, 2.304890883965269600E-041, 4.609781767930539200E-041, 9.219563535861078400E-041, + 1.843912707172215700E-040, 3.687825414344431300E-040, 7.375650828688862700E-040, 1.475130165737772500E-039, + 2.950260331475545100E-039, 5.900520662951090200E-039, 1.180104132590218000E-038, 2.360208265180436100E-038, + 4.720416530360872100E-038, 9.440833060721744200E-038, 1.888166612144348800E-037, 3.776333224288697700E-037, + 7.552666448577395400E-037, 1.510533289715479100E-036, 3.021066579430958200E-036, 6.042133158861916300E-036, + 1.208426631772383300E-035, 2.416853263544766500E-035, 4.833706527089533100E-035, 9.667413054179066100E-035, + 1.933482610835813200E-034, 3.866965221671626400E-034, 7.733930443343252900E-034, 1.546786088668650600E-033, + 3.093572177337301200E-033, 6.187144354674602300E-033, 1.237428870934920500E-032, 2.474857741869840900E-032, + 4.949715483739681800E-032, 9.899430967479363700E-032, 1.979886193495872700E-031, 3.959772386991745500E-031, + 7.919544773983491000E-031, 1.583908954796698200E-030, 3.167817909593396400E-030, 6.335635819186792800E-030, + 1.267127163837358600E-029, 2.534254327674717100E-029, 5.068508655349434200E-029, 1.013701731069886800E-028, + 2.027403462139773700E-028, 4.054806924279547400E-028, 8.109613848559094700E-028, 1.621922769711818900E-027, + 3.243845539423637900E-027, 6.487691078847275800E-027, 1.297538215769455200E-026, 2.595076431538910300E-026, + 5.190152863077820600E-026, 1.038030572615564100E-025, 2.076061145231128300E-025, 4.152122290462256500E-025, + 8.304244580924513000E-025, 1.660848916184902600E-024, 3.321697832369805200E-024, 6.643395664739610400E-024, + 1.328679132947922100E-023, 2.657358265895844200E-023, 5.314716531791688300E-023, 1.062943306358337700E-022, + 2.125886612716675300E-022, 4.251773225433350700E-022, 8.503546450866701300E-022, 1.700709290173340300E-021, + 3.401418580346680500E-021, 6.802837160693361100E-021, 1.360567432138672200E-020, 2.721134864277344400E-020, + 5.442269728554688800E-020, 1.088453945710937800E-019, 2.176907891421875500E-019, 4.353815782843751100E-019, + 8.707631565687502200E-019, 1.741526313137500400E-018, 3.483052626275000900E-018, 6.966105252550001700E-018, + 1.393221050510000300E-017, 2.786442101020000700E-017, 5.572884202040001400E-017, 1.114576840408000300E-016, + 2.229153680816000600E-016, 4.458307361632001100E-016, 8.916614723264002200E-016, 1.783322944652800400E-015, + 3.566645889305600900E-015, 7.133291778611201800E-015, 1.426658355722240400E-014, 2.853316711444480700E-014, + 5.706633422888961400E-014, 1.141326684577792300E-013, 2.282653369155584600E-013, 4.565306738311169100E-013, + 9.130613476622338300E-013, 1.826122695324467700E-012, 3.652245390648935300E-012, 7.304490781297870600E-012, + 1.460898156259574100E-011, 2.921796312519148200E-011, 5.843592625038296500E-011, 1.168718525007659300E-010, + 2.337437050015318600E-010, 4.674874100030637200E-010, 9.349748200061274400E-010, 1.869949640012254900E-009, + 3.739899280024509800E-009, 7.479798560049019500E-009, 1.495959712009803900E-008, 2.991919424019607800E-008, + 5.983838848039215600E-008, 1.196767769607843100E-007, 2.393535539215686200E-007, 4.787071078431372500E-007, + 9.574142156862745000E-007, 1.914828431372549000E-006, 3.829656862745098000E-006, 7.659313725490196000E-006, + 1.531862745098039200E-005, 3.063725490196078400E-005, 6.127450980392156800E-005, 1.225490196078431400E-004, + 2.450980392156862700E-004, 4.901960784313725400E-004, 9.803921568627450800E-004, 1.960784313725490200E-003, + 3.921568627450980300E-003, 7.843137254901960700E-003, 1.568627450980392100E-002, 3.137254901960784300E-002, + 6.274509803921568500E-002, 1.254901960784313700E-001, 2.509803921568627400E-001, 5.019607843137254800E-001, + 1.003921568627451000E+000, 2.007843137254901900E+000, 4.015686274509803900E+000, 8.031372549019607700E+000, + 1.606274509803921500E+001, 3.212549019607843100E+001, 6.425098039215686200E+001, 1.285019607843137200E+002, + 2.570039215686274500E+002, 5.140078431372548900E+002, 1.028015686274509800E+003, 2.056031372549019600E+003, + 4.112062745098039200E+003, 8.224125490196078300E+003, 1.644825098039215700E+004, 3.289650196078431300E+004, + 6.579300392156862700E+004, 1.315860078431372500E+005, 2.631720156862745100E+005, 5.263440313725490100E+005, + 1.052688062745098000E+006, 2.105376125490196000E+006, 4.210752250980392100E+006, 8.421504501960784200E+006, + 1.684300900392156800E+007, 3.368601800784313700E+007, 6.737203601568627400E+007, 1.347440720313725500E+008, + 2.694881440627450900E+008, 5.389762881254901900E+008, 1.077952576250980400E+009, 2.155905152501960800E+009, + 4.311810305003921500E+009, 8.623620610007843000E+009, 1.724724122001568600E+010, 3.449448244003137200E+010, + 6.898896488006274400E+010, 1.379779297601254900E+011, 2.759558595202509800E+011, 5.519117190405019500E+011, + 1.103823438081003900E+012, 2.207646876162007800E+012, 4.415293752324015600E+012, 8.830587504648031200E+012, + 1.766117500929606200E+013, 3.532235001859212500E+013, 7.064470003718425000E+013, 1.412894000743685000E+014, + 2.825788001487370000E+014, 5.651576002974740000E+014, 1.130315200594948000E+015, 2.260630401189896000E+015, + 4.521260802379792000E+015, 9.042521604759584000E+015, 1.808504320951916800E+016, 3.617008641903833600E+016, + 7.234017283807667200E+016, 1.446803456761533400E+017, 2.893606913523066900E+017, 5.787213827046133800E+017, + 1.157442765409226800E+018, 2.314885530818453500E+018, 4.629771061636907000E+018, 9.259542123273814000E+018, + 1.851908424654762800E+019, 3.703816849309525600E+019, 7.407633698619051200E+019, 1.481526739723810200E+020, + 2.963053479447620500E+020, 5.926106958895241000E+020, 1.185221391779048200E+021, 2.370442783558096400E+021, + 4.740885567116192800E+021, 9.481771134232385600E+021, 1.896354226846477100E+022, 3.792708453692954200E+022, + 7.585416907385908400E+022, 1.517083381477181700E+023, 3.034166762954363400E+023, 6.068333525908726800E+023, + 1.213666705181745400E+024, 2.427333410363490700E+024, 4.854666820726981400E+024, 9.709333641453962800E+024, + 1.941866728290792600E+025, 3.883733456581585100E+025, 7.767466913163170200E+025, 1.553493382632634000E+026, + 3.106986765265268100E+026, 6.213973530530536200E+026, 1.242794706106107200E+027, 2.485589412212214500E+027, + 4.971178824424429000E+027, 9.942357648848857900E+027, 1.988471529769771600E+028, 3.976943059539543200E+028, + 7.953886119079086300E+028, 1.590777223815817300E+029, 3.181554447631634500E+029, 6.363108895263269100E+029, + 1.272621779052653800E+030, 2.545243558105307600E+030, 5.090487116210615300E+030, 1.018097423242123100E+031, + 2.036194846484246100E+031, 4.072389692968492200E+031, 8.144779385936984400E+031, 1.628955877187396900E+032, + 3.257911754374793800E+032, 6.515823508749587500E+032, 1.303164701749917500E+033, 2.606329403499835000E+033, + 5.212658806999670000E+033, 1.042531761399934000E+034, 2.085063522799868000E+034, 4.170127045599736000E+034, + 8.340254091199472000E+034, 1.668050818239894400E+035, 3.336101636479788800E+035, 6.672203272959577600E+035 +}; + +// You can use this to double check the exponent table and assert that +// the precomputation is correct. +#ifdef DBGFLAG_ASSERT +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning( disable : 4189 ) // disable unused local variable warning +#endif +#ifdef __GNUC__ +__attribute__((unused)) static void CheckExponentTable() +#else +static void CheckExponentTable() +#endif +{ + for( int i = 0; i < 256; i++ ) + { + float testAgainst = pow( 2.0f, i - 128 ) / 255.0f; + float diff = testAgainst - power2_n[i] ; + float relativeDiff = diff / testAgainst; + Assert( sizeof(relativeDiff) > 0 && testAgainst == 0 ? + power2_n[i] < 1.16E-041 : + power2_n[i] == testAgainst ); + } +} +#ifdef _MSC_VER +#pragma warning(pop) +#endif +#endif + +void BuildGammaTable( float gamma, float texGamma, float brightness, int overbright ) +{ + int i, inf; + float g1, g3; + + // Con_Printf("BuildGammaTable %.1f %.1f %.1f\n", g, v_lightgamma.GetFloat(), v_texgamma.GetFloat() ); + + float g = gamma; + if (g > 3.0) + { + g = 3.0; + } + + g = 1.0 / g; + g1 = texGamma * g; + + if (brightness <= 0.0) + { + g3 = 0.125; + } + else if (brightness > 1.0) + { + g3 = 0.05; + } + else + { + g3 = 0.125 - (brightness * brightness) * 0.075; + } + + for (i=0 ; i<256 ; i++) + { + inf = static_cast(255 * pow ( i/255.f, g1 )); + if (inf < 0) + inf = 0; + if (inf > 255) + inf = 255; + texgammatable[i] = inf; + } + + for (i=0 ; i<1024 ; i++) + { + float f; + + f = i / 1023.0; + + // scale up + if (brightness > 1.0) + f = f * brightness; + + // shift up + if (f <= g3) + f = (f / g3) * 0.125; + else + f = 0.125 + ((f - g3) / (1.0 - g3)) * 0.875; + + // convert linear space to desired gamma space + inf = static_cast(255 * pow ( f, g )); + + if (inf < 0) + inf = 0; + if (inf > 255) + inf = 255; + lineartoscreen[i] = inf; + } + + /* + for (i=0 ; i<1024 ; i++) + { + // convert from screen gamma space to linear space + lineargammatable[i] = 1023 * pow ( i/1023.0, v_gamma.GetFloat() ); + // convert from linear gamma space to screen space + screengammatable[i] = 1023 * pow ( i/1023.0, 1.0 / v_gamma.GetFloat() ); + } + */ + + for (i=0 ; i<256 ; i++) + { + // convert from nonlinear texture space (0..255) to linear space (0..1) + texturetolinear[i] = pow( i / 255.f, texGamma ); + + // convert from linear space (0..1) to nonlinear (sRGB) space (0..1) + g_Mathlib_LinearToGamma[i] = LinearToGammaFullRange( i / 255.f ); + + // convert from sRGB gamma space (0..1) to linear space (0..1) + g_Mathlib_GammaToLinear[i] = GammaToLinearFullRange( i / 255.f ); + } + + for (i=0 ; i<1024 ; i++) + { + // convert from linear space (0..1) to nonlinear texture space (0..255) + lineartotexture[i] = static_cast(pow( i / 1023.0, 1.0 / texGamma ) * 255); + } + +#if 0 + for (i=0 ; i<256 ; i++) + { + float f; + + // convert from nonlinear lightmap space (0..255) to linear space (0..4) + // f = (i / 255.0) * sqrt( 4 ); + f = i * (2.0 / 255.0); + f = f * f; + + texlighttolinear[i] = f; + } +#endif + + { + float f; + float overbrightFactor = 1.0f; + + // Can't do overbright without texcombine + // UNDONE: Add GAMMA ramp to rectify this + if ( overbright == 2 ) + { + overbrightFactor = 0.5; + } + else if ( overbright == 4 ) + { + overbrightFactor = 0.25; + } + + for (i=0 ; i<4096 ; i++) + { + // convert from linear 0..4 (x1024) to screen corrected vertex space (0..1?) + f = pow ( i/1024.0, 1.0 / gamma ); + + lineartovertex[i] = f * overbrightFactor; + if (lineartovertex[i] > 1) + lineartovertex[i] = 1; + + int nLightmap = RoundFloatToInt( f * 255 * overbrightFactor ); + nLightmap = clamp( nLightmap, 0, 255 ); + lineartolightmap[i] = (unsigned char)nLightmap; + } + } +} + +float GammaToLinearFullRange( float gamma ) +{ + return pow( gamma, 2.2f ); +} + +float LinearToGammaFullRange( float linear ) +{ + return pow( linear, 1.0f / 2.2f ); +} + +float GammaToLinear( float gamma ) +{ + Assert( s_bMathlibInitialized ); + if ( gamma < 0.0f ) + { + return 0.0f; + } + + if ( gamma >= 0.95f ) + { + // Use GammaToLinearFullRange maybe if you trip this. +// X360TEMP +// Assert( gamma <= 1.0f ); + return 1.0f; + } + + int index = RoundFloatToInt( gamma * 255.0f ); + Assert( index >= 0 && index < 256 ); + return g_Mathlib_GammaToLinear[index]; +} + +float LinearToGamma( float linear ) +{ + Assert( s_bMathlibInitialized ); + if ( linear < 0.0f ) + { + return 0.0f; + } + if ( linear > 1.0f ) + { + // Use LinearToGammaFullRange maybe if you trip this. + Assert( 0 ); + return 1.0f; + } + + int index = RoundFloatToInt( linear * 255.0f ); + Assert( index >= 0 && index < 256 ); + return g_Mathlib_LinearToGamma[index]; +} + +//----------------------------------------------------------------------------- +// Helper functions to convert between sRGB and 360 gamma space +//----------------------------------------------------------------------------- +float SrgbGammaToLinear( float flSrgbGammaValue ) +{ + float x = clamp( flSrgbGammaValue, 0.0f, 1.0f ); + return ( x <= 0.04045f ) ? ( x / 12.92f ) : ( pow( ( x + 0.055f ) / 1.055f, 2.4f ) ); +} + +float SrgbLinearToGamma( float flLinearValue ) +{ + float x = clamp( flLinearValue, 0.0f, 1.0f ); + return ( x <= 0.0031308f ) ? ( x * 12.92f ) : ( 1.055f * pow( x, ( 1.0f / 2.4f ) ) ) - 0.055f; +} + +float X360GammaToLinear( float fl360GammaValue ) +{ + float flLinearValue; + + fl360GammaValue = clamp( fl360GammaValue, 0.0f, 1.0f ); + if ( fl360GammaValue < ( 96.0f / 255.0f ) ) + { + if ( fl360GammaValue < ( 64.0f / 255.0f ) ) + { + flLinearValue = fl360GammaValue * 255.0f; + } + else + { + flLinearValue = fl360GammaValue * ( 255.0f * 2.0f ) - 64.0f; + flLinearValue += floor( flLinearValue * ( 1.0f / 512.0f ) ); + } + } + else + { + if( fl360GammaValue < ( 192.0f / 255.0f ) ) + { + flLinearValue = fl360GammaValue * ( 255.0f * 4.0f ) - 256.0f; + flLinearValue += floor( flLinearValue * ( 1.0f / 256.0f ) ); + } + else + { + flLinearValue = fl360GammaValue * ( 255.0f * 8.0f ) - 1024.0f; + flLinearValue += floor( flLinearValue * ( 1.0f / 128.0f ) ); + } + } + + flLinearValue *= 1.0f / 1023.0f; + + flLinearValue = clamp( flLinearValue, 0.0f, 1.0f ); + return flLinearValue; +} + +float X360LinearToGamma( float flLinearValue ) +{ + float fl360GammaValue; + + flLinearValue = clamp( flLinearValue, 0.0f, 1.0f ); + if ( flLinearValue < ( 128.0f / 1023.0f ) ) + { + if ( flLinearValue < ( 64.0f / 1023.0f ) ) + { + fl360GammaValue = flLinearValue * ( 1023.0f * ( 1.0f / 255.0f ) ); + } + else + { + fl360GammaValue = flLinearValue * ( ( 1023.0f / 2.0f ) * ( 1.0f / 255.0f ) ) + ( 32.0f / 255.0f ); + } + } + else + { + if ( flLinearValue < ( 512.0f / 1023.0f ) ) + { + fl360GammaValue = flLinearValue * ( ( 1023.0f / 4.0f ) * ( 1.0f / 255.0f ) ) + ( 64.0f / 255.0f ); + } + else + { + fl360GammaValue = flLinearValue * ( ( 1023.0f /8.0f ) * ( 1.0f / 255.0f ) ) + ( 128.0f /255.0f ); // 1.0 -> 1.0034313725490196078431372549016 + if ( fl360GammaValue > 1.0f ) + { + fl360GammaValue = 1.0f; + } + } + } + + fl360GammaValue = clamp( fl360GammaValue, 0.0f, 1.0f ); + return fl360GammaValue; +} + +float SrgbGammaTo360Gamma( float flSrgbGammaValue ) +{ + float flLinearValue = SrgbGammaToLinear( flSrgbGammaValue ); + float fl360GammaValue = X360LinearToGamma( flLinearValue ); + return fl360GammaValue; +} + +// convert texture to linear 0..1 value +float TextureToLinear( int c ) +{ + Assert( s_bMathlibInitialized ); + if (c < 0) + return 0; + if (c > 255) + return 1.0; + + return texturetolinear[c]; +} + +// convert texture to linear 0..1 value +int LinearToTexture( float f ) +{ + Assert( s_bMathlibInitialized ); + int i; + i = static_cast(f * 1023); // assume 0..1 range + if (i < 0) + i = 0; + if (i > 1023) + i = 1023; + + return lineartotexture[i]; +} + + +// converts 0..1 linear value to screen gamma (0..255) +int LinearToScreenGamma( float f ) +{ + Assert( s_bMathlibInitialized ); + int i; + i = static_cast(f * 1023); // assume 0..1 range + if (i < 0) + i = 0; + if (i > 1023) + i = 1023; + + return lineartoscreen[i]; +} + +void ColorRGBExp32ToVector( const ColorRGBExp32& in, Vector& out ) +{ + Assert( s_bMathlibInitialized ); + // FIXME: Why is there a factor of 255 built into this? + out.x = 255.0f * TexLightToLinear( in.r, in.exponent ); + out.y = 255.0f * TexLightToLinear( in.g, in.exponent ); + out.z = 255.0f * TexLightToLinear( in.b, in.exponent ); +} + +#if 0 +// assumes that the desired mantissa range is 128..255 +static int VectorToColorRGBExp32_CalcExponent( float in ) +{ + int power = 0; + + if( in != 0.0f ) + { + while( in > 255.0f ) + { + power += 1; + in *= 0.5f; + } + + while( in < 128.0f ) + { + power -= 1; + in *= 2.0f; + } + } + + return power; +} + +void VectorToColorRGBExp32( const Vector& vin, ColorRGBExp32 &c ) +{ + Vector v = vin; + Assert( s_bMathlibInitialized ); + Assert( v.x >= 0.0f && v.y >= 0.0f && v.z >= 0.0f ); + int i; + float max = v[0]; + for( i = 1; i < 3; i++ ) + { + // Get the maximum value. + if( v[i] > max ) + { + max = v[i]; + } + } + + // figure out the exponent for this luxel. + int exponent = VectorToColorRGBExp32_CalcExponent( max ); + + // make the exponent fits into a signed byte. + if( exponent < -128 ) + { + exponent = -128; + } + else if( exponent > 127 ) + { + exponent = 127; + } + + // undone: optimize with a table + float scalar = pow( 2.0f, -exponent ); + // convert to mantissa x 2^exponent format + for( i = 0; i < 3; i++ ) + { + v[i] *= scalar; + // clamp + if( v[i] > 255.0f ) + { + v[i] = 255.0f; + } + } + c.r = ( unsigned char )v[0]; + c.g = ( unsigned char )v[1]; + c.b = ( unsigned char )v[2]; + c.exponent = ( signed char )exponent; +} + +#else + +// given a floating point number f, return an exponent e such that +// for f' = f * 2^e, f is on [128..255]. +// Uses IEEE 754 representation to directly extract this information +// from the float. +inline static int VectorToColorRGBExp32_CalcExponent( const float *pin ) +{ + // The thing we will take advantage of here is that the exponent component + // is stored in the float itself, and because we want to map to 128..255, we + // want an "ideal" exponent of 2^7. So, we compute the difference between the + // input exponent and 7 to work out the normalizing exponent. Thus if you pass in + // 32 (represented in IEEE 754 as 2^5), this function will return 2 + // (because 32 * 2^2 = 128) + if (*pin == 0.0f) + return 0; + + unsigned int fbits = *reinterpret_cast(pin); + + // the exponent component is bits 23..30, and biased by +127 + const unsigned int biasedSeven = 7 + 127; + + signed int expComponent = ( fbits & 0x7F800000 ) >> 23; + expComponent -= biasedSeven; // now the difference from seven (positive if was less than, etc) + return expComponent; +} + + + +/// Slightly faster version of the function to turn a float-vector color into +/// a compressed-exponent notation 32bit color. However, still not SIMD optimized. +/// PS3 developer: note there is a movement of a float onto an int here, which is +/// bad on the base registers -- consider doing this as Altivec code, or better yet +/// moving it onto the cell. +/// \warning: Assumes an IEEE 754 single-precision float representation! Those of you +/// porting to an 8080 are out of luck. +void VectorToColorRGBExp32( const Vector& vin, ColorRGBExp32 &c ) +{ + Assert( s_bMathlibInitialized ); + Assert( vin.x >= 0.0f && vin.y >= 0.0f && vin.z >= 0.0f ); + + // work out which of the channels is the largest ( we will use that to map the exponent ) + // this is a sluggish branch-based decision tree -- most architectures will offer a [max] + // assembly opcode to do this faster. + const float *pMax; + if (vin.x > vin.y) + { + if (vin.x > vin.z) + { + pMax = &vin.x; + } + else + { + pMax = &vin.z; + } + } + else + { + if (vin.y > vin.z) + { + pMax = &vin.y; + } + else + { + pMax = &vin.z; + } + } + + // now work out the exponent for this luxel. + signed int exponent = VectorToColorRGBExp32_CalcExponent( pMax ); + + // make sure the exponent fits into a signed byte. + // (in single precision format this is assured because it was a signed byte to begin with) + Assert(exponent > -128 && exponent <= 127); + + // promote the exponent back onto a scalar that we'll use to normalize all the numbers + float scalar; + { + unsigned int fbits = (127 - exponent) << 23; + scalar = *reinterpret_cast(&fbits); + } + + // we should never need to clamp: + Assert(vin.x * scalar <= 255.0f && + vin.y * scalar <= 255.0f && + vin.z * scalar <= 255.0f); + + // This awful construction is necessary to prevent VC2005 from using the + // fldcw/fnstcw control words around every float-to-unsigned-char operation. + { + int red = static_cast(vin.x * scalar); + int green = static_cast(vin.y * scalar); + int blue = static_cast(vin.z * scalar); + + c.r = red; + c.g = green; + c.b = blue; + } + /* + c.r = ( unsigned char )(vin.x * scalar); + c.g = ( unsigned char )(vin.y * scalar); + c.b = ( unsigned char )(vin.z * scalar); + */ + + c.exponent = ( signed char )exponent; +} + +#endif diff --git a/mathlib/datagen.pl b/mathlib/datagen.pl new file mode 100644 index 00000000..9e434034 --- /dev/null +++ b/mathlib/datagen.pl @@ -0,0 +1,63 @@ +#! perl +use Text::Wrap; + +# generate output data for noise generators + +srand(31456); + +print < + +HaltonSequenceGenerator_t::HaltonSequenceGenerator_t(int b) +{ + base=b; + fbase=(float) b; + seed=1; + +} + +float HaltonSequenceGenerator_t::GetElement(int elem) +{ + int tmpseed=seed; + float ret=0.0; + float base_inv=1.0/fbase; + while(tmpseed) + { + int dig=tmpseed % base; + ret+=((float) dig)*base_inv; + base_inv/=fbase; + tmpseed/=base; + } + return ret; +} diff --git a/mathlib/imagequant.cpp b/mathlib/imagequant.cpp new file mode 100644 index 00000000..bdba52a6 --- /dev/null +++ b/mathlib/imagequant.cpp @@ -0,0 +1,95 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include + +#define N_EXTRAVALUES 1 +#define N_DIMENSIONS (3+N_EXTRAVALUES) + +#define PIXEL(x,y,c) Image[4*((x)+((Width*(y))))+c] + +static uint8 Weights[]={5,7,4,8}; +static int ExtraValueXForms[3*N_EXTRAVALUES]={ + 76,151,28, +}; + + + +#define MAX_QUANTIZE_IMAGE_WIDTH 4096 + +void ColorQuantize(uint8 const *Image, + int Width, + int Height, + int flags, int ncolors, + uint8 *out_pixels, + uint8 *out_palette, + int firstcolor) +{ + int Error[MAX_QUANTIZE_IMAGE_WIDTH+1][3][2]; + struct Sample *s=AllocSamples(Width*Height,N_DIMENSIONS); + int x,y,c; + for(y=0;yValue[c]=PIXEL(x,y,c); + // now, let's generate extra values to quantize on + for(int i=0;i>=8; + NthSample(s,y*Width+x,N_DIMENSIONS)->Value[c]=(uint8) + (MIN(255,MAX(0,val1))); + } + } + struct QuantizedValue *q=Quantize(s,Width*Height,N_DIMENSIONS, + ncolors,Weights,firstcolor); + delete[] s; + memset(out_palette,0x55,768); + for(int p=0;p<256;p++) + { + struct QuantizedValue *v=FindQNode(q,p); + if (v) + for(int c=0;c<3;c++) + out_palette[p*3+c]=v->Mean[c]; + } + memset(Error,0,sizeof(Error)); + for(y=0;yvalue); + if (! (flags & QUANTFLAGS_NODITHER)) + for(int i=0;i<3;i++) + { + int newerr=samp[i]-f->Mean[i]; + int orthog_error=(newerr*3)/8; + Error[x+1][i][ErrorUse]+=orthog_error; + Error[x][i][ErrorUpdate]=orthog_error; + Error[x+1][i][ErrorUpdate]=newerr-2*orthog_error; + } + } + } + if (q) FreeQuantization(q); +} + diff --git a/mathlib/lightdesc.cpp b/mathlib/lightdesc.cpp new file mode 100644 index 00000000..23c51c76 --- /dev/null +++ b/mathlib/lightdesc.cpp @@ -0,0 +1,320 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=====================================================================================// + +#include +#include +#include "mathlib.h" + +void LightDesc_t::RecalculateDerivedValues(void) +{ + m_Flags=0; + if (m_Attenuation0) + m_Flags|=LIGHTTYPE_OPTIMIZATIONFLAGS_HAS_ATTENUATION0; + if (m_Attenuation1) + m_Flags|=LIGHTTYPE_OPTIMIZATIONFLAGS_HAS_ATTENUATION1; + if (m_Attenuation2) + m_Flags|=LIGHTTYPE_OPTIMIZATIONFLAGS_HAS_ATTENUATION2; + + if (m_Type==MATERIAL_LIGHT_SPOT) + { + m_ThetaDot=cos(m_Theta); + m_PhiDot=cos(m_Phi); + float spread=m_ThetaDot-m_PhiDot; + if (spread>1.0e-10) + { + // note - this quantity is very sensitive to round off error. the sse + // reciprocal approximation won't cut it here. + OneOver_ThetaDot_Minus_PhiDot=1.0/spread; + } + else + { + // hard falloff instead of divide by zero + OneOver_ThetaDot_Minus_PhiDot=1.0; + } + } + if (m_Type==MATERIAL_LIGHT_DIRECTIONAL) + { + // set position to be real far away in the right direction + m_Position=m_Direction; + m_Position *= 2.0e6; + } + + m_RangeSquared=m_Range*m_Range; + +} + +void LightDesc_t::ComputeLightAtPointsForDirectional( + const FourVectors &pos, const FourVectors &normal, + FourVectors &color, bool DoHalfLambert ) const +{ + FourVectors delta; + delta.DuplicateVector(m_Direction); +// delta.VectorNormalizeFast(); + fltx4 strength=delta*normal; + if (DoHalfLambert) + { + strength=AddSIMD(MulSIMD(strength,Four_PointFives),Four_PointFives); + } + else + strength=MaxSIMD(Four_Zeros,delta*normal); + + color.x=AddSIMD(color.x,MulSIMD(strength,ReplicateX4(m_Color.x))); + color.y=AddSIMD(color.y,MulSIMD(strength,ReplicateX4(m_Color.y))); + color.z=AddSIMD(color.z,MulSIMD(strength,ReplicateX4(m_Color.z))); +} + + +void LightDesc_t::ComputeLightAtPoints( const FourVectors &pos, const FourVectors &normal, + FourVectors &color, bool DoHalfLambert ) const +{ + FourVectors delta; + Assert((m_Type==MATERIAL_LIGHT_POINT) || (m_Type==MATERIAL_LIGHT_SPOT) || (m_Type==MATERIAL_LIGHT_DIRECTIONAL)); + switch (m_Type) + { + case MATERIAL_LIGHT_POINT: + case MATERIAL_LIGHT_SPOT: + delta.DuplicateVector(m_Position); + delta-=pos; + break; + + case MATERIAL_LIGHT_DIRECTIONAL: + ComputeLightAtPointsForDirectional( pos, normal, color, DoHalfLambert ); + return; + + default: + return; + } + + fltx4 dist2 = delta*delta; + + dist2=MaxSIMD( Four_Ones, dist2 ); + + fltx4 falloff; + + if( m_Flags & LIGHTTYPE_OPTIMIZATIONFLAGS_HAS_ATTENUATION0 ) + { + falloff = ReplicateX4(m_Attenuation0); + } + else + falloff= Four_Epsilons; + + if( m_Flags & LIGHTTYPE_OPTIMIZATIONFLAGS_HAS_ATTENUATION1 ) + { + falloff=AddSIMD(falloff,MulSIMD(ReplicateX4(m_Attenuation1),SqrtEstSIMD(dist2))); + } + + if( m_Flags & LIGHTTYPE_OPTIMIZATIONFLAGS_HAS_ATTENUATION2 ) + { + falloff=AddSIMD(falloff,MulSIMD(ReplicateX4(m_Attenuation2),dist2)); + } + + falloff=ReciprocalEstSIMD(falloff); + // Cull out light beyond this radius + // now, zero out elements for which dist2 was > range^2. !!speed!! lights should store dist^2 in sse format + if (m_Range != 0.f) + { + fltx4 RangeSquared=ReplicateX4(m_RangeSquared); // !!speed!! + falloff=AndSIMD(falloff,CmpLtSIMD(dist2,RangeSquared)); + } + + delta.VectorNormalizeFast(); + fltx4 strength=delta*normal; + if (DoHalfLambert) + { + strength=AddSIMD(MulSIMD(strength,Four_PointFives),Four_PointFives); + } + else + strength=MaxSIMD(Four_Zeros,delta*normal); + + switch(m_Type) + { + case MATERIAL_LIGHT_POINT: + // half-lambert + break; + + case MATERIAL_LIGHT_SPOT: + { + fltx4 dot2=SubSIMD(Four_Zeros,delta*m_Direction); // dot position with spot light dir for cone falloff + + + fltx4 cone_falloff_scale=MulSIMD(ReplicateX4(OneOver_ThetaDot_Minus_PhiDot), + SubSIMD(dot2,ReplicateX4(m_PhiDot))); + cone_falloff_scale=MinSIMD(cone_falloff_scale,Four_Ones); + + if ((m_Falloff!=0.0) && (m_Falloff!=1.0)) + { + // !!speed!! could compute integer exponent needed by powsimd and store in light + cone_falloff_scale=PowSIMD(cone_falloff_scale,m_Falloff); + } + strength=MulSIMD(cone_falloff_scale,strength); + + // now, zero out lighting where dot2 range^2. !!speed!! lights should store dist^2 in sse format + if (m_Range != 0.f) + { + fltx4 RangeSquared=ReplicateX4(m_RangeSquared); // !!speed!! + falloff=AndSIMD(falloff,CmpLtSIMD(dist2,RangeSquared)); + } + + delta.VectorNormalizeFast(); + fltx4 strength = Four_Ones; + //fltx4 strength=delta; + //fltx4 strength = MaxSIMD(Four_Zeros,delta); + + switch(m_Type) + { + case MATERIAL_LIGHT_POINT: + // half-lambert + break; + + case MATERIAL_LIGHT_SPOT: + { + fltx4 dot2=SubSIMD(Four_Zeros,delta*m_Direction); // dot position with spot light dir for cone falloff + + + fltx4 cone_falloff_scale=MulSIMD(ReplicateX4(OneOver_ThetaDot_Minus_PhiDot), + SubSIMD(dot2,ReplicateX4(m_PhiDot))); + cone_falloff_scale=MinSIMD(cone_falloff_scale,Four_Ones); + + if ((m_Falloff!=0.0) && (m_Falloff!=1.0)) + { + // !!speed!! could compute integer exponent needed by powsimd and store in light + cone_falloff_scale=PowSIMD(cone_falloff_scale,m_Falloff); + } + strength=MulSIMD(cone_falloff_scale,strength); + + // now, zero out lighting where dot2 0 ) + m_Color *= fScaleFactor; +} + +void LightDesc_t::SetupNewStyleAttenuation( float fFiftyPercentDistance, + float fZeroPercentDistance ) +{ + // new style storing 50% and 0% distances + float d50=fFiftyPercentDistance; + float d0=fZeroPercentDistance; + if (d0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mathlib/mathlib_base.cpp b/mathlib/mathlib_base.cpp new file mode 100644 index 00000000..353dc2af --- /dev/null +++ b/mathlib/mathlib_base.cpp @@ -0,0 +1,4090 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: Math primitives. +// +//===========================================================================// + +/// FIXME: As soon as all references to mathlib.c are gone, include it in here + +#include +#include // Needed for FLT_EPSILON + +#include "tier0/basetypes.h" +#include +#include "tier0/dbg.h" + +#include "tier0/vprof.h" +//#define _VPROF_MATHLIB + +#ifdef _MSC_VER +#pragma warning(disable:4244) // "conversion from 'const int' to 'float', possible loss of data" +#pragma warning(disable:4730) // "mixing _m64 and floating point expressions may result in incorrect code" +#endif + +#include "mathlib/mathlib.h" +#include "mathlib/vector.h" +#if !defined( _X360 ) +#include "mathlib/amd3dx.h" +#include "3dnow.h" +#include "sse.h" +#endif + +#include "mathlib/ssemath.h" +#include "mathlib/ssequaternion.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +bool s_bMathlibInitialized = false; + +#ifdef PARANOID +// User must provide an implementation of Sys_Error() +void Sys_Error (char *error, ...); +#endif + +const Vector vec3_origin(0,0,0); +const QAngle vec3_angle(0,0,0); +const Vector vec3_invalid( FLT_MAX, FLT_MAX, FLT_MAX ); +const int nanmask = 255<<23; + +//----------------------------------------------------------------------------- +// Standard C implementations of optimized routines: +//----------------------------------------------------------------------------- +float _sqrtf(float _X) +{ + Assert( s_bMathlibInitialized ); + return sqrtf(_X); +} + +float _rsqrtf(float x) +{ + Assert( s_bMathlibInitialized ); + + return 1.f / _sqrtf( x ); +} + +float FASTCALL _VectorNormalize (Vector& vec) +{ +#ifdef _VPROF_MATHLIB + VPROF_BUDGET( "_VectorNormalize", "Mathlib" ); +#endif + Assert( s_bMathlibInitialized ); + float radius = sqrtf(vec.x*vec.x + vec.y*vec.y + vec.z*vec.z); + + // FLT_EPSILON is added to the radius to eliminate the possibility of divide by zero. + float iradius = 1.f / ( radius + FLT_EPSILON ); + + vec.x *= iradius; + vec.y *= iradius; + vec.z *= iradius; + + return radius; +} + +// TODO: Add fast C VectorNormalizeFast. +// Perhaps use approximate rsqrt trick, if the accuracy isn't too bad. +void FASTCALL _VectorNormalizeFast (Vector& vec) +{ + Assert( s_bMathlibInitialized ); + + // FLT_EPSILON is added to the radius to eliminate the possibility of divide by zero. + float iradius = 1.f / ( sqrtf(vec.x*vec.x + vec.y*vec.y + vec.z*vec.z) + FLT_EPSILON ); + + vec.x *= iradius; + vec.y *= iradius; + vec.z *= iradius; + +} + +float _InvRSquared(const float* v) +{ + Assert( s_bMathlibInitialized ); + float r2 = DotProduct(v, v); + return r2 < 1.f ? 1.f : 1/r2; +} + +//----------------------------------------------------------------------------- +// Function pointers selecting the appropriate implementation +//----------------------------------------------------------------------------- +float (*pfSqrt)(float x) = _sqrtf; +float (*pfRSqrt)(float x) = _rsqrtf; +float (*pfRSqrtFast)(float x) = _rsqrtf; +float (FASTCALL *pfVectorNormalize)(Vector& v) = _VectorNormalize; +void (FASTCALL *pfVectorNormalizeFast)(Vector& v) = _VectorNormalizeFast; +float (*pfInvRSquared)(const float* v) = _InvRSquared; +void (*pfFastSinCos)(float x, float* s, float* c) = SinCos; +float (*pfFastCos)(float x) = cosf; + +float SinCosTable[SIN_TABLE_SIZE]; +void InitSinCosTable() +{ + for( int i = 0; i < SIN_TABLE_SIZE; i++ ) + { + SinCosTable[i] = sin(i * 2.0 * M_PI / SIN_TABLE_SIZE); + } +} + +qboolean VectorsEqual( const float *v1, const float *v2 ) +{ + Assert( s_bMathlibInitialized ); + return ( ( v1[0] == v2[0] ) && + ( v1[1] == v2[1] ) && + ( v1[2] == v2[2] ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Generates Euler angles given a left-handed orientation matrix. The +// columns of the matrix contain the forward, left, and up vectors. +// Input : matrix - Left-handed orientation matrix. +// angles[PITCH, YAW, ROLL]. Receives right-handed counterclockwise +// rotations in degrees around Y, Z, and X respectively. +//----------------------------------------------------------------------------- + +void MatrixAngles( const matrix3x4_t& matrix, RadianEuler &angles, Vector &position ) +{ + MatrixGetColumn( matrix, 3, position ); + MatrixAngles( matrix, angles ); +} + +void MatrixAngles( const matrix3x4_t &matrix, Quaternion &q, Vector &pos ) +{ +#ifdef _VPROF_MATHLIB + VPROF_BUDGET( "MatrixQuaternion", "Mathlib" ); +#endif + float trace; + trace = matrix[0][0] + matrix[1][1] + matrix[2][2] + 1.0f; + if( trace > 1.0f + FLT_EPSILON ) + { + // VPROF_INCREMENT_COUNTER("MatrixQuaternion A",1); + q.x = ( matrix[2][1] - matrix[1][2] ); + q.y = ( matrix[0][2] - matrix[2][0] ); + q.z = ( matrix[1][0] - matrix[0][1] ); + q.w = trace; + } + else if ( matrix[0][0] > matrix[1][1] && matrix[0][0] > matrix[2][2] ) + { + // VPROF_INCREMENT_COUNTER("MatrixQuaternion B",1); + trace = 1.0f + matrix[0][0] - matrix[1][1] - matrix[2][2]; + q.x = trace; + q.y = (matrix[1][0] + matrix[0][1] ); + q.z = (matrix[0][2] + matrix[2][0] ); + q.w = (matrix[2][1] - matrix[1][2] ); + } + else if (matrix[1][1] > matrix[2][2]) + { + // VPROF_INCREMENT_COUNTER("MatrixQuaternion C",1); + trace = 1.0f + matrix[1][1] - matrix[0][0] - matrix[2][2]; + q.x = (matrix[0][1] + matrix[1][0] ); + q.y = trace; + q.z = (matrix[2][1] + matrix[1][2] ); + q.w = (matrix[0][2] - matrix[2][0] ); + } + else + { + // VPROF_INCREMENT_COUNTER("MatrixQuaternion D",1); + trace = 1.0f + matrix[2][2] - matrix[0][0] - matrix[1][1]; + q.x = (matrix[0][2] + matrix[2][0] ); + q.y = (matrix[2][1] + matrix[1][2] ); + q.z = trace; + q.w = (matrix[1][0] - matrix[0][1] ); + } + + QuaternionNormalize( q ); + +#if 0 + // check against the angle version + RadianEuler ang; + MatrixAngles( matrix, ang ); + Quaternion test; + AngleQuaternion( ang, test ); + float d = QuaternionDotProduct( q, test ); + Assert( fabs(d) > 0.99 && fabs(d) < 1.01 ); +#endif + + MatrixGetColumn( matrix, 3, pos ); +} + +void MatrixAngles( const matrix3x4_t& matrix, float *angles ) +{ +#ifdef _VPROF_MATHLIB + VPROF_BUDGET( "MatrixAngles", "Mathlib" ); +#endif + Assert( s_bMathlibInitialized ); + float forward[3]; + float left[3]; + float up[3]; + + // + // Extract the basis vectors from the matrix. Since we only need the Z + // component of the up vector, we don't get X and Y. + // + forward[0] = matrix[0][0]; + forward[1] = matrix[1][0]; + forward[2] = matrix[2][0]; + left[0] = matrix[0][1]; + left[1] = matrix[1][1]; + left[2] = matrix[2][1]; + up[2] = matrix[2][2]; + + float xyDist = sqrtf( forward[0] * forward[0] + forward[1] * forward[1] ); + + // enough here to get angles? + if ( xyDist > 0.001f ) + { + // (yaw) y = ATAN( forward.y, forward.x ); -- in our space, forward is the X axis + angles[1] = RAD2DEG( atan2f( forward[1], forward[0] ) ); + + // (pitch) x = ATAN( -forward.z, sqrt(forward.x*forward.x+forward.y*forward.y) ); + angles[0] = RAD2DEG( atan2f( -forward[2], xyDist ) ); + + // (roll) z = ATAN( left.z, up.z ); + angles[2] = RAD2DEG( atan2f( left[2], up[2] ) ); + } + else // forward is mostly Z, gimbal lock- + { + // (yaw) y = ATAN( -left.x, left.y ); -- forward is mostly z, so use right for yaw + angles[1] = RAD2DEG( atan2f( -left[0], left[1] ) ); + + // (pitch) x = ATAN( -forward.z, sqrt(forward.x*forward.x+forward.y*forward.y) ); + angles[0] = RAD2DEG( atan2f( -forward[2], xyDist ) ); + + // Assume no roll in this case as one degree of freedom has been lost (i.e. yaw == roll) + angles[2] = 0; + } +} + + +// transform in1 by the matrix in2 +void VectorTransform (const float *in1, const matrix3x4_t& in2, float *out) +{ + Assert( s_bMathlibInitialized ); + Assert( in1 != out ); + out[0] = DotProduct(in1, in2[0]) + in2[0][3]; + out[1] = DotProduct(in1, in2[1]) + in2[1][3]; + out[2] = DotProduct(in1, in2[2]) + in2[2][3]; +} + + +// assuming the matrix is orthonormal, transform in1 by the transpose (also the inverse in this case) of in2. +void VectorITransform (const float *in1, const matrix3x4_t& in2, float *out) +{ + Assert( s_bMathlibInitialized ); + float in1t[3]; + + in1t[0] = in1[0] - in2[0][3]; + in1t[1] = in1[1] - in2[1][3]; + in1t[2] = in1[2] - in2[2][3]; + + out[0] = in1t[0] * in2[0][0] + in1t[1] * in2[1][0] + in1t[2] * in2[2][0]; + out[1] = in1t[0] * in2[0][1] + in1t[1] * in2[1][1] + in1t[2] * in2[2][1]; + out[2] = in1t[0] * in2[0][2] + in1t[1] * in2[1][2] + in1t[2] * in2[2][2]; +} + + +// assume in2 is a rotation and rotate the input vector +void VectorRotate( const float *in1, const matrix3x4_t& in2, float *out ) +{ + Assert( s_bMathlibInitialized ); + Assert( in1 != out ); + out[0] = DotProduct( in1, in2[0] ); + out[1] = DotProduct( in1, in2[1] ); + out[2] = DotProduct( in1, in2[2] ); +} + +// assume in2 is a rotation and rotate the input vector +void VectorRotate( const Vector &in1, const QAngle &in2, Vector &out ) +{ + matrix3x4_t matRotate; + AngleMatrix( in2, matRotate ); + VectorRotate( in1, matRotate, out ); +} + +// assume in2 is a rotation and rotate the input vector +void VectorRotate( const Vector &in1, const Quaternion &in2, Vector &out ) +{ + matrix3x4_t matRotate; + QuaternionMatrix( in2, matRotate ); + VectorRotate( in1, matRotate, out ); +} + + +// rotate by the inverse of the matrix +void VectorIRotate( const float *in1, const matrix3x4_t& in2, float *out ) +{ + Assert( s_bMathlibInitialized ); + Assert( in1 != out ); + out[0] = in1[0]*in2[0][0] + in1[1]*in2[1][0] + in1[2]*in2[2][0]; + out[1] = in1[0]*in2[0][1] + in1[1]*in2[1][1] + in1[2]*in2[2][1]; + out[2] = in1[0]*in2[0][2] + in1[1]*in2[1][2] + in1[2]*in2[2][2]; +} + +#ifndef VECTOR_NO_SLOW_OPERATIONS +// transform a set of angles in the output space of parentMatrix to the input space +QAngle TransformAnglesToLocalSpace( const QAngle &angles, const matrix3x4_t &parentMatrix ) +{ + matrix3x4_t angToWorld, worldToParent, localMatrix; + MatrixInvert( parentMatrix, worldToParent ); + AngleMatrix( angles, angToWorld ); + ConcatTransforms( worldToParent, angToWorld, localMatrix ); + + QAngle out; + MatrixAngles( localMatrix, out ); + return out; +} + +// transform a set of angles in the input space of parentMatrix to the output space +QAngle TransformAnglesToWorldSpace( const QAngle &angles, const matrix3x4_t &parentMatrix ) +{ + matrix3x4_t angToParent, angToWorld; + AngleMatrix( angles, angToParent ); + ConcatTransforms( parentMatrix, angToParent, angToWorld ); + QAngle out; + MatrixAngles( angToWorld, out ); + return out; +} + +#endif // VECTOR_NO_SLOW_OPERATIONS + +void MatrixInitialize( matrix3x4_t &mat, const Vector &vecOrigin, const Vector &vecXAxis, const Vector &vecYAxis, const Vector &vecZAxis ) +{ + MatrixSetColumn( vecXAxis, 0, mat ); + MatrixSetColumn( vecYAxis, 1, mat ); + MatrixSetColumn( vecZAxis, 2, mat ); + MatrixSetColumn( vecOrigin, 3, mat ); +} + +void MatrixCopy( const matrix3x4_t& in, matrix3x4_t& out ) +{ + Assert( s_bMathlibInitialized ); + memcpy( out.Base(), in.Base(), sizeof( float ) * 3 * 4 ); +} + +//----------------------------------------------------------------------------- +// Matrix equality test +//----------------------------------------------------------------------------- +bool MatricesAreEqual( const matrix3x4_t &src1, const matrix3x4_t &src2, float flTolerance ) +{ + for ( int i = 0; i < 3; ++i ) + { + for ( int j = 0; j < 4; ++j ) + { + if ( fabs( src1[i][j] - src2[i][j] ) > flTolerance ) + return false; + } + } + return true; +} + +// NOTE: This is just the transpose not a general inverse +void MatrixInvert( const matrix3x4_t& in, matrix3x4_t& out ) +{ + Assert( s_bMathlibInitialized ); + if ( &in == &out ) + { + swap(out[0][1],out[1][0]); + swap(out[0][2],out[2][0]); + swap(out[1][2],out[2][1]); + } + else + { + // transpose the matrix + out[0][0] = in[0][0]; + out[0][1] = in[1][0]; + out[0][2] = in[2][0]; + + out[1][0] = in[0][1]; + out[1][1] = in[1][1]; + out[1][2] = in[2][1]; + + out[2][0] = in[0][2]; + out[2][1] = in[1][2]; + out[2][2] = in[2][2]; + } + + // now fix up the translation to be in the other space + float tmp[3]; + tmp[0] = in[0][3]; + tmp[1] = in[1][3]; + tmp[2] = in[2][3]; + + out[0][3] = -DotProduct( tmp, out[0] ); + out[1][3] = -DotProduct( tmp, out[1] ); + out[2][3] = -DotProduct( tmp, out[2] ); +} + +void MatrixGetColumn( const matrix3x4_t& in, int column, Vector &out ) +{ + out.x = in[0][column]; + out.y = in[1][column]; + out.z = in[2][column]; +} + +void MatrixSetColumn( const Vector &in, int column, matrix3x4_t& out ) +{ + out[0][column] = in.x; + out[1][column] = in.y; + out[2][column] = in.z; +} + + +int VectorCompare (const float *v1, const float *v2) +{ + Assert( s_bMathlibInitialized ); + int i; + + for (i=0 ; i<3 ; i++) + if (v1[i] != v2[i]) + return 0; + + return 1; +} + +void CrossProduct (const float* v1, const float* v2, float* cross) +{ + Assert( s_bMathlibInitialized ); + Assert( v1 != cross ); + Assert( v2 != cross ); + cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; + cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; + cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +int Q_log2(int val) +{ + int answer=0; + while (val>>=1) + answer++; + return answer; +} + +// Matrix is right-handed x=forward, y=left, z=up. We a left-handed convention for vectors in the game code (forward, right, up) +void MatrixVectors( const matrix3x4_t &matrix, Vector* pForward, Vector *pRight, Vector *pUp ) +{ + MatrixGetColumn( matrix, 0, *pForward ); + MatrixGetColumn( matrix, 1, *pRight ); + MatrixGetColumn( matrix, 2, *pUp ); + *pRight *= -1.0f; +} + + +void VectorVectors( const Vector &forward, Vector &right, Vector &up ) +{ + Assert( s_bMathlibInitialized ); + Vector tmp; + + if (forward[0] == 0 && forward[1] == 0) + { + // pitch 90 degrees up/down from identity + right[0] = 0; + right[1] = -1; + right[2] = 0; + up[0] = -forward[2]; + up[1] = 0; + up[2] = 0; + } + else + { + tmp[0] = 0; tmp[1] = 0; tmp[2] = 1.0; + CrossProduct( forward, tmp, right ); + VectorNormalize( right ); + CrossProduct( right, forward, up ); + VectorNormalize( up ); + } +} + +void VectorMatrix( const Vector &forward, matrix3x4_t& matrix) +{ + Assert( s_bMathlibInitialized ); + Vector right, up; + VectorVectors(forward, right, up); + + MatrixSetColumn( forward, 0, matrix ); + MatrixSetColumn( -right, 1, matrix ); + MatrixSetColumn( up, 2, matrix ); +} + + +void VectorAngles( const float *forward, float *angles ) +{ + Assert( s_bMathlibInitialized ); + float tmp, yaw, pitch; + + if (forward[1] == 0 && forward[0] == 0) + { + yaw = 0; + if (forward[2] > 0) + pitch = 270; + else + pitch = 90; + } + else + { + yaw = (atan2(forward[1], forward[0]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + + tmp = sqrt (forward[0]*forward[0] + forward[1]*forward[1]); + pitch = (atan2(-forward[2], tmp) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + + angles[0] = pitch; + angles[1] = yaw; + angles[2] = 0; +} + + +/* +================ +R_ConcatRotations +================ +*/ +void ConcatRotations (const float in1[3][3], const float in2[3][3], float out[3][3]) +{ + Assert( s_bMathlibInitialized ); + Assert( in1 != out ); + Assert( in2 != out ); + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; +} + + +/* +================ +R_ConcatTransforms +================ +*/ +void ConcatTransforms (const matrix3x4_t& in1, const matrix3x4_t& in2, matrix3x4_t& out) +{ + Assert( s_bMathlibInitialized ); + if ( &in1 == &out ) + { + matrix3x4_t in1b; + MatrixCopy( in1, in1b ); + ConcatTransforms( in1b, in2, out ); + return; + } + if ( &in2 == &out ) + { + matrix3x4_t in2b; + MatrixCopy( in2, in2b ); + ConcatTransforms( in1, in2b, out ); + return; + } + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + + in1[0][2] * in2[2][3] + in1[0][3]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + + in1[1][2] * in2[2][3] + in1[1][3]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; + out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + + in1[2][2] * in2[2][3] + in1[2][3]; +} + + +/* +=================== +FloorDivMod + +Returns mathematically correct (floor-based) quotient and remainder for +numer and denom, both of which should contain no fractional part. The +quotient must fit in 32 bits. +==================== +*/ + +void FloorDivMod (double numer, double denom, int *quotient, + int *rem) +{ + Assert( s_bMathlibInitialized ); + int q, r; + double x; + +#ifdef PARANOID + if (denom <= 0.0) + Sys_Error ("FloorDivMod: bad denominator %d\n", denom); + +// if ((floor(numer) != numer) || (floor(denom) != denom)) +// Sys_Error ("FloorDivMod: non-integer numer or denom %f %f\n", +// numer, denom); +#endif + + if (numer >= 0.0) + { + + x = floor(numer / denom); + q = (int)x; + r = Floor2Int(numer - (x * denom)); + } + else + { + // + // perform operations with positive values, and fix mod to make floor-based + // + x = floor(-numer / denom); + q = -(int)x; + r = Floor2Int(-numer - (x * denom)); + if (r != 0) + { + q--; + r = (int)denom - r; + } + } + + *quotient = q; + *rem = r; +} + + +/* +=================== +GreatestCommonDivisor +==================== +*/ +int GreatestCommonDivisor (int i1, int i2) +{ + Assert( s_bMathlibInitialized ); + if (i1 > i2) + { + if (i2 == 0) + return (i1); + return GreatestCommonDivisor (i2, i1 % i2); + } + else + { + if (i1 == 0) + return (i2); + return GreatestCommonDivisor (i1, i2 % i1); + } +} + + +bool IsDenormal( const float &val ) +{ + const int x = *reinterpret_cast (&val); // needs 32-bit int + const int abs_mantissa = x & 0x007FFFFF; + const int biased_exponent = x & 0x7F800000; + + return ( biased_exponent == 0 && abs_mantissa != 0 ); +} + +int SignbitsForPlane (cplane_t *out) +{ + Assert( s_bMathlibInitialized ); + int bits, j; + + // for fast box on planeside test + + bits = 0; + for (j=0 ; j<3 ; j++) + { + if (out->normal[j] < 0) + bits |= 1<type < 3) + { + if (p->dist <= emins[p->type]) + return 1; + if (p->dist >= emaxs[p->type]) + return 2; + return 3; + } + + // general case + switch (p->signbits) + { + case 0: + dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + break; + case 1: + dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + break; + case 2: + dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + break; + case 3: + dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + break; + case 4: + dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + break; + case 5: + dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + break; + case 6: + dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + break; + case 7: + dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + break; + default: + dist1 = dist2 = 0; // shut up compiler + Assert( 0 ); + break; + } + + sides = 0; + if (dist1 >= p->dist) + sides = 1; + if (dist2 < p->dist) + sides |= 2; + + Assert( sides != 0 ); + + return sides; +} + +//----------------------------------------------------------------------------- +// Euler QAngle -> Basis Vectors +//----------------------------------------------------------------------------- + +void AngleVectors (const QAngle &angles, Vector *forward) +{ + Assert( s_bMathlibInitialized ); + Assert( forward ); + + float sp, sy, cp, cy; + + SinCos( DEG2RAD( angles[YAW] ), &sy, &cy ); + SinCos( DEG2RAD( angles[PITCH] ), &sp, &cp ); + + forward->x = cp*cy; + forward->y = cp*sy; + forward->z = -sp; +} + +//----------------------------------------------------------------------------- +// Euler QAngle -> Basis Vectors. Each vector is optional +//----------------------------------------------------------------------------- +void AngleVectors( const QAngle &angles, Vector *forward, Vector *right, Vector *up ) +{ + Assert( s_bMathlibInitialized ); + + float sr, sp, sy, cr, cp, cy; + +#ifdef _X360 + fltx4 radians, scale, sine, cosine; + radians = LoadUnaligned3SIMD( angles.Base() ); + scale = ReplicateX4( M_PI_F / 180.f ); + radians = MulSIMD( radians, scale ); + SinCos3SIMD( sine, cosine, radians ); + sp = SubFloat( sine, 0 ); sy = SubFloat( sine, 1 ); sr = SubFloat( sine, 2 ); + cp = SubFloat( cosine, 0 ); cy = SubFloat( cosine, 1 ); cr = SubFloat( cosine, 2 ); +#else + SinCos( DEG2RAD( angles[YAW] ), &sy, &cy ); + SinCos( DEG2RAD( angles[PITCH] ), &sp, &cp ); + SinCos( DEG2RAD( angles[ROLL] ), &sr, &cr ); +#endif + + if (forward) + { + forward->x = cp*cy; + forward->y = cp*sy; + forward->z = -sp; + } + + if (right) + { + right->x = (-1*sr*sp*cy+-1*cr*-sy); + right->y = (-1*sr*sp*sy+-1*cr*cy); + right->z = -1*sr*cp; + } + + if (up) + { + up->x = (cr*sp*cy+-sr*-sy); + up->y = (cr*sp*sy+-sr*cy); + up->z = cr*cp; + } +} + +//----------------------------------------------------------------------------- +// Euler QAngle -> Basis Vectors transposed +//----------------------------------------------------------------------------- + +void AngleVectorsTranspose (const QAngle &angles, Vector *forward, Vector *right, Vector *up) +{ + Assert( s_bMathlibInitialized ); + float sr, sp, sy, cr, cp, cy; + + SinCos( DEG2RAD( angles[YAW] ), &sy, &cy ); + SinCos( DEG2RAD( angles[PITCH] ), &sp, &cp ); + SinCos( DEG2RAD( angles[ROLL] ), &sr, &cr ); + + if (forward) + { + forward->x = cp*cy; + forward->y = (sr*sp*cy+cr*-sy); + forward->z = (cr*sp*cy+-sr*-sy); + } + + if (right) + { + right->x = cp*sy; + right->y = (sr*sp*sy+cr*cy); + right->z = (cr*sp*sy+-sr*cy); + } + + if (up) + { + up->x = -sp; + up->y = sr*cp; + up->z = cr*cp; + } +} + +//----------------------------------------------------------------------------- +// Forward direction vector -> Euler angles +//----------------------------------------------------------------------------- + +void VectorAngles( const Vector& forward, QAngle &angles ) +{ + Assert( s_bMathlibInitialized ); + float tmp, yaw, pitch; + + if (forward[1] == 0 && forward[0] == 0) + { + yaw = 0; + if (forward[2] > 0) + pitch = 270; + else + pitch = 90; + } + else + { + yaw = (atan2(forward[1], forward[0]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + + tmp = FastSqrt (forward[0]*forward[0] + forward[1]*forward[1]); + pitch = (atan2(-forward[2], tmp) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + + angles[0] = pitch; + angles[1] = yaw; + angles[2] = 0; +} + +//----------------------------------------------------------------------------- +// Forward direction vector with a reference up vector -> Euler angles +//----------------------------------------------------------------------------- + +void VectorAngles( const Vector &forward, const Vector &pseudoup, QAngle &angles ) +{ + Assert( s_bMathlibInitialized ); + + Vector left; + + CrossProduct( pseudoup, forward, left ); + VectorNormalizeFast( left ); + + float xyDist = sqrtf( forward[0] * forward[0] + forward[1] * forward[1] ); + + // enough here to get angles? + if ( xyDist > 0.001f ) + { + // (yaw) y = ATAN( forward.y, forward.x ); -- in our space, forward is the X axis + angles[1] = RAD2DEG( atan2f( forward[1], forward[0] ) ); + + // The engine does pitch inverted from this, but we always end up negating it in the DLL + // UNDONE: Fix the engine to make it consistent + // (pitch) x = ATAN( -forward.z, sqrt(forward.x*forward.x+forward.y*forward.y) ); + angles[0] = RAD2DEG( atan2f( -forward[2], xyDist ) ); + + float up_z = (left[1] * forward[0]) - (left[0] * forward[1]); + + // (roll) z = ATAN( left.z, up.z ); + angles[2] = RAD2DEG( atan2f( left[2], up_z ) ); + } + else // forward is mostly Z, gimbal lock- + { + // (yaw) y = ATAN( -left.x, left.y ); -- forward is mostly z, so use right for yaw + angles[1] = RAD2DEG( atan2f( -left[0], left[1] ) ); //This was originally copied from the "void MatrixAngles( const matrix3x4_t& matrix, float *angles )" code, and it's 180 degrees off, negated the values and it all works now (Dave Kircher) + + // The engine does pitch inverted from this, but we always end up negating it in the DLL + // UNDONE: Fix the engine to make it consistent + // (pitch) x = ATAN( -forward.z, sqrt(forward.x*forward.x+forward.y*forward.y) ); + angles[0] = RAD2DEG( atan2f( -forward[2], xyDist ) ); + + // Assume no roll in this case as one degree of freedom has been lost (i.e. yaw == roll) + angles[2] = 0; + } +} + +void SetIdentityMatrix( matrix3x4_t& matrix ) +{ + memset( matrix.Base(), 0, sizeof(float)*3*4 ); + matrix[0][0] = 1.0; + matrix[1][1] = 1.0; + matrix[2][2] = 1.0; +} + + +//----------------------------------------------------------------------------- +// Builds a scale matrix +//----------------------------------------------------------------------------- +void SetScaleMatrix( float x, float y, float z, matrix3x4_t &dst ) +{ + dst[0][0] = x; dst[0][1] = 0.0f; dst[0][2] = 0.0f; dst[0][3] = 0.0f; + dst[1][0] = 0.0f; dst[1][1] = y; dst[1][2] = 0.0f; dst[1][3] = 0.0f; + dst[2][0] = 0.0f; dst[2][1] = 0.0f; dst[2][2] = z; dst[2][3] = 0.0f; +} + + +//----------------------------------------------------------------------------- +// Purpose: Builds the matrix for a counterclockwise rotation about an arbitrary axis. +// +// | ax2 + (1 - ax2)cosQ axay(1 - cosQ) - azsinQ azax(1 - cosQ) + aysinQ | +// Ra(Q) = | axay(1 - cosQ) + azsinQ ay2 + (1 - ay2)cosQ ayaz(1 - cosQ) - axsinQ | +// | azax(1 - cosQ) - aysinQ ayaz(1 - cosQ) + axsinQ az2 + (1 - az2)cosQ | +// +// Input : mat - +// vAxisOrRot - +// angle - +//----------------------------------------------------------------------------- +void MatrixBuildRotationAboutAxis( const Vector &vAxisOfRot, float angleDegrees, matrix3x4_t &dst ) +{ + float radians; + float axisXSquared; + float axisYSquared; + float axisZSquared; + float fSin; + float fCos; + + radians = angleDegrees * ( M_PI / 180.0 ); + fSin = sin( radians ); + fCos = cos( radians ); + + axisXSquared = vAxisOfRot[0] * vAxisOfRot[0]; + axisYSquared = vAxisOfRot[1] * vAxisOfRot[1]; + axisZSquared = vAxisOfRot[2] * vAxisOfRot[2]; + + // Column 0: + dst[0][0] = axisXSquared + (1 - axisXSquared) * fCos; + dst[1][0] = vAxisOfRot[0] * vAxisOfRot[1] * (1 - fCos) + vAxisOfRot[2] * fSin; + dst[2][0] = vAxisOfRot[2] * vAxisOfRot[0] * (1 - fCos) - vAxisOfRot[1] * fSin; + + // Column 1: + dst[0][1] = vAxisOfRot[0] * vAxisOfRot[1] * (1 - fCos) - vAxisOfRot[2] * fSin; + dst[1][1] = axisYSquared + (1 - axisYSquared) * fCos; + dst[2][1] = vAxisOfRot[1] * vAxisOfRot[2] * (1 - fCos) + vAxisOfRot[0] * fSin; + + // Column 2: + dst[0][2] = vAxisOfRot[2] * vAxisOfRot[0] * (1 - fCos) + vAxisOfRot[1] * fSin; + dst[1][2] = vAxisOfRot[1] * vAxisOfRot[2] * (1 - fCos) - vAxisOfRot[0] * fSin; + dst[2][2] = axisZSquared + (1 - axisZSquared) * fCos; + + // Column 3: + dst[0][3] = 0; + dst[1][3] = 0; + dst[2][3] = 0; +} + + +//----------------------------------------------------------------------------- +// Computes the transpose +//----------------------------------------------------------------------------- +void MatrixTranspose( matrix3x4_t& mat ) +{ + vec_t tmp; + tmp = mat[0][1]; mat[0][1] = mat[1][0]; mat[1][0] = tmp; + tmp = mat[0][2]; mat[0][2] = mat[2][0]; mat[2][0] = tmp; + tmp = mat[1][2]; mat[1][2] = mat[2][1]; mat[2][1] = tmp; +} + +void MatrixTranspose( const matrix3x4_t& src, matrix3x4_t& dst ) +{ + dst[0][0] = src[0][0]; dst[0][1] = src[1][0]; dst[0][2] = src[2][0]; dst[0][3] = 0.0f; + dst[1][0] = src[0][1]; dst[1][1] = src[1][1]; dst[1][2] = src[2][1]; dst[1][3] = 0.0f; + dst[2][0] = src[0][2]; dst[2][1] = src[1][2]; dst[2][2] = src[2][2]; dst[2][3] = 0.0f; +} + + +//----------------------------------------------------------------------------- +// Purpose: converts engine euler angles into a matrix +// Input : vec3_t angles - PITCH, YAW, ROLL +// Output : *matrix - left-handed column matrix +// the basis vectors for the rotations will be in the columns as follows: +// matrix[][0] is forward +// matrix[][1] is left +// matrix[][2] is up +//----------------------------------------------------------------------------- +void AngleMatrix( RadianEuler const &angles, const Vector &position, matrix3x4_t& matrix ) +{ + AngleMatrix( angles, matrix ); + MatrixSetColumn( position, 3, matrix ); +} + +void AngleMatrix( const RadianEuler& angles, matrix3x4_t& matrix ) +{ + QAngle quakeEuler( RAD2DEG( angles.y ), RAD2DEG( angles.z ), RAD2DEG( angles.x ) ); + + AngleMatrix( quakeEuler, matrix ); +} + + +void AngleMatrix( const QAngle &angles, const Vector &position, matrix3x4_t& matrix ) +{ + AngleMatrix( angles, matrix ); + MatrixSetColumn( position, 3, matrix ); +} + +void AngleMatrix( const QAngle &angles, matrix3x4_t& matrix ) +{ +#ifdef _VPROF_MATHLIB + VPROF_BUDGET( "AngleMatrix", "Mathlib" ); +#endif + Assert( s_bMathlibInitialized ); + + float sr, sp, sy, cr, cp, cy; + +#ifdef _X360 + fltx4 radians, scale, sine, cosine; + radians = LoadUnaligned3SIMD( angles.Base() ); + scale = ReplicateX4( M_PI_F / 180.f ); + radians = MulSIMD( radians, scale ); + SinCos3SIMD( sine, cosine, radians ); + + sp = SubFloat( sine, 0 ); sy = SubFloat( sine, 1 ); sr = SubFloat( sine, 2 ); + cp = SubFloat( cosine, 0 ); cy = SubFloat( cosine, 1 ); cr = SubFloat( cosine, 2 ); +#else + SinCos( DEG2RAD( angles[YAW] ), &sy, &cy ); + SinCos( DEG2RAD( angles[PITCH] ), &sp, &cp ); + SinCos( DEG2RAD( angles[ROLL] ), &sr, &cr ); +#endif + + // matrix = (YAW * PITCH) * ROLL + matrix[0][0] = cp*cy; + matrix[1][0] = cp*sy; + matrix[2][0] = -sp; + + float crcy = cr*cy; + float crsy = cr*sy; + float srcy = sr*cy; + float srsy = sr*sy; + matrix[0][1] = sp*srcy-crsy; + matrix[1][1] = sp*srsy+crcy; + matrix[2][1] = sr*cp; + + matrix[0][2] = (sp*crcy+srsy); + matrix[1][2] = (sp*crsy-srcy); + matrix[2][2] = cr*cp; + + matrix[0][3] = 0.0f; + matrix[1][3] = 0.0f; + matrix[2][3] = 0.0f; +} + +void AngleIMatrix( const RadianEuler& angles, matrix3x4_t& matrix ) +{ + QAngle quakeEuler( RAD2DEG( angles.y ), RAD2DEG( angles.z ), RAD2DEG( angles.x ) ); + + AngleIMatrix( quakeEuler, matrix ); +} + +void AngleIMatrix (const QAngle& angles, matrix3x4_t& matrix ) +{ + Assert( s_bMathlibInitialized ); + float sr, sp, sy, cr, cp, cy; + + SinCos( DEG2RAD( angles[YAW] ), &sy, &cy ); + SinCos( DEG2RAD( angles[PITCH] ), &sp, &cp ); + SinCos( DEG2RAD( angles[ROLL] ), &sr, &cr ); + + // matrix = (YAW * PITCH) * ROLL + matrix[0][0] = cp*cy; + matrix[0][1] = cp*sy; + matrix[0][2] = -sp; + matrix[1][0] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[1][2] = sr*cp; + matrix[2][0] = (cr*sp*cy+-sr*-sy); + matrix[2][1] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.f; + matrix[1][3] = 0.f; + matrix[2][3] = 0.f; +} + +void AngleIMatrix (const QAngle &angles, const Vector &position, matrix3x4_t &mat ) +{ + AngleIMatrix( angles, mat ); + + Vector vecTranslation; + VectorRotate( position, mat, vecTranslation ); + vecTranslation *= -1.0f; + MatrixSetColumn( vecTranslation, 3, mat ); +} + + +//----------------------------------------------------------------------------- +// Bounding box construction methods +//----------------------------------------------------------------------------- + +void ClearBounds (Vector& mins, Vector& maxs) +{ + Assert( s_bMathlibInitialized ); + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; +} + +void AddPointToBounds (const Vector& v, Vector& mins, Vector& maxs) +{ + Assert( s_bMathlibInitialized ); + int i; + vec_t val; + + for (i=0 ; i<3 ; i++) + { + val = v[i]; + if (val < mins[i]) + mins[i] = val; + if (val > maxs[i]) + maxs[i] = val; + } +} + +// solve a x^2 + b x + c = 0 +bool SolveQuadratic( float a, float b, float c, float &root1, float &root2 ) +{ + Assert( s_bMathlibInitialized ); + if (a == 0) + { + if (b != 0) + { + // no x^2 component, it's a linear system + root1 = root2 = -c / b; + return true; + } + if (c == 0) + { + // all zero's + root1 = root2 = 0; + return true; + } + return false; + } + + float tmp = b * b - 4.0f * a * c; + + if (tmp < 0) + { + // imaginary number, bah, no solution. + return false; + } + + tmp = sqrt( tmp ); + root1 = (-b + tmp) / (2.0f * a); + root2 = (-b - tmp) / (2.0f * a); + return true; +} + +// solves for "a, b, c" where "a x^2 + b x + c = y", return true if solution exists +bool SolveInverseQuadratic( float x1, float y1, float x2, float y2, float x3, float y3, float &a, float &b, float &c ) +{ + float det = (x1 - x2)*(x1 - x3)*(x2 - x3); + + // FIXME: check with some sort of epsilon + if (det == 0.0) + return false; + + a = (x3*(-y1 + y2) + x2*(y1 - y3) + x1*(-y2 + y3)) / det; + + b = (x3*x3*(y1 - y2) + x1*x1*(y2 - y3) + x2*x2*(-y1 + y3)) / det; + + c = (x1*x3*(-x1 + x3)*y2 + x2*x2*(x3*y1 - x1*y3) + x2*(-(x3*x3*y1) + x1*x1*y3)) / det; + + return true; +} + +bool SolveInverseQuadraticMonotonic( float x1, float y1, float x2, float y2, float x3, float y3, + float &a, float &b, float &c ) +{ + // use SolveInverseQuadratic, but if the sigm of the derivative at the start point is the wrong + // sign, displace the mid point + + // first, sort parameters + if (x1>x2) + { + swap(x1,x2); + swap(y1,y2); + } + if (x2>x3) + { + swap(x2,x3); + swap(y2,y3); + } + if (x1>x2) + { + swap(x1,x2); + swap(y1,y2); + } + // this code is not fast. what it does is when the curve would be non-monotonic, slowly shifts + // the center point closer to the linear line between the endpoints. Should anyone need htis + // function to be actually fast, it would be fairly easy to change it to be so. + for(float blend_to_linear_factor=0.0;blend_to_linear_factor<=1.0;blend_to_linear_factor+=0.05) + { + float tempy2=(1-blend_to_linear_factor)*y2+blend_to_linear_factor*FLerp(y1,y3,x1,x3,x2); + if (!SolveInverseQuadratic(x1,y1,x2,tempy2,x3,y3,a,b,c)) + return false; + float derivative=2.0*a+b; + if ( (y1=0.0) + return true; + } + else + { + if ( (y1>y2) && (y2>y3)) // monotonically decreasing + { + if (derivative<=0.0) + return true; + } + else + return true; + } + } + return true; +} + + +// solves for "a, b, c" where "1/(a x^2 + b x + c ) = y", return true if solution exists +bool SolveInverseReciprocalQuadratic( float x1, float y1, float x2, float y2, float x3, float y3, float &a, float &b, float &c ) +{ + float det = (x1 - x2)*(x1 - x3)*(x2 - x3)*y1*y2*y3; + + // FIXME: check with some sort of epsilon + if (det == 0.0) + return false; + + a = (x1*y1*(y2 - y3) + x3*(y1 - y2)*y3 + x2*y2*(-y1 + y3)) / det; + + b = (x2*x2*y2*(y1 - y3) + x3*x3*(-y1 + y2)*y3 + x1*x1*y1*(-y2 + y3)) / det; + + c = (x2*(x2 - x3)*x3*y2*y3 + x1*x1*y1*(x2*y2 - x3*y3) + x1*(-(x2*x2*y1*y2) + x3*x3*y1*y3)) / det; + + return true; +} + + +// Rotate a vector around the Z axis (YAW) +void VectorYawRotate( const Vector &in, float flYaw, Vector &out) +{ + Assert( s_bMathlibInitialized ); + if (&in == &out ) + { + Vector tmp; + tmp = in; + VectorYawRotate( tmp, flYaw, out ); + return; + } + + float sy, cy; + + SinCos( DEG2RAD(flYaw), &sy, &cy ); + + out.x = in.x * cy - in.y * sy; + out.y = in.x * sy + in.y * cy; + out.z = in.z; +} + + + +float Bias( float x, float biasAmt ) +{ + // WARNING: not thread safe + static float lastAmt = -1; + static float lastExponent = 0; + if( lastAmt != biasAmt ) + { + lastExponent = log( biasAmt ) * -1.4427f; // (-1.4427 = 1 / log(0.5)) + } + return pow( x, lastExponent ); +} + + +float Gain( float x, float biasAmt ) +{ + // WARNING: not thread safe + if( x < 0.5 ) + return 0.5f * Bias( 2*x, 1-biasAmt ); + else + return 1 - 0.5f * Bias( 2 - 2*x, 1-biasAmt ); +} + + +float SmoothCurve( float x ) +{ + return (1 - cos( x * M_PI )) * 0.5f; +} + + +inline float MovePeak( float x, float flPeakPos ) +{ + // Todo: make this higher-order? + if( x < flPeakPos ) + return x * 0.5f / flPeakPos; + else + return 0.5 + 0.5 * (x - flPeakPos) / (1 - flPeakPos); +} + + +float SmoothCurve_Tweak( float x, float flPeakPos, float flPeakSharpness ) +{ + float flMovedPeak = MovePeak( x, flPeakPos ); + float flSharpened = Gain( flMovedPeak, flPeakSharpness ); + return SmoothCurve( flSharpened ); +} + +//----------------------------------------------------------------------------- +// make sure quaternions are within 180 degrees of one another, if not, reverse q +//----------------------------------------------------------------------------- + +void QuaternionAlign( const Quaternion &p, const Quaternion &q, Quaternion &qt ) +{ + Assert( s_bMathlibInitialized ); + + // FIXME: can this be done with a quat dot product? + + int i; + // decide if one of the quaternions is backwards + float a = 0; + float b = 0; + for (i = 0; i < 4; i++) + { + a += (p[i]-q[i])*(p[i]-q[i]); + b += (p[i]+q[i])*(p[i]+q[i]); + } + if (a > b) + { + for (i = 0; i < 4; i++) + { + qt[i] = -q[i]; + } + } + else if (&qt != &q) + { + for (i = 0; i < 4; i++) + { + qt[i] = q[i]; + } + } +} + + +//----------------------------------------------------------------------------- +// Do a piecewise addition of the quaternion elements. This actually makes little +// mathematical sense, but it's a cheap way to simulate a slerp. +//----------------------------------------------------------------------------- +void QuaternionBlend( const Quaternion &p, const Quaternion &q, float t, Quaternion &qt ) +{ + Assert( s_bMathlibInitialized ); +#if ALLOW_SIMD_QUATERNION_MATH + fltx4 psimd, qsimd, qtsimd; + psimd = LoadUnalignedSIMD( p.Base() ); + qsimd = LoadUnalignedSIMD( q.Base() ); + qtsimd = QuaternionBlendSIMD( psimd, qsimd, t ); + StoreUnalignedSIMD( qt.Base(), qtsimd ); +#else + // decide if one of the quaternions is backwards + Quaternion q2; + QuaternionAlign( p, q, q2 ); + QuaternionBlendNoAlign( p, q2, t, qt ); +#endif +} + + +void QuaternionBlendNoAlign( const Quaternion &p, const Quaternion &q, float t, Quaternion &qt ) +{ + Assert( s_bMathlibInitialized ); + float sclp, sclq; + int i; + + // 0.0 returns p, 1.0 return q. + sclp = 1.0f - t; + sclq = t; + for (i = 0; i < 4; i++) { + qt[i] = sclp * p[i] + sclq * q[i]; + } + QuaternionNormalize( qt ); +} + + + +void QuaternionIdentityBlend( const Quaternion &p, float t, Quaternion &qt ) +{ + Assert( s_bMathlibInitialized ); + float sclp; + + sclp = 1.0f - t; + + qt.x = p.x * sclp; + qt.y = p.y * sclp; + qt.z = p.z * sclp; + if (qt.w < 0.0) + { + qt.w = p.w * sclp - t; + } + else + { + qt.w = p.w * sclp + t; + } + QuaternionNormalize( qt ); +} + +//----------------------------------------------------------------------------- +// Quaternion sphereical linear interpolation +//----------------------------------------------------------------------------- + +void QuaternionSlerp( const Quaternion &p, const Quaternion &q, float t, Quaternion &qt ) +{ + Quaternion q2; + // 0.0 returns p, 1.0 return q. + + // decide if one of the quaternions is backwards + QuaternionAlign( p, q, q2 ); + + QuaternionSlerpNoAlign( p, q2, t, qt ); +} + + +void QuaternionSlerpNoAlign( const Quaternion &p, const Quaternion &q, float t, Quaternion &qt ) +{ + Assert( s_bMathlibInitialized ); + float omega, cosom, sinom, sclp, sclq; + int i; + + // 0.0 returns p, 1.0 return q. + + cosom = p[0]*q[0] + p[1]*q[1] + p[2]*q[2] + p[3]*q[3]; + + if ((1.0f + cosom) > 0.000001f) { + if ((1.0f - cosom) > 0.000001f) { + omega = acos( cosom ); + sinom = sin( omega ); + sclp = sin( (1.0f - t)*omega) / sinom; + sclq = sin( t*omega ) / sinom; + } + else { + // TODO: add short circuit for cosom == 1.0f? + sclp = 1.0f - t; + sclq = t; + } + for (i = 0; i < 4; i++) { + qt[i] = sclp * p[i] + sclq * q[i]; + } + } + else { + Assert( &qt != &q ); + + qt[0] = -q[1]; + qt[1] = q[0]; + qt[2] = -q[3]; + qt[3] = q[2]; + sclp = sin( (1.0f - t) * (0.5f * M_PI)); + sclq = sin( t * (0.5f * M_PI)); + for (i = 0; i < 3; i++) { + qt[i] = sclp * p[i] + sclq * qt[i]; + } + } + + Assert( qt.IsValid() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns the angular delta between the two normalized quaternions in degrees. +//----------------------------------------------------------------------------- +float QuaternionAngleDiff( const Quaternion &p, const Quaternion &q ) +{ +#if 1 + // this code path is here for 2 reasons: + // 1 - acos maps 1-epsilon to values much larger than epsilon (vs asin, which maps epsilon to itself) + // this means that in floats, anything below ~0.05 degrees truncates to 0 + // 2 - normalized quaternions are frequently slightly non-normalized due to float precision issues, + // and the epsilon off of normalized can be several percents of a degree + Quaternion qInv, diff; + QuaternionConjugate( q, qInv ); + QuaternionMult( p, qInv, diff ); + + float sinang = sqrt( diff.x * diff.x + diff.y * diff.y + diff.z * diff.z ); + float angle = RAD2DEG( 2 * asin( sinang ) ); + return angle; +#else + Quaternion q2; + QuaternionAlign( p, q, q2 ); + + Assert( s_bMathlibInitialized ); + float cosom = p.x * q2.x + p.y * q2.y + p.z * q2.z + p.w * q2.w; + + if ( cosom > -1.0f ) + { + if ( cosom < 1.0f ) + { + float omega = 2 * fabs( acos( cosom ) ); + return RAD2DEG( omega ); + } + return 0.0f; + } + + return 180.0f; +#endif +} + +void QuaternionConjugate( const Quaternion &p, Quaternion &q ) +{ + Assert( s_bMathlibInitialized ); + Assert( q.IsValid() ); + + q.x = -p.x; + q.y = -p.y; + q.z = -p.z; + q.w = p.w; +} + +void QuaternionInvert( const Quaternion &p, Quaternion &q ) +{ + Assert( s_bMathlibInitialized ); + Assert( q.IsValid() ); + + QuaternionConjugate( p, q ); + + float magnitudeSqr = QuaternionDotProduct( p, p ); + Assert( magnitudeSqr ); + if ( magnitudeSqr ) + { + float inv = 1.0f / magnitudeSqr; + q.x *= inv; + q.y *= inv; + q.z *= inv; + q.w *= inv; + } +} + +//----------------------------------------------------------------------------- +// Make sure the quaternion is of unit length +//----------------------------------------------------------------------------- +float QuaternionNormalize( Quaternion &q ) +{ + Assert( s_bMathlibInitialized ); + float radius, iradius; + + Assert( q.IsValid() ); + + radius = q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3]; + + if ( radius ) // > FLT_EPSILON && ((radius < 1.0f - 4*FLT_EPSILON) || (radius > 1.0f + 4*FLT_EPSILON)) + { + radius = sqrt(radius); + iradius = 1.0f/radius; + q[3] *= iradius; + q[2] *= iradius; + q[1] *= iradius; + q[0] *= iradius; + } + return radius; +} + + +void QuaternionScale( const Quaternion &p, float t, Quaternion &q ) +{ + Assert( s_bMathlibInitialized ); + +#if 0 + Quaternion p0; + Quaternion q; + p0.Init( 0.0, 0.0, 0.0, 1.0 ); + + // slerp in "reverse order" so that p doesn't get realigned + QuaternionSlerp( p, p0, 1.0 - fabs( t ), q ); + if (t < 0.0) + { + q.w = -q.w; + } +#else + float r; + + // FIXME: nick, this isn't overly sensitive to accuracy, and it may be faster to + // use the cos part (w) of the quaternion (sin(omega)*N,cos(omega)) to figure the new scale. + float sinom = sqrt( DotProduct( &p.x, &p.x ) ); + sinom = MIN( sinom, 1.f ); + + float sinsom = sin( asin( sinom ) * t ); + + t = sinsom / (sinom + FLT_EPSILON); + VectorScale( &p.x, t, &q.x ); + + // rescale rotation + r = 1.0f - sinsom * sinsom; + + // Assert( r >= 0 ); + if (r < 0.0f) + r = 0.0f; + r = sqrt( r ); + + // keep sign of rotation + if (p.w < 0) + q.w = -r; + else + q.w = r; +#endif + + Assert( q.IsValid() ); + + return; +} + + +void QuaternionAdd( const Quaternion &p, const Quaternion &q, Quaternion &qt ) +{ + Assert( s_bMathlibInitialized ); + Assert( p.IsValid() ); + Assert( q.IsValid() ); + + // decide if one of the quaternions is backwards + Quaternion q2; + QuaternionAlign( p, q, q2 ); + + // is this right??? + qt[0] = p[0] + q2[0]; + qt[1] = p[1] + q2[1]; + qt[2] = p[2] + q2[2]; + qt[3] = p[3] + q2[3]; + + return; +} + + +float QuaternionDotProduct( const Quaternion &p, const Quaternion &q ) +{ + Assert( s_bMathlibInitialized ); + Assert( p.IsValid() ); + Assert( q.IsValid() ); + + return p.x * q.x + p.y * q.y + p.z * q.z + p.w * q.w; +} + + +// qt = p * q +void QuaternionMult( const Quaternion &p, const Quaternion &q, Quaternion &qt ) +{ + Assert( s_bMathlibInitialized ); + Assert( p.IsValid() ); + Assert( q.IsValid() ); + + if (&p == &qt) + { + Quaternion p2 = p; + QuaternionMult( p2, q, qt ); + return; + } + + // decide if one of the quaternions is backwards + Quaternion q2; + QuaternionAlign( p, q, q2 ); + + qt.x = p.x * q2.w + p.y * q2.z - p.z * q2.y + p.w * q2.x; + qt.y = -p.x * q2.z + p.y * q2.w + p.z * q2.x + p.w * q2.y; + qt.z = p.x * q2.y - p.y * q2.x + p.z * q2.w + p.w * q2.z; + qt.w = -p.x * q2.x - p.y * q2.y - p.z * q2.z + p.w * q2.w; +} + + +void QuaternionMatrix( const Quaternion &q, const Vector &pos, matrix3x4_t& matrix ) +{ + Assert( pos.IsValid() ); + + QuaternionMatrix( q, matrix ); + + matrix[0][3] = pos.x; + matrix[1][3] = pos.y; + matrix[2][3] = pos.z; +} + +void QuaternionMatrix( const Quaternion &q, matrix3x4_t& matrix ) +{ + Assert( s_bMathlibInitialized ); + Assert( q.IsValid() ); + +#ifdef _VPROF_MATHLIB + VPROF_BUDGET( "QuaternionMatrix", "Mathlib" ); +#endif + +// Original code +// This should produce the same code as below with optimization, but looking at the assmebly, +// it doesn't. There are 7 extra multiplies in the release build of this, go figure. +#if 1 + matrix[0][0] = 1.0 - 2.0 * q.y * q.y - 2.0 * q.z * q.z; + matrix[1][0] = 2.0 * q.x * q.y + 2.0 * q.w * q.z; + matrix[2][0] = 2.0 * q.x * q.z - 2.0 * q.w * q.y; + + matrix[0][1] = 2.0f * q.x * q.y - 2.0f * q.w * q.z; + matrix[1][1] = 1.0f - 2.0f * q.x * q.x - 2.0f * q.z * q.z; + matrix[2][1] = 2.0f * q.y * q.z + 2.0f * q.w * q.x; + + matrix[0][2] = 2.0f * q.x * q.z + 2.0f * q.w * q.y; + matrix[1][2] = 2.0f * q.y * q.z - 2.0f * q.w * q.x; + matrix[2][2] = 1.0f - 2.0f * q.x * q.x - 2.0f * q.y * q.y; + + matrix[0][3] = 0.0f; + matrix[1][3] = 0.0f; + matrix[2][3] = 0.0f; +#else + float wx, wy, wz, xx, yy, yz, xy, xz, zz, x2, y2, z2; + + // precalculate common multiplitcations + x2 = q.x + q.x; + y2 = q.y + q.y; + z2 = q.z + q.z; + xx = q.x * x2; + xy = q.x * y2; + xz = q.x * z2; + yy = q.y * y2; + yz = q.y * z2; + zz = q.z * z2; + wx = q.w * x2; + wy = q.w * y2; + wz = q.w * z2; + + matrix[0][0] = 1.0 - (yy + zz); + matrix[0][1] = xy - wz; + matrix[0][2] = xz + wy; + matrix[0][3] = 0.0f; + + matrix[1][0] = xy + wz; + matrix[1][1] = 1.0 - (xx + zz); + matrix[1][2] = yz - wx; + matrix[1][3] = 0.0f; + + matrix[2][0] = xz - wy; + matrix[2][1] = yz + wx; + matrix[2][2] = 1.0 - (xx + yy); + matrix[2][3] = 0.0f; +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: Converts a quaternion into engine angles +// Input : *quaternion - q3 + q0.i + q1.j + q2.k +// *outAngles - PITCH, YAW, ROLL +//----------------------------------------------------------------------------- +void QuaternionAngles( const Quaternion &q, QAngle &angles ) +{ + Assert( s_bMathlibInitialized ); + Assert( q.IsValid() ); + +#ifdef _VPROF_MATHLIB + VPROF_BUDGET( "QuaternionAngles", "Mathlib" ); +#endif + +#if 1 + // FIXME: doing it this way calculates too much data, needs to do an optimized version... + matrix3x4_t matrix; + QuaternionMatrix( q, matrix ); + MatrixAngles( matrix, angles ); +#else + float m11, m12, m13, m23, m33; + + m11 = ( 2.0f * q.w * q.w ) + ( 2.0f * q.x * q.x ) - 1.0f; + m12 = ( 2.0f * q.x * q.y ) + ( 2.0f * q.w * q.z ); + m13 = ( 2.0f * q.x * q.z ) - ( 2.0f * q.w * q.y ); + m23 = ( 2.0f * q.y * q.z ) + ( 2.0f * q.w * q.x ); + m33 = ( 2.0f * q.w * q.w ) + ( 2.0f * q.z * q.z ) - 1.0f; + + // FIXME: this code has a singularity near PITCH +-90 + angles[YAW] = RAD2DEG( atan2(m12, m11) ); + angles[PITCH] = RAD2DEG( asin(-m13) ); + angles[ROLL] = RAD2DEG( atan2(m23, m33) ); +#endif + + Assert( angles.IsValid() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Converts a quaternion to an axis / angle in degrees +// (exponential map) +//----------------------------------------------------------------------------- +void QuaternionAxisAngle( const Quaternion &q, Vector &axis, float &angle ) +{ + angle = RAD2DEG(2 * acos(q.w)); + if ( angle > 180 ) + { + angle -= 360; + } + axis.x = q.x; + axis.y = q.y; + axis.z = q.z; + VectorNormalize( axis ); +} + +//----------------------------------------------------------------------------- +// Purpose: Converts an exponential map (ang/axis) to a quaternion +//----------------------------------------------------------------------------- +void AxisAngleQuaternion( const Vector &axis, float angle, Quaternion &q ) +{ + float sa, ca; + + SinCos( DEG2RAD(angle) * 0.5f, &sa, &ca ); + + q.x = axis.x * sa; + q.y = axis.y * sa; + q.z = axis.z * sa; + q.w = ca; +} + + +//----------------------------------------------------------------------------- +// Purpose: Converts radian-euler axis aligned angles to a quaternion +// Input : *pfAngles - Right-handed Euler angles in radians +// *outQuat - quaternion of form (i,j,k,real) +//----------------------------------------------------------------------------- +void AngleQuaternion( const RadianEuler &angles, Quaternion &outQuat ) +{ + Assert( s_bMathlibInitialized ); +// Assert( angles.IsValid() ); + +#ifdef _VPROF_MATHLIB + VPROF_BUDGET( "AngleQuaternion", "Mathlib" ); +#endif + + float sr, sp, sy, cr, cp, cy; + +#ifdef _X360 + fltx4 radians, scale, sine, cosine; + radians = LoadUnaligned3SIMD( &angles.x ); + scale = ReplicateX4( 0.5f ); + radians = MulSIMD( radians, scale ); + SinCos3SIMD( sine, cosine, radians ); + + // NOTE: The ordering here is *different* from the AngleQuaternion below + // because p, y, r are not in the same locations in QAngle + RadianEuler. Yay! + sr = SubFloat( sine, 0 ); sp = SubFloat( sine, 1 ); sy = SubFloat( sine, 2 ); + cr = SubFloat( cosine, 0 ); cp = SubFloat( cosine, 1 ); cy = SubFloat( cosine, 2 ); +#else + SinCos( angles.z * 0.5f, &sy, &cy ); + SinCos( angles.y * 0.5f, &sp, &cp ); + SinCos( angles.x * 0.5f, &sr, &cr ); +#endif + + // NJS: for some reason VC6 wasn't recognizing the common subexpressions: + float srXcp = sr * cp, crXsp = cr * sp; + outQuat.x = srXcp*cy-crXsp*sy; // X + outQuat.y = crXsp*cy+srXcp*sy; // Y + + float crXcp = cr * cp, srXsp = sr * sp; + outQuat.z = crXcp*sy-srXsp*cy; // Z + outQuat.w = crXcp*cy+srXsp*sy; // W (real component) +} + + +//----------------------------------------------------------------------------- +// Purpose: Converts engine-format euler angles to a quaternion +// Input : angles - Right-handed Euler angles in degrees as follows: +// [0]: PITCH: Clockwise rotation around the Y axis. +// [1]: YAW: Counterclockwise rotation around the Z axis. +// [2]: ROLL: Counterclockwise rotation around the X axis. +// *outQuat - quaternion of form (i,j,k,real) +//----------------------------------------------------------------------------- +void AngleQuaternion( const QAngle &angles, Quaternion &outQuat ) +{ +#ifdef _VPROF_MATHLIB + VPROF_BUDGET( "AngleQuaternion", "Mathlib" ); +#endif + + float sr, sp, sy, cr, cp, cy; + +#ifdef _X360 + fltx4 radians, scale, sine, cosine; + radians = LoadUnaligned3SIMD( angles.Base() ); + scale = ReplicateX4( 0.5f * M_PI_F / 180.f ); + radians = MulSIMD( radians, scale ); + SinCos3SIMD( sine, cosine, radians ); + + // NOTE: The ordering here is *different* from the AngleQuaternion above + // because p, y, r are not in the same locations in QAngle + RadianEuler. Yay! + sp = SubFloat( sine, 0 ); sy = SubFloat( sine, 1 ); sr = SubFloat( sine, 2 ); + cp = SubFloat( cosine, 0 ); cy = SubFloat( cosine, 1 ); cr = SubFloat( cosine, 2 ); +#else + SinCos( DEG2RAD( angles.y ) * 0.5f, &sy, &cy ); + SinCos( DEG2RAD( angles.x ) * 0.5f, &sp, &cp ); + SinCos( DEG2RAD( angles.z ) * 0.5f, &sr, &cr ); +#endif + + // NJS: for some reason VC6 wasn't recognizing the common subexpressions: + float srXcp = sr * cp, crXsp = cr * sp; + outQuat.x = srXcp*cy-crXsp*sy; // X + outQuat.y = crXsp*cy+srXcp*sy; // Y + + float crXcp = cr * cp, srXsp = sr * sp; + outQuat.z = crXcp*sy-srXsp*cy; // Z + outQuat.w = crXcp*cy+srXsp*sy; // W (real component) +} + + +//----------------------------------------------------------------------------- +// Purpose: Converts a basis to a quaternion +//----------------------------------------------------------------------------- +void BasisToQuaternion( const Vector &vecForward, const Vector &vecRight, const Vector &vecUp, Quaternion &q ) +{ + Assert( fabs( vecForward.LengthSqr() - 1.0f ) < 1e-3 ); + Assert( fabs( vecRight.LengthSqr() - 1.0f ) < 1e-3 ); + Assert( fabs( vecUp.LengthSqr() - 1.0f ) < 1e-3 ); + + Vector vecLeft; + VectorMultiply( vecRight, -1.0f, vecLeft ); + + // FIXME: Don't know why, but this doesn't match at all with other result + // so we can't use this super-fast way. + /* + // Find the trace of the matrix: + float flTrace = vecForward.x + vecLeft.y + vecUp.z + 1.0f; + if ( flTrace > 1e-6 ) + { + float flSqrtTrace = FastSqrt( flTrace ); + float s = 0.5f / flSqrtTrace; + q.x = ( vecUp.y - vecLeft.z ) * s; + q.y = ( vecForward.z - vecUp.x ) * s; + q.z = ( vecLeft.x - vecForward.y ) * s; + q.w = 0.5f * flSqrtTrace; + } + else + { + if (( vecForward.x > vecLeft.y ) && ( vecForward.x > vecUp.z ) ) + { + float flSqrtTrace = FastSqrt( 1.0f + vecForward.x - vecLeft.y - vecUp.z ); + float s = 0.5f / flSqrtTrace; + q.x = 0.5f * flSqrtTrace; + q.y = ( vecForward.y + vecLeft.x ) * s; + q.z = ( vecUp.x + vecForward.z ) * s; + q.w = ( vecUp.y - vecLeft.z ) * s; + } + else if ( vecLeft.y > vecUp.z ) + { + float flSqrtTrace = FastSqrt( 1.0f + vecLeft.y - vecForward.x - vecUp.z ); + float s = 0.5f / flSqrtTrace; + q.x = ( vecForward.y + vecLeft.x ) * s; + q.y = 0.5f * flSqrtTrace; + q.z = ( vecUp.y + vecLeft.z ) * s; + q.w = ( vecForward.z - vecUp.x ) * s; + } + else + { + float flSqrtTrace = FastSqrt( 1.0 + vecUp.z - vecForward.x - vecLeft.y ); + float s = 0.5f / flSqrtTrace; + q.x = ( vecUp.x + vecForward.z ) * s; + q.y = ( vecUp.y + vecLeft.z ) * s; + q.z = 0.5f * flSqrtTrace; + q.w = ( vecLeft.x - vecForward.y ) * s; + } + } + QuaternionNormalize( q ); + */ + + // Version 2: Go through angles + + matrix3x4_t mat; + MatrixSetColumn( vecForward, 0, mat ); + MatrixSetColumn( vecLeft, 1, mat ); + MatrixSetColumn( vecUp, 2, mat ); + + QAngle angles; + MatrixAngles( mat, angles ); + +// Quaternion q2; + AngleQuaternion( angles, q ); + +// Assert( fabs(q.x - q2.x) < 1e-3 ); +// Assert( fabs(q.y - q2.y) < 1e-3 ); +// Assert( fabs(q.z - q2.z) < 1e-3 ); +// Assert( fabs(q.w - q2.w) < 1e-3 ); +} + +// FIXME: Optimize! +void MatrixQuaternion( const matrix3x4_t &mat, Quaternion &q ) +{ + QAngle angles; + MatrixAngles( mat, angles ); + AngleQuaternion( angles, q ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Converts a quaternion into engine angles +// Input : *quaternion - q3 + q0.i + q1.j + q2.k +// *outAngles - PITCH, YAW, ROLL +//----------------------------------------------------------------------------- +void QuaternionAngles( const Quaternion &q, RadianEuler &angles ) +{ + Assert( s_bMathlibInitialized ); + Assert( q.IsValid() ); + + // FIXME: doing it this way calculates too much data, needs to do an optimized version... + matrix3x4_t matrix; + QuaternionMatrix( q, matrix ); + MatrixAngles( matrix, angles ); + + Assert( angles.IsValid() ); +} + +//----------------------------------------------------------------------------- +// Purpose: A helper function to normalize p2.x->p1.x and p3.x->p4.x to +// be the same length as p2.x->p3.x +// Input : &p2 - +// &p4 - +// p4n - +//----------------------------------------------------------------------------- +void Spline_Normalize( + const Vector &p1, + const Vector &p2, + const Vector &p3, + const Vector &p4, + Vector& p1n, + Vector& p4n ) +{ + float dt = p3.x - p2.x; + + p1n = p1; + p4n = p4; + + if ( dt != 0.0 ) + { + if (p1.x != p2.x) + { + // Equivalent to p1n = p2 - (p2 - p1) * (dt / (p2.x - p1.x)); + VectorLerp( p2, p1, dt / (p2.x - p1.x), p1n ); + } + if (p4.x != p3.x) + { + // Equivalent to p4n = p3 + (p4 - p3) * (dt / (p4.x - p3.x)); + VectorLerp( p3, p4, dt / (p4.x - p3.x), p4n ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +//----------------------------------------------------------------------------- + +void Catmull_Rom_Spline( + const Vector &p1, + const Vector &p2, + const Vector &p3, + const Vector &p4, + float t, + Vector& output ) +{ + Assert( s_bMathlibInitialized ); + float tSqr = t*t*0.5f; + float tSqrSqr = t*tSqr; + t *= 0.5f; + + Assert( &output != &p1 ); + Assert( &output != &p2 ); + Assert( &output != &p3 ); + Assert( &output != &p4 ); + + output.Init(); + + Vector a, b, c, d; + + // matrix row 1 + VectorScale( p1, -tSqrSqr, a ); // 0.5 t^3 * [ (-1*p1) + ( 3*p2) + (-3*p3) + p4 ] + VectorScale( p2, tSqrSqr*3, b ); + VectorScale( p3, tSqrSqr*-3, c ); + VectorScale( p4, tSqrSqr, d ); + + VectorAdd( a, output, output ); + VectorAdd( b, output, output ); + VectorAdd( c, output, output ); + VectorAdd( d, output, output ); + + // matrix row 2 + VectorScale( p1, tSqr*2, a ); // 0.5 t^2 * [ ( 2*p1) + (-5*p2) + ( 4*p3) - p4 ] + VectorScale( p2, tSqr*-5, b ); + VectorScale( p3, tSqr*4, c ); + VectorScale( p4, -tSqr, d ); + + VectorAdd( a, output, output ); + VectorAdd( b, output, output ); + VectorAdd( c, output, output ); + VectorAdd( d, output, output ); + + // matrix row 3 + VectorScale( p1, -t, a ); // 0.5 t * [ (-1*p1) + p3 ] + VectorScale( p3, t, b ); + + VectorAdd( a, output, output ); + VectorAdd( b, output, output ); + + // matrix row 4 + VectorAdd( p2, output, output ); // p2 +} + +void Catmull_Rom_Spline_Tangent( + const Vector &p1, + const Vector &p2, + const Vector &p3, + const Vector &p4, + float t, + Vector& output ) +{ + Assert( s_bMathlibInitialized ); + float tOne = 3*t*t*0.5f; + float tTwo = 2*t*0.5f; + float tThree = 0.5; + + Assert( &output != &p1 ); + Assert( &output != &p2 ); + Assert( &output != &p3 ); + Assert( &output != &p4 ); + + output.Init(); + + Vector a, b, c, d; + + // matrix row 1 + VectorScale( p1, -tOne, a ); // 0.5 t^3 * [ (-1*p1) + ( 3*p2) + (-3*p3) + p4 ] + VectorScale( p2, tOne*3, b ); + VectorScale( p3, tOne*-3, c ); + VectorScale( p4, tOne, d ); + + VectorAdd( a, output, output ); + VectorAdd( b, output, output ); + VectorAdd( c, output, output ); + VectorAdd( d, output, output ); + + // matrix row 2 + VectorScale( p1, tTwo*2, a ); // 0.5 t^2 * [ ( 2*p1) + (-5*p2) + ( 4*p3) - p4 ] + VectorScale( p2, tTwo*-5, b ); + VectorScale( p3, tTwo*4, c ); + VectorScale( p4, -tTwo, d ); + + VectorAdd( a, output, output ); + VectorAdd( b, output, output ); + VectorAdd( c, output, output ); + VectorAdd( d, output, output ); + + // matrix row 3 + VectorScale( p1, -tThree, a ); // 0.5 t * [ (-1*p1) + p3 ] + VectorScale( p3, tThree, b ); + + VectorAdd( a, output, output ); + VectorAdd( b, output, output ); +} + +// area under the curve [0..t] +void Catmull_Rom_Spline_Integral( + const Vector &p1, + const Vector &p2, + const Vector &p3, + const Vector &p4, + float t, + Vector& output ) +{ + output = p2*t + -0.25f*(p1 - p3)*t*t + + (1.0f/6.0f)*(2.0f*p1 - 5.0f*p2 + 4.0f*p3 - p4)*t*t*t + - 0.125f*(p1 - 3.0f*p2 + 3.0f*p3 - p4)*t*t*t*t; +} + + +// area under the curve [0..1] +void Catmull_Rom_Spline_Integral( + const Vector &p1, + const Vector &p2, + const Vector &p3, + const Vector &p4, + Vector& output ) +{ + output = (-0.25f * p1 + 3.25f * p2 + 3.25f * p3 - 0.25f * p4) * (1.0f / 6.0f); +} + + +void Catmull_Rom_Spline_Normalize( + const Vector &p1, + const Vector &p2, + const Vector &p3, + const Vector &p4, + float t, + Vector& output ) +{ + // Normalize p2->p1 and p3->p4 to be the same length as p2->p3 + float dt = p3.DistTo(p2); + + Vector p1n, p4n; + VectorSubtract( p1, p2, p1n ); + VectorSubtract( p4, p3, p4n ); + + VectorNormalize( p1n ); + VectorNormalize( p4n ); + + VectorMA( p2, dt, p1n, p1n ); + VectorMA( p3, dt, p4n, p4n ); + + Catmull_Rom_Spline( p1n, p2, p3, p4n, t, output ); +} + + +void Catmull_Rom_Spline_Integral_Normalize( + const Vector &p1, + const Vector &p2, + const Vector &p3, + const Vector &p4, + float t, + Vector& output ) +{ + // Normalize p2->p1 and p3->p4 to be the same length as p2->p3 + float dt = p3.DistTo(p2); + + Vector p1n, p4n; + VectorSubtract( p1, p2, p1n ); + VectorSubtract( p4, p3, p4n ); + + VectorNormalize( p1n ); + VectorNormalize( p4n ); + + VectorMA( p2, dt, p1n, p1n ); + VectorMA( p3, dt, p4n, p4n ); + + Catmull_Rom_Spline_Integral( p1n, p2, p3, p4n, t, output ); +} + + +void Catmull_Rom_Spline_NormalizeX( + const Vector &p1, + const Vector &p2, + const Vector &p3, + const Vector &p4, + float t, + Vector& output ) +{ + Vector p1n, p4n; + Spline_Normalize( p1, p2, p3, p4, p1n, p4n ); + Catmull_Rom_Spline( p1n, p2, p3, p4n, t, output ); +} + + +//----------------------------------------------------------------------------- +// Purpose: basic hermite spline. t = 0 returns p1, t = 1 returns p2, +// d1 and d2 are used to entry and exit slope of curve +// Input : +//----------------------------------------------------------------------------- + +void Hermite_Spline( + const Vector &p1, + const Vector &p2, + const Vector &d1, + const Vector &d2, + float t, + Vector& output ) +{ + Assert( s_bMathlibInitialized ); + float tSqr = t*t; + float tCube = t*tSqr; + + Assert( &output != &p1 ); + Assert( &output != &p2 ); + Assert( &output != &d1 ); + Assert( &output != &d2 ); + + float b1 = 2.0f*tCube-3.0f*tSqr+1.0f; + float b2 = 1.0f - b1; // -2*tCube+3*tSqr; + float b3 = tCube-2*tSqr+t; + float b4 = tCube-tSqr; + + VectorScale( p1, b1, output ); + VectorMA( output, b2, p2, output ); + VectorMA( output, b3, d1, output ); + VectorMA( output, b4, d2, output ); +} + +float Hermite_Spline( + float p1, + float p2, + float d1, + float d2, + float t ) +{ + Assert( s_bMathlibInitialized ); + float output; + float tSqr = t*t; + float tCube = t*tSqr; + + float b1 = 2.0f*tCube-3.0f*tSqr+1.0f; + float b2 = 1.0f - b1; // -2*tCube+3*tSqr; + float b3 = tCube-2*tSqr+t; + float b4 = tCube-tSqr; + + output = p1 * b1; + output += p2 * b2; + output += d1 * b3; + output += d2 * b4; + + return output; +} + + +void Hermite_SplineBasis( float t, float basis[4] ) +{ + float tSqr = t*t; + float tCube = t*tSqr; + + basis[0] = 2.0f*tCube-3.0f*tSqr+1.0f; + basis[1] = 1.0f - basis[0]; // -2*tCube+3*tSqr; + basis[2] = tCube-2*tSqr+t; + basis[3] = tCube-tSqr; +} + +//----------------------------------------------------------------------------- +// Purpose: simple three data point hermite spline. +// t = 0 returns p1, t = 1 returns p2, +// slopes are generated from the p0->p1 and p1->p2 segments +// this is reasonable C1 method when there's no "p3" data yet. +// Input : +//----------------------------------------------------------------------------- + +// BUG: the VectorSubtract()'s calls go away if the global optimizer is enabled +#ifdef _MSC_VER +#pragma optimize( "g", off ) +#endif + +void Hermite_Spline( const Vector &p0, const Vector &p1, const Vector &p2, float t, Vector& output ) +{ + Vector e10, e21; + VectorSubtract( p1, p0, e10 ); + VectorSubtract( p2, p1, e21 ); + Hermite_Spline( p1, p2, e10, e21, t, output ); +} + +#ifdef _MSC_VER +#pragma optimize( "", on ) +#endif + +float Hermite_Spline( float p0, float p1, float p2, float t ) +{ + return Hermite_Spline( p1, p2, p1 - p0, p2 - p1, t ); +} + + +void Hermite_Spline( const Quaternion &q0, const Quaternion &q1, const Quaternion &q2, float t, Quaternion &output ) +{ + // cheap, hacked version of quaternions + Quaternion q0a; + Quaternion q1a; + + QuaternionAlign( q2, q0, q0a ); + QuaternionAlign( q2, q1, q1a ); + + output.x = Hermite_Spline( q0a.x, q1a.x, q2.x, t ); + output.y = Hermite_Spline( q0a.y, q1a.y, q2.y, t ); + output.z = Hermite_Spline( q0a.z, q1a.z, q2.z, t ); + output.w = Hermite_Spline( q0a.w, q1a.w, q2.w, t ); + + QuaternionNormalize( output ); +} + +// See http://en.wikipedia.org/wiki/Kochanek-Bartels_curves +// +// Tension: -1 = Round -> 1 = Tight +// Bias: -1 = Pre-shoot (bias left) -> 1 = Post-shoot (bias right) +// Continuity: -1 = Box corners -> 1 = Inverted corners +// +// If T=B=C=0 it's the same matrix as Catmull-Rom. +// If T=1 & B=C=0 it's the same as Cubic. +// If T=B=0 & C=-1 it's just linear interpolation +// +// See http://news.povray.org/povray.binaries.tutorials/attachment/%3CXns91B880592482seed7@povray.org%3E/Splines.bas.txt +// for example code and descriptions of various spline types... +// +void Kochanek_Bartels_Spline( + float tension, + float bias, + float continuity, + const Vector &p1, + const Vector &p2, + const Vector &p3, + const Vector &p4, + float t, + Vector& output ) +{ + Assert( s_bMathlibInitialized ); + + float ffa, ffb, ffc, ffd; + + ffa = ( 1.0f - tension ) * ( 1.0f + continuity ) * ( 1.0f + bias ); + ffb = ( 1.0f - tension ) * ( 1.0f - continuity ) * ( 1.0f - bias ); + ffc = ( 1.0f - tension ) * ( 1.0f - continuity ) * ( 1.0f + bias ); + ffd = ( 1.0f - tension ) * ( 1.0f + continuity ) * ( 1.0f - bias ); + + float tSqr = t*t*0.5f; + float tSqrSqr = t*tSqr; + t *= 0.5f; + + Assert( &output != &p1 ); + Assert( &output != &p2 ); + Assert( &output != &p3 ); + Assert( &output != &p4 ); + + output.Init(); + + Vector a, b, c, d; + + // matrix row 1 + VectorScale( p1, tSqrSqr * -ffa, a ); + VectorScale( p2, tSqrSqr * ( 4.0f + ffa - ffb - ffc ), b ); + VectorScale( p3, tSqrSqr * ( -4.0f + ffb + ffc - ffd ), c ); + VectorScale( p4, tSqrSqr * ffd, d ); + + VectorAdd( a, output, output ); + VectorAdd( b, output, output ); + VectorAdd( c, output, output ); + VectorAdd( d, output, output ); + + // matrix row 2 + VectorScale( p1, tSqr* 2 * ffa, a ); + VectorScale( p2, tSqr * ( -6 - 2 * ffa + 2 * ffb + ffc ), b ); + VectorScale( p3, tSqr * ( 6 - 2 * ffb - ffc + ffd ), c ); + VectorScale( p4, tSqr * -ffd, d ); + + VectorAdd( a, output, output ); + VectorAdd( b, output, output ); + VectorAdd( c, output, output ); + VectorAdd( d, output, output ); + + // matrix row 3 + VectorScale( p1, t * -ffa, a ); + VectorScale( p2, t * ( ffa - ffb ), b ); + VectorScale( p3, t * ffb, c ); + // p4 unchanged + + VectorAdd( a, output, output ); + VectorAdd( b, output, output ); + VectorAdd( c, output, output ); + + // matrix row 4 + // p1, p3, p4 unchanged + // p2 is multiplied by 1 and added, so just added it directly + + VectorAdd( p2, output, output ); +} + +void Kochanek_Bartels_Spline_NormalizeX( + float tension, + float bias, + float continuity, + const Vector &p1, + const Vector &p2, + const Vector &p3, + const Vector &p4, + float t, + Vector& output ) +{ + Vector p1n, p4n; + Spline_Normalize( p1, p2, p3, p4, p1n, p4n ); + Kochanek_Bartels_Spline( tension, bias, continuity, p1n, p2, p3, p4n, t, output ); +} + +void Cubic_Spline( + const Vector &p1, + const Vector &p2, + const Vector &p3, + const Vector &p4, + float t, + Vector& output ) +{ + Assert( s_bMathlibInitialized ); + + float tSqr = t*t; + float tSqrSqr = t*tSqr; + + Assert( &output != &p1 ); + Assert( &output != &p2 ); + Assert( &output != &p3 ); + Assert( &output != &p4 ); + + output.Init(); + + Vector a, b, c, d; + + // matrix row 1 + VectorScale( p2, tSqrSqr * 2, b ); + VectorScale( p3, tSqrSqr * -2, c ); + + VectorAdd( b, output, output ); + VectorAdd( c, output, output ); + + // matrix row 2 + VectorScale( p2, tSqr * -3, b ); + VectorScale( p3, tSqr * 3, c ); + + VectorAdd( b, output, output ); + VectorAdd( c, output, output ); + + // matrix row 3 + // no influence + // p4 unchanged + + // matrix row 4 + // p1, p3, p4 unchanged + VectorAdd( p2, output, output ); +} + +void Cubic_Spline_NormalizeX( + const Vector &p1, + const Vector &p2, + const Vector &p3, + const Vector &p4, + float t, + Vector& output ) +{ + Vector p1n, p4n; + Spline_Normalize( p1, p2, p3, p4, p1n, p4n ); + Cubic_Spline( p1n, p2, p3, p4n, t, output ); +} + +void BSpline( + const Vector &p1, + const Vector &p2, + const Vector &p3, + const Vector &p4, + float t, + Vector& output ) +{ + Assert( s_bMathlibInitialized ); + + float oneOver6 = 1.0f / 6.0f; + + float tSqr = t * t * oneOver6; + float tSqrSqr = t*tSqr; + t *= oneOver6; + + Assert( &output != &p1 ); + Assert( &output != &p2 ); + Assert( &output != &p3 ); + Assert( &output != &p4 ); + + output.Init(); + + Vector a, b, c, d; + + // matrix row 1 + VectorScale( p1, -tSqrSqr, a ); + VectorScale( p2, tSqrSqr * 3.0f, b ); + VectorScale( p3, tSqrSqr * -3.0f, c ); + VectorScale( p4, tSqrSqr, d ); + + VectorAdd( a, output, output ); + VectorAdd( b, output, output ); + VectorAdd( c, output, output ); + VectorAdd( d, output, output ); + + // matrix row 2 + VectorScale( p1, tSqr * 3.0f, a ); + VectorScale( p2, tSqr * -6.0f, b ); + VectorScale( p3, tSqr * 3.0f, c ); + + VectorAdd( a, output, output ); + VectorAdd( b, output, output ); + VectorAdd( c, output, output ); + + // matrix row 3 + VectorScale( p1, t * -3.0f, a ); + VectorScale( p3, t * 3.0f, c ); + // p4 unchanged + + VectorAdd( a, output, output ); + VectorAdd( c, output, output ); + + // matrix row 4 + // p1 and p3 scaled by 1.0f, so done below + VectorScale( p1, oneOver6, a ); + VectorScale( p2, 4.0f * oneOver6, b ); + VectorScale( p3, oneOver6, c ); + + VectorAdd( a, output, output ); + VectorAdd( b, output, output ); + VectorAdd( c, output, output ); +} + +void BSpline_NormalizeX( + const Vector &p1, + const Vector &p2, + const Vector &p3, + const Vector &p4, + float t, + Vector& output ) +{ + Vector p1n, p4n; + Spline_Normalize( p1, p2, p3, p4, p1n, p4n ); + BSpline( p1n, p2, p3, p4n, t, output ); +} + +void Parabolic_Spline( + const Vector &p1, + const Vector &p2, + const Vector &p3, + const Vector &p4, + float t, + Vector& output ) +{ + Assert( s_bMathlibInitialized ); + + float tSqr = t*t*0.5f; + t *= 0.5f; + + Assert( &output != &p1 ); + Assert( &output != &p2 ); + Assert( &output != &p3 ); + Assert( &output != &p4 ); + + output.Init(); + + Vector a, b, c, d; + + // matrix row 1 + // no influence from t cubed + + // matrix row 2 + VectorScale( p1, tSqr, a ); + VectorScale( p2, tSqr * -2.0f, b ); + VectorScale( p3, tSqr, c ); + + VectorAdd( a, output, output ); + VectorAdd( b, output, output ); + VectorAdd( c, output, output ); + + // matrix row 3 + VectorScale( p1, t * -2.0f, a ); + VectorScale( p2, t * 2.0f, b ); + // p4 unchanged + + VectorAdd( a, output, output ); + VectorAdd( b, output, output ); + + // matrix row 4 + VectorScale( p1, 0.5f, a ); + VectorScale( p2, 0.5f, b ); + + VectorAdd( a, output, output ); + VectorAdd( b, output, output ); +} + +void Parabolic_Spline_NormalizeX( + const Vector &p1, + const Vector &p2, + const Vector &p3, + const Vector &p4, + float t, + Vector& output ) +{ + Vector p1n, p4n; + Spline_Normalize( p1, p2, p3, p4, p1n, p4n ); + Parabolic_Spline( p1n, p2, p3, p4n, t, output ); +} + +//----------------------------------------------------------------------------- +// Purpose: Compress the input values for a ranged result such that from 75% to 200% smoothly of the range maps +//----------------------------------------------------------------------------- + +float RangeCompressor( float flValue, float flMin, float flMax, float flBase ) +{ + // clamp base + if (flBase < flMin) + flBase = flMin; + if (flBase > flMax) + flBase = flMax; + + flValue += flBase; + + // convert to 0 to 1 value + float flMid = (flValue - flMin) / (flMax - flMin); + // convert to -1 to 1 value + float flTarget = flMid * 2 - 1; + + if (fabs(flTarget) > 0.75) + { + float t = (fabs(flTarget) - 0.75) / (1.25); + if (t < 1.0) + { + if (flTarget > 0) + { + flTarget = Hermite_Spline( 0.75, 1, 0.75, 0, t ); + } + else + { + flTarget = -Hermite_Spline( 0.75, 1, 0.75, 0, t ); + } + } + else + { + flTarget = (flTarget > 0) ? 1.0f : -1.0f; + } + } + + flMid = (flTarget + 1 ) / 2.0; + flValue = flMin * (1 - flMid) + flMax * flMid; + + flValue -= flBase; + + return flValue; +} + + +//#pragma optimize( "", on ) + +//----------------------------------------------------------------------------- +// Transforms a AABB into another space; which will inherently grow the box. +//----------------------------------------------------------------------------- +void TransformAABB( const matrix3x4_t& transform, const Vector &vecMinsIn, const Vector &vecMaxsIn, Vector &vecMinsOut, Vector &vecMaxsOut ) +{ + Vector localCenter; + VectorAdd( vecMinsIn, vecMaxsIn, localCenter ); + localCenter *= 0.5f; + + Vector localExtents; + VectorSubtract( vecMaxsIn, localCenter, localExtents ); + + Vector worldCenter; + VectorTransform( localCenter, transform, worldCenter ); + + Vector worldExtents; + worldExtents.x = DotProductAbs( localExtents, transform[0] ); + worldExtents.y = DotProductAbs( localExtents, transform[1] ); + worldExtents.z = DotProductAbs( localExtents, transform[2] ); + + VectorSubtract( worldCenter, worldExtents, vecMinsOut ); + VectorAdd( worldCenter, worldExtents, vecMaxsOut ); +} + + +//----------------------------------------------------------------------------- +// Uses the inverse transform of in1 +//----------------------------------------------------------------------------- +void ITransformAABB( const matrix3x4_t& transform, const Vector &vecMinsIn, const Vector &vecMaxsIn, Vector &vecMinsOut, Vector &vecMaxsOut ) +{ + Vector worldCenter; + VectorAdd( vecMinsIn, vecMaxsIn, worldCenter ); + worldCenter *= 0.5f; + + Vector worldExtents; + VectorSubtract( vecMaxsIn, worldCenter, worldExtents ); + + Vector localCenter; + VectorITransform( worldCenter, transform, localCenter ); + + Vector localExtents; + localExtents.x = FloatMakePositive( worldExtents.x * transform[0][0] ) + + FloatMakePositive( worldExtents.y * transform[1][0] ) + + FloatMakePositive( worldExtents.z * transform[2][0] ); + localExtents.y = FloatMakePositive( worldExtents.x * transform[0][1] ) + + FloatMakePositive( worldExtents.y * transform[1][1] ) + + FloatMakePositive( worldExtents.z * transform[2][1] ); + localExtents.z = FloatMakePositive( worldExtents.x * transform[0][2] ) + + FloatMakePositive( worldExtents.y * transform[1][2] ) + + FloatMakePositive( worldExtents.z * transform[2][2] ); + + VectorSubtract( localCenter, localExtents, vecMinsOut ); + VectorAdd( localCenter, localExtents, vecMaxsOut ); +} + + +//----------------------------------------------------------------------------- +// Rotates a AABB into another space; which will inherently grow the box. +// (same as TransformAABB, but doesn't take the translation into account) +//----------------------------------------------------------------------------- +void RotateAABB( const matrix3x4_t &transform, const Vector &vecMinsIn, const Vector &vecMaxsIn, Vector &vecMinsOut, Vector &vecMaxsOut ) +{ + Vector localCenter; + VectorAdd( vecMinsIn, vecMaxsIn, localCenter ); + localCenter *= 0.5f; + + Vector localExtents; + VectorSubtract( vecMaxsIn, localCenter, localExtents ); + + Vector newCenter; + VectorRotate( localCenter, transform, newCenter ); + + Vector newExtents; + newExtents.x = DotProductAbs( localExtents, transform[0] ); + newExtents.y = DotProductAbs( localExtents, transform[1] ); + newExtents.z = DotProductAbs( localExtents, transform[2] ); + + VectorSubtract( newCenter, newExtents, vecMinsOut ); + VectorAdd( newCenter, newExtents, vecMaxsOut ); +} + + +//----------------------------------------------------------------------------- +// Uses the inverse transform of in1 +//----------------------------------------------------------------------------- +void IRotateAABB( const matrix3x4_t &transform, const Vector &vecMinsIn, const Vector &vecMaxsIn, Vector &vecMinsOut, Vector &vecMaxsOut ) +{ + Vector oldCenter; + VectorAdd( vecMinsIn, vecMaxsIn, oldCenter ); + oldCenter *= 0.5f; + + Vector oldExtents; + VectorSubtract( vecMaxsIn, oldCenter, oldExtents ); + + Vector newCenter; + VectorIRotate( oldCenter, transform, newCenter ); + + Vector newExtents; + newExtents.x = FloatMakePositive( oldExtents.x * transform[0][0] ) + + FloatMakePositive( oldExtents.y * transform[1][0] ) + + FloatMakePositive( oldExtents.z * transform[2][0] ); + newExtents.y = FloatMakePositive( oldExtents.x * transform[0][1] ) + + FloatMakePositive( oldExtents.y * transform[1][1] ) + + FloatMakePositive( oldExtents.z * transform[2][1] ); + newExtents.z = FloatMakePositive( oldExtents.x * transform[0][2] ) + + FloatMakePositive( oldExtents.y * transform[1][2] ) + + FloatMakePositive( oldExtents.z * transform[2][2] ); + + VectorSubtract( newCenter, newExtents, vecMinsOut ); + VectorAdd( newCenter, newExtents, vecMaxsOut ); +} + + +float CalcSqrDistanceToAABB( const Vector &mins, const Vector &maxs, const Vector &point ) +{ + float flDelta; + float flDistSqr = 0.0f; + + if ( point.x < mins.x ) + { + flDelta = (mins.x - point.x); + flDistSqr += flDelta * flDelta; + } + else if ( point.x > maxs.x ) + { + flDelta = (point.x - maxs.x); + flDistSqr += flDelta * flDelta; + } + + if ( point.y < mins.y ) + { + flDelta = (mins.y - point.y); + flDistSqr += flDelta * flDelta; + } + else if ( point.y > maxs.y ) + { + flDelta = (point.y - maxs.y); + flDistSqr += flDelta * flDelta; + } + + if ( point.z < mins.z ) + { + flDelta = (mins.z - point.z); + flDistSqr += flDelta * flDelta; + } + else if ( point.z > maxs.z ) + { + flDelta = (point.z - maxs.z); + flDistSqr += flDelta * flDelta; + } + + return flDistSqr; +} + + +void CalcClosestPointOnAABB( const Vector &mins, const Vector &maxs, const Vector &point, Vector &closestOut ) +{ + closestOut.x = clamp( point.x, mins.x, maxs.x ); + closestOut.y = clamp( point.y, mins.y, maxs.y ); + closestOut.z = clamp( point.z, mins.z, maxs.z ); +} + +void CalcSqrDistAndClosestPointOnAABB( const Vector &mins, const Vector &maxs, const Vector &point, Vector &closestOut, float &distSqrOut ) +{ + distSqrOut = 0.0f; + for ( int i = 0; i < 3; i++ ) + { + if ( point[i] < mins[i] ) + { + closestOut[i] = mins[i]; + float flDelta = closestOut[i] - mins[i]; + distSqrOut += flDelta * flDelta; + } + else if ( point[i] > maxs[i] ) + { + closestOut[i] = maxs[i]; + float flDelta = closestOut[i] - maxs[i]; + distSqrOut += flDelta * flDelta; + } + else + { + closestOut[i] = point[i]; + } + } + +} + +float CalcClosestPointToLineT( const Vector &P, const Vector &vLineA, const Vector &vLineB, Vector &vDir ) +{ + Assert( s_bMathlibInitialized ); + VectorSubtract( vLineB, vLineA, vDir ); + + // D dot [P - (A + D*t)] = 0 + // t = ( DP - DA) / DD + float div = vDir.Dot( vDir ); + if( div < 0.00001f ) + { + return 0; + } + else + { + return (vDir.Dot( P ) - vDir.Dot( vLineA )) / div; + } +} + +void CalcClosestPointOnLine( const Vector &P, const Vector &vLineA, const Vector &vLineB, Vector &vClosest, float *outT ) +{ + Assert( s_bMathlibInitialized ); + Vector vDir; + float t = CalcClosestPointToLineT( P, vLineA, vLineB, vDir ); + if ( outT ) *outT = t; + vClosest.MulAdd( vLineA, vDir, t ); +} + + +float CalcDistanceToLine( const Vector &P, const Vector &vLineA, const Vector &vLineB, float *outT ) +{ + Assert( s_bMathlibInitialized ); + Vector vClosest; + CalcClosestPointOnLine( P, vLineA, vLineB, vClosest, outT ); + return P.DistTo(vClosest); +} + +float CalcDistanceSqrToLine( const Vector &P, const Vector &vLineA, const Vector &vLineB, float *outT ) +{ + Assert( s_bMathlibInitialized ); + Vector vClosest; + CalcClosestPointOnLine( P, vLineA, vLineB, vClosest, outT ); + return P.DistToSqr(vClosest); +} + +void CalcClosestPointOnLineSegment( const Vector &P, const Vector &vLineA, const Vector &vLineB, Vector &vClosest, float *outT ) +{ + Vector vDir; + float t = CalcClosestPointToLineT( P, vLineA, vLineB, vDir ); + t = clamp( t, 0, 1 ); + if ( outT ) + { + *outT = t; + } + vClosest.MulAdd( vLineA, vDir, t ); +} + + +float CalcDistanceToLineSegment( const Vector &P, const Vector &vLineA, const Vector &vLineB, float *outT ) +{ + Assert( s_bMathlibInitialized ); + Vector vClosest; + CalcClosestPointOnLineSegment( P, vLineA, vLineB, vClosest, outT ); + return P.DistTo( vClosest ); +} + +float CalcDistanceSqrToLineSegment( const Vector &P, const Vector &vLineA, const Vector &vLineB, float *outT ) +{ + Assert( s_bMathlibInitialized ); + Vector vClosest; + CalcClosestPointOnLineSegment( P, vLineA, vLineB, vClosest, outT ); + return P.DistToSqr(vClosest); +} + +float CalcClosestPointToLineT2D( const Vector2D &P, const Vector2D &vLineA, const Vector2D &vLineB, Vector2D &vDir ) +{ + Assert( s_bMathlibInitialized ); + Vector2DSubtract( vLineB, vLineA, vDir ); + + // D dot [P - (A + D*t)] = 0 + // t = (DP - DA) / DD + float div = vDir.Dot( vDir ); + if( div < 0.00001f ) + { + return 0; + } + else + { + return (vDir.Dot( P ) - vDir.Dot( vLineA )) / div; + } +} + +void CalcClosestPointOnLine2D( const Vector2D &P, const Vector2D &vLineA, const Vector2D &vLineB, Vector2D &vClosest, float *outT ) +{ + Assert( s_bMathlibInitialized ); + Vector2D vDir; + float t = CalcClosestPointToLineT2D( P, vLineA, vLineB, vDir ); + if ( outT ) *outT = t; + vClosest.MulAdd( vLineA, vDir, t ); +} + +float CalcDistanceToLine2D( const Vector2D &P, const Vector2D &vLineA, const Vector2D &vLineB, float *outT ) +{ + Assert( s_bMathlibInitialized ); + Vector2D vClosest; + CalcClosestPointOnLine2D( P, vLineA, vLineB, vClosest, outT ); + return P.DistTo( vClosest ); +} + +float CalcDistanceSqrToLine2D( const Vector2D &P, const Vector2D &vLineA, const Vector2D &vLineB, float *outT ) +{ + Assert( s_bMathlibInitialized ); + Vector2D vClosest; + CalcClosestPointOnLine2D( P, vLineA, vLineB, vClosest, outT ); + return P.DistToSqr(vClosest); +} + +void CalcClosestPointOnLineSegment2D( const Vector2D &P, const Vector2D &vLineA, const Vector2D &vLineB, Vector2D &vClosest, float *outT ) +{ + Vector2D vDir; + float t = CalcClosestPointToLineT2D( P, vLineA, vLineB, vDir ); + t = clamp( t, 0, 1 ); + if ( outT ) + { + *outT = t; + } + vClosest.MulAdd( vLineA, vDir, t ); +} + +float CalcDistanceToLineSegment2D( const Vector2D &P, const Vector2D &vLineA, const Vector2D &vLineB, float *outT ) +{ + Assert( s_bMathlibInitialized ); + Vector2D vClosest; + CalcClosestPointOnLineSegment2D( P, vLineA, vLineB, vClosest, outT ); + return P.DistTo( vClosest ); +} + +float CalcDistanceSqrToLineSegment2D( const Vector2D &P, const Vector2D &vLineA, const Vector2D &vLineB, float *outT ) +{ + Assert( s_bMathlibInitialized ); + Vector2D vClosest; + CalcClosestPointOnLineSegment2D( P, vLineA, vLineB, vClosest, outT ); + return P.DistToSqr( vClosest ); +} + +// Do we have another epsilon we could use +#define LINE_EPS ( 0.000001f ) + +//----------------------------------------------------------------------------- +// Purpose: Given lines p1->p2 and p3->p4, computes a line segment (pa->pb) and returns the parameters 0->1 multipliers +// along each segment for the returned points +// Input : p1 - +// p2 - +// p3 - +// p4 - +// *s1 - +// *s2 - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CalcLineToLineIntersectionSegment( + const Vector& p1,const Vector& p2,const Vector& p3,const Vector& p4,Vector *s1,Vector *s2, + float *t1, float *t2) +{ + Vector p13,p43,p21; + float d1343,d4321,d1321,d4343,d2121; + float numer,denom; + + p13.x = p1.x - p3.x; + p13.y = p1.y - p3.y; + p13.z = p1.z - p3.z; + p43.x = p4.x - p3.x; + p43.y = p4.y - p3.y; + p43.z = p4.z - p3.z; + + if (fabs(p43.x) < LINE_EPS && fabs(p43.y) < LINE_EPS && fabs(p43.z) < LINE_EPS) + return false; + p21.x = p2.x - p1.x; + p21.y = p2.y - p1.y; + p21.z = p2.z - p1.z; + if (fabs(p21.x) < LINE_EPS && fabs(p21.y) < LINE_EPS && fabs(p21.z) < LINE_EPS) + return false; + + d1343 = p13.x * p43.x + p13.y * p43.y + p13.z * p43.z; + d4321 = p43.x * p21.x + p43.y * p21.y + p43.z * p21.z; + d1321 = p13.x * p21.x + p13.y * p21.y + p13.z * p21.z; + d4343 = p43.x * p43.x + p43.y * p43.y + p43.z * p43.z; + d2121 = p21.x * p21.x + p21.y * p21.y + p21.z * p21.z; + + denom = d2121 * d4343 - d4321 * d4321; + if (fabs(denom) < LINE_EPS) + return false; + numer = d1343 * d4321 - d1321 * d4343; + + *t1 = numer / denom; + *t2 = (d1343 + d4321 * (*t1)) / d4343; + + s1->x = p1.x + *t1 * p21.x; + s1->y = p1.y + *t1 * p21.y; + s1->z = p1.z + *t1 * p21.z; + s2->x = p3.x + *t2 * p43.x; + s2->y = p3.y + *t2 * p43.y; + s2->z = p3.z + *t2 * p43.z; + + return true; +} + +#ifdef _MSC_VER +#pragma optimize( "", off ) +#endif + +#ifndef EXCEPTION_EXECUTE_HANDLER +#define EXCEPTION_EXECUTE_HANDLER 1 +#endif + +#ifdef _MSC_VER +#pragma optimize( "", on ) +#endif + +static bool s_b3DNowEnabled = false; +static bool s_bMMXEnabled = false; +static bool s_bSSEEnabled = false; +static bool s_bSSE2Enabled = false; + +void MathLib_Init( float gamma, float texGamma, float brightness, int overbright, bool bAllow3DNow, bool bAllowSSE, bool bAllowSSE2, bool bAllowMMX ) +{ + if ( s_bMathlibInitialized ) + return; + + // FIXME: Hook SSE into VectorAligned + Vector4DAligned + +#if !defined( _X360 ) + // Grab the processor information: + const CPUInformation& pi = GetCPUInformation(); + + // Select the default generic routines. + pfSqrt = _sqrtf; + pfRSqrt = _rsqrtf; + pfRSqrtFast = _rsqrtf; + pfVectorNormalize = _VectorNormalize; + pfVectorNormalizeFast = _VectorNormalizeFast; + pfInvRSquared = _InvRSquared; + pfFastSinCos = SinCos; + pfFastCos = cosf; + + if ( bAllowMMX && pi.m_bMMX ) + { + // Select the MMX specific routines if available + // (MMX routines were used by SW span fillers - not currently used for HW) + s_bMMXEnabled = true; + } + else + { + s_bMMXEnabled = false; + } + + // SSE Generally performs better than 3DNow when present, so this is placed + // first to allow SSE to override these settings. + if ( bAllow3DNow && pi.m_b3DNow ) + { + s_b3DNowEnabled = true; + + // Select the 3DNow specific routines if available; + pfVectorNormalize = _3DNow_VectorNormalize; + pfVectorNormalizeFast = _3DNow_VectorNormalizeFast; + pfInvRSquared = _3DNow_InvRSquared; + pfSqrt = _3DNow_Sqrt; + pfRSqrt = _3DNow_RSqrt; + pfRSqrtFast = _3DNow_RSqrt; + } + else + { + s_b3DNowEnabled = false; + } + + if ( bAllowSSE && pi.m_bSSE ) + { + s_bSSEEnabled = true; + + // Select the SSE specific routines if available + pfVectorNormalize = _VectorNormalize; + pfVectorNormalizeFast = _SSE_VectorNormalizeFast; + pfInvRSquared = _SSE_InvRSquared; + pfSqrt = _SSE_Sqrt; + pfRSqrt = _SSE_RSqrtAccurate; + pfRSqrtFast = _SSE_RSqrtFast; +#ifdef _WIN32 + pfFastSinCos = _SSE_SinCos; + pfFastCos = _SSE_cos; +#endif + } + else + { + s_bSSEEnabled = false; + } + + if ( bAllowSSE2 && pi.m_bSSE2 ) + { + s_bSSE2Enabled = true; +#ifdef _WIN32 + pfFastSinCos = _SSE2_SinCos; + pfFastCos = _SSE2_cos; +#endif + } + else + { + s_bSSE2Enabled = false; + } +#endif + + s_bMathlibInitialized = true; + + InitSinCosTable(); + BuildGammaTable( gamma, texGamma, brightness, overbright ); +} + +bool MathLib_3DNowEnabled( void ) +{ + Assert( s_bMathlibInitialized ); + return s_b3DNowEnabled; +} + +bool MathLib_MMXEnabled( void ) +{ + Assert( s_bMathlibInitialized ); + return s_bMMXEnabled; +} + +bool MathLib_SSEEnabled( void ) +{ + Assert( s_bMathlibInitialized ); + return s_bSSEEnabled; +} + +bool MathLib_SSE2Enabled( void ) +{ + Assert( s_bMathlibInitialized ); + return s_bSSE2Enabled; +} + +float Approach( float target, float value, float speed ) +{ + float delta = target - value; + + if ( delta > speed ) + value += speed; + else if ( delta < -speed ) + value -= speed; + else + value = target; + + return value; +} + +// BUGBUG: Why doesn't this call angle diff?!?!? +float ApproachAngle( float target, float value, float speed ) +{ + target = anglemod( target ); + value = anglemod( value ); + + float delta = target - value; + + // Speed is assumed to be positive + if ( speed < 0 ) + speed = -speed; + + if ( delta < -180 ) + delta += 360; + else if ( delta > 180 ) + delta -= 360; + + if ( delta > speed ) + value += speed; + else if ( delta < -speed ) + value -= speed; + else + value = target; + + return value; +} + + +// BUGBUG: Why do we need both of these? +float AngleDiff( float destAngle, float srcAngle ) +{ + float delta; + + delta = fmodf(destAngle - srcAngle, 360.0f); + if ( destAngle > srcAngle ) + { + if ( delta >= 180 ) + delta -= 360; + } + else + { + if ( delta <= -180 ) + delta += 360; + } + return delta; +} + + +float AngleDistance( float next, float cur ) +{ + float delta = next - cur; + + if ( delta < -180 ) + delta += 360; + else if ( delta > 180 ) + delta -= 360; + + return delta; +} + + +float AngleNormalize( float angle ) +{ + angle = fmodf(angle, 360.0f); + if (angle > 180) + { + angle -= 360; + } + if (angle < -180) + { + angle += 360; + } + return angle; +} + +//-------------------------------------------------------------------------------------------------------------- +// ensure that 0 <= angle <= 360 +float AngleNormalizePositive( float angle ) +{ + angle = fmodf( angle, 360.0f ); + + if (angle < 0.0f) + { + angle += 360.0f; + } + + return angle; +} + +//-------------------------------------------------------------------------------------------------------------- +bool AnglesAreEqual( float a, float b, float tolerance ) +{ + return (fabs( AngleDiff( a, b ) ) < tolerance); +} + +void RotationDeltaAxisAngle( const QAngle &srcAngles, const QAngle &destAngles, Vector &deltaAxis, float &deltaAngle ) +{ + Quaternion srcQuat, destQuat, srcQuatInv, out; + AngleQuaternion( srcAngles, srcQuat ); + AngleQuaternion( destAngles, destQuat ); + QuaternionScale( srcQuat, -1, srcQuatInv ); + QuaternionMult( destQuat, srcQuatInv, out ); + + QuaternionNormalize( out ); + QuaternionAxisAngle( out, deltaAxis, deltaAngle ); +} + +void RotationDelta( const QAngle &srcAngles, const QAngle &destAngles, QAngle *out ) +{ + matrix3x4_t src, srcInv; + matrix3x4_t dest; + AngleMatrix( srcAngles, src ); + AngleMatrix( destAngles, dest ); + // xform = src(-1) * dest + MatrixInvert( src, srcInv ); + matrix3x4_t xform; + ConcatTransforms( dest, srcInv, xform ); + QAngle xformAngles; + MatrixAngles( xform, xformAngles ); + if ( out ) + { + *out = xformAngles; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Computes a triangle normal +//----------------------------------------------------------------------------- +void ComputeTrianglePlane( const Vector& v1, const Vector& v2, const Vector& v3, Vector& normal, float& intercept ) +{ + Vector e1, e2; + VectorSubtract( v2, v1, e1 ); + VectorSubtract( v3, v1, e2 ); + CrossProduct( e1, e2, normal ); + VectorNormalize( normal ); + intercept = DotProduct( normal, v1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: This is a clone of BaseWindingForPlane() +// Input : *outVerts - an array of preallocated verts to build the polygon in +// normal - the plane normal +// dist - the plane constant +// Output : int - vert count (always 4) +//----------------------------------------------------------------------------- +int PolyFromPlane( Vector *outVerts, const Vector& normal, float dist, float fHalfScale ) +{ + int i, x; + vec_t max, v; + Vector org, vright, vup; + + // find the major axis + + max = -16384; //MAX_COORD_INTEGER + x = -1; + for (i=0 ; i<3; i++) + { + v = fabs(normal[i]); + if (v > max) + { + x = i; + max = v; + } + } + + if (x==-1) + return 0; + + // Build a unit vector along something other than the major axis + VectorCopy (vec3_origin, vup); + switch (x) + { + case 0: + case 1: + vup[2] = 1; + break; + case 2: + vup[0] = 1; + break; + } + + // Remove the component of this vector along the normal + v = DotProduct (vup, normal); + VectorMA (vup, -v, normal, vup); + // Make it a unit (perpendicular) + VectorNormalize (vup); + + // Center of the poly is at normal * dist + VectorScale (normal, dist, org); + // Calculate the third orthonormal basis vector for our plane space (this one and vup are in the plane) + CrossProduct (vup, normal, vright); + + // Make the plane's basis vectors big (these are the half-sides of the polygon we're making) + VectorScale (vup, fHalfScale, vup); + VectorScale (vright, fHalfScale, vright); + + // Move diagonally away from org to create the corner verts + VectorSubtract (org, vright, outVerts[0]); // left + VectorAdd (outVerts[0], vup, outVerts[0]); // up + + VectorAdd (org, vright, outVerts[1]); // right + VectorAdd (outVerts[1], vup, outVerts[1]); // up + + VectorAdd (org, vright, outVerts[2]); // right + VectorSubtract (outVerts[2], vup, outVerts[2]); // down + + VectorSubtract (org, vright, outVerts[3]); // left + VectorSubtract (outVerts[3], vup, outVerts[3]); // down + + // The four corners form a planar quadrilateral normal to "normal" + return 4; +} + +//----------------------------------------------------------------------------- +// Purpose: clip a poly to the plane and return the poly on the front side of the plane +// Input : *inVerts - input polygon +// vertCount - # verts in input poly +// *outVerts - destination poly +// normal - plane normal +// dist - plane constant +// Output : int - # verts in output poly +//----------------------------------------------------------------------------- + +int ClipPolyToPlane( Vector *inVerts, int vertCount, Vector *outVerts, const Vector& normal, float dist, float fOnPlaneEpsilon ) +{ + vec_t *dists = (vec_t *)stackalloc( sizeof(vec_t) * vertCount * 4 ); //4x vertcount should cover all cases + int *sides = (int *)stackalloc( sizeof(vec_t) * vertCount * 4 ); + int counts[3]; + vec_t dot; + int i, j; + Vector mid = vec3_origin; + int outCount; + + counts[0] = counts[1] = counts[2] = 0; + + // determine sides for each point + for ( i = 0; i < vertCount; i++ ) + { + dot = DotProduct( inVerts[i], normal) - dist; + dists[i] = dot; + if ( dot > fOnPlaneEpsilon ) + { + sides[i] = SIDE_FRONT; + } + else if ( dot < -fOnPlaneEpsilon ) + { + sides[i] = SIDE_BACK; + } + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + if (!counts[0]) + return 0; + + if (!counts[1]) + { + // Copy to output verts + for ( i = 0; i < vertCount; i++ ) + { + VectorCopy( inVerts[i], outVerts[i] ); + } + return vertCount; + } + + outCount = 0; + for ( i = 0; i < vertCount; i++ ) + { + Vector& p1 = inVerts[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy( p1, outVerts[outCount]); + outCount++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy( p1, outVerts[outCount]); + outCount++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + Vector& p2 = inVerts[(i+1)%vertCount]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, outVerts[outCount]); + outCount++; + } + + return outCount; +} + + +int ClipPolyToPlane_Precise( double *inVerts, int vertCount, double *outVerts, const double *normal, double dist, double fOnPlaneEpsilon ) +{ + double *dists = (double *)stackalloc( sizeof(double) * vertCount * 4 ); //4x vertcount should cover all cases + int *sides = (int *)stackalloc( sizeof(double) * vertCount * 4 ); + int counts[3]; + double dot; + int i, j; + //Vector mid = vec3_origin; + double mid[3]; + mid[0] = 0.0; + mid[1] = 0.0; + mid[2] = 0.0; + int outCount; + + counts[0] = counts[1] = counts[2] = 0; + + // determine sides for each point + for ( i = 0; i < vertCount; i++ ) + { + //dot = DotProduct( inVerts[i], normal) - dist; + dot = ((inVerts[i*3 + 0] * normal[0]) + (inVerts[i*3 + 1] * normal[1]) + (inVerts[i*3 + 2] * normal[2])) - dist; + dists[i] = dot; + if ( dot > fOnPlaneEpsilon ) + { + sides[i] = SIDE_FRONT; + } + else if ( dot < -fOnPlaneEpsilon ) + { + sides[i] = SIDE_BACK; + } + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + if (!counts[0]) + return 0; + + if (!counts[1]) + { + // Copy to output verts + //for ( i = 0; i < vertCount; i++ ) + for ( i = 0; i < vertCount * 3; i++ ) + { + //VectorCopy( inVerts[i], outVerts[i] ); + outVerts[i] = inVerts[i]; + } + return vertCount; + } + + outCount = 0; + for ( i = 0; i < vertCount; i++ ) + { + //Vector& p1 = inVerts[i]; + double *p1 = &inVerts[i*3]; + //p1[0] = inVerts[i*3 + 0]; + //p1[1] = inVerts[i*3 + 1]; + //p1[2] = inVerts[i*3 + 2]; + + if (sides[i] == SIDE_ON) + { + //VectorCopy( p1, outVerts[outCount]); + outVerts[outCount*3 + 0] = p1[0]; + outVerts[outCount*3 + 1] = p1[1]; + outVerts[outCount*3 + 2] = p1[2]; + outCount++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + //VectorCopy( p1, outVerts[outCount]); + outVerts[outCount*3 + 0] = p1[0]; + outVerts[outCount*3 + 1] = p1[1]; + outVerts[outCount*3 + 2] = p1[2]; + outCount++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + //Vector& p2 = inVerts[(i+1)%vertCount]; + int wrappedindex = (i+1)%vertCount; + double *p2 = &inVerts[wrappedindex*3]; + //p2[0] = inVerts[wrappedindex*3 + 0]; + //p2[1] = inVerts[wrappedindex*3 + 1]; + //p2[2] = inVerts[wrappedindex*3 + 2]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { + mid[j] = (double)p1[j] + dot*((double)p2[j]-(double)p1[j]); + } + + //VectorCopy (mid, outVerts[outCount]); + outVerts[outCount*3 + 0] = mid[0]; + outVerts[outCount*3 + 1] = mid[1]; + outVerts[outCount*3 + 2] = mid[2]; + outCount++; + } + + return outCount; +} + +int CeilPow2( int in ) +{ + int retval; + + retval = 1; + while( retval < in ) + retval <<= 1; + return retval; +} + +int FloorPow2( int in ) +{ + int retval; + + retval = 1; + while( retval < in ) + retval <<= 1; + return retval >> 1; +} + + +//----------------------------------------------------------------------------- +// Computes Y fov from an X fov and a screen aspect ratio +//----------------------------------------------------------------------------- +float CalcFovY( float flFovX, float flAspect ) +{ + if ( flFovX < 1 || flFovX > 179) + { + flFovX = 90; // error, set to 90 + } + + // The long, but illustrative version (more closely matches CShaderAPIDX8::PerspectiveX, which + // is what it's based on). + // + //float width = 2 * zNear * tan( DEG2RAD( fov_x / 2.0 ) ); + //float height = width / screenaspect; + //float yRadians = atan( (height/2.0) / zNear ); + //return RAD2DEG( yRadians ) * 2; + + // The short and sweet version. + float val = atan( tan( DEG2RAD( flFovX ) * 0.5f ) / flAspect ); + val = RAD2DEG( val ) * 2.0f; + return val; +} + +float CalcFovX( float flFovY, float flAspect ) +{ + return RAD2DEG( atan( tan( DEG2RAD( flFovY ) * 0.5f ) * flAspect ) ) * 2.0f; +} + + +//----------------------------------------------------------------------------- +// Generate a frustum based on perspective view parameters +//----------------------------------------------------------------------------- +void GeneratePerspectiveFrustum( const Vector& origin, const Vector &forward, + const Vector &right, const Vector &up, float flZNear, float flZFar, + float flFovX, float flFovY, Frustum_t &frustum ) +{ + float flIntercept = DotProduct( origin, forward ); + + // Setup the near and far planes. + frustum.SetPlane( FRUSTUM_FARZ, PLANE_ANYZ, -forward, -flZFar - flIntercept ); + frustum.SetPlane( FRUSTUM_NEARZ, PLANE_ANYZ, forward, flZNear + flIntercept ); + + flFovX *= 0.5f; + flFovY *= 0.5f; + + float flTanX = tan( DEG2RAD( flFovX ) ); + float flTanY = tan( DEG2RAD( flFovY ) ); + + // OPTIMIZE: Normalizing these planes is not necessary for culling + Vector normalPos, normalNeg; + + VectorMA( right, flTanX, forward, normalPos ); + VectorMA( normalPos, -2.0f, right, normalNeg ); + + VectorNormalize( normalPos ); + VectorNormalize( normalNeg ); + + frustum.SetPlane( FRUSTUM_LEFT, PLANE_ANYZ, normalPos, normalPos.Dot( origin ) ); + frustum.SetPlane( FRUSTUM_RIGHT, PLANE_ANYZ, normalNeg, normalNeg.Dot( origin ) ); + + VectorMA( up, flTanY, forward, normalPos ); + VectorMA( normalPos, -2.0f, up, normalNeg ); + + VectorNormalize( normalPos ); + VectorNormalize( normalNeg ); + + frustum.SetPlane( FRUSTUM_BOTTOM, PLANE_ANYZ, normalPos, normalPos.Dot( origin ) ); + frustum.SetPlane( FRUSTUM_TOP, PLANE_ANYZ, normalNeg, normalNeg.Dot( origin ) ); +} + + +//----------------------------------------------------------------------------- +// Version that accepts angles instead of vectors +//----------------------------------------------------------------------------- +void GeneratePerspectiveFrustum( const Vector& origin, const QAngle &angles, float flZNear, float flZFar, float flFovX, float flAspectRatio, Frustum_t &frustum ) +{ + Vector vecForward, vecRight, vecUp; + AngleVectors( angles, &vecForward, &vecRight, &vecUp ); + float flFovY = CalcFovY( flFovX, flAspectRatio ); + GeneratePerspectiveFrustum( origin, vecForward, vecRight, vecUp, flZNear, flZFar, flFovX, flFovY, frustum ); +} + +bool R_CullBox( const Vector& mins, const Vector& maxs, const Frustum_t &frustum ) +{ + return (( BoxOnPlaneSide( mins, maxs, frustum.GetPlane(FRUSTUM_RIGHT) ) == 2 ) || + ( BoxOnPlaneSide( mins, maxs, frustum.GetPlane(FRUSTUM_LEFT) ) == 2 ) || + ( BoxOnPlaneSide( mins, maxs, frustum.GetPlane(FRUSTUM_TOP) ) == 2 ) || + ( BoxOnPlaneSide( mins, maxs, frustum.GetPlane(FRUSTUM_BOTTOM) ) == 2 ) || + ( BoxOnPlaneSide( mins, maxs, frustum.GetPlane(FRUSTUM_NEARZ) ) == 2 ) || + ( BoxOnPlaneSide( mins, maxs, frustum.GetPlane(FRUSTUM_FARZ) ) == 2 ) ); +} + +bool R_CullBoxSkipNear( const Vector& mins, const Vector& maxs, const Frustum_t &frustum ) +{ + return (( BoxOnPlaneSide( mins, maxs, frustum.GetPlane(FRUSTUM_RIGHT) ) == 2 ) || + ( BoxOnPlaneSide( mins, maxs, frustum.GetPlane(FRUSTUM_LEFT) ) == 2 ) || + ( BoxOnPlaneSide( mins, maxs, frustum.GetPlane(FRUSTUM_TOP) ) == 2 ) || + ( BoxOnPlaneSide( mins, maxs, frustum.GetPlane(FRUSTUM_BOTTOM) ) == 2 ) || + ( BoxOnPlaneSide( mins, maxs, frustum.GetPlane(FRUSTUM_FARZ) ) == 2 ) ); +} + + +// NOTE: This routine was taken (and modified) from NVidia's BlinnReflection demo +// Creates basis vectors, based on a vertex and index list. +// See the NVidia white paper 'GDC2K PerPixel Lighting' for a description +// of how this computation works +#define SMALL_FLOAT 1e-12 + +void CalcTriangleTangentSpace( const Vector &p0, const Vector &p1, const Vector &p2, + const Vector2D &t0, const Vector2D &t1, const Vector2D& t2, + Vector &sVect, Vector &tVect ) +{ + /* Compute the partial derivatives of X, Y, and Z with respect to S and T. */ + sVect.Init( 0.0f, 0.0f, 0.0f ); + tVect.Init( 0.0f, 0.0f, 0.0f ); + + // x, s, t + Vector edge01( p1.x - p0.x, t1.x - t0.x, t1.y - t0.y ); + Vector edge02( p2.x - p0.x, t2.x - t0.x, t2.y - t0.y ); + + Vector cross; + CrossProduct( edge01, edge02, cross ); + if ( fabs( cross.x ) > SMALL_FLOAT ) + { + sVect.x += -cross.y / cross.x; + tVect.x += -cross.z / cross.x; + } + + // y, s, t + edge01.Init( p1.y - p0.y, t1.x - t0.x, t1.y - t0.y ); + edge02.Init( p2.y - p0.y, t2.x - t0.x, t2.y - t0.y ); + + CrossProduct( edge01, edge02, cross ); + if ( fabs( cross.x ) > SMALL_FLOAT ) + { + sVect.y += -cross.y / cross.x; + tVect.y += -cross.z / cross.x; + } + + // z, s, t + edge01.Init( p1.z - p0.z, t1.x - t0.x, t1.y - t0.y ); + edge02.Init( p2.z - p0.z, t2.x - t0.x, t2.y - t0.y ); + + CrossProduct( edge01, edge02, cross ); + if( fabs( cross.x ) > SMALL_FLOAT ) + { + sVect.z += -cross.y / cross.x; + tVect.z += -cross.z / cross.x; + } + + // Normalize sVect and tVect + VectorNormalize( sVect ); + VectorNormalize( tVect ); +} + + +//----------------------------------------------------------------------------- +// Convert RGB to HSV +//----------------------------------------------------------------------------- +void RGBtoHSV( const Vector &rgb, Vector &hsv ) +{ + float flMax = MAX( rgb.x, rgb.y ); + flMax = MAX( flMax, rgb.z ); + float flMin = MIN( rgb.x, rgb.y ); + flMin = MIN( flMin, rgb.z ); + + // hsv.z is the value + hsv.z = flMax; + + // hsv.y is the saturation + if (flMax != 0.0F) + { + hsv.y = (flMax - flMin) / flMax; + } + else + { + hsv.y = 0.0F; + } + + // hsv.x is the hue + if (hsv.y == 0.0F) + { + hsv.x = -1.0f; + } + else + { + float32 d = flMax - flMin; + if (rgb.x == flMax) + { + hsv.x = (rgb.y - rgb.z) / d; + } + else if (rgb.y == flMax) + { + hsv.x = 2.0F + (rgb.z - rgb.x) / d; + } + else + { + hsv.x = 4.0F + (rgb.x - rgb.y) / d; + } + hsv.x *= 60.0F; + if ( hsv.x < 0.0F ) + { + hsv.x += 360.0F; + } + } +} + + +//----------------------------------------------------------------------------- +// Convert HSV to RGB +//----------------------------------------------------------------------------- +void HSVtoRGB( const Vector &hsv, Vector &rgb ) +{ + if ( hsv.y == 0.0F ) + { + rgb.Init( hsv.z, hsv.z, hsv.z ); + return; + } + + float32 hue = hsv.x; + if (hue == 360.0F) + { + hue = 0.0F; + } + hue /= 60.0F; + int i = static_cast(hue); // integer part + float32 f = hue - i; // fractional part + float32 p = hsv.z * (1.0F - hsv.y); + float32 q = hsv.z * (1.0F - hsv.y * f); + float32 t = hsv.z * (1.0F - hsv.y * (1.0F - f)); + switch(i) + { + case 0: rgb.Init( hsv.z, t, p ); break; + case 1: rgb.Init( q, hsv.z, p ); break; + case 2: rgb.Init( p, hsv.z, t ); break; + case 3: rgb.Init( p, q, hsv.z ); break; + case 4: rgb.Init( t, p, hsv.z ); break; + case 5: rgb.Init( hsv.z, p, q ); break; + } +} + + +void GetInterpolationData( float const *pKnotPositions, + float const *pKnotValues, + int nNumValuesinList, + int nInterpolationRange, + float flPositionToInterpolateAt, + bool bWrap, + float *pValueA, + float *pValueB, + float *pInterpolationValue) +{ + // first, find the bracketting knots by looking for the first knot >= our index + + int idx; + for(idx = 0; idx < nNumValuesinList; idx++ ) + { + if ( pKnotPositions[idx] >= flPositionToInterpolateAt ) + break; + } + int nKnot1, nKnot2; + float flOffsetFromStartOfGap, flSizeOfGap; + if ( idx == 0) + { + if ( bWrap ) + { + nKnot1 = nNumValuesinList-1; + nKnot2 = 0; + flSizeOfGap = + ( pKnotPositions[nKnot2] + ( nInterpolationRange-pKnotPositions[nKnot1] ) ); + flOffsetFromStartOfGap = + flPositionToInterpolateAt + ( nInterpolationRange-pKnotPositions[nKnot1] ); + } + else + { + *pValueA = *pValueB = pKnotValues[0]; + *pInterpolationValue = 1.0; + return; + } + } + else if ( idx == nNumValuesinList ) // ran out of values + { + if ( bWrap ) + { + nKnot1 = nNumValuesinList -1; + nKnot2 = 0; + flSizeOfGap = ( pKnotPositions[nKnot2] + + ( nInterpolationRange-pKnotPositions[nKnot1] ) ); + flOffsetFromStartOfGap = flPositionToInterpolateAt - pKnotPositions[nKnot1]; + } + else + { + *pValueA = *pValueB = pKnotValues[nNumValuesinList-1]; + *pInterpolationValue = 1.0; + return; + } + + } + else + { + nKnot1 = idx-1; + nKnot2 = idx; + flSizeOfGap = pKnotPositions[nKnot2]-pKnotPositions[nKnot1]; + flOffsetFromStartOfGap = flPositionToInterpolateAt-pKnotPositions[nKnot1]; + } + + *pValueA = pKnotValues[nKnot1]; + *pValueB = pKnotValues[nKnot2]; + *pInterpolationValue = FLerp( 0, 1, 0, flSizeOfGap, flOffsetFromStartOfGap ); + return; +} diff --git a/mathlib/noisedata.h b/mathlib/noisedata.h new file mode 100644 index 00000000..0cd37932 --- /dev/null +++ b/mathlib/noisedata.h @@ -0,0 +1,180 @@ +//========= Copyright © 1996-2006, Valve Corporation, All rights reserved. ============// +// +// Purpose: static data for noise() primitives. +// +// $Workfile: $ +// $NoKeywords: $ +//=============================================================================// +// +// **** DO NOT EDIT THIS FILE. GENERATED BY DATAGEN.PL **** +// + +static const int perm_a[]={ + 66,147,106,213,89,115,239,25,171,175,9,114,141,226,118,128,41,208,4,56, + 180,248,43,82,246,219,94,245,133,131,222,103,160,130,168,145,238,38,23,6, + 236,67,99,2,70,232,80,209,1,3,68,65,102,210,13,73,55,252,187,170,22,36, + 52,181,117,163,46,79,166,224,148,75,113,95,156,185,220,164,51,142,161,35, + 206,251,45,136,197,190,132,32,218,127,63,27,137,93,242,20,189,108,183, + 122,139,191,249,253,87,98,69,0,144,64,24,214,97,116,158,42,107,15,53,212, + 83,111,152,240,74,237,62,77,205,149,26,151,178,204,91,176,234,49,154,203, + 33,221,125,134,165,124,86,39,37,60,150,157,179,109,110,44,159,153,5,100, + 10,207,40,186,96,215,143,162,230,184,101,54,174,247,76,59,241,223,192,84, + 104,78,169,146,138,30,48,85,233,19,29,92,126,17,199,250,31,81,188,225,28, + 112,88,11,182,173,211,129,194,172,14,120,200,167,135,12,177,227,229,155, + 201,61,105,195,193,244,235,58,8,196,123,254,16,18,50,121,71,243,90,57, + 202,119,255,47,7,198,228,21,217,216,231,140,72,34 +}; + +static const int perm_b[]={ + 123,108,201,64,40,75,24,221,137,110,191,142,9,69,230,83,7,247,51,54,115, + 133,180,248,109,116,62,99,251,55,89,253,65,106,228,167,131,132,58,143, + 97,102,163,202,149,234,12,117,174,94,121,74,32,113,20,60,159,182,204,29, + 244,118,3,178,255,38,6,114,36,93,30,134,213,90,245,209,88,232,162,125, + 84,166,70,136,208,231,27,71,157,80,76,0,170,225,203,176,33,161,196,128, + 252,236,246,2,138,1,250,197,77,243,218,242,19,164,68,212,14,237,144,63, + 46,103,177,188,85,223,8,160,222,4,216,219,35,15,44,23,126,127,100,226, + 235,37,168,101,49,22,11,73,61,135,111,183,72,96,185,239,82,18,50,155, + 186,153,17,233,146,156,107,5,254,10,192,198,148,207,104,13,124,48,95, + 129,120,206,199,81,249,91,150,210,119,240,122,194,92,34,28,205,175,227, + 179,220,140,152,79,26,195,47,66,173,169,241,53,184,187,145,112,238,214, + 147,98,171,229,200,151,25,67,78,189,217,130,224,57,172,59,41,43,16,105, + 158,165,21,45,56,141,139,215,190,86,42,52,39,87,181,31,154,193,211 +}; + +static const int perm_c[]={ + 97,65,96,25,122,26,219,85,148,251,102,0,140,130,136,213,138,60,236,52, + 178,131,115,183,144,78,147,168,39,45,169,70,57,146,67,142,252,216,28,54, + 86,222,194,200,48,5,205,125,214,56,181,255,196,155,37,218,153,208,66, + 242,73,248,206,61,62,246,177,2,197,107,162,152,89,41,6,160,94,8,201,38, + 235,228,165,93,111,239,74,231,121,47,166,221,157,64,77,244,29,105,150, + 123,190,191,225,118,133,42,10,84,185,159,124,132,240,180,44,1,9,19,99, + 254,12,207,186,71,234,184,11,20,16,193,139,175,98,59,113,27,170,230,91, + 187,46,156,249,108,195,171,114,14,188,82,192,233,24,32,241,87,164,90,43, + 163,245,92,40,215,55,226,15,3,112,158,250,172,22,227,137,35,128,145,247, + 161,119,80,217,189,81,7,63,202,120,223,83,179,4,106,199,229,95,53,50,33, + 182,72,143,23,243,75,18,173,141,167,198,204,58,174,237,17,129,238,127, + 31,101,176,36,30,110,209,34,203,135,232,68,149,49,134,126,212,79,76,117, + 104,210,211,224,253,100,220,109,116,88,13,151,154,69,21,51,103 +}; + +static const int perm_d[]={ + 94,234,145,235,151,166,187,238,4,5,128,115,87,107,229,175,190,108,218, + 32,17,220,97,90,122,121,71,109,64,227,225,75,81,19,27,162,3,89,139,69, + 92,26,48,215,116,191,114,2,104,157,66,39,1,127,96,124,30,0,82,233,219, + 42,131,173,35,201,182,144,14,98,148,244,160,159,179,91,31,68,119,154, + 205,113,149,167,44,60,18,228,251,245,43,10,80,15,129,67,181,174,6,45, + 194,237,213,52,99,232,211,212,164,217,57,153,156,102,134,20,249,132,55, + 204,65,33,231,85,61,37,163,193,189,170,226,63,168,236,165,224,242,195, + 41,200,40,70,112,100,36,172,130,74,137,252,243,135,230,161,207,16,146, + 198,118,150,24,29,250,188,25,209,103,23,105,47,7,46,133,83,184,50,79, + 110,120,53,253,206,214,9,240,101,147,152,183,254,59,126,216,197,171,51, + 208,248,202,58,176,28,72,177,185,141,12,11,56,222,86,178,155,223,88,111, + 73,142,210,138,239,221,199,192,84,93,241,125,76,77,255,95,8,78,247,186, + 123,196,13,140,180,143,54,106,136,34,62,169,38,117,22,21,49,203,158,246 +}; + +static const float impulse_xcoords[]={ + 0.788235,0.541176,0.972549,0.082353,0.352941,0.811765,0.286275,0.752941, + 0.203922,0.705882,0.537255,0.886275,0.580392,0.137255,0.800000,0.533333, + 0.117647,0.447059,0.129412,0.925490,0.086275,0.478431,0.666667,0.568627, + 0.678431,0.313725,0.321569,0.349020,0.988235,0.419608,0.898039,0.219608, + 0.243137,0.623529,0.501961,0.772549,0.952941,0.517647,0.949020,0.701961, + 0.454902,0.505882,0.564706,0.960784,0.207843,0.007843,0.831373,0.184314, + 0.576471,0.462745,0.572549,0.247059,0.262745,0.694118,0.615686,0.121569, + 0.384314,0.749020,0.145098,0.717647,0.415686,0.607843,0.105882,0.101961, + 0.200000,0.807843,0.521569,0.780392,0.466667,0.552941,0.996078,0.627451, + 0.992157,0.529412,0.407843,0.011765,0.709804,0.458824,0.058824,0.819608, + 0.176471,0.317647,0.392157,0.223529,0.156863,0.490196,0.325490,0.074510, + 0.239216,0.164706,0.890196,0.603922,0.921569,0.839216,0.854902,0.098039, + 0.686275,0.843137,0.152941,0.372549,0.062745,0.474510,0.486275,0.227451, + 0.400000,0.298039,0.309804,0.274510,0.054902,0.815686,0.647059,0.635294, + 0.662745,0.976471,0.094118,0.509804,0.650980,0.211765,0.180392,0.003922, + 0.827451,0.278431,0.023529,0.525490,0.450980,0.725490,0.690196,0.941176, + 0.639216,0.560784,0.196078,0.364706,0.043137,0.494118,0.796078,0.113725, + 0.760784,0.729412,0.258824,0.290196,0.584314,0.674510,0.823529,0.905882, + 0.917647,0.070588,0.862745,0.345098,0.913725,0.937255,0.031373,0.215686, + 0.768627,0.333333,0.411765,0.423529,0.945098,0.721569,0.039216,0.792157, + 0.956863,0.266667,0.254902,0.047059,0.294118,0.658824,0.250980,1.000000, + 0.984314,0.756863,0.027451,0.305882,0.835294,0.513725,0.360784,0.776471, + 0.611765,0.192157,0.866667,0.858824,0.592157,0.803922,0.141176,0.435294, + 0.588235,0.619608,0.341176,0.109804,0.356863,0.270588,0.737255,0.847059, + 0.050980,0.764706,0.019608,0.870588,0.933333,0.784314,0.549020,0.337255, + 0.631373,0.929412,0.231373,0.427451,0.078431,0.498039,0.968627,0.654902, + 0.125490,0.698039,0.015686,0.878431,0.713725,0.368627,0.431373,0.874510, + 0.403922,0.556863,0.443137,0.964706,0.909804,0.301961,0.035294,0.850980, + 0.882353,0.741176,0.380392,0.133333,0.470588,0.643137,0.282353,0.396078, + 0.980392,0.168627,0.149020,0.235294,0.670588,0.596078,0.733333,0.160784, + 0.376471,0.682353,0.545098,0.482353,0.745098,0.894118,0.188235,0.329412, + 0.439216,0.901961,0.000000,0.600000,0.388235,0.172549,0.090196,0.066667 +}; + +static const float impulse_ycoords[]={ + 0.827451,0.337255,0.941176,0.886275,0.878431,0.239216,0.400000,0.164706, + 0.490196,0.411765,0.964706,0.349020,0.803922,0.317647,0.647059,0.431373, + 0.933333,0.156863,0.094118,0.219608,0.039216,0.521569,0.498039,0.705882, + 0.717647,0.047059,0.631373,0.517647,0.984314,0.847059,0.482353,0.439216, + 0.250980,0.862745,0.690196,0.913725,0.270588,0.070588,0.027451,0.694118, + 0.811765,0.000000,0.494118,0.823529,0.800000,0.600000,0.003922,0.443137, + 0.639216,0.376471,0.031373,0.035294,0.552941,0.215686,0.305882,0.133333, + 0.564706,0.176471,0.211765,0.874510,0.360784,0.654902,0.223529,0.807843, + 0.372549,0.137255,0.321569,0.015686,0.007843,0.262745,0.125490,0.078431, + 0.396078,0.976471,0.929412,1.000000,0.937255,0.509804,0.188235,0.850980, + 0.831373,0.392157,0.741176,0.541176,0.592157,0.286275,0.345098,0.572549, + 0.537255,0.725490,0.839216,0.184314,0.772549,0.149020,0.505882,0.423529, + 0.780392,0.011765,0.890196,0.086275,0.427451,0.023529,0.788235,0.050980, + 0.760784,0.603922,0.066667,0.643137,0.623529,0.960784,0.172549,0.333333, + 0.082353,0.290196,0.992157,0.709804,0.894118,0.596078,0.243137,0.752941, + 0.486275,0.670588,0.949020,0.784314,0.145098,0.560784,0.513725,0.180392, + 0.580392,0.996078,0.380392,0.556863,0.407843,0.945098,0.117647,0.058824, + 0.678431,0.129412,0.192157,0.105882,0.968627,0.545098,0.462745,0.227451, + 0.019608,0.866667,0.674510,0.207843,0.627451,0.819608,0.921569,0.356863, + 0.447059,0.533333,0.435294,0.341176,0.054902,0.529412,0.235294,0.764706, + 0.615686,0.043137,0.745098,0.266667,0.501961,0.619608,0.776471,0.450980, + 0.309804,0.325490,0.200000,0.635294,0.247059,0.698039,0.721569,0.168627, + 0.854902,0.141176,0.611765,0.525490,0.415686,0.298039,0.254902,0.858824, + 0.568627,0.329412,0.062745,0.843137,0.588235,0.733333,0.607843,0.478431, + 0.576471,0.662745,0.470588,0.666667,0.980392,0.113725,0.898039,0.203922, + 0.294118,0.152941,0.098039,0.909804,0.796078,0.768627,0.713725,0.196078, + 0.368627,0.419608,0.352941,0.090196,0.749020,0.121569,0.882353,0.278431, + 0.388235,0.917647,0.701961,0.729412,0.835294,0.258824,0.301961,0.101961, + 0.792157,0.474510,0.686275,0.658824,0.364706,0.682353,0.458824,0.815686, + 0.282353,0.160784,0.870588,0.988235,0.756863,0.549020,0.274510,0.384314, + 0.650980,0.737255,0.901961,0.956863,0.972549,0.584314,0.925490,0.403922, + 0.074510,0.454902,0.952941,0.109804,0.313725,0.905882,0.231373,0.466667 +}; + +static const float impulse_zcoords[]={ + 0.082353,0.643137,0.415686,0.929412,0.568627,0.509804,0.537255,0.815686, + 0.698039,0.941176,0.776471,0.752941,0.737255,0.525490,0.498039,0.423529, + 0.792157,0.125490,0.619608,0.164706,0.368627,0.870588,0.137255,0.372549, + 0.466667,0.486275,0.501961,0.513725,0.709804,0.576471,0.203922,0.258824, + 0.152941,0.556863,0.223529,0.047059,0.235294,0.474510,0.764706,0.552941, + 0.847059,0.145098,0.176471,0.937255,0.654902,0.894118,0.729412,0.054902, + 0.666667,0.749020,0.262745,0.560784,0.431373,0.286275,0.352941,0.239216, + 0.156863,0.839216,0.427451,0.949020,0.384314,0.227451,0.180392,0.074510, + 0.172549,0.356863,0.066667,0.517647,0.447059,0.184314,0.062745,0.670588, + 0.603922,0.219608,0.270588,0.976471,0.505882,0.627451,0.819608,0.854902, + 0.843137,0.019608,0.713725,0.035294,0.925490,0.349020,0.866667,0.701961, + 0.909804,0.811765,0.717647,0.141176,0.917647,0.023529,0.098039,0.803922, + 0.733333,0.658824,0.827451,0.133333,0.858824,0.800000,0.635294,1.000000, + 0.078431,0.450980,0.835294,0.321569,0.360784,0.529412,0.725490,0.572549, + 0.639216,0.341176,0.533333,0.094118,0.149020,0.545098,0.101961,0.901961, + 0.278431,0.694118,0.521569,0.490196,0.454902,0.329412,0.274510,0.027451, + 0.745098,0.933333,0.443137,0.168627,0.192157,0.988235,0.070588,0.972549, + 0.768627,0.400000,0.470588,0.207843,0.215686,0.388235,0.439216,0.780392, + 0.482353,0.121569,0.964706,0.086275,0.890196,0.337255,0.109804,0.305882, + 0.113725,0.435294,0.721569,0.772549,0.807843,0.741176,0.254902,0.596078, + 0.494118,0.317647,0.419608,0.000000,0.188235,0.031373,0.376471,0.380392, + 0.611765,0.945098,0.411765,0.313725,0.874510,0.588235,0.678431,0.160784, + 0.007843,0.090196,0.850980,0.788235,0.705882,0.266667,0.309804,0.541176, + 0.231373,0.129412,0.294118,0.243137,0.913725,0.996078,0.117647,0.478431, + 0.290196,0.549020,0.682353,0.784314,0.396078,0.831373,0.984314,0.584314, + 0.039216,0.250980,0.600000,0.392157,0.298039,0.050980,0.364706,0.105882, + 0.623529,0.886275,0.980392,0.325490,0.247059,0.690196,0.674510,0.960784, + 0.647059,0.211765,0.882353,0.686275,0.823529,0.058824,0.956863,0.043137, + 0.345098,0.301961,0.592157,0.862745,0.607843,0.458824,0.282353,0.003922, + 0.580392,0.760784,0.564706,0.011765,0.968627,0.905882,0.756863,0.952941, + 0.662745,0.015686,0.898039,0.196078,0.333333,0.992157,0.650980,0.407843, + 0.796078,0.615686,0.878431,0.921569,0.631373,0.200000,0.403922,0.462745 +}; + diff --git a/mathlib/polyhedron.cpp b/mathlib/polyhedron.cpp new file mode 100644 index 00000000..c7fb669e --- /dev/null +++ b/mathlib/polyhedron.cpp @@ -0,0 +1,2294 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "mathlib/polyhedron.h" +#include "mathlib/vmatrix.h" +#include +#include +#include "tier1/utlvector.h" + + + +struct GeneratePolyhedronFromPlanes_Point; +struct GeneratePolyhedronFromPlanes_PointLL; +struct GeneratePolyhedronFromPlanes_Line; +struct GeneratePolyhedronFromPlanes_LineLL; +struct GeneratePolyhedronFromPlanes_Polygon; +struct GeneratePolyhedronFromPlanes_PolygonLL; + +struct GeneratePolyhedronFromPlanes_UnorderedPointLL; +struct GeneratePolyhedronFromPlanes_UnorderedLineLL; +struct GeneratePolyhedronFromPlanes_UnorderedPolygonLL; + +Vector FindPointInPlanes( const float *pPlanes, int planeCount ); +bool FindConvexShapeLooseAABB( const float *pInwardFacingPlanes, int iPlaneCount, Vector *pAABBMins, Vector *pAABBMaxs ); +CPolyhedron *ClipLinkedGeometry( GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pPolygons, GeneratePolyhedronFromPlanes_UnorderedLineLL *pLines, GeneratePolyhedronFromPlanes_UnorderedPointLL *pPoints, const float *pOutwardFacingPlanes, int iPlaneCount, float fOnPlaneEpsilon, bool bUseTemporaryMemory ); +CPolyhedron *ConvertLinkedGeometryToPolyhedron( GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pPolygons, GeneratePolyhedronFromPlanes_UnorderedLineLL *pLines, GeneratePolyhedronFromPlanes_UnorderedPointLL *pPoints, bool bUseTemporaryMemory ); + +//#define ENABLE_DEBUG_POLYHEDRON_DUMPS //Dumps debug information to disk for use with glview. Requires that tier2 also be in all projects using debug mathlib +//#define DEBUG_DUMP_POLYHEDRONS_TO_NUMBERED_GLVIEWS //dumps successfully generated polyhedrons + +#ifdef _DEBUG +#include "filesystem.h" +void DumpPolyhedronToGLView( const CPolyhedron *pPolyhedron, const char *pFilename, const VMatrix *pTransform ); +void DumpPlaneToGlView( const float *pPlane, float fGrayScale, const char *pszFileName, const VMatrix *pTransform ); +void DumpLineToGLView( const Vector &vPoint1, const Vector &vColor1, const Vector &vPoint2, const Vector &vColor2, float fThickness, FILE *pFile ); +void DumpAABBToGLView( const Vector &vCenter, const Vector &vExtents, const Vector &vColor, FILE *pFile ); + +#if defined( ENABLE_DEBUG_POLYHEDRON_DUMPS ) && defined( WIN32 ) +#include "winlite.h" +#endif + +static VMatrix s_matIdentity( 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f ); +#endif + +#if defined( DEBUG_DUMP_POLYHEDRONS_TO_NUMBERED_GLVIEWS ) +static int g_iPolyhedronDumpCounter = 0; +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#if defined( _DEBUG ) && defined( ENABLE_DEBUG_POLYHEDRON_DUMPS ) +void CreateDumpDirectory( const char *szDirectoryName ) +{ +#if defined( WIN32 ) + CreateDirectory( szDirectoryName, NULL ); +#else + Assert( false ); //TODO: create directories in linux +#endif +} +#endif + + + +void CPolyhedron_AllocByNew::Release( void ) +{ + delete this; +} + +CPolyhedron_AllocByNew *CPolyhedron_AllocByNew::Allocate( unsigned short iVertices, unsigned short iLines, unsigned short iIndices, unsigned short iPolygons ) //creates the polyhedron along with enough memory to hold all it's data in a single allocation +{ + void *pMemory = new unsigned char [ sizeof( CPolyhedron_AllocByNew ) + + (iVertices * sizeof(Vector)) + + (iLines * sizeof(Polyhedron_IndexedLine_t)) + + (iIndices * sizeof( Polyhedron_IndexedLineReference_t )) + + (iPolygons * sizeof( Polyhedron_IndexedPolygon_t ))]; + +#include "tier0/memdbgoff.h" //the following placement new doesn't compile with memory debugging + CPolyhedron_AllocByNew *pAllocated = new ( pMemory ) CPolyhedron_AllocByNew; +#include "tier0/memdbgon.h" + + pAllocated->iVertexCount = iVertices; + pAllocated->iLineCount = iLines; + pAllocated->iIndexCount = iIndices; + pAllocated->iPolygonCount = iPolygons; + pAllocated->pVertices = (Vector *)(pAllocated + 1); //start vertex memory at the end of the class + pAllocated->pLines = (Polyhedron_IndexedLine_t *)(pAllocated->pVertices + iVertices); + pAllocated->pIndices = (Polyhedron_IndexedLineReference_t *)(pAllocated->pLines + iLines); + pAllocated->pPolygons = (Polyhedron_IndexedPolygon_t *)(pAllocated->pIndices + iIndices); + + return pAllocated; +} + + +class CPolyhedron_TempMemory : public CPolyhedron +{ +public: +#ifdef _DEBUG + int iReferenceCount; +#endif + + virtual void Release( void ) + { +#ifdef _DEBUG + --iReferenceCount; +#endif + } + + CPolyhedron_TempMemory( void ) +#ifdef _DEBUG + : iReferenceCount( 0 ) +#endif + { }; +}; + + +static CUtlVector s_TempMemoryPolyhedron_Buffer; +static CPolyhedron_TempMemory s_TempMemoryPolyhedron; + +CPolyhedron *GetTempPolyhedron( unsigned short iVertices, unsigned short iLines, unsigned short iIndices, unsigned short iPolygons ) //grab the temporary polyhedron. Avoids new/delete for quick work. Can only be in use by one chunk of code at a time +{ + AssertMsg( s_TempMemoryPolyhedron.iReferenceCount == 0, "Temporary polyhedron memory being rewritten before released" ); +#ifdef _DEBUG + ++s_TempMemoryPolyhedron.iReferenceCount; +#endif + s_TempMemoryPolyhedron_Buffer.SetCount( (sizeof( Vector ) * iVertices) + + (sizeof( Polyhedron_IndexedLine_t ) * iLines) + + (sizeof( Polyhedron_IndexedLineReference_t ) * iIndices) + + (sizeof( Polyhedron_IndexedPolygon_t ) * iPolygons) ); + + s_TempMemoryPolyhedron.iVertexCount = iVertices; + s_TempMemoryPolyhedron.iLineCount = iLines; + s_TempMemoryPolyhedron.iIndexCount = iIndices; + s_TempMemoryPolyhedron.iPolygonCount = iPolygons; + + s_TempMemoryPolyhedron.pVertices = (Vector *)s_TempMemoryPolyhedron_Buffer.Base(); + s_TempMemoryPolyhedron.pLines = (Polyhedron_IndexedLine_t *)(&s_TempMemoryPolyhedron.pVertices[s_TempMemoryPolyhedron.iVertexCount]); + s_TempMemoryPolyhedron.pIndices = (Polyhedron_IndexedLineReference_t *)(&s_TempMemoryPolyhedron.pLines[s_TempMemoryPolyhedron.iLineCount]); + s_TempMemoryPolyhedron.pPolygons = (Polyhedron_IndexedPolygon_t *)(&s_TempMemoryPolyhedron.pIndices[s_TempMemoryPolyhedron.iIndexCount]); + + return &s_TempMemoryPolyhedron; +} + + +Vector CPolyhedron::Center( void ) +{ + if( iVertexCount == 0 ) + return vec3_origin; + + Vector vAABBMin, vAABBMax; + vAABBMin = vAABBMax = pVertices[0]; + for( int i = 1; i != iVertexCount; ++i ) + { + Vector &vPoint = pVertices[i]; + if( vPoint.x < vAABBMin.x ) + vAABBMin.x = vPoint.x; + if( vPoint.y < vAABBMin.y ) + vAABBMin.y = vPoint.y; + if( vPoint.z < vAABBMin.z ) + vAABBMin.z = vPoint.z; + + if( vPoint.x > vAABBMax.x ) + vAABBMax.x = vPoint.x; + if( vPoint.y > vAABBMax.y ) + vAABBMax.y = vPoint.y; + if( vPoint.z > vAABBMax.z ) + vAABBMax.z = vPoint.z; + } + return ((vAABBMin + vAABBMax) * 0.5f); +} + +enum PolyhedronPointPlanarity +{ + POINT_DEAD, + POINT_ONPLANE, + POINT_ALIVE +}; + +struct GeneratePolyhedronFromPlanes_Point +{ + Vector ptPosition; + GeneratePolyhedronFromPlanes_LineLL *pConnectedLines; //keep these in a clockwise order, circular linking + float fPlaneDist; //used in plane cutting + PolyhedronPointPlanarity planarity; + int iSaveIndices; +}; + +struct GeneratePolyhedronFromPlanes_Line +{ + GeneratePolyhedronFromPlanes_Point *pPoints[2]; //the 2 connecting points in no particular order + GeneratePolyhedronFromPlanes_Polygon *pPolygons[2]; //viewing from the outside with the point connections going up, 0 is the left polygon, 1 is the right + int iSaveIndices; + bool bAlive; //connected to at least one living point + bool bCut; //connected to at least one dead point + + GeneratePolyhedronFromPlanes_LineLL *pPointLineLinks[2]; //rather than going into a point and searching for its link to this line, lets just cache it to eliminate searching + GeneratePolyhedronFromPlanes_LineLL *pPolygonLineLinks[2]; //rather than going into a polygon and searching for its link to this line, lets just cache it to eliminate searching +#ifdef POLYHEDRON_EXTENSIVE_DEBUGGING + int iDebugFlags; +#endif +}; + +struct GeneratePolyhedronFromPlanes_LineLL +{ + GeneratePolyhedronFromPlanes_Line *pLine; + int iReferenceIndex; //whatever is referencing the line should know which side of the line it's on (points and polygons), for polygons, it's which point to follow to continue going clockwise, which makes polygon 0 the one on the left side of an upward facing line vector, for points, it's the OTHER point's index + GeneratePolyhedronFromPlanes_LineLL *pPrev; + GeneratePolyhedronFromPlanes_LineLL *pNext; +}; + +struct GeneratePolyhedronFromPlanes_Polygon +{ + Vector vSurfaceNormal; + GeneratePolyhedronFromPlanes_LineLL *pLines; //keep these in a clockwise order, circular linking + + bool bMissingASide; +}; + +struct GeneratePolyhedronFromPlanes_UnorderedPolygonLL //an unordered collection of polygons +{ + GeneratePolyhedronFromPlanes_Polygon *pPolygon; + GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pNext; + GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pPrev; +}; + +struct GeneratePolyhedronFromPlanes_UnorderedLineLL //an unordered collection of lines +{ + GeneratePolyhedronFromPlanes_Line *pLine; + GeneratePolyhedronFromPlanes_UnorderedLineLL *pNext; + GeneratePolyhedronFromPlanes_UnorderedLineLL *pPrev; +}; + +struct GeneratePolyhedronFromPlanes_UnorderedPointLL //an unordered collection of points +{ + GeneratePolyhedronFromPlanes_Point *pPoint; + GeneratePolyhedronFromPlanes_UnorderedPointLL *pNext; + GeneratePolyhedronFromPlanes_UnorderedPointLL *pPrev; +}; + + + + +CPolyhedron *ClipPolyhedron( const CPolyhedron *pExistingPolyhedron, const float *pOutwardFacingPlanes, int iPlaneCount, float fOnPlaneEpsilon, bool bUseTemporaryMemory ) +{ + if( pExistingPolyhedron == NULL ) + return NULL; + + AssertMsg( (pExistingPolyhedron->iVertexCount >= 3) && (pExistingPolyhedron->iPolygonCount >= 2), "Polyhedron doesn't meet absolute minimum spec" ); + + float *pUsefulPlanes = (float *)stackalloc( sizeof( float ) * 4 * iPlaneCount ); + int iUsefulPlaneCount = 0; + Vector *pExistingVertices = pExistingPolyhedron->pVertices; + + //A large part of clipping will either eliminate the polyhedron entirely, or clip nothing at all, so lets just check for those first and throw away useless planes + { + int iLiveCount = 0; + int iDeadCount = 0; + const float fNegativeOnPlaneEpsilon = -fOnPlaneEpsilon; + + for( int i = 0; i != iPlaneCount; ++i ) + { + Vector vNormal = *((Vector *)&pOutwardFacingPlanes[(i * 4) + 0]); + float fPlaneDist = pOutwardFacingPlanes[(i * 4) + 3]; + + for( int j = 0; j != pExistingPolyhedron->iVertexCount; ++j ) + { + float fPointDist = vNormal.Dot( pExistingVertices[j] ) - fPlaneDist; + + if( fPointDist <= fNegativeOnPlaneEpsilon ) + ++iLiveCount; + else if( fPointDist > fOnPlaneEpsilon ) + ++iDeadCount; + } + + if( iLiveCount == 0 ) + { + //all points are dead or on the plane, so the polyhedron is dead + return NULL; + } + + if( iDeadCount != 0 ) + { + //at least one point died, this plane yields useful results + pUsefulPlanes[(iUsefulPlaneCount * 4) + 0] = vNormal.x; + pUsefulPlanes[(iUsefulPlaneCount * 4) + 1] = vNormal.y; + pUsefulPlanes[(iUsefulPlaneCount * 4) + 2] = vNormal.z; + pUsefulPlanes[(iUsefulPlaneCount * 4) + 3] = fPlaneDist; + ++iUsefulPlaneCount; + } + } + } + + if( iUsefulPlaneCount == 0 ) + { + //testing shows that the polyhedron won't even be cut, clone the existing polyhedron and return that + + CPolyhedron *pReturn; + if( bUseTemporaryMemory ) + { + pReturn = GetTempPolyhedron( pExistingPolyhedron->iVertexCount, + pExistingPolyhedron->iLineCount, + pExistingPolyhedron->iIndexCount, + pExistingPolyhedron->iPolygonCount ); + } + else + { + pReturn = CPolyhedron_AllocByNew::Allocate( pExistingPolyhedron->iVertexCount, + pExistingPolyhedron->iLineCount, + pExistingPolyhedron->iIndexCount, + pExistingPolyhedron->iPolygonCount ); + } + + memcpy( pReturn->pVertices, pExistingPolyhedron->pVertices, sizeof( Vector ) * pReturn->iVertexCount ); + memcpy( pReturn->pLines, pExistingPolyhedron->pLines, sizeof( Polyhedron_IndexedLine_t ) * pReturn->iLineCount ); + memcpy( pReturn->pIndices, pExistingPolyhedron->pIndices, sizeof( Polyhedron_IndexedLineReference_t ) * pReturn->iIndexCount ); + memcpy( pReturn->pPolygons, pExistingPolyhedron->pPolygons, sizeof( Polyhedron_IndexedPolygon_t ) * pReturn->iPolygonCount ); + + return pReturn; + } + + + + //convert the polyhedron to linked geometry + GeneratePolyhedronFromPlanes_Point *pStartPoints = (GeneratePolyhedronFromPlanes_Point *)stackalloc( pExistingPolyhedron->iVertexCount * sizeof( GeneratePolyhedronFromPlanes_Point ) ); + GeneratePolyhedronFromPlanes_Line *pStartLines = (GeneratePolyhedronFromPlanes_Line *)stackalloc( pExistingPolyhedron->iLineCount * sizeof( GeneratePolyhedronFromPlanes_Line ) ); + GeneratePolyhedronFromPlanes_Polygon *pStartPolygons = (GeneratePolyhedronFromPlanes_Polygon *)stackalloc( pExistingPolyhedron->iPolygonCount * sizeof( GeneratePolyhedronFromPlanes_Polygon ) ); + + GeneratePolyhedronFromPlanes_LineLL *pStartLineLinks = (GeneratePolyhedronFromPlanes_LineLL *)stackalloc( pExistingPolyhedron->iLineCount * 4 * sizeof( GeneratePolyhedronFromPlanes_LineLL ) ); + + int iCurrentLineLinkIndex = 0; + + //setup points + for( int i = 0; i != pExistingPolyhedron->iVertexCount; ++i ) + { + pStartPoints[i].ptPosition = pExistingPolyhedron->pVertices[i]; + pStartPoints[i].pConnectedLines = NULL; //we won't be circular linking until later + } + + //setup lines and interlink to points (line links are not yet circularly linked, and are unordered) + for( int i = 0; i != pExistingPolyhedron->iLineCount; ++i ) + { + for( int j = 0; j != 2; ++j ) + { + pStartLines[i].pPoints[j] = &pStartPoints[pExistingPolyhedron->pLines[i].iPointIndices[j]]; + + GeneratePolyhedronFromPlanes_LineLL *pLineLink = &pStartLineLinks[iCurrentLineLinkIndex++]; + pStartLines[i].pPointLineLinks[j] = pLineLink; + pLineLink->pLine = &pStartLines[i]; + pLineLink->iReferenceIndex = 1 - j; + //pLineLink->pPrev = NULL; + pLineLink->pNext = pStartLines[i].pPoints[j]->pConnectedLines; + pStartLines[i].pPoints[j]->pConnectedLines = pLineLink; + } + } + + + + //setup polygons + for( int i = 0; i != pExistingPolyhedron->iPolygonCount; ++i ) + { + pStartPolygons[i].vSurfaceNormal = pExistingPolyhedron->pPolygons[i].polyNormal; + Polyhedron_IndexedLineReference_t *pOffsetPolyhedronLines = &pExistingPolyhedron->pIndices[pExistingPolyhedron->pPolygons[i].iFirstIndex]; + + + GeneratePolyhedronFromPlanes_LineLL *pFirstLink = &pStartLineLinks[iCurrentLineLinkIndex]; + pStartPolygons[i].pLines = pFirstLink; //technically going to link to itself on first pass, then get linked properly immediately afterward + for( int j = 0; j != pExistingPolyhedron->pPolygons[i].iIndexCount; ++j ) + { + GeneratePolyhedronFromPlanes_LineLL *pLineLink = &pStartLineLinks[iCurrentLineLinkIndex++]; + pLineLink->pLine = &pStartLines[pOffsetPolyhedronLines[j].iLineIndex]; + pLineLink->iReferenceIndex = pOffsetPolyhedronLines[j].iEndPointIndex; + + pLineLink->pLine->pPolygons[pLineLink->iReferenceIndex] = &pStartPolygons[i]; + pLineLink->pLine->pPolygonLineLinks[pLineLink->iReferenceIndex] = pLineLink; + + pLineLink->pPrev = pStartPolygons[i].pLines; + pStartPolygons[i].pLines->pNext = pLineLink; + pStartPolygons[i].pLines = pLineLink; + } + + pFirstLink->pPrev = pStartPolygons[i].pLines; + pStartPolygons[i].pLines->pNext = pFirstLink; + } + + Assert( iCurrentLineLinkIndex == (pExistingPolyhedron->iLineCount * 4) ); + + //go back to point line links so we can circularly link them as well as order them now that every point has all its line links + for( int i = 0; i != pExistingPolyhedron->iVertexCount; ++i ) + { + //interlink the points + { + GeneratePolyhedronFromPlanes_LineLL *pLastVisitedLink = pStartPoints[i].pConnectedLines; + GeneratePolyhedronFromPlanes_LineLL *pCurrentLink = pLastVisitedLink; + + do + { + pCurrentLink->pPrev = pLastVisitedLink; + pLastVisitedLink = pCurrentLink; + pCurrentLink = pCurrentLink->pNext; + } while( pCurrentLink ); + + //circular link + pLastVisitedLink->pNext = pStartPoints[i].pConnectedLines; + pStartPoints[i].pConnectedLines->pPrev = pLastVisitedLink; + } + + + //fix ordering + GeneratePolyhedronFromPlanes_LineLL *pFirstLink = pStartPoints[i].pConnectedLines; + GeneratePolyhedronFromPlanes_LineLL *pWorkLink = pFirstLink; + GeneratePolyhedronFromPlanes_LineLL *pSearchLink; + GeneratePolyhedronFromPlanes_Polygon *pLookingForPolygon; + Assert( pFirstLink->pNext != pFirstLink ); + do + { + pLookingForPolygon = pWorkLink->pLine->pPolygons[1 - pWorkLink->iReferenceIndex]; //grab pointer to left polygon + pSearchLink = pWorkLink->pPrev; + + while( pSearchLink->pLine->pPolygons[pSearchLink->iReferenceIndex] != pLookingForPolygon ) + pSearchLink = pSearchLink->pPrev; + + Assert( pSearchLink->pLine->pPolygons[pSearchLink->iReferenceIndex] == pWorkLink->pLine->pPolygons[1 - pWorkLink->iReferenceIndex] ); + + //pluck the search link from wherever it is + pSearchLink->pPrev->pNext = pSearchLink->pNext; + pSearchLink->pNext->pPrev = pSearchLink->pPrev; + + //insert the search link just before the work link + pSearchLink->pPrev = pWorkLink->pPrev; + pSearchLink->pNext = pWorkLink; + + pSearchLink->pPrev->pNext = pSearchLink; + pWorkLink->pPrev = pSearchLink; + + pWorkLink = pSearchLink; + } while( pWorkLink != pFirstLink ); + } + + GeneratePolyhedronFromPlanes_UnorderedPointLL *pPoints = (GeneratePolyhedronFromPlanes_UnorderedPointLL *)stackalloc( pExistingPolyhedron->iVertexCount * sizeof( GeneratePolyhedronFromPlanes_UnorderedPointLL ) ); + GeneratePolyhedronFromPlanes_UnorderedLineLL *pLines = (GeneratePolyhedronFromPlanes_UnorderedLineLL *)stackalloc( pExistingPolyhedron->iLineCount * sizeof( GeneratePolyhedronFromPlanes_UnorderedLineLL ) ); + GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pPolygons = (GeneratePolyhedronFromPlanes_UnorderedPolygonLL *)stackalloc( pExistingPolyhedron->iPolygonCount * sizeof( GeneratePolyhedronFromPlanes_UnorderedPolygonLL ) ); + + //setup point collection + { + pPoints[0].pPrev = NULL; + pPoints[0].pPoint = &pStartPoints[0]; + pPoints[0].pNext = &pPoints[1]; + int iLastPoint = pExistingPolyhedron->iVertexCount - 1; + for( int i = 1; i != iLastPoint; ++i ) + { + pPoints[i].pPrev = &pPoints[i - 1]; + pPoints[i].pPoint = &pStartPoints[i]; + pPoints[i].pNext = &pPoints[i + 1]; + } + pPoints[iLastPoint].pPrev = &pPoints[iLastPoint - 1]; + pPoints[iLastPoint].pPoint = &pStartPoints[iLastPoint]; + pPoints[iLastPoint].pNext = NULL; + } + + //setup line collection + { + pLines[0].pPrev = NULL; + pLines[0].pLine = &pStartLines[0]; + pLines[0].pNext = &pLines[1]; + int iLastLine = pExistingPolyhedron->iLineCount - 1; + for( int i = 1; i != iLastLine; ++i ) + { + pLines[i].pPrev = &pLines[i - 1]; + pLines[i].pLine = &pStartLines[i]; + pLines[i].pNext = &pLines[i + 1]; + } + pLines[iLastLine].pPrev = &pLines[iLastLine - 1]; + pLines[iLastLine].pLine = &pStartLines[iLastLine]; + pLines[iLastLine].pNext = NULL; + } + + //setup polygon collection + { + pPolygons[0].pPrev = NULL; + pPolygons[0].pPolygon = &pStartPolygons[0]; + pPolygons[0].pNext = &pPolygons[1]; + int iLastPolygon = pExistingPolyhedron->iPolygonCount - 1; + for( int i = 1; i != iLastPolygon; ++i ) + { + pPolygons[i].pPrev = &pPolygons[i - 1]; + pPolygons[i].pPolygon = &pStartPolygons[i]; + pPolygons[i].pNext = &pPolygons[i + 1]; + } + pPolygons[iLastPolygon].pPrev = &pPolygons[iLastPolygon - 1]; + pPolygons[iLastPolygon].pPolygon = &pStartPolygons[iLastPolygon]; + pPolygons[iLastPolygon].pNext = NULL; + } + + return ClipLinkedGeometry( pPolygons, pLines, pPoints, pUsefulPlanes, iUsefulPlaneCount, fOnPlaneEpsilon, bUseTemporaryMemory ); +} + + + +Vector FindPointInPlanes( const float *pPlanes, int planeCount ) +{ + Vector point = vec3_origin; + + for ( int i = 0; i < planeCount; i++ ) + { + float fD = DotProduct( *(Vector *)&pPlanes[i*4], point ) - pPlanes[i*4 + 3]; + if ( fD < 0 ) + { + point -= fD * (*(Vector *)&pPlanes[i*4]); + } + } + return point; +} + + + +bool FindConvexShapeLooseAABB( const float *pInwardFacingPlanes, int iPlaneCount, Vector *pAABBMins, Vector *pAABBMaxs ) //bounding box of the convex shape (subject to floating point error) +{ + //returns false if the AABB hasn't been set + if( pAABBMins == NULL && pAABBMaxs == NULL ) //no use in actually finding out what it is + return false; + + struct FindConvexShapeAABB_Polygon_t + { + float *verts; + int iVertCount; + }; + + float *pMovedPlanes = (float *)stackalloc( iPlaneCount * 4 * sizeof( float ) ); + //Vector vPointInPlanes = FindPointInPlanes( pInwardFacingPlanes, iPlaneCount ); + + for( int i = 0; i != iPlaneCount; ++i ) + { + pMovedPlanes[(i * 4) + 0] = pInwardFacingPlanes[(i * 4) + 0]; + pMovedPlanes[(i * 4) + 1] = pInwardFacingPlanes[(i * 4) + 1]; + pMovedPlanes[(i * 4) + 2] = pInwardFacingPlanes[(i * 4) + 2]; + pMovedPlanes[(i * 4) + 3] = pInwardFacingPlanes[(i * 4) + 3] - 100.0f; //move planes out a lot to kill some imprecision problems + } + + + + //vAABBMins = vAABBMaxs = FindPointInPlanes( pPlanes, iPlaneCount ); + float *vertsIn = NULL; //we'll be allocating a new buffer for this with each new polygon, and moving it off to the polygon array + float *vertsOut = (float *)stackalloc( (iPlaneCount + 4) * (sizeof( float ) * 3) ); //each plane will initially have 4 points in its polygon representation, and each plane clip has the possibility to add 1 point to the polygon + float *vertsSwap; + + FindConvexShapeAABB_Polygon_t *pPolygons = (FindConvexShapeAABB_Polygon_t *)stackalloc( iPlaneCount * sizeof( FindConvexShapeAABB_Polygon_t ) ); + int iPolyCount = 0; + + for ( int i = 0; i < iPlaneCount; i++ ) + { + Vector *pPlaneNormal = (Vector *)&pInwardFacingPlanes[i*4]; + float fPlaneDist = pInwardFacingPlanes[(i*4) + 3]; + + if( vertsIn == NULL ) + vertsIn = (float *)stackalloc( (iPlaneCount + 4) * (sizeof( float ) * 3) ); + + // Build a big-ass poly in this plane + int vertCount = PolyFromPlane( (Vector *)vertsIn, *pPlaneNormal, fPlaneDist, 100000.0f ); + + //chop it by every other plane + for( int j = 0; j < iPlaneCount; j++ ) + { + // don't clip planes with themselves + if ( i == j ) + continue; + + // Chop the polygon against this plane + vertCount = ClipPolyToPlane( (Vector *)vertsIn, vertCount, (Vector *)vertsOut, *(Vector *)&pMovedPlanes[j*4], pMovedPlanes[(j*4) + 3], 0.0f ); + + //swap the input and output arrays + vertsSwap = vertsIn; vertsIn = vertsOut; vertsOut = vertsSwap; + + // Less than a poly left, something's wrong, don't bother with this polygon + if ( vertCount < 3 ) + break; + } + + if ( vertCount < 3 ) + continue; //not enough to work with + + pPolygons[iPolyCount].iVertCount = vertCount; + pPolygons[iPolyCount].verts = vertsIn; + vertsIn = NULL; + ++iPolyCount; + } + + if( iPolyCount == 0 ) + return false; + + //initialize the AABB to the first point available + Vector vAABBMins, vAABBMaxs; + vAABBMins = vAABBMaxs = ((Vector *)pPolygons[0].verts)[0]; + + if( pAABBMins && pAABBMaxs ) //they want the full box + { + for( int i = 0; i != iPolyCount; ++i ) + { + Vector *PolyVerts = (Vector *)pPolygons[i].verts; + for( int j = 0; j != pPolygons[i].iVertCount; ++j ) + { + if( PolyVerts[j].x < vAABBMins.x ) + vAABBMins.x = PolyVerts[j].x; + if( PolyVerts[j].y < vAABBMins.y ) + vAABBMins.y = PolyVerts[j].y; + if( PolyVerts[j].z < vAABBMins.z ) + vAABBMins.z = PolyVerts[j].z; + + if( PolyVerts[j].x > vAABBMaxs.x ) + vAABBMaxs.x = PolyVerts[j].x; + if( PolyVerts[j].y > vAABBMaxs.y ) + vAABBMaxs.y = PolyVerts[j].y; + if( PolyVerts[j].z > vAABBMaxs.z ) + vAABBMaxs.z = PolyVerts[j].z; + } + } + *pAABBMins = vAABBMins; + *pAABBMaxs = vAABBMaxs; + } + else if( pAABBMins ) //they only want the min + { + for( int i = 0; i != iPolyCount; ++i ) + { + Vector *PolyVerts = (Vector *)pPolygons[i].verts; + for( int j = 0; j != pPolygons[i].iVertCount; ++j ) + { + if( PolyVerts[j].x < vAABBMins.x ) + vAABBMins.x = PolyVerts[j].x; + if( PolyVerts[j].y < vAABBMins.y ) + vAABBMins.y = PolyVerts[j].y; + if( PolyVerts[j].z < vAABBMins.z ) + vAABBMins.z = PolyVerts[j].z; + } + } + *pAABBMins = vAABBMins; + } + else //they only want the max + { + for( int i = 0; i != iPolyCount; ++i ) + { + Vector *PolyVerts = (Vector *)pPolygons[i].verts; + for( int j = 0; j != pPolygons[i].iVertCount; ++j ) + { + if( PolyVerts[j].x > vAABBMaxs.x ) + vAABBMaxs.x = PolyVerts[j].x; + if( PolyVerts[j].y > vAABBMaxs.y ) + vAABBMaxs.y = PolyVerts[j].y; + if( PolyVerts[j].z > vAABBMaxs.z ) + vAABBMaxs.z = PolyVerts[j].z; + } + } + *pAABBMaxs = vAABBMaxs; + } + + return true; +} + + + + + + + +CPolyhedron *ConvertLinkedGeometryToPolyhedron( GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pPolygons, GeneratePolyhedronFromPlanes_UnorderedLineLL *pLines, GeneratePolyhedronFromPlanes_UnorderedPointLL *pPoints, bool bUseTemporaryMemory ) +{ + Assert( (pPolygons != NULL) && (pLines != NULL) && (pPoints != NULL) ); + unsigned int iPolyCount = 0, iLineCount = 0, iPointCount = 0, iIndexCount = 0; + + GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pActivePolygonWalk = pPolygons; + do + { + ++iPolyCount; + GeneratePolyhedronFromPlanes_LineLL *pLineWalk = pActivePolygonWalk->pPolygon->pLines; + GeneratePolyhedronFromPlanes_LineLL *pFirstLine = pLineWalk; + Assert( pLineWalk != NULL ); + + do + { + ++iIndexCount; + pLineWalk = pLineWalk->pNext; + } while( pLineWalk != pFirstLine ); + + pActivePolygonWalk = pActivePolygonWalk->pNext; + } while( pActivePolygonWalk ); + + GeneratePolyhedronFromPlanes_UnorderedLineLL *pActiveLineWalk = pLines; + do + { + ++iLineCount; + pActiveLineWalk = pActiveLineWalk->pNext; + } while( pActiveLineWalk ); + + GeneratePolyhedronFromPlanes_UnorderedPointLL *pActivePointWalk = pPoints; + do + { + ++iPointCount; + pActivePointWalk = pActivePointWalk->pNext; + } while( pActivePointWalk ); + + CPolyhedron *pReturn; + if( bUseTemporaryMemory ) + { + pReturn = GetTempPolyhedron( iPointCount, iLineCount, iIndexCount, iPolyCount ); + } + else + { + pReturn = CPolyhedron_AllocByNew::Allocate( iPointCount, iLineCount, iIndexCount, iPolyCount ); + } + + Vector *pVertexArray = pReturn->pVertices; + Polyhedron_IndexedLine_t *pLineArray = pReturn->pLines; + Polyhedron_IndexedLineReference_t *pIndexArray = pReturn->pIndices; + Polyhedron_IndexedPolygon_t *pPolyArray = pReturn->pPolygons; + + //copy points + pActivePointWalk = pPoints; + for( unsigned int i = 0; i != iPointCount; ++i ) + { + pVertexArray[i] = pActivePointWalk->pPoint->ptPosition; + pActivePointWalk->pPoint->iSaveIndices = i; //storing array indices + pActivePointWalk = pActivePointWalk->pNext; + } + + //copy lines + pActiveLineWalk = pLines; + for( unsigned int i = 0; i != iLineCount; ++i ) + { + pLineArray[i].iPointIndices[0] = (unsigned short)pActiveLineWalk->pLine->pPoints[0]->iSaveIndices; + pLineArray[i].iPointIndices[1] = (unsigned short)pActiveLineWalk->pLine->pPoints[1]->iSaveIndices; + + pActiveLineWalk->pLine->iSaveIndices = i; //storing array indices + + pActiveLineWalk = pActiveLineWalk->pNext; + } + + //copy polygons and indices at the same time + pActivePolygonWalk = pPolygons; + iIndexCount = 0; + for( unsigned int i = 0; i != iPolyCount; ++i ) + { + pPolyArray[i].polyNormal = pActivePolygonWalk->pPolygon->vSurfaceNormal; + pPolyArray[i].iFirstIndex = iIndexCount; + + GeneratePolyhedronFromPlanes_LineLL *pLineWalk = pActivePolygonWalk->pPolygon->pLines; + GeneratePolyhedronFromPlanes_LineLL *pFirstLine = pLineWalk; + do + { + //pIndexArray[iIndexCount] = pLineWalk->pLine->pPoints[pLineWalk->iReferenceIndex]->iWorkData; //startpoint of each line, iWorkData is the index of the vertex + pIndexArray[iIndexCount].iLineIndex = pLineWalk->pLine->iSaveIndices; + pIndexArray[iIndexCount].iEndPointIndex = pLineWalk->iReferenceIndex; + + ++iIndexCount; + pLineWalk = pLineWalk->pNext; + } while( pLineWalk != pFirstLine ); + + pPolyArray[i].iIndexCount = iIndexCount - pPolyArray[i].iFirstIndex; + + pActivePolygonWalk = pActivePolygonWalk->pNext; + } + +#if defined( _DEBUG ) && defined( ENABLE_DEBUG_POLYHEDRON_DUMPS ) && defined( DEBUG_DUMP_POLYHEDRONS_TO_NUMBERED_GLVIEWS ) + char szCollisionFile[128]; + CreateDumpDirectory( "PolyhedronDumps" ); + Q_snprintf( szCollisionFile, 128, "PolyhedronDumps/NewStyle_PolyhedronDump%i.txt", g_iPolyhedronDumpCounter ); + ++g_iPolyhedronDumpCounter; + + remove( szCollisionFile ); + DumpPolyhedronToGLView( pReturn, szCollisionFile, &s_matIdentity ); + DumpPolyhedronToGLView( pReturn, "PolyhedronDumps/NewStyle_PolyhedronDump_All-Appended.txt", &s_matIdentity ); +#endif + + return pReturn; +} + + + +#ifdef _DEBUG + +void DumpPointListToGLView( GeneratePolyhedronFromPlanes_UnorderedPointLL *pHead, PolyhedronPointPlanarity planarity, const Vector &vColor, const char *szDumpFile, const VMatrix *pTransform ) +{ +#ifdef ENABLE_DEBUG_POLYHEDRON_DUMPS + if( pTransform == NULL ) + pTransform = &s_matIdentity; + + FILE *pFile = fopen( szDumpFile, "ab" ); + + while( pHead ) + { + if( pHead->pPoint->planarity == planarity ) + { + const Vector vPointExtents( 0.5f, 0.5f, 0.01f ); + DumpAABBToGLView( (*pTransform) * pHead->pPoint->ptPosition, vPointExtents, vColor, pFile ); + } + pHead = pHead->pNext; + } + + fclose( pFile ); +#endif +} + +const char * DumpPolyhedronCutHistory( const CUtlVector &DumpedHistory, const CUtlVector &CutHistory, const VMatrix *pTransform ) +{ +#ifdef ENABLE_DEBUG_POLYHEDRON_DUMPS + if( pTransform == NULL ) + pTransform = &s_matIdentity; + + static char szDumpFile[100] = "FailedPolyhedronCut_Error.txt"; //most recent filename returned for further dumping + + for( int i = 0; i != DumpedHistory.Count(); ++i ) + { + if( DumpedHistory[i] != NULL ) + { + Q_snprintf( szDumpFile, 100, "FailedPolyhedronCut_%d.txt", i ); + DumpPolyhedronToGLView( DumpedHistory[i], szDumpFile, pTransform ); + DumpPlaneToGlView( CutHistory[i], 1.0f, szDumpFile, pTransform ); + } + } + + return szDumpFile; +#else + return NULL; +#endif +} + +#ifdef ENABLE_DEBUG_POLYHEDRON_DUMPS +#define AssertMsg_DumpPolyhedron(condition, message)\ + if( (condition) == false )\ + {\ + VMatrix matTransform;\ + matTransform.Identity();\ + matTransform[0][0] = matTransform[1][1] = matTransform[2][2] = 25.0f;\ + matTransform.SetTranslation( -DebugCutHistory.Tail()->Center() * 25.0f );\ + const char *szLastDumpFile = DumpPolyhedronCutHistory( DebugCutHistory, PlaneCutHistory, &matTransform );\ + DumpPointListToGLView( pAllPoints, POINT_ALIVE, Vector( 0.9f, 0.9f, 0.9f ), szLastDumpFile, &matTransform );\ + DumpPointListToGLView( pAllPoints, POINT_ONPLANE, Vector( 0.5f, 0.5f, 0.5f ), szLastDumpFile, &matTransform );\ + DumpPointListToGLView( pDeadPointCollection, POINT_DEAD, Vector( 0.1f, 0.1f, 0.1f ), szLastDumpFile, &matTransform );\ + if( pStartPoint )\ + {\ + FILE *pFileDumpRepairProgress = fopen( szLastDumpFile, "ab" );\ + DumpAABBToGLView( matTransform * pStartPoint->ptPosition, Vector( 2.0f, 0.05f, 0.05f ), Vector( 0.0f, 1.0f, 0.0f ), pFileDumpRepairProgress );\ + DumpAABBToGLView( matTransform * pWorkPoint->ptPosition, Vector( 2.0f, 0.05f, 0.05f ), Vector( 1.0f, 0.0f, 0.0f ), pFileDumpRepairProgress );\ + fclose( pFileDumpRepairProgress );\ + }\ + AssertMsg( condition, message );\ + } +#else +#define AssertMsg_DumpPolyhedron(condition, message) AssertMsg( condition, message ) +#endif +#define Assert_DumpPolyhedron(condition) AssertMsg_DumpPolyhedron( condition, #condition ) + +#else + +#define AssertMsg_DumpPolyhedron(condition, message) +#define Assert_DumpPolyhedron(condition) + +#endif + +CPolyhedron *ClipLinkedGeometry( GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pAllPolygons, GeneratePolyhedronFromPlanes_UnorderedLineLL *pAllLines, GeneratePolyhedronFromPlanes_UnorderedPointLL *pAllPoints, const float *pOutwardFacingPlanes, int iPlaneCount, float fOnPlaneEpsilon, bool bUseTemporaryMemory ) +{ + const float fNegativeOnPlaneEpsilon = -fOnPlaneEpsilon; + +#ifdef _DEBUG + CUtlVector DebugCutHistory; + CUtlVector PlaneCutHistory; + GeneratePolyhedronFromPlanes_Point *pStartPoint = NULL; + GeneratePolyhedronFromPlanes_Point *pWorkPoint = NULL; + + static int iPolyhedronClipCount = 0; + ++iPolyhedronClipCount; + + DebugCutHistory.AddToTail( ConvertLinkedGeometryToPolyhedron( pAllPolygons, pAllLines, pAllPoints, false ) ); +#endif + + //clear out polygon work variables + { + GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pActivePolygonWalk = pAllPolygons; + do + { + pActivePolygonWalk->pPolygon->bMissingASide = false; + pActivePolygonWalk = pActivePolygonWalk->pNext; + } while( pActivePolygonWalk ); + } + + + //Collections of dead pointers for reallocation, shouldn't be touched until the current loop iteration is done. + GeneratePolyhedronFromPlanes_UnorderedPointLL *pDeadPointCollection = NULL; + GeneratePolyhedronFromPlanes_UnorderedLineLL *pDeadLineCollection = NULL; + GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pDeadPolygonCollection = NULL; + GeneratePolyhedronFromPlanes_LineLL *pDeadLineLinkCollection = NULL; + + + for( int iCurrentPlane = 0; iCurrentPlane != iPlaneCount; ++iCurrentPlane ) + { + //clear out line work variables + { + GeneratePolyhedronFromPlanes_UnorderedLineLL *pActiveLineWalk = pAllLines; + do + { + pActiveLineWalk->pLine->bAlive = false; + pActiveLineWalk->pLine->bCut = false; + + pActiveLineWalk = pActiveLineWalk->pNext; + } while( pActiveLineWalk ); + } + + //TODO: Move these pointers into a reallocation pool + pDeadPointCollection = NULL; + pDeadLineCollection = NULL; + pDeadLineLinkCollection = NULL; + pDeadPolygonCollection = NULL; + + Vector vNormal = *((Vector *)&pOutwardFacingPlanes[(iCurrentPlane * 4) + 0]); + /*double vNormalAsDouble[3]; + vNormalAsDouble[0] = vNormal.x; + vNormalAsDouble[1] = vNormal.y; + vNormalAsDouble[2] = vNormal.z;*/ + float fPlaneDist = pOutwardFacingPlanes[(iCurrentPlane * 4) + 3]; + + //=================================================================================================== + // Step 1: Categorize each point as being either cut, split, or alive + //=================================================================================================== + { + bool bAllPointsDead = true; + bool bAllPointsAlive = true; + + //find point distances from the plane + GeneratePolyhedronFromPlanes_UnorderedPointLL *pActivePointWalk = pAllPoints; + do + { + GeneratePolyhedronFromPlanes_Point *pPoint = pActivePointWalk->pPoint; + float fPointDist = vNormal.Dot( pPoint->ptPosition ) - fPlaneDist; + if( fPointDist > fOnPlaneEpsilon ) + { + pPoint->planarity = POINT_DEAD; //point is dead, bang bang + + //mark connected lines as cut + GeneratePolyhedronFromPlanes_LineLL *pLineWalk = pPoint->pConnectedLines; + GeneratePolyhedronFromPlanes_LineLL *pFirstLine = pLineWalk; + do + { + pLineWalk->pLine->bCut = true; + pLineWalk = pLineWalk->pNext; + } while( pLineWalk != pFirstLine ); + + bAllPointsAlive = false; + } + else if( fPointDist <= fNegativeOnPlaneEpsilon ) + { + pPoint->planarity = POINT_ALIVE; //point is in behind plane, not voted off the island....yet + bAllPointsDead = false; + + //mark connected lines as alive + GeneratePolyhedronFromPlanes_LineLL *pLineWalk = pPoint->pConnectedLines; + GeneratePolyhedronFromPlanes_LineLL *pFirstLine = pLineWalk; + do + { + pLineWalk->pLine->bAlive = true; //mark the line as alive + pLineWalk = pLineWalk->pNext; + } while( pLineWalk != pFirstLine ); + } + else + { + pPoint->planarity = POINT_ONPLANE; //point is on the plane, he's everyone's buddy + + //Project on-plane points leaning towards death closer to the plane. This battles floating point precision decay. + // Consider the case of a large on-plane epsilon leaving protrusions over time + /*if( fPointDist < 0.0f ) + { + double distAsDouble = fPointDist; + double vPositionAsDouble[3]; + vPositionAsDouble[0] = pPoint->ptPosition.x; + vPositionAsDouble[1] = pPoint->ptPosition.y; + vPositionAsDouble[2] = pPoint->ptPosition.z; + + pPoint->ptPosition.x = vPositionAsDouble[0] - (distAsDouble * vNormalAsDouble[0]); + pPoint->ptPosition.y = vPositionAsDouble[1] - (distAsDouble * vNormalAsDouble[1]); + pPoint->ptPosition.z = vPositionAsDouble[2] - (distAsDouble * vNormalAsDouble[2]); + +#if ( 0 && defined( _DEBUG ) ) + float fDebugDist = vNormal.Dot( pPoint->ptPosition ) - fPlaneDist; //just for looking at in watch windows + AssertMsg( fabs( fDebugDist ) < fabs(fPointDist), "Projected point is further from plane than unprojected." ); +#endif + fPointDist = vNormal.Dot( pPoint->ptPosition ) - fPlaneDist; //recompute dist (not guaranteed to be 0.0 like we want) + }*/ + } + + pPoint->fPlaneDist = fPointDist; + + pActivePointWalk = pActivePointWalk->pNext; + } while( pActivePointWalk ); + + if( bAllPointsDead ) //all the points either died or are on the plane, no polyhedron left at all + { +#ifdef _DEBUG + for( int i = DebugCutHistory.Count(); --i >= 0; ) + { + if( DebugCutHistory[i] ) + DebugCutHistory[i]->Release(); + } + DebugCutHistory.RemoveAll(); +#endif + + return NULL; + } + + if( bAllPointsAlive ) + continue; //no cuts made + + + //Scan for onplane points connected to only other onplane/dead points, these points get downgraded to dead status. + { + GeneratePolyhedronFromPlanes_UnorderedPointLL *pActivePointWalk = pAllPoints; + do + { + if( pActivePointWalk->pPoint->planarity == POINT_ONPLANE ) + { + GeneratePolyhedronFromPlanes_LineLL *pOnPlaneLineWalk = pActivePointWalk->pPoint->pConnectedLines; + GeneratePolyhedronFromPlanes_LineLL *pStartLineWalk = pOnPlaneLineWalk; + bool bDead = true; //assume it's dead and disprove + do + { + if ( pOnPlaneLineWalk->pLine->bAlive ) + { + bDead = false; + } + else if ( pOnPlaneLineWalk->pLine->bCut ) + { + //connected to a dead point. + if( pOnPlaneLineWalk->pNext->pLine->bCut || pOnPlaneLineWalk->pPrev->pLine->bCut ) + { + //This on-plane point is surrounded by dead points on one polygon of the polyhedron. + // We have to downgrade this point to dead to avoid situations where float imprecision + // turns the polyhedron into a *slightly* concave shape. Concave shapes might break this algorithm, even falsely concave shapes. + bDead = true; + break; + } + } + + pOnPlaneLineWalk = pOnPlaneLineWalk->pNext; + } while( pOnPlaneLineWalk != pStartLineWalk ); + + if( bDead ) + { + pActivePointWalk->pPoint->planarity = POINT_DEAD; + + pOnPlaneLineWalk = pStartLineWalk; + + //mark connected lines as cut + do + { + pOnPlaneLineWalk->pLine->bCut = true; + pOnPlaneLineWalk = pOnPlaneLineWalk->pNext; + } while( pOnPlaneLineWalk != pStartLineWalk ); + } + } + pActivePointWalk = pActivePointWalk->pNext; + } while( pActivePointWalk ); + } +#ifdef _DEBUG + PlaneCutHistory.AddToTail( &pOutwardFacingPlanes[iCurrentPlane * 4] ); +#endif + } + + + + +#ifdef _DEBUG + //Run around the edges of all the polygons and ensure they don't have more than one point of lowered "alive" status (alive > onplane > dead) surrounded by higher status + // It indicates a concave shape. It's impossible to have it occur in theoretical space. But floating point numbers introduce error. + { + GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pDebugPolygonWalk = pAllPolygons; + do + { + int iSurroundedCount = 0; + GeneratePolyhedronFromPlanes_LineLL *pDebugLineWalk = pDebugPolygonWalk->pPolygon->pLines; + GeneratePolyhedronFromPlanes_LineLL *pFirstDebugLine = pDebugLineWalk; + + do + { + PolyhedronPointPlanarity currentPlanarity = pDebugLineWalk->pLine->pPoints[pDebugLineWalk->iReferenceIndex]->planarity; + + GeneratePolyhedronFromPlanes_LineLL *pNext = pDebugLineWalk->pNext; + PolyhedronPointPlanarity nextPlanarity = pNext->pLine->pPoints[pNext->iReferenceIndex]->planarity; + + if( currentPlanarity < nextPlanarity ) + { + GeneratePolyhedronFromPlanes_LineLL *pPrev = pDebugLineWalk->pPrev; + PolyhedronPointPlanarity prevPlanarity = pPrev->pLine->pPoints[pPrev->iReferenceIndex]->planarity; + + if( currentPlanarity < prevPlanarity ) + { + ++iSurroundedCount; + } + } + + pDebugLineWalk = pDebugLineWalk->pNext; + } while( pDebugLineWalk != pFirstDebugLine ); + + AssertMsg_DumpPolyhedron( iSurroundedCount <= 1, "Concave polygon, cutting process might break. Consider adjusting the on-plane epsilon to better compensate for floating point precision." ); + pDebugPolygonWalk = pDebugPolygonWalk->pNext; + } while( pDebugPolygonWalk ); + } +#endif + + //=================================================================================================== + // Step 2: Remove dead lines. A dead line is one with a dead point that isn't connected to a living point + //=================================================================================================== + { + GeneratePolyhedronFromPlanes_UnorderedLineLL *pActiveLineWalk = pAllLines; + do + { + GeneratePolyhedronFromPlanes_Line *pLine = pActiveLineWalk->pLine; + if( (pLine->bAlive == false) && (pLine->bCut == true) ) //not connected to a live point, but connected to a dead one. Dead line + { + //remove line from connected polygons + for( int i = 0; i != 2; ++i ) + { + GeneratePolyhedronFromPlanes_Polygon *pPolygon = pLine->pPolygons[i]; + GeneratePolyhedronFromPlanes_LineLL *pLineLink = pLine->pPolygonLineLinks[i]; + + pPolygon->bMissingASide = true; + + if( pLineLink->pNext == pLineLink ) + { + //this was the last line of the polygon, it's dead + pPolygon->pLines = NULL; + } + else + { + //link around this line + pPolygon->pLines = pLineLink->pPrev; //Always have the polygon's head line be just before the gap in the polygon + pLineLink->pNext->pPrev = pLineLink->pPrev; + pLineLink->pPrev->pNext = pLineLink->pNext; + } + + //move the line link to the dead list + pLineLink->pNext = pDeadLineLinkCollection; + pDeadLineLinkCollection = pLineLink; + } + + //remove the line from connected points + for( int i = 0; i != 2; ++i ) + { + GeneratePolyhedronFromPlanes_Point *pPoint = pLine->pPoints[i]; + GeneratePolyhedronFromPlanes_LineLL *pLineLink = pLine->pPointLineLinks[i]; + + if( pLineLink->pNext == pLineLink ) + { + //this is the last line + pPoint->pConnectedLines = NULL; + Assert( pPoint->planarity != POINT_ALIVE ); + pPoint->planarity = POINT_DEAD; //in case it was merely POINT_ONPLANE before + } + else + { + //link around this line + pPoint->pConnectedLines = pLineLink->pNext; //in case pLineLink was the head line + pLineLink->pNext->pPrev = pLineLink->pPrev; + pLineLink->pPrev->pNext = pLineLink->pNext; + } + + //move the line link to the dead list + pLineLink->pNext = pDeadLineLinkCollection; + pDeadLineLinkCollection = pLineLink; + } + + //move the line to the dead list + { + //link past this node + if( pActiveLineWalk->pPrev ) + pActiveLineWalk->pPrev->pNext = pActiveLineWalk->pNext; + else + pAllLines = pActiveLineWalk->pNext; + + if( pActiveLineWalk->pNext ) + pActiveLineWalk->pNext->pPrev = pActiveLineWalk->pPrev; + + GeneratePolyhedronFromPlanes_UnorderedLineLL *pNextLineWalk = pActiveLineWalk->pNext; + + //add to the dead list + pActiveLineWalk->pNext = pDeadLineCollection; + pDeadLineCollection = pActiveLineWalk; + + //next + pActiveLineWalk = pNextLineWalk; + } + } + else + { + pActiveLineWalk = pActiveLineWalk->pNext; + } + } while( pActiveLineWalk ); + } + + + //=================================================================================================== + // Step 3: Remove dead polygons. A dead polygon has less than 2 lines. + //=================================================================================================== + { + GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pActivePolygonWalk = pAllPolygons; + do + { + GeneratePolyhedronFromPlanes_Polygon *pPolygon = pActivePolygonWalk->pPolygon; + GeneratePolyhedronFromPlanes_LineLL *pHeadLine = pPolygon->pLines; + + bool bDead = (pHeadLine == NULL) || (pHeadLine->pNext == pHeadLine); + if( !bDead ) + { + //there's a rare case where a polygon can be almost entirely coplanar with the cut, it comes purely out of the land of imprecision + bDead = true; //assume it's dead, and disprove + + GeneratePolyhedronFromPlanes_LineLL *pTestLineWalk = pHeadLine; + do + { + if( pTestLineWalk->pLine->bAlive ) + { + bDead = false; + break; + } + + pTestLineWalk = pTestLineWalk->pNext; + } while( pTestLineWalk != pHeadLine ); + } + + if( bDead ) + { + //dead polygon, move it to the dead list + + //link around this node + if( pActivePolygonWalk->pPrev ) + pActivePolygonWalk->pPrev->pNext = pActivePolygonWalk->pNext; + else + pAllPolygons = pAllPolygons->pNext; //pActivePolygonWalk was the head node + + if( pActivePolygonWalk->pNext ) + pActivePolygonWalk->pNext->pPrev = pActivePolygonWalk->pPrev; + + GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pNextPolygonWalk = pActivePolygonWalk->pNext; + + //add to the dead list + pActivePolygonWalk->pNext = pDeadPolygonCollection; + pDeadPolygonCollection = pActivePolygonWalk; + + //next + pActivePolygonWalk = pNextPolygonWalk; + } + else + { + AssertMsg_DumpPolyhedron( (pActivePolygonWalk->pPolygon->pLines != NULL) && + (pActivePolygonWalk->pPolygon->pLines != pActivePolygonWalk->pPolygon->pLines->pNext), "Living polygon with less than 2 lines" ); + + pActivePolygonWalk = pActivePolygonWalk->pNext; + } + } while( pActivePolygonWalk ); + } + + //=================================================================================================== + // Step 4: Remove dead points. + //=================================================================================================== + { + GeneratePolyhedronFromPlanes_UnorderedPointLL *pActivePointWalk = pAllPoints; + do + { + if( pActivePointWalk->pPoint->planarity == POINT_DEAD ) + { + GeneratePolyhedronFromPlanes_UnorderedPointLL *pNext = pActivePointWalk->pNext; + + if( pActivePointWalk->pPrev ) + pActivePointWalk->pPrev->pNext = pActivePointWalk->pNext; + else + pAllPoints = pAllPoints->pNext; + + if( pActivePointWalk->pNext ) + pActivePointWalk->pNext->pPrev = pActivePointWalk->pPrev; + + pActivePointWalk->pNext = pDeadPointCollection; + pDeadPointCollection = pActivePointWalk; + + pActivePointWalk = pNext; + } + else + { + pActivePointWalk = pActivePointWalk->pNext; + } + } while( pActivePointWalk ); + } + + + //=================================================================================================== + // Step 5: Handle cut lines + //=================================================================================================== + { + GeneratePolyhedronFromPlanes_UnorderedLineLL *pActiveLineWalk = pAllLines; + do + { + GeneratePolyhedronFromPlanes_Line *pWorkLine = pActiveLineWalk->pLine; + Assert_DumpPolyhedron( (pWorkLine->bAlive == true) || (pWorkLine->bCut == false) ); //all dead lines should have already been removed + + if( pWorkLine->bCut ) + { + GeneratePolyhedronFromPlanes_Point **pLinePoints = pWorkLine->pPoints; + + Assert_DumpPolyhedron( (pLinePoints[0]->planarity == POINT_DEAD) || (pLinePoints[1]->planarity == POINT_DEAD) ); //one of the two has to be a dead point + + int iDeadIndex = (pLinePoints[0]->planarity == POINT_DEAD)?(0):(1); + int iLivingIndex = 1 - iDeadIndex; + GeneratePolyhedronFromPlanes_Point *pDeadPoint = pLinePoints[iDeadIndex]; + GeneratePolyhedronFromPlanes_Point *pLivingPoint = pLinePoints[iLivingIndex]; + + Assert_DumpPolyhedron( pLivingPoint->planarity == POINT_ALIVE ); //if this point were on-plane or dead, the line should be dead + + //We'll be de-linking from the old point and generating a new one. We do this so other lines can still access the dead point's untouched data. + + //Generate a new point + GeneratePolyhedronFromPlanes_Point *pNewPoint = (GeneratePolyhedronFromPlanes_Point *)stackalloc( sizeof( GeneratePolyhedronFromPlanes_Point ) ); + { + //add this point to the active list + pAllPoints->pPrev = (GeneratePolyhedronFromPlanes_UnorderedPointLL *)stackalloc( sizeof( GeneratePolyhedronFromPlanes_UnorderedPointLL ) ); + pAllPoints->pPrev->pNext = pAllPoints; + pAllPoints = pAllPoints->pPrev; + pAllPoints->pPrev = NULL; + pAllPoints->pPoint = pNewPoint; + + + float fInvTotalDist = 1.0f/(pDeadPoint->fPlaneDist - pLivingPoint->fPlaneDist); //subtraction because the living index is known to be negative + pNewPoint->ptPosition = (pLivingPoint->ptPosition * (pDeadPoint->fPlaneDist * fInvTotalDist)) - (pDeadPoint->ptPosition * (pLivingPoint->fPlaneDist * fInvTotalDist)); + +#if ( 0 && defined( _DEBUG ) ) + float fDebugDist = vNormal.Dot( pNewPoint->ptPosition ) - fPlaneDist; //just for looking at in watch windows + AssertMsg_DumpPolyhedron( fabs( fDebugDist ) < fOnPlaneEpsilon, "Generated split point is far from plane" ); + + //verify that the new point isn't sitting on top of another + { + GeneratePolyhedronFromPlanes_UnorderedPointLL *pActivePointWalk = pAllPoints; + do + { + if( pActivePointWalk->pPoint != pNewPoint ) + { + Vector vDiff = pActivePointWalk->pPoint->ptPosition - pNewPoint->ptPosition; + + AssertMsg_DumpPolyhedron( vDiff.Length() > fOnPlaneEpsilon, "Generated a point on top of another" ); + } + pActivePointWalk = pActivePointWalk->pNext; + } while( pActivePointWalk ); + } +#endif + + pNewPoint->planarity = POINT_ONPLANE; + pNewPoint->fPlaneDist = 0.0f; + } + + GeneratePolyhedronFromPlanes_LineLL *pNewLineLink = pNewPoint->pConnectedLines = (GeneratePolyhedronFromPlanes_LineLL *)stackalloc( sizeof( GeneratePolyhedronFromPlanes_LineLL ) ); + pNewLineLink->pLine = pWorkLine; + pNewLineLink->pNext = pNewLineLink; + pNewLineLink->pPrev = pNewLineLink; + pNewLineLink->iReferenceIndex = iLivingIndex; + + pWorkLine->pPoints[iDeadIndex] = pNewPoint; + pWorkLine->pPointLineLinks[iDeadIndex] = pNewLineLink; + pNewPoint->pConnectedLines = pNewLineLink; + + //A new line is needed on each polygon touching the dead point to connect the two new endpoints for split lines. + // So mark connected polygons as missing a side. + for( int i = 0; i != 2; ++i ) + pWorkLine->pPolygons[i]->bMissingASide = true; + + + //Always have a cut polygon's head line be just before the gap in the polygon. + // In this case, we know that one of the two polygons goes clockwise into the dead point, so have that polygon point at this line. + // We don't know enough about the other polygon to do anything here, but another cut line will handle that polygon. So it all works out in the end. + pWorkLine->pPolygons[iDeadIndex]->pLines = pWorkLine->pPolygonLineLinks[iDeadIndex]; + } + + pActiveLineWalk = pActiveLineWalk->pNext; + } while( pActiveLineWalk ); + } + + + //=================================================================================================== + // Step 6: Repair polygons that are missing a side. And generate the new coplanar polygon. + //=================================================================================================== + { + //Find the first polygon missing a side. + // We'll then walk from polygon to polygon using line connections so that we can generate the new polygon in a clockwise manner. + GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pActivePolygonWalk = pAllPolygons; + + while( (pActivePolygonWalk != NULL) && (pActivePolygonWalk->pPolygon->bMissingASide == false) ) + { + pActivePolygonWalk = pActivePolygonWalk->pNext; + } + + //acquire iteration data +#ifndef _DEBUG + GeneratePolyhedronFromPlanes_Point *pStartPoint; + GeneratePolyhedronFromPlanes_Point *pWorkPoint; +#endif + + GeneratePolyhedronFromPlanes_LineLL *pLastLineLink; + GeneratePolyhedronFromPlanes_Polygon *pWorkPolygon; + GeneratePolyhedronFromPlanes_LineLL *pTestLine; + +#ifdef _DEBUG + GeneratePolyhedronFromPlanes_Polygon *pLastWorkPolygon = NULL; + GeneratePolyhedronFromPlanes_Point *pLastWorkPoint = NULL; +#endif + + if( pActivePolygonWalk ) + { + //grab the polygon we'll be starting with + GeneratePolyhedronFromPlanes_Polygon *pBrokenPolygon = pActivePolygonWalk->pPolygon; + + { + GeneratePolyhedronFromPlanes_LineLL *pTemp = pBrokenPolygon->pLines->pNext; + pStartPoint = pTemp->pLine->pPoints[1 - pTemp->iReferenceIndex]; + Assert_DumpPolyhedron( pStartPoint->planarity == POINT_ONPLANE ); //every working point should be coplanar + pLastLineLink = pTemp->pLine->pPointLineLinks[1 - pTemp->iReferenceIndex]->pNext; + pWorkPolygon = pBrokenPolygon; + } + + pWorkPoint = pStartPoint; + pTestLine = pLastLineLink->pPrev; //rotate counterclockwise around the point + } + else + { + //apparently the plane was entirely through existing polygonal borders, extremely rare but it can happen with inefficient cutting planes + GeneratePolyhedronFromPlanes_UnorderedPointLL *pActivePointWalk = pAllPoints; + while( (pActivePointWalk != NULL) && (pActivePointWalk->pPoint->planarity != POINT_ONPLANE) ) + { + pActivePointWalk = pActivePointWalk->pNext; + } + + Assert( pActivePointWalk != NULL ); + + pStartPoint = pWorkPoint = pActivePointWalk->pPoint; + GeneratePolyhedronFromPlanes_LineLL *pLines = pWorkPoint->pConnectedLines; + + while( !pLines->pLine->bAlive ) //seek clockwise until we find a line not on the plane + pLines = pLines->pNext; + + while( pLines->pLine->bAlive ) //now seek counterclockwise until we find a line on the plane (in case we started on an alive line last seek) + pLines = pLines->pPrev; + + //now pLines points at one side of the polygon, with pActivePointWalk + pLastLineLink = pLines; + pTestLine = pLines->pPrev; + pWorkPolygon = pTestLine->pLine->pPolygons[1 - pTestLine->iReferenceIndex]; + + } + + //create the new polygon + GeneratePolyhedronFromPlanes_Polygon *pNewPolygon = (GeneratePolyhedronFromPlanes_Polygon *)stackalloc( sizeof( GeneratePolyhedronFromPlanes_Polygon ) ); + { + //before we forget, add this polygon to the active list + pAllPolygons->pPrev = (GeneratePolyhedronFromPlanes_UnorderedPolygonLL *)stackalloc( sizeof( GeneratePolyhedronFromPlanes_UnorderedPolygonLL ) ); + pAllPolygons->pPrev->pNext = pAllPolygons; + pAllPolygons = pAllPolygons->pPrev; + pAllPolygons->pPrev = NULL; + pAllPolygons->pPolygon = pNewPolygon; + + pNewPolygon->bMissingASide = false; //technically missing all it's sides, but we're fixing it now + pNewPolygon->vSurfaceNormal = vNormal; + pNewPolygon->pLines = NULL; + } + + + + //=================================================================================================================== + // The general idea of the upcoming algorithm to put together a new polygon and patch broken polygons... + // You have a point and a line the algorithm just jumped across. + // 1. Rotate through the point's line links one time counterclockwise (pPrev) + // 2. If the line is cut, then we make a new bridging line in the polygon between that line and the one counterclockwise to it. (pPrev) + // If the line is on-plane. Skip the bridge line making, but set links to the new polygon as if we'd just created the bridge + // 3. Once we follow a line back to the point where we started, we should be all done. + + do + { + if( pWorkPolygon->bMissingASide ) + { + //during the cutting process we made sure that the head line link was going clockwise into the missing area + GeneratePolyhedronFromPlanes_LineLL *pGapLines[2]; + pGapLines[1] = pTestLine->pLine->pPolygonLineLinks[pTestLine->iReferenceIndex]; //get the same line, but in the polygons linked list. + Assert_DumpPolyhedron( pGapLines[1]->pLine == pTestLine->pLine ); + pGapLines[0] = pGapLines[1]->pPrev; + + Assert_DumpPolyhedron( pWorkPolygon->bMissingASide ); + +#ifdef _DEBUG + { + //ensure that the space between the gap lines is the only space where fixing is required + GeneratePolyhedronFromPlanes_LineLL *pDebugLineWalk = pGapLines[1]->pNext; + + while( pDebugLineWalk != pGapLines[0] ) + { + Assert_DumpPolyhedron( pDebugLineWalk->pLine->bCut == false ); + pDebugLineWalk = pDebugLineWalk->pNext; + } + } +#endif + + GeneratePolyhedronFromPlanes_Line *pJoinLine = (GeneratePolyhedronFromPlanes_Line *)stackalloc( sizeof( GeneratePolyhedronFromPlanes_Line ) ); + { + //before we forget, add this line to the active list + pAllLines->pPrev = (GeneratePolyhedronFromPlanes_UnorderedLineLL *)stackalloc( sizeof( GeneratePolyhedronFromPlanes_UnorderedLineLL ) ); + pAllLines->pPrev->pNext = pAllLines; + pAllLines = pAllLines->pPrev; + pAllLines->pPrev = NULL; + pAllLines->pLine = pJoinLine; + + pJoinLine->bAlive = false; + pJoinLine->bCut = false; + } + + + pJoinLine->pPoints[0] = pGapLines[0]->pLine->pPoints[pGapLines[0]->iReferenceIndex]; + pJoinLine->pPoints[1] = pGapLines[1]->pLine->pPoints[1 - pGapLines[1]->iReferenceIndex]; + + pJoinLine->pPolygons[0] = pNewPolygon; + pJoinLine->pPolygons[1] = pWorkPolygon; + + //now create all 4 links into the line + GeneratePolyhedronFromPlanes_LineLL *pPointLinks[2]; + pPointLinks[0] = (GeneratePolyhedronFromPlanes_LineLL *)stackalloc( sizeof( GeneratePolyhedronFromPlanes_LineLL ) ); + pPointLinks[1] = (GeneratePolyhedronFromPlanes_LineLL *)stackalloc( sizeof( GeneratePolyhedronFromPlanes_LineLL ) ); + + GeneratePolyhedronFromPlanes_LineLL *pPolygonLinks[2]; + pPolygonLinks[0] = (GeneratePolyhedronFromPlanes_LineLL *)stackalloc( sizeof( GeneratePolyhedronFromPlanes_LineLL ) ); + pPolygonLinks[1] = (GeneratePolyhedronFromPlanes_LineLL *)stackalloc( sizeof( GeneratePolyhedronFromPlanes_LineLL ) ); + + pPointLinks[0]->pLine = pPointLinks[1]->pLine = pPolygonLinks[0]->pLine = pPolygonLinks[1]->pLine = pJoinLine; + + pJoinLine->pPointLineLinks[0] = pPointLinks[0]; + pJoinLine->pPointLineLinks[1] = pPointLinks[1]; + pJoinLine->pPolygonLineLinks[0] = pPolygonLinks[0]; + pJoinLine->pPolygonLineLinks[1] = pPolygonLinks[1]; + + + + pPointLinks[0]->iReferenceIndex = 1; + pPointLinks[1]->iReferenceIndex = 0; + + //Insert before the link from point 0 to gap line 0 (counterclockwise rotation) + { + GeneratePolyhedronFromPlanes_LineLL *pWorkLink = pGapLines[0]->pLine->pPointLineLinks[pGapLines[0]->iReferenceIndex]; + Assert_DumpPolyhedron( pWorkLink->pLine == pGapLines[0]->pLine ); + + pPointLinks[0]->pPrev = pWorkLink->pPrev; + pPointLinks[0]->pNext = pWorkLink; + + pWorkLink->pPrev->pNext = pPointLinks[0]; + pWorkLink->pPrev = pPointLinks[0]; + } + + //Insert after the link from point 1 to gap line 1 (clockwise rotation) + { + GeneratePolyhedronFromPlanes_LineLL *pWorkLink = pGapLines[1]->pLine->pPointLineLinks[1 - pGapLines[1]->iReferenceIndex]; + Assert_DumpPolyhedron( pWorkLink->pLine == pGapLines[1]->pLine ); + + pPointLinks[1]->pNext = pWorkLink->pNext; + pPointLinks[1]->pPrev = pWorkLink; + + pWorkLink->pNext->pPrev = pPointLinks[1]; + pWorkLink->pNext = pPointLinks[1]; + } + + + + + pPolygonLinks[0]->iReferenceIndex = 0; + pPolygonLinks[1]->iReferenceIndex = 1; + + //Insert before the head line in the new polygon (at the end of the clockwise order) + { + if( pNewPolygon->pLines == NULL ) + { + //this is the first line being added to the polygon + pNewPolygon->pLines = pPolygonLinks[0]; + pPolygonLinks[0]->pNext = pPolygonLinks[0]; + pPolygonLinks[0]->pPrev = pPolygonLinks[0]; + } + else + { + GeneratePolyhedronFromPlanes_LineLL *pWorkLink = pNewPolygon->pLines; + + pPolygonLinks[0]->pNext = pWorkLink; + pPolygonLinks[0]->pPrev = pWorkLink->pPrev; + + pWorkLink->pPrev->pNext = pPolygonLinks[0]; + pWorkLink->pPrev = pPolygonLinks[0]; + } + } + + //Insert after the head line in the work polygon + { + GeneratePolyhedronFromPlanes_LineLL *pWorkLink = pWorkPolygon->pLines; + + pPolygonLinks[1]->pNext = pWorkLink->pNext; + pPolygonLinks[1]->pPrev = pWorkLink; + + pWorkLink->pNext->pPrev = pPolygonLinks[1]; + pWorkLink->pNext = pPolygonLinks[1]; + } + + pWorkPolygon->bMissingASide = false; //repairs are finished + +#ifdef _DEBUG + pLastWorkPolygon = pWorkPolygon; + pLastWorkPoint = pWorkPoint; +#endif + //move to the next point + pWorkPoint = pJoinLine->pPoints[0]; + pLastLineLink = pJoinLine->pPointLineLinks[0]; + Assert_DumpPolyhedron( pWorkPoint->planarity == POINT_ONPLANE ); //every working point should be coplanar + + pTestLine = pLastLineLink->pPrev; + if( pTestLine->pLine->pPoints[pTestLine->iReferenceIndex]->planarity == POINT_ALIVE ) + pWorkPolygon = pTestLine->pLine->pPolygons[pTestLine->iReferenceIndex]; + else + pWorkPolygon = pTestLine->pLine->pPolygons[1 - pTestLine->iReferenceIndex]; + + Assert_DumpPolyhedron( pWorkPolygon != pLastWorkPolygon ); + Assert_DumpPolyhedron( (pWorkPoint == pStartPoint) || + (pGapLines[0]->pLine->bCut == false) || + (pWorkPolygon->bMissingASide == true) ); //if we're not done fixing, and if the shared line was cut, the next polygon must be missing a side + } + else + { + //line is on the plane, meaning the polygon isn't broken and doesn't need patching + Assert_DumpPolyhedron( pTestLine->pLine->bCut == false ); + Assert_DumpPolyhedron( (pTestLine->pLine->pPoints[0]->planarity == POINT_ONPLANE) && (pTestLine->pLine->pPoints[1]->planarity == POINT_ONPLANE) ); + + + //link to this line from the new polygon + GeneratePolyhedronFromPlanes_LineLL *pNewLineLink; + pNewLineLink = (GeneratePolyhedronFromPlanes_LineLL *)stackalloc( sizeof( GeneratePolyhedronFromPlanes_LineLL ) ); + + pNewLineLink->pLine = pTestLine->pLine; + pNewLineLink->iReferenceIndex = pTestLine->iReferenceIndex; + + //Insert before the head line in the new polygon (at the end of the clockwise order) + { + if( pNewPolygon->pLines == NULL ) + { + //this is the first line being added to the polygon + pNewPolygon->pLines = pNewLineLink; + pNewLineLink->pNext = pNewLineLink; + pNewLineLink->pPrev = pNewLineLink; + } + else + { + GeneratePolyhedronFromPlanes_LineLL *pWorkLink = pNewPolygon->pLines; + + pNewLineLink->pNext = pWorkLink; + pNewLineLink->pPrev = pWorkLink->pPrev; + + pWorkLink->pPrev->pNext = pNewLineLink; + pWorkLink->pPrev = pNewLineLink; + } + } + + //Since the entire line is on the plane, that means it used to point to something that used to reside where the new polygon is going + // Update the link to the new the polygon pointer and be on our way + pTestLine->pLine->pPolygons[pTestLine->iReferenceIndex] = pNewPolygon; + pTestLine->pLine->pPolygonLineLinks[pTestLine->iReferenceIndex] = pNewLineLink; + +#ifdef _DEBUG + pLastWorkPolygon = pWorkPolygon; + pLastWorkPoint = pWorkPoint; +#endif + + pWorkPoint = pTestLine->pLine->pPoints[pTestLine->iReferenceIndex]; + pLastLineLink = pTestLine->pLine->pPointLineLinks[pTestLine->iReferenceIndex]; + Assert_DumpPolyhedron( pWorkPoint->planarity == POINT_ONPLANE ); //every working point should be coplanar + + pTestLine = pLastLineLink->pPrev; + if( pTestLine->pLine->pPoints[pTestLine->iReferenceIndex]->planarity == POINT_ALIVE ) + pWorkPolygon = pTestLine->pLine->pPolygons[pTestLine->iReferenceIndex]; + else + pWorkPolygon = pTestLine->pLine->pPolygons[1 - pTestLine->iReferenceIndex]; + + Assert_DumpPolyhedron( pWorkPolygon != pLastWorkPolygon ); + } + } while( pWorkPoint != pStartPoint ); + } + +#ifdef _DEBUG + //verify that repairs are complete + { + GeneratePolyhedronFromPlanes_UnorderedPolygonLL *pDebugPolygonWalk = pAllPolygons; + do + { + AssertMsg_DumpPolyhedron( pDebugPolygonWalk->pPolygon->bMissingASide == false, "Some polygons not repaired after cut" ); + pDebugPolygonWalk = pDebugPolygonWalk->pNext; + } while( pDebugPolygonWalk ); + + + GeneratePolyhedronFromPlanes_UnorderedPointLL *pDebugPointWalk = pAllPoints; + do + { + AssertMsg_DumpPolyhedron( pDebugPointWalk->pPoint->pConnectedLines, "Point connected to no lines after cut" ); + pDebugPointWalk = pDebugPointWalk->pNext; + } while( pDebugPointWalk ); + + pStartPoint = NULL; + } + + //maintain the cut history + DebugCutHistory.AddToTail( ConvertLinkedGeometryToPolyhedron( pAllPolygons, pAllLines, pAllPoints, false ) ); +#endif + } + +#ifdef _DEBUG + for( int i = DebugCutHistory.Count(); --i >= 0; ) + { + if( DebugCutHistory[i] ) + DebugCutHistory[i]->Release(); + } + DebugCutHistory.RemoveAll(); +#endif + + return ConvertLinkedGeometryToPolyhedron( pAllPolygons, pAllLines, pAllPoints, bUseTemporaryMemory ); +} + + + +#define STARTPOINTTOLINELINKS(iPointNum, lineindex1, iOtherPointIndex1, lineindex2, iOtherPointIndex2, lineindex3, iOtherPointIndex3 )\ + StartingBoxPoints[iPointNum].pConnectedLines = &StartingPoints_To_Lines_Links[(iPointNum * 3) + 0];\ + StartingPoints_To_Lines_Links[(iPointNum * 3) + 0].pLine = &StartingBoxLines[lineindex1];\ + StartingPoints_To_Lines_Links[(iPointNum * 3) + 0].iReferenceIndex = iOtherPointIndex1;\ + StartingBoxLines[lineindex1].pPointLineLinks[1 - iOtherPointIndex1] = &StartingPoints_To_Lines_Links[(iPointNum * 3) + 0];\ + StartingPoints_To_Lines_Links[(iPointNum * 3) + 0].pPrev = &StartingPoints_To_Lines_Links[(iPointNum * 3) + 2];\ + StartingPoints_To_Lines_Links[(iPointNum * 3) + 0].pNext = &StartingPoints_To_Lines_Links[(iPointNum * 3) + 1];\ + StartingPoints_To_Lines_Links[(iPointNum * 3) + 1].pLine = &StartingBoxLines[lineindex2];\ + StartingPoints_To_Lines_Links[(iPointNum * 3) + 1].iReferenceIndex = iOtherPointIndex2;\ + StartingBoxLines[lineindex2].pPointLineLinks[1 - iOtherPointIndex2] = &StartingPoints_To_Lines_Links[(iPointNum * 3) + 1];\ + StartingPoints_To_Lines_Links[(iPointNum * 3) + 1].pPrev = &StartingPoints_To_Lines_Links[(iPointNum * 3) + 0];\ + StartingPoints_To_Lines_Links[(iPointNum * 3) + 1].pNext = &StartingPoints_To_Lines_Links[(iPointNum * 3) + 2];\ + StartingPoints_To_Lines_Links[(iPointNum * 3) + 2].pLine = &StartingBoxLines[lineindex3];\ + StartingPoints_To_Lines_Links[(iPointNum * 3) + 2].iReferenceIndex = iOtherPointIndex3;\ + StartingBoxLines[lineindex3].pPointLineLinks[1 - iOtherPointIndex3] = &StartingPoints_To_Lines_Links[(iPointNum * 3) + 2];\ + StartingPoints_To_Lines_Links[(iPointNum * 3) + 2].pPrev = &StartingPoints_To_Lines_Links[(iPointNum * 3) + 1];\ + StartingPoints_To_Lines_Links[(iPointNum * 3) + 2].pNext = &StartingPoints_To_Lines_Links[(iPointNum * 3) + 0]; + +#define STARTBOXCONNECTION( linenum, point1, point2, poly1, poly2 )\ + StartingBoxLines[linenum].pPoints[0] = &StartingBoxPoints[point1];\ + StartingBoxLines[linenum].pPoints[1] = &StartingBoxPoints[point2];\ + StartingBoxLines[linenum].pPolygons[0] = &StartingBoxPolygons[poly1];\ + StartingBoxLines[linenum].pPolygons[1] = &StartingBoxPolygons[poly2]; + +#define STARTPOLYGONTOLINELINKS( polynum, lineindex1, iThisPolyIndex1, lineindex2, iThisPolyIndex2, lineindex3, iThisPolyIndex3, lineindex4, iThisPolyIndex4 )\ + StartingBoxPolygons[polynum].pLines = &StartingPolygon_To_Lines_Links[(polynum * 4) + 0];\ + StartingPolygon_To_Lines_Links[(polynum * 4) + 0].pLine = &StartingBoxLines[lineindex1];\ + StartingPolygon_To_Lines_Links[(polynum * 4) + 0].iReferenceIndex = iThisPolyIndex1;\ + StartingBoxLines[lineindex1].pPolygonLineLinks[iThisPolyIndex1] = &StartingPolygon_To_Lines_Links[(polynum * 4) + 0];\ + StartingPolygon_To_Lines_Links[(polynum * 4) + 0].pPrev = &StartingPolygon_To_Lines_Links[(polynum * 4) + 3];\ + StartingPolygon_To_Lines_Links[(polynum * 4) + 0].pNext = &StartingPolygon_To_Lines_Links[(polynum * 4) + 1];\ + StartingPolygon_To_Lines_Links[(polynum * 4) + 1].pLine = &StartingBoxLines[lineindex2];\ + StartingPolygon_To_Lines_Links[(polynum * 4) + 1].iReferenceIndex = iThisPolyIndex2;\ + StartingBoxLines[lineindex2].pPolygonLineLinks[iThisPolyIndex2] = &StartingPolygon_To_Lines_Links[(polynum * 4) + 1];\ + StartingPolygon_To_Lines_Links[(polynum * 4) + 1].pPrev = &StartingPolygon_To_Lines_Links[(polynum * 4) + 0];\ + StartingPolygon_To_Lines_Links[(polynum * 4) + 1].pNext = &StartingPolygon_To_Lines_Links[(polynum * 4) + 2];\ + StartingPolygon_To_Lines_Links[(polynum * 4) + 2].pLine = &StartingBoxLines[lineindex3];\ + StartingPolygon_To_Lines_Links[(polynum * 4) + 2].iReferenceIndex = iThisPolyIndex3;\ + StartingBoxLines[lineindex3].pPolygonLineLinks[iThisPolyIndex3] = &StartingPolygon_To_Lines_Links[(polynum * 4) + 2];\ + StartingPolygon_To_Lines_Links[(polynum * 4) + 2].pPrev = &StartingPolygon_To_Lines_Links[(polynum * 4) + 1];\ + StartingPolygon_To_Lines_Links[(polynum * 4) + 2].pNext = &StartingPolygon_To_Lines_Links[(polynum * 4) + 3];\ + StartingPolygon_To_Lines_Links[(polynum * 4) + 3].pLine = &StartingBoxLines[lineindex4];\ + StartingPolygon_To_Lines_Links[(polynum * 4) + 3].iReferenceIndex = iThisPolyIndex4;\ + StartingBoxLines[lineindex4].pPolygonLineLinks[iThisPolyIndex4] = &StartingPolygon_To_Lines_Links[(polynum * 4) + 3];\ + StartingPolygon_To_Lines_Links[(polynum * 4) + 3].pPrev = &StartingPolygon_To_Lines_Links[(polynum * 4) + 2];\ + StartingPolygon_To_Lines_Links[(polynum * 4) + 3].pNext = &StartingPolygon_To_Lines_Links[(polynum * 4) + 0]; + + +CPolyhedron *GeneratePolyhedronFromPlanes( const float *pOutwardFacingPlanes, int iPlaneCount, float fOnPlaneEpsilon, bool bUseTemporaryMemory ) +{ + //this is version 2 of the polyhedron generator, version 1 made individual polygons and joined points together, some guesswork is involved and it therefore isn't a solid method + //this version will start with a cube and hack away at it (retaining point connection information) to produce a polyhedron with no guesswork involved, this method should be rock solid + + //the polygon clipping functions we're going to use want inward facing planes + float *pFlippedPlanes = (float *)stackalloc( (iPlaneCount * 4) * sizeof( float ) ); + for( int i = 0; i != iPlaneCount * 4; ++i ) + { + pFlippedPlanes[i] = -pOutwardFacingPlanes[i]; + } + + //our first goal is to find the size of a cube big enough to encapsulate all points that will be in the final polyhedron + Vector vAABBMins, vAABBMaxs; + if( FindConvexShapeLooseAABB( pFlippedPlanes, iPlaneCount, &vAABBMins, &vAABBMaxs ) == false ) + return NULL; //no shape to work with apparently + + + //grow the bounding box to a larger size since it's probably inaccurate a bit + { + Vector vGrow = (vAABBMaxs - vAABBMins) * 0.5f; + vGrow.x += 100.0f; + vGrow.y += 100.0f; + vGrow.z += 100.0f; + + vAABBMaxs += vGrow; + vAABBMins -= vGrow; + } + + //generate our starting cube using the 2x AABB so we can start hacking away at it + + + + //create our starting box on the stack + GeneratePolyhedronFromPlanes_Point StartingBoxPoints[8]; + GeneratePolyhedronFromPlanes_Line StartingBoxLines[12]; + GeneratePolyhedronFromPlanes_Polygon StartingBoxPolygons[6]; + GeneratePolyhedronFromPlanes_LineLL StartingPoints_To_Lines_Links[24]; //8 points, 3 lines per point + GeneratePolyhedronFromPlanes_LineLL StartingPolygon_To_Lines_Links[24]; //6 polygons, 4 lines per poly + + GeneratePolyhedronFromPlanes_UnorderedPolygonLL StartingPolygonList[6]; //6 polygons + GeneratePolyhedronFromPlanes_UnorderedLineLL StartingLineList[12]; //12 lines + GeneratePolyhedronFromPlanes_UnorderedPointLL StartingPointList[8]; //8 points + + + //I had to work all this out on a whiteboard if it seems completely unintuitive. + { + StartingBoxPoints[0].ptPosition.Init( vAABBMins.x, vAABBMins.y, vAABBMins.z ); + STARTPOINTTOLINELINKS( 0, 0, 1, 4, 1, 3, 0 ); + + StartingBoxPoints[1].ptPosition.Init( vAABBMins.x, vAABBMaxs.y, vAABBMins.z ); + STARTPOINTTOLINELINKS( 1, 0, 0, 1, 1, 5, 1 ); + + StartingBoxPoints[2].ptPosition.Init( vAABBMins.x, vAABBMins.y, vAABBMaxs.z ); + STARTPOINTTOLINELINKS( 2, 4, 0, 8, 1, 11, 0 ); + + StartingBoxPoints[3].ptPosition.Init( vAABBMins.x, vAABBMaxs.y, vAABBMaxs.z ); + STARTPOINTTOLINELINKS( 3, 5, 0, 9, 1, 8, 0 ); + + StartingBoxPoints[4].ptPosition.Init( vAABBMaxs.x, vAABBMins.y, vAABBMins.z ); + STARTPOINTTOLINELINKS( 4, 2, 0, 3, 1, 7, 1 ); + + StartingBoxPoints[5].ptPosition.Init( vAABBMaxs.x, vAABBMaxs.y, vAABBMins.z ); + STARTPOINTTOLINELINKS( 5, 1, 0, 2, 1, 6, 1 ); + + StartingBoxPoints[6].ptPosition.Init( vAABBMaxs.x, vAABBMins.y, vAABBMaxs.z ); + STARTPOINTTOLINELINKS( 6, 7, 0, 11, 1, 10, 0 ); + + StartingBoxPoints[7].ptPosition.Init( vAABBMaxs.x, vAABBMaxs.y, vAABBMaxs.z ); + STARTPOINTTOLINELINKS( 7, 6, 0, 10, 1, 9, 0 ); + + STARTBOXCONNECTION( 0, 0, 1, 0, 5 ); + STARTBOXCONNECTION( 1, 1, 5, 1, 5 ); + STARTBOXCONNECTION( 2, 5, 4, 2, 5 ); + STARTBOXCONNECTION( 3, 4, 0, 3, 5 ); + STARTBOXCONNECTION( 4, 0, 2, 3, 0 ); + STARTBOXCONNECTION( 5, 1, 3, 0, 1 ); + STARTBOXCONNECTION( 6, 5, 7, 1, 2 ); + STARTBOXCONNECTION( 7, 4, 6, 2, 3 ); + STARTBOXCONNECTION( 8, 2, 3, 4, 0 ); + STARTBOXCONNECTION( 9, 3, 7, 4, 1 ); + STARTBOXCONNECTION( 10, 7, 6, 4, 2 ); + STARTBOXCONNECTION( 11, 6, 2, 4, 3 ); + + + STARTBOXCONNECTION( 0, 0, 1, 5, 0 ); + STARTBOXCONNECTION( 1, 1, 5, 5, 1 ); + STARTBOXCONNECTION( 2, 5, 4, 5, 2 ); + STARTBOXCONNECTION( 3, 4, 0, 5, 3 ); + STARTBOXCONNECTION( 4, 0, 2, 0, 3 ); + STARTBOXCONNECTION( 5, 1, 3, 1, 0 ); + STARTBOXCONNECTION( 6, 5, 7, 2, 1 ); + STARTBOXCONNECTION( 7, 4, 6, 3, 2 ); + STARTBOXCONNECTION( 8, 2, 3, 0, 4 ); + STARTBOXCONNECTION( 9, 3, 7, 1, 4 ); + STARTBOXCONNECTION( 10, 7, 6, 2, 4 ); + STARTBOXCONNECTION( 11, 6, 2, 3, 4 ); + + StartingBoxPolygons[0].vSurfaceNormal.Init( -1.0f, 0.0f, 0.0f ); + StartingBoxPolygons[1].vSurfaceNormal.Init( 0.0f, 1.0f, 0.0f ); + StartingBoxPolygons[2].vSurfaceNormal.Init( 1.0f, 0.0f, 0.0f ); + StartingBoxPolygons[3].vSurfaceNormal.Init( 0.0f, -1.0f, 0.0f ); + StartingBoxPolygons[4].vSurfaceNormal.Init( 0.0f, 0.0f, 1.0f ); + StartingBoxPolygons[5].vSurfaceNormal.Init( 0.0f, 0.0f, -1.0f ); + + + STARTPOLYGONTOLINELINKS( 0, 0, 1, 5, 1, 8, 0, 4, 0 ); + STARTPOLYGONTOLINELINKS( 1, 1, 1, 6, 1, 9, 0, 5, 0 ); + STARTPOLYGONTOLINELINKS( 2, 2, 1, 7, 1, 10, 0, 6, 0 ); + STARTPOLYGONTOLINELINKS( 3, 3, 1, 4, 1, 11, 0, 7, 0 ); + STARTPOLYGONTOLINELINKS( 4, 8, 1, 9, 1, 10, 1, 11, 1 ); + STARTPOLYGONTOLINELINKS( 5, 0, 0, 3, 0, 2, 0, 1, 0 ); + + + { + StartingPolygonList[0].pPolygon = &StartingBoxPolygons[0]; + StartingPolygonList[0].pNext = &StartingPolygonList[1]; + StartingPolygonList[0].pPrev = NULL; + + StartingPolygonList[1].pPolygon = &StartingBoxPolygons[1]; + StartingPolygonList[1].pNext = &StartingPolygonList[2]; + StartingPolygonList[1].pPrev = &StartingPolygonList[0]; + + StartingPolygonList[2].pPolygon = &StartingBoxPolygons[2]; + StartingPolygonList[2].pNext = &StartingPolygonList[3]; + StartingPolygonList[2].pPrev = &StartingPolygonList[1]; + + StartingPolygonList[3].pPolygon = &StartingBoxPolygons[3]; + StartingPolygonList[3].pNext = &StartingPolygonList[4]; + StartingPolygonList[3].pPrev = &StartingPolygonList[2]; + + StartingPolygonList[4].pPolygon = &StartingBoxPolygons[4]; + StartingPolygonList[4].pNext = &StartingPolygonList[5]; + StartingPolygonList[4].pPrev = &StartingPolygonList[3]; + + StartingPolygonList[5].pPolygon = &StartingBoxPolygons[5]; + StartingPolygonList[5].pNext = NULL; + StartingPolygonList[5].pPrev = &StartingPolygonList[4]; + } + + + + { + StartingLineList[0].pLine = &StartingBoxLines[0]; + StartingLineList[0].pNext = &StartingLineList[1]; + StartingLineList[0].pPrev = NULL; + + StartingLineList[1].pLine = &StartingBoxLines[1]; + StartingLineList[1].pNext = &StartingLineList[2]; + StartingLineList[1].pPrev = &StartingLineList[0]; + + StartingLineList[2].pLine = &StartingBoxLines[2]; + StartingLineList[2].pNext = &StartingLineList[3]; + StartingLineList[2].pPrev = &StartingLineList[1]; + + StartingLineList[3].pLine = &StartingBoxLines[3]; + StartingLineList[3].pNext = &StartingLineList[4]; + StartingLineList[3].pPrev = &StartingLineList[2]; + + StartingLineList[4].pLine = &StartingBoxLines[4]; + StartingLineList[4].pNext = &StartingLineList[5]; + StartingLineList[4].pPrev = &StartingLineList[3]; + + StartingLineList[5].pLine = &StartingBoxLines[5]; + StartingLineList[5].pNext = &StartingLineList[6]; + StartingLineList[5].pPrev = &StartingLineList[4]; + + StartingLineList[6].pLine = &StartingBoxLines[6]; + StartingLineList[6].pNext = &StartingLineList[7]; + StartingLineList[6].pPrev = &StartingLineList[5]; + + StartingLineList[7].pLine = &StartingBoxLines[7]; + StartingLineList[7].pNext = &StartingLineList[8]; + StartingLineList[7].pPrev = &StartingLineList[6]; + + StartingLineList[8].pLine = &StartingBoxLines[8]; + StartingLineList[8].pNext = &StartingLineList[9]; + StartingLineList[8].pPrev = &StartingLineList[7]; + + StartingLineList[9].pLine = &StartingBoxLines[9]; + StartingLineList[9].pNext = &StartingLineList[10]; + StartingLineList[9].pPrev = &StartingLineList[8]; + + StartingLineList[10].pLine = &StartingBoxLines[10]; + StartingLineList[10].pNext = &StartingLineList[11]; + StartingLineList[10].pPrev = &StartingLineList[9]; + + StartingLineList[11].pLine = &StartingBoxLines[11]; + StartingLineList[11].pNext = NULL; + StartingLineList[11].pPrev = &StartingLineList[10]; + } + + { + StartingPointList[0].pPoint = &StartingBoxPoints[0]; + StartingPointList[0].pNext = &StartingPointList[1]; + StartingPointList[0].pPrev = NULL; + + StartingPointList[1].pPoint = &StartingBoxPoints[1]; + StartingPointList[1].pNext = &StartingPointList[2]; + StartingPointList[1].pPrev = &StartingPointList[0]; + + StartingPointList[2].pPoint = &StartingBoxPoints[2]; + StartingPointList[2].pNext = &StartingPointList[3]; + StartingPointList[2].pPrev = &StartingPointList[1]; + + StartingPointList[3].pPoint = &StartingBoxPoints[3]; + StartingPointList[3].pNext = &StartingPointList[4]; + StartingPointList[3].pPrev = &StartingPointList[2]; + + StartingPointList[4].pPoint = &StartingBoxPoints[4]; + StartingPointList[4].pNext = &StartingPointList[5]; + StartingPointList[4].pPrev = &StartingPointList[3]; + + StartingPointList[5].pPoint = &StartingBoxPoints[5]; + StartingPointList[5].pNext = &StartingPointList[6]; + StartingPointList[5].pPrev = &StartingPointList[4]; + + StartingPointList[6].pPoint = &StartingBoxPoints[6]; + StartingPointList[6].pNext = &StartingPointList[7]; + StartingPointList[6].pPrev = &StartingPointList[5]; + + StartingPointList[7].pPoint = &StartingBoxPoints[7]; + StartingPointList[7].pNext = NULL; + StartingPointList[7].pPrev = &StartingPointList[6]; + } + } + + return ClipLinkedGeometry( StartingPolygonList, StartingLineList, StartingPointList, pOutwardFacingPlanes, iPlaneCount, fOnPlaneEpsilon, bUseTemporaryMemory ); +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#ifdef _DEBUG +void DumpAABBToGLView( const Vector &vCenter, const Vector &vExtents, const Vector &vColor, FILE *pFile ) +{ +#ifdef ENABLE_DEBUG_POLYHEDRON_DUMPS + Vector vMins = vCenter - vExtents; + Vector vMaxs = vCenter + vExtents; + + //x min side + fprintf( pFile, "4\n" ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMins.y, vMins.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMins.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMaxs.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMaxs.y, vMins.z, vColor.x, vColor.y, vColor.z ); + + fprintf( pFile, "4\n" ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMaxs.y, vMins.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMaxs.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMins.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMins.y, vMins.z, vColor.x, vColor.y, vColor.z ); + + //x max side + fprintf( pFile, "4\n" ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMins.y, vMins.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMins.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMaxs.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMaxs.y, vMins.z, vColor.x, vColor.y, vColor.z ); + + fprintf( pFile, "4\n" ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMaxs.y, vMins.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMaxs.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMins.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMins.y, vMins.z, vColor.x, vColor.y, vColor.z ); + + + //y min side + fprintf( pFile, "4\n" ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMins.y, vMins.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMins.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMins.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMins.y, vMins.z, vColor.x, vColor.y, vColor.z ); + + fprintf( pFile, "4\n" ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMins.y, vMins.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMins.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMins.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMins.y, vMins.z, vColor.x, vColor.y, vColor.z ); + + + + //y max side + fprintf( pFile, "4\n" ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMaxs.y, vMins.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMaxs.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMaxs.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMaxs.y, vMins.z, vColor.x, vColor.y, vColor.z ); + + fprintf( pFile, "4\n" ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMaxs.y, vMins.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMaxs.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMaxs.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMaxs.y, vMins.z, vColor.x, vColor.y, vColor.z ); + + + + //z min side + fprintf( pFile, "4\n" ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMins.y, vMins.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMaxs.y, vMins.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMaxs.y, vMins.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMins.y, vMins.z, vColor.x, vColor.y, vColor.z ); + + fprintf( pFile, "4\n" ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMins.y, vMins.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMaxs.y, vMins.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMaxs.y, vMins.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMins.y, vMins.z, vColor.x, vColor.y, vColor.z ); + + + //z max side + fprintf( pFile, "4\n" ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMins.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMaxs.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMaxs.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMins.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); + + fprintf( pFile, "4\n" ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMins.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMaxs.x, vMaxs.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMaxs.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vMins.x, vMins.y, vMaxs.z, vColor.x, vColor.y, vColor.z ); +#endif +} + +void DumpLineToGLView( const Vector &vPoint1, const Vector &vColor1, const Vector &vPoint2, const Vector &vColor2, float fThickness, FILE *pFile ) +{ +#ifdef ENABLE_DEBUG_POLYHEDRON_DUMPS + Vector vDirection = vPoint2 - vPoint1; + vDirection.NormalizeInPlace(); + + Vector vPseudoPerpandicular = vec3_origin; + + if( vDirection.x != 0.0f ) + vPseudoPerpandicular.z = 1.0f; + else + vPseudoPerpandicular.x = 1.0f; + + Vector vWidth = vDirection.Cross( vPseudoPerpandicular ); + vWidth.NormalizeInPlace(); + + Vector vHeight = vDirection.Cross( vWidth ); + vHeight.NormalizeInPlace(); + + fThickness *= 0.5f; //we use half thickness in both directions + vDirection *= fThickness; + vWidth *= fThickness; + vHeight *= fThickness; + + Vector vLinePoints[8]; + vLinePoints[0] = vPoint1 - vDirection - vWidth - vHeight; + vLinePoints[1] = vPoint1 - vDirection - vWidth + vHeight; + vLinePoints[2] = vPoint1 - vDirection + vWidth - vHeight; + vLinePoints[3] = vPoint1 - vDirection + vWidth + vHeight; + + vLinePoints[4] = vPoint2 + vDirection - vWidth - vHeight; + vLinePoints[5] = vPoint2 + vDirection - vWidth + vHeight; + vLinePoints[6] = vPoint2 + vDirection + vWidth - vHeight; + vLinePoints[7] = vPoint2 + vDirection + vWidth + vHeight; + + const Vector *pLineColors[8] = { &vColor1, &vColor1, &vColor1, &vColor1, &vColor2, &vColor2, &vColor2, &vColor2 }; + + +#define DPTGLV_LINE_WRITEPOINT(index) fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vLinePoints[index].x, vLinePoints[index].y, vLinePoints[index].z, pLineColors[index]->x, pLineColors[index]->y, pLineColors[index]->z ); +#define DPTGLV_LINE_DOUBLESIDEDQUAD(index1,index2,index3,index4)\ + fprintf( pFile, "4\n" );\ + DPTGLV_LINE_WRITEPOINT(index1);\ + DPTGLV_LINE_WRITEPOINT(index2);\ + DPTGLV_LINE_WRITEPOINT(index3);\ + DPTGLV_LINE_WRITEPOINT(index4);\ + fprintf( pFile, "4\n" );\ + DPTGLV_LINE_WRITEPOINT(index4);\ + DPTGLV_LINE_WRITEPOINT(index3);\ + DPTGLV_LINE_WRITEPOINT(index2);\ + DPTGLV_LINE_WRITEPOINT(index1); + + + DPTGLV_LINE_DOUBLESIDEDQUAD(0,4,6,2); + DPTGLV_LINE_DOUBLESIDEDQUAD(3,7,5,1); + DPTGLV_LINE_DOUBLESIDEDQUAD(1,5,4,0); + DPTGLV_LINE_DOUBLESIDEDQUAD(2,6,7,3); + DPTGLV_LINE_DOUBLESIDEDQUAD(0,2,3,1); + DPTGLV_LINE_DOUBLESIDEDQUAD(5,7,6,4); +#endif +} + +void DumpPolyhedronToGLView( const CPolyhedron *pPolyhedron, const char *pFilename, const VMatrix *pTransform ) +{ +#ifdef ENABLE_DEBUG_POLYHEDRON_DUMPS + if ( (pPolyhedron == NULL) || (pPolyhedron->iVertexCount == 0) ) + return; + + if( pTransform == NULL ) + pTransform = &s_matIdentity; + + printf("Writing %s...\n", pFilename ); + + FILE *pFile = fopen( pFilename, "ab" ); + + //randomizing an array of colors to help spot shared/unshared vertices + Vector *pColors = (Vector *)stackalloc( sizeof( Vector ) * pPolyhedron->iVertexCount ); + int counter; + for( counter = 0; counter != pPolyhedron->iVertexCount; ++counter ) + { + pColors[counter].Init( rand()/32768.0f, rand()/32768.0f, rand()/32768.0f ); + } + + Vector *pTransformedPoints = (Vector *)stackalloc( pPolyhedron->iVertexCount * sizeof( Vector ) ); + for ( counter = 0; counter != pPolyhedron->iVertexCount; ++counter ) + { + pTransformedPoints[counter] = (*pTransform) * pPolyhedron->pVertices[counter]; + } + + for ( counter = 0; counter != pPolyhedron->iPolygonCount; ++counter ) + { + fprintf( pFile, "%i\n", pPolyhedron->pPolygons[counter].iIndexCount ); + int counter2; + for( counter2 = 0; counter2 != pPolyhedron->pPolygons[counter].iIndexCount; ++counter2 ) + { + Polyhedron_IndexedLineReference_t *pLineReference = &pPolyhedron->pIndices[pPolyhedron->pPolygons[counter].iFirstIndex + counter2]; + + Vector *pVertex = &pTransformedPoints[pPolyhedron->pLines[pLineReference->iLineIndex].iPointIndices[pLineReference->iEndPointIndex]]; + Vector *pColor = &pColors[pPolyhedron->pLines[pLineReference->iLineIndex].iPointIndices[pLineReference->iEndPointIndex]]; + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n",pVertex->x, pVertex->y, pVertex->z, pColor->x, pColor->y, pColor->z ); + } + } + + for( counter = 0; counter != pPolyhedron->iLineCount; ++counter ) + { + const Vector vOne( 1.0f, 1.0f, 1.0f ); + DumpLineToGLView( pTransformedPoints[pPolyhedron->pLines[counter].iPointIndices[0]], vOne - pColors[pPolyhedron->pLines[counter].iPointIndices[0]], + pTransformedPoints[pPolyhedron->pLines[counter].iPointIndices[1]], vOne - pColors[pPolyhedron->pLines[counter].iPointIndices[1]], + 0.1f, pFile ); + } + + for( counter = 0; counter != pPolyhedron->iVertexCount; ++counter ) + { + const Vector vPointHalfSize(0.15f, 0.15f, 0.15f ); + DumpAABBToGLView( pTransformedPoints[counter], vPointHalfSize, pColors[counter], pFile ); + } + + fclose( pFile ); +#endif +} + + +void DumpPlaneToGlView( const float *pPlane, float fGrayScale, const char *pszFileName, const VMatrix *pTransform ) +{ +#ifdef ENABLE_DEBUG_POLYHEDRON_DUMPS + if( pTransform == NULL ) + pTransform = &s_matIdentity; + + FILE *pFile = fopen( pszFileName, "ab" ); + + //transform the plane + Vector vNormal = pTransform->ApplyRotation( *(Vector *)pPlane ); + float fDist = pPlane[3] * vNormal.NormalizeInPlace(); //possible scaling going on + fDist += vNormal.Dot( pTransform->GetTranslation() ); + + Vector vPlaneVerts[4]; + + PolyFromPlane( vPlaneVerts, vNormal, fDist, 100000.0f ); + + fprintf( pFile, "4\n" ); + + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vPlaneVerts[0].x, vPlaneVerts[0].y, vPlaneVerts[0].z, fGrayScale, fGrayScale, fGrayScale ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vPlaneVerts[1].x, vPlaneVerts[1].y, vPlaneVerts[1].z, fGrayScale, fGrayScale, fGrayScale ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vPlaneVerts[2].x, vPlaneVerts[2].y, vPlaneVerts[2].z, fGrayScale, fGrayScale, fGrayScale ); + fprintf( pFile, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", vPlaneVerts[3].x, vPlaneVerts[3].y, vPlaneVerts[3].z, fGrayScale, fGrayScale, fGrayScale ); + + fclose( pFile ); +#endif +} +#endif + + diff --git a/mathlib/powsse.cpp b/mathlib/powsse.cpp new file mode 100644 index 00000000..ebd91fec --- /dev/null +++ b/mathlib/powsse.cpp @@ -0,0 +1,39 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=====================================================================================// + +#include "mathlib/ssemath.h" + +fltx4 Pow_FixedPoint_Exponent_SIMD( const fltx4 & x, int exponent) +{ + fltx4 rslt=Four_Ones; // x^0=1.0 + int xp=abs(exponent); + if (xp & 3) // fraction present? + { + fltx4 sq_rt=SqrtEstSIMD(x); + if (xp & 1) // .25? + rslt=SqrtEstSIMD(sq_rt); // x^.25 + if (xp & 2) + rslt=MulSIMD(rslt,sq_rt); + } + xp>>=2; // strip fraction + fltx4 curpower=x; // curpower iterates through x,x^2,x^4,x^8,x^16... + + while(1) + { + if (xp & 1) + rslt=MulSIMD(rslt,curpower); + xp>>=1; + if (xp) + curpower=MulSIMD(curpower,curpower); + else + break; + } + if (exponent<0) + return ReciprocalEstSIMD(rslt); // pow(x,-b)=1/pow(x,b) + else + return rslt; +} + diff --git a/mathlib/quantize.cpp b/mathlib/quantize.cpp new file mode 100644 index 00000000..dbaf15f2 --- /dev/null +++ b/mathlib/quantize.cpp @@ -0,0 +1,678 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#ifndef STDIO_H +#include +#endif + +#ifndef STRING_H +#include +#endif + +#ifndef QUANTIZE_H +#include +#endif + +#include + +#include + +static int current_ndims; +static struct QuantizedValue *current_root; +static int current_ssize; + +static uint8 *current_weights; + +double SquaredError; + +#define SPLIT_THEN_SORT 1 + +#define SQ(x) ((x)*(x)) + +static struct QuantizedValue *AllocQValue(void) +{ + struct QuantizedValue *ret=new QuantizedValue; + ret->Samples=0; + ret->Children[0]=ret->Children[1]=0; + ret->NSamples=0; + + ret->ErrorMeasure=new double[current_ndims]; + ret->Mean=new uint8[current_ndims]; + ret->Mins=new uint8[current_ndims]; + ret->Maxs=new uint8[current_ndims]; + ret->Sums=new int [current_ndims]; + memset(ret->Sums,0,sizeof(int)*current_ndims); + ret->NQuant=0; + ret->sortdim=-1; + return ret; +} + +void FreeQuantization(struct QuantizedValue *t) +{ + if (t) + { + delete[] t->ErrorMeasure; + delete[] t->Mean; + delete[] t->Mins; + delete[] t->Maxs; + FreeQuantization(t->Children[0]); + FreeQuantization(t->Children[1]); + delete[] t->Sums; + delete[] t; + } +} + +static int QNumSort(void const *a, void const *b) +{ + int32 as=((struct Sample *) a)->QNum; + int32 bs=((struct Sample *) b)->QNum; + if (as==bs) return 0; + return (as>bs)?1:-1; +} + +#if SPLIT_THEN_SORT +#else +static int current_sort_dim; + +static int samplesort(void const *a, void const *b) +{ + uint8 as=((struct Sample *) a)->Value[current_sort_dim]; + uint8 bs=((struct Sample *) b)->Value[current_sort_dim]; + if (as==bs) return 0; + return (as>bs)?1:-1; +} +#endif + +static int sortlong(void const *a, void const *b) +{ + // treat the entire vector of values as a long integer for duplicate removal. + return memcmp(((struct Sample *) a)->Value, + ((struct Sample *) b)->Value,current_ndims); +} + + + +#define NEXTSAMPLE(s) ( (struct Sample *) (((uint8 *) s)+current_ssize)) +#define SAMPLE(s,i) NthSample(s,i,current_ndims) + +static void SetNDims(int n) +{ + current_ssize=sizeof(struct Sample)+(n-1); + current_ndims=n; +} + +int CompressSamples(struct Sample *s, int nsamples, int ndims) +{ + SetNDims(ndims); + qsort(s,nsamples,current_ssize,sortlong); + // now, they are all sorted by treating all dimensions as a large number. + // we may now remove duplicates. + struct Sample *src=s; + struct Sample *dst=s; + struct Sample *lastdst=dst; + dst=NEXTSAMPLE(dst); // copy first sample to get the ball rolling + src=NEXTSAMPLE(src); + int noutput=1; + while(--nsamples) // while some remain + { + if (memcmp(src->Value,lastdst->Value,current_ndims)) + { + // yikes, a difference has been found! + memcpy(dst,src,current_ssize); + lastdst=dst; + dst=NEXTSAMPLE(dst); + noutput++; + } + else + lastdst->Count++; + src=NEXTSAMPLE(src); + } + return noutput; +} + +void PrintSamples(struct Sample const *s, int nsamples, int ndims) +{ + SetNDims(ndims); + int cnt=0; + while(nsamples--) + { + printf("sample #%d, count=%d, values=\n { ",cnt++,s->Count); + for(int d=0;dValue[d]); + printf("}\n"); + s=NEXTSAMPLE(s); + } +} + +void PrintQTree(struct QuantizedValue const *p,int idlevel) +{ + int i; + + if (p) + { + for(i=0;iNSamples,p->value); + for(i=0;iMean[i]); + printf("}\n"); + for(i=0;iErrorMeasure[i]); + printf("}\n"); + for(i=0;iMins[i]); + printf("} Maxs={"); + for(i=0;iMaxs[i]); + printf("}\n"); + PrintQTree(p->Children[0],idlevel+2); + PrintQTree(p->Children[1],idlevel+2); + } +} + +static void UpdateStats(struct QuantizedValue *v) +{ + // first, find mean + int32 Means[MAXDIMS]; + double Errors[MAXDIMS]; + double WorstError[MAXDIMS]; + int i,j; + + memset(Means,0,sizeof(Means)); + int N=0; + for(i=0;iNSamples;i++) + { + struct Sample *s=SAMPLE(v->Samples,i); + N+=s->Count; + for(j=0;jValue[j]; + Means[j]+=v*s->Count; + } + } + for(j=0;jMean[j]=(uint8) (Means[j]/N); + Errors[j]=WorstError[j]=0.; + } + for(i=0;iNSamples;i++) + { + struct Sample *s=SAMPLE(v->Samples,i); + double c=s->Count; + for(j=0;jValue[j]-v->Mean[j]); + Errors[j]+=c*diff; // charles uses abs not sq() + if (diff>WorstError[j]) + WorstError[j]=diff; + } + } + v->TotalError=0.; + double ErrorScale=1.; // /sqrt((double) (N)); + for(j=0;jErrorMeasure[j]=(ErrorScale*Errors[j]*current_weights[j]); + v->TotalError+=v->ErrorMeasure[j]; +#if SPLIT_THEN_SORT + v->ErrorMeasure[j]*=WorstError[j]; +#endif + } + v->TotSamples=N; +} + +static int ErrorDim; +static double ErrorVal; +static struct QuantizedValue *ErrorNode; + +static void UpdateWorst(struct QuantizedValue *q) +{ + if (q->Children[0]) + { + // not a leaf node + UpdateWorst(q->Children[0]); + UpdateWorst(q->Children[1]); + } + else + { + if (q->TotalError>ErrorVal) + { + ErrorVal=q->TotalError; + ErrorNode=q; + ErrorDim=0; + for(int d=0;dErrorMeasure[d]>q->ErrorMeasure[ErrorDim]) + ErrorDim=d; + } + } +} + +static int FindWorst(void) +{ + ErrorVal=-1.; + UpdateWorst(current_root); + return (ErrorVal>0); +} + + + +static void SubdivideNode(struct QuantizedValue *n, int whichdim) +{ + int NAdded=0; + int i; + +#if SPLIT_THEN_SORT + // we will try the "split then sort" method. This works by finding the + // means for all samples above and below the mean along the given axis. + // samples are then split into two groups, with the selection based upon + // which of the n-dimensional means the sample is closest to. + double LocalMean[MAXDIMS][2]; + int totsamps[2]; + for(i=0;iNSamples;i++) + { + uint8 v; + int whichside=1; + struct Sample *sl; + sl=SAMPLE(n->Samples,i); + v=sl->Value[whichdim]; + if (vmaxv) { maxv=v; maxS=sl; } + if (vMean[whichdim]) + whichside=0; + totsamps[whichside]+=sl->Count; + for(int d=0;dCount*sl->Value[d]; + } + + if (totsamps[0] && totsamps[1]) + for(i=0;iValue[i]; + LocalMean[i][1]=maxS->Value[i]; + } + } + + // now, we have 2 n-dimensional means. We will label each sample + // for which one it is nearer to by using the QNum field. + for(i=0;iNSamples;i++) + { + double dist[2]; + dist[0]=dist[1]=0.; + struct Sample *s=SAMPLE(n->Samples,i); + for(int d=0;dValue[d]); + s->QNum=(dist[0]sortdim=-1; + qsort(n->Samples,n->NSamples,current_ssize,QNumSort); + for(i=0;iNSamples;i++,NAdded++) + if (SAMPLE(n->Samples,i)->QNum) + break; + +#else + if (whichdim != n->sortdim) + { + current_sort_dim=whichdim; + qsort(n->Samples,n->NSamples,current_ssize,samplesort); + n->sortdim=whichdim; + } + // now, the samples are sorted along the proper dimension. we need + // to find the place to cut in order to split the node. this is + // complicated by the fact that each sample entry can represent many + // samples. What we will do is start at the beginning of the array, + // adding samples to the first node, until either the number added + // is >=TotSamples/2, or there is only one left. + int TotAdded=0; + for(;;) + { + if (NAdded==n->NSamples-1) + break; + if (TotAdded>=n->TotSamples/2) + break; + TotAdded+=SAMPLE(n->Samples,NAdded)->Count; + NAdded++; + } +#endif + struct QuantizedValue *a=AllocQValue(); + a->sortdim=n->sortdim; + a->Samples=n->Samples; + a->NSamples=NAdded; + n->Children[0]=a; + UpdateStats(a); + a=AllocQValue(); + a->Samples=SAMPLE(n->Samples,NAdded); + a->NSamples=n->NSamples-NAdded; + a->sortdim=n->sortdim; + n->Children[1]=a; + UpdateStats(a); +} + +static int colorid=0; + +static void Label(struct QuantizedValue *q, int updatecolor) +{ + // fill in max/min values for tree, etc. + if (q) + { + Label(q->Children[0],updatecolor); + Label(q->Children[1],updatecolor); + if (! q->Children[0]) // leaf node? + { + if (updatecolor) + { + q->value=colorid++; + for(int j=0;jNSamples;j++) + { + SAMPLE(q->Samples,j)->QNum=q->value; + SAMPLE(q->Samples,j)->qptr=q; + } + } + for(int i=0;iMins[i]=q->Mean[i]; + q->Maxs[i]=q->Mean[i]; + } + } + else + for(int i=0;iMins[i]=MIN(q->Children[0]->Mins[i],q->Children[1]->Mins[i]); + q->Maxs[i]=MAX(q->Children[0]->Maxs[i],q->Children[1]->Maxs[i]); + } + } +} + +struct QuantizedValue *FindQNode(struct QuantizedValue const *q, int32 code) +{ + if (! (q->Children[0])) + if (code==q->value) return (struct QuantizedValue *) q; + else return 0; + else + { + struct QuantizedValue *found=FindQNode(q->Children[0],code); + if (! found) found=FindQNode(q->Children[1],code); + return found; + } +} + + +void CheckInRange(struct QuantizedValue *q, uint8 *max, uint8 *min) +{ + if (q) + { + if (q->Children[0]) + { + // non-leaf node + CheckInRange(q->Children[0],q->Maxs, q->Mins); + CheckInRange(q->Children[1],q->Maxs, q->Mins); + CheckInRange(q->Children[0],max, min); + CheckInRange(q->Children[1],max, min); + } + for (int i=0;iMaxs[i]>max[i]) printf("error1\n"); + if (q->Mins[i]Samples=s; + current_root->NSamples=nsamples; + UpdateStats(current_root); + while(--nvalues) + { + if (! FindWorst()) + break; // if Mins[i]<=val2) && (q->Maxs[i]>=val2)) val1=val2; + else + { + val1=(val2<=q->Mins[i])?q->Mins[i]:q->Maxs[i]; + } + err+=weights[i]*SQ(val1-val2); + } + return err; +} + +double MaximumError(struct QuantizedValue const *q, uint8 const *sample, + int ndims, uint8 const *weights) +{ + double err=0; + for(int i=0;iMins[i])>abs(val2-q->Maxs[i]))? + q->Mins[i]: + q->Maxs[i]; + err+=weights[i]*SQ(val2-val1); + } + return err; +} + + + +// heap (priority queue) routines used for nearest-neghbor searches +struct FHeap { + int heap_n; + double *heap[MAXQUANT]; +}; + +void InitHeap(struct FHeap *h) +{ + h->heap_n=0; +} + + +void UpHeap(int k, struct FHeap *h) +{ + double *tmpk=h->heap[k]; + double tmpkn=*tmpk; + while((k>1) && (tmpkn <= *(h->heap[k/2]))) + { + h->heap[k]=h->heap[k/2]; + k/=2; + } + h->heap[k]=tmpk; +} + +void HeapInsert(struct FHeap *h,double *elem) +{ + h->heap_n++; + h->heap[h->heap_n]=elem; + UpHeap(h->heap_n,h); +} + +void DownHeap(int k, struct FHeap *h) +{ + double *v=h->heap[k]; + while(k<=h->heap_n/2) + { + int j=2*k; + if (jheap_n) + if (*(h->heap[j]) >= *(h->heap[j+1])) + j++; + if (*v < *(h->heap[j])) + { + h->heap[k]=v; + return; + } + h->heap[k]=h->heap[j]; k=j; + } + h->heap[k]=v; +} + +void *RemoveHeapItem(struct FHeap *h) +{ + void *ret=0; + if (h->heap_n!=0) + { + ret=h->heap[1]; + h->heap[1]=h->heap[h->heap_n]; + h->heap_n--; + DownHeap(1,h); + } + return ret; +} + +// now, nearest neighbor finder. Use a heap to traverse the tree, stopping +// when there are no nodes with a minimum error < the current error. + +struct FHeap TheQueue; + +#define PUSHNODE(a) { \ + (a)->MinError=MinimumError(a,sample,ndims,weights); \ + if ((a)->MinError < besterror) HeapInsert(&TheQueue,&(a)->MinError); \ + } + +struct QuantizedValue *FindMatch(uint8 const *sample, int ndims, + uint8 *weights, struct QuantizedValue *q) +{ + InitHeap(&TheQueue); + struct QuantizedValue *bestmatch=0; + double besterror=1.0e63; + PUSHNODE(q); + for(;;) + { + struct QuantizedValue *test=(struct QuantizedValue *) + RemoveHeapItem(&TheQueue); + if (! test) break; // heap empty +// printf("got pop node =%p minerror=%f\n",test,test->MinError); + + if (test->MinError>besterror) break; + if (test->Children[0]) + { + // it's a parent node. put the children on the queue + struct QuantizedValue *c1=test->Children[0]; + struct QuantizedValue *c2=test->Children[1]; + c1->MinError=MinimumError(c1,sample,ndims,weights); + if (c1->MinError < besterror) + HeapInsert(&TheQueue,&(c1->MinError)); + c2->MinError=MinimumError(c2,sample,ndims,weights); + if (c2->MinError < besterror) + HeapInsert(&TheQueue,&(c2->MinError)); + } + else + { + // it's a leaf node. This must be a new minimum or the MinError + // test would have failed. + if (test->MinError < besterror) + { + bestmatch=test; + besterror=test->MinError; + } + } + } + if (bestmatch) + { + SquaredError+=besterror; + bestmatch->NQuant++; + for(int i=0;iSums[i]+=sample[i]; + } + return bestmatch; +} + +static void RecalcMeans(struct QuantizedValue *q) +{ + if (q) + { + if (q->Children[0]) + { + // not a leaf, invoke recursively. + RecalcMeans(q->Children[0]); + RecalcMeans(q->Children[0]); + } + else + { + // it's a leaf. Set the means + if (q->NQuant) + { + for(int i=0;iMean[i]=(uint8) (q->Sums[i]/q->NQuant); + q->Sums[i]=0; + } + q->NQuant=0; + } + } + } +} + +void OptimizeQuantizer(struct QuantizedValue *q, int ndims) +{ + SetNDims(ndims); + RecalcMeans(q); // reset q values + Label(q,0); // update max/mins +} + + +static void RecalcStats(struct QuantizedValue *q) +{ + if (q) + { + UpdateStats(q); + RecalcStats(q->Children[0]); + RecalcStats(q->Children[1]); + } +} + +void RecalculateValues(struct QuantizedValue *q, int ndims) +{ + SetNDims(ndims); + RecalcStats(q); + Label(q,0); +} diff --git a/mathlib/randsse.cpp b/mathlib/randsse.cpp new file mode 100644 index 00000000..52adccfc --- /dev/null +++ b/mathlib/randsse.cpp @@ -0,0 +1,109 @@ +//========= Copyright © 1996-2006, Valve Corporation, All rights reserved. ============// +// +// Purpose: generates 4 randum numbers in the range 0..1 quickly, using SIMD +// +//=====================================================================================// + +#include +#include // Needed for FLT_EPSILON +#include "basetypes.h" +#include +#include "tier0/dbg.h" +#include "mathlib/mathlib.h" +#include "mathlib/vector.h" +#include "mathlib/ssemath.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// see knuth volume 3 for insight. + +class SIMDRandStreamContext +{ + fltx4 m_RandY[55]; + + fltx4 *m_pRand_J, *m_pRand_K; + + +public: + void Seed( uint32 seed ) + { + m_pRand_J=m_RandY+23; m_pRand_K=m_RandY+54; + for(int i=0;i<55;i++) + { + for(int j=0;j<4;j++) + { + SubFloat( m_RandY[i], j) = (seed>>16)/65536.0; + seed=(seed+1)*3141592621u; + } + } + } + + inline fltx4 RandSIMD( void ) + { + // ret= rand[k]+rand[j] + fltx4 retval=AddSIMD( *m_pRand_K, *m_pRand_J ); + + // if ( ret>=1.0) ret-=1.0 + fltx4 overflow_mask=CmpGeSIMD( retval, Four_Ones ); + retval=SubSIMD( retval, AndSIMD( Four_Ones, overflow_mask ) ); + + *m_pRand_K = retval; + + // update pointers w/ wrap-around + if ( --m_pRand_J < m_RandY ) + m_pRand_J=m_RandY+54; + if ( --m_pRand_K < m_RandY ) + m_pRand_K=m_RandY+54; + + return retval; + } +}; + +#define MAX_SIMULTANEOUS_RANDOM_STREAMS 32 + +static SIMDRandStreamContext s_SIMDRandContexts[MAX_SIMULTANEOUS_RANDOM_STREAMS]; + +static volatile int s_nRandContextsInUse[MAX_SIMULTANEOUS_RANDOM_STREAMS]; + +void SeedRandSIMD(uint32 seed) +{ + for( int i = 0; i> 2); + int ntrailing_pixels_per_source_line=(srcwidth & 3); + for(int y=0;y( p_write_ptr ); + // copy full input blocks + for(int x=0;xx=Pow_FixedPoint_Exponent_SIMD( src->x, fixed_point_exp ); + src->y=Pow_FixedPoint_Exponent_SIMD( src->y, fixed_point_exp ); + src->z=Pow_FixedPoint_Exponent_SIMD( src->z, fixed_point_exp ); + src++; + } while (--nv); + } +} + +CSIMDVectorMatrix & CSIMDVectorMatrix::operator+=( CSIMDVectorMatrix const &src ) +{ + Assert( m_nWidth == src.m_nWidth ); + Assert( m_nHeight == src.m_nHeight ); + int nv=NVectors(); + if ( nv ) + { + FourVectors *srcv=src.m_pData; + FourVectors *destv=m_pData; + do // !! speed !! inline more iters + { + *( destv++ ) += *( srcv++ ); + } while ( --nv ); + } + return *this; +} + +CSIMDVectorMatrix & CSIMDVectorMatrix::operator*=( Vector const &src ) +{ + int nv=NVectors(); + if ( nv ) + { + FourVectors scalevalue; + scalevalue.DuplicateVector( src ); + FourVectors *destv=m_pData; + do // !! speed !! inline more iters + { + destv->VProduct( scalevalue ); + destv++; + } while ( --nv ); + } + return *this; +} + diff --git a/mathlib/sparse_convolution_noise.cpp b/mathlib/sparse_convolution_noise.cpp new file mode 100644 index 00000000..e358135e --- /dev/null +++ b/mathlib/sparse_convolution_noise.cpp @@ -0,0 +1,218 @@ +//========= Copyright © 1996-2006, Valve Corporation, All rights reserved. ============// +// +// Purpose: noise() primitives. +// +//=====================================================================================// + +#include +#include "basetypes.h" +#include +#include "tier0/dbg.h" +#include "mathlib/mathlib.h" +#include "mathlib/vector.h" +#include "mathlib/noise.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// generate high quality noise based upon "sparse convolution". HIgher quality than perlin noise, +// and no direcitonal artifacts. + +#include "noisedata.h" + +#define N_IMPULSES_PER_CELL 5 +#define NORMALIZING_FACTOR 1.0 + +//(0.5/N_IMPULSES_PER_CELL) + +static inline int LatticeCoord(float x) +{ + return ((int) floor(x)) & 0xff; +} + +static inline int Hash4D(int ix, int iy, int iz, int idx) +{ + int ret=perm_a[ix]; + ret=perm_b[(ret+iy) & 0xff]; + ret=perm_c[(ret+iz) & 0xff]; + ret=perm_d[(ret+idx) & 0xff]; + return ret; +} + +#define SQ(x) ((x)*(x)) + +static float CellNoise( int ix, int iy, int iz, float xfrac, float yfrac, float zfrac, + float (*pNoiseShapeFunction)(float) ) +{ + float ret=0; + for(int idx=0;idx +#include // Needed for FLT_EPSILON +#include "basetypes.h" +#include +#include "tier0/dbg.h" +#include "mathlib/mathlib.h" +#include "mathlib/vector.h" +#include "sse.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static const uint32 _sincos_masks[] = { (uint32)0x0, (uint32)~0x0 }; +static const uint32 _sincos_inv_masks[] = { (uint32)~0x0, (uint32)0x0 }; + +//----------------------------------------------------------------------------- +// Macros and constants required by some of the SSE assembly: +//----------------------------------------------------------------------------- + +#ifdef _WIN32 + #define _PS_EXTERN_CONST(Name, Val) \ + const __declspec(align(16)) float _ps_##Name[4] = { Val, Val, Val, Val } + + #define _PS_EXTERN_CONST_TYPE(Name, Type, Val) \ + const __declspec(align(16)) Type _ps_##Name[4] = { Val, Val, Val, Val }; \ + + #define _EPI32_CONST(Name, Val) \ + static const __declspec(align(16)) __int32 _epi32_##Name[4] = { Val, Val, Val, Val } + + #define _PS_CONST(Name, Val) \ + static const __declspec(align(16)) float _ps_##Name[4] = { Val, Val, Val, Val } +#elif defined _LINUX || defined __APPLE__ + #define _PS_EXTERN_CONST(Name, Val) \ + const __attribute__((aligned(16))) float _ps_##Name[4] = { Val, Val, Val, Val } + + #define _PS_EXTERN_CONST_TYPE(Name, Type, Val) \ + const __attribute__((aligned(16))) Type _ps_##Name[4] = { Val, Val, Val, Val }; \ + + #define _EPI32_CONST(Name, Val) \ + static const __attribute__((aligned(16))) int32 _epi32_##Name[4] = { Val, Val, Val, Val } + + #define _PS_CONST(Name, Val) \ + static const __attribute__((aligned(16))) float _ps_##Name[4] = { Val, Val, Val, Val } +#endif + +_PS_EXTERN_CONST(am_0, 0.0f); +_PS_EXTERN_CONST(am_1, 1.0f); +_PS_EXTERN_CONST(am_m1, -1.0f); +_PS_EXTERN_CONST(am_0p5, 0.5f); +_PS_EXTERN_CONST(am_1p5, 1.5f); +_PS_EXTERN_CONST(am_pi, (float)M_PI); +_PS_EXTERN_CONST(am_pi_o_2, (float)(M_PI / 2.0)); +_PS_EXTERN_CONST(am_2_o_pi, (float)(2.0 / M_PI)); +_PS_EXTERN_CONST(am_pi_o_4, (float)(M_PI / 4.0)); +_PS_EXTERN_CONST(am_4_o_pi, (float)(4.0 / M_PI)); +_PS_EXTERN_CONST_TYPE(am_sign_mask, int32, 0x80000000); +_PS_EXTERN_CONST_TYPE(am_inv_sign_mask, int32, ~0x80000000); +_PS_EXTERN_CONST_TYPE(am_min_norm_pos,int32, 0x00800000); +_PS_EXTERN_CONST_TYPE(am_mant_mask, int32, 0x7f800000); +_PS_EXTERN_CONST_TYPE(am_inv_mant_mask, int32, ~0x7f800000); + +_EPI32_CONST(1, 1); +_EPI32_CONST(2, 2); + +_PS_CONST(sincos_p0, 0.15707963267948963959e1f); +_PS_CONST(sincos_p1, -0.64596409750621907082e0f); +_PS_CONST(sincos_p2, 0.7969262624561800806e-1f); +_PS_CONST(sincos_p3, -0.468175413106023168e-2f); + +#ifdef PFN_VECTORMA +void __cdecl _SSE_VectorMA( const float *start, float scale, const float *direction, float *dest ); +#endif + +//----------------------------------------------------------------------------- +// SSE implementations of optimized routines: +//----------------------------------------------------------------------------- +float _SSE_Sqrt(float x) +{ + Assert( s_bMathlibInitialized ); + float root = 0.f; +#ifdef _WIN32 + _asm + { + sqrtss xmm0, x + movss root, xmm0 + } +#elif defined _LINUX || defined __APPLE__ + __asm__ __volatile__( + "movss %1,%%xmm2\n" + "sqrtss %%xmm2,%%xmm1\n" + "movss %%xmm1,%0" + : "=m" (root) + : "m" (x) + ); +#endif + return root; +} + +// Single iteration NewtonRaphson reciprocal square root: +// 0.5 * rsqrtps * (3 - x * rsqrtps(x) * rsqrtps(x)) +// Very low error, and fine to use in place of 1.f / sqrtf(x). +#if 0 +float _SSE_RSqrtAccurate(float x) +{ + Assert( s_bMathlibInitialized ); + + float rroot; + _asm + { + rsqrtss xmm0, x + movss rroot, xmm0 + } + + return (0.5f * rroot) * (3.f - (x * rroot) * rroot); +} +#else +// Intel / Kipps SSE RSqrt. Significantly faster than above. +float _SSE_RSqrtAccurate(float a) +{ + float x; + float half = 0.5f; + float three = 3.f; + +#ifdef _WIN32 + __asm + { + movss xmm3, a; + movss xmm1, half; + movss xmm2, three; + rsqrtss xmm0, xmm3; + + mulss xmm3, xmm0; + mulss xmm1, xmm0; + mulss xmm3, xmm0; + subss xmm2, xmm3; + mulss xmm1, xmm2; + + movss x, xmm1; + } +#elif defined _LINUX || defined __APPLE__ + __asm__ __volatile__( + "movss %1, %%xmm3 \n\t" + "movss %2, %%xmm1 \n\t" + "movss %3, %%xmm2 \n\t" + "rsqrtss %%xmm3, %%xmm0 \n\t" + "mulss %%xmm0, %%xmm3 \n\t" + "mulss %%xmm0, %%xmm1 \n\t" + "mulss %%xmm0, %%xmm3 \n\t" + "subss %%xmm3, %%xmm2 \n\t" + "mulss %%xmm2, %%xmm1 \n\t" + "movss %%xmm1, %0 \n\t" + : "=m" (x) + : "m" (a), "m" (half), "m" (three) +); +#else + #error "Not Implemented" +#endif + + return x; +} +#endif + +// Simple SSE rsqrt. Usually accurate to around 6 (relative) decimal places +// or so, so ok for closed transforms. (ie, computing lighting normals) +float _SSE_RSqrtFast(float x) +{ + Assert( s_bMathlibInitialized ); + + float rroot; +#ifdef _WIN32 + _asm + { + rsqrtss xmm0, x + movss rroot, xmm0 + } +#elif defined _LINUX || defined __APPLE__ + __asm__ __volatile__( + "rsqrtss %1, %%xmm0 \n\t" + "movss %%xmm0, %0 \n\t" + : "=m" (x) + : "m" (rroot) + : "%xmm0" + ); +#else +#error +#endif + + return rroot; +} + +float FASTCALL _SSE_VectorNormalize (Vector& vec) +{ + Assert( s_bMathlibInitialized ); + + // NOTE: This is necessary to prevent an memory overwrite... + // sice vec only has 3 floats, we can't "movaps" directly into it. +#ifdef _WIN32 + __declspec(align(16)) float result[4]; +#elif defined _LINUX || defined __APPLE__ + __attribute__((aligned(16))) float result[4]; +#endif + + float *v = &vec[0]; + + float radius = 0.f; + // Blah, get rid of these comparisons ... in reality, if you have all 3 as zero, it shouldn't + // be much of a performance win, considering you will very likely miss 3 branch predicts in a row. + if ( v[0] || v[1] || v[2] ) + { +#ifdef _WIN32 + float *r = &result[0]; + _asm + { + mov eax, v + mov edx, r +#ifdef ALIGNED_VECTOR + movaps xmm4, [eax] // r4 = vx, vy, vz, X + movaps xmm1, xmm4 // r1 = r4 +#else + movups xmm4, [eax] // r4 = vx, vy, vz, X + movaps xmm1, xmm4 // r1 = r4 +#endif + mulps xmm1, xmm4 // r1 = vx * vx, vy * vy, vz * vz, X + movhlps xmm3, xmm1 // r3 = vz * vz, X, X, X + movaps xmm2, xmm1 // r2 = r1 + shufps xmm2, xmm2, 1 // r2 = vy * vy, X, X, X + addss xmm1, xmm2 // r1 = (vx * vx) + (vy * vy), X, X, X + addss xmm1, xmm3 // r1 = (vx * vx) + (vy * vy) + (vz * vz), X, X, X + sqrtss xmm1, xmm1 // r1 = sqrt((vx * vx) + (vy * vy) + (vz * vz)), X, X, X + movss radius, xmm1 // radius = sqrt((vx * vx) + (vy * vy) + (vz * vz)) + rcpss xmm1, xmm1 // r1 = 1/radius, X, X, X + shufps xmm1, xmm1, 0 // r1 = 1/radius, 1/radius, 1/radius, X + mulps xmm4, xmm1 // r4 = vx * 1/radius, vy * 1/radius, vz * 1/radius, X + movaps [edx], xmm4 // v = vx * 1/radius, vy * 1/radius, vz * 1/radius, X + } +#elif defined _LINUX || defined __APPLE__ + __asm__ __volatile__( +#ifdef ALIGNED_VECTOR + "movaps %2, %%xmm4 \n\t" + "movaps %%xmm4, %%xmm1 \n\t" +#else + "movups %2, %%xmm4 \n\t" + "movaps %%xmm4, %%xmm1 \n\t" +#endif + "mulps %%xmm4, %%xmm1 \n\t" + "movhlps %%xmm1, %%xmm3 \n\t" + "movaps %%xmm1, %%xmm2 \n\t" + "shufps $1, %%xmm2, %%xmm2 \n\t" + "addss %%xmm2, %%xmm1 \n\t" + "addss %%xmm3, %%xmm1 \n\t" + "sqrtss %%xmm1, %%xmm1 \n\t" + "movss %%xmm1, %0 \n\t" + "rcpss %%xmm1, %%xmm1 \n\t" + "shufps $0, %%xmm1, %%xmm1 \n\t" + "mulps %%xmm1, %%xmm4 \n\t" + "movaps %%xmm4, %1 \n\t" + : "=m" (radius), "=m" (result) + : "m" (*v) + ); +#else + #error "Not Implemented" +#endif + vec.x = result[0]; + vec.y = result[1]; + vec.z = result[2]; + + } + + return radius; +} + +void FASTCALL _SSE_VectorNormalizeFast (Vector& vec) +{ + float ool = _SSE_RSqrtAccurate( FLT_EPSILON + vec.x * vec.x + vec.y * vec.y + vec.z * vec.z ); + + vec.x *= ool; + vec.y *= ool; + vec.z *= ool; +} + +float _SSE_InvRSquared(const float* v) +{ + float inv_r2 = 1.f; +#ifdef _WIN32 + _asm { // Intel SSE only routine + mov eax, v + movss xmm5, inv_r2 // x5 = 1.0, 0, 0, 0 +#ifdef ALIGNED_VECTOR + movaps xmm4, [eax] // x4 = vx, vy, vz, X +#else + movups xmm4, [eax] // x4 = vx, vy, vz, X +#endif + movaps xmm1, xmm4 // x1 = x4 + mulps xmm1, xmm4 // x1 = vx * vx, vy * vy, vz * vz, X + movhlps xmm3, xmm1 // x3 = vz * vz, X, X, X + movaps xmm2, xmm1 // x2 = x1 + shufps xmm2, xmm2, 1 // x2 = vy * vy, X, X, X + addss xmm1, xmm2 // x1 = (vx * vx) + (vy * vy), X, X, X + addss xmm1, xmm3 // x1 = (vx * vx) + (vy * vy) + (vz * vz), X, X, X + maxss xmm1, xmm5 // x1 = MAX( 1.0, x1 ) + rcpss xmm0, xmm1 // x0 = 1 / MAX( 1.0, x1 ) + movss inv_r2, xmm0 // inv_r2 = x0 + } +#elif defined _LINUX || defined __APPLE__ + __asm__ __volatile__( +#ifdef ALIGNED_VECTOR + "movaps %1, %%xmm4 \n\t" +#else + "movups %1, %%xmm4 \n\t" +#endif + "movaps %%xmm4, %%xmm1 \n\t" + "mulps %%xmm4, %%xmm1 \n\t" + "movhlps %%xmm1, %%xmm3 \n\t" + "movaps %%xmm1, %%xmm2 \n\t" + "shufps $1, %%xmm2, %%xmm2 \n\t" + "addss %%xmm2, %%xmm1 \n\t" + "addss %%xmm3, %%xmm1 \n\t" + "maxss %%xmm5, %%xmm1 \n\t" + "rcpss %%xmm1, %%xmm0 \n\t" + "movss %%xmm0, %0 \n\t" + : "=m" (inv_r2) + : "m" (*v), "m" (inv_r2) + ); +#else + #error "Not Implemented" +#endif + + return inv_r2; +} + +void _SSE_SinCos(float x, float* s, float* c) +{ +#ifdef _WIN32 + float t4, t8, t12; + + __asm + { + movss xmm0, x + movss t12, xmm0 + movss xmm1, _ps_am_inv_sign_mask + mov eax, t12 + mulss xmm0, _ps_am_2_o_pi + andps xmm0, xmm1 + and eax, 0x80000000 + + cvttss2si edx, xmm0 + mov ecx, edx + mov t12, esi + mov esi, edx + add edx, 0x1 + shl ecx, (31 - 1) + shl edx, (31 - 1) + + movss xmm4, _ps_am_1 + cvtsi2ss xmm3, esi + mov t8, eax + and esi, 0x1 + + subss xmm0, xmm3 + movss xmm3, _sincos_inv_masks[esi * 4] + minss xmm0, xmm4 + + subss xmm4, xmm0 + + movss xmm6, xmm4 + andps xmm4, xmm3 + and ecx, 0x80000000 + movss xmm2, xmm3 + andnps xmm3, xmm0 + and edx, 0x80000000 + movss xmm7, t8 + andps xmm0, xmm2 + mov t8, ecx + mov t4, edx + orps xmm4, xmm3 + + mov eax, s //mov eax, [esp + 4 + 16] + mov edx, c //mov edx, [esp + 4 + 16 + 4] + + andnps xmm2, xmm6 + orps xmm0, xmm2 + + movss xmm2, t8 + movss xmm1, xmm0 + movss xmm5, xmm4 + xorps xmm7, xmm2 + movss xmm3, _ps_sincos_p3 + mulss xmm0, xmm0 + mulss xmm4, xmm4 + movss xmm2, xmm0 + movss xmm6, xmm4 + orps xmm1, xmm7 + movss xmm7, _ps_sincos_p2 + mulss xmm0, xmm3 + mulss xmm4, xmm3 + movss xmm3, _ps_sincos_p1 + addss xmm0, xmm7 + addss xmm4, xmm7 + movss xmm7, _ps_sincos_p0 + mulss xmm0, xmm2 + mulss xmm4, xmm6 + addss xmm0, xmm3 + addss xmm4, xmm3 + movss xmm3, t4 + mulss xmm0, xmm2 + mulss xmm4, xmm6 + orps xmm5, xmm3 + mov esi, t12 + addss xmm0, xmm7 + addss xmm4, xmm7 + mulss xmm0, xmm1 + mulss xmm4, xmm5 + + // use full stores since caller might reload with full loads + movss [eax], xmm0 + movss [edx], xmm4 + } +#elif defined _LINUX || defined __APPLE__ +// #warning "_SSE_sincos NOT implemented!" +#else + #error "Not Implemented" +#endif +} + +float _SSE_cos( float x ) +{ +#ifdef _WIN32 + float temp; + __asm + { + movss xmm0, x + movss xmm1, _ps_am_inv_sign_mask + andps xmm0, xmm1 + addss xmm0, _ps_am_pi_o_2 + mulss xmm0, _ps_am_2_o_pi + + cvttss2si ecx, xmm0 + movss xmm5, _ps_am_1 + mov edx, ecx + shl edx, (31 - 1) + cvtsi2ss xmm1, ecx + and edx, 0x80000000 + and ecx, 0x1 + + subss xmm0, xmm1 + movss xmm6, _sincos_masks[ecx * 4] + minss xmm0, xmm5 + + movss xmm1, _ps_sincos_p3 + subss xmm5, xmm0 + + andps xmm5, xmm6 + movss xmm7, _ps_sincos_p2 + andnps xmm6, xmm0 + mov temp, edx + orps xmm5, xmm6 + movss xmm0, xmm5 + + mulss xmm5, xmm5 + movss xmm4, _ps_sincos_p1 + movss xmm2, xmm5 + mulss xmm5, xmm1 + movss xmm1, _ps_sincos_p0 + addss xmm5, xmm7 + mulss xmm5, xmm2 + movss xmm3, temp + addss xmm5, xmm4 + mulss xmm5, xmm2 + orps xmm0, xmm3 + addss xmm5, xmm1 + mulss xmm0, xmm5 + + movss x, xmm0 + + } +#elif defined _LINUX || defined __APPLE__ +// #warning "_SSE_cos NOT implemented!" +#else + #error "Not Implemented" +#endif + + return x; +} + +//----------------------------------------------------------------------------- +// SSE2 implementations of optimized routines: +//----------------------------------------------------------------------------- +void _SSE2_SinCos(float x, float* s, float* c) // any x +{ +#ifdef _WIN32 + __asm + { + movss xmm0, x + movaps xmm7, xmm0 + movss xmm1, _ps_am_inv_sign_mask + movss xmm2, _ps_am_sign_mask + movss xmm3, _ps_am_2_o_pi + andps xmm0, xmm1 + andps xmm7, xmm2 + mulss xmm0, xmm3 + + pxor xmm3, xmm3 + movd xmm5, _epi32_1 + movss xmm4, _ps_am_1 + + cvttps2dq xmm2, xmm0 + pand xmm5, xmm2 + movd xmm1, _epi32_2 + pcmpeqd xmm5, xmm3 + movd xmm3, _epi32_1 + cvtdq2ps xmm6, xmm2 + paddd xmm3, xmm2 + pand xmm2, xmm1 + pand xmm3, xmm1 + subss xmm0, xmm6 + pslld xmm2, (31 - 1) + minss xmm0, xmm4 + + mov eax, s // mov eax, [esp + 4 + 16] + mov edx, c // mov edx, [esp + 4 + 16 + 4] + + subss xmm4, xmm0 + pslld xmm3, (31 - 1) + + movaps xmm6, xmm4 + xorps xmm2, xmm7 + movaps xmm7, xmm5 + andps xmm6, xmm7 + andnps xmm7, xmm0 + andps xmm0, xmm5 + andnps xmm5, xmm4 + movss xmm4, _ps_sincos_p3 + orps xmm6, xmm7 + orps xmm0, xmm5 + movss xmm5, _ps_sincos_p2 + + movaps xmm1, xmm0 + movaps xmm7, xmm6 + mulss xmm0, xmm0 + mulss xmm6, xmm6 + orps xmm1, xmm2 + orps xmm7, xmm3 + movaps xmm2, xmm0 + movaps xmm3, xmm6 + mulss xmm0, xmm4 + mulss xmm6, xmm4 + movss xmm4, _ps_sincos_p1 + addss xmm0, xmm5 + addss xmm6, xmm5 + movss xmm5, _ps_sincos_p0 + mulss xmm0, xmm2 + mulss xmm6, xmm3 + addss xmm0, xmm4 + addss xmm6, xmm4 + mulss xmm0, xmm2 + mulss xmm6, xmm3 + addss xmm0, xmm5 + addss xmm6, xmm5 + mulss xmm0, xmm1 + mulss xmm6, xmm7 + + // use full stores since caller might reload with full loads + movss [eax], xmm0 + movss [edx], xmm6 + } +#elif defined _LINUX || defined __APPLE__ +// #warning "_SSE2_SinCos NOT implemented!" +#else + #error "Not Implemented" +#endif +} + +float _SSE2_cos(float x) +{ +#ifdef _WIN32 + __asm + { + movss xmm0, x + movss xmm1, _ps_am_inv_sign_mask + movss xmm2, _ps_am_pi_o_2 + movss xmm3, _ps_am_2_o_pi + andps xmm0, xmm1 + addss xmm0, xmm2 + mulss xmm0, xmm3 + + pxor xmm3, xmm3 + movd xmm5, _epi32_1 + movss xmm4, _ps_am_1 + cvttps2dq xmm2, xmm0 + pand xmm5, xmm2 + movd xmm1, _epi32_2 + pcmpeqd xmm5, xmm3 + cvtdq2ps xmm6, xmm2 + pand xmm2, xmm1 + pslld xmm2, (31 - 1) + + subss xmm0, xmm6 + movss xmm3, _ps_sincos_p3 + minss xmm0, xmm4 + subss xmm4, xmm0 + andps xmm0, xmm5 + andnps xmm5, xmm4 + orps xmm0, xmm5 + + movaps xmm1, xmm0 + movss xmm4, _ps_sincos_p2 + mulss xmm0, xmm0 + movss xmm5, _ps_sincos_p1 + orps xmm1, xmm2 + movaps xmm7, xmm0 + mulss xmm0, xmm3 + movss xmm6, _ps_sincos_p0 + addss xmm0, xmm4 + mulss xmm0, xmm7 + addss xmm0, xmm5 + mulss xmm0, xmm7 + addss xmm0, xmm6 + mulss xmm0, xmm1 + movss x, xmm0 + } +#elif defined _LINUX || defined __APPLE__ +// #warning "_SSE2_cos NOT implemented!" +#else + #error "Not Implemented" +#endif + + return x; +} + +// SSE Version of VectorTransform +void VectorTransformSSE(const float *in1, const matrix3x4_t& in2, float *out1) +{ + Assert( s_bMathlibInitialized ); + Assert( in1 != out1 ); + +#ifdef _WIN32 + __asm + { + mov eax, in1; + mov ecx, in2; + mov edx, out1; + + movss xmm0, [eax]; + mulss xmm0, [ecx]; + movss xmm1, [eax+4]; + mulss xmm1, [ecx+4]; + movss xmm2, [eax+8]; + mulss xmm2, [ecx+8]; + addss xmm0, xmm1; + addss xmm0, xmm2; + addss xmm0, [ecx+12] + movss [edx], xmm0; + add ecx, 16; + + movss xmm0, [eax]; + mulss xmm0, [ecx]; + movss xmm1, [eax+4]; + mulss xmm1, [ecx+4]; + movss xmm2, [eax+8]; + mulss xmm2, [ecx+8]; + addss xmm0, xmm1; + addss xmm0, xmm2; + addss xmm0, [ecx+12] + movss [edx+4], xmm0; + add ecx, 16; + + movss xmm0, [eax]; + mulss xmm0, [ecx]; + movss xmm1, [eax+4]; + mulss xmm1, [ecx+4]; + movss xmm2, [eax+8]; + mulss xmm2, [ecx+8]; + addss xmm0, xmm1; + addss xmm0, xmm2; + addss xmm0, [ecx+12] + movss [edx+8], xmm0; + } +#elif defined _LINUX || defined __APPLE__ +// #warning "VectorTransformSSE C implementation only" + out1[0] = DotProduct(in1, in2[0]) + in2[0][3]; + out1[1] = DotProduct(in1, in2[1]) + in2[1][3]; + out1[2] = DotProduct(in1, in2[2]) + in2[2][3]; +#else + #error "Not Implemented" +#endif +} + +void VectorRotateSSE( const float *in1, const matrix3x4_t& in2, float *out1 ) +{ + Assert( s_bMathlibInitialized ); + Assert( in1 != out1 ); + +#ifdef _WIN32 + __asm + { + mov eax, in1; + mov ecx, in2; + mov edx, out1; + + movss xmm0, [eax]; + mulss xmm0, [ecx]; + movss xmm1, [eax+4]; + mulss xmm1, [ecx+4]; + movss xmm2, [eax+8]; + mulss xmm2, [ecx+8]; + addss xmm0, xmm1; + addss xmm0, xmm2; + movss [edx], xmm0; + add ecx, 16; + + movss xmm0, [eax]; + mulss xmm0, [ecx]; + movss xmm1, [eax+4]; + mulss xmm1, [ecx+4]; + movss xmm2, [eax+8]; + mulss xmm2, [ecx+8]; + addss xmm0, xmm1; + addss xmm0, xmm2; + movss [edx+4], xmm0; + add ecx, 16; + + movss xmm0, [eax]; + mulss xmm0, [ecx]; + movss xmm1, [eax+4]; + mulss xmm1, [ecx+4]; + movss xmm2, [eax+8]; + mulss xmm2, [ecx+8]; + addss xmm0, xmm1; + addss xmm0, xmm2; + movss [edx+8], xmm0; + } +#elif defined _LINUX || defined __APPLE__ +// #warning "VectorRotateSSE C implementation only" + out1[0] = DotProduct( in1, in2[0] ); + out1[1] = DotProduct( in1, in2[1] ); + out1[2] = DotProduct( in1, in2[2] ); +#else + #error "Not Implemented" +#endif +} + +#ifdef _WIN32 +void _declspec(naked) _SSE_VectorMA( const float *start, float scale, const float *direction, float *dest ) +{ + // FIXME: This don't work!! It will overwrite memory in the write to dest + Assert(0); + + Assert( s_bMathlibInitialized ); + _asm { // Intel SSE only routine + mov eax, DWORD PTR [esp+0x04] ; *start, s0..s2 + mov ecx, DWORD PTR [esp+0x0c] ; *direction, d0..d2 + mov edx, DWORD PTR [esp+0x10] ; *dest + movss xmm2, [esp+0x08] ; x2 = scale, 0, 0, 0 +#ifdef ALIGNED_VECTOR + movaps xmm3, [ecx] ; x3 = dir0,dir1,dir2,X + pshufd xmm2, xmm2, 0 ; x2 = scale, scale, scale, scale + movaps xmm1, [eax] ; x1 = start1, start2, start3, X + mulps xmm3, xmm2 ; x3 *= x2 + addps xmm3, xmm1 ; x3 += x1 + movaps [edx], xmm3 ; *dest = x3 +#else + movups xmm3, [ecx] ; x3 = dir0,dir1,dir2,X + pshufd xmm2, xmm2, 0 ; x2 = scale, scale, scale, scale + movups xmm1, [eax] ; x1 = start1, start2, start3, X + mulps xmm3, xmm2 ; x3 *= x2 + addps xmm3, xmm1 ; x3 += x1 + movups [edx], xmm3 ; *dest = x3 +#endif + } +} +#endif + +#ifdef _WIN32 +#ifdef PFN_VECTORMA +void _declspec(naked) __cdecl _SSE_VectorMA( const Vector &start, float scale, const Vector &direction, Vector &dest ) +{ + // FIXME: This don't work!! It will overwrite memory in the write to dest + Assert(0); + + Assert( s_bMathlibInitialized ); + _asm + { + // Intel SSE only routine + mov eax, DWORD PTR [esp+0x04] ; *start, s0..s2 + mov ecx, DWORD PTR [esp+0x0c] ; *direction, d0..d2 + mov edx, DWORD PTR [esp+0x10] ; *dest + movss xmm2, [esp+0x08] ; x2 = scale, 0, 0, 0 +#ifdef ALIGNED_VECTOR + movaps xmm3, [ecx] ; x3 = dir0,dir1,dir2,X + pshufd xmm2, xmm2, 0 ; x2 = scale, scale, scale, scale + movaps xmm1, [eax] ; x1 = start1, start2, start3, X + mulps xmm3, xmm2 ; x3 *= x2 + addps xmm3, xmm1 ; x3 += x1 + movaps [edx], xmm3 ; *dest = x3 +#else + movups xmm3, [ecx] ; x3 = dir0,dir1,dir2,X + pshufd xmm2, xmm2, 0 ; x2 = scale, scale, scale, scale + movups xmm1, [eax] ; x1 = start1, start2, start3, X + mulps xmm3, xmm2 ; x3 *= x2 + addps xmm3, xmm1 ; x3 += x1 + movups [edx], xmm3 ; *dest = x3 +#endif + } +} +float (__cdecl *pfVectorMA)(Vector& v) = _VectorMA; +#endif +#endif + + +// SSE DotProduct -- it's a smidgen faster than the asm DotProduct... +// Should be validated too! :) +// NJS: (Nov 1 2002) -NOT- faster. may time a couple cycles faster in a single function like +// this, but when inlined, and instruction scheduled, the C version is faster. +// Verified this via VTune +/* +vec_t DotProduct (const vec_t *a, const vec_t *c) +{ + vec_t temp; + + __asm + { + mov eax, a; + mov ecx, c; + mov edx, DWORD PTR [temp] + movss xmm0, [eax]; + mulss xmm0, [ecx]; + movss xmm1, [eax+4]; + mulss xmm1, [ecx+4]; + movss xmm2, [eax+8]; + mulss xmm2, [ecx+8]; + addss xmm0, xmm1; + addss xmm0, xmm2; + movss [edx], xmm0; + fld DWORD PTR [edx]; + ret + } +} +*/ + diff --git a/mathlib/sse.h b/mathlib/sse.h new file mode 100644 index 00000000..0bc9f8c4 --- /dev/null +++ b/mathlib/sse.h @@ -0,0 +1,23 @@ +//========= Copyright © 1996-2006, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=====================================================================================// + +#ifndef _SSE_H +#define _SSE_H + +float _SSE_Sqrt(float x); +float _SSE_RSqrtAccurate(float a); +float _SSE_RSqrtFast(float x); +float FASTCALL _SSE_VectorNormalize(Vector& vec); +void FASTCALL _SSE_VectorNormalizeFast(Vector& vec); +float _SSE_InvRSquared(const float* v); +void _SSE_SinCos(float x, float* s, float* c); +float _SSE_cos( float x); +void _SSE2_SinCos(float x, float* s, float* c); +float _SSE2_cos(float x); +void VectorTransformSSE(const float *in1, const matrix3x4_t& in2, float *out1); +void VectorRotateSSE( const float *in1, const matrix3x4_t& in2, float *out1 ); + +#endif // _SSE_H diff --git a/mathlib/sseconst.cpp b/mathlib/sseconst.cpp new file mode 100644 index 00000000..662943be --- /dev/null +++ b/mathlib/sseconst.cpp @@ -0,0 +1,1164 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +//===========================================================================// + +#include "mathlib/ssemath.h" +#include "mathlib/ssequaternion.h" + +const fltx4 Four_PointFives={0.5,0.5,0.5,0.5}; +#ifndef _X360 +const fltx4 Four_Zeros={0.0,0.0,0.0,0.0}; +const fltx4 Four_Ones={1.0,1.0,1.0,1.0}; +#endif +const fltx4 Four_Twos={2.0,2.0,2.0,2.0}; +const fltx4 Four_Threes={3.0,3.0,3.0,3.0}; +const fltx4 Four_Fours={4.0,4.0,4.0,4.0}; +const fltx4 Four_Origin={0,0,0,1}; +const fltx4 Four_NegativeOnes={-1,-1,-1,-1}; + +const fltx4 Four_2ToThe21s={ (float) (1<<21), (float) (1<<21), (float) (1<<21), (float)(1<<21) }; +const fltx4 Four_2ToThe22s={ (float) (1<<22), (float) (1<<22), (float) (1<<22), (float)(1<<22) }; +const fltx4 Four_2ToThe23s={ (float) (1<<23), (float) (1<<23), (float) (1<<23), (float)(1<<23) }; +const fltx4 Four_2ToThe24s={ (float) (1<<24), (float) (1<<24), (float) (1<<24), (float)(1<<24) }; + +const fltx4 Four_Point225s={ .225, .225, .225, .225 }; +const fltx4 Four_Epsilons={FLT_EPSILON,FLT_EPSILON,FLT_EPSILON,FLT_EPSILON}; + +const fltx4 Four_FLT_MAX={FLT_MAX,FLT_MAX,FLT_MAX,FLT_MAX}; +const fltx4 Four_Negative_FLT_MAX={-FLT_MAX,-FLT_MAX,-FLT_MAX,-FLT_MAX}; +const fltx4 g_SIMD_0123 = { 0., 1., 2., 3. }; + +const fltx4 g_QuatMultRowSign[4] = +{ + { 1.0f, 1.0f, -1.0f, 1.0f }, + { -1.0f, 1.0f, 1.0f, 1.0f }, + { 1.0f, -1.0f, 1.0f, 1.0f }, + { -1.0f, -1.0f, -1.0f, 1.0f } +}; + +const int32 ALIGN16 g_SIMD_clear_signmask[4]= {0x7fffffff,0x7fffffff,0x7fffffff,0x7fffffff}; +const int32 ALIGN16 g_SIMD_signmask[4]= { 0x80000000, 0x80000000, 0x80000000, 0x80000000 }; +const int32 ALIGN16 g_SIMD_lsbmask[4]= { 0xfffffffe, 0xfffffffe, 0xfffffffe, 0xfffffffe }; +const int32 ALIGN16 g_SIMD_clear_wmask[4]= { 0xffffffff, 0xffffffff, 0xffffffff, 0 }; +const int32 ALIGN16 g_SIMD_AllOnesMask[4]= { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff }; // ~0,~0,~0,~0 +const int32 ALIGN16 g_SIMD_Low16BitsMask[4]= { 0xffff, 0xffff, 0xffff, 0xffff }; // 0xffff x 4 + +const int32 ALIGN16 g_SIMD_ComponentMask[4][4] = +{ + { 0xFFFFFFFF, 0, 0, 0 }, { 0, 0xFFFFFFFF, 0, 0 }, { 0, 0, 0xFFFFFFFF, 0 }, { 0, 0, 0, 0xFFFFFFFF } +}; + +const int32 ALIGN16 g_SIMD_SkipTailMask[4][4] = +{ + { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff }, + { 0xffffffff, 0x00000000, 0x00000000, 0x00000000 }, + { 0xffffffff, 0xffffffff, 0x00000000, 0x00000000 }, + { 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000 }, +}; + + + // FUNCTIONS + // NOTE: WHY YOU **DO NOT** WANT TO PUT FUNCTIONS HERE +// Generally speaking, you want to make sure SIMD math functions +// are inlined, because that gives the compiler much more latitude +// in instruction scheduling. It's not that the overhead of calling +// the function is particularly great; rather, many of the SIMD +// opcodes have long latencies, and if you have a sequence of +// several dependent ones inside a function call, the latencies +// stack up to create a big penalty. If the function is inlined, +// the compiler can interleave its operations with ones from the +// caller to better hide those latencies. Finally, on the 360, +// putting parameters or return values on the stack, and then +// reading them back within the next forty cycles, is a very +// severe penalty. So, as much as possible, you want to leave your +// data on the registers. + +// That said, there are certain occasions where it is appropriate +// to call into functions -- particularly for very large blocks +// of code that will spill most of the registers anyway. Unless your +// function is more than one screen long, yours is probably not one +// of those occasions. + + + +/// You can use this to rotate a long array of FourVectors all by the same +/// matrix. The first parameter is the head of the array. The second is the +/// number of vectors to rotate. The third is the matrix. +void FourVectors::RotateManyBy(FourVectors * RESTRICT pVectors, unsigned int numVectors, const matrix3x4_t& rotationMatrix ) +{ + Assert(numVectors > 0); + if ( numVectors == 0 ) + return; + + // Splat out each of the entries in the matrix to a fltx4. Do this + // in the order that we will need them, to hide latency. I'm + // avoiding making an array of them, so that they'll remain in + // registers. + fltx4 matSplat00, matSplat01, matSplat02, + matSplat10, matSplat11, matSplat12, + matSplat20, matSplat21, matSplat22; + + { + // Load the matrix into local vectors. Sadly, matrix3x4_ts are + // often unaligned. The w components will be the tranpose row of + // the matrix, but we don't really care about that. + fltx4 matCol0 = LoadUnalignedSIMD(rotationMatrix[0]); + fltx4 matCol1 = LoadUnalignedSIMD(rotationMatrix[1]); + fltx4 matCol2 = LoadUnalignedSIMD(rotationMatrix[2]); + + matSplat00 = SplatXSIMD(matCol0); + matSplat01 = SplatYSIMD(matCol0); + matSplat02 = SplatZSIMD(matCol0); + + matSplat10 = SplatXSIMD(matCol1); + matSplat11 = SplatYSIMD(matCol1); + matSplat12 = SplatZSIMD(matCol1); + + matSplat20 = SplatXSIMD(matCol2); + matSplat21 = SplatYSIMD(matCol2); + matSplat22 = SplatZSIMD(matCol2); + } + +#ifdef _X360 + // Same algorithm as above, but the loop is unrolled to eliminate data hazard latencies + // and simplify prefetching. Named variables are deliberately used instead of arrays to + // ensure that the variables live on the registers instead of the stack (stack load/store + // is a serious penalty on 360). Nb: for prefetching to be most efficient here, the + // loop should be unrolled to 8 FourVectors per iteration; because each FourVectors is + // 48 bytes long, 48 * 8 = 384, its least common multiple with the 128-byte cache line. + // That way you can fetch the next 3 cache lines while you work on these three. + // If you do go this route, be sure to dissassemble and make sure it doesn't spill + // registers to stack as you do this; the cost of that will be excessive. Unroll the loop + // a little and just live with the fact that you'll be doing a couple of redundant dbcts + // (they don't cost you anything). Be aware that all three cores share L2 and it can only + // have eight cache lines fetching at a time. + fltx4 outX0, outY0, outZ0; // bank one of outputs + fltx4 outX1, outY1, outZ1; // bank two of outputs + + + // Because of instruction latencies and scheduling, it's actually faster to use adds and muls + // rather than madds. (Empirically determined by timing.) + const FourVectors * stop = pVectors + numVectors; + FourVectors * RESTRICT pVectNext; + // prime the pump. + if (numVectors & 0x01) + { + // odd number of vectors to process + // prime the 1 group of registers + pVectNext = pVectors++; + outX1 = AddSIMD( AddSIMD( MulSIMD( pVectNext->x, matSplat00 ), MulSIMD( pVectNext->y, matSplat01 ) ), MulSIMD( pVectNext->z, matSplat02 ) ); + outY1 = AddSIMD( AddSIMD( MulSIMD( pVectNext->x, matSplat10 ), MulSIMD( pVectNext->y, matSplat11 ) ), MulSIMD( pVectNext->z, matSplat12 ) ); + outZ1 = AddSIMD( AddSIMD( MulSIMD( pVectNext->x, matSplat20 ), MulSIMD( pVectNext->y, matSplat21 ) ), MulSIMD( pVectNext->z, matSplat22 ) ); + } + else + { + // even number of total vectors to process; + // prime the zero group and jump into the middle of the loop + outX0 = AddSIMD( AddSIMD( MulSIMD( pVectors->x, matSplat00 ), MulSIMD( pVectors->y, matSplat01 ) ), MulSIMD( pVectors->z, matSplat02 ) ); + outY0 = AddSIMD( AddSIMD( MulSIMD( pVectors->x, matSplat10 ), MulSIMD( pVectors->y, matSplat11 ) ), MulSIMD( pVectors->z, matSplat12 ) ); + outZ0 = AddSIMD( AddSIMD( MulSIMD( pVectors->x, matSplat20 ), MulSIMD( pVectors->y, matSplat21 ) ), MulSIMD( pVectors->z, matSplat22 ) ); + goto EVEN_CASE; + } + + // perform an even number of iterations through this loop. + while (pVectors < stop) + { + outX0 = MaddSIMD( pVectors->z, matSplat02, AddSIMD( MulSIMD( pVectors->x, matSplat00 ), MulSIMD( pVectors->y, matSplat01 ) ) ); + outY0 = MaddSIMD( pVectors->z, matSplat12, AddSIMD( MulSIMD( pVectors->x, matSplat10 ), MulSIMD( pVectors->y, matSplat11 ) ) ); + outZ0 = MaddSIMD( pVectors->z, matSplat22, AddSIMD( MulSIMD( pVectors->x, matSplat20 ), MulSIMD( pVectors->y, matSplat21 ) ) ); + + pVectNext->x = outX1; + pVectNext->y = outY1; + pVectNext->z = outZ1; + +EVEN_CASE: + pVectNext = pVectors+1; + + outX1 = MaddSIMD( pVectNext->z, matSplat02, AddSIMD( MulSIMD( pVectNext->x, matSplat00 ), MulSIMD( pVectNext->y, matSplat01 ) ) ); + outY1 = MaddSIMD( pVectNext->z, matSplat12, AddSIMD( MulSIMD( pVectNext->x, matSplat10 ), MulSIMD( pVectNext->y, matSplat11 ) ) ); + outZ1 = MaddSIMD( pVectNext->z, matSplat22, AddSIMD( MulSIMD( pVectNext->x, matSplat20 ), MulSIMD( pVectNext->y, matSplat21 ) ) ); + + pVectors->x = outX0; + pVectors->y = outY0; + pVectors->z = outZ0; + + pVectors += 2; + } + + // flush the last round of output + pVectNext->x = outX1; + pVectNext->y = outY1; + pVectNext->z = outZ1; +#else + // PC does not benefit from the unroll/scheduling above + fltx4 outX0, outY0, outZ0; // bank one of outputs + + + // Because of instruction latencies and scheduling, it's actually faster to use adds and muls + // rather than madds. (Empirically determined by timing.) + const FourVectors * stop = pVectors + numVectors; + + // perform an even number of iterations through this loop. + while (pVectors < stop) + { + outX0 = MaddSIMD( pVectors->z, matSplat02, AddSIMD( MulSIMD( pVectors->x, matSplat00 ), MulSIMD( pVectors->y, matSplat01 ) ) ); + outY0 = MaddSIMD( pVectors->z, matSplat12, AddSIMD( MulSIMD( pVectors->x, matSplat10 ), MulSIMD( pVectors->y, matSplat11 ) ) ); + outZ0 = MaddSIMD( pVectors->z, matSplat22, AddSIMD( MulSIMD( pVectors->x, matSplat20 ), MulSIMD( pVectors->y, matSplat21 ) ) ); + + pVectors->x = outX0; + pVectors->y = outY0; + pVectors->z = outZ0; + pVectors++; + } +#endif +} + +#ifdef _X360 +// Loop-scheduled code to process FourVectors in groups of eight quite efficiently. +void FourVectors_TransformManyGroupsOfEightBy(FourVectors * RESTRICT pVectors, unsigned int numVectors, const matrix3x4_t& rotationMatrix, FourVectors * RESTRICT pOut ) +{ + Assert(numVectors > 0); + if ( numVectors == 0 ) + return; + + AssertMsg( (pOut < pVectors && pOut+numVectors <= pVectors) || + (pOut > pVectors && pVectors+numVectors <= pOut), "FourVectors::TransformManyBy called with overlapping buffer pointers." ); + + // Splat out each of the entries in the matrix to a fltx4. Do this + // in the order that we will need them, to hide latency. I'm + // avoiding making an array of them, so that they'll remain in + // registers. + fltx4 matSplat00, matSplat01, matSplat02, matSplat03, // TWELVE REGISTERS + matSplat10, matSplat11, matSplat12, matSplat13, + matSplat20, matSplat21, matSplat22, matSplat23; + + { + // Load the matrix into local vectors. Sadly, matrix3x4_ts are + // often unaligned. The w components will be the tranpose row of + // the matrix. + fltx4 matCol0 = LoadUnalignedSIMD(rotationMatrix[0]); + fltx4 matCol1 = LoadUnalignedSIMD(rotationMatrix[1]); + fltx4 matCol2 = LoadUnalignedSIMD(rotationMatrix[2]); + + matSplat00 = SplatXSIMD(matCol0); + matSplat01 = SplatYSIMD(matCol0); + matSplat02 = SplatZSIMD(matCol0); + matSplat03 = SplatWSIMD(matCol0); + + matSplat10 = SplatXSIMD(matCol1); + matSplat11 = SplatYSIMD(matCol1); + matSplat12 = SplatZSIMD(matCol1); + matSplat13 = SplatWSIMD(matCol1); + + matSplat20 = SplatXSIMD(matCol2); + matSplat21 = SplatYSIMD(matCol2); + matSplat22 = SplatZSIMD(matCol2); + matSplat23 = SplatWSIMD(matCol2); + } + + // this macro defines how to compute a specific row from an input and certain splat columns +#define COMPUTE(res, invec, xterm, yterm, zterm, transterm) res = AddSIMD( AddSIMD( MulSIMD((invec)->z, zterm), AddSIMD( MulSIMD( (invec)->x, xterm ), MulSIMD( (invec)->y, yterm ) ) ), transterm ) +#define WRITE(term, reg, toptr) toptr->term = reg + + // define result groups (we're going to have an eight-way unroll) + + fltx4 res0X, res0Y, res0Z, res0XTemp, res0YTemp, res0ZTemp; // 48 REGISTERS + fltx4 res1X, res1Y, res1Z, res1XTemp, res1YTemp, res1ZTemp; + fltx4 res2X, res2Y, res2Z, res2XTemp, res2YTemp, res2ZTemp; + fltx4 res3X, res3Y, res3Z, res3XTemp, res3YTemp, res3ZTemp; + fltx4 res4X, res4Y, res4Z, res4XTemp, res4YTemp, res4ZTemp; + fltx4 res5X, res5Y, res5Z, res5XTemp, res5YTemp, res5ZTemp; + fltx4 res6X, res6Y, res6Z, res6XTemp, res6YTemp, res6ZTemp; + fltx4 res7X, res7Y, res7Z, res7XTemp, res7YTemp, res7ZTemp; + + +// #define FROZ(out,in,offset) COMPUTE((out+offset)->x, (in + offset), matSplat00, matSplat01, matSplat02, matSplat03); COMPUTE((out + offset )->y, (in + offset), matSplat10, matSplat11, matSplat12, matSplat13); COMPUTE((out + offset)->z, (in + offset), matSplat20, matSplat21, matSplat22, matSplat23) +#define COMPUTE_GROUP(resgroup,dataptr) COMPUTE(resgroup ## X, (dataptr), matSplat00, matSplat01, matSplat02, matSplat03); COMPUTE(resgroup ## Y, (dataptr), matSplat10, matSplat11, matSplat12, matSplat13); COMPUTE(resgroup ## Z, (dataptr), matSplat20, matSplat21, matSplat22, matSplat23) +#define WRITE_GROUP(ptr, resgroup) (ptr)->x = resgroup ## X; (ptr)->y = resgroup ## Y; (ptr)->z = resgroup ## Z + + /* + // stage 1 -- 6 ops for xyz, each w 12 cycle latency + res0X = MulSIMD( (invec)->y, matSplat01 ); + res0Temp = MaddSIMD((invec)->z, matSplat02, matSplat03); + // stage 2 -- 3 clocks for xyz + res0X = MaddSIMD( (invec)->x, matSplat00, res0X ); + // stage 3 -- 3 clocks for xyz + res0X = AddSIMD(res0X, res0Temp); + */ +#define COMPUTE_STAGE1_ROW(res, tempvar, invec, xsplat, ysplat, zsplat, transplat) res = MulSIMD( (invec)->y, ysplat ); tempvar = MaddSIMD((invec)->z, zsplat, transplat) +#define COMPUTE_STAGE2_ROW(res, tempvar, invec, xsplat, ysplat, zsplat, transplat) res = MaddSIMD( (invec)->x, xsplat, res ) +#define COMPUTE_STAGE3_ROW(res, tempvar, invec, xsplat, ysplat, zsplat, transplat) res = AddSIMD(res, tempvar) // frees up the tempvar + +#define COMPUTE_STAGE1_GROUP(resgroup, invec) COMPUTE_STAGE1_ROW(resgroup ## X, resgroup ## X ## Temp, invec, matSplat00, matSplat01, matSplat02, matSplat03);\ + COMPUTE_STAGE1_ROW(resgroup ## Y, resgroup ## Y ## Temp, invec, matSplat10, matSplat11, matSplat12, matSplat13);\ + COMPUTE_STAGE1_ROW(resgroup ## Z, resgroup ## Z ## Temp, invec, matSplat20, matSplat21, matSplat22, matSplat23) + +#define COMPUTE_STAGE2_GROUP(resgroup, invec) COMPUTE_STAGE2_ROW(resgroup ## X, resgroup ## X ## Temp, invec, matSplat00, matSplat01, matSplat02, matSplat03);\ + COMPUTE_STAGE2_ROW(resgroup ## Y, resgroup ## Y ## Temp, invec, matSplat10, matSplat11, matSplat12, matSplat13);\ + COMPUTE_STAGE2_ROW(resgroup ## Z, resgroup ## Z ## Temp, invec, matSplat20, matSplat21, matSplat22, matSplat23) + +#define COMPUTE_STAGE3_GROUP(resgroup, invec) COMPUTE_STAGE3_ROW(resgroup ## X, resgroup ## X ## Temp, invec, matSplat00, matSplat01, matSplat02, matSplat03);\ + COMPUTE_STAGE3_ROW(resgroup ## Y, resgroup ## Y ## Temp, invec, matSplat10, matSplat11, matSplat12, matSplat13);\ + COMPUTE_STAGE3_ROW(resgroup ## Z, resgroup ## Z ## Temp, invec, matSplat20, matSplat21, matSplat22, matSplat23) + + FourVectors * RESTRICT inData = pVectors; + FourVectors * RESTRICT outData = pOut; + const FourVectors * const RESTRICT STOP = pVectors + numVectors; + + // Use techniques of loop scheduling to eliminate data hazards; process + // eight groups simultaneously so that we never have any operations stalling + // waiting for data. + // Note: this loop, while pretty fast, could be faster still -- you'll notice + // that it does all of its loads, then all computation, then writes everything + // out. If made truly cyclic, such that every line interleaved a stage 1, stage 2, + // stage 3, and write, then throughput could be higher (probably by about 50%). + while (inData < STOP) + { + // start prefetching the three cache lines + // we'll hit two iterations from now + __dcbt( sizeof(FourVectors) * 16, inData ); + __dcbt( sizeof(FourVectors) * 16 + 128, inData ); + __dcbt( sizeof(FourVectors) * 16 + 256, inData ); + + // synchro + COMPUTE_STAGE1_GROUP(res0, inData + 0); + COMPUTE_STAGE1_GROUP(res1, inData + 1); + COMPUTE_STAGE1_GROUP(res2, inData + 2); + COMPUTE_STAGE1_GROUP(res3, inData + 3); + + COMPUTE_STAGE2_GROUP(res0, inData + 0); + COMPUTE_STAGE1_GROUP(res4, inData + 4); + COMPUTE_STAGE2_GROUP(res1, inData + 1); + COMPUTE_STAGE1_GROUP(res5, inData + 5); + COMPUTE_STAGE2_GROUP(res2, inData + 2); + COMPUTE_STAGE1_GROUP(res6, inData + 6); + COMPUTE_STAGE2_GROUP(res3, inData + 3); + COMPUTE_STAGE1_GROUP(res7, inData + 7); + + COMPUTE_STAGE3_GROUP(res0, inData + 0); + COMPUTE_STAGE2_GROUP(res4, inData + 4); + COMPUTE_STAGE3_GROUP(res1, inData + 1); + COMPUTE_STAGE2_GROUP(res5, inData + 5); + COMPUTE_STAGE3_GROUP(res2, inData + 2); + COMPUTE_STAGE2_GROUP(res6, inData + 6); + COMPUTE_STAGE3_GROUP(res3, inData + 3); + COMPUTE_STAGE2_GROUP(res7, inData + 7); + + COMPUTE_STAGE3_GROUP(res4, inData + 4); + WRITE_GROUP( outData + 0, res0 ); + COMPUTE_STAGE3_GROUP(res5, inData + 5); + WRITE_GROUP( outData + 1, res1 ); + COMPUTE_STAGE3_GROUP(res6, inData + 6); + WRITE_GROUP( outData + 2, res2 ); + COMPUTE_STAGE3_GROUP(res7, inData + 7); + WRITE_GROUP( outData + 3, res3 ); + + + WRITE_GROUP( outData + 4, res4 ); + WRITE_GROUP( outData + 5, res5 ); + WRITE_GROUP( outData + 6, res6 ); + WRITE_GROUP( outData + 7, res7 ); + + inData += 8; + outData += 8; + } + + +#undef COMPUTE +#undef WRITE +#undef COMPUTE_STAGE1_ROW +#undef COMPUTE_STAGE2_ROW +#undef COMPUTE_STAGE3_ROW +#undef COMPUTE_STAGE1_GROUP +#undef COMPUTE_STAGE2_GROUP +#undef COMPUTE_STAGE3_GROUP +#undef COMPUTE_GROUP +#undef WRITE_GROUP +} + +#ifdef _X360 +// Loop-scheduled code to process FourVectors in groups of eight quite efficiently. This is the version +// to call when starting on a 128-byte-aligned address. +void FourVectors_TransformManyGroupsOfEightBy_128byteAligned(FourVectors * RESTRICT pVectors, unsigned int numVectors, const matrix3x4_t& rotationMatrix, FourVectors * RESTRICT pOut ) +{ + /* If this has changed, you will need to change all the prefetches, * + * and groups of eight are no longer the ideal unit for iterating * + * on many vectors. */ + COMPILE_TIME_ASSERT( sizeof(FourVectors) == 48 ) ; + + Assert(numVectors > 0); + if ( numVectors == 0 ) + return; + + AssertMsg((numVectors & 0x07) == 0, "FourVectors_TransformManyGroupsOfEight called with numVectors % 8 != 0!"); + + // Assert alignment + AssertMsg( ( ( reinterpret_cast( pVectors ) & 127 ) == 0) && + ( ( reinterpret_cast(pOut) & 127 ) == 0), + "FourVectors_Transform..aligned called with non-128-byte-aligned buffers." ); + + // Assert non overlap + AssertMsg( (pOut < pVectors && pOut+numVectors <= pVectors) || + (pOut > pVectors && pVectors+numVectors <= pOut), "FourVectors::TransformManyBy called with overlapping buffer pointers." ); + + // Here's the plan. 8 four-vecs = 3 cache lines exactly. It takes about 400 cycles to process a group + // of eight, and cache latency is 600 cycles, so we try to prefetch two iterations ahead (eg fetch + // iteration 3 while working on iteration 1). In the case of the output, we can simply zero-flush + // the cache lines since we are sure to write into them. Because we're reading and fetching two ahead, + // we want to stop two away from the last iteration. + + // No matter what, we will need to prefetch the first two groups of eight of input (that's the + // first six cache lines) + __dcbt( 0, pVectors ); + __dcbt( 128, pVectors ); + __dcbt( 256, pVectors ); + __dcbt( 384, pVectors ); + __dcbt( 512, pVectors ); + __dcbt( 640, pVectors ); + + + // Splat out each of the entries in the matrix to a fltx4. Do this + // in the order that we will need them, to hide latency. I'm + // avoiding making an array of them, so that they'll remain in + // registers. + fltx4 matSplat00, matSplat01, matSplat02, matSplat03, // TWELVE REGISTERS + matSplat10, matSplat11, matSplat12, matSplat13, + matSplat20, matSplat21, matSplat22, matSplat23; + + { + // Load the matrix into local vectors. Sadly, matrix3x4_ts are + // often unaligned. The w components will be the tranpose row of + // the matrix. + fltx4 matCol0 = LoadUnalignedSIMD(rotationMatrix[0]); + fltx4 matCol1 = LoadUnalignedSIMD(rotationMatrix[1]); + fltx4 matCol2 = LoadUnalignedSIMD(rotationMatrix[2]); + + matSplat00 = SplatXSIMD(matCol0); + matSplat01 = SplatYSIMD(matCol0); + matSplat02 = SplatZSIMD(matCol0); + matSplat03 = SplatWSIMD(matCol0); + + matSplat10 = SplatXSIMD(matCol1); + matSplat11 = SplatYSIMD(matCol1); + matSplat12 = SplatZSIMD(matCol1); + matSplat13 = SplatWSIMD(matCol1); + + matSplat20 = SplatXSIMD(matCol2); + matSplat21 = SplatYSIMD(matCol2); + matSplat22 = SplatZSIMD(matCol2); + matSplat23 = SplatWSIMD(matCol2); + } + + // this macro defines how to compute a specific row from an input and certain splat columns +#define COMPUTE(res, invec, xterm, yterm, zterm, transterm) res = AddSIMD( AddSIMD( MulSIMD((invec)->z, zterm), AddSIMD( MulSIMD( (invec)->x, xterm ), MulSIMD( (invec)->y, yterm ) ) ), transterm ) +#define WRITE(term, reg, toptr) toptr->term = reg + + // define result groups (we're going to have an eight-way unroll) + + fltx4 res0X, res0Y, res0Z, res0XTemp, res0YTemp, res0ZTemp; // 48 REGISTERS + fltx4 res1X, res1Y, res1Z, res1XTemp, res1YTemp, res1ZTemp; + fltx4 res2X, res2Y, res2Z, res2XTemp, res2YTemp, res2ZTemp; + fltx4 res3X, res3Y, res3Z, res3XTemp, res3YTemp, res3ZTemp; + fltx4 res4X, res4Y, res4Z, res4XTemp, res4YTemp, res4ZTemp; + fltx4 res5X, res5Y, res5Z, res5XTemp, res5YTemp, res5ZTemp; + fltx4 res6X, res6Y, res6Z, res6XTemp, res6YTemp, res6ZTemp; + fltx4 res7X, res7Y, res7Z, res7XTemp, res7YTemp, res7ZTemp; + + + // #define FROZ(out,in,offset) COMPUTE((out+offset)->x, (in + offset), matSplat00, matSplat01, matSplat02, matSplat03); COMPUTE((out + offset )->y, (in + offset), matSplat10, matSplat11, matSplat12, matSplat13); COMPUTE((out + offset)->z, (in + offset), matSplat20, matSplat21, matSplat22, matSplat23) +#define COMPUTE_GROUP(resgroup,dataptr) COMPUTE(resgroup ## X, (dataptr), matSplat00, matSplat01, matSplat02, matSplat03); COMPUTE(resgroup ## Y, (dataptr), matSplat10, matSplat11, matSplat12, matSplat13); COMPUTE(resgroup ## Z, (dataptr), matSplat20, matSplat21, matSplat22, matSplat23) +#define WRITE_GROUP(ptr, resgroup) (ptr)->x = resgroup ## X; (ptr)->y = resgroup ## Y; (ptr)->z = resgroup ## Z + + /* + // stage 1 -- 6 ops for xyz, each w 12 cycle latency + res0X = MulSIMD( (invec)->y, matSplat01 ); + res0Temp = MaddSIMD((invec)->z, matSplat02, matSplat03); + // stage 2 -- 3 clocks for xyz + res0X = MaddSIMD( (invec)->x, matSplat00, res0X ); + // stage 3 -- 3 clocks for xyz + res0X = AddSIMD(res0X, res0Temp); + */ +#define COMPUTE_STAGE1_ROW(res, tempvar, invec, xsplat, ysplat, zsplat, transplat) res = MulSIMD( (invec)->y, ysplat ); tempvar = MaddSIMD((invec)->z, zsplat, transplat) +#define COMPUTE_STAGE2_ROW(res, tempvar, invec, xsplat, ysplat, zsplat, transplat) res = MaddSIMD( (invec)->x, xsplat, res ) +#define COMPUTE_STAGE3_ROW(res, tempvar, invec, xsplat, ysplat, zsplat, transplat) res = AddSIMD(res, tempvar) // frees up the tempvar + +#define COMPUTE_STAGE1_GROUP(resgroup, invec) COMPUTE_STAGE1_ROW(resgroup ## X, resgroup ## X ## Temp, invec, matSplat00, matSplat01, matSplat02, matSplat03);\ + COMPUTE_STAGE1_ROW(resgroup ## Y, resgroup ## Y ## Temp, invec, matSplat10, matSplat11, matSplat12, matSplat13);\ + COMPUTE_STAGE1_ROW(resgroup ## Z, resgroup ## Z ## Temp, invec, matSplat20, matSplat21, matSplat22, matSplat23) + +#define COMPUTE_STAGE2_GROUP(resgroup, invec) COMPUTE_STAGE2_ROW(resgroup ## X, resgroup ## X ## Temp, invec, matSplat00, matSplat01, matSplat02, matSplat03);\ + COMPUTE_STAGE2_ROW(resgroup ## Y, resgroup ## Y ## Temp, invec, matSplat10, matSplat11, matSplat12, matSplat13);\ + COMPUTE_STAGE2_ROW(resgroup ## Z, resgroup ## Z ## Temp, invec, matSplat20, matSplat21, matSplat22, matSplat23) + +#define COMPUTE_STAGE3_GROUP(resgroup, invec) COMPUTE_STAGE3_ROW(resgroup ## X, resgroup ## X ## Temp, invec, matSplat00, matSplat01, matSplat02, matSplat03);\ + COMPUTE_STAGE3_ROW(resgroup ## Y, resgroup ## Y ## Temp, invec, matSplat10, matSplat11, matSplat12, matSplat13);\ + COMPUTE_STAGE3_ROW(resgroup ## Z, resgroup ## Z ## Temp, invec, matSplat20, matSplat21, matSplat22, matSplat23) + + + // Okay. First do all but the last two turns of the crank; we don't want to overshoot with the flush-to-zero. + FourVectors * RESTRICT inData = pVectors; + FourVectors * RESTRICT outData = pOut; + const FourVectors * RESTRICT STOP; + if (numVectors > 16) + { + STOP = pVectors + numVectors - 16; + // flush the first two blocks we'll write into + __dcbz128( 0, outData ); + __dcbz128( 128, outData ); + __dcbz128( 256, outData ); + + while (inData < STOP) + { + // start prefetching the three cache lines + // we'll hit two iterations from now + __dcbt( sizeof(FourVectors) * 16, inData ); + __dcbt( sizeof(FourVectors) * 16 + 128, inData ); + __dcbt( sizeof(FourVectors) * 16 + 256, inData ); + + // synchro + COMPUTE_STAGE1_GROUP(res0, inData + 0); + COMPUTE_STAGE1_GROUP(res1, inData + 1); + COMPUTE_STAGE1_GROUP(res2, inData + 2); + COMPUTE_STAGE1_GROUP(res3, inData + 3); + + // pre-zero the three cache lines we'll overwrite + // in the next iteration + __dcbz128( 384, outData ); + __dcbz128( 512, outData ); + __dcbz128( 640, outData ); + + + COMPUTE_STAGE2_GROUP(res0, inData + 0); + COMPUTE_STAGE1_GROUP(res4, inData + 4); + COMPUTE_STAGE2_GROUP(res1, inData + 1); + COMPUTE_STAGE1_GROUP(res5, inData + 5); + COMPUTE_STAGE2_GROUP(res2, inData + 2); + COMPUTE_STAGE1_GROUP(res6, inData + 6); + COMPUTE_STAGE2_GROUP(res3, inData + 3); + COMPUTE_STAGE1_GROUP(res7, inData + 7); + + COMPUTE_STAGE3_GROUP(res0, inData + 0); + COMPUTE_STAGE2_GROUP(res4, inData + 4); + COMPUTE_STAGE3_GROUP(res1, inData + 1); + COMPUTE_STAGE2_GROUP(res5, inData + 5); + COMPUTE_STAGE3_GROUP(res2, inData + 2); + COMPUTE_STAGE2_GROUP(res6, inData + 6); + COMPUTE_STAGE3_GROUP(res3, inData + 3); + COMPUTE_STAGE2_GROUP(res7, inData + 7); + + COMPUTE_STAGE3_GROUP(res4, inData + 4); + WRITE_GROUP( outData + 0, res0 ); + COMPUTE_STAGE3_GROUP(res5, inData + 5); + WRITE_GROUP( outData + 1, res1 ); + COMPUTE_STAGE3_GROUP(res6, inData + 6); + WRITE_GROUP( outData + 2, res2 ); + COMPUTE_STAGE3_GROUP(res7, inData + 7); + WRITE_GROUP( outData + 3, res3 ); + + + WRITE_GROUP( outData + 4, res4 ); + WRITE_GROUP( outData + 5, res5 ); + WRITE_GROUP( outData + 6, res6 ); + WRITE_GROUP( outData + 7, res7 ); + + inData += 8; + outData += 8; + } + } + else if (numVectors == 16) + { + // zero out the exactly six cache lines we will write into + __dcbz128( 0, outData ); + __dcbz128( 128, outData ); + __dcbz128( 256, outData ); + __dcbz128( 384, outData ); + __dcbz128( 512, outData ); + __dcbz128( 640, outData ); + } + else if (numVectors == 8) + { + // zero out the exactly three cache lines we will write into + __dcbz128( 0, outData ); + __dcbz128( 128, outData ); + __dcbz128( 256, outData ); + } + else + { + AssertMsg(false, "Can't happen!"); + } + + // deal with the ultimate two groups (or, if we were fed + // less than 16 groups, the whole shebang) + STOP = pVectors + numVectors - 16; + + + // Use techniques of loop scheduling to eliminate data hazards; process + // eight groups simultaneously so that we never have any operations stalling + // waiting for data. + // Note: this loop, while pretty fast, could be faster still -- you'll notice + // that it does all of its loads, then all computation, then writes everything + // out. If made truly cyclic, such that every line interleaved a stage 1, stage 2, + // stage 3, and write, then throughput could be higher (probably by about 50%). + while (inData < STOP) + { + // synchro + COMPUTE_STAGE1_GROUP(res0, inData + 0); + COMPUTE_STAGE1_GROUP(res1, inData + 1); + COMPUTE_STAGE1_GROUP(res2, inData + 2); + COMPUTE_STAGE1_GROUP(res3, inData + 3); + + COMPUTE_STAGE2_GROUP(res0, inData + 0); + COMPUTE_STAGE1_GROUP(res4, inData + 4); + COMPUTE_STAGE2_GROUP(res1, inData + 1); + COMPUTE_STAGE1_GROUP(res5, inData + 5); + COMPUTE_STAGE2_GROUP(res2, inData + 2); + COMPUTE_STAGE1_GROUP(res6, inData + 6); + COMPUTE_STAGE2_GROUP(res3, inData + 3); + COMPUTE_STAGE1_GROUP(res7, inData + 7); + + COMPUTE_STAGE3_GROUP(res0, inData + 0); + COMPUTE_STAGE2_GROUP(res4, inData + 4); + COMPUTE_STAGE3_GROUP(res1, inData + 1); + COMPUTE_STAGE2_GROUP(res5, inData + 5); + COMPUTE_STAGE3_GROUP(res2, inData + 2); + COMPUTE_STAGE2_GROUP(res6, inData + 6); + COMPUTE_STAGE3_GROUP(res3, inData + 3); + COMPUTE_STAGE2_GROUP(res7, inData + 7); + + COMPUTE_STAGE3_GROUP(res4, inData + 4); + WRITE_GROUP( outData + 0, res0 ); + COMPUTE_STAGE3_GROUP(res5, inData + 5); + WRITE_GROUP( outData + 1, res1 ); + COMPUTE_STAGE3_GROUP(res6, inData + 6); + WRITE_GROUP( outData + 2, res2 ); + COMPUTE_STAGE3_GROUP(res7, inData + 7); + WRITE_GROUP( outData + 3, res3 ); + + + WRITE_GROUP( outData + 4, res4 ); + WRITE_GROUP( outData + 5, res5 ); + WRITE_GROUP( outData + 6, res6 ); + WRITE_GROUP( outData + 7, res7 ); + + inData += 8; + outData += 8; + } + + +#undef COMPUTE +#undef WRITE +#undef COMPUTE_STAGE1_ROW +#undef COMPUTE_STAGE2_ROW +#undef COMPUTE_STAGE3_ROW +#undef COMPUTE_STAGE1_GROUP +#undef COMPUTE_STAGE2_GROUP +#undef COMPUTE_STAGE3_GROUP +#undef COMPUTE_GROUP +#undef WRITE_GROUP +} +#endif + +// Transform a long array of FourVectors by a given matrix. +void FourVectors::TransformManyBy(FourVectors * RESTRICT pVectors, unsigned int numVectors, const matrix3x4_t& rotationMatrix, FourVectors * RESTRICT pOut ) +{ + Assert(numVectors > 0); + + AssertMsg( (pOut < pVectors && pOut+numVectors <= pVectors) || + (pOut > pVectors && pVectors+numVectors <= pOut), "FourVectors::TransformManyBy called with overlapping buffer pointers." ); + +#ifdef _X360 + // The really fast version of this function likes to operate on blocks of eight. So, chug through + // groups of eight, then deal with any leftovers. + int numVectorsRoundedToNearestEight = numVectors & (~0x07); + if (numVectors >= 8) + { + // aligned? + if ((reinterpret_cast(pVectors) & 127) == 0 && (reinterpret_cast(pOut) & 127) == 0) + { + FourVectors_TransformManyGroupsOfEightBy_128byteAligned(pVectors, numVectorsRoundedToNearestEight, rotationMatrix, pOut); + } + else + { + FourVectors_TransformManyGroupsOfEightBy(pVectors, numVectorsRoundedToNearestEight, rotationMatrix, pOut); + } + numVectors -= numVectorsRoundedToNearestEight; + pVectors += numVectorsRoundedToNearestEight; + pOut += numVectorsRoundedToNearestEight; + } +#endif + + // any left over? + if (numVectors > 0) + { + + // Splat out each of the entries in the matrix to a fltx4. Do this + // in the order that we will need them, to hide latency. I'm + // avoiding making an array of them, so that they'll remain in + // registers. + fltx4 matSplat00, matSplat01, matSplat02, matSplat03, // TWELVE REGISTERS + matSplat10, matSplat11, matSplat12, matSplat13, + matSplat20, matSplat21, matSplat22, matSplat23; + + { + // Load the matrix into local vectors. Sadly, matrix3x4_ts are + // often unaligned. The w components will be the transpose row of + // the matrix. + fltx4 matCol0 = LoadUnalignedSIMD(rotationMatrix[0]); + fltx4 matCol1 = LoadUnalignedSIMD(rotationMatrix[1]); + fltx4 matCol2 = LoadUnalignedSIMD(rotationMatrix[2]); + + matSplat00 = SplatXSIMD(matCol0); + matSplat01 = SplatYSIMD(matCol0); + matSplat02 = SplatZSIMD(matCol0); + matSplat03 = SplatWSIMD(matCol0); + + matSplat10 = SplatXSIMD(matCol1); + matSplat11 = SplatYSIMD(matCol1); + matSplat12 = SplatZSIMD(matCol1); + matSplat13 = SplatWSIMD(matCol1); + + matSplat20 = SplatXSIMD(matCol2); + matSplat21 = SplatYSIMD(matCol2); + matSplat22 = SplatZSIMD(matCol2); + matSplat23 = SplatWSIMD(matCol2); + } + + do + { + // Trust in the compiler to schedule these operations correctly: + pOut->x = MaddSIMD(pVectors->z, matSplat02, MaddSIMD(pVectors->y, matSplat01, MaddSIMD(pVectors->x, matSplat00, matSplat03))); + pOut->y = MaddSIMD(pVectors->z, matSplat12, MaddSIMD(pVectors->y, matSplat11, MaddSIMD(pVectors->x, matSplat00, matSplat13))); + pOut->z = MaddSIMD(pVectors->z, matSplat22, MaddSIMD(pVectors->y, matSplat21, MaddSIMD(pVectors->x, matSplat00, matSplat23))); + + ++pOut; + ++pVectors; + --numVectors; + } while(numVectors > 0); + } +} + +#ifdef _X360 +// Loop-scheduled code to process FourVectors in groups of eight quite efficiently. +static void FourVectors_TransformManyGroupsOfEightBy_InPlace(FourVectors * RESTRICT pVectors, unsigned int numVectors, const matrix3x4_t& rotationMatrix ) +{ + Assert(numVectors > 0); + if ( numVectors == 0 ) + return; + + // Prefetch line 1 and 2 + __dcbt(0,pVectors); + __dcbt(128,pVectors); + + // Splat out each of the entries in the matrix to a fltx4. Do this + // in the order that we will need them, to hide latency. I'm + // avoiding making an array of them, so that they'll remain in + // registers. + fltx4 matSplat00, matSplat01, matSplat02, matSplat03, // TWELVE REGISTERS + matSplat10, matSplat11, matSplat12, matSplat13, + matSplat20, matSplat21, matSplat22, matSplat23; + + { + // Load the matrix into local vectors. Sadly, matrix3x4_ts are + // often unaligned. The w components will be the tranpose row of + // the matrix. + fltx4 matCol0 = LoadUnalignedSIMD(rotationMatrix[0]); + fltx4 matCol1 = LoadUnalignedSIMD(rotationMatrix[1]); + fltx4 matCol2 = LoadUnalignedSIMD(rotationMatrix[2]); + + matSplat00 = SplatXSIMD(matCol0); + matSplat01 = SplatYSIMD(matCol0); + matSplat02 = SplatZSIMD(matCol0); + matSplat03 = SplatWSIMD(matCol0); + + matSplat10 = SplatXSIMD(matCol1); + matSplat11 = SplatYSIMD(matCol1); + matSplat12 = SplatZSIMD(matCol1); + matSplat13 = SplatWSIMD(matCol1); + + matSplat20 = SplatXSIMD(matCol2); + matSplat21 = SplatYSIMD(matCol2); + matSplat22 = SplatZSIMD(matCol2); + matSplat23 = SplatWSIMD(matCol2); + } + + // this macro defines how to compute a specific row from an input and certain splat columns +#define COMPUTE(res, invec, xterm, yterm, zterm, transterm) res = AddSIMD( AddSIMD( MulSIMD((invec)->z, zterm), AddSIMD( MulSIMD( (invec)->x, xterm ), MulSIMD( (invec)->y, yterm ) ) ), transterm ) +#define WRITE(term, reg, toptr) toptr->term = reg + + // define result groups (we're going to have an eight-way unroll) + + fltx4 res0X, res0Y, res0Z, res0XTemp, res0YTemp, res0ZTemp; // 48 REGISTERS + fltx4 res1X, res1Y, res1Z, res1XTemp, res1YTemp, res1ZTemp; + fltx4 res2X, res2Y, res2Z, res2XTemp, res2YTemp, res2ZTemp; + fltx4 res3X, res3Y, res3Z, res3XTemp, res3YTemp, res3ZTemp; + fltx4 res4X, res4Y, res4Z, res4XTemp, res4YTemp, res4ZTemp; + fltx4 res5X, res5Y, res5Z, res5XTemp, res5YTemp, res5ZTemp; + fltx4 res6X, res6Y, res6Z, res6XTemp, res6YTemp, res6ZTemp; + fltx4 res7X, res7Y, res7Z, res7XTemp, res7YTemp, res7ZTemp; + + + // #define FROZ(out,in,offset) COMPUTE((out+offset)->x, (in + offset), matSplat00, matSplat01, matSplat02, matSplat03); COMPUTE((out + offset )->y, (in + offset), matSplat10, matSplat11, matSplat12, matSplat13); COMPUTE((out + offset)->z, (in + offset), matSplat20, matSplat21, matSplat22, matSplat23) +#define COMPUTE_GROUP(resgroup,dataptr) COMPUTE(resgroup ## X, (dataptr), matSplat00, matSplat01, matSplat02, matSplat03); COMPUTE(resgroup ## Y, (dataptr), matSplat10, matSplat11, matSplat12, matSplat13); COMPUTE(resgroup ## Z, (dataptr), matSplat20, matSplat21, matSplat22, matSplat23) +#define WRITE_GROUP(ptr, resgroup) (ptr)->x = resgroup ## X; (ptr)->y = resgroup ## Y; (ptr)->z = resgroup ## Z + + /* + // stage 1 -- 6 ops for xyz, each w 12 cycle latency + res0X = MulSIMD( (invec)->y, matSplat01 ); + res0Temp = MaddSIMD((invec)->z, matSplat02, matSplat03); + // stage 2 -- 3 clocks for xyz + res0X = MaddSIMD( (invec)->x, matSplat00, res0X ); + // stage 3 -- 3 clocks for xyz + res0X = AddSIMD(res0X, res0Temp); + */ +#define COMPUTE_STAGE1_ROW(res, tempvar, invec, xsplat, ysplat, zsplat, transplat) res = MulSIMD( (invec)->y, ysplat ); tempvar = MaddSIMD((invec)->z, zsplat, transplat) +#define COMPUTE_STAGE2_ROW(res, tempvar, invec, xsplat, ysplat, zsplat, transplat) res = MaddSIMD( (invec)->x, xsplat, res ) +#define COMPUTE_STAGE3_ROW(res, tempvar, invec, xsplat, ysplat, zsplat, transplat) res = AddSIMD(res, tempvar) // frees up the tempvar + +#define COMPUTE_STAGE1_GROUP(resgroup, invec) COMPUTE_STAGE1_ROW(resgroup ## X, resgroup ## X ## Temp, invec, matSplat00, matSplat01, matSplat02, matSplat03);\ + COMPUTE_STAGE1_ROW(resgroup ## Y, resgroup ## Y ## Temp, invec, matSplat10, matSplat11, matSplat12, matSplat13);\ + COMPUTE_STAGE1_ROW(resgroup ## Z, resgroup ## Z ## Temp, invec, matSplat20, matSplat21, matSplat22, matSplat23) + +#define COMPUTE_STAGE2_GROUP(resgroup, invec) COMPUTE_STAGE2_ROW(resgroup ## X, resgroup ## X ## Temp, invec, matSplat00, matSplat01, matSplat02, matSplat03);\ + COMPUTE_STAGE2_ROW(resgroup ## Y, resgroup ## Y ## Temp, invec, matSplat10, matSplat11, matSplat12, matSplat13);\ + COMPUTE_STAGE2_ROW(resgroup ## Z, resgroup ## Z ## Temp, invec, matSplat20, matSplat21, matSplat22, matSplat23) + +#define COMPUTE_STAGE3_GROUP(resgroup, invec) COMPUTE_STAGE3_ROW(resgroup ## X, resgroup ## X ## Temp, invec, matSplat00, matSplat01, matSplat02, matSplat03);\ + COMPUTE_STAGE3_ROW(resgroup ## Y, resgroup ## Y ## Temp, invec, matSplat10, matSplat11, matSplat12, matSplat13);\ + COMPUTE_STAGE3_ROW(resgroup ## Z, resgroup ## Z ## Temp, invec, matSplat20, matSplat21, matSplat22, matSplat23) + + const FourVectors * const RESTRICT STOP = pVectors + numVectors; + + // Use techniques of loop scheduling to eliminate data hazards; process + // eight groups simultaneously so that we never have any operations stalling + // waiting for data. + // Note: this loop, while pretty fast, could be faster still -- you'll notice + // that it does all of its loads, then all computation, then writes everything + // out. If made truly cyclic, such that every line interleaved a stage 1, stage 2, + // stage 3, and write, then throughput could be higher (probably by about 50%). + while (pVectors < STOP) + { + // start prefetching the three cache lines + // we'll hit two iterations from now + __dcbt( sizeof(FourVectors) * 16, pVectors ); + __dcbt( sizeof(FourVectors) * 16 + 128, pVectors ); + __dcbt( sizeof(FourVectors) * 16 + 256, pVectors ); + + // synchro + COMPUTE_STAGE1_GROUP(res0, pVectors + 0); + COMPUTE_STAGE1_GROUP(res1, pVectors + 1); + COMPUTE_STAGE1_GROUP(res2, pVectors + 2); + COMPUTE_STAGE1_GROUP(res3, pVectors + 3); + + COMPUTE_STAGE2_GROUP(res0, pVectors + 0); + COMPUTE_STAGE1_GROUP(res4, pVectors + 4); + COMPUTE_STAGE2_GROUP(res1, pVectors + 1); + COMPUTE_STAGE1_GROUP(res5, pVectors + 5); + COMPUTE_STAGE2_GROUP(res2, pVectors + 2); + COMPUTE_STAGE1_GROUP(res6, pVectors + 6); + COMPUTE_STAGE2_GROUP(res3, pVectors + 3); + COMPUTE_STAGE1_GROUP(res7, pVectors + 7); + + COMPUTE_STAGE3_GROUP(res0, pVectors + 0); + COMPUTE_STAGE2_GROUP(res4, pVectors + 4); + COMPUTE_STAGE3_GROUP(res1, pVectors + 1); + COMPUTE_STAGE2_GROUP(res5, pVectors + 5); + COMPUTE_STAGE3_GROUP(res2, pVectors + 2); + COMPUTE_STAGE2_GROUP(res6, pVectors + 6); + COMPUTE_STAGE3_GROUP(res3, pVectors + 3); + COMPUTE_STAGE2_GROUP(res7, pVectors + 7); + + COMPUTE_STAGE3_GROUP(res4, pVectors + 4); + WRITE_GROUP( pVectors + 0, res0 ); + COMPUTE_STAGE3_GROUP(res5, pVectors + 5); + WRITE_GROUP( pVectors + 1, res1 ); + COMPUTE_STAGE3_GROUP(res6, pVectors + 6); + WRITE_GROUP( pVectors + 2, res2 ); + COMPUTE_STAGE3_GROUP(res7, pVectors + 7); + WRITE_GROUP( pVectors + 3, res3 ); + + WRITE_GROUP( pVectors + 4, res4 ); + WRITE_GROUP( pVectors + 5, res5 ); + WRITE_GROUP( pVectors + 6, res6 ); + WRITE_GROUP( pVectors + 7, res7 ); + + pVectors += 8; + } + + +#undef COMPUTE +#undef WRITE +#undef COMPUTE_STAGE1_ROW +#undef COMPUTE_STAGE2_ROW +#undef COMPUTE_STAGE3_ROW +#undef COMPUTE_STAGE1_GROUP +#undef COMPUTE_STAGE2_GROUP +#undef COMPUTE_STAGE3_GROUP +#undef COMPUTE_GROUP +#undef WRITE_GROUP +} +#endif + +// In-place version of above. It's necessary to have this, rather than just allowing pOut and pVectors +// to equal each other, because of the semantics of RESTRICT: pVectors and pOut must not be allowed +// to alias. (Simply un-restricting the pointers results in very poor scheduling.) +void FourVectors::TransformManyBy(FourVectors * RESTRICT pVectors, unsigned int numVectors, const matrix3x4_t& rotationMatrix ) +{ + Assert(numVectors > 0); + +#ifdef _X360 + // The really fast version of this function likes to operate on blocks of eight. So, chug through + // groups of eight, then deal with any leftovers. + int numVectorsRoundedToNearestEight = numVectors & (~0x07); + if (numVectors >= 8) + { + FourVectors_TransformManyGroupsOfEightBy_InPlace(pVectors, numVectorsRoundedToNearestEight, rotationMatrix); + numVectors -= numVectorsRoundedToNearestEight; + pVectors += numVectorsRoundedToNearestEight; + } +#endif + + // any left over? + if (numVectors > 0) + { + + // Splat out each of the entries in the matrix to a fltx4. Do this + // in the order that we will need them, to hide latency. I'm + // avoiding making an array of them, so that they'll remain in + // registers. + fltx4 matSplat00, matSplat01, matSplat02, matSplat03, // TWELVE REGISTERS + matSplat10, matSplat11, matSplat12, matSplat13, + matSplat20, matSplat21, matSplat22, matSplat23; + + { + // Load the matrix into local vectors. Sadly, matrix3x4_ts are + // often unaligned. The w components will be the transpose row of + // the matrix. + fltx4 matCol0 = LoadUnalignedSIMD(rotationMatrix[0]); + fltx4 matCol1 = LoadUnalignedSIMD(rotationMatrix[1]); + fltx4 matCol2 = LoadUnalignedSIMD(rotationMatrix[2]); + + matSplat00 = SplatXSIMD(matCol0); + matSplat01 = SplatYSIMD(matCol0); + matSplat02 = SplatZSIMD(matCol0); + matSplat03 = SplatWSIMD(matCol0); + + matSplat10 = SplatXSIMD(matCol1); + matSplat11 = SplatYSIMD(matCol1); + matSplat12 = SplatZSIMD(matCol1); + matSplat13 = SplatWSIMD(matCol1); + + matSplat20 = SplatXSIMD(matCol2); + matSplat21 = SplatYSIMD(matCol2); + matSplat22 = SplatZSIMD(matCol2); + matSplat23 = SplatWSIMD(matCol2); + } + + do + { + fltx4 resultX, resultY, resultZ; + // Trust in the compiler to schedule these operations correctly: + resultX = MaddSIMD(pVectors->z, matSplat02, MaddSIMD(pVectors->y, matSplat01, MaddSIMD(pVectors->x, matSplat00, matSplat03))); + resultY = MaddSIMD(pVectors->z, matSplat12, MaddSIMD(pVectors->y, matSplat11, MaddSIMD(pVectors->x, matSplat00, matSplat13))); + resultZ = MaddSIMD(pVectors->z, matSplat22, MaddSIMD(pVectors->y, matSplat21, MaddSIMD(pVectors->x, matSplat00, matSplat23))); + + pVectors->x = resultX; + pVectors->y = resultY; + pVectors->z = resultZ; + + ++pVectors; + --numVectors; + } while(numVectors > 0); + } +} + + +#endif + +// Transform many (horizontal) points in-place by a 3x4 matrix, +// here already loaded onto three fltx4 registers but not transposed. +// The points must be stored as 16-byte aligned. They are points +// and not vectors because we assume the w-component to be 1. +#ifdef _X360 +void TransformManyPointsBy(VectorAligned * RESTRICT pVectors, unsigned int numVectors, FLTX4 mRow0, FLTX4 mRow1, FLTX4 mRow2) +{ + /************************************************** + * Here is an elaborate and carefully scheduled * + * algorithm nicked from xboxmath.inl and hacked * + * up for 3x4 matrices. * + **************************************************/ + + COMPILE_TIME_ASSERT(sizeof(VectorAligned) == sizeof(XMFLOAT4)); // VectorAligned's need to be 16 bytes + + XMVECTOR R0[8], R1[8], R2[8]; + XMVECTOR vIn[8]; + + // C_ASSERT(UnrollCount == 8); + // C_ASSERT(sizeof(XMFLOAT4) == 16); + Assert(pVectors); + Assert(((UINT_PTR)pVectors & 3) == 0); // assert alignment + + UINT GroupIndex; + + VectorAligned * RESTRICT vCurrent = pVectors; + // sentinel pointers + VectorAligned * vStreamEnd, *vStreamGroupBase, *vStreamGroupEnd; + + { + // cook up the pointers from integer math. Necessary because otherwise we LHS all over + // the place. (Odd that this doesn't happen to the xbox math.) + + UINT_PTR InputVector = (UINT_PTR)pVectors; + UINT_PTR InputStreamEnd = InputVector + numVectors * sizeof(XMFLOAT4); + // compute start and end points on 128-byte alignment + UINT_PTR InputStreamCGroupBase = XMMin(InputVector + (XM_CACHE_LINE_SIZE - 1), InputStreamEnd) & ~(XM_CACHE_LINE_SIZE - 1); + UINT_PTR InputStreamCGroupEnd = InputStreamCGroupBase + ((InputStreamEnd - InputStreamCGroupBase) & ~(4 * XM_CACHE_LINE_SIZE - 1)); + + vStreamEnd = (VectorAligned *)InputStreamEnd; + vStreamGroupBase = (VectorAligned *)InputStreamCGroupBase; + vStreamGroupEnd = (VectorAligned *)InputStreamCGroupEnd; + } + + + __dcbt(0, vStreamGroupBase); + __dcbt(XM_CACHE_LINE_SIZE, vStreamGroupBase); + __dcbt(XM_CACHE_LINE_SIZE * 2, vStreamGroupBase); + __dcbt(XM_CACHE_LINE_SIZE * 3, vStreamGroupBase); + + while (vCurrent < vStreamGroupBase) + { + fltx4 vec = __lvx(vCurrent->Base(), 0); + + R0[0] = __vmsum4fp(vec, mRow0); + R1[0] = __vmsum4fp(vec, mRow1); + R2[0] = __vmsum4fp(vec, mRow2); + + __stvewx(R0[0], vCurrent->Base(), 0); + __stvewx(R1[0], vCurrent->Base(), 4); + __stvewx(R2[0], vCurrent->Base(), 8); + + vCurrent++; + } + + while (vCurrent < vStreamGroupEnd) + { + __dcbt(XM_CACHE_LINE_SIZE * 4, vCurrent); + __dcbt(XM_CACHE_LINE_SIZE * 5, vCurrent); + __dcbt(XM_CACHE_LINE_SIZE * 6, vCurrent); + __dcbt(XM_CACHE_LINE_SIZE * 7, vCurrent); + + for (GroupIndex = 0; GroupIndex < 4; GroupIndex++) + { + // all kinds of LHS on this pointer. Why? + VectorAligned* OutputVector = vCurrent; + + vIn[0] = __lvx(vCurrent->Base(), 0); + vCurrent++; + vIn[1] = __lvx(vCurrent->Base(), 0); + vCurrent++; + vIn[2] = __lvx(vCurrent->Base(), 0); + vCurrent++; + vIn[3] = __lvx(vCurrent->Base(), 0); + vCurrent++; + vIn[4] = __lvx(vCurrent->Base(), 0); + vCurrent++; + vIn[5] = __lvx(vCurrent->Base(), 0); + vCurrent++; + vIn[6] = __lvx(vCurrent->Base(), 0); + vCurrent++; + vIn[7] = __lvx(vCurrent->Base(), 0); + vCurrent++; + + R0[0] = __vmsum4fp(vIn[0], mRow0); + R1[0] = __vmsum4fp(vIn[0], mRow1); + R2[0] = __vmsum4fp(vIn[0], mRow2); + + R0[1] = __vmsum4fp(vIn[1], mRow0); + R1[1] = __vmsum4fp(vIn[1], mRow1); + R2[1] = __vmsum4fp(vIn[1], mRow2); + + R0[2] = __vmsum4fp(vIn[2], mRow0); + R1[2] = __vmsum4fp(vIn[2], mRow1); + R2[2] = __vmsum4fp(vIn[2], mRow2); + + R0[3] = __vmsum4fp(vIn[3], mRow0); + R1[3] = __vmsum4fp(vIn[3], mRow1); + R2[3] = __vmsum4fp(vIn[3], mRow2); + + R0[4] = __vmsum4fp(vIn[4], mRow0); + R1[4] = __vmsum4fp(vIn[4], mRow1); + R2[4] = __vmsum4fp(vIn[4], mRow2); + + R0[5] = __vmsum4fp(vIn[5], mRow0); + R1[5] = __vmsum4fp(vIn[5], mRow1); + R2[5] = __vmsum4fp(vIn[5], mRow2); + + R0[6] = __vmsum4fp(vIn[6], mRow0); + R1[6] = __vmsum4fp(vIn[6], mRow1); + R2[6] = __vmsum4fp(vIn[6], mRow2); + + R0[7] = __vmsum4fp(vIn[7], mRow0); + R1[7] = __vmsum4fp(vIn[7], mRow1); + R2[7] = __vmsum4fp(vIn[7], mRow2); + + __stvewx(R0[0], OutputVector, 0); + __stvewx(R1[0], OutputVector, 4); + __stvewx(R2[0], OutputVector, 8); + OutputVector++; + + __stvewx(R0[1], OutputVector, 0); + __stvewx(R1[1], OutputVector, 4); + __stvewx(R2[1], OutputVector, 8); + OutputVector++; + + __stvewx(R0[2], OutputVector, 0); + __stvewx(R1[2], OutputVector, 4); + __stvewx(R2[2], OutputVector, 8); + OutputVector++; + + __stvewx(R0[3], OutputVector, 0); + __stvewx(R1[3], OutputVector, 4); + __stvewx(R2[3], OutputVector, 8); + OutputVector++; + + __stvewx(R0[4], OutputVector, 0); + __stvewx(R1[4], OutputVector, 4); + __stvewx(R2[4], OutputVector, 8); + OutputVector++; + + __stvewx(R0[5], OutputVector, 0); + __stvewx(R1[5], OutputVector, 4); + __stvewx(R2[5], OutputVector, 8); + OutputVector++; + + __stvewx(R0[6], OutputVector, 0); + __stvewx(R1[6], OutputVector, 4); + __stvewx(R2[6], OutputVector, 8); + OutputVector++; + + __stvewx(R0[7], OutputVector, 0); + __stvewx(R1[7], OutputVector, 4); + __stvewx(R2[7], OutputVector, 8); + OutputVector++; + } + } + + while (vCurrent < vStreamEnd) + { + vIn[0] = __lvx(vCurrent->Base(), 0); + + R0[0] = __vmsum4fp(vIn[0], mRow0); + R1[0] = __vmsum4fp(vIn[0], mRow1); + R2[0] = __vmsum4fp(vIn[0], mRow2); + + __stvewx(R0[0], vCurrent->Base(), 0); + __stvewx(R1[0], vCurrent->Base(), 4); + __stvewx(R2[0], vCurrent->Base(), 8); + + vCurrent++; + } + + +} +#endif diff --git a/mathlib/ssenoise.cpp b/mathlib/ssenoise.cpp new file mode 100644 index 00000000..9aad6712 --- /dev/null +++ b/mathlib/ssenoise.cpp @@ -0,0 +1,105 @@ +//========= Copyright © 1996-2006, Valve Corporation, All rights reserved. ============// +// +// Purpose: Fast low quality noise suitable for real time use +// +//=====================================================================================// + +#include +#include // Needed for FLT_EPSILON +#include "basetypes.h" +#include +#include "tier0/dbg.h" +#include "mathlib/mathlib.h" +#include "mathlib/vector.h" +#include "mathlib/ssemath.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" +#include "noisedata.h" + + +#define MAGIC_NUMBER (1<<15) // gives 8 bits of fraction + +static fltx4 Four_MagicNumbers = { MAGIC_NUMBER, MAGIC_NUMBER, MAGIC_NUMBER, MAGIC_NUMBER }; + + +static ALIGN16 int32 idx_mask[4]= {0xffff, 0xffff, 0xffff, 0xffff}; + +#define MASK255 (*((fltx4 *)(& idx_mask ))) + +// returns 0..1 +static inline float GetLatticePointValue( int idx_x, int idx_y, int idx_z ) +{ + int ret_idx = perm_a[idx_x & 0xff]; + ret_idx = perm_b[( idx_y + ret_idx ) & 0xff]; + ret_idx = perm_c[( idx_z + ret_idx ) & 0xff]; + return impulse_xcoords[ret_idx]; + +} + +fltx4 NoiseSIMD( const fltx4 & x, const fltx4 & y, const fltx4 & z ) +{ + // use magic to convert to integer index + fltx4 x_idx = AndSIMD( MASK255, AddSIMD( x, Four_MagicNumbers ) ); + fltx4 y_idx = AndSIMD( MASK255, AddSIMD( y, Four_MagicNumbers ) ); + fltx4 z_idx = AndSIMD( MASK255, AddSIMD( z, Four_MagicNumbers ) ); + + fltx4 lattice000 = Four_Zeros, lattice001 = Four_Zeros, lattice010 = Four_Zeros, lattice011 = Four_Zeros; + fltx4 lattice100 = Four_Zeros, lattice101 = Four_Zeros, lattice110 = Four_Zeros, lattice111 = Four_Zeros; + + // FIXME: Converting the input vectors to int indices will cause load-hit-stores (48 bytes) + // Converting the indexed noise values back to vectors will cause more (128 bytes) + // The noise table could store vectors if we chunked it into 2x2x2 blocks. + fltx4 xfrac = Four_Zeros, yfrac = Four_Zeros, zfrac = Four_Zeros; +#define DOPASS(i) \ + { unsigned int xi = SubInt( x_idx, i ); \ + unsigned int yi = SubInt( y_idx, i ); \ + unsigned int zi = SubInt( z_idx, i ); \ + SubFloat( xfrac, i ) = (xi & 0xff)*(1.0/256.0); \ + SubFloat( yfrac, i ) = (yi & 0xff)*(1.0/256.0); \ + SubFloat( zfrac, i ) = (zi & 0xff)*(1.0/256.0); \ + xi>>=8; \ + yi>>=8; \ + zi>>=8; \ + \ + SubFloat( lattice000, i ) = GetLatticePointValue( xi,yi,zi ); \ + SubFloat( lattice001, i ) = GetLatticePointValue( xi,yi,zi+1 ); \ + SubFloat( lattice010, i ) = GetLatticePointValue( xi,yi+1,zi ); \ + SubFloat( lattice011, i ) = GetLatticePointValue( xi,yi+1,zi+1 ); \ + SubFloat( lattice100, i ) = GetLatticePointValue( xi+1,yi,zi ); \ + SubFloat( lattice101, i ) = GetLatticePointValue( xi+1,yi,zi+1 ); \ + SubFloat( lattice110, i ) = GetLatticePointValue( xi+1,yi+1,zi ); \ + SubFloat( lattice111, i ) = GetLatticePointValue( xi+1,yi+1,zi+1 ); \ + } + + DOPASS( 0 ); + DOPASS( 1 ); + DOPASS( 2 ); + DOPASS( 3 ); + + // now, we have 8 lattice values for each of four points as m128s, and interpolant values for + // each axis in m128 form in [xyz]frac. Perfom the trilinear interpolation as SIMD ops + + // first, do x interpolation + fltx4 l2d00 = AddSIMD( lattice000, MulSIMD( xfrac, SubSIMD( lattice100, lattice000 ) ) ); + fltx4 l2d01 = AddSIMD( lattice001, MulSIMD( xfrac, SubSIMD( lattice101, lattice001 ) ) ); + fltx4 l2d10 = AddSIMD( lattice010, MulSIMD( xfrac, SubSIMD( lattice110, lattice010 ) ) ); + fltx4 l2d11 = AddSIMD( lattice011, MulSIMD( xfrac, SubSIMD( lattice111, lattice011 ) ) ); + + // now, do y interpolation + fltx4 l1d0 = AddSIMD( l2d00, MulSIMD( yfrac, SubSIMD( l2d10, l2d00 ) ) ); + fltx4 l1d1 = AddSIMD( l2d01, MulSIMD( yfrac, SubSIMD( l2d11, l2d01 ) ) ); + + // final z interpolation + fltx4 rslt = AddSIMD( l1d0, MulSIMD( zfrac, SubSIMD( l1d1, l1d0 ) ) ); + + // map to 0..1 + return MulSIMD( Four_Twos, SubSIMD( rslt, Four_PointFives ) ); + + +} + +fltx4 NoiseSIMD( FourVectors const &pos ) +{ + return NoiseSIMD( pos.x, pos.y, pos.z ); +} diff --git a/mathlib/vector.cpp b/mathlib/vector.cpp new file mode 100644 index 00000000..318f7ec1 --- /dev/null +++ b/mathlib/vector.cpp @@ -0,0 +1,12 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "mathlib/vector.h" + +Vector vec3_origin(0,0,0); + diff --git a/mathlib/vmatrix.cpp b/mathlib/vmatrix.cpp new file mode 100644 index 00000000..e4aa096f --- /dev/null +++ b/mathlib/vmatrix.cpp @@ -0,0 +1,1263 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#if !defined(_STATIC_LINKED) || defined(_SHARED_LIB) + +#include "basetypes.h" +#include "mathlib/vmatrix.h" +#include "mathlib/mathlib.h" +#include +#include "mathlib/vector4d.h" +#include "tier0/dbg.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#ifdef _MSC_VER +#pragma warning (disable : 4700) // local variable 'x' used without having been initialized +#endif + +// ------------------------------------------------------------------------------------------- // +// Helper functions. +// ------------------------------------------------------------------------------------------- // + +#ifndef VECTOR_NO_SLOW_OPERATIONS + +VMatrix SetupMatrixIdentity() +{ + return VMatrix( + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); +} + +VMatrix SetupMatrixTranslation(const Vector &vTranslation) +{ + return VMatrix( + 1.0f, 0.0f, 0.0f, vTranslation.x, + 0.0f, 1.0f, 0.0f, vTranslation.y, + 0.0f, 0.0f, 1.0f, vTranslation.z, + 0.0f, 0.0f, 0.0f, 1.0f + ); +} + +VMatrix SetupMatrixScale(const Vector &vScale) +{ + return VMatrix( + vScale.x, 0.0f, 0.0f, 0.0f, + 0.0f, vScale.y, 0.0f, 0.0f, + 0.0f, 0.0f, vScale.z, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + ); +} + +VMatrix SetupMatrixReflection(const VPlane &thePlane) +{ + VMatrix mReflect, mBack, mForward; + Vector vOrigin, N; + + N = thePlane.m_Normal; + + mReflect.Init( + -2.0f*N.x*N.x + 1.0f, -2.0f*N.x*N.y, -2.0f*N.x*N.z, 0.0f, + -2.0f*N.y*N.x, -2.0f*N.y*N.y + 1.0f, -2.0f*N.y*N.z, 0.0f, + -2.0f*N.z*N.x, -2.0f*N.z*N.y, -2.0f*N.z*N.z + 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + ); + + vOrigin = thePlane.GetPointOnPlane(); + + mBack.Identity(); + mBack.SetTranslation(-vOrigin); + + mForward.Identity(); + mForward.SetTranslation(vOrigin); + + // (multiplied in reverse order, so it translates to the origin point, + // reflects, and translates back). + return mForward * mReflect * mBack; +} + +VMatrix SetupMatrixProjection(const Vector &vOrigin, const VPlane &thePlane) +{ + vec_t dot; + VMatrix mRet; + + + #define PN thePlane.m_Normal + #define PD thePlane.m_Dist; + + dot = PN[0]*vOrigin.x + PN[1]*vOrigin.y + PN[2]*vOrigin.z - PD; + + mRet.m[0][0] = dot - vOrigin.x * PN[0]; + mRet.m[0][1] = -vOrigin.x * PN[1]; + mRet.m[0][2] = -vOrigin.x * PN[2]; + mRet.m[0][3] = -vOrigin.x * -PD; + + mRet.m[1][0] = -vOrigin.y * PN[0]; + mRet.m[1][1] = dot - vOrigin.y * PN[1]; + mRet.m[1][2] = -vOrigin.y * PN[2]; + mRet.m[1][3] = -vOrigin.y * -PD; + + mRet.m[2][0] = -vOrigin.z * PN[0]; + mRet.m[2][1] = -vOrigin.z * PN[1]; + mRet.m[2][2] = dot - vOrigin.z * PN[2]; + mRet.m[2][3] = -vOrigin.z * -PD; + + mRet.m[3][0] = -PN[0]; + mRet.m[3][1] = -PN[1]; + mRet.m[3][2] = -PN[2]; + mRet.m[3][3] = dot + PD; + + #undef PN + #undef PD + + return mRet; +} + +VMatrix SetupMatrixAxisRot(const Vector &vAxis, vec_t fDegrees) +{ + vec_t s, c, t; + vec_t tx, ty, tz; + vec_t sx, sy, sz; + vec_t fRadians; + + + fRadians = fDegrees * (M_PI / 180.0f); + + s = (vec_t)sin(fRadians); + c = (vec_t)cos(fRadians); + t = 1.0f - c; + + tx = t * vAxis.x; ty = t * vAxis.y; tz = t * vAxis.z; + sx = s * vAxis.x; sy = s * vAxis.y; sz = s * vAxis.z; + + return VMatrix( + tx*vAxis.x + c, tx*vAxis.y - sz, tx*vAxis.z + sy, 0.0f, + tx*vAxis.y + sz, ty*vAxis.y + c, ty*vAxis.z - sx, 0.0f, + tx*vAxis.z - sy, ty*vAxis.z + sx, tz*vAxis.z + c, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); +} + +VMatrix SetupMatrixAngles(const QAngle &vAngles) +{ + VMatrix mRet; + MatrixFromAngles( vAngles, mRet ); + return mRet; +} + +VMatrix SetupMatrixOrgAngles(const Vector &origin, const QAngle &vAngles) +{ + VMatrix mRet; + mRet.SetupMatrixOrgAngles( origin, vAngles ); + return mRet; +} + +#endif // VECTOR_NO_SLOW_OPERATIONS + + +bool PlaneIntersection( const VPlane &vp1, const VPlane &vp2, const VPlane &vp3, Vector &vOut ) +{ + VMatrix mMat, mInverse; + + mMat.Init( + vp1.m_Normal.x, vp1.m_Normal.y, vp1.m_Normal.z, -vp1.m_Dist, + vp2.m_Normal.x, vp2.m_Normal.y, vp2.m_Normal.z, -vp2.m_Dist, + vp3.m_Normal.x, vp3.m_Normal.y, vp3.m_Normal.z, -vp3.m_Dist, + 0.0f, 0.0f, 0.0f, 1.0f + ); + + if(mMat.InverseGeneral(mInverse)) + { + //vOut = mInverse * Vector(0.0f, 0.0f, 0.0f); + mInverse.GetTranslation( vOut ); + return true; + } + else + { + return false; + } +} + + + +// ------------------------------------------------------------------------------------------- // +// VMatrix functions. +// ------------------------------------------------------------------------------------------- // + +VMatrix& VMatrix::operator=(const VMatrix &mOther) +{ + m[0][0] = mOther.m[0][0]; + m[0][1] = mOther.m[0][1]; + m[0][2] = mOther.m[0][2]; + m[0][3] = mOther.m[0][3]; + + m[1][0] = mOther.m[1][0]; + m[1][1] = mOther.m[1][1]; + m[1][2] = mOther.m[1][2]; + m[1][3] = mOther.m[1][3]; + + m[2][0] = mOther.m[2][0]; + m[2][1] = mOther.m[2][1]; + m[2][2] = mOther.m[2][2]; + m[2][3] = mOther.m[2][3]; + + m[3][0] = mOther.m[3][0]; + m[3][1] = mOther.m[3][1]; + m[3][2] = mOther.m[3][2]; + m[3][3] = mOther.m[3][3]; + + return *this; +} + +bool VMatrix::operator==( const VMatrix& src ) const +{ + return !memcmp( src.m, m, sizeof(m) ); +} + +void VMatrix::MatrixMul( const VMatrix &vm, VMatrix &out ) const +{ + out.Init( + m[0][0]*vm.m[0][0] + m[0][1]*vm.m[1][0] + m[0][2]*vm.m[2][0] + m[0][3]*vm.m[3][0], + m[0][0]*vm.m[0][1] + m[0][1]*vm.m[1][1] + m[0][2]*vm.m[2][1] + m[0][3]*vm.m[3][1], + m[0][0]*vm.m[0][2] + m[0][1]*vm.m[1][2] + m[0][2]*vm.m[2][2] + m[0][3]*vm.m[3][2], + m[0][0]*vm.m[0][3] + m[0][1]*vm.m[1][3] + m[0][2]*vm.m[2][3] + m[0][3]*vm.m[3][3], + + m[1][0]*vm.m[0][0] + m[1][1]*vm.m[1][0] + m[1][2]*vm.m[2][0] + m[1][3]*vm.m[3][0], + m[1][0]*vm.m[0][1] + m[1][1]*vm.m[1][1] + m[1][2]*vm.m[2][1] + m[1][3]*vm.m[3][1], + m[1][0]*vm.m[0][2] + m[1][1]*vm.m[1][2] + m[1][2]*vm.m[2][2] + m[1][3]*vm.m[3][2], + m[1][0]*vm.m[0][3] + m[1][1]*vm.m[1][3] + m[1][2]*vm.m[2][3] + m[1][3]*vm.m[3][3], + + m[2][0]*vm.m[0][0] + m[2][1]*vm.m[1][0] + m[2][2]*vm.m[2][0] + m[2][3]*vm.m[3][0], + m[2][0]*vm.m[0][1] + m[2][1]*vm.m[1][1] + m[2][2]*vm.m[2][1] + m[2][3]*vm.m[3][1], + m[2][0]*vm.m[0][2] + m[2][1]*vm.m[1][2] + m[2][2]*vm.m[2][2] + m[2][3]*vm.m[3][2], + m[2][0]*vm.m[0][3] + m[2][1]*vm.m[1][3] + m[2][2]*vm.m[2][3] + m[2][3]*vm.m[3][3], + + m[3][0]*vm.m[0][0] + m[3][1]*vm.m[1][0] + m[3][2]*vm.m[2][0] + m[3][3]*vm.m[3][0], + m[3][0]*vm.m[0][1] + m[3][1]*vm.m[1][1] + m[3][2]*vm.m[2][1] + m[3][3]*vm.m[3][1], + m[3][0]*vm.m[0][2] + m[3][1]*vm.m[1][2] + m[3][2]*vm.m[2][2] + m[3][3]*vm.m[3][2], + m[3][0]*vm.m[0][3] + m[3][1]*vm.m[1][3] + m[3][2]*vm.m[2][3] + m[3][3]*vm.m[3][3] + ); +} + +#ifndef VECTOR_NO_SLOW_OPERATIONS + +VMatrix VMatrix::operator*(const VMatrix &vm) const +{ + VMatrix ret; + MatrixMul( vm, ret ); + return ret; +} + +#endif + +bool VMatrix::InverseGeneral(VMatrix &vInverse) const +{ + return MatrixInverseGeneral( *this, vInverse ); +} + + +bool MatrixInverseGeneral(const VMatrix& src, VMatrix& dst) +{ + int iRow, i, j, iTemp, iTest; + vec_t mul, fTest, fLargest; + vec_t mat[4][8]; + int rowMap[4], iLargest; + vec_t *pOut, *pRow, *pScaleRow; + + + // How it's done. + // AX = I + // A = this + // X = the matrix we're looking for + // I = identity + + // Setup AI + for(i=0; i < 4; i++) + { + const vec_t *pIn = src[i]; + pOut = mat[i]; + + for(j=0; j < 4; j++) + { + pOut[j] = pIn[j]; + } + + pOut[4] = 0.0f; + pOut[5] = 0.0f; + pOut[6] = 0.0f; + pOut[7] = 0.0f; + pOut[i+4] = 1.0f; + + rowMap[i] = i; + } + + // Use row operations to get to reduced row-echelon form using these rules: + // 1. Multiply or divide a row by a nonzero number. + // 2. Add a multiple of one row to another. + // 3. Interchange two rows. + + for(iRow=0; iRow < 4; iRow++) + { + // Find the row with the largest element in this column. + fLargest = 0.001f; + iLargest = -1; + for(iTest=iRow; iTest < 4; iTest++) + { + fTest = (vec_t)FloatMakePositive(mat[rowMap[iTest]][iRow]); + if(fTest > fLargest) + { + iLargest = iTest; + fLargest = fTest; + } + } + + // They're all too small.. sorry. + if(iLargest == -1) + { + return false; + } + + // Swap the rows. + iTemp = rowMap[iLargest]; + rowMap[iLargest] = rowMap[iRow]; + rowMap[iRow] = iTemp; + + pRow = mat[rowMap[iRow]]; + + // Divide this row by the element. + mul = 1.0f / pRow[iRow]; + for(j=0; j < 8; j++) + pRow[j] *= mul; + + pRow[iRow] = 1.0f; // Preserve accuracy... + + // Eliminate this element from the other rows using operation 2. + for(i=0; i < 4; i++) + { + if(i == iRow) + continue; + + pScaleRow = mat[rowMap[i]]; + + // Multiply this row by -(iRow*the element). + mul = -pScaleRow[iRow]; + for(j=0; j < 8; j++) + { + pScaleRow[j] += pRow[j] * mul; + } + + pScaleRow[iRow] = 0.0f; // Preserve accuracy... + } + } + + // The inverse is on the right side of AX now (the identity is on the left). + for(i=0; i < 4; i++) + { + const vec_t *pIn = mat[rowMap[i]] + 4; + pOut = dst.m[i]; + + for(j=0; j < 4; j++) + { + pOut[j] = pIn[j]; + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Does a fast inverse, assuming the matrix only contains translation and rotation. +//----------------------------------------------------------------------------- +void MatrixInverseTR( const VMatrix& src, VMatrix &dst ) +{ + Vector vTrans, vNewTrans; + + // Transpose the upper 3x3. + dst.m[0][0] = src.m[0][0]; dst.m[0][1] = src.m[1][0]; dst.m[0][2] = src.m[2][0]; + dst.m[1][0] = src.m[0][1]; dst.m[1][1] = src.m[1][1]; dst.m[1][2] = src.m[2][1]; + dst.m[2][0] = src.m[0][2]; dst.m[2][1] = src.m[1][2]; dst.m[2][2] = src.m[2][2]; + + // Transform the translation. + vTrans.Init( -src.m[0][3], -src.m[1][3], -src.m[2][3] ); + Vector3DMultiply( dst, vTrans, vNewTrans ); + MatrixSetColumn( dst, 3, vNewTrans ); + + // Fill in the bottom row. + dst.m[3][0] = dst.m[3][1] = dst.m[3][2] = 0.0f; + dst.m[3][3] = 1.0f; +} + + +void VMatrix::InverseTR( VMatrix &ret ) const +{ + MatrixInverseTR( *this, ret ); +} + +void MatrixInverseTranspose( const VMatrix& src, VMatrix& dst ) +{ + src.InverseGeneral( dst ); + MatrixTranspose( dst, dst ); +} + +//----------------------------------------------------------------------------- +// Computes the inverse transpose +//----------------------------------------------------------------------------- +void MatrixInverseTranspose( const matrix3x4_t& src, matrix3x4_t& dst ) +{ + VMatrix tmp, out; + tmp.CopyFrom3x4( src ); + ::MatrixInverseTranspose( tmp, out ); + out.Set3x4( dst ); +} + + +#ifndef VECTOR_NO_SLOW_OPERATIONS + +VMatrix VMatrix::InverseTR() const +{ + VMatrix ret; + MatrixInverseTR( *this, ret ); + return ret; +} + +Vector VMatrix::GetScale() const +{ + Vector vecs[3]; + + GetBasisVectors(vecs[0], vecs[1], vecs[2]); + + return Vector( + vecs[0].Length(), + vecs[1].Length(), + vecs[2].Length() + ); +} + +VMatrix VMatrix::Scale(const Vector &vScale) +{ + return VMatrix( + m[0][0]*vScale.x, m[0][1]*vScale.y, m[0][2]*vScale.z, m[0][3], + m[1][0]*vScale.x, m[1][1]*vScale.y, m[1][2]*vScale.z, m[1][3], + m[2][0]*vScale.x, m[2][1]*vScale.y, m[2][2]*vScale.z, m[2][3], + m[3][0]*vScale.x, m[3][1]*vScale.y, m[3][2]*vScale.z, 1.0f + ); +} + +VMatrix VMatrix::NormalizeBasisVectors() const +{ + Vector vecs[3]; + VMatrix mRet; + + + GetBasisVectors(vecs[0], vecs[1], vecs[2]); + + VectorNormalize( vecs[0] ); + VectorNormalize( vecs[1] ); + VectorNormalize( vecs[2] ); + + mRet.SetBasisVectors(vecs[0], vecs[1], vecs[2]); + + // Set everything but basis vectors to identity. + mRet.m[3][0] = mRet.m[3][1] = mRet.m[3][2] = 0.0f; + mRet.m[3][3] = 1.0f; + + return mRet; +} + +VMatrix VMatrix::Transpose() const +{ + return VMatrix( + m[0][0], m[1][0], m[2][0], m[3][0], + m[0][1], m[1][1], m[2][1], m[3][1], + m[0][2], m[1][2], m[2][2], m[3][2], + m[0][3], m[1][3], m[2][3], m[3][3]); +} + +// Transpose upper-left 3x3. +VMatrix VMatrix::Transpose3x3() const +{ + return VMatrix( + m[0][0], m[1][0], m[2][0], m[0][3], + m[0][1], m[1][1], m[2][1], m[1][3], + m[0][2], m[1][2], m[2][2], m[2][3], + m[3][0], m[3][1], m[3][2], m[3][3]); +} + +#endif // VECTOR_NO_SLOW_OPERATIONS + + +bool VMatrix::IsRotationMatrix() const +{ + Vector &v1 = (Vector&)m[0][0]; + Vector &v2 = (Vector&)m[1][0]; + Vector &v3 = (Vector&)m[2][0]; + + return + FloatMakePositive( 1 - v1.Length() ) < 0.01f && + FloatMakePositive( 1 - v2.Length() ) < 0.01f && + FloatMakePositive( 1 - v3.Length() ) < 0.01f && + FloatMakePositive( v1.Dot(v2) ) < 0.01f && + FloatMakePositive( v1.Dot(v3) ) < 0.01f && + FloatMakePositive( v2.Dot(v3) ) < 0.01f; +} + +void VMatrix::SetupMatrixOrgAngles( const Vector &origin, const QAngle &vAngles ) +{ + float sr, sp, sy, cr, cp, cy; + + SinCos( DEG2RAD( vAngles[YAW] ), &sy, &cy ); + SinCos( DEG2RAD( vAngles[PITCH] ), &sp, &cp ); + SinCos( DEG2RAD( vAngles[ROLL] ), &sr, &cr ); + + // matrix = (YAW * PITCH) * ROLL + m[0][0] = cp*cy; + m[1][0] = cp*sy; + m[2][0] = -sp; + m[0][1] = sr*sp*cy+cr*-sy; + m[1][1] = sr*sp*sy+cr*cy; + m[2][1] = sr*cp; + m[0][2] = (cr*sp*cy+-sr*-sy); + m[1][2] = (cr*sp*sy+-sr*cy); + m[2][2] = cr*cp; + m[0][3] = 0.f; + m[1][3] = 0.f; + m[2][3] = 0.f; + + // Add translation + m[0][3] = origin.x; + m[1][3] = origin.y; + m[2][3] = origin.z; + m[3][0] = 0.0f; + m[3][1] = 0.0f; + m[3][2] = 0.0f; + m[3][3] = 1.0f; +} + + +//----------------------------------------------------------------------------- +// Sets matrix to identity +//----------------------------------------------------------------------------- +void MatrixSetIdentity( VMatrix &dst ) +{ + dst[0][0] = 1.0f; dst[0][1] = 0.0f; dst[0][2] = 0.0f; dst[0][3] = 0.0f; + dst[1][0] = 0.0f; dst[1][1] = 1.0f; dst[1][2] = 0.0f; dst[1][3] = 0.0f; + dst[2][0] = 0.0f; dst[2][1] = 0.0f; dst[2][2] = 1.0f; dst[2][3] = 0.0f; + dst[3][0] = 0.0f; dst[3][1] = 0.0f; dst[3][2] = 0.0f; dst[3][3] = 1.0f; +} + + +//----------------------------------------------------------------------------- +// Setup a matrix from euler angles. +//----------------------------------------------------------------------------- +void MatrixFromAngles( const QAngle& vAngles, VMatrix& dst ) +{ + dst.SetupMatrixOrgAngles( vec3_origin, vAngles ); +} + + +//----------------------------------------------------------------------------- +// Creates euler angles from a matrix +//----------------------------------------------------------------------------- +void MatrixToAngles( const VMatrix& src, QAngle& vAngles ) +{ + float forward[3]; + float left[3]; + float up[3]; + + // Extract the basis vectors from the matrix. Since we only need the Z + // component of the up vector, we don't get X and Y. + forward[0] = src[0][0]; + forward[1] = src[1][0]; + forward[2] = src[2][0]; + left[0] = src[0][1]; + left[1] = src[1][1]; + left[2] = src[2][1]; + up[2] = src[2][2]; + + float xyDist = sqrtf( forward[0] * forward[0] + forward[1] * forward[1] ); + + // enough here to get angles? + if ( xyDist > 0.001f ) + { + // (yaw) y = ATAN( forward.y, forward.x ); -- in our space, forward is the X axis + vAngles[1] = RAD2DEG( atan2f( forward[1], forward[0] ) ); + + // The engine does pitch inverted from this, but we always end up negating it in the DLL + // UNDONE: Fix the engine to make it consistent + // (pitch) x = ATAN( -forward.z, sqrt(forward.x*forward.x+forward.y*forward.y) ); + vAngles[0] = RAD2DEG( atan2f( -forward[2], xyDist ) ); + + // (roll) z = ATAN( left.z, up.z ); + vAngles[2] = RAD2DEG( atan2f( left[2], up[2] ) ); + } + else // forward is mostly Z, gimbal lock- + { + // (yaw) y = ATAN( -left.x, left.y ); -- forward is mostly z, so use right for yaw + vAngles[1] = RAD2DEG( atan2f( -left[0], left[1] ) ); + + // The engine does pitch inverted from this, but we always end up negating it in the DLL + // UNDONE: Fix the engine to make it consistent + // (pitch) x = ATAN( -forward.z, sqrt(forward.x*forward.x+forward.y*forward.y) ); + vAngles[0] = RAD2DEG( atan2f( -forward[2], xyDist ) ); + + // Assume no roll in this case as one degree of freedom has been lost (i.e. yaw == roll) + vAngles[2] = 0; + } +} + + +//----------------------------------------------------------------------------- +// Transpose +//----------------------------------------------------------------------------- +inline void Swap( float& a, float& b ) +{ + float tmp = a; + a = b; + b = tmp; +} + +void MatrixTranspose( const VMatrix& src, VMatrix& dst ) +{ + if (&src == &dst) + { + Swap( dst[0][1], dst[1][0] ); + Swap( dst[0][2], dst[2][0] ); + Swap( dst[0][3], dst[3][0] ); + Swap( dst[1][2], dst[2][1] ); + Swap( dst[1][3], dst[3][1] ); + Swap( dst[2][3], dst[3][2] ); + } + else + { + dst[0][0] = src[0][0]; dst[0][1] = src[1][0]; dst[0][2] = src[2][0]; dst[0][3] = src[3][0]; + dst[1][0] = src[0][1]; dst[1][1] = src[1][1]; dst[1][2] = src[2][1]; dst[1][3] = src[3][1]; + dst[2][0] = src[0][2]; dst[2][1] = src[1][2]; dst[2][2] = src[2][2]; dst[2][3] = src[3][2]; + dst[3][0] = src[0][3]; dst[3][1] = src[1][3]; dst[3][2] = src[2][3]; dst[3][3] = src[3][3]; + } +} + + +//----------------------------------------------------------------------------- +// Matrix copy +//----------------------------------------------------------------------------- + +void MatrixCopy( const VMatrix& src, VMatrix& dst ) +{ + if (&src != &dst) + { + memcpy( dst.m, src.m, 16 * sizeof(float) ); + } +} + +//----------------------------------------------------------------------------- +// Matrix multiply +//----------------------------------------------------------------------------- +typedef float VMatrixRaw_t[4]; + +void MatrixMultiply( const VMatrix& src1, const VMatrix& src2, VMatrix& dst ) +{ + // Make sure it works if src1 == dst or src2 == dst + VMatrix tmp1, tmp2; + const VMatrixRaw_t* s1 = (&src1 == &dst) ? tmp1.m : src1.m; + const VMatrixRaw_t* s2 = (&src2 == &dst) ? tmp2.m : src2.m; + + if (&src1 == &dst) + { + MatrixCopy( src1, tmp1 ); + } + if (&src2 == &dst) + { + MatrixCopy( src2, tmp2 ); + } + + dst[0][0] = s1[0][0] * s2[0][0] + s1[0][1] * s2[1][0] + s1[0][2] * s2[2][0] + s1[0][3] * s2[3][0]; + dst[0][1] = s1[0][0] * s2[0][1] + s1[0][1] * s2[1][1] + s1[0][2] * s2[2][1] + s1[0][3] * s2[3][1]; + dst[0][2] = s1[0][0] * s2[0][2] + s1[0][1] * s2[1][2] + s1[0][2] * s2[2][2] + s1[0][3] * s2[3][2]; + dst[0][3] = s1[0][0] * s2[0][3] + s1[0][1] * s2[1][3] + s1[0][2] * s2[2][3] + s1[0][3] * s2[3][3]; + + dst[1][0] = s1[1][0] * s2[0][0] + s1[1][1] * s2[1][0] + s1[1][2] * s2[2][0] + s1[1][3] * s2[3][0]; + dst[1][1] = s1[1][0] * s2[0][1] + s1[1][1] * s2[1][1] + s1[1][2] * s2[2][1] + s1[1][3] * s2[3][1]; + dst[1][2] = s1[1][0] * s2[0][2] + s1[1][1] * s2[1][2] + s1[1][2] * s2[2][2] + s1[1][3] * s2[3][2]; + dst[1][3] = s1[1][0] * s2[0][3] + s1[1][1] * s2[1][3] + s1[1][2] * s2[2][3] + s1[1][3] * s2[3][3]; + + dst[2][0] = s1[2][0] * s2[0][0] + s1[2][1] * s2[1][0] + s1[2][2] * s2[2][0] + s1[2][3] * s2[3][0]; + dst[2][1] = s1[2][0] * s2[0][1] + s1[2][1] * s2[1][1] + s1[2][2] * s2[2][1] + s1[2][3] * s2[3][1]; + dst[2][2] = s1[2][0] * s2[0][2] + s1[2][1] * s2[1][2] + s1[2][2] * s2[2][2] + s1[2][3] * s2[3][2]; + dst[2][3] = s1[2][0] * s2[0][3] + s1[2][1] * s2[1][3] + s1[2][2] * s2[2][3] + s1[2][3] * s2[3][3]; + + dst[3][0] = s1[3][0] * s2[0][0] + s1[3][1] * s2[1][0] + s1[3][2] * s2[2][0] + s1[3][3] * s2[3][0]; + dst[3][1] = s1[3][0] * s2[0][1] + s1[3][1] * s2[1][1] + s1[3][2] * s2[2][1] + s1[3][3] * s2[3][1]; + dst[3][2] = s1[3][0] * s2[0][2] + s1[3][1] * s2[1][2] + s1[3][2] * s2[2][2] + s1[3][3] * s2[3][2]; + dst[3][3] = s1[3][0] * s2[0][3] + s1[3][1] * s2[1][3] + s1[3][2] * s2[2][3] + s1[3][3] * s2[3][3]; +} + +//----------------------------------------------------------------------------- +// Matrix/vector multiply +//----------------------------------------------------------------------------- + +void Vector4DMultiply( const VMatrix& src1, Vector4D const& src2, Vector4D& dst ) +{ + // Make sure it works if src2 == dst + Vector4D tmp; + Vector4D const&v = (&src2 == &dst) ? tmp : src2; + + if (&src2 == &dst) + { + Vector4DCopy( src2, tmp ); + } + + dst[0] = src1[0][0] * v[0] + src1[0][1] * v[1] + src1[0][2] * v[2] + src1[0][3] * v[3]; + dst[1] = src1[1][0] * v[0] + src1[1][1] * v[1] + src1[1][2] * v[2] + src1[1][3] * v[3]; + dst[2] = src1[2][0] * v[0] + src1[2][1] * v[1] + src1[2][2] * v[2] + src1[2][3] * v[3]; + dst[3] = src1[3][0] * v[0] + src1[3][1] * v[1] + src1[3][2] * v[2] + src1[3][3] * v[3]; +} + +//----------------------------------------------------------------------------- +// Matrix/vector multiply +//----------------------------------------------------------------------------- + +void Vector4DMultiplyPosition( const VMatrix& src1, Vector const& src2, Vector4D& dst ) +{ + // Make sure it works if src2 == dst + Vector tmp; + Vector const&v = ( &src2 == &dst.AsVector3D() ) ? tmp : src2; + + if (&src2 == &dst.AsVector3D()) + { + VectorCopy( src2, tmp ); + } + + dst[0] = src1[0][0] * v[0] + src1[0][1] * v[1] + src1[0][2] * v[2] + src1[0][3]; + dst[1] = src1[1][0] * v[0] + src1[1][1] * v[1] + src1[1][2] * v[2] + src1[1][3]; + dst[2] = src1[2][0] * v[0] + src1[2][1] * v[1] + src1[2][2] * v[2] + src1[2][3]; + dst[3] = src1[3][0] * v[0] + src1[3][1] * v[1] + src1[3][2] * v[2] + src1[3][3]; +} + + + +//----------------------------------------------------------------------------- +// Matrix/vector multiply +//----------------------------------------------------------------------------- + +void Vector3DMultiply( const VMatrix &src1, const Vector &src2, Vector &dst ) +{ + // Make sure it works if src2 == dst + Vector tmp; + const Vector &v = (&src2 == &dst) ? tmp : src2; + + if( &src2 == &dst ) + { + VectorCopy( src2, tmp ); + } + + dst[0] = src1[0][0] * v[0] + src1[0][1] * v[1] + src1[0][2] * v[2]; + dst[1] = src1[1][0] * v[0] + src1[1][1] * v[1] + src1[1][2] * v[2]; + dst[2] = src1[2][0] * v[0] + src1[2][1] * v[1] + src1[2][2] * v[2]; +} + + +//----------------------------------------------------------------------------- +// Vector3DMultiplyPositionProjective treats src2 as if it's a point +// and does the perspective divide at the end +//----------------------------------------------------------------------------- +void Vector3DMultiplyPositionProjective( const VMatrix& src1, const Vector &src2, Vector& dst ) +{ + // Make sure it works if src2 == dst + Vector tmp; + const Vector &v = (&src2 == &dst) ? tmp: src2; + if( &src2 == &dst ) + { + VectorCopy( src2, tmp ); + } + + float w = src1[3][0] * v[0] + src1[3][1] * v[1] + src1[3][2] * v[2] + src1[3][3]; + if ( w != 0.0f ) + { + w = 1.0f / w; + } + + dst[0] = src1[0][0] * v[0] + src1[0][1] * v[1] + src1[0][2] * v[2] + src1[0][3]; + dst[1] = src1[1][0] * v[0] + src1[1][1] * v[1] + src1[1][2] * v[2] + src1[1][3]; + dst[2] = src1[2][0] * v[0] + src1[2][1] * v[1] + src1[2][2] * v[2] + src1[2][3]; + dst *= w; +} + + +//----------------------------------------------------------------------------- +// Vector3DMultiplyProjective treats src2 as if it's a direction +// and does the perspective divide at the end +//----------------------------------------------------------------------------- +void Vector3DMultiplyProjective( const VMatrix& src1, const Vector &src2, Vector& dst ) +{ + // Make sure it works if src2 == dst + Vector tmp; + const Vector &v = (&src2 == &dst) ? tmp : src2; + if( &src2 == &dst ) + { + VectorCopy( src2, tmp ); + } + + float w; + dst[0] = src1[0][0] * v[0] + src1[0][1] * v[1] + src1[0][2] * v[2]; + dst[1] = src1[1][0] * v[0] + src1[1][1] * v[1] + src1[1][2] * v[2]; + dst[2] = src1[2][0] * v[0] + src1[2][1] * v[1] + src1[2][2] * v[2]; + w = src1[3][0] * v[0] + src1[3][1] * v[1] + src1[3][2] * v[2]; + if (w != 0.0f) + { + dst /= w; + } + else + { + dst = vec3_origin; + } +} + + +//----------------------------------------------------------------------------- +// Multiplies the vector by the transpose of the matrix +//----------------------------------------------------------------------------- +void Vector4DMultiplyTranspose( const VMatrix& src1, Vector4D const& src2, Vector4D& dst ) +{ + // Make sure it works if src2 == dst + bool srcEqualsDst = (&src2 == &dst); + + Vector4D tmp; + Vector4D const&v = srcEqualsDst ? tmp : src2; + + if (srcEqualsDst) + { + Vector4DCopy( src2, tmp ); + } + + dst[0] = src1[0][0] * v[0] + src1[1][0] * v[1] + src1[2][0] * v[2] + src1[3][0] * v[3]; + dst[1] = src1[0][1] * v[0] + src1[1][1] * v[1] + src1[2][1] * v[2] + src1[3][1] * v[3]; + dst[2] = src1[0][2] * v[0] + src1[1][2] * v[1] + src1[2][2] * v[2] + src1[3][2] * v[3]; + dst[3] = src1[0][3] * v[0] + src1[1][3] * v[1] + src1[2][3] * v[2] + src1[3][3] * v[3]; +} + +//----------------------------------------------------------------------------- +// Multiplies the vector by the transpose of the matrix +//----------------------------------------------------------------------------- +void Vector3DMultiplyTranspose( const VMatrix& src1, const Vector& src2, Vector& dst ) +{ + // Make sure it works if src2 == dst + bool srcEqualsDst = (&src2 == &dst); + + Vector tmp; + const Vector&v = srcEqualsDst ? tmp : src2; + + if (srcEqualsDst) + { + VectorCopy( src2, tmp ); + } + + dst[0] = src1[0][0] * v[0] + src1[1][0] * v[1] + src1[2][0] * v[2]; + dst[1] = src1[0][1] * v[0] + src1[1][1] * v[1] + src1[2][1] * v[2]; + dst[2] = src1[0][2] * v[0] + src1[1][2] * v[1] + src1[2][2] * v[2]; +} + + +//----------------------------------------------------------------------------- +// Transform a plane +//----------------------------------------------------------------------------- +void MatrixTransformPlane( const VMatrix &src, const cplane_t &inPlane, cplane_t &outPlane ) +{ + // What we want to do is the following: + // 1) transform the normal into the new space. + // 2) Determine a point on the old plane given by plane dist * plane normal + // 3) Transform that point into the new space + // 4) Plane dist = DotProduct( new normal, new point ) + + // An optimized version, which works if the plane is orthogonal. + // 1) Transform the normal into the new space + // 2) Realize that transforming the old plane point into the new space + // is given by [ d * n'x + Tx, d * n'y + Ty, d * n'z + Tz ] + // where d = old plane dist, n' = transformed normal, Tn = translational component of transform + // 3) Compute the new plane dist using the dot product of the normal result of #2 + + // For a correct result, this should be an inverse-transpose matrix + // but that only matters if there are nonuniform scale or skew factors in this matrix. + Vector vTrans; + Vector3DMultiply( src, inPlane.normal, outPlane.normal ); + outPlane.dist = inPlane.dist * DotProduct( outPlane.normal, outPlane.normal ); + outPlane.dist += DotProduct( outPlane.normal, src.GetTranslation(vTrans) ); +} + + +#ifndef VECTOR_NO_SLOW_OPERATIONS + +VPlane VMatrix::operator*(const VPlane &thePlane) const +{ + VPlane ret; + TransformPlane( thePlane, ret ); + return ret; +} + +#endif + + +//----------------------------------------------------------------------------- +// Builds a rotation matrix that rotates one direction vector into another +//----------------------------------------------------------------------------- +void MatrixBuildTranslation( VMatrix& dst, float x, float y, float z ) +{ + MatrixSetIdentity( dst ); + dst[0][3] = x; + dst[1][3] = y; + dst[2][3] = z; +} + +void MatrixBuildTranslation( VMatrix& dst, const Vector &translation ) +{ + MatrixSetIdentity( dst ); + dst[0][3] = translation[0]; + dst[1][3] = translation[1]; + dst[2][3] = translation[2]; +} + + +//----------------------------------------------------------------------------- +// Purpose: Builds the matrix for a counterclockwise rotation about an arbitrary axis. +// +// | ax2 + (1 - ax2)cosQ axay(1 - cosQ) - azsinQ azax(1 - cosQ) + aysinQ | +// Ra(Q) = | axay(1 - cosQ) + azsinQ ay2 + (1 - ay2)cosQ ayaz(1 - cosQ) - axsinQ | +// | azax(1 - cosQ) - aysinQ ayaz(1 - cosQ) + axsinQ az2 + (1 - az2)cosQ | +// +// Input : mat - +// vAxisOrRot - +// angle - +//----------------------------------------------------------------------------- +void MatrixBuildRotationAboutAxis( VMatrix &dst, const Vector &vAxisOfRot, float angleDegrees ) +{ + MatrixBuildRotationAboutAxis( vAxisOfRot, angleDegrees, dst.As3x4() ); + dst[3][0] = 0; + dst[3][1] = 0; + dst[3][2] = 0; + dst[3][3] = 1; +} + + +//----------------------------------------------------------------------------- +// Builds a rotation matrix that rotates one direction vector into another +//----------------------------------------------------------------------------- +void MatrixBuildRotation( VMatrix &dst, const Vector& initialDirection, const Vector& finalDirection ) +{ + float angle = DotProduct( initialDirection, finalDirection ); + Assert( IsFinite(angle) ); + + Vector axis; + + // No rotation required + if (angle - 1.0 > -1e-3) + { + // parallel case + MatrixSetIdentity(dst); + return; + } + else if (angle + 1.0 < 1e-3) + { + // antiparallel case, pick any axis in the plane + // perpendicular to the final direction. Choose the direction (x,y,z) + // which has the minimum component of the final direction, use that + // as an initial guess, then subtract out the component which is + // parallel to the final direction + int idx = 0; + if (FloatMakePositive(finalDirection[1]) < FloatMakePositive(finalDirection[idx])) + idx = 1; + if (FloatMakePositive(finalDirection[2]) < FloatMakePositive(finalDirection[idx])) + idx = 2; + + axis.Init( 0, 0, 0 ); + axis[idx] = 1.0f; + VectorMA( axis, -DotProduct( axis, finalDirection ), finalDirection, axis ); + VectorNormalize(axis); + angle = 180.0f; + } + else + { + CrossProduct( initialDirection, finalDirection, axis ); + VectorNormalize( axis ); + angle = acos(angle) * 180 / M_PI; + } + + MatrixBuildRotationAboutAxis( dst, axis, angle ); + +#ifdef _DEBUG + Vector test; + Vector3DMultiply( dst, initialDirection, test ); + test -= finalDirection; + Assert( test.LengthSqr() < 1e-3 ); +#endif +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void MatrixBuildRotateZ( VMatrix &dst, float angleDegrees ) +{ + float radians = angleDegrees * ( M_PI / 180.0f ); + + float fSin = ( float )sin( radians ); + float fCos = ( float )cos( radians ); + + dst[0][0] = fCos; dst[0][1] = -fSin; dst[0][2] = 0.0f; dst[0][3] = 0.0f; + dst[1][0] = fSin; dst[1][1] = fCos; dst[1][2] = 0.0f; dst[1][3] = 0.0f; + dst[2][0] = 0.0f; dst[2][1] = 0.0f; dst[2][2] = 1.0f; dst[2][3] = 0.0f; + dst[3][0] = 0.0f; dst[3][1] = 0.0f; dst[3][2] = 0.0f; dst[3][3] = 1.0f; +} + +// Builds a scale matrix +void MatrixBuildScale( VMatrix &dst, float x, float y, float z ) +{ + dst[0][0] = x; dst[0][1] = 0.0f; dst[0][2] = 0.0f; dst[0][3] = 0.0f; + dst[1][0] = 0.0f; dst[1][1] = y; dst[1][2] = 0.0f; dst[1][3] = 0.0f; + dst[2][0] = 0.0f; dst[2][1] = 0.0f; dst[2][2] = z; dst[2][3] = 0.0f; + dst[3][0] = 0.0f; dst[3][1] = 0.0f; dst[3][2] = 0.0f; dst[3][3] = 1.0f; +} + +void MatrixBuildScale( VMatrix &dst, const Vector& scale ) +{ + MatrixBuildScale( dst, scale.x, scale.y, scale.z ); +} + +void MatrixBuildPerspective( VMatrix &dst, float fovX, float fovY, float zNear, float zFar ) +{ + // FIXME: collapse all of this into one matrix after we figure out what all should be in here. + float width = 2 * zNear * tan( fovX * ( M_PI/180.0f ) * 0.5f ); + float height = 2 * zNear * tan( fovY * ( M_PI/180.0f ) * 0.5f ); + + memset( dst.Base(), 0, sizeof( dst ) ); + dst[0][0] = 2.0F * zNear / width; + dst[1][1] = 2.0F * zNear / height; + dst[2][2] = -zFar / ( zNear - zFar ); + dst[3][2] = 1.0f; + dst[2][3] = zNear * zFar / ( zNear - zFar ); + + // negate X and Y so that X points right, and Y points up. + VMatrix negateXY; + negateXY.Identity(); + negateXY[0][0] = -1.0f; + negateXY[1][1] = -1.0f; + MatrixMultiply( negateXY, dst, dst ); + + VMatrix addW; + addW.Identity(); + addW[0][3] = 1.0f; + addW[1][3] = 1.0f; + addW[2][3] = 0.0f; + MatrixMultiply( addW, dst, dst ); + + VMatrix scaleHalf; + scaleHalf.Identity(); + scaleHalf[0][0] = 0.5f; + scaleHalf[1][1] = 0.5f; + MatrixMultiply( scaleHalf, dst, dst ); +} + +static inline void CalculateAABBForNormalizedFrustum_Helper( float x, float y, float z, const VMatrix &volumeToWorld, Vector &mins, Vector &maxs ) +{ + Vector volumeSpacePos( x, y, z ); + + // Make sure it's been clipped + Assert( volumeSpacePos[0] >= -1e-3f ); + Assert( volumeSpacePos[0] - 1.0f <= 1e-3f ); + Assert( volumeSpacePos[1] >= -1e-3f ); + Assert( volumeSpacePos[1] - 1.0f <= 1e-3f ); + Assert( volumeSpacePos[2] >= -1e-3f ); + Assert( volumeSpacePos[2] - 1.0f <= 1e-3f ); + + Vector worldPos; + Vector3DMultiplyPositionProjective( volumeToWorld, volumeSpacePos, worldPos ); + AddPointToBounds( worldPos, mins, maxs ); +} + +//----------------------------------------------------------------------------- +// Given an inverse projection matrix, take the extremes of the space in transformed into world space and +// get a bounding box. +//----------------------------------------------------------------------------- +void CalculateAABBFromProjectionMatrixInverse( const VMatrix &volumeToWorld, Vector *pMins, Vector *pMaxs ) +{ + // FIXME: Could maybe do better than the compile with all of these multiplies by 0 and 1. + ClearBounds( *pMins, *pMaxs ); + CalculateAABBForNormalizedFrustum_Helper( 0, 0, 0, volumeToWorld, *pMins, *pMaxs ); + CalculateAABBForNormalizedFrustum_Helper( 0, 0, 1, volumeToWorld, *pMins, *pMaxs ); + CalculateAABBForNormalizedFrustum_Helper( 0, 1, 0, volumeToWorld, *pMins, *pMaxs ); + CalculateAABBForNormalizedFrustum_Helper( 0, 1, 1, volumeToWorld, *pMins, *pMaxs ); + CalculateAABBForNormalizedFrustum_Helper( 1, 0, 0, volumeToWorld, *pMins, *pMaxs ); + CalculateAABBForNormalizedFrustum_Helper( 1, 0, 1, volumeToWorld, *pMins, *pMaxs ); + CalculateAABBForNormalizedFrustum_Helper( 1, 1, 0, volumeToWorld, *pMins, *pMaxs ); + CalculateAABBForNormalizedFrustum_Helper( 1, 1, 1, volumeToWorld, *pMins, *pMaxs ); +} + +void CalculateAABBFromProjectionMatrix( const VMatrix &worldToVolume, Vector *pMins, Vector *pMaxs ) +{ + VMatrix volumeToWorld; + MatrixInverseGeneral( worldToVolume, volumeToWorld ); + CalculateAABBFromProjectionMatrixInverse( volumeToWorld, pMins, pMaxs ); +} + +//----------------------------------------------------------------------------- +// Given an inverse projection matrix, take the extremes of the space in transformed into world space and +// get a bounding sphere. +//----------------------------------------------------------------------------- +void CalculateSphereFromProjectionMatrixInverse( const VMatrix &volumeToWorld, Vector *pCenter, float *pflRadius ) +{ + // FIXME: Could maybe do better than the compile with all of these multiplies by 0 and 1. + + // Need 3 points: the endpoint of the line through the center of the near + far planes, + // and one point on the far plane. From that, we can derive a point somewhere on the center line + // which would produce the smallest bounding sphere. + Vector vecCenterNear, vecCenterFar, vecNearEdge, vecFarEdge; + Vector3DMultiplyPositionProjective( volumeToWorld, Vector( 0.5f, 0.5f, 0.0f ), vecCenterNear ); + Vector3DMultiplyPositionProjective( volumeToWorld, Vector( 0.5f, 0.5f, 1.0f ), vecCenterFar ); + Vector3DMultiplyPositionProjective( volumeToWorld, Vector( 0.0f, 0.0f, 0.0f ), vecNearEdge ); + Vector3DMultiplyPositionProjective( volumeToWorld, Vector( 0.0f, 0.0f, 1.0f ), vecFarEdge ); + + // Let the distance between the near + far center points = l + // Let the distance between the near center point + near edge point = h1 + // Let the distance between the far center point + far edge point = h2 + // Let the distance along the center line from the near point to the sphere center point = x + // Then let the distance between the sphere center point + near edge point == + // the distance between the sphere center point + far edge point == r == radius of sphere + // Then h1^2 + x^2 == r^2 == (l-x)^2 + h2^2 + // h1^x + x^2 = l^2 - 2 * l * x + x^2 + h2^2 + // 2 * l * x = l^2 + h2^2 - h1^2 + // x = (l^2 + h2^2 - h1^2) / (2 * l) + // r = sqrt( hl^1 + x^2 ) + Vector vecDelta; + VectorSubtract( vecCenterFar, vecCenterNear, vecDelta ); + float l = vecDelta.Length(); + float h1Sqr = vecCenterNear.DistToSqr( vecNearEdge ); + float h2Sqr = vecCenterFar.DistToSqr( vecFarEdge ); + float x = (l*l + h2Sqr - h1Sqr) / (2.0f * l); + VectorMA( vecCenterNear, (x / l), vecDelta, *pCenter ); + *pflRadius = sqrt( h1Sqr + x*x ); +} + +//----------------------------------------------------------------------------- +// Given a projection matrix, take the extremes of the space in transformed into world space and +// get a bounding sphere. +//----------------------------------------------------------------------------- +void CalculateSphereFromProjectionMatrix( const VMatrix &worldToVolume, Vector *pCenter, float *pflRadius ) +{ + VMatrix volumeToWorld; + MatrixInverseGeneral( worldToVolume, volumeToWorld ); + CalculateSphereFromProjectionMatrixInverse( volumeToWorld, pCenter, pflRadius ); +} + + +static inline void FrustumPlanesFromMatrixHelper( const VMatrix &shadowToWorld, const Vector &p1, const Vector &p2, const Vector &p3, + Vector &normal, float &dist ) +{ + Vector world1, world2, world3; + Vector3DMultiplyPositionProjective( shadowToWorld, p1, world1 ); + Vector3DMultiplyPositionProjective( shadowToWorld, p2, world2 ); + Vector3DMultiplyPositionProjective( shadowToWorld, p3, world3 ); + + Vector v1, v2; + VectorSubtract( world2, world1, v1 ); + VectorSubtract( world3, world1, v2 ); + + CrossProduct( v1, v2, normal ); + VectorNormalize( normal ); + dist = DotProduct( normal, world1 ); +} + +void FrustumPlanesFromMatrix( const VMatrix &clipToWorld, Frustum_t &frustum ) +{ + Vector normal; + float dist; + + FrustumPlanesFromMatrixHelper( clipToWorld, + Vector( 0.0f, 0.0f, 0.0f ), Vector( 1.0f, 0.0f, 0.0f ), Vector( 0.0f, 1.0f, 0.0f ), normal, dist ); + frustum.SetPlane( FRUSTUM_NEARZ, PLANE_ANYZ, normal, dist ); + + FrustumPlanesFromMatrixHelper( clipToWorld, + Vector( 0.0f, 0.0f, 1.0f ), Vector( 0.0f, 1.0f, 1.0f ), Vector( 1.0f, 0.0f, 1.0f ), normal, dist ); + frustum.SetPlane( FRUSTUM_FARZ, PLANE_ANYZ, normal, dist ); + + FrustumPlanesFromMatrixHelper( clipToWorld, + Vector( 1.0f, 0.0f, 0.0f ), Vector( 1.0f, 1.0f, 1.0f ), Vector( 1.0f, 1.0f, 0.0f ), normal, dist ); + frustum.SetPlane( FRUSTUM_RIGHT, PLANE_ANYZ, normal, dist ); + + FrustumPlanesFromMatrixHelper( clipToWorld, + Vector( 0.0f, 0.0f, 0.0f ), Vector( 0.0f, 1.0f, 1.0f ), Vector( 0.0f, 0.0f, 1.0f ), normal, dist ); + frustum.SetPlane( FRUSTUM_LEFT, PLANE_ANYZ, normal, dist ); + + FrustumPlanesFromMatrixHelper( clipToWorld, + Vector( 1.0f, 1.0f, 0.0f ), Vector( 1.0f, 1.0f, 1.0f ), Vector( 0.0f, 1.0f, 1.0f ), normal, dist ); + frustum.SetPlane( FRUSTUM_TOP, PLANE_ANYZ, normal, dist ); + + FrustumPlanesFromMatrixHelper( clipToWorld, + Vector( 1.0f, 0.0f, 0.0f ), Vector( 0.0f, 0.0f, 1.0f ), Vector( 1.0f, 0.0f, 1.0f ), normal, dist ); + frustum.SetPlane( FRUSTUM_BOTTOM, PLANE_ANYZ, normal, dist ); +} + +void MatrixBuildOrtho( VMatrix& dst, double left, double top, double right, double bottom, double zNear, double zFar ) +{ + // FIXME: This is being used incorrectly! Should read: + // D3DXMatrixOrthoOffCenterRH( &matrix, left, right, bottom, top, zNear, zFar ); + // Which is certainly why we need these extra -1 scales in y. Bleah + + // NOTE: The camera can be imagined as the following diagram: + // /z + // / + // /____ x Z is going into the screen + // | + // | + // |y + // + // (0,0,z) represents the upper-left corner of the screen. + // Our projection transform needs to transform from this space to a LH coordinate + // system that looks thusly: + // + // y| /z + // | / + // |/____ x Z is going into the screen + // + // Where x,y lies between -1 and 1, and z lies from 0 to 1 + // This is because the viewport transformation from projection space to pixels + // introduces a -1 scale in the y coordinates + // D3DXMatrixOrthoOffCenterRH( &matrix, left, right, top, bottom, zNear, zFar ); + + dst.Init( 2.0f / ( right - left ), 0.0f, 0.0f, ( left + right ) / ( left - right ), + 0.0f, 2.0f / ( bottom - top ), 0.0f, ( bottom + top ) / ( top - bottom ), + 0.0f, 0.0f, 1.0f / ( zNear - zFar ), zNear / ( zNear - zFar ), + 0.0f, 0.0f, 0.0f, 1.0f ); +} + +void MatrixBuildPerspectiveX( VMatrix& dst, double flFovX, double flAspect, double flZNear, double flZFar ) +{ + float flWidth = 2.0f * flZNear * tanf( flFovX * M_PI / 360.0f ); + float flHeight = flWidth / flAspect; + dst.Init( 2.0f * flZNear / flWidth, 0.0f, 0.0f, 0.0f, + 0.0f, 2.0f * flZNear/ flHeight, 0.0f, 0.0f, + 0.0f, 0.0f, flZFar / ( flZNear - flZFar ), flZNear * flZFar / ( flZNear - flZFar ), + 0.0f, 0.0f, -1.0f, 0.0f ); +} + +void MatrixBuildPerspectiveOffCenterX( VMatrix& dst, double flFovX, double flAspect, double flZNear, double flZFar, double bottom, double top, double left, double right ) +{ + float flWidth = 2.0f * flZNear * tanf( flFovX * M_PI / 360.0f ); + float flHeight = flWidth / flAspect; + + // bottom, top, left, right are 0..1 so convert to -/2../2 + float flLeft = -(flWidth/2.0f) * (1.0f - left) + left * (flWidth/2.0f); + float flRight = -(flWidth/2.0f) * (1.0f - right) + right * (flWidth/2.0f); + float flBottom = -(flHeight/2.0f) * (1.0f - bottom) + bottom * (flHeight/2.0f); + float flTop = -(flHeight/2.0f) * (1.0f - top) + top * (flHeight/2.0f); + + dst.Init( (2.0f * flZNear) / (flRight-flLeft), 0.0f, (flLeft+flRight)/(flRight-flLeft), 0.0f, + 0.0f, 2.0f*flZNear/(flTop-flBottom), (flTop+flBottom)/(flTop-flBottom), 0.0f, + 0.0f, 0.0f, flZFar/(flZNear-flZFar), flZNear*flZFar/(flZNear-flZFar), + 0.0f, 0.0f, -1.0f, 0.0f ); +} +#endif // !_STATIC_LINKED || _SHARED_LIB + diff --git a/tier1/KeyValues.cpp b/tier1/KeyValues.cpp new file mode 100644 index 00000000..18b1c220 --- /dev/null +++ b/tier1/KeyValues.cpp @@ -0,0 +1,2521 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#if defined( _WIN32 ) && !defined( _X360 ) +#include // for WideCharToMultiByte and MultiByteToWideChar +#elif defined( _LINUX ) || defined( __APPLE__ ) +#include // wcslen() +#define _alloca alloca +#endif + +#include +#include "filesystem.h" +#include + +#include +#include +#include "tier0/dbg.h" +#include "tier0/mem.h" +#include "utlvector.h" +#include "utlbuffer.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include + +static const char * s_LastFileLoadingFrom = "unknown"; // just needed for error messages + +#define KEYVALUES_TOKEN_SIZE 1024 +static char s_pTokenBuf[KEYVALUES_TOKEN_SIZE]; + + +#define INTERNALWRITE( pData, len ) InternalWrite( filesystem, f, pBuf, pData, len ) + + +// a simple class to keep track of a stack of valid parsed symbols +const int MAX_ERROR_STACK = 64; +class CKeyValuesErrorStack +{ +public: + CKeyValuesErrorStack() : m_pFilename("NULL"), m_errorIndex(0), m_maxErrorIndex(0) {} + + void SetFilename( const char *pFilename ) + { + m_pFilename = pFilename; + m_maxErrorIndex = 0; + } + + // entering a new keyvalues block, save state for errors + // Not save symbols instead of pointers because the pointers can move! + int Push( int symName ) + { + if ( m_errorIndex < MAX_ERROR_STACK ) + { + m_errorStack[m_errorIndex] = symName; + } + m_errorIndex++; + m_maxErrorIndex = MAX( m_maxErrorIndex, (m_errorIndex-1) ); + return m_errorIndex-1; + } + + // exiting block, error isn't in this block, remove. + void Pop() + { + m_errorIndex--; + Assert(m_errorIndex>=0); + } + + // Allows you to keep the same stack level, but change the name as you parse peers + void Reset( int stackLevel, int symName ) + { + Assert( stackLevel >= 0 && stackLevel < m_errorIndex ); + m_errorStack[stackLevel] = symName; + } + + // Hit an error, report it and the parsing stack for context + void ReportError( const char *pError ) + { + Warning( "KeyValues Error: %s in file %s\n", pError, m_pFilename ); + for ( int i = 0; i < m_maxErrorIndex; i++ ) + { + if ( m_errorStack[i] != INVALID_KEY_SYMBOL ) + { + if ( i < m_errorIndex ) + { + Warning( "%s, ", KeyValuesSystem()->GetStringForSymbol(m_errorStack[i]) ); + } + else + { + Warning( "(*%s*), ", KeyValuesSystem()->GetStringForSymbol(m_errorStack[i]) ); + } + } + } + Warning( "\n" ); + } + +private: + int m_errorStack[MAX_ERROR_STACK]; + const char *m_pFilename; + int m_errorIndex; + int m_maxErrorIndex; +} g_KeyValuesErrorStack; + + +// a simple helper that creates stack entries as it goes in & out of scope +class CKeyErrorContext +{ +public: + CKeyErrorContext( KeyValues *pKv ) + { + Init( pKv->GetNameSymbol() ); + } + + ~CKeyErrorContext() + { + g_KeyValuesErrorStack.Pop(); + } + CKeyErrorContext( int symName ) + { + Init( symName ); + } + void Reset( int symName ) + { + g_KeyValuesErrorStack.Reset( m_stackLevel, symName ); + } +private: + void Init( int symName ) + { + m_stackLevel = g_KeyValuesErrorStack.Push( symName ); + } + + int m_stackLevel; +}; + +// Uncomment this line to hit the ~CLeakTrack assert to see what's looking like it's leaking +// #define LEAKTRACK + +#ifdef LEAKTRACK + +class CLeakTrack +{ +public: + CLeakTrack() + { + } + ~CLeakTrack() + { + if ( keys.Count() != 0 ) + { + Assert( 0 ); + } + } + + struct kve + { + KeyValues *kv; + char name[ 256 ]; + }; + + void AddKv( KeyValues *kv, char const *name ) + { + kve k; + Q_strncpy( k.name, name ? name : "NULL", sizeof( k.name ) ); + k.kv = kv; + + keys.AddToTail( k ); + } + + void RemoveKv( KeyValues *kv ) + { + int c = keys.Count(); + for ( int i = 0; i < c; i++ ) + { + if ( keys[i].kv == kv ) + { + keys.Remove( i ); + break; + } + } + } + + CUtlVector< kve > keys; +}; + +static CLeakTrack track; + +#define TRACK_KV_ADD( ptr, name ) track.AddKv( ptr, name ) +#define TRACK_KV_REMOVE( ptr ) track.RemoveKv( ptr ) + +#else + +#define TRACK_KV_ADD( ptr, name ) +#define TRACK_KV_REMOVE( ptr ) + +#endif + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +KeyValues::KeyValues( const char *setName ) +{ + TRACK_KV_ADD( this, setName ); + + Init(); + SetName ( setName ); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +KeyValues::KeyValues( const char *setName, const char *firstKey, const char *firstValue ) +{ + TRACK_KV_ADD( this, setName ); + + Init(); + SetName( setName ); + SetString( firstKey, firstValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +KeyValues::KeyValues( const char *setName, const char *firstKey, const wchar_t *firstValue ) +{ + TRACK_KV_ADD( this, setName ); + + Init(); + SetName( setName ); + SetWString( firstKey, firstValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +KeyValues::KeyValues( const char *setName, const char *firstKey, int firstValue ) +{ + TRACK_KV_ADD( this, setName ); + + Init(); + SetName( setName ); + SetInt( firstKey, firstValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +KeyValues::KeyValues( const char *setName, const char *firstKey, const char *firstValue, const char *secondKey, const char *secondValue ) +{ + TRACK_KV_ADD( this, setName ); + + Init(); + SetName( setName ); + SetString( firstKey, firstValue ); + SetString( secondKey, secondValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +KeyValues::KeyValues( const char *setName, const char *firstKey, int firstValue, const char *secondKey, int secondValue ) +{ + TRACK_KV_ADD( this, setName ); + + Init(); + SetName( setName ); + SetInt( firstKey, firstValue ); + SetInt( secondKey, secondValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Initialize member variables +//----------------------------------------------------------------------------- +void KeyValues::Init() +{ + m_iKeyName = INVALID_KEY_SYMBOL; + m_iDataType = TYPE_NONE; + + m_pSub = NULL; + m_pPeer = NULL; + m_pChain = NULL; + + m_sValue = NULL; + m_wsValue = NULL; + m_pValue = NULL; + + m_bHasEscapeSequences = false; + + // for future proof + memset( unused, 0, sizeof(unused) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +KeyValues::~KeyValues() +{ + TRACK_KV_REMOVE( this ); + + RemoveEverything(); +} + +//----------------------------------------------------------------------------- +// Purpose: remove everything +//----------------------------------------------------------------------------- +void KeyValues::RemoveEverything() +{ + KeyValues *dat; + KeyValues *datNext = NULL; + for ( dat = m_pSub; dat != NULL; dat = datNext ) + { + datNext = dat->m_pPeer; + dat->m_pPeer = NULL; + delete dat; + } + + for ( dat = m_pPeer; dat && dat != this; dat = datNext ) + { + datNext = dat->m_pPeer; + dat->m_pPeer = NULL; + delete dat; + } + + delete [] m_sValue; + m_sValue = NULL; + delete [] m_wsValue; + m_wsValue = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *f - +//----------------------------------------------------------------------------- + +void KeyValues::RecursiveSaveToFile( CUtlBuffer& buf, int indentLevel ) +{ + RecursiveSaveToFile( NULL, FILESYSTEM_INVALID_HANDLE, &buf, indentLevel ); +} + +//----------------------------------------------------------------------------- +// Adds a chain... if we don't find stuff in this keyvalue, we'll look +// in the one we're chained to. +//----------------------------------------------------------------------------- + +void KeyValues::ChainKeyValue( KeyValues* pChain ) +{ + m_pChain = pChain; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the name of the current key section +//----------------------------------------------------------------------------- +const char *KeyValues::GetName( void ) const +{ + return KeyValuesSystem()->GetStringForSymbol(m_iKeyName); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the symbol name of the current key section +//----------------------------------------------------------------------------- +int KeyValues::GetNameSymbol() const +{ + return m_iKeyName; +} + + +//----------------------------------------------------------------------------- +// Purpose: Read a single token from buffer (0 terminated) +//----------------------------------------------------------------------------- +#ifdef _MSC_VER +#pragma warning (disable:4706) +#endif +const char *KeyValues::ReadToken( CUtlBuffer &buf, bool &wasQuoted, bool &wasConditional ) +{ + wasQuoted = false; + wasConditional = false; + + if ( !buf.IsValid() ) + return NULL; + + // eating white spaces and remarks loop + while ( true ) + { + buf.EatWhiteSpace(); + if ( !buf.IsValid() ) + return NULL; // file ends after reading whitespaces + + // stop if it's not a comment; a new token starts here + if ( !buf.EatCPPComment() ) + break; + } + + const char *c = (const char*)buf.PeekGet( sizeof(char), 0 ); + if ( !c ) + return NULL; + + // read quoted strings specially + if ( *c == '\"' ) + { + wasQuoted = true; + buf.GetDelimitedString( m_bHasEscapeSequences ? GetCStringCharConversion() : GetNoEscCharConversion(), + s_pTokenBuf, KEYVALUES_TOKEN_SIZE ); + return s_pTokenBuf; + } + + if ( *c == '{' || *c == '}' ) + { + // it's a control char, just add this one char and stop reading + s_pTokenBuf[0] = *c; + s_pTokenBuf[1] = 0; + buf.SeekGet( CUtlBuffer::SEEK_CURRENT, 1 ); + return s_pTokenBuf; + } + + // read in the token until we hit a whitespace or a control character + bool bReportedError = false; + bool bConditionalStart = false; + int nCount = 0; + while ( (c = (const char*)buf.PeekGet( sizeof(char), 0 )) ) + { + // end of file + if ( *c == 0 ) + break; + + // break if any control character appears in non quoted tokens + if ( *c == '"' || *c == '{' || *c == '}' ) + break; + + if ( *c == '[' ) + bConditionalStart = true; + + if ( *c == ']' && bConditionalStart ) + { + wasConditional = true; + } + + // break on whitespace + if ( isspace(*c) ) + break; + + if (nCount < (KEYVALUES_TOKEN_SIZE-1) ) + { + s_pTokenBuf[nCount++] = *c; // add char to buffer + } + else if ( !bReportedError ) + { + bReportedError = true; + g_KeyValuesErrorStack.ReportError(" ReadToken overflow" ); + } + + buf.SeekGet( CUtlBuffer::SEEK_CURRENT, 1 ); + } + s_pTokenBuf[ nCount ] = 0; + return s_pTokenBuf; +} +#ifdef _MSC_VER +#pragma warning (default:4706) +#endif + + +//----------------------------------------------------------------------------- +// Purpose: if parser should translate escape sequences ( /n, /t etc), set to true +//----------------------------------------------------------------------------- +void KeyValues::UsesEscapeSequences(bool state) +{ + m_bHasEscapeSequences = state; +} + + +//----------------------------------------------------------------------------- +// Purpose: Load keyValues from disk +//----------------------------------------------------------------------------- +bool KeyValues::LoadFromFile( IBaseFileSystem *filesystem, const char *resourceName, const char *pathID ) +{ + Assert(filesystem); +#ifndef _LINUX + Assert( IsX360() || ( IsPC() && _heapchk() == _HEAPOK ) ); +#endif + + FileHandle_t f = filesystem->Open(resourceName, "rb", pathID); + if ( !f ) + return false; + + s_LastFileLoadingFrom = (char*)resourceName; + + // load file into a null-terminated buffer + int fileSize = filesystem->Size( f ); + unsigned bufSize = ((IFileSystem *)filesystem)->GetOptimalReadSize( f, fileSize + 1 ); + + char *buffer = (char*)((IFileSystem *)filesystem)->AllocOptimalReadBuffer( f, bufSize ); + Assert( buffer ); + + // read into local buffer + bool bRetOK = ( ((IFileSystem *)filesystem)->ReadEx( buffer, bufSize, fileSize, f ) != 0 ); + + filesystem->Close( f ); // close file after reading + + if ( bRetOK ) + { + buffer[fileSize] = 0; // null terminate file as EOF + bRetOK = LoadFromBuffer( resourceName, buffer, filesystem ); + } + + ((IFileSystem *)filesystem)->FreeOptimalReadBuffer( buffer ); + + return bRetOK; +} + +//----------------------------------------------------------------------------- +// Purpose: Save the keyvalues to disk +// Creates the path to the file if it doesn't exist +//----------------------------------------------------------------------------- +bool KeyValues::SaveToFile( IBaseFileSystem *filesystem, const char *resourceName, const char *pathID ) +{ + // create a write file + FileHandle_t f = filesystem->Open(resourceName, "wb", pathID); + + if ( f == FILESYSTEM_INVALID_HANDLE ) + { + DevMsg(1, "KeyValues::SaveToFile: couldn't open file \"%s\" in path \"%s\".\n", + resourceName?resourceName:"NULL", pathID?pathID:"NULL" ); + return false; + } + + RecursiveSaveToFile(filesystem, f, NULL, 0); + filesystem->Close(f); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Write out a set of indenting +//----------------------------------------------------------------------------- +void KeyValues::WriteIndents( IBaseFileSystem *filesystem, FileHandle_t f, CUtlBuffer *pBuf, int indentLevel ) +{ + for ( int i = 0; i < indentLevel; i++ ) + { + INTERNALWRITE( "\t", 1 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Write out a string where we convert the double quotes to backslash double quote +//----------------------------------------------------------------------------- +void KeyValues::WriteConvertedString( IBaseFileSystem *filesystem, FileHandle_t f, CUtlBuffer *pBuf, const char *pszString ) +{ + // handle double quote chars within the string + // the worst possible case is that the whole string is quotes + int len = Q_strlen(pszString); + char *convertedString = (char *) _alloca ((len + 1) * sizeof(char) * 2); + int j=0; + for (int i=0; i <= len; i++) + { + if (pszString[i] == '\"') + { + convertedString[j] = '\\'; + j++; + } + else if ( m_bHasEscapeSequences && pszString[i] == '\\' ) + { + convertedString[j] = '\\'; + j++; + } + convertedString[j] = pszString[i]; + j++; + } + + INTERNALWRITE(convertedString, strlen(convertedString)); +} + + +void KeyValues::InternalWrite( IBaseFileSystem *filesystem, FileHandle_t f, CUtlBuffer *pBuf, const void *pData, int len ) +{ + if ( filesystem ) + { + filesystem->Write( pData, len, f ); + } + + if ( pBuf ) + { + pBuf->Put( pData, len ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Save keyvalues from disk, if subkey values are detected, calls +// itself to save those +//----------------------------------------------------------------------------- +void KeyValues::RecursiveSaveToFile( IBaseFileSystem *filesystem, FileHandle_t f, CUtlBuffer *pBuf, int indentLevel ) +{ + // write header + WriteIndents( filesystem, f, pBuf, indentLevel ); + INTERNALWRITE("\"", 1); + WriteConvertedString(filesystem, f, pBuf, GetName()); + INTERNALWRITE("\"\n", 2); + WriteIndents( filesystem, f, pBuf, indentLevel ); + INTERNALWRITE("{\n", 2); + + // loop through all our keys writing them to disk + for ( KeyValues *dat = m_pSub; dat != NULL; dat = dat->m_pPeer ) + { + if ( dat->m_pSub ) + { + dat->RecursiveSaveToFile( filesystem, f, pBuf, indentLevel + 1 ); + } + else + { + // only write non-empty keys + + switch (dat->m_iDataType) + { + case TYPE_STRING: + { + if (dat->m_sValue && *(dat->m_sValue)) + { + WriteIndents(filesystem, f, pBuf, indentLevel + 1); + INTERNALWRITE("\"", 1); + WriteConvertedString(filesystem, f, pBuf, dat->GetName()); + INTERNALWRITE("\"\t\t\"", 4); + + WriteConvertedString(filesystem, f, pBuf, dat->m_sValue); + + INTERNALWRITE("\"\n", 2); + } + break; + } + case TYPE_WSTRING: + { +#ifdef _WIN32 + if ( dat->m_wsValue ) + { + static char buf[KEYVALUES_TOKEN_SIZE]; + // make sure we have enough space + Assert(::WideCharToMultiByte(CP_UTF8, 0, dat->m_wsValue, -1, NULL, 0, NULL, NULL) < KEYVALUES_TOKEN_SIZE); + int result = ::WideCharToMultiByte(CP_UTF8, 0, dat->m_wsValue, -1, buf, KEYVALUES_TOKEN_SIZE, NULL, NULL); + if (result) + { + WriteIndents(filesystem, f, pBuf, indentLevel + 1); + INTERNALWRITE("\"", 1); + INTERNALWRITE(dat->GetName(), Q_strlen(dat->GetName())); + INTERNALWRITE("\"\t\t\"", 4); + + WriteConvertedString(filesystem, f, pBuf, buf); + + INTERNALWRITE("\"\n", 2); + } + } +#endif + break; + } + + case TYPE_INT: + { + WriteIndents(filesystem, f, pBuf, indentLevel + 1); + INTERNALWRITE("\"", 1); + INTERNALWRITE(dat->GetName(), Q_strlen(dat->GetName())); + INTERNALWRITE("\"\t\t\"", 4); + + char buf[32]; + Q_snprintf(buf, sizeof( buf ), "%d", dat->m_iValue); + + INTERNALWRITE(buf, Q_strlen(buf)); + INTERNALWRITE("\"\n", 2); + break; + } + + case TYPE_UINT64: + { + WriteIndents(filesystem, f, pBuf, indentLevel + 1); + INTERNALWRITE("\"", 1); + INTERNALWRITE(dat->GetName(), Q_strlen(dat->GetName())); + INTERNALWRITE("\"\t\t\"", 4); + + char buf[32]; + // write "0x" + 16 char 0-padded hex encoded 64 bit value + Q_snprintf( buf, sizeof( buf ), "0x%016I64X", *( (uint64 *)dat->m_sValue ) ); + + INTERNALWRITE(buf, Q_strlen(buf)); + INTERNALWRITE("\"\n", 2); + break; + } + + case TYPE_FLOAT: + { + WriteIndents(filesystem, f, pBuf, indentLevel + 1); + INTERNALWRITE("\"", 1); + INTERNALWRITE(dat->GetName(), Q_strlen(dat->GetName())); + INTERNALWRITE("\"\t\t\"", 4); + + char buf[48]; + Q_snprintf(buf, sizeof( buf ), "%f", dat->m_flValue); + + INTERNALWRITE(buf, Q_strlen(buf)); + INTERNALWRITE("\"\n", 2); + break; + } + case TYPE_COLOR: + DevMsg(1, "KeyValues::RecursiveSaveToFile: TODO, missing code for TYPE_COLOR.\n"); + break; + + default: + break; + } + } + } + + // write tail + WriteIndents(filesystem, f, pBuf, indentLevel); + INTERNALWRITE("}\n", 2); +} + +//----------------------------------------------------------------------------- +// Purpose: looks up a key by symbol name +//----------------------------------------------------------------------------- +KeyValues *KeyValues::FindKey(int keySymbol) const +{ + for (KeyValues *dat = m_pSub; dat != NULL; dat = dat->m_pPeer) + { + if (dat->m_iKeyName == keySymbol) + return dat; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Find a keyValue, create it if it is not found. +// Set bCreate to true to create the key if it doesn't already exist +// (which ensures a valid pointer will be returned) +//----------------------------------------------------------------------------- +KeyValues *KeyValues::FindKey(const char *keyName, bool bCreate) +{ + // return the current key if a NULL subkey is asked for + if (!keyName || !keyName[0]) + return this; + + // look for '/' characters deliminating sub fields + char szBuf[256]; + const char *subStr = strchr(keyName, '/'); + const char *searchStr = keyName; + + // pull out the substring if it exists + if (subStr) + { + int size = subStr - keyName; + Q_memcpy( szBuf, keyName, size ); + szBuf[size] = 0; + searchStr = szBuf; + } + + // lookup the symbol for the search string + HKeySymbol iSearchStr = KeyValuesSystem()->GetSymbolForString( searchStr, bCreate ); + if ( iSearchStr == INVALID_KEY_SYMBOL ) + { + // not found, couldn't possibly be in key value list + return NULL; + } + + KeyValues *lastItem = NULL; + KeyValues *dat; + // find the searchStr in the current peer list + for (dat = m_pSub; dat != NULL; dat = dat->m_pPeer) + { + lastItem = dat; // record the last item looked at (for if we need to append to the end of the list) + + // symbol compare + if (dat->m_iKeyName == iSearchStr) + { + break; + } + } + + if ( !dat && m_pChain ) + { + dat = m_pChain->FindKey(keyName, false); + } + + // make sure a key was found + if (!dat) + { + if (bCreate) + { + // we need to create a new key + dat = new KeyValues( searchStr ); +// Assert(dat != NULL); + + // insert new key at end of list + if (lastItem) + { + lastItem->m_pPeer = dat; + } + else + { + m_pSub = dat; + } + dat->m_pPeer = NULL; + + // a key graduates to be a submsg as soon as it's m_pSub is set + // this should be the only place m_pSub is set + m_iDataType = TYPE_NONE; + } + else + { + return NULL; + } + } + + // if we've still got a subStr we need to keep looking deeper in the tree + if ( subStr ) + { + // recursively chain down through the paths in the string + return dat->FindKey(subStr + 1, bCreate); + } + + return dat; +} + +//----------------------------------------------------------------------------- +// Purpose: Create a new key, with an autogenerated name. +// Name is guaranteed to be an integer, of value 1 higher than the highest +// other integer key name +//----------------------------------------------------------------------------- +KeyValues *KeyValues::CreateNewKey() +{ + int newID = 1; + + // search for any key with higher values + for (KeyValues *dat = m_pSub; dat != NULL; dat = dat->m_pPeer) + { + // case-insensitive string compare + int val = atoi(dat->GetName()); + if (newID <= val) + { + newID = val + 1; + } + } + + char buf[12]; + Q_snprintf( buf, sizeof(buf), "%d", newID ); + + return CreateKey( buf ); +} + + +//----------------------------------------------------------------------------- +// Create a key +//----------------------------------------------------------------------------- +KeyValues* KeyValues::CreateKey( const char *keyName ) +{ + // key wasn't found so just create a new one + KeyValues* dat = new KeyValues( keyName ); + + dat->UsesEscapeSequences( m_bHasEscapeSequences != 0 ); // use same format as parent does + + // add into subkey list + AddSubKey( dat ); + + return dat; +} + + +//----------------------------------------------------------------------------- +// Adds a subkey. Make sure the subkey isn't a child of some other keyvalues +//----------------------------------------------------------------------------- +void KeyValues::AddSubKey( KeyValues *pSubkey ) +{ + // Make sure the subkey isn't a child of some other keyvalues + Assert( pSubkey->m_pPeer == NULL ); + + // add into subkey list + if ( m_pSub == NULL ) + { + m_pSub = pSubkey; + } + else + { + KeyValues *pTempDat = m_pSub; + while ( pTempDat->GetNextKey() != NULL ) + { + pTempDat = pTempDat->GetNextKey(); + } + + pTempDat->SetNextKey( pSubkey ); + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: Remove a subkey from the list +//----------------------------------------------------------------------------- +void KeyValues::RemoveSubKey(KeyValues *subKey) +{ + if (!subKey) + return; + + // check the list pointer + if (m_pSub == subKey) + { + m_pSub = subKey->m_pPeer; + } + else + { + // look through the list + KeyValues *kv = m_pSub; + while (kv->m_pPeer) + { + if (kv->m_pPeer == subKey) + { + kv->m_pPeer = subKey->m_pPeer; + break; + } + + kv = kv->m_pPeer; + } + } + + subKey->m_pPeer = NULL; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Return the first subkey in the list +//----------------------------------------------------------------------------- +KeyValues *KeyValues::GetFirstSubKey() +{ + return m_pSub; +} + +//----------------------------------------------------------------------------- +// Purpose: Return the next subkey +//----------------------------------------------------------------------------- +KeyValues *KeyValues::GetNextKey() +{ + return m_pPeer; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets this key's peer to the KeyValues passed in +//----------------------------------------------------------------------------- +void KeyValues::SetNextKey( KeyValues *pDat ) +{ + m_pPeer = pDat; +} + + +KeyValues* KeyValues::GetFirstTrueSubKey() +{ + KeyValues *pRet = m_pSub; + while ( pRet && pRet->m_iDataType != TYPE_NONE ) + pRet = pRet->m_pPeer; + + return pRet; +} + +KeyValues* KeyValues::GetNextTrueSubKey() +{ + KeyValues *pRet = m_pPeer; + while ( pRet && pRet->m_iDataType != TYPE_NONE ) + pRet = pRet->m_pPeer; + + return pRet; +} + +KeyValues* KeyValues::GetFirstValue() +{ + KeyValues *pRet = m_pSub; + while ( pRet && pRet->m_iDataType == TYPE_NONE ) + pRet = pRet->m_pPeer; + + return pRet; +} + +KeyValues* KeyValues::GetNextValue() +{ + KeyValues *pRet = m_pPeer; + while ( pRet && pRet->m_iDataType == TYPE_NONE ) + pRet = pRet->m_pPeer; + + return pRet; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get the integer value of a keyName. Default value is returned +// if the keyName can't be found. +//----------------------------------------------------------------------------- +int KeyValues::GetInt( const char *keyName, int defaultValue ) +{ + KeyValues *dat = FindKey( keyName, false ); + if ( dat ) + { + switch ( dat->m_iDataType ) + { + case TYPE_STRING: + return atoi(dat->m_sValue); + case TYPE_WSTRING: +#ifdef _WIN32 + return _wtoi(dat->m_wsValue); +#else + DevMsg( "TODO: implement _wtoi\n"); + return 0; +#endif + case TYPE_FLOAT: + return (int)dat->m_flValue; + case TYPE_UINT64: + // can't convert, since it would lose data + Assert(0); + return 0; + case TYPE_INT: + case TYPE_PTR: + default: + return dat->m_iValue; + }; + } + return defaultValue; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the integer value of a keyName. Default value is returned +// if the keyName can't be found. +//----------------------------------------------------------------------------- +uint64 KeyValues::GetUint64( const char *keyName, uint64 defaultValue ) +{ + KeyValues *dat = FindKey( keyName, false ); + if ( dat ) + { + switch ( dat->m_iDataType ) + { + case TYPE_STRING: + return atoi(dat->m_sValue); + case TYPE_WSTRING: +#ifdef _WIN32 + return _wtoi(dat->m_wsValue); +#else + AssertFatal( 0 ); + return 0; +#endif + case TYPE_FLOAT: + return (int)dat->m_flValue; + case TYPE_UINT64: + return *((uint64 *)dat->m_sValue); + case TYPE_INT: + case TYPE_PTR: + default: + return dat->m_iValue; + }; + } + return defaultValue; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the pointer value of a keyName. Default value is returned +// if the keyName can't be found. +//----------------------------------------------------------------------------- +void *KeyValues::GetPtr( const char *keyName, void *defaultValue ) +{ + KeyValues *dat = FindKey( keyName, false ); + if ( dat ) + { + switch ( dat->m_iDataType ) + { + case TYPE_PTR: + return dat->m_pValue; + + case TYPE_WSTRING: + case TYPE_STRING: + case TYPE_FLOAT: + case TYPE_INT: + case TYPE_UINT64: + default: + return NULL; + }; + } + return defaultValue; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the float value of a keyName. Default value is returned +// if the keyName can't be found. +//----------------------------------------------------------------------------- +float KeyValues::GetFloat( const char *keyName, float defaultValue ) +{ + KeyValues *dat = FindKey( keyName, false ); + if ( dat ) + { + switch ( dat->m_iDataType ) + { + case TYPE_STRING: + return (float)atof(dat->m_sValue); + case TYPE_WSTRING: +#ifdef _WIN32 + return (float) _wtof(dat->m_wsValue); // no wtof +#else + Assert(0); + return 0.; +#endif + case TYPE_FLOAT: + return dat->m_flValue; + case TYPE_INT: + return (float)dat->m_iValue; + case TYPE_UINT64: + return (float)(*((uint64 *)dat->m_sValue)); + case TYPE_PTR: + default: + return 0.0f; + }; + } + return defaultValue; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the string pointer of a keyName. Default value is returned +// if the keyName can't be found. +//----------------------------------------------------------------------------- +const char *KeyValues::GetString( const char *keyName, const char *defaultValue ) +{ + KeyValues *dat = FindKey( keyName, false ); + if ( dat ) + { + // convert the data to string form then return it + char buf[64]; + switch ( dat->m_iDataType ) + { + case TYPE_FLOAT: + Q_snprintf( buf, sizeof( buf ), "%f", dat->m_flValue ); + SetString( keyName, buf ); + break; + case TYPE_INT: + case TYPE_PTR: + Q_snprintf( buf, sizeof( buf ), "%d", dat->m_iValue ); + SetString( keyName, buf ); + break; + case TYPE_UINT64: + Q_snprintf( buf, sizeof( buf ), "%I64i", *((uint64 *)(dat->m_sValue)) ); + SetString( keyName, buf ); + break; + + case TYPE_WSTRING: + { +#ifdef _WIN32 + // convert the string to char *, set it for future use, and return it + char wideBuf[512]; + int result = ::WideCharToMultiByte(CP_UTF8, 0, dat->m_wsValue, -1, wideBuf, 512, NULL, NULL); + if ( result ) + { + // note: this will copy wideBuf + SetString( keyName, wideBuf ); + } + else + { + return defaultValue; + } +#endif + break; + } + case TYPE_STRING: + break; + default: + return defaultValue; + }; + + return dat->m_sValue; + } + return defaultValue; +} + +const wchar_t *KeyValues::GetWString( const char *keyName, const wchar_t *defaultValue) +{ +#ifdef _WIN32 + KeyValues *dat = FindKey( keyName, false ); + if ( dat ) + { + wchar_t wbuf[64]; + switch ( dat->m_iDataType ) + { + case TYPE_FLOAT: + swprintf(wbuf, L"%f", dat->m_flValue); + SetWString( keyName, wbuf); + break; + case TYPE_INT: + case TYPE_PTR: + swprintf( wbuf, L"%d", dat->m_iValue ); + SetWString( keyName, wbuf ); + break; + case TYPE_UINT64: + { + swprintf( wbuf, L"%I64i", *((uint64 *)(dat->m_sValue)) ); + SetWString( keyName, wbuf ); + } + break; + + case TYPE_WSTRING: + break; + case TYPE_STRING: + { + static wchar_t wbuftemp[512]; // convert to wide + int result = ::MultiByteToWideChar(CP_UTF8, 0, dat->m_sValue, -1, wbuftemp, 512); + if ( result ) + { + SetWString( keyName, wbuftemp); + } + else + { + return defaultValue; + } + break; + } + default: + return defaultValue; + }; + + return (const wchar_t* )dat->m_wsValue; + } +#else + DevMsg("TODO: implement wide char functions\n"); +#endif + return defaultValue; +} + +//----------------------------------------------------------------------------- +// Purpose: Gets a color +//----------------------------------------------------------------------------- +Color KeyValues::GetColor( const char *keyName ) +{ + Color color(0, 0, 0, 0); + KeyValues *dat = FindKey( keyName, false ); + if ( dat ) + { + if ( dat->m_iDataType == TYPE_COLOR ) + { + color[0] = dat->m_Color[0]; + color[1] = dat->m_Color[1]; + color[2] = dat->m_Color[2]; + color[3] = dat->m_Color[3]; + } + else if ( dat->m_iDataType == TYPE_FLOAT ) + { + color[0] = (unsigned char)dat->m_flValue; + } + else if ( dat->m_iDataType == TYPE_INT ) + { + color[0] = dat->m_iValue; + } + else if ( dat->m_iDataType == TYPE_STRING ) + { + // parse the colors out of the string + float a, b, c, d; + sscanf(dat->m_sValue, "%f %f %f %f", &a, &b, &c, &d); + color[0] = (unsigned char)a; + color[1] = (unsigned char)b; + color[2] = (unsigned char)c; + color[3] = (unsigned char)d; + } + } + return color; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets a color +//----------------------------------------------------------------------------- +void KeyValues::SetColor( const char *keyName, Color value) +{ + KeyValues *dat = FindKey( keyName, true ); + + if ( dat ) + { + dat->m_iDataType = TYPE_COLOR; + dat->m_Color[0] = value[0]; + dat->m_Color[1] = value[1]; + dat->m_Color[2] = value[2]; + dat->m_Color[3] = value[3]; + } +} + +void KeyValues::SetStringValue( char const *strValue ) +{ + // delete the old value + delete [] m_sValue; + // make sure we're not storing the WSTRING - as we're converting over to STRING + delete [] m_wsValue; + m_wsValue = NULL; + + if (!strValue) + { + // ensure a valid value + strValue = ""; + } + + // allocate memory for the new value and copy it in + int len = Q_strlen( strValue ); + m_sValue = new char[len + 1]; + Q_memcpy( m_sValue, strValue, len+1 ); + + m_iDataType = TYPE_STRING; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the string value of a keyName. +//----------------------------------------------------------------------------- +void KeyValues::SetString( const char *keyName, const char *value ) +{ + KeyValues *dat = FindKey( keyName, true ); + + if ( dat ) + { + // delete the old value + delete [] dat->m_sValue; + // make sure we're not storing the WSTRING - as we're converting over to STRING + delete [] dat->m_wsValue; + dat->m_wsValue = NULL; + + if (!value) + { + // ensure a valid value + value = ""; + } + + // allocate memory for the new value and copy it in + int len = Q_strlen( value ); + dat->m_sValue = new char[len + 1]; + Q_memcpy( dat->m_sValue, value, len+1 ); + + dat->m_iDataType = TYPE_STRING; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set the string value of a keyName. +//----------------------------------------------------------------------------- +void KeyValues::SetWString( const char *keyName, const wchar_t *value ) +{ + KeyValues *dat = FindKey( keyName, true ); + if ( dat ) + { + // delete the old value + delete [] dat->m_wsValue; + // make sure we're not storing the STRING - as we're converting over to WSTRING + delete [] dat->m_sValue; + dat->m_sValue = NULL; + + if (!value) + { + // ensure a valid value + value = L""; + } + + // allocate memory for the new value and copy it in + int len = wcslen( value ); + dat->m_wsValue = new wchar_t[len + 1]; + Q_memcpy( dat->m_wsValue, value, (len+1) * sizeof(wchar_t) ); + + dat->m_iDataType = TYPE_WSTRING; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set the integer value of a keyName. +//----------------------------------------------------------------------------- +void KeyValues::SetInt( const char *keyName, int value ) +{ + KeyValues *dat = FindKey( keyName, true ); + + if ( dat ) + { + dat->m_iValue = value; + dat->m_iDataType = TYPE_INT; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set the integer value of a keyName. +//----------------------------------------------------------------------------- +void KeyValues::SetUint64( const char *keyName, uint64 value ) +{ + KeyValues *dat = FindKey( keyName, true ); + + if ( dat ) + { + // delete the old value + delete [] dat->m_sValue; + // make sure we're not storing the WSTRING - as we're converting over to STRING + delete [] dat->m_wsValue; + dat->m_wsValue = NULL; + + dat->m_sValue = new char[sizeof(uint64)]; + *((uint64 *)dat->m_sValue) = value; + dat->m_iDataType = TYPE_UINT64; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set the float value of a keyName. +//----------------------------------------------------------------------------- +void KeyValues::SetFloat( const char *keyName, float value ) +{ + KeyValues *dat = FindKey( keyName, true ); + + if ( dat ) + { + dat->m_flValue = value; + dat->m_iDataType = TYPE_FLOAT; + } +} + +void KeyValues::SetName( const char * setName ) +{ + m_iKeyName = KeyValuesSystem()->GetSymbolForString( setName ); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the pointer value of a keyName. +//----------------------------------------------------------------------------- +void KeyValues::SetPtr( const char *keyName, void *value ) +{ + KeyValues *dat = FindKey( keyName, true ); + + if ( dat ) + { + dat->m_pValue = value; + dat->m_iDataType = TYPE_PTR; + } +} + +void KeyValues::RecursiveCopyKeyValues( KeyValues& src ) +{ + // garymcthack - need to check this code for possible buffer overruns. + + m_iKeyName = src.GetNameSymbol(); + + if( !src.m_pSub ) + { + m_iDataType = src.m_iDataType; + char buf[256]; + switch( src.m_iDataType ) + { + case TYPE_NONE: + break; + case TYPE_STRING: + if( src.m_sValue ) + { + int len = Q_strlen(src.m_sValue) + 1; + m_sValue = new char[len]; + Q_strncpy( m_sValue, src.m_sValue, len ); + } + break; + case TYPE_INT: + { + m_iValue = src.m_iValue; + Q_snprintf( buf,sizeof(buf), "%d", m_iValue ); + int len = Q_strlen(buf) + 1; + m_sValue = new char[len]; + Q_strncpy( m_sValue, buf, len ); + } + break; + case TYPE_FLOAT: + { + m_flValue = src.m_flValue; + Q_snprintf( buf,sizeof(buf), "%f", m_flValue ); + int len = Q_strlen(buf) + 1; + m_sValue = new char[len]; + Q_strncpy( m_sValue, buf, len ); + } + break; + case TYPE_PTR: + { + m_pValue = src.m_pValue; + } + break; + case TYPE_UINT64: + { + m_sValue = new char[sizeof(uint64)]; + Q_memcpy( m_sValue, src.m_sValue, sizeof(uint64) ); + } + break; + case TYPE_COLOR: + { + m_Color[0] = src.m_Color[0]; + m_Color[1] = src.m_Color[1]; + m_Color[2] = src.m_Color[2]; + m_Color[3] = src.m_Color[3]; + } + break; + + default: + { + // do nothing . .what the heck is this? + Assert( 0 ); + } + break; + } + + } +#if 0 + KeyValues *pDst = this; + for ( KeyValues *pSrc = src.m_pSub; pSrc; pSrc = pSrc->m_pPeer ) + { + if ( pSrc->m_pSub ) + { + pDst->m_pSub = new KeyValues( pSrc->m_pSub->getName() ); + pDst->m_pSub->RecursiveCopyKeyValues( *pSrc->m_pSub ); + } + else + { + // copy non-empty keys + if ( pSrc->m_sValue && *(pSrc->m_sValue) ) + { + pDst->m_pPeer = new KeyValues( + } + } + } +#endif + + // Handle the immediate child + if( src.m_pSub ) + { + m_pSub = new KeyValues( NULL ); + m_pSub->RecursiveCopyKeyValues( *src.m_pSub ); + } + + // Handle the immediate peer + if( src.m_pPeer ) + { + m_pPeer = new KeyValues( NULL ); + m_pPeer->RecursiveCopyKeyValues( *src.m_pPeer ); + } +} + +KeyValues& KeyValues::operator=( KeyValues& src ) +{ + RemoveEverything(); + Init(); // reset all values + RecursiveCopyKeyValues( src ); + return *this; +} + + +//----------------------------------------------------------------------------- +// Make a new copy of all subkeys, add them all to the passed-in keyvalues +//----------------------------------------------------------------------------- +void KeyValues::CopySubkeys( KeyValues *pParent ) const +{ + // recursively copy subkeys + // Also maintain ordering.... + KeyValues *pPrev = NULL; + for ( KeyValues *sub = m_pSub; sub != NULL; sub = sub->m_pPeer ) + { + // take a copy of the subkey + KeyValues *dat = sub->MakeCopy(); + + // add into subkey list + if (pPrev) + { + pPrev->m_pPeer = dat; + } + else + { + pParent->m_pSub = dat; + } + dat->m_pPeer = NULL; + pPrev = dat; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Makes a copy of the whole key-value pair set +//----------------------------------------------------------------------------- +KeyValues *KeyValues::MakeCopy( void ) const +{ + KeyValues *newKeyValue = new KeyValues(GetName()); + + // copy data + newKeyValue->m_iDataType = m_iDataType; + switch ( m_iDataType ) + { + case TYPE_STRING: + { + if ( m_sValue ) + { + int len = Q_strlen( m_sValue ); + Assert( !newKeyValue->m_sValue ); + newKeyValue->m_sValue = new char[len + 1]; + Q_memcpy( newKeyValue->m_sValue, m_sValue, len+1 ); + } + } + break; + case TYPE_WSTRING: + { + if ( m_wsValue ) + { + int len = wcslen( m_wsValue ); + newKeyValue->m_wsValue = new wchar_t[len+1]; + Q_memcpy( newKeyValue->m_wsValue, m_wsValue, (len+1)*sizeof(wchar_t)); + } + } + break; + + case TYPE_INT: + newKeyValue->m_iValue = m_iValue; + break; + + case TYPE_FLOAT: + newKeyValue->m_flValue = m_flValue; + break; + + case TYPE_PTR: + newKeyValue->m_pValue = m_pValue; + break; + + case TYPE_COLOR: + newKeyValue->m_Color[0] = m_Color[0]; + newKeyValue->m_Color[1] = m_Color[1]; + newKeyValue->m_Color[2] = m_Color[2]; + newKeyValue->m_Color[3] = m_Color[3]; + break; + + case TYPE_UINT64: + newKeyValue->m_sValue = new char[sizeof(uint64)]; + Q_memcpy( newKeyValue->m_sValue, m_sValue, sizeof(uint64) ); + break; + }; + + // recursively copy subkeys + CopySubkeys( newKeyValue ); + return newKeyValue; +} + + +//----------------------------------------------------------------------------- +// Purpose: Check if a keyName has no value assigned to it. +//----------------------------------------------------------------------------- +bool KeyValues::IsEmpty(const char *keyName) +{ + KeyValues *dat = FindKey(keyName, false); + if (!dat) + return true; + + if (dat->m_iDataType == TYPE_NONE && dat->m_pSub == NULL) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Clear out all subkeys, and the current value +//----------------------------------------------------------------------------- +void KeyValues::Clear( void ) +{ + delete m_pSub; + m_pSub = NULL; + m_iDataType = TYPE_NONE; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the data type of the value stored in a keyName +//----------------------------------------------------------------------------- +KeyValues::types_t KeyValues::GetDataType(const char *keyName) +{ + KeyValues *dat = FindKey(keyName, false); + if (dat) + return (types_t)dat->m_iDataType; + + return TYPE_NONE; +} + +//----------------------------------------------------------------------------- +// Purpose: Deletion, ensures object gets deleted from correct heap +//----------------------------------------------------------------------------- +void KeyValues::deleteThis() +{ + delete this; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : includedKeys - +//----------------------------------------------------------------------------- +void KeyValues::AppendIncludedKeys( CUtlVector< KeyValues * >& includedKeys ) +{ + // Append any included keys, too... + int includeCount = includedKeys.Count(); + int i; + for ( i = 0; i < includeCount; i++ ) + { + KeyValues *kv = includedKeys[ i ]; + Assert( kv ); + + KeyValues *insertSpot = this; + while ( insertSpot->GetNextKey() ) + { + insertSpot = insertSpot->GetNextKey(); + } + + insertSpot->SetNextKey( kv ); + } +} + +void KeyValues::ParseIncludedKeys( char const *resourceName, const char *filetoinclude, + IBaseFileSystem* pFileSystem, const char *pPathID, CUtlVector< KeyValues * >& includedKeys ) +{ + Assert( resourceName ); + Assert( filetoinclude ); + Assert( pFileSystem ); + + // Load it... + if ( !pFileSystem ) + { + return; + } + + // Get relative subdirectory + char fullpath[ 512 ]; + Q_strncpy( fullpath, resourceName, sizeof( fullpath ) ); + + // Strip off characters back to start or first / + bool done = false; + int len = Q_strlen( fullpath ); + while ( !done ) + { + if ( len <= 0 ) + { + break; + } + + if ( fullpath[ len - 1 ] == '\\' || + fullpath[ len - 1 ] == '/' ) + { + break; + } + + // zero it + fullpath[ len - 1 ] = 0; + --len; + } + + // Append included file + Q_strncat( fullpath, filetoinclude, sizeof( fullpath ), COPY_ALL_CHARACTERS ); + + KeyValues *newKV = new KeyValues( fullpath ); + + // CUtlSymbol save = s_CurrentFileSymbol; // did that had any use ??? + + newKV->UsesEscapeSequences( m_bHasEscapeSequences != 0 ); // use same format as parent + + if ( newKV->LoadFromFile( pFileSystem, fullpath, pPathID ) ) + { + includedKeys.AddToTail( newKV ); + } + else + { + DevMsg( "KeyValues::ParseIncludedKeys: Couldn't load included keyvalue file %s\n", fullpath ); + newKV->deleteThis(); + } + + // s_CurrentFileSymbol = save; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : baseKeys - +//----------------------------------------------------------------------------- +void KeyValues::MergeBaseKeys( CUtlVector< KeyValues * >& baseKeys ) +{ + int includeCount = baseKeys.Count(); + int i; + for ( i = 0; i < includeCount; i++ ) + { + KeyValues *kv = baseKeys[ i ]; + Assert( kv ); + + RecursiveMergeKeyValues( kv ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : baseKV - keyvalues we're basing ourselves on +//----------------------------------------------------------------------------- +void KeyValues::RecursiveMergeKeyValues( KeyValues *baseKV ) +{ + // Merge ourselves + // we always want to keep our value, so nothing to do here + + // Now merge our children + for ( KeyValues *baseChild = baseKV->m_pSub; baseChild != NULL; baseChild = baseChild->m_pPeer ) + { + // for each child in base, see if we have a matching kv + + bool bFoundMatch = false; + + // If we have a child by the same name, merge those keys + for ( KeyValues *newChild = m_pSub; newChild != NULL; newChild = newChild->m_pPeer ) + { + if ( !Q_strcmp( baseChild->GetName(), newChild->GetName() ) ) + { + newChild->RecursiveMergeKeyValues( baseChild ); + bFoundMatch = true; + break; + } + } + + // If not merged, append this key + if ( !bFoundMatch ) + { + KeyValues *dat = baseChild->MakeCopy(); + Assert( dat ); + AddSubKey( dat ); + } + } +} + +//----------------------------------------------------------------------------- +// Returns whether a keyvalues conditional evaluates to true or false +// Needs more flexibility with conditionals, checking convars would be nice. +//----------------------------------------------------------------------------- +bool EvaluateConditional( const char *str ) +{ + bool bResult = false; + bool bXboxUI = IsX360(); + + if ( bXboxUI ) + { + bResult = !Q_stricmp( "[$X360]", str ); + } + else + { + bResult = !Q_stricmp( "[$WIN32]", str ); + } + + return bResult; +} + + +//----------------------------------------------------------------------------- +// Read from a buffer... +//----------------------------------------------------------------------------- +bool KeyValues::LoadFromBuffer( char const *resourceName, CUtlBuffer &buf, IBaseFileSystem* pFileSystem, const char *pPathID ) +{ + KeyValues *pPreviousKey = NULL; + KeyValues *pCurrentKey = this; + CUtlVector< KeyValues * > includedKeys; + CUtlVector< KeyValues * > baseKeys; + bool wasQuoted; + bool wasConditional; + g_KeyValuesErrorStack.SetFilename( resourceName ); + do + { + bool bAccepted = true; + + // the first thing must be a key + const char *s = ReadToken( buf, wasQuoted, wasConditional ); + if ( !buf.IsValid() || !s || *s == 0 ) + break; + + if ( !Q_stricmp( s, "#include" ) ) // special include macro (not a key name) + { + s = ReadToken( buf, wasQuoted, wasConditional ); + // Name of subfile to load is now in s + + if ( !s || *s == 0 ) + { + g_KeyValuesErrorStack.ReportError("#include is NULL " ); + } + else + { + ParseIncludedKeys( resourceName, s, pFileSystem, pPathID, includedKeys ); + } + + continue; + } + else if ( !Q_stricmp( s, "#base" ) ) + { + s = ReadToken( buf, wasQuoted, wasConditional ); + // Name of subfile to load is now in s + + if ( !s || *s == 0 ) + { + g_KeyValuesErrorStack.ReportError("#base is NULL " ); + } + else + { + ParseIncludedKeys( resourceName, s, pFileSystem, pPathID, baseKeys ); + } + + continue; + } + + if ( !pCurrentKey ) + { + pCurrentKey = new KeyValues( s ); + Assert( pCurrentKey ); + + pCurrentKey->UsesEscapeSequences( m_bHasEscapeSequences != 0 ); // same format has parent use + + if ( pPreviousKey ) + { + pPreviousKey->SetNextKey( pCurrentKey ); + } + } + else + { + pCurrentKey->SetName( s ); + } + + // get the '{' + s = ReadToken( buf, wasQuoted, wasConditional ); + + if ( wasConditional ) + { + bAccepted = EvaluateConditional( s ); + + // Now get the '{' + s = ReadToken( buf, wasQuoted, wasConditional ); + } + + if ( s && *s == '{' && !wasQuoted ) + { + // header is valid so load the file + pCurrentKey->RecursiveLoadFromBuffer( resourceName, buf ); + } + else + { + g_KeyValuesErrorStack.ReportError("LoadFromBuffer: missing {" ); + } + + if ( !bAccepted ) + { + if ( pPreviousKey ) + { + pPreviousKey->SetNextKey( NULL ); + } + pCurrentKey->Clear(); + } + else + { + pPreviousKey = pCurrentKey; + pCurrentKey = NULL; + } + } while ( buf.IsValid() ); + + AppendIncludedKeys( includedKeys ); + { + // delete included keys! + int i; + for ( i = includedKeys.Count() - 1; i > 0; i-- ) + { + KeyValues *kv = includedKeys[ i ]; + kv->deleteThis(); + } + } + + MergeBaseKeys( baseKeys ); + { + // delete base keys! + int i; + for ( i = baseKeys.Count() - 1; i >= 0; i-- ) + { + KeyValues *kv = baseKeys[ i ]; + kv->deleteThis(); + } + } + + g_KeyValuesErrorStack.SetFilename( "" ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Read from a buffer... +//----------------------------------------------------------------------------- +bool KeyValues::LoadFromBuffer( char const *resourceName, const char *pBuffer, IBaseFileSystem* pFileSystem, const char *pPathID ) +{ + if ( !pBuffer ) + return true; + + int nLen = Q_strlen( pBuffer ); + CUtlBuffer buf( pBuffer, nLen, CUtlBuffer::READ_ONLY | CUtlBuffer::TEXT_BUFFER ); + return LoadFromBuffer( resourceName, buf, pFileSystem, pPathID ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void KeyValues::RecursiveLoadFromBuffer( char const *resourceName, CUtlBuffer &buf ) +{ + CKeyErrorContext errorReport(this); + bool wasQuoted; + bool wasConditional; + // keep this out of the stack until a key is parsed + CKeyErrorContext errorKey( INVALID_KEY_SYMBOL ); + while ( 1 ) + { + bool bAccepted = true; + + // get the key name + const char * name = ReadToken( buf, wasQuoted, wasConditional ); + + if ( !name ) // EOF stop reading + { + g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got EOF instead of keyname" ); + break; + } + + if ( !*name ) // empty token, maybe "" or EOF + { + g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got empty keyname" ); + break; + } + + if ( *name == '}' && !wasQuoted ) // top level closed, stop reading + break; + + // Always create the key; note that this could potentially + // cause some duplication, but that's what we want sometimes + KeyValues *dat = CreateKey( name ); + + errorKey.Reset( dat->GetNameSymbol() ); + + // get the value + const char * value = ReadToken( buf, wasQuoted, wasConditional ); + + if ( wasConditional && value ) + { + bAccepted = EvaluateConditional( value ); + + // get the real value + value = ReadToken( buf, wasQuoted, wasConditional ); + } + + if ( !value ) + { + g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got NULL key" ); + break; + } + + if ( *value == '}' && !wasQuoted ) + { + g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got } in key" ); + break; + } + + if ( *value == '{' && !wasQuoted ) + { + // this isn't a key, it's a section + errorKey.Reset( INVALID_KEY_SYMBOL ); + // sub value list + dat->RecursiveLoadFromBuffer( resourceName, buf ); + } + else + { + if ( wasConditional ) + { + g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got conditional between key and value" ); + break; + } + + if (dat->m_sValue) + { + delete[] dat->m_sValue; + dat->m_sValue = NULL; + } + + int len = Q_strlen( value ); + + // Here, let's determine if we got a float or an int.... + char* pIEnd; // pos where int scan ended + char* pFEnd; // pos where float scan ended + const char* pSEnd = value + len ; // pos where token ends + + int ival = strtol( value, &pIEnd, 10 ); + float fval = (float)strtod( value, &pFEnd ); + + if ( *value == 0 ) + { + dat->m_iDataType = TYPE_STRING; + } + else if ( ( 18 == len ) && ( value[0] == '0' ) && ( value[1] == 'x' ) ) + { + // an 18-byte value prefixed with "0x" (followed by 16 hex digits) is an int64 value + int64 retVal = 0; + for( int i=2; i < 2 + 16; i++ ) + { + char digit = value[i]; + if ( digit >= 'a' ) + digit -= 'a' - ( '9' + 1 ); + else + if ( digit >= 'A' ) + digit -= 'A' - ( '9' + 1 ); + retVal = ( retVal * 16 ) + ( digit - '0' ); + } + dat->m_sValue = new char[sizeof(uint64)]; + *((uint64 *)dat->m_sValue) = retVal; + dat->m_iDataType = TYPE_UINT64; + } + else if ( (pFEnd > pIEnd) && (pFEnd == pSEnd) ) + { + dat->m_flValue = fval; + dat->m_iDataType = TYPE_FLOAT; + } + else if (pIEnd == pSEnd) + { + dat->m_iValue = ival; + dat->m_iDataType = TYPE_INT; + } + else + { + dat->m_iDataType = TYPE_STRING; + } + + if (dat->m_iDataType == TYPE_STRING) + { + // copy in the string information + dat->m_sValue = new char[len+1]; + Q_memcpy( dat->m_sValue, value, len+1 ); + } + + // Look ahead one token for a conditional tag + int prevPos = buf.TellGet(); + const char *peek = ReadToken( buf, wasQuoted, wasConditional ); + if ( wasConditional ) + { + bAccepted = EvaluateConditional( peek ); + } + else + { + buf.SeekGet( CUtlBuffer::SEEK_HEAD, prevPos ); + } + } + + if ( !bAccepted ) + { + this->RemoveSubKey( dat ); + dat->deleteThis(); + dat = NULL; + } + } +} + + + +// writes KeyValue as binary data to buffer +bool KeyValues::WriteAsBinary( CUtlBuffer &buffer ) +{ + if ( buffer.IsText() ) // must be a binary buffer + return false; + + if ( !buffer.IsValid() ) // must be valid, no overflows etc + return false; + + // Write subkeys: + + // loop through all our peers + for ( KeyValues *dat = this; dat != NULL; dat = dat->m_pPeer ) + { + // write type + buffer.PutUnsignedChar( dat->m_iDataType ); + + // write name + buffer.PutString( dat->GetName() ); + + // write type + switch (dat->m_iDataType) + { + case TYPE_NONE: + { + dat->m_pSub->WriteAsBinary( buffer ); + break; + } + case TYPE_STRING: + { + if (dat->m_sValue && *(dat->m_sValue)) + { + buffer.PutString( dat->m_sValue ); + } + else + { + buffer.PutString( "" ); + } + break; + } + case TYPE_WSTRING: + { + Assert( !"TYPE_WSTRING" ); + break; + } + + case TYPE_INT: + { + buffer.PutInt( dat->m_iValue ); + break; + } + + case TYPE_UINT64: + { + buffer.PutDouble( *((double *)dat->m_sValue) ); + break; + } + + case TYPE_FLOAT: + { + buffer.PutFloat( dat->m_flValue ); + break; + } + case TYPE_COLOR: + { + buffer.PutUnsignedChar( dat->m_Color[0] ); + buffer.PutUnsignedChar( dat->m_Color[1] ); + buffer.PutUnsignedChar( dat->m_Color[2] ); + buffer.PutUnsignedChar( dat->m_Color[3] ); + break; + } + case TYPE_PTR: + { + buffer.PutUnsignedInt( (int)dat->m_pValue ); + } + + default: + break; + } + } + + // write tail, marks end of peers + buffer.PutUnsignedChar( TYPE_NUMTYPES ); + + return buffer.IsValid(); +} + +// read KeyValues from binary buffer, returns true if parsing was successful +bool KeyValues::ReadAsBinary( CUtlBuffer &buffer ) +{ + if ( buffer.IsText() ) // must be a binary buffer + return false; + + if ( !buffer.IsValid() ) // must be valid, no overflows etc + return false; + + RemoveEverything(); // remove current content + Init(); // reset + + char token[KEYVALUES_TOKEN_SIZE]; + KeyValues *dat = this; + types_t type = (types_t)buffer.GetUnsignedChar(); + + // loop through all our peers + while ( true ) + { + if ( type == TYPE_NUMTYPES ) + break; // no more peers + + dat->m_iDataType = type; + + buffer.GetString( token, KEYVALUES_TOKEN_SIZE-1 ); + token[KEYVALUES_TOKEN_SIZE-1] = 0; + + dat->SetName( token ); + + switch ( type ) + { + case TYPE_NONE: + { + dat->m_pSub = new KeyValues(""); + dat->m_pSub->ReadAsBinary( buffer ); + break; + } + case TYPE_STRING: + { + buffer.GetString( token, KEYVALUES_TOKEN_SIZE-1 ); + token[KEYVALUES_TOKEN_SIZE-1] = 0; + + int len = Q_strlen( token ); + dat->m_sValue = new char[len + 1]; + Q_memcpy( dat->m_sValue, token, len+1 ); + + break; + } + case TYPE_WSTRING: + { + Assert( !"TYPE_WSTRING" ); + break; + } + + case TYPE_INT: + { + dat->m_iValue = buffer.GetInt(); + break; + } + + case TYPE_UINT64: + { + dat->m_sValue = new char[sizeof(uint64)]; + *((double *)dat->m_sValue) = buffer.GetDouble(); + } + + case TYPE_FLOAT: + { + dat->m_flValue = buffer.GetFloat(); + break; + } + case TYPE_COLOR: + { + dat->m_Color[0] = buffer.GetUnsignedChar(); + dat->m_Color[1] = buffer.GetUnsignedChar(); + dat->m_Color[2] = buffer.GetUnsignedChar(); + dat->m_Color[3] = buffer.GetUnsignedChar(); + break; + } + case TYPE_PTR: + { + dat->m_pValue = (void*)buffer.GetUnsignedInt(); + } + + default: + break; + } + + if ( !buffer.IsValid() ) // error occured + return false; + + type = (types_t)buffer.GetUnsignedChar(); + + if ( type == TYPE_NUMTYPES ) + break; + + // new peer follows + dat->m_pPeer = new KeyValues(""); + dat = dat->m_pPeer; + } + + return buffer.IsValid(); +} + +#include "tier0/memdbgoff.h" + +//----------------------------------------------------------------------------- +// Purpose: memory allocator +//----------------------------------------------------------------------------- +void *KeyValues::operator new( size_t iAllocSize ) +{ + MEM_ALLOC_CREDIT(); + return KeyValuesSystem()->AllocKeyValuesMemory(iAllocSize); +} + +void *KeyValues::operator new( size_t iAllocSize, int nBlockUse, const char *pFileName, int nLine ) +{ + MemAlloc_PushAllocDbgInfo( pFileName, nLine ); + void *p = KeyValuesSystem()->AllocKeyValuesMemory(iAllocSize); + MemAlloc_PopAllocDbgInfo(); + return p; +} + +//----------------------------------------------------------------------------- +// Purpose: deallocator +//----------------------------------------------------------------------------- +void KeyValues::operator delete( void *pMem ) +{ + KeyValuesSystem()->FreeKeyValuesMemory(pMem); +} + +void KeyValues::operator delete( void *pMem, int nBlockUse, const char *pFileName, int nLine ) +{ + KeyValuesSystem()->FreeKeyValuesMemory(pMem); +} + +void KeyValues::UnpackIntoStructure( KeyValuesUnpackStructure const *pUnpackTable, void *pDest ) +{ + uint8 *dest=(uint8 *) pDest; + while( pUnpackTable->m_pKeyName ) + { + uint8 *dest_field=dest+pUnpackTable->m_nFieldOffset; + KeyValues *find_it=FindKey( pUnpackTable->m_pKeyName ); + switch( pUnpackTable->m_eDataType ) + { + case UNPACK_TYPE_FLOAT: + { + float default_value=(pUnpackTable->m_pKeyDefault)?atof(pUnpackTable->m_pKeyDefault):0.0; + *( ( float *) dest_field)=GetFloat( pUnpackTable->m_pKeyName, default_value ); + break; + } + break; + + case UNPACK_TYPE_VECTOR: + { + Vector *dest_v=(Vector *) dest_field; + char const *src_string= + GetString( pUnpackTable->m_pKeyName, pUnpackTable->m_pKeyDefault ); + if ( (!src_string) || + ( sscanf(src_string,"%f %f %f", + &(dest_v->x), &(dest_v->y), &(dest_v->z)) != 3)) + dest_v->Init( 0, 0, 0 ); + } + break; + + case UNPACK_TYPE_FOUR_FLOATS: + { + float *dest_f=(float *) dest_field; + char const *src_string= + GetString( pUnpackTable->m_pKeyName, pUnpackTable->m_pKeyDefault ); + if ( (!src_string) || + ( sscanf(src_string,"%f %f %f %f", + dest_f,dest_f+1,dest_f+2,dest_f+3)) != 4) + memset( dest_f, 0, 4*sizeof(float) ); + } + break; + + case UNPACK_TYPE_TWO_FLOATS: + { + float *dest_f=(float *) dest_field; + char const *src_string= + GetString( pUnpackTable->m_pKeyName, pUnpackTable->m_pKeyDefault ); + if ( (!src_string) || + ( sscanf(src_string,"%f %f", + dest_f,dest_f+1)) != 2) + memset( dest_f, 0, 2*sizeof(float) ); + } + break; + + case UNPACK_TYPE_STRING: + { + char *dest_s=(char *) dest_field; + strncpy( dest_s, GetString( pUnpackTable->m_pKeyName, + pUnpackTable->m_pKeyDefault ), + pUnpackTable->m_nFieldSize ); + + } + break; + + case UNPACK_TYPE_INT: + { + int *dest_i=(int *) dest_field; + int default_int=0; + if ( pUnpackTable->m_pKeyDefault) + default_int = atoi( pUnpackTable->m_pKeyDefault ); + *(dest_i)=GetInt( pUnpackTable->m_pKeyName, default_int ); + } + break; + + case UNPACK_TYPE_VECTOR_COLOR: + { + Vector *dest_v=(Vector *) dest_field; + if (find_it) + { + Color c=GetColor( pUnpackTable->m_pKeyName ); + dest_v->x = c.r(); + dest_v->y = c.g(); + dest_v->z = c.b(); + } + else + { + if ( pUnpackTable->m_pKeyDefault ) + sscanf(pUnpackTable->m_pKeyDefault,"%f %f %f", + &(dest_v->x), &(dest_v->y), &(dest_v->z)); + else + dest_v->Init( 0, 0, 0 ); + } + *(dest_v) *= (1.0/255); + } + } + pUnpackTable++; + } +} + +//----------------------------------------------------------------------------- +// Helper function for processing a keyvalue tree for console resolution support. +// Alters key/values for easier console video resolution support. +// If running SD (640x480), the presence of "???_lodef" creates or slams "???". +// If running HD (1280x720), the presence of "???_hidef" creates or slams "???". +//----------------------------------------------------------------------------- +bool KeyValues::ProcessResolutionKeys( const char *pResString ) +{ + if ( !pResString ) + { + // not for pc, console only + return false; + } + + KeyValues *pSubKey = GetFirstSubKey(); + if ( !pSubKey ) + { + // not a block + return false; + } + + for ( ; pSubKey != NULL; pSubKey = pSubKey->GetNextKey() ) + { + // recursively descend each sub block + pSubKey->ProcessResolutionKeys( pResString ); + + // check to see if our substring is present + if ( Q_stristr( pSubKey->GetName(), pResString ) != NULL ) + { + char normalKeyName[128]; + V_strncpy( normalKeyName, pSubKey->GetName(), sizeof( normalKeyName ) ); + + // substring must match exactly, otherwise keys like "_lodef" and "_lodef_wide" would clash. + char *pString = Q_stristr( normalKeyName, pResString ); + if ( pString && !Q_stricmp( pString, pResString ) ) + { + *pString = '\0'; + + // find and delete the original key (if any) + KeyValues *pKey = FindKey( normalKeyName ); + if ( pKey ) + { + // remove the key + RemoveSubKey( pKey ); + } + + // rename the marked key + pSubKey->SetName( normalKeyName ); + } + } + } + + return true; +} diff --git a/tier1/NetAdr.cpp b/tier1/NetAdr.cpp new file mode 100644 index 00000000..78657a5d --- /dev/null +++ b/tier1/NetAdr.cpp @@ -0,0 +1,331 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// NetAdr.cpp: implementation of the CNetAdr class. +// +//===========================================================================// +#if defined( _WIN32 ) && !defined( _X360 ) +#include +#endif + +#include "tier0/dbg.h" +#include "netadr.h" +#include "tier1/strtools.h" + +#if defined( _WIN32 ) && !defined( _X360 ) +#define WIN32_LEAN_AND_MEAN +#include +typedef int socklen_t; +#elif !defined( _X360 ) +#include // ntohs() +#include // gethostbyname() +#include // getsockname() +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +bool netadr_t::CompareAdr (const netadr_t &a, bool onlyBase) const +{ + if ( a.type != type ) + return false; + + if ( type == NA_LOOPBACK ) + return true; + + if ( type == NA_BROADCAST ) + return true; + + if ( type == NA_IP ) + { + if ( !onlyBase && (port != a.port) ) + return false; + + if ( a.ip[0] == ip[0] && a.ip[1] == ip[1] && a.ip[2] == ip[2] && a.ip[3] == ip[3] ) + return true; + } + + return false; +} + +bool netadr_t::CompareClassBAdr (const netadr_t &a) const +{ + if ( a.type != type ) + return false; + + if ( type == NA_LOOPBACK ) + return true; + + if ( type == NA_IP ) + { + if (a.ip[0] == ip[0] && a.ip[1] == ip[1] ) + return true; + } + + return false; +} + +bool netadr_t::CompareClassCAdr (const netadr_t &a) const +{ + if ( a.type != type ) + return false; + + if ( type == NA_LOOPBACK ) + return true; + + if ( type == NA_IP ) + { + if (a.ip[0] == ip[0] && a.ip[1] == ip[1] && a.ip[2] == ip[2] ) + return true; + } + + return false; +} +// reserved addresses are not routeable, so they can all be used in a LAN game +bool netadr_t::IsReservedAdr () const +{ + if ( type == NA_LOOPBACK ) + return true; + + if ( type == NA_IP ) + { + if ( (ip[0] == 10) || // 10.x.x.x is reserved + (ip[0] == 127) || // 127.x.x.x + (ip[0] == 172 && ip[1] >= 16 && ip[1] <= 31) || // 172.16.x.x - 172.31.x.x + (ip[0] == 192 && ip[1] >= 168) ) // 192.168.x.x + return true; + } + return false; +} + +const char * netadr_t::ToString(bool baseOnly) const +{ + static char s[64]; + + Q_strncpy (s, "unknown", sizeof( s ) ); + + if (type == NA_LOOPBACK) + { + Q_strncpy (s, "loopback", sizeof( s ) ); + } + else if (type == NA_BROADCAST) + { + Q_strncpy (s, "broadcast", sizeof( s ) ); + } + else if (type == NA_IP) + { + if ( baseOnly) + { + Q_snprintf (s, sizeof( s ), "%i.%i.%i.%i", ip[0], ip[1], ip[2], ip[3]); + } + else + { + Q_snprintf (s, sizeof( s ), "%i.%i.%i.%i:%i", ip[0], ip[1], ip[2], ip[3], ntohs(port)); + } + } + + return s; +} + +bool netadr_t::IsLocalhost() const +{ + // are we 127.0.0.1 ? + return (ip[0] == 127) && (ip[1] == 0) && (ip[2] == 0) && (ip[3] == 1); +} + +bool netadr_t::IsLoopback() const +{ + // are we useding engine loopback buffers + return type == NA_LOOPBACK; +} + +void netadr_t::Clear() +{ + ip[0] = ip[1] = ip[2] = ip[3] = 0; + port = 0; + type = NA_NULL; +} + +void netadr_t::SetIP(uint8 b1, uint8 b2, uint8 b3, uint8 b4) +{ + ip[0] = b1; + ip[1] = b2; + ip[2] = b3; + ip[3] = b4; +} + +void netadr_t::SetIP(uint unIP) +{ + *((uint*)ip) = BigLong( unIP ); +} + +void netadr_t::SetType(netadrtype_t newtype) +{ + type = newtype; +} + +netadrtype_t netadr_t::GetType() const +{ + return type; +} + +unsigned short netadr_t::GetPort() const +{ + return BigShort( port ); +} + +unsigned int netadr_t::GetIP() const +{ + return *(unsigned int *)&ip;; +} + +unsigned long netadr_t::addr_ntohl() const +{ + return ntohl( GetIP() ); +} + +unsigned long netadr_t::addr_htonl() const +{ + return htonl( GetIP() ); +} + + +void netadr_t::ToSockadr (struct sockaddr * s) const +{ + Q_memset ( s, 0, sizeof(struct sockaddr)); + + if (type == NA_BROADCAST) + { + ((struct sockaddr_in*)s)->sin_family = AF_INET; + ((struct sockaddr_in*)s)->sin_port = port; + ((struct sockaddr_in*)s)->sin_addr.s_addr = INADDR_BROADCAST; + } + else if (type == NA_IP) + { + ((struct sockaddr_in*)s)->sin_family = AF_INET; + ((struct sockaddr_in*)s)->sin_addr.s_addr = *(int *)&ip; + ((struct sockaddr_in*)s)->sin_port = port; + } + else if (type == NA_LOOPBACK ) + { + ((struct sockaddr_in*)s)->sin_family = AF_INET; + ((struct sockaddr_in*)s)->sin_port = port; + ((struct sockaddr_in*)s)->sin_addr.s_addr = INADDR_LOOPBACK ; + } +} + +bool netadr_t::SetFromSockadr(const struct sockaddr * s) +{ + if (s->sa_family == AF_INET) + { + type = NA_IP; + *(int *)&ip = ((struct sockaddr_in *)s)->sin_addr.s_addr; + port = ((struct sockaddr_in *)s)->sin_port; + return true; + } + else + { + Clear(); + return false; + } +} + +bool netadr_t::IsValid() const +{ + return ( (port !=0 ) && (type != NA_NULL) && + ( ip[0] != 0 || ip[1] != 0 || ip[2] != 0 || ip[3] != 0 ) ); +} + +#ifdef _WIN32 +#undef SetPort // get around stupid WINSPOOL.H macro +#endif + +void netadr_t::SetPort(unsigned short newport) +{ + port = BigShort( newport ); +} + +void netadr_t::SetFromString( const char *pch, bool bUseDNS ) +{ + Clear(); + type = NA_IP; + + Assert( pch ); // invalid to call this with NULL pointer; fix your code bug! + if ( !pch ) // but let's not crash + return; + + + if ( pch[0] >= '0' && pch[0] <= '9' && strchr( pch, '.' ) ) + { + int n1, n2, n3, n4, n5; + int nRes = sscanf( pch, "%d.%d.%d.%d:%d", &n1, &n2, &n3, &n4, &n5 ); + if ( nRes >= 4 ) + { + SetIP( n1, n2, n3, n4 ); + } + + if ( nRes == 5 ) + { + SetPort( ( uint16 ) n5 ); + } + } + else if ( bUseDNS ) + { +// X360TBD: +#if !defined( _X360 ) + char szHostName[ 256 ]; + Q_strncpy( szHostName, pch, sizeof(szHostName) ); + char *pchColon = strchr( szHostName, ':' ); + if ( pchColon ) + { + *pchColon = 0; + } + + // DNS it + struct hostent *h = gethostbyname( szHostName ); + if ( !h ) + return; + + SetIP( ntohl( *(int *)h->h_addr_list[0] ) ); + + if ( pchColon ) + { + SetPort( atoi( ++pchColon ) ); + } +#else + Assert( 0 ); +#endif + } +} + +bool netadr_t::operator<(const netadr_t &netadr) const +{ + if ( *((uint *)netadr.ip) < *((uint *)ip) ) + return true; + else if ( *((uint *)netadr.ip) > *((uint *)ip) ) + return false; + return ( netadr.port < port ); +} + + +void netadr_t::SetFromSocket( int hSocket ) +{ +#if !defined(_X360) + Clear(); + type = NA_IP; + + struct sockaddr address; + int namelen = sizeof(address); + if ( getsockname( hSocket, (struct sockaddr *)&address, (socklen_t *)&namelen) == 0 ) + { + SetFromSockadr( &address ); + } +#else + Assert(0); +#endif +} diff --git a/tier1/bitbuf.cpp b/tier1/bitbuf.cpp new file mode 100644 index 00000000..f49f5ea0 --- /dev/null +++ b/tier1/bitbuf.cpp @@ -0,0 +1,1269 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "bitbuf.h" +#include "coordsize.h" +#include "mathlib/vector.h" +#include "mathlib/mathlib.h" +#include "tier1/strtools.h" +#include "bitvec.h" + +// FIXME: Can't use this until we get multithreaded allocations in tier0 working for tools +// This is used by VVIS and fails to link +// NOTE: This must be the last file included!!! +//#include "tier0/memdbgon.h" + +#ifdef _X360 +// mandatory ... wary of above comment and isolating, tier0 is built as MT though +#include "tier0/memdbgon.h" +#endif + +#if _WIN32 +#define FAST_BIT_SCAN 1 +#if _X360 +#define CountLeadingZeros(x) _CountLeadingZeros(x) +inline unsigned int CountTrailingZeros( unsigned int elem ) +{ + // this implements CountTrailingZeros() / BitScanForward() + unsigned int mask = elem-1; + unsigned int comp = ~elem; + elem = mask & comp; + return (32 - _CountLeadingZeros(elem)); +} +#else +#include +#pragma intrinsic(_BitScanReverse) +#pragma intrinsic(_BitScanForward) + +inline unsigned int CountLeadingZeros(unsigned int x) +{ + unsigned long firstBit; + if ( _BitScanReverse(&firstBit,x) ) + return 31 - firstBit; + return 32; +} +inline unsigned int CountTrailingZeros(unsigned int elem) +{ + unsigned long out; + if ( _BitScanForward(&out, elem) ) + return out; + return 32; +} + +#endif +#else +#define FAST_BIT_SCAN 0 +#endif + + +static BitBufErrorHandler g_BitBufErrorHandler = 0; + +inline int BitForBitnum(int bitnum) +{ + return GetBitForBitnum(bitnum); +} + +void InternalBitBufErrorHandler( BitBufErrorType errorType, const char *pDebugName ) +{ + if ( g_BitBufErrorHandler ) + g_BitBufErrorHandler( errorType, pDebugName ); +} + + +void SetBitBufErrorHandler( BitBufErrorHandler fn ) +{ + g_BitBufErrorHandler = fn; +} + + +// #define BB_PROFILING + + +// Precalculated bit masks for WriteUBitLong. Using these tables instead of +// doing the calculations gives a 33% speedup in WriteUBitLong. +unsigned long g_BitWriteMasks[32][33]; + +// (1 << i) - 1 +unsigned long g_ExtraMasks[32]; + +class CBitWriteMasksInit +{ +public: + CBitWriteMasksInit() + { + for( unsigned int startbit=0; startbit < 32; startbit++ ) + { + for( unsigned int nBitsLeft=0; nBitsLeft < 33; nBitsLeft++ ) + { + unsigned int endbit = startbit + nBitsLeft; + g_BitWriteMasks[startbit][nBitsLeft] = BitForBitnum(startbit) - 1; + if(endbit < 32) + g_BitWriteMasks[startbit][nBitsLeft] |= ~(BitForBitnum(endbit) - 1); + } + } + + for ( unsigned int maskBit=0; maskBit < 32; maskBit++ ) + g_ExtraMasks[maskBit] = BitForBitnum(maskBit) - 1; + } +}; +CBitWriteMasksInit g_BitWriteMasksInit; + + +// ---------------------------------------------------------------------------------------- // +// old_bf_write +// ---------------------------------------------------------------------------------------- // + +old_bf_write::old_bf_write() +{ + m_pData = NULL; + m_nDataBytes = 0; + m_nDataBits = -1; // set to -1 so we generate overflow on any operation + m_iCurBit = 0; + m_bOverflow = false; + m_bAssertOnOverflow = true; + m_pDebugName = NULL; +} + +old_bf_write::old_bf_write( const char *pDebugName, void *pData, int nBytes, int nBits ) +{ + m_bAssertOnOverflow = true; + m_pDebugName = pDebugName; + StartWriting( pData, nBytes, 0, nBits ); +} + +old_bf_write::old_bf_write( void *pData, int nBytes, int nBits ) +{ + m_bAssertOnOverflow = true; + m_pDebugName = NULL; + StartWriting( pData, nBytes, 0, nBits ); +} + +void old_bf_write::StartWriting( void *pData, int nBytes, int iStartBit, int nBits ) +{ + // Make sure it's dword aligned and padded. + Assert( (nBytes % 4) == 0 ); + Assert(((unsigned long)pData & 3) == 0); + + // The writing code will overrun the end of the buffer if it isn't dword aligned, so truncate to force alignment + nBytes &= ~3; + + m_pData = (unsigned char*)pData; + m_nDataBytes = nBytes; + + if ( nBits == -1 ) + { + m_nDataBits = nBytes << 3; + } + else + { + Assert( nBits <= nBytes*8 ); + m_nDataBits = nBits; + } + + m_iCurBit = iStartBit; + m_bOverflow = false; +} + +void old_bf_write::Reset() +{ + m_iCurBit = 0; + m_bOverflow = false; +} + + +void old_bf_write::SetAssertOnOverflow( bool bAssert ) +{ + m_bAssertOnOverflow = bAssert; +} + + +const char* old_bf_write::GetDebugName() +{ + return m_pDebugName; +} + + +void old_bf_write::SetDebugName( const char *pDebugName ) +{ + m_pDebugName = pDebugName; +} + + +void old_bf_write::SeekToBit( int bitPos ) +{ + m_iCurBit = bitPos; +} + + +// Sign bit comes first +void old_bf_write::WriteSBitLong( int data, int numbits ) +{ + // Do we have a valid # of bits to encode with? + Assert( numbits >= 1 ); + + // Note: it does this wierdness here so it's bit-compatible with regular integer data in the buffer. + // (Some old code writes direct integers right into the buffer). + if(data < 0) + { +#ifdef _DEBUG + if( numbits < 32 ) + { + // Make sure it doesn't overflow. + + if( data < 0 ) + { + Assert( data >= -(BitForBitnum(numbits-1)) ); + } + else + { + Assert( data < (BitForBitnum(numbits-1)) ); + } + } +#endif + + WriteUBitLong( (unsigned int)(0x80000000 + data), numbits - 1, false ); + WriteOneBit( 1 ); + } + else + { + WriteUBitLong((unsigned int)data, numbits - 1); + WriteOneBit( 0 ); + } +} + +#if _WIN32 +inline unsigned int BitCountNeededToEncode(unsigned int data) +{ +#if defined(_X360) + return (32 - CountLeadingZeros(data+1)) - 1; +#else + unsigned long firstBit; + _BitScanReverse(&firstBit,data+1); + return firstBit; +#endif +} +#endif // _WIN32 + +// writes an unsigned integer with variable bit length +void old_bf_write::WriteUBitVar( unsigned int data ) +{ + if ( ( data &0xf ) == data ) + { + WriteUBitLong( 0, 2 ); + WriteUBitLong( data, 4 ); + } + else + { + if ( ( data & 0xff ) == data ) + { + WriteUBitLong( 1, 2 ); + WriteUBitLong( data, 8 ); + } + else + { + if ( ( data & 0xfff ) == data ) + { + WriteUBitLong( 2, 2 ); + WriteUBitLong( data, 12 ); + } + else + { + WriteUBitLong( 0x3, 2 ); + WriteUBitLong( data, 32 ); + } + } + } +#if 0 +#if !FAST_BIT_SCAN + unsigned int bits = 0; + unsigned int base = 0; + + while (data > (base<<1)) + { + bits++; + base = BitForBitnum(bits)-1; + } +#else + unsigned int bits = BitCountNeededToEncode(data); + unsigned int base = GetBitForBitnum(bits)-1; +#endif + + // how many bits do we use + WriteUBitLong( 0, bits ); + + // end marker + WriteOneBit( 1 ); + + // write the value + if ( bits > 0) + WriteUBitLong( data - base , bits ); +#endif +} + +void old_bf_write::WriteBitLong(unsigned int data, int numbits, bool bSigned) +{ + if(bSigned) + WriteSBitLong((int)data, numbits); + else + WriteUBitLong(data, numbits); +} + +bool old_bf_write::WriteBits(const void *pInData, int nBits) +{ +#if defined( BB_PROFILING ) + VPROF( "old_bf_write::WriteBits" ); +#endif + + unsigned char *pOut = (unsigned char*)pInData; + int nBitsLeft = nBits; + + // Bounds checking.. + if ( (m_iCurBit+nBits) > m_nDataBits ) + { + SetOverflowFlag(); + CallErrorHandler( BITBUFERROR_BUFFER_OVERRUN, GetDebugName() ); + return false; + } + + // Align output to dword boundary + while (((unsigned long)pOut & 3) != 0 && nBitsLeft >= 8) + { + + WriteUBitLong( *pOut, 8, false ); + ++pOut; + nBitsLeft -= 8; + } + + if ( IsPC() && (nBitsLeft >= 32) && (m_iCurBit & 7) == 0 ) + { + // current bit is byte aligned, do block copy + int numbytes = nBitsLeft >> 3; + int numbits = numbytes << 3; + + Q_memcpy( m_pData+(m_iCurBit>>3), pOut, numbytes ); + pOut += numbytes; + nBitsLeft -= numbits; + m_iCurBit += numbits; + } + + // X360TBD: Can't write dwords in WriteBits because they'll get swapped + if ( IsPC() && nBitsLeft >= 32 ) + { + unsigned long iBitsRight = (m_iCurBit & 31); + unsigned long iBitsLeft = 32 - iBitsRight; + unsigned long bitMaskLeft = g_BitWriteMasks[iBitsRight][32]; + unsigned long bitMaskRight = g_BitWriteMasks[0][iBitsRight]; + + unsigned long *pData = &((unsigned long*)m_pData)[m_iCurBit>>5]; + + // Read dwords. + while(nBitsLeft >= 32) + { + unsigned long curData = *(unsigned long*)pOut; + pOut += sizeof(unsigned long); + + *pData &= bitMaskLeft; + *pData |= curData << iBitsRight; + + pData++; + + if ( iBitsLeft < 32 ) + { + curData >>= iBitsLeft; + *pData &= bitMaskRight; + *pData |= curData; + } + + nBitsLeft -= 32; + m_iCurBit += 32; + } + } + + + // write remaining bytes + while ( nBitsLeft >= 8 ) + { + WriteUBitLong( *pOut, 8, false ); + ++pOut; + nBitsLeft -= 8; + } + + // write remaining bits + if ( nBitsLeft ) + { + WriteUBitLong( *pOut, nBitsLeft, false ); + } + + return !IsOverflowed(); +} + + +bool old_bf_write::WriteBitsFromBuffer( bf_read *pIn, int nBits ) +{ + // This could be optimized a little by + while ( nBits > 32 ) + { + WriteUBitLong( pIn->ReadUBitLong( 32 ), 32 ); + nBits -= 32; + } + + WriteUBitLong( pIn->ReadUBitLong( nBits ), nBits ); + return !IsOverflowed() && !pIn->IsOverflowed(); +} + + +void old_bf_write::WriteBitAngle( float fAngle, int numbits ) +{ + int d; + unsigned int mask; + unsigned int shift; + + shift = BitForBitnum(numbits); + mask = shift - 1; + + d = (int)( (fAngle / 360.0) * shift ); + d &= mask; + + WriteUBitLong((unsigned int)d, numbits); +} + +void old_bf_write::WriteBitCoordMP( const float f, bool bIntegral, bool bLowPrecision ) +{ +#if defined( BB_PROFILING ) + VPROF( "old_bf_write::WriteBitCoordMP" ); +#endif + int signbit = (f <= -( bLowPrecision ? COORD_RESOLUTION_LOWPRECISION : COORD_RESOLUTION )); + int intval = (int)fabs(f); + int fractval = bLowPrecision ? + ( abs((int)(f*COORD_DENOMINATOR_LOWPRECISION)) & (COORD_DENOMINATOR_LOWPRECISION-1) ) : + ( abs((int)(f*COORD_DENOMINATOR)) & (COORD_DENOMINATOR-1) ); + + + bool bInBounds = intval < (1 << COORD_INTEGER_BITS_MP ); + + WriteOneBit( bInBounds ); + + if ( bIntegral ) + { + // Send the sign bit + WriteOneBit( intval ); + if ( intval ) + { + WriteOneBit( signbit ); + // Send the integer if we have one. + // Adjust the integers from [1..MAX_COORD_VALUE] to [0..MAX_COORD_VALUE-1] + intval--; + if ( bInBounds ) + { + WriteUBitLong( (unsigned int)intval, COORD_INTEGER_BITS_MP ); + } + else + { + WriteUBitLong( (unsigned int)intval, COORD_INTEGER_BITS ); + } + } + } + else + { + // Send the bit flags that indicate whether we have an integer part and/or a fraction part. + WriteOneBit( intval ); + // Send the sign bit + WriteOneBit( signbit ); + + if ( intval ) + { + // Adjust the integers from [1..MAX_COORD_VALUE] to [0..MAX_COORD_VALUE-1] + intval--; + if ( bInBounds ) + { + WriteUBitLong( (unsigned int)intval, COORD_INTEGER_BITS_MP ); + } + else + { + WriteUBitLong( (unsigned int)intval, COORD_INTEGER_BITS ); + } + } + WriteUBitLong( (unsigned int)fractval, bLowPrecision ? COORD_FRACTIONAL_BITS_MP_LOWPRECISION : COORD_FRACTIONAL_BITS ); + } +} + +void old_bf_write::WriteBitCoord (const float f) +{ +#if defined( BB_PROFILING ) + VPROF( "old_bf_write::WriteBitCoord" ); +#endif + int signbit = (f <= -COORD_RESOLUTION); + int intval = (int)fabs(f); + int fractval = abs((int)(f*COORD_DENOMINATOR)) & (COORD_DENOMINATOR-1); + + + // Send the bit flags that indicate whether we have an integer part and/or a fraction part. + WriteOneBit( intval ); + WriteOneBit( fractval ); + + if ( intval || fractval ) + { + // Send the sign bit + WriteOneBit( signbit ); + + // Send the integer if we have one. + if ( intval ) + { + // Adjust the integers from [1..MAX_COORD_VALUE] to [0..MAX_COORD_VALUE-1] + intval--; + WriteUBitLong( (unsigned int)intval, COORD_INTEGER_BITS ); + } + + // Send the fraction if we have one + if ( fractval ) + { + WriteUBitLong( (unsigned int)fractval, COORD_FRACTIONAL_BITS ); + } + } +} + +void old_bf_write::WriteBitFloat(float val) +{ + long intVal; + + Assert(sizeof(long) == sizeof(float)); + Assert(sizeof(float) == 4); + + intVal = *((long*)&val); + WriteUBitLong( intVal, 32 ); +} + +void old_bf_write::WriteBitVec3Coord( const Vector& fa ) +{ + int xflag, yflag, zflag; + + xflag = (fa[0] >= COORD_RESOLUTION) || (fa[0] <= -COORD_RESOLUTION); + yflag = (fa[1] >= COORD_RESOLUTION) || (fa[1] <= -COORD_RESOLUTION); + zflag = (fa[2] >= COORD_RESOLUTION) || (fa[2] <= -COORD_RESOLUTION); + + WriteOneBit( xflag ); + WriteOneBit( yflag ); + WriteOneBit( zflag ); + + if ( xflag ) + WriteBitCoord( fa[0] ); + if ( yflag ) + WriteBitCoord( fa[1] ); + if ( zflag ) + WriteBitCoord( fa[2] ); +} + +void old_bf_write::WriteBitNormal( float f ) +{ + int signbit = (f <= -NORMAL_RESOLUTION); + + // NOTE: Since +/-1 are valid values for a normal, I'm going to encode that as all ones + unsigned int fractval = abs( (int)(f*NORMAL_DENOMINATOR) ); + + // clamp.. + if (fractval > NORMAL_DENOMINATOR) + fractval = NORMAL_DENOMINATOR; + + // Send the sign bit + WriteOneBit( signbit ); + + // Send the fractional component + WriteUBitLong( fractval, NORMAL_FRACTIONAL_BITS ); +} + +void old_bf_write::WriteBitVec3Normal( const Vector& fa ) +{ + int xflag, yflag; + + xflag = (fa[0] >= NORMAL_RESOLUTION) || (fa[0] <= -NORMAL_RESOLUTION); + yflag = (fa[1] >= NORMAL_RESOLUTION) || (fa[1] <= -NORMAL_RESOLUTION); + + WriteOneBit( xflag ); + WriteOneBit( yflag ); + + if ( xflag ) + WriteBitNormal( fa[0] ); + if ( yflag ) + WriteBitNormal( fa[1] ); + + // Write z sign bit + int signbit = (fa[2] <= -NORMAL_RESOLUTION); + WriteOneBit( signbit ); +} + +void old_bf_write::WriteBitAngles( const QAngle& fa ) +{ + // FIXME: + Vector tmp( fa.x, fa.y, fa.z ); + WriteBitVec3Coord( tmp ); +} + +void old_bf_write::WriteChar(int val) +{ + WriteSBitLong(val, sizeof(char) << 3); +} + +void old_bf_write::WriteByte(int val) +{ + WriteUBitLong(val, sizeof(unsigned char) << 3); +} + +void old_bf_write::WriteShort(int val) +{ + WriteSBitLong(val, sizeof(short) << 3); +} + +void old_bf_write::WriteWord(int val) +{ + WriteUBitLong(val, sizeof(unsigned short) << 3); +} + +void old_bf_write::WriteLong(long val) +{ + WriteSBitLong(val, sizeof(long) << 3); +} + +void old_bf_write::WriteLongLong(int64 val) +{ + uint *pLongs = (uint*)&val; + + // Insert the two DWORDS according to network endian + const short endianIndex = 0x0100; + byte *idx = (byte*)&endianIndex; + WriteUBitLong(pLongs[*idx++], sizeof(long) << 3); + WriteUBitLong(pLongs[*idx], sizeof(long) << 3); +} + +void old_bf_write::WriteFloat(float val) +{ + // Pre-swap the float, since WriteBits writes raw data + LittleFloat( &val, &val ); + + WriteBits(&val, sizeof(val) << 3); +} + +bool old_bf_write::WriteBytes( const void *pBuf, int nBytes ) +{ + return WriteBits(pBuf, nBytes << 3); +} + +bool old_bf_write::WriteString(const char *pStr) +{ + if(pStr) + { + do + { + WriteChar( *pStr ); + ++pStr; + } while( *(pStr-1) != 0 ); + } + else + { + WriteChar( 0 ); + } + + return !IsOverflowed(); +} + +// ---------------------------------------------------------------------------------------- // +// old_bf_read +// ---------------------------------------------------------------------------------------- // + +old_bf_read::old_bf_read() +{ + m_pData = NULL; + m_nDataBytes = 0; + m_nDataBits = -1; // set to -1 so we overflow on any operation + m_iCurBit = 0; + m_bOverflow = false; + m_bAssertOnOverflow = true; + m_pDebugName = NULL; +} + +old_bf_read::old_bf_read( const void *pData, int nBytes, int nBits ) +{ + m_bAssertOnOverflow = true; + StartReading( pData, nBytes, 0, nBits ); +} + +old_bf_read::old_bf_read( const char *pDebugName, const void *pData, int nBytes, int nBits ) +{ + m_bAssertOnOverflow = true; + m_pDebugName = pDebugName; + StartReading( pData, nBytes, 0, nBits ); +} + +void old_bf_read::StartReading( const void *pData, int nBytes, int iStartBit, int nBits ) +{ + // Make sure we're dword aligned. + Assert(((unsigned long)pData & 3) == 0); + + m_pData = (unsigned char*)pData; + m_nDataBytes = nBytes; + + if ( nBits == -1 ) + { + m_nDataBits = m_nDataBytes << 3; + } + else + { + Assert( nBits <= nBytes*8 ); + m_nDataBits = nBits; + } + + m_iCurBit = iStartBit; + m_bOverflow = false; +} + +void old_bf_read::Reset() +{ + m_iCurBit = 0; + m_bOverflow = false; +} + +void old_bf_read::SetAssertOnOverflow( bool bAssert ) +{ + m_bAssertOnOverflow = bAssert; +} + +const char* old_bf_read::GetDebugName() +{ + return m_pDebugName; +} + +void old_bf_read::SetDebugName( const char *pName ) +{ + m_pDebugName = pName; +} + +unsigned int old_bf_read::CheckReadUBitLong(int numbits) +{ + // Ok, just read bits out. + int i, nBitValue; + unsigned int r = 0; + + for(i=0; i < numbits; i++) + { + nBitValue = ReadOneBitNoCheck(); + r |= nBitValue << i; + } + m_iCurBit -= numbits; + + return r; +} + +void old_bf_read::ReadBits(void *pOutData, int nBits) +{ +#if defined( BB_PROFILING ) + VPROF( "old_bf_write::ReadBits" ); +#endif + + unsigned char *pOut = (unsigned char*)pOutData; + int nBitsLeft = nBits; + + + // align output to dword boundary + while( ((unsigned long)pOut & 3) != 0 && nBitsLeft >= 8 ) + { + *pOut = (unsigned char)ReadUBitLong(8); + ++pOut; + nBitsLeft -= 8; + } + + // X360TBD: Can't read dwords in ReadBits because they'll get swapped + if ( IsPC() ) + { + // read dwords + while ( nBitsLeft >= 32 ) + { + *((unsigned long*)pOut) = ReadUBitLong(32); + pOut += sizeof(unsigned long); + nBitsLeft -= 32; + } + } + + // read remaining bytes + while ( nBitsLeft >= 8 ) + { + *pOut = ReadUBitLong(8); + ++pOut; + nBitsLeft -= 8; + } + + // read remaining bits + if ( nBitsLeft ) + { + *pOut = ReadUBitLong(nBitsLeft); + } + +} + +float old_bf_read::ReadBitAngle( int numbits ) +{ + float fReturn; + int i; + float shift; + + shift = (float)( BitForBitnum(numbits) ); + + i = ReadUBitLong( numbits ); + fReturn = (float)i * (360.0 / shift); + + return fReturn; +} + +unsigned int old_bf_read::PeekUBitLong( int numbits ) +{ + unsigned int r; + int i, nBitValue; +#ifdef BIT_VERBOSE + int nShifts = numbits; +#endif + + old_bf_read savebf; + + savebf = *this; // Save current state info + + r = 0; + for(i=0; i < numbits; i++) + { + nBitValue = ReadOneBit(); + + // Append to current stream + if ( nBitValue ) + { + r |= BitForBitnum(i); + } + } + + *this = savebf; + +#ifdef BIT_VERBOSE + Con_Printf( "PeekBitLong: %i %i\n", nShifts, (unsigned int)r ); +#endif + + return r; +} + +// Append numbits least significant bits from data to the current bit stream +int old_bf_read::ReadSBitLong( int numbits ) +{ + int r, sign; + + r = ReadUBitLong(numbits - 1); + + // Note: it does this wierdness here so it's bit-compatible with regular integer data in the buffer. + // (Some old code writes direct integers right into the buffer). + sign = ReadOneBit(); + if(sign) + r = -((BitForBitnum(numbits-1)) - r); + + return r; +} + +const byte g_BitMask[8] = {0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80}; +const byte g_TrailingMask[8] = {0xff, 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80}; + +inline int old_bf_read::CountRunOfZeros() +{ + int bits = 0; + if ( m_iCurBit + 32 < m_nDataBits ) + { +#if !FAST_BIT_SCAN + while (true) + { + int value = (m_pData[m_iCurBit >> 3] & g_BitMask[m_iCurBit & 7]); + ++m_iCurBit; + if ( value ) + return bits; + ++bits; + } +#else + while (true) + { + int value = (m_pData[m_iCurBit >> 3] & g_TrailingMask[m_iCurBit & 7]); + if ( !value ) + { + int zeros = (8-(m_iCurBit&7)); + bits += zeros; + m_iCurBit += zeros; + } + else + { + int zeros = CountTrailingZeros(value) - (m_iCurBit & 7); + m_iCurBit += zeros + 1; + bits += zeros; + return bits; + } + } +#endif + } + else + { + while ( ReadOneBit() == 0 ) + bits++; + } + return bits; +} + +unsigned int old_bf_read::ReadUBitVar() +{ + switch( ReadUBitLong( 2 ) ) + { + case 0: + return ReadUBitLong( 4 ); + + case 1: + return ReadUBitLong( 8 ); + + case 2: + return ReadUBitLong( 12 ); + + default: + case 3: + return ReadUBitLong( 32 ); + } +#if 0 + int bits = CountRunOfZeros(); + + unsigned int data = BitForBitnum(bits)-1; + + // read the value + if ( bits > 0) + data += ReadUBitLong( bits ); + + return data; +#endif +} + + +unsigned int old_bf_read::ReadBitLong(int numbits, bool bSigned) +{ + if(bSigned) + return (unsigned int)ReadSBitLong(numbits); + else + return ReadUBitLong(numbits); +} + + +// Basic Coordinate Routines (these contain bit-field size AND fixed point scaling constants) +float old_bf_read::ReadBitCoord (void) +{ +#if defined( BB_PROFILING ) + VPROF( "old_bf_write::ReadBitCoord" ); +#endif + int intval=0,fractval=0,signbit=0; + float value = 0.0; + + + // Read the required integer and fraction flags + intval = ReadOneBit(); + fractval = ReadOneBit(); + + // If we got either parse them, otherwise it's a zero. + if ( intval || fractval ) + { + // Read the sign bit + signbit = ReadOneBit(); + + // If there's an integer, read it in + if ( intval ) + { + // Adjust the integers from [0..MAX_COORD_VALUE-1] to [1..MAX_COORD_VALUE] + intval = ReadUBitLong( COORD_INTEGER_BITS ) + 1; + } + + // If there's a fraction, read it in + if ( fractval ) + { + fractval = ReadUBitLong( COORD_FRACTIONAL_BITS ); + } + + // Calculate the correct floating point value + value = intval + ((float)fractval * COORD_RESOLUTION); + + // Fixup the sign if negative. + if ( signbit ) + value = -value; + } + + return value; +} + +float old_bf_read::ReadBitCoordMP( bool bIntegral, bool bLowPrecision ) +{ +#if defined( BB_PROFILING ) + VPROF( "old_bf_write::ReadBitCoordMP" ); +#endif + int intval=0,fractval=0,signbit=0; + float value = 0.0; + + + bool bInBounds = ReadOneBit() ? true : false; + + if ( bIntegral ) + { + // Read the required integer and fraction flags + intval = ReadOneBit(); + // If we got either parse them, otherwise it's a zero. + if ( intval ) + { + // Read the sign bit + signbit = ReadOneBit(); + + // If there's an integer, read it in + // Adjust the integers from [0..MAX_COORD_VALUE-1] to [1..MAX_COORD_VALUE] + if ( bInBounds ) + { + value = ReadUBitLong( COORD_INTEGER_BITS_MP ) + 1; + } + else + { + value = ReadUBitLong( COORD_INTEGER_BITS ) + 1; + } + } + } + else + { + // Read the required integer and fraction flags + intval = ReadOneBit(); + + // Read the sign bit + signbit = ReadOneBit(); + + // If we got either parse them, otherwise it's a zero. + if ( intval ) + { + if ( bInBounds ) + { + intval = ReadUBitLong( COORD_INTEGER_BITS_MP ) + 1; + } + else + { + intval = ReadUBitLong( COORD_INTEGER_BITS ) + 1; + } + } + + // If there's a fraction, read it in + fractval = ReadUBitLong( bLowPrecision ? COORD_FRACTIONAL_BITS_MP_LOWPRECISION : COORD_FRACTIONAL_BITS ); + + // Calculate the correct floating point value + value = intval + ((float)fractval * ( bLowPrecision ? COORD_RESOLUTION_LOWPRECISION : COORD_RESOLUTION ) ); + } + + // Fixup the sign if negative. + if ( signbit ) + value = -value; + + return value; +} + +void old_bf_read::ReadBitVec3Coord( Vector& fa ) +{ + int xflag, yflag, zflag; + + // This vector must be initialized! Otherwise, If any of the flags aren't set, + // the corresponding component will not be read and will be stack garbage. + fa.Init( 0, 0, 0 ); + + xflag = ReadOneBit(); + yflag = ReadOneBit(); + zflag = ReadOneBit(); + + if ( xflag ) + fa[0] = ReadBitCoord(); + if ( yflag ) + fa[1] = ReadBitCoord(); + if ( zflag ) + fa[2] = ReadBitCoord(); +} + +float old_bf_read::ReadBitNormal (void) +{ + // Read the sign bit + int signbit = ReadOneBit(); + + // Read the fractional part + unsigned int fractval = ReadUBitLong( NORMAL_FRACTIONAL_BITS ); + + // Calculate the correct floating point value + float value = (float)fractval * NORMAL_RESOLUTION; + + // Fixup the sign if negative. + if ( signbit ) + value = -value; + + return value; +} + +void old_bf_read::ReadBitVec3Normal( Vector& fa ) +{ + int xflag = ReadOneBit(); + int yflag = ReadOneBit(); + + if (xflag) + fa[0] = ReadBitNormal(); + else + fa[0] = 0.0f; + + if (yflag) + fa[1] = ReadBitNormal(); + else + fa[1] = 0.0f; + + // The first two imply the third (but not its sign) + int znegative = ReadOneBit(); + + float fafafbfb = fa[0] * fa[0] + fa[1] * fa[1]; + if (fafafbfb < 1.0f) + fa[2] = sqrt( 1.0f - fafafbfb ); + else + fa[2] = 0.0f; + + if (znegative) + fa[2] = -fa[2]; +} + +void old_bf_read::ReadBitAngles( QAngle& fa ) +{ + Vector tmp; + ReadBitVec3Coord( tmp ); + fa.Init( tmp.x, tmp.y, tmp.z ); +} + +int old_bf_read::ReadChar() +{ + return ReadSBitLong(sizeof(char) << 3); +} + +int old_bf_read::ReadByte() +{ + return ReadUBitLong(sizeof(unsigned char) << 3); +} + +int old_bf_read::ReadShort() +{ + return ReadSBitLong(sizeof(short) << 3); +} + +int old_bf_read::ReadWord() +{ + return ReadUBitLong(sizeof(unsigned short) << 3); +} + +long old_bf_read::ReadLong() +{ + return ReadSBitLong(sizeof(long) << 3); +} + +int64 old_bf_read::ReadLongLong() +{ + int64 retval; + uint *pLongs = (uint*)&retval; + + // Read the two DWORDs according to network endian + const short endianIndex = 0x0100; + byte *idx = (byte*)&endianIndex; + pLongs[*idx++] = ReadUBitLong(sizeof(long) << 3); + pLongs[*idx] = ReadUBitLong(sizeof(long) << 3); + + return retval; +} + +float old_bf_read::ReadFloat() +{ + float ret; + Assert( sizeof(ret) == 4 ); + ReadBits(&ret, 32); + + // Swap the float, since ReadBits reads raw data + LittleFloat( &ret, &ret ); + return ret; +} + +bool old_bf_read::ReadBytes(void *pOut, int nBytes) +{ + ReadBits(pOut, nBytes << 3); + return !IsOverflowed(); +} + +bool old_bf_read::ReadString( char *pStr, int maxLen, bool bLine, int *pOutNumChars ) +{ + Assert( maxLen != 0 ); + + bool bTooSmall = false; + int iChar = 0; + while(1) + { + char val = ReadChar(); + if ( val == 0 ) + break; + else if ( bLine && val == '\n' ) + break; + + if ( iChar < (maxLen-1) ) + { + pStr[iChar] = val; + ++iChar; + } + else + { + bTooSmall = true; + } + } + + // Make sure it's null-terminated. + Assert( iChar < maxLen ); + pStr[iChar] = 0; + + if ( pOutNumChars ) + *pOutNumChars = iChar; + + return !IsOverflowed() && !bTooSmall; +} + + +char* old_bf_read::ReadAndAllocateString( bool *pOverflow ) +{ + char str[2048]; + + int nChars; + bool bOverflow = !ReadString( str, sizeof( str ), false, &nChars ); + if ( pOverflow ) + *pOverflow = bOverflow; + + // Now copy into the output and return it; + char *pRet = new char[ nChars + 1 ]; + for ( int i=0; i <= nChars; i++ ) + pRet[i] = str[i]; + + return pRet; +} + +void old_bf_read::ExciseBits( int startbit, int bitstoremove ) +{ + int endbit = startbit + bitstoremove; + int remaining_to_end = m_nDataBits - endbit; + + old_bf_write temp; + temp.StartWriting( (void *)m_pData, m_nDataBits << 3, startbit ); + + Seek( endbit ); + + for ( int i = 0; i < remaining_to_end; i++ ) + { + temp.WriteOneBit( ReadOneBit() ); + } + + Seek( startbit ); + + m_nDataBits -= bitstoremove; + m_nDataBytes = m_nDataBits >> 3; +} + + diff --git a/tier1/byteswap.cpp b/tier1/byteswap.cpp new file mode 100644 index 00000000..9ebcee26 --- /dev/null +++ b/tier1/byteswap.cpp @@ -0,0 +1,90 @@ +//========= Copyright © 1996-2006, Valve LLC, All rights reserved. ============ +// +// Purpose: Low level byte swapping routines. +// +// $NoKeywords: $ +//============================================================================= + +#include "byteswap.h" + +//----------------------------------------------------------------------------- +// Copy a single field from the input buffer to the output buffer, swapping the bytes if necessary +//----------------------------------------------------------------------------- +void CByteswap::SwapFieldToTargetEndian( void* pOutputBuffer, void *pData, typedescription_t *pField ) +{ + switch ( pField->fieldType ) + { + case FIELD_CHARACTER: + SwapBufferToTargetEndian( (char*)pOutputBuffer, (char*)pData, pField->fieldSize ); + break; + + case FIELD_BOOLEAN: + SwapBufferToTargetEndian( (bool*)pOutputBuffer, (bool*)pData, pField->fieldSize ); + break; + + case FIELD_SHORT: + SwapBufferToTargetEndian( (short*)pOutputBuffer, (short*)pData, pField->fieldSize ); + break; + + case FIELD_FLOAT: + SwapBufferToTargetEndian( (uint*)pOutputBuffer, (uint*)pData, pField->fieldSize ); + break; + + case FIELD_INTEGER: + SwapBufferToTargetEndian( (int*)pOutputBuffer, (int*)pData, pField->fieldSize ); + break; + + case FIELD_VECTOR: + SwapBufferToTargetEndian( (uint*)pOutputBuffer, (uint*)pData, pField->fieldSize * 3 ); + break; + + case FIELD_VECTOR2D: + SwapBufferToTargetEndian( (uint*)pOutputBuffer, (uint*)pData, pField->fieldSize * 2 ); + break; + + case FIELD_QUATERNION: + SwapBufferToTargetEndian( (uint*)pOutputBuffer, (uint*)pData, pField->fieldSize * 4 ); + break; + + case FIELD_EMBEDDED: + { + typedescription_t *pEmbed = pField->td->dataDesc; + for ( int i = 0; i < pField->fieldSize; ++i ) + { + SwapFieldsToTargetEndian( (byte*)pOutputBuffer + pEmbed->fieldOffset, + (byte*)pData + pEmbed->fieldOffset, + pField->td ); + + pOutputBuffer = (byte*)pOutputBuffer + pField->fieldSizeInBytes; + pData = (byte*)pData + pField->fieldSizeInBytes; + } + } + break; + + default: + assert(0); + } +} + +//----------------------------------------------------------------------------- +// Write a block of fields. Works a bit like the saverestore code. +//----------------------------------------------------------------------------- +void CByteswap::SwapFieldsToTargetEndian( void *pOutputBuffer, void *pBaseData, datamap_t *pDataMap ) +{ + // deal with base class first + if ( pDataMap->baseMap ) + { + SwapFieldsToTargetEndian( pOutputBuffer, pBaseData, pDataMap->baseMap ); + } + + typedescription_t *pFields = pDataMap->dataDesc; + int fieldCount = pDataMap->dataNumFields; + for ( int i = 0; i < fieldCount; ++i ) + { + typedescription_t *pField = &pFields[i]; + SwapFieldToTargetEndian( (BYTE*)pOutputBuffer + pField->fieldOffset, + (BYTE*)pBaseData + pField->fieldOffset, + pField ); + } +} + diff --git a/tier1/characterset.cpp b/tier1/characterset.cpp new file mode 100644 index 00000000..3914ae0a --- /dev/null +++ b/tier1/characterset.cpp @@ -0,0 +1,41 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include +#include "characterset.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: builds a simple lookup table of a group of important characters +// Input : *pParseGroup - pointer to the buffer for the group +// *pGroupString - null terminated list of characters to flag +//----------------------------------------------------------------------------- +void CharacterSetBuild( characterset_t *pSetBuffer, const char *pszSetString ) +{ + int i = 0; + + // Test our pointers + if ( !pSetBuffer || !pszSetString ) + return; + + memset( pSetBuffer->set, 0, sizeof(pSetBuffer->set) ); + + while ( pszSetString[i] ) + { + pSetBuffer->set[ static_cast(pszSetString[i]) ] = 1; + i++; + } + +} diff --git a/tier1/checksum_crc.cpp b/tier1/checksum_crc.cpp new file mode 100644 index 00000000..3e0f20d4 --- /dev/null +++ b/tier1/checksum_crc.cpp @@ -0,0 +1,180 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Generic CRC functions +// +//=============================================================================// + +#include "basetypes.h" +#include "commonmacros.h" +#include "checksum_crc.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define CRC32_INIT_VALUE 0xFFFFFFFFUL +#define CRC32_XOR_VALUE 0xFFFFFFFFUL + +#define NUM_BYTES 256 +static const CRC32_t pulCRCTable[NUM_BYTES] = +{ + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, + 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, + 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, + 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, + 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, + 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, + 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, + 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, + 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, + 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, + 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, + 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, + 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, + 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, + 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, + 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, + 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, + 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + +void CRC32_Init(CRC32_t *pulCRC) +{ + *pulCRC = CRC32_INIT_VALUE; +} + +void CRC32_Final(CRC32_t *pulCRC) +{ + *pulCRC ^= CRC32_XOR_VALUE; +} + +CRC32_t CRC32_GetTableEntry( unsigned int slot ) +{ + return pulCRCTable[(unsigned char)slot]; +} + +void CRC32_ProcessBuffer(CRC32_t *pulCRC, const void *pBuffer, int nBuffer) +{ + CRC32_t ulCrc = *pulCRC; + unsigned char *pb = (unsigned char *)pBuffer; + unsigned int nFront; + int nMain; + +JustAfew: + + switch (nBuffer) + { + case 7: + ulCrc = pulCRCTable[*pb++ ^ (unsigned char)ulCrc] ^ (ulCrc >> 8); + + case 6: + ulCrc = pulCRCTable[*pb++ ^ (unsigned char)ulCrc] ^ (ulCrc >> 8); + + case 5: + ulCrc = pulCRCTable[*pb++ ^ (unsigned char)ulCrc] ^ (ulCrc >> 8); + + case 4: + ulCrc ^= LittleLong( *(CRC32_t *)pb ); + ulCrc = pulCRCTable[(unsigned char)ulCrc] ^ (ulCrc >> 8); + ulCrc = pulCRCTable[(unsigned char)ulCrc] ^ (ulCrc >> 8); + ulCrc = pulCRCTable[(unsigned char)ulCrc] ^ (ulCrc >> 8); + ulCrc = pulCRCTable[(unsigned char)ulCrc] ^ (ulCrc >> 8); + *pulCRC = ulCrc; + return; + + case 3: + ulCrc = pulCRCTable[*pb++ ^ (unsigned char)ulCrc] ^ (ulCrc >> 8); + + case 2: + ulCrc = pulCRCTable[*pb++ ^ (unsigned char)ulCrc] ^ (ulCrc >> 8); + + case 1: + ulCrc = pulCRCTable[*pb++ ^ (unsigned char)ulCrc] ^ (ulCrc >> 8); + + case 0: + *pulCRC = ulCrc; + return; + } + + // We may need to do some alignment work up front, and at the end, so that + // the main loop is aligned and only has to worry about 8 byte at a time. + // + // The low-order two bits of pb and nBuffer in total control the + // upfront work. + // + nFront = ((unsigned int)pb) & 3; + nBuffer -= nFront; + switch (nFront) + { + case 3: + ulCrc = pulCRCTable[*pb++ ^ (unsigned char)ulCrc] ^ (ulCrc >> 8); + case 2: + ulCrc = pulCRCTable[*pb++ ^ (unsigned char)ulCrc] ^ (ulCrc >> 8); + case 1: + ulCrc = pulCRCTable[*pb++ ^ (unsigned char)ulCrc] ^ (ulCrc >> 8); + } + + nMain = nBuffer >> 3; + while (nMain--) + { + ulCrc ^= LittleLong( *(CRC32_t *)pb ); + ulCrc = pulCRCTable[(unsigned char)ulCrc] ^ (ulCrc >> 8); + ulCrc = pulCRCTable[(unsigned char)ulCrc] ^ (ulCrc >> 8); + ulCrc = pulCRCTable[(unsigned char)ulCrc] ^ (ulCrc >> 8); + ulCrc = pulCRCTable[(unsigned char)ulCrc] ^ (ulCrc >> 8); + ulCrc ^= LittleLong( *(CRC32_t *)(pb + 4) ); + ulCrc = pulCRCTable[(unsigned char)ulCrc] ^ (ulCrc >> 8); + ulCrc = pulCRCTable[(unsigned char)ulCrc] ^ (ulCrc >> 8); + ulCrc = pulCRCTable[(unsigned char)ulCrc] ^ (ulCrc >> 8); + ulCrc = pulCRCTable[(unsigned char)ulCrc] ^ (ulCrc >> 8); + pb += 8; + } + + nBuffer &= 7; + goto JustAfew; +} diff --git a/tier1/checksum_md5.cpp b/tier1/checksum_md5.cpp new file mode 100644 index 00000000..d41f1de2 --- /dev/null +++ b/tier1/checksum_md5.cpp @@ -0,0 +1,271 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +//===========================================================================// + +#include "basetypes.h" +#include "commonmacros.h" +#include "checksum_md5.h" +#include +#include +#include "tier1/strtools.h" +#include "tier0/dbg.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// The four core functions - F1 is optimized somewhat +// #define F1(x, y, z) (x & y | ~x & z) +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +// This is the central step in the MD5 algorithm. +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +//----------------------------------------------------------------------------- +// Purpose: The core of the MD5 algorithm, this alters an existing MD5 hash to +// reflect the addition of 16 longwords of new data. MD5Update blocks +// the data and converts bytes into longwords for this routine. +// Input : buf[4] - +// in[16] - +// Output : static void +//----------------------------------------------------------------------------- +static void MD5Transform(unsigned int buf[4], unsigned int const in[16]) +{ + register unsigned int a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +//----------------------------------------------------------------------------- +// Purpose: Start MD5 accumulation. Set bit count to 0 and buffer to mysterious initialization constants. + +// Input : *ctx - +//----------------------------------------------------------------------------- +void MD5Init(MD5Context_t *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Update context to reflect the concatenation of another buffer full of bytes. +// Input : *ctx - +// *buf - +// len - +//----------------------------------------------------------------------------- +void MD5Update(MD5Context_t *ctx, unsigned char const *buf, unsigned int len) +{ + unsigned int t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((unsigned int) len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) + { + unsigned char *p = (unsigned char *) ctx->in + t; + + t = 64 - t; + if (len < t) + { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + //byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (unsigned int *) ctx->in); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) + { + memcpy(ctx->in, buf, 64); + //byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (unsigned int *) ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + memcpy(ctx->in, buf, len); +} + +//----------------------------------------------------------------------------- +// Purpose: Final wrapup - pad to 64-byte boundary with the bit pattern +// 1 0* (64-bit count of bits processed, MSB-first) +// Input : digest[MD5_DIGEST_LENGTH] - +// *ctx - +//----------------------------------------------------------------------------- +void MD5Final(unsigned char digest[MD5_DIGEST_LENGTH], MD5Context_t *ctx) +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) + { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + //byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (unsigned int *) ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } + else + { + /* Pad block to 56 bytes */ + memset(p, 0, count - 8); + } + //byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((unsigned int *) ctx->in)[14] = ctx->bits[0]; + ((unsigned int *) ctx->in)[15] = ctx->bits[1]; + + MD5Transform(ctx->buf, (unsigned int *) ctx->in); + //byteReverse((unsigned char *) ctx->buf, 4); + memcpy(digest, ctx->buf, MD5_DIGEST_LENGTH); + memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *hash - +// hashlen - +// Output : char +//----------------------------------------------------------------------------- +char *MD5_Print( unsigned char *hash, int hashlen ) +{ + static char szReturn[64]; + + Assert( hashlen <= 32 ); + + Q_binarytohex( hash, hashlen, szReturn, sizeof( szReturn ) ); + return szReturn; +} + +//----------------------------------------------------------------------------- +// Purpose: generate pseudo random number from a seed number +// Input : seed number +// Output : pseudo random number +//----------------------------------------------------------------------------- +unsigned int MD5_PseudoRandom(unsigned int nSeed) +{ + MD5Context_t ctx; + unsigned char digest[MD5_DIGEST_LENGTH]; // The MD5 Hash + + memset( &ctx, 0, sizeof( ctx ) ); + + MD5Init(&ctx); + MD5Update(&ctx, (unsigned char*)&nSeed, sizeof(nSeed) ); + MD5Final(digest, &ctx); + + return *(unsigned int*)(digest+6); // use 4 middle bytes for random value +} diff --git a/tier1/commandbuffer.cpp b/tier1/commandbuffer.cpp new file mode 100644 index 00000000..a8f600a7 --- /dev/null +++ b/tier1/commandbuffer.cpp @@ -0,0 +1,636 @@ +//===== Copyright © 1996-2006, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//===========================================================================// + +#include "tier1/CommandBuffer.h" +#include "tier1/utlbuffer.h" +#include "tier1/strtools.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define MAX_ALIAS_NAME 32 +#define MAX_COMMAND_LENGTH 1024 + +struct cmdalias_t +{ + cmdalias_t *next; + char name[ MAX_ALIAS_NAME ]; + char *value; +}; + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +CCommandBuffer::CCommandBuffer( ) : m_Commands( 32, 32 ) +{ + m_hNextCommand = m_Commands.InvalidIndex(); + m_nWaitDelayTicks = 1; + m_nCurrentTick = 0; + m_nLastTickToProcess = -1; + m_nArgSBufferSize = 0; + m_bIsProcessingCommands = false; + m_nMaxArgSBufferLength = ARGS_BUFFER_LENGTH; +} + +CCommandBuffer::~CCommandBuffer() +{ +} + + +//----------------------------------------------------------------------------- +// Indicates how long to delay when encoutering a 'wait' command +//----------------------------------------------------------------------------- +void CCommandBuffer::SetWaitDelayTime( int nTickDelay ) +{ + Assert( nTickDelay >= 0 ); + m_nWaitDelayTicks = nTickDelay; +} + + +//----------------------------------------------------------------------------- +// Specifies a max limit of the args buffer. For unittesting. Size == 0 means use default +//----------------------------------------------------------------------------- +void CCommandBuffer::LimitArgumentBufferSize( int nSize ) +{ + if ( nSize > ARGS_BUFFER_LENGTH ) + { + nSize = ARGS_BUFFER_LENGTH; + } + + m_nMaxArgSBufferLength = ( nSize == 0 ) ? ARGS_BUFFER_LENGTH : nSize; +} + + +//----------------------------------------------------------------------------- +// Parses argv0 out of the buffer +//----------------------------------------------------------------------------- +bool CCommandBuffer::ParseArgV0( CUtlBuffer &buf, char *pArgV0, int nMaxLen, const char **pArgS ) +{ + pArgV0[0] = 0; + *pArgS = NULL; + + if ( !buf.IsValid() ) + return false; + + int nSize = buf.ParseToken( CCommand::DefaultBreakSet(), pArgV0, nMaxLen ); + if ( ( nSize <= 0 ) || ( nMaxLen == nSize ) ) + return false; + + int nArgSLen = buf.TellMaxPut() - buf.TellGet(); + *pArgS = (nArgSLen > 0) ? (const char*)buf.PeekGet() : NULL; + return true; +} + + +//----------------------------------------------------------------------------- +// Insert a command into the command queue +//----------------------------------------------------------------------------- +void CCommandBuffer::InsertCommandAtAppropriateTime( int hCommand ) +{ + int i; + Command_t &command = m_Commands[hCommand]; + for ( i = m_Commands.Head(); i != m_Commands.InvalidIndex(); i = m_Commands.Next(i) ) + { + if ( m_Commands[i].m_nTick > command.m_nTick ) + break; + } + m_Commands.LinkBefore( i, hCommand ); +} + + +//----------------------------------------------------------------------------- +// Insert a command into the command queue at the appropriate time +//----------------------------------------------------------------------------- +void CCommandBuffer::InsertImmediateCommand( int hCommand ) +{ + m_Commands.LinkBefore( m_hNextCommand, hCommand ); +} + + +//----------------------------------------------------------------------------- +// Insert a command into the command queue +//----------------------------------------------------------------------------- +bool CCommandBuffer::InsertCommand( const char *pArgS, int nCommandSize, int nTick ) +{ + if ( nCommandSize >= CCommand::MaxCommandLength() ) + { + Warning( "WARNING: Command too long... ignoring!\n%s\n", pArgS ); + return false; + } + + // Add one for null termination + if ( m_nArgSBufferSize + nCommandSize + 1 > m_nMaxArgSBufferLength ) + { + Compact(); + if ( m_nArgSBufferSize + nCommandSize + 1 > m_nMaxArgSBufferLength ) + return false; + } + + memcpy( &m_pArgSBuffer[m_nArgSBufferSize], pArgS, nCommandSize ); + m_pArgSBuffer[m_nArgSBufferSize + nCommandSize] = 0; + ++nCommandSize; + + int hCommand = m_Commands.Alloc(); + Command_t &command = m_Commands[hCommand]; + command.m_nTick = nTick; + command.m_nFirstArgS = m_nArgSBufferSize; + command.m_nBufferSize = nCommandSize; + + m_nArgSBufferSize += nCommandSize; + + if ( !m_bIsProcessingCommands || ( nTick > m_nCurrentTick ) ) + { + InsertCommandAtAppropriateTime( hCommand ); + } + else + { + InsertImmediateCommand( hCommand ); + } + return true; +} + + +//----------------------------------------------------------------------------- +// Returns the length of the next command +//----------------------------------------------------------------------------- +void CCommandBuffer::GetNextCommandLength( const char *pText, int nMaxLen, int *pCommandLength, int *pNextCommandOffset ) +{ + int nCommandLength = 0; + int nNextCommandOffset; + bool bIsQuoted = false; + bool bIsCommented = false; + for ( nNextCommandOffset=0; nNextCommandOffset < nMaxLen; ++nNextCommandOffset, nCommandLength += bIsCommented ? 0 : 1 ) + { + char c = pText[nNextCommandOffset]; + if ( !bIsCommented ) + { + if ( c == '"' ) + { + bIsQuoted = !bIsQuoted; + continue; + } + + // don't break if inside a C++ style comment + if ( !bIsQuoted && c == '/' ) + { + bIsCommented = ( nNextCommandOffset < nMaxLen-1 ) && pText[nNextCommandOffset+1] == '/'; + if ( bIsCommented ) + { + ++nNextCommandOffset; + continue; + } + } + + // don't break if inside a quoted string + if ( !bIsQuoted && c == ';' ) + break; + } + + // FIXME: This is legacy behavior; should we not break if a \n is inside a quoted string? + if ( c == '\n' ) + break; + } + + *pCommandLength = nCommandLength; + *pNextCommandOffset = nNextCommandOffset; +} + + +//----------------------------------------------------------------------------- +// Add text to command buffer, return false if it couldn't owing to overflow +//----------------------------------------------------------------------------- +bool CCommandBuffer::AddText( const char *pText, int nTickDelay ) +{ + Assert( nTickDelay >= 0 ); + + int nLen = Q_strlen( pText ); + int nTick = m_nCurrentTick + nTickDelay; + + // Parse the text into distinct commands + const char *pCurrentCommand = pText; + int nOffsetToNextCommand; + for( ; nLen > 0; nLen -= nOffsetToNextCommand+1, pCurrentCommand += nOffsetToNextCommand+1 ) + { + // find a \n or ; line break + int nCommandLength; + GetNextCommandLength( pCurrentCommand, nLen, &nCommandLength, &nOffsetToNextCommand ); + if ( nCommandLength <= 0 ) + continue; + + const char *pArgS; + char *pArgV0 = (char*)_alloca( nCommandLength+1 ); + CUtlBuffer bufParse( pCurrentCommand, nCommandLength, CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY ); + ParseArgV0( bufParse, pArgV0, nCommandLength+1, &pArgS ); + if ( pArgV0[0] == 0 ) + continue; + + // Deal with the special 'wait' command + if ( !Q_stricmp( pArgV0, "wait" ) ) + { + int nDelay = pArgS ? atoi( pArgS ) : m_nWaitDelayTicks; + nTick += nDelay; + continue; + } + + if ( !InsertCommand( pCurrentCommand, nCommandLength, nTick ) ) + return false; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Are we in the middle of processing commands? +//----------------------------------------------------------------------------- +bool CCommandBuffer::IsProcessingCommands() +{ + return m_bIsProcessingCommands; +} + + +//----------------------------------------------------------------------------- +// Delays all queued commands to execute at a later time +//----------------------------------------------------------------------------- +void CCommandBuffer::DelayAllQueuedCommands( int nDelay ) +{ + if ( nDelay <= 0 ) + return; + + for ( int i = m_Commands.Head(); i != m_Commands.InvalidIndex(); i = m_Commands.Next(i) ) + { + m_Commands[i].m_nTick += nDelay; + } +} + + +//----------------------------------------------------------------------------- +// Call this to begin iterating over all commands up to flCurrentTime +//----------------------------------------------------------------------------- +void CCommandBuffer::BeginProcessingCommands( int nDeltaTicks ) +{ + if ( nDeltaTicks == 0 ) + return; + + Assert( !m_bIsProcessingCommands ); + m_bIsProcessingCommands = true; + m_nLastTickToProcess = m_nCurrentTick + nDeltaTicks - 1; + + // Necessary to insert commands while commands are being processed + m_hNextCommand = m_Commands.Head(); +} + + +//----------------------------------------------------------------------------- +// Returns the next command +//----------------------------------------------------------------------------- +bool CCommandBuffer::DequeueNextCommand( ) +{ + m_CurrentCommand.Reset(); + + Assert( m_bIsProcessingCommands ); + if ( m_Commands.Count() == 0 ) + return false; + + int nHead = m_Commands.Head(); + Command_t &command = m_Commands[ nHead ]; + if ( command.m_nTick > m_nLastTickToProcess ) + return false; + + m_nCurrentTick = command.m_nTick; + + // Copy the current command into a temp buffer + // NOTE: This is here to avoid the pointers returned by DequeueNextCommand + // to become invalid by calling AddText. Is there a way we can avoid the memcpy? + if ( command.m_nBufferSize > 0 ) + { + m_CurrentCommand.Tokenize( &m_pArgSBuffer[command.m_nFirstArgS] ); + } + + m_Commands.Remove( nHead ); + + // Necessary to insert commands while commands are being processed + m_hNextCommand = m_Commands.Head(); + +// Msg("Dequeue : "); +// for ( int i = 0; i < nArgc; ++i ) +// { +// Msg("%s ", m_pCurrentArgv[i] ); +// } +// Msg("\n"); + return true; +} + + +//----------------------------------------------------------------------------- +// Returns the next command +//----------------------------------------------------------------------------- +int CCommandBuffer::DequeueNextCommand( const char **& ppArgv ) +{ + DequeueNextCommand(); + ppArgv = ArgV(); + return ArgC(); +} + + +//----------------------------------------------------------------------------- +// Compacts the command buffer +//----------------------------------------------------------------------------- +void CCommandBuffer::Compact() +{ + // Compress argvbuffer + argv + // NOTE: I'm using this choice instead of calling malloc + free + // per command to allocate arguments because I expect to post a + // bunch of commands but not have many delayed commands; + // avoiding the allocation cost seems more important that the memcpy + // cost here since I expect to not have much to copy. + m_nArgSBufferSize = 0; + + char pTempBuffer[ ARGS_BUFFER_LENGTH ]; + for ( int i = m_Commands.Head(); i != m_Commands.InvalidIndex(); i = m_Commands.Next(i) ) + { + Command_t &command = m_Commands[ i ]; + + memcpy( &pTempBuffer[m_nArgSBufferSize], &m_pArgSBuffer[command.m_nFirstArgS], command.m_nBufferSize ); + command.m_nFirstArgS = m_nArgSBufferSize; + m_nArgSBufferSize += command.m_nBufferSize; + } + + // NOTE: We could also store 2 buffers in the command buffer and switch + // between the two to avoid the 2nd memcpy; but again I'm guessing the memory + // tradeoff isn't worth it + memcpy( m_pArgSBuffer, pTempBuffer, m_nArgSBufferSize ); +} + + +//----------------------------------------------------------------------------- +// Call this to finish iterating over all commands +//----------------------------------------------------------------------------- +void CCommandBuffer::EndProcessingCommands() +{ + Assert( m_bIsProcessingCommands ); + m_bIsProcessingCommands = false; + m_nCurrentTick = m_nLastTickToProcess + 1; + m_hNextCommand = m_Commands.InvalidIndex(); + + // Extract commands that are before the end time + // NOTE: This is a bug for this to + int i = m_Commands.Head(); + if ( i == m_Commands.InvalidIndex() ) + { + m_nArgSBufferSize = 0; + return; + } + + while ( i != m_Commands.InvalidIndex() ) + { + if ( m_Commands[i].m_nTick >= m_nCurrentTick ) + break; + + AssertMsgOnce( false, "CCommandBuffer::EndProcessingCommands() called before all appropriate commands were dequeued.\n" ); + int nNext = i; + Msg( "Warning: Skipping command %s\n", m_pArgSBuffer[ m_Commands[i].m_nFirstArgS ] ); + m_Commands.Remove( i ); + i = nNext; + } + + Compact(); +} + + +//----------------------------------------------------------------------------- +// Returns a handle to the next command to process +//----------------------------------------------------------------------------- +CommandHandle_t CCommandBuffer::GetNextCommandHandle() +{ + Assert( m_bIsProcessingCommands ); + return m_Commands.Head(); +} + + +#if 0 +/* +=============== +Cmd_Alias_f + +Creates a new command that executes a command string (possibly ; seperated) +=============== +*/ +void Cmd_Alias_f (void) +{ + cmdalias_t *a; + char cmd[MAX_COMMAND_LENGTH]; + int i, c; + char *s; + + if (Cmd_Argc() == 1) + { + Con_Printf ("Current alias commands:\n"); + for (a = cmd_alias ; a ; a=a->next) + Con_Printf ("%s : %s\n", a->name, a->value); + return; + } + + s = Cmd_Argv(1); + if (strlen(s) >= MAX_ALIAS_NAME) + { + Con_Printf ("Alias name is too long\n"); + return; + } + +// copy the rest of the command line + cmd[0] = 0; // start out with a null string + c = Cmd_Argc(); + for (i=2 ; i< c ; i++) + { + Q_strncat(cmd, Cmd_Argv(i), sizeof( cmd ), COPY_ALL_CHARACTERS); + if (i != c) + { + Q_strncat (cmd, " ", sizeof( cmd ), COPY_ALL_CHARACTERS ); + } + } + Q_strncat (cmd, "\n", sizeof( cmd ), COPY_ALL_CHARACTERS); + + // if the alias already exists, reuse it + for (a = cmd_alias ; a ; a=a->next) + { + if (!strcmp(s, a->name)) + { + if ( !strcmp( a->value, cmd ) ) // Re-alias the same thing + return; + + delete[] a->value; + break; + } + } + + if (!a) + { + a = (cmdalias_t *)new cmdalias_t; + a->next = cmd_alias; + cmd_alias = a; + } + Q_strncpy (a->name, s, sizeof( a->name ) ); + + a->value = COM_StringCopy(cmd); +} + + + +/* +============================================================================= + + COMMAND EXECUTION + +============================================================================= +*/ + +#define MAX_ARGS 80 + +static int cmd_argc; +static char *cmd_argv[MAX_ARGS]; +static char *cmd_null_string = ""; +static const char *cmd_args = NULL; + +cmd_source_t cmd_source; + +//----------------------------------------------------------------------------- +// Purpose: +// Output : void Cmd_Init +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Cmd_Shutdown( void ) +{ + // TODO, cleanup + while ( cmd_alias ) + { + cmdalias_t *next = cmd_alias->next; + delete cmd_alias->value; // created by StringCopy() + delete cmd_alias; + cmd_alias = next; + } +} + + + +/* +============ +Cmd_ExecuteString + +A complete command line has been parsed, so try to execute it +FIXME: lookupnoadd the token to speed search? +============ +*/ +const ConCommandBase *Cmd_ExecuteString (const char *text, cmd_source_t src) +{ + cmdalias_t *a; + + cmd_source = src; + Cmd_TokenizeString (text); + +// execute the command line + if (!Cmd_Argc()) + return NULL; // no tokens + +// check alias + for (a=cmd_alias ; a ; a=a->next) + { + if (!Q_strcasecmp (cmd_argv[0], a->name)) + { + Cbuf_InsertText (a->value); + return NULL; + } + } + +// check ConCommands + ConCommandBase const *pCommand = ConCommandBase::FindCommand( cmd_argv[ 0 ] ); + if ( pCommand && pCommand->IsCommand() ) + { + bool isServerCommand = ( pCommand->IsBitSet( FCVAR_GAMEDLL ) && + // Typed at console + cmd_source == src_command && + // Not HLDS + !sv.IsDedicated() ); + + // Hook to allow game .dll to figure out who type the message on a listen server + if ( serverGameClients ) + { + // We're actually the server, so set it up locally + if ( sv.IsActive() ) + { + g_pServerPluginHandler->SetCommandClient( -1 ); + +#ifndef SWDS + // Special processing for listen server player + if ( isServerCommand ) + { + g_pServerPluginHandler->SetCommandClient( cl.m_nPlayerSlot ); + } +#endif + } + // We're not the server, but we've been a listen server (game .dll loaded) + // forward this command tot he server instead of running it locally if we're still + // connected + // Otherwise, things like "say" won't work unless you quit and restart + else if ( isServerCommand ) + { + if ( cl.IsConnected() ) + { + Cmd_ForwardToServer(); + return NULL; + } + else + { + // It's a server command, but we're not connected to a server. Don't try to execute it. + return NULL; + } + } + } + + // Allow cheat commands in singleplayer, debug, or multiplayer with sv_cheats on +#ifndef _DEBUG + if ( pCommand->IsBitSet( FCVAR_CHEAT ) ) + { + if ( !Host_IsSinglePlayerGame() && sv_cheats.GetInt() == 0 ) + { + Msg( "Can't use cheat command %s in multiplayer, unless the server has sv_cheats set to 1.\n", pCommand->GetName() ); + return NULL; + } + } +#endif + + (( ConCommand * )pCommand )->Dispatch(); + return pCommand; + } + + // check cvars + if ( cv->IsCommand() ) + { + return pCommand; + } + + // forward the command line to the server, so the entity DLL can parse it + if ( cmd_source == src_command ) + { + if ( cl.IsConnected() ) + { + Cmd_ForwardToServer(); + return NULL; + } + } + + Msg("Unknown command \"%s\"\n", Cmd_Argv(0)); + + return NULL; +} +#endif diff --git a/tier1/convar.cpp b/tier1/convar.cpp new file mode 100644 index 00000000..17dd3fbb --- /dev/null +++ b/tier1/convar.cpp @@ -0,0 +1,1287 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// + +#include +#include +#include +#include "basetypes.h" +#include "tier1/convar.h" +#include "tier1/strtools.h" +#include "tier1/characterset.h" +#include "tier1/utlbuffer.h" +#include "tier1/tier1.h" +#include "tier1/convar_serverbounded.h" +#include "icvar.h" +#include "tier0/dbg.h" +#include "Color.h" +#if defined( _X360 ) +#include "xbox/xbox_console.h" +#endif +#include "tier0/memdbgon.h" + + +// Comment this out when we release. +//#define ALLOW_DEVELOPMENT_CVARS + + + +//----------------------------------------------------------------------------- +// Statically constructed list of ConCommandBases, +// used for registering them with the ICVar interface +//----------------------------------------------------------------------------- +ConCommandBase *ConCommandBase::s_pConCommandBases = NULL; +IConCommandBaseAccessor *ConCommandBase::s_pAccessor = NULL; +static int s_nCVarFlag = 0; +static int s_nDLLIdentifier = -1; // A unique identifier indicating which DLL this convar came from +static bool s_bRegistered = false; + +class CDefaultAccessor : public IConCommandBaseAccessor +{ +public: + virtual bool RegisterConCommandBase( ConCommandBase *pVar ) + { + // Link to engine's list instead + g_pCVar->RegisterConCommand( pVar ); + return true; + } +}; + +static CDefaultAccessor s_DefaultAccessor; + +//----------------------------------------------------------------------------- +// Called by the framework to register ConCommandBases with the ICVar +//----------------------------------------------------------------------------- +void ConVar_Register( int nCVarFlag, IConCommandBaseAccessor *pAccessor ) +{ + if ( !g_pCVar || s_bRegistered ) + return; + + Assert( s_nDLLIdentifier < 0 ); + s_bRegistered = true; + s_nCVarFlag = nCVarFlag; + s_nDLLIdentifier = g_pCVar->AllocateDLLIdentifier(); + + ConCommandBase *pCur, *pNext; + + ConCommandBase::s_pAccessor = pAccessor ? pAccessor : &s_DefaultAccessor; + pCur = ConCommandBase::s_pConCommandBases; + while ( pCur ) + { + pNext = pCur->m_pNext; + pCur->AddFlags( s_nCVarFlag ); + pCur->Init(); + pCur = pNext; + } + + ConCommandBase::s_pConCommandBases = NULL; +} + +void ConVar_Unregister( ) +{ + if ( !g_pCVar || !s_bRegistered ) + return; + + Assert( s_nDLLIdentifier >= 0 ); + g_pCVar->UnregisterConCommands( s_nDLLIdentifier ); + s_nDLLIdentifier = -1; + s_bRegistered = false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Default constructor +//----------------------------------------------------------------------------- +ConCommandBase::ConCommandBase( void ) +{ + m_bRegistered = false; + m_pszName = NULL; + m_pszHelpString = NULL; + + m_nFlags = 0; + m_pNext = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: The base console invoked command/cvar interface +// Input : *pName - name of variable/command +// *pHelpString - help text +// flags - flags +//----------------------------------------------------------------------------- +ConCommandBase::ConCommandBase( const char *pName, const char *pHelpString /*=0*/, int flags /*= 0*/ ) +{ + Create( pName, pHelpString, flags ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +ConCommandBase::~ConCommandBase( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool ConCommandBase::IsCommand( void ) const +{ +// Assert( 0 ); This can't assert. . causes a recursive assert in Sys_Printf, etc. + return true; +} + + +//----------------------------------------------------------------------------- +// Returns the DLL identifier +//----------------------------------------------------------------------------- +CVarDLLIdentifier_t ConCommandBase::GetDLLIdentifier() const +{ + return s_nDLLIdentifier; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pName - +// callback - +// *pHelpString - +// flags - +//----------------------------------------------------------------------------- +void ConCommandBase::Create( const char *pName, const char *pHelpString /*= 0*/, int flags /*= 0*/ ) +{ + static const char *empty_string = ""; + + m_bRegistered = false; + + // Name should be static data + Assert( pName ); + m_pszName = pName; + m_pszHelpString = pHelpString ? pHelpString : empty_string; + + m_nFlags = flags; + +#ifdef ALLOW_DEVELOPMENT_CVARS + m_nFlags &= ~FCVAR_DEVELOPMENTONLY; +#endif + + if ( !( m_nFlags & FCVAR_UNREGISTERED ) ) + { + m_pNext = s_pConCommandBases; + s_pConCommandBases = this; + } + else + { + // It's unregistered + m_pNext = NULL; + } + + // If s_pAccessor is already set (this ConVar is not a global variable), + // register it. + if ( s_pAccessor ) + { + Init(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Used internally by OneTimeInit to initialize. +//----------------------------------------------------------------------------- +void ConCommandBase::Init() +{ + if ( s_pAccessor ) + { + s_pAccessor->RegisterConCommandBase( this ); + } +} + +void ConCommandBase::Shutdown() +{ + if ( g_pCVar ) + { + g_pCVar->UnregisterConCommand( this ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Return name of the command/var +// Output : const char +//----------------------------------------------------------------------------- +const char *ConCommandBase::GetName( void ) const +{ + return m_pszName; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flag - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool ConCommandBase::IsFlagSet( int flag ) const +{ + return ( flag & m_nFlags ) ? true : false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flags - +//----------------------------------------------------------------------------- +void ConCommandBase::AddFlags( int flags ) +{ + m_nFlags |= flags; + +#ifdef ALLOW_DEVELOPMENT_CVARS + m_nFlags &= ~FCVAR_DEVELOPMENTONLY; +#endif +} + +void ConCommandBase::RemoveFlags( int flags ) +{ + m_nFlags &= ~flags; +} + +int ConCommandBase::GetFlags( void ) const +{ + return m_nFlags; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const ConCommandBase +//----------------------------------------------------------------------------- +const ConCommandBase *ConCommandBase::GetNext( void ) const +{ + return m_pNext; +} + +ConCommandBase *ConCommandBase::GetNext( void ) +{ + return m_pNext; +} + + +//----------------------------------------------------------------------------- +// Purpose: Copies string using local new/delete operators +// Input : *from - +// Output : char +//----------------------------------------------------------------------------- +char *ConCommandBase::CopyString( const char *from ) +{ + int len; + char *to; + + len = strlen( from ); + if ( len <= 0 ) + { + to = new char[1]; + to[0] = 0; + } + else + { + to = new char[len+1]; + Q_strncpy( to, from, len+1 ); + } + return to; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *ConCommandBase::GetHelpText( void ) const +{ + return m_pszHelpString; +} + +//----------------------------------------------------------------------------- +// Purpose: Has this cvar been registered +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool ConCommandBase::IsRegistered( void ) const +{ + return m_bRegistered; +} + + +//----------------------------------------------------------------------------- +// +// Con Commands start here +// +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// Global methods +//----------------------------------------------------------------------------- +static characterset_t s_BreakSet; +static bool s_bBuiltBreakSet = false; + + +//----------------------------------------------------------------------------- +// Tokenizer class +//----------------------------------------------------------------------------- +CCommand::CCommand() +{ + if ( !s_bBuiltBreakSet ) + { + s_bBuiltBreakSet = true; + CharacterSetBuild( &s_BreakSet, "{}()':" ); + } + + Reset(); +} + +CCommand::CCommand( int nArgC, const char **ppArgV ) +{ + Assert( nArgC > 0 ); + + if ( !s_bBuiltBreakSet ) + { + s_bBuiltBreakSet = true; + CharacterSetBuild( &s_BreakSet, "{}()':" ); + } + + Reset(); + + char *pBuf = m_pArgvBuffer; + char *pSBuf = m_pArgSBuffer; + m_nArgc = nArgC; + for ( int i = 0; i < nArgC; ++i ) + { + m_ppArgv[i] = pBuf; + int nLen = Q_strlen( ppArgV[i] ); + memcpy( pBuf, ppArgV[i], nLen+1 ); + if ( i == 0 ) + { + m_nArgv0Size = nLen; + } + pBuf += nLen+1; + + bool bContainsSpace = strchr( ppArgV[i], ' ' ) != NULL; + if ( bContainsSpace ) + { + *pSBuf++ = '\"'; + } + memcpy( pSBuf, ppArgV[i], nLen ); + pSBuf += nLen; + if ( bContainsSpace ) + { + *pSBuf++ = '\"'; + } + + if ( i != nArgC - 1 ) + { + *pSBuf++ = ' '; + } + } +} + +void CCommand::Reset() +{ + m_nArgc = 0; + m_nArgv0Size = 0; + m_pArgSBuffer[0] = 0; +} + +characterset_t* CCommand::DefaultBreakSet() +{ + return &s_BreakSet; +} + +bool CCommand::Tokenize( const char *pCommand, characterset_t *pBreakSet ) +{ + Reset(); + if ( !pCommand ) + return false; + + // Use default break set + if ( !pBreakSet ) + { + pBreakSet = &s_BreakSet; + } + + // Copy the current command into a temp buffer + // NOTE: This is here to avoid the pointers returned by DequeueNextCommand + // to become invalid by calling AddText. Is there a way we can avoid the memcpy? + int nLen = Q_strlen( pCommand ); + if ( nLen >= COMMAND_MAX_LENGTH - 1 ) + { + Warning( "CCommand::Tokenize: Encountered command which overflows the tokenizer buffer.. Skipping!\n" ); + return false; + } + + memcpy( m_pArgSBuffer, pCommand, nLen + 1 ); + + // Parse the current command into the current command buffer + CUtlBuffer bufParse( m_pArgSBuffer, nLen, CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY ); + int nArgvBufferSize = 0; + while ( bufParse.IsValid() && ( m_nArgc < COMMAND_MAX_ARGC ) ) + { + char *pArgvBuf = &m_pArgvBuffer[nArgvBufferSize]; + int nMaxLen = COMMAND_MAX_LENGTH - nArgvBufferSize; + int nStartGet = bufParse.TellGet(); + int nSize = bufParse.ParseToken( pBreakSet, pArgvBuf, nMaxLen ); + if ( nSize < 0 ) + break; + + // Check for overflow condition + if ( nMaxLen == nSize ) + { + Reset(); + return false; + } + + if ( m_nArgc == 1 ) + { + // Deal with the case where the arguments were quoted + m_nArgv0Size = bufParse.TellGet(); + bool bFoundEndQuote = m_pArgSBuffer[m_nArgv0Size-1] == '\"'; + if ( bFoundEndQuote ) + { + --m_nArgv0Size; + } + m_nArgv0Size -= nSize; + Assert( m_nArgv0Size != 0 ); + + // The StartGet check is to handle this case: "foo"bar + // which will parse into 2 different args. ArgS should point to bar. + bool bFoundStartQuote = ( m_nArgv0Size > nStartGet ) && ( m_pArgSBuffer[m_nArgv0Size-1] == '\"' ); + Assert( bFoundEndQuote == bFoundStartQuote ); + if ( bFoundStartQuote ) + { + --m_nArgv0Size; + } + } + + m_ppArgv[ m_nArgc++ ] = pArgvBuf; + if( m_nArgc >= COMMAND_MAX_ARGC ) + { + Warning( "CCommand::Tokenize: Encountered command which overflows the argument buffer.. Clamped!\n" ); + } + + nArgvBufferSize += nSize + 1; + Assert( nArgvBufferSize <= COMMAND_MAX_LENGTH ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Helper function to parse arguments to commands. +//----------------------------------------------------------------------------- +const char* CCommand::FindArg( const char *pName ) const +{ + int nArgC = ArgC(); + for ( int i = 1; i < nArgC; i++ ) + { + if ( !Q_stricmp( Arg(i), pName ) ) + return (i+1) < nArgC ? Arg( i+1 ) : ""; + } + return 0; +} + +int CCommand::FindArgInt( const char *pName, int nDefaultVal ) const +{ + const char *pVal = FindArg( pName ); + if ( pVal ) + return atoi( pVal ); + else + return nDefaultVal; +} + + +//----------------------------------------------------------------------------- +// Default console command autocompletion function +//----------------------------------------------------------------------------- +int DefaultCompletionFunc( const char *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] ) +{ + return 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: Constructs a console command +//----------------------------------------------------------------------------- +//ConCommand::ConCommand() +//{ +// m_bIsNewConCommand = true; +//} + +ConCommand::ConCommand( const char *pName, FnCommandCallbackV1_t callback, const char *pHelpString /*= 0*/, int flags /*= 0*/, FnCommandCompletionCallback completionFunc /*= 0*/ ) +{ + // Set the callback + m_fnCommandCallbackV1 = callback; + m_bUsingNewCommandCallback = false; + m_bUsingCommandCallbackInterface = false; + m_fnCompletionCallback = completionFunc ? completionFunc : DefaultCompletionFunc; + m_bHasCompletionCallback = completionFunc != 0 ? true : false; + + // Setup the rest + BaseClass::Create( pName, pHelpString, flags ); +} + +ConCommand::ConCommand( const char *pName, FnCommandCallback_t callback, const char *pHelpString /*= 0*/, int flags /*= 0*/, FnCommandCompletionCallback completionFunc /*= 0*/ ) +{ + // Set the callback + m_fnCommandCallback = callback; + m_bUsingNewCommandCallback = true; + m_fnCompletionCallback = completionFunc ? completionFunc : DefaultCompletionFunc; + m_bHasCompletionCallback = completionFunc != 0 ? true : false; + m_bUsingCommandCallbackInterface = false; + + // Setup the rest + BaseClass::Create( pName, pHelpString, flags ); +} + +ConCommand::ConCommand( const char *pName, ICommandCallback *pCallback, const char *pHelpString /*= 0*/, int flags /*= 0*/, ICommandCompletionCallback *pCompletionCallback /*= 0*/ ) +{ + // Set the callback + m_pCommandCallback = pCallback; + m_bUsingNewCommandCallback = false; + m_pCommandCompletionCallback = pCompletionCallback; + m_bHasCompletionCallback = ( pCompletionCallback != 0 ); + m_bUsingCommandCallbackInterface = true; + + // Setup the rest + BaseClass::Create( pName, pHelpString, flags ); +} + +//----------------------------------------------------------------------------- +// Destructor +//----------------------------------------------------------------------------- +ConCommand::~ConCommand( void ) +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns true if this is a command +//----------------------------------------------------------------------------- +bool ConCommand::IsCommand( void ) const +{ + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Invoke the function if there is one +//----------------------------------------------------------------------------- +void ConCommand::Dispatch( const CCommand &command ) +{ + if ( m_bUsingNewCommandCallback ) + { + if ( m_fnCommandCallback ) + { + ( *m_fnCommandCallback )( command ); + return; + } + } + else if ( m_bUsingCommandCallbackInterface ) + { + if ( m_pCommandCallback ) + { + m_pCommandCallback->CommandCallback( command ); + return; + } + } + else + { + if ( m_fnCommandCallbackV1 ) + { + ( *m_fnCommandCallbackV1 )(); + return; + } + } + + // Command without callback!!! + AssertMsg( 0, ( "Encountered ConCommand without a callback!\n" ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Calls the autocompletion method to get autocompletion suggestions +//----------------------------------------------------------------------------- +int ConCommand::AutoCompleteSuggest( const char *partial, CUtlVector< CUtlString > &commands ) +{ + if ( m_bUsingCommandCallbackInterface ) + { + if ( !m_pCommandCompletionCallback ) + return 0; + return m_pCommandCompletionCallback->CommandCompletionCallback( partial, commands ); + } + + Assert( m_fnCompletionCallback ); + if ( !m_fnCompletionCallback ) + return 0; + + char rgpchCommands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ]; + int iret = ( m_fnCompletionCallback )( partial, rgpchCommands ); + for ( int i = 0 ; i < iret; ++i ) + { + CUtlString str = rgpchCommands[ i ]; + commands.AddToTail( str ); + } + return iret; +} + + +//----------------------------------------------------------------------------- +// Returns true if the console command can autocomplete +//----------------------------------------------------------------------------- +bool ConCommand::CanAutoComplete( void ) +{ + return m_bHasCompletionCallback; +} + + + +//----------------------------------------------------------------------------- +// +// Console Variables +// +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Various constructors +//----------------------------------------------------------------------------- +ConVar::ConVar( const char *pName, const char *pDefaultValue, int flags /* = 0 */ ) +{ + Create( pName, pDefaultValue, flags ); +} + +ConVar::ConVar( const char *pName, const char *pDefaultValue, int flags, const char *pHelpString ) +{ + Create( pName, pDefaultValue, flags, pHelpString ); +} + +ConVar::ConVar( const char *pName, const char *pDefaultValue, int flags, const char *pHelpString, bool bMin, float fMin, bool bMax, float fMax ) +{ + Create( pName, pDefaultValue, flags, pHelpString, bMin, fMin, bMax, fMax ); +} + +ConVar::ConVar( const char *pName, const char *pDefaultValue, int flags, const char *pHelpString, FnChangeCallback_t callback ) +{ + Create( pName, pDefaultValue, flags, pHelpString, false, 0.0, false, 0.0, callback ); +} + +ConVar::ConVar( const char *pName, const char *pDefaultValue, int flags, const char *pHelpString, bool bMin, float fMin, bool bMax, float fMax, FnChangeCallback_t callback ) +{ + Create( pName, pDefaultValue, flags, pHelpString, bMin, fMin, bMax, fMax, callback ); +} + + +//----------------------------------------------------------------------------- +// Destructor +//----------------------------------------------------------------------------- +ConVar::~ConVar( void ) +{ + if ( m_pszString ) + { + delete[] m_pszString; + m_pszString = NULL; + } +} + + +//----------------------------------------------------------------------------- +// Install a change callback (there shouldn't already be one....) +//----------------------------------------------------------------------------- +void ConVar::InstallChangeCallback( FnChangeCallback_t callback ) +{ + Assert( !m_pParent->m_fnChangeCallback || !callback ); + m_pParent->m_fnChangeCallback = callback; + + if ( m_pParent->m_fnChangeCallback ) + { + // Call it immediately to set the initial value... + m_pParent->m_fnChangeCallback( this, m_pszString, m_fValue ); + } +} + +bool ConVar::IsFlagSet( int flag ) const +{ + return ( flag & m_pParent->m_nFlags ) ? true : false; +} + +const char *ConVar::GetHelpText( void ) const +{ + return m_pParent->m_pszHelpString; +} + +void ConVar::AddFlags( int flags ) +{ + m_pParent->m_nFlags |= flags; + +#ifdef ALLOW_DEVELOPMENT_CVARS + m_pParent->m_nFlags &= ~FCVAR_DEVELOPMENTONLY; +#endif +} + +int ConVar::GetFlags( void ) const +{ + return m_pParent->m_nFlags; +} + +bool ConVar::IsRegistered( void ) const +{ + return m_pParent->m_bRegistered; +} + +const char *ConVar::GetName( void ) const +{ + return m_pParent->m_pszName; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool ConVar::IsCommand( void ) const +{ + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +//----------------------------------------------------------------------------- +void ConVar::Init() +{ + BaseClass::Init(); +} + +const char *ConVar::GetBaseName( void ) const +{ + return m_pParent->m_pszName; +} + +int ConVar::GetSplitScreenPlayerSlot( void ) const +{ + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *value - +//----------------------------------------------------------------------------- +void ConVar::InternalSetValue( const char *value ) +{ + float fNewValue; + char tempVal[ 32 ]; + char *val; + + Assert(m_pParent == this); // Only valid for root convars. + + float flOldValue = m_fValue; + + val = (char *)value; + fNewValue = ( float )atof( value ); + + if ( ClampValue( fNewValue ) ) + { + Q_snprintf( tempVal,sizeof(tempVal), "%f", fNewValue ); + val = tempVal; + } + + // Redetermine value + m_fValue = fNewValue; + m_nValue = ( int )( m_fValue ); + + if ( !( m_nFlags & FCVAR_NEVER_AS_STRING ) ) + { + ChangeStringValue( val, flOldValue ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *tempVal - +//----------------------------------------------------------------------------- +void ConVar::ChangeStringValue( const char *tempVal, float flOldValue ) +{ + Assert( !( m_nFlags & FCVAR_NEVER_AS_STRING ) ); + + char* pszOldValue = (char*)stackalloc( m_StringLength ); + memcpy( pszOldValue, m_pszString, m_StringLength ); + + int len = Q_strlen(tempVal) + 1; + + if ( len > m_StringLength) + { + if (m_pszString) + { + delete[] m_pszString; + } + + m_pszString = new char[len]; + m_StringLength = len; + } + + memcpy( m_pszString, tempVal, len ); + + // Invoke any necessary callback function + if ( m_fnChangeCallback ) + { + m_fnChangeCallback( this, pszOldValue, flOldValue ); + } + + g_pCVar->CallGlobalChangeCallbacks( this, pszOldValue, flOldValue ); + + stackfree( pszOldValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Check whether to clamp and then perform clamp +// Input : value - +// Output : Returns true if value changed +//----------------------------------------------------------------------------- +bool ConVar::ClampValue( float& value ) +{ + if ( m_bHasMin && ( value < m_fMinVal ) ) + { + value = m_fMinVal; + return true; + } + + if ( m_bHasMax && ( value > m_fMaxVal ) ) + { + value = m_fMaxVal; + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *value - +//----------------------------------------------------------------------------- +void ConVar::InternalSetFloatValue( float fNewValue ) +{ + if ( fNewValue == m_fValue ) + return; + + Assert( m_pParent == this ); // Only valid for root convars. + + // Check bounds + ClampValue( fNewValue ); + + // Redetermine value + float flOldValue = m_fValue; + m_fValue = fNewValue; + m_nValue = ( int )m_fValue; + + if ( !( m_nFlags & FCVAR_NEVER_AS_STRING ) ) + { + char tempVal[ 32 ]; + Q_snprintf( tempVal, sizeof( tempVal), "%f", m_fValue ); + ChangeStringValue( tempVal, flOldValue ); + } + else + { + Assert( !m_fnChangeCallback ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *value - +//----------------------------------------------------------------------------- +void ConVar::InternalSetIntValue( int nValue ) +{ + if ( nValue == m_nValue ) + return; + + Assert( m_pParent == this ); // Only valid for root convars. + + float fValue = (float)nValue; + if ( ClampValue( fValue ) ) + { + nValue = ( int )( fValue ); + } + + // Redetermine value + float flOldValue = m_fValue; + m_fValue = fValue; + m_nValue = nValue; + + if ( !( m_nFlags & FCVAR_NEVER_AS_STRING ) ) + { + char tempVal[ 32 ]; + Q_snprintf( tempVal, sizeof( tempVal ), "%d", m_nValue ); + ChangeStringValue( tempVal, flOldValue ); + } + else + { + Assert( !m_fnChangeCallback ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *value - +//----------------------------------------------------------------------------- +void ConVar::InternalSetColorValue( Color cValue ) +{ + int color = cValue.GetRawColor(); + InternalSetIntValue( color ); +} + +//----------------------------------------------------------------------------- +// Purpose: Private creation +//----------------------------------------------------------------------------- +void ConVar::Create( const char *pName, const char *pDefaultValue, int flags /*= 0*/, + const char *pHelpString /*= NULL*/, bool bMin /*= false*/, float fMin /*= 0.0*/, + bool bMax /*= false*/, float fMax /*= false*/, FnChangeCallback_t callback /*= NULL*/ ) +{ + static const char *empty_string = ""; + + m_pParent = this; + + // Name should be static data + m_pszDefaultValue = pDefaultValue ? pDefaultValue : empty_string; + Assert( m_pszDefaultValue ); + + m_StringLength = strlen( m_pszDefaultValue ) + 1; + m_pszString = new char[m_StringLength]; + memcpy( m_pszString, m_pszDefaultValue, m_StringLength ); + + m_bHasMin = bMin; + m_fMinVal = fMin; + m_bHasMax = bMax; + m_fMaxVal = fMax; + + m_fnChangeCallback = callback; + + m_fValue = ( float )atof( m_pszString ); + + // Bounds Check, should never happen, if it does, no big deal + if ( m_bHasMin && ( m_fValue < m_fMinVal ) ) + { + Assert( 0 ); + } + + if ( m_bHasMax && ( m_fValue > m_fMaxVal ) ) + { + Assert( 0 ); + } + + m_nValue = ( int )m_fValue; + + BaseClass::Create( pName, pHelpString, flags ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *value - +//----------------------------------------------------------------------------- +void ConVar::SetValue(const char *value) +{ + ConVar *var = ( ConVar * )m_pParent; + var->InternalSetValue( value ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : value - +//----------------------------------------------------------------------------- +void ConVar::SetValue( float value ) +{ + ConVar *var = ( ConVar * )m_pParent; + var->InternalSetFloatValue( value ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : value - +//----------------------------------------------------------------------------- +void ConVar::SetValue( int value ) +{ + ConVar *var = ( ConVar * )m_pParent; + var->InternalSetIntValue( value ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : value - +//----------------------------------------------------------------------------- +void ConVar::SetValue( Color value ) +{ + ConVar *var = ( ConVar * )m_pParent; + var->InternalSetColorValue( value ); +} + +//----------------------------------------------------------------------------- +// Purpose: Reset to default value +//----------------------------------------------------------------------------- +void ConVar::Revert( void ) +{ + // Force default value again + ConVar *var = ( ConVar * )m_pParent; + var->SetValue( var->m_pszDefaultValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : minVal - +// Output : true if there is a min set +//----------------------------------------------------------------------------- +bool ConVar::GetMin( float& minVal ) const +{ + minVal = m_pParent->m_fMinVal; + return m_pParent->m_bHasMin; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : maxVal - +//----------------------------------------------------------------------------- +bool ConVar::GetMax( float& maxVal ) const +{ + maxVal = m_pParent->m_fMaxVal; + return m_pParent->m_bHasMax; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *ConVar::GetDefault( void ) const +{ + return m_pParent->m_pszDefaultValue; +} + + +//----------------------------------------------------------------------------- +// This version is simply used to make reading convars simpler. +// Writing convars isn't allowed in this mode +//----------------------------------------------------------------------------- +class CEmptyConVar : public ConVar +{ +public: + CEmptyConVar() : ConVar( "", "0" ) {} + // Used for optimal read access + virtual void SetValue( const char *pValue ) {} + virtual void SetValue( float flValue ) {} + virtual void SetValue( int nValue ) {} + virtual void SetValue( Color cValue ) {} + virtual const char *GetName( void ) const { return ""; } + virtual bool IsFlagSet( int nFlags ) const { return false; } +}; + +static CEmptyConVar s_EmptyConVar; + +ConVarRef::ConVarRef( const char *pName ) +{ + Init( pName, false ); +} + +ConVarRef::ConVarRef( const char *pName, bool bIgnoreMissing ) +{ + Init( pName, bIgnoreMissing ); +} + +void ConVarRef::Init( const char *pName, bool bIgnoreMissing ) +{ + m_pConVar = g_pCVar ? g_pCVar->FindVar( pName ) : &s_EmptyConVar; + if ( !m_pConVar ) + { + m_pConVar = &s_EmptyConVar; + } + m_pConVarState = static_cast< ConVar * >( m_pConVar ); + if( !IsValid() ) + { + static bool bFirst = true; + if ( g_pCVar || bFirst ) + { + if ( !bIgnoreMissing ) + { + Warning( "ConVarRef %s doesn't point to an existing ConVar\n", pName ); + } + bFirst = false; + } + } +} + +ConVarRef::ConVarRef( IConVar *pConVar ) +{ + m_pConVar = pConVar ? pConVar : &s_EmptyConVar; + m_pConVarState = static_cast< ConVar * >( m_pConVar ); +} + +bool ConVarRef::IsValid() const +{ + return m_pConVar != &s_EmptyConVar; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ConVar_PrintFlags( const ConCommandBase *var ) +{ + bool any = false; + if ( var->IsFlagSet( FCVAR_GAMEDLL ) ) + { + ConMsg( " game" ); + any = true; + } + + if ( var->IsFlagSet( FCVAR_CLIENTDLL ) ) + { + ConMsg( " client" ); + any = true; + } + + if ( var->IsFlagSet( FCVAR_ARCHIVE ) ) + { + ConMsg( " archive" ); + any = true; + } + + if ( var->IsFlagSet( FCVAR_NOTIFY ) ) + { + ConMsg( " notify" ); + any = true; + } + + if ( var->IsFlagSet( FCVAR_SPONLY ) ) + { + ConMsg( " singleplayer" ); + any = true; + } + + if ( var->IsFlagSet( FCVAR_NOT_CONNECTED ) ) + { + ConMsg( " notconnected" ); + any = true; + } + + if ( var->IsFlagSet( FCVAR_CHEAT ) ) + { + ConMsg( " cheat" ); + any = true; + } + + if ( var->IsFlagSet( FCVAR_REPLICATED ) ) + { + ConMsg( " replicated" ); + any = true; + } + + if ( var->IsFlagSet( FCVAR_SERVER_CAN_EXECUTE ) ) + { + ConMsg( " server_can_execute" ); + any = true; + } + + if ( var->IsFlagSet( FCVAR_CLIENTCMD_CAN_EXECUTE ) ) + { + ConMsg( " clientcmd_can_execute" ); + any = true; + } + + if ( any ) + { + ConMsg( "\n" ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ConVar_PrintDescription( const ConCommandBase *pVar ) +{ + bool bMin, bMax; + float fMin, fMax; + const char *pStr; + + assert( pVar ); + + Color clr; + clr.SetColor( 255, 100, 100, 255 ); + + if ( !pVar->IsCommand() ) + { + ConVar *var = ( ConVar * )pVar; + const ConVar_ServerBounded *pBounded = dynamic_cast( var ); + + bMin = var->GetMin( fMin ); + bMax = var->GetMax( fMax ); + + const char *value = NULL; + char tempVal[ 32 ]; + + if ( pBounded || var->IsFlagSet( FCVAR_NEVER_AS_STRING ) ) + { + value = tempVal; + + int intVal = pBounded ? pBounded->GetInt() : var->GetInt(); + float floatVal = pBounded ? pBounded->GetFloat() : var->GetFloat(); + + if ( fabs( (float)intVal - floatVal ) < 0.000001 ) + { + Q_snprintf( tempVal, sizeof( tempVal ), "%d", intVal ); + } + else + { + Q_snprintf( tempVal, sizeof( tempVal ), "%f", floatVal ); + } + } + else + { + value = var->GetString(); + } + + if ( value ) + { + ConColorMsg( clr, "\"%s\" = \"%s\"", var->GetName(), value ); + + if ( stricmp( value, var->GetDefault() ) ) + { + ConMsg( " ( def. \"%s\" )", var->GetDefault() ); + } + } + + if ( bMin ) + { + ConMsg( " min. %f", fMin ); + } + if ( bMax ) + { + ConMsg( " max. %f", fMax ); + } + + ConMsg( "\n" ); + + // Handled virtualized cvars. + if ( pBounded && fabs( pBounded->GetFloat() - var->GetFloat() ) > 0.0001f ) + { + ConColorMsg( clr, "** NOTE: The real value is %.3f but the server has temporarily restricted it to %.3f **\n", + var->GetFloat(), pBounded->GetFloat() ); + } + } + else + { + ConCommand *var = ( ConCommand * )pVar; + + ConColorMsg( clr, "\"%s\"\n", var->GetName() ); + } + + ConVar_PrintFlags( pVar ); + + pStr = pVar->GetHelpText(); + if ( pStr && pStr[0] ) + { + ConMsg( " - %s\n", pStr ); + } +} diff --git a/tier1/datamanager.cpp b/tier1/datamanager.cpp new file mode 100644 index 00000000..24935280 --- /dev/null +++ b/tier1/datamanager.cpp @@ -0,0 +1,410 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "basetypes.h" +#include "datamanager.h" + +DECLARE_POINTER_HANDLE( memhandle_t ); + +#define AUTO_LOCK_DM() AUTO_LOCK_( CDataManagerBase, *this ) + +CDataManagerBase::CDataManagerBase( unsigned int maxSize ) +{ + m_targetMemorySize = maxSize; + m_memUsed = 0; + m_lruList = m_memoryLists.CreateList(); + m_lockList = m_memoryLists.CreateList(); + m_freeList = m_memoryLists.CreateList(); + m_listsAreFreed = 0; +} + +CDataManagerBase::~CDataManagerBase() +{ + Assert( m_listsAreFreed ); +} + +void CDataManagerBase::NotifySizeChanged( memhandle_t handle, unsigned int oldSize, unsigned int newSize ) +{ + Lock(); + m_memUsed += (int)newSize - (int)oldSize; + Unlock(); +} + +void CDataManagerBase::SetTargetSize( unsigned int targetSize ) +{ + m_targetMemorySize = targetSize; +} + +unsigned int CDataManagerBase::FlushAllUnlocked() +{ + Lock(); + + int nFlush = m_memoryLists.Count( m_lruList ); + void **pScratch = (void **)_alloca( nFlush * sizeof(void *) ); + CUtlVector destroyList( pScratch, nFlush ); + + unsigned nBytesInitial = MemUsed_Inline(); + + int node = m_memoryLists.Head(m_lruList); + while ( node != m_memoryLists.InvalidIndex() ) + { + int next = m_memoryLists.Next(node); + m_memoryLists.Unlink( m_lruList, node ); + destroyList.AddToTail( GetForFreeByIndex( node ) ); + node = next; + } + + Unlock(); + + for ( int i = 0; i < nFlush; i++ ) + { + DestroyResourceStorage( destroyList[i] ); + } + + return ( nBytesInitial - MemUsed_Inline() ); +} + +unsigned int CDataManagerBase::FlushToTargetSize() +{ + return EnsureCapacity(0); +} + +// Frees everything! The LRU AND the LOCKED items. This is only used to forcibly free the resources, +// not to make space. + +unsigned int CDataManagerBase::FlushAll() +{ + Lock(); + + int nFlush = m_memoryLists.Count( m_lruList ) + m_memoryLists.Count( m_lockList ); + void **pScratch = (void **)_alloca( nFlush * sizeof(void *) ); + CUtlVector destroyList( pScratch, nFlush ); + + unsigned result = MemUsed_Inline(); + int node; + int nextNode; + + node = m_memoryLists.Head(m_lruList); + while ( node != m_memoryLists.InvalidIndex() ) + { + nextNode = m_memoryLists.Next(node); + m_memoryLists.Unlink( m_lruList, node ); + destroyList.AddToTail( GetForFreeByIndex( node ) ); + node = nextNode; + } + + node = m_memoryLists.Head(m_lockList); + while ( node != m_memoryLists.InvalidIndex() ) + { + nextNode = m_memoryLists.Next(node); + m_memoryLists.Unlink( m_lockList, node ); + m_memoryLists[node].lockCount = 0; + destroyList.AddToTail( GetForFreeByIndex( node ) ); + node = nextNode; + } + + m_listsAreFreed = false; + Unlock(); + + for ( int i = 0; i < nFlush; i++ ) + { + DestroyResourceStorage( destroyList[i] ); + } + + return result; +} + +unsigned int CDataManagerBase::Purge( unsigned int nBytesToPurge ) +{ + unsigned int nTargetSize = MemUsed_Inline() - nBytesToPurge; + if ( nTargetSize < 0 ) + nTargetSize = 0; + unsigned int nImpliedCapacity = MemTotal_Inline() - nTargetSize; + return EnsureCapacity( nImpliedCapacity ); +} + + +void CDataManagerBase::DestroyResource( memhandle_t handle ) +{ + Lock(); + unsigned short index = FromHandle( handle ); + if ( !m_memoryLists.IsValidIndex(index) ) + { + Unlock(); + return; + } + + Assert( m_memoryLists[index].lockCount == 0 ); + if ( m_memoryLists[index].lockCount ) + BreakLock( handle ); + m_memoryLists.Unlink( m_lruList, index ); + void *p = GetForFreeByIndex( index ); + Unlock(); + + DestroyResourceStorage( p ); +} + + +void *CDataManagerBase::LockResource( memhandle_t handle ) +{ + AUTO_LOCK_DM(); + unsigned short memoryIndex = FromHandle(handle); + if ( memoryIndex != m_memoryLists.InvalidIndex() ) + { + if ( m_memoryLists[memoryIndex].lockCount == 0 ) + { + m_memoryLists.Unlink( m_lruList, memoryIndex ); + m_memoryLists.LinkToTail( m_lockList, memoryIndex ); + } + Assert(m_memoryLists[memoryIndex].lockCount != (unsigned short)-1); + m_memoryLists[memoryIndex].lockCount++; + return m_memoryLists[memoryIndex].pStore; + } + + return NULL; +} + +int CDataManagerBase::UnlockResource( memhandle_t handle ) +{ + AUTO_LOCK_DM(); + unsigned short memoryIndex = FromHandle(handle); + if ( memoryIndex != m_memoryLists.InvalidIndex() ) + { + Assert( m_memoryLists[memoryIndex].lockCount > 0 ); + if ( m_memoryLists[memoryIndex].lockCount > 0 ) + { + m_memoryLists[memoryIndex].lockCount--; + if ( m_memoryLists[memoryIndex].lockCount == 0 ) + { + m_memoryLists.Unlink( m_lockList, memoryIndex ); + m_memoryLists.LinkToTail( m_lruList, memoryIndex ); + } + } + return m_memoryLists[memoryIndex].lockCount; + } + + return 0; +} + +void *CDataManagerBase::GetResource_NoLockNoLRUTouch( memhandle_t handle ) +{ + AUTO_LOCK_DM(); + unsigned short memoryIndex = FromHandle(handle); + if ( memoryIndex != m_memoryLists.InvalidIndex() ) + { + return m_memoryLists[memoryIndex].pStore; + } + return NULL; +} + + +void *CDataManagerBase::GetResource_NoLock( memhandle_t handle ) +{ + AUTO_LOCK_DM(); + unsigned short memoryIndex = FromHandle(handle); + if ( memoryIndex != m_memoryLists.InvalidIndex() ) + { + TouchByIndex( memoryIndex ); + return m_memoryLists[memoryIndex].pStore; + } + return NULL; +} + +void CDataManagerBase::TouchResource( memhandle_t handle ) +{ + AUTO_LOCK_DM(); + TouchByIndex( FromHandle(handle) ); +} + +void CDataManagerBase::MarkAsStale( memhandle_t handle ) +{ + AUTO_LOCK_DM(); + unsigned short memoryIndex = FromHandle(handle); + if ( memoryIndex != m_memoryLists.InvalidIndex() ) + { + if ( m_memoryLists[memoryIndex].lockCount == 0 ) + { + m_memoryLists.Unlink( m_lruList, memoryIndex ); + m_memoryLists.LinkToHead( m_lruList, memoryIndex ); + } + } +} + +int CDataManagerBase::BreakLock( memhandle_t handle ) +{ + AUTO_LOCK_DM(); + unsigned short memoryIndex = FromHandle(handle); + if ( memoryIndex != m_memoryLists.InvalidIndex() && m_memoryLists[memoryIndex].lockCount ) + { + int nBroken = m_memoryLists[memoryIndex].lockCount; + m_memoryLists[memoryIndex].lockCount = 0; + m_memoryLists.Unlink( m_lockList, memoryIndex ); + m_memoryLists.LinkToTail( m_lruList, memoryIndex ); + + return nBroken; + } + return 0; +} + +int CDataManagerBase::BreakAllLocks() +{ + AUTO_LOCK_DM(); + int nBroken = 0; + int node; + int nextNode; + + node = m_memoryLists.Head(m_lockList); + while ( node != m_memoryLists.InvalidIndex() ) + { + nBroken++; + nextNode = m_memoryLists.Next(node); + m_memoryLists[node].lockCount = 0; + m_memoryLists.Unlink( m_lockList, node ); + m_memoryLists.LinkToTail( m_lruList, node ); + node = nextNode; + } + + return nBroken; + +} + +unsigned short CDataManagerBase::CreateHandle( bool bCreateLocked ) +{ + AUTO_LOCK_DM(); + int memoryIndex = m_memoryLists.Head(m_freeList); + unsigned short list = ( bCreateLocked ) ? m_lockList : m_lruList; + if ( memoryIndex != m_memoryLists.InvalidIndex() ) + { + m_memoryLists.Unlink( m_freeList, memoryIndex ); + m_memoryLists.LinkToTail( list, memoryIndex ); + } + else + { + memoryIndex = m_memoryLists.AddToTail( list ); + } + + if ( bCreateLocked ) + { + m_memoryLists[memoryIndex].lockCount++; + } + + return memoryIndex; +} + +memhandle_t CDataManagerBase::StoreResourceInHandle( unsigned short memoryIndex, void *pStore, unsigned int realSize ) +{ + AUTO_LOCK_DM(); + resource_lru_element_t &mem = m_memoryLists[memoryIndex]; + mem.pStore = pStore; + m_memUsed += realSize; + return ToHandle(memoryIndex); +} + +void CDataManagerBase::TouchByIndex( unsigned short memoryIndex ) +{ + if ( memoryIndex != m_memoryLists.InvalidIndex() ) + { + if ( m_memoryLists[memoryIndex].lockCount == 0 ) + { + m_memoryLists.Unlink( m_lruList, memoryIndex ); + m_memoryLists.LinkToTail( m_lruList, memoryIndex ); + } + } +} + +memhandle_t CDataManagerBase::ToHandle( unsigned short index ) +{ + unsigned int hiword = m_memoryLists.Element(index).serial; + hiword <<= 16; + index++; + return (memhandle_t)( hiword|index ); +} + +unsigned int CDataManagerBase::TargetSize() +{ + return MemTotal_Inline(); +} + +unsigned int CDataManagerBase::AvailableSize() +{ + return MemAvailable_Inline(); +} + + +unsigned int CDataManagerBase::UsedSize() +{ + return MemUsed_Inline(); +} + +// free resources until there is enough space to hold "size" +unsigned int CDataManagerBase::EnsureCapacity( unsigned int size ) +{ + unsigned nBytesInitial = MemUsed_Inline(); + while ( MemUsed_Inline() > MemTotal_Inline() || MemAvailable_Inline() < size ) + { + Lock(); + int lruIndex = m_memoryLists.Head( m_lruList ); + if ( lruIndex == m_memoryLists.InvalidIndex() ) + { + Unlock(); + break; + } + m_memoryLists.Unlink( m_lruList, lruIndex ); + void *p = GetForFreeByIndex( lruIndex ); + Unlock(); + DestroyResourceStorage( p ); + } + return ( nBytesInitial - MemUsed_Inline() ); +} + +// free this resource and move the handle to the free list +void *CDataManagerBase::GetForFreeByIndex( unsigned short memoryIndex ) +{ + void *p = NULL; + if ( memoryIndex != m_memoryLists.InvalidIndex() ) + { + Assert( m_memoryLists[memoryIndex].lockCount == 0 ); + + resource_lru_element_t &mem = m_memoryLists[memoryIndex]; + unsigned size = GetRealSize( mem.pStore ); + if ( size > m_memUsed ) + { + ExecuteOnce( Warning( "Data manager 'used' memory incorrect\n" ) ); + size = m_memUsed; + } + m_memUsed -= size; + p = mem.pStore; + mem.pStore = NULL; + mem.serial++; + m_memoryLists.LinkToTail( m_freeList, memoryIndex ); + } + return p; +} + +// get a list of everything in the LRU +void CDataManagerBase::GetLRUHandleList( CUtlVector< memhandle_t >& list ) +{ + for ( int node = m_memoryLists.Tail(m_lruList); + node != m_memoryLists.InvalidIndex(); + node = m_memoryLists.Previous(node) ) + { + list.AddToTail( ToHandle( node ) ); + } +} + +// get a list of everything locked +void CDataManagerBase::GetLockHandleList( CUtlVector< memhandle_t >& list ) +{ + for ( int node = m_memoryLists.Head(m_lockList); + node != m_memoryLists.InvalidIndex(); + node = m_memoryLists.Next(node) ) + { + list.AddToTail( ToHandle( node ) ); + } +} + diff --git a/tier1/diff.cpp b/tier1/diff.cpp new file mode 100644 index 00000000..151a1b6a --- /dev/null +++ b/tier1/diff.cpp @@ -0,0 +1,547 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "tier0/platform.h" +#include "tier0/dbg.h" +#include "tier1/diff.h" +#include "mathlib/mathlib.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +// format of diff output: +// 0NN (N=1..127) copy next N literaly +// +// 1NN (N=1..127) ofs (-128..127) copy next N bytes from original, changin offset by N bytes from +// last copy end +// 100 N ofs(-32768..32767) copy next N, with larger delta offset +// 00 NNNN(1..65535) ofs(-32768..32767) big copy from old +// 80 00 NN NN NN big raw copy +// +// available codes (could be used for additonal compression ops) +// long offset form whose offset could have fit in short offset + +// note - this algorithm uses storage equal to 8* the old buffer size. 64k=.5mb + + +#define MIN_MATCH_LEN 8 +#define ACCEPTABLE_MATCH_LEN 4096 + +struct BlockPtr +{ + BlockPtr *Next; + uint8 const *dataptr; +}; + +template static inline void AddToHead(T * & head, V * node) +{ + node->Next=head; + head=node; +} + +void Fail(char const *msg) +{ + Assert(0); +} + +void ApplyDiffs(uint8 const *OldBlock, uint8 const *DiffList, + int OldSize, int DiffListSize, int &ResultListSize,uint8 *Output,uint32 OutSize) +{ + uint8 const *copy_src=OldBlock; + uint8 const *end_of_diff_list=DiffList+DiffListSize; + uint8 const *obuf=Output; + while(DiffList32767) + copy_ofs|=0xffff0000; + // printf("long cp from %x to %x len=%d\n", copy_src+copy_ofs-OldBlock,Output-obuf,copy_sz); + + memcpy(Output,copy_src+copy_ofs,copy_sz); + Output+=copy_sz; + copy_src=copy_src+copy_ofs+copy_sz; + DiffList+=4; + } + else + { + if (op & 0x80) + { + int copy_sz=op & 0x7f; + int copy_ofs; + if (copy_sz==0) + { + copy_sz=DiffList[0]; + if (copy_sz==0) + { + // big raw copy + copy_sz=DiffList[1]+256*DiffList[2]+65536*DiffList[3]; + memcpy(Output,DiffList+4,copy_sz); + // printf("big rawcopy to %x len=%d\n", Output-obuf,copy_sz); + + DiffList+=copy_sz+4; + Output+=copy_sz; + } + else + { + copy_ofs=DiffList[1]+(DiffList[2]*256); + if (copy_ofs>32767) + copy_ofs|=0xffff0000; + // printf("long ofs cp from %x to %x len=%d\n", copy_src+copy_ofs-OldBlock,Output-obuf,copy_sz); + + memcpy(Output,copy_src+copy_ofs,copy_sz); + Output+=copy_sz; + copy_src=copy_src+copy_ofs+copy_sz; + DiffList+=3; + } + } + else + { + copy_ofs=DiffList[0]; + if (copy_ofs>127) + copy_ofs|=0xffffff80; + // printf("cp from %x to %x len=%d\n", copy_src+copy_ofs-OldBlock,Output-obuf,copy_sz); + + memcpy(Output,copy_src+copy_ofs,copy_sz); + Output+=copy_sz; + copy_src=copy_src+copy_ofs+copy_sz; + DiffList++; + } + } + else + { + // printf("raw copy %d to %x\n",op & 127,Output-obuf); + memcpy(Output,DiffList,op & 127); + Output+=op & 127; + DiffList+=(op & 127); + } + } + } + ResultListSize=Output-obuf; + +} + +static void CopyPending(int len, uint8 const *rawbytes,uint8 * &outbuf, uint8 const *limit) +{ +// printf("copy raw len=%d\n",len); + if (len<128) + { + if (limit-outbuf < len+1) + Fail("diff buffer overrun"); + *(outbuf++)=len; + memcpy(outbuf,rawbytes,len); + outbuf+=len; + } + else + { + if (limit-outbuf < len+5) + Fail("diff buffer overrun"); + *(outbuf++)=0x80; + *(outbuf++)=0x00; + *(outbuf++)=(len & 255); + *(outbuf++)=((len>>8) & 255); + *(outbuf++)=((len>>16) & 255); + memcpy(outbuf,rawbytes,len); + outbuf+=len; + } +} + +static uint32 hasher(uint8 const *mdata) +{ + // attempt to scramble the bits of h1 and h2 together + uint32 ret=0; + for(int i=0;idataptr=walk; + AddToHead(HashedMatches[hash1],newnode); + walk++; + } + else + ret=1; + // now, we have the hash table which may be used to search. begin the output step + int pending_raw_len=0; + walk=NewBlock; + uint8 *outbuf=Output; + uint8 const *lastmatchend=OldBlock; + while(walkMIN_MATCH_LEN, take it + for(BlockPtr *b=HashedMatches[hash1];b;b=b->Next) + { + // find the match length + int match_of=b->dataptr-lastmatchend; + if ((match_of>-32768) && (match_of<32767)) + { + int max_mlength=MIN(65535,OldBlock+OldSize-b->dataptr); + max_mlength=MIN(max_mlength,NewBlock+NewSize-walk); + int i; + for(i=0;idataptr[i]) + break; + if ((i>MIN_MATCH_LEN) && (i>longest)) + { + longest=i; + longest_block=b; + if (longest>ACCEPTABLE_MATCH_LEN) + break; + } + } + } + } + // now, we have a match maybe + if (longest_block) + { + if (pending_raw_len) // must output + { + ret=1; + CopyPending(pending_raw_len,walk-pending_raw_len,outbuf,Output+OutSize); + pending_raw_len=0; + } + // now, output copy block + int match_of=longest_block->dataptr-lastmatchend; + int nremaining=OutSize-(outbuf-Output); + + if (match_of) + ret=1; +// printf("copy from %x to %x len=%d\n", match_of,outbuf-Output,longest); + if (longest>127) + { + // use really long encoding + if (nremaining<5) + Fail("diff buff needs increase"); + *(outbuf++)=00; + *(outbuf++)=(longest & 255); + *(outbuf++)=((longest>>8) & 255); + *(outbuf++)=(match_of & 255); + *(outbuf++)=((match_of>>8) & 255); + + } + else + { + if ((match_of>=-128) && (match_of<128)) + { + if (nremaining<2) + Fail("diff buff needs increase"); + *(outbuf++)=128+longest; + *(outbuf++)=(match_of&255); + } + else + { + // use long encoding + if (nremaining<4) + Fail("diff buff needs increase"); + *(outbuf++)=0x80; + *(outbuf++)=longest; + *(outbuf++)=(match_of & 255); + *(outbuf++)=((match_of>>8) & 255); + } + } + lastmatchend=longest_block->dataptr+longest; + walk+=longest; + } + else + { + walk++; + pending_raw_len++; + } + } + // now, flush pending raw copy + if (pending_raw_len) // must output + { + ret=1; + CopyPending(pending_raw_len,walk-pending_raw_len,outbuf,Output+OutSize); + pending_raw_len=0; + } + delete[] HashedMatches; + if (Blocks) + delete[] Blocks; + DiffListSize=outbuf-Output; + return ret; +} + + +int FindDiffs(uint8 const *NewBlock, uint8 const *OldBlock, + int NewSize, int OldSize, int &DiffListSize,uint8 *Output,uint32 OutSize) +{ + + int ret=0; + if (OldSize!=NewSize) + ret=1; + // first, build the hash table + BlockPtr *HashedMatches[65536]; + memset(HashedMatches,0,sizeof(HashedMatches)); + BlockPtr *Blocks=0; + if (OldSize) + Blocks=new BlockPtr[OldSize]; + BlockPtr *FreeList=Blocks; + // now, build the hash table + uint8 const *walk=OldBlock; + if (OldBlock && OldSize) + while(walkdataptr=walk; + AddToHead(HashedMatches[hash1],newnode); + walk++; + } + else + ret=1; + // now, we have the hash table which may be used to search. begin the output step + int pending_raw_len=0; + walk=NewBlock; + uint8 *outbuf=Output; + uint8 const *lastmatchend=OldBlock; + while(walkMIN_MATCH_LEN, take it + for(BlockPtr *b=HashedMatches[hash1];b;b=b->Next) + { + // find the match length + int match_of=b->dataptr-lastmatchend; + if ((match_of>-32768) && (match_of<32767)) + { + int max_mlength=MIN(65535,OldBlock+OldSize-b->dataptr); + max_mlength=MIN(max_mlength,NewBlock+NewSize-walk); + int i; + for(i=0;idataptr[i]) + break; + if ((i>MIN_MATCH_LEN) && (i>longest)) + { + longest=i; + longest_block=b; + } + } + } + } + // now, we have a match maybe + if (longest_block) + { + if (pending_raw_len) // must output + { + ret=1; + CopyPending(pending_raw_len,walk-pending_raw_len,outbuf,Output+OutSize); + pending_raw_len=0; + } + // now, output copy block + int match_of=longest_block->dataptr-lastmatchend; + int nremaining=OutSize-(outbuf-Output); + if (match_of) + ret=1; + if (longest>127) + { + // use really long encoding + if (nremaining<5) + Fail("diff buff needs increase"); + *(outbuf++)=00; + *(outbuf++)=(longest & 255); + *(outbuf++)=((longest>>8) & 255); + *(outbuf++)=(match_of & 255); + *(outbuf++)=((match_of>>8) & 255); + } + else + { + if ((match_of>=-128) && (match_of<128)) + { + if (nremaining<2) + Fail("diff buff needs increase"); + *(outbuf++)=128+longest; + *(outbuf++)=(match_of&255); + } + else + { + // use long encoding + if (nremaining<4) + Fail("diff buff needs increase"); + *(outbuf++)=0x80; + *(outbuf++)=longest; + *(outbuf++)=(match_of & 255); + *(outbuf++)=((match_of>>8) & 255); + } + } + lastmatchend=longest_block->dataptr+longest; + walk+=longest; + } + else + { + walk++; + pending_raw_len++; + } + } + // now, flush pending raw copy + if (pending_raw_len) // must output + { + ret=1; + CopyPending(pending_raw_len,walk-pending_raw_len,outbuf,Output+OutSize); + pending_raw_len=0; + } + if (Blocks) + delete[] Blocks; + DiffListSize=outbuf-Output; + return ret; +} + + +int FindDiffsLowMemory(uint8 const *NewBlock, uint8 const *OldBlock, + int NewSize, int OldSize, int &DiffListSize,uint8 *Output,uint32 OutSize) +{ + + int ret=0; + if (OldSize!=NewSize) + ret=1; + uint8 const *old_data_hash[256]; + memset(old_data_hash,0,sizeof(old_data_hash)); + int pending_raw_len=0; + uint8 const *walk=NewBlock; + uint8 const *oldptr=OldBlock; + uint8 *outbuf=Output; + uint8 const *lastmatchend=OldBlock; + while(walkMIN_MATCH_LEN) + { + longest_block=old_data_hash[hash1]; + longest=nmatches; + } + } + } + // now, we have a match maybe + if (longest_block) + { + if (pending_raw_len) // must output + { + ret=1; + CopyPending(pending_raw_len,walk-pending_raw_len,outbuf,Output+OutSize); + pending_raw_len=0; + } + // now, output copy block + int match_of=longest_block-lastmatchend; + int nremaining=OutSize-(outbuf-Output); + if (match_of) + ret=1; + if (longest>127) + { + // use really long encoding + if (nremaining<5) + Fail("diff buff needs increase"); + *(outbuf++)=00; + *(outbuf++)=(longest & 255); + *(outbuf++)=((longest>>8) & 255); + *(outbuf++)=(match_of & 255); + *(outbuf++)=((match_of>>8) & 255); + } + else + { + if ((match_of>=-128) && (match_of<128)) + { + if (nremaining<2) + Fail("diff buff needs increase"); + *(outbuf++)=128+longest; + *(outbuf++)=(match_of&255); + } + else + { + // use long encoding + if (nremaining<4) + Fail("diff buff needs increase"); + *(outbuf++)=0x80; + *(outbuf++)=longest; + *(outbuf++)=(match_of & 255); + *(outbuf++)=((match_of>>8) & 255); + } + } + lastmatchend=longest_block+longest; + walk+=longest; + } + else + { + walk++; + pending_raw_len++; + } + } + // now, flush pending raw copy + if (pending_raw_len) // must output + { + ret=1; + CopyPending(pending_raw_len,walk-pending_raw_len,outbuf,Output+OutSize); + pending_raw_len=0; + } + DiffListSize=outbuf-Output; + return ret; +} + + diff --git a/tier1/generichash.cpp b/tier1/generichash.cpp new file mode 100644 index 00000000..af784fc4 --- /dev/null +++ b/tier1/generichash.cpp @@ -0,0 +1,303 @@ +//======= Copyright © 2005, , Valve Corporation, All rights reserved. ========= +// +// Purpose: Variant Pearson Hash general purpose hashing algorithm described +// by Cargill in C++ Report 1994. Generates a 16-bit result. +// +//============================================================================= + +#include +#include "tier0/basetypes.h" +#include "tier0/platform.h" +#include "generichash.h" +#include + +//----------------------------------------------------------------------------- +// +// Table of randomly shuffled values from 0-255 generated by: +// +//----------------------------------------------------------------------------- +/* +void MakeRandomValues() +{ + int i, j, r; + unsigned t; + srand( 0xdeadbeef ); + + for ( i = 0; i < 256; i++ ) + { + g_nRandomValues[i] = (unsigned )i; + } + + for (j = 0; j < 8; j++) + { + for (i = 0; i < 256; i++) + { + r = rand() & 0xff; + t = g_nRandomValues[i]; + g_nRandomValues[i] = g_nRandomValues[r]; + g_nRandomValues[r] = t; + } + } + + printf("static unsigned g_nRandomValues[256] =\n{\n"); + + for (i = 0; i < 256; i += 16) + { + printf("\t"); + for (j = 0; j < 16; j++) + printf(" %3d,", g_nRandomValues[i+j]); + printf("\n"); + } + printf("};\n"); +} +*/ + +static unsigned g_nRandomValues[256] = +{ + 238, 164, 191, 168, 115, 16, 142, 11, 213, 214, 57, 151, 248, 252, 26, 198, + 13, 105, 102, 25, 43, 42, 227, 107, 210, 251, 86, 66, 83, 193, 126, 108, + 131, 3, 64, 186, 192, 81, 37, 158, 39, 244, 14, 254, 75, 30, 2, 88, + 172, 176, 255, 69, 0, 45, 116, 139, 23, 65, 183, 148, 33, 46, 203, 20, + 143, 205, 60, 197, 118, 9, 171, 51, 233, 135, 220, 49, 71, 184, 82, 109, + 36, 161, 169, 150, 63, 96, 173, 125, 113, 67, 224, 78, 232, 215, 35, 219, + 79, 181, 41, 229, 149, 153, 111, 217, 21, 72, 120, 163, 133, 40, 122, 140, + 208, 231, 211, 200, 160, 182, 104, 110, 178, 237, 15, 101, 27, 50, 24, 189, + 177, 130, 187, 92, 253, 136, 100, 212, 19, 174, 70, 22, 170, 206, 162, 74, + 247, 5, 47, 32, 179, 117, 132, 195, 124, 123, 245, 128, 236, 223, 12, 84, + 54, 218, 146, 228, 157, 94, 106, 31, 17, 29, 194, 34, 56, 134, 239, 246, + 241, 216, 127, 98, 7, 204, 154, 152, 209, 188, 48, 61, 87, 97, 225, 85, + 90, 167, 155, 112, 145, 114, 141, 93, 250, 4, 201, 156, 38, 89, 226, 196, + 1, 235, 44, 180, 159, 121, 119, 166, 190, 144, 10, 91, 76, 230, 221, 80, + 207, 55, 58, 53, 175, 8, 6, 52, 68, 242, 18, 222, 103, 249, 147, 129, + 138, 243, 28, 185, 62, 59, 240, 202, 234, 99, 77, 73, 199, 137, 95, 165, +}; + +//----------------------------------------------------------------------------- +// String +//----------------------------------------------------------------------------- +unsigned FASTCALL HashString( const char *pszKey ) +{ + const uint8 *k = (const uint8 *)pszKey; + unsigned even = 0, + odd = 0, + n; + + while ((n = *k++) != 0) + { + even = g_nRandomValues[odd ^ n]; + if ((n = *k++) != 0) + odd = g_nRandomValues[even ^ n]; + else + break; + } + + return (even << 8) | odd ; +} + + +//----------------------------------------------------------------------------- +// Case-insensitive string +//----------------------------------------------------------------------------- +unsigned FASTCALL HashStringCaseless( const char *pszKey ) +{ + const uint8 *k = (const uint8 *) pszKey; + unsigned even = 0, + odd = 0, + n; + + while ((n = toupper(*k++)) != 0) + { + even = g_nRandomValues[odd ^ n]; + if ((n = toupper(*k++)) != 0) + odd = g_nRandomValues[even ^ n]; + else + break; + } + + return (even << 8) | odd; +} + +//----------------------------------------------------------------------------- +// 32 bit conventional case-insensitive string +//----------------------------------------------------------------------------- +unsigned FASTCALL HashStringCaselessConventional( const char *pszKey ) +{ + unsigned hash = 0xAAAAAAAA; // Alternating 1's and 0's to maximize the effect of the later multiply and add + + for( ; *pszKey ; pszKey++ ) + { + hash = ( ( hash << 5 ) + hash ) + (uint8)tolower(*pszKey); + } + + return hash; +} + +//----------------------------------------------------------------------------- +// int hash +//----------------------------------------------------------------------------- +unsigned FASTCALL HashInt( const int n ) +{ + register unsigned even, odd; + even = g_nRandomValues[n & 0xff]; + odd = g_nRandomValues[((n >> 8) & 0xff)]; + + even = g_nRandomValues[odd ^ (n >> 24)]; + odd = g_nRandomValues[even ^ (n >> 16) & 0xff]; + even = g_nRandomValues[odd ^ ((n >> 8) & 0xff)]; + odd = g_nRandomValues[even ^ (n & 0xff)]; + + return (even << 8) | odd; +} + +//----------------------------------------------------------------------------- +// 4-byte hash +//----------------------------------------------------------------------------- +unsigned FASTCALL Hash4( const void *pKey ) +{ + register const uint32 * p = (const uint32 *) pKey; + register unsigned even, + odd, + n; + n = *p; + even = g_nRandomValues[n & 0xff]; + odd = g_nRandomValues[((n >> 8) & 0xff)]; + + even = g_nRandomValues[odd ^ (n >> 24)]; + odd = g_nRandomValues[even ^ (n >> 16) & 0xff]; + even = g_nRandomValues[odd ^ ((n >> 8) & 0xff)]; + odd = g_nRandomValues[even ^ (n & 0xff)]; + + return (even << 8) | odd; +} + + +//----------------------------------------------------------------------------- +// 8-byte hash +//----------------------------------------------------------------------------- +unsigned FASTCALL Hash8( const void *pKey ) +{ + register const uint32 * p = (const uint32 *) pKey; + register unsigned even, + odd, + n; + n = *p; + even = g_nRandomValues[n & 0xff]; + odd = g_nRandomValues[((n >> 8) & 0xff)]; + + even = g_nRandomValues[odd ^ (n >> 24)]; + odd = g_nRandomValues[even ^ (n >> 16) & 0xff]; + even = g_nRandomValues[odd ^ ((n >> 8) & 0xff)]; + odd = g_nRandomValues[even ^ (n & 0xff)]; + + n = *(p+1); + even = g_nRandomValues[odd ^ (n >> 24)]; + odd = g_nRandomValues[even ^ ((n >> 16) & 0xff)]; + even = g_nRandomValues[odd ^ ((n >> 8) & 0xff)]; + odd = g_nRandomValues[even ^ (n & 0xff)]; + + return (even << 8) | odd; +} + + +//----------------------------------------------------------------------------- +// 12-byte hash +//----------------------------------------------------------------------------- +unsigned FASTCALL Hash12( const void *pKey ) +{ + register const uint32 * p = (const uint32 *) pKey; + register unsigned even, + odd, + n; + n = *p; + even = g_nRandomValues[n & 0xff]; + odd = g_nRandomValues[((n >> 8) & 0xff)]; + + even = g_nRandomValues[odd ^ (n >> 24)]; + odd = g_nRandomValues[even ^ (n >> 16) & 0xff]; + even = g_nRandomValues[odd ^ ((n >> 8) & 0xff)]; + odd = g_nRandomValues[even ^ (n & 0xff)]; + + n = *(p+1); + even = g_nRandomValues[odd ^ (n >> 24)]; + odd = g_nRandomValues[even ^ ((n >> 16) & 0xff)]; + even = g_nRandomValues[odd ^ ((n >> 8) & 0xff)]; + odd = g_nRandomValues[even ^ (n & 0xff)]; + + n = *(p+2); + even = g_nRandomValues[odd ^ (n >> 24)]; + odd = g_nRandomValues[even ^ ((n >> 16) & 0xff)]; + even = g_nRandomValues[odd ^ ((n >> 8) & 0xff)]; + odd = g_nRandomValues[even ^ (n & 0xff)]; + + return (even << 8) | odd; +} + + +//----------------------------------------------------------------------------- +// 16-byte hash +//----------------------------------------------------------------------------- +unsigned FASTCALL Hash16( const void *pKey ) +{ + register const uint32 * p = (const uint32 *) pKey; + register unsigned even, + odd, + n; + n = *p; + even = g_nRandomValues[n & 0xff]; + odd = g_nRandomValues[((n >> 8) & 0xff)]; + + even = g_nRandomValues[odd ^ (n >> 24)]; + odd = g_nRandomValues[even ^ (n >> 16) & 0xff]; + even = g_nRandomValues[odd ^ ((n >> 8) & 0xff)]; + odd = g_nRandomValues[even ^ (n & 0xff)]; + + n = *(p+1); + even = g_nRandomValues[odd ^ (n >> 24)]; + odd = g_nRandomValues[even ^ ((n >> 16) & 0xff)]; + even = g_nRandomValues[odd ^ ((n >> 8) & 0xff)]; + odd = g_nRandomValues[even ^ (n & 0xff)]; + + n = *(p+2); + even = g_nRandomValues[odd ^ (n >> 24)]; + odd = g_nRandomValues[even ^ ((n >> 16) & 0xff)]; + even = g_nRandomValues[odd ^ ((n >> 8) & 0xff)]; + odd = g_nRandomValues[even ^ (n & 0xff)]; + + n = *(p+3); + even = g_nRandomValues[odd ^ (n >> 24)]; + odd = g_nRandomValues[even ^ ((n >> 16) & 0xff)]; + even = g_nRandomValues[odd ^ ((n >> 8) & 0xff)]; + odd = g_nRandomValues[even ^ (n & 0xff)]; + + return (even << 8) | odd; +} + + +//----------------------------------------------------------------------------- +// Arbitrary fixed length hash +//----------------------------------------------------------------------------- +unsigned FASTCALL HashBlock( const void *pKey, unsigned size ) +{ + const uint8 * k = (const uint8 *) pKey; + unsigned even = 0, + odd = 0, + n; + + while (size) + { + --size; + n = *k++; + even = g_nRandomValues[odd ^ n]; + if (size) + { + --size; + n = *k++; + odd = g_nRandomValues[even ^ n]; + } + else + break; + } + + return (even << 8) | odd; +} + diff --git a/tier1/interface.cpp b/tier1/interface.cpp new file mode 100644 index 00000000..8252e570 --- /dev/null +++ b/tier1/interface.cpp @@ -0,0 +1,465 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +//===========================================================================// +#if defined( _WIN32 ) && !defined( _X360 ) +#include +#endif + +#if !defined( DONT_PROTECT_FILEIO_FUNCTIONS ) +#define DONT_PROTECT_FILEIO_FUNCTIONS // for protected_things.h +#endif + +#if defined( PROTECTED_THINGS_ENABLE ) +#undef PROTECTED_THINGS_ENABLE // from protected_things.h +#endif + +#include +#include "interface.h" +#include "basetypes.h" +#include "tier0/dbg.h" +#include +#include +#include "tier1/strtools.h" +#include "tier0/icommandline.h" +#include "tier0/dbg.h" +#include "tier0/threadtools.h" +#ifdef _WIN32 +#include // getcwd +#elif defined _LINUX || defined __APPLE__ +#define _getcwd getcwd +#endif +#if defined( _X360 ) +#include "xbox/xbox_win32stubs.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// ------------------------------------------------------------------------------------ // +// InterfaceReg. +// ------------------------------------------------------------------------------------ // +InterfaceReg *InterfaceReg::s_pInterfaceRegs = NULL; + +InterfaceReg::InterfaceReg( InstantiateInterfaceFn fn, const char *pName ) : + m_pName(pName) +{ + m_CreateFn = fn; + m_pNext = s_pInterfaceRegs; + s_pInterfaceRegs = this; +} + +// ------------------------------------------------------------------------------------ // +// CreateInterface. +// This is the primary exported function by a dll, referenced by name via dynamic binding +// that exposes an opqaue function pointer to the interface. +// ------------------------------------------------------------------------------------ // +void* CreateInterface( const char *pName, int *pReturnCode ) +{ + InterfaceReg *pCur; + + for (pCur=InterfaceReg::s_pInterfaceRegs; pCur; pCur=pCur->m_pNext) + { + if (strcmp(pCur->m_pName, pName) == 0) + { + if (pReturnCode) + { + *pReturnCode = IFACE_OK; + } + return pCur->m_CreateFn(); + } + } + + if (pReturnCode) + { + *pReturnCode = IFACE_FAILED; + } + return NULL; +} + + +#if defined _LINUX || defined __APPLE__ +// Linux doesn't have this function so this emulates its functionality +void *GetModuleHandle(const char *name) +{ + void *handle; + + if( name == NULL ) + { + // hmm, how can this be handled under linux.... + // is it even needed? + return NULL; + } + + if( (handle=dlopen(name, RTLD_NOW))==NULL) + { + printf("DLOPEN Error:%s\n",dlerror()); + // couldn't open this file + return NULL; + } + + // read "man dlopen" for details + // in short dlopen() inc a ref count + // so dec the ref count by performing the close + dlclose(handle); + return handle; +} +#endif + +#if defined( _WIN32 ) && !defined( _X360 ) +#define WIN32_LEAN_AND_MEAN +#include "windows.h" +#endif + +//----------------------------------------------------------------------------- +// Purpose: returns a pointer to a function, given a module +// Input : pModuleName - module name +// *pName - proc name +//----------------------------------------------------------------------------- +static void *Sys_GetProcAddress( const char *pModuleName, const char *pName ) +{ + HMODULE hModule = GetModuleHandle( pModuleName ); + return GetProcAddress( hModule, pName ); +} + +static void *Sys_GetProcAddress( HMODULE hModule, const char *pName ) +{ + return GetProcAddress( hModule, pName ); +} + +bool Sys_IsDebuggerPresent() +{ + return Plat_IsInDebugSession(); +} + +struct ThreadedLoadLibaryContext_t +{ + const char *m_pLibraryName; + HMODULE m_hLibrary; +}; + +#ifdef _WIN32 + +// wraps LoadLibraryEx() since 360 doesn't support that +static HMODULE InternalLoadLibrary( const char *pName ) +{ +#if defined(_X360) + return LoadLibrary( pName ); +#else + return LoadLibraryEx( pName, NULL, LOAD_WITH_ALTERED_SEARCH_PATH ); +#endif +} +unsigned ThreadedLoadLibraryFunc( void *pParam ) +{ + ThreadedLoadLibaryContext_t *pContext = (ThreadedLoadLibaryContext_t*)pParam; + pContext->m_hLibrary = InternalLoadLibrary(pContext->m_pLibraryName); + return 0; +} +#endif + +HMODULE Sys_LoadLibrary( const char *pLibraryName ) +{ + char str[1024]; +#if defined( _WIN32 ) && !defined( _X360 ) + const char *pModuleExtension = ".dll"; + const char *pModuleAddition = pModuleExtension; +#elif defined( _X360 ) + const char *pModuleExtension = "_360.dll"; + const char *pModuleAddition = pModuleExtension; +#elif defined( _LINUX ) + const char *pModuleExtension = ".so"; + const char *pModuleAddition = ".so"; +#elif defined( __APPLE__ ) + const char *pModuleExtension = ".dylib"; + const char *pModuleAddition = ".dylib"; +#endif + Q_strncpy( str, pLibraryName, sizeof(str) ); + if ( !Q_stristr( str, pModuleExtension ) ) + { + if ( IsX360() ) + { + Q_StripExtension( str, str, sizeof(str) ); + } + Q_strncat( str, pModuleAddition, sizeof(str) ); + } + Q_FixSlashes( str ); + +#ifdef _WIN32 + ThreadedLoadLibraryFunc_t threadFunc = GetThreadedLoadLibraryFunc(); + if ( !threadFunc ) + return InternalLoadLibrary( str ); + + ThreadedLoadLibaryContext_t context; + context.m_pLibraryName = str; + context.m_hLibrary = 0; + + ThreadHandle_t h = CreateSimpleThread( ThreadedLoadLibraryFunc, &context ); + +#ifdef _X360 + ThreadSetAffinity( h, XBOX_PROCESSOR_3 ); +#endif + + unsigned int nTimeout = 0; + while( ThreadWaitForObject( h, true, nTimeout ) == TW_TIMEOUT ) + { + nTimeout = threadFunc(); + } + + ReleaseThreadHandle( h ); + return context.m_hLibrary; + +#elif defined _LINUX || defined __APPLE__ + HMODULE ret = dlopen( str, RTLD_NOW ); + if ( ! ret ) + { + const char *pError = dlerror(); + if ( pError && ( strstr( pError, "No such file" ) == 0 ) ) + { + Msg( " failed to dlopen %s error=%s\n", str, pError ); + + } + } + + return ret; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Loads a DLL/component from disk and returns a handle to it +// Input : *pModuleName - filename of the component +// Output : opaque handle to the module (hides system dependency) +//----------------------------------------------------------------------------- +CSysModule *Sys_LoadModule( const char *pModuleName ) +{ + // If using the Steam filesystem, either the DLL must be a minimum footprint + // file in the depot (MFP) or a filesystem GetLocalCopy() call must be made + // prior to the call to this routine. + char szCwd[1024]; + HMODULE hDLL = NULL; + + if ( !Q_IsAbsolutePath( pModuleName ) ) + { + // full path wasn't passed in, using the current working dir + _getcwd( szCwd, sizeof( szCwd ) ); + if ( IsX360() ) + { + int i = CommandLine()->FindParm( "-basedir" ); + if ( i ) + { + strcpy( szCwd, CommandLine()->GetParm( i+1 ) ); + } + } + if (szCwd[strlen(szCwd) - 1] == '/' || szCwd[strlen(szCwd) - 1] == '\\' ) + { + szCwd[strlen(szCwd) - 1] = 0; + } + + char szAbsoluteModuleName[1024]; + if ( strstr( pModuleName, "bin/") == pModuleName ) + { + // don't make bin/bin path + Q_snprintf( szAbsoluteModuleName, sizeof(szAbsoluteModuleName), "%s/%s", szCwd, pModuleName ); + } + else + { + Q_snprintf( szAbsoluteModuleName, sizeof(szAbsoluteModuleName), "%s/bin/%s", szCwd, pModuleName ); + } + hDLL = Sys_LoadLibrary( szAbsoluteModuleName ); + } + + if ( !hDLL ) + { + // full path failed, let LoadLibrary() try to search the PATH now + hDLL = Sys_LoadLibrary( pModuleName ); +#if defined( _DEBUG ) + if ( !hDLL ) + { +// So you can see what the error is in the debugger... +#if defined( _WIN32 ) && !defined( _X360 ) + char *lpMsgBuf; + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + + LocalFree( (HLOCAL)lpMsgBuf ); +#elif defined( _X360 ) + Msg( "Failed to load %s:\n", pModuleName ); +#else + Error( "Failed to load %s: %s\n", pModuleName, dlerror() ); +#endif // _WIN32 + } +#endif // DEBUG + } + + // If running in the debugger, assume debug binaries are okay, otherwise they must run with -allowdebug + if ( !IsX360() && hDLL && + !CommandLine()->FindParm( "-allowdebug" ) && + !Sys_IsDebuggerPresent() ) + { + if ( Sys_GetProcAddress( hDLL, "BuiltDebug" ) ) + { + Error( "Module %s is a debug build\n", pModuleName ); + } + } + + return reinterpret_cast(hDLL); +} + + +//----------------------------------------------------------------------------- +// Purpose: Unloads a DLL/component from +// Input : *pModuleName - filename of the component +// Output : opaque handle to the module (hides system dependency) +//----------------------------------------------------------------------------- +void Sys_UnloadModule( CSysModule *pModule ) +{ + if ( !pModule ) + return; + + HMODULE hDLL = reinterpret_cast(pModule); + +#ifdef _WIN32 + FreeLibrary( hDLL ); +#elif defined(_LINUX) || defined(__APPLE__) + dlclose((void *)hDLL); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: returns a pointer to a function, given a module +// Input : module - windows HMODULE from Sys_LoadModule() +// *pName - proc name +// Output : factory for this module +//----------------------------------------------------------------------------- +CreateInterfaceFn Sys_GetFactory( CSysModule *pModule ) +{ + if ( !pModule ) + return NULL; + + HMODULE hDLL = reinterpret_cast(pModule); +#ifdef _WIN32 + return reinterpret_cast(GetProcAddress( hDLL, CREATEINTERFACE_PROCNAME )); +#elif defined(_LINUX) || defined(__APPLE__) + // Linux gives this error: + //../public/interface.cpp: In function `IBaseInterface *(*Sys_GetFactory + //(CSysModule *)) (const char *, int *)': + //../public/interface.cpp:154: ISO C++ forbids casting between + //pointer-to-function and pointer-to-object + // + // so lets get around it :) + return (CreateInterfaceFn)(GetProcAddress( hDLL, CREATEINTERFACE_PROCNAME )); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: returns the instance of this module +// Output : interface_instance_t +//----------------------------------------------------------------------------- +CreateInterfaceFn Sys_GetFactoryThis( void ) +{ + return CreateInterface; +} + +//----------------------------------------------------------------------------- +// Purpose: returns the instance of the named module +// Input : *pModuleName - name of the module +// Output : interface_instance_t - instance of that module +//----------------------------------------------------------------------------- +CreateInterfaceFn Sys_GetFactory( const char *pModuleName ) +{ +#ifdef _WIN32 + return static_cast( Sys_GetProcAddress( pModuleName, CREATEINTERFACE_PROCNAME ) ); +#elif defined(_LINUX) || defined(__APPLE__) + // see Sys_GetFactory( CSysModule *pModule ) for an explanation + return (CreateInterfaceFn)( Sys_GetProcAddress( pModuleName, CREATEINTERFACE_PROCNAME ) ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: get the interface for the specified module and version +// Input : +// Output : +//----------------------------------------------------------------------------- +bool Sys_LoadInterface( + const char *pModuleName, + const char *pInterfaceVersionName, + CSysModule **pOutModule, + void **pOutInterface ) +{ + CSysModule *pMod = Sys_LoadModule( pModuleName ); + if ( !pMod ) + return false; + + CreateInterfaceFn fn = Sys_GetFactory( pMod ); + if ( !fn ) + { + Sys_UnloadModule( pMod ); + return false; + } + + *pOutInterface = fn( pInterfaceVersionName, NULL ); + if ( !( *pOutInterface ) ) + { + Sys_UnloadModule( pMod ); + return false; + } + + if ( pOutModule ) + *pOutModule = pMod; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Place this as a singleton at module scope (e.g.) and use it to get the factory from the specified module name. +// +// When the singleton goes out of scope (.dll unload if at module scope), +// then it'll call Sys_UnloadModule on the module so that the refcount is decremented +// and the .dll actually can unload from memory. +//----------------------------------------------------------------------------- +CDllDemandLoader::CDllDemandLoader( char const *pchModuleName ) : + m_pchModuleName( pchModuleName ), + m_hModule( 0 ), + m_bLoadAttempted( false ) +{ +} + +CDllDemandLoader::~CDllDemandLoader() +{ + Unload(); +} + +CreateInterfaceFn CDllDemandLoader::GetFactory() +{ + if ( !m_hModule && !m_bLoadAttempted ) + { + m_bLoadAttempted = true; + m_hModule = Sys_LoadModule( m_pchModuleName ); + } + + if ( !m_hModule ) + { + return NULL; + } + + return Sys_GetFactory( m_hModule ); +} + +void CDllDemandLoader::Unload() +{ + if ( m_hModule ) + { + Sys_UnloadModule( m_hModule ); + m_hModule = 0; + } +} diff --git a/tier1/mempool.cpp b/tier1/mempool.cpp new file mode 100644 index 00000000..14663a39 --- /dev/null +++ b/tier1/mempool.cpp @@ -0,0 +1,316 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +//===========================================================================// + +#include "mempool.h" +#include +#ifdef __APPLE__ +#include +#else +#include +#endif +#include +#include "tier0/dbg.h" +#include +#include "tier1/strtools.h" + +// Should be last include +#include "tier0/memdbgon.h" + +MemoryPoolReportFunc_t CMemoryPool::g_ReportFunc = 0; + +//----------------------------------------------------------------------------- +// Error reporting... (debug only) +//----------------------------------------------------------------------------- + +void CMemoryPool::SetErrorReportFunc( MemoryPoolReportFunc_t func ) +{ + g_ReportFunc = func; +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CMemoryPool::CMemoryPool( int blockSize, int numElements, int growMode, const char *pszAllocOwner, int nAlignment ) +{ +#ifdef _X360 + if( numElements > 0 && growMode != GROW_NONE ) + { + numElements = 1; + } +#endif + + m_nAlignment = ( nAlignment != 0 ) ? nAlignment : 1; + Assert( IsPowerOfTwo( m_nAlignment ) ); + m_BlockSize = blockSize < (int)sizeof(void*) ? sizeof(void*) : blockSize; + m_BlockSize = AlignValue( m_BlockSize, m_nAlignment ); + m_BlocksPerBlob = numElements; + m_PeakAlloc = 0; + m_GrowMode = growMode; + if ( !pszAllocOwner ) + { + pszAllocOwner = __FILE__; + } + m_pszAllocOwner = pszAllocOwner; + Init(); + AddNewBlob(); +} + +//----------------------------------------------------------------------------- +// Purpose: Frees the memory contained in the mempool, and invalidates it for +// any further use. +// Input : *memPool - the mempool to shutdown +//----------------------------------------------------------------------------- +CMemoryPool::~CMemoryPool() +{ + if (m_BlocksAllocated > 0) + { + ReportLeaks(); + } + Clear(); +} + + +//----------------------------------------------------------------------------- +// Resets the pool +//----------------------------------------------------------------------------- +void CMemoryPool::Init() +{ + m_NumBlobs = 0; + m_BlocksAllocated = 0; + m_pHeadOfFreeList = 0; + m_BlobHead.m_pNext = m_BlobHead.m_pPrev = &m_BlobHead; +} + + +//----------------------------------------------------------------------------- +// Frees everything +//----------------------------------------------------------------------------- +void CMemoryPool::Clear() +{ + // Free everything.. + CBlob *pNext; + for( CBlob *pCur = m_BlobHead.m_pNext; pCur != &m_BlobHead; pCur = pNext ) + { + pNext = pCur->m_pNext; + free( pCur ); + } + Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: Reports memory leaks +//----------------------------------------------------------------------------- + +void CMemoryPool::ReportLeaks() +{ + if (!g_ReportFunc) + return; + + g_ReportFunc("Memory leak: mempool blocks left in memory: %d\n", m_BlocksAllocated); + +#ifdef _DEBUG + // walk and destroy the free list so it doesn't intefere in the scan + while (m_pHeadOfFreeList != NULL) + { + void *next = *((void**)m_pHeadOfFreeList); + memset(m_pHeadOfFreeList, 0, m_BlockSize); + m_pHeadOfFreeList = next; + } + + g_ReportFunc("Dumping memory: \'"); + + for( CBlob *pCur=m_BlobHead.m_pNext; pCur != &m_BlobHead; pCur=pCur->m_pNext ) + { + // scan the memory block and dump the leaks + char *scanPoint = (char *)pCur->m_Data; + char *scanEnd = pCur->m_Data + pCur->m_NumBytes; + bool needSpace = false; + + while (scanPoint < scanEnd) + { + // search for and dump any strings + if ((unsigned)(*scanPoint + 1) <= 256 && isprint(*scanPoint)) + { + g_ReportFunc("%c", *scanPoint); + needSpace = true; + } + else if (needSpace) + { + needSpace = false; + g_ReportFunc(" "); + } + + scanPoint++; + } + } + + g_ReportFunc("\'\n"); +#endif // _DEBUG +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMemoryPool::AddNewBlob() +{ + MEM_ALLOC_CREDIT_(m_pszAllocOwner); + + int sizeMultiplier; + + if( m_GrowMode == GROW_SLOW ) + { + sizeMultiplier = 1; + } + else + { + if ( m_GrowMode == GROW_NONE ) + { + // Can only have one allocation when we're in this mode + if( m_NumBlobs != 0 ) + { + Assert( !"CMemoryPool::AddNewBlob: mode == GROW_NONE" ); + return; + } + } + + // GROW_FAST and GROW_NONE use this. + sizeMultiplier = m_NumBlobs + 1; + } + + // maybe use something other than malloc? + int nElements = m_BlocksPerBlob * sizeMultiplier; + int blobSize = m_BlockSize * nElements; + CBlob *pBlob = (CBlob*)malloc( sizeof(CBlob) - 1 + blobSize + ( m_nAlignment - 1 ) ); + Assert( pBlob ); + + // Link it in at the end of the blob list. + pBlob->m_NumBytes = blobSize; + pBlob->m_pNext = &m_BlobHead; + pBlob->m_pPrev = pBlob->m_pNext->m_pPrev; + pBlob->m_pNext->m_pPrev = pBlob->m_pPrev->m_pNext = pBlob; + + // setup the free list + m_pHeadOfFreeList = AlignValue( pBlob->m_Data, m_nAlignment ); + Assert (m_pHeadOfFreeList); + + void **newBlob = (void**)m_pHeadOfFreeList; + for (int j = 0; j < nElements-1; j++) + { + newBlob[0] = (char*)newBlob + m_BlockSize; + newBlob = (void**)newBlob[0]; + } + + // null terminate list + newBlob[0] = NULL; + m_NumBlobs++; +} + + +void* CMemoryPool::Alloc() +{ + return Alloc( m_BlockSize ); +} + + +void* CMemoryPool::AllocZero() +{ + return AllocZero( m_BlockSize ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Allocs a single block of memory from the pool. +// Input : amount - +//----------------------------------------------------------------------------- +void *CMemoryPool::Alloc( size_t amount ) +{ + void *returnBlock; + + if ( amount > (unsigned int)m_BlockSize ) + return NULL; + + if( !m_pHeadOfFreeList ) + { + // returning NULL is fine in GROW_NONE + if( m_GrowMode == GROW_NONE ) + { + //Assert( !"CMemoryPool::Alloc: tried to make new blob with GROW_NONE" ); + return NULL; + } + + // overflow + AddNewBlob(); + + // still failure, error out + if( !m_pHeadOfFreeList ) + { + Assert( !"CMemoryPool::Alloc: ran out of memory" ); + return NULL; + } + } + m_BlocksAllocated++; + m_PeakAlloc = MAX(m_PeakAlloc, m_BlocksAllocated); + + returnBlock = m_pHeadOfFreeList; + + // move the pointer the next block + m_pHeadOfFreeList = *((void**)m_pHeadOfFreeList); + + return returnBlock; +} + +//----------------------------------------------------------------------------- +// Purpose: Allocs a single block of memory from the pool, zeroes the memory before returning +// Input : amount - +//----------------------------------------------------------------------------- +void *CMemoryPool::AllocZero( size_t amount ) +{ + void *mem = Alloc( amount ); + if ( mem ) + { + V_memset( mem, 0x00, amount ); + } + return mem; +} + +//----------------------------------------------------------------------------- +// Purpose: Frees a block of memory +// Input : *memBlock - the memory to free +//----------------------------------------------------------------------------- +void CMemoryPool::Free( void *memBlock ) +{ + if ( !memBlock ) + return; // trying to delete NULL pointer, ignore + +#ifdef _DEBUG + // check to see if the memory is from the allocated range + bool bOK = false; + for( CBlob *pCur=m_BlobHead.m_pNext; pCur != &m_BlobHead; pCur=pCur->m_pNext ) + { + if (memBlock >= pCur->m_Data && (char*)memBlock < (pCur->m_Data + pCur->m_NumBytes)) + { + bOK = true; + } + } + Assert (bOK); +#endif // _DEBUG + +#ifdef _DEBUG + // invalidate the memory + memset( memBlock, 0xDD, m_BlockSize ); +#endif + + m_BlocksAllocated--; + + // make the block point to the first item in the list + *((void**)memBlock) = m_pHeadOfFreeList; + + // the list head is now the new block + m_pHeadOfFreeList = memBlock; +} + + diff --git a/tier1/memstack.cpp b/tier1/memstack.cpp new file mode 100644 index 00000000..e4888f1f --- /dev/null +++ b/tier1/memstack.cpp @@ -0,0 +1,300 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#if defined( _WIN32 ) && !defined( _X360 ) +#define WIN_32_LEAN_AND_MEAN +#include +#define VA_COMMIT_FLAGS MEM_COMMIT +#define VA_RESERVE_FLAGS MEM_RESERVE +#elif defined( _X360 ) +#define VA_COMMIT_FLAGS (MEM_COMMIT|MEM_NOZERO|MEM_LARGE_PAGES) +#define VA_RESERVE_FLAGS (MEM_RESERVE|MEM_LARGE_PAGES) +#endif + +#include "tier0/dbg.h" +#include "memstack.h" +#include "utlmap.h" +#include "tier0/memdbgon.h" + +#ifdef _WIN32 +#pragma warning(disable:4073) +#pragma init_seg(lib) +#endif + +//----------------------------------------------------------------------------- + +MEMALLOC_DEFINE_EXTERNAL_TRACKING(CMemoryStack); + +//----------------------------------------------------------------------------- + +CMemoryStack::CMemoryStack() + : m_pNextAlloc( NULL ), + m_pCommitLimit( NULL ), + m_pAllocLimit( NULL ), + m_pBase( NULL ), + m_maxSize( 0 ), +#if defined (_LINUX) || defined (__APPLE__) + m_alignment( 16 ) +#elif defined(_WIN32) + m_alignment( 16 ), + m_commitSize( 0 ), + m_minCommit( 0 ) +#endif + +{ +} + +//------------------------------------- + +CMemoryStack::~CMemoryStack() +{ + if ( m_pBase ) + Term(); +} + +//------------------------------------- + +bool CMemoryStack::Init( unsigned maxSize, unsigned commitSize, unsigned initialCommit, unsigned alignment ) +{ + Assert( !m_pBase ); + +#ifdef _X360 + m_bPhysical = false; +#endif + + m_maxSize = maxSize; + m_alignment = AlignValue( alignment, 4 ); + + Assert( m_alignment == alignment ); + Assert( m_maxSize > 0 ); + +#if defined(_WIN32) + if ( commitSize != 0 ) + { + m_commitSize = commitSize; + } + + unsigned pageSize; + +#ifndef _X360 + SYSTEM_INFO sysInfo; + GetSystemInfo( &sysInfo ); + Assert( !( sysInfo.dwPageSize & (sysInfo.dwPageSize-1)) ); + pageSize = sysInfo.dwPageSize; +#else + pageSize = 64*1024; +#endif + + if ( m_commitSize == 0 ) + { + m_commitSize = pageSize; + } + else + { + m_commitSize = AlignValue( m_commitSize, pageSize ); + } + + m_maxSize = AlignValue( m_maxSize, m_commitSize ); + + Assert( m_maxSize % pageSize == 0 && m_commitSize % pageSize == 0 && m_commitSize <= m_maxSize ); + + m_pBase = (unsigned char *)VirtualAlloc( NULL, m_maxSize, VA_RESERVE_FLAGS, PAGE_NOACCESS ); + Assert( m_pBase ); + m_pCommitLimit = m_pNextAlloc = m_pBase; + + if ( initialCommit ) + { + initialCommit = AlignValue( initialCommit, m_commitSize ); + Assert( initialCommit < m_maxSize ); + if ( !VirtualAlloc( m_pCommitLimit, initialCommit, VA_COMMIT_FLAGS, PAGE_READWRITE ) ) + return false; + m_minCommit = initialCommit; + m_pCommitLimit += initialCommit; + MemAlloc_RegisterExternalAllocation( CMemoryStack, GetBase(), GetSize() ); + } + +#else + m_pBase = (byte *)MemAlloc_AllocAligned( m_maxSize, alignment ? alignment : 1 ); + m_pNextAlloc = m_pBase; + m_pCommitLimit = m_pBase + m_maxSize; +#endif + + m_pAllocLimit = m_pBase + m_maxSize; + + return ( m_pBase != NULL ); +} + +//------------------------------------- + +#ifdef _X360 +bool CMemoryStack::InitPhysical( unsigned size, unsigned alignment ) +{ + m_bPhysical = true; + + m_maxSize = m_commitSize = size; + m_alignment = AlignValue( alignment, 4 ); + + int flags = PAGE_READWRITE; + if ( size >= 16*1024*1024 ) + { + flags |= MEM_16MB_PAGES; + } + else + { + flags |= MEM_LARGE_PAGES; + } + m_pBase = (unsigned char *)XPhysicalAlloc( m_maxSize, MAXULONG_PTR, 4096, flags ); + Assert( m_pBase ); + m_pNextAlloc = m_pBase; + m_pCommitLimit = m_pBase + m_maxSize; + m_pAllocLimit = m_pBase + m_maxSize; + + MemAlloc_RegisterExternalAllocation( CMemoryStack, GetBase(), GetSize() ); + return ( m_pBase != NULL ); +} +#endif + +//------------------------------------- + +void CMemoryStack::Term() +{ + FreeAll(); + if ( m_pBase ) + { +#if defined(_WIN32) + VirtualFree( m_pBase, 0, MEM_RELEASE ); +#else + MemAlloc_FreeAligned( m_pBase ); +#endif + m_pBase = NULL; + } +} + +//------------------------------------- + +int CMemoryStack::GetSize() +{ +#ifdef _WIN32 + return m_pCommitLimit - m_pBase; +#else + return m_maxSize; +#endif +} + + +//------------------------------------- + +bool CMemoryStack::CommitTo( byte *pNextAlloc ) RESTRICT +{ +#ifdef _X360 + if ( m_bPhysical ) + { + return NULL; + } +#endif +#if defined(_WIN32) + unsigned char * pNewCommitLimit = AlignValue( pNextAlloc, m_commitSize ); + unsigned commitSize = pNewCommitLimit - m_pCommitLimit; + + if ( GetSize() ) + MemAlloc_RegisterExternalDeallocation( CMemoryStack, GetBase(), GetSize() ); + + if( m_pCommitLimit + commitSize > m_pAllocLimit ) + { + return false; + } + + if ( !VirtualAlloc( m_pCommitLimit, commitSize, VA_COMMIT_FLAGS, PAGE_READWRITE ) ) + { + Assert( 0 ); + return false; + } + m_pCommitLimit = pNewCommitLimit; + + if ( GetSize() ) + MemAlloc_RegisterExternalAllocation( CMemoryStack, GetBase(), GetSize() ); + return true; +#else + Assert( 0 ); + return false; +#endif +} + +//------------------------------------- + +void CMemoryStack::FreeToAllocPoint( MemoryStackMark_t mark, bool bDecommit ) +{ + void *pAllocPoint = m_pBase + mark; + Assert( pAllocPoint >= m_pBase && pAllocPoint <= m_pNextAlloc ); + + if ( pAllocPoint >= m_pBase && pAllocPoint < m_pNextAlloc ) + { + if ( bDecommit ) + { +#if defined(_WIN32) + unsigned char *pDecommitPoint = AlignValue( (unsigned char *)pAllocPoint, m_commitSize ); + + if ( pDecommitPoint < m_pBase + m_minCommit ) + { + pDecommitPoint = m_pBase + m_minCommit; + } + + unsigned decommitSize = m_pCommitLimit - pDecommitPoint; + + if ( decommitSize > 0 ) + { + MemAlloc_RegisterExternalDeallocation( CMemoryStack, GetBase(), GetSize() ); + + VirtualFree( pDecommitPoint, decommitSize, MEM_DECOMMIT ); + m_pCommitLimit = pDecommitPoint; + + if ( mark > 0 ) + { + MemAlloc_RegisterExternalAllocation( CMemoryStack, GetBase(), GetSize() ); + } + } +#endif + } + m_pNextAlloc = (unsigned char *)pAllocPoint; + } +} + +//------------------------------------- + +void CMemoryStack::FreeAll( bool bDecommit ) +{ + if ( m_pBase && m_pCommitLimit - m_pBase > 0 ) + { + if ( bDecommit ) + { +#if defined(_WIN32) + MemAlloc_RegisterExternalDeallocation( CMemoryStack, GetBase(), GetSize() ); + + VirtualFree( m_pBase, m_pCommitLimit - m_pBase, MEM_DECOMMIT ); + m_pCommitLimit = m_pBase; +#endif + } + m_pNextAlloc = m_pBase; + } +} + +//------------------------------------- + +void CMemoryStack::Access( void **ppRegion, unsigned *pBytes ) +{ + *ppRegion = m_pBase; + *pBytes = ( m_pNextAlloc - m_pBase); +} + +//------------------------------------- + +void CMemoryStack::PrintContents() +{ + Msg( "Total used memory: %d\n", GetUsed() ); + Msg( "Total committed memory: %d\n", GetSize() ); +} + +//----------------------------------------------------------------------------- diff --git a/tier1/newbitbuf.cpp b/tier1/newbitbuf.cpp new file mode 100644 index 00000000..420556ec --- /dev/null +++ b/tier1/newbitbuf.cpp @@ -0,0 +1,713 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "bitbuf.h" +#include "coordsize.h" +#include "mathlib/vector.h" +#include "mathlib/mathlib.h" +#include "tier1/strtools.h" +#include "bitvec.h" + +// FIXME: Can't use this until we get multithreaded allocations in tier0 working for tools +// This is used by VVIS and fails to link +// NOTE: This must be the last file included!!! +//#include "tier0/memdbgon.h" + +#ifdef _X360 +// mandatory ... wary of above comment and isolating, tier0 is built as MT though +#include "tier0/memdbgon.h" +#endif + +#include "stdio.h" + +void CBitWrite::StartWriting( void *pData, int nBytes, int iStartBit, int nBits ) +{ + // Make sure it's dword aligned and padded. + Assert( (nBytes % 4) == 0 ); + Assert(((unsigned long)pData & 3) == 0); + Assert( iStartBit == 0 ); + m_pData = (uint32 *) pData; + m_pDataOut = m_pData; + m_nDataBytes = nBytes; + + if ( nBits == -1 ) + { + m_nDataBits = nBytes << 3; + } + else + { + Assert( nBits <= nBytes*8 ); + m_nDataBits = nBits; + } + m_bOverflow = false; + m_nOutBufWord = 0; + m_nOutBitsAvail = 32; + m_pBufferEnd = m_pDataOut + ( nBytes >> 2 ); +} + +const uint32 CBitBuffer::s_nMaskTable[33] = { + 0, + ( 1 << 1 ) - 1, + ( 1 << 2 ) - 1, + ( 1 << 3 ) - 1, + ( 1 << 4 ) - 1, + ( 1 << 5 ) - 1, + ( 1 << 6 ) - 1, + ( 1 << 7 ) - 1, + ( 1 << 8 ) - 1, + ( 1 << 9 ) - 1, + ( 1 << 10 ) - 1, + ( 1 << 11 ) - 1, + ( 1 << 12 ) - 1, + ( 1 << 13 ) - 1, + ( 1 << 14 ) - 1, + ( 1 << 15 ) - 1, + ( 1 << 16 ) - 1, + ( 1 << 17 ) - 1, + ( 1 << 18 ) - 1, + ( 1 << 19 ) - 1, + ( 1 << 20 ) - 1, + ( 1 << 21 ) - 1, + ( 1 << 22 ) - 1, + ( 1 << 23 ) - 1, + ( 1 << 24 ) - 1, + ( 1 << 25 ) - 1, + ( 1 << 26 ) - 1, + ( 1 << 27 ) - 1, + ( 1 << 28 ) - 1, + ( 1 << 29 ) - 1, + ( 1 << 30 ) - 1, + 0x7fffffff, + 0xffffffff, +}; + +bool CBitWrite::WriteString( const char *pStr ) +{ + if(pStr) + { + while( *pStr ) + { + WriteChar( * ( pStr++ ) ); + } + } + WriteChar( 0 ); + return !IsOverflowed(); +} + + +void CBitWrite::WriteLongLong(int64 val) +{ + uint *pLongs = (uint*)&val; + + // Insert the two DWORDS according to network endian + const short endianIndex = 0x0100; + byte *idx = (byte*)&endianIndex; + WriteUBitLong(pLongs[*idx++], sizeof(long) << 3); + WriteUBitLong(pLongs[*idx], sizeof(long) << 3); +} + +bool CBitWrite::WriteBits(const void *pInData, int nBits) +{ + unsigned char *pOut = (unsigned char*)pInData; + int nBitsLeft = nBits; + + // Bounds checking.. + if ( ( GetNumBitsWritten() + nBits) > m_nDataBits ) + { + SetOverflowFlag(); + CallErrorHandler( BITBUFERROR_BUFFER_OVERRUN, m_pDebugName ); + return false; + } + + // !! speed!! need fast paths + // write remaining bytes + while ( nBitsLeft >= 8 ) + { + WriteUBitLong( *pOut, 8, false ); + ++pOut; + nBitsLeft -= 8; + } + + // write remaining bits + if ( nBitsLeft ) + { + WriteUBitLong( *pOut, nBitsLeft, false ); + } + + return !IsOverflowed(); +} + +void CBitWrite::WriteBytes( const void *pBuf, int nBytes ) +{ + WriteBits(pBuf, nBytes << 3); +} + +void CBitWrite::WriteBitCoord (const float f) +{ + int signbit = (f <= -COORD_RESOLUTION); + int intval = (int)fabs(f); + int fractval = abs((int)(f*COORD_DENOMINATOR)) & (COORD_DENOMINATOR-1); + + + // Send the bit flags that indicate whether we have an integer part and/or a fraction part. + WriteOneBit( intval ); + WriteOneBit( fractval ); + + if ( intval || fractval ) + { + // Send the sign bit + WriteOneBit( signbit ); + + // Send the integer if we have one. + if ( intval ) + { + // Adjust the integers from [1..MAX_COORD_VALUE] to [0..MAX_COORD_VALUE-1] + intval--; + WriteUBitLong( (unsigned int)intval, COORD_INTEGER_BITS ); + } + + // Send the fraction if we have one + if ( fractval ) + { + WriteUBitLong( (unsigned int)fractval, COORD_FRACTIONAL_BITS ); + } + } +} + +void CBitWrite::WriteBitCoordMP (const float f, bool bIntegral, bool bLowPrecision ) +{ + int signbit = (f <= -( bLowPrecision ? COORD_RESOLUTION_LOWPRECISION : COORD_RESOLUTION )); + int intval = (int)fabs(f); + int fractval = bLowPrecision ? + ( abs((int)(f*COORD_DENOMINATOR_LOWPRECISION)) & (COORD_DENOMINATOR_LOWPRECISION-1) ) : + ( abs((int)(f*COORD_DENOMINATOR)) & (COORD_DENOMINATOR-1) ); + + bool bInBounds = intval < (1 << COORD_INTEGER_BITS_MP ); + + WriteOneBit( bInBounds ); + + if ( bIntegral ) + { + // Send the sign bit + WriteOneBit( intval ); + if ( intval ) + { + WriteOneBit( signbit ); + // Send the integer if we have one. + // Adjust the integers from [1..MAX_COORD_VALUE] to [0..MAX_COORD_VALUE-1] + intval--; + if ( bInBounds ) + { + WriteUBitLong( (unsigned int)intval, COORD_INTEGER_BITS_MP ); + } + else + { + WriteUBitLong( (unsigned int)intval, COORD_INTEGER_BITS ); + } + } + } + else + { + // Send the bit flags that indicate whether we have an integer part and/or a fraction part. + WriteOneBit( intval ); + // Send the sign bit + WriteOneBit( signbit ); + + // Send the integer if we have one. + if ( intval ) + { + // Adjust the integers from [1..MAX_COORD_VALUE] to [0..MAX_COORD_VALUE-1] + intval--; + if ( bInBounds ) + { + WriteUBitLong( (unsigned int)intval, COORD_INTEGER_BITS_MP ); + } + else + { + WriteUBitLong( (unsigned int)intval, COORD_INTEGER_BITS ); + } + } + WriteUBitLong( (unsigned int)fractval, bLowPrecision ? COORD_FRACTIONAL_BITS_MP_LOWPRECISION : COORD_FRACTIONAL_BITS ); + } +} + +void CBitWrite::SeekToBit( int nBit ) +{ + TempFlush(); + m_pDataOut = m_pData + ( nBit / 32 ); + m_nOutBufWord = *( m_pDataOut ); + m_nOutBitsAvail = 32 - ( nBit & 31 ); +} + + + +void CBitWrite::WriteBitVec3Coord( const Vector& fa ) +{ + int xflag, yflag, zflag; + + xflag = (fa[0] >= COORD_RESOLUTION) || (fa[0] <= -COORD_RESOLUTION); + yflag = (fa[1] >= COORD_RESOLUTION) || (fa[1] <= -COORD_RESOLUTION); + zflag = (fa[2] >= COORD_RESOLUTION) || (fa[2] <= -COORD_RESOLUTION); + + WriteOneBit( xflag ); + WriteOneBit( yflag ); + WriteOneBit( zflag ); + + if ( xflag ) + WriteBitCoord( fa[0] ); + if ( yflag ) + WriteBitCoord( fa[1] ); + if ( zflag ) + WriteBitCoord( fa[2] ); +} + +void CBitWrite::WriteBitNormal( float f ) +{ + int signbit = (f <= -NORMAL_RESOLUTION); + + // NOTE: Since +/-1 are valid values for a normal, I'm going to encode that as all ones + unsigned int fractval = abs( (int)(f*NORMAL_DENOMINATOR) ); + + // clamp.. + if (fractval > NORMAL_DENOMINATOR) + fractval = NORMAL_DENOMINATOR; + + // Send the sign bit + WriteOneBit( signbit ); + + // Send the fractional component + WriteUBitLong( fractval, NORMAL_FRACTIONAL_BITS ); +} + +void CBitWrite::WriteBitVec3Normal( const Vector& fa ) +{ + int xflag, yflag; + + xflag = (fa[0] >= NORMAL_RESOLUTION) || (fa[0] <= -NORMAL_RESOLUTION); + yflag = (fa[1] >= NORMAL_RESOLUTION) || (fa[1] <= -NORMAL_RESOLUTION); + + WriteOneBit( xflag ); + WriteOneBit( yflag ); + + if ( xflag ) + WriteBitNormal( fa[0] ); + if ( yflag ) + WriteBitNormal( fa[1] ); + + // Write z sign bit + int signbit = (fa[2] <= -NORMAL_RESOLUTION); + WriteOneBit( signbit ); +} + +void CBitWrite::WriteBitAngle( float fAngle, int numbits ) +{ + + unsigned int shift = GetBitForBitnum(numbits); + unsigned int mask = shift - 1; + + int d = (int)( (fAngle / 360.0) * shift ); + d &= mask; + + WriteUBitLong((unsigned int)d, numbits); +} + +bool CBitWrite::WriteBitsFromBuffer( bf_read *pIn, int nBits ) +{ +// This could be optimized a little by + while ( nBits > 32 ) + { + WriteUBitLong( pIn->ReadUBitLong( 32 ), 32 ); + nBits -= 32; + } + + WriteUBitLong( pIn->ReadUBitLong( nBits ), nBits ); + return !IsOverflowed() && !pIn->IsOverflowed(); +} + +void CBitWrite::WriteBitAngles( const QAngle& fa ) +{ + // FIXME: + Vector tmp( fa.x, fa.y, fa.z ); + WriteBitVec3Coord( tmp ); +} + +bool CBitRead::Seek( int nPosition ) +{ + bool bSucc = true; + if ( nPosition < 0 || nPosition > m_nDataBits) + { + SetOverflowFlag(); + bSucc = false; + nPosition = m_nDataBits; + } + int nHead = m_nDataBytes & 3; // non-multiple-of-4 bytes at head of buffer. We put the "round off" + // at the head to make reading and detecting the end efficient. + + int nByteOfs = nPosition / 8; + if ( ( m_nDataBytes < 4 ) || ( nHead && ( nByteOfs < nHead ) ) ) + { + // partial first dword + uint8 const *pPartial = ( uint8 const *) m_pData; + if ( m_pData ) + { + m_nInBufWord = *( pPartial++ ); + if ( nHead > 1 ) + m_nInBufWord |= ( *pPartial++ ) << 8; + if ( nHead > 2 ) + m_nInBufWord |= ( *pPartial++ ) << 16; + } + m_pDataIn = ( uint32 const * ) pPartial; + m_nInBufWord >>= ( nPosition & 31 ); + m_nBitsAvail = ( nHead << 3 ) - ( nPosition & 31 ); + } + else + { + int nAdjPosition = nPosition - ( nHead << 3 ); + m_pDataIn = reinterpret_cast ( + reinterpret_cast( m_pData ) + ( ( nAdjPosition / 32 ) << 2 ) + nHead ); + if ( m_pData ) + { + m_nBitsAvail = 32; + GrabNextDWord(); + } + else + { + m_nInBufWord = 0; + m_nBitsAvail = 1; + } + m_nInBufWord >>= ( nAdjPosition & 31 ); + m_nBitsAvail = MIN( m_nBitsAvail, 32 - ( nAdjPosition & 31 ) ); // in case grabnextdword overflowed + } + return bSucc; +} + + +void CBitRead::StartReading( const void *pData, int nBytes, int iStartBit, int nBits ) +{ +// Make sure it's dword aligned and padded. + Assert(((unsigned long)pData & 3) == 0); + m_pData = (uint32 *) pData; + m_pDataIn = m_pData; + m_nDataBytes = nBytes; + + if ( nBits == -1 ) + { + m_nDataBits = nBytes << 3; + } + else + { + Assert( nBits <= nBytes*8 ); + m_nDataBits = nBits; + } + m_bOverflow = false; + m_pBufferEnd = reinterpret_cast ( reinterpret_cast< uint8 const *> (m_pData) + nBytes ); + if ( m_pData ) + Seek( iStartBit ); + +} + +bool CBitRead::ReadString( char *pStr, int maxLen, bool bLine, int *pOutNumChars ) +{ + Assert( maxLen != 0 ); + + bool bTooSmall = false; + int iChar = 0; + while(1) + { + char val = ReadChar(); + if ( val == 0 ) + break; + else if ( bLine && val == '\n' ) + break; + + if ( iChar < (maxLen-1) ) + { + pStr[iChar] = val; + ++iChar; + } + else + { + bTooSmall = true; + } + } + + // Make sure it's null-terminated. + Assert( iChar < maxLen ); + pStr[iChar] = 0; + + if ( pOutNumChars ) + *pOutNumChars = iChar; + + return !IsOverflowed() && !bTooSmall; +} + +char* CBitRead::ReadAndAllocateString( bool *pOverflow ) +{ + char str[2048]; + + int nChars; + bool bOverflow = !ReadString( str, sizeof( str ), false, &nChars ); + if ( pOverflow ) + *pOverflow = bOverflow; + + // Now copy into the output and return it; + char *pRet = new char[ nChars + 1 ]; + for ( int i=0; i <= nChars; i++ ) + pRet[i] = str[i]; + + return pRet; +} + +int64 CBitRead::ReadLongLong( void ) +{ + int64 retval; + uint *pLongs = (uint*)&retval; + + // Read the two DWORDs according to network endian + const short endianIndex = 0x0100; + byte *idx = (byte*)&endianIndex; + pLongs[*idx++] = ReadUBitLong(sizeof(long) << 3); + pLongs[*idx] = ReadUBitLong(sizeof(long) << 3); + return retval; +} + +void CBitRead::ReadBits(void *pOutData, int nBits) +{ + unsigned char *pOut = (unsigned char*)pOutData; + int nBitsLeft = nBits; + + + // align output to dword boundary + while( ((unsigned long)pOut & 3) != 0 && nBitsLeft >= 8 ) + { + *pOut = (unsigned char)ReadUBitLong(8); + ++pOut; + nBitsLeft -= 8; + } + + // X360TBD: Can't read dwords in ReadBits because they'll get swapped + if ( IsPC() ) + { + // read dwords + while ( nBitsLeft >= 32 ) + { + *((unsigned long*)pOut) = ReadUBitLong(32); + pOut += sizeof(unsigned long); + nBitsLeft -= 32; + } + } + + // read remaining bytes + while ( nBitsLeft >= 8 ) + { + *pOut = ReadUBitLong(8); + ++pOut; + nBitsLeft -= 8; + } + + // read remaining bits + if ( nBitsLeft ) + { + *pOut = ReadUBitLong(nBitsLeft); + } + +} + +bool CBitRead::ReadBytes(void *pOut, int nBytes) +{ + ReadBits(pOut, nBytes << 3); + return !IsOverflowed(); +} + +float CBitRead::ReadBitAngle( int numbits ) +{ + float shift = (float)( GetBitForBitnum(numbits) ); + + int i = ReadUBitLong( numbits ); + float fReturn = (float)i * (360.0 / shift); + + return fReturn; +} + +// Basic Coordinate Routines (these contain bit-field size AND fixed point scaling constants) +float CBitRead::ReadBitCoord (void) +{ + int intval=0,fractval=0,signbit=0; + float value = 0.0; + + + // Read the required integer and fraction flags + intval = ReadOneBit(); + fractval = ReadOneBit(); + + // If we got either parse them, otherwise it's a zero. + if ( intval || fractval ) + { + // Read the sign bit + signbit = ReadOneBit(); + + // If there's an integer, read it in + if ( intval ) + { + // Adjust the integers from [0..MAX_COORD_VALUE-1] to [1..MAX_COORD_VALUE] + intval = ReadUBitLong( COORD_INTEGER_BITS ) + 1; + } + + // If there's a fraction, read it in + if ( fractval ) + { + fractval = ReadUBitLong( COORD_FRACTIONAL_BITS ); + } + + // Calculate the correct floating point value + value = intval + ((float)fractval * COORD_RESOLUTION); + + // Fixup the sign if negative. + if ( signbit ) + value = -value; + } + + return value; +} + +float CBitRead::ReadBitCoordMP( bool bIntegral, bool bLowPrecision ) +{ + int intval=0,fractval=0,signbit=0; + float value = 0.0; + + bool bInBounds = ReadOneBit() ? true : false; + + if ( bIntegral ) + { + // Read the required integer and fraction flags + intval = ReadOneBit(); + // If we got either parse them, otherwise it's a zero. + if ( intval ) + { + // Read the sign bit + signbit = ReadOneBit(); + + // If there's an integer, read it in + // Adjust the integers from [0..MAX_COORD_VALUE-1] to [1..MAX_COORD_VALUE] + if ( bInBounds ) + { + value = ReadUBitLong( COORD_INTEGER_BITS_MP ) + 1; + } + else + { + value = ReadUBitLong( COORD_INTEGER_BITS ) + 1; + } + } + } + else + { + // Read the required integer and fraction flags + intval = ReadOneBit(); + + // Read the sign bit + signbit = ReadOneBit(); + + // If we got either parse them, otherwise it's a zero. + if ( intval ) + { + if ( bInBounds ) + { + intval = ReadUBitLong( COORD_INTEGER_BITS_MP ) + 1; + } + else + { + intval = ReadUBitLong( COORD_INTEGER_BITS ) + 1; + } + } + + // If there's a fraction, read it in + fractval = ReadUBitLong( bLowPrecision ? COORD_FRACTIONAL_BITS_MP_LOWPRECISION : COORD_FRACTIONAL_BITS ); + + // Calculate the correct floating point value + value = intval + ((float)fractval * ( bLowPrecision ? COORD_RESOLUTION_LOWPRECISION : COORD_RESOLUTION ) ); + } + + // Fixup the sign if negative. + if ( signbit ) + value = -value; + + return value; +} + +void CBitRead::ReadBitVec3Coord( Vector& fa ) +{ + int xflag, yflag, zflag; + + // This vector must be initialized! Otherwise, If any of the flags aren't set, + // the corresponding component will not be read and will be stack garbage. + fa.Init( 0, 0, 0 ); + + xflag = ReadOneBit(); + yflag = ReadOneBit(); + zflag = ReadOneBit(); + + if ( xflag ) + fa[0] = ReadBitCoord(); + if ( yflag ) + fa[1] = ReadBitCoord(); + if ( zflag ) + fa[2] = ReadBitCoord(); +} + +float CBitRead::ReadBitNormal (void) +{ + // Read the sign bit + int signbit = ReadOneBit(); + + // Read the fractional part + unsigned int fractval = ReadUBitLong( NORMAL_FRACTIONAL_BITS ); + + // Calculate the correct floating point value + float value = (float)fractval * NORMAL_RESOLUTION; + + // Fixup the sign if negative. + if ( signbit ) + value = -value; + + return value; +} + +void CBitRead::ReadBitVec3Normal( Vector& fa ) +{ + int xflag = ReadOneBit(); + int yflag = ReadOneBit(); + + if (xflag) + fa[0] = ReadBitNormal(); + else + fa[0] = 0.0f; + + if (yflag) + fa[1] = ReadBitNormal(); + else + fa[1] = 0.0f; + + // The first two imply the third (but not its sign) + int znegative = ReadOneBit(); + + float fafafbfb = fa[0] * fa[0] + fa[1] * fa[1]; + if (fafafbfb < 1.0f) + fa[2] = sqrt( 1.0f - fafafbfb ); + else + fa[2] = 0.0f; + + if (znegative) + fa[2] = -fa[2]; +} + +void CBitRead::ReadBitAngles( QAngle& fa ) +{ + Vector tmp; + ReadBitVec3Coord( tmp ); + fa.Init( tmp.x, tmp.y, tmp.z ); +} diff --git a/tier1/processor_detect.cpp b/tier1/processor_detect.cpp new file mode 100644 index 00000000..a6bfe91b --- /dev/null +++ b/tier1/processor_detect.cpp @@ -0,0 +1,278 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: win32 dependant ASM code for CPU capability detection +// +// $Workfile: $ +// $NoKeywords: $ +//=============================================================================// + +#if defined _LINUX || defined __APPLE__ + +#include "processor_detect_linux.cpp" + +#elif defined( _X360 ) + +bool CheckMMXTechnology(void) { return false; } +bool CheckSSETechnology(void) { return false; } +bool CheckSSE2Technology(void) { return false; } +bool Check3DNowTechnology(void) { return false; } + +#elif defined( _WIN32 ) && !defined( _X360 ) + +#pragma optimize( "", off ) +#pragma warning( disable: 4800 ) //'int' : forcing value to bool 'true' or 'false' (performance warning) + +// stuff from windows.h +#ifndef EXCEPTION_EXECUTE_HANDLER +#define EXCEPTION_EXECUTE_HANDLER 1 +#endif + +bool CheckMMXTechnology(void) +{ + int retval = true; + unsigned int RegEDX = 0; + +#ifdef CPUID + _asm pushad; +#endif + + __try + { + _asm + { +#ifdef CPUID + xor edx, edx // Clue the compiler that EDX is about to be used. +#endif + mov eax, 1 // set up CPUID to return processor version and features + // 0 = vendor string, 1 = version info, 2 = cache info + CPUID // code bytes = 0fh, 0a2h + mov RegEDX, edx // features returned in edx + } + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + retval = false; + } + + // If CPUID not supported, then certainly no MMX extensions. + if (retval) + { + if (RegEDX & 0x800000) // bit 23 is set for MMX technology + { + __try + { + // try executing the MMX instruction "emms" + _asm EMMS + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + retval = false; + } + } + + else + retval = false; // processor supports CPUID but does not support MMX technology + + // if retval == 0 here, it means the processor has MMX technology but + // floating-point emulation is on; so MMX technology is unavailable + } + +#ifdef CPUID + _asm popad; +#endif + + return retval; +} + +bool CheckSSETechnology(void) +{ + int retval = true; + unsigned int RegEDX = 0; + +#ifdef CPUID + _asm pushad; +#endif + + // Do we have support for the CPUID function? + __try + { + _asm + { +#ifdef CPUID + xor edx, edx // Clue the compiler that EDX is about to be used. +#endif + mov eax, 1 // set up CPUID to return processor version and features + // 0 = vendor string, 1 = version info, 2 = cache info + CPUID // code bytes = 0fh, 0a2h + mov RegEDX, edx // features returned in edx + } + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + retval = false; + } + + // If CPUID not supported, then certainly no SSE extensions. + if (retval) + { + // Do we have support for SSE in this processor? + if ( RegEDX & 0x2000000L ) // bit 25 is set for SSE technology + { + // Make sure that SSE is supported by executing an inline SSE instruction + +// BUGBUG, FIXME - Visual C Version 6.0 does not support SSE inline code YET (No macros from Intel either) +// Fix this if VC7 supports inline SSE instructinons like "xorps" as shown below. +#if 1 + __try + { + _asm + { + // Attempt execution of a SSE instruction to make sure OS supports SSE FPU context switches + xorps xmm0, xmm0 + // This will work on Win2k+ (Including masking SSE FPU exception to "normalized" values) + // This will work on Win98+ (But no "masking" of FPU exceptions provided) + } + } + __except(EXCEPTION_EXECUTE_HANDLER) +#endif + + { + retval = false; + } + } + else + retval = false; + } +#ifdef CPUID + _asm popad; +#endif + + return retval; +} + +bool CheckSSE2Technology(void) +{ + int retval = true; + unsigned int RegEDX = 0; + +#ifdef CPUID + _asm pushad; +#endif + + // Do we have support for the CPUID function? + __try + { + _asm + { +#ifdef CPUID + xor edx, edx // Clue the compiler that EDX is about to be used. +#endif + mov eax, 1 // set up CPUID to return processor version and features + // 0 = vendor string, 1 = version info, 2 = cache info + CPUID // code bytes = 0fh, 0a2h + mov RegEDX, edx // features returned in edx + } + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + retval = false; + } + + // If CPUID not supported, then certainly no SSE extensions. + if (retval) + { + // Do we have support for SSE in this processor? + if ( RegEDX & 0x04000000 ) // bit 26 is set for SSE2 technology + { + // Make sure that SSE is supported by executing an inline SSE instruction + + __try + { + _asm + { + // Attempt execution of a SSE2 instruction to make sure OS supports SSE FPU context switches + xorpd xmm0, xmm0 + } + } + __except(EXCEPTION_EXECUTE_HANDLER) + + { + retval = false; + } + } + else + retval = false; + } +#ifdef CPUID + _asm popad; +#endif + + return retval; +} + +bool Check3DNowTechnology(void) +{ + int retval = true; + unsigned int RegEAX = 0; + +#ifdef CPUID + _asm pushad; +#endif + + // First see if we can execute CPUID at all + __try + { + _asm + { +#ifdef CPUID +// xor edx, edx // Clue the compiler that EDX is about to be used. +#endif + mov eax, 0x80000000 // setup CPUID to return whether AMD >0x80000000 function are supported. + // 0x80000000 = Highest 0x80000000+ function, 0x80000001 = 3DNow support + CPUID // code bytes = 0fh, 0a2h + mov RegEAX, eax // result returned in eax + } + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + retval = false; + } + + // If CPUID not supported, then there is definitely no 3DNow support + if (retval) + { + // Are there any "higher" AMD CPUID functions? + if (RegEAX > 0x80000000L ) + { + __try + { + _asm + { + mov eax, 0x80000001 // setup to test for CPU features + CPUID // code bytes = 0fh, 0a2h + shr edx, 31 // If bit 31 is set, we have 3DNow support! + mov retval, edx // Save the return value for end of function + } + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + retval = false; + } + } + else + { + // processor supports CPUID but does not support AMD CPUID functions + retval = false; + } + } + +#ifdef CPUID + _asm popad; +#endif + + return retval; +} + +#pragma optimize( "", on ) + +#endif // _WIN32 diff --git a/tier1/processor_detect_linux.cpp b/tier1/processor_detect_linux.cpp new file mode 100644 index 00000000..b09df0e0 --- /dev/null +++ b/tier1/processor_detect_linux.cpp @@ -0,0 +1,47 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: linux dependant ASM code for CPU capability detection +// +// $Workfile: $ +// $NoKeywords: $ +//=============================================================================// + +#define cpuid(in,a,b,c,d) \ + asm("pushl %%ebx\n\t" "cpuid\n\t" "movl %%ebx,%%esi\n\t" "pop %%ebx": "=a" (a), "=S" (b), "=c" (c), "=d" (d) : "a" (in)); + +bool CheckMMXTechnology(void) +{ + unsigned long eax,ebx,edx,unused; + cpuid(1,eax,ebx,unused,edx); + + return edx & 0x800000; +} + +bool CheckSSETechnology(void) +{ + unsigned long eax,ebx,edx,unused; + cpuid(1,eax,ebx,unused,edx); + + return edx & 0x2000000L; +} + +bool CheckSSE2Technology(void) +{ + unsigned long eax,ebx,edx,unused; + cpuid(1,eax,ebx,unused,edx); + + return edx & 0x04000000; +} + +bool Check3DNowTechnology(void) +{ + unsigned long eax, unused; + cpuid(0x80000000,eax,unused,unused,unused); + + if ( eax > 0x80000000L ) + { + cpuid(0x80000001,unused,unused,unused,eax); + return ( eax & 1<<31 ); + } + return false; +} diff --git a/tier1/rangecheckedvar.cpp b/tier1/rangecheckedvar.cpp new file mode 100644 index 00000000..a342dcd9 --- /dev/null +++ b/tier1/rangecheckedvar.cpp @@ -0,0 +1,41 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "rangecheckedvar.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +bool g_bDoRangeChecks = true; + + +static int g_nDisables = 0; + + +CDisableRangeChecks::CDisableRangeChecks() +{ + if ( !ThreadInMainThread() ) + return; + g_nDisables++; + g_bDoRangeChecks = false; +} + + +CDisableRangeChecks::~CDisableRangeChecks() +{ + if ( !ThreadInMainThread() ) + return; + Assert( g_nDisables > 0 ); + --g_nDisables; + if ( g_nDisables == 0 ) + { + g_bDoRangeChecks = true; + } +} + + + + diff --git a/tier1/stringpool.cpp b/tier1/stringpool.cpp new file mode 100644 index 00000000..8924034e --- /dev/null +++ b/tier1/stringpool.cpp @@ -0,0 +1,334 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#include "convar.h" +#include "tier0/dbg.h" +#include "stringpool.h" +#include "tier1/strtools.h" +#include "generichash.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Comparison function for string sorted associative data structures +//----------------------------------------------------------------------------- + +bool StrLess( const char * const &pszLeft, const char * const &pszRight ) +{ + return ( Q_stricmp( pszLeft, pszRight) < 0 ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +CStringPool::CStringPool() + : m_Strings( 32, 256, StrLess ) +{ +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +CStringPool::~CStringPool() +{ + FreeAll(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +unsigned int CStringPool::Count() const +{ + return m_Strings.Count(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +const char * CStringPool::Find( const char *pszValue ) +{ + unsigned short i = m_Strings.Find(pszValue); + if ( m_Strings.IsValidIndex(i) ) + return m_Strings[i]; + + return NULL; +} + +const char * CStringPool::Allocate( const char *pszValue ) +{ + char *pszNew; + + unsigned short i = m_Strings.Find(pszValue); + bool bNew = (i == m_Strings.InvalidIndex()); + + if ( !bNew ) + return m_Strings[i]; + + pszNew = strdup( pszValue ); + + if ( bNew ) + m_Strings.Insert( pszNew ); + + return pszNew; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +void CStringPool::FreeAll() +{ + unsigned short i = m_Strings.FirstInorder(); + while ( i != m_Strings.InvalidIndex() ) + { + free( (void *)m_Strings[i] ); + i = m_Strings.NextInorder(i); + } + m_Strings.RemoveAll(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + + +CCountedStringPool::CCountedStringPool() +{ + MEM_ALLOC_CREDIT(); + m_HashTable.EnsureCount(HASH_TABLE_SIZE); + + for( int i = 0; i < m_HashTable.Count(); i++ ) + { + m_HashTable[i] = INVALID_ELEMENT; + } + + m_FreeListStart = INVALID_ELEMENT; + m_Elements.AddToTail(); + m_Elements[0].pString = NULL; + m_Elements[0].nReferenceCount = 0; + m_Elements[0].nNextElement = INVALID_ELEMENT; +} + +CCountedStringPool::~CCountedStringPool() +{ + FreeAll(); +} + +void CCountedStringPool::FreeAll() +{ + int i; + + // Reset the hash table: + for( i = 0; i < m_HashTable.Count(); i++ ) + { + m_HashTable[i] = INVALID_ELEMENT; + } + + // Blow away the free list: + m_FreeListStart = INVALID_ELEMENT; + + for( i = 0; i < m_Elements.Count(); i++ ) + { + if( m_Elements[i].pString ) + { + delete [] m_Elements[i].pString; + m_Elements[i].pString = NULL; + m_Elements[i].nReferenceCount = 0; + m_Elements[i].nNextElement = INVALID_ELEMENT; + } + } + + // Remove all but the invalid element: + m_Elements.RemoveAll(); + m_Elements.AddToTail(); + m_Elements[0].pString = NULL; + m_Elements[0].nReferenceCount = 0; + m_Elements[0].nNextElement = INVALID_ELEMENT; +} + + +unsigned short CCountedStringPool::FindStringHandle( const char* pIntrinsic ) +{ + if( pIntrinsic == NULL ) + return INVALID_ELEMENT; + + unsigned short nHashBucketIndex = (HashStringCaseless(pIntrinsic ) %HASH_TABLE_SIZE); + unsigned short nCurrentBucket = m_HashTable[ nHashBucketIndex ]; + + // Does the bucket already exist? + if( nCurrentBucket != INVALID_ELEMENT ) + { + for( ; nCurrentBucket != INVALID_ELEMENT ; nCurrentBucket = m_Elements[nCurrentBucket].nNextElement ) + { + if( !Q_stricmp( pIntrinsic, m_Elements[nCurrentBucket].pString ) ) + { + return nCurrentBucket; + } + } + } + + return 0; + +} + +char* CCountedStringPool::FindString( const char* pIntrinsic ) +{ + if( pIntrinsic == NULL ) + return NULL; + + // Yes, this will be NULL on failure. + return m_Elements[FindStringHandle(pIntrinsic)].pString; +} + +unsigned short CCountedStringPool::ReferenceStringHandle( const char* pIntrinsic ) +{ + if( pIntrinsic == NULL ) + return INVALID_ELEMENT; + + unsigned short nHashBucketIndex = (HashStringCaseless( pIntrinsic ) % HASH_TABLE_SIZE); + unsigned short nCurrentBucket = m_HashTable[ nHashBucketIndex ]; + + // Does the bucket already exist? + if( nCurrentBucket != INVALID_ELEMENT ) + { + for( ; nCurrentBucket != INVALID_ELEMENT ; nCurrentBucket = m_Elements[nCurrentBucket].nNextElement ) + { + if( !Q_stricmp( pIntrinsic, m_Elements[nCurrentBucket].pString ) ) + { + // Anyone who hits 65k references is permanant + if( m_Elements[nCurrentBucket].nReferenceCount < MAX_REFERENCE ) + { + m_Elements[nCurrentBucket].nReferenceCount ++ ; + } + return nCurrentBucket; + } + } + } + + if( m_FreeListStart != INVALID_ELEMENT ) + { + nCurrentBucket = m_FreeListStart; + m_FreeListStart = m_Elements[nCurrentBucket].nNextElement; + } + else + { + nCurrentBucket = m_Elements.AddToTail(); + } + + m_Elements[nCurrentBucket].nReferenceCount = 1; + + // Insert at the beginning of the bucket: + m_Elements[nCurrentBucket].nNextElement = m_HashTable[ nHashBucketIndex ]; + m_HashTable[ nHashBucketIndex ] = nCurrentBucket; + + m_Elements[nCurrentBucket].pString = new char[Q_strlen( pIntrinsic ) + 1]; + Q_strcpy( m_Elements[nCurrentBucket].pString, pIntrinsic ); + + return nCurrentBucket; +} + + +char* CCountedStringPool::ReferenceString( const char* pIntrinsic ) +{ + if(!pIntrinsic) + return NULL; + + return m_Elements[ReferenceStringHandle( pIntrinsic)].pString; +} + +void CCountedStringPool::DereferenceString( const char* pIntrinsic ) +{ + // If we get a NULL pointer, just return + if (!pIntrinsic) + return; + + unsigned short nHashBucketIndex = (HashStringCaseless( pIntrinsic ) % m_HashTable.Count()); + unsigned short nCurrentBucket = m_HashTable[ nHashBucketIndex ]; + + // If there isn't anything in the bucket, just return. + if ( nCurrentBucket == INVALID_ELEMENT ) + return; + + for( unsigned short previous = INVALID_ELEMENT; nCurrentBucket != INVALID_ELEMENT ; nCurrentBucket = m_Elements[nCurrentBucket].nNextElement ) + { + if( !Q_stricmp( pIntrinsic, m_Elements[nCurrentBucket].pString ) ) + { + // Anyone who hits 65k references is permanant + if( m_Elements[nCurrentBucket].nReferenceCount < MAX_REFERENCE ) + { + m_Elements[nCurrentBucket].nReferenceCount --; + } + + if( m_Elements[nCurrentBucket].nReferenceCount == 0 ) + { + if( previous == INVALID_ELEMENT ) + { + m_HashTable[nHashBucketIndex] = m_Elements[nCurrentBucket].nNextElement; + } + else + { + m_Elements[previous].nNextElement = m_Elements[nCurrentBucket].nNextElement; + } + + delete [] m_Elements[nCurrentBucket].pString; + m_Elements[nCurrentBucket].pString = NULL; + m_Elements[nCurrentBucket].nReferenceCount = 0; + + m_Elements[nCurrentBucket].nNextElement = m_FreeListStart; + m_FreeListStart = nCurrentBucket; + break; + + } + } + + previous = nCurrentBucket; + } +} + +char* CCountedStringPool::HandleToString( unsigned short handle ) +{ + return m_Elements[handle].pString; +} + +void CCountedStringPool::SpewStrings() +{ + int i; + for ( i = 0; i < m_Elements.Count(); i++ ) + { + char* string = m_Elements[i].pString; + + Msg("String %d: ref:%d %s", i, m_Elements[i].nReferenceCount, string == NULL? "EMPTY - ok for slot zero only!" : string); + } + + Msg("\n%d total counted strings.", m_Elements.Count()); +} + +#ifdef _DEBUG +CON_COMMAND( test_stringpool, "Tests the class CStringPool" ) +{ + CStringPool pool; + + Assert(pool.Count() == 0); + + pool.Allocate("test"); + Assert(pool.Count() == 1); + + pool.Allocate("test"); + Assert(pool.Count() == 1); + + pool.Allocate("test2"); + Assert(pool.Count() == 2); + + Assert( pool.Find("test2") != NULL ); + Assert( pool.Find("TEST") != NULL ); + Assert( pool.Find("Test2") != NULL ); + Assert( pool.Find("test") != NULL ); + + pool.FreeAll(); + Assert(pool.Count() == 0); + + Msg("Pass."); +} +#endif diff --git a/tier1/strtools.cpp b/tier1/strtools.cpp new file mode 100644 index 00000000..3188f209 --- /dev/null +++ b/tier1/strtools.cpp @@ -0,0 +1,2015 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: String Tools +// +//===========================================================================// + +// These are redefined in the project settings to prevent anyone from using them. +// We in this module are of a higher caste and thus are privileged in their use. +#ifdef strncpy + #undef strncpy +#endif + +#ifdef _snprintf + #undef _snprintf +#endif + +#if defined( sprintf ) + #undef sprintf +#endif + +#if defined( vsprintf ) + #undef vsprintf +#endif + +#ifdef _vsnprintf +#ifdef _WIN32 + #undef _vsnprintf +#endif +#endif + +#ifdef vsnprintf +#ifndef _WIN32 + #undef vsnprintf +#endif +#endif + +#if defined( strcat ) + #undef strcat +#endif + +#ifdef strncat + #undef strncat +#endif + +// NOTE: I have to include stdio + stdarg first so vsnprintf gets compiled in +#include +#include + +#if defined _LINUX || defined __APPLE__ +#include +#include +#include +#define _getcwd getcwd +#elif _WIN32 +#include +#if !defined( _X360 ) +#define WIN32_LEAN_AND_MEAN +#include +#endif +#endif + +#ifdef _WIN32 +#ifndef CP_UTF8 +#define CP_UTF8 65001 +#endif +#endif +#include "tier0/dbg.h" +#include "tier1/strtools.h" +#include +#include +#include "tier0/basetypes.h" +#include "tier1/utldict.h" +#if defined( _X360 ) +#include "xbox/xbox_win32stubs.h" +#endif +#include "tier0/memdbgon.h" + +void _V_memset (const char* file, int line, void *dest, int fill, int count) +{ + Assert( count >= 0 ); + AssertValidWritePtr( dest, count ); + + memset(dest,fill,count); +} + +void _V_memcpy (const char* file, int line, void *dest, const void *src, int count) +{ + Assert( count >= 0 ); + AssertValidReadPtr( src, count ); + AssertValidWritePtr( dest, count ); + + memcpy( dest, src, count ); +} + +void _V_memmove(const char* file, int line, void *dest, const void *src, int count) +{ + Assert( count >= 0 ); + AssertValidReadPtr( src, count ); + AssertValidWritePtr( dest, count ); + + memmove( dest, src, count ); +} + +int _V_memcmp (const char* file, int line, const void *m1, const void *m2, int count) +{ + Assert( count >= 0 ); + AssertValidReadPtr( m1, count ); + AssertValidReadPtr( m2, count ); + + return memcmp( m1, m2, count ); +} + +int _V_strlen(const char* file, int line, const char *str) +{ + AssertValidStringPtr(str); + return strlen( str ); +} + +void _V_strcpy (const char* file, int line, char *dest, const char *src) +{ + AssertValidWritePtr(dest); + AssertValidStringPtr(src); + + strcpy( dest, src ); +} + +int _V_wcslen(const char* file, int line, const wchar_t *pwch) +{ + return wcslen( pwch ); +} + +char *_V_strrchr(const char* file, int line, const char *s, char c) +{ + AssertValidStringPtr( s ); + int len = V_strlen(s); + s += len; + while (len--) + if (*--s == c) return (char *)s; + return 0; +} + +int _V_strcmp (const char* file, int line, const char *s1, const char *s2) +{ + AssertValidStringPtr( s1 ); + AssertValidStringPtr( s2 ); + + return strcmp( s1, s2 ); +} + +int _V_wcscmp (const char* file, int line, const wchar_t *s1, const wchar_t *s2) +{ + while (1) + { + if (*s1 != *s2) + return -1; // strings not equal + if (!*s1) + return 0; // strings are equal + s1++; + s2++; + } + + return -1; +} + + + +int _V_stricmp(const char* file, int line, const char *s1, const char *s2 ) +{ + AssertValidStringPtr( s1 ); + AssertValidStringPtr( s2 ); + + return stricmp( s1, s2 ); +} + + +char *_V_strstr(const char* file, int line, const char *s1, const char *search ) +{ + AssertValidStringPtr( s1 ); + AssertValidStringPtr( search ); + +#if defined( _X360 ) + return (char *)strstr( (char *)s1, search ); +#else + return (char *)strstr( s1, search ); +#endif +} + +char *_V_strupr (const char* file, int line, char *start) +{ + AssertValidStringPtr( start ); + return strupr( start ); +} + + +char *_V_strlower (const char* file, int line, char *start) +{ + AssertValidStringPtr( start ); + return strlwr(start); +} + +int V_strncmp (const char *s1, const char *s2, int count) +{ + Assert( count >= 0 ); + AssertValidStringPtr( s1, count ); + AssertValidStringPtr( s2, count ); + + while ( count-- > 0 ) + { + if ( *s1 != *s2 ) + return *s1 < *s2 ? -1 : 1; // string different + if ( *s1 == '\0' ) + return 0; // null terminator hit - strings the same + s1++; + s2++; + } + + return 0; // count characters compared the same +} + +char *V_strnlwr(char *s, size_t count) +{ + Assert( count >= 0 ); + AssertValidStringPtr( s, count ); + + char* pRet = s; + if ( !s ) + return s; + + while ( --count >= 0 ) + { + if ( !*s ) + break; + + *s = tolower( *s ); + ++s; + } + + if ( count > 0 ) + { + s[count-1] = 0; + } + + return pRet; +} + + +int V_strncasecmp (const char *s1, const char *s2, int n) +{ + Assert( n >= 0 ); + AssertValidStringPtr( s1 ); + AssertValidStringPtr( s2 ); + + while ( n-- > 0 ) + { + int c1 = *s1++; + int c2 = *s2++; + + if (c1 != c2) + { + if (c1 >= 'a' && c1 <= 'z') + c1 -= ('a' - 'A'); + if (c2 >= 'a' && c2 <= 'z') + c2 -= ('a' - 'A'); + if (c1 != c2) + return c1 < c2 ? -1 : 1; + } + if ( c1 == '\0' ) + return 0; // null terminator hit - strings the same + } + + return 0; // n characters compared the same +} + +int V_strcasecmp( const char *s1, const char *s2 ) +{ + AssertValidStringPtr( s1 ); + AssertValidStringPtr( s2 ); + + return stricmp( s1, s2 ); +} + +int V_strnicmp (const char *s1, const char *s2, int n) +{ + Assert( n >= 0 ); + AssertValidStringPtr(s1); + AssertValidStringPtr(s2); + + return V_strncasecmp( s1, s2, n ); +} + + +const char *StringAfterPrefix( const char *str, const char *prefix ) +{ + AssertValidStringPtr( str ); + AssertValidStringPtr( prefix ); + do + { + if ( !*prefix ) + return str; + } + while ( tolower( *str++ ) == tolower( *prefix++ ) ); + return NULL; +} + +const char *StringAfterPrefixCaseSensitive( const char *str, const char *prefix ) +{ + AssertValidStringPtr( str ); + AssertValidStringPtr( prefix ); + do + { + if ( !*prefix ) + return str; + } + while ( *str++ == *prefix++ ); + return NULL; +} + + +int V_atoi (const char *str) +{ + AssertValidStringPtr( str ); + + int val; + int sign; + int c; + + Assert( str ); + if (*str == '-') + { + sign = -1; + str++; + } + else + sign = 1; + + val = 0; + +// +// check for hex +// + if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X') ) + { + str += 2; + while (1) + { + c = *str++; + if (c >= '0' && c <= '9') + val = (val<<4) + c - '0'; + else if (c >= 'a' && c <= 'f') + val = (val<<4) + c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + val = (val<<4) + c - 'A' + 10; + else + return val*sign; + } + } + +// +// check for character +// + if (str[0] == '\'') + { + return sign * str[1]; + } + +// +// assume decimal +// + while (1) + { + c = *str++; + if (c <'0' || c > '9') + return val*sign; + val = val*10 + c - '0'; + } + + return 0; +} + + +float V_atof (const char *str) +{ + AssertValidStringPtr( str ); + double val; + int sign; + int c; + int decimal, total; + + if (*str == '-') + { + sign = -1; + str++; + } + else + sign = 1; + + val = 0; + +// +// check for hex +// + if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X') ) + { + str += 2; + while (1) + { + c = *str++; + if (c >= '0' && c <= '9') + val = (val*16) + c - '0'; + else if (c >= 'a' && c <= 'f') + val = (val*16) + c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + val = (val*16) + c - 'A' + 10; + else + return val*sign; + } + } + +// +// check for character +// + if (str[0] == '\'') + { + return sign * str[1]; + } + +// +// assume decimal +// + decimal = -1; + total = 0; + while (1) + { + c = *str++; + if (c == '.') + { + decimal = total; + continue; + } + if (c <'0' || c > '9') + break; + val = val*10 + c - '0'; + total++; + } + + if (decimal == -1) + return val*sign; + while (total > decimal) + { + val /= 10; + total--; + } + + return val*sign; +} + +//----------------------------------------------------------------------------- +// Normalizes a float string in place. +// +// (removes leading zeros, trailing zeros after the decimal point, and the decimal point itself where possible) +//----------------------------------------------------------------------------- +void V_normalizeFloatString( char* pFloat ) +{ + // If we have a decimal point, remove trailing zeroes: + if( strchr( pFloat,'.' ) ) + { + int len = V_strlen(pFloat); + + while( len > 1 && pFloat[len - 1] == '0' ) + { + pFloat[len - 1] = '\0'; + len--; + } + + if( len > 1 && pFloat[ len - 1 ] == '.' ) + { + pFloat[len - 1] = '\0'; + len--; + } + } + + // TODO: Strip leading zeros + +} + + +//----------------------------------------------------------------------------- +// Finds a string in another string with a case insensitive test +//----------------------------------------------------------------------------- +char const* V_stristr( char const* pStr, char const* pSearch ) +{ + AssertValidStringPtr(pStr); + AssertValidStringPtr(pSearch); + + if (!pStr || !pSearch) + return 0; + + char const* pLetter = pStr; + + // Check the entire string + while (*pLetter != 0) + { + // Skip over non-matches + if (tolower((unsigned char)*pLetter) == tolower((unsigned char)*pSearch)) + { + // Check for match + char const* pMatch = pLetter + 1; + char const* pTest = pSearch + 1; + while (*pTest != 0) + { + // We've run off the end; don't bother. + if (*pMatch == 0) + return 0; + + if (tolower((unsigned char)*pMatch) != tolower((unsigned char)*pTest)) + break; + + ++pMatch; + ++pTest; + } + + // Found a match! + if (*pTest == 0) + return pLetter; + } + + ++pLetter; + } + + return 0; +} + +char* V_stristr( char* pStr, char const* pSearch ) +{ + AssertValidStringPtr( pStr ); + AssertValidStringPtr( pSearch ); + + return (char*)V_stristr( (char const*)pStr, pSearch ); +} + +//----------------------------------------------------------------------------- +// Finds a string in another string with a case insensitive test w/ length validation +//----------------------------------------------------------------------------- +char const* V_strnistr( char const* pStr, char const* pSearch, int n ) +{ + AssertValidStringPtr(pStr); + AssertValidStringPtr(pSearch); + + if (!pStr || !pSearch) + return 0; + + char const* pLetter = pStr; + + // Check the entire string + while (*pLetter != 0) + { + if ( n <= 0 ) + return 0; + + // Skip over non-matches + if (tolower(*pLetter) == tolower(*pSearch)) + { + int n1 = n - 1; + + // Check for match + char const* pMatch = pLetter + 1; + char const* pTest = pSearch + 1; + while (*pTest != 0) + { + if ( n1 <= 0 ) + return 0; + + // We've run off the end; don't bother. + if (*pMatch == 0) + return 0; + + if (tolower(*pMatch) != tolower(*pTest)) + break; + + ++pMatch; + ++pTest; + --n1; + } + + // Found a match! + if (*pTest == 0) + return pLetter; + } + + ++pLetter; + --n; + } + + return 0; +} + +const char* V_strnchr( const char* pStr, char c, int n ) +{ + char const* pLetter = pStr; + char const* pLast = pStr + n; + + // Check the entire string + while ( (pLetter < pLast) && (*pLetter != 0) ) + { + if (*pLetter == c) + return pLetter; + ++pLetter; + } + return NULL; +} + +void V_strncpy( char *pDest, char const *pSrc, int maxLen ) +{ + Assert( maxLen >= 0 ); + AssertValidWritePtr( pDest, maxLen ); + AssertValidStringPtr( pSrc ); + + strncpy( pDest, pSrc, maxLen ); + if ( maxLen > 0 ) + { + pDest[maxLen-1] = 0; + } +} + +void V_wcsncpy( wchar_t *pDest, wchar_t const *pSrc, int maxLenInBytes ) +{ + Assert( maxLenInBytes >= 0 ); + AssertValidWritePtr( pDest, maxLenInBytes ); + AssertValidReadPtr( pSrc ); + + int maxLen = maxLenInBytes / sizeof(wchar_t); + + wcsncpy( pDest, pSrc, maxLen ); + if( maxLen ) + { + pDest[maxLen-1] = 0; + } +} + + + +int V_snwprintf( wchar_t *pDest, int maxLen, const wchar_t *pFormat, ... ) +{ + Assert( maxLen >= 0 ); + AssertValidWritePtr( pDest, maxLen ); + AssertValidReadPtr( pFormat ); + + va_list marker; + + va_start( marker, pFormat ); +#ifdef _WIN32 + int len = _snwprintf( pDest, maxLen, pFormat, marker ); +#elif defined _LINUX || defined __APPLE__ + int len = swprintf( pDest, maxLen, pFormat, marker ); +#else +#error "define vsnwprintf type." +#endif + va_end( marker ); + + // Len < 0 represents an overflow + if( len < 0 ) + { + len = maxLen; + pDest[maxLen-1] = 0; + } + + return len; +} + + +int V_snprintf( char *pDest, int maxLen, char const *pFormat, ... ) +{ + Assert( maxLen >= 0 ); + AssertValidWritePtr( pDest, maxLen ); + AssertValidStringPtr( pFormat ); + + va_list marker; + + va_start( marker, pFormat ); +#ifdef _WIN32 + int len = _vsnprintf( pDest, maxLen, pFormat, marker ); +#elif defined _LINUX || defined __APPLE__ + int len = vsnprintf( pDest, maxLen, pFormat, marker ); +#else + #error "define vsnprintf type." +#endif + va_end( marker ); + + // Len < 0 represents an overflow + if( len < 0 ) + { + len = maxLen; + pDest[maxLen-1] = 0; + } + + return len; +} + + +int V_vsnprintf( char *pDest, int maxLen, char const *pFormat, va_list params ) +{ + Assert( maxLen > 0 ); + AssertValidWritePtr( pDest, maxLen ); + AssertValidStringPtr( pFormat ); + + int len = _vsnprintf( pDest, maxLen, pFormat, params ); + + if( len < 0 ) + { + len = maxLen; + pDest[maxLen-1] = 0; + } + + return len; +} + + + +//----------------------------------------------------------------------------- +// Purpose: If COPY_ALL_CHARACTERS == max_chars_to_copy then we try to add the whole pSrc to the end of pDest, otherwise +// we copy only as many characters as are specified in max_chars_to_copy (or the # of characters in pSrc if thats's less). +// Input : *pDest - destination buffer +// *pSrc - string to append +// destBufferSize - sizeof the buffer pointed to by pDest +// max_chars_to_copy - COPY_ALL_CHARACTERS in pSrc or max # to copy +// Output : char * the copied buffer +//----------------------------------------------------------------------------- +char *V_strncat(char *pDest, const char *pSrc, size_t destBufferSize, int max_chars_to_copy ) +{ + size_t charstocopy = (size_t)0; + + Assert( destBufferSize >= 0 ); + AssertValidStringPtr( pDest); + AssertValidStringPtr( pSrc ); + + size_t len = strlen(pDest); + size_t srclen = strlen( pSrc ); + if ( max_chars_to_copy <= COPY_ALL_CHARACTERS ) + { + charstocopy = srclen; + } + else + { + charstocopy = (size_t)MIN( max_chars_to_copy, (int)srclen ); + } + + if ( len + charstocopy >= destBufferSize ) + { + charstocopy = destBufferSize - len - 1; + } + + if ( !charstocopy ) + { + return pDest; + } + + char *pOut = strncat( pDest, pSrc, charstocopy ); + pOut[destBufferSize-1] = 0; + return pOut; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Converts value into x.xx MB/ x.xx KB, x.xx bytes format, including commas +// Input : value - +// 2 - +// false - +// Output : char +//----------------------------------------------------------------------------- +#define NUM_PRETIFYMEM_BUFFERS 8 +char *V_pretifymem( float value, int digitsafterdecimal /*= 2*/, bool usebinaryonek /*= false*/ ) +{ + static char output[ NUM_PRETIFYMEM_BUFFERS ][ 32 ]; + static int current; + + float onekb = usebinaryonek ? 1024.0f : 1000.0f; + float onemb = onekb * onekb; + + char *out = output[ current ]; + current = ( current + 1 ) & ( NUM_PRETIFYMEM_BUFFERS -1 ); + + char suffix[ 8 ]; + + // First figure out which bin to use + if ( value > onemb ) + { + value /= onemb; + V_snprintf( suffix, sizeof( suffix ), " MB" ); + } + else if ( value > onekb ) + { + value /= onekb; + V_snprintf( suffix, sizeof( suffix ), " KB" ); + } + else + { + V_snprintf( suffix, sizeof( suffix ), " bytes" ); + } + + char val[ 32 ]; + + // Clamp to >= 0 + digitsafterdecimal = MAX( digitsafterdecimal, 0 ); + + // If it's basically integral, don't do any decimals + if ( FloatMakePositive( value - (int)value ) < 0.00001 ) + { + V_snprintf( val, sizeof( val ), "%i%s", (int)value, suffix ); + } + else + { + char fmt[ 32 ]; + + // Otherwise, create a format string for the decimals + V_snprintf( fmt, sizeof( fmt ), "%%.%if%s", digitsafterdecimal, suffix ); + V_snprintf( val, sizeof( val ), fmt, value ); + } + + // Copy from in to out + char *i = val; + char *o = out; + + // Search for decimal or if it was integral, find the space after the raw number + char *dot = strstr( i, "." ); + if ( !dot ) + { + dot = strstr( i, " " ); + } + + // Compute position of dot + int pos = dot - i; + // Don't put a comma if it's <= 3 long + pos -= 3; + + while ( *i ) + { + // If pos is still valid then insert a comma every third digit, except if we would be + // putting one in the first spot + if ( pos >= 0 && !( pos % 3 ) ) + { + // Never in first spot + if ( o != out ) + { + *o++ = ','; + } + } + + // Count down comma position + pos--; + + // Copy rest of data as normal + *o++ = *i++; + } + + // Terminate + *o = 0; + + return out; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns a string representation of an integer with commas +// separating the 1000s (ie, 37,426,421) +// Input : value - Value to convert +// Output : Pointer to a static buffer containing the output +//----------------------------------------------------------------------------- +#define NUM_PRETIFYNUM_BUFFERS 8 +char *V_pretifynum( int64 value ) +{ + static char output[ NUM_PRETIFYMEM_BUFFERS ][ 32 ]; + static int current; + + char *out = output[ current ]; + current = ( current + 1 ) & ( NUM_PRETIFYMEM_BUFFERS -1 ); + + *out = 0; + + // Render the leading -, if necessary + if ( value < 0 ) + { + char *pchRender = out + V_strlen( out ); + V_snprintf( pchRender, 32, "-" ); + value = -value; + } + + // Render quadrillions + if ( value >= 1000000000000LL ) + { + char *pchRender = out + V_strlen( out ); + V_snprintf( pchRender, 32, "%d,", value / 1000000000000LL ); + } + + // Render trillions + if ( value >= 1000000000000LL ) + { + char *pchRender = out + V_strlen( out ); + V_snprintf( pchRender, 32, "%d,", value / 1000000000000LL ); + } + + // Render billions + if ( value >= 1000000000 ) + { + char *pchRender = out + V_strlen( out ); + V_snprintf( pchRender, 32, "%d,", value / 1000000000 ); + } + + // Render millions + if ( value >= 1000000 ) + { + char *pchRender = out + V_strlen( out ); + if ( value >= 1000000000 ) + V_snprintf( pchRender, 32, "%03d,", ( value / 1000000 ) % 1000 ); + else + V_snprintf( pchRender, 32, "%d,", ( value / 1000000 ) % 1000 ); + } + + // Render thousands + if ( value >= 1000 ) + { + char *pchRender = out + V_strlen( out ); + if ( value >= 1000000 ) + V_snprintf( pchRender, 32, "%03d,", ( value / 1000 ) % 1000 ); + else + V_snprintf( pchRender, 32, "%d,", ( value / 1000 ) % 1000 ); + } + + // Render units + char *pchRender = out + V_strlen( out ); + if ( value > 1000 ) + V_snprintf( pchRender, 32, "%03d", value % 1000 ); + else + V_snprintf( pchRender, 32, "%d", value % 1000 ); + + return out; +} + + +//----------------------------------------------------------------------------- +// Purpose: Converts a UTF8 string into a unicode string +//----------------------------------------------------------------------------- +int V_UTF8ToUnicode( const char *pUTF8, wchar_t *pwchDest, int cubDestSizeInBytes ) +{ + AssertValidStringPtr(pUTF8); + AssertValidWritePtr(pwchDest); + + pwchDest[0] = 0; +#ifdef _WIN32 + int cchResult = MultiByteToWideChar( CP_UTF8, 0, pUTF8, -1, pwchDest, cubDestSizeInBytes / sizeof(wchar_t) ); +#elif defined _LINUX || defined __APPLE__ + int cchResult = mbstowcs( pwchDest, pUTF8, cubDestSizeInBytes / sizeof(wchar_t) ); +#endif + pwchDest[(cubDestSizeInBytes / sizeof(wchar_t)) - 1] = 0; + return cchResult; +} + +//----------------------------------------------------------------------------- +// Purpose: Converts a unicode string into a UTF8 (standard) string +//----------------------------------------------------------------------------- +int V_UnicodeToUTF8( const wchar_t *pUnicode, char *pUTF8, int cubDestSizeInBytes ) +{ + AssertValidStringPtr(pUTF8, cubDestSizeInBytes); + AssertValidReadPtr(pUnicode); + + pUTF8[0] = 0; +#ifdef _WIN32 + int cchResult = WideCharToMultiByte( CP_UTF8, 0, pUnicode, -1, pUTF8, cubDestSizeInBytes, NULL, NULL ); +#elif defined _LINUX || defined __APPLE__ + int cchResult = wcstombs( pUTF8, pUnicode, cubDestSizeInBytes ); +#endif + pUTF8[cubDestSizeInBytes - 1] = 0; + return cchResult; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the 4 bit nibble for a hex character +// Input : c - +// Output : unsigned char +//----------------------------------------------------------------------------- +static unsigned char V_nibble( char c ) +{ + if ( ( c >= '0' ) && + ( c <= '9' ) ) + { + return (unsigned char)(c - '0'); + } + + if ( ( c >= 'A' ) && + ( c <= 'F' ) ) + { + return (unsigned char)(c - 'A' + 0x0a); + } + + if ( ( c >= 'a' ) && + ( c <= 'f' ) ) + { + return (unsigned char)(c - 'a' + 0x0a); + } + + return '0'; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *in - +// numchars - +// *out - +// maxoutputbytes - +//----------------------------------------------------------------------------- +void V_hextobinary( char const *in, int numchars, byte *out, int maxoutputbytes ) +{ + int len = V_strlen( in ); + numchars = MIN( len, numchars ); + // Make sure it's even + numchars = ( numchars ) & ~0x1; + + // Must be an even # of input characters (two chars per output byte) + Assert( numchars >= 2 ); + + memset( out, 0x00, maxoutputbytes ); + + byte *p; + int i; + + p = out; + for ( i = 0; + ( i < numchars ) && ( ( p - out ) < maxoutputbytes ); + i+=2, p++ ) + { + *p = ( V_nibble( in[i] ) << 4 ) | V_nibble( in[i+1] ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *in - +// inputbytes - +// *out - +// outsize - +//----------------------------------------------------------------------------- +void V_binarytohex( const byte *in, int inputbytes, char *out, int outsize ) +{ + Assert( outsize >= 1 ); + char doublet[10]; + int i; + + out[0]=0; + + for ( i = 0; i < inputbytes; i++ ) + { + unsigned char c = in[i]; + V_snprintf( doublet, sizeof( doublet ), "%02x", c ); + V_strncat( out, doublet, outsize, COPY_ALL_CHARACTERS ); + } +} + +#if defined( _WIN32 ) || defined( WIN32 ) +#define PATHSEPARATOR(c) ((c) == '\\' || (c) == '/') +#else //_WIN32 +#define PATHSEPARATOR(c) ((c) == '/') +#endif //_WIN32 + + +//----------------------------------------------------------------------------- +// Purpose: Extracts the base name of a file (no path, no extension, assumes '/' or '\' as path separator) +// Input : *in - +// *out - +// maxlen - +//----------------------------------------------------------------------------- +void V_FileBase( const char *in, char *out, int maxlen ) +{ + Assert( maxlen >= 1 ); + Assert( in ); + Assert( out ); + + if ( !in || !in[ 0 ] ) + { + *out = 0; + return; + } + + int len, start, end; + + len = V_strlen( in ); + + // scan backward for '.' + end = len - 1; + while ( end&& in[end] != '.' && !PATHSEPARATOR( in[end] ) ) + { + end--; + } + + if ( in[end] != '.' ) // no '.', copy to end + { + end = len-1; + } + else + { + end--; // Found ',', copy to left of '.' + } + + // Scan backward for '/' + start = len-1; + while ( start >= 0 && !PATHSEPARATOR( in[start] ) ) + { + start--; + } + + if ( start < 0 || !PATHSEPARATOR( in[start] ) ) + { + start = 0; + } + else + { + start++; + } + + // Length of new sting + len = end - start + 1; + + int maxcopy = MIN( len + 1, maxlen ); + + // Copy partial string + V_strncpy( out, &in[start], maxcopy ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *ppath - +//----------------------------------------------------------------------------- +void V_StripTrailingSlash( char *ppath ) +{ + Assert( ppath ); + + int len = V_strlen( ppath ); + if ( len > 0 ) + { + if ( PATHSEPARATOR( ppath[ len - 1 ] ) ) + { + ppath[ len - 1 ] = 0; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *in - +// *out - +// outSize - +//----------------------------------------------------------------------------- +void V_StripExtension( const char *in, char *out, int outSize ) +{ + // Find the last dot. If it's followed by a dot or a slash, then it's part of a + // directory specifier like ../../somedir/./blah. + + // scan backward for '.' + int end = V_strlen( in ) - 1; + while ( end > 0 && in[end] != '.' && !PATHSEPARATOR( in[end] ) ) + { + --end; + } + + if (end > 0 && !PATHSEPARATOR( in[end] ) && end < outSize) + { + int nChars = MIN( end, outSize-1 ); + if ( out != in ) + { + memcpy( out, in, nChars ); + } + out[nChars] = 0; + } + else + { + // nothing found + if ( out != in ) + { + V_strncpy( out, in, outSize ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *path - +// *extension - +// pathStringLength - +//----------------------------------------------------------------------------- +void V_DefaultExtension( char *path, const char *extension, int pathStringLength ) +{ + Assert( path ); + Assert( pathStringLength >= 1 ); + Assert( extension ); + Assert( extension[0] == '.' ); + + char *src; + + // if path doesn't have a .EXT, append extension + // (extension should include the .) + src = path + V_strlen(path) - 1; + + while ( !PATHSEPARATOR( *src ) && ( src > path ) ) + { + if (*src == '.') + { + // it has an extension + return; + } + src--; + } + + // Concatenate the desired extension + V_strncat( path, extension, pathStringLength, COPY_ALL_CHARACTERS ); +} + +//----------------------------------------------------------------------------- +// Purpose: Force extension... +// Input : *path - +// *extension - +// pathStringLength - +//----------------------------------------------------------------------------- +void V_SetExtension( char *path, const char *extension, int pathStringLength ) +{ + V_StripExtension( path, path, pathStringLength ); + V_DefaultExtension( path, extension, pathStringLength ); +} + +//----------------------------------------------------------------------------- +// Purpose: Remove final filename from string +// Input : *path - +// Output : void V_StripFilename +//----------------------------------------------------------------------------- +void V_StripFilename (char *path) +{ + int length; + + length = V_strlen( path )-1; + if ( length <= 0 ) + return; + + while ( length > 0 && + !PATHSEPARATOR( path[length] ) ) + { + length--; + } + + path[ length ] = 0; +} + +#ifdef _WIN32 +#define CORRECT_PATH_SEPARATOR '\\' +#define INCORRECT_PATH_SEPARATOR '/' +#elif defined _LINUX || defined __APPLE__ +#define CORRECT_PATH_SEPARATOR '/' +#define INCORRECT_PATH_SEPARATOR '\\' +#endif + +//----------------------------------------------------------------------------- +// Purpose: Changes all '/' or '\' characters into separator +// Input : *pname - +// separator - +//----------------------------------------------------------------------------- +void V_FixSlashes( char *pname, char separator /* = CORRECT_PATH_SEPARATOR */ ) +{ + while ( *pname ) + { + if ( *pname == INCORRECT_PATH_SEPARATOR || *pname == CORRECT_PATH_SEPARATOR ) + { + *pname = separator; + } + pname++; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: This function fixes cases of filenames like materials\\blah.vmt or somepath\otherpath\\ and removes the extra double slash. +//----------------------------------------------------------------------------- +void V_FixDoubleSlashes( char *pStr ) +{ + int len = V_strlen( pStr ); + + for ( int i=1; i < len-1; i++ ) + { + if ( (pStr[i] == '/' || pStr[i] == '\\') && (pStr[i+1] == '/' || pStr[i+1] == '\\') ) + { + // This means there's a double slash somewhere past the start of the filename. That + // can happen in Hammer if they use a material in the root directory. You'll get a filename + // that looks like 'materials\\blah.vmt' + V_memmove( &pStr[i], &pStr[i+1], len - i ); + --len; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Strip off the last directory from dirName +// Input : *dirName - +// maxlen - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool V_StripLastDir( char *dirName, int maxlen ) +{ + if( dirName[0] == 0 || + !V_stricmp( dirName, "./" ) || + !V_stricmp( dirName, ".\\" ) ) + return false; + + int len = V_strlen( dirName ); + + Assert( len < maxlen ); + + // skip trailing slash + if ( PATHSEPARATOR( dirName[len-1] ) ) + { + len--; + } + + while ( len > 0 ) + { + if ( PATHSEPARATOR( dirName[len-1] ) ) + { + dirName[len] = 0; + V_FixSlashes( dirName, CORRECT_PATH_SEPARATOR ); + return true; + } + len--; + } + + // Allow it to return an empty string and true. This can happen if something like "tf2/" is passed in. + // The correct behavior is to strip off the last directory ("tf2") and return true. + if( len == 0 ) + { + V_snprintf( dirName, maxlen, ".%c", CORRECT_PATH_SEPARATOR ); + return true; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns a pointer to the beginning of the unqualified file name +// (no path information) +// Input: in - file name (may be unqualified, relative or absolute path) +// Output: pointer to unqualified file name +//----------------------------------------------------------------------------- +const char * V_UnqualifiedFileName( const char * in ) +{ + // back up until the character after the first path separator we find, + // or the beginning of the string + const char * out = in + strlen( in ) - 1; + while ( ( out > in ) && ( !PATHSEPARATOR( *( out-1 ) ) ) ) + out--; + return out; +} + + +//----------------------------------------------------------------------------- +// Purpose: Composes a path and filename together, inserting a path separator +// if need be +// Input: path - path to use +// filename - filename to use +// dest - buffer to compose result in +// destSize - size of destination buffer +//----------------------------------------------------------------------------- +void V_ComposeFileName( const char *path, const char *filename, char *dest, int destSize ) +{ + V_strncpy( dest, path, destSize ); + V_AppendSlash( dest, destSize ); + V_strncat( dest, filename, destSize, COPY_ALL_CHARACTERS ); + V_FixSlashes( dest ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *path - +// *dest - +// destSize - +// Output : void V_ExtractFilePath +//----------------------------------------------------------------------------- +bool V_ExtractFilePath (const char *path, char *dest, int destSize ) +{ + Assert( destSize >= 1 ); + if ( destSize < 1 ) + { + return false; + } + + // Last char + int len = V_strlen(path); + const char *src = path + (len ? len-1 : 0); + + // back up until a \ or the start + while ( src != path && !PATHSEPARATOR( *(src-1) ) ) + { + src--; + } + + int copysize = MIN( src - path, destSize - 1 ); + memcpy( dest, path, copysize ); + dest[copysize] = 0; + + return copysize != 0 ? true : false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *path - +// *dest - +// destSize - +// Output : void V_ExtractFileExtension +//----------------------------------------------------------------------------- +void V_ExtractFileExtension( const char *path, char *dest, int destSize ) +{ + *dest = '\0'; + const char * extension = V_GetFileExtension( path ); + if ( NULL != extension ) + V_strncpy( dest, extension, destSize ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns a pointer to the file extension within a file name string +// Input: in - file name +// Output: pointer to beginning of extension (after the "."), or NULL +// if there is no extension +//----------------------------------------------------------------------------- +const char * V_GetFileExtension( const char * path ) +{ + const char *src; + + src = path + strlen(path) - 1; + +// +// back up until a . or the start +// + while (src != path && *(src-1) != '.' ) + src--; + + // check to see if the '.' is part of a pathname + if (src == path || PATHSEPARATOR( *src ) ) + { + return NULL; // no extension + } + + return src; +} + +bool V_RemoveDotSlashes( char *pFilename, char separator ) +{ + // Remove '//' or '\\' + char *pIn = pFilename; + char *pOut = pFilename; + bool bPrevPathSep = false; + while ( *pIn ) + { + bool bIsPathSep = PATHSEPARATOR( *pIn ); + if ( !bIsPathSep || !bPrevPathSep ) + { + *pOut++ = *pIn; + } + bPrevPathSep = bIsPathSep; + ++pIn; + } + *pOut = 0; + + // Get rid of "./"'s + pIn = pFilename; + pOut = pFilename; + while ( *pIn ) + { + // The logic on the second line is preventing it from screwing up "../" + if ( pIn[0] == '.' && PATHSEPARATOR( pIn[1] ) && + (pIn == pFilename || pIn[-1] != '.') ) + { + pIn += 2; + } + else + { + *pOut = *pIn; + ++pIn; + ++pOut; + } + } + *pOut = 0; + + // Get rid of a trailing "/." (needless). + int len = strlen( pFilename ); + if ( len > 2 && pFilename[len-1] == '.' && PATHSEPARATOR( pFilename[len-2] ) ) + { + pFilename[len-2] = 0; + } + + // Each time we encounter a "..", back up until we've read the previous directory name, + // then get rid of it. + pIn = pFilename; + while ( *pIn ) + { + if ( pIn[0] == '.' && + pIn[1] == '.' && + (pIn == pFilename || PATHSEPARATOR(pIn[-1])) && // Preceding character must be a slash. + (pIn[2] == 0 || PATHSEPARATOR(pIn[2])) ) // Following character must be a slash or the end of the string. + { + char *pEndOfDots = pIn + 2; + char *pStart = pIn - 2; + + // Ok, now scan back for the path separator that starts the preceding directory. + while ( 1 ) + { + if ( pStart < pFilename ) + return false; + + if ( PATHSEPARATOR( *pStart ) ) + break; + + --pStart; + } + + // Now slide the string down to get rid of the previous directory and the ".." + memmove( pStart, pEndOfDots, strlen( pEndOfDots ) + 1 ); + + // Start over. + pIn = pFilename; + } + else + { + ++pIn; + } + } + + V_FixSlashes( pFilename, separator ); + return true; +} + + +void V_AppendSlash( char *pStr, int strSize ) +{ + int len = V_strlen( pStr ); + if ( len > 0 && !PATHSEPARATOR(pStr[len-1]) ) + { + if ( len+1 >= strSize ) + Error( "V_AppendSlash: ran out of space on %s.", pStr ); + + pStr[len] = CORRECT_PATH_SEPARATOR; + pStr[len+1] = 0; + } +} + + +void V_MakeAbsolutePath( char *pOut, int outLen, const char *pPath, const char *pStartingDir ) +{ + if ( V_IsAbsolutePath( pPath ) ) + { + // pPath is not relative.. just copy it. + V_strncpy( pOut, pPath, outLen ); + } + else + { + // Make sure the starting directory is absolute.. + if ( pStartingDir && V_IsAbsolutePath( pStartingDir ) ) + { + V_strncpy( pOut, pStartingDir, outLen ); + } + else + { + if ( !_getcwd( pOut, outLen ) ) + Error( "V_MakeAbsolutePath: _getcwd failed." ); + + if ( pStartingDir ) + { + V_AppendSlash( pOut, outLen ); + V_strncat( pOut, pStartingDir, outLen, COPY_ALL_CHARACTERS ); + } + } + + // Concatenate the paths. + V_AppendSlash( pOut, outLen ); + V_strncat( pOut, pPath, outLen, COPY_ALL_CHARACTERS ); + } + + if ( !V_RemoveDotSlashes( pOut ) ) + Error( "V_MakeAbsolutePath: tried to \"..\" past the root." ); + + V_FixSlashes( pOut ); +} + + +//----------------------------------------------------------------------------- +// Makes a relative path +//----------------------------------------------------------------------------- +bool V_MakeRelativePath( const char *pFullPath, const char *pDirectory, char *pRelativePath, int nBufLen ) +{ + pRelativePath[0] = 0; + + const char *pPath = pFullPath; + const char *pDir = pDirectory; + + // Strip out common parts of the path + const char *pLastCommonPath = NULL; + const char *pLastCommonDir = NULL; + while ( *pPath && ( tolower( *pPath ) == tolower( *pDir ) || + ( PATHSEPARATOR( *pPath ) && ( PATHSEPARATOR( *pDir ) || (*pDir == 0) ) ) ) ) + { + if ( PATHSEPARATOR( *pPath ) ) + { + pLastCommonPath = pPath + 1; + pLastCommonDir = pDir + 1; + } + if ( *pDir == 0 ) + { + --pLastCommonDir; + break; + } + ++pDir; ++pPath; + } + + // Nothing in common + if ( !pLastCommonPath ) + return false; + + // For each path separator remaining in the dir, need a ../ + int nOutLen = 0; + bool bLastCharWasSeparator = true; + for ( ; *pLastCommonDir; ++pLastCommonDir ) + { + if ( PATHSEPARATOR( *pLastCommonDir ) ) + { + pRelativePath[nOutLen++] = '.'; + pRelativePath[nOutLen++] = '.'; + pRelativePath[nOutLen++] = CORRECT_PATH_SEPARATOR; + bLastCharWasSeparator = true; + } + else + { + bLastCharWasSeparator = false; + } + } + + // Deal with relative paths not specified with a trailing slash + if ( !bLastCharWasSeparator ) + { + pRelativePath[nOutLen++] = '.'; + pRelativePath[nOutLen++] = '.'; + pRelativePath[nOutLen++] = CORRECT_PATH_SEPARATOR; + } + + // Copy the remaining part of the relative path over, fixing the path separators + for ( ; *pLastCommonPath; ++pLastCommonPath ) + { + if ( PATHSEPARATOR( *pLastCommonPath ) ) + { + pRelativePath[nOutLen++] = CORRECT_PATH_SEPARATOR; + } + else + { + pRelativePath[nOutLen++] = *pLastCommonPath; + } + + // Check for overflow + if ( nOutLen == nBufLen - 1 ) + break; + } + + pRelativePath[nOutLen] = 0; + return true; +} + + +//----------------------------------------------------------------------------- +// small helper function shared by lots of modules +//----------------------------------------------------------------------------- +bool V_IsAbsolutePath( const char *pStr ) +{ + bool bIsAbsolute = ( pStr[0] && pStr[1] == ':' ) || pStr[0] == '/' || pStr[0] == '\\'; + if ( IsX360() && !bIsAbsolute ) + { + bIsAbsolute = ( V_stristr( pStr, ":" ) != NULL ); + } + return bIsAbsolute; +} + + +// Copies at most nCharsToCopy bytes from pIn into pOut. +// Returns false if it would have overflowed pOut's buffer. +static bool CopyToMaxChars( char *pOut, int outSize, const char *pIn, int nCharsToCopy ) +{ + if ( outSize == 0 ) + return false; + + int iOut = 0; + while ( *pIn && nCharsToCopy > 0 ) + { + if ( iOut == (outSize-1) ) + { + pOut[iOut] = 0; + return false; + } + pOut[iOut] = *pIn; + ++iOut; + ++pIn; + --nCharsToCopy; + } + + pOut[iOut] = 0; + return true; +} + + +//----------------------------------------------------------------------------- +// Fixes up a file name, removing dot slashes, fixing slashes, converting to lowercase, etc. +//----------------------------------------------------------------------------- +void V_FixupPathName( char *pOut, size_t nOutLen, const char *pPath ) +{ + V_strncpy( pOut, pPath, nOutLen ); + V_FixSlashes( pOut ); + V_RemoveDotSlashes( pOut ); + V_FixDoubleSlashes( pOut ); + V_strlower( pOut ); +} + + +// Returns true if it completed successfully. +// If it would overflow pOut, it fills as much as it can and returns false. +bool V_StrSubst( + const char *pIn, + const char *pMatch, + const char *pReplaceWith, + char *pOut, + int outLen, + bool bCaseSensitive + ) +{ + int replaceFromLen = strlen( pMatch ); + int replaceToLen = strlen( pReplaceWith ); + + const char *pInStart = pIn; + char *pOutPos = pOut; + pOutPos[0] = 0; + + while ( 1 ) + { + int nRemainingOut = outLen - (pOutPos - pOut); + + const char *pTestPos = ( bCaseSensitive ? strstr( pInStart, pMatch ) : V_stristr( pInStart, pMatch ) ); + if ( pTestPos ) + { + // Found an occurence of pMatch. First, copy whatever leads up to the string. + int copyLen = pTestPos - pInStart; + if ( !CopyToMaxChars( pOutPos, nRemainingOut, pInStart, copyLen ) ) + return false; + + // Did we hit the end of the output string? + if ( copyLen > nRemainingOut-1 ) + return false; + + pOutPos += strlen( pOutPos ); + nRemainingOut = outLen - (pOutPos - pOut); + + // Now add the replacement string. + if ( !CopyToMaxChars( pOutPos, nRemainingOut, pReplaceWith, replaceToLen ) ) + return false; + + pInStart += copyLen + replaceFromLen; + pOutPos += replaceToLen; + } + else + { + // We're at the end of pIn. Copy whatever remains and get out. + int copyLen = strlen( pInStart ); + V_strncpy( pOutPos, pInStart, nRemainingOut ); + return ( copyLen <= nRemainingOut-1 ); + } + } +} + + +char* AllocString( const char *pStr, int nMaxChars ) +{ + int allocLen; + if ( nMaxChars == -1 ) + allocLen = strlen( pStr ) + 1; + else + allocLen = MIN( (int)strlen(pStr), nMaxChars ) + 1; + + char *pOut = new char[allocLen]; + V_strncpy( pOut, pStr, allocLen ); + return pOut; +} + + +void V_SplitString2( const char *pString, const char **pSeparators, int nSeparators, CUtlVector &outStrings ) +{ + outStrings.Purge(); + const char *pCurPos = pString; + while ( 1 ) + { + int iFirstSeparator = -1; + const char *pFirstSeparator = 0; + for ( int i=0; i < nSeparators; i++ ) + { + const char *pTest = V_stristr( pCurPos, pSeparators[i] ); + if ( pTest && (!pFirstSeparator || pTest < pFirstSeparator) ) + { + iFirstSeparator = i; + pFirstSeparator = pTest; + } + } + + if ( pFirstSeparator ) + { + // Split on this separator and continue on. + int separatorLen = strlen( pSeparators[iFirstSeparator] ); + if ( pFirstSeparator > pCurPos ) + { + outStrings.AddToTail( AllocString( pCurPos, pFirstSeparator-pCurPos ) ); + } + + pCurPos = pFirstSeparator + separatorLen; + } + else + { + // Copy the rest of the string + if ( strlen( pCurPos ) ) + { + outStrings.AddToTail( AllocString( pCurPos, -1 ) ); + } + return; + } + } +} + + +void V_SplitString( const char *pString, const char *pSeparator, CUtlVector &outStrings ) +{ + V_SplitString2( pString, &pSeparator, 1, outStrings ); +} + + +bool V_GetCurrentDirectory( char *pOut, int maxLen ) +{ +#if defined _LINUX || defined __APPLE__ + return getcwd( pOut, maxLen ) == pOut; +#else + return _getcwd( pOut, maxLen ) == pOut; +#endif +} + + +bool V_SetCurrentDirectory( const char *pDirName ) +{ +#if defined _LINUX || defined __APPLE__ + return chdir( pDirName ) == 0; +#else + return _chdir( pDirName ) == 0; +#endif +} + + +// This function takes a slice out of pStr and stores it in pOut. +// It follows the Python slice convention: +// Negative numbers wrap around the string (-1 references the last character). +// Numbers are clamped to the end of the string. +void V_StrSlice( const char *pStr, int firstChar, int lastCharNonInclusive, char *pOut, int outSize ) +{ + if ( outSize == 0 ) + return; + + int length = strlen( pStr ); + + // Fixup the string indices. + if ( firstChar < 0 ) + { + firstChar = length - (-firstChar % length); + } + else if ( firstChar >= length ) + { + pOut[0] = 0; + return; + } + + if ( lastCharNonInclusive < 0 ) + { + lastCharNonInclusive = length - (-lastCharNonInclusive % length); + } + else if ( lastCharNonInclusive > length ) + { + lastCharNonInclusive %= length; + } + + if ( lastCharNonInclusive <= firstChar ) + { + pOut[0] = 0; + return; + } + + int copyLen = lastCharNonInclusive - firstChar; + if ( copyLen <= (outSize-1) ) + { + memcpy( pOut, &pStr[firstChar], copyLen ); + pOut[copyLen] = 0; + } + else + { + memcpy( pOut, &pStr[firstChar], outSize-1 ); + pOut[outSize-1] = 0; + } +} + + +void V_StrLeft( const char *pStr, int nChars, char *pOut, int outSize ) +{ + if ( nChars == 0 ) + { + if ( outSize != 0 ) + pOut[0] = 0; + + return; + } + + V_StrSlice( pStr, 0, nChars, pOut, outSize ); +} + + +void V_StrRight( const char *pStr, int nChars, char *pOut, int outSize ) +{ + int len = strlen( pStr ); + if ( nChars >= len ) + { + V_strncpy( pOut, pStr, outSize ); + } + else + { + V_StrSlice( pStr, -nChars, strlen( pStr ), pOut, outSize ); + } +} + +//----------------------------------------------------------------------------- +// Convert multibyte to wchar + back +//----------------------------------------------------------------------------- +void V_strtowcs( const char *pString, int nInSize, wchar_t *pWString, int nOutSize ) +{ +#ifdef _WIN32 + if ( !MultiByteToWideChar( CP_UTF8, 0, pString, nInSize, pWString, nOutSize ) ) + { + *pWString = L'\0'; + } +#elif defined _LINUX || defined __APPLE__ + if ( mbstowcs( pWString, pString, nOutSize / sizeof(wchar_t) ) <= 0 ) + { + *pWString = 0; + } +#endif +} + +void V_wcstostr( const wchar_t *pWString, int nInSize, char *pString, int nOutSize ) +{ +#ifdef _WIN32 + if ( !WideCharToMultiByte( CP_UTF8, 0, pWString, nInSize, pString, nOutSize, NULL, NULL ) ) + { + *pString = '\0'; + } +#elif defined _LINUX || defined __APPLE__ + if ( wcstombs( pString, pWString, nOutSize ) <= 0 ) + { + *pString = '\0'; + } +#endif +} + + + +//-------------------------------------------------------------------------------- +// backslashification +//-------------------------------------------------------------------------------- + +static char s_BackSlashMap[]="\tt\nn\rr\"\"\\\\"; + +char *V_AddBackSlashesToSpecialChars( char const *pSrc ) +{ + // first, count how much space we are going to need + int nSpaceNeeded = 0; + for( char const *pScan = pSrc; *pScan; pScan++ ) + { + nSpaceNeeded++; + for(char const *pCharSet=s_BackSlashMap; *pCharSet; pCharSet += 2 ) + { + if ( *pCharSet == *pScan ) + nSpaceNeeded++; // we need to store a bakslash + } + } + char *pRet = new char[ nSpaceNeeded + 1 ]; // +1 for null + char *pOut = pRet; + + for( char const *pScan = pSrc; *pScan; pScan++ ) + { + bool bIsSpecial = false; + for(char const *pCharSet=s_BackSlashMap; *pCharSet; pCharSet += 2 ) + { + if ( *pCharSet == *pScan ) + { + *( pOut++ ) = '\\'; + *( pOut++ ) = pCharSet[1]; + bIsSpecial = true; + break; + } + } + if (! bIsSpecial ) + { + *( pOut++ ) = *pScan; + } + } + *( pOut++ ) = 0; + return pRet; +} diff --git a/tier1/tier1-2005.vcproj b/tier1/tier1-2005.vcproj new file mode 100644 index 00000000..34cff4df --- /dev/null +++ b/tier1/tier1-2005.vcproj @@ -0,0 +1,568 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tier1/tier1.cpp b/tier1/tier1.cpp new file mode 100644 index 00000000..7a20917d --- /dev/null +++ b/tier1/tier1.cpp @@ -0,0 +1,63 @@ +//===== Copyright © 2005-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: A higher level link library for general use in the game and tools. +// +//===========================================================================// + +#include +#include "tier0/dbg.h" +#include "vstdlib/iprocessutils.h" +#include "icvar.h" + + +//----------------------------------------------------------------------------- +// These tier1 libraries must be set by any users of this library. +// They can be set by calling ConnectTier1Libraries or InitDefaultFileSystem. +// It is hoped that setting this, and using this library will be the common mechanism for +// allowing link libraries to access tier1 library interfaces +//----------------------------------------------------------------------------- +ICvar *cvar = 0; +ICvar *g_pCVar = 0; +IProcessUtils *g_pProcessUtils = 0; +static bool s_bConnected = false; + +// for utlsortvector.h +#ifndef _WIN32 + void *g_pUtlSortVectorQSortContext = NULL; +#endif + + +//----------------------------------------------------------------------------- +// Call this to connect to all tier 1 libraries. +// It's up to the caller to check the globals it cares about to see if ones are missing +//----------------------------------------------------------------------------- +void ConnectTier1Libraries( CreateInterfaceFn *pFactoryList, int nFactoryCount ) +{ + // Don't connect twice.. + if ( s_bConnected ) + return; + + s_bConnected = true; + + for ( int i = 0; i < nFactoryCount; ++i ) + { + if ( !g_pCVar ) + { + cvar = g_pCVar = ( ICvar * )pFactoryList[i]( CVAR_INTERFACE_VERSION, NULL ); + } + if ( !g_pProcessUtils ) + { + g_pProcessUtils = ( IProcessUtils * )pFactoryList[i]( PROCESS_UTILS_INTERFACE_VERSION, NULL ); + } + } +} + +void DisconnectTier1Libraries() +{ + if ( !s_bConnected ) + return; + + g_pCVar = cvar = 0; + g_pProcessUtils = NULL; + s_bConnected = false; +} diff --git a/tier1/tokenreader.cpp b/tier1/tokenreader.cpp new file mode 100644 index 00000000..46d9547c --- /dev/null +++ b/tier1/tokenreader.cpp @@ -0,0 +1,480 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#include +#include +#include +#include "tokenreader.h" +#include "tier0/platform.h" +#include "tier1/strtools.h" +#include "tier0/dbg.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +TokenReader::TokenReader(void) +{ + m_szFilename[0] = '\0'; + m_nLine = 1; + m_nErrorCount = 0; + m_bStuffed = false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pszFilename - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool TokenReader::Open(const char *pszFilename) +{ + open(pszFilename, std::ios::in | std::ios::binary ); + Q_strncpy(m_szFilename, pszFilename, sizeof( m_szFilename ) ); + m_nLine = 1; + m_nErrorCount = 0; + m_bStuffed = false; + return(is_open() != 0); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TokenReader::Close() +{ + close(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *error - +// Output : const char +//----------------------------------------------------------------------------- +const char *TokenReader::Error(char *error, ...) +{ + static char szErrorBuf[256]; + Q_snprintf(szErrorBuf, sizeof( szErrorBuf ), "File %s, line %d: ", m_szFilename, m_nLine); + Q_strncat(szErrorBuf, error, sizeof( szErrorBuf ), COPY_ALL_CHARACTERS ); + m_nErrorCount++; + return(szErrorBuf); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pszStore - +// nSize - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +trtoken_t TokenReader::GetString(char *pszStore, int nSize) +{ + if (nSize <= 0) + { + return TOKENERROR; + } + + char szBuf[1024]; + + // + // Until we reach the end of this string or run out of room in + // the destination buffer... + // + while (true) + { + // + // Fetch the next batch of text from the file. + // + get(szBuf, sizeof(szBuf), '\"'); + if (eof()) + { + return TOKENEOF; + } + + if (fail()) + { + // Just means nothing was read (empty string probably "") + clear(); + } + + // + // Transfer the text to the destination buffer. + // + char *pszSrc = szBuf; + while ((*pszSrc != '\0') && (nSize > 1)) + { + if (*pszSrc == 0x0d) + { + // + // Newline encountered before closing quote -- unterminated string. + // + *pszStore = '\0'; + return TOKENSTRINGTOOLONG; + } + else if (*pszSrc != '\\') + { + *pszStore = *pszSrc; + pszSrc++; + } + else + { + // + // Backslash sequence - replace with the appropriate character. + // + pszSrc++; + + if (*pszSrc == 'n') + { + *pszStore = '\n'; + } + + pszSrc++; + } + + pszStore++; + nSize--; + } + + if (*pszSrc != '\0') + { + // + // Ran out of room in the destination buffer. Skip to the close-quote, + // terminate the string, and exit. + // + ignore(1024, '\"'); + *pszStore = '\0'; + return TOKENSTRINGTOOLONG; + } + + // + // Check for closing quote. + // + if (peek() == '\"') + { + // + // Eat the close quote and any whitespace. + // + get(); + + bool bCombineStrings = SkipWhiteSpace(); + + // + // Combine consecutive quoted strings if the combine strings character was + // encountered between the two strings. + // + if (bCombineStrings && (peek() == '\"')) + { + // + // Eat the open quote and keep parsing this string. + // + get(); + } + else + { + // + // Done with this string, terminate the string and exit. + // + *pszStore = '\0'; + return STRING; + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns the next token, allocating enough memory to store the token +// plus a terminating NULL. +// Input : pszStore - Pointer to a string that will be allocated. +// Output : Returns the type of token that was read, or TOKENERROR. +//----------------------------------------------------------------------------- +trtoken_t TokenReader::NextTokenDynamic(char **ppszStore) +{ + char szTempBuffer[8192]; + trtoken_t eType = NextToken(szTempBuffer, sizeof(szTempBuffer)); + + int len = Q_strlen(szTempBuffer) + 1; + *ppszStore = new char [len]; + Assert( *ppszStore ); + Q_strncpy(*ppszStore, szTempBuffer, len ); + + return(eType); +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns the next token. +// Input : pszStore - Pointer to a string that will receive the token. +// Output : Returns the type of token that was read, or TOKENERROR. +//----------------------------------------------------------------------------- +trtoken_t TokenReader::NextToken(char *pszStore, int nSize) +{ + char *pStart = pszStore; + + if (!is_open()) + { + return TOKENEOF; + } + + // + // If they stuffed a token, return that token. + // + if (m_bStuffed) + { + m_bStuffed = false; + Q_strncpy( pszStore, m_szStuffed, nSize ); + return m_eStuffed; + } + + SkipWhiteSpace(); + + if (eof()) + { + return TOKENEOF; + } + + if (fail()) + { + return TOKENEOF; + } + + char ch = get(); + + // + // Look for all the valid operators. + // + switch (ch) + { + case '@': + case ',': + case '!': + case '+': + case '&': + case '*': + case '$': + case '.': + case '=': + case ':': + case '[': + case ']': + case '(': + case ')': + case '{': + case '}': + case '\\': + { + pszStore[0] = ch; + pszStore[1] = 0; + return OPERATOR; + } + } + + // + // Look for the start of a quoted string. + // + if (ch == '\"') + { + return GetString(pszStore, nSize); + } + + // + // Integers consist of numbers with an optional leading minus sign. + // + if (isdigit(ch) || (ch == '-')) + { + do + { + if ( (pszStore - pStart + 1) < nSize ) + { + *pszStore = ch; + pszStore++; + } + + ch = get(); + if (ch == '-') + { + return TOKENERROR; + } + } while (isdigit(ch)); + + // + // No identifier characters are allowed contiguous with numbers. + // + if (isalpha(ch) || (ch == '_')) + { + return TOKENERROR; + } + + // + // Put back the non-numeric character for the next call. + // + putback(ch); + *pszStore = '\0'; + return INTEGER; + } + + // + // Identifiers consist of a consecutive string of alphanumeric + // characters and underscores. + // + while ( isalpha(ch) || isdigit(ch) || (ch == '_') ) + { + if ( (pszStore - pStart + 1) < nSize ) + { + *pszStore = ch; + pszStore++; + } + + ch = get(); + } + + // + // Put back the non-identifier character for the next call. + // + putback(ch); + *pszStore = '\0'; + return IDENT; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : ttype - +// *pszToken - +//----------------------------------------------------------------------------- +void TokenReader::IgnoreTill(trtoken_t ttype, const char *pszToken) +{ + trtoken_t _ttype; + char szBuf[1024]; + + while(1) + { + _ttype = NextToken(szBuf, sizeof(szBuf)); + if(_ttype == TOKENEOF) + return; + if(_ttype == ttype) + { + if(IsToken(pszToken, szBuf)) + { + Stuff(ttype, pszToken); + return; + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : ttype - +// pszToken - +//----------------------------------------------------------------------------- +void TokenReader::Stuff(trtoken_t eType, const char *pszToken) +{ + m_eStuffed = eType; + Q_strncpy(m_szStuffed, pszToken, sizeof( m_szStuffed ) ); + m_bStuffed = true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : ttype - +// pszToken - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool TokenReader::Expecting(trtoken_t ttype, const char *pszToken) +{ + char szBuf[1024]; + if (NextToken(szBuf, sizeof(szBuf)) != ttype || !IsToken(pszToken, szBuf)) + { + return false; + } + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pszStore - +// Output : +//----------------------------------------------------------------------------- +trtoken_t TokenReader::PeekTokenType(char *pszStore, int maxlen ) +{ + if (!m_bStuffed) + { + m_eStuffed = NextToken(m_szStuffed, sizeof(m_szStuffed)); + m_bStuffed = true; + } + + if (pszStore) + { + Q_strncpy(pszStore, m_szStuffed, maxlen ); + } + + return(m_eStuffed); +} + + +//----------------------------------------------------------------------------- +// Purpose: Gets the next non-whitespace character from the file. +// Input : ch - Receives the character. +// Output : Returns true if the whitespace contained the combine strings +// character '\', which is used to merge consecutive quoted strings. +//----------------------------------------------------------------------------- +bool TokenReader::SkipWhiteSpace(void) +{ + bool bCombineStrings = false; + + while (true) + { + char ch = get(); + + if ((ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == 0)) + { + continue; + } + + if (ch == '+') + { + bCombineStrings = true; + continue; + } + + if (ch == '\n') + { + m_nLine++; + continue; + } + + if (eof()) + { + return(bCombineStrings); + } + + // + // Check for the start of a comment. + // + if (ch == '/') + { + if (peek() == '/') + { + ignore(1024, '\n'); + m_nLine++; + } + } + else + { + // + // It is a worthy character. Put it back. + // + putback(ch); + return(bCombineStrings); + } + } +} + diff --git a/tier1/undiff.cpp b/tier1/undiff.cpp new file mode 100644 index 00000000..17505b27 --- /dev/null +++ b/tier1/undiff.cpp @@ -0,0 +1,94 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// UnDiff - Apply difference block +// +//=============================================================================// + +#include "tier0/platform.h" +#include "tier0/dbg.h" +#include "tier1/diff.h" +#include "mathlib/mathlib.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +void ApplyDiffs(uint8 const *OldBlock, uint8 const *DiffList, + int OldSize, int DiffListSize, int &ResultListSize,uint8 *Output,uint32 OutSize) +{ + uint8 const *copy_src=OldBlock; + uint8 const *end_of_diff_list=DiffList+DiffListSize; + uint8 const *obuf=Output; + while(DiffList32767) + copy_ofs|=0xffff0000; + // printf("long cp from %x to %x len=%d\n", copy_src+copy_ofs-OldBlock,Output-obuf,copy_sz); + + memcpy(Output,copy_src+copy_ofs,copy_sz); + Output+=copy_sz; + copy_src=copy_src+copy_ofs+copy_sz; + DiffList+=4; + } + else + { + if (op & 0x80) + { + int copy_sz=op & 0x7f; + int copy_ofs; + if (copy_sz==0) + { + copy_sz=DiffList[0]; + if (copy_sz==0) + { + // big raw copy + copy_sz=DiffList[1]+256*DiffList[2]+65536*DiffList[3]; + memcpy(Output,DiffList+4,copy_sz); + // printf("big rawcopy to %x len=%d\n", Output-obuf,copy_sz); + + DiffList+=copy_sz+4; + Output+=copy_sz; + } + else + { + copy_ofs=DiffList[1]+(DiffList[2]*256); + if (copy_ofs>32767) + copy_ofs|=0xffff0000; + // printf("long ofs cp from %x to %x len=%d\n", copy_src+copy_ofs-OldBlock,Output-obuf,copy_sz); + + memcpy(Output,copy_src+copy_ofs,copy_sz); + Output+=copy_sz; + copy_src=copy_src+copy_ofs+copy_sz; + DiffList+=3; + } + } + else + { + copy_ofs=DiffList[0]; + if (copy_ofs>127) + copy_ofs|=0xffffff80; + // printf("cp from %x to %x len=%d\n", copy_src+copy_ofs-OldBlock,Output-obuf,copy_sz); + + memcpy(Output,copy_src+copy_ofs,copy_sz); + Output+=copy_sz; + copy_src=copy_src+copy_ofs+copy_sz; + DiffList++; + } + } + else + { + // printf("raw copy %d to %x\n",op & 127,Output-obuf); + memcpy(Output,DiffList,op & 127); + Output+=op & 127; + DiffList+=(op & 127); + } + } + } + ResultListSize=Output-obuf; + +} diff --git a/tier1/uniqueid.cpp b/tier1/uniqueid.cpp new file mode 100644 index 00000000..3fa2661d --- /dev/null +++ b/tier1/uniqueid.cpp @@ -0,0 +1,177 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. =======// +// +// Purpose: +// +// $NoKeywords: $ +// +// Unique ID generation +//=============================================================================// + +#include "tier0/platform.h" + +#ifdef IS_WINDOWS_PC +#include // UUIDCreate +#else +#include "checksum_crc.h" +#endif +#include "tier1/uniqueid.h" +#include "tier1/utlbuffer.h" + +//----------------------------------------------------------------------------- +// Creates a new unique id +//----------------------------------------------------------------------------- +void CreateUniqueId( UniqueId_t *pDest ) +{ +#ifdef IS_WINDOWS_PC + Assert( sizeof( UUID ) == sizeof( *pDest ) ); + UuidCreate( (UUID *)pDest ); +#else + // X360/linux TBD: Need a real UUID Implementation + Q_memset( pDest, 0, sizeof( UniqueId_t ) ); +#endif +} + + +//----------------------------------------------------------------------------- +// Creates a new unique id from a string representation of one +//----------------------------------------------------------------------------- +bool UniqueIdFromString( UniqueId_t *pDest, const char *pBuf, int nMaxLen ) +{ + if ( nMaxLen == 0 ) + { + nMaxLen = Q_strlen( pBuf ); + } + + char *pTemp = (char*)stackalloc( nMaxLen + 1 ); + V_strncpy( pTemp, pBuf, nMaxLen + 1 ); + --nMaxLen; + while( (nMaxLen >= 0) && isspace( pTemp[nMaxLen] ) ) + { + --nMaxLen; + } + pTemp[ nMaxLen + 1 ] = 0; + + while( *pTemp && isspace( *pTemp ) ) + { + ++pTemp; + } + +#ifdef IS_WINDOWS_PC + Assert( sizeof( UUID ) == sizeof( *pDest ) ); + + if ( RPC_S_OK != UuidFromString( (unsigned char *)pTemp, (UUID *)pDest ) ) + { + InvalidateUniqueId( pDest ); + return false; + } +#else + // X360TBD: Need a real UUID Implementation + // For now, use crc to generate a unique ID from the UUID string. + Q_memset( pDest, 0, sizeof( UniqueId_t ) ); + if ( nMaxLen > 0 ) + { + CRC32_t crc; + CRC32_Init( &crc ); + CRC32_ProcessBuffer( &crc, pBuf, nMaxLen ); + CRC32_Final( &crc ); + Q_memcpy( pDest, &crc, sizeof( CRC32_t ) ); + } +#endif + + return true; +} + +//----------------------------------------------------------------------------- +// Sets an object ID to be an invalid state +//----------------------------------------------------------------------------- +void InvalidateUniqueId( UniqueId_t *pDest ) +{ + Assert( pDest ); + memset( pDest, 0, sizeof( UniqueId_t ) ); +} + +bool IsUniqueIdValid( const UniqueId_t &id ) +{ + UniqueId_t invalidId; + memset( &invalidId, 0, sizeof( UniqueId_t ) ); + return !IsUniqueIdEqual( invalidId, id ); +} + +bool IsUniqueIdEqual( const UniqueId_t &id1, const UniqueId_t &id2 ) +{ + return memcmp( &id1, &id2, sizeof( UniqueId_t ) ) == 0; +} + +void UniqueIdToString( const UniqueId_t &id, char *pBuf, int nMaxLen ) +{ + pBuf[ 0 ] = 0; + +// X360TBD: Need a real UUID Implementation +#ifdef IS_WINDOWS_PC + UUID *self = ( UUID * )&id; + + unsigned char *outstring = NULL; + + UuidToString( self, &outstring ); + if ( outstring && *outstring ) + { + Q_strncpy( pBuf, (const char *)outstring, nMaxLen ); + RpcStringFree( &outstring ); + } +#endif +} + +void CopyUniqueId( const UniqueId_t &src, UniqueId_t *pDest ) +{ + memcpy( pDest, &src, sizeof( UniqueId_t ) ); +} + +bool Serialize( CUtlBuffer &buf, const UniqueId_t &src ) +{ +// X360TBD: Need a real UUID Implementation +#ifdef IS_WINDOWS_PC + if ( buf.IsText() ) + { + UUID *pId = ( UUID * )&src; + + unsigned char *outstring = NULL; + + UuidToString( pId, &outstring ); + if ( outstring && *outstring ) + { + buf.PutString( (const char *)outstring ); + RpcStringFree( &outstring ); + } + else + { + buf.PutChar( '\0' ); + } + } + else + { + buf.Put( &src, sizeof(UniqueId_t) ); + } + return buf.IsValid(); +#else + return false; +#endif +} + +bool Unserialize( CUtlBuffer &buf, UniqueId_t &dest ) +{ + if ( buf.IsText() ) + { + int nTextLen = buf.PeekStringLength(); + char *pBuf = (char*)stackalloc( nTextLen ); + buf.GetString( pBuf, nTextLen ); + UniqueIdFromString( &dest, pBuf, nTextLen ); + } + else + { + buf.Get( &dest, sizeof(UniqueId_t) ); + } + return buf.IsValid(); +} + + + diff --git a/tier1/utlbuffer.cpp b/tier1/utlbuffer.cpp new file mode 100644 index 00000000..f71ec6f8 --- /dev/null +++ b/tier1/utlbuffer.cpp @@ -0,0 +1,1753 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// $Header: $ +// $NoKeywords: $ +// +// Serialization buffer +//===========================================================================// + +#ifdef _MSC_VER +#pragma warning (disable : 4514) +#endif + +#include "utlbuffer.h" +#include +#include +#include +#include +#include +#include "tier1/strtools.h" +#include "tier1/characterset.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Character conversions for C strings +//----------------------------------------------------------------------------- +class CUtlCStringConversion : public CUtlCharConversion +{ +public: + CUtlCStringConversion( char nEscapeChar, const char *pDelimiter, int nCount, ConversionArray_t *pArray ); + + // Finds a conversion for the passed-in string, returns length + virtual char FindConversion( const char *pString, int *pLength ); + +private: + char m_pConversion[255]; +}; + + +//----------------------------------------------------------------------------- +// Character conversions for no-escape sequence strings +//----------------------------------------------------------------------------- +class CUtlNoEscConversion : public CUtlCharConversion +{ +public: + CUtlNoEscConversion( char nEscapeChar, const char *pDelimiter, int nCount, ConversionArray_t *pArray ) : + CUtlCharConversion( nEscapeChar, pDelimiter, nCount, pArray ) {} + + // Finds a conversion for the passed-in string, returns length + virtual char FindConversion( const char *pString, int *pLength ) { *pLength = 0; return 0; } +}; + + +//----------------------------------------------------------------------------- +// List of character conversions +//----------------------------------------------------------------------------- +BEGIN_CUSTOM_CHAR_CONVERSION( CUtlCStringConversion, s_StringCharConversion, "\"", '\\' ) + { '\n', "n" }, + { '\t', "t" }, + { '\v', "v" }, + { '\b', "b" }, + { '\r', "r" }, + { '\f', "f" }, + { '\a', "a" }, + { '\\', "\\" }, + { '\?', "\?" }, + { '\'', "\'" }, + { '\"', "\"" }, +END_CUSTOM_CHAR_CONVERSION( CUtlCStringConversion, s_StringCharConversion, "\"", '\\' ) + +CUtlCharConversion *GetCStringCharConversion() +{ + return &s_StringCharConversion; +} + +BEGIN_CUSTOM_CHAR_CONVERSION( CUtlNoEscConversion, s_NoEscConversion, "\"", 0x7F ) + { 0x7F, "" }, +END_CUSTOM_CHAR_CONVERSION( CUtlNoEscConversion, s_NoEscConversion, "\"", 0x7F ) + +CUtlCharConversion *GetNoEscCharConversion() +{ + return &s_NoEscConversion; +} + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CUtlCStringConversion::CUtlCStringConversion( char nEscapeChar, const char *pDelimiter, int nCount, ConversionArray_t *pArray ) : + CUtlCharConversion( nEscapeChar, pDelimiter, nCount, pArray ) +{ + memset( m_pConversion, 0x0, sizeof(m_pConversion) ); + for ( int i = 0; i < nCount; ++i ) + { + m_pConversion[ static_cast(pArray[i].m_pReplacementString[0]) ] = pArray[i].m_nActualChar; + } +} + +// Finds a conversion for the passed-in string, returns length +char CUtlCStringConversion::FindConversion( const char *pString, int *pLength ) +{ + char c = m_pConversion[ static_cast(pString[0]) ]; + *pLength = (c != '\0') ? 1 : 0; + return c; +} + + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CUtlCharConversion::CUtlCharConversion( char nEscapeChar, const char *pDelimiter, int nCount, ConversionArray_t *pArray ) +{ + m_nEscapeChar = nEscapeChar; + m_pDelimiter = pDelimiter; + m_nCount = nCount; + m_nDelimiterLength = Q_strlen( pDelimiter ); + m_nMaxConversionLength = 0; + + memset( m_pReplacements, 0, sizeof(m_pReplacements) ); + + for ( int i = 0; i < nCount; ++i ) + { + m_pList[i] = pArray[i].m_nActualChar; + ConversionInfo_t &info = m_pReplacements[ static_cast(m_pList[i]) ]; + Assert( info.m_pReplacementString == 0 ); + info.m_pReplacementString = pArray[i].m_pReplacementString; + info.m_nLength = Q_strlen( info.m_pReplacementString ); + if ( info.m_nLength > m_nMaxConversionLength ) + { + m_nMaxConversionLength = info.m_nLength; + } + } +} + + +//----------------------------------------------------------------------------- +// Escape character + delimiter +//----------------------------------------------------------------------------- +char CUtlCharConversion::GetEscapeChar() const +{ + return m_nEscapeChar; +} + +const char *CUtlCharConversion::GetDelimiter() const +{ + return m_pDelimiter; +} + +int CUtlCharConversion::GetDelimiterLength() const +{ + return m_nDelimiterLength; +} + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +const char *CUtlCharConversion::GetConversionString( char c ) const +{ + return m_pReplacements[ static_cast(c) ].m_pReplacementString; +} + +int CUtlCharConversion::GetConversionLength( char c ) const +{ + return m_pReplacements[ static_cast(c) ].m_nLength; +} + +int CUtlCharConversion::MaxConversionLength() const +{ + return m_nMaxConversionLength; +} + + +//----------------------------------------------------------------------------- +// Finds a conversion for the passed-in string, returns length +//----------------------------------------------------------------------------- +char CUtlCharConversion::FindConversion( const char *pString, int *pLength ) +{ + for ( int i = 0; i < m_nCount; ++i ) + { + if ( !Q_strcmp( pString, m_pReplacements[ static_cast(m_pList[i]) ].m_pReplacementString ) ) + { + *pLength = m_pReplacements[ static_cast(m_pList[i]) ].m_nLength; + return m_pList[i]; + } + } + + *pLength = 0; + return '\0'; +} + + +//----------------------------------------------------------------------------- +// constructors +//----------------------------------------------------------------------------- +CUtlBuffer::CUtlBuffer( int growSize, int initSize, int nFlags ) : + m_Memory( growSize, initSize ), m_Error(0) +{ + m_Get = 0; + m_Put = 0; + m_nTab = 0; + m_nOffset = 0; + m_Flags = nFlags; + if ( (initSize != 0) && !IsReadOnly() ) + { + m_nMaxPut = -1; + AddNullTermination(); + } + else + { + m_nMaxPut = 0; + } + SetOverflowFuncs( &CUtlBuffer::GetOverflow, &CUtlBuffer::PutOverflow ); +} + +CUtlBuffer::CUtlBuffer( const void *pBuffer, int nSize, int nFlags ) : + m_Memory( (unsigned char*)pBuffer, nSize ), m_Error(0) +{ + Assert( nSize != 0 ); + + m_Get = 0; + m_Put = 0; + m_nTab = 0; + m_nOffset = 0; + m_Flags = nFlags; + if ( IsReadOnly() ) + { + m_nMaxPut = nSize; + } + else + { + m_nMaxPut = -1; + AddNullTermination(); + } + SetOverflowFuncs( &CUtlBuffer::GetOverflow, &CUtlBuffer::PutOverflow ); +} + + +//----------------------------------------------------------------------------- +// Modifies the buffer to be binary or text; Blows away the buffer and the CONTAINS_CRLF value. +//----------------------------------------------------------------------------- +void CUtlBuffer::SetBufferType( bool bIsText, bool bContainsCRLF ) +{ +#ifdef _DEBUG + // If the buffer is empty, there is no opportunity for this stuff to fail + if ( TellMaxPut() != 0 ) + { + if ( IsText() ) + { + if ( bIsText ) + { + Assert( ContainsCRLF() == bContainsCRLF ); + } + else + { + Assert( ContainsCRLF() ); + } + } + else + { + if ( bIsText ) + { + Assert( bContainsCRLF ); + } + } + } +#endif + + if ( bIsText ) + { + m_Flags |= TEXT_BUFFER; + } + else + { + m_Flags &= ~TEXT_BUFFER; + } + if ( bContainsCRLF ) + { + m_Flags |= CONTAINS_CRLF; + } + else + { + m_Flags &= ~CONTAINS_CRLF; + } +} + + +//----------------------------------------------------------------------------- +// Attaches the buffer to external memory.... +//----------------------------------------------------------------------------- +void CUtlBuffer::SetExternalBuffer( void* pMemory, int nSize, int nInitialPut, int nFlags ) +{ + m_Memory.SetExternalBuffer( (unsigned char*)pMemory, nSize ); + + // Reset all indices; we just changed memory + m_Get = 0; + m_Put = nInitialPut; + m_nTab = 0; + m_Error = 0; + m_nOffset = 0; + m_Flags = nFlags; + m_nMaxPut = -1; + AddNullTermination(); +} + +//----------------------------------------------------------------------------- +// Assumes an external buffer but manages its deletion +//----------------------------------------------------------------------------- +void CUtlBuffer::AssumeMemory( void *pMemory, int nSize, int nInitialPut, int nFlags ) +{ + m_Memory.AssumeMemory( (unsigned char*) pMemory, nSize ); + + // Reset all indices; we just changed memory + m_Get = 0; + m_Put = nInitialPut; + m_nTab = 0; + m_Error = 0; + m_nOffset = 0; + m_Flags = nFlags; + m_nMaxPut = -1; + AddNullTermination(); +} + +//----------------------------------------------------------------------------- +// Makes sure we've got at least this much memory +//----------------------------------------------------------------------------- +void CUtlBuffer::EnsureCapacity( int num ) +{ + // Add one extra for the null termination + num += 1; + if ( m_Memory.IsExternallyAllocated() ) + { + if ( IsGrowable() && ( m_Memory.NumAllocated() < num ) ) + { + m_Memory.ConvertToGrowableMemory( 0 ); + } + else + { + num -= 1; + } + } + + m_Memory.EnsureCapacity( num ); +} + + +//----------------------------------------------------------------------------- +// Base get method from which all others derive +//----------------------------------------------------------------------------- +void CUtlBuffer::Get( void* pMem, int size ) +{ + if ( CheckGet( size ) ) + { + memcpy( pMem, &m_Memory[m_Get - m_nOffset], size ); + m_Get += size; + } +} + + +//----------------------------------------------------------------------------- +// This will get at least 1 byte and up to nSize bytes. +// It will return the number of bytes actually read. +//----------------------------------------------------------------------------- +int CUtlBuffer::GetUpTo( void *pMem, int nSize ) +{ + if ( CheckArbitraryPeekGet( 0, nSize ) ) + { + memcpy( pMem, &m_Memory[m_Get - m_nOffset], nSize ); + m_Get += nSize; + return nSize; + } + return 0; +} + + +//----------------------------------------------------------------------------- +// Eats whitespace +//----------------------------------------------------------------------------- +void CUtlBuffer::EatWhiteSpace() +{ + if ( IsText() && IsValid() ) + { + while ( CheckGet( sizeof(char) ) ) + { + if ( !isspace( *(const unsigned char*)PeekGet() ) ) + break; + m_Get += sizeof(char); + } + } +} + + +//----------------------------------------------------------------------------- +// Eats C++ style comments +//----------------------------------------------------------------------------- +bool CUtlBuffer::EatCPPComment() +{ + if ( IsText() && IsValid() ) + { + // If we don't have a a c++ style comment next, we're done + const char *pPeek = (const char *)PeekGet( 2 * sizeof(char), 0 ); + if ( !pPeek || ( pPeek[0] != '/' ) || ( pPeek[1] != '/' ) ) + return false; + + // Deal with c++ style comments + m_Get += 2; + + // read complete line + for ( char c = GetChar(); IsValid(); c = GetChar() ) + { + if ( c == '\n' ) + break; + } + return true; + } + return false; +} + + +//----------------------------------------------------------------------------- +// Peeks how much whitespace to eat +//----------------------------------------------------------------------------- +int CUtlBuffer::PeekWhiteSpace( int nOffset ) +{ + if ( !IsText() || !IsValid() ) + return 0; + + while ( CheckPeekGet( nOffset, sizeof(char) ) ) + { + if ( !isspace( *(unsigned char*)PeekGet( nOffset ) ) ) + break; + nOffset += sizeof(char); + } + + return nOffset; +} + + +//----------------------------------------------------------------------------- +// Peek size of sting to come, check memory bound +//----------------------------------------------------------------------------- +int CUtlBuffer::PeekStringLength() +{ + if ( !IsValid() ) + return 0; + + // Eat preceeding whitespace + int nOffset = 0; + if ( IsText() ) + { + nOffset = PeekWhiteSpace( nOffset ); + } + + int nStartingOffset = nOffset; + + do + { + int nPeekAmount = 128; + + // NOTE: Add 1 for the terminating zero! + if ( !CheckArbitraryPeekGet( nOffset, nPeekAmount ) ) + { + if ( nOffset == nStartingOffset ) + return 0; + return nOffset - nStartingOffset + 1; + } + + const char *pTest = (const char *)PeekGet( nOffset ); + + if ( !IsText() ) + { + for ( int i = 0; i < nPeekAmount; ++i ) + { + // The +1 here is so we eat the terminating 0 + if ( pTest[i] == 0 ) + return (i + nOffset - nStartingOffset + 1); + } + } + else + { + for ( int i = 0; i < nPeekAmount; ++i ) + { + // The +1 here is so we eat the terminating 0 + if ( isspace((unsigned char)pTest[i]) || (pTest[i] == 0) ) + return (i + nOffset - nStartingOffset + 1); + } + } + + nOffset += nPeekAmount; + + } while ( true ); +} + + +//----------------------------------------------------------------------------- +// Peek size of line to come, check memory bound +//----------------------------------------------------------------------------- +int CUtlBuffer::PeekLineLength() +{ + if ( !IsValid() ) + return 0; + + int nOffset = 0; + int nStartingOffset = nOffset; + + do + { + int nPeekAmount = 128; + + // NOTE: Add 1 for the terminating zero! + if ( !CheckArbitraryPeekGet( nOffset, nPeekAmount ) ) + { + if ( nOffset == nStartingOffset ) + return 0; + return nOffset - nStartingOffset + 1; + } + + const char *pTest = (const char *)PeekGet( nOffset ); + + for ( int i = 0; i < nPeekAmount; ++i ) + { + // The +2 here is so we eat the terminating '\n' and 0 + if ( pTest[i] == '\n' || pTest[i] == '\r' ) + return (i + nOffset - nStartingOffset + 2); + // The +1 here is so we eat the terminating 0 + if ( pTest[i] == 0 ) + return (i + nOffset - nStartingOffset + 1); + } + + nOffset += nPeekAmount; + + } while ( true ); +} + + +//----------------------------------------------------------------------------- +// Does the next bytes of the buffer match a pattern? +//----------------------------------------------------------------------------- +bool CUtlBuffer::PeekStringMatch( int nOffset, const char *pString, int nLen ) +{ + if ( !CheckPeekGet( nOffset, nLen ) ) + return false; + return !Q_strncmp( (const char*)PeekGet(nOffset), pString, nLen ); +} + + +//----------------------------------------------------------------------------- +// This version of PeekStringLength converts \" to \\ and " to \, etc. +// It also reads a " at the beginning and end of the string +//----------------------------------------------------------------------------- +int CUtlBuffer::PeekDelimitedStringLength( CUtlCharConversion *pConv, bool bActualSize ) +{ + if ( !IsText() || !pConv ) + return PeekStringLength(); + + // Eat preceeding whitespace + int nOffset = 0; + if ( IsText() ) + { + nOffset = PeekWhiteSpace( nOffset ); + } + + if ( !PeekStringMatch( nOffset, pConv->GetDelimiter(), pConv->GetDelimiterLength() ) ) + return 0; + + // Try to read ending ", but don't accept \" + int nActualStart = nOffset; + nOffset += pConv->GetDelimiterLength(); + int nLen = 1; // Starts at 1 for the '\0' termination + + do + { + if ( PeekStringMatch( nOffset, pConv->GetDelimiter(), pConv->GetDelimiterLength() ) ) + break; + + if ( !CheckPeekGet( nOffset, 1 ) ) + break; + + char c = *(const char*)PeekGet( nOffset ); + ++nLen; + ++nOffset; + if ( c == pConv->GetEscapeChar() ) + { + int nLength = pConv->MaxConversionLength(); + if ( !CheckArbitraryPeekGet( nOffset, nLength ) ) + break; + + pConv->FindConversion( (const char*)PeekGet(nOffset), &nLength ); + nOffset += nLength; + } + } while (true); + + return bActualSize ? nLen : nOffset - nActualStart + pConv->GetDelimiterLength() + 1; +} + + +//----------------------------------------------------------------------------- +// Reads a null-terminated string +//----------------------------------------------------------------------------- +void CUtlBuffer::GetString( char* pString, int nMaxChars ) +{ + if (!IsValid()) + { + *pString = 0; + return; + } + + if ( nMaxChars == 0 ) + { + nMaxChars = INT_MAX; + } + + // Remember, this *includes* the null character + // It will be 0, however, if the buffer is empty. + int nLen = PeekStringLength(); + + if ( IsText() ) + { + EatWhiteSpace(); + } + + if ( nLen == 0 ) + { + *pString = 0; + m_Error |= GET_OVERFLOW; + return; + } + + // Strip off the terminating NULL + if ( nLen <= nMaxChars ) + { + Get( pString, nLen - 1 ); + pString[ nLen - 1 ] = 0; + } + else + { + Get( pString, nMaxChars - 1 ); + pString[ nMaxChars - 1 ] = 0; + SeekGet( SEEK_CURRENT, nLen - 1 - nMaxChars ); + } + + // Read the terminating NULL in binary formats + if ( !IsText() ) + { + VerifyEquals( GetChar(), 0 ); + } +} + + +//----------------------------------------------------------------------------- +// Reads up to and including the first \n +//----------------------------------------------------------------------------- +void CUtlBuffer::GetLine( char* pLine, int nMaxChars ) +{ + Assert( IsText() && !ContainsCRLF() ); + + if ( !IsValid() ) + { + *pLine = 0; + return; + } + + if ( nMaxChars == 0 ) + { + nMaxChars = INT_MAX; + } + + // Remember, this *includes* the null character + // It will be 0, however, if the buffer is empty. + int nLen = PeekLineLength(); + if ( nLen == 0 ) + { + *pLine = 0; + m_Error |= GET_OVERFLOW; + return; + } + + // Strip off the terminating NULL + if ( nLen <= nMaxChars ) + { + Get( pLine, nLen - 1 ); + pLine[ nLen - 1 ] = 0; + } + else + { + Get( pLine, nMaxChars - 1 ); + pLine[ nMaxChars - 1 ] = 0; + SeekGet( SEEK_CURRENT, nLen - 1 - nMaxChars ); + } +} + + +//----------------------------------------------------------------------------- +// This version of GetString converts \ to \\ and " to \", etc. +// It also places " at the beginning and end of the string +//----------------------------------------------------------------------------- +char CUtlBuffer::GetDelimitedCharInternal( CUtlCharConversion *pConv ) +{ + char c = GetChar(); + if ( c == pConv->GetEscapeChar() ) + { + int nLength = pConv->MaxConversionLength(); + if ( !CheckArbitraryPeekGet( 0, nLength ) ) + return '\0'; + + c = pConv->FindConversion( (const char *)PeekGet(), &nLength ); + SeekGet( SEEK_CURRENT, nLength ); + } + + return c; +} + +char CUtlBuffer::GetDelimitedChar( CUtlCharConversion *pConv ) +{ + if ( !IsText() || !pConv ) + return GetChar( ); + return GetDelimitedCharInternal( pConv ); +} + +void CUtlBuffer::GetDelimitedString( CUtlCharConversion *pConv, char *pString, int nMaxChars ) +{ + if ( !IsText() || !pConv ) + { + GetString( pString, nMaxChars ); + return; + } + + if (!IsValid()) + { + *pString = 0; + return; + } + + if ( nMaxChars == 0 ) + { + nMaxChars = INT_MAX; + } + + EatWhiteSpace(); + if ( !PeekStringMatch( 0, pConv->GetDelimiter(), pConv->GetDelimiterLength() ) ) + return; + + // Pull off the starting delimiter + SeekGet( SEEK_CURRENT, pConv->GetDelimiterLength() ); + + int nRead = 0; + while ( IsValid() ) + { + if ( PeekStringMatch( 0, pConv->GetDelimiter(), pConv->GetDelimiterLength() ) ) + { + SeekGet( SEEK_CURRENT, pConv->GetDelimiterLength() ); + break; + } + + char c = GetDelimitedCharInternal( pConv ); + + if ( nRead < nMaxChars ) + { + pString[nRead] = c; + ++nRead; + } + } + + if ( nRead >= nMaxChars ) + { + nRead = nMaxChars - 1; + } + pString[nRead] = '\0'; +} + + +//----------------------------------------------------------------------------- +// Checks if a get is ok +//----------------------------------------------------------------------------- +bool CUtlBuffer::CheckGet( int nSize ) +{ + if ( m_Error & GET_OVERFLOW ) + return false; + + if ( TellMaxPut() < m_Get + nSize ) + { + m_Error |= GET_OVERFLOW; + return false; + } + + if ( ( m_Get < m_nOffset ) || ( m_Memory.NumAllocated() < m_Get - m_nOffset + nSize ) ) + { + if ( !OnGetOverflow( nSize ) ) + { + m_Error |= GET_OVERFLOW; + return false; + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Checks if a peek get is ok +//----------------------------------------------------------------------------- +bool CUtlBuffer::CheckPeekGet( int nOffset, int nSize ) +{ + if ( m_Error & GET_OVERFLOW ) + return false; + + // Checking for peek can't set the overflow flag + bool bOk = CheckGet( nOffset + nSize ); + m_Error &= ~GET_OVERFLOW; + return bOk; +} + + +//----------------------------------------------------------------------------- +// Call this to peek arbitrarily long into memory. It doesn't fail unless +// it can't read *anything* new +//----------------------------------------------------------------------------- +bool CUtlBuffer::CheckArbitraryPeekGet( int nOffset, int &nIncrement ) +{ + if ( TellGet() + nOffset >= TellMaxPut() ) + { + nIncrement = 0; + return false; + } + + if ( TellGet() + nOffset + nIncrement > TellMaxPut() ) + { + nIncrement = TellMaxPut() - TellGet() - nOffset; + } + + // NOTE: CheckPeekGet could modify TellMaxPut for streaming files + // We have to call TellMaxPut again here + CheckPeekGet( nOffset, nIncrement ); + int nMaxGet = TellMaxPut() - TellGet(); + if ( nMaxGet < nIncrement ) + { + nIncrement = nMaxGet; + } + return (nIncrement != 0); +} + + +//----------------------------------------------------------------------------- +// Peek part of the butt +//----------------------------------------------------------------------------- +const void* CUtlBuffer::PeekGet( int nMaxSize, int nOffset ) +{ + if ( !CheckPeekGet( nOffset, nMaxSize ) ) + return NULL; + return &m_Memory[ m_Get + nOffset - m_nOffset ]; +} + + +//----------------------------------------------------------------------------- +// Change where I'm reading +//----------------------------------------------------------------------------- +void CUtlBuffer::SeekGet( SeekType_t type, int offset ) +{ + switch( type ) + { + case SEEK_HEAD: + m_Get = offset; + break; + + case SEEK_CURRENT: + m_Get += offset; + break; + + case SEEK_TAIL: + m_Get = m_nMaxPut - offset; + break; + } + + if ( m_Get > m_nMaxPut ) + { + m_Error |= GET_OVERFLOW; + } + else + { + m_Error &= ~GET_OVERFLOW; + if ( m_Get < m_nOffset || m_Get >= m_nOffset + Size() ) + { + OnGetOverflow( -1 ); + } + } +} + + +//----------------------------------------------------------------------------- +// Parse... +//----------------------------------------------------------------------------- + +#ifdef _MSC_VER +#pragma warning ( disable : 4706 ) +#endif + +int CUtlBuffer::VaScanf( const char* pFmt, va_list list ) +{ + Assert( pFmt ); + if ( m_Error || !IsText() ) + return 0; + + int numScanned = 0; + int nLength; + char c; + char* pEnd; + while ( (c = *pFmt++) ) + { + // Stop if we hit the end of the buffer + if ( m_Get >= TellMaxPut() ) + { + m_Error |= GET_OVERFLOW; + break; + } + + switch (c) + { + case ' ': + // eat all whitespace + EatWhiteSpace(); + break; + + case '%': + { + // Conversion character... try to convert baby! + char type = *pFmt++; + if (type == 0) + return numScanned; + + switch(type) + { + case 'c': + { + char* ch = va_arg( list, char * ); + if ( CheckPeekGet( 0, sizeof(char) ) ) + { + *ch = *(const char*)PeekGet(); + ++m_Get; + } + else + { + *ch = 0; + return numScanned; + } + } + break; + + case 'i': + case 'd': + { + int* i = va_arg( list, int * ); + + // NOTE: This is not bullet-proof; it assumes numbers are < 128 characters + nLength = 128; + if ( !CheckArbitraryPeekGet( 0, nLength ) ) + { + *i = 0; + return numScanned; + } + + *i = strtol( (char*)PeekGet(), &pEnd, 10 ); + int nBytesRead = (int)( pEnd - (char*)PeekGet() ); + if ( nBytesRead == 0 ) + return numScanned; + m_Get += nBytesRead; + } + break; + + case 'x': + { + int* i = va_arg( list, int * ); + + // NOTE: This is not bullet-proof; it assumes numbers are < 128 characters + nLength = 128; + if ( !CheckArbitraryPeekGet( 0, nLength ) ) + { + *i = 0; + return numScanned; + } + + *i = strtol( (char*)PeekGet(), &pEnd, 16 ); + int nBytesRead = (int)( pEnd - (char*)PeekGet() ); + if ( nBytesRead == 0 ) + return numScanned; + m_Get += nBytesRead; + } + break; + + case 'u': + { + unsigned int* u = va_arg( list, unsigned int *); + + // NOTE: This is not bullet-proof; it assumes numbers are < 128 characters + nLength = 128; + if ( !CheckArbitraryPeekGet( 0, nLength ) ) + { + *u = 0; + return numScanned; + } + + *u = strtoul( (char*)PeekGet(), &pEnd, 10 ); + int nBytesRead = (int)( pEnd - (char*)PeekGet() ); + if ( nBytesRead == 0 ) + return numScanned; + m_Get += nBytesRead; + } + break; + + case 'f': + { + float* f = va_arg( list, float *); + + // NOTE: This is not bullet-proof; it assumes numbers are < 128 characters + nLength = 128; + if ( !CheckArbitraryPeekGet( 0, nLength ) ) + { + *f = 0.0f; + return numScanned; + } + + *f = (float)strtod( (char*)PeekGet(), &pEnd ); + int nBytesRead = (int)( pEnd - (char*)PeekGet() ); + if ( nBytesRead == 0 ) + return numScanned; + m_Get += nBytesRead; + } + break; + + case 's': + { + char* s = va_arg( list, char * ); + GetString( s ); + } + break; + + default: + { + // unimplemented scanf type + Assert(0); + return numScanned; + } + break; + } + + ++numScanned; + } + break; + + default: + { + // Here we have to match the format string character + // against what's in the buffer or we're done. + if ( !CheckPeekGet( 0, sizeof(char) ) ) + return numScanned; + + if ( c != *(const char*)PeekGet() ) + return numScanned; + + ++m_Get; + } + } + } + return numScanned; +} + +#ifdef _MSC_VER +#pragma warning ( default : 4706 ) +#endif + +int CUtlBuffer::Scanf( const char* pFmt, ... ) +{ + va_list args; + + va_start( args, pFmt ); + int count = VaScanf( pFmt, args ); + va_end( args ); + + return count; +} + + +//----------------------------------------------------------------------------- +// Advance the get index until after the particular string is found +// Do not eat whitespace before starting. Return false if it failed +//----------------------------------------------------------------------------- +bool CUtlBuffer::GetToken( const char *pToken ) +{ + Assert( pToken ); + + // Look for the token + int nLen = Q_strlen( pToken ); + + int nSizeToCheck = Size() - TellGet() - m_nOffset; + + int nGet = TellGet(); + do + { + int nMaxSize = TellMaxPut() - TellGet(); + if ( nMaxSize < nSizeToCheck ) + { + nSizeToCheck = nMaxSize; + } + if ( nLen > nSizeToCheck ) + break; + + if ( !CheckPeekGet( 0, nSizeToCheck ) ) + break; + + const char *pBufStart = (const char*)PeekGet(); + const char *pFoundEnd = Q_strnistr( pBufStart, pToken, nSizeToCheck ); + if ( pFoundEnd ) + { + size_t nOffset = (size_t)pFoundEnd - (size_t)pBufStart; + SeekGet( CUtlBuffer::SEEK_CURRENT, nOffset + nLen ); + return true; + } + + SeekGet( CUtlBuffer::SEEK_CURRENT, nSizeToCheck - nLen - 1 ); + nSizeToCheck = Size() - (nLen-1); + + } while ( true ); + + SeekGet( CUtlBuffer::SEEK_HEAD, nGet ); + return false; +} + + +//----------------------------------------------------------------------------- +// (For text buffers only) +// Parse a token from the buffer: +// Grab all text that lies between a starting delimiter + ending delimiter +// (skipping whitespace that leads + trails both delimiters). +// Note the delimiter checks are case-insensitive. +// If successful, the get index is advanced and the function returns true, +// otherwise the index is not advanced and the function returns false. +//----------------------------------------------------------------------------- +bool CUtlBuffer::ParseToken( const char *pStartingDelim, const char *pEndingDelim, char* pString, int nMaxLen ) +{ + int nCharsToCopy = 0; + int nCurrentGet = 0; + + size_t nEndingDelimLen; + + // Starting delimiter is optional + char emptyBuf = '\0'; + if ( !pStartingDelim ) + { + pStartingDelim = &emptyBuf; + } + + // Ending delimiter is not + Assert( pEndingDelim && pEndingDelim[0] ); + nEndingDelimLen = Q_strlen( pEndingDelim ); + + int nStartGet = TellGet(); + char nCurrChar; + int nTokenStart = -1; + EatWhiteSpace( ); + while ( *pStartingDelim ) + { + nCurrChar = *pStartingDelim++; + if ( !isspace((unsigned char)nCurrChar) ) + { + if ( tolower( GetChar() ) != tolower( nCurrChar ) ) + goto parseFailed; + } + else + { + EatWhiteSpace(); + } + } + + EatWhiteSpace(); + nTokenStart = TellGet(); + if ( !GetToken( pEndingDelim ) ) + goto parseFailed; + + nCurrentGet = TellGet(); + nCharsToCopy = (nCurrentGet - nEndingDelimLen) - nTokenStart; + if ( nCharsToCopy >= nMaxLen ) + { + nCharsToCopy = nMaxLen - 1; + } + + if ( nCharsToCopy > 0 ) + { + SeekGet( CUtlBuffer::SEEK_HEAD, nTokenStart ); + Get( pString, nCharsToCopy ); + if ( !IsValid() ) + goto parseFailed; + + // Eat trailing whitespace + for ( ; nCharsToCopy > 0; --nCharsToCopy ) + { + if ( !isspace( (unsigned char)pString[ nCharsToCopy-1 ] ) ) + break; + } + } + pString[ nCharsToCopy ] = '\0'; + + // Advance the Get index + SeekGet( CUtlBuffer::SEEK_HEAD, nCurrentGet ); + return true; + +parseFailed: + // Revert the get index + SeekGet( SEEK_HEAD, nStartGet ); + pString[0] = '\0'; + return false; +} + + +//----------------------------------------------------------------------------- +// Parses the next token, given a set of character breaks to stop at +//----------------------------------------------------------------------------- +int CUtlBuffer::ParseToken( characterset_t *pBreaks, char *pTokenBuf, int nMaxLen, bool bParseComments ) +{ + Assert( nMaxLen > 0 ); + pTokenBuf[0] = 0; + + // skip whitespace + comments + while ( true ) + { + if ( !IsValid() ) + return -1; + EatWhiteSpace(); + if ( bParseComments ) + { + if ( !EatCPPComment() ) + break; + } + else + { + break; + } + } + + char c = GetChar(); + + // End of buffer + if ( c == 0 ) + return -1; + + // handle quoted strings specially + if ( c == '\"' ) + { + int nLen = 0; + while( IsValid() ) + { + c = GetChar(); + if ( c == '\"' || !c ) + { + pTokenBuf[nLen] = 0; + return nLen; + } + pTokenBuf[nLen] = c; + if ( ++nLen == nMaxLen ) + { + pTokenBuf[nLen-1] = 0; + return nMaxLen; + } + } + + // In this case, we hit the end of the buffer before hitting the end qoute + pTokenBuf[nLen] = 0; + return nLen; + } + + // parse single characters + if ( IN_CHARACTERSET( *pBreaks, c ) ) + { + pTokenBuf[0] = c; + pTokenBuf[1] = 0; + return 1; + } + + // parse a regular word + int nLen = 0; + while ( true ) + { + pTokenBuf[nLen] = c; + if ( ++nLen == nMaxLen ) + { + pTokenBuf[nLen-1] = 0; + return nMaxLen; + } + c = GetChar(); + if ( !IsValid() ) + break; + + if ( IN_CHARACTERSET( *pBreaks, c ) || c == '\"' || c <= ' ' ) + { + SeekGet( SEEK_CURRENT, -1 ); + break; + } + } + + pTokenBuf[nLen] = 0; + return nLen; +} + + + +//----------------------------------------------------------------------------- +// Serialization +//----------------------------------------------------------------------------- +void CUtlBuffer::Put( const void *pMem, int size ) +{ + if ( size && CheckPut( size ) ) + { + memcpy( &m_Memory[m_Put - m_nOffset], pMem, size ); + m_Put += size; + + AddNullTermination(); + } +} + + +//----------------------------------------------------------------------------- +// Writes a null-terminated string +//----------------------------------------------------------------------------- +void CUtlBuffer::PutString( const char* pString ) +{ + if (!IsText()) + { + if ( pString ) + { + // Not text? append a null at the end. + size_t nLen = Q_strlen( pString ) + 1; + Put( pString, nLen * sizeof(char) ); + return; + } + else + { + PutTypeBin( 0 ); + } + } + else if (pString) + { + int nTabCount = ( m_Flags & AUTO_TABS_DISABLED ) ? 0 : m_nTab; + if ( nTabCount > 0 ) + { + if ( WasLastCharacterCR() ) + { + PutTabs(); + } + + const char* pEndl = strchr( pString, '\n' ); + while ( pEndl ) + { + size_t nSize = (size_t)pEndl - (size_t)pString + sizeof(char); + Put( pString, nSize ); + pString = pEndl + 1; + if ( *pString ) + { + PutTabs(); + pEndl = strchr( pString, '\n' ); + } + else + { + pEndl = NULL; + } + } + } + size_t nLen = Q_strlen( pString ); + if ( nLen ) + { + Put( pString, nLen * sizeof(char) ); + } + } +} + + +//----------------------------------------------------------------------------- +// This version of PutString converts \ to \\ and " to \", etc. +// It also places " at the beginning and end of the string +//----------------------------------------------------------------------------- +inline void CUtlBuffer::PutDelimitedCharInternal( CUtlCharConversion *pConv, char c ) +{ + int l = pConv->GetConversionLength( c ); + if ( l == 0 ) + { + PutChar( c ); + } + else + { + PutChar( pConv->GetEscapeChar() ); + Put( pConv->GetConversionString( c ), l ); + } +} + +void CUtlBuffer::PutDelimitedChar( CUtlCharConversion *pConv, char c ) +{ + if ( !IsText() || !pConv ) + { + PutChar( c ); + return; + } + + PutDelimitedCharInternal( pConv, c ); +} + +void CUtlBuffer::PutDelimitedString( CUtlCharConversion *pConv, const char *pString ) +{ + if ( !IsText() || !pConv ) + { + PutString( pString ); + return; + } + + if ( WasLastCharacterCR() ) + { + PutTabs(); + } + Put( pConv->GetDelimiter(), pConv->GetDelimiterLength() ); + + int nLen = pString ? Q_strlen( pString ) : 0; + for ( int i = 0; i < nLen; ++i ) + { + PutDelimitedCharInternal( pConv, pString[i] ); + } + + if ( WasLastCharacterCR() ) + { + PutTabs(); + } + Put( pConv->GetDelimiter(), pConv->GetDelimiterLength() ); +} + + +void CUtlBuffer::VaPrintf( const char* pFmt, va_list list ) +{ + char temp[2048]; +#ifdef _DEBUG + int nLen = +#endif + Q_vsnprintf( temp, sizeof( temp ), pFmt, list ); + Assert( nLen < 2048 ); + PutString( temp ); +} + +void CUtlBuffer::Printf( const char* pFmt, ... ) +{ + va_list args; + + va_start( args, pFmt ); + VaPrintf( pFmt, args ); + va_end( args ); +} + + +//----------------------------------------------------------------------------- +// Calls the overflow functions +//----------------------------------------------------------------------------- +void CUtlBuffer::SetOverflowFuncs( UtlBufferOverflowFunc_t getFunc, UtlBufferOverflowFunc_t putFunc ) +{ + m_GetOverflowFunc = getFunc; + m_PutOverflowFunc = putFunc; +} + + +//----------------------------------------------------------------------------- +// Calls the overflow functions +//----------------------------------------------------------------------------- +bool CUtlBuffer::OnPutOverflow( int nSize ) +{ + return (this->*m_PutOverflowFunc)( nSize ); +} + +bool CUtlBuffer::OnGetOverflow( int nSize ) +{ + return (this->*m_GetOverflowFunc)( nSize ); +} + + +//----------------------------------------------------------------------------- +// Checks if a put is ok +//----------------------------------------------------------------------------- +bool CUtlBuffer::PutOverflow( int nSize ) +{ + if ( m_Memory.IsExternallyAllocated() ) + { + if ( !IsGrowable() ) + return false; + + m_Memory.ConvertToGrowableMemory( 0 ); + } + + while( Size() < m_Put - m_nOffset + nSize ) + { + m_Memory.Grow(); + } + + return true; +} + +bool CUtlBuffer::GetOverflow( int nSize ) +{ + return false; +} + + +//----------------------------------------------------------------------------- +// Checks if a put is ok +//----------------------------------------------------------------------------- +bool CUtlBuffer::CheckPut( int nSize ) +{ + if ( ( m_Error & PUT_OVERFLOW ) || IsReadOnly() ) + return false; + + if ( ( m_Put < m_nOffset ) || ( m_Memory.NumAllocated() < m_Put - m_nOffset + nSize ) ) + { + if ( !OnPutOverflow( nSize ) ) + { + m_Error |= PUT_OVERFLOW; + return false; + } + } + return true; +} + +void CUtlBuffer::SeekPut( SeekType_t type, int offset ) +{ + int nNextPut = m_Put; + switch( type ) + { + case SEEK_HEAD: + nNextPut = offset; + break; + + case SEEK_CURRENT: + nNextPut += offset; + break; + + case SEEK_TAIL: + nNextPut = m_nMaxPut - offset; + break; + } + + // Force a write of the data + // FIXME: We could make this more optimal potentially by writing out + // the entire buffer if you seek outside the current range + + // NOTE: This call will write and will also seek the file to nNextPut. + OnPutOverflow( -nNextPut-1 ); + m_Put = nNextPut; + + AddNullTermination(); +} + + +void CUtlBuffer::ActivateByteSwapping( bool bActivate ) +{ + m_Byteswap.ActivateByteSwapping( bActivate ); +} + +void CUtlBuffer::SetBigEndian( bool bigEndian ) +{ + m_Byteswap.SetTargetBigEndian( bigEndian ); +} + +bool CUtlBuffer::IsBigEndian( void ) +{ + return m_Byteswap.IsTargetBigEndian(); +} + + +//----------------------------------------------------------------------------- +// null terminate the buffer +//----------------------------------------------------------------------------- +void CUtlBuffer::AddNullTermination( void ) +{ + if ( m_Put > m_nMaxPut ) + { + if ( !IsReadOnly() && ((m_Error & PUT_OVERFLOW) == 0) ) + { + // Add null termination value + if ( CheckPut( 1 ) ) + { + m_Memory[m_Put - m_nOffset] = 0; + } + else + { + // Restore the overflow state, it was valid before... + m_Error &= ~PUT_OVERFLOW; + } + } + m_nMaxPut = m_Put; + } +} + + +//----------------------------------------------------------------------------- +// Converts a buffer from a CRLF buffer to a CR buffer (and back) +// Returns false if no conversion was necessary (and outBuf is left untouched) +// If the conversion occurs, outBuf will be cleared. +//----------------------------------------------------------------------------- +bool CUtlBuffer::ConvertCRLF( CUtlBuffer &outBuf ) +{ + if ( !IsText() || !outBuf.IsText() ) + return false; + + if ( ContainsCRLF() == outBuf.ContainsCRLF() ) + return false; + + int nInCount = TellMaxPut(); + + outBuf.Purge(); + outBuf.EnsureCapacity( nInCount ); + + bool bFromCRLF = ContainsCRLF(); + + // Start reading from the beginning + int nGet = TellGet(); + int nPut = TellPut(); + int nGetDelta = 0; + int nPutDelta = 0; + + const char *pBase = (const char*)Base(); + int nCurrGet = 0; + while ( nCurrGet < nInCount ) + { + const char *pCurr = &pBase[nCurrGet]; + if ( bFromCRLF ) + { + const char *pNext = Q_strnistr( pCurr, "\r\n", nInCount - nCurrGet ); + if ( !pNext ) + { + outBuf.Put( pCurr, nInCount - nCurrGet ); + break; + } + + int nBytes = (size_t)pNext - (size_t)pCurr; + outBuf.Put( pCurr, nBytes ); + outBuf.PutChar( '\n' ); + nCurrGet += nBytes + 2; + if ( nGet >= nCurrGet - 1 ) + { + --nGetDelta; + } + if ( nPut >= nCurrGet - 1 ) + { + --nPutDelta; + } + } + else + { + const char *pNext = Q_strnchr( pCurr, '\n', nInCount - nCurrGet ); + if ( !pNext ) + { + outBuf.Put( pCurr, nInCount - nCurrGet ); + break; + } + + int nBytes = (size_t)pNext - (size_t)pCurr; + outBuf.Put( pCurr, nBytes ); + outBuf.PutChar( '\r' ); + outBuf.PutChar( '\n' ); + nCurrGet += nBytes + 1; + if ( nGet >= nCurrGet ) + { + ++nGetDelta; + } + if ( nPut >= nCurrGet ) + { + ++nPutDelta; + } + } + } + + Assert( nPut + nPutDelta <= outBuf.TellMaxPut() ); + + outBuf.SeekGet( SEEK_HEAD, nGet + nGetDelta ); + outBuf.SeekPut( SEEK_HEAD, nPut + nPutDelta ); + + return true; +} + + +//--------------------------------------------------------------------------- +// Implementation of CUtlInplaceBuffer +//--------------------------------------------------------------------------- + +CUtlInplaceBuffer::CUtlInplaceBuffer( int growSize /* = 0 */, int initSize /* = 0 */, int nFlags /* = 0 */ ) : + CUtlBuffer( growSize, initSize, nFlags ) +{ + +} + +bool CUtlInplaceBuffer::InplaceGetLinePtr( char **ppszInBufferPtr, int *pnLineLength ) +{ + Assert( IsText() && !ContainsCRLF() ); + + int nLineLen = PeekLineLength(); + if ( nLineLen <= 1 ) + { + SeekGet( SEEK_TAIL, 0 ); + return false; + } + + -- nLineLen; // because it accounts for putting a terminating null-character + + char *pszLine = ( char * ) const_cast< void * >( PeekGet() ); + SeekGet( SEEK_CURRENT, nLineLen ); + + // Set the out args + if ( ppszInBufferPtr ) + *ppszInBufferPtr = pszLine; + + if ( pnLineLength ) + *pnLineLength = nLineLen; + + return true; +} + +char * CUtlInplaceBuffer::InplaceGetLinePtr( void ) +{ + char *pszLine = NULL; + int nLineLen = 0; + + if ( InplaceGetLinePtr( &pszLine, &nLineLen ) ) + { + Assert( nLineLen >= 1 ); + + switch ( pszLine[ nLineLen - 1 ] ) + { + case '\n': + case '\r': + pszLine[ nLineLen - 1 ] = 0; + if ( -- nLineLen ) + { + switch ( pszLine[ nLineLen - 1 ] ) + { + case '\n': + case '\r': + pszLine[ nLineLen - 1 ] = 0; + break; + } + } + break; + + default: + Assert( pszLine[ nLineLen ] == 0 ); + break; + } + } + + return pszLine; +} + diff --git a/tier1/utlbufferutil.cpp b/tier1/utlbufferutil.cpp new file mode 100644 index 00000000..a608ad91 --- /dev/null +++ b/tier1/utlbufferutil.cpp @@ -0,0 +1,561 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// $Header: $ +// $NoKeywords: $ +// +// Serialization buffer +//===========================================================================// + +#ifdef _MSC_VER +#pragma warning (disable : 4514) +#endif + +#include "tier1/utlbufferutil.h" +#include "tier1/utlbuffer.h" +#include "mathlib/vector.h" +#include "mathlib/vector2d.h" +#include "mathlib/vector4d.h" +#include "mathlib/vmatrix.h" +#include "Color.h" +#include +#include +#include +#include +#include +#include "tier1/utlstring.h" +#include "tier1/strtools.h" +#include "tier1/characterset.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// For serialization, set the delimiter rules +//----------------------------------------------------------------------------- +CUtlCharConversion *s_pConv = NULL; +const char *s_pUtlBufferUtilArrayDelim = NULL; +void SetSerializationDelimiter( CUtlCharConversion *pConv ) +{ + s_pConv = pConv; +} + +void SetSerializationArrayDelimiter( const char *pDelimiter ) +{ + s_pUtlBufferUtilArrayDelim = pDelimiter; +} + + +//----------------------------------------------------------------------------- +// Serialize a floating point number in text mode in a readably friendly fashion +//----------------------------------------------------------------------------- +static void SerializeFloat( CUtlBuffer &buf, float f ) +{ + Assert( buf.IsText() ); + + // FIXME: Print this in a way that we never lose precision + char pTemp[256]; + int nLen = Q_snprintf( pTemp, sizeof(pTemp), "%.10f", f ); + while ( nLen > 0 && pTemp[nLen-1] == '0' ) + { + --nLen; + pTemp[nLen] = 0; + } + if ( nLen > 0 && pTemp[nLen-1] == '.' ) + { + --nLen; + pTemp[nLen] = 0; + } + buf.PutString( pTemp ); +} + +static void SerializeFloats( CUtlBuffer &buf, int nCount, const float *pFloats ) +{ + for ( int i = 0; i < nCount; ++i ) + { + SerializeFloat( buf, pFloats[i] ); + if ( i != nCount-1 ) + { + buf.PutChar( ' ' ); + } + } +} + + +//----------------------------------------------------------------------------- +// Serialization methods for basic types +//----------------------------------------------------------------------------- +bool Serialize( CUtlBuffer &buf, const bool &src ) +{ + if ( buf.IsText() ) + { + buf.Printf( "%d", src ); + } + else + { + buf.PutChar( src ); + } + return buf.IsValid(); +} + +bool Unserialize( CUtlBuffer &buf, bool &dest ) +{ + if ( buf.IsText() ) + { + int nValue = 0; + int nRetVal = buf.Scanf( "%d", &nValue ); + dest = ( nValue != 0 ); + return (nRetVal == 1) && buf.IsValid(); + } + + dest = ( buf.GetChar( ) != 0 ); + return buf.IsValid(); +} + + +bool Serialize( CUtlBuffer &buf, const int &src ) +{ + if ( buf.IsText() ) + { + buf.Printf( "%d", src ); + } + else + { + buf.PutInt( src ); + } + return buf.IsValid(); +} + +bool Unserialize( CUtlBuffer &buf, int &dest ) +{ + if ( buf.IsText() ) + { + int nRetVal = buf.Scanf( "%d", &dest ); + return (nRetVal == 1) && buf.IsValid(); + } + + dest = buf.GetInt( ); + return buf.IsValid(); +} + +bool Serialize( CUtlBuffer &buf, const float &src ) +{ + if ( buf.IsText() ) + { + SerializeFloat( buf, src ); + } + else + { + buf.PutFloat( src ); + } + return buf.IsValid(); +} + +bool Unserialize( CUtlBuffer &buf, float &dest ) +{ + if ( buf.IsText() ) + { + // FIXME: Print this in a way that we never lose precision + int nRetVal = buf.Scanf( "%f", &dest ); + return (nRetVal == 1) && buf.IsValid(); + } + + dest = buf.GetFloat( ); + return buf.IsValid(); +} + + +//----------------------------------------------------------------------------- +// Attribute types related to vector math +//----------------------------------------------------------------------------- +bool Serialize( CUtlBuffer &buf, const Vector2D &src ) +{ + if ( buf.IsText() ) + { + SerializeFloats( buf, 2, src.Base() ); + } + else + { + buf.PutFloat( src.x ); + buf.PutFloat( src.y ); + } + return buf.IsValid(); +} + +bool Unserialize( CUtlBuffer &buf, Vector2D &dest ) +{ + if ( buf.IsText() ) + { + // FIXME: Print this in a way that we never lose precision + int nRetVal = buf.Scanf( "%f %f", &dest.x, &dest.y ); + return (nRetVal == 2) && buf.IsValid(); + } + + dest.x = buf.GetFloat( ); + dest.y = buf.GetFloat( ); + return buf.IsValid(); +} + +bool Serialize( CUtlBuffer &buf, const Vector &src ) +{ + if ( buf.IsText() ) + { + SerializeFloats( buf, 3, src.Base() ); + } + else + { + buf.PutFloat( src.x ); + buf.PutFloat( src.y ); + buf.PutFloat( src.z ); + } + return buf.IsValid(); +} + +bool Unserialize( CUtlBuffer &buf, Vector &dest ) +{ + if ( buf.IsText() ) + { + // FIXME: Print this in a way that we never lose precision + int nRetVal = buf.Scanf( "%f %f %f", &dest.x, &dest.y, &dest.z ); + return (nRetVal == 3) && buf.IsValid(); + } + + dest.x = buf.GetFloat( ); + dest.y = buf.GetFloat( ); + dest.z = buf.GetFloat( ); + return buf.IsValid(); +} + +bool Serialize( CUtlBuffer &buf, const Vector4D &src ) +{ + if ( buf.IsText() ) + { + SerializeFloats( buf, 4, src.Base() ); + } + else + { + buf.PutFloat( src.x ); + buf.PutFloat( src.y ); + buf.PutFloat( src.z ); + buf.PutFloat( src.w ); + } + return buf.IsValid(); +} + +bool Unserialize( CUtlBuffer &buf, Vector4D &dest ) +{ + if ( buf.IsText() ) + { + // FIXME: Print this in a way that we never lose precision + int nRetVal = buf.Scanf( "%f %f %f %f", &dest.x, &dest.y, &dest.z, &dest.w ); + return (nRetVal == 4) && buf.IsValid(); + } + + dest.x = buf.GetFloat( ); + dest.y = buf.GetFloat( ); + dest.z = buf.GetFloat( ); + dest.w = buf.GetFloat( ); + return buf.IsValid(); +} + +bool Serialize( CUtlBuffer &buf, const QAngle &src ) +{ + if ( buf.IsText() ) + { + SerializeFloats( buf, 3, src.Base() ); + } + else + { + buf.PutFloat( src.x ); + buf.PutFloat( src.y ); + buf.PutFloat( src.z ); + } + return buf.IsValid(); +} + +bool Unserialize( CUtlBuffer &buf, QAngle &dest ) +{ + if ( buf.IsText() ) + { + // FIXME: Print this in a way that we never lose precision + int nRetVal = buf.Scanf( "%f %f %f", &dest.x, &dest.y, &dest.z ); + return (nRetVal == 3) && buf.IsValid(); + } + + dest.x = buf.GetFloat( ); + dest.y = buf.GetFloat( ); + dest.z = buf.GetFloat( ); + return buf.IsValid(); +} + +bool Serialize( CUtlBuffer &buf, const Quaternion &src ) +{ + if ( buf.IsText() ) + { + SerializeFloats( buf, 4, &src.x ); + } + else + { + buf.PutFloat( src.x ); + buf.PutFloat( src.y ); + buf.PutFloat( src.z ); + buf.PutFloat( src.w ); + } + return buf.IsValid(); +} + +bool Unserialize( CUtlBuffer &buf, Quaternion &dest ) +{ + if ( buf.IsText() ) + { + // FIXME: Print this in a way that we never lose precision + int nRetVal = buf.Scanf( "%f %f %f %f", &dest.x, &dest.y, &dest.z, &dest.w ); + return (nRetVal == 4) && buf.IsValid(); + } + + dest.x = buf.GetFloat( ); + dest.y = buf.GetFloat( ); + dest.z = buf.GetFloat( ); + dest.w = buf.GetFloat( ); + return buf.IsValid(); +} + +bool Serialize( CUtlBuffer &buf, const VMatrix &src ) +{ + if ( buf.IsText() ) + { + buf.Printf( "\n" ); + SerializeFloats( buf, 4, src[0] ); + buf.Printf( "\n" ); + SerializeFloats( buf, 4, src[1] ); + buf.Printf( "\n" ); + SerializeFloats( buf, 4, src[2] ); + buf.Printf( "\n" ); + SerializeFloats( buf, 4, src[3] ); + buf.Printf( "\n" ); + } + else + { + buf.Put( &src, sizeof(VMatrix) ); + } + return buf.IsValid(); +} + +bool Unserialize( CUtlBuffer &buf, VMatrix &dest ) +{ + if ( !buf.IsValid() ) + return false; + + if ( buf.IsText() ) + { + int nRetVal = buf.Scanf( "%f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f", + &dest[ 0 ][ 0 ], &dest[ 0 ][ 1 ], &dest[ 0 ][ 2 ], &dest[ 0 ][ 3 ], + &dest[ 1 ][ 0 ], &dest[ 1 ][ 1 ], &dest[ 1 ][ 2 ], &dest[ 1 ][ 3 ], + &dest[ 2 ][ 0 ], &dest[ 2 ][ 1 ], &dest[ 2 ][ 2 ], &dest[ 2 ][ 3 ], + &dest[ 3 ][ 0 ], &dest[ 3 ][ 1 ], &dest[ 3 ][ 2 ], &dest[ 3 ][ 3 ] ); + return (nRetVal == 16); + } + + buf.Get( &dest, sizeof(VMatrix) ); + return true; +} + + +//----------------------------------------------------------------------------- +// Color attribute +//----------------------------------------------------------------------------- +bool Serialize( CUtlBuffer &buf, const Color &src ) +{ + if ( buf.IsText() ) + { + buf.Printf( "%d %d %d %d", src[0], src[1], src[2], src[3] ); + } + else + { + buf.PutUnsignedChar( src[0] ); + buf.PutUnsignedChar( src[1] ); + buf.PutUnsignedChar( src[2] ); + buf.PutUnsignedChar( src[3] ); + } + return buf.IsValid(); +} + +bool Unserialize( CUtlBuffer &buf, Color &dest ) +{ + if ( buf.IsText() ) + { + int r = 0, g = 0, b = 0, a = 255; + int nRetVal = buf.Scanf( "%d %d %d %d", &r, &g, &b, &a ); + dest.SetColor( r, g, b, a ); + return (nRetVal == 4) && buf.IsValid(); + } + + dest[0] = buf.GetUnsignedChar( ); + dest[1] = buf.GetUnsignedChar( ); + dest[2] = buf.GetUnsignedChar( ); + dest[3] = buf.GetUnsignedChar( ); + return buf.IsValid(); +} + +/* +//----------------------------------------------------------------------------- +// Object ID attribute +//----------------------------------------------------------------------------- +bool Serialize( CUtlBuffer &buf, const DmObjectId_t &src ) +{ + return g_pDataModel->Serialize( buf, src ); +} + +bool Unserialize( CUtlBuffer &buf, DmObjectId_t &dest ) +{ + return g_pDataModel->Unserialize( buf, &dest ); +} +*/ + +//----------------------------------------------------------------------------- +// Binary buffer attribute +//----------------------------------------------------------------------------- +bool Serialize( CUtlBuffer &buf, const CUtlBinaryBlock &src ) +{ + int nLength = src.Length(); + if ( !buf.IsText() ) + { + buf.PutInt( nLength ); + if ( nLength != 0 ) + { + buf.Put( src.Get(), nLength ); + } + return buf.IsValid(); + } + + // Writes out uuencoded binaries + for ( int i = 0; i < nLength; ++i ) + { + if ( (i % 40) == 0 ) + { + buf.PutChar( '\n' ); + } + + char b1 = src[i] & 0xF; + char b2 = src[i] >> 4; + + char c1 = ( b1 <= 9 ) ? b1 + '0' : b1 - 10 + 'A'; + char c2 = ( b2 <= 9 ) ? b2 + '0' : b2 - 10 + 'A'; + + buf.PutChar( c2 ); + buf.PutChar( c1 ); + } + + buf.PutChar( '\n' ); + return buf.IsValid(); +} + +static int CountBinaryBytes( CUtlBuffer &buf, int *pEndGet ) +{ + // This counts the number of bytes in the uuencoded text + int nStartGet = buf.TellGet(); + buf.EatWhiteSpace(); + *pEndGet = buf.TellGet(); + int nByteCount = 0; + while ( buf.IsValid() ) + { + char c1 = buf.GetChar(); + char c2 = buf.GetChar(); + + bool bIsNum1 = ( c1 >= '0' ) && ( c1 <= '9' ); + bool bIsNum2 = ( c2 >= '0' ) && ( c2 <= '9' ); + + bool bIsAlpha1 = (( c1 >= 'A' ) && ( c1 <= 'F' )) || (( c1 >= 'a' ) && ( c1 <= 'f' )); + bool bIsAlpha2 = (( c2 >= 'A' ) && ( c2 <= 'F' )) || (( c2 >= 'a' ) && ( c2 <= 'f' )); + + if ( !(bIsNum1 || bIsAlpha1) || !(bIsNum2 || bIsAlpha2) ) + break; + + buf.EatWhiteSpace(); + *pEndGet = buf.TellGet(); + ++nByteCount; + } + buf.SeekGet( CUtlBuffer::SEEK_HEAD, nStartGet ); + return nByteCount; +} + +inline static unsigned char HexCharToInt( int c1 ) +{ + if (( c1 >= '0' ) && ( c1 <= '9' )) + return c1 - '0'; + + if (( c1 >= 'A' ) && ( c1 <= 'F' )) + return 10 + c1 - 'A'; + + if (( c1 >= 'a' ) && ( c1 <= 'f' )) + return 10 + c1 - 'a'; + + return 0xFF; +} + +bool Unserialize( CUtlBuffer &buf, CUtlBinaryBlock &dest ) +{ + if ( !buf.IsText() ) + { + int nLen = buf.GetInt( ); + dest.SetLength( nLen ); + if ( dest.Length() != 0 ) + { + buf.Get( dest.Get(), dest.Length() ); + } + + if ( nLen != dest.Length() ) + { + buf.SeekGet( CUtlBuffer::SEEK_CURRENT, nLen - dest.Length() ); + return false; + } + + return buf.IsValid(); + } + + int nEndGet; + int nByteCount = CountBinaryBytes( buf, &nEndGet ); + if ( nByteCount < 0 ) + return false; + + buf.EatWhiteSpace(); + int nDest = 0; + dest.SetLength( nByteCount ); + while( buf.TellGet() < nEndGet ) + { + char c1 = buf.GetChar(); + char c2 = buf.GetChar(); + + unsigned char b1 = HexCharToInt( c1 ); + unsigned char b2 = HexCharToInt( c2 ); + if ( b1 == 0xFF || b2 == 0xFF ) + return false; + + dest[ nDest++ ] = b2 | ( b1 << 4 ); + buf.EatWhiteSpace(); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// String attribute +//----------------------------------------------------------------------------- +bool Serialize( CUtlBuffer &buf, const CUtlString &src ) +{ + buf.PutDelimitedString( s_pConv, src.Get() ); + return buf.IsValid(); +} + +bool Unserialize( CUtlBuffer &buf, CUtlString &dest ) +{ + int nLen = buf.PeekDelimitedStringLength( s_pConv ); + dest.SetLength( nLen - 1 ); // -1 because the length returned includes space for \0 + buf.GetDelimitedString( s_pConv, dest.Get(), nLen ); + return buf.IsValid(); +} + + + + diff --git a/tier1/utlstring.cpp b/tier1/utlstring.cpp new file mode 100644 index 00000000..a34e6638 --- /dev/null +++ b/tier1/utlstring.cpp @@ -0,0 +1,334 @@ +//====== Copyright © 1996-2004, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= + +#include "tier1/utlstring.h" +#include "tier1/strtools.h" + + +//----------------------------------------------------------------------------- +// Base class, containing simple memory management +//----------------------------------------------------------------------------- +CUtlBinaryBlock::CUtlBinaryBlock( int growSize, int initSize ) : m_Memory( growSize, initSize ) +{ + m_nActualLength = 0; +} + +CUtlBinaryBlock::CUtlBinaryBlock( void* pMemory, int nSizeInBytes, int nInitialLength ) : m_Memory( (unsigned char*)pMemory, nSizeInBytes ) +{ + m_nActualLength = nInitialLength; +} + +CUtlBinaryBlock::CUtlBinaryBlock( const void* pMemory, int nSizeInBytes ) : m_Memory( (const unsigned char*)pMemory, nSizeInBytes ) +{ + m_nActualLength = nSizeInBytes; +} + +CUtlBinaryBlock::CUtlBinaryBlock( const CUtlBinaryBlock& src ) +{ + Set( src.Get(), src.Length() ); +} + +void CUtlBinaryBlock::Get( void *pValue, int nLen ) const +{ + Assert( nLen > 0 ); + if ( m_nActualLength < nLen ) + { + nLen = m_nActualLength; + } + + if ( nLen > 0 ) + { + memcpy( pValue, m_Memory.Base(), nLen ); + } +} + +void CUtlBinaryBlock::SetLength( int nLength ) +{ + Assert( !m_Memory.IsReadOnly() ); + + m_nActualLength = nLength; + if ( nLength > m_Memory.NumAllocated() ) + { + int nOverFlow = nLength - m_Memory.NumAllocated(); + m_Memory.Grow( nOverFlow ); + + // If the reallocation failed, clamp length + if ( nLength > m_Memory.NumAllocated() ) + { + m_nActualLength = m_Memory.NumAllocated(); + } + } + +#ifdef _DEBUG + if ( m_Memory.NumAllocated() > m_nActualLength ) + { + memset( ( ( char * )m_Memory.Base() ) + m_nActualLength, 0xEB, m_Memory.NumAllocated() - m_nActualLength ); + } +#endif +} + +void CUtlBinaryBlock::Set( const void *pValue, int nLen ) +{ + Assert( !m_Memory.IsReadOnly() ); + + if ( !pValue ) + { + nLen = 0; + } + + SetLength( nLen ); + + if ( m_nActualLength ) + { + if ( ( ( const char * )m_Memory.Base() ) >= ( ( const char * )pValue ) + nLen || + ( ( const char * )m_Memory.Base() ) + m_nActualLength <= ( ( const char * )pValue ) ) + { + memcpy( m_Memory.Base(), pValue, m_nActualLength ); + } + else + { + memmove( m_Memory.Base(), pValue, m_nActualLength ); + } + } +} + + +CUtlBinaryBlock &CUtlBinaryBlock::operator=( const CUtlBinaryBlock &src ) +{ + Assert( !m_Memory.IsReadOnly() ); + Set( src.Get(), src.Length() ); + return *this; +} + + +bool CUtlBinaryBlock::operator==( const CUtlBinaryBlock &src ) const +{ + if ( src.Length() != Length() ) + return false; + + return !memcmp( src.Get(), Get(), Length() ); +} + + +//----------------------------------------------------------------------------- +// Simple string class. +//----------------------------------------------------------------------------- +CUtlString::CUtlString() +{ +} + +CUtlString::CUtlString( const char *pString ) +{ + Set( pString ); +} + +CUtlString::CUtlString( const CUtlString& string ) +{ + Set( string.Get() ); +} + +// Attaches the string to external memory. Useful for avoiding a copy +CUtlString::CUtlString( void* pMemory, int nSizeInBytes, int nInitialLength ) : m_Storage( pMemory, nSizeInBytes, nInitialLength ) +{ +} + +CUtlString::CUtlString( const void* pMemory, int nSizeInBytes ) : m_Storage( pMemory, nSizeInBytes ) +{ +} + +void CUtlString::Set( const char *pValue ) +{ + Assert( !m_Storage.IsReadOnly() ); + int nLen = pValue ? Q_strlen(pValue) + 1 : 0; + m_Storage.Set( pValue, nLen ); +} + +// Returns strlen +int CUtlString::Length() const +{ + return m_Storage.Length() ? m_Storage.Length() - 1 : 0; +} + +// Sets the length (used to serialize into the buffer ) +void CUtlString::SetLength( int nLen ) +{ + Assert( !m_Storage.IsReadOnly() ); + + // Add 1 to account for the NULL + m_Storage.SetLength( nLen > 0 ? nLen + 1 : 0 ); +} + +const char *CUtlString::Get( ) const +{ + if ( m_Storage.Length() == 0 ) + { + return ""; + } + + return reinterpret_cast< const char* >( m_Storage.Get() ); +} + +// Converts to c-strings +CUtlString::operator const char*() const +{ + return Get(); +} + +char *CUtlString::Get() +{ + Assert( !m_Storage.IsReadOnly() ); + + if ( m_Storage.Length() == 0 ) + { + // In general, we optimise away small mallocs for empty strings + // but if you ask for the non-const bytes, they must be writable + // so we can't return "" here, like we do for the const version - jd + m_Storage.SetLength( 1 ); + m_Storage[ 0 ] = '\0'; + } + + return reinterpret_cast< char* >( m_Storage.Get() ); +} + +CUtlString &CUtlString::operator=( const CUtlString &src ) +{ + Assert( !m_Storage.IsReadOnly() ); + m_Storage = src.m_Storage; + return *this; +} + +CUtlString &CUtlString::operator=( const char *src ) +{ + Assert( !m_Storage.IsReadOnly() ); + Set( src ); + return *this; +} + +bool CUtlString::operator==( const CUtlString &src ) const +{ + return m_Storage == src.m_Storage; +} + +bool CUtlString::operator==( const char *src ) const +{ + return ( strcmp( Get(), src ) == 0 ); +} + +CUtlString &CUtlString::operator+=( const CUtlString &rhs ) +{ + Assert( !m_Storage.IsReadOnly() ); + + const int lhsLength( Length() ); + const int rhsLength( rhs.Length() ); + const int requestedLength( lhsLength + rhsLength ); + + SetLength( requestedLength ); + const int allocatedLength( Length() ); + const int copyLength( allocatedLength - lhsLength < rhsLength ? allocatedLength - lhsLength : rhsLength ); + memcpy( Get() + lhsLength, rhs.Get(), copyLength ); + m_Storage[ allocatedLength ] = '\0'; + + return *this; +} + +CUtlString &CUtlString::operator+=( const char *rhs ) +{ + Assert( !m_Storage.IsReadOnly() ); + + const int lhsLength( Length() ); + const int rhsLength( Q_strlen( rhs ) ); + const int requestedLength( lhsLength + rhsLength ); + + SetLength( requestedLength ); + const int allocatedLength( Length() ); + const int copyLength( allocatedLength - lhsLength < rhsLength ? allocatedLength - lhsLength : rhsLength ); + memcpy( Get() + lhsLength, rhs, copyLength ); + m_Storage[ allocatedLength ] = '\0'; + + return *this; +} + +CUtlString &CUtlString::operator+=( char c ) +{ + Assert( !m_Storage.IsReadOnly() ); + + int nLength = Length(); + SetLength( nLength + 1 ); + m_Storage[ nLength ] = c; + m_Storage[ nLength+1 ] = '\0'; + return *this; +} + +CUtlString &CUtlString::operator+=( int rhs ) +{ + Assert( !m_Storage.IsReadOnly() ); + Assert( sizeof( rhs ) == 4 ); + + char tmpBuf[ 12 ]; // Sufficient for a signed 32 bit integer [ -2147483648 to +2147483647 ] + Q_snprintf( tmpBuf, sizeof( tmpBuf ), "%d", rhs ); + tmpBuf[ sizeof( tmpBuf ) - 1 ] = '\0'; + + return operator+=( tmpBuf ); +} + +CUtlString &CUtlString::operator+=( double rhs ) +{ + Assert( !m_Storage.IsReadOnly() ); + + char tmpBuf[ 256 ]; // How big can doubles be??? Dunno. + Q_snprintf( tmpBuf, sizeof( tmpBuf ), "%lg", rhs ); + tmpBuf[ sizeof( tmpBuf ) - 1 ] = '\0'; + + return operator+=( tmpBuf ); +} + +int CUtlString::Format( const char *pFormat, ... ) +{ + Assert( !m_Storage.IsReadOnly() ); + + char tmpBuf[ 4096 ]; //< Nice big 4k buffer, as much memory as my first computer had, a Radio Shack Color Computer + + va_list marker; + + va_start( marker, pFormat ); +#ifdef _WIN32 + int len = _vsnprintf( tmpBuf, sizeof( tmpBuf ) - 1, pFormat, marker ); +#elif defined _LINUX || defined __APPLE__ + int len = vsnprintf( tmpBuf, sizeof( tmpBuf ) - 1, pFormat, marker ); +#else +#error "define vsnprintf type." +#endif + va_end( marker ); + + // Len < 0 represents an overflow + if( len < 0 ) + { + len = sizeof( tmpBuf ) - 1; + tmpBuf[sizeof( tmpBuf ) - 1] = 0; + } + + Set( tmpBuf ); + + return len; +} + +//----------------------------------------------------------------------------- +// Strips the trailing slash +//----------------------------------------------------------------------------- +void CUtlString::StripTrailingSlash() +{ + if ( IsEmpty() ) + return; + + int nLastChar = Length() - 1; + char c = m_Storage[ nLastChar ]; + if ( c == '\\' || c == '/' ) + { + m_Storage[ nLastChar ] = 0; + m_Storage.SetLength( m_Storage.Length() - 1 ); + } +} + diff --git a/tier1/utlsymbol.cpp b/tier1/utlsymbol.cpp new file mode 100644 index 00000000..385ccaf9 --- /dev/null +++ b/tier1/utlsymbol.cpp @@ -0,0 +1,397 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Defines a symbol table +// +// $Header: $ +// $NoKeywords: $ +//=============================================================================// + +#ifdef _MSC_VER +#pragma warning (disable:4514) +#endif + +#include "utlsymbol.h" +#include "KeyValues.h" +#include "tier0/threadtools.h" +#include "tier0/memdbgon.h" +#include "stringpool.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define INVALID_STRING_INDEX CStringPoolIndex( 0xFFFF, 0xFFFF ) + +#define MIN_STRING_POOL_SIZE 2048 + +//----------------------------------------------------------------------------- +// globals +//----------------------------------------------------------------------------- + +CUtlSymbolTableMT* CUtlSymbol::s_pSymbolTable = 0; +bool CUtlSymbol::s_bAllowStaticSymbolTable = true; + + +//----------------------------------------------------------------------------- +// symbol methods +//----------------------------------------------------------------------------- + +void CUtlSymbol::Initialize() +{ + // If this assert fails, then the module that this call is in has chosen to disallow + // use of the static symbol table. Usually, it's to prevent confusion because it's easy + // to accidentally use the global symbol table when you really want to use a specific one. + Assert( s_bAllowStaticSymbolTable ); + + // necessary to allow us to create global symbols + static bool symbolsInitialized = false; + if (!symbolsInitialized) + { + s_pSymbolTable = new CUtlSymbolTableMT; + symbolsInitialized = true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Singleton to delete table on exit from module +//----------------------------------------------------------------------------- +class CCleanupUtlSymbolTable +{ +public: + ~CCleanupUtlSymbolTable() + { + delete CUtlSymbol::s_pSymbolTable; + CUtlSymbol::s_pSymbolTable = NULL; + } +}; + +static CCleanupUtlSymbolTable g_CleanupSymbolTable; + +CUtlSymbolTableMT* CUtlSymbol::CurrTable() +{ + Initialize(); + return s_pSymbolTable; +} + + +//----------------------------------------------------------------------------- +// string->symbol->string +//----------------------------------------------------------------------------- + +CUtlSymbol::CUtlSymbol( const char* pStr ) +{ + m_Id = CurrTable()->AddString( pStr ); +} + +const char* CUtlSymbol::String( ) const +{ + return CurrTable()->String(m_Id); +} + +void CUtlSymbol::DisableStaticSymbolTable() +{ + s_bAllowStaticSymbolTable = false; +} + +//----------------------------------------------------------------------------- +// checks if the symbol matches a string +//----------------------------------------------------------------------------- + +bool CUtlSymbol::operator==( const char* pStr ) const +{ + if (m_Id == UTL_INVAL_SYMBOL) + return false; + return strcmp( String(), pStr ) == 0; +} + + + +//----------------------------------------------------------------------------- +// symbol table stuff +//----------------------------------------------------------------------------- + +inline const char* CUtlSymbolTable::StringFromIndex( const CStringPoolIndex &index ) const +{ + Assert( index.m_iPool < m_StringPools.Count() ); + Assert( index.m_iOffset < m_StringPools[index.m_iPool]->m_TotalLen ); + + return &m_StringPools[index.m_iPool]->m_Data[index.m_iOffset]; +} + + +bool CUtlSymbolTable::CLess::operator()( const CStringPoolIndex &i1, const CStringPoolIndex &i2 ) const +{ + // Need to do pointer math because CUtlSymbolTable is used in CUtlVectors, and hence + // can be arbitrarily moved in memory on a realloc. Yes, this is portable. In reality, + // right now at least, because m_LessFunc is the first member of CUtlRBTree, and m_Lookup + // is the first member of CUtlSymbolTabke, this == pTable + CUtlSymbolTable *pTable = (CUtlSymbolTable *)( (byte *)this - offsetof(CUtlSymbolTable::CTree, m_LessFunc) ) - offsetof(CUtlSymbolTable, m_Lookup ); + const char* str1 = (i1 == INVALID_STRING_INDEX) ? pTable->m_pUserSearchString : + pTable->StringFromIndex( i1 ); + const char* str2 = (i2 == INVALID_STRING_INDEX) ? pTable->m_pUserSearchString : + pTable->StringFromIndex( i2 ); + + if ( !pTable->m_bInsensitive ) + return strcmp( str1, str2 ) < 0; + else + return strcmpi( str1, str2 ) < 0; +} + + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- +CUtlSymbolTable::CUtlSymbolTable( int growSize, int initSize, bool caseInsensitive ) : + m_Lookup( growSize, initSize ), m_bInsensitive( caseInsensitive ), m_StringPools( 8 ) +{ +} + +CUtlSymbolTable::~CUtlSymbolTable() +{ + // Release the stringpool string data + RemoveAll(); +} + + +CUtlSymbol CUtlSymbolTable::Find( const char* pString ) const +{ + if (!pString) + return CUtlSymbol(); + + // Store a special context used to help with insertion + m_pUserSearchString = pString; + + // Passing this special invalid symbol makes the comparison function + // use the string passed in the context + UtlSymId_t idx = m_Lookup.Find( INVALID_STRING_INDEX ); + +#ifdef _DEBUG + m_pUserSearchString = NULL; +#endif + + return CUtlSymbol( idx ); +} + + +int CUtlSymbolTable::FindPoolWithSpace( int len ) const +{ + for ( int i=0; i < m_StringPools.Count(); i++ ) + { + StringPool_t *pPool = m_StringPools[i]; + + if ( (pPool->m_TotalLen - pPool->m_SpaceUsed) >= len ) + { + return i; + } + } + + return -1; +} + + +//----------------------------------------------------------------------------- +// Finds and/or creates a symbol based on the string +//----------------------------------------------------------------------------- + +CUtlSymbol CUtlSymbolTable::AddString( const char* pString ) +{ + if (!pString) + return CUtlSymbol( UTL_INVAL_SYMBOL ); + + CUtlSymbol id = Find( pString ); + + if (id.IsValid()) + return id; + + int len = strlen(pString) + 1; + + // Find a pool with space for this string, or allocate a new one. + int iPool = FindPoolWithSpace( len ); + if ( iPool == -1 ) + { + // Add a new pool. + int newPoolSize = MAX( len, MIN_STRING_POOL_SIZE ); + StringPool_t *pPool = (StringPool_t*)malloc( sizeof( StringPool_t ) + newPoolSize - 1 ); + pPool->m_TotalLen = newPoolSize; + pPool->m_SpaceUsed = 0; + iPool = m_StringPools.AddToTail( pPool ); + } + + // Copy the string in. + StringPool_t *pPool = m_StringPools[iPool]; + Assert( pPool->m_SpaceUsed < 0xFFFF ); // This should never happen, because if we had a string > 64k, it + // would have been given its entire own pool. + + unsigned short iStringOffset = pPool->m_SpaceUsed; + + memcpy( &pPool->m_Data[pPool->m_SpaceUsed], pString, len ); + pPool->m_SpaceUsed += len; + + // didn't find, insert the string into the vector. + CStringPoolIndex index; + index.m_iPool = iPool; + index.m_iOffset = iStringOffset; + + UtlSymId_t idx = m_Lookup.Insert( index ); + return CUtlSymbol( idx ); +} + + +//----------------------------------------------------------------------------- +// Look up the string associated with a particular symbol +//----------------------------------------------------------------------------- + +const char* CUtlSymbolTable::String( CUtlSymbol id ) const +{ + if (!id.IsValid()) + return ""; + + Assert( m_Lookup.IsValidIndex((UtlSymId_t)id) ); + return StringFromIndex( m_Lookup[id] ); +} + + +//----------------------------------------------------------------------------- +// Remove all symbols in the table. +//----------------------------------------------------------------------------- + +void CUtlSymbolTable::RemoveAll() +{ + m_Lookup.Purge(); + + for ( int i=0; i < m_StringPools.Count(); i++ ) + free( m_StringPools[i] ); + + m_StringPools.RemoveAll(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pFileName - +// Output : FileNameHandle_t +//----------------------------------------------------------------------------- +FileNameHandle_t CUtlFilenameSymbolTable::FindOrAddFileName( const char *pFileName ) +{ + if ( !pFileName ) + { + return NULL; + } + + // find first + FileNameHandle_t hFileName = FindFileName( pFileName ); + if ( hFileName ) + { + return hFileName; + } + + // Fix slashes+dotslashes and make lower case first.. + char fn[ MAX_PATH ]; + Q_strncpy( fn, pFileName, sizeof( fn ) ); + Q_RemoveDotSlashes( fn ); +#ifdef _WIN32 + strlwr( fn ); +#endif + + // Split the filename into constituent parts + char basepath[ MAX_PATH ]; + Q_ExtractFilePath( fn, basepath, sizeof( basepath ) ); + char filename[ MAX_PATH ]; + Q_strncpy( filename, fn + Q_strlen( basepath ), sizeof( filename ) ); + + // not found, lock and look again + FileNameHandleInternal_t handle; + m_lock.LockForWrite(); + handle.path = m_StringPool.FindStringHandle( basepath ); + handle.file = m_StringPool.FindStringHandle( filename ); + if ( handle.path && handle.file ) + { + // found + m_lock.UnlockWrite(); + return *( FileNameHandle_t * )( &handle ); + } + + // safely add it + handle.path = m_StringPool.ReferenceStringHandle( basepath ); + handle.file = m_StringPool.ReferenceStringHandle( filename ); + m_lock.UnlockWrite(); + + return *( FileNameHandle_t * )( &handle ); +} + +FileNameHandle_t CUtlFilenameSymbolTable::FindFileName( const char *pFileName ) +{ + if ( !pFileName ) + { + return NULL; + } + + // Fix slashes+dotslashes and make lower case first.. + char fn[ MAX_PATH ]; + Q_strncpy( fn, pFileName, sizeof( fn ) ); + Q_RemoveDotSlashes( fn ); +#ifdef _WIN32 + strlwr( fn ); +#endif + + // Split the filename into constituent parts + char basepath[ MAX_PATH ]; + Q_ExtractFilePath( fn, basepath, sizeof( basepath ) ); + char filename[ MAX_PATH ]; + Q_strncpy( filename, fn + Q_strlen( basepath ), sizeof( filename ) ); + + FileNameHandleInternal_t handle; + + m_lock.LockForRead(); + handle.path = m_StringPool.FindStringHandle(basepath); + handle.file = m_StringPool.FindStringHandle(filename); + m_lock.UnlockRead(); + + if ( handle.path == 0 || handle.file == 0 ) + return NULL; + + return *( FileNameHandle_t * )( &handle ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : handle - +// Output : const char +//----------------------------------------------------------------------------- +bool CUtlFilenameSymbolTable::String( const FileNameHandle_t& handle, char *buf, int buflen ) +{ + buf[ 0 ] = 0; + + FileNameHandleInternal_t *internal = ( FileNameHandleInternal_t * )&handle; + if ( !internal ) + { + return false; + } + + m_lock.LockForRead(); + const char *path = m_StringPool.HandleToString(internal->path); + const char *fn = m_StringPool.HandleToString(internal->file); + m_lock.UnlockRead(); + + if ( !path || !fn ) + { + return false; + } + + Q_strncpy( buf, path, buflen ); + Q_strncat( buf, fn, buflen, COPY_ALL_CHARACTERS ); + + return true; +} + +void CUtlFilenameSymbolTable::RemoveAll() +{ + m_StringPool.FreeAll(); +} + +void CUtlFilenameSymbolTable::SpewStrings() +{ + m_lock.LockForRead(); + m_StringPool.SpewStrings(); + m_lock.UnlockRead(); + +}