diff --git a/.appveyor/Build.sh b/.appveyor/Build.sh index 23d3fe07f..a0efff856 100755 --- a/.appveyor/Build.sh +++ b/.appveyor/Build.sh @@ -1,30 +1,30 @@ #!/usr/bin/env bash set -ex -# Creating flatpak directories -mkdir -p "${APPVEYOR_BUILD_FOLDER}/.flatpak/lib" "${APPVEYOR_BUILD_FOLDER}/.flatpak/data" "${APPVEYOR_BUILD_FOLDER}/.flatpak/bin" - +# Configure cd "$APPVEYOR_BUILD_FOLDER/src_rebuild" - ./premake5 gmake2 - cd project_gmake2_linux +# Build for config in debug_x86 release_x86 release_dev_x86 do make config=$config -j$(nproc) done -find ${APPVEYOR_BUILD_FOLDER}/src_rebuild/bin -name 'REDRIVER2*' -exec cp -t ${APPVEYOR_BUILD_FOLDER}/.flatpak/bin {} + +cd ${APPVEYOR_BUILD_FOLDER} + +# Creating flatpak directories +mkdir -p "${APPVEYOR_BUILD_FOLDER}/.flatpak/lib" "${APPVEYOR_BUILD_FOLDER}/.flatpak/data" "${APPVEYOR_BUILD_FOLDER}/.flatpak/bin" +find ${APPVEYOR_BUILD_FOLDER}/src_rebuild/bin/Release -name 'REDRIVER2*' -exec cp -t ${APPVEYOR_BUILD_FOLDER}/.flatpak/bin {} + # Copy missing libraries in the runtime -for lib in libjpeg libopenal libsndio libbsd +for lib in libjpeg libopenal do cp -Lf $(ldd "${APPVEYOR_BUILD_FOLDER}/src_rebuild/bin/Release/REDRIVER2" | awk '/ => / { print $3 }' | grep ${lib}) "${APPVEYOR_BUILD_FOLDER}/.flatpak/lib" done cp -r "${APPVEYOR_BUILD_FOLDER}/data" "${APPVEYOR_BUILD_FOLDER}/.flatpak/" -cd ${APPVEYOR_BUILD_FOLDER} # Editing metadatas with the current version export APPVEYOR_BUILD_DATE=$(date "+%Y-%m-%d") diff --git a/.appveyor/Install.sh b/.appveyor/Install.sh index afe2e9760..7ca639f0b 100755 --- a/.appveyor/Install.sh +++ b/.appveyor/Install.sh @@ -26,7 +26,7 @@ export XDG_DATA_DIRS="/var/lib/flatpak/exports/share:${HOME}/.local/share/flatpa flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo # Adding Platform/SDK for the Linux flatpak release -flatpak --user install flathub org.freedesktop.Platform/x86_64/20.08 -y -flatpak --user install flathub org.freedesktop.Sdk/x86_64/20.08 -y -flatpak --user install flathub org.freedesktop.Sdk.Compat.i386/x86_64/20.08 -y -flatpak --user install flathub org.freedesktop.Sdk.Extension.toolchain-i386/x86_64/20.08 -y +flatpak --user install flathub org.freedesktop.Platform/x86_64/22.08 -y +flatpak --user install flathub org.freedesktop.Sdk/x86_64/22.08 -y +flatpak --user install flathub org.freedesktop.Sdk.Compat.i386/x86_64/22.08 -y +flatpak --user install flathub org.freedesktop.Sdk.Extension.toolchain-i386/x86_64/22.08 -y diff --git a/.flatpak/io.github.opendriver2.Redriver2.appdata.xml b/.flatpak/io.github.opendriver2.Redriver2.appdata.xml index 52950d8fc..c08e84b9b 100644 --- a/.flatpak/io.github.opendriver2.Redriver2.appdata.xml +++ b/.flatpak/io.github.opendriver2.Redriver2.appdata.xml @@ -4,9 +4,9 @@ MIT MIT REDRIVER2 - Driver 2 Playstation game reverse engineering effort + Driver 2 - Back On The Streets / The Wheelman Is Back -

Driver 2 Playstation game reverse engineering effort

+

Reverse-Engineered version of Driver 2. https://opendriver2.github.io

Game diff --git a/.flatpak/ld.so.conf b/.flatpak/ld.so.conf new file mode 100644 index 000000000..76daf6576 --- /dev/null +++ b/.flatpak/ld.so.conf @@ -0,0 +1,5 @@ +# We just make any GL32 extension have higher priority +include /run/flatpak/ld.so.conf.d/app-*-org.freedesktop.Platform.GL32.*.conf +/app/lib32 +/app/lib/i386-linux-gnu +/lib64 \ No newline at end of file diff --git a/.flatpak/start.sh b/.flatpak/start.sh index d268502f7..7d0019d48 100644 --- a/.flatpak/start.sh +++ b/.flatpak/start.sh @@ -13,7 +13,7 @@ function importDefaultData { } if [ ! -d /var/data/DRIVER2 ]; then - zenity --error --no-wrap --text="`printf "DRIVER2 files are missing! Add the folder in:\n ${HOME}/.var/io.github.opendriver2.Redriver2/data"`" + zenity --error --no-wrap --text="`printf "DRIVER2 files are missing! Please provide DRIVER2 folder in:\n ${HOME}/.var/app/io.github.opendriver2.Redriver2/data"`" exit 0 fi diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..f6462a3b3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +FROM fedora:37 +LABEL Description="Build environment" + +ENV HOME /root + +SHELL ["/bin/bash", "-c"] + +RUN dnf update -y && \ + dnf groupinstall -y 'Development Tools' && \ + dnf install -y make gcc gcc-c++ \ + libjpeg-turbo-devel.i686 \ + glibc-devel.i686 \ + SDL2-devel.i686 \ + openal-soft-devel.i686 \ + flatpak flatpak-builder + +ENV APPVEYOR_BUILD_FOLDER=/src + +# Setting XDG_DATA_DIRS environement variable for flatpak +ENV XDG_DATA_DIRS="/var/lib/flatpak/exports/share:${HOME}/.local/share/flatpak/exports/share:$XDG_DATA_DIRS" + +# Adding the flathub repo +RUN flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo + +# Adding Platform/SDK for the Linux flatpak release +RUN flatpak --user install flathub org.freedesktop.Platform/x86_64/22.08 -y +RUN flatpak --user install flathub org.freedesktop.Sdk/x86_64/22.08 -y +RUN flatpak --user install flathub org.freedesktop.Sdk.Compat.i386/x86_64/22.08 -y +RUN flatpak --user install flathub org.freedesktop.Sdk.Extension.toolchain-i386/x86_64/22.08 -y + +WORKDIR /src + +CMD ["/bin/bash"] + +# Building example: +# docker build -t builder/multiarch_build:1.0 -f Dockerfile . + +# Running example: +# docker run -it --privileged=true --rm --name=builder --mount type=bind,source=${PWD},target=/src builder/multiarch_build:1.0 bash diff --git a/PSXToolchain/README.md b/PSXToolchain/README.md index 7e18defa0..53eaf5328 100644 --- a/PSXToolchain/README.md +++ b/PSXToolchain/README.md @@ -1,10 +1,11 @@ # Playstation build toolchain -In order to start building a Playstation version of **REDRIVER2** you'll need to perform following steps: +This is an instruction to build Playstation version of **REDRIVER2** +### Windows + - Install **make** (In powershell `choco install make`) - Put **mipsel-unknown-elf** toolchain to this folder (https://github.com/majenkotech/mipsel-unknown-elf/releases) - - Install **make** - - Obtain **Psy-Q SDK** converted for *latest GCC* + - Put Nugget version of **Psy-Q SDK** - Copy (https://github.com/pcsx-redux/nugget/tree/main/common) to `/PSXToolchain/PsyQ/common` - Execute **psx_build.bat** - Install [mkpsxiso](https://github.com/Lameguy64/mkpsxiso/releases) diff --git a/PSXToolchain/psx_build.bat b/PSXToolchain/psx_build.bat deleted file mode 100644 index bebe8ea64..000000000 --- a/PSXToolchain/psx_build.bat +++ /dev/null @@ -1,25 +0,0 @@ -echo off -cls -set REDRIVER_FOLDER=%cd%\.. - -rem Make a symlink -mklink /J %REDRIVER_FOLDER%\PSXToolchain\GameSRC %REDRIVER_FOLDER%\src_rebuild - -rem Create a virtual drive -SUBST X: %REDRIVER_FOLDER%\PSXToolchain - -set PATH=%PATH%;X:\mipsel-unknown-elf\bin; - -if exist %REDRIVER_FOLDER%\PSXToolchain\CDSrc\0_CD_DATA\ ( - rem Yes -) else ( - mkdir %REDRIVER_FOLDER%\PSXToolchain\CDSrc\0_CD_DATA\ -) - -make - -rem Cleanup -rem del GameSRC -SUBST X: /D - -pause \ No newline at end of file diff --git a/PSXToolchain/psx_build.ps1 b/PSXToolchain/psx_build.ps1 new file mode 100644 index 000000000..c481b4ec9 --- /dev/null +++ b/PSXToolchain/psx_build.ps1 @@ -0,0 +1,33 @@ +$REDRIVER_FOLDER=[string](Get-Location) + "\.." +$cdDataPath = "$($REDRIVER_FOLDER)\PSXToolchain\CDSrc\0_CD_DATA\" + +Write-Host $cdDataPath + +# Make a symlink +$link_exist = Get-Item -Path "$($REDRIVER_FOLDER)\PSXToolchain\GameSRC" -ErrorAction Ignore +if (-Not $link_exist) { + New-Item -ItemType Junction -Path "$($REDRIVER_FOLDER)\PSXToolchain\GameSRC" -Target "$($REDRIVER_FOLDER)\src_rebuild" +} + +# Create a virtual drive +Invoke-Expression "SUBST X: $($REDRIVER_FOLDER)\PSXToolchain" + +# Set tools path +$envPath = $env:PATH +$env:PATH="$($envPath);X:\mipsel-unknown-elf\bin"; + +Write-Host $env:PATH + +$folder_exist = Get-Item -Path $cdDataPath -ErrorAction Ignore +if (-Not $folder_exist) { + New-Item -ItemType Directory -Path "$($REDRIVER_FOLDER)\PSX]Toolchain\CDSrc\0_CD_DATA" +} + +Write-Host "Starting build process..." + +Invoke-Expression "make" + +# Cleanup +Invoke-Expression "SUBST X: /D" + +Read-Host -Prompt "Build completed, press any key" diff --git a/appveyor.yml b/appveyor.yml index 9757870f4..876327b53 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 7.4.{build} +version: 7.8.{build} branches: only: diff --git a/data/DRIVER2/GFX/HQ/digits.tga b/data/DRIVER2/GFX/HQ/digits.tga new file mode 100644 index 000000000..356e01567 Binary files /dev/null and b/data/DRIVER2/GFX/HQ/digits.tga differ diff --git a/data/DRIVER2/GFX/HQ/fefont.fn2 b/data/DRIVER2/GFX/HQ/fefont.fn2 new file mode 100644 index 000000000..9349c4d8e Binary files /dev/null and b/data/DRIVER2/GFX/HQ/fefont.fn2 differ diff --git a/data/DRIVER2/GFX/HQ/fefont.tga b/data/DRIVER2/GFX/HQ/fefont.tga new file mode 100644 index 000000000..89b1bb79a Binary files /dev/null and b/data/DRIVER2/GFX/HQ/fefont.tga differ diff --git a/data/DRIVER2/GFX/HQ/font2.fn2 b/data/DRIVER2/GFX/HQ/font2.fn2 new file mode 100644 index 000000000..313289bec Binary files /dev/null and b/data/DRIVER2/GFX/HQ/font2.fn2 differ diff --git a/data/DRIVER2/GFX/HQ/font2.tga b/data/DRIVER2/GFX/HQ/font2.tga new file mode 100644 index 000000000..1bfe5c967 Binary files /dev/null and b/data/DRIVER2/GFX/HQ/font2.tga differ diff --git a/data/DRIVER2/LANG/EN_GAME.LTXT b/data/DRIVER2/LANG/EN_GAME.LTXT index 3cef42942..cc758c0c9 100644 --- a/data/DRIVER2/LANG/EN_GAME.LTXT +++ b/data/DRIVER2/LANG/EN_GAME.LTXT @@ -53,6 +53,7 @@ Music Volume Film Director Quick Replay Exit +Back Rotation Move Skip cutscene diff --git a/data/DRIVER2/LANG/EN_MISSION.LTXT b/data/DRIVER2/LANG/EN_MISSION.LTXT index 164ea2c98..27feec48b 100644 --- a/data/DRIVER2/LANG/EN_MISSION.LTXT +++ b/data/DRIVER2/LANG/EN_MISSION.LTXT @@ -20,7 +20,7 @@ Back to Jones Tail Jericho Pursue Jericho Escape the Brazilians -Casino getaway +Casino Getaway Beat the train Car bomb Car bomb getaway @@ -30,7 +30,7 @@ Stake Out Steal the keys C4 deal Destroy the yard -Bus crash +Bus Crush Steal the cop car Caine's cash Save Jones @@ -66,7 +66,7 @@ Lenny's getaway Lenny gets it Back in Chicago Vasquez meets Caine -Credits +The End Downtown Wrigleyville Greektown diff --git a/data/DRIVER2/LANG/FR_GAME.LTXT b/data/DRIVER2/LANG/FR_GAME.LTXT index 7c705b489..61db1d7dd 100644 --- a/data/DRIVER2/LANG/FR_GAME.LTXT +++ b/data/DRIVER2/LANG/FR_GAME.LTXT @@ -53,6 +53,7 @@ Volume de la musique Réalisateur de film Replay rapide Sortie +Retour Rotation Déplacement Passer la cinématique diff --git a/data/DRIVER2/LANG/GE_GAME.LTXT b/data/DRIVER2/LANG/GE_GAME.LTXT index a4b654774..40bd0949c 100644 --- a/data/DRIVER2/LANG/GE_GAME.LTXT +++ b/data/DRIVER2/LANG/GE_GAME.LTXT @@ -53,6 +53,7 @@ Musik Filmregisseur Wiederholung Ende +Zurück Rotation Bewegung Zwischensequenz überspringen diff --git a/data/DRIVER2/LANG/IT_GAME.LTXT b/data/DRIVER2/LANG/IT_GAME.LTXT index 743ffa01c..bfe2d350d 100644 --- a/data/DRIVER2/LANG/IT_GAME.LTXT +++ b/data/DRIVER2/LANG/IT_GAME.LTXT @@ -53,6 +53,7 @@ Volume musica Regista video Replay veloce Esci +Indietro Ruota Sposta Salta filmato diff --git a/data/DRIVER2/LANG/SP_GAME.LTXT b/data/DRIVER2/LANG/SP_GAME.LTXT index 15512eb13..397a58d18 100644 --- a/data/DRIVER2/LANG/SP_GAME.LTXT +++ b/data/DRIVER2/LANG/SP_GAME.LTXT @@ -53,6 +53,7 @@ Volumen de m Director de película Repetición rápida Salir +Atrás Rotación Mover Saltear escena diff --git a/data/config.ini b/data/config.ini index e21c80797..1ae3f6a91 100644 --- a/data/config.ini +++ b/data/config.ini @@ -2,25 +2,6 @@ # dataFolder=REDRIVER2 # dataFolder=. -# Game keyboard controls (Psy-X layer PSX mapping) -[kbcontrols_game] -cross=up -square=down -circle=right shift -triangle=space -up=NONE -down=NONE -left=left -right=right -start=escape -select=c -l1=right ctrl -r1=H -l2=A -r2=D -l3=S -r3=return - # Available controller binds (refer to SDL2 game controller) # # Axes: @@ -37,51 +18,46 @@ r3=return # leftshoulder rightshoulder # dpup dpdown dpleft dpright +# Game keyboard controls (Psy-X layer PSX mapping) +[kbcontrols_game] +cross=Up +square=Down +circle=Right Shift +triangle=Space +up= +down= +left=Left +right=Right +start=Escape +select=C +l1=Right Ctrl +r1=H +l2=A +r2=D +l3=S +r3=Return [controls_game] -# cross=a -# square=x -# circle=b -# triangle=y -# up=dpup -# down=dpdown -# left=dpleft -# right=dpright -# start=start -# select=back -# l1=leftshoulder -# r1=rightshoulder -# l2=lefttrigger -# r2=righttrigger -# l3=leftstick -# r3=rightstick -# axis_left_x=leftx -# axis_left_y=lefty -# axis_right_x=rightx -# axis_right_y=righty - -[controls_menu] -# cross=x -# square=x -# circle=b -# triangle=y -# up=dpup -# down=dpdown -# left=dpleft -# right=dpright -# start=start -# select=back -# l1=leftshoulder -# r1=rightshoulder -# l2=lefttrigger -# r2=righttrigger -# l3=leftstick -# r3=rightstick -# axis_left_x=leftx -# axis_left_y=lefty -# axis_right_x=rightx -# axis_right_y=righty - +cross=a +square=x +circle=b +triangle=y +up=dpup +down=dpdown +left=dpleft +right=dpright +start=start +select=back +l1=leftshoulder +r1=rightshoulder +l2=lefttrigger +r2=righttrigger +l3=leftstick +r3=rightstick +axis_left_x=leftx +axis_left_y=lefty +axis_right_x=rightx +axis_right_y=righty [kbcontrols_menu] cross=return @@ -97,31 +73,31 @@ start=w select=H [cdfs] -#image=install/Driver2CD1.bin -mode=1,2,2352 # track, mode, sector size. DO NOT MODIFY +image=install/Driver2CD1.bin +mode=1,2,2352 # track, mode, sector size. DO NOT MODIFY [pad] -pad1device=-1 # player 1 controller device; -1 for automatic assignment -pad2device=-1 # player 2 controller device; -1 for automatic assignment +pad1device=-1 # player 1 controller device; -1 for automatic assignment +pad2device=-1 # player 2 controller device; -1 for automatic assignment [render] windowWidth=1280 windowHeight=720 -fullscreen=0 # enable full screen mode -screenWidth=1280 # screen resolution when fullscreen is on -screenHeight=720 -vsync=1 # Prevents screen tearing in Full screen. Turn it off if you have framerate problems. +fullscreen=0 # enable full screen mode; it takes screen resolution pgxpTextureMapping=1 -bilinearFiltering=1 # "smooth" textures -pgxpZbuffer=1 # full Z-buffer on PSX polygons +bilinearFiltering=1 # "smooth" textures +pgxpZbuffer=1 # full Z-buffer on PSX polygons +vsync=1 [game] -languageId=0 # 0 = ENGLISH; 1 = ITALIAN; 2 = GERMAN; 3 = FRENCH; 4 = SPANISH; -drawDistance=1800 # 441..1800 -fieldOfView=256 # 128..384, 256 is default -disableChicagoBridges=0 # Experimental: also activate AI roads -freeCamera=1 # Press F7 in game to enable +languageId=0 # 0 = ENGLISH; 1 = ITALIAN; 2 = GERMAN; 3 = FRENCH; 4 = SPANISH; +drawDistance=1800 # 441..1800 +dynamicLights=1 # set 1 to enable dynamic street lights +fieldOfView=256 # 128..384, 256 is default +disableChicagoBridges=0 # Experimental: also activate AI roads +freeCamera=1 # Press F7 in game to enable fastLoadingScreens=1 -widescreenOverlays=1 # set 1 to see map, bars and stats aligned to screen corners -driver1music=0 # put Driver 1's MUSIC.BIN as D1MUSIC.BIN to DRIVER2\SOUNDS folder -overrideContent=0 # this enables texture and car model modding \ No newline at end of file +widescreenOverlays=1 # set 1 to see map, bars and stats aligned to screen corners +driver1music=0 # put Driver 1's MUSIC.BIN as D1MUSIC.BIN to DRIVER2\SOUND folder +overrideContent=0 # this enables texture and car model modding +userChases=RacingFreak,Snoopi,Olanov,Vortex,Fireboyd78 \ No newline at end of file diff --git a/dockerbuild.sh b/dockerbuild.sh new file mode 100755 index 000000000..6158a1a17 --- /dev/null +++ b/dockerbuild.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -ex + +docker build -t builder/multiarch_build:1.0 -f Dockerfile . +docker run -it --privileged=true --rm --name=builder --mount type=bind,source=${PWD},target=/src builder/multiarch_build:1.0 ./.appveyor/Build.sh diff --git a/io.github.opendriver.redriver2.yaml b/io.github.opendriver.redriver2.yaml index 2fa0acf17..91a6d90fd 100644 --- a/io.github.opendriver.redriver2.yaml +++ b/io.github.opendriver.redriver2.yaml @@ -1,9 +1,11 @@ ---- app-id: io.github.opendriver2.Redriver2 runtime: org.freedesktop.Platform -runtime-version: '20.08' -rename-icon: 'icon' +runtime-version: &runtime-version '22.08' +x-gl-version: &gl-version '1.4' +x-gl-versions: &gl-versions 22.08;22.08-extra;1.4 sdk: org.freedesktop.Sdk +separate-locales: false +rename-icon: 'icon' command: start.sh finish-args: - "--socket=x11" @@ -14,47 +16,66 @@ finish-args: - "--persist=." - "--allow=multiarch" - "--env=SDL_DYNAMIC_API=/app/lib/i386-linux-gnu/libSDL2-2.0.so.0" + add-extensions: org.freedesktop.Platform.Compat.i386: directory: lib/i386-linux-gnu - version: '20.08' + version: *runtime-version + org.freedesktop.Platform.Compat.i386.Debug: directory: lib/debug/lib/i386-linux-gnu - version: '20.08' + version: *runtime-version no-autodownload: true + org.freedesktop.Platform.GL32: directory: lib/i386-linux-gnu/GL - version: '20.08' + version: *gl-version + versions: *gl-versions subdirectories: true no-autodownload: true autodelete: false add-ld-path: lib - merge-dirs: vulkan/icd.d;glvnd/egl_vendor.d + merge-dirs: vulkan/icd.d;glvnd/egl_vendor.d;OpenCL/vendors;lib/dri;lib/d3d;vulkan/explicit_layer.d;vulkan/implicit_layer.d download-if: active-gl-driver enable-if: active-gl-driver + sdk-extensions: - org.freedesktop.Sdk.Compat.i386 - org.freedesktop.Sdk.Extension.toolchain-i386 + build-options: - prepend-pkg-config-path: "/app/lib32/pkgconfig:/usr/lib/i386-linux-gnu/pkgconfig" - ldflags: "-L/app/lib32" - append-path: "/usr/lib/sdk/toolchain-i386/bin" + prepend-pkg-config-path: /app/lib32/pkgconfig:/usr/lib/i386-linux-gnu/pkgconfig + ldflags: -L/app/lib32 + prepend-path: /usr/lib/sdk/toolchain-i386/bin env: CC: i686-unknown-linux-gnu-gcc CXX: i686-unknown-linux-gnu-g++ - libdir: "/app/lib32" + libdir: /app/lib32 + cleanup: - "/include" + modules: -- name: ld-i386 + +- name: platform-bootstrap buildsystem: simple build-commands: - - mkdir -p /app/lib/i386-linux-gnu /app/lib/debug/lib/i386-linux-gnu - - install -Dm644 -t /app/etc ld.so.conf + - | + set -e + mkdir -p /app/bin + mkdir -p /app/lib/i386-linux-gnu + mkdir -p /app/lib/debug/lib/i386-linux-gnu + mkdir -p /app/lib/i386-linux-gnu/GL + cp /usr/bin/addr2line /app/bin/ + cp /usr/lib/x86_64-linux-gnu/libbfd-*.so /app/lib/ + install -Dm644 -t /app/etc ld.so.conf + mkdir -p /app/links/lib + ln -srv /app/lib /app/links/lib/x86_64-linux-gnu + ln -srv /app/lib32 /app/links/lib/i386-linux-gnu sources: - - type: shell - commands: - - echo "/app/lib32" > ld.so.conf + - type: dir + path: .flatpak + - name: game buildsystem: simple build-commands: diff --git a/src_rebuild/Game/ASM/rnc_2.c b/src_rebuild/Game/ASM/rnc_2.c index 72459ba92..e5a8bfd82 100644 --- a/src_rebuild/Game/ASM/rnc_2.c +++ b/src_rebuild/Game/ASM/rnc_2.c @@ -28,7 +28,7 @@ struct RNCheader */ /*____________________________________________________________________________*/ -short testRNC(unsigned long firstLong) +short testRNC(unsigned int firstLong) { int method = 0; method = (firstLong & 0xFF000000) >> 24; //get low byte @@ -113,8 +113,8 @@ unsigned short get_offset(unsigned char** byteStreamPtr) /*____________________________________________________________________________*/ //RNC2 unpack -int RNCunpack2(unsigned char* packed, unsigned long srcSize, - unsigned char* unpacked, unsigned long dstSize) +int RNCunpack2(unsigned char* packed, unsigned int srcSize, + unsigned char* unpacked, unsigned int dstSize) { unsigned char* src = packed; unsigned char* dst = unpacked; diff --git a/src_rebuild/Game/ASM/rndrasm.c b/src_rebuild/Game/ASM/rndrasm.c index 0bb1c7598..ae1cd7ba5 100644 --- a/src_rebuild/Game/ASM/rndrasm.c +++ b/src_rebuild/Game/ASM/rndrasm.c @@ -2,7 +2,11 @@ #include "C/camera.h" #include "C/draw.h" -extern MATRIX frustrum_matrix; +#ifdef PSX +#pragma GCC optimization ("O3") +#endif + +#define FRUSTUM_THRESHOLD (-80) // [D] [T] void SetCameraVector(void) @@ -32,8 +36,7 @@ void Apply_Inv_CameraMatrix(VECTOR* v) // [D] [T] int Apply_InvCameraMatrixSetTrans(VECTOR_NOPAD* pos) { - VECTOR vfc; - VECTOR vec; + VECTOR vfc, vec; SVECTOR local; gte_stfc(&vfc); @@ -63,8 +66,7 @@ int Apply_InvCameraMatrixSetTrans(VECTOR_NOPAD* pos) // [D] [T] int Apply_InvCameraMatrixAndSetMatrix(VECTOR_NOPAD* pos, MATRIX2* mtx) { - VECTOR vfc; - VECTOR vec; + VECTOR vfc, vec; SVECTOR local; gte_stfc(&vfc); @@ -97,8 +99,9 @@ int Apply_InvCameraMatrixAndSetMatrix(VECTOR_NOPAD* pos, MATRIX2* mtx) // [D] [T] int FrustrumCheck16(PACKED_CELL_OBJECT* pcop, int bounding_sphere) { - VECTOR result; + VECTOR_NOPAD result; SVECTOR local; + int ang; VecSubtract(&local, &pcop->pos, &camera_position); gte_ldsv(&local); @@ -106,10 +109,12 @@ int FrustrumCheck16(PACKED_CELL_OBJECT* pcop, int bounding_sphere) gte_stlvnl(&result); - int ang = frustrum_matrix.t[0] - bounding_sphere; + ang = FRUSTUM_THRESHOLD - bounding_sphere; - if (ang <= result.vx && ang <= result.vy && ang <= result.vz) + if (ang <= result.vx && ang <= result.vy && ang <= result.vz) + { return 0; + } return -1; } @@ -117,8 +122,9 @@ int FrustrumCheck16(PACKED_CELL_OBJECT* pcop, int bounding_sphere) // [D] [T] int FrustrumCheck(VECTOR* pos, int bounding_sphere) { - VECTOR result; + VECTOR_NOPAD result; SVECTOR local; + int ang; VecSubtract(&local, pos, &camera_position); gte_ldsv(&local); @@ -126,10 +132,12 @@ int FrustrumCheck(VECTOR* pos, int bounding_sphere) gte_stlvnl(&result); - int ang = frustrum_matrix.t[0] - bounding_sphere; + ang = FRUSTUM_THRESHOLD - bounding_sphere; - if (ang <= result.vx && ang <= result.vy && ang <= result.vz) + if (ang <= result.vx && ang <= result.vy && ang <= result.vz) + { return 0; + } return -1; } diff --git a/src_rebuild/Game/C/bcollide.c b/src_rebuild/Game/C/bcollide.c index 5a2d90012..3ca46c5e8 100644 --- a/src_rebuild/Game/C/bcollide.c +++ b/src_rebuild/Game/C/bcollide.c @@ -13,6 +13,7 @@ #include "camera.h" #include "objanim.h" #include "system.h" +#include "cutscene.h" extern int gCameraBoxOverlap; @@ -45,8 +46,8 @@ int bcollided2d(CDATA2D *body, int* boxOverlap) } // hmmm, why? - as = RSIN(dtheta & 0x7ff); // rcossin_tbl[(dtheta & 0x7ff) * 2]; - ac = RSIN(dtheta + 1024 & 0x7ff); // rcossin_tbl[(dtheta + 1024 & 0x7ff) * 2]; + as = RSIN(dtheta & 2047); // rcossin_tbl[(dtheta & 0x7ff) * 2]; + ac = RSIN(dtheta + 1024 & 2047); // rcossin_tbl[(dtheta + 1024 & 0x7ff) * 2]; delta.vx = body[0].x.vx - body[1].x.vx; delta.vz = body[0].x.vz - body[1].x.vz; @@ -547,29 +548,19 @@ void DamageCar(CAR_DATA *cp, CDATA2D *cd, CRET2D *collisionResult, int strikeVel // [D] [T] int CarBuildingCollision(CAR_DATA *cp, BUILDING_BOX *building, CELL_OBJECT *cop, int flags) { - int temp; int strikeVel; - int boxDiffY; + int boxDiffY, buildingHeightY; int collided; - short scale; - int chan; int player_id; - SMASHABLE_OBJECT *match; + SMASHABLE_OBJECT* match; SMASHABLE_OBJECT* sip; CAR_COSMETICS* car_cos; MODEL *model; - VECTOR tempwhere; + VECTOR tempwhere, velocity; SVECTOR boxDisp; - VECTOR velocity; - LONGVECTOR4 pointVel; - LONGVECTOR4 reaction; - LONGVECTOR4 lever; - VECTOR LeafPosition; - VECTOR lamp_velocity; + LONGVECTOR4 pointVel, reaction, lever; int debris_colour; - int displacement; - int denom; - int buildingHeightY; + int displacement, denom; #if 0 //def PSX CDATA2D* cd = (CDATA2D*)(u_char*)getScratchAddr(0); @@ -583,423 +574,408 @@ int CarBuildingCollision(CAR_DATA *cp, BUILDING_BOX *building, CELL_OBJECT *cop, #endif model = modelpointers[cop->type]; - player_id = GetPlayerId(cp); - - cd[0].isCameraOrTanner = (cp->controlType == CONTROL_TYPE_TANNERCOLLIDER || cp->controlType == CONTROL_TYPE_CAMERACOLLIDER); - - if (cp->controlType == CONTROL_TYPE_TANNERCOLLIDER) - cd[0].isCameraOrTanner += 2; - - cd[1].isCameraOrTanner = (flags & CollisionCheckFlag_MightBeABarrier) == 0; boxDiffY = cp->hd.oBox.location.vy + building->pos.vy; boxDiffY = ABS(boxDiffY); - collided = 0; - - car_cos = cp->ap.carCos; - // [A] Boat Jump: make player's life easier getting out if (cop->type == 1246 && gCurrentMissionNumber == 35) buildingHeightY = building->height / 5; else buildingHeightY = building->height >> 1; - if (boxDiffY <= buildingHeightY + (cp->hd.oBox.length[1] >> 1) && (cop->pos.vx != OBJECT_SMASHED_MARK) && (model->shape_flags & SHAPE_FLAG_NOCOLLIDE) == 0) + if (boxDiffY > buildingHeightY + (cp->hd.oBox.length[1] >> 1) || + (cop->pos.vx == OBJECT_SMASHED_MARK) || + (model->shape_flags & SHAPE_FLAG_NOCOLLIDE)) { - tempwhere.vx = cp->hd.where.t[0]; - tempwhere.vz = cp->hd.where.t[2]; + return 0; + } + + collided = 0; + car_cos = cp->ap.carCos; - debris_colour = GetDebrisColour(cp); + player_id = GetPlayerId(cp); - cd[0].theta = cp->hd.direction; + cd[0].isCameraOrTanner = (cp->controlType == CONTROL_TYPE_TANNERCOLLIDER || cp->controlType == CONTROL_TYPE_CAMERACOLLIDER); - if (cp->controlType == CONTROL_TYPE_TANNERCOLLIDER) - { - cd[0].x.vx = cp->hd.where.t[0]; - cd[0].x.vy = cp->hd.where.t[1]; - cd[0].x.vz = cp->hd.where.t[2]; - - cd[0].vel.vx = FIXEDH(cp->st.n.linearVelocity[0]); - cd[0].vel.vz = FIXEDH(cp->st.n.linearVelocity[2]); + if (cp->controlType == CONTROL_TYPE_TANNERCOLLIDER) + cd[0].isCameraOrTanner += 2; - cp->hd.where.t[0] += cd[0].vel.vx; - cp->hd.where.t[2] += cd[0].vel.vz; + cd[1].isCameraOrTanner = (flags & CollisionCheckFlag_MightBeABarrier) == 0; - cd[0].length[0] = 90; - cd[0].length[1] = 90; - } - else if (cp->controlType == CONTROL_TYPE_CAMERACOLLIDER) - { - cd[0].x.vx = cp->hd.where.t[0]; - cd[0].x.vy = cp->hd.where.t[1]; - cd[0].x.vz = cp->hd.where.t[2]; + if (cp->controlType == CONTROL_TYPE_TANNERCOLLIDER) + { + cd[0].x.vx = cp->hd.where.t[0]; + cd[0].x.vy = cp->hd.where.t[1]; + cd[0].x.vz = cp->hd.where.t[2]; - cd[0].vel.vx = 0; - cd[0].vel.vz = 0; - cd[0].length[1] = 5; - cd[0].length[0] = gCameraDistance / 2; - } - else - { - gte_SetRotMatrix(&cp->hd.where); - gte_SetTransMatrix(&cp->hd.where); - - boxDisp.vx = -car_cos->cog.vx; - boxDisp.vy = -car_cos->cog.vy; - boxDisp.vz = -car_cos->cog.vz; + cd[0].vel.vx = FIXEDH(cp->st.n.linearVelocity[0]); + cd[0].vel.vz = FIXEDH(cp->st.n.linearVelocity[2]); - gte_ldv0(&boxDisp); + cd[0].length[0] = 90; + cd[0].length[1] = 90; + } + else if (cp->controlType == CONTROL_TYPE_CAMERACOLLIDER) + { + cd[0].x.vx = cp->hd.where.t[0]; + cd[0].x.vy = cp->hd.where.t[1]; + cd[0].x.vz = cp->hd.where.t[2]; + + cd[0].vel.vx = 0; + cd[0].vel.vz = 0; + cd[0].length[1] = 5; + cd[0].length[0] = gCameraDistance / 2; + } + else + { + gte_SetRotMatrix(&cp->hd.where); + gte_SetTransMatrix(&cp->hd.where); - gte_rtv0tr(); + boxDisp.vx = -car_cos->cog.vx; + boxDisp.vy = -car_cos->cog.vy; + boxDisp.vz = -car_cos->cog.vz; - gte_stlvnl(&cd[0].x); + gte_ldv0(&boxDisp); + gte_rtv0tr(); + gte_stlvnl(&cd[0].x); - cd[0].vel.vx = FIXEDH(cp->st.n.linearVelocity[0]); - cd[0].vel.vz = FIXEDH(cp->st.n.linearVelocity[2]); + cd[0].vel.vx = FIXEDH(cp->st.n.linearVelocity[0]); + cd[0].vel.vz = FIXEDH(cp->st.n.linearVelocity[2]); - cp->hd.where.t[0] += cd[0].vel.vx; - cp->hd.where.t[2] += cd[0].vel.vz; + cd[0].length[0] = car_cos->colBox.vz + 15; + cd[0].length[1] = car_cos->colBox.vx + 15; - cd[0].length[0] = car_cos->colBox.vz + 15; - cd[0].length[1] = car_cos->colBox.vx + 15; + if (handlingType[cp->hndType].fourWheelDrive == 1 || cp->hndType == 5) + cd[0].length[1] = (cd[0].length[1] * 13) / 16; + } - if (handlingType[cp->hndType].fourWheelDrive == 1 || cp->hndType == 5) - cd[0].length[1] = (cd[0].length[1] * 13) / 16; - } + cd[0].theta = cp->hd.direction; + cd[0].avel = FIXEDH(cp->st.n.angularVelocity[1]) * 5 >> 5; - cd[0].avel = FIXEDH(cp->st.n.angularVelocity[1]) * 5 >> 5; + tempwhere.vx = cp->hd.where.t[0]; + tempwhere.vz = cp->hd.where.t[2]; - cd[1].x.vx = cp->hd.where.t[0] + (((building->pos.vx - cp->hd.where.t[0]) << 0x10) >> 0x10); - cd[1].x.vz = cp->hd.where.t[2] + (((building->pos.vz - cp->hd.where.t[2]) << 0x10) >> 0x10); + cp->hd.where.t[0] += cd[0].vel.vx; + cp->hd.where.t[2] += cd[0].vel.vz; - cd[1].theta = building->theta; - cd[1].length[0] = building->xsize; - cd[1].length[1] = building->zsize; - cd[1].vel.vx = 0; - cd[1].vel.vz = 0; - cd[1].avel = 0; + cd[1].x.vx = cp->hd.where.t[0] + (short)(building->pos.vx - cp->hd.where.t[0]); + cd[1].x.vz = cp->hd.where.t[2] + (short)(building->pos.vz - cp->hd.where.t[2]); - if (cp->controlType == CONTROL_TYPE_CAMERACOLLIDER) - { - collided = bcollided2d(cd, &gCameraBoxOverlap); - } - else - { - collided = bcollided2d(cd); + cd[1].theta = building->theta; + cd[1].length[0] = building->xsize; + cd[1].length[1] = building->zsize; + cd[1].vel.vx = 0; + cd[1].vel.vz = 0; + cd[1].avel = 0; + + collided = bcollided2d(cd, cp->controlType == CONTROL_TYPE_CAMERACOLLIDER ? &gCameraBoxOverlap : 0); #if defined(COLLISION_DEBUG) && !defined(PSX) - extern int gShowCollisionDebug; - if (gShowCollisionDebug == 1) - { - extern void Debug_AddLine(VECTOR & pointA, VECTOR & pointB, CVECTOR & color); - extern void Debug_AddLineOfs(VECTOR & pointA, VECTOR & pointB, VECTOR & ofs, CVECTOR & color); + extern int gShowCollisionDebug; + if (gShowCollisionDebug == 1) + { + extern void Debug_AddLine(VECTOR & pointA, VECTOR & pointB, CVECTOR & color); + extern void Debug_AddLineOfs(VECTOR & pointA, VECTOR & pointB, VECTOR & ofs, CVECTOR & color); - CVECTOR bbcv = { 0, 0, 250 }; - CVECTOR rrcv = { 250, 0, 0 }; - CVECTOR yycv = { 250, 250, 0 }; + CVECTOR bbcv = { 0, 0, 250 }; + CVECTOR rrcv = { 250, 0, 0 }; + CVECTOR yycv = { 250, 250, 0 }; - // show both box axes - { - VECTOR _zero = { 0 }; - VECTOR b1p = cd[0].x; - VECTOR b2p = cd[1].x; - b2p.vy = b1p.vy; + // show both box axes + { + VECTOR _zero = { 0 }; + VECTOR b1p = cd[0].x; + VECTOR b2p = cd[1].x; + b2p.vy = b1p.vy; - // show position to position - //Debug_AddLine(b1p1, b2p1, yycv); + // show position to position + //Debug_AddLine(b1p1, b2p1, yycv); - VECTOR b1ax[2] = { {0} , {0} }; - b1ax[0].vx = FIXEDH(cd[0].axis[0].vx * cd[0].length[0]); - b1ax[0].vz = FIXEDH(cd[0].axis[0].vz * cd[0].length[0]); - b1ax[1].vx = FIXEDH(cd[0].axis[1].vx * cd[0].length[1]); - b1ax[1].vz = FIXEDH(cd[0].axis[1].vz * cd[0].length[1]); + VECTOR b1ax[2] = { {0} , {0} }; + b1ax[0].vx = FIXEDH(cd[0].axis[0].vx * cd[0].length[0]); + b1ax[0].vz = FIXEDH(cd[0].axis[0].vz * cd[0].length[0]); + b1ax[1].vx = FIXEDH(cd[0].axis[1].vx * cd[0].length[1]); + b1ax[1].vz = FIXEDH(cd[0].axis[1].vz * cd[0].length[1]); - // show axis of body 1 - Debug_AddLineOfs(_zero, b1ax[0], b1p, rrcv); - Debug_AddLineOfs(_zero, b1ax[1], b1p, yycv); + // show axis of body 1 + Debug_AddLineOfs(_zero, b1ax[0], b1p, rrcv); + Debug_AddLineOfs(_zero, b1ax[1], b1p, yycv); - // display 2D box 1 - { - int h = b1ax[0].vy; - VECTOR box_points[4] = { - {b1ax[0].vx - b1ax[1].vx, h, b1ax[0].vz - b1ax[1].vz, 0}, // front left - {b1ax[0].vx + b1ax[1].vx, h, b1ax[0].vz + b1ax[1].vz, 0}, // front right - - {-b1ax[0].vx + b1ax[1].vx, h, -b1ax[0].vz + b1ax[1].vz, 0}, // back right - {-b1ax[0].vx - b1ax[1].vx, h, -b1ax[0].vz - b1ax[1].vz, 0} // back left - }; - - Debug_AddLineOfs(box_points[0], box_points[1], b1p, bbcv); - Debug_AddLineOfs(box_points[1], box_points[2], b1p, bbcv); - Debug_AddLineOfs(box_points[2], box_points[3], b1p, bbcv); - Debug_AddLineOfs(box_points[3], box_points[0], b1p, bbcv); - } + // display 2D box 1 + { + int h = b1ax[0].vy; + VECTOR box_points[4] = { + {b1ax[0].vx - b1ax[1].vx, h, b1ax[0].vz - b1ax[1].vz, 0}, // front left + {b1ax[0].vx + b1ax[1].vx, h, b1ax[0].vz + b1ax[1].vz, 0}, // front right + + {-b1ax[0].vx + b1ax[1].vx, h, -b1ax[0].vz + b1ax[1].vz, 0}, // back right + {-b1ax[0].vx - b1ax[1].vx, h, -b1ax[0].vz - b1ax[1].vz, 0} // back left + }; + + Debug_AddLineOfs(box_points[0], box_points[1], b1p, bbcv); + Debug_AddLineOfs(box_points[1], box_points[2], b1p, bbcv); + Debug_AddLineOfs(box_points[2], box_points[3], b1p, bbcv); + Debug_AddLineOfs(box_points[3], box_points[0], b1p, bbcv); + } - VECTOR b2ax[2] = { {0} , {0} }; - b2ax[0].vx += FIXEDH(cd[1].axis[0].vx * cd[1].length[0]); - b2ax[0].vz += FIXEDH(cd[1].axis[0].vz * cd[1].length[0]); - b2ax[1].vx += FIXEDH(cd[1].axis[1].vx * cd[1].length[1]); - b2ax[1].vz += FIXEDH(cd[1].axis[1].vz * cd[1].length[1]); + VECTOR b2ax[2] = { {0} , {0} }; + b2ax[0].vx += FIXEDH(cd[1].axis[0].vx * cd[1].length[0]); + b2ax[0].vz += FIXEDH(cd[1].axis[0].vz * cd[1].length[0]); + b2ax[1].vx += FIXEDH(cd[1].axis[1].vx * cd[1].length[1]); + b2ax[1].vz += FIXEDH(cd[1].axis[1].vz * cd[1].length[1]); - // show axis of body 2 - Debug_AddLineOfs(_zero, b2ax[0], b2p, rrcv); - Debug_AddLineOfs(_zero, b2ax[1], b2p, yycv); + // show axis of body 2 + Debug_AddLineOfs(_zero, b2ax[0], b2p, rrcv); + Debug_AddLineOfs(_zero, b2ax[1], b2p, yycv); - CVECTOR& collColor = collided ? rrcv : yycv; + CVECTOR& collColor = collided ? rrcv : yycv; - // display 2D box 2 - { - int h = b2ax[0].vy; - VECTOR box_points[4] = { - {b2ax[0].vx - b2ax[1].vx, h, b2ax[0].vz - b2ax[1].vz, 0}, // front left - {b2ax[0].vx + b2ax[1].vx, h, b2ax[0].vz + b2ax[1].vz, 0}, // front right - - {-b2ax[0].vx + b2ax[1].vx, h, -b2ax[0].vz + b2ax[1].vz, 0}, // back right - {-b2ax[0].vx - b2ax[1].vx, h, -b2ax[0].vz - b2ax[1].vz, 0} // back left - }; - - Debug_AddLineOfs(box_points[0], box_points[1], b2p, collColor); - Debug_AddLineOfs(box_points[1], box_points[2], b2p, collColor); - Debug_AddLineOfs(box_points[2], box_points[3], b2p, collColor); - Debug_AddLineOfs(box_points[3], box_points[0], b2p, collColor); - } - } + // display 2D box 2 + { + int h = b2ax[0].vy; + VECTOR box_points[4] = { + {b2ax[0].vx - b2ax[1].vx, h, b2ax[0].vz - b2ax[1].vz, 0}, // front left + {b2ax[0].vx + b2ax[1].vx, h, b2ax[0].vz + b2ax[1].vz, 0}, // front right + + {-b2ax[0].vx + b2ax[1].vx, h, -b2ax[0].vz + b2ax[1].vz, 0}, // back right + {-b2ax[0].vx - b2ax[1].vx, h, -b2ax[0].vz - b2ax[1].vz, 0} // back left + }; + + Debug_AddLineOfs(box_points[0], box_points[1], b2p, collColor); + Debug_AddLineOfs(box_points[1], box_points[2], b2p, collColor); + Debug_AddLineOfs(box_points[2], box_points[3], b2p, collColor); + Debug_AddLineOfs(box_points[3], box_points[0], b2p, collColor); } + } + } #endif - if (collided) - { - bFindCollisionTime(cd, &collisionResult); - bFindCollisionPoint(cd, &collisionResult); + if (cp->controlType != CONTROL_TYPE_CAMERACOLLIDER) + { + if (collided) + { + bFindCollisionTime(cd, &collisionResult); + bFindCollisionPoint(cd, &collisionResult); #if defined(COLLISION_DEBUG) && !defined(PSX) - extern int gShowCollisionDebug; - if(gShowCollisionDebug == 1) - { - extern void Debug_AddLine(VECTOR& pointA, VECTOR& pointB, CVECTOR& color); - extern void Debug_AddLineOfs(VECTOR& pointA, VECTOR& pointB, VECTOR& ofs, CVECTOR& color); + extern int gShowCollisionDebug; + if(gShowCollisionDebug == 1) + { + extern void Debug_AddLine(VECTOR& pointA, VECTOR& pointB, CVECTOR& color); + extern void Debug_AddLineOfs(VECTOR& pointA, VECTOR& pointB, VECTOR& ofs, CVECTOR& color); - CVECTOR bbcv = { 0, 0, 250 }; - CVECTOR rrcv = { 250, 0, 0 }; - CVECTOR yycv = { 250, 250, 0 }; + CVECTOR bbcv = { 0, 0, 250 }; + CVECTOR rrcv = { 250, 0, 0 }; + CVECTOR yycv = { 250, 250, 0 }; - // show collision point and normal - { + // show collision point and normal + { - VECTOR pb = collisionResult.hit; - pb.vy += 16; + VECTOR pb = collisionResult.hit; + pb.vy += 16; - // display collision point - Debug_AddLine(collisionResult.hit, pb, rrcv); + // display collision point + Debug_AddLine(collisionResult.hit, pb, rrcv); - VECTOR nb = collisionResult.hit; - nb.vx += collisionResult.surfNormal.vx / 16; - nb.vy += collisionResult.surfNormal.vy / 16; - nb.vz += collisionResult.surfNormal.vz / 16; + VECTOR nb = collisionResult.hit; + nb.vx += collisionResult.surfNormal.vx / 16; + nb.vy += collisionResult.surfNormal.vy / 16; + nb.vz += collisionResult.surfNormal.vz / 16; - // show collision normal - Debug_AddLine(collisionResult.hit, nb, bbcv); - } + // show collision normal + Debug_AddLine(collisionResult.hit, nb, bbcv); } + } #endif - collisionResult.surfNormal.vx = -collisionResult.surfNormal.vx; - collisionResult.surfNormal.vy = 0; - collisionResult.surfNormal.vz = -collisionResult.surfNormal.vz; + collisionResult.surfNormal.vx = -collisionResult.surfNormal.vx; + collisionResult.surfNormal.vy = 0; + collisionResult.surfNormal.vz = -collisionResult.surfNormal.vz; - collisionResult.hit.vy = cp->hd.where.t[1] + 41; + collisionResult.hit.vy = cp->hd.where.t[1] + 41; - // perform error correction + // perform error correction + if((model->flags2 & MODEL_FLAG_SMASHABLE) == 0 || gInGameCutsceneActive == 0 || cd[0].isCameraOrTanner) + { cp->hd.where.t[0] += FIXEDH(collisionResult.penetration * collisionResult.surfNormal.vx); cp->hd.where.t[2] += FIXEDH(collisionResult.penetration * collisionResult.surfNormal.vz); + } + + lever[0] = collisionResult.hit.vx - cp->hd.where.t[0]; + lever[1] = collisionResult.hit.vy - cp->hd.where.t[1]; + lever[2] = collisionResult.hit.vz - cp->hd.where.t[2]; + + pointVel[0] = FIXEDH(cp->st.n.angularVelocity[1] * lever[2] - cp->st.n.angularVelocity[2] * lever[1]) + cp->st.n.linearVelocity[0]; + pointVel[1] = FIXEDH(cp->st.n.angularVelocity[2] * lever[0] - cp->st.n.angularVelocity[0] * lever[2]) + cp->st.n.linearVelocity[1]; + pointVel[2] = FIXEDH(cp->st.n.angularVelocity[0] * lever[1] - cp->st.n.angularVelocity[1] * lever[0]) + cp->st.n.linearVelocity[2]; + + if (flags & CollisionCheckFlag_IsVegasMovingTrain) // [A] Vegas train velocity - added here + { + pointVel[2] += 700000; + } - lever[0] = collisionResult.hit.vx - cp->hd.where.t[0]; - lever[1] = collisionResult.hit.vy - cp->hd.where.t[1]; - lever[2] = collisionResult.hit.vz - cp->hd.where.t[2]; - - pointVel[0] = FIXEDH(cp->st.n.angularVelocity[1] * lever[2] - cp->st.n.angularVelocity[2] * lever[1]) + cp->st.n.linearVelocity[0]; - pointVel[1] = FIXEDH(cp->st.n.angularVelocity[2] * lever[0] - cp->st.n.angularVelocity[0] * lever[2]) + cp->st.n.linearVelocity[1]; - pointVel[2] = FIXEDH(cp->st.n.angularVelocity[0] * lever[1] - cp->st.n.angularVelocity[1] * lever[0]) + cp->st.n.linearVelocity[2]; + strikeVel = -((pointVel[0] / 256) * (collisionResult.surfNormal.vx / 16) + + (pointVel[1] / 256) * (collisionResult.surfNormal.vy / 16) + + (pointVel[2] / 256) * (collisionResult.surfNormal.vz / 16)); - if (flags & CollisionCheckFlag_IsVegasMovingTrain) // [A] Vegas train velocity - added here + if (strikeVel > 0) + { + if (cp->controlType == CONTROL_TYPE_PLAYER) { - pointVel[2] += 700000; + short scale; + + if (strikeVel < 32) + scale = ((strikeVel << 23) >> 16); + else + scale = 4096; + + if (model->flags2 & MODEL_FLAG_SMASHABLE) + NoteFelony(&felonyData, 7, scale); + else + NoteFelony(&felonyData, 6, scale); } - strikeVel = -((pointVel[0] / 256) * (collisionResult.surfNormal.vx / 16) + - (pointVel[1] / 256) * (collisionResult.surfNormal.vy / 16) + - (pointVel[2] / 256) * (collisionResult.surfNormal.vz / 16)); + collisionResult.hit.vy = -collisionResult.hit.vy; - if (strikeVel > 0) - { - if (cp->controlType == CONTROL_TYPE_PLAYER) - { - if (strikeVel < 32) - scale = ((strikeVel << 23) >> 16); - else - scale = 4096; - - if (model->flags2 & MODEL_FLAG_SMASHABLE) - NoteFelony(&felonyData, 7, scale); - else - NoteFelony(&felonyData, 6, scale); - } + velocity.vx = cp->st.n.linearVelocity[0] / ONE; + velocity.vy = -17; + velocity.vz = cp->st.n.linearVelocity[2] / ONE; - collisionResult.hit.vy = -collisionResult.hit.vy; + debris_colour = GetDebrisColour(cp); - velocity.vx = cp->st.n.linearVelocity[0] / ONE; - velocity.vy = -17; - velocity.vz = cp->st.n.linearVelocity[2] / ONE; + if (model->flags2 & MODEL_FLAG_SMASHABLE) + { + // smash object + damage_object(cop, &velocity); - if (model->flags2 & MODEL_FLAG_SMASHABLE) + // smash object + if ((model->shape_flags & SHAPE_FLAG_TRANS) == 0) { - // smash object - damage_object(cop, &velocity); + int chan; - // smash object - if ((model->shape_flags & SHAPE_FLAG_TRANS) == 0) - { - sip = smashable; - match = sip; + sip = smashable; + match = sip; - while (sip->name != NULL) + while (sip->name != NULL) + { + if (sip->modelIdx == cop->type) { - if (sip->modelIdx == cop->type) - { - match = sip; - break; - } - sip++; + match = sip; + break; } + sip++; + } - chan = GetFreeChannel(); + chan = GetFreeChannel(); - if (NumPlayers > 1 && NoPlayerControl == 0) - SetPlayerOwnsChannel(chan, player_id); + if (NumPlayers > 1 && NoPlayerControl == 0) + SetPlayerOwnsChannel(chan, player_id); - Start3DSoundVolPitch(chan, SOUND_BANK_SFX, match->sound, - collisionResult.hit.vx, -collisionResult.hit.vy, collisionResult.hit.vz, - match->volume, match->pitch + (((velocity.vx ^ velocity.vz) * (collisionResult.hit.vx ^ collisionResult.hit.vz) & 0x3ff) - 0x200)); - } + Start3DSoundVolPitch(chan, SOUND_BANK_SFX, match->sound, + collisionResult.hit.vx, -collisionResult.hit.vy, collisionResult.hit.vz, + match->volume, match->pitch + (((velocity.vx ^ velocity.vz) * (collisionResult.hit.vx ^ collisionResult.hit.vz) & 1023) - 512)); + } - cp->hd.where.t[0] = tempwhere.vx; - cp->hd.where.t[2] = tempwhere.vz; + cp->hd.where.t[0] = tempwhere.vx; + cp->hd.where.t[2] = tempwhere.vz; - collisionResult.hit.vy += 30; + collisionResult.hit.vy += 30; - Setup_Sparks(&collisionResult.hit, &velocity, 10, 0); - Setup_Debris(&collisionResult.hit, &velocity, 5, 0); - Setup_Debris(&collisionResult.hit, &velocity, 5, debris_colour << 0x10); + Setup_Sparks(&collisionResult.hit, &velocity, 10, 0); + Setup_Debris(&collisionResult.hit, &velocity, 5, 0); + Setup_Debris(&collisionResult.hit, &velocity, 5, debris_colour << 0x10); - if (cp->controlType == CONTROL_TYPE_PLAYER) - SetPadVibration(*cp->ai.padid, 3); + if (cp->controlType == CONTROL_TYPE_PLAYER) + SetPadVibration(*cp->ai.padid, 3); - return 0; - } + return 0; + } - // add leaves - if (strikeVel > 0x3600 && cp->hd.wheel_speed + 16000U > 32000) + // add leaves + if (strikeVel > 0x3600 && cp->hd.wheel_speed + 16000U > 32000) + { + if (model->flags2 & MODEL_FLAG_TREE) { - if (model->flags2 & MODEL_FLAG_TREE) - { - LeafPosition.vx = collisionResult.hit.vx; - LeafPosition.vy = -((rand() & 0xfe) + 600) - collisionResult.hit.vy; - LeafPosition.vz = collisionResult.hit.vz; + VECTOR LeafPosition; + LeafPosition.vx = collisionResult.hit.vx; + LeafPosition.vy = -((rand() & 0xfe) + 600) - collisionResult.hit.vy; + LeafPosition.vz = collisionResult.hit.vz; - AddLeaf(&LeafPosition, 3, 1); - } - else + AddLeaf(&LeafPosition, 3, 1); + } + else + { + if (gNight && (model->flags2 & MODEL_FLAG_LAMP)) { - if (gNight && (model->flags2 & MODEL_FLAG_LAMP)) + if (damage_lamp(cop)) { - if (damage_lamp(cop)) - { - ClearMem((char*)&lamp_velocity, sizeof(lamp_velocity)); - - collisionResult.hit.vy -= 730; - Setup_Sparks(&collisionResult.hit, &lamp_velocity, 0x14, 0); - collisionResult.hit.vy += 730; - } + VECTOR lamp_velocity; + lamp_velocity.vx = 0; + lamp_velocity.vy = 16; + lamp_velocity.vz = 0; + + collisionResult.hit.vy -= 730; + Setup_Sparks(&collisionResult.hit, &lamp_velocity, 20, 0); + collisionResult.hit.vy += 730; } - - velocity.vy -= 20; - collisionResult.hit.vy += 30; - - Setup_Sparks(&collisionResult.hit, &velocity, 4, 0); - - collisionResult.hit.vy -= 30; - velocity.vy += 20; } - if (strikeVel > 0x1b000) - { - Setup_Debris(&collisionResult.hit, &velocity, 6, debris_colour << 0x10); - - if(cp->controlType == CONTROL_TYPE_PLAYER) - SetPadVibration(*cp->ai.padid, 1); - } - } - - DamageCar(cp, cd, &collisionResult, strikeVel); - - displacement = FIXEDH(lever[0] * collisionResult.surfNormal.vx + lever[1] * collisionResult.surfNormal.vy + lever[2] * collisionResult.surfNormal.vz); - displacement = FIXEDH(((lever[0] * lever[0] + lever[2] * lever[2]) - displacement * displacement) * car_cos->twistRateY) + 4096; - - if (strikeVel < 0x7f001) - denom = (strikeVel * 4096) / displacement; - else - denom = (strikeVel / displacement) * 4096; - - denom /= 64; + velocity.vy -= 20; + collisionResult.hit.vy += 30; - reaction[0] = denom * (collisionResult.surfNormal.vx / 64); - reaction[1] = denom * (collisionResult.surfNormal.vy / 64); - reaction[2] = denom * (collisionResult.surfNormal.vz / 64); + Setup_Sparks(&collisionResult.hit, &velocity, 4, 0); - cp->hd.aacc[1] += FIXEDH(lever[2] * reaction[0]) - FIXEDH(lever[0] * reaction[2]); + collisionResult.hit.vy -= 30; + velocity.vy += 20; + } - // angular impulse calculation and modifiers - if (cp->controlType != CONTROL_TYPE_LEAD_AI) + if (strikeVel > 0x1b000) { - temp = FIXEDH(lever[1] * reaction[2]); - - if (cp->controlType == CONTROL_TYPE_PURSUER_AI) - temp >>= 1; + Setup_Debris(&collisionResult.hit, &velocity, 6, debris_colour << 0x10); - cp->hd.aacc[0] += temp; + if(cp->controlType == CONTROL_TYPE_PLAYER) + SetPadVibration(*cp->ai.padid, 1); + } + } - temp = FIXEDH(lever[2] * reaction[1]); + DamageCar(cp, cd, &collisionResult, strikeVel); - if (cp->controlType == CONTROL_TYPE_PURSUER_AI) - temp >>= 1; - - cp->hd.aacc[0] -= temp; + displacement = FIXEDH(lever[0] * collisionResult.surfNormal.vx + lever[1] * collisionResult.surfNormal.vy + lever[2] * collisionResult.surfNormal.vz); + displacement = FIXEDH(((lever[0] * lever[0] + lever[2] * lever[2]) - displacement * displacement) * car_cos->twistRateY) + 4096; - temp = FIXEDH(lever[0] * reaction[1]); + if (strikeVel < 0x7f001) + denom = (strikeVel * 4096) / displacement; + else + denom = (strikeVel / displacement) * 4096; - if (cp->controlType == CONTROL_TYPE_PURSUER_AI) - temp >>= 1; + denom /= 64; - cp->hd.aacc[2] += temp; + reaction[0] = denom * (collisionResult.surfNormal.vx / 64); + reaction[1] = denom * (collisionResult.surfNormal.vy / 64); + reaction[2] = denom * (collisionResult.surfNormal.vz / 64); - temp = FIXEDH(lever[1] * reaction[0]); + cp->hd.aacc[1] += FIXEDH(lever[2] * reaction[0]) - FIXEDH(lever[0] * reaction[2]); - if (cp->controlType == CONTROL_TYPE_PURSUER_AI) - temp >>= 1; - - cp->hd.aacc[2] -= temp; + // angular impulse calculation and modifiers + if (cp->controlType != CONTROL_TYPE_LEAD_AI) + { + int reduction; + reduction = (cp->controlType == CONTROL_TYPE_PURSUER_AI); - cp->st.n.linearVelocity[1] += reaction[1]; - } + cp->hd.aacc[0] += FIXEDH(lever[1] * reaction[2]) >> reduction; + cp->hd.aacc[0] -= FIXEDH(lever[2] * reaction[1]) >> reduction; + cp->hd.aacc[2] += FIXEDH(lever[0] * reaction[1]) >> reduction; + cp->hd.aacc[2] -= FIXEDH(lever[1] * reaction[0]) >> reduction; - cp->st.n.linearVelocity[0] += reaction[0]; - cp->st.n.linearVelocity[2] += reaction[2]; + cp->st.n.linearVelocity[1] += reaction[1]; } - } - cp->hd.where.t[0] -= FIXEDH(cp->st.n.linearVelocity[0]); - cp->hd.where.t[2] -= FIXEDH(cp->st.n.linearVelocity[2]); + cp->st.n.linearVelocity[0] += reaction[0]; + cp->st.n.linearVelocity[2] += reaction[2]; + } } + + cp->hd.where.t[0] -= FIXEDH(cp->st.n.linearVelocity[0]); + cp->hd.where.t[2] -= FIXEDH(cp->st.n.linearVelocity[2]); } return collided; diff --git a/src_rebuild/Game/C/camera.c b/src_rebuild/Game/C/camera.c index 2dfc3a077..26012cebf 100644 --- a/src_rebuild/Game/C/camera.c +++ b/src_rebuild/Game/C/camera.c @@ -286,9 +286,7 @@ int CameraCollisionCheck(void) if (gCameraDistance > 0) { - ppco = GetFirstPackedCop(cellx, cellz, &ci, 0, cellLevel); - - while (ppco) + for (ppco = GetFirstPackedCop(cellx, cellz, &ci, 0, cellLevel); ppco; ppco = GetNextPackedCop(&ci)) { int type = (ppco->value >> 6) | ((ppco->pos.vy & 1) << 10); @@ -349,7 +347,6 @@ int CameraCollisionCheck(void) } } } - ppco = GetNextPackedCop(&ci); } } count++; @@ -592,9 +589,9 @@ void PlaceCameraInCar(PLAYER *lp, int BumperCam) // [A] handle REDRIVER2 dedicated look back button if ((paddCamera & CAMERA_PAD_LOOK_BACK) == CAMERA_PAD_LOOK_BACK || (paddCamera & CAMERA_PAD_LOOK_BACK_DED)) - camera_angle.vy = 2048 - baseDir & 0xfff; + camera_angle.vy = 2048 - baseDir & 4095; else - camera_angle.vy = (lp->headPos >> 16) - baseDir & 0xfff; + camera_angle.vy = (lp->headPos >> 16) - baseDir & 4095; if (cp) { @@ -632,13 +629,18 @@ void PlaceCameraInCar(PLAYER *lp, int BumperCam) } else { + LPPEDESTRIAN pPlayerPed; + pPlayerPed = lp->pPed; + camera_angle.vx = -tannerLookAngle.vx; - // pedestrian camera is much simpler BuildWorldMatrix(); - viewer_position.vy += lp->pPed->head_pos - 25; - //viewer_position.vz += 45; + if (pPlayerPed) + { + // apply pedestrian bobbing + viewer_position.vy += pPlayerPed->head_pos - 25; + } angle = -baseDir; @@ -702,7 +704,7 @@ void PlaceCameraAtLocation(PLAYER* lp, int zoom) d = PointAtTarget(&lp->cameraPos, &temp, &camera_angle); - if (d > 16000) + if (d > VIEW_DRAW_DISTANCE) { lp->cameraView = 0; return; diff --git a/src_rebuild/Game/C/camera.h b/src_rebuild/Game/C/camera.h index bb40847f7..875dc8dca 100644 --- a/src_rebuild/Game/C/camera.h +++ b/src_rebuild/Game/C/camera.h @@ -3,6 +3,12 @@ #define CAMERA_COLLIDER_CARID (MAX_CARS+1) +#ifdef PSX +#define VIEW_DRAW_DISTANCE 16000 +#else +#define VIEW_DRAW_DISTANCE 22000 +#endif + extern VECTOR camera_position; extern char old_camera_change; extern char camera_change; diff --git a/src_rebuild/Game/C/cars.c b/src_rebuild/Game/C/cars.c index e1aebfc91..5e539056f 100644 --- a/src_rebuild/Game/C/cars.c +++ b/src_rebuild/Game/C/cars.c @@ -26,11 +26,7 @@ struct plotCarGlobals OTTYPE* ot; u_int intensity; u_short* pciv_clut; - u_int ShineyTPageASL16; - u_int ShineyClutASL16; u_char* damageLevel; - u_char* shineyTable; - int ghost; }; @@ -41,10 +37,24 @@ struct plotCarGlobals #endif MATRIX light_matrix = -{ { { 4096, 0, 0 }, { 0, 0, 0 }, { 0, 0, 0 } }, { 0, 0, 0 } }; +{ + { + { 4096, 0, 0 }, + { 0, 0, 0 }, + { 0, 0, 0 } + }, + { 0, 0, 0 } +}; MATRIX colour_matrix = -{ { { 4032, 0, 0 }, { 3936, 0, 0 }, { 3520, 0, 0 } }, { 0, 0, 0 } }; +{ + { + { 4032, 0, 0 }, + { 3936, 0, 0 }, + { 3520, 0, 0 } + }, + { 0, 0, 0 } +}; // PHYSICS CAR_DATA car_data[MAX_CARS + 2]; // all cars + Tanner cbox + Camera cbox @@ -56,9 +66,9 @@ MODEL* gCleanWheelModelPtr; MODEL* gFastWheelModelPtr; MODEL* gDamWheelModelPtr; -// active carsg +// active cars CAR_DATA* active_car_list[MAX_CARS]; -BOUND_BOX bbox[MAX_CARS]; + u_char lightsOnDelay[MAX_CARS]; short FrontWheelRotation[MAX_CARS]; // offset 0x0 short BackWheelRotation[MAX_CARS]; // offset 0x30 @@ -250,7 +260,7 @@ void plotCarPolyGT3(int numTris, CAR_POLY *src, SVECTOR *vlist, SVECTOR *nlist, ofse = pg->damageLevel[src->originalindex]; - *(u_int*)&prim->u0 = (src->clut_uv0 & 0xffffU | pg->pciv_clut[palette + (src->clut_uv0 >> 0x10)] << 0x10) + ofse; + *(u_int*)&prim->u0 = pg->pciv_clut[(src->clut_uv0 >> 0x10) + palette] << 0x10 | (src->clut_uv0 & 0xffff) + ofse; *(u_int*)&prim->u1 = src->tpage_uv1 + ofse; *(u_int*)&prim->u2 = src->uv3_uv2 + ofse; @@ -269,6 +279,95 @@ void plotCarPolyGT3(int numTris, CAR_POLY *src, SVECTOR *vlist, SVECTOR *nlist, pg->primptr = (unsigned char*)prim; } + +#ifdef DYNAMIC_LIGHTING +void plotCarPolyGT3Lit(int numTris, CAR_POLY* src, SVECTOR* vlist, SVECTOR* nlist, plotCarGlobals* pg, int palette) +{ + int Z; + int otz; + SVECTOR* v2; + SVECTOR* v1; + SVECTOR* v0; + u_int indices; + POLY_GT3* prim; + u_int r0, r1, r2; + int ofse; + + prim = (POLY_GT3*)pg->primptr; + + int GT3rgb = pg->intensity | 0x34000000; + gte_ldrgb(>3rgb); + + while (numTris > 0) + { + indices = src->vindices; + + v0 = vlist + (indices & 0xff); + v1 = vlist + (indices >> 8 & 0xff); + v2 = vlist + (indices >> 16 & 0xff); + + gte_ldv3(v0, v1, v2); + + gte_rtpt(); + gte_nclip(); + + gte_stopz(&Z); + + gte_avsz3(); + + gte_stotz(&otz); + + if (Z > -1 && otz > 0) + { + indices = src->nindices; + + r0 = (u_int)(ushort)nlist[indices & 0xff].pad; + r1 = (u_int)(ushort)nlist[indices >> 8 & 0xff].pad; + r2 = (u_int)(ushort)nlist[indices >> 16 & 0xff].pad; + + *(u_int*)&prim->r0 = (r0 & 0xff) << 0x10 | r0; + *(u_int*)&prim->r1 = (r1 & 0xff) << 0x10 | r1; + *(u_int*)&prim->r2 = (r2 & 0xff) << 0x10 | r2; + + ofse = pg->damageLevel[src->originalindex]; + + *(u_int*)&prim->u0 = pg->pciv_clut[(src->clut_uv0 >> 0x10) + palette] << 0x10 | (src->clut_uv0 & 0xffff) + ofse; + *(u_int*)&prim->u1 = src->tpage_uv1 + ofse; + *(u_int*)&prim->u2 = src->uv3_uv2 + ofse; + + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + + SVECTOR tmpPos; + gte_ldv0(v0); + gte_rtps(); + gte_stsv(&tmpPos); + GetDLightLevel(&tmpPos, (u_int*)&prim->r0); + + gte_ldv0(v1); + gte_rtps(); + gte_stsv(&tmpPos); + GetDLightLevel(&tmpPos, (u_int*)&prim->r1); + + gte_ldv0(v2); + gte_rtps(); + gte_stsv(&tmpPos); + GetDLightLevel(&tmpPos, (u_int*)&prim->r2); + + setPolyGT3(prim); + addPrim(pg->ot + (otz >> 1), prim); + + prim++; + } + + src++; + numTris--; + } + + pg->primptr = (unsigned char*)prim; +} +#endif // DYNAMIC_LIGHTING + + // [D] [T] void plotCarPolyGT3nolight(int numTris, CAR_POLY *src, SVECTOR *vlist, plotCarGlobals *pg, int palette) { @@ -298,11 +397,9 @@ void plotCarPolyGT3nolight(int numTris, CAR_POLY *src, SVECTOR *vlist, plotCarGl gte_rtpt(); gte_nclip(); - gte_stopz(&Z); gte_avsz3(); - gte_stotz(&otz); if (Z > -1 && otz > 0) @@ -311,7 +408,7 @@ void plotCarPolyGT3nolight(int numTris, CAR_POLY *src, SVECTOR *vlist, plotCarGl ofse = pg->damageLevel[src->originalindex]; - *(u_int*)&prim->u0 = (src->clut_uv0 & 0xffffU | pg->pciv_clut[palette + (src->clut_uv0 >> 0x10)] << 0x10) + ofse; + *(u_int*)&prim->u0 = pg->pciv_clut[(src->clut_uv0 >> 0x10) + palette] << 0x10 | (src->clut_uv0 & 0xffff) + ofse; *(u_int*)&prim->u1 = src->tpage_uv1 + ofse; *(u_int*)&prim->u2 = src->uv3_uv2 + ofse; @@ -342,9 +439,9 @@ void setupLightingMatrices(void) gte_SetColorMatrix(&colour_matrix); gte_SetLightMatrix(&light_matrix); - if (gTimeOfDay == 3) + if (gTimeOfDay == TIME_NIGHT) { - gte_SetBackColor(64, 64, 64); + gte_SetBackColor(48, 48, 48); } else { @@ -362,35 +459,31 @@ void restoreLightingMatrices(void) // [D] [T] void ComputeCarLightingLevels(CAR_DATA* cp, char detail) { - MATRIX& scratchPadMat = *(MATRIX*)((u_char*)getScratchAddr(0) + 0x344); +#ifdef PSX + MATRIX& scratchPadMat = *(MATRIX*)((u_char*)getScratchAddr(0) + 0x100); +#else + MATRIX scratchPadMat; +#endif int doLight; - int orW; - int orY; + int orW, orY; MODEL* model; - int num_norms; - int count; + int num_norms, count; SVECTOR* ppads; SVECTOR* norms; - SVECTOR lightsourcevector; - SVECTOR colour; - CVECTOR c0; - CVECTOR c1; - CVECTOR c2; + SVECTOR colour, lightsourcevector; + CVECTOR c0, c1, c2; u_int GT3rgb; - if (gTimeOfDay > -1) + if (gTimeOfDay == TIME_NIGHT) { - if (gTimeOfDay < 3) - { - lightsourcevector = day_vectors[GameLevel]; - colour = day_colours[GameLevel]; - } - else if (gTimeOfDay == 3) - { - lightsourcevector = night_vectors[GameLevel]; - colour = night_colours[GameLevel]; - } + lightsourcevector = night_vectors[GameLevel]; + colour = night_colours[GameLevel]; + } + else + { + lightsourcevector = day_vectors[GameLevel]; + colour = day_colours[GameLevel]; } InvertMatrix(&cp->hd.where, &scratchPadMat); @@ -398,71 +491,58 @@ void ComputeCarLightingLevels(CAR_DATA* cp, char detail) gte_ldv0(&lightsourcevector); gte_rtv0(); - gte_stsv(light_matrix.m[0]); - doLight = 0; - colour_matrix.m[0][0] = colour.vx; colour_matrix.m[1][0] = colour.vy; colour_matrix.m[2][0] = colour.vz; - if (gTimeOfDay != 3) - { - orY = cp->st.n.orientation[1] - cp->ap.qy; - - if (orY < 1) - orY = cp->ap.qy - cp->st.n.orientation[1]; - - orW = cp->st.n.orientation[3] - cp->ap.qw; + orY = ABS(cp->st.n.orientation[1] - cp->ap.qy); + orW = ABS(cp->st.n.orientation[3] - cp->ap.qw); - if (orW < 1) - orW = cp->ap.qw - cp->st.n.orientation[3]; + doLight = 0; - if ((orY + orW > 200) || (cp->lowDetail != (detail | lightning))) - doLight = 1; + if ((orY + orW > 200) || (cp->lowDetail != (detail | lightning))) + doLight = 1; - if ((gTimeOfDay == 0 || gTimeOfDay == 2) && (cp->id & 0xf) == (CameraCnt & 0xfU)) - doLight = 1; + if ((M_BIT(gTimeOfDay) & (M_BIT(TIME_DAWN) | M_BIT(TIME_DUSK))) && (cp->id & 15) == (CameraCnt & 15)) + doLight = 1; + if (doLight) + { setupLightingMatrices(); - if (doLight) - { - GT3rgb = combointensity & 0xffffffU | 0x34000000; - gte_ldrgb(>3rgb); - - cp->ap.qy = cp->st.n.orientation[1]; - cp->ap.qw = cp->st.n.orientation[3]; - cp->lowDetail = detail | lightning; - - if (detail == 0) - model = gCarLowModelPtr[cp->ap.model]; - else - model = gCarCleanModelPtr[cp->ap.model]; + GT3rgb = combointensity & 0xffffffU | 0x34000000; + gte_ldrgb(>3rgb); - num_norms = model->num_point_normals / 3; - norms = (SVECTOR*)model->point_normals; + cp->ap.qy = cp->st.n.orientation[1]; + cp->ap.qw = cp->st.n.orientation[3]; + cp->lowDetail = detail | lightning; - ppads = gTempCarVertDump[cp->id]; - count = num_norms;// +1; + if (detail == 0) + model = gCarLowModelPtr[cp->ap.model]; + else + model = gCarCleanModelPtr[cp->ap.model]; - while (count >= 0) - { - gte_ldv3(&norms[0], &norms[1], &norms[2]); + num_norms = model->num_point_normals / 3; + norms = (SVECTOR*)model->point_normals; - gte_ncct(); + ppads = gTempCarVertDump[cp->id]; + count = num_norms;// +1; - gte_strgb3(&c0, &c1, &c2); + while (count >= 0) + { + gte_ldv3(&norms[0], &norms[1], &norms[2]); + gte_ncct(); + gte_strgb3(&c0, &c1, &c2); - ppads[0].pad = *(short*)&c0; - ppads[1].pad = *(short*)&c1; - ppads[2].pad = *(short*)&c2; + ppads[0].pad = *(short*)&c0; + ppads[1].pad = *(short*)&c1; + ppads[2].pad = *(short*)&c2; - count--; - norms += 3; - ppads += 3; - } + count--; + norms += 3; + ppads += 3; } restoreLightingMatrices(); @@ -487,16 +567,16 @@ void DrawWheelObject(MODEL* model, SVECTOR* verts, int transparent, int wheelnum poly = (POLY_FT4*)current->primptr; clut = texture_cluts[src->texture_set][src->texture_id]; - tpage = texture_pages[src->texture_set]; + tpage = texture_pages[src->texture_set] | 0x20; if (gTimeOfDay > -1) { - if (gTimeOfDay < 3) + if (gTimeOfDay < TIME_NIGHT) { bright = combointensity & 0xffffffU | 0x2c000000; dim = (combointensity & 0xfcfcfcU) >> 2 | 0x2c000000; } - else if (gTimeOfDay == 3) + else if (gTimeOfDay == TIME_NIGHT) { combo = (combointensity & 0xffU) / 3; combo = combo << 0x10 | combo << 8 | combo; @@ -541,19 +621,12 @@ void DrawWheelObject(MODEL* model, SVECTOR* verts, int transparent, int wheelnum gte_stsxy3(&poly->x1, &poly->x3, &poly->x2); - poly->u0 = src->uv0.u; - poly->v0 = src->uv0.v; + *(u_short*)&poly->u0 = *(u_short*)&src->uv0; + *(u_short*)&poly->u1 = *(u_short*)&src->uv1; + *(u_short*)&poly->u2 = *(u_short*)&src->uv3; + *(u_short*)&poly->u3 = *(u_short*)&src->uv2; poly->clut = clut; - - poly->u1 = src->uv1.u; - poly->v1 = src->uv1.v; - poly->tpage = tpage | 0x20; - - poly->u2 = src->uv3.u; - poly->v2 = src->uv3.v; - - poly->u3 = src->uv2.u; - poly->v3 = src->uv2.v; + poly->tpage = tpage; poly++; } @@ -566,27 +639,25 @@ void DrawWheelObject(MODEL* model, SVECTOR* verts, int transparent, int wheelnum void DrawCarWheels(CAR_DATA *cp, MATRIX *RearMatrix, VECTOR *pos, int zclip) { short wheelSize; - int FW1z; - int FW2z; - int BW1z; - int BW2z; - int FrontWheelIncrement; - int BackWheelIncrement; + int FW1z, FW2z; + int BW1z, BW2z; + int FrontWheelIncrement, BackWheelIncrement; int sizeScale; int wheelnum; SVECTOR* VertPtr; - MODEL* model; SVECTOR* wheelDisp; WHEEL* wheel; int car_id; - MODEL *WheelModelBack; - MODEL *WheelModelFront; + MODEL* WheelModelBack; + MODEL* WheelModelFront; + MODEL* model; -#if 0 //def PSX +#ifdef PSX MATRIX& FrontMatrix = *(MATRIX*)(u_char*)getScratchAddr(0); MATRIX& SteerMatrix = *(MATRIX*)((u_char*)getScratchAddr(0) + sizeof(MATRIX)); VECTOR& WheelPos = *(VECTOR*)((u_char*)getScratchAddr(0) + sizeof(MATRIX) * 2); SVECTOR& sWheelPos = *(SVECTOR*)((u_char*)getScratchAddr(0) + sizeof(MATRIX) * 2 + sizeof(VECTOR)); + static_assert(sizeof(MATRIX) * 2 + sizeof(VECTOR) + sizeof(SVECTOR) * 25 < 1024 - sizeof(_pct), "Scratchpad overflow"); #else MATRIX FrontMatrix; MATRIX SteerMatrix; @@ -803,8 +874,8 @@ void PlayerCarFX(CAR_DATA *cp) // [D] [T] void plotNewCarModel(CAR_MODEL* car, int palette) { -#if 0 //def PSX - plotCarGlobals& _pg = *(plotCarGlobals*)(u_char*)getScratchAddr(0); +#ifdef PSX + plotCarGlobals& _pg = *(plotCarGlobals*)((u_char*)getScratchAddr(0) + 1024 - sizeof(plotCarGlobals) - sizeof(_pct)); #else plotCarGlobals _pg; #endif @@ -820,9 +891,9 @@ void plotNewCarModel(CAR_MODEL* car, int palette) if (gTimeOfDay > -1) { - if (gTimeOfDay < 3) + if (gTimeOfDay < TIME_NIGHT) lightlevel = combointensity | 0x30000000; - else if (gTimeOfDay == 3) + else if (gTimeOfDay == TIME_NIGHT) lightlevel = 0x302a2a2a; } @@ -852,15 +923,23 @@ void plotNewCarModel(CAR_MODEL* car, int palette) // draw car body _pg.ot = (OTTYPE*)(current->ot + 4); - if (gTimeOfDay == 3) + if (gTimeOfDay == TIME_NIGHT) { _pg.intensity = (combointensity & 0xfcfcf0U) >> 2; +#ifdef DYNAMIC_LIGHTING + (gEnableDlights ? plotCarPolyGT3Lit : plotCarPolyGT3)(car->numGT3, car->pGT3, car->vlist, car->nlist, &_pg, palette); +#else plotCarPolyGT3nolight(car->numGT3, car->pGT3, car->vlist, &_pg, palette); +#endif // DYNAMIC_LIGHTING } else { _pg.intensity = combointensity & 0xffffff; +#ifdef DYNAMIC_LIGHTING + (gEnableDlights ? plotCarPolyGT3Lit : plotCarPolyGT3)(car->numGT3, car->pGT3, car->vlist, car->nlist, &_pg, palette); +#else plotCarPolyGT3(car->numGT3, car->pGT3, car->vlist, car->nlist, &_pg, palette); +#endif } current->primptr = (char*)_pg.primptr; @@ -892,7 +971,8 @@ void buildNewCars(void) // [D] [T] void buildNewCarFromModel(CAR_MODEL *car, MODEL *model, int first) { - u_char ptype, clut; + ushort clut; + u_char ptype, carid; u_char *polyList; CAR_POLY *cp; @@ -1001,12 +1081,14 @@ void buildNewCarFromModel(CAR_MODEL *car, MODEL *model, int first) { POLYGT3* pgt3 = (POLYGT3*)polyList; - clut = GetCarPalIndex(pgt3->texture_set); - civ_clut[clut][pgt3->texture_id][0] = texture_cluts[pgt3->texture_set][pgt3->texture_id]; + carid = GetCarPalIndex(pgt3->texture_set); + clut = (carid - 1) * 6 * 32 + pgt3->texture_id * 6; + + civ_clut[carid][pgt3->texture_id][0] = texture_cluts[pgt3->texture_set][pgt3->texture_id]; cp->vindices = M_INT_4R(pgt3->v0, pgt3->v1, pgt3->v2, 0); cp->nindices = M_INT_4R(pgt3->n0, pgt3->n1, pgt3->n2, 0); - cp->clut_uv0 = M_INT_2((clut * 384 + pgt3->texture_id * 12 - 384) >> 1, * (ushort*)&pgt3->uv0); + cp->clut_uv0 = M_INT_2(clut, *(ushort*)&pgt3->uv0); cp->tpage_uv1 = M_INT_2(texture_pages[pgt3->texture_set], *(ushort *)&pgt3->uv1); cp->uv3_uv2 = *(ushort *)&pgt3->uv2; cp->originalindex = i; @@ -1019,12 +1101,14 @@ void buildNewCarFromModel(CAR_MODEL *car, MODEL *model, int first) { POLYGT4* pgt4 = (POLYGT4*)polyList; - clut = GetCarPalIndex(pgt4->texture_set); - civ_clut[clut][pgt4->texture_id][0] = texture_cluts[pgt4->texture_set][pgt4->texture_id]; + carid = GetCarPalIndex(pgt4->texture_set); + clut = (carid - 1) * 6 * 32 + pgt4->texture_id * 6; + + civ_clut[carid][pgt4->texture_id][0] = texture_cluts[pgt4->texture_set][pgt4->texture_id]; cp->vindices = M_INT_4R(pgt4->v0, pgt4->v1, pgt4->v2, 0); cp->nindices = M_INT_4R(pgt4->n0, pgt4->n1, pgt4->n2, 0); - cp->clut_uv0 = M_INT_2((clut * 384 + pgt4->texture_id * 12 - 384) >> 1, *(ushort*)&pgt4->uv0); + cp->clut_uv0 = M_INT_2(clut, *(ushort*)&pgt4->uv0); cp->tpage_uv1 = M_INT_2(texture_pages[pgt4->texture_set], *(ushort*)&pgt4->uv1); cp->uv3_uv2 = *(ushort*)&pgt4->uv2; cp->originalindex = i; @@ -1033,7 +1117,7 @@ void buildNewCarFromModel(CAR_MODEL *car, MODEL *model, int first) cp->vindices = M_INT_4R(pgt4->v0, pgt4->v2, pgt4->v3, 0); cp->nindices = M_INT_4R(pgt4->n0, pgt4->n2, pgt4->n3, 0); - cp->clut_uv0 = M_INT_2((clut * 384 + pgt4->texture_id * 12 - 384) >> 1, *(ushort*)&pgt4->uv0); + cp->clut_uv0 = M_INT_2(clut, *(ushort*)&pgt4->uv0); cp->tpage_uv1 = M_INT_2(texture_pages[pgt4->texture_set], *(ushort *)&pgt4->uv2); cp->uv3_uv2 = *(ushort *)&pgt4->uv3; cp->originalindex = i; @@ -1158,7 +1242,7 @@ void MangleWheelModels(void) src++; } - } while (++i < 3); + } // HACK: Show clean model only in Rio. //if (GameLevel == 3) @@ -1192,24 +1276,23 @@ void ProcessPalletLump(char *lump_ptr, int lump_size) texnum = buffPtr[1]; tpageindex = buffPtr[2]; clut_number = buffPtr[3]; + buffPtr += 4; if (clut_number == -1) { // store clut - LoadImage(&clutpos, (u_long*)(buffPtr + 4)); + LoadImage(&clutpos, (u_long*)buffPtr); + buffPtr += 8; clutValue = GetClut(clutpos.x, clutpos.y); - *clutTablePtr++ = clutValue; - IncrementClutNum(&clutpos); - buffPtr += 12; + *clutTablePtr++ = clutValue; } else { // use stored clut clutValue = clutTable[clut_number]; - buffPtr += 4; } civ_clut[GetCarPalIndex(tpageindex)][texnum][palette + 1] = clutValue; @@ -1219,8 +1302,6 @@ void ProcessPalletLump(char *lump_ptr, int lump_size) // [D] [T] void DrawCarObject(CAR_MODEL* car, MATRIX* matrix, VECTOR* pos, int palette, CAR_DATA* cp, int detail) { - static u_long savedSP; - VECTOR modelLocation; SVECTOR cog; @@ -1245,11 +1326,7 @@ void DrawCarObject(CAR_MODEL* car, MATRIX* matrix, VECTOR* pos, int palette, CAR gte_SetTransVector(&modelLocation); - savedSP = SetSp((u_long)((u_char*)getScratchAddr(0) + 0x308)); - plotNewCarModel(car, palette); - - SetSp(savedSP); } // [D] [T] [A] @@ -1262,10 +1339,9 @@ void DrawCar(CAR_DATA* cp, int view) CAR_MODEL* CarModelPtr; int model; CVECTOR col; - VECTOR pos; + SVECTOR d; + VECTOR pos, dist; VECTOR corners[4]; - VECTOR d; - VECTOR dist; MATRIX workmatrix; D_CHECK_ERROR(cp < car_data, "Invalid car"); @@ -1435,7 +1511,9 @@ void DrawCar(CAR_DATA* cp, int view) if (doSmoke && WheelSpeed + 399999U < 1199999) AddSmokingEngine(cp, doSmoke - 1, WheelSpeed); +#if ENABLE_GAME_ENCHANCEMENTS AddExhaustSmoke(cp, doSmoke > 1, WheelSpeed); +#endif SetShadowPoints(cp, corners); PlaceShadowForCar(corners, 4, 10, yVal < 0 ? 0 : 2); diff --git a/src_rebuild/Game/C/cars.h b/src_rebuild/Game/C/cars.h index 072790a04..7ebec0e53 100644 --- a/src_rebuild/Game/C/cars.h +++ b/src_rebuild/Game/C/cars.h @@ -11,7 +11,6 @@ extern CAR_DATA car_data[MAX_CARS + 2]; // all cars + Tanner cbox + Camera cbox // active cars extern CAR_DATA* active_car_list[MAX_CARS]; -extern BOUND_BOX bbox[MAX_CARS]; extern unsigned char lightsOnDelay[MAX_CARS]; extern CAR_MODEL NewCarModel[MAX_CAR_MODELS]; diff --git a/src_rebuild/Game/C/cell.c b/src_rebuild/Game/C/cell.c index 15b147279..3a7aff13c 100644 --- a/src_rebuild/Game/C/cell.c +++ b/src_rebuild/Game/C/cell.c @@ -7,7 +7,7 @@ int cell_object_index = 0; CELL_OBJECT cell_object_buffer[1024]; -unsigned char cell_object_computed_values[2048]; +u_char cell_object_computed_values[2048]; extern u_char NumPlayers; @@ -69,11 +69,11 @@ PACKED_CELL_OBJECT * GetFirstPackedCop(int cellx, int cellz, CELL_ITERATOR *pci, 8767,555,445,223 - objects of list 2 0x8000 - end of cell objects */ - - while (cell->num != (level | 0x4000)) // skip until we reach the needed list header + + level |= 0x4000; + while (cell->num != level) // skip until we reach the needed list header { cell++; - if (cell->num & 0x8000) // end of cell objects? return NULL; } @@ -86,8 +86,8 @@ PACKED_CELL_OBJECT * GetFirstPackedCop(int cellx, int cellz, CELL_ITERATOR *pci, pci->pcd = cell; - num = cell->num; - ppco = &cell_objects[num & 0x3fff]; + num = cell->num & 16383; + ppco = &cell_objects[num]; if (ppco->value == 0xffff && (ppco->pos.vy & 1)) { @@ -95,9 +95,9 @@ PACKED_CELL_OBJECT * GetFirstPackedCop(int cellx, int cellz, CELL_ITERATOR *pci, } else if (use_computed) { - value = 1 << (num & 7) & 0xffff; + value = 1 << (num & 7); - if (cell_object_computed_values[(num & 0x3fff) >> 3] & value) // get cached value + if (cell_object_computed_values[num / 8] & value) // get cached value { ppco = GetNextPackedCop(pci); pci->ppco = ppco; @@ -105,7 +105,7 @@ PACKED_CELL_OBJECT * GetFirstPackedCop(int cellx, int cellz, CELL_ITERATOR *pci, return ppco; } - cell_object_computed_values[(num & 0x3fff) >> 3] |= value; + cell_object_computed_values[num / 8] |= value; } pci->ppco = ppco; @@ -135,20 +135,19 @@ PACKED_CELL_OBJECT* GetNextPackedCop(CELL_ITERATOR* pci) if (num & 0x4000) // end of list? return NULL; - ppco = &cell_objects[num & 0x3fff]; + num &= 16383; + ppco = &cell_objects[num]; } while (ppco->value == 0xffff && (ppco->pos.vy & 1)); if (!pci->use_computed) break; - value = 1 << (num & 7) & 0xffff; - - if ((cell_object_computed_values[(num & 0x3fff) >> 3] & value) == 0) + value = 1 << (num & 7); + if ((cell_object_computed_values[num / 8] & value) == 0) { - cell_object_computed_values[(num & 0x3fff) >> 3] |= value; + cell_object_computed_values[num / 8] |= value; break; } - } while (true); pci->pcd = celld; @@ -157,34 +156,20 @@ PACKED_CELL_OBJECT* GetNextPackedCop(CELL_ITERATOR* pci) return ppco; } + // [D] [T] CELL_OBJECT* UnpackCellObject(PACKED_CELL_OBJECT* ppco, XZPAIR* near) { + int newIndex; CELL_OBJECT* pco; if (ppco == NULL) return NULL; - pco = &cell_object_buffer[cell_object_index]; - cell_object_index = cell_object_index + 1 & 0x3ff; - - pco->pos.vx = near->x + (((ppco->pos.vx - near->x) << 0x10) >> 0x10); - pco->pos.vz = near->z + (((ppco->pos.vz - near->z) << 0x10) >> 0x10); + pco = &cell_object_buffer[newIndex = cell_object_index]; + cell_object_index = newIndex + 1 & 1023; - pco->pos.vy = (ppco->pos.vy << 0x10) >> 0x11; - pco->yang = ppco->value & 0x3f; - pco->type = (ppco->value >> 6) | ((ppco->pos.vy & 1) << 10); + QuickUnpackCellObject(ppco, near, pco); return pco; -} - -// [D] [T] -void QuickUnpackCellObject(PACKED_CELL_OBJECT* ppco, XZPAIR* near, CELL_OBJECT* pco) -{ - pco->pos.vx = near->x + (((ppco->pos.vx - near->x) << 0x10) >> 0x10); - pco->pos.vz = near->z + (((ppco->pos.vz - near->z) << 0x10) >> 0x10); - - pco->pos.vy = (ppco->pos.vy << 0x10) >> 0x11; - pco->yang = ppco->value & 0x3f; - pco->type = (ppco->value >> 6) | ((ppco->pos.vy & 1) << 10); } \ No newline at end of file diff --git a/src_rebuild/Game/C/cell.h b/src_rebuild/Game/C/cell.h index e8474ebfd..15767f6a2 100644 --- a/src_rebuild/Game/C/cell.h +++ b/src_rebuild/Game/C/cell.h @@ -9,6 +9,15 @@ extern PACKED_CELL_OBJECT * GetFirstPackedCop(int cellx, int cellz, CELL_ITERATO extern PACKED_CELL_OBJECT* GetNextPackedCop(CELL_ITERATOR* pci); // 0x0003F5F0 extern CELL_OBJECT* UnpackCellObject(PACKED_CELL_OBJECT* ppco, XZPAIR* near); // 0x000418E8 -extern void QuickUnpackCellObject(PACKED_CELL_OBJECT* ppco, XZPAIR* near, CELL_OBJECT* pco); +inline void QuickUnpackCellObject(PACKED_CELL_OBJECT* ppco, XZPAIR* near, CELL_OBJECT* pco) +{ + pco->pos.vx = near->x + (short)(ppco->pos.vx - near->x); + pco->pos.vz = near->z + (short)(ppco->pos.vz - near->z); + + pco->pos.vy = (short)ppco->pos.vy >> 1; + + pco->yang = ppco->value & 63; + pco->type = (ppco->value >> 6) | ((ppco->pos.vy & 1) << 10); +} #endif diff --git a/src_rebuild/Game/C/civ_ai.c b/src_rebuild/Game/C/civ_ai.c index b5a037f96..fd6822f2e 100644 --- a/src_rebuild/Game/C/civ_ai.c +++ b/src_rebuild/Game/C/civ_ai.c @@ -24,9 +24,11 @@ #include "objcoll.h" #include "overlay.h" #include "cutrecorder.h" +#include "draw.h" const u_char speedLimits[3] = { 56, 97, 138 }; +#ifdef DEBUG struct { int NumPingedIn; @@ -40,6 +42,7 @@ struct int TooClosePlayer; int InvalidRegion; } civPingTest; +#endif // DEBUG char modelRandomList[] = { 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 0, 1, 0, 4 }; u_char reservedSlots[MAX_CARS] = { 0 }; @@ -75,13 +78,15 @@ int test555 = 0; #ifdef _DEBUG #define CIV_STATE_SET_CONFUSED(cp) \ - printInfo("CIV confused: at %s, %d\n", __FUNCTION__, __LINE__);\ + printInfo("CIV confused: at %s, %d\n", FUNCNAME, __LINE__);\ cp->ai.c.thrustState = 3; cp->ai.c.ctrlState = 7; #else #define CIV_STATE_SET_CONFUSED(cp) \ cp->ai.c.thrustState = 3; cp->ai.c.ctrlState = 7; #endif +int GetNodePos(DRIVER2_STRAIGHT* straight, DRIVER2_JUNCTION* junction, DRIVER2_CURVE* curve, int distAlongPath, CAR_DATA* cp, int* x, int* z, int laneNo); + // [D] [T] int InitCar(CAR_DATA* cp, int direction, LONGVECTOR4* startPos, unsigned char control, int model, int palette, char* extraData) { @@ -234,6 +239,66 @@ void CivCarFX(CAR_DATA* cp) AddBrakeLight(cp); } +int GetLeftBoundLane(DRIVER2_ROAD_INFO& roadInfo, int oppDir) +{ + int i, laneCount, laneNo; + + laneCount = ROAD_WIDTH_IN_LANES(&roadInfo); + laneNo = laneCount; + + for (i = laneNo - 1; i >= 0; i--) + { + if (ROAD_IS_AI_LANE(&roadInfo, i) && !ROAD_IS_PARKING_ALLOWED_AT(&roadInfo, i)) + { + test42 = ROAD_LANE_DIR(&roadInfo, i) ^ 1; + laneNo = i; + + if (test42 == 0) + { + if (oppDir != 0) + break; + } + else + { + if (oppDir == 0) + break; + } + } + } + + return laneNo; +}; + +int GetRightBoundLane(DRIVER2_ROAD_INFO& roadInfo, int oppDir) +{ + int i, laneCount, laneNo; + + laneCount = ROAD_WIDTH_IN_LANES(&roadInfo); + laneNo = ROAD_IS_LEFTMOST_LANE_PARKING(&roadInfo); + + for (i = laneNo; i < laneCount; i++) + { + if (ROAD_IS_AI_LANE(&roadInfo, i) && !ROAD_IS_PARKING_ALLOWED_AT(&roadInfo, i)) + { + test555 = ROAD_LANE_DIR(&roadInfo, i) ^ 1; + laneNo = i; + + if (test555 == 0) + { + if (oppDir != 0) + break; + } + else + { + if (oppDir == 0) + break; + } + } + } + + return laneNo; +} + // [D] [T] int GetNextRoadInfo(CAR_DATA* cp, int randomExit, int* turnAngle, int* startDist, CIV_ROUTE_ENTRY* oldNode) { @@ -243,34 +308,22 @@ int GetNextRoadInfo(CAR_DATA* cp, int randomExit, int* turnAngle, int* startDist DRIVER2_ROAD_INFO currentRoadInfo; DRIVER2_ROAD_INFO roadInfo; - int currentRoadId = 0; int tmpNewRoad[2]; - int newExit = 0; int tmpNewLane[2]; int laneFit[2]; short validExitIdx[4]; - int newLane; - int newRoad; - - int numExits; - int leftLane; - int rightLane; + int newRoad, newLane, newExit, numExits; + int currentRoadId, leftLane, rightLane; - int oldOppDir; - int oppDir; - int turnDir; - int currentLaneDir; + int oppDir, oldOppDir; + int turnDir, currentLaneDir; currentRoadId = cp->ai.c.currentRoad; if (GetSurfaceRoadInfo(¤tRoadInfo, currentRoadId)) { - int widthInLanes; int laneNo; - int count; - - widthInLanes = ROAD_WIDTH_IN_LANES(¤tRoadInfo); currentLaneDir = ROAD_LANE_DIR(¤tRoadInfo, cp->ai.c.currentLane); @@ -284,76 +337,36 @@ int GetNextRoadInfo(CAR_DATA* cp, int randomExit, int* turnAngle, int* startDist dx = oldNode->x - currentRoadInfo.curve->Midx; dz = oldNode->z - currentRoadInfo.curve->Midz; - oldOppDir = DIFF_ANGLES(ratan2(dx, dz), oldNode->dir); // (((oldNode->dir - ratan2(dx, dz)) + 2048U & 0xfff) - 2048); + oldOppDir = DIFF_ANGLES(ratan2(dx, dz), oldNode->dir); oldOppDir = (oldOppDir < 1) * 2048; } // first road is picked from road direction - tmpNewRoad[0] = ((short*)currentRoadInfo.ConnectIdx)[(oldOppDir > 0) * 2]; + tmpNewRoad[0] = currentRoadInfo.ConnectIdx[(oldOppDir > 0) * 2]; // and second picked from lane direction - tmpNewRoad[1] = ((short*)currentRoadInfo.ConnectIdx)[(currentLaneDir > 0) ? 3 : 1]; - - count = widthInLanes; // counter - laneNo = widthInLanes; // bestLane - - do - { - count--; - if (ROAD_IS_AI_LANE(¤tRoadInfo, count) && !ROAD_IS_PARKING_ALLOWED_AT(¤tRoadInfo, count)) - { - test42 = ROAD_LANE_DIR(¤tRoadInfo, count); - laneNo = count; - } - } while (count >= 0); + tmpNewRoad[1] = currentRoadInfo.ConnectIdx[(currentLaneDir > 0) ? 3 : 1]; + laneNo = GetLeftBoundLane(currentRoadInfo, currentLaneDir); - if (currentLaneDir == 0) - leftLane = laneNo & 0xff; + if (oldOppDir == 0) + leftLane = laneNo; else - rightLane = laneNo & 0xff; + rightLane = laneNo; - // ifhas fast lane, use second lane - count = ROAD_HAS_FAST_LANES(¤tRoadInfo); - laneNo = widthInLanes; - - while (count < widthInLanes) - { - if (ROAD_IS_AI_LANE(¤tRoadInfo, count) && !ROAD_IS_PARKING_ALLOWED_AT(¤tRoadInfo, count)) - { - test555 = ROAD_LANE_DIR(¤tRoadInfo, count) ^ 1; - laneNo = count; - - if (test555 == 0) - { - if (currentLaneDir != 0) - break; - } - else - { - if (currentLaneDir == 0) - break; - } - } - laneNo = widthInLanes; - count++; - } + laneNo = GetRightBoundLane(currentRoadInfo, currentLaneDir); - if (currentLaneDir != 0) - leftLane = laneNo & 0xff; + if (oldOppDir != 0) + leftLane = laneNo; else - rightLane = laneNo & 0xff; + rightLane = laneNo; } newLane = -1; if (IS_JUNCTION_SURFACE(tmpNewRoad[0])) { - int bestExit; - - int exitFrom; - int exitCnt; + int exitFrom, exitCnt, bestExit; int rnd; - numExits = 0; cp->ai.c.changeLaneCount = 0; jn = GET_JUNCTION(tmpNewRoad[0]); @@ -362,15 +375,13 @@ int GetNextRoadInfo(CAR_DATA* cp, int randomExit, int* turnAngle, int* startDist // check if road is valid for this junction // by determining connection with junction - exitCnt = 0; - while(exitCnt < 4) + for(exitCnt = 0; exitCnt < 4; exitCnt++) { if(jn->ExitIdx[exitCnt] == currentRoadId) { exitFrom = exitCnt; break; } - exitCnt++; } if (exitFrom == -1) @@ -380,45 +391,46 @@ int GetNextRoadInfo(CAR_DATA* cp, int randomExit, int* turnAngle, int* startDist } // check directions of exits - //printWarning("Checking directions, exitFrom: %d, \n", exitFrom); - exitCnt = 0; - do { - int exitSurfId; - int exitIdx = (exitFrom + exitCnt + 1) % 4; - int valid; + numExits = 0; + + // only need 3 cycles because we don't want make U-turns + for (exitCnt = 0; exitCnt < 3; exitCnt++) + { + int exitSurfId, exitIdx, valid; + + exitIdx = (exitFrom + exitCnt + 1) % 4; valid = 0; exitSurfId = jn->ExitIdx[exitIdx]; if (exitSurfId != -1) { + int turnAng; int exitDir; exitDir = ((exitIdx + 4) - exitFrom) % 4; - //exitDir = exitDir - (exitDir / 4) * 4; if (exitDir == 1) - *turnAngle = -1024; // left + turnAng = -1024; // left else if (exitDir == 2) - *turnAngle = 0; // forward + turnAng = 0; // forward else if (exitDir == 3) - *turnAngle = 1024; // right + turnAng = 1024; // right else - *turnAngle = 0; // forward again? + turnAng = 0; // forward again? test123 = 666; test555 = 666; test42 = 666; // current node direction and new direction - turnDir = oldNode->dir + *turnAngle; + turnDir = oldNode->dir + turnAng; if (GetSurfaceRoadInfo(&roadInfo, exitSurfId)) { - int turnAng; int dx, dz; - int laneCount; + int numLanes; - laneCount = ROAD_WIDTH_IN_LANES(&roadInfo); + numLanes = ROAD_WIDTH_IN_LANES(&roadInfo); if(roadInfo.straight) { @@ -429,94 +441,42 @@ int GetNextRoadInfo(CAR_DATA* cp, int randomExit, int* turnAngle, int* startDist dx = oldNode->x - roadInfo.curve->Midx; dz = oldNode->z - roadInfo.curve->Midz; - oppDir = DIFF_ANGLES(ratan2(dx, dz), turnDir);// ((turnDir - ratan2(dx, dz)) + 2048U & 0xfff) - 2048; // [A] + oppDir = DIFF_ANGLES(ratan2(dx, dz), turnDir); oppDir = (oppDir < 1) * 2048; } - - turnAng = *turnAngle; - - if (oppDir == 0) - turnAng = -turnAng; - + if (turnAng == 0) // going forward { if (oppDir != oldOppDir) // next road is flipped - newLane = laneCount - (cp->ai.c.currentLane + 1); + newLane = numLanes - (cp->ai.c.currentLane + 1); else newLane = cp->ai.c.currentLane; - - // goes on invalid lane? - // [A] bug fix with exitDir == 0 - if (oppDir == 0 && exitDir == 0 || !ROAD_IS_AI_LANE(&roadInfo, newLane)) - newLane = -1; } else if (turnAng == -1024) // going left { - int count; - - //printInfo("check left\n"); - - count = ROAD_HAS_FAST_LANES(&roadInfo); // lane counter - newLane = laneCount; - - // check if allowed to go on any of lanes - while (count < laneCount) - { - if (ROAD_IS_AI_LANE(&roadInfo, count) && !ROAD_IS_PARKING_ALLOWED_AT(&roadInfo, count)) - { - test555 = (ROAD_LANE_DIR(&roadInfo, count) ^ 1) & 1; - newLane = count; - - if (test555 == 0) - { - if (oppDir != 0) - break; - } - else - { - if (oppDir == 0) - break; - } - } - - count++; - newLane = laneCount; - } + newLane = GetRightBoundLane(roadInfo, oppDir); } else if (turnAng == 1024) { - int count; - - count = laneCount; // lane counter - newLane = laneCount; - - do - { - if (ROAD_IS_AI_LANE(&roadInfo, count) && !ROAD_IS_PARKING_ALLOWED_AT(&roadInfo, count)) - { - test42 = (ROAD_LANE_DIR(&roadInfo, count) ^ 1) & 1; - - if (test42 == 0) - { - if (oppDir != 0) - newLane = count; - } - else - { - if (oppDir == 0) - newLane = count; - } - - } - count--; - } while (count >= 0); + newLane = GetLeftBoundLane(roadInfo, oppDir); } // validate lane - if (turnAng == 0 || newLane >= 0 && newLane < laneCount) + if (newLane >= 0 && newLane < numLanes) { valid = ROAD_IS_AI_LANE(&roadInfo, newLane) && !ROAD_IS_PARKING_ALLOWED_AT(&roadInfo, newLane); } + + if (oppDir != oldOppDir) + { + if (currentLaneDir == ROAD_LANE_DIR(&roadInfo, newLane)) + valid = 0; + } + else + { + if (currentLaneDir != ROAD_LANE_DIR(&roadInfo, newLane)) + valid = 0; + } } } @@ -529,10 +489,7 @@ int GetNextRoadInfo(CAR_DATA* cp, int randomExit, int* turnAngle, int* startDist { validExitIdx[exitCnt] = 42; } - - // only need 3 cycles because we don't want make U-turns - exitCnt++; - } while (exitCnt < 3); + } if (leftLane != rightLane && numExits != 1 && ROAD_LANES_COUNT(¤tRoadInfo) > 1) { @@ -580,11 +537,9 @@ int GetNextRoadInfo(CAR_DATA* cp, int randomExit, int* turnAngle, int* startDist newRoad = jn->ExitIdx[bestExit]; - if (turnAngle != NULL) { int invExit; invExit = (bestExit + 4 - exitFrom) % 4; - //invExit = invExit - (invExit / 4) * 4; if (invExit == 1) *turnAngle = -1024; @@ -660,9 +615,9 @@ int GetNextRoadInfo(CAR_DATA* cp, int randomExit, int* turnAngle, int* startDist turnDir = oldNode->dir + *turnAngle; + // determine the new lane on the new road { - int numLanes; - int turnAng; + int numLanes, turnAng; numLanes = ROAD_WIDTH_IN_LANES(&roadInfo); turnAng = *turnAngle; @@ -677,7 +632,7 @@ int GetNextRoadInfo(CAR_DATA* cp, int randomExit, int* turnAngle, int* startDist dx = oldNode->x - roadInfo.curve->Midx; dz = oldNode->z - roadInfo.curve->Midz; - oppDir = DIFF_ANGLES(ratan2(dx, dz), turnDir); // (((turnDir - ratan2(dx, dz)) + 2048U & 0xfff) - 2048); + oppDir = DIFF_ANGLES(ratan2(dx, dz), turnDir); oppDir = (oppDir < 1) * 2048; } @@ -706,68 +661,14 @@ int GetNextRoadInfo(CAR_DATA* cp, int randomExit, int* turnAngle, int* startDist } else if (turnAng == -1024) { - int count; - - count = ROAD_HAS_FAST_LANES(&roadInfo); - newLane = numLanes; - - // check if allowed to go on any of lanes - while (count < numLanes) - { - if (ROAD_IS_AI_LANE(&roadInfo, count) && !ROAD_IS_PARKING_ALLOWED_AT(&roadInfo, count)) - { - test555 = (ROAD_LANE_DIR(&roadInfo, count) ^ 1) & 1; - newLane = count; - - if (test555 == 0) - { - if (oppDir != 0) - break; - } - else - { - if (oppDir == 0) - break; - } - } - - count++; - newLane = numLanes; - } - - //printWarning("car %d check left lane, chosen %d\n", cp->id, newLane); + newLane = GetRightBoundLane(roadInfo, oppDir); } else if (turnAng == 1024) { - int count; - - count = numLanes; - newLane = numLanes; - - do - { - if (ROAD_IS_AI_LANE(&roadInfo, count) && !ROAD_IS_PARKING_ALLOWED_AT(&roadInfo, count)) - { - test42 = (ROAD_LANE_DIR(&roadInfo, count) ^ 1) & 1; - - if (test42 == 0) - { - if (oppDir != 0) - newLane = count; - } - else - { - if (oppDir == 0) - newLane = count; - } - - } - count--; - } while (count >= 0); - - //printWarning("car %d check right lane, chosen %d\n", cp->id, newLane); + newLane = GetLeftBoundLane(roadInfo, oppDir); } + if (*turnAngle != 0) { if (numLanes - 1 == newLane) @@ -812,11 +713,23 @@ int GetNextRoadInfo(CAR_DATA* cp, int randomExit, int* turnAngle, int* startDist { if (tmpNewRoad[roadCnt] != -1) { + int px, pz; int dx, dz; int numLanes; numLanes = 0; + // [A] fix lane changingg issues + if(cp->ai.c.changeLaneCount > 0) + { + GetNodePos(currentRoadInfo.straight, NULL, currentRoadInfo.curve, oldNode->distAlongSegment, NULL, &px, &pz, cp->ai.c.currentLane); + } + else + { + px = oldNode->x; + pz = oldNode->z; + } + if (GetSurfaceRoadInfo(&roadInfo, tmpNewRoad[roadCnt])) { numLanes = ROAD_WIDTH_IN_LANES(&roadInfo); @@ -824,8 +737,8 @@ int GetNextRoadInfo(CAR_DATA* cp, int randomExit, int* turnAngle, int* startDist // determine new lane by old node position if(roadInfo.straight) { - dx = (oldNode->x - roadInfo.straight->Midx); - dz = (oldNode->z - roadInfo.straight->Midz); + dx = (px - roadInfo.straight->Midx); + dz = (pz - roadInfo.straight->Midz); tmpNewLane[roadCnt] = ROAD_LANES_COUNT(&roadInfo) - (FIXEDH(dx * RCOS(roadInfo.straight->angle) - dz * RSIN(roadInfo.straight->angle)) + 512 >> 9); @@ -833,8 +746,8 @@ int GetNextRoadInfo(CAR_DATA* cp, int randomExit, int* turnAngle, int* startDist } else { - dx = oldNode->x - roadInfo.curve->Midx; - dz = oldNode->z - roadInfo.curve->Midz; + dx = px - roadInfo.curve->Midx; + dz = pz - roadInfo.curve->Midz; tmpNewLane[roadCnt] = (SquareRoot0(dx * dx + dz * dz) >> 9) - roadInfo.curve->inside * 2; } @@ -851,7 +764,6 @@ int GetNextRoadInfo(CAR_DATA* cp, int randomExit, int* turnAngle, int* startDist } } - // fit new lane newLane = tmpNewLane[roadCnt]; @@ -1024,11 +936,44 @@ void InitNodeList(CAR_DATA* cp, EXTRA_CIV_DATA* extraData) cr->distAlongSegment = extraData->distAlongSegment; } +// [A] bug fixes the incorrect lane changes +void RemapLaneChange(CAR_DATA* cp) +{ + CIV_ROUTE_ENTRY* currentNode; + DRIVER2_ROAD_INFO roadInfo; + int numLanes, dx, dz; + int newLane; + + if (!GetSurfaceRoadInfo(&roadInfo, cp->ai.c.currentRoad)) + return; + + currentNode = &cp->ai.c.targetRoute[cp->ai.c.currentNode]; + numLanes = ROAD_WIDTH_IN_LANES(&roadInfo); + + // determine lane on new road by old node position + if (roadInfo.straight) + { + dx = (currentNode->x - roadInfo.straight->Midx); + dz = (currentNode->z - roadInfo.straight->Midz); + + newLane = ROAD_LANES_COUNT(&roadInfo) + - (FIXEDH(dx * RCOS(roadInfo.straight->angle) - dz * RSIN(roadInfo.straight->angle)) + 512 >> 9); + } + else + { + dx = currentNode->x - roadInfo.curve->Midx; + dz = currentNode->z - roadInfo.curve->Midz; + + newLane = (SquareRoot0(dx * dx + dz * dz) >> 9) - roadInfo.curve->inside * 2; + } + + cp->ai.c.currentLane = newLane; +} + // [D] [T] int GetNodePos(DRIVER2_STRAIGHT* straight, DRIVER2_JUNCTION* junction, DRIVER2_CURVE* curve, int distAlongPath, CAR_DATA* cp, int* x, int* z, int laneNo) { - unsigned char oldLane; - unsigned char changeLaneCount; + u_char oldLane, changeLaneCount; int angle; int distFromCentre; int sideShift; @@ -1104,13 +1049,10 @@ int GetNodePos(DRIVER2_STRAIGHT* straight, DRIVER2_JUNCTION* junction, DRIVER2_C // [D] [T] int CheckChangeLanes(DRIVER2_STRAIGHT* straight, DRIVER2_CURVE* curve, int distAlongSegment, CAR_DATA* cp, int tryToPark) { - int oldLane; - int currentLane; - int newLane; + int oldLane, newLane, currentLane; int trials; CAR_COSMETICS* car_cos; - int dx; - int dz; + int dx, dz; u_int theta; int length; CAR_DATA* lcp; @@ -1718,7 +1660,7 @@ int TrafficLightCycle(int exit) { int timeCnt; - timeCnt = CameraCnt - frameStart & 0x1ff; + timeCnt = CameraCnt - frameStart & 511; if (exit == 0 || exit == 2) { @@ -1732,7 +1674,7 @@ int TrafficLightCycle(int exit) if (timeCnt < 150) return 3; - else if (timeCnt > 199) + else if (timeCnt >= 200) return 1; return 2; @@ -1762,7 +1704,7 @@ void InitCivCars(void) closeEncounter = 3; } -const int EVENT_CAR_SPEED = 71; +const int EVENT_CAR_SPEED = 60; const int DistanceTriggerCarMoves = 5000; // [D] [T] [A] @@ -1839,7 +1781,7 @@ int CreateCivCarWotDrivesABitThenStops(int direction, LONGVECTOR4* startPos, LON } // [D] [T] -int CreateStationaryCivCar(int direction, long orientX, long orientZ, LONGVECTOR4* startPos, int externalModel, int palette, int controlFlags) +int CreateStationaryCivCar(int direction, int orientX, int orientZ, LONGVECTOR4* startPos, int externalModel, int palette, int controlFlags) { unsigned char* slot; CAR_DATA* newCar; @@ -1933,32 +1875,28 @@ int CreateStationaryCivCar(int direction, long orientX, long orientZ, LONGVECTOR return -1; } - -VECTOR baseLoc; -VECTOR randomLoc; -int dx = 0; // offset 0xAAB40 -int dy = 0; // offset 0xAAB44 -int dz = 0; // offset 0xAAB48 - #define PINGIN_DIST_WANTED_MULT (10) #define PINGIN_DIST_MULT (8) // [D] [T] [A] - some register is not properly decompiled int PingInCivCar(int minPingInDist) { - int model; - CAR_DATA* carCnt; - CAR_DATA* newCar; - - //DRIVER2_CURVE* curve; - //DRIVER2_STRAIGHT* straight; - +#ifdef PSX + DRIVER2_ROAD_INFO& roadInfo = *(DRIVER2_ROAD_INFO*)(u_char*)getScratchAddr(0); + EXTRA_CIV_DATA& civDat = *(EXTRA_CIV_DATA*)((u_char*)getScratchAddr(0) + sizeof(DRIVER2_ROAD_INFO)); + u_char* possibleLanes = ((u_char*)getScratchAddr(0) + sizeof(DRIVER2_ROAD_INFO) + sizeof(EXTRA_CIV_DATA)); + static_assert(sizeof(CELL_ITERATOR) + sizeof(char) + 12 < 1024 - sizeof(_pct), "scratchpad overflow"); +#else DRIVER2_ROAD_INFO roadInfo; - EXTRA_CIV_DATA civDat; u_char possibleLanes[12]; - // carDistLanes removed as it's unused +#endif LONGVECTOR4 pos; + VECTOR baseLoc, randomLoc; + int dx, dy, dz; + CAR_DATA* carCnt; + CAR_DATA* newCar; + int model; int distSq; int dir; int curveLength; @@ -1971,9 +1909,6 @@ int PingInCivCar(int minPingInDist) u_int retDistSq; unsigned char* slot; - //straight = NULL; - //curve = NULL; - civDat.distAlongSegment = -5; lane = -1; dir = 0xffffff; @@ -2020,8 +1955,6 @@ int PingInCivCar(int minPingInDist) newCar = NULL; - ClearMem((char*)&civDat, sizeof(civDat)); - baseLoc.vx = player[playerNum].spoolXZ->vx; baseLoc.vz = player[playerNum].spoolXZ->vz; @@ -2145,7 +2078,10 @@ int PingInCivCar(int minPingInDist) // wtf there were before? car wasn't set to 'confused' state if (!GetSurfaceRoadInfo(&roadInfo, roadSeg)) { +#ifdef DEBUG civPingTest.OffRoad++; +#endif // DEBUG + //CIV_STATE_SET_CONFUSED(newCar); return 0; } @@ -2181,7 +2117,7 @@ int PingInCivCar(int minPingInDist) // this is closest to OG decompiled. Works different! //if (( // ((tryPingInParkedCars && allowedToPark))) || - // ((ROAD_IS_AI_LANE(straight, i) && (((i != 0 || ((straight->NumLanes & 0x40U) == 0)) && (((straight->NumLanes & 0xffffff0f) * 2 - 1 != i || ((straight->NumLanes & 0x80U) == 0)))))))) + // ((ROAD_IS_AI_LANE(&roadInfo, i) && (((i != 0 || ((roadInfo.NumLanes & 0x40U) == 0)) && (((roadInfo.NumLanes & 0xffffff0f) * 2 - 1 != i || ((roadInfo.NumLanes & 0x80U) == 0)))))))) // pick only non-parkable driveable lanes if parked cars not requested if (tryPingInParkedCars && allowedToPark || ROAD_IS_AI_LANE(&roadInfo, i) && !allowedToPark) @@ -2194,6 +2130,8 @@ int PingInCivCar(int minPingInDist) lane = possibleLanes[(Random2(0) >> 8) % numPossibleLanes]; } + ClearMem((char*)&civDat, sizeof(civDat)); + // check if need to make a parked car if (ROAD_IS_PARKING_ALLOWED_AT(&roadInfo, lane)) { @@ -2209,7 +2147,9 @@ int PingInCivCar(int minPingInDist) // Car is not active. Permanently parked if (ROAD_IS_AI_LANE(&roadInfo, lane) == 0) { +#ifdef DEBUG civPingTest.NotDrivable++; +#endif return 0; } @@ -2312,7 +2252,7 @@ int PingInCivCar(int minPingInDist) } else { - scDist = lbody / 2; + scDist = lbody * 2; minDistAlong = 0; } @@ -2777,221 +2717,198 @@ static u_char hornchanflag[2] = { 0 }; // [D] [T] void SetUpCivCollFlags(void) { - CAR_COSMETICS* car_cos; + CAR_DATA* cp0; CAR_DATA* cp1; + CAR_COSMETICS* car0_cos; + CAR_COSMETICS* car1_cos; SVECTOR boxDisp; - CAR_DATA* cp0; - int carLength[2]; + int cp0Length; + int cp0Computed; int i; - int brake; - int dx; - int extraLength; - int boxOverlap; CDATA2D cd[2]; ClearMem((char*)brakeLength, sizeof(brakeLength)); - cp0 = &car_data[MAX_CARS - 1]; - - while (cp0 >= car_data) + for (cp0 = &car_data[MAX_CARS - 1]; cp0 >= car_data; cp0--) { - if (cp0->controlType == CONTROL_TYPE_CIV_AI) + int extraLength; + int cp0BrakeLength; + + if (cp0->controlType != CONTROL_TYPE_CIV_AI) { - extraLength = FIXEDH(cp0->hd.wheel_speed); + continue; + } - if (cp0->wheel_angle < 61) - extraLength *= 13; - else - extraLength *= 4; + extraLength = FIXEDH(cp0->hd.wheel_speed); - extraLength = ABS(extraLength); + if (cp0->wheel_angle < 61) + extraLength *= 13; + else + extraLength *= 4; - car_cos = cp0->ap.carCos; - carLength[0] = car_cos->colBox.vz; + extraLength = ABS(extraLength); - cd[0].length[0] = carLength[0] + 93 + extraLength; - cd[0].length[1] = car_cos->colBox.vx; - cd[0].theta = cp0->hd.direction; + car0_cos = cp0->ap.carCos; + cp0Length = car0_cos->colBox.vz; - gte_SetRotMatrix(&cp0->hd.where.m); - gte_SetTransMatrix(&cp0->hd.where.m); + cd[0].length[0] = cp0Length + extraLength + 93; + cd[0].x.vx = cp0->hd.where.t[0]; + cd[0].x.vy = cp0->hd.where.t[1]; + cd[0].x.vz = cp0->hd.where.t[2]; - boxDisp.vx = -car_cos->cog.vx; - boxDisp.vy = -car_cos->cog.vy; - boxDisp.vz = (extraLength - car_cos->cog.vz) + 93; + cp0Computed = 0; + cp0BrakeLength = brakeLength[cp0->id]; - gte_ldv0(&boxDisp); - gte_rtv0tr(); - gte_stlvnl(&cd[0].x); + for (cp1 = &car_data[MAX_CARS]; cp1 >= car_data; cp1--) + { + int dist, isTanner; + int brake, boxOverlap; + + if (cp1->controlType == CONTROL_TYPE_NONE || cp1 == cp0) + { + continue; + } - cp1 = &car_data[MAX_CARS]; + car1_cos = cp1->ap.carCos; - while (cp1 >= car_data) + isTanner = 0; + if (CAR_INDEX(cp1) == TANNER_COLLIDER_CARID) { - if (cp1->controlType != CONTROL_TYPE_NONE && cp1 != cp0) + if (player[0].playerType != 2) { - car_cos = cp1->ap.carCos; + continue; + } + + cd[1].length[0] = 60; + cd[1].length[1] = 60; + cd[1].x.vx = player[0].pos[0]; + cd[1].x.vy = player[0].pos[1]; + cd[1].x.vz = player[0].pos[2]; + cd[1].theta = player[0].dir; - if (CAR_INDEX(cp1) == TANNER_COLLIDER_CARID) - { - if (player[0].playerType != 2) - { - cp1--; - continue; - } + isTanner = 1; + } + else + { + cd[1].length[0] = car1_cos->colBox.vz; + cd[1].length[1] = car1_cos->colBox.vx; + cd[1].x.vx = cp1->hd.oBox.location.vx; + cd[1].x.vy = cp1->hd.oBox.location.vy; + cd[1].x.vz = cp1->hd.oBox.location.vz; + cd[1].theta = cp1->hd.direction; + } - cd[1].length[0] = 60; - cd[1].length[1] = 60; - cd[1].x.vx = player[0].pos[0]; - cd[1].x.vz = player[0].pos[2]; - cd[1].theta = player[0].dir; - } - else - { - cd[1].length[0] = car_cos->colBox.vz; - cd[1].length[1] = car_cos->colBox.vx; - cd[1].x.vx = cp1->hd.oBox.location.vx; - cd[1].x.vy = cp1->hd.oBox.location.vy; - cd[1].x.vz = cp1->hd.oBox.location.vz; - cd[1].theta = cp1->hd.direction; - } + dist = ((cd[0].length[0] + cd[1].length[0]) * 3) / 2; - dx = ((cd[0].length[0] + cd[1].length[0]) * 3) / 2; + if (ABS(cd[0].x.vx - cd[1].x.vx) >= dist || + ABS(cd[0].x.vz - cd[1].x.vz) >= dist || + ABS(cd[0].x.vy - cd[1].x.vy) >= 500) + { + continue; + } - if (cd[0].x.vx - cd[1].x.vx < 0) - { - if (cd[1].x.vx - cd[0].x.vx >= dx) - { - cp1--; - continue; - } + if (cp0Computed == 0) + { + cd[0].length[1] = car0_cos->colBox.vx; + cd[0].theta = cp0->hd.direction; - } - else if (dx <= cd[0].x.vx - cd[1].x.vx) - { - cp1--; - continue; - } + gte_SetRotMatrix(&cp0->hd.where.m); + gte_SetTransMatrix(&cp0->hd.where.m); - if (cd[0].x.vz - cd[1].x.vz < 0) - { - if (cd[1].x.vz - cd[0].x.vz >= dx) - { - cp1--; - continue; - } - } - else if (dx <= cd[0].x.vz - cd[1].x.vz) - { - cp1--; - continue; - } + boxDisp.vx = -car0_cos->cog.vx; + boxDisp.vy = -car0_cos->cog.vy; + boxDisp.vz = (extraLength - car0_cos->cog.vz) + 93; - // check height difference - if (CAR_INDEX(cp1) == CAMERA_COLLIDER_CARID) - { - if (ABS(player[0].pos[1] - cp0->hd.where.t[1]) >= 500) - { - cp1--; - continue; - } - } - else if (ABS(cp1->hd.where.t[1] - cp0->hd.where.t[1]) >= 500 && ABS(player[0].pos[1] - cp0->hd.where.t[1]) >= 500) - { - cp1--; - continue; - } + gte_ldv0(&boxDisp); + gte_rtv0tr(); + gte_stlvnl(&cd[0].x); + cp0Computed = 1; + } - // do overlap test between boxes - if (!bcollided2d(cd, &boxOverlap)) - { - cp1--; - continue; - } + // do overlap test between boxes + if (!bcollided2d(cd, &boxOverlap)) + { + continue; + } - brake = (cd[0].length[0] - carLength[0]) - boxOverlap; + brake = MAX(1, (cd[0].length[0] - cp0Length) - boxOverlap); - if (brake < 1) - brake = 1; + if (cp0BrakeLength == 0 || brake < cp0BrakeLength) + { + cp0BrakeLength = brake; + brakeLength[cp0->id] = cp0BrakeLength; + } - if (brakeLength[cp0->id] == 0 || brake < brakeLength[cp0->id]) - brakeLength[cp0->id] = brake; + // don't do anything further when it tries to park + if (cp0->ai.c.thrustState == 3) + { + continue; + } - // don't do anything further when it tries to park - if (cp0->ai.c.thrustState == 3) + // wait for Tanner to get into car + if (isTanner) + { + cp0->ai.c.carPauseCnt = CAR_PAUSE_START; + } + + // do horns + // horn to player and chased cars (except Steal the Ambulance) + if (cp0->ai.c.thrustState != 3 && + (isTanner || cp1->controlType == CONTROL_TYPE_PLAYER || cp1->controlType == CONTROL_TYPE_CUTSCENE && gCurrentMissionNumber != 26 && ProxyBar.active == 0)) + { + int dont; + int rnd; + rnd = Random2(0); + + dont = 0; + + for (i = 0; i < 2; i++) + { + if (horncarflag[i] == cp0) { - cp1--; - continue; + dont = 1; + break; } + } - if (CAR_INDEX(cp1) == TANNER_COLLIDER_CARID) - cp0->ai.c.carPauseCnt = CAR_PAUSE_START; + if (dont) + { + continue; + } - // do horns - // horn to player and chased cars (except Steal the Ambulance) - if (cp0->ai.c.thrustState != 3 && - (cp1->controlType == CONTROL_TYPE_PLAYER || cp1->controlType == CONTROL_TYPE_CUTSCENE && gCurrentMissionNumber != 26 && ProxyBar.active == 0 || - CAR_INDEX(cp1) == TANNER_COLLIDER_CARID)) + for (i = 0; i < 2; i++) + { + if (hornchanflag[i] == 0) { - int dont; - int rnd; - rnd = Random2(0); + int sample; - dont = 0; + hornchanflag[i] = GetFreeChannel(); + SpuSetVoiceAR(hornchanflag[i], 27); - for (i = 0; i < 2; i++) - { - if (horncarflag[i] == cp0) - { - dont = 1; - break; - } - } - - if (dont) - { - cp1--; - continue; - } + if (cp0->ap.model == 4) + sample = ResidentModelsBodge(); + else if (cp0->ap.model < 3) + sample = cp0->ap.model; + else + sample = cp0->ap.model - 1; - for (i = 0; i < 2; i++) - { - if (hornchanflag[i] == 0) - { - int sample; - - hornchanflag[i] = GetFreeChannel(); - SpuSetVoiceAR(hornchanflag[i], 27); - - if (cp0->ap.model == 4) - sample = ResidentModelsBodge(); - else if (cp0->ap.model < 3) - sample = cp0->ap.model; - else - sample = cp0->ap.model - 1; - - // [A] use tracking sound - Start3DTrackingSound(hornchanflag[i], SOUND_BANK_CARS, sample * 3 + 2, - (VECTOR*)cp0->hd.where.t, - (LONGVECTOR3*)cp0->st.n.linearVelocity); + // [A] use tracking sound + Start3DTrackingSound(hornchanflag[i], SOUND_BANK_CARS, sample * 3 + 2, + (VECTOR*)cp0->hd.where.t, + (LONGVECTOR3*)cp0->st.n.linearVelocity); - SetChannelVolume(hornchanflag[i], -2000, 0); + SetChannelVolume(hornchanflag[i], -2000, 0); - horncarflag[i] = cp0; + horncarflag[i] = cp0; - channels[hornchanflag[i]].time += rnd - (rnd / 30) * 30; - break; - } - } + channels[hornchanflag[i]].time += rnd - (rnd / 30) * 30; + break; } } - - cp1--; } - } - cp0--; - } + } // for cp1 + } // for cp0 // clear on timeout for (i = 0; i < 2; i++) @@ -3194,10 +3111,8 @@ int CivFindPointOnPath(CAR_DATA * cp, int station, VECTOR * ppoint) CIV_ROUTE_ENTRY* start; CIV_ROUTE_ENTRY* currentNode; CIV_ROUTE_ENTRY* retNode; - int dx; - int dz; - int sx; - int sz; + int dx, dz; + int sx, sz; start = cp->ai.c.pnode; @@ -3355,18 +3270,16 @@ int CivSteerAngle(CAR_DATA* cp) // [D] [T] void CreateRoadblock(void) { - int laneNo; + VECTOR startPos, endPos, currentPos; + VECTOR baseLoc, randomLoc; CAR_COSMETICS* car_cos; CAR_DATA* cp; - int distAlongSegment; - DRIVER2_CURVE* crv; DRIVER2_STRAIGHT* str; - VECTOR startPos; - VECTOR endPos; - - VECTOR currentPos; + int dx, dz; + int laneNo; + int distAlongSegment; int numLanes; int externalCopModel; int noMoreCars; @@ -3671,9 +3584,12 @@ int CivControl(CAR_DATA* cp) if (cp->ai.c.thrustState != 3) steer = CivSteerAngle(cp); - thrust = CivAccel(cp) - MAX(ABS(steer), 4) * 3; // [A] reduce acceleration when steering is applied - - if (thrust < 0 && cp->hd.wheel_speed < 5) + thrust = CivAccel(cp); + if (thrust != 0) // [A] reduce acceleration when steering is applied + thrust = CivAccel(cp) - MAX(ABS(steer), 4) * 3; + + // [A] fix backwards crawl + if (thrust < 0 && cp->hd.wheel_speed < 100) thrust = 0; cp->wheel_angle = steer; @@ -3681,8 +3597,8 @@ int CivControl(CAR_DATA* cp) #if 0 { - //maxCivCars = 2; - //maxCopCars = 0; + //maxCivCars = 2; + //maxCopCars = 0; extern void Debug_AddLine(VECTOR & pointA, VECTOR & pointB, CVECTOR & color); extern void Debug_AddLineOfs(VECTOR & pointA, VECTOR & pointB, VECTOR & ofs, CVECTOR & color); diff --git a/src_rebuild/Game/C/civ_ai.h b/src_rebuild/Game/C/civ_ai.h index 069ebf00f..91a28aad9 100644 --- a/src_rebuild/Game/C/civ_ai.h +++ b/src_rebuild/Game/C/civ_ai.h @@ -46,7 +46,7 @@ extern void SetUpTrafficLightPhase(); // 0x0002D220 extern int TrafficLightCycle(int exit); // 0x0002CF18 extern int CreateCivCarWotDrivesABitThenStops(int direction, LONGVECTOR4* startPos, LONGVECTOR4* stopPos, unsigned char internalModel, int palette); // 0x000286E0 -extern int CreateStationaryCivCar(int direction, long orientX, long orientZ, LONGVECTOR4* startPos, int externalModel, int palette, int controlFlags); // 0x00028960 +extern int CreateStationaryCivCar(int direction, int orientX, int orientZ, LONGVECTOR4* startPos, int externalModel, int palette, int controlFlags); // 0x00028960 extern int CheckChangeLanes(DRIVER2_STRAIGHT* straight, DRIVER2_CURVE* curve, int distAlongSegment, CAR_DATA* cp, int tryToPark); // 0x00026F20 extern int NotTravellingAlongCurve(int x, int z, int dir, DRIVER2_CURVE* cv); diff --git a/src_rebuild/Game/C/convert.c b/src_rebuild/Game/C/convert.c index 2d8e1ca6f..3c5f24297 100644 --- a/src_rebuild/Game/C/convert.c +++ b/src_rebuild/Game/C/convert.c @@ -183,5 +183,5 @@ extern int frameStart; // [D] [T] int Random2(int step) { - return (CameraCnt - frameStart) * (CameraCnt - frameStart) * 0x19660d + 0x3c6ef35fU >> 8 & 0xffff; + return RAND((CameraCnt - frameStart) * (CameraCnt - frameStart)) >> 8 & 65535; } diff --git a/src_rebuild/Game/C/cop_ai.c b/src_rebuild/Game/C/cop_ai.c index e4b66db11..5607550a6 100644 --- a/src_rebuild/Game/C/cop_ai.c +++ b/src_rebuild/Game/C/cop_ai.c @@ -65,7 +65,7 @@ static int said_picked_up = 0; char last_cop_phrase = 0; -char CopWorkMem[444]; // PVS table +char CopWorkMem[PVS_CELL_COUNT * PVS_CELL_COUNT + 3]; // PVS table COP_SIGHT_DATA copSightData; int player_position_known = 0; @@ -379,6 +379,7 @@ void CopControl1(CAR_DATA *cp) // [A] periodically beat player in ass if (!doBatter && *playerFelony > gCopData.autoBatterPlayerTrigger) { +#if ENABLE_GAME_ENCHANCEMENTS int batterTrigger; if(gCopDifficultyLevel == 0) @@ -393,10 +394,11 @@ void CopControl1(CAR_DATA *cp) cp->ai.p.batterTimer++; cp->ai.p.batterTimer &= 127; +#else + doBatter = 1; +#endif } - - if (cp->ai.p.dying != 0 || cp->totalDamage > 27000 && gCopData.immortal == 0) { @@ -854,7 +856,11 @@ void ControlCopDetection(void) } // [A] also check player elevation from cops (block cops vision from bridges, tunnels etc) - if (spotted && ABS(cp->hd.where.t[1] - vec.vy) < 1000) + if (spotted +#if ENABLE_GAME_ENCHANCEMENTS + && ABS(cp->hd.where.t[1] - vec.vy) < 1000 +#endif + ) { CopsCanSeePlayer = 1; break; @@ -865,6 +871,7 @@ void ControlCopDetection(void) } } +#if ENABLE_GAME_ENCHANCEMENTS // [A] if Tanner is outside car, cops can arrest him if they are too close if(player[0].playerType == 2 && minDistanceToPlayer < 2048 && !player[0].dying && pedestrianFelony > FELONY_PURSUIT_MIN_VALUE) { @@ -873,6 +880,7 @@ void ControlCopDetection(void) SetMissionMessage(G_LTXT(GTXT_YouveBeenCaught),3,2); SetMissionFailed(FAILED_MESSAGESET); } +#endif if (numActiveCops == 0 && OutOfSightCount < 256 && CameraCnt > 8) { diff --git a/src_rebuild/Game/C/cosmetic.c b/src_rebuild/Game/C/cosmetic.c index 44cf49fde..d537efe9c 100644 --- a/src_rebuild/Game/C/cosmetic.c +++ b/src_rebuild/Game/C/cosmetic.c @@ -19,9 +19,29 @@ char* CosmeticFiles[] = { CAR_COSMETICS car_cosmetics[MAX_CAR_MODELS]; +#if ENABLE_GAME_FIXES // [A] storage for spooled models // remember: we already have more than 1k of free memory with optimizations CAR_COSMETICS levelSpecCosmetics[5]; +#endif + +#if USE_PC_FILESYSTEM +extern int gContentOverride; + +// [A] loads car cosmetics from file +void LoadCustomCarCosmetics(CAR_COSMETICS* dest, int modelNumber) +{ + char filename[64]; + + sprintf(filename, "LEVELS\\%s\\CARMODEL_%d.COS", LevelNames[GameLevel], modelNumber); + if (!FileExists(filename)) + { + return; + } + + LoadfileSeg(filename, (char*)dest, 0, sizeof(CAR_COSMETICS)); +} +#endif // [D] [T] void ProcessCosmeticsLump(char *lump_ptr, int lump_size) @@ -47,20 +67,26 @@ void ProcessCosmeticsLump(char *lump_ptr, int lump_size) if (model != -1) { offset = *(int*)(lump_ptr + model * sizeof(int)); - memcpy((u_char*)&car_cosmetics[i], (u_char*)lump_ptr + offset, sizeof(CAR_COSMETICS)); + car_cosmetics[i] = *(CAR_COSMETICS*)((u_char*)lump_ptr + offset); +#if USE_PC_FILESYSTEM + if(gContentOverride) + LoadCustomCarCosmetics(&car_cosmetics[i], model); +#endif FixCarCos(&car_cosmetics[i], model); } } // [A] cache all special vehicle cosmetics +#if ENABLE_GAME_FIXES for (i = 0; i < 5; i++) { model = 8 + i; offset = *(int*)(lump_ptr + model * sizeof(int)); - memcpy((u_char*)&levelSpecCosmetics[i], (u_char*)lump_ptr + offset, sizeof(CAR_COSMETICS)); + levelSpecCosmetics[i] = *(CAR_COSMETICS*)((u_char*)lump_ptr + offset); } +#endif } // [D] [T] @@ -113,11 +139,16 @@ void SetupSpecCosmetics(char *loadbuffer) int model; model = MissionHeader->residentModels[4]; -#if 1 +#if ENABLE_GAME_FIXES // [A] always use cached cosmetics - memcpy((u_char*)&car_cosmetics[4], (u_char*)&levelSpecCosmetics[model - 8], sizeof(CAR_COSMETICS)); + car_cosmetics[4] = levelSpecCosmetics[model - 8]; #else - memcpy((u_char*)&car_cosmetics[4], loadbuffer, sizeof(CAR_COSMETICS)); + car_cosmetics[4] = *(CAR_COSMETICS*)loadbuffer; +#endif + +#if USE_PC_FILESYSTEM + if (gContentOverride) + LoadCustomCarCosmetics(&car_cosmetics[4], model); #endif // [A] don't forget diff --git a/src_rebuild/Game/C/cutrecorder.c b/src_rebuild/Game/C/cutrecorder.c index 85aadd831..44c1acc84 100644 --- a/src_rebuild/Game/C/cutrecorder.c +++ b/src_rebuild/Game/C/cutrecorder.c @@ -564,7 +564,7 @@ int CutRec_LoadCutsceneAsReplayFromBuffer(char* buffer) REPLAY_STREAM* destStream = &ReplayStreams[i]; // copy source type - memcpy((u_char*)&destStream->SourceType, (u_char*)&sheader->SourceType, sizeof(STREAM_SOURCE)); + destStream->SourceType = sheader->SourceType; int size = (sheader->Size + sizeof(PADRECORD)) & -4; diff --git a/src_rebuild/Game/C/cutscene.c b/src_rebuild/Game/C/cutscene.c index 1e5451e4b..5828e9dba 100644 --- a/src_rebuild/Game/C/cutscene.c +++ b/src_rebuild/Game/C/cutscene.c @@ -86,55 +86,6 @@ void FreeCutsceneBuffer(); int IsCutsceneResident(int cutscene); -#ifndef PSX -char gUserReplayFolderList[MAX_USER_REPLAYS][48]; -int gNumUserChases = 0; -int gUserChaseLoaded = -1; - -// [A] user replay folders initialization -void InitUserReplays(const char* str) -{ - int quit; - char* ptr; - char* strStart; - gNumUserChases = 0; - - if (!str) - return; - - ptr = (char*)str; - strStart = NULL; - memset(gUserReplayFolderList, 0, sizeof(gUserReplayFolderList)); - - quit = 0; - - while(true) - { - if (strStart == NULL) - strStart = ptr; - - // if we're encountered string end go on - if(*ptr == ',' || *ptr == ' ' || *ptr == '\0') - { - if (*ptr == '\0') - quit = 1; - - *ptr = '\0'; - strcpy(gUserReplayFolderList[gNumUserChases++], strStart); - strStart = NULL; - } - - ptr++; - - if (quit) - break; - } -} - - -#endif - - // [D] [T] void InitInGameCutsceneVariables(void) { @@ -159,10 +110,6 @@ void InitInGameCutsceneVariables(void) gSkipInGameCutscene = 0; -#ifndef PSX - gUserChaseLoaded = -1; -#endif - FreeCutsceneBuffer(); } @@ -231,23 +178,11 @@ void DrawInGameCutscene(void) TILE *tile; #ifndef PSX - PrintXASubtitles(); + PrintXASubtitles(SCREEN_H - 28); #endif if (gInGameCutsceneActive == 0 && gInGameCutsceneDelay == 0) { -#ifndef PSX - if(gInGameChaseActive && gUserChaseLoaded != -1 && (CameraCnt - frameStart) < 200) - { - // [A] print user chaser name on screen - char tempStr[80]; - - sprintf(tempStr, "%s %s", G_LTXT(GTXT_GetawayIs), gUserReplayFolderList[gUserChaseLoaded]); - - SetTextColour(128, 128, 64); - PrintString(tempStr, gOverlayXPos, 230); - } -#endif return; } @@ -392,34 +327,8 @@ int SelectCutsceneFile(char* filename, int init, int subindex) if (init) { // try load replacement bundle -#ifndef PSX - int userId = -1; - - // [A] REDRIVER2 PC - custom user chases - if (gNumUserChases) - { - userId = rand() % (gNumUserChases + 1); - - // if random decides to have no user chase - get og or replacement one - if (userId == gNumUserChases) - userId = -1; - } - - // try loading user chase - if (userId != -1) - sprintf(filename, "REPLAYS\\UserChases\\%s\\CUT%d_N.R", (char*)gUserReplayFolderList[userId], gCurrentMissionNumber); - - if (FileExists(filename)) - { - gUserChaseLoaded = userId; - gReChaseAvailable = 0; - } - else -#endif - { - sprintf(filename, "REPLAYS\\ReChases\\CUT%d_N.R", gCurrentMissionNumber); - gReChaseAvailable = FileExists(filename); - } + sprintf(filename, "REPLAYS\\ReChases\\CUT%d_N.R", gCurrentMissionNumber); + gReChaseAvailable = FileExists(filename); } if (subindex >= 2) @@ -427,21 +336,14 @@ int SelectCutsceneFile(char* filename, int init, int subindex) if (gReChaseAvailable == 1) { sprintf(filename, "REPLAYS\\ReChases\\CUT%d_N.R", gCurrentMissionNumber); + return FileExists(filename); } -#ifndef PSX - else if (gUserChaseLoaded != -1) - { - sprintf(filename, "REPLAYS\\UserChases\\%s\\CUT%d_N.R", (char*)gUserReplayFolderList[gUserChaseLoaded], gCurrentMissionNumber); - } -#endif } + + if (gCurrentMissionNumber < 21) + sprintf(filename, "REPLAYS\\CUT%d.R", gCurrentMissionNumber); else - { - if (gCurrentMissionNumber < 21) - sprintf(filename, "REPLAYS\\CUT%d.R", gCurrentMissionNumber); - else - sprintf(filename, "REPLAYS\\A\\CUT%d.R", gCurrentMissionNumber); - } + sprintf(filename, "REPLAYS\\A\\CUT%d.R", gCurrentMissionNumber); return FileExists(filename); } @@ -462,7 +364,7 @@ int CalcInGameCutsceneSize(void) LoadfileSeg(filename, (char*)&CutsceneHeader, 0, sizeof(CUTSCENE_HEADER)); // load re-chase file header - if(SelectCutsceneFile(filename, 0, 2)) + if (SelectCutsceneFile(filename, 0, 2)) LoadfileSeg(filename, (char*)&ChaseHeader, 0, sizeof(CUTSCENE_HEADER)); maxSize = 0; @@ -502,7 +404,7 @@ void ReleaseInGameCutscene(void) { if (PlayerStartInfo[i]->flags & 4) { - memcpy((u_char*)&player[0], (u_char*)&player[i], sizeof(PLAYER)); + player[0] = player[i]; if (player[0].playerType == 2) { @@ -916,13 +818,14 @@ int LoadCutsceneToReplayBuffer(int residentCutscene) // add to existing replay streams for (int i = NumReplayStreams; i < (NumReplayStreams + rheader->NumReplayStreams); i++) { + int size; sheader = (REPLAY_STREAM_HEADER *)pt; pt += sizeof(REPLAY_STREAM_HEADER); REPLAY_STREAM* destStream = &ReplayStreams[i]; // copy source type - memcpy((u_char*)&destStream->SourceType, (u_char*)&sheader->SourceType, sizeof(STREAM_SOURCE)); + destStream->SourceType = sheader->SourceType; // init buffers destStream->InitialPadRecordBuffer = (PADRECORD*)replayptr; @@ -932,7 +835,7 @@ int LoadCutsceneToReplayBuffer(int residentCutscene) destStream->length = sheader->Length; destStream->playbackrun = 0; - int size = (sheader->Size + sizeof(PADRECORD)) & -4; + size = (sheader->Size + sizeof(PADRECORD)) & -4; // copy pad data and advance buffer memcpy((u_char*)replayptr, (u_char*)pt, size); diff --git a/src_rebuild/Game/C/cutscene.h b/src_rebuild/Game/C/cutscene.h index 535314247..20fa8b74a 100644 --- a/src_rebuild/Game/C/cutscene.h +++ b/src_rebuild/Game/C/cutscene.h @@ -13,16 +13,6 @@ struct CUTSCENE_HEADER CUTSCENE_INFO data[15]; }; -#ifndef PSX -#define MAX_USER_REPLAYS 16 - -extern char gUserReplayFolderList[MAX_USER_REPLAYS][48]; -extern int gNumUserChases; - -extern void InitUserReplays(const char* str); - -#endif // PSX - extern int NumCutsceneStreams; extern int gSkipInGameCutscene; extern int gInGameCutsceneID; diff --git a/src_rebuild/Game/C/debris.c b/src_rebuild/Game/C/debris.c index 0765a8048..2c3833f1b 100644 --- a/src_rebuild/Game/C/debris.c +++ b/src_rebuild/Game/C/debris.c @@ -401,6 +401,8 @@ DAMAGED_LAMP damaged_lamp[MAX_DAMAGED_LAMPS]; MATRIX debris_mat; MATRIX leaf_mat; +#define LAMP_STREAK_ID(x,y) (((x) & 0xffff) | (((y) & 0xffff) << 16)) + // [D] [T] void PlacePoolForCar(CAR_DATA *cp, CVECTOR *col, int front, int in_car) { @@ -735,11 +737,11 @@ void AddLeaf(VECTOR *Position, int num_leaves, int Type) } // apply colors - if (gTimeOfDay == 3) + if (gTimeOfDay == TIME_NIGHT) { *(u_int*)&myleaf->rgb = *(u_int*)&myleaf->rgb >> 2 & 0x3f3f3f3f; } - else if (gWeather == 1 || gWeather == 2) + else if (gWeather == WEATHER_RAIN || gWeather == WEATHER_WET) { *(u_int*)&myleaf->rgb = *(u_int*)&myleaf->rgb >> 1 & 0x7f7f7f7f; } @@ -815,26 +817,26 @@ void InitDebrisNames(void) GetTextureDetails("FOREARM1", &forearm1_texture); GetTextureDetails("CHEST1", &chest1_texture); GetTextureDetails("LOWHEAD", &head1_texture); - GetTextureDetails("ADDCAM", &addcam); - GetTextureDetails("FRAMEADV", &frameadv); - GetTextureDetails("AUTO", &autocam); - GetTextureDetails("CHASEC", &chasecar); - GetTextureDetails("CHOOSECA", &choosecar); - GetTextureDetails("CLOCK", &clock); - GetTextureDetails("DELCAM", &delcam); - GetTextureDetails("EDITCAM", &editcam); - GetTextureDetails("FIXEDCA", &fixedcam); - GetTextureDetails("INCAR", &incar); - GetTextureDetails("LENSCHA", &lenschan); - GetTextureDetails("LOOKCAR", &lookcar); - GetTextureDetails("MOVECMP", &movecam); - GetTextureDetails("MOVECAM", &movecampos); - GetTextureDetails("OK", &ok); - GetTextureDetails("PAUSE", &pause); - GetTextureDetails("PLAYCAM", &playcam); - GetTextureDetails("PLAYPAU", &playpause); - GetTextureDetails("SAVE2CA", &save2card); - GetTextureDetails("RESTREP", &restart); + GetTextureDetails("ADDCAM", &icon_addcam); + GetTextureDetails("FRAMEADV", &icon_frameadv); + GetTextureDetails("AUTO", &icon_autocam); + GetTextureDetails("CHASEC", &icon_chasecar); + GetTextureDetails("CHOOSECA", &icon_choosecar); + GetTextureDetails("CLOCK", &icon_clock); + GetTextureDetails("DELCAM", &icon_delcam); + GetTextureDetails("EDITCAM", &icon_editcam); + GetTextureDetails("FIXEDCA", &icon_fixedcam); + GetTextureDetails("INCAR", &icon_incar); + GetTextureDetails("LENSCHA", &icon_lenschan); + GetTextureDetails("LOOKCAR", &icon_lookcar); + GetTextureDetails("MOVECMP", &icon_movecam); + GetTextureDetails("MOVECAM", &icon_movecampos); + GetTextureDetails("OK", &icon_ok); + GetTextureDetails("PAUSE", &icon_pause); + GetTextureDetails("PLAYCAM", &icon_playcam); + GetTextureDetails("PLAYPAU", &icon_playpause); + GetTextureDetails("SAVE2CA", &icon_save2card); + GetTextureDetails("RESTREP", &icon_restart); GetTextureDetails("HEAD1", &texturePedHead); GetTextureDetails("TSHADOW", &tannerShadow_texture); @@ -1092,7 +1094,7 @@ void DrawSmashable_sprites(void) { UNIMPLEMENTED(); - if (gWeather - 1U < 2 || gTimeOfDay == 3) + if (gWeather - 1U < 2 || gTimeOfDay == TIME_NIGHT) { plotContext.colour = NightAmbient << 0x10 | NightAmbient << 8 | NightAmbient | 0x2c000000; } @@ -1257,21 +1259,18 @@ int find_lamp_streak(int LampId) return -1; } + + // [D] [T] void AddSmallStreetLight(CELL_OBJECT *cop, int x, int y, int z, int type) { int count; DAMAGED_LAMP* dam; int halo_size; - short size; - short angle; - VECTOR v1; - VECTOR v2; - VECTOR v3; - SVECTOR pos; - CVECTOR col; - CVECTOR col1; - SVECTOR dpos; + short size, angle; + VECTOR v1, v2, v3; + SVECTOR pos, dpos; + CVECTOR col, col1; dam = damaged_lamp; col = {140, 140, 140}; @@ -1296,7 +1295,8 @@ void AddSmallStreetLight(CELL_OBJECT *cop, int x, int y, int z, int type) } count = 0; - do { + for(count = 0; count < 4; count++) + { if (dam->index == cop->pos.vx + cop->pos.vz) { if (dam->damage > 2) @@ -1308,10 +1308,8 @@ void AddSmallStreetLight(CELL_OBJECT *cop, int x, int y, int z, int type) break; } - - count++; dam++; - } while (count < 4); + } dpos.vx = cop->pos.vx - camera_position.vx; dpos.vy = cop->pos.vy - camera_position.vy; @@ -1371,7 +1369,7 @@ void AddSmallStreetLight(CELL_OBJECT *cop, int x, int y, int z, int type) v3 = v1; - LightIndex = find_lamp_streak(cop->pos.vx + cop->pos.vz + x); // [A] was pointer. + LightIndex = find_lamp_streak(LAMP_STREAK_ID(cop->pos.vx + x, cop->pos.vz)); // [A] was pointer. if (LightIndex > -1) col.cd = 0x60; @@ -1381,6 +1379,10 @@ void AddSmallStreetLight(CELL_OBJECT *cop, int x, int y, int z, int type) DisplayLightReflections(&v2, &col1, halo_size * 2, &lightref_texture); +#ifdef DYNAMIC_LIGHTING + AddDlight(&v3, &col, size * 280 >> 4); +#endif // DYNAMIC_LIGHTING + LightSortCorrect = -10; } @@ -1390,8 +1392,7 @@ void AddLightEffect(CELL_OBJECT *cop, int x, int y, int z, int type, int colour) short yang; int angle; int size; - VECTOR v1; - VECTOR v2; + VECTOR v1, v2; VECTOR dpos; SVECTOR pos; CVECTOR col; @@ -1503,6 +1504,11 @@ void AddLightEffect(CELL_OBJECT *cop, int x, int y, int z, int type, int colour) gte_SetTransVector(&v1); ShowLight1(&v1, &col, size, &light_texture); + +#ifdef DYNAMIC_LIGHTING + AddDlight(&v1, &col, size * 180 >> 4); +#endif // DYNAMIC_LIGHTING + DisplayLightReflections(&v2, &col, (size << 0xd) >> 0x10, &lightref_texture); } @@ -1748,6 +1754,7 @@ void AddTrafficLight(CELL_OBJECT *cop, int x, int y, int z, int flag, int yang) v2.vx = v1.vx; v2.vz = v1.vz; + v2.vy = v1.vy; v2.vy = -camera_position.vy - MapHeight((VECTOR*)&cop->pos); if (gNight) @@ -1767,7 +1774,7 @@ void AddTrafficLight(CELL_OBJECT *cop, int x, int y, int z, int flag, int yang) a.g = (a.g * tempfade) >> 10; LightSortCorrect = -140; - LightIndex = find_lamp_streak(cop->pos.vx + cop->pos.vz + x + y); // [A] was pointer. + LightIndex = find_lamp_streak(LAMP_STREAK_ID(cop->pos.vx + x, cop->pos.vz + y)); // [A] was pointer. if (LightIndex < 0) a.cd = 0; @@ -2027,18 +2034,17 @@ void ShowLight(VECTOR *v1, CVECTOR *col, short size, TEXTURE_DETAILS *texture) if (z < 0) z = 0; - if (z < 10000) - tail_width = (10000 - z) >> 0xd; - else - tail_width = 0; - addPrim(current->ot + z, poly); current->primptr += sizeof(POLY_FT4); - if (CameraCnt <= 4 || NumPlayers > 1) // [A] don't draw trails in multiplayer return; + if (z < 10000) + tail_width = (10000 - z) >> 13; + else + return; + if ((col->cd & 0x20) && gLightsOn) { trails = Known_Lamps[LightIndex].light_trails; @@ -2066,15 +2072,15 @@ void ShowLight(VECTOR *v1, CVECTOR *col, short size, TEXTURE_DETAILS *texture) } #ifndef PSX - x = (poly->x0 + poly->x3) / 2.0f; - y = (poly->y0 + poly->y3) / 2.0f; + x = (poly->x0 + poly->x3) * 0.5f; + y = (poly->y0 + poly->y3) * 0.5f; #else x = (poly->x0 + poly->x3) / 2; y = (poly->y0 + poly->y3) / 2; #endif // unified drawing both for car and lamps - if (CameraChanged == 0 && *clock == (FrameCnt & 0xffffU)-1) + if (CameraChanged == 0 && *clock == (FrameCnt & 0xffffU) - 1) { int old_x, old_y; @@ -2087,7 +2093,11 @@ void ShowLight(VECTOR *v1, CVECTOR *col, short size, TEXTURE_DETAILS *texture) if (size > 1 && ABS(old_x - x) + ABS(old_y - y) > 1) { int angle, width; - VERTTYPE dx, dy; +#ifdef PSX + int dx, dy; +#else + float dx, dy; +#endif trail = (POLY_G4 *)current->primptr; @@ -2097,21 +2107,20 @@ void ShowLight(VECTOR *v1, CVECTOR *col, short size, TEXTURE_DETAILS *texture) angle = -ratan2(old_x - x,old_y - y) & 0xfff; width = ABS(poly->x0 - poly->x3); -#ifdef PSX dx = RCOS(angle) * width * 3; dy = RSIN(angle) * width * 3; - + if (col->cd & 0x40) { - dx >>= 0x10; - dy >>= 0x10; + dx /= 1 << 16; + dy /= 1 << 16; } else { - dx >>= 0xf; - dy >>= 0xf; + dx /= 1 << 15; + dy /= 1 << 15; } - + trail->x0 = x + dx * tail_width; trail->y0 = y + dy * tail_width; @@ -2123,34 +2132,6 @@ void ShowLight(VECTOR *v1, CVECTOR *col, short size, TEXTURE_DETAILS *texture) trail->x3 = old_x - dx; trail->y3 = old_y - dy; -#else - // [A] slightly bigger light trail - dx = RCOS(angle); - dy = RSIN(angle); - - if (col->cd & 0x40) - { - dx = dx / 40000.0f; - dy = dy / 40000.0f; - } - else - { - dx = dx / 32768.0f; - dy = dy / 32768.0f; - } - - trail->x0 = x + dx * width * 3 * tail_width; - trail->y0 = y + dy * width * 3 * tail_width; - - trail->x1 = x - dx * width * 3 * tail_width; - trail->y1 = y - dy * width * 3 * tail_width; - - trail->x2 = old_x + dx * width * 3; - trail->y2 = old_y + dy * width * 3; - - trail->x3 = old_x - dx * width * 3; - trail->y3 = old_y - dy * width * 3; -#endif if (col->cd & 0x18) { @@ -2185,8 +2166,8 @@ void ShowLight(VECTOR *v1, CVECTOR *col, short size, TEXTURE_DETAILS *texture) addPrim(current->ot + z, null); current->primptr += sizeof(POLY_FT3); - } } + } else { for (i = 0; i < 4; i++) @@ -2461,7 +2442,7 @@ void DisplaySpark(SMOKE *spark) { colorind = spark->drift.vy * 3 & 3; - if (gTimeOfDay == 1) + if (gTimeOfDay == TIME_DAY) { poly->r0 = grassColour[colorind][0]; poly->g0 = grassColour[colorind][1]; @@ -2578,7 +2559,7 @@ void Setup_Debris(VECTOR *ipos, VECTOR *ispeed, int num_debris, int type) mydebris->rgb.g = debris_colour[GameLevel][col].g; mydebris->rgb.b = debris_colour[GameLevel][col].b; - if (gTimeOfDay == 3) + if (gTimeOfDay == TIME_NIGHT) { mydebris->rgb.r >>= 1; mydebris->rgb.g >>= 1; @@ -3472,43 +3453,49 @@ void DisplaySplashes(void) if (pauseflag != 0) return; - if (gRainCount >> 2 > 30) + SplashNo = gRainCount >> 1; + + if (SplashNo > 30) SplashNo = 30; - else - SplashNo = (gRainCount >> 2); SplashFrac = FIXEDH(SplashNo * FrAng * 3); gte_SetRotMatrix(&identity); // [A] norot CamGnd.vx = camera_position.vx; + CamGnd.vy = camera_position.vy; CamGnd.vz = camera_position.vz; + CamGnd.vy = -camera_position.vy - MapHeight(&CamGnd); ang = FrAng - camera_angle.vy; + + Gnd1.vx = (RSIN(ang) >> 1) + camera_position.vx; + Gnd1.vy = CamGnd.vy; + Gnd1.vz = (RCOS(ang) >> 1) + camera_position.vz; - Gnd1.vx = RSIN(ang) + camera_position.vx; - Gnd1.vz = RCOS(ang) + camera_position.vz; - - Gnd1.vx = Gnd1.vx - CamGnd.vx; - Gnd1.vy = -camera_position.vy - MapHeight(&Gnd1) - CamGnd.vy; - Gnd1.vz = Gnd1.vz - CamGnd.vz; + Gnd1.vy = -camera_position.vy - MapHeight(&Gnd1); + Gnd1.vx -= CamGnd.vx; + Gnd1.vy -= CamGnd.vy; + Gnd1.vz -= CamGnd.vz; ang = -FrAng - camera_angle.vy; - Gnd2.vx = RSIN(ang) + camera_position.vx; - Gnd2.vz = RCOS(ang) + camera_position.vz; + Gnd2.vx = (RSIN(ang) >> 1) + camera_position.vx; + Gnd2.vy = CamGnd.vy; + Gnd2.vz = (RCOS(ang) >> 1) + camera_position.vz; - Gnd2.vx = Gnd2.vx - CamGnd.vx; - Gnd2.vy = (-camera_position.vy - MapHeight(&Gnd2)) - CamGnd.vy; - Gnd2.vz = Gnd2.vz - CamGnd.vz; + Gnd2.vy = -camera_position.vy - MapHeight(&Gnd2); + Gnd2.vx -= CamGnd.vx; + Gnd2.vy -= CamGnd.vy; + Gnd2.vz -= CamGnd.vz; while (--SplashFrac >= 0) { - d2 = rand * 0x19660d + 0x3c6ef35f; - d1 = d2 >> 4 & 0xfff; - rand = d2 * 0x19660d + 0x3c6ef35f; - d2 = rand >> 0xe & 0xfff; + d2 = RAND(rand); + d1 = d2 >> 4 & 4095; + rand = RAND(d2); + d2 = rand >> 14 & 4095; Position.vx = FIXEDH(Gnd1.vx * d1 + Gnd2.vx * d2); Position.vy = FIXEDH(Gnd1.vy * d1 + Gnd2.vy * d2) + CamGnd.vy; @@ -3582,7 +3569,6 @@ void DrawRainDrops(void) RainPtr->position.vy += RAIN_DROP_SPEED; RainPtr->position.vx -= drift.vx * 2; RainPtr->position.vz -= drift.vz * 2; - *(u_int *)&poly->x0 = *(u_int *)&RainPtr->oldposition; } @@ -3625,10 +3611,9 @@ void DrawRainDrops(void) else *(u_int *)&RainPtr->oldposition = *(u_int *)&poly->x2; } - else + else if (pauseflag == 0) { - if(pauseflag == 0) - ReleaseRainDrop(RainPtr->oldposition.pad); + ReleaseRainDrop(RainPtr->oldposition.pad); } RainPtr++; @@ -3677,14 +3662,14 @@ void AddRainDrops(void) if (RainIndex < 0) return; - tmp = rand * 0x19660d + 0x3c6ef35f; - v.vz = (tmp >> 0x14 & 0x1ffU) + 400; + tmp = RAND(rand); + v.vz = (tmp >> 20 & 511) + 400; - tmp = tmp * 0x19660d + 0x3c6ef35f; - v.vy = -((tmp >> 0x14) & 0x1ff); + tmp = RAND(tmp); + v.vy = -((tmp >> 20) & 511); - rand = tmp * 0x19660d + 0x3c6ef35f; - v.vx = ((rand >> 0x14) & 0x1ff) - 256; + rand = RAND(tmp); + v.vx = ((rand >> 20) & 511) - 256; if (v.vz > 512) { @@ -3805,7 +3790,7 @@ void DoThunder(void) // [D] [T] void DoWeather(int weather) { - if (weather != 1) + if (weather != WEATHER_RAIN) return; if(pauseflag == 0) diff --git a/src_rebuild/Game/C/denting.c b/src_rebuild/Game/C/denting.c index 82bf54edf..cb3926ffe 100644 --- a/src_rebuild/Game/C/denting.c +++ b/src_rebuild/Game/C/denting.c @@ -12,6 +12,29 @@ #include "players.h" #include "main.h" +#if USE_PC_FILESYSTEM +extern int gContentOverride; + +// [A] loads car model from file +char* LoadCustomCarDentingFromFile(char* dest, int modelNumber) +{ + char* mem; + char filename[64]; + + sprintf(filename, "LEVELS\\%s\\CARMODEL_%d.DEN", LevelNames[GameLevel], modelNumber); + if (FileExists(filename)) + { + mem = dest ? dest : ((char*)_other_buffer + modelNumber * 4096); + + // get model from file + Loadfile(filename, mem); + return mem; + } + + return NULL; +} +#endif + char* DentingFiles[] = { "LEVELS\\CHICAGO.DEN", @@ -28,7 +51,7 @@ char* DentingFiles[] = u_char gCarDamageZoneVerts[MAX_CAR_MODELS][NUM_DAMAGE_ZONES][MAX_DAMAGE_ZONE_VERTS]; u_char gHDCarDamageZonePolys[MAX_CAR_MODELS][NUM_DAMAGE_ZONES][MAX_DAMAGE_ZONE_POLYS]; -u_char gHDCarDamageLevels[MAX_CAR_MODELS][MAX_DAMAGE_LEVELS]; +u_char gHDCarDamageLevels[MAX_CAR_MODELS][MAX_DAMAGE_LEVELS]; // the damage level (texture) count for polygons // [D] [T] void InitialiseDenting(void) @@ -66,12 +89,11 @@ void DentCar(CAR_DATA *cp) // collect vertices from zones if (pCleanModel != NULL) { - VertNo = 0; - while (VertNo < pCleanModel->num_vertices) - tempDamage[VertNo++] = 0; + for (VertNo = 0; VertNo < pCleanModel->num_vertices; VertNo++) + tempDamage[VertNo] = 0; - Zone = 0; - do { + for (Zone = 0; Zone < NUM_DAMAGE_ZONES; Zone++) + { Damage = cp->ap.damage[Zone]; if (Damage > MaxDamage) @@ -79,21 +101,14 @@ void DentCar(CAR_DATA *cp) DamPtr = gCarDamageZoneVerts[cp->ap.model][Zone]; - VertNo = 0; - while (VertNo < MAX_DAMAGE_ZONE_VERTS && *DamPtr != 0xFF) + for (VertNo = 0; VertNo < MAX_DAMAGE_ZONE_VERTS && *DamPtr != 0xFF; VertNo++, DamPtr++) { if (tempDamage[*DamPtr] == 0) tempDamage[*DamPtr] += Damage; else tempDamage[*DamPtr] += Damage / 2; - - DamPtr++; - - VertNo++; } - - Zone++; - } while (Zone < NUM_DAMAGE_ZONES); + } } // update vertices positon @@ -102,17 +117,11 @@ void DentCar(CAR_DATA *cp) DamVertPtr = (SVECTOR *)gCarDamModelPtr[model]->vertices; CleanVertPtr = (SVECTOR *)gCarCleanModelPtr[model]->vertices; - VertNo = 0; - while (VertNo < pCleanModel->num_vertices) + for (VertNo = 0; VertNo < pCleanModel->num_vertices; VertNo++, DamVertPtr++, CleanVertPtr++) { gTempCarVertDump[cp->id][VertNo].vx = CleanVertPtr->vx + FIXEDH((DamVertPtr->vx - CleanVertPtr->vx) * tempDamage[VertNo] / 2); gTempCarVertDump[cp->id][VertNo].vy = CleanVertPtr->vy + FIXEDH((DamVertPtr->vy - CleanVertPtr->vy) * tempDamage[VertNo] / 2); gTempCarVertDump[cp->id][VertNo].vz = CleanVertPtr->vz + FIXEDH((DamVertPtr->vz - CleanVertPtr->vz) * tempDamage[VertNo] / 2); - - DamVertPtr++; - CleanVertPtr++; - - VertNo++; } } @@ -122,21 +131,22 @@ void DentCar(CAR_DATA *cp) dentptr = gTempHDCarUVDump[cp->id]; // reset UV coordinates - Poly = 0; - while (Poly < pCleanModel->num_polys) + + for (Poly = 0; Poly < pCleanModel->num_polys; Poly++) { dentptr->u3 = 0; - Poly++; dentptr++; } - Zone = 0; - do { + for(Zone = 0; Zone < NUM_DAMAGE_ZONES; Zone++) + { Damage = cp->ap.damage[Zone]; - Poly = 0; - while (Poly < MAX_DAMAGE_ZONE_POLYS && gHDCarDamageZonePolys[cp->ap.model][Zone][Poly] != 0xFF) + for (Poly = 0; Poly < MAX_DAMAGE_ZONE_POLYS; Poly++) { + if (gHDCarDamageZonePolys[cp->ap.model][Zone][Poly] == 0xFF) + break; + dentptr = gTempHDCarUVDump[cp->id] + gHDCarDamageZonePolys[cp->ap.model][Zone][Poly]; // add a damage level @@ -145,28 +155,17 @@ void DentCar(CAR_DATA *cp) // clamp level if (dentptr->u3 > 2) dentptr->u3 = 2; - - Poly++; } - - Zone++; - } while (Zone < NUM_DAMAGE_ZONES); - - Poly = 0; + } DamPtr = gHDCarDamageLevels[model]; dentptr = gTempHDCarUVDump[cp->id]; - while (Poly < pCleanModel->num_polys) + for (Poly = 0; Poly < pCleanModel->num_polys; Poly++, DamPtr++, dentptr++) { // calculate the UV offset with strange XORs if(dentptr->u3 > 0) dentptr->u3 = (*DamPtr ^ 1 ^ (*DamPtr ^ 1 | dentptr->u3)) * 64; - - dentptr++; - - DamPtr++; - Poly++; } } } @@ -194,11 +193,9 @@ void CreateDentableCar(CAR_DATA *cp) while (vcount-- != -1) *dst++ = *src++; - count = 0; - while (count < srcModel->num_polys) + for (count = 0; count < srcModel->num_polys; count++) { gTempHDCarUVDump[cp->id][count].u3 = 0; - count++; } } else @@ -209,11 +206,9 @@ void CreateDentableCar(CAR_DATA *cp) srcModel = gCarLowModelPtr[model]; if (srcModel != NULL) { - count = 0; - while (count < srcModel->num_polys) + for (count = 0; count < srcModel->num_polys; count++) { gTempLDCarUVDump[cp->id][count].u3 = 0; - count++; } } else @@ -223,11 +218,9 @@ void CreateDentableCar(CAR_DATA *cp) if (gDontResetCarDamage == 0) { - count = 0; - while (count < NUM_DAMAGE_ZONES) + for (count = 0; count < NUM_DAMAGE_ZONES; count++) { cp->ap.damage[count] = 0; - count++; } cp->totalDamage = 0; @@ -392,7 +385,7 @@ void MoveHubcap() _MatrixRotate(&Position); savecombo = combointensity; - if (gTimeOfDay == 3) + if (gTimeOfDay == TIME_NIGHT) { cmb = (combointensity & 0xffU) / 3; combointensity = cmb << 0x10 | cmb << 8 | cmb; @@ -407,27 +400,6 @@ void MoveHubcap() } } -#ifndef PSX -// [A] loads car model from file -char* LoadCarDentingFromFile(char* dest, int modelNumber) -{ - char* mem; - char filename[64]; - - sprintf(filename, "LEVELS\\%s\\CARMODEL_%d.DEN", LevelNames[GameLevel], modelNumber); - if(FileExists(filename)) - { - mem = dest ? dest : ((char*)_other_buffer + modelNumber * 4096); - - // get model from file - Loadfile(filename, mem); - return mem; - } - - return NULL; -} -#endif - // [D] [T] void ProcessDentLump(char *lump_ptr, int lump_size) { @@ -436,9 +408,7 @@ void ProcessDentLump(char *lump_ptr, int lump_size) int offset; u_char* mem; - i = 0; - - while (i < MAX_CAR_MODELS) + for (i = 0; i < MAX_CAR_MODELS; i++) { model = MissionHeader->residentModels[i]; @@ -456,12 +426,16 @@ void ProcessDentLump(char *lump_ptr, int lump_size) { offset = *(int *)(lump_ptr + model * 4); mem = (u_char*)lump_ptr; -#ifndef PSX - char* newDenting = LoadCarDentingFromFile(NULL, model); - if(newDenting) +#if USE_PC_FILESYSTEM + if (gContentOverride) { - mem = (u_char*)newDenting; - offset = 0; + char* newDenting; + newDenting = LoadCustomCarDentingFromFile(NULL, model); + if (newDenting) + { + mem = (u_char*)newDenting; + offset = 0; + } } #endif @@ -473,8 +447,6 @@ void ProcessDentLump(char *lump_ptr, int lump_size) memcpy((u_char*)gHDCarDamageLevels[i], mem + offset, MAX_FILE_DAMAGE_LEVELS); } - - i++; } } @@ -484,6 +456,20 @@ void SetupSpecDenting(char *loadbuffer) { int offset; +#if USE_PC_FILESYSTEM + if (gContentOverride) + { + char* newDenting; + int model; + model = MissionHeader->residentModels[4]; + + newDenting = LoadCustomCarDentingFromFile(NULL, model); + if (newDenting) + loadbuffer = newDenting; + } + +#endif + // [A] this is better memcpy((u_char*)gCarDamageZoneVerts[4], (u_char*)loadbuffer, NUM_DAMAGE_ZONES * MAX_FILE_DAMAGE_ZONE_VERTS); offset = NUM_DAMAGE_ZONES * MAX_FILE_DAMAGE_ZONE_VERTS; diff --git a/src_rebuild/Game/C/director.c b/src_rebuild/Game/C/director.c index e7b836683..22d8448b0 100644 --- a/src_rebuild/Game/C/director.c +++ b/src_rebuild/Game/C/director.c @@ -27,52 +27,52 @@ struct REPLAY_ICON short tx, ty; }; -TEXTURE_DETAILS delcam; // address 0xC0EE0 -TEXTURE_DETAILS incar; // address 0xBF950 -TEXTURE_DETAILS lenschan; // address 0xC1DB0 -TEXTURE_DETAILS lookcar; // address 0xC1CB0 -TEXTURE_DETAILS movecam; // address 0xC1C18 -TEXTURE_DETAILS movecampos; // address 0xC08C0 -TEXTURE_DETAILS ok; // address 0xC1BD8 -TEXTURE_DETAILS pause; // address 0xBF970 -TEXTURE_DETAILS playpause; // address 0xC2A28 -TEXTURE_DETAILS clock; // address 0xBF940 -TEXTURE_DETAILS choosecar; // address 0xC2A08 -TEXTURE_DETAILS chasecar; // address 0xC1D60 -TEXTURE_DETAILS addcam; // address 0xC0A50 -TEXTURE_DETAILS autocam; // address 0xC1CA0 -TEXTURE_DETAILS playcam; // address 0xC1C08 -TEXTURE_DETAILS restart; // address 0xC1CD0 -TEXTURE_DETAILS save2card; // address 0xC1F40 -TEXTURE_DETAILS editcam; // address 0xC1BE8 -TEXTURE_DETAILS fixedcam; // address 0xC1D70 -TEXTURE_DETAILS frameadv; // address 0xC1D80 +TEXTURE_DETAILS icon_delcam; // address 0xC0EE0 +TEXTURE_DETAILS icon_incar; // address 0xBF950 +TEXTURE_DETAILS icon_lenschan; // address 0xC1DB0 +TEXTURE_DETAILS icon_lookcar; // address 0xC1CB0 +TEXTURE_DETAILS icon_movecam; // address 0xC1C18 +TEXTURE_DETAILS icon_movecampos; // address 0xC08C0 +TEXTURE_DETAILS icon_ok; // address 0xC1BD8 +TEXTURE_DETAILS icon_pause; // address 0xBF970 +TEXTURE_DETAILS icon_playpause; // address 0xC2A28 +TEXTURE_DETAILS icon_clock; // address 0xBF940 +TEXTURE_DETAILS icon_choosecar; // address 0xC2A08 +TEXTURE_DETAILS icon_chasecar; // address 0xC1D60 +TEXTURE_DETAILS icon_addcam; // address 0xC0A50 +TEXTURE_DETAILS icon_autocam; // address 0xC1CA0 +TEXTURE_DETAILS icon_playcam; // address 0xC1C08 +TEXTURE_DETAILS icon_restart; // address 0xC1CD0 +TEXTURE_DETAILS icon_save2card; // address 0xC1F40 +TEXTURE_DETAILS icon_editcam; // address 0xC1BE8 +TEXTURE_DETAILS icon_fixedcam; // address 0xC1D70 +TEXTURE_DETAILS icon_frameadv; // address 0xC1D80 REPLAY_ICON replay_icons[] = { - { 20, 26, &pause, G_LTXT_ID(GTXT_Pause), 20, 48 }, - { 20, 26, &playpause, G_LTXT_ID(GTXT_Play), 20, 48 }, - { 44, 26, &autocam, G_LTXT_ID(GTXT_AutoDirector), 44, 48 }, - { 68, 26, &playcam, G_LTXT_ID(GTXT_FastForward), 68, 48 }, - { 92, 26, &frameadv, G_LTXT_ID(GTXT_FrameAdvance), 92, 48 }, - { 116, 26, &restart, G_LTXT_ID(GTXT_Rewindtobeginning), 116, 48 }, - { 140, 26, &addcam, G_LTXT_ID(GTXT_AddCamera), 140, 48 }, - { 164, 26, &editcam, G_LTXT_ID(GTXT_EditCamera), 164, 48 }, - { 188, 26, &save2card, G_LTXT_ID(GTXT_SaveReplay), 188, 48 }, - { 212, 26, &ok, G_LTXT_ID(GTXT_Exit), 212, 48 }, - { 140, 50, &incar, G_LTXT_ID(GTXT_InCar), 164, 48 }, - { 140, 74, &chasecar, G_LTXT_ID(GTXT_ChaseCamera), 164, 72 }, - { 140, 98, &fixedcam, G_LTXT_ID(GTXT_TripodCamera), 164, 96 }, - { 140, 122, &ok, G_LTXT_ID(GTXT_Accept), 164, 120 }, - { 140, 122, &clock, G_LTXT_ID(GTXT_MoveCameraStart), 164, 120 }, - { 140, 146, &delcam, G_LTXT_ID(GTXT_DeleteCamera), 164, 144 }, - { 140, 170, &ok, G_LTXT_ID(GTXT_Accept), 164, 168 }, - { 164, 50, &choosecar, G_LTXT_ID(GTXT_YouorPursuer), 164, 72 }, - { 164, 74, &movecampos, G_LTXT_ID(GTXT_MoveCamera), 164, 96 }, - { 164, 98, &movecampos, G_LTXT_ID(GTXT_MoveCamera), 164, 120 }, - { 188, 98, &lookcar, G_LTXT_ID(GTXT_LocktoCar), 188, 120 }, - { 212, 98, &movecam, G_LTXT_ID(GTXT_Rotate), 212, 120 }, - { 236, 98, &lenschan, G_LTXT_ID(GTXT_Zoom), 236, 120 } + { 20, 26, &icon_pause, G_LTXT_ID(GTXT_Pause), 20, 48 }, + { 20, 26, &icon_playpause, G_LTXT_ID(GTXT_Play), 20, 48 }, + { 44, 26, &icon_autocam, G_LTXT_ID(GTXT_AutoDirector), 44, 48 }, + { 68, 26, &icon_playcam, G_LTXT_ID(GTXT_FastForward), 68, 48 }, + { 92, 26, &icon_frameadv, G_LTXT_ID(GTXT_FrameAdvance), 92, 48 }, + { 116, 26, &icon_restart, G_LTXT_ID(GTXT_Rewindtobeginning), 116, 48 }, + { 140, 26, &icon_addcam, G_LTXT_ID(GTXT_AddCamera), 140, 48 }, + { 164, 26, &icon_editcam, G_LTXT_ID(GTXT_EditCamera), 164, 48 }, + { 188, 26, &icon_save2card, G_LTXT_ID(GTXT_SaveReplay), 188, 48 }, + { 212, 26, &icon_ok, G_LTXT_ID(GTXT_Exit), 212, 48 }, + { 140, 50, &icon_incar, G_LTXT_ID(GTXT_InCar), 164, 48 }, + { 140, 74, &icon_chasecar, G_LTXT_ID(GTXT_ChaseCamera), 164, 72 }, + { 140, 98, &icon_fixedcam, G_LTXT_ID(GTXT_TripodCamera), 164, 96 }, + { 140, 122, &icon_ok, G_LTXT_ID(GTXT_Accept), 164, 120 }, + { 140, 122, &icon_clock, G_LTXT_ID(GTXT_MoveCameraStart), 164, 120 }, + { 140, 146, &icon_delcam, G_LTXT_ID(GTXT_DeleteCamera), 164, 144 }, + { 140, 170, &icon_ok, G_LTXT_ID(GTXT_Accept), 164, 168 }, + { 164, 50, &icon_choosecar, G_LTXT_ID(GTXT_YouorPursuer), 164, 72 }, + { 164, 74, &icon_movecampos, G_LTXT_ID(GTXT_MoveCamera), 164, 96 }, + { 164, 98, &icon_movecampos, G_LTXT_ID(GTXT_MoveCamera), 164, 120 }, + { 188, 98, &icon_lookcar, G_LTXT_ID(GTXT_LocktoCar), 188, 120 }, + { 212, 98, &icon_movecam, G_LTXT_ID(GTXT_Rotate), 212, 120 }, + { 236, 98, &icon_lenschan, G_LTXT_ID(GTXT_Zoom), 236, 120 } }; unsigned char menu0[] = { 0, 0xFF }; diff --git a/src_rebuild/Game/C/director.h b/src_rebuild/Game/C/director.h index 3aa6219f7..4432dae0e 100644 --- a/src_rebuild/Game/C/director.h +++ b/src_rebuild/Game/C/director.h @@ -1,26 +1,26 @@ #ifndef DIRECTOR_H #define DIRECTOR_H -extern TEXTURE_DETAILS addcam; -extern TEXTURE_DETAILS frameadv; -extern TEXTURE_DETAILS autocam; -extern TEXTURE_DETAILS chasecar; -extern TEXTURE_DETAILS choosecar; -extern TEXTURE_DETAILS clock; -extern TEXTURE_DETAILS delcam; -extern TEXTURE_DETAILS editcam; -extern TEXTURE_DETAILS fixedcam; -extern TEXTURE_DETAILS incar; -extern TEXTURE_DETAILS lenschan; -extern TEXTURE_DETAILS lookcar; -extern TEXTURE_DETAILS movecam; -extern TEXTURE_DETAILS movecampos; -extern TEXTURE_DETAILS ok; -extern TEXTURE_DETAILS pause; -extern TEXTURE_DETAILS playcam; -extern TEXTURE_DETAILS playpause; -extern TEXTURE_DETAILS save2card; -extern TEXTURE_DETAILS restart; +extern TEXTURE_DETAILS icon_addcam; +extern TEXTURE_DETAILS icon_frameadv; +extern TEXTURE_DETAILS icon_autocam; +extern TEXTURE_DETAILS icon_chasecar; +extern TEXTURE_DETAILS icon_choosecar; +extern TEXTURE_DETAILS icon_clock; +extern TEXTURE_DETAILS icon_delcam; +extern TEXTURE_DETAILS icon_editcam; +extern TEXTURE_DETAILS icon_fixedcam; +extern TEXTURE_DETAILS icon_incar; +extern TEXTURE_DETAILS icon_lenschan; +extern TEXTURE_DETAILS icon_lookcar; +extern TEXTURE_DETAILS icon_movecam; +extern TEXTURE_DETAILS icon_movecampos; +extern TEXTURE_DETAILS icon_ok; +extern TEXTURE_DETAILS icon_pause; +extern TEXTURE_DETAILS icon_playcam; +extern TEXTURE_DETAILS icon_playpause; +extern TEXTURE_DETAILS icon_save2card; +extern TEXTURE_DETAILS icon_restart; extern int FastForward; extern int EditMode; diff --git a/src_rebuild/Game/C/dr2roads.c b/src_rebuild/Game/C/dr2roads.c index 236d0f902..bade3a62b 100644 --- a/src_rebuild/Game/C/dr2roads.c +++ b/src_rebuild/Game/C/dr2roads.c @@ -5,7 +5,6 @@ #include "map.h" #include "event.h" #include "convert.h" -#include "cutscene.h" #include "mission.h" #include "handling.h" #include "main.h" @@ -102,7 +101,7 @@ int GetSurfaceRoadInfo(DRIVER2_ROAD_INFO* outRoadInfo, int surfId) if(IS_CURVED_SURFACE(surfId)) { outRoadInfo->curve = curve = GET_CURVE(surfId); - outRoadInfo->ConnectIdx = &curve->ConnectIdx; + outRoadInfo->ConnectIdx = curve->ConnectIdx; outRoadInfo->NumLanes = curve->NumLanes; outRoadInfo->LaneDirs = curve->LaneDirs; outRoadInfo->AILanes = curve->AILanes; @@ -111,7 +110,7 @@ int GetSurfaceRoadInfo(DRIVER2_ROAD_INFO* outRoadInfo, int surfId) else if (IS_STRAIGHT_SURFACE(surfId)) { outRoadInfo->straight = straight = GET_STRAIGHT(surfId); - outRoadInfo->ConnectIdx = &straight->ConnectIdx; + outRoadInfo->ConnectIdx = straight->ConnectIdx; outRoadInfo->NumLanes = straight->NumLanes; outRoadInfo->LaneDirs = straight->LaneDirs; outRoadInfo->AILanes = straight->AILanes; @@ -120,7 +119,7 @@ int GetSurfaceRoadInfo(DRIVER2_ROAD_INFO* outRoadInfo, int surfId) else if (IS_JUNCTION_SURFACE(surfId)) { junction = GET_JUNCTION(surfId); - outRoadInfo->ConnectIdx = &junction->ExitIdx; + outRoadInfo->ConnectIdx = junction->ExitIdx; } return 0; @@ -204,11 +203,13 @@ void ProcessStraightsDriver2Lump(char *lump_file, int lump_size) } else if (GameLevel == 2) { +#if ENABLE_GAME_FIXES int i; DRIVER2_STRAIGHT* str; Driver2StraightsPtr[348].ConnectIdx[2] = 8244; Driver2StraightsPtr[348].ConnectIdx[3] = 351; +#endif } } @@ -348,71 +349,71 @@ sdPlane* sdGetCell(VECTOR *pos) // Alpha 1.6 code, works too; not widely tested yet //buffer = *(short**)((int)RoadMapDataRegions + (cellPos.x >> 14 & 4 ^ cellPos.y >> 13 & 8 ^ sdSelfModifyingCode)); - plane = NULL; + plane = GetSeaPlane(); - if (*buffer == 2) - { - sdPlane* planeData = (sdPlane*)((char*)buffer + buffer[1]); - short* bspData = (short*)((char*)buffer + buffer[2]); - sdNode* nodeData = (sdNode*)((char*)buffer + buffer[3]); + if (*buffer != 2) + return &default_plane; + + sdPlane* planeData = (sdPlane*)((char*)buffer + buffer[1]); + short* bspData = (short*)((char*)buffer + buffer[2]); + sdNode* nodeData = (sdNode*)((char*)buffer + buffer[3]); - surface = &buffer[(cellPos.x >> 10 & 63) + - (cellPos.y >> 10 & 63) * 64 + 4]; + surface = &buffer[(cellPos.x >> 10 & 63) + + (cellPos.y >> 10 & 63) * 64 + 4]; - // initial surface - if (*surface == -1) - return GetSeaPlane(); + // initial surface + if (*surface == -1) + return plane; - // check surface has overlapping planes flag (aka multiple levels) - if ((*surface & 0x6000) == 0x2000) - { - surface = &bspData[*surface & 0x1fff]; - do { - if(-256 - pos->vy > *surface) - { - surface += 2; - sdLevel++; - } - else - break; - } while (*surface != -0x8000); // end flag + // check surface has overlapping planes flag (aka multiple levels) + if ((*surface & 0x6000) == 0x2000) + { + surface = &bspData[*surface & 0x1fff]; + do { + if(-256 - pos->vy > *surface) + { + surface += 2; + sdLevel++; + } + else + break; + } while (*surface != -0x8000); // end flag - surface += 1; - } + surface += 1; + } - // iterate surfaces if BSP - do { - nextLevel = 0; + // iterate surfaces if BSP + do { + nextLevel = 0; - // check if it's has BSP properties - // basically it determines surface bounds - if (*surface & 0x4000) - { - // get closest surface by BSP lookup - BSPSurface = sdGetBSP(&nodeData[*surface & 0x3fff], &cell); + // check if it's has BSP properties + // basically it determines surface bounds + if (*surface & 0x4000) + { + // get closest surface by BSP lookup + BSPSurface = sdGetBSP(&nodeData[*surface & 0x3fff], &cell); - if (*BSPSurface == 0x7fff) - { - sdLevel++; - nextLevel = 1; + if (*BSPSurface == 0x7fff) + { + sdLevel++; + nextLevel = 1; - BSPSurface = surface + 2; // get to the next node - } - - surface = BSPSurface; + BSPSurface = surface + 2; // get to the next node } - } while (nextLevel); - plane = &planeData[*surface]; - - if (((int)plane & 3) == 0 && *(int *)plane != -1) - { - if (plane->surface - 16U < 16) - plane = EventSurface(pos, plane); + surface = BSPSurface; } - else - plane = GetSeaPlane(); + } while (nextLevel); + + plane = &planeData[*surface]; + + if (((int)plane & 3) == 0 && *(int *)plane != -1) + { + if (plane->surface - 16U < 16) + plane = EventSurface(pos, plane); } + else + plane = GetSeaPlane(); return plane; } @@ -433,7 +434,7 @@ sdPlane * FindRoadInBSP(sdNode *node, sdPlane *base) { sdPlane *plane; - while (true) + do { if (node->value > -1) { @@ -442,12 +443,8 @@ sdPlane * FindRoadInBSP(sdNode *node, sdPlane *base) } plane = FindRoadInBSP(node + 1, base); - - if (plane != NULL) - break; - node += node->n.offset; - } + } while (plane == NULL); return plane; } @@ -550,14 +547,17 @@ int MapHeight(VECTOR *pos) } // [D] [T] -int FindSurfaceD2(VECTOR *pos, VECTOR *normal, VECTOR *out, sdPlane **plane) +void FindSurfaceD2(VECTOR *pos, VECTOR *normal, VECTOR *out, sdPlane **plane) { - *plane = sdGetCell(pos); + sdPlane* pl; + pl = sdGetCell(pos); + + *plane = pl; out->vx = pos->vx; out->vz = pos->vz; - out->vy = sdHeightOnPlane(pos, *plane); + out->vy = sdHeightOnPlane(pos, pl); - if (*plane == NULL || (*plane)->b == 0) + if (pl == NULL || pl->b == 0) { normal->vx = 0; normal->vy = 4096; @@ -565,24 +565,8 @@ int FindSurfaceD2(VECTOR *pos, VECTOR *normal, VECTOR *out, sdPlane **plane) } else { - normal->vx = (int)(*plane)->a >> 2; - normal->vy = (int)(*plane)->b >> 2; - normal->vz = (int)(*plane)->c >> 2; - } - - if (*plane == NULL) - { - return 4096; + normal->vx = (int)pl->a >> 2; + normal->vy = (int)pl->b >> 2; + normal->vz = (int)pl->c >> 2; } - else if ((*plane)->surface == 4) - { - if (gInGameCutsceneActive && gCurrentMissionNumber == 23 && gInGameCutsceneID == 0) - out->vy += RSIN((pos->vx + pos->vz) * 2) >> 9; // rcossin_tbl[(pos->vx + pos->vz) * 4 & 0x1fff] >> 9; - else - out->vy += (RSIN((pos->vx + pos->vz) * 2) >> 8) / 3; // (rcossin_tbl[(pos->vx + pos->vz) * 4 & 0x1fff] >> 8) / 3; - - return 2048; - } - - return 4096; } \ No newline at end of file diff --git a/src_rebuild/Game/C/dr2roads.h b/src_rebuild/Game/C/dr2roads.h index c989e26b8..27bdb39d4 100644 --- a/src_rebuild/Game/C/dr2roads.h +++ b/src_rebuild/Game/C/dr2roads.h @@ -12,20 +12,25 @@ #define GET_CURVE(surfid) (Driver2CurvesPtr + ((surfid) & 0x1FFF)) #define GET_JUNCTION(surfid) (Driver2JunctionsPtr + ((surfid) & 0x1FFF)) +// AI lanes - each bit represents lane being enabled for Civ AI to be driven +// Lane dirs - each bit represents lane direction invertion +// NumLanes - | Lane count (4 bits - max 15) | speed limit id (2 bits - max 3) | Leftmost Lane parking | Rightmost Lane parking + // $DEPRECATED: as it's used to detect lane direction, use ROAD_LANE_DIR instead #define IS_NARROW_ROAD(rd) \ ((*(ushort*)&(rd)->NumLanes & 0xFFFF) == 0xFF01) // those macros can be applied to straights and junctions -#define ROAD_LANES_COUNT(rd) ((u_int)(rd)->NumLanes & 0xF) // lane count -#define ROAD_WIDTH_IN_LANES(rd) (ROAD_LANES_COUNT(rd) * 2) // width in lanes -#define ROAD_IS_AI_LANE(rd, lane) ((u_char)(rd)->AILanes >> ((lane) / 2) & 1U) // lane AI driveable flag -#define ROAD_IS_LEFTMOST_LANE_PARKING(rd) (((u_char)(rd)->NumLanes & 0x40) != 0) // allows parking on leftmost lane -#define ROAD_IS_RIGHTMOST_LANE_PARKING(rd) (((u_char)(rd)->NumLanes & 0x80) != 0) // allows parking on rightmost lane -#define ROAD_LANE_DIRECTION_BIT(rd, lane) ((u_char)(rd)->LaneDirs >> ((lane) / 2) & 1U) // direction bit -#define ROAD_SPEED_LIMIT(rd) (((u_char)(rd)->NumLanes >> 4) & 3) // speed limit id -#define ROAD_HAS_FAST_LANES(rd) (((u_char)(rd)->NumLanes >> 6) & 1) // & 0x20; in fact speed limit check too +#define ROAD_IS_AI_LANE(rd, lane) ((u_char)(rd)->AILanes >> ((lane) / 2) & 1U) // lane AI driveable flag +#define ROAD_LANE_DIRECTION_BIT(rd, lane) ((u_char)(rd)->LaneDirs >> ((lane) / 2) & 1U) // direction bit + +#define ROAD_LANES_COUNT(rd) ((u_int)(rd)->NumLanes & 15) // lane count +#define ROAD_WIDTH_IN_LANES(rd) (ROAD_LANES_COUNT(rd) * 2) // width in lanes + +#define ROAD_IS_LEFTMOST_LANE_PARKING(rd) (((u_char)(rd)->NumLanes & 0x40) != 0) // bit 7 allows parking on leftmost lane +#define ROAD_IS_RIGHTMOST_LANE_PARKING(rd) (((u_char)(rd)->NumLanes & 0x80) != 0) // bit 8 allows parking on rightmost lane +#define ROAD_SPEED_LIMIT(rd) (((u_char)(rd)->NumLanes >> 4) & 3) // speed limit id #define ROAD_LANE_DIR(rd, lane) \ (((u_char)(rd)->LaneDirs == 0xFF && (rd)->NumLanes == 1) ? ((lane) & 1) : ROAD_LANE_DIRECTION_BIT(rd, lane)) @@ -38,7 +43,7 @@ struct DRIVER2_ROAD_INFO { int surfId; - short (*ConnectIdx)[4]; + short* ConnectIdx; // [4] char NumLanes; char LaneDirs; char AILanes; @@ -75,7 +80,7 @@ extern void ProcessJunctionsDriver2Lump(char *lump_file, int lump_size, int fix) extern int MapHeight(VECTOR *pos); // 0x000137CC -extern int FindSurfaceD2(VECTOR *pos, VECTOR *normal, VECTOR *out, sdPlane **plane); // 0x00012EF4 +extern void FindSurfaceD2(VECTOR *pos, VECTOR *normal, VECTOR *out, sdPlane **plane); // 0x00012EF4 extern int GetSurfaceIndex(VECTOR *pos); // 0x0001380C extern int RoadInCell(VECTOR *pos); // 0x0001322C diff --git a/src_rebuild/Game/C/draw.c b/src_rebuild/Game/C/draw.c index e5756978e..4a02c713a 100644 --- a/src_rebuild/Game/C/draw.c +++ b/src_rebuild/Game/C/draw.c @@ -16,6 +16,9 @@ #include "ASM/rndrasm.h" #include "event.h" +#ifdef PSX +#pragma GCC optimization ("O3") +#endif MATRIX aspect = { @@ -91,26 +94,26 @@ void* model_tile_ptrs[MAX_DRAWN_TILES]; void* anim_obj_buffer[MAX_DRAWN_ANIMATING]; void* spriteList[MAX_DRAWN_SPRITES]; -u_int planeColours[8]; - MATRIX inv_camera_matrix; MATRIX face_camera; MATRIX2 CompoundMatrix[64]; u_int farClip2Player = 36000; +#ifdef PSX +int goFaster = 1; +#else int goFaster = 0; // [A] was 1 +#endif int fasterToggle = 0; -//int current_object_computed_value = 0; - int combointensity; -char CurrentPVS[444]; // 20*20+4 +char CurrentPVS[PVS_CELL_COUNT * PVS_CELL_COUNT + 3]; // 20*20+4 MATRIX2 matrixtable[64]; int setupYet = 0; -int gDrawDistance = 441; +int gDrawDistance = PVS_CELL_COUNT * PVS_CELL_COUNT; #ifndef PSX _pct& plotContext = *(_pct*)((u_char*)getScratchAddr(0) + 1024 - sizeof(_pct)); // orig offset: 0x1f800020 @@ -136,8 +139,8 @@ void addSubdivSpriteShadow(POLYFT4* src, SVECTOR* verts, int z) plotContext.colour = 0x2E000000; plotContext.flags = PLOT_INV_CULL; - plotContext.clut = texture_cluts[src->texture_set][src->texture_id] << 0x10; - plotContext.tpage = texture_pages[src->texture_set] << 0x10; + plotContext.clut = texture_cluts[src->texture_set][src->texture_id]; + plotContext.tpage = texture_pages[src->texture_set]; if (z > 3200) m = 1; //2; @@ -162,9 +165,6 @@ void addSubdivSpriteShadow(POLYFT4* src, SVECTOR* verts, int z) plotContext.ot -= 28; } - -MATRIX shadowMatrix; - // [D] [T] [A] void DrawSprites(PACKED_CELL_OBJECT** sprites, int numFound) { @@ -179,30 +179,26 @@ void DrawSprites(PACKED_CELL_OBJECT** sprites, int numFound) #if 0 //def PSX MVERTEX5x5& subdiVerts = *(MVERTEX5x5*)(u_char*)getScratchAddr(0); + MATRIX shadowMatrix = *(MATRIX*)((u_char*)getScratchAddr(0) + sizeof(MVERTEX5x5)); #else MVERTEX5x5 subdiVerts; + MATRIX shadowMatrix; #endif + SVECTOR* lightVec; - lightdd = FIXEDH(camera_matrix.m[2][0] * day_vectors[GameLevel].vx) + - FIXEDH(camera_matrix.m[2][1] * day_vectors[GameLevel].vy) + - FIXEDH(camera_matrix.m[2][2] * day_vectors[GameLevel].vz) + ONE * 3072; + lightVec = (gTimeOfDay == TIME_NIGHT) ? night_vectors : day_vectors; - lightLevel = (lightdd >> 0x12) + 0x20U & 0xff; + lightdd = FIXEDH(camera_matrix.m[2][0] * lightVec[GameLevel].vx) + + FIXEDH(camera_matrix.m[2][1] * lightVec[GameLevel].vy) + + FIXEDH(camera_matrix.m[2][2] * lightVec[GameLevel].vz) + ONE * 3072; - if (gWeather > 0 && gTimeOfDay == 1) - { - lightLevel = (lightLevel * 2 * NightAmbient) >> 8 & 0xff; - } + lightLevel = (lightdd >> 18) + 32 & 255; - if (gTimeOfDay == 0) - { - lightLevel = (lightLevel * 2 * NightAmbient) >> 8; - } - else if (gTimeOfDay == 2) + if (gWeather > WEATHER_NONE && gTimeOfDay == TIME_DAY || (M_BIT(gTimeOfDay) & (M_BIT(TIME_DAWN) | M_BIT(TIME_DUSK)))) { lightLevel = (lightLevel * 2 * NightAmbient) >> 8; } - else if (gTimeOfDay == 3) + if (gTimeOfDay == TIME_NIGHT) { if (GameLevel == 0) lightLevel *= 2; // [A] level bug - Chicago trees lit wrong @@ -210,43 +206,44 @@ void DrawSprites(PACKED_CELL_OBJECT** sprites, int numFound) lightLevel /= 3; } - spriteColour = lightLevel << 0x10 | lightLevel << 8 | 0x2c000000 | lightLevel; + spriteColour = lightLevel << 0x10 | lightLevel << 8 | lightLevel | 0x2c000000; - i = 2; - do { + for (i = 0; i < 3; i++) + { shadowMatrix.m[i][0] = inv_camera_matrix.m[i][2]; shadowMatrix.m[i][1] = -inv_camera_matrix.m[i][0]; shadowMatrix.m[i][2] = inv_camera_matrix.m[i][0]; - } while (i--); - + } plotContext.primptr = current->primptr; plotContext.ptexture_pages = (ushort(*)[128])texture_pages; plotContext.ptexture_cluts = (ushort(*)[128][32])texture_cluts; plotContext.polySizes = PolySizes; plotContext.ot = current->ot; - - list = sprites; - plotContext.colour = spriteColour; plotContext.current = current; + list = sprites; numShadows = 0; while (numFound--) { + int modelnumber; pco = *list; list++; - int modelnumber = (pco->value >> 6) | (pco->pos.vy & 1) << 10; - + modelnumber = (pco->value >> 6) | (pco->pos.vy & 1) << 10; model = modelpointers[modelnumber]; - plotContext.colour = spriteColour; -#ifndef PSX - if ((pco->value & 0x3f) == 63 || (gTimeOfDay == 3 && modelnumber == 945)) // [A] Vegas tree fix + if ((pco->value & 63) == 63 || litSprites[modelnumber >> 5] & 1 << (modelnumber & 31)) // [A] multiple sprites lighting fixes + { plotContext.colour = 0x2c808080; -#endif + } + else + { + plotContext.colour = spriteColour; + } + plotContext.scribble[0] = pco->pos.vx; plotContext.scribble[1] = (pco->pos.vy << 0x10) >> 0x11; plotContext.scribble[2] = pco->pos.vz; @@ -263,12 +260,12 @@ void DrawSprites(PACKED_CELL_OBJECT** sprites, int numFound) src = (POLYFT4*)model->poly_block; verts = (SVECTOR*)model->vertices; - plotContext.flags |= 4; + plotContext.flags |= PLOT_NO_CULL; while (numPolys--, numPolys >= 0) { - plotContext.clut = texture_cluts[src->texture_set][src->texture_id] << 0x10; - plotContext.tpage = texture_pages[src->texture_set] << 0x10; + plotContext.clut = texture_cluts[src->texture_set][src->texture_id]; + plotContext.tpage = texture_pages[src->texture_set]; copyVector(&subdiVerts.verts[0][0], &verts[src->v0]); subdiVerts.verts[0][0].uv.val = *(ushort*)&src->uv0; @@ -288,7 +285,7 @@ void DrawSprites(PACKED_CELL_OBJECT** sprites, int numFound) src++; } - plotContext.flags &= ~4; + plotContext.flags &= ~PLOT_NO_CULL; } else { @@ -305,8 +302,8 @@ void DrawSprites(PACKED_CELL_OBJECT** sprites, int numFound) #define MAX_TREE_SHADOW_DISTANCE 14000 #endif - if (wetness == 0 && gTimeOfDay != 3 && - (pco->value & 0x20) == 0 && + if (wetness == 0 && gTimeOfDay != TIME_NIGHT && + (pco->value & 32) == 0 && z < MAX_TREE_SHADOW_DISTANCE && numShadows < 40) { @@ -330,76 +327,86 @@ void SetupPlaneColours(u_int ambient) { u_int r, g, b; - if ((gWeather - 1U > 1) && gTimeOfDay != 0 && gTimeOfDay != 2) + if (gWeather == 0 && (M_BIT(gTimeOfDay) & (M_BIT(TIME_DAWN) | M_BIT(TIME_DUSK))) == 0) { - if (gTimeOfDay == 1) + if (gTimeOfDay == TIME_DAY) { - b = ambient & 0xff; - g = ambient >> 8 & 0xff; - r = ambient >> 0x10 & 0xff; - - planeColours[1] = (r * 0x78 >> 7) << 0x10 | (g * 0x78 >> 7) << 8 | b * 0x78 >> 7; - planeColours[2] = (r * 0x67 >> 7) << 0x10 | (g * 0x67 >> 7) << 8 | b * 0x67 >> 7; - planeColours[3] = (r * 0xd >> 5) << 0x10 | (g * 0xd >> 5) << 8 | b * 0xd >> 5; - planeColours[0] = r << 0x10 | g << 8 | b; - planeColours[4] = (r * 3 >> 3) << 0x10 | (g * 3 >> 3) << 8 | b * 3 >> 3; - planeColours[5] = planeColours[3]; - planeColours[6] = planeColours[2]; - planeColours[7] = planeColours[1]; + b = ambient & 255; + g = ambient >> 8 & 255; + r = ambient >> 16 & 255; + + plotContext.planeColours[1] = (r * 120 >> 7) << 16 | (g * 120 >> 7) << 8 | b * 120 >> 7; + plotContext.planeColours[2] = (r * 103 >> 7) << 16 | (g * 103 >> 7) << 8 | b * 103 >> 7; + plotContext.planeColours[3] = (r * 13 >> 5) << 16 | (g * 13 >> 5) << 8 | b * 13 >> 5; + plotContext.planeColours[0] = r << 16 | g << 8 | b; + plotContext.planeColours[4] = (r * 3 >> 3) << 16 | (g * 3 >> 3) << 8 | b * 3 >> 3; + plotContext.planeColours[5] = plotContext.planeColours[3]; + plotContext.planeColours[6] = plotContext.planeColours[2]; + plotContext.planeColours[7] = plotContext.planeColours[1]; return; } - planeColours[0] = ambient; - planeColours[1] = ambient; - planeColours[2] = ambient; - planeColours[3] = ambient; - planeColours[4] = ambient; - planeColours[5] = ambient; - planeColours[6] = ambient; - planeColours[7] = ambient; + plotContext.planeColours[0] = ambient; + plotContext.planeColours[1] = ambient; + plotContext.planeColours[2] = ambient; + plotContext.planeColours[3] = ambient; + plotContext.planeColours[4] = ambient; + plotContext.planeColours[5] = ambient; + plotContext.planeColours[6] = ambient; + plotContext.planeColours[7] = ambient; return; } - planeColours[0] = ambient; - planeColours[1] = ambient + 0x10101; - planeColours[2] = ambient + 0x30303; - planeColours[3] = ambient + 0x80808; - planeColours[4] = ambient + 0xa0a0a; - planeColours[5] = ambient + 0x80808; - planeColours[6] = ambient + 0x30303; - planeColours[7] = ambient + 0x10101; + plotContext.planeColours[0] = ambient; + plotContext.planeColours[1] = ambient + 0x10101; + plotContext.planeColours[2] = ambient + 0x30303; + plotContext.planeColours[3] = ambient + 0x80808; + plotContext.planeColours[4] = ambient + 0xa0a0a; + plotContext.planeColours[5] = ambient + 0x80808; + plotContext.planeColours[6] = ambient + 0x30303; + plotContext.planeColours[7] = ambient + 0x10101; } +int current_pvs_cell; + + // [D] [T] void SetupDrawMapPSX(void) { - int region_x1; - int region_z1; - int current_barrel_region_x1; - int current_barrel_region_z1; + int cell_x, cell_z; int theta; + int pvs_cell; if (setupYet != 0) + { return; + } + + cell_x = (camera_position.vx + units_across_halved) / MAP_CELL_SIZE; + cell_z = (camera_position.vz + units_down_halved) / MAP_CELL_SIZE; - current_cell_x = (camera_position.vx + units_across_halved) / MAP_CELL_SIZE; - current_cell_z = (camera_position.vz + units_down_halved) / MAP_CELL_SIZE; + current_cell_x = cell_x; + current_cell_z = cell_z; - region_x1 = current_cell_x / MAP_REGION_SIZE; - region_z1 = current_cell_z / MAP_REGION_SIZE; + pvs_cell = (cell_z % MAP_REGION_SIZE) * MAP_REGION_SIZE + (cell_x % MAP_REGION_SIZE); + if (pvs_cell != current_pvs_cell) + { + int region_x1, region_z1; + int current_barrel_region_x1, current_barrel_region_z1; - current_barrel_region_x1 = (region_x1 & 1); - current_barrel_region_z1 = (region_z1 & 1); + region_x1 = cell_x / MAP_REGION_SIZE; + region_z1 = cell_z / MAP_REGION_SIZE; - GetPVSRegionCell2( - current_barrel_region_x1 + current_barrel_region_z1 * 2, - region_x1 + region_z1 * regions_across, - (current_cell_z % MAP_REGION_SIZE) * MAP_REGION_SIZE + (current_cell_x % MAP_REGION_SIZE), - CurrentPVS); + current_barrel_region_x1 = (region_x1 & 1); + current_barrel_region_z1 = (region_z1 & 1); - for (theta = 0; theta < 64; theta++) - MulMatrix0(&inv_camera_matrix, (MATRIX*)&matrixtable[theta], (MATRIX*)&CompoundMatrix[theta]); + current_pvs_cell = pvs_cell; + GetPVSRegionCell2( + current_barrel_region_x1 + current_barrel_region_z1 * 2, + region_x1 + region_z1 * regions_across, + pvs_cell, CurrentPVS); + } InitFrustrumMatrix(); SetFrustrumMatrix(); @@ -412,7 +419,7 @@ MATRIX frustrum_matrix; // [D] [T] void InitFrustrumMatrix(void) { - int a; + int a, t; a = -camera_angle.vy; @@ -431,7 +438,6 @@ void InitFrustrumMatrix(void) frustrum_matrix.m[2][1] = 0; frustrum_matrix.m[2][0] = RSIN(a); frustrum_matrix.m[2][2] = RCOS(a); - frustrum_matrix.t[0] = -80; } // [D] [T] @@ -474,6 +480,12 @@ void PlotMDL_less_than_128(MODEL* model) RenderModel(model, NULL, NULL, 0, 0, 0, 0); } +// [D] [T] +int PositionVisible(VECTOR* pos) +{ + return newPositionVisible(pos, CurrentPVS, current_cell_x, current_cell_z); +} + int gForceLowDetailCars = 0; int num_cars_drawn = 0; @@ -508,7 +520,7 @@ void DrawAllTheCars(int view) else dist = dx + dz / 2; - if (dist < 16000) + if (dist < VIEW_DRAW_DISTANCE) { car_distance[num_cars_to_draw] = dx + dz; cars_to_draw[num_cars_to_draw] = cp; @@ -523,36 +535,28 @@ void DrawAllTheCars(int view) { gForceLowDetailCars = 0; - // sort cars by distance - i = 1; - while (i < num_cars_to_draw) + // insertion sort of cars by distance + for (i = 1; i < num_cars_to_draw; i++) { cp = cars_to_draw[i]; dist = car_distance[i]; j = i - 1; - while (dist < car_distance[j]) + while (j >= 0 && dist < car_distance[j]) { car_distance[i] = car_distance[j]; cars_to_draw[i] = cars_to_draw[j]; - - if (j == 0) - break; - j--; } - cars_to_draw[i] = cp; - car_distance[i] = dist; - - i++; + cars_to_draw[j+1] = cp; + car_distance[j+1] = dist; } - i = 0; spacefree = (num_cars_to_draw - 1) * 2000; - while (i < num_cars_to_draw) + for (i = 0; i < num_cars_to_draw; i++) { // Don't exceed draw buffers if ((int)(current->primtab + (-3000 - (int)(current->primptr - PRIMTAB_SIZE))) < 5800) @@ -568,7 +572,6 @@ void DrawAllTheCars(int view) DrawCar(cars_to_draw[i], view); spacefree -= 2000; - i++; } } } @@ -585,9 +588,9 @@ u_int normalIndex(SVECTOR* verts, u_int vidx) SVECTOR p, q; - v0 = verts + (vidx & 0xff); - v1 = verts + (vidx >> 8 & 0xff); - v2 = verts + (vidx >> 16 & 0xff); + v0 = verts + (vidx & 255); + v1 = verts + (vidx >> 8 & 255); + v2 = verts + (vidx >> 16 & 255); p.vz = v1->vz - v0->vz; q.vz = v2->vz - v0->vz; @@ -618,7 +621,7 @@ u_int normalIndex(SVECTOR* verts, u_int vidx) if (x + y < 1) th23 = x < 0 ? 6 : 7; else - th23 = 0 < y ? 1 : 0; + th23 = y > 0 ? 1 : 0; } th23 *= 4; @@ -632,9 +635,9 @@ u_int normalIndex(SVECTOR* verts, u_int vidx) th23 += 1; if (nx + nz < -ny) - th23 = th23 & 0x1f | 2; + th23 = th23 & 31 | 2; else - th23 = th23 & 0x1f; + th23 = th23 & 31; return th23 | 0x80; } @@ -648,12 +651,12 @@ void ConvertPolygonTypes(MODEL* model, _pct* pc) int i; // [A] we are storing the processing flag here - if (model->tri_verts & 0x8000) + if (model->tri_verts & 0x80) { return; } - model->tri_verts |= 0x8000; + model->tri_verts |= 0x80; srcVerts = (SVECTOR*)model->vertices; polys = (PL_POLYFT4*)model->poly_block; @@ -662,10 +665,10 @@ void ConvertPolygonTypes(MODEL* model, _pct* pc) // pre-process vertices while (i-- > 0) { - ptype = polys->id & 0x1f; + ptype = polys->id & 31; // convert poly types - if ((ptype & 0x1) == 0 && ptype != 8) // is FT3 triangle? + if ((ptype & 1) == 0 && ptype != 8) // is FT3 triangle? { temp = polys->uv2.v; polys->uv3.u = polys->uv2.u; @@ -709,10 +712,12 @@ void PlotBuildingModel(MODEL* model, int rot, _pct* pc) ConvertPolygonTypes(model, pc); + r = (rot >> 3) * 4; + i = model->num_polys; while (i-- > 0) { - ptype = polys->id & 0x1f; + ptype = polys->id & 31; // skip certain polygons if (ptype != 11 && ptype != 21 && ptype != 23) @@ -729,15 +734,13 @@ void PlotBuildingModel(MODEL* model, int rot, _pct* pc) gte_nclip(); gte_stopz(&opz); - r = rot; - if (ptype == 21) { - pc->colour = combo & 0x2ffffffU | 0x2c000000; + pc->colour = combo & 0x2ffffff | 0x2c000000; } else { - pc->colour = pc->f4colourTable[(r >> 3) * 4 - polys->th & 31]; + pc->colour = pc->f4colourTable[r - polys->th & 31]; } if (opz > 0) @@ -804,12 +807,14 @@ void PlotBuildingModelSubdivNxN(MODEL* model, int rot, _pct* pc, int n) ConvertPolygonTypes(model, pc); + r = (rot >> 3) * 4; + i = model->num_polys; while (i-- > 0) { // iterate through polygons // with skipping - ptype = polys->id & 0x1f; + ptype = polys->id & 31; if (ptype != 11 && ptype != 21 && ptype != 23) { @@ -825,23 +830,21 @@ void PlotBuildingModelSubdivNxN(MODEL* model, int rot, _pct* pc, int n) gte_nclip(); gte_stopz(&opz); - r = rot; - if (ptype == 21) { pc->colour = combo & 0x2ffffffU | 0x2c000000; } else { - pc->colour = pc->f4colourTable[(r >> 3) * 4 - polys->th & 31]; + pc->colour = pc->f4colourTable[r - polys->th & 31]; } if (opz > 0) { gte_stsz3(&pc->scribble[0], &pc->scribble[1], &pc->scribble[2]); - pc->tpage = (*pc->ptexture_pages)[polys->texture_set] << 0x10; - pc->clut = (*pc->ptexture_cluts)[polys->texture_set][polys->texture_id] << 0x10; + pc->tpage = (*pc->ptexture_pages)[polys->texture_set]; + pc->clut = (*pc->ptexture_cluts)[polys->texture_set][polys->texture_id]; minZ = pc->scribble[2]; if (pc->scribble[1] < minZ) @@ -896,8 +899,8 @@ void PlotBuildingModelSubdivNxN(MODEL* model, int rot, _pct* pc, int n) gte_stsxy(&prims->x3); - prims->tpage = pc->tpage >> 0x10; - prims->clut = pc->clut >> 0x10; + prims->tpage = pc->tpage; + prims->clut = pc->clut; *(ushort*)&prims->u0 = uv0; *(ushort*)&prims->u1 = uv1; @@ -910,13 +913,14 @@ void PlotBuildingModelSubdivNxN(MODEL* model, int rot, _pct* pc, int n) } else { - r = n; + int sub; + sub = n; if (n == 1) { if (minZ - 150 < (diff << 1)) - r = 4; + sub = 4; else - r = 2; + sub = 2; } copyVector(&subdiVerts.verts[0][0], &srcVerts[polys->v0]); @@ -931,8 +935,8 @@ void PlotBuildingModelSubdivNxN(MODEL* model, int rot, _pct* pc, int n) copyVector(&subdiVerts.verts[0][3], &srcVerts[polys->v2]); subdiVerts.verts[0][3].uv.val = uv2; - makeMesh((MVERTEX(*)[5][5])subdiVerts.verts, r, r); - drawMesh((MVERTEX(*)[5][5])subdiVerts.verts, r, r, pc); + makeMesh((MVERTEX(*)[5][5])subdiVerts.verts, sub, sub); + drawMesh((MVERTEX(*)[5][5])subdiVerts.verts, sub, sub, pc); } } @@ -955,10 +959,10 @@ int DrawAllBuildings(CELL_OBJECT** objects, int num_buildings) for (i = 0; i < 8; i++) { - plotContext.f4colourTable[i * 4 + 0] = planeColours[i] | 0x2C000000; - plotContext.f4colourTable[i * 4 + 1] = planeColours[0] | 0x2C000000; - plotContext.f4colourTable[i * 4 + 2] = planeColours[5] | 0x2C000000; - plotContext.f4colourTable[i * 4 + 3] = planeColours[0] | 0x2C000000; // default: 0x2C00F0F0 + plotContext.f4colourTable[i * 4 + 0] = plotContext.planeColours[i] | 0x2C000000; + plotContext.f4colourTable[i * 4 + 1] = plotContext.planeColours[0] | 0x2C000000; + plotContext.f4colourTable[i * 4 + 2] = plotContext.planeColours[5] | 0x2C000000; + plotContext.f4colourTable[i * 4 + 3] = plotContext.planeColours[0] | 0x2C000000; // default: 0x2C00F0F0 } plotContext.current = current; @@ -1015,13 +1019,10 @@ int DrawAllBuildings(CELL_OBJECT** objects, int num_buildings) // [D] [T] [A] custom void PlotModelSubdivNxN(MODEL* model, int rot, _pct* pc, int n) { - int opz; + int opz, Z, r, pr, nr; int diff, minZ, maxZ; - int Z; PL_POLYFT4* polys; int i; - int r; - u_char temp; u_char ptype; POLY_FT4* prims; SVECTOR* srcVerts; @@ -1033,11 +1034,16 @@ void PlotModelSubdivNxN(MODEL* model, int rot, _pct* pc, int n) MVERTEX5x5 subdiVerts; #endif + ConvertPolygonTypes(model, pc); + srcVerts = (SVECTOR*)model->vertices; polys = (PL_POLYFT4*)model->poly_block; combo = combointensity; + pr = (rot >> 3) * 4; + nr = ((rot + 32 & 63) >> 3) * 4; + // transparent object flag if (pc->flags & PLOT_TRANSPARENT) combo |= 0x2000000; @@ -1047,19 +1053,7 @@ void PlotModelSubdivNxN(MODEL* model, int rot, _pct* pc, int n) { // iterate through polygons // with skipping - ptype = polys->id & 0x1f; - - if ((ptype & 0x1) == 0 && ptype != 8) // is FT3 triangle? - { - temp = polys->uv2.v; - polys->uv3.u = polys->uv2.u; - polys->uv3.v = temp; - - polys->v3 = polys->v2; - - polys->id |= 1; - ptype |= 1; - } + ptype = polys->id & 31; if (ptype != 11 && ptype != 21 && ptype != 23) { @@ -1076,12 +1070,12 @@ void PlotModelSubdivNxN(MODEL* model, int rot, _pct* pc, int n) gte_nclip(); gte_stopz(&opz); - r = rot; + r = pr; if (pc->flags & (PLOT_INV_CULL | PLOT_NO_CULL)) { if (opz < 0) - r = rot + 32 & 63; + r = nr; if (pc->flags & PLOT_NO_CULL) opz = 1; // no culling @@ -1095,22 +1089,17 @@ void PlotModelSubdivNxN(MODEL* model, int rot, _pct* pc, int n) } else { - temp = polys->th; - - if ((polys->th & 0x80) == 0) // cache normal index if it were not - temp = polys->th = normalIndex(srcVerts, *(u_int*)&polys->v0); - - pc->colour = pc->f4colourTable[(r >> 3) * 4 - temp & 31]; + pc->colour = pc->f4colourTable[r - polys->th & 31]; } if (opz > 0) { gte_stsz3(&pc->scribble[0], &pc->scribble[1], &pc->scribble[2]); - pc->tpage = (*pc->ptexture_pages)[polys->texture_set] << 0x10; + pc->tpage = (*pc->ptexture_pages)[polys->texture_set]; if ((pc->flags & PLOT_CUSTOM_PALETTE) == 0) // [A] custom palette flag - for pedestrian heads - pc->clut = (*pc->ptexture_cluts)[polys->texture_set][polys->texture_id] << 0x10; + pc->clut = (*pc->ptexture_cluts)[polys->texture_set][polys->texture_id]; minZ = pc->scribble[2]; if (pc->scribble[1] < minZ) @@ -1165,8 +1154,8 @@ void PlotModelSubdivNxN(MODEL* model, int rot, _pct* pc, int n) gte_stsxy(&prims->x3); - prims->tpage = pc->tpage >> 0x10; - prims->clut = pc->clut >> 0x10; + prims->tpage = pc->tpage; + prims->clut = pc->clut; *(ushort*)&prims->u0 = uv0; *(ushort*)&prims->u1 = uv1; @@ -1235,10 +1224,10 @@ void RenderModel(MODEL* model, MATRIX* matrix, VECTOR* pos, int zBias, int flags for (i = 0; i < 8; i++) { - plotContext.f4colourTable[i * 4 + 0] = planeColours[i] | 0x2C000000; - plotContext.f4colourTable[i * 4 + 1] = planeColours[0] | 0x2C000000; - plotContext.f4colourTable[i * 4 + 2] = planeColours[5] | 0x2C000000; - plotContext.f4colourTable[i * 4 + 3] = planeColours[0] | 0x2C000000; // default: 0x2C00F0F0 + plotContext.f4colourTable[i * 4 + 0] = plotContext.planeColours[i] | 0x2C000000; + plotContext.f4colourTable[i * 4 + 1] = plotContext.planeColours[0] | 0x2C000000; + plotContext.f4colourTable[i * 4 + 2] = plotContext.planeColours[5] | 0x2C000000; + plotContext.f4colourTable[i * 4 + 3] = plotContext.planeColours[0] | 0x2C000000; // default: 0x2C00F0F0 } plotContext.ptexture_pages = &texture_pages; @@ -1291,20 +1280,19 @@ struct DrawMapData // [D] [T] void DrawMapPSX(int* comp_val) { - int dir; - PACKED_CELL_OBJECT* ppco; - int distScale; - int cellx; - int cellz; CELL_OBJECT* cop; + PACKED_CELL_OBJECT* ppco; MODEL* model; - int hloop; - int vloop; + int dir; + int distScale; + int cellx, cellz; + int hloop, vloop; -#if 0 //def PSX +#ifdef PSX CELL_ITERATOR& ci = *(CELL_ITERATOR*)(u_char*)getScratchAddr(0); MATRIX& mRotStore = *(MATRIX*)((u_char*)getScratchAddr(0) + sizeof(CELL_ITERATOR)); DrawMapData& drawData = *(DrawMapData*)((u_char*)getScratchAddr(0) + sizeof(CELL_ITERATOR) + sizeof(MATRIX)); + static_assert(sizeof(CELL_ITERATOR) + sizeof(MATRIX) + sizeof(DrawMapData) < 1024 - sizeof(_pct), "scratchpad overflow"); #else CELL_ITERATOR ci; MATRIX mRotStore; @@ -1353,7 +1341,7 @@ void DrawMapPSX(int* comp_val) drawData.tiles_found = 0; drawData.sprites_found = 0; - drawData.current_object_computed_value = *comp_val; + drawData.current_object_computed_value = (*comp_val & 4095) | drawData.cellLevel << 16; drawData.other_models_found = 0; drawData.anim_objs_found = 0; @@ -1378,11 +1366,11 @@ void DrawMapPSX(int* comp_val) // walk through all cells do { - if (ABS(hloop) + ABS(vloop) < 21) + if (ABS(hloop) + ABS(vloop) < PVS_CELL_COUNT) { // clamped vis values - int vis_h = MIN(MAX(hloop, -9), 10); - int vis_v = MIN(MAX(vloop, -9), 10); + int vis_h = MIN(MAX(hloop, -9), PVS_CELL_COUNT / 2); + int vis_v = MIN(MAX(vloop, -9), PVS_CELL_COUNT / 2); cellx = drawData.cellxpos + hloop; cellz = drawData.cellzpos + vloop; @@ -1394,10 +1382,8 @@ void DrawMapPSX(int* comp_val) cellz > -1 && cellz < cells_down && PVS_ptr[vis_v * pvs_square + vis_h]) // check PVS table { - ppco = GetFirstPackedCop(cellx, cellz, &ci, 1, drawData.cellLevel); - // walk each cell object in cell - while (ppco != NULL) + for (ppco = GetFirstPackedCop(cellx, cellz, &ci, 1, drawData.cellLevel); ppco; ppco = GetNextPackedCop(&ci)) { model = modelpointers[(ppco->value >> 6) | ((ppco->pos).vy & 1) << 10]; @@ -1435,25 +1421,19 @@ void DrawMapPSX(int* comp_val) } else { - int modelNumber; - modelNumber = ppco->value & 0x3f; - - if (modelNumber > 0) - { - MATRIX2* cmat; - cmat = &CompoundMatrix[modelNumber]; + int yang; + MATRIX2* cmat; - if (cmat->computed != drawData.current_object_computed_value) - { - cmat->computed = drawData.current_object_computed_value; - - gte_ReadRotMatrix(&mRotStore); - gte_sttr(mRotStore.t); + yang = ppco->value & 63; + cmat = &CompoundMatrix[yang]; - MulMatrix0(&inv_camera_matrix, (MATRIX*)&matrixtable[modelNumber], (MATRIX*)cmat); - - gte_SetRotMatrix(&mRotStore); - } + if (cmat->computed != drawData.current_object_computed_value) + { + cmat->computed = drawData.current_object_computed_value; + if (yang > 0) + MulMatrix0(&inv_camera_matrix, (MATRIX*)&matrixtable[yang], (MATRIX*)cmat); + else + *(MATRIX*)cmat = inv_camera_matrix; } if ((model->shape_flags & (SHAPE_FLAG_WATER | SHAPE_FLAG_TILE)) || @@ -1486,13 +1466,11 @@ void DrawMapPSX(int* comp_val) if (drawData.other_models_found < MAX_DRAWN_BUILDINGS) model_object_ptrs[drawData.other_models_found++] = cop; - if ((model->flags2 & MODEL_FLAG_ANIMOBJ) && drawData.anim_objs_found < MAX_DRAWN_ANIMATING) + if (drawData.anim_objs_found < MAX_DRAWN_ANIMATING && (model->flags2 & MODEL_FLAG_ANIMOBJ)) anim_obj_buffer[drawData.anim_objs_found++] = cop; } } } - - ppco = GetNextPackedCop(&ci); } } } @@ -1534,24 +1512,27 @@ void DrawMapPSX(int* comp_val) SetTextColour(255, 255, 0); - sprintf(tempBuf, "Buildings: %d", other_models_found); + sprintf(tempBuf, "Buildings: %d", drawData.other_models_found); PrintString(tempBuf, 10, 60); - sprintf(tempBuf, "Sprites: %d", sprites_found); + sprintf(tempBuf, "Sprites: %d", drawData.sprites_found); PrintString(tempBuf, 10, 75); - sprintf(tempBuf, "Tiles: %d", tiles_found); + sprintf(tempBuf, "Tiles: %d", drawData.tiles_found); PrintString(tempBuf, 10, 90); - sprintf(tempBuf, "Anims: %d", anim_objs_found); + sprintf(tempBuf, "Anims: %d", drawData.anim_objs_found); PrintString(tempBuf, 10, 105); - sprintf(tempBuf, "TOTAL: %d", other_models_found + sprites_found + tiles_found + anim_objs_found); + sprintf(tempBuf, "TOTAL: %d", drawData.other_models_found + drawData.sprites_found + drawData.tiles_found + drawData.anim_objs_found); PrintString(tempBuf, 10, 120); #endif SetupPlaneColours(combointensity); + if (drawData.anim_objs_found) + DrawAllAnimatingObjects((CELL_OBJECT**)anim_obj_buffer, drawData.anim_objs_found); + if (drawData.sprites_found) DrawSprites((PACKED_CELL_OBJECT**)spriteList, drawData.sprites_found); @@ -1561,8 +1542,74 @@ void DrawMapPSX(int* comp_val) if (drawData.other_models_found) DrawAllBuildings((CELL_OBJECT**)model_object_ptrs, drawData.other_models_found); - if (drawData.anim_objs_found) - DrawAllAnimatingObjects((CELL_OBJECT**)anim_obj_buffer, drawData.anim_objs_found); - setupYet = 0; } + +#ifdef DYNAMIC_LIGHTING +struct DLIGHT +{ + SVECTOR position; + CVECTOR color; +}; + +int gEnableDlights = 0; + +int gNumDlights = 0; +DLIGHT gLights[MAX_DLIGHTS]; + +void AddDlight(VECTOR* position, CVECTOR* color, int radius) +{ + DLIGHT* pLight; + VECTOR lightPos; + if (gNumDlights + 1 >= MAX_DLIGHTS) + { + return; + } + + pLight = &gLights[gNumDlights++]; + + lightPos = *position; + VecCopy(&pLight->position, &lightPos); + pLight->position.pad = ABS(radius); + + pLight->color = *color; +} + +void GetDLightLevel(SVECTOR* position, u_int* inOutColor) +{ + DLIGHT* pLight; + int dx, dy, dz, dist, light; + u_int lightR, lightG, lightB; + + lightR = (*inOutColor & 255); + lightG = (*inOutColor >> 8 & 255); + lightB = (*inOutColor >> 16 & 255); + + for (int i = 0; i < gNumDlights; i++) + { + int radius; + pLight = &gLights[i]; + + dx = (int)position->vx - (int)pLight->position.vx; + dy = (int)position->vy - (int)pLight->position.vy; + dz = (int)position->vz - (int)pLight->position.vz; + + dist = SquareRoot0(dx * dx + dy * dy + dz * dz); + + radius = pLight->position.pad; + + if (dist > radius) { + continue; + } + + light = radius - dist; + + lightR += pLight->color.r * light >> 13; + lightG += pLight->color.g * light >> 13; + lightB += pLight->color.b * light >> 13; + } + + *inOutColor = MIN(lightB, 255) << 16 | MIN(lightG, 255) << 8 | MIN(lightR, 255) | (*inOutColor & 0xFF000000); +} + +#endif // DYNAMIC_LIGHTING \ No newline at end of file diff --git a/src_rebuild/Game/C/draw.h b/src_rebuild/Game/C/draw.h index 26477a77c..862974f69 100644 --- a/src_rebuild/Game/C/draw.h +++ b/src_rebuild/Game/C/draw.h @@ -1,26 +1,42 @@ #ifndef DRAW_H #define DRAW_H +enum PlotFlags +{ + PLOT_TRANSPARENT = (1 << 0), + PLOT_INV_CULL = (1 << 1), + PLOT_NO_CULL = (1 << 2), + PLOT_NO_SHADE = (1 << 3), + PLOT_CUSTOM_PALETTE = (1 << 4), +}; + // Primitive plot context used in scratchpad struct _pct { - struct DB* current; + int f4colourTable[32]; + u_int planeColours[8]; + int scribble[8]; u_short(*ptexture_pages)[128]; u_short(*ptexture_cluts)[128][32]; - int f4colourTable[32]; + struct DB* current; + SVECTOR* verts; int* polySizes; char* primptr; OTTYPE* ot; - u_int clut; - u_int tpage; u_int colour; - int flags; - SVECTOR* verts; - u_int lastTexInfo; - int scribble[8]; - int model; + u_int flags; + u_short clut; + u_short tpage; }; +#ifdef DYNAMIC_LIGHTING +extern int gEnableDlights; +extern int gNumDlights; + +extern void AddDlight(VECTOR* position, CVECTOR* color, int radius); +extern void GetDLightLevel(SVECTOR* position, u_int* inOutColor); +#endif + extern SVECTOR day_vectors[4]; extern SVECTOR night_vectors[4]; extern SVECTOR day_colours[4]; @@ -60,15 +76,6 @@ extern _pct& plotContext; #endif -enum PlotFlags -{ - PLOT_TRANSPARENT = (1 << 0), - PLOT_INV_CULL = (1 << 1), - PLOT_NO_CULL = (1 << 2), - PLOT_NO_SHADE = (1 << 3), - PLOT_CUSTOM_PALETTE = (1 << 4), -}; - extern void* model_tile_ptrs[MAX_DRAWN_TILES]; extern int units_across_halved; @@ -84,7 +91,7 @@ extern int combointensity; extern int gForceLowDetailCars; extern int num_cars_drawn; -extern char CurrentPVS[444]; +extern int PositionVisible(VECTOR* pos); // 0x0005D560 extern void DrawMapPSX(int *comp_val); // 0x0003F6B0 diff --git a/src_rebuild/Game/C/drivinggames.c b/src_rebuild/Game/C/drivinggames.c index 15d7c7d03..821206391 100644 --- a/src_rebuild/Game/C/drivinggames.c +++ b/src_rebuild/Game/C/drivinggames.c @@ -27,11 +27,12 @@ struct TRAILBLAZER_DATA struct SMASHED_CONE { + VECTOR position; + VECTOR velocity; + short rot_speed; char cone; u_char active : 7; u_char side : 1; - short rot_speed; - VECTOR velocity; }; MODEL* gTrailblazerConeModel; @@ -67,7 +68,7 @@ void InitDrivingGames(void) if(NewLevel) { - gTrailblazerData = (TRAILBLAZER_DATA*)D_MALLOC(1200); // [A] use malloc + gTrailblazerData = (TRAILBLAZER_DATA*)D_MALLOC(sizeof(TRAILBLAZER_DATA) * 100); // [A] use malloc sprintf(filename, "TRAILS\\TRAIL.%d", gCurrentMissionNumber); @@ -125,7 +126,7 @@ int CarConeCollision(VECTOR *pPos, int car) return bcollided2d(cd); } - +void GetConePos(int cone, VECTOR* pos, int side); // [D] [T] void SetSmashedCone(int cone, VECTOR *velocity, int player, int side) @@ -144,6 +145,8 @@ void SetSmashedCone(int cone, VECTOR *velocity, int player, int side) sc->side = side; sc->cone = cone; + GetConePos(cone, &sc->position, side); + sc->velocity.vx = velocity->vx >> 10; sc->velocity.vz = velocity->vz >> 10; @@ -198,11 +201,11 @@ void MoveSmashedCones(void) { tbd = &gTrailblazerData[sc->cone]; - if (tbd->y < 50 - player[0].pos[1]) + if (sc->position.vy < 50 - player[0].pos[1]) { - tbd->x += sc->velocity.vx; - tbd->y += sc->velocity.vy; - tbd->z += sc->velocity.vz; + sc->position.vx += sc->velocity.vx; + sc->position.vy += sc->velocity.vy; + sc->position.vz += sc->velocity.vz; sc->velocity.vy += 10; sc->active++; @@ -245,19 +248,22 @@ void DrawCone(VECTOR *position, int cone) } // [D] [T] -void DrawSmashedCone(SMASHED_CONE *sc, VECTOR *wpos) +void DrawSmashedCone(SMASHED_CONE *sc) { MATRIX object_matrix; VECTOR pos; + if (FrustrumCheck(&sc->position, gTrailblazerConeModel->bounding_sphere) == -1) + return; + InitMatrix(object_matrix); - RotMatrixY(sc->rot_speed * sc->active * 3 & 0xfff, &object_matrix); - RotMatrixZ(sc->rot_speed * sc->active & 0xfff, &object_matrix); + RotMatrixY(sc->rot_speed * sc->active * 3 & 4095, &object_matrix); + RotMatrixZ(sc->rot_speed * sc->active & 4095, &object_matrix); - pos.vx = wpos->vx - camera_position.vx; - pos.vy = wpos->vy - camera_position.vy; - pos.vz = wpos->vz - camera_position.vz; + pos.vx = sc->position.vx - camera_position.vx; + pos.vy = sc->position.vy - camera_position.vy; + pos.vz = sc->position.vz - camera_position.vz; Apply_Inv_CameraMatrix(&pos); SetRotMatrix(&object_matrix); @@ -265,11 +271,7 @@ void DrawSmashedCone(SMASHED_CONE *sc, VECTOR *wpos) gte_SetTransVector(&pos); SetFrustrumMatrix(); - - if (FrustrumCheck(wpos, gTrailblazerConeModel->bounding_sphere) != -1) - { - PlotMDL_less_than_128(gTrailblazerConeModel); - } + PlotMDL_less_than_128(gTrailblazerConeModel); } // [D] [T] @@ -331,13 +333,11 @@ void DrawSmashedCones(void) { if (GameType == GAME_GATERACE) { - GetConePos(sc->cone, &wpos, sc->side); - DrawSmashedCone(sc, &wpos); + DrawSmashedCone(sc); } else { - GetConePos(sc->cone, &wpos, -1); - DrawSmashedCone(sc, &wpos); + DrawSmashedCone(sc); } } } @@ -477,7 +477,7 @@ void HandleDrivingGames(void) vel.vy = -17; vel.vz = FIXED(car_data[playerCarId].st.n.linearVelocity[2]); - SetSmashedCone(cone, &vel, id, 0); + SetSmashedCone(cone, &vel, id, -1); gTrailblazerConeCount++; gTrailblazerConeIndex += i + 1; diff --git a/src_rebuild/Game/C/event.c b/src_rebuild/Game/C/event.c index 64da3c7c3..bf1298dcf 100644 --- a/src_rebuild/Game/C/event.c +++ b/src_rebuild/Game/C/event.c @@ -556,6 +556,12 @@ static EventCamera eventCamera; void MakeEventTrackable(EVENT* ev); +#ifdef PSX +#define EVENT_DRAW_DISTANCE VIEW_DRAW_DISTANCE +#else +#define EVENT_DRAW_DISTANCE 15000 // [A] world becomes repetetive if further :( +#endif // PSX + // [D] [T] int GetVisValue(int index, int zDir) { @@ -566,17 +572,16 @@ int GetVisValue(int index, int zDir) if (index & 0xC000) { - camera = (index >> 0xf ^ 1) & 1; + camera = (index >> 15 ^ 1) & 1; + radius = EVENT_DRAW_DISTANCE; if (zDir == 0) { pos.vx = player[camera].cameraPos.vx; - radius = 16000; } else { pos.vx = player[camera].cameraPos.vz; - radius = 16000; } } else @@ -584,9 +589,9 @@ int GetVisValue(int index, int zDir) int multiple; if (index & 0x80) - ev = (EVENT*)&fixedEvent[index & 0x7f]; + ev = (EVENT*)&fixedEvent[index & 127]; else - ev = &event[index & 0x7f]; + ev = &event[index & 127]; if (index & 0xf00) { @@ -748,9 +753,9 @@ void VisibilityLists(VisType type, int i) else if (type == VIS_ADD) { if (i & 0x80) - ev = (EVENT*)&fixedEvent[i & 0x7f]; + ev = (EVENT*)&fixedEvent[i & 127]; else - ev = &event[i & 0x7f]; + ev = &event[i & 127]; if (ev->radius == 0) { @@ -1087,7 +1092,7 @@ void SetUpEvents(int full) missionTrain[1].engine = missionTrain[0].engine; // add trains - while (n < count-1) + while (n < count) { // randomize carriage count if (n != 0) @@ -1741,7 +1746,7 @@ void StepFromToEvent(EVENT* ev) { int df; int md; - long* curr; + decltype(VECTOR::vx)* curr; int* to; int direction; int d, d2; @@ -1833,7 +1838,7 @@ void StepPathEvent(EVENT* ev) int* to; Station station; int direction; - long* curr; + decltype(VECTOR::vx)* curr; int dir; int turn[4]; XZPAIR centre; @@ -2038,7 +2043,7 @@ void StepPathEvent(EVENT* ev) ev->position.vx += offset.x; ev->position.vz += offset.z; - ev->rotation = ratan2(offset.x, offset.z) + 1024U & 0xfff; + ev->rotation = ratan2(offset.x, offset.z) + 1024U & 4095; if (ev->flags & 0x8000) { @@ -2330,7 +2335,7 @@ void StepHelicopter(EVENT* ev) } ev->rotation += FIXEDH(FIXEDH(direction * direction) * direction); - ev->rotation &= 0xfff; + ev->rotation &= 4095; if (GetSurfaceIndex(&ev->position) == -23) { @@ -2603,7 +2608,7 @@ void StepEvents(void) } chicagoDoor[2].rotation -= chicagoDoor[2].active; - chicagoDoor[2].rotation &= 0xfff; + chicagoDoor[2].rotation &= 4095; } } @@ -2643,9 +2648,9 @@ void StepEvents(void) if ((*x & otherCamera) == 0) { if ((*x & 0x80) == 0) - evt = &event[*x & 0x7f]; + evt = &event[*x & 127]; else - evt = (EVENT*)&fixedEvent[*x & 0x7f]; + evt = (EVENT*)&fixedEvent[*x & 127]; // events that enable drawing if ((evt->flags & 0x204) == 0x200) @@ -2654,7 +2659,7 @@ void StepEvents(void) while ((*z & thisCamera) == 0) { - if ((*z & otherCamera) == 0 && (*x & 0xfff) == (*z & 0xfff)) + if ((*z & otherCamera) == 0 && (*x & 4095) == (*z & 4095)) { cop = &EventCop[event_models_active++]; @@ -2777,7 +2782,7 @@ void DrawRotor(VECTOR pos, MATRIX* matrix) pos.vy -= camera_position.vy; pos.vz -= camera_position.vz; - _RotMatrixY(&localMat, HelicopterData.rotorrot & 0xfff); + _RotMatrixY(&localMat, HelicopterData.rotorrot & 4095); ApplyMatrixSV(matrix, v, v); MulMatrix0(matrix, &localMat, &localMat); ApplyMatrixSV(&localMat, v + 1, v + 1); @@ -2925,15 +2930,15 @@ void DrawEvents(int camera) if ((*x & otherCamera) == 0) { if ((*x & 0x80) == 0) - ev = &event[*x & 0x7f]; + ev = &event[*x & 127]; else - ev = (EVENT*)&fixedEvent[*x & 0x7f]; + ev = (EVENT*)&fixedEvent[*x & 127]; z = zVis; while ((*z & thisCamera) == 0) { - if ((*z & otherCamera) == 0 && (*x & 0xfff) == (*z & 0xfff)) + if ((*z & otherCamera) == 0 && (*x & 4095) == (*z & 4095)) { if ((ev->flags & 4) == 0 && (ev->flags & 1) == camera) { @@ -3051,9 +3056,9 @@ void DrawEvents(int camera) { if (ev->model == HelicopterData.cleanModel) { - _RotMatrixZ(&matrix, HelicopterData.roll & 0xfff); - _RotMatrixX(&matrix, HelicopterData.pitch & 0xfff); - _RotMatrixY(&matrix, ev->rotation & 0xfff); + _RotMatrixZ(&matrix, HelicopterData.roll & 4095); + _RotMatrixX(&matrix, HelicopterData.pitch & 4095); + _RotMatrixY(&matrix, ev->rotation & 4095); DrawRotor(ev->position, &matrix); @@ -3132,7 +3137,7 @@ void DrawEvents(int camera) pos.vy += ev->data[2]; - if (gTimeOfDay != 1) + if (gTimeOfDay != TIME_DAY) SetupPlaneColours(0x00282828); RenderModel(foam.model, &matrix, &pos, 200, (foam.rotate & 0x8000) ? 0x3 : 0x1, 1, 0); @@ -3144,6 +3149,7 @@ void DrawEvents(int camera) { SetCamera(ev); DrawMapPSX(&ObjectDrawnValue); + ObjectDrawnValue += 16; } } } @@ -4022,7 +4028,7 @@ void MultiCarEvent(MS_TARGET* target) ev->position.vx = mcd->x; ev->position.vz = mcd->z; - ev->position.vy = -312; + ev->position.vy = -312; // MapHeight may not be available due to region is not yet loaded ev->rotation = mcd->rot; diff --git a/src_rebuild/Game/C/felony.c b/src_rebuild/Game/C/felony.c index abfb7a228..13c388408 100644 --- a/src_rebuild/Game/C/felony.c +++ b/src_rebuild/Game/C/felony.c @@ -386,7 +386,7 @@ void CheckPlayerMiscFelonies(void) NoteFelony(&felonyData, 8, 4096); // if lights are off (broken) - if (gTimeOfDay == 3 && cp->ap.damage[0] > 1000 && cp->ap.damage[1] > 1000) + if (gTimeOfDay == TIME_NIGHT && cp->ap.damage[0] > 1000 && cp->ap.damage[1] > 1000) NoteFelony(&felonyData, 9, 4096); // reckless driving. diff --git a/src_rebuild/Game/C/gamesnd.c b/src_rebuild/Game/C/gamesnd.c index 1ed5e8a08..5d780eb1e 100644 --- a/src_rebuild/Game/C/gamesnd.c +++ b/src_rebuild/Game/C/gamesnd.c @@ -206,15 +206,17 @@ int ResidentModelsBodge(void) int i; int j; - j = MissionHeader->residentModels[4]; - - if (gCurrentMissionNumber == 24 || gCurrentMissionNumber == 27 || + if (gCurrentMissionNumber == 24 || + gCurrentMissionNumber == 27 || gCurrentMissionNumber == 29 || - (gCurrentMissionNumber == 30 || gCurrentMissionNumber == 35)) + gCurrentMissionNumber == 30 || + gCurrentMissionNumber == 35) { return 3; } + j = MissionHeader->residentModels[4]; + if (gCurrentMissionNumber - 50U < 16 && j == 12) { return 5; @@ -224,7 +226,7 @@ int ResidentModelsBodge(void) { i = 11; - if (j != 9) + if (j != 9 && j != i) return 3; } else if (GameLevel == 1) @@ -271,7 +273,6 @@ int MapCarIndexToBank(int index) model = RM[index]; - // [A] Rev 1.1 removes this block if (gCurrentMissionNumber - 39U < 2 && RM[index] == 13) { model = 10 - (RM[0] + RM[1] + RM[2]); @@ -304,7 +305,7 @@ void LoadLevelSFX(int missionNum) int i; u_int city_night_fx; - city_night_fx = (gTimeOfDay == 3); + city_night_fx = (gTimeOfDay == TIME_NIGHT); cop_bank = missionNum % 4 + 1; cop_model = 3; @@ -1005,20 +1006,19 @@ void DoDopplerSFX(void) } } - // sort cars by distance distance - for (i = 0; i < num_noisy_cars - 1; i++) + // sort cars by distance + for (i = 1; i < num_noisy_cars; i++) { - for (j = i + 1; j < num_noisy_cars; j++) - { - int tmpi; - tmpi = indexlist[i]; + int tmpi; + tmpi = indexlist[i]; - if (car_dist[indexlist[j]] < car_dist[tmpi]) - { - indexlist[i] = indexlist[j]; - indexlist[j] = tmpi; - } + j = i - 1; + while (j >= 0 && car_dist[indexlist[j]] > car_dist[tmpi]) + { + indexlist[j + 1] = indexlist[j]; + j = j - 1; } + indexlist[j + 1] = tmpi; } car_flags = 0; @@ -1244,6 +1244,7 @@ void DoDopplerSFX(void) // update sounds of cars (swap between idle and rev) for (j = 0; j < MAX_CAR_NOISES; j++) { + CAR_DATA* cp; char old_idle; if (car_noise[j].in_use == 0) @@ -1251,12 +1252,13 @@ void DoDopplerSFX(void) car = car_noise[j].car; old_idle = car_noise[j].idle; + cp = &car_data[car]; // determine which sound type it has to play if (gInGameCutsceneActive != 0 && force_idle[car] > -1) car_noise[j].idle = force_idle[car]; else - car_noise[j].idle = (car_data[car].hd.speed < 17); + car_noise[j].idle = (cp->hd.speed < 17); // restart sound if it's changed if (old_idle != car_noise[j].idle) @@ -1266,7 +1268,7 @@ void DoDopplerSFX(void) StopChannel(car_noise[j].chan); UnlockChannel(car_noise[j].chan); - model = car_data[car].ap.model; + model = cp->ap.model; if (model == 3) model = cop_model; @@ -1284,9 +1286,7 @@ void DoDopplerSFX(void) else sample = bank * 3; - car_noise[j].chan = Start3DTrackingSound(-1, SOUND_BANK_CARS, sample, - (VECTOR*)car_data[car].hd.where.t, - (LONGVECTOR3*)car_data[car].st.n.linearVelocity); + car_noise[j].chan = Start3DTrackingSound(-1, SOUND_BANK_CARS, sample, (VECTOR*)cp->hd.where.t, (LONGVECTOR3*)cp->st.n.linearVelocity); LockChannel(car_noise[j].chan); } @@ -1305,10 +1305,7 @@ void DoDopplerSFX(void) car_noise[j].in_use = 1; - SetChannelPosition3(car_noise[j].chan, - (VECTOR*)car_data[car].hd.where.t, - (LONGVECTOR3*)car_data[car].st.n.linearVelocity, - volume, pitch, 0); + SetChannelPosition3(car_noise[j].chan, (VECTOR*)cp->hd.where.t, (LONGVECTOR3*)cp->st.n.linearVelocity, volume, pitch, 0); } // bark on player @@ -1324,13 +1321,12 @@ void DoDopplerSFX(void) } // update each sound channel with new info - for (j = 0; j < MAX_SFX_CHANNELS; j++) + CHANNEL_DATA* c = &channels[0]; + for (j = 0; j < MAX_SFX_CHANNELS; j++, c++) { - if (channels[j].loop == 0 && channels[j].time != 0 && channels[j].srcposition != NULL) + if ((c->flags & CHAN_LOOP) == 0 && c->time != 0 && c->srcposition != NULL) { - SetChannelPosition3(j, - channels[j].srcposition, channels[j].srcvelocity, - channels[j].srcvolume, channels[j].srcpitch, 0); + SetChannelPosition3(j, c->srcposition, c->srcvelocity, c->srcvolume, c->srcpitch, 0); } } } @@ -1583,7 +1579,7 @@ void FunkUpDaBGMTunez(int funk) if (copmusic != 0) { copmusic = 0; - XM_SetSongPos(Song_ID, 0); + Song_SetPos = 0; } } else @@ -1591,7 +1587,7 @@ void FunkUpDaBGMTunez(int funk) if (copmusic == 0) { copmusic = 1; - XM_SetSongPos(Song_ID, xm_coptrackpos[current_music_id]); + Song_SetPos = xm_coptrackpos[current_music_id]; } } } diff --git a/src_rebuild/Game/C/glaunch.c b/src_rebuild/Game/C/glaunch.c index 15f268f09..f4dd417af 100644 --- a/src_rebuild/Game/C/glaunch.c +++ b/src_rebuild/Game/C/glaunch.c @@ -26,76 +26,81 @@ struct MISSION_STEP u_char disc : 1; }; -MISSION_STEP MissionLadder[68] = +#define DML_FMV 1 +#define DML_MISSION 2 +#define DML_CREDITS 4 +#define DML_RECAP 6 + +MISSION_STEP MissionLadder[] = { - { 1, 0, 1, 0 }, - { 1, 0, 2, 0 }, - { 2, 0, 1, 0 }, - { 2, 1, 2, 0 }, - { 2, 1, 3, 0 }, - { 1, 1, 3, 0 }, - { 1, 1, 4, 0 }, - { 2, 1, 4, 0 }, - { 1, 2, 5, 0 }, - { 2, 2, 5, 0 }, - { 1, 3, 6, 0 }, - { 2, 3, 6, 0 }, - { 1, 4, 7, 0 }, - { 2, 5, 7, 0 }, - { 6, 5, 0, 0 }, - { 2, 5, 9, 0 }, - { 1, 5, 8, 0 }, - { 1, 5, 9, 0 }, - { 2, 6, 10, 0 }, - { 1, 6, 10, 0 }, - { 1, 6, 11, 0 }, - { 2, 7, 11, 0 }, - { 6, 7, 0, 0 }, - { 2, 7, 13, 0 }, - { 2, 7, 14, 0 }, - { 1, 7, 12, 0 }, - { 2, 8, 15, 0 }, - { 2, 8, 16, 0 }, - { 1, 8, 13, 0 }, - { 2, 9, 17, 0 }, - { 2, 9, 18, 0 }, - { 1, 9, 14, 0 }, - { 2, 10, 19, 0 }, - { 1, 10, 15, 0 }, - { 2, 11, 20, 0 }, - { 1, 12, 16, 1 }, - { 1, 12, 17, 1 }, - { 2, 13, 21, 1 }, - { 2, 13, 22, 1 }, - { 2, 13, 23, 1 }, - { 2, 13, 24, 1 }, - { 1, 13, 18, 1 }, - { 2, 14, 25, 1 }, - { 2, 14, 26, 1 }, - { 1, 14, 19, 1 }, - { 2, 15, 27, 1 }, - { 2, 15, 28, 1 }, - { 2, 15, 29, 1 }, - { 2, 15, 30, 1 }, - { 1, 15, 20, 1 }, - { 1, 15, 21, 1 }, - { 2, 16, 31, 1 }, - { 2, 16, 32, 1 }, - { 2, 16, 33, 1 }, - { 2, 16, 34, 1 }, - { 1, 16, 22, 1 }, - { 2, 17, 35, 1 }, - { 6, 17, 0, 1 }, - { 2, 17, 37, 1 }, - { 1, 17, 23, 1 }, - { 2, 18, 38, 1 }, - { 2, 18, 39, 1 }, - { 1, 18, 24, 1 }, - { 2, 19, 40, 1 }, - { 1, 19, 25, 1 }, - { 1, 19, 26, 1 }, - { 1, 19, 27, 1 }, - { 4, 19, 28, 1 } + { DML_FMV, 0, 1, 0 }, + { DML_FMV, 0, 2, 0 }, + { DML_MISSION, 0, 1, 0 }, + { DML_MISSION, 1, 2, 0 }, + { DML_MISSION, 1, 3, 0 }, + { DML_FMV, 1, 3, 0 }, + { DML_FMV, 1, 4, 0 }, + { DML_MISSION, 1, 4, 0 }, + { DML_FMV, 2, 5, 0 }, + { DML_MISSION, 2, 5, 0 }, + { DML_FMV, 3, 6, 0 }, + { DML_MISSION, 3, 6, 0 }, + { DML_FMV, 4, 7, 0 }, + { DML_MISSION, 5, 7, 0 }, + { DML_RECAP, 5, 0, 0 }, + { DML_MISSION, 5, 9, 0 }, + { DML_FMV, 5, 8, 0 }, + { DML_FMV, 5, 9, 0 }, + { DML_MISSION, 6, 10, 0 }, + { DML_FMV, 6, 10, 0 }, + { DML_FMV, 6, 11, 0 }, + { DML_MISSION, 7, 11, 0 }, + { DML_RECAP, 7, 0, 0 }, + { DML_MISSION, 7, 13, 0 }, + { DML_MISSION, 7, 14, 0 }, + { DML_FMV, 7, 12, 0 }, + { DML_MISSION, 8, 15, 0 }, + { DML_MISSION, 8, 16, 0 }, + { DML_FMV, 8, 13, 0 }, + { DML_MISSION, 9, 17, 0 }, + { DML_MISSION, 9, 18, 0 }, + { DML_FMV, 9, 14, 0 }, + { DML_MISSION, 10, 19, 0 }, + { DML_FMV, 10, 15, 0 }, + { DML_MISSION, 11, 20, 0 }, + { DML_FMV, 12, 16, 1 }, + { DML_FMV, 12, 17, 1 }, + { DML_MISSION, 13, 21, 1 }, + { DML_MISSION, 13, 22, 1 }, + { DML_MISSION, 13, 23, 1 }, + { DML_MISSION, 13, 24, 1 }, + { DML_FMV, 13, 18, 1 }, + { DML_MISSION, 14, 25, 1 }, + { DML_MISSION, 14, 26, 1 }, + { DML_FMV, 14, 19, 1 }, + { DML_MISSION, 15, 27, 1 }, + { DML_MISSION, 15, 28, 1 }, + { DML_MISSION, 15, 29, 1 }, + { DML_MISSION, 15, 30, 1 }, + { DML_FMV, 15, 20, 1 }, + { DML_FMV, 15, 21, 1 }, + { DML_MISSION, 16, 31, 1 }, + { DML_MISSION, 16, 32, 1 }, + { DML_MISSION, 16, 33, 1 }, + { DML_MISSION, 16, 34, 1 }, + { DML_FMV, 16, 22, 1 }, + { DML_MISSION, 17, 35, 1 }, + { DML_RECAP, 17, 0, 1 }, + { DML_MISSION, 17, 37, 1 }, + { DML_FMV, 17, 23, 1 }, + { DML_MISSION, 18, 38, 1 }, + { DML_MISSION, 18, 39, 1 }, + { DML_FMV, 18, 24, 1 }, + { DML_MISSION, 19, 40, 1 }, + { DML_FMV, 19, 25, 1 }, + { DML_FMV, 19, 26, 1 }, + { DML_FMV, 19, 27, 1 }, + { DML_CREDITS, 19, 28, 1 } }; unsigned short RecapFrameLength[19] = { @@ -403,7 +408,7 @@ void State_MissionLadder(void* param) SetPleaseWait(NULL); } - if (CurrentStep->flags == 2) + if (CurrentStep->flags == DML_MISSION) { if (RenderArgs.nRenders != 0) { @@ -419,14 +424,14 @@ void State_MissionLadder(void* param) quit = 1; } - else if (CurrentStep->flags == 1) // any render + else if (CurrentStep->flags == DML_FMV) // any render { RenderArgs.Args[RenderArgs.nRenders].render = CurrentStep->data; RenderArgs.Args[RenderArgs.nRenders].recap = 0; RenderArgs.Args[RenderArgs.nRenders].credits = 0; RenderArgs.nRenders++; } - else if (CurrentStep->flags == 4) // ending + else if (CurrentStep->flags == DML_CREDITS) // ending { SetPleaseWait(NULL); diff --git a/src_rebuild/Game/C/handling.c b/src_rebuild/Game/C/handling.c index 77959c0dc..538e0a798 100644 --- a/src_rebuild/Game/C/handling.c +++ b/src_rebuild/Game/C/handling.c @@ -23,6 +23,14 @@ #include "shadow.h" #include "players.h" +struct BOUND_BOX +{ + int x0, y0, z0; + int x1, y1, z1; +}; + +BOUND_BOX bbox[MAX_CARS]; + inline void UpdateCarDrawMatrix(CAR_DATA* cp) { cp->hd.drawCarMat.m[0][0] = -cp->hd.where.m[0][0]; @@ -275,37 +283,20 @@ void GlobalTimeStep(void) static RigidBodyState _d1[MAX_CARS]; // offset 0x820 int mayBeCollidingBits; - int howHard; int tmp; - RigidBodyState* thisState_i; - RigidBodyState* thisState_j; - RigidBodyState* thisDelta; - CAR_DATA* cp; - CAR_DATA* c1; - RigidBodyState* st; - RigidBodyState* tp; - RigidBodyState* d0; - RigidBodyState* d1; + RigidBodyState* thisState_i, *thisState_j, * thisDelta; + CAR_DATA* cp, * c1; + RigidBodyState* st, *tp; + RigidBodyState* d0, *d1; LONGVECTOR4 AV; LONGQUATERNION delta_orientation; - LONGVECTOR4 normal; - LONGVECTOR4 collisionpoint; - LONGVECTOR4 lever0; - LONGVECTOR4 lever1; - LONGVECTOR4 torque; - LONGVECTOR4 pointVel0; + LONGVECTOR4 normal, collisionpoint, lever0, lever1, torque, pointVel0; VECTOR velocity; - int depth; - int RKstep; - int subframe; - int j; - int strikeVel; - int i; - int do1; - int do2; - int m1; - int m2; - int strength; + int subframe, RKstep; + int i, j; + int howHard, depth, strikeVel, strength; + int do1, do2; + int m1, m2; int carsDentedThisFrame; short *felony; @@ -369,38 +360,37 @@ void GlobalTimeStep(void) if ((tmp < st->n.angularVelocity[2]) || (tmp = -tmp, st->n.angularVelocity[2] < tmp)) st->n.angularVelocity[2] = tmp; - // without precision - if (!cp->hd.mayBeColliding) + if (cp->hd.mayBeColliding) { - long* orient = st->n.orientation; // LONGQUATERNION + continue; + } - st->n.fposition[0] += st->n.linearVelocity[0] >> 8; - st->n.fposition[1] += st->n.linearVelocity[1] >> 8; - st->n.fposition[2] += st->n.linearVelocity[2] >> 8; + int* orient = st->n.orientation; // LONGQUATERNION - AV[0] = FixHalfRound(st->n.angularVelocity[0], 13); - AV[1] = FixHalfRound(st->n.angularVelocity[1], 13); - AV[2] = FixHalfRound(st->n.angularVelocity[2], 13); + st->n.fposition[0] += st->n.linearVelocity[0] >> 8; + st->n.fposition[1] += st->n.linearVelocity[1] >> 8; + st->n.fposition[2] += st->n.linearVelocity[2] >> 8; - delta_orientation[0] = -orient[1] * AV[2] + orient[2] * AV[1] + orient[3] * AV[0]; - delta_orientation[1] = orient[0] * AV[2] - orient[2] * AV[0] + orient[3] * AV[1]; - delta_orientation[2] = -orient[0] * AV[1] + orient[1] * AV[0] + orient[3] * AV[2]; - delta_orientation[3] = -orient[0] * AV[0] - orient[1] * AV[1] - orient[2] * AV[2]; + AV[0] = FixHalfRound(st->n.angularVelocity[0], 13); + AV[1] = FixHalfRound(st->n.angularVelocity[1], 13); + AV[2] = FixHalfRound(st->n.angularVelocity[2], 13); - orient[0] += FIXEDH(delta_orientation[0]); - orient[1] += FIXEDH(delta_orientation[1]); - orient[2] += FIXEDH(delta_orientation[2]); - orient[3] += FIXEDH(delta_orientation[3]); + delta_orientation[0] = -orient[1] * AV[2] + orient[2] * AV[1] + orient[3] * AV[0]; + delta_orientation[1] = orient[0] * AV[2] - orient[2] * AV[0] + orient[3] * AV[1]; + delta_orientation[2] = -orient[0] * AV[1] + orient[1] * AV[0] + orient[3] * AV[2]; + delta_orientation[3] = -orient[0] * AV[0] - orient[1] * AV[1] - orient[2] * AV[2]; - RebuildCarMatrix(st, cp); - } + orient[0] += FIXEDH(delta_orientation[0]); + orient[1] += FIXEDH(delta_orientation[1]); + orient[2] += FIXEDH(delta_orientation[2]); + orient[3] += FIXEDH(delta_orientation[3]); + + RebuildCarMatrix(st, cp); } // do collision interactions for(subframe = 0; subframe < 4; subframe++) { - RKstep = 0; - for (RKstep = 0; RKstep < 2; RKstep++) { for (i = 0; i < num_active_cars; i++) @@ -416,315 +406,322 @@ void GlobalTimeStep(void) mayBeCollidingBits = cp->hd.mayBeColliding; // if has any collision, process with double precision - if (mayBeCollidingBits) + if (mayBeCollidingBits == 0) + { + continue; + } + + if (RKstep == 0) + { + thisState_i = &cp->st; + thisDelta = _d0; + } + else + { + thisState_i = &_tp[i]; + thisDelta = _d1; + } + + int* orient = thisState_i->n.orientation; // LONGQUATERNION + + thisDelta[i].n.fposition[0] = thisState_i->n.linearVelocity[0] >> 8; + thisDelta[i].n.fposition[1] = thisState_i->n.linearVelocity[1] >> 8; + thisDelta[i].n.fposition[2] = thisState_i->n.linearVelocity[2] >> 8; + + AV[0] = FixHalfRound(thisState_i->n.angularVelocity[0], 13); + AV[1] = FixHalfRound(thisState_i->n.angularVelocity[1], 13); + AV[2] = FixHalfRound(thisState_i->n.angularVelocity[2], 13); + + thisDelta[i].n.orientation[0] = FIXEDH(-orient[1] * AV[2] + orient[2] * AV[1] + orient[3] * AV[0]); + thisDelta[i].n.orientation[1] = FIXEDH(orient[0] * AV[2] - orient[2] * AV[0] + orient[3] * AV[1]); + thisDelta[i].n.orientation[2] = FIXEDH(-orient[0] * AV[1] + orient[1] * AV[0] + orient[3] * AV[2]); + thisDelta[i].n.orientation[3] = FIXEDH(-orient[0] * AV[0] - orient[1] * AV[1] - orient[2] * AV[2]); + + thisDelta[i].n.linearVelocity[0] = 0; + thisDelta[i].n.linearVelocity[1] = 0; + thisDelta[i].n.linearVelocity[2] = 0; + thisDelta[i].n.angularVelocity[0] = 0; + thisDelta[i].n.angularVelocity[1] = 0; + thisDelta[i].n.angularVelocity[2] = 0; + + for (j = 0; j < i; j++) { - if (RKstep == 0) + c1 = active_car_list[j]; + + // [A] optimized run to not use the box checking + // as it has already composed bitfield / pairs + if ((mayBeCollidingBits & (1 << CAR_INDEX(c1))) == 0 || (c1->hd.speed == 0 && cp->hd.speed == 0)) { - thisState_i = &cp->st; - thisDelta = _d0; + continue; } - else + + if (CarCarCollision3(cp, c1, &depth, (VECTOR*)collisionpoint, (VECTOR*)normal) == 0) { - thisState_i = &_tp[i]; - thisDelta = _d1; + continue; } + + if (RKstep > 0) + thisState_j = &_tp[j]; + else + thisState_j = &c1->st; + + int c1InfiniteMass; + int c2InfiniteMass; + + collisionpoint[1] -= 0; + + lever0[0] = collisionpoint[0] - cp->hd.where.t[0]; + lever0[1] = collisionpoint[1] - cp->hd.where.t[1]; + lever0[2] = collisionpoint[2] - cp->hd.where.t[2]; + + lever1[0] = collisionpoint[0] - c1->hd.where.t[0]; + lever1[1] = collisionpoint[1] - c1->hd.where.t[1]; + lever1[2] = collisionpoint[2] - c1->hd.where.t[2]; + + strength = 47 - (lever0[1] + lever1[1]) / 2; + + lever0[1] += strength; + lever1[1] += strength; - long* orient = thisState_i->n.orientation; // LONGQUATERNION + strikeVel = depth * 0xc000; - thisDelta[i].n.fposition[0] = thisState_i->n.linearVelocity[0] >> 8; - thisDelta[i].n.fposition[1] = thisState_i->n.linearVelocity[1] >> 8; - thisDelta[i].n.fposition[2] = thisState_i->n.linearVelocity[2] >> 8; + pointVel0[0] = (FIXEDH(thisState_i->n.angularVelocity[1] * lever0[2] - thisState_i->n.angularVelocity[2] * lever0[1]) + thisState_i->n.linearVelocity[0]) - + (FIXEDH(thisState_j->n.angularVelocity[1] * lever1[2] - thisState_j->n.angularVelocity[2] * lever1[1]) + thisState_j->n.linearVelocity[0]); - AV[0] = FixHalfRound(thisState_i->n.angularVelocity[0], 13); - AV[1] = FixHalfRound(thisState_i->n.angularVelocity[1], 13); - AV[2] = FixHalfRound(thisState_i->n.angularVelocity[2], 13); + pointVel0[1] = (FIXEDH(thisState_i->n.angularVelocity[2] * lever0[0] - thisState_i->n.angularVelocity[0] * lever0[2]) + thisState_i->n.linearVelocity[1]) - + (FIXEDH(thisState_j->n.angularVelocity[2] * lever1[0] - thisState_j->n.angularVelocity[0] * lever1[2]) + thisState_j->n.linearVelocity[1]); - thisDelta[i].n.orientation[0] = FIXEDH(-orient[1] * AV[2] + orient[2] * AV[1] + orient[3] * AV[0]); - thisDelta[i].n.orientation[1] = FIXEDH(orient[0] * AV[2] - orient[2] * AV[0] + orient[3] * AV[1]); - thisDelta[i].n.orientation[2] = FIXEDH(-orient[0] * AV[1] + orient[1] * AV[0] + orient[3] * AV[2]); - thisDelta[i].n.orientation[3] = FIXEDH(-orient[0] * AV[0] - orient[1] * AV[1] - orient[2] * AV[2]); + pointVel0[2] = (FIXEDH(thisState_i->n.angularVelocity[0] * lever0[1] - thisState_i->n.angularVelocity[1] * lever0[0]) + thisState_i->n.linearVelocity[2]) - + (FIXEDH(thisState_j->n.angularVelocity[0] * lever1[1] - thisState_j->n.angularVelocity[1] * lever1[0]) + thisState_j->n.linearVelocity[2]); - thisDelta[i].n.linearVelocity[0] = 0; - thisDelta[i].n.linearVelocity[1] = 0; - thisDelta[i].n.linearVelocity[2] = 0; - thisDelta[i].n.angularVelocity[0] = 0; - thisDelta[i].n.angularVelocity[1] = 0; - thisDelta[i].n.angularVelocity[2] = 0; + howHard = (pointVel0[0] / 256) * (normal[0] / 32) + + (pointVel0[1] / 256) * (normal[1] / 32) + + (pointVel0[2] / 256) * (normal[2] / 32); - for (j = 0; j < i; j++) + if (howHard > 0) { - c1 = active_car_list[j]; + if (DamageCar3D(c1, &lever1, howHard >> 1, cp)) + c1->ap.needsDenting = 1; - // [A] optimized run to not use the box checking - // as it has already composed bitfield / pairs - if((mayBeCollidingBits & (1 << CAR_INDEX(c1))) != 0 && (c1->hd.speed != 0 || cp->hd.speed != 0)) + if (DamageCar3D(cp, &lever0, howHard >> 1, c1)) + cp->ap.needsDenting = 1; + + if (howHard > 0x32000) + { + if (cp->controlType == CONTROL_TYPE_CIV_AI) + cp->ai.c.carMustDie = 1; + + if (c1->controlType == CONTROL_TYPE_CIV_AI) + c1->ai.c.carMustDie = 1; + } + + // wake up cops if they've ben touched + // [A] check player felony. + // If player touch them without felony player will be charged with felony (hit cop car) + if (numCopCars < 4 && numActiveCops < maxCopCars && GameType != GAME_GETAWAY && *felony >= FELONY_PURSUIT_MIN_VALUE) { - if(CarCarCollision3(cp, c1, &depth, (VECTOR*)collisionpoint, (VECTOR*)normal)) + if (cp->controlType == CONTROL_TYPE_PLAYER && IS_ROADBLOCK_CAR(c1)) { - if (RKstep > 0) - thisState_j = &_tp[j]; - else - thisState_j = &c1->st; - - int c1InfiniteMass; - int c2InfiniteMass; + InitCopState(c1, NULL); + c1->ai.p.justPinged = 0; + } - collisionpoint[1] -= 0; + if (c1->controlType == CONTROL_TYPE_PLAYER && IS_ROADBLOCK_CAR(cp)) + { + InitCopState(cp, NULL); + cp->ai.p.justPinged = 0; + } + } + + if (howHard > 0x1b00) + { + velocity.vy = -17; + velocity.vx = FIXED(cp->st.n.linearVelocity[0]); + velocity.vz = FIXED(cp->st.n.linearVelocity[2]); - lever0[0] = collisionpoint[0] - cp->hd.where.t[0]; - lever0[1] = collisionpoint[1] - cp->hd.where.t[1]; - lever0[2] = collisionpoint[2] - cp->hd.where.t[2]; + collisionpoint[1] = -collisionpoint[1]; - lever1[0] = collisionpoint[0] - c1->hd.where.t[0]; - lever1[1] = collisionpoint[1] - c1->hd.where.t[1]; - lever1[2] = collisionpoint[2] - c1->hd.where.t[2]; + if (cp->controlType == CONTROL_TYPE_PLAYER || c1->controlType == CONTROL_TYPE_PLAYER) + { + Setup_Sparks((VECTOR*)collisionpoint, &velocity, 6, 0); - strength = 47 - (lever0[1] + lever1[1]) / 2; + if (cp->controlType == CONTROL_TYPE_PLAYER) + SetPadVibration(*cp->ai.padid, 1); - lever0[1] += strength; - lever1[1] += strength; - - strikeVel = depth * 0xc000; - - pointVel0[0] = (FIXEDH(thisState_i->n.angularVelocity[1] * lever0[2] - thisState_i->n.angularVelocity[2] * lever0[1]) + thisState_i->n.linearVelocity[0]) - - (FIXEDH(thisState_j->n.angularVelocity[1] * lever1[2] - thisState_j->n.angularVelocity[2] * lever1[1]) + thisState_j->n.linearVelocity[0]); - - pointVel0[1] = (FIXEDH(thisState_i->n.angularVelocity[2] * lever0[0] - thisState_i->n.angularVelocity[0] * lever0[2]) + thisState_i->n.linearVelocity[1]) - - (FIXEDH(thisState_j->n.angularVelocity[2] * lever1[0] - thisState_j->n.angularVelocity[0] * lever1[2]) + thisState_j->n.linearVelocity[1]); - - pointVel0[2] = (FIXEDH(thisState_i->n.angularVelocity[0] * lever0[1] - thisState_i->n.angularVelocity[1] * lever0[0]) + thisState_i->n.linearVelocity[2]) - - (FIXEDH(thisState_j->n.angularVelocity[0] * lever1[1] - thisState_j->n.angularVelocity[1] * lever1[0]) + thisState_j->n.linearVelocity[2]); - - howHard = (pointVel0[0] / 256) * (normal[0] / 32) + - (pointVel0[1] / 256) * (normal[1] / 32) + - (pointVel0[2] / 256) * (normal[2] / 32); - - if (howHard > 0 && RKstep > -1) - { - if (DamageCar3D(c1, &lever1, howHard >> 1, cp)) - c1->ap.needsDenting = 1; - - if (DamageCar3D(cp, &lever0, howHard >> 1, c1)) - cp->ap.needsDenting = 1; - - if (howHard > 0x32000) - { - if (cp->controlType == CONTROL_TYPE_CIV_AI) - cp->ai.c.carMustDie = 1; - - if (c1->controlType == CONTROL_TYPE_CIV_AI) - c1->ai.c.carMustDie = 1; - } - - // wake up cops if they've ben touched - // [A] check player felony. - // If player touch them without felony player will be charged with felony (hit cop car) - if (numCopCars < 4 && numActiveCops < maxCopCars && GameType != GAME_GETAWAY && *felony >= FELONY_PURSUIT_MIN_VALUE) - { - if (cp->controlType == CONTROL_TYPE_PLAYER && IS_ROADBLOCK_CAR(c1)) - { - InitCopState(c1, NULL); - c1->ai.p.justPinged = 0; - } - - if (c1->controlType == CONTROL_TYPE_PLAYER && IS_ROADBLOCK_CAR(cp)) - { - InitCopState(cp, NULL); - cp->ai.p.justPinged = 0; - } - } - - if (howHard > 0x1b00) - { - velocity.vy = -17; - velocity.vx = FIXED(cp->st.n.linearVelocity[0]); - velocity.vz = FIXED(cp->st.n.linearVelocity[2]); - - collisionpoint[1] = -collisionpoint[1]; - - if (cp->controlType == CONTROL_TYPE_PLAYER || c1->controlType == CONTROL_TYPE_PLAYER) - { - Setup_Sparks((VECTOR*)collisionpoint, &velocity, 6, 0); - - if (cp->controlType == CONTROL_TYPE_PLAYER) - SetPadVibration(*cp->ai.padid, 1); - - if (c1->controlType == CONTROL_TYPE_PLAYER) - SetPadVibration(*c1->ai.padid, 1); - } - - if (howHard > 0x2400) - { - int debris1; - int debris2; - - debris1 = GetDebrisColour(cp); - debris2 = GetDebrisColour(c1); - - Setup_Debris((VECTOR*)collisionpoint, &velocity, 3, 0); - Setup_Debris((VECTOR*)collisionpoint, &velocity, 6, debris1 << 0x10); - Setup_Debris((VECTOR*)collisionpoint, &velocity, 2, debris2 << 0x10); - } - } - } - - strikeVel += (howHard * 9) / 4; - - if (strikeVel > 0x69000) - strikeVel = 0x69000; - - m1 = cp->ap.carCos->mass; - m2 = c1->ap.carCos->mass; - - if (m2 < m1) - { - do1 = (m2 * 4096) / m1; - do2 = 4096; - } - else - { - do2 = (m1 * 4096) / m2; - do1 = 4096; - } - - c1InfiniteMass = cp->controlType == CONTROL_TYPE_CUTSCENE || m1 == 0x7fff; - c2InfiniteMass = c1->controlType == CONTROL_TYPE_CUTSCENE || m2 == 0x7fff; - - // [A] if any checked cars has infinite mass, reduce bouncing - // TODO: very easy difficulty - if (c1InfiniteMass || c2InfiniteMass) - strikeVel = strikeVel * 10 >> 2; - - // apply force to car 0 - if (!c1InfiniteMass) - { - int twistY, strength1; - - if (cp->controlType == CONTROL_TYPE_PURSUER_AI && c1->controlType != CONTROL_TYPE_LEAD_AI && c1->hndType != 0) - strength1 = (strikeVel * (7 - gCopDifficultyLevel)) / 8; - else if (cp->controlType == CONTROL_TYPE_LEAD_AI && c1->hndType != 0) - strength1 = (strikeVel * 5) / 8; - else - strength1 = strikeVel; - - strength1 = FIXEDH(strength1) * do1 >> 3; - - velocity.vx = (normal[0] >> 3) * strength1 >> 6; - velocity.vz = (normal[2] >> 3) * strength1 >> 6; - velocity.vy = (normal[1] >> 3) * strength1 >> 6; - - thisDelta[i].n.linearVelocity[0] -= velocity.vx; - thisDelta[i].n.linearVelocity[1] -= velocity.vy; - thisDelta[i].n.linearVelocity[2] -= velocity.vz; - - twistY = car_cosmetics[cp->ap.model].twistRateY / 2; - - torque[0] = FIXEDH(velocity.vy * lever0[2] - velocity.vz * lever0[1]) * twistY; - torque[1] = FIXEDH(velocity.vz * lever0[0] - velocity.vx * lever0[2]) * twistY; - torque[2] = FIXEDH(velocity.vx * lever0[1] - velocity.vy * lever0[0]) * twistY; - - if (c1->controlType == CONTROL_TYPE_LEAD_AI) - { - torque[0] = 0; - torque[2] = 0; - } - - thisDelta[i].n.angularVelocity[0] += torque[0]; - thisDelta[i].n.angularVelocity[1] += torque[1]; - thisDelta[i].n.angularVelocity[2] += torque[2]; - } - - // apply force to car 1 - if (!c2InfiniteMass) - { - int twistY, strength2; - - if (cp->controlType == CONTROL_TYPE_PURSUER_AI && c1->controlType != CONTROL_TYPE_LEAD_AI && c1->hndType != 0) - strength2 = (strikeVel * (7 - gCopDifficultyLevel)) / 8; - else if (c1->controlType == CONTROL_TYPE_LEAD_AI && cp->hndType != 0) - strength2 = (strikeVel * 5) / 8; - else - strength2 = strikeVel; - - strength2 = FIXEDH(strength2) * do2 >> 3; - - velocity.vx = (normal[0] >> 3) * strength2 >> 6; - velocity.vy = (normal[1] >> 3) * strength2 >> 6; - velocity.vz = (normal[2] >> 3) * strength2 >> 6; - - thisDelta[j].n.linearVelocity[0] += velocity.vx; - thisDelta[j].n.linearVelocity[1] += velocity.vy; - thisDelta[j].n.linearVelocity[2] += velocity.vz; - - twistY = car_cosmetics[c1->ap.model].twistRateY / 2; - - torque[0] = FIXEDH(lever1[1] * velocity.vz - lever1[2] * velocity.vy) * twistY; - torque[1] = FIXEDH(lever1[2] * velocity.vx - lever1[0] * velocity.vz) * twistY; - torque[2] = FIXEDH(lever1[0] * velocity.vy - lever1[1] * velocity.vx) * twistY; - - if (c1->controlType == CONTROL_TYPE_LEAD_AI) - { - torque[0] = 0; - torque[2] = 0; - } - - thisDelta[j].n.angularVelocity[0] += torque[0]; - thisDelta[j].n.angularVelocity[1] += torque[1]; - thisDelta[j].n.angularVelocity[2] += torque[2]; - } - - if (cp->id == player[0].playerCarId || c1->id == player[0].playerCarId) - RegisterChaseHit(cp->id, c1->id); - - if (cp->id == player[0].playerCarId) - CarHitByPlayer(c1, howHard); - - if (c1->id == player[0].playerCarId) - CarHitByPlayer(cp, howHard); + if (c1->controlType == CONTROL_TYPE_PLAYER) + SetPadVibration(*c1->ai.padid, 1); } - } // maybe colliding - } // j loop - } - } - // update forces and rebuild matrix of the cars - for (i = 0; i < num_active_cars; i++) - { - cp = active_car_list[i]; + if (howHard > 0x2400) + { + int debris1; + int debris2; - // if has any collision, process with double precision - if (cp->hd.mayBeColliding) - { - st = &cp->st; - tp = &_tp[i]; - d0 = &_d0[i]; - d1 = &_d1[i]; + debris1 = GetDebrisColour(cp); + debris2 = GetDebrisColour(c1); + + Setup_Debris((VECTOR*)collisionpoint, &velocity, 3, 0); + Setup_Debris((VECTOR*)collisionpoint, &velocity, 6, debris1 << 0x10); + Setup_Debris((VECTOR*)collisionpoint, &velocity, 2, debris2 << 0x10); + } + } + } + + strikeVel += (howHard * 9) / 4; + + if (strikeVel > 0x69000) + strikeVel = 0x69000; + + m1 = cp->ap.carCos->mass; + m2 = c1->ap.carCos->mass; - if (RKstep == 0) + if (m2 < m1) { - for (j = 0; j < 13; j++) + do1 = (m2 * 4096) / m1; + do2 = 4096; + } + else + { + do2 = (m1 * 4096) / m2; + do1 = 4096; + } + + c1InfiniteMass = cp->controlType == CONTROL_TYPE_CUTSCENE || m1 == 0x7fff; + c2InfiniteMass = c1->controlType == CONTROL_TYPE_CUTSCENE || m2 == 0x7fff; + + // [A] if any checked cars has infinite mass, reduce bouncing + // TODO: very easy difficulty + if (c1InfiniteMass || c2InfiniteMass) + strikeVel = strikeVel * 10 >> 2; + + // apply force to car 0 + if (!c1InfiniteMass) + { + int twistY, strength1; + + if (cp->controlType == CONTROL_TYPE_PURSUER_AI && c1->controlType != CONTROL_TYPE_LEAD_AI && c1->hndType != 0) + strength1 = (strikeVel * (7 - gCopDifficultyLevel)) / 8; + else if (cp->controlType == CONTROL_TYPE_LEAD_AI && c1->hndType != 0) + strength1 = (strikeVel * 5) / 8; + else + strength1 = strikeVel; + + strength1 = FIXEDH(strength1) * do1 >> 3; + + velocity.vx = (normal[0] >> 3) * strength1 >> 6; + velocity.vz = (normal[2] >> 3) * strength1 >> 6; + velocity.vy = (normal[1] >> 3) * strength1 >> 6; + + thisDelta[i].n.linearVelocity[0] -= velocity.vx; + thisDelta[i].n.linearVelocity[1] -= velocity.vy; + thisDelta[i].n.linearVelocity[2] -= velocity.vz; + + twistY = car_cosmetics[cp->ap.model].twistRateY / 2; + + torque[0] = FIXEDH(velocity.vy * lever0[2] - velocity.vz * lever0[1]) * twistY; + torque[1] = FIXEDH(velocity.vz * lever0[0] - velocity.vx * lever0[2]) * twistY; + torque[2] = FIXEDH(velocity.vx * lever0[1] - velocity.vy * lever0[0]) * twistY; + + if (c1->controlType == CONTROL_TYPE_LEAD_AI) { - tp->v[j] = st->v[j] + (d0->v[j] >> 2); + torque[0] = 0; + torque[2] = 0; } - RebuildCarMatrix(tp, cp); + thisDelta[i].n.angularVelocity[0] += torque[0]; + thisDelta[i].n.angularVelocity[1] += torque[1]; + thisDelta[i].n.angularVelocity[2] += torque[2]; } - else if (RKstep == 1) + + // apply force to car 1 + if (!c2InfiniteMass) { - for (j = 0; j < 13; j++) + int twistY, strength2; + + if (cp->controlType == CONTROL_TYPE_PURSUER_AI && c1->controlType != CONTROL_TYPE_LEAD_AI && c1->hndType != 0) + strength2 = (strikeVel * (7 - gCopDifficultyLevel)) / 8; + else if (c1->controlType == CONTROL_TYPE_LEAD_AI && cp->hndType != 0) + strength2 = (strikeVel * 5) / 8; + else + strength2 = strikeVel; + + strength2 = FIXEDH(strength2) * do2 >> 3; + + velocity.vx = (normal[0] >> 3) * strength2 >> 6; + velocity.vy = (normal[1] >> 3) * strength2 >> 6; + velocity.vz = (normal[2] >> 3) * strength2 >> 6; + + thisDelta[j].n.linearVelocity[0] += velocity.vx; + thisDelta[j].n.linearVelocity[1] += velocity.vy; + thisDelta[j].n.linearVelocity[2] += velocity.vz; + + twistY = car_cosmetics[c1->ap.model].twistRateY / 2; + + torque[0] = FIXEDH(lever1[1] * velocity.vz - lever1[2] * velocity.vy) * twistY; + torque[1] = FIXEDH(lever1[2] * velocity.vx - lever1[0] * velocity.vz) * twistY; + torque[2] = FIXEDH(lever1[0] * velocity.vy - lever1[1] * velocity.vx) * twistY; + + if (c1->controlType == CONTROL_TYPE_LEAD_AI) { - st->v[j] += d0->v[j] + d1->v[j] >> 3; + torque[0] = 0; + torque[2] = 0; } - RebuildCarMatrix(st, cp); + thisDelta[j].n.angularVelocity[0] += torque[0]; + thisDelta[j].n.angularVelocity[1] += torque[1]; + thisDelta[j].n.angularVelocity[2] += torque[2]; + } + + if (cp->id == player[0].playerCarId || c1->id == player[0].playerCarId) + RegisterChaseHit(cp->id, c1->id); + + if (cp->id == player[0].playerCarId) + CarHitByPlayer(c1, howHard); + + if (c1->id == player[0].playerCarId) + CarHitByPlayer(cp, howHard); + + } // j loop + } // i loop + + // update forces and rebuild matrix of the cars + for (i = 0; i < num_active_cars; i++) + { + cp = active_car_list[i]; + + // if has any collision, process with double precision + if (cp->hd.mayBeColliding == 0) + { + continue; + } + + st = &cp->st; + tp = &_tp[i]; + d0 = &_d0[i]; + d1 = &_d1[i]; + + if (RKstep == 0) + { + for (j = 0; j < 13; j++) + { + tp->v[j] = st->v[j] + (d0->v[j] >> 2); } + + RebuildCarMatrix(tp, cp); + } + else if (RKstep == 1) + { + for (j = 0; j < 13; j++) + { + st->v[j] += d0->v[j] + d1->v[j] >> 3; + } + + RebuildCarMatrix(st, cp); } } } } - - // second sub frame passed, update matrices and physics direction // dent cars - no more than 5 cars in per frame carsDentedThisFrame = 0; @@ -751,9 +748,6 @@ void GlobalTimeStep(void) void SetShadowPoints(CAR_DATA* c0, VECTOR* outpoints) { int i; - SVECTOR disp; - VECTOR pointPos; - VECTOR surfaceNormal; CAR_COSMETICS* car_cos; sdPlane* surfacePtr; @@ -766,15 +760,10 @@ void SetShadowPoints(CAR_DATA* c0, VECTOR* outpoints) for (i = 0; i < 4; i++) { - disp = car_cos->cPoints[i]; - - gte_ldv0(&disp); - + gte_ldv0(&car_cos->cPoints[i]); gte_rtv0tr(); - - gte_stlvnl(&pointPos); - - FindSurfaceD2(&pointPos, &surfaceNormal, &outpoints[i], &surfacePtr); + gte_stlvnl(&outpoints[i]); + outpoints[i].vy = MapHeight(&outpoints[i]); } } @@ -864,12 +853,8 @@ void initOBox(CAR_DATA* cp) // [D] [T] void RebuildCarMatrix(RigidBodyState* st, CAR_DATA* cp) { - int sm; - int osm; - int qw; - int qz; - int qy; - int qx; + int sm, osm; + int qw, qz, qy, qx; cp->hd.where.t[0] = st->n.fposition[0] >> 4; cp->hd.where.t[1] = st->n.fposition[1] >> 4; @@ -978,9 +963,7 @@ void CheckCarToCarCollisions(void) int lbod, wbod, hbod; int xx, zz; - BOUND_BOX* bb; - BOUND_BOX* bb2; - BOUND_BOX* bb1; + BOUND_BOX* bb, *bb1, *bb2; CAR_DATA* cp; SVECTOR* colBox; @@ -1523,7 +1506,9 @@ void CheckCarEffects(CAR_DATA* cp, int player_id) rear_vel = ABS(cp->hd.rear_vel); front_vel = ABS(cp->hd.front_vel); - if ((wheels_on_ground & 5) && (rear_vel > 15000 || cp->wheelspin)) + const int front_wh = 0x1 | 0x4; + const int rear_wh = 0x2 | 0x8; + if ((wheels_on_ground & 10) && (rear_vel > 15000 || cp->wheelspin)) { lay_down_tracks |= 1; @@ -1536,7 +1521,7 @@ void CheckCarEffects(CAR_DATA* cp, int player_id) skidsound = 13000; } - if ((wheels_on_ground & 10) && front_vel > 15000) + if ((wheels_on_ground & 5) && front_vel > 15000) { lay_down_tracks |= 2; } @@ -1677,18 +1662,18 @@ void CheckCarEffects(CAR_DATA* cp, int player_id) if (lay_down_tracks & 1) // rear { #if MAX_TYRE_TRACK_WHEELS == 4 - ADD_WHEEL_TYRE_TRACK(0, 0) - ADD_WHEEL_TYRE_TRACK(2, 2) + ADD_WHEEL_TYRE_TRACK(1, 1) + ADD_WHEEL_TYRE_TRACK(3, 3) #else - ADD_WHEEL_TYRE_TRACK(0, 0) - ADD_WHEEL_TYRE_TRACK(2, 1) + ADD_WHEEL_TYRE_TRACK(1, 0) + ADD_WHEEL_TYRE_TRACK(3, 1) #endif } else { #if MAX_TYRE_TRACK_WHEELS == 4 - last_track_state[player_id][0] = -1; - last_track_state[player_id][2] = -1; + last_track_state[player_id][1] = -1; + last_track_state[player_id][3] = -1; #else last_track_state[player_id][0] = -1; last_track_state[player_id][1] = -1; @@ -1698,13 +1683,13 @@ void CheckCarEffects(CAR_DATA* cp, int player_id) #if MAX_TYRE_TRACK_WHEELS == 4 if (lay_down_tracks & 2) // front { - ADD_WHEEL_TYRE_TRACK(1, 1) - ADD_WHEEL_TYRE_TRACK(3, 3) + ADD_WHEEL_TYRE_TRACK(0, 0) + ADD_WHEEL_TYRE_TRACK(2, 2) } else { - last_track_state[player_id][1] = -1; - last_track_state[player_id][3] = -1; + last_track_state[player_id][0] = -1; + last_track_state[player_id][2] = -1; } #endif diff --git a/src_rebuild/Game/C/leadai.c b/src_rebuild/Game/C/leadai.c index 4f6fddde0..dc2d06d9b 100644 --- a/src_rebuild/Game/C/leadai.c +++ b/src_rebuild/Game/C/leadai.c @@ -1522,9 +1522,7 @@ void UpdateRoadPosition(CAR_DATA* cp, VECTOR* basePos, int intention) if (RoadMapRegions[cbr] != cbrX + cbrZ * regions_across) continue; - ppco = GetFirstPackedCop(cell_x, cell_z, &ci, 1); - - while (ppco) + for (ppco = GetFirstPackedCop(cell_x, cell_z, &ci, 1); ppco; ppco = GetNextPackedCop(&ci)) { int type = (ppco->value >> 6) | ((ppco->pos.vy & 1) << 10); model = modelpointers[type]; @@ -1666,7 +1664,6 @@ void UpdateRoadPosition(CAR_DATA* cp, VECTOR* basePos, int intention) collide++; } } - ppco = GetNextPackedCop(&ci); } } } @@ -2819,8 +2816,9 @@ u_int hypot(int x, int y) if (x < y) { + t = y; y = x; - x = y; + x = t; } if (x < 0x8000) diff --git a/src_rebuild/Game/C/loadview.c b/src_rebuild/Game/C/loadview.c index 3658dada4..0a500a5ae 100644 --- a/src_rebuild/Game/C/loadview.c +++ b/src_rebuild/Game/C/loadview.c @@ -177,7 +177,7 @@ void ShowLoadingScreen(char *screen_name, int effect, int loading_steps) DrawSync(0); setRECT(&dest, 320, 0, 160, 511); - LoadImage(&dest, (u_long *)&_other_buffer[544]); + LoadImage(&dest, (u_long*)&_other_buffer[544]); DrawSync(0); @@ -418,8 +418,8 @@ void DrawFadePoly(void) setRGB2(fl_g4, fadeVal, fadeVal, fadeVal); setRGB3(fl_g4, fadeVal, fadeVal, fadeVal); - addPrim(¤t->ot[1], fl_g4); - addPrim(¤t->ot[1], &fade_gt4[current->id]); + addPrim(current->ot + 1, fl_g4); + addPrim(current->ot + 1, &fade_gt4[current->id]); } // [D] [T] diff --git a/src_rebuild/Game/C/main.c b/src_rebuild/Game/C/main.c index 54a3ab617..6405104c5 100644 --- a/src_rebuild/Game/C/main.c +++ b/src_rebuild/Game/C/main.c @@ -388,14 +388,14 @@ void LoadGameLevel(void) if (gMultiplayerLevels == 0) { - if (gTimeOfDay == 3) + if (gTimeOfDay == TIME_NIGHT) SetCityType(CITYTYPE_NIGHT); else SetCityType(CITYTYPE_DAY); } else { - if (gTimeOfDay == 3) + if (gTimeOfDay == TIME_NIGHT) SetCityType(CITYTYPE_MULTI_NIGHT); else SetCityType(CITYTYPE_MULTI_DAY); @@ -619,7 +619,7 @@ void State_GameInit(void* param) InitMap(); InitSpecSpool(); - if ((NewLevel == 0 || gCarCleanModelPtr[4] == NULL) && allowSpecSpooling == 1) // [A] to load even more secret truck from Chicago + if (NewLevel == 0 && allowSpecSpooling == 1) { QuickSpoolSpecial(); } @@ -694,14 +694,12 @@ void State_GameInit(void* param) FrAng = 512; - if (gWeather == 1) + if (gWeather == WEATHER_RAIN) wetness = 7000; - //else if (gWeather == 2) // [A] addition that I have disabled - // wetness = 3000; else wetness = 0; - if (gTimeOfDay == 2) + if (gTimeOfDay == TIME_DUSK) { for ( i = 0; i < MAX_CARS; i++) lightsOnDelay[i] = (i * 11); @@ -840,7 +838,6 @@ void StepSim(void) static u_int t0; // offset 0x0 static char t1; // offset 0x4 static char t2; // offset 0x5 - static int oldsp; // offset 0x8 char padAcc; short* playerFelony; @@ -849,8 +846,9 @@ void StepSim(void) PLAYER* pl; int i, j; int car; + int timeOfDay; - if (gTimeOfDay == 0 || gTimeOfDay == 2) + if (M_BIT(gTimeOfDay) & (M_BIT(TIME_DAWN) | M_BIT(TIME_DUSK))) { DawnCount++; } @@ -871,8 +869,6 @@ void StepSim(void) pauseflag = 1; } - //oldsp = SetSp((u_long)((u_char*)getScratchAddr(0) + 0x3e8)); // i don't know what this does - lead_pad = (u_int)controller_bits; if (player[0].playerCarId < 0) @@ -1148,8 +1144,6 @@ void StepSim(void) DoScenaryCollisions(); CheckPlayerMiscFelonies(); - //SetSp(oldsp); - CameraCnt++; pl = player; @@ -1299,7 +1293,7 @@ void StepGame(void) ControlMap(); } - if (gTimeOfDay == 3) + if (gTimeOfDay == TIME_NIGHT) PreLampStreak(); if ((padd & 0x2000U) && (padd & 0x8000U)) @@ -1321,7 +1315,7 @@ void StepGame(void) lis_pos = camera_position; // update colours of ambience - if (gTimeOfDay == 0) + if (gTimeOfDay == TIME_DAWN) { NightAmbient = (DawnCount >> 7) + 26; gLightsOn = (DawnCount < 4000); @@ -1329,7 +1323,7 @@ void StepGame(void) if (NightAmbient > 96) NightAmbient = 96; } - else if (gTimeOfDay == 1) + else if (gTimeOfDay == TIME_DAY) { gLightsOn = 0; @@ -1338,7 +1332,7 @@ void StepGame(void) else NightAmbient = 78; } - else if (gTimeOfDay == 2) + else if (gTimeOfDay == TIME_DUSK) { if (DawnCount < 3000) { @@ -1363,13 +1357,13 @@ void StepGame(void) if (NightAmbient < 45) NightAmbient = 45; } - else if (gTimeOfDay == 3) + else if (gTimeOfDay == TIME_NIGHT) { gLightsOn = 1; NightAmbient = 128; } - if (gWeather != 0 && gWeather == 1) + if (gWeather == WEATHER_RAIN) { DoLightning(); DoThunder(); @@ -1610,9 +1604,7 @@ void State_GameLoop(void* param) _CutRec_Step(); } -// TODO: DRAW.C? int ObjectDrawnValue = 0; -int ObjectDrawnCounter = 0; // [D] [T] void DrawGame(void) @@ -1623,26 +1615,19 @@ void DrawGame(void) DrawPauseMenus(); RenderGame2(0); - - ObjectDrawnCounter++; - SwapDrawBuffers(); } else { ObjectDrawnValue = FrameCnt; RenderGame2(0); - ObjectDrawnCounter++; - SwapDrawBuffers2(0); ObjectDrawnValue += 16; - + DrawPauseMenus(); RenderGame2(1); - ObjectDrawnCounter++; - SwapDrawBuffers2(1); } @@ -2387,6 +2372,11 @@ void RenderGame2(int view) DrawAllTheCars(view); #ifndef PSX + +#ifdef DYNAMIC_LIGHTING + gNumDlights = 0; +#endif + extern void DrawDebugOverlays(); DrawDebugOverlays(); @@ -2542,7 +2532,7 @@ void DealWithHorn(char* hr, int i) *hr = (*hr + 1) % 3; } -// [D] [T] [A] Has bugs - some rooms not drawn properly +// [D] [T] int Havana3DOcclusion(occlFunc func, int* param) { int loop; diff --git a/src_rebuild/Game/C/main.h b/src_rebuild/Game/C/main.h index d1da426b1..f07ec1320 100644 --- a/src_rebuild/Game/C/main.h +++ b/src_rebuild/Game/C/main.h @@ -22,9 +22,7 @@ extern int wetness; extern int scr_z; extern int DawnCount; - extern int ObjectDrawnValue; -extern int ObjectDrawnCounter; extern int Havana3DLevelDraw; diff --git a/src_rebuild/Game/C/map.c b/src_rebuild/Game/C/map.c index 0ed0bd525..a65f7e628 100644 --- a/src_rebuild/Game/C/map.c +++ b/src_rebuild/Game/C/map.c @@ -98,10 +98,9 @@ void ProcessMapLump(char* lump_ptr, int lump_size) trap(0x400); } #endif - - view_dist = 10; - pvs_square = 21; - pvs_square_sq = 21 * 21; + view_dist = PVS_CELL_COUNT / 2; + pvs_square = PVS_CELL_COUNT; + pvs_square_sq = PVS_CELL_COUNT * PVS_CELL_COUNT; units_across_halved = cells_across / 2 * MAP_CELL_SIZE; units_down_halved = cells_down / 2 * MAP_CELL_SIZE; @@ -167,50 +166,20 @@ int newPositionVisible(VECTOR *pos, char *pvs, int ccx, int ccz) cellx = (dx / MAP_CELL_SIZE) - ccx; cellz = (dz / MAP_CELL_SIZE) - ccz; - if (ABS(cellx) <= view_dist && ABS(cellz) <= view_dist) - { - return pvs[cellx + 10 + (cellz + 10) * pvs_square] != 0; - } - - return 0; -} - -// [D] [T] -int PositionVisible(VECTOR *pos) -{ - int dx; // $a1 - int dz; // $a0 - int cellx; // $v1 - int cellz; // $v0 - - int ab; - - dx = pos->vx + units_across_halved; - dz = pos->vz + units_down_halved; - - cellx = (dx / MAP_CELL_SIZE) - current_cell_x; - cellz = (dz / MAP_CELL_SIZE) - current_cell_z; - - if (cellx < 0) - ab = -cellx; - else - ab = cellx; +#ifndef PSX + cellx = MIN(MAX(cellx, -9), PVS_CELL_COUNT / 2); + cellz = MIN(MAX(cellz, -9), PVS_CELL_COUNT / 2); +#endif // PSX - if (ab <= view_dist) + if (ABS(cellx) <= view_dist && + ABS(cellz) <= view_dist) { - if (cellz < 0) - ab = -cellz; - else - ab = cellz; - - if (ab <= view_dist) - return CurrentPVS[cellx + 10 + (cellz + 10) * pvs_square] != 0; + return pvs[cellx + 10 + (cellz + 10) * pvs_square] != 0; } return 0; } - // FIXME: move it somewhere else extern int saved_leadcar_pos; diff --git a/src_rebuild/Game/C/map.h b/src_rebuild/Game/C/map.h index b38a0ae2c..d9f2242a4 100644 --- a/src_rebuild/Game/C/map.h +++ b/src_rebuild/Game/C/map.h @@ -1,6 +1,8 @@ #ifndef MAP_H #define MAP_H +#define PVS_CELL_COUNT 21 // cells. Do not modify + #ifdef PSX // original Driver 2 definition @@ -73,7 +75,6 @@ extern void ProcessJuncBoundsLump(char *lump_file, int lump_size); // 0x0005D6DC extern void ProcessMapLump(char* lump_ptr, int lump_size); // 0x00040608 extern int newPositionVisible(VECTOR *pos, char *pvs, int ccx, int ccz); // 0x0005D61C -extern int PositionVisible(VECTOR *pos); // 0x0005D560 extern void ControlMap(); // 0x0005CC00 extern void GetVisSetAtPosition(VECTOR *pos, char *tgt, int *ccx, int *ccz); // 0x0005D6E4 diff --git a/src_rebuild/Game/C/mc_snd.c b/src_rebuild/Game/C/mc_snd.c index b70b14170..22ac154ff 100644 --- a/src_rebuild/Game/C/mc_snd.c +++ b/src_rebuild/Game/C/mc_snd.c @@ -665,7 +665,7 @@ void DoMissionSound(void) else { int dx, dz; - long* C = (long*)bodgevar; // Ahhh, Reflections... // LONGVECTOR3 + int* C = (int*)bodgevar; // Ahhh, Reflections... // LONGVECTOR3 dx = C[0] - car_data[player[0].playerCarId].hd.where.t[0]; dz = C[2] - car_data[player[0].playerCarId].hd.where.t[2]; diff --git a/src_rebuild/Game/C/mdraw.c b/src_rebuild/Game/C/mdraw.c index a234ca381..7b23e3977 100644 --- a/src_rebuild/Game/C/mdraw.c +++ b/src_rebuild/Game/C/mdraw.c @@ -159,7 +159,7 @@ void DrawOverheadTarget(MS_TARGET *target) break; } case Target_Event: // event target - tv = *target->s.event.eventPos; + tv = *(*(VECTOR**)&target->s.event.eventPos); break; default: return; @@ -194,7 +194,7 @@ void DrawFullscreenTarget(MS_TARGET *target) break; } case Target_Event: // event target - tv = *target->s.event.eventPos; + tv = *(*(VECTOR**)&target->s.event.eventPos); break; default: return; @@ -297,7 +297,7 @@ void DrawWorldTarget(MS_TARGET *target) } case Target_Event: { - tv = *target->s.event.eventPos; + tv = *(*(VECTOR**)&target->s.event.eventPos); break; } default: @@ -414,7 +414,7 @@ void DrawMultiplayerTarget(MS_TARGET *target) } case Target_Event: { - tv = *target->s.event.eventPos; + tv = *(*(VECTOR**)&target->s.event.eventPos); break; } default: diff --git a/src_rebuild/Game/C/mission.c b/src_rebuild/Game/C/mission.c index 414a2e069..dee72be54 100644 --- a/src_rebuild/Game/C/mission.c +++ b/src_rebuild/Game/C/mission.c @@ -151,8 +151,8 @@ MS_MISSION* MissionHeader; STREAM_SOURCE* PlayerStartInfo[8]; int numPlayersToCreate = 0; int gStartOnFoot = 0; -int gWeather = 0; -int gTimeOfDay = 0; +int gWeather = WEATHER_NONE; +int gTimeOfDay = TIME_DAY; int gShowPlayerDamage = 0; int gDontPingInCops = 0; int gBatterPlayer = 1; @@ -263,7 +263,7 @@ void InitialiseMissionDefaults(void) prevCopsInPursuit = 0; - for (i = 0; i < 15; i++) + for (i = 0; i < MAX_MISSION_THREADS; i++) { MissionThreads[i].initial_sp = MissionStack[i]; MissionThreads[i].active = 0; @@ -436,12 +436,12 @@ void LoadMission(int missionnum) LoadfileSeg(filename, (char *)MissionLoadAddress, offset, sizeof(MS_MISSION)); MissionHeader = MissionLoadAddress; - MissionTargets = (MS_TARGET *)((int)MissionLoadAddress + MissionLoadAddress->size); + MissionTargets = (MS_TARGET *)((char*)MissionLoadAddress + MissionLoadAddress->size); MissionScript = (u_int *)(MissionTargets + MAX_MISSION_TARGETS); - MissionStrings = (char*)((int*)MissionScript + MissionLoadAddress->strings); + MissionStrings = (char*)(MissionScript + MissionLoadAddress->strings); if (MissionLoadAddress->route && !NewLevel) - loadsize = (int)MissionStrings + (MissionLoadAddress->route - (int)MissionLoadAddress); + loadsize = (u_int)((char*)MissionStrings + ((char*)MissionLoadAddress->route - (char*)MissionLoadAddress)); else loadsize = length; @@ -514,13 +514,13 @@ void LoadMission(int missionnum) if (wantedWeather > -1) gWeather = wantedWeather; - if (gTimeOfDay >= 3) + if (gTimeOfDay >= TIME_NIGHT) gNight = 1; else gNight = 0; // setup weather - if (gWeather == 1) + if (gWeather == WEATHER_RAIN) { gRainCount = 30; gEffectsTimer = 41; @@ -1386,6 +1386,7 @@ int Swap2Cars(int curslot, int newslot) gDontResetCarDamage = 0; +#if ENABLE_GAME_FIXES // [A] swap cars in targets and fix "Bank Job" bug for (int i = 0; i < MAX_MISSION_TARGETS; i++) { @@ -1399,6 +1400,7 @@ int Swap2Cars(int curslot, int newslot) swapTgt->s.car.slot = curslot; } } +#endif return newslot; } @@ -1670,7 +1672,7 @@ int MRCommand(MR_THREAD *thread, u_int cmd) else if (cmd == 0x1000090) // SetRaining { MR_DebugWarn("MR %d command: SetRaining\n", thread - MissionThreads); - gWeather = 1; + gWeather = WEATHER_RAIN; return 1; } else if (cmd == 0x1000040) @@ -2565,7 +2567,7 @@ int MRProcessTarget(MR_THREAD *thread, MS_TARGET *target) if (target->s.target_flags & TARGET_FLAG_EVENT_TRIGGERED) { // [A] Ahhhh, 32 bit pointers... for future full-scale refactoring - if (target->s.event.loseMessage != -1 && Long2DDistance(target->s.event.eventPos, &pv) > 30000) + if (target->s.event.loseMessage != -1 && Long2DDistance((*(VECTOR**)&target->s.event.eventPos), &pv) > 30000) { message = MissionStrings + target->s.event.loseMessage; SetPlayerMessage(thread->player, message, 2, 2); @@ -2574,7 +2576,7 @@ int MRProcessTarget(MR_THREAD *thread, MS_TARGET *target) } else { - target->s.event.eventPos = TriggerEvent(target->s.event.eventId); + (*(VECTOR**)&target->s.event.eventPos) = TriggerEvent(target->s.event.eventId); target->s.target_flags |= TARGET_FLAG_EVENT_TRIGGERED; } @@ -2967,6 +2969,7 @@ void CompleteAllActiveTargets(int player) if (pTarget->type >= Target_Point && pTarget->type <= Target_Event && (pTarget->s.target_flags & flag1)) { + pTarget->s.target_flags &= ~flag1; pTarget->s.target_flags |= flag2; } } diff --git a/src_rebuild/Game/C/models.c b/src_rebuild/Game/C/models.c index 06911349c..987ee1303 100644 --- a/src_rebuild/Game/C/models.c +++ b/src_rebuild/Game/C/models.c @@ -5,6 +5,10 @@ #include "mission.h" #include "cars.h" +#if USE_PC_FILESYSTEM +extern int gContentOverride; +#endif + MODEL dummyModel = { 0 }; char* modelname_buffer = NULL; @@ -20,6 +24,7 @@ u_short *Low2LowerDetailTable = NULL; // [A] int staticModelSlotBitfield[48]; +int litSprites[48]; // [A] returns freed slot count int CleanSpooledModelSlots() @@ -39,6 +44,7 @@ int CleanSpooledModelSlots() { modelpointers[i] = &dummyModel; pLodModels[i] = &dummyModel; + litSprites[i >> 5] &= ~(1 << (i & 31)); num_freed++; } @@ -48,6 +54,25 @@ int CleanSpooledModelSlots() return num_freed; } +// [A] +void ProcessModel(int modelIdx) +{ + MODEL* model; + + model = modelpointers[modelIdx]; + + model->tri_verts = 0; // [A] this is used as additional flags for animated models and triangle processing + + if (gTimeOfDay == TIME_NIGHT) + { + if (model->shape_flags & SHAPE_FLAG_SPRITE) + { + if (modelIdx != 1223 && (!(model->flags2 & MODEL_FLAG_TREE) || modelIdx == 945 || modelIdx == 497)) + litSprites[modelIdx >> 5] |= 1 << (modelIdx & 31); + } + } +} + // [D] [T] void ProcessMDSLump(char *lump_file, int lump_size) { @@ -56,6 +81,7 @@ void ProcessMDSLump(char *lump_file, int lump_size) MODEL *parentmodel; int modelAmts; int i, size; + int litModel; modelAmts = *(int *)lump_file; mdsfile = (lump_file + 4); @@ -63,6 +89,7 @@ void ProcessMDSLump(char *lump_file, int lump_size) // [A] usage bits ClearMem((char*)staticModelSlotBitfield, sizeof(staticModelSlotBitfield)); + ClearMem((char*)litSprites, sizeof(litSprites)); // assign model pointers for (i = 0; i < MAX_MODEL_SLOTS; i++) // [A] bug fix. Init with dummyModel @@ -83,6 +110,8 @@ void ProcessMDSLump(char *lump_file, int lump_size) model = (MODEL*)mdsfile; modelpointers[i] = model; + + ProcessModel(i); } mdsfile += size; @@ -216,11 +245,47 @@ int ProcessCarModelLump(char *lump_ptr, int lump_size) int cleanOfs = offsets[0]; int damOfs = offsets[1]; int lowOfs = offsets[2]; + +#if USE_PC_FILESYSTEM + if (gContentOverride) + { + char* mem; + if (mem = LoadCarModelFromFile(NULL, model_number, CAR_MODEL_CLEAN)) + { + D_MALLOC_BEGIN(); + model = GetCarModel(mem, (char**)&mallocptr, 1); + D_MALLOC_END(); + + gCarCleanModelPtr[i] = model; + cleanOfs = -1; // skip loading + } + + if (mem = LoadCarModelFromFile(NULL, model_number, CAR_MODEL_DAMAGED)) + { + D_MALLOC_BEGIN(); + model = GetCarModel(mem, (char**)&mallocptr, 1); + D_MALLOC_END(); + + gCarDamModelPtr[i] = model; + damOfs = -1; // skip loading + } + + if (mem = LoadCarModelFromFile(NULL, model_number, CAR_MODEL_LOWDETAIL)) + { + D_MALLOC_BEGIN(); + model = GetCarModel(mem, (char**)&mallocptr, 1); + D_MALLOC_END(); + + gCarLowModelPtr[i] = model; + lowOfs = -1; // skip loading + } + } +#endif if (cleanOfs != -1) { D_MALLOC_BEGIN(); - model = GetCarModel(models_offset + cleanOfs, (char**)&mallocptr, 1, model_number, CAR_MODEL_CLEAN); + model = GetCarModel(models_offset + cleanOfs, (char**)&mallocptr, 1); gCarCleanModelPtr[i] = model; D_MALLOC_END(); } @@ -228,7 +293,7 @@ int ProcessCarModelLump(char *lump_ptr, int lump_size) if (damOfs != -1) { D_MALLOC_BEGIN(); - model = GetCarModel(models_offset + damOfs, (char**)&mallocptr, 0, model_number, CAR_MODEL_DAMAGED); + model = GetCarModel(models_offset + damOfs, (char**)&mallocptr, 0); gCarDamModelPtr[i] = model; D_MALLOC_END(); } @@ -236,7 +301,7 @@ int ProcessCarModelLump(char *lump_ptr, int lump_size) if (lowOfs != -1) { D_MALLOC_BEGIN(); - model = GetCarModel(models_offset + lowOfs, (char**)&mallocptr, 1, model_number, CAR_MODEL_LOWDETAIL); + model = GetCarModel(models_offset + lowOfs, (char**)&mallocptr, 1); gCarLowModelPtr[i] = model; D_MALLOC_END(); } @@ -244,6 +309,15 @@ int ProcessCarModelLump(char *lump_ptr, int lump_size) } D_MALLOC_BEGIN(); + +#if USE_PC_FILESYSTEM + if (gContentOverride) + { + // extra spool memory needed + specMemReq += 4096; + } +#endif + mallocptr = specmallocptr + specMemReq; specLoadBuffer = specmallocptr + specMemReq - 2048; D_MALLOC_END(); @@ -260,14 +334,14 @@ char* CarModelTypeNames[] = { "LOW", }; -#ifndef PSX +#if USE_PC_FILESYSTEM // [A] loads car model from file char* LoadCarModelFromFile(char* dest, int modelNumber, int type) { char* mem; char filename[64]; - sprintf(filename, "LEVELS\\%s\\CARMODEL_%d_%s.DMODEL", LevelNames[GameLevel], modelNumber, CarModelTypeNames[type-1]); + sprintf(filename, "LEVELS\\%s\\CARMODEL_%d_%s.MDL", LevelNames[GameLevel], modelNumber, CarModelTypeNames[type-1]); if(FileExists(filename)) { mem = (char*)(dest ? dest : (_other_buffer + modelNumber * 0x10000 + (type-1) * 0x4000)); @@ -282,30 +356,21 @@ char* LoadCarModelFromFile(char* dest, int modelNumber, int type) #endif // [D] [T] -MODEL* GetCarModel(char *src, char **dest, int KeepNormals, int modelNumber, int type) +MODEL* GetCarModel(char *src, char **dest, int KeepNormals) { int size; MODEL *model; char* mem; - -#ifndef PSX - mem = LoadCarModelFromFile(NULL, modelNumber, type); - - if (!mem) // fallback to lump - mem = src; -#else - mem = src; -#endif model = (MODEL *)*dest; if (KeepNormals == 0) - size = ((MODEL*)mem)->normals; + size = ((MODEL*)src)->normals; else - size = ((MODEL*)mem)->poly_block; + size = ((MODEL*)src)->poly_block; // if loaded externally don't copy from source lump - memcpy((u_char*)*dest, (u_char*)mem, size); + memcpy((u_char*)*dest, (u_char*)src, size); if (KeepNormals == 0) size = model->normals; @@ -317,7 +382,7 @@ MODEL* GetCarModel(char *src, char **dest, int KeepNormals, int modelNumber, int model->vertices += (int)model; model->normals += (int)model; - model->poly_block = (int)mem + model->poly_block; + model->poly_block = (int)src + model->poly_block; if (KeepNormals == 0) model->point_normals = 0; diff --git a/src_rebuild/Game/C/models.h b/src_rebuild/Game/C/models.h index 452f83923..fdda47a79 100644 --- a/src_rebuild/Game/C/models.h +++ b/src_rebuild/Game/C/models.h @@ -15,6 +15,7 @@ extern char *car_models_lump; extern MODEL* modelpointers[MAX_MODEL_SLOTS]; extern MODEL* pLodModels[MAX_MODEL_SLOTS]; +extern int litSprites[48]; extern unsigned short *Low2HighDetailTable; extern unsigned short *Low2LowerDetailTable; @@ -22,12 +23,13 @@ extern unsigned short *Low2LowerDetailTable; extern int num_models_in_pack; extern int CleanSpooledModelSlots(); +extern void ProcessModel(int modelIdx); extern void ProcessMDSLump(char *lump_file, int lump_size); // 0x00064CFC extern int ProcessCarModelLump(char *lump_ptr, int lump_size); // 0x00064E6C -extern MODEL * GetCarModel(char *src, char **dest, int KeepNormals, int modelNumber, int type); // 0x00065134 +extern MODEL * GetCarModel(char *src, char **dest, int KeepNormals); // 0x00065134 extern MODEL * FindModelPtrWithName(char *name); // 0x0005D40C diff --git a/src_rebuild/Game/C/motion_c.c b/src_rebuild/Game/C/motion_c.c index d76ec7586..c94bec7c4 100644 --- a/src_rebuild/Game/C/motion_c.c +++ b/src_rebuild/Game/C/motion_c.c @@ -16,7 +16,7 @@ #include "cars.h" #include "convert.h" -#ifdef USE_PGXP +#if USE_PGXP #include #endif @@ -524,7 +524,7 @@ void DrawBodySprite(LPPEDESTRIAN pDrawingPed, int boneId, VERTTYPE v1[2], VERTTY int pal; int cs, sn; - bone = (LIMBS)(boneId & 0x7f); + bone = (LIMBS)(boneId & 127); body_texture = MainPed[bone].ptd; if (bDoingShadow) @@ -607,12 +607,13 @@ void DrawBodySprite(LPPEDESTRIAN pDrawingPed, int boneId, VERTTYPE v1[2], VERTTY prims->x3 = v2[0] - FIXEDH(cs) - dx1; prims->y3 = v2[1] - FIXEDH(sn) - dy1; -#ifdef USE_PGXP +#if USE_PGXP if (!bDoingShadow) // [A] Psy-X is currently incorrectly offsets the offscreen PGXP geometry. We don't need it anyway. { + ushort pgxpIdx = PGXP_GetIndex(0) - 64; PGXPVData vdata1, vdata2; - PGXP_GetCacheData(&vdata1, PGXP_LOOKUP_VALUE(v1[0], v1[1]), 0); - PGXP_GetCacheData(&vdata2, PGXP_LOOKUP_VALUE(v2[0], v2[1]), 0); + PGXP_GetCacheData(&vdata1, PGXP_LOOKUP_VALUE(v1[0], v1[1]), pgxpIdx); + PGXP_GetCacheData(&vdata2, PGXP_LOOKUP_VALUE(v2[0], v2[1]), pgxpIdx); { float len; @@ -897,6 +898,13 @@ void SetupTannerSkeleton(LPPEDESTRIAN pDrawingPed) char* pC; SVECTOR* store; SVECTOR_NOPAD* pSVNP; +#ifdef PSX + store = (SVECTOR*)((u_char*)getScratchAddr(0) + 0x200); + static_assert(sizeof(SVECTOR) + NUM_BONES < 1024 - sizeof(_pct), "scratchpad overflow"); +#else + SVECTOR scratchVectors[64]; + store = scratchVectors; +#endif Skel[ROOT].pvOrigPos = (SVECTOR_NOPAD*)(pDrawingPed->motion + pDrawingPed->frame1 * 144 + 146); Skel[ROOT].pvRotation = (SVECTOR*)(pDrawingPed->motion + pDrawingPed->frame1 * 144 + 152); @@ -922,13 +930,6 @@ void SetupTannerSkeleton(LPPEDESTRIAN pDrawingPed) pC += sizeof(SVECTOR_NOPAD); } -#ifdef PSX - store = (SVECTOR*)((u_char*)getScratchAddr(0) + 0x200); -#else - SVECTOR scratchVectors[64]; - store = scratchVectors; -#endif - store[LOWERBACK].vx = Skel[LOWERBACK].pvOrigPos->vx; store[LOWERBACK].vy = -Skel[LOWERBACK].pvOrigPos->vy; store[LOWERBACK].vz = -Skel[LOWERBACK].pvOrigPos->vz; @@ -1003,25 +1004,26 @@ void SetupTannerSkeleton(LPPEDESTRIAN pDrawingPed) } // [A] - was inlined in newShowTanner -void DrawSprite(LPPEDESTRIAN pDrawingPed, BONE* pBone, VECTOR* vJPos) +void DrawSprite(LPPEDESTRIAN pDrawingPed, BONE* pBone, SVECTOR* vJPos) { VERTTYPE t0[2], t1[2]; // [A] was two longs int z, z1, z2; #ifdef PSX SVECTOR* data = (SVECTOR*)((u_char*)getScratchAddr(0) + 0x200); + static_assert(sizeof(SVECTOR) + NUM_BONES < 1024 - sizeof(_pct), "scratchpad overflow"); #else SVECTOR scratchVectors[64]; SVECTOR* data = scratchVectors; #endif - data[0].vx = vJPos[pBone->id & 0x7f].vx + pDrawingPed->position.vx - camera_position.vx; - data[0].vy = vJPos[pBone->id & 0x7f].vy + pDrawingPed->position.vy - camera_position.vy; - data[0].vz = vJPos[pBone->id & 0x7f].vz + pDrawingPed->position.vz - camera_position.vz; + data[0].vx = vJPos[pBone->id & 127].vx + pDrawingPed->position.vx - camera_position.vx; + data[0].vy = vJPos[pBone->id & 127].vy + pDrawingPed->position.vy - camera_position.vy; + data[0].vz = vJPos[pBone->id & 127].vz + pDrawingPed->position.vz - camera_position.vz; - data[1].vx = vJPos[pBone->pParent->id & 0x7f].vx + pDrawingPed->position.vx - camera_position.vx; - data[1].vy = vJPos[pBone->pParent->id & 0x7f].vy + pDrawingPed->position.vy - camera_position.vy; - data[1].vz = vJPos[pBone->pParent->id & 0x7f].vz + pDrawingPed->position.vz - camera_position.vz; + data[1].vx = vJPos[pBone->pParent->id & 127].vx + pDrawingPed->position.vx - camera_position.vx; + data[1].vy = vJPos[pBone->pParent->id & 127].vy + pDrawingPed->position.vy - camera_position.vy; + data[1].vz = vJPos[pBone->pParent->id & 127].vz + pDrawingPed->position.vz - camera_position.vz; gte_ldv0(&data[0]); gte_ldv1(&data[1]); @@ -1046,14 +1048,18 @@ void newShowTanner(LPPEDESTRIAN pDrawingPed) int draw; #ifdef PSX - VECTOR* spad = (VECTOR*)((u_char*)getScratchAddr(0) + 0x100); + VECTOR* playerPos = (VECTOR*)((u_char*)getScratchAddr(0) + 0x100); + VECTOR* cameraPos = (VECTOR*)((u_char*)getScratchAddr(0) + 0x100 + sizeof(VECTOR)); + SVECTOR* vJPos = (SVECTOR*)((u_char*)getScratchAddr(0) + 0x100 + sizeof(VECTOR) * 2); + static_assert(sizeof(VECTOR) * 2 + sizeof(SVECTOR) * NUM_BONES < 0x100, "Scratchpad local overflow"); + static_assert(sizeof(VECTOR) * 2 + sizeof(SVECTOR) * NUM_BONES < 1024 - sizeof(_pct), "Scratchpad overflow"); #else VECTOR spad[64]; -#endif VECTOR* playerPos = &spad[0]; VECTOR* cameraPos = &spad[1]; - VECTOR* vJPos = &spad[2]; + SVECTOR* vJPos = (SVECTOR*)&spad[2]; +#endif playerPos->vx = pDrawingPed->position.vx; playerPos->vy = pDrawingPed->position.vy - 15; // [A] elevate Tanner model a little bit so his legs are not in the ground (when Z-buffer enabled) @@ -1091,7 +1097,7 @@ void newShowTanner(LPPEDESTRIAN pDrawingPed) BONE* pBone = &Skel[id]; - if (pBone->id < 0x7f) + if (pBone->id < 127) { int lval; @@ -1137,7 +1143,7 @@ void newShowTanner(LPPEDESTRIAN pDrawingPed) BONE* pBone; pBone = &Skel[i]; - id = pBone->id & 0x7f; + id = pBone->id & 127; if (bDoingShadow) { @@ -1178,15 +1184,15 @@ void newShowTanner(LPPEDESTRIAN pDrawingPed) CVECTOR yycv = { 250, 250, 0 }; VECTOR v0 = { - vJPos[pBone->id & 0x7f].vx, - -vJPos[pBone->id & 0x7f].vy, - vJPos[pBone->id & 0x7f].vz + vJPos[pBone->id & 127].vx, + -vJPos[pBone->id & 127].vy, + vJPos[pBone->id & 127].vz }; VECTOR v1 = { - vJPos[pBone->pParent->id & 0x7f].vx, - -vJPos[pBone->pParent->id & 0x7f].vy, - vJPos[pBone->pParent->id & 0x7f].vz + vJPos[pBone->pParent->id & 127].vx, + -vJPos[pBone->pParent->id & 127].vy, + vJPos[pBone->pParent->id & 127].vz }; VECTOR ofs = *(VECTOR*)&pDrawingPed->position; @@ -1205,7 +1211,7 @@ void newShowTanner(LPPEDESTRIAN pDrawingPed) { BONE* pBone = &Skel[i]; - int id = pBone->id & 0x7f; + int id = pBone->id & 127; if (id != LSHOULDER && id != RSHOULDER @@ -1232,13 +1238,13 @@ void newShowTanner(LPPEDESTRIAN pDrawingPed) { SVECTOR v1, v2; - v1.vx = vJPos[pBone->id & 0x7f].vx; - v1.vy = vJPos[pBone->id & 0x7f].vy; - v1.vz = vJPos[pBone->id & 0x7f].vz; + v1.vx = vJPos[pBone->id & 127].vx; + v1.vy = vJPos[pBone->id & 127].vy; + v1.vz = vJPos[pBone->id & 127].vz; - v2.vx = vJPos[pBone->pParent->id & 0x7f].vx; - v2.vy = vJPos[pBone->pParent->id & 0x7f].vy; - v2.vz = vJPos[pBone->pParent->id & 0x7f].vz; + v2.vx = vJPos[pBone->pParent->id & 127].vx; + v2.vy = vJPos[pBone->pParent->id & 127].vy; + v2.vz = vJPos[pBone->pParent->id & 127].vz; bAllreadyRotated = 1; DoCivHead(pDrawingPed, &v2, &v1); @@ -1249,7 +1255,7 @@ void newShowTanner(LPPEDESTRIAN pDrawingPed) // clear all id flags for (i = 0; i < NUM_BONES; i++) - Skel[i].id = (LIMBS)(Skel[i].id & 0x7f); + Skel[i].id = (LIMBS)(Skel[i].id & 127); } // [D] [T] @@ -1259,8 +1265,8 @@ SVECTOR* GetModelVertPtr(LPPEDESTRIAN pDrawingPed, int boneId, int modelType) if (pDrawingPed->pedType != OTHER_MODEL) { - if (cTannerVNumbers[boneId & 0x7f] != -1) - return vTannerList + cTannerVNumbers[boneId & 0x7f]; + if (cTannerVNumbers[boneId & 127] != -1) + return vTannerList + cTannerVNumbers[boneId & 127]; return NULL; } @@ -1286,7 +1292,7 @@ SVECTOR* GetModelVertPtr(LPPEDESTRIAN pDrawingPed, int boneId, int modelType) startVertex = cJerichoVNumbers[5]; break; default: - return vTannerList + cTannerVNumbers[boneId & 0x7f]; + return vTannerList + cTannerVNumbers[boneId & 127]; } return vJerichoList + startVertex; @@ -1364,7 +1370,7 @@ void newRotateBones(LPPEDESTRIAN pDrawingPed, BONE* poBone) _sMatrix.t[1] = _svBone[0].vy; _sMatrix.t[2] = _svBone[0].vz; - _pMatrix = mStore[pBone->pParent->id & 0x7f]; + _pMatrix = mStore[pBone->pParent->id & 127]; gte_MulMatrix0(&_pMatrix, &_sMatrix, &_oMatrix); gte_SetRotMatrix(&_oMatrix); @@ -1378,7 +1384,7 @@ void newRotateBones(LPPEDESTRIAN pDrawingPed, BONE* poBone) pBone->vCurrPos.vy = _vBoneRotated.vy; pBone->vCurrPos.vz = _vBoneRotated.vz; - if (pBone->id < 0x7f) + if (pBone->id < 127) { pVerts = GetModelVertPtr(pDrawingPed, pBone->id, 0); @@ -1645,7 +1651,7 @@ void DrawTanner(LPPEDESTRIAN pPed) if (pPed->padId == 0) { - if (gTimeOfDay == 3) + if (gTimeOfDay == TIME_NIGHT) { cV.b = 12; cV.g = 12; @@ -1693,7 +1699,7 @@ int DrawCharacter(LPPEDESTRIAN pPed) bDoingShadow = 1; v.vy = -camera_position.vy - MapHeight((VECTOR*)&pPed->position); - if (gTimeOfDay == 3) + if (gTimeOfDay == TIME_NIGHT) { cV.b = cV.g = cV.r = 12; TannerShadow(pPed, &v, moon_position + GameLevel, &cV, pPed->dir.vy); @@ -1747,7 +1753,7 @@ void InitTannerShadow(void) POLY_FT4* poly; int i; - if (gTimeOfDay == 3) + if (gTimeOfDay == TIME_NIGHT) brightness = 12; else brightness = 32; @@ -2002,7 +2008,7 @@ void DoCivHead(LPPEDESTRIAN pPed, SVECTOR* vert1, SVECTOR* vert2) if (pPed->pallet & 0xf) { flags |= PLOT_CUSTOM_PALETTE; // set custom palette flag - plotContext.clut = civ_clut[0][texturePedHead.texture_number][pPed->pallet & 0xf] << 0x10; + plotContext.clut = civ_clut[0][texturePedHead.texture_number][pPed->pallet & 15]; } oldcombointensity = combointensity; diff --git a/src_rebuild/Game/C/objanim.c b/src_rebuild/Game/C/objanim.c index 89f11962b..039363c44 100644 --- a/src_rebuild/Game/C/objanim.c +++ b/src_rebuild/Game/C/objanim.c @@ -14,6 +14,7 @@ #include "spool.h" #include "system.h" #include "pause.h" +#include "draw.h" struct ANIMATED_OBJECT { @@ -78,7 +79,7 @@ CYCLE_OBJECT* Lev_CycleObjPtrs[] = { Lev3 }; -int Num_LevCycleObjs[] = { 2, 0, 12, 0 }; +int Num_LevCycleObjs[] = { numberOf(Lev0), 0, numberOf(Lev2), 0 }; ANIMATED_OBJECT Lev0AnimObjects[9] = { @@ -175,7 +176,6 @@ SMASHABLE_OBJECT smashable[] = { 0, 0, 0, 0, 0 } }; - int num_anim_objects = 0; int num_cycle_obj = 0; @@ -191,7 +191,7 @@ void InitCyclingPals(void) int i; // only enabled at Night - if (gTimeOfDay != 3) + if (gTimeOfDay != TIME_NIGHT) { num_cycle_obj = 0; return; @@ -220,7 +220,9 @@ void ColourCycle(void) RECT16 vram; if (!num_cycle_obj) + { return; + } if (LoadingArea != 0) { @@ -228,66 +230,76 @@ void ColourCycle(void) return; } + if (FrameCnt & 1) // [A] + { + return; + } + vram.w = 16; vram.h = 1; cyc = Lev_CycleObjPtrs[GameLevel]; - for (i = 0; i < num_cycle_obj; i++) + for (i = 0; i < num_cycle_obj; i++, cyc++) { + TEXTURE_DETAILS* cycTex = &cycle_tex[i]; bufaddr = (u_short*)cyclecluts[i].p; - if (tpageloaded[cycle_tex[i].texture_page] != 0) + if (tpageloaded[cycTex->texture_page] == 0) { - if (cycle_phase == 0) - { - // initialize - temp = texture_cluts[cycle_tex[i].texture_page][cycle_tex[i].texture_number]; + continue; + } - cyc->vx = vram.x = (temp & 0x3f) << 4; - cyc->vy = vram.y = (temp >> 6); + if (cycle_phase == 0) + { + // initialize clut data from VRAM + temp = texture_cluts[cycTex->texture_page][cycTex->texture_number]; - StoreImage(&vram, (u_long*)bufaddr); - } - else + cyc->vx = vram.x = (temp & 63) << 4; + cyc->vy = vram.y = (temp >> 6); + + StoreImage(&vram, (u_long*)bufaddr); + } + else + { + if ((cycle_timer & cyc->speed1) == 0) { - if ((cycle_timer & cyc->speed1) == 0) + if (cyc->start1 != -1) { - if (cyc->start1 != -1) - { - temp = bufaddr[cyc->start1]; - memmove((u_char*)(bufaddr + cyc->start1), (u_char*)(bufaddr + cyc->start1 + 1), (cyc->stop1 - cyc->start1) << 1); + temp = bufaddr[cyc->start1]; + memmove((u_char*)(bufaddr + cyc->start1), (u_char*)(bufaddr + cyc->start1 + 1), (cyc->stop1 - cyc->start1) * sizeof(ushort)); - bufaddr[cyc->stop1] = temp; - } + bufaddr[cyc->stop1] = temp; } + } - if ((cycle_timer & cyc->speed2) == 0) + if ((cycle_timer & cyc->speed2) == 0) + { + if (cyc->start2 != -1) { - if (cyc->start2 != -1) - { - temp = bufaddr[cyc->start2]; - memmove((u_char*)(bufaddr + cyc->start2), (u_char*)(bufaddr + cyc->start2 + 1), (cyc->stop2 - cyc->start2) << 1); + temp = bufaddr[cyc->start2]; + memmove((u_char*)(bufaddr + cyc->start2), (u_char*)(bufaddr + cyc->start2 + 1), (cyc->stop2 - cyc->start2) * sizeof(ushort)); - bufaddr[cyc->stop2] = temp; - } + bufaddr[cyc->stop2] = temp; } + } - vram.x = cyc->vx; - vram.y = cyc->vy; + vram.x = cyc->vx; + vram.y = cyc->vy; - SetDrawLoad(&cyclecluts[i], &vram); + SetDrawLoad(&cyclecluts[i], &vram); - addPrim(current->ot, &cyclecluts[i]); - } + addPrim(current->ot, &cyclecluts[i]); } - - cyc++; } if (cycle_phase != 0) + { cycle_timer++; - - cycle_phase ^= 1; + } + else + { + cycle_phase = 1; + } } @@ -319,20 +331,26 @@ void InitAnimatingObjects(void) { // My way int model_idx = FindModelIdxWithName(aop->name); + aop->model_num = model_idx; if (model_idx != -1 && modelpointers[model_idx] != &dummyModel) { modelPtr = modelpointers[model_idx]; modelPtr->flags2 |= MODEL_FLAG_ANIMOBJ; +#ifdef DYNAMIC_LIGHTING + if (gEnableDlights) + { + modelPtr->bounding_sphere <<= 3; + } +#endif // DYNAMIC_LIGHTING + if (aop->LitPoly) modelPtr->flags2 |= MODEL_FLAG_LAMP; - - aop->model_num = model_idx; // [A] store animated object number in normals pointer // after all it was always unused - modelPtr->normals = loop; + modelPtr->tri_verts = loop; if (modelPtr->instance_number != -1 && modelpointers[modelPtr->instance_number] != &dummyModel) @@ -340,16 +358,21 @@ void InitAnimatingObjects(void) modelPtr = modelpointers[modelPtr->instance_number]; modelPtr->flags2 |= MODEL_FLAG_ANIMOBJ; +#ifdef DYNAMIC_LIGHTING + if (gEnableDlights) + { + modelPtr->bounding_sphere <<= 3; + } +#endif // DYNAMIC_LIGHTING + if (aop->LitPoly) modelPtr->flags2 |= MODEL_FLAG_LAMP; // [A] store animated object number in normals pointer // after all it was always unused - modelPtr->normals = loop; + modelPtr->tri_verts = loop; } } - else - aop->model_num = -1; aop++; } @@ -380,9 +403,16 @@ void InitSpooledAnimObj(int model_number) if (aop->LitPoly) modelPtr->flags2 |= MODEL_FLAG_LAMP; +#ifdef DYNAMIC_LIGHTING + if (gEnableDlights) + { + modelPtr->bounding_sphere <<= 3; + } +#endif // DYNAMIC_LIGHTING + // [A] store animated object number in normals pointer // after all it was always unused - modelPtr->normals = i; + modelPtr->tri_verts = i; break; } @@ -416,10 +446,11 @@ void DrawAllAnimatingObjects(CELL_OBJECT** objects, int num_animated) model = modelpointers[type]; // [A] optimized - animate_object(cop, aop[model->normals].internal_id); + animate_object(cop, aop[model->tri_verts & 31].internal_id); } } + // [D] [T] void animate_object(CELL_OBJECT* cop, int type) { @@ -428,12 +459,11 @@ void animate_object(CELL_OBJECT* cop, int type) yang = cop->yang * 64; - // [A] Rev 1.1 has less of those types - if (GameLevel == 0) { switch (type) { + case 0: case 7: yang += 1024; @@ -441,15 +471,15 @@ void animate_object(CELL_OBJECT* cop, int type) if (phase == 1) { - AddTrafficLight(cop, 0, -0x2c4, -0x2d, 0x200, yang); + AddTrafficLight(cop, 0, -708, -45, 0x200, yang); } else if (phase == 2) { - AddTrafficLight(cop, 0, -0x284, -0x2d, 0x400, yang); + AddTrafficLight(cop, 0, -644, -45, 0x400, yang); } else if (phase == 3) { - AddTrafficLight(cop, 0, -0x244, -0x2d, 0x800, yang); + AddTrafficLight(cop, 0, -580, -45, 0x800, yang); } break; case 1: @@ -458,15 +488,15 @@ void animate_object(CELL_OBJECT* cop, int type) if (phase == 1) { - AddTrafficLight(cop, 0x196, -0x2c4, -0x2e, 0x200, yang); + AddTrafficLight(cop, 406, -708, -46, 0x200, yang); } else if (phase == 2) { - AddTrafficLight(cop, 0x196, -0x292, -0x2e, 0x400, yang); + AddTrafficLight(cop, 406, -658, -46, 0x400, yang); } else if (phase == 3) { - AddTrafficLight(cop, 0x196, -0x252, -0x2e, 0x800, yang); + AddTrafficLight(cop, 406, -594, -46, 0x800, yang); } break; case 2: @@ -474,27 +504,27 @@ void animate_object(CELL_OBJECT* cop, int type) if (gLightsOn == 0) break; - AddLightEffect(cop, 0x1ad, -0x4d2, 0, 0, 3); + AddLightEffect(cop, 429, -1234, 0, 0, 3); break; case 4: if (gLightsOn == 0) break; - AddLightEffect(cop, -0x1b0, -0x4d9, 0, 0, 3); - AddLightEffect(cop, 0x1b0, -0x4d9, 0, 0, 3); + AddLightEffect(cop, -432, -1241, 0, 0, 3); + AddLightEffect(cop, 432, -1241, 0, 0, 3); break; case 5: if (gLightsOn == 0) break; - AddLightEffect(cop, 0, -0x50, 0, 2, 2); + AddLightEffect(cop, 0, -80, 0, 2, 2); break; case 6: if (gLightsOn == 0) break; - AddLightEffect(cop, 0xea, -0x47a, 0, 0, 3); - AddLightEffect(cop, -0xea, -0x47a, 0, 0, 3); + AddLightEffect(cop, 234, -1146, 0, 0, 3); + AddLightEffect(cop, -234, -1146, 0, 0, 3); } } else if (GameLevel == 1) @@ -506,15 +536,15 @@ void animate_object(CELL_OBJECT* cop, int type) if (phase == 2) { - AddTrafficLight(cop, -0x1a1, -800, -0x1e, 0x400, yang); + AddTrafficLight(cop, -417, -800, -30, 0x400, yang); } else if (phase == 1) { - AddTrafficLight(cop, -0x1ea, -800, -0x1e, 0x200, yang); + AddTrafficLight(cop, -490, -800, -30, 0x200, yang); } else if (phase == 3) { - AddTrafficLight(cop, -0x157, -800, -0x1e, 0x800, yang); + AddTrafficLight(cop, -343, -800, -30, 0x800, yang); } break; @@ -522,20 +552,20 @@ void animate_object(CELL_OBJECT* cop, int type) if (gLightsOn == 0) break; - AddSmallStreetLight(cop, 0xe6, -0x442, 0, 0); + AddSmallStreetLight(cop, 230, -1090, 0, 0); break; case 3: if (gLightsOn == 0) break; - AddLightEffect(cop, -0x265, -0x7d2, 0, 0, 3); + AddLightEffect(cop, -613, -2002, 0, 0, 3); break; case 4: if (gLightsOn == 0) break; - AddLightEffect(cop, -0x250, -2000, 0, 0, 3); - AddLightEffect(cop, 0x252, -2000, 0, 0, 3); + AddLightEffect(cop, -592, -2000, 0, 0, 3); + AddLightEffect(cop, 594, -2000, 0, 0, 3); break; } } @@ -548,21 +578,21 @@ void animate_object(CELL_OBJECT* cop, int type) if (phase == 1) { - AddTrafficLight(cop, -0x857, -0x458, -0x44, 0x200, yang); - AddTrafficLight(cop, -0x520, -0x421, -0x44, 0x200, yang); - AddTrafficLight(cop, -0x202, -0x400, -0x44, 0x200, yang); + AddTrafficLight(cop, -2135, -1112, -68, 0x200, yang); + AddTrafficLight(cop, -1312, -1057, -68, 0x200, yang); + AddTrafficLight(cop, -514, -1024, -68, 0x200, yang); } else if (phase == 2) { - AddTrafficLight(cop, -0x85c, -0x3fd, -0x41, 0x400, yang); - AddTrafficLight(cop, -0x51e, -0x3d4, -0x41, 0x400, yang); - AddTrafficLight(cop, -0x208, -0x3ab, -0x41, 0x400, yang); + AddTrafficLight(cop, -2140, -1021, -65, 0x400, yang); + AddTrafficLight(cop, -1310, -980, -65, 0x400, yang); + AddTrafficLight(cop, -520, -939, -65, 0x400, yang); } else if (phase == 3) { - AddTrafficLight(cop, -0x85a, -0x3a9, -0x3e, 0x800, yang); - AddTrafficLight(cop, -0x51d, -0x381, -0x3e, 0x800, yang); - AddTrafficLight(cop, -0x206, -0x353, -0x3e, 0x800, yang); + AddTrafficLight(cop, -2138, -937, -62, 0x800, yang); + AddTrafficLight(cop, -1309, -897, -62, 0x800, yang); + AddTrafficLight(cop, -518, -851, -62, 0x800, yang); } break; case 1: @@ -570,41 +600,42 @@ void animate_object(CELL_OBJECT* cop, int type) if (phase == 1) { - AddTrafficLight(cop, -4, -0x219, -0x29, 0x200, yang); + AddTrafficLight(cop, -4, -537, -41, 0x200, yang); } else if (phase == 2) { - AddTrafficLight(cop, -4, -0x1d2, -0x29, 0x400, yang); + AddTrafficLight(cop, -4, -466, -41, 0x400, yang); } else if (phase == 3) { - AddTrafficLight(cop, -4, -0x18b, -0x29, 0x800, yang); + AddTrafficLight(cop, -4, -395, -41, 0x800, yang); } break; case 2: if (gLightsOn == 0) break; - AddLightEffect(cop, -0x348, -0x7b4, -0x3d, 0, 3); + AddLightEffect(cop, -840, -1972, -61, 0, 3); break; case 3: if (gLightsOn == 0) break; - AddLightEffect(cop, -0x361, -0x8c9, 0, 0, 3); - AddLightEffect(cop, 0x361, -0x8c9, 0, 0, 3); + AddLightEffect(cop, -865, -2249, 0, 0, 3); + AddLightEffect(cop, 865, -2249, 0, 0, 3); break; case 4: if (gLightsOn == 0) break; + // spooled? if (cop->pos.vx - 137190U < 50687 && cop->pos.vz > 713372 && cop->pos.vz < 719516) { - AddSmallStreetLight(cop, -0x26c, -0x65, 0, 0); + AddSmallStreetLight(cop, -620, -101, 0, 0); break; } - AddLightEffect(cop, -0x26c, -0x652, 0, 0, 3); + AddLightEffect(cop, -620, -1618, 0, 0, 3); break; } } @@ -617,15 +648,15 @@ void animate_object(CELL_OBJECT* cop, int type) if (phase == 1) { - AddTrafficLight(cop, -0x2cf, -0x38a, -0x16, 0x200, yang); + AddTrafficLight(cop, -719, -906, -22, 0x200, yang); } else if (phase == 2) { - AddTrafficLight(cop, -0x2cf, -0x345, -0x16, 0x400, yang); + AddTrafficLight(cop, -719, -837, -22, 0x400, yang); } else if (phase == 3) { - AddTrafficLight(cop, -0x2cf, -0x2fa, -0x16, 0x800, yang); + AddTrafficLight(cop, -719, -762, -22, 0x800, yang); } break; case 1: @@ -633,36 +664,36 @@ void animate_object(CELL_OBJECT* cop, int type) if (phase == 1) { - AddTrafficLight(cop, 0, -0x28e, -0x15, 0x200, yang); + AddTrafficLight(cop, 0, -654, -21, 0x200, yang); } else if (phase == 2) { - AddTrafficLight(cop, 0, -0x242, -0x15, 0x400, yang); + AddTrafficLight(cop, 0, -578, -21, 0x400, yang); } else if (phase == 3) { - AddTrafficLight(cop, 0, -0x1fd, -0x15, 0x800, yang); + AddTrafficLight(cop, 0, -509, -21, 0x800, yang); } break; case 2: if (gLightsOn == 0) break; - AddLightEffect(cop, -0x1f1, -0x59d, 0, 0, 3); + AddLightEffect(cop, -497, -1437, 0, 0, 3); break; case 3: if (gLightsOn == 0) break; - AddLightEffect(cop, 0, -0xaa7, 0, 0, 3); + AddLightEffect(cop, 0, -2727, 0, 0, 3); break; case 4: if (gLightsOn == 0) break; - AddSmallStreetLight(cop, 0, -0x492, 0, 1); + AddSmallStreetLight(cop, 0, -1170, 0, 1); break; } } diff --git a/src_rebuild/Game/C/objcoll.c b/src_rebuild/Game/C/objcoll.c index f20ed8188..a74cfcb9d 100644 --- a/src_rebuild/Game/C/objcoll.c +++ b/src_rebuild/Game/C/objcoll.c @@ -41,15 +41,12 @@ char CellEmpty(VECTOR *pPosition, int radius) cell_x = (pPosition->vx + units_across_halved) / MAP_CELL_SIZE; cell_z = (pPosition->vz + units_down_halved) / MAP_CELL_SIZE; - ppco = GetFirstPackedCop(cell_x, cell_z, &ci, 0); - - while(ppco) + for (ppco = GetFirstPackedCop(cell_x, cell_z, &ci, 0); ppco; ppco = GetNextPackedCop(&ci)) { type = (ppco->value >> 6) | ((ppco->pos.vy & 1) << 10); pModel = modelpointers[type]; - if ((uint)pModel->collision_block > 0 && - (pModel->flags2 & (MODEL_FLAG_CHAIR | MODEL_FLAG_SMASHABLE)) == 0) + if ((uint)pModel->collision_block > 0 && (pModel->flags2 & (MODEL_FLAG_CHAIR | MODEL_FLAG_SMASHABLE)) == 0) { QuickUnpackCellObject(ppco, &ci.nearCell, &tempCO); @@ -87,7 +84,7 @@ char CellEmpty(VECTOR *pPosition, int radius) int theta; MATRIX2* mat; - yang = -tempCO.yang & 0x3f; + yang = -tempCO.yang & 63; theta = (tempCO.yang + collide->yang) * 64; mat = &matrixtable[yang]; @@ -198,8 +195,6 @@ char CellEmpty(VECTOR *pPosition, int radius) } } } - - ppco = GetNextPackedCop(&ci); } return 1; @@ -238,22 +233,34 @@ char CellAtPositionEmpty(VECTOR *pPosition, int radius) } +struct tRay +{ + LONGVECTOR4 org, dir; +}; + +struct tRange +{ + int lower, upper; +}; + +struct tAABB +{ + tRange slab[3]; +}; + + // [D] [T] int RaySlabsIntersection(tRay *ray, tAABB *bbox) { - int dir; - int d; - int i; + int dir, d, i; - tRange inside; - tRange cabbage; - tRange scaledCabbage; + tRange inside, cabbage, scaledCabbage; inside.lower = 0; inside.upper = 4096; - i = 0; - do { + for (i = 0; i < 3; i++) + { d = -1; cabbage.lower = bbox->slab[i].lower - ray->org[i]; @@ -294,10 +301,7 @@ int RaySlabsIntersection(tRay *ray, tAABB *bbox) if (inside.upper < inside.lower) return 0; - - i++; - - } while(i < 3); + } return 1; } @@ -310,31 +314,20 @@ char lineClear(VECTOR *v1, VECTOR *v2) PACKED_CELL_OBJECT* ppco; CELL_OBJECT tempCO; MATRIX2* mat; - int cell_z; - int cell_x; - int yang; + int cell_x, cell_z; + int yang, theta; MODEL* pModel; - int theta; - int dx,dz; - - int cs; - int sn; - + int cs, sn; COLLISION_PACKET* collide; - VECTOR pos; - VECTOR va; - VECTOR vb; + VECTOR pos, va, vb; tRay ray; tAABB box; int we; - int ocz; - int ocx; - int box_loop; // $s5 - int num_cb; // $s6 - int sphere_sq; // $v0 - int xd; // $a0 - int zd; // $v1 + int ocx, ocz; + int box_loop, num_cb; + int sphere_sq; + int xd, zd; #if 0 //def PSX CELL_ITERATOR& ci = *(CELL_ITERATOR*)((u_char*)getScratchAddr(0) + 1024 - sizeof(CELL_ITERATOR)); @@ -356,9 +349,8 @@ char lineClear(VECTOR *v1, VECTOR *v2) ocx = -1; ocz = -1; - we = 0; - - do { + for (we = 0; we < 2; we++) + { if (we == 0) { cell_x = (v2->vx + units_across_halved) / MAP_CELL_SIZE; @@ -370,17 +362,15 @@ char lineClear(VECTOR *v1, VECTOR *v2) cell_z = (v1->vz + units_down_halved) / MAP_CELL_SIZE; } - if ((ocx != cell_x) || (ocz != cell_z)) + if (ocx != cell_x || ocz != cell_z) { - ppco = GetFirstPackedCop(cell_x, cell_z, &ci, 0); - - while (ppco) + for (ppco = GetFirstPackedCop(cell_x, cell_z, &ci, 0); ppco; ppco = GetNextPackedCop(&ci)) { QuickUnpackCellObject(ppco, &ci.nearCell, &tempCO); pModel = modelpointers[tempCO.type]; - xd = ((tempCO.pos.vx - ((va.vx + vb.vx) / 2 & 0xffffU)) * 0x10000) >> 0x10; - zd = ((tempCO.pos.vz - ((va.vz + vb.vz) / 2 & 0xffffU)) * 0x10000) >> 0x10; + xd = tempCO.pos.vx - (short)((va.vx + vb.vx) / 2); + zd = tempCO.pos.vz - (short)((va.vz + vb.vz) / 2); sphere_sq = pModel->bounding_sphere + 800; @@ -406,13 +396,13 @@ char lineClear(VECTOR *v1, VECTOR *v2) dx = va.vx - (tempCO.pos.vx + FIXEDH(collide->xpos * mat->m[0][0] + collide->zpos * mat->m[2][0])); dz = va.vz - (tempCO.pos.vz + FIXEDH(collide->xpos * mat->m[0][2] + collide->zpos * mat->m[2][2])); - box.slab[0].upper = collide->xsize / 2 +testRadius; + box.slab[0].upper = collide->xsize / 2 + testRadius; box.slab[0].lower = -box.slab[0].upper; - - box.slab[1].upper = collide->ysize / 2 +testRadius; + + box.slab[1].upper = collide->ysize / 2 + testRadius; box.slab[1].lower = -box.slab[1].upper; - - box.slab[2].upper = collide->zsize / 2 +testRadius; + + box.slab[2].upper = collide->zsize / 2 + testRadius; box.slab[2].lower = -box.slab[2].upper; ray.org[0] = FIXEDH(cs * dx - sn * dz); @@ -503,15 +493,12 @@ char lineClear(VECTOR *v1, VECTOR *v2) collide++; } } - ppco = GetNextPackedCop(&ci); } } - we++; - ocx = cell_x; ocz = cell_z; - } while (we < 2); + } return 1; } @@ -555,9 +542,7 @@ void CollisionCopList(XZPAIR* pos, int* count) // check if we have valid region if (cbr.x + cbr.z * regions_across == RoadMapRegions[(cbr.x & 1) + (cbr.z & 1) * 2]) { - ppco = GetFirstPackedCop(cell.x, cell.z, &ci, 1, cellLevel); - - while (ppco) + for (ppco = GetFirstPackedCop(cell.x, cell.z, &ci, 1, cellLevel); ppco; ppco = GetNextPackedCop(&ci)) { type = (ppco->value >> 6) | ((ppco->pos.vy & 1) << 10); @@ -576,8 +561,6 @@ void CollisionCopList(XZPAIR* pos, int* count) pcoplist[cnt] = ppco; cnt++; } - - ppco = GetNextPackedCop(&ci); } } @@ -623,7 +606,6 @@ void CheckScenaryCollisions(CAR_DATA *cp) { int count; int num_cb; - int coll_test_count; int yang; int minDist; COLLISION_PACKET *collide; @@ -660,7 +642,6 @@ void CheckScenaryCollisions(CAR_DATA *cp) else extraDist = 580; - // [A] FIXME: replace with 'cell_header.cell_size' cell.x = (player_pos.vx + units_across_halved - (MAP_REGION_SIZE*MAP_REGION_SIZE)) / MAP_CELL_SIZE; cell.z = (player_pos.vz + units_down_halved - (MAP_REGION_SIZE*MAP_REGION_SIZE)) / MAP_CELL_SIZE; @@ -678,14 +659,19 @@ void CheckScenaryCollisions(CAR_DATA *cp) cop = &EventCop[count - mdcount]; model = modelpointers[cop->type]; - num_cb = *(int*)model->collision_block; // box count if ((uint)model->collision_block > 0 /*&& model->num_vertices - 3 < 300 && model->num_point_normals < 300 && - model->num_polys < 300*/ && - num_cb > 0) + model->num_polys < 300*/) { + num_cb = *(int*)model->collision_block; // box count + + if (!num_cb) + { + continue; + } + dx = cop->pos.vx - player_pos.vx; dz = cop->pos.vz - player_pos.vz; @@ -700,7 +686,6 @@ void CheckScenaryCollisions(CAR_DATA *cp) yang = -cop->yang & 63; // box 'rotated' by matrix - // [A] FIXME: replace add+shift by division bbox.pos.vx = cop->pos.vx + FIXEDH(collide->xpos * matrixtable[yang].m[0][0] + collide->zpos * matrixtable[yang].m[2][0]); bbox.pos.vy = cop->pos.vy + collide->ypos; bbox.pos.vz = cop->pos.vz + FIXEDH(collide->xpos * matrixtable[yang].m[0][2] + collide->zpos * matrixtable[yang].m[2][2]); @@ -741,11 +726,12 @@ void CheckScenaryCollisions(CAR_DATA *cp) } else if (cp->controlType == CONTROL_TYPE_CAMERACOLLIDER) { - if ((model->flags2 & (MODEL_FLAG_CHAIR | MODEL_FLAG_SMASHABLE)) == 0 && - (bbox.xsize > 100 || (bbox.zsize > 100))) + if ((model->flags2 & (MODEL_FLAG_CHAIR | MODEL_FLAG_SMASHABLE)) == 0 && (bbox.xsize > 100 || bbox.zsize > 100)) { - coll_test_count = 5; - + int diff; + int coll_test_count = 2; // [A] only two tests needed + int prevDistance = gCameraDistance; + bbox.xsize += 100; bbox.zsize += 100; @@ -753,16 +739,15 @@ void CheckScenaryCollisions(CAR_DATA *cp) minDist = lbody / 2; - while (coll_test_count > 0 && minDist <= gCameraDistance && CarBuildingCollision(cp, &bbox, cop, 0)) + while (coll_test_count > 0 && gCameraDistance > minDist && CarBuildingCollision(cp, &bbox, cop, 0)) { - gCameraDistance -= gCameraBoxOverlap; - - if (gCameraDistance < minDist) - gCameraDistance = minDist; - - cp->hd.where.t[0] = car_data[0].hd.where.t[0] + FIXEDH((gCameraDistance * RSIN(cp->hd.direction)) / 2); - cp->hd.where.t[2] = car_data[0].hd.where.t[2] + FIXEDH((gCameraDistance * RCOS(cp->hd.direction)) / 2); - + gCameraDistance = MAX(minDist, gCameraDistance - gCameraBoxOverlap); + diff = prevDistance - gCameraDistance; + prevDistance = gCameraDistance; + + cp->hd.where.t[0] -= FIXEDH((diff * RSIN(cp->hd.direction)) / 2); + cp->hd.where.t[2] -= FIXEDH((diff * RCOS(cp->hd.direction)) / 2); + coll_test_count--; } } @@ -771,14 +756,18 @@ void CheckScenaryCollisions(CAR_DATA *cp) { if (count >= mdcount && cop->pad != 0) { + int extraFlags; + extraFlags = 0; cp->st.n.linearVelocity[2] = ExBoxDamage + cp->st.n.linearVelocity[2]; - - if (CarBuildingCollision(cp, &bbox, cop, (cop->pad == 1) ? CollisionCheckFlag_IsVegasMovingTrain : 0)) +#if ENABLE_GAME_FIXES + extraFlags |= (cop->pad == 1) ? CollisionCheckFlag_IsVegasMovingTrain : 0; +#endif + if (CarBuildingCollision(cp, &bbox, cop, extraFlags)) { cp->ap.needsDenting = 1; } - //cp->st.n.linearVelocity[2] -= 700000; // [A] Vegas train velocity - disabled here + //cp->st.n.linearVelocity[2] -= 700000; // [A] Vegas train velocity - disabled here, see flag above } else { @@ -888,8 +877,8 @@ int QuickBuildingCollisionCheck(VECTOR *pPos, int dir, int l, int w, int extra) cd[1].length[0] = collide->zsize / 2 + 100; cd[1].length[1] = collide->xsize / 2 + 100; - cd[1].x.vx = pPos->vx + (((offset.vx - pPos->vx) << 0x10) >> 0x10); - cd[1].x.vz = pPos->vz + (((offset.vz - pPos->vz) << 0x10) >> 0x10); + cd[1].x.vx = pPos->vx + (short)(offset.vx - pPos->vx); + cd[1].x.vz = pPos->vz + (short)(offset.vz - pPos->vz); cd[1].vel.vx = 0; cd[1].vel.vz = 0; diff --git a/src_rebuild/Game/C/overlay.c b/src_rebuild/Game/C/overlay.c index ea17f0439..3fbfd6cdd 100644 --- a/src_rebuild/Game/C/overlay.c +++ b/src_rebuild/Game/C/overlay.c @@ -123,19 +123,17 @@ void InitOverlays(void) } // [D] [T] -void SetFullscreenDrawing(void) +void SetFullscreenDrawing(int ofs) { DR_ENV *drenv; DRAWENV drawenv; - drenv = (DR_ENV *)current->primptr; - drawenv.clip.x = 256; SetDefDrawEnv(&drawenv, 0, current->draw.clip.y & 256, 320, 256); + drenv = (DR_ENV*)current->primptr; SetDrawEnv(drenv, &drawenv); - - addPrim(current->ot + 2, drenv); + addPrim(current->ot + ofs, drenv); current->primptr += sizeof(DR_ENV); } @@ -238,7 +236,7 @@ void DrawPercentageBar(PERCENTAGE_BAR *bar) poly->y2 = max_y; poly->y3 = max_y; - addPrim((u_long*)(current->ot + 1), poly); + addPrim(current->ot + 1, poly); current->primptr += sizeof(POLY_G4); } @@ -271,7 +269,7 @@ void DrawPercentageBar(PERCENTAGE_BAR *bar) poly2->y2 = max_y; poly2->y3 = max_y; - addPrim((u_long*)(current->ot+1), poly2); + addPrim(current->ot+1, poly2); current->primptr += sizeof(POLY_G4); // draw contours @@ -293,7 +291,7 @@ void DrawPercentageBar(PERCENTAGE_BAR *bar) lineF4->y2 = max_y; lineF4->y3 = max_y; - addPrim((u_long*)(current->ot + 1), lineF4); + addPrim(current->ot + 1, lineF4); current->primptr += sizeof(LINE_F4); LINE_F2* lineF2 = (LINE_F2*)current->primptr; @@ -308,7 +306,7 @@ void DrawPercentageBar(PERCENTAGE_BAR *bar) lineF2->x1 = min_x - 1; lineF2->y1 = max_y; - addPrim((u_long*)(current->ot + 1), lineF2); + addPrim(current->ot + 1, lineF2); current->primptr += sizeof(LINE_F2); TransparencyOn(current->ot + 1, 0x20); @@ -441,7 +439,7 @@ void DrawProximityBar(PERCENTAGE_BAR *bar) lineF4->y2 = max_y; lineF4->y3 = max_y; - addPrim((u_long*)(current->ot + 1), lineF4); + addPrim(current->ot + 1, lineF4); current->primptr += sizeof(LINE_F4); LINE_F2* lineF2 = (LINE_F2*)current->primptr; @@ -456,7 +454,7 @@ void DrawProximityBar(PERCENTAGE_BAR *bar) lineF2->x1 = min_x - 1; lineF2->y1 = max_y; - addPrim((u_long*)(current->ot + 1), lineF2); + addPrim(current->ot + 1, lineF2); current->primptr += sizeof(LINE_F2); TransparencyOn(current->ot + 1, 0x20); @@ -704,7 +702,7 @@ void DisplayOverlays(void) if (CurrentPlayerView == 0) return; - SetFullscreenDrawing(); + SetFullscreenDrawing(2); } UpdateFlashValue(); diff --git a/src_rebuild/Game/C/overlay.h b/src_rebuild/Game/C/overlay.h index 0163251a7..bddde693f 100644 --- a/src_rebuild/Game/C/overlay.h +++ b/src_rebuild/Game/C/overlay.h @@ -36,7 +36,7 @@ extern char OverlayFlashValue; extern void InitOverlays(); // 0x00014A58 extern void DisplayOverlays(); // 0x00014C3C -extern void SetFullscreenDrawing(); // 0x00015E70 +extern void SetFullscreenDrawing(int ofs); // 0x00015E70 extern void InitPercentageBar(PERCENTAGE_BAR *bar, int size, COLOUR_BAND *pColourBand, char *tag); // 0x00015F20 extern void EnablePercentageBar(PERCENTAGE_BAR *bar, int max); // 0x00015F58 diff --git a/src_rebuild/Game/C/overmap.c b/src_rebuild/Game/C/overmap.c index 16018e361..5c0de75ca 100644 --- a/src_rebuild/Game/C/overmap.c +++ b/src_rebuild/Game/C/overmap.c @@ -404,7 +404,7 @@ void ProcessOverlayLump(char *lump_ptr, int lump_size) D_MALLOC_END(); // load CLUT - LoadImage(&mapclutpos, (u_long *)(MapBitMaps + 512)); + LoadImage(&mapclutpos, (u_long*)(MapBitMaps + 512)); DrawSync(0); } @@ -425,14 +425,14 @@ void LoadMapTile(int tpage, int x, int y) temp = x * 32; if (idx > -1 && idx < overlaidmaps[GameLevel].toptile && - temp > -1 && (temp < overlaidmaps[GameLevel].width)) + temp > -1 && temp < overlaidmaps[GameLevel].width) { UnpackRNC(MapBitMaps + *((ushort*)MapBitMaps + idx), MapBuffer); } else { for (count = 0; count < 512; count++) - MapBuffer[count++] = overlaidmaps[GameLevel].dummy; + MapBuffer[count] = overlaidmaps[GameLevel].dummy; } #ifdef PSX @@ -527,7 +527,7 @@ void DrawN(VECTOR *pScreenPosition, int direct) linef2->x1 = lastPoint.x; linef2->y1 = lastPoint.y; - if (gTimeOfDay == 3) + if (gTimeOfDay == TIME_NIGHT) { linef2->r0 = 75; linef2->g0 = 75; @@ -849,8 +849,7 @@ void DrawSightCone(COP_SIGHT_DATA *pCopSightData, VECTOR *pPosition, int directi // [D] [T] u_int Long2DDistance(VECTOR *pPoint1, VECTOR *pPoint2) { - int tempTheta; - int theta; + int theta, tempTheta; u_int result; VECTOR delta; @@ -859,7 +858,7 @@ u_int Long2DDistance(VECTOR *pPoint1, VECTOR *pPoint2) theta = ratan2(delta.vz, delta.vx); - if ((theta & 0x7ff) - 512U <= 1024) + if ((theta & 2047) - 512U <= 1024) { tempTheta = RSIN(theta); result = delta.vz; @@ -870,7 +869,7 @@ u_int Long2DDistance(VECTOR *pPoint1, VECTOR *pPoint2) result = delta.vx; } - if (result < 0x80000) + if (result < 512 * 1024) result = (result << 12) / tempTheta; else result = (result << 9) / tempTheta << 3; @@ -899,7 +898,7 @@ void InitMultiplayerMap(void) rect.w = 16; rect.h = 64; - LoadImage(&rect, (u_long *)MapBitMaps); + LoadImage(&rect, (u_long*)MapBitMaps); DrawSync(0); } @@ -1060,7 +1059,7 @@ void DrawMultiplayerMap(void) poly->clut = MapClut; poly->tpage = MapTPage; - if (gTimeOfDay == 3) + if (gTimeOfDay == TIME_NIGHT) r = 50; else r = 100; @@ -1174,12 +1173,7 @@ void DrawOverheadMap(void) } } - // for restoring - drarea = (DR_AREA *)current->primptr; - SetDrawArea(drarea, ¤t->draw.clip); - - addPrim(current->ot + 1, drarea); - current->primptr += sizeof(DR_AREA); + SetFullscreenDrawing(1); WorldToOverheadMapPositions((VECTOR *)player->pos, &vec, 1, 0, 0); @@ -1370,7 +1364,7 @@ void DrawOverheadMap(void) setPolyFT4(spt); setSemiTrans(spt, 1); - if (gTimeOfDay == 3) + if (gTimeOfDay == TIME_NIGHT) spt->r0 = spt->g0 = spt->b0 = 50; else spt->r0 = spt->g0 = spt->b0 = 100; @@ -1449,7 +1443,7 @@ void DrawOverheadMap(void) clipped_size.x = map_minX + 1; clipped_size.w = MAP_SIZE_W - 1; clipped_size.h = MAP_SIZE_H; - clipped_size.y = current->draw.clip.y + map_minY;// +1; + clipped_size.y = (current->draw.clip.y & 256) + map_minY; drarea = (DR_AREA*)current->primptr; @@ -1810,7 +1804,7 @@ void DrawFullscreenMap(void) SetTextColour(128, 128, 128); // print string with special characters representing some images inserted into it - sprintf(str, "\x80 %s \x81 %s \x8a %s", G_LTXT(GTXT_Exit), G_LTXT(GTXT_Rotation), G_LTXT(GTXT_Move)); + sprintf(str, "\x80 %s \x81 %s \x8a %s", G_LTXT(GTXT_Back), G_LTXT(GTXT_Rotation), G_LTXT(GTXT_Move)); PrintStringCentred(str, SCREEN_H - 30); // 226 } diff --git a/src_rebuild/Game/C/pathfind.c b/src_rebuild/Game/C/pathfind.c index 86409d876..7ee0a9225 100644 --- a/src_rebuild/Game/C/pathfind.c +++ b/src_rebuild/Game/C/pathfind.c @@ -10,6 +10,10 @@ #include "felony.h" #include "map.h" +#ifdef PSX +#pragma GCC optimization ("O3") +#endif + #define DEBUG_PATHFINDING_VIEW 0 #if DEBUG_PATHFINDING_VIEW && !defined(PSX) @@ -19,19 +23,17 @@ struct tNode { - int vx; - int vy; - int vz; + int vx, vy, vz; u_short dist; u_short ptoey; // just a padding. }; struct XZDIR { - short dx; - short dz; + short dx, dz; }; +// Fast Marching method ushort distanceCache[16384]; char omap[128][16]; // obstacle map 128x128 (bit field) int dunyet[32][2]; // scanned cell map (32x32, multi-level bitfield) @@ -146,7 +148,7 @@ void DebugDisplayObstacleMap() n.vx = player[0].pos[0]; n.vz = player[0].pos[2]; n.vy = pos_y; - n.vy = MapHeight((VECTOR*)&n); + //n.vy = MapHeight((VECTOR*)&n); for (int i = 0; i < 128; i++) { @@ -178,7 +180,7 @@ void DebugDisplayObstacleMap() n.vx = px << 8; n.vz = pz << 8; - n.vy = MapHeight((VECTOR*)&n); + n.vy = 0;// MapHeight((VECTOR*)&n); int dist = distanceCache[(n.vx >> 2 & 0x3f80U | n.vz >> 9 & 0x7fU) ^ (n.vy & 1U) * 0x2040 ^ (n.vy & 2U) << 0xc];// distanceCache[((pos_x+i & 127) * 128) + (j + pos_z & 127)]; @@ -268,9 +270,10 @@ void WunCell(VECTOR* pbase) int height1; int i, j; - // [A] hack with height map (fixes some bits in Havana) - height1 = MapHeight(pbase); - +#if ENABLE_GAME_FIXES + // start with the base (player) height + height1 = pbase->vy; + pbase->vx += 512; pbase->vz += 512; @@ -279,7 +282,9 @@ void WunCell(VECTOR* pbase) pbase->vx -= 512; pbase->vz -= 512; - if (height1 - v[0].vy > 100) + // if base height differs from map height too much (we are on bridge etc) + // we then use base height to ensure that obstacles are locally correct + if (ABS(height1 - v[0].vy) > 100) v[0].vy = height1; v[0].vy += 32; @@ -287,7 +292,6 @@ void WunCell(VECTOR* pbase) // [A] definitely better code // new 16 vs old 12 passes but map is not leaky at all - for (i = 0; i < 4; i++) { for (j = 0; j < 4; j++) @@ -298,6 +302,45 @@ void WunCell(VECTOR* pbase) OMapSet(v[0].vx >> 8, v[0].vz >> 8, CellAtPositionEmpty(&v[0], 128) == 0); } } +#else + pbase->vx = pbase->vx + 512; + pbase->vz = pbase->vz + 512; + + height1 = MapHeight(pbase); + + v[0].vy = height1 + 60; + + pbase->vx = pbase->vx - 512; + pbase->vz = pbase->vz - 512; + v[1].vy = v[0].vy; + + for (i = 0; i < 2; i++) + { + if (i != 0) + pbase->vx += 512; + + v[0].vx = pbase->vx; + v[0].vz = pbase->vz; + + for (j = 0; j < 6; j++) + { + int dx, dz; + v[0].vx = pbase->vx + ends[j][0].dx; + v[0].vz = pbase->vz + ends[j][0].dz; + + v[1].vx = pbase->vx + ends[j][1].dx; + v[1].vz = pbase->vz + ends[j][1].dz; + + dx = v[0].vx + v[1].vx >> 1; + dz = v[0].vz + v[1].vz >> 1; + + OMapSet(dx >> 8, dz >> 8, lineClear(&v[0], &v[1]) == 0); + } + + if (i != 0) + pbase->vx -= 512; + } +#endif } // [A] function that invalidates map at ends @@ -308,8 +351,8 @@ void InvalidateMapEnds() int tile; int i; XZPAIR pos; - pos.x = (player[0].pos[0] & 0xfffffc00) >> 10; - pos.z = (player[0].pos[2] & 0xfffffc00) >> 10; + pos.x = (player[0].pos[0] & ~1023) >> 10; + pos.z = (player[0].pos[2] & ~1023) >> 10; for(i = 0; i < 32; i++) { @@ -331,8 +374,7 @@ void InvalidateMapEnds() void InvalidateMap(void) { int dir; - int p; - int q; + int p, q; int count; int px, pz; VECTOR bPos; @@ -342,11 +384,10 @@ void InvalidateMap(void) p = 0; dir = 0; - bPos.vx = player[0].pos[0] & 0xfffffc00; - bPos.vz = player[0].pos[2] & 0xfffffc00; + bPos.vx = player[0].pos[0] & ~1023; + bPos.vz = player[0].pos[2] & ~1023; - count = 0; - do + for(count = 0; count < 1024; count++) { px = bPos.vx >> 10; pz = bPos.vz >> 10; @@ -358,39 +399,25 @@ void InvalidateMap(void) if (dir == 0) { - p++; bPos.vx += MAP_CELL_SIZE / 2; - - if (p + q == 1) - dir = 1; + dir = (++p + q == 1) ? 1 : dir; } else if (dir == 1) { - q++; bPos.vz += MAP_CELL_SIZE / 2; - - if (p == q) - dir = 2; + dir = (p == ++q) ? 2 : dir; } else if (dir == 2) { - p--; bPos.vx -= MAP_CELL_SIZE / 2; - - if (p + q == 0) - dir = 3; + dir = (--p + q == 0) ? 3 : dir; } else { - q--; bPos.vz -= MAP_CELL_SIZE / 2; - - if (p == q) - dir = 0; + dir = (p == --q) ? 0 : dir; } - - count++; - }while (count < 1024); + } } @@ -400,40 +427,39 @@ u_int cellsPerFrame = 4; // [D] [T] void BloodyHell(void) { + VECTOR bPos; int dir; - int p; - int q; + int p, q; int px, pz; u_int howMany; int count; - VECTOR bPos; int tile, i; cellsThisFrame = 0; // [A] really it should be based on player's height - bPos.vy = player[0].pos[1] ; - - bPos.vx = player[0].pos[0] & 0xfffffc00; - bPos.vz = player[0].pos[2] & 0xfffffc00; + bPos.vy = player[0].pos[1]; + bPos.vx = player[0].pos[0] & ~1023; + bPos.vz = player[0].pos[2] & ~1023; howMany = cellsPerFrame; if (CameraCnt < 4) howMany = cellsPerFrame + 20; - - q = 0; if (CameraCnt < 8) howMany += 4; + q = 0; p = 0; dir = 0; - count = 0; +#if ENABLE_GAME_FIXES InvalidateMapEnds(); +#endif - do { + for (count = 0; count < 840; count++) + { if (count == 200) howMany--; @@ -446,51 +472,33 @@ void BloodyHell(void) if (i != 0) { DONEMAP_V(px, pz) = tile ^ i; - WunCell(&bPos); - cellsThisFrame++; - - if (cellsThisFrame >= howMany) + if (++cellsThisFrame >= howMany) return; } if (dir == 0) { - p++; bPos.vx += MAP_CELL_SIZE / 2; - - if (p + q == 1) - dir = 1; + dir = (++p + q == 1) ? 1 : dir; } - else if (dir == 1) + else if (dir == 1) { - q++; bPos.vz += MAP_CELL_SIZE / 2; - - if (p == q) - dir = 2; + dir = (p == ++q) ? 2 : dir; } else if (dir == 2) { - p--; - bPos.vx -= MAP_CELL_SIZE / 2; - - if (p + q == 0) - dir = 3; + dir = (--p + q == 0) ? 3 : dir; } - else + else { - q--; bPos.vz -= MAP_CELL_SIZE / 2; - - if (p == q) - dir = 0; + dir = (p == --q) ? 0 : dir; } - - count++; - } while( count < 840 ); + } } int slowWallTests = 0; @@ -503,9 +511,15 @@ int blocked(tNode* v1, tNode* v2) if (slowWallTests != 0) return lineClear((VECTOR*)v1, (VECTOR*)v2) == 0; - x = v1->vx + v1->vx >> 9; + x = v1->vx + v2->vx >> 9; z = v1->vz + v2->vz >> 9; + int prev = DONEMAP_V(x >> 2, z >> 2); + int val = DONEMAP_GETVALUE(x >> 2, z >> 2, prev, 0); + + if (val != 0) + return 1; + return OMAP_GETVALUE(x, z); } @@ -518,10 +532,10 @@ void setDistance(tNode* n, ushort dist) } // [A] -void SetNodeDistanceWithParents(tNode* startNode, ushort dist); +void pushNode(tNode* startNode, ushort dist); // [D] [T] -void iterate(void) +int iterate(void) { tNode pathNodes[8]; @@ -538,16 +552,14 @@ void iterate(void) int r; if (numHeapEntries == 0) - return; + return 0; popNode(&itHere); nbr = pathNodes; // check directions - for(dir = 0; dir < 6; dir++) + for(dir = 0; dir < 6; dir++, nbr++) { - nbr++; - nbr->vx = itHere.vx + dirs[dir].dx; nbr->vy = itHere.vy; nbr->vz = itHere.vz + dirs[dir].dz; @@ -562,39 +574,35 @@ void iterate(void) { if (ABS(nbr->vy - itHere.vy) < 201) { - if ((dist & 1) == 0) - nbr->dist = 0; - + nbr->dist = 0; continue; } } nbr->dist = 1; } - else + else if (dist <= itHere.dist - 288) { - if (dist <= itHere.dist - 288) - { - nbr->dist = 1; - } + nbr->dist = 1; } } // now we have distance let's compute the rest of the map for(dir = 0; dir < 6; dir++) { - if (pathNodes[dir + 1].dist != 0) + // visited? + if (pathNodes[dir].dist != 0) continue; if (dir != 5) - nr = pathNodes[dir + 2].dist; + nr = pathNodes[dir + 1].dist; else - nr = pathNodes[1].dist; + nr = pathNodes[0].dist; if (dir != 0) - nl = pathNodes[dir].dist; + nl = pathNodes[dir - 1].dist; else - nl = pathNodes[6].dist; + nl = pathNodes[5].dist; // uhhmm... distance function selection? if (nl < 2) @@ -604,33 +612,32 @@ void iterate(void) else dist = (nr + itHere.dist >> 1) + 221; } + else if (nr < 2) + { + dist = (nl + itHere.dist >> 1) + 221; + } else { - if (nr < 2) + r = nr - nl; + dist = 0x10000 - (r * r) / 3; + + if (dist < 0) { - dist = (nl + itHere.dist >> 1) + 221; + dist = 0; } else { - r = nr - nl; - dist = 0x10000 - (r * r) / 3; - - if (dist < 0) - { - dist = 0; - } - else - { - a = (dist >> 9) + 128; - dist = dist / a + a >> 1; - } - - dist += itHere.dist; + a = (dist >> 9) + 128; + dist = dist / a + a >> 1; } + + dist += itHere.dist; } - SetNodeDistanceWithParents(&pathNodes[dir + 1], dist); + pushNode(&pathNodes[dir], dist); } + + return numHeapEntries > 0; } // [D] [T] @@ -655,7 +662,7 @@ void InitPathFinding(void) searchTarget.vy = -12367; searchTarget.vz = 0; playerTargetDistanceSq = 0; - pathFrames = 0; + //pathFrames = 0; pathIterations = 129; } @@ -672,7 +679,7 @@ int getInterpolatedDistance(VECTOR* pos) VECTOR sp; // WHY? - n.vx = ((pos->vx + (pos->vz >> 1 & 0x1ffU)) >> 9) * 512 - ((pos->vz & 0x200) >> 1); + n.vx = ((pos->vx + (pos->vz >> 1 & 511)) >> 9) * 512 - ((pos->vz & 512) >> 1); n.vy = pos->vy; n.vz = (pos->vz >> 9) << 9; @@ -855,7 +862,7 @@ void addCivs(void) } // [A] -void SetNodeDistanceWithParents(tNode* startNode, ushort dist) +void pushNode(tNode* node, ushort dist) { int i; u_int pnode, parent; @@ -863,8 +870,9 @@ void SetNodeDistanceWithParents(tNode* startNode, ushort dist) if (numHeapEntries == 198) return; - setDistance(startNode, dist); + setDistance(node, dist); + // up heap i = numHeapEntries + 1; pnode = i; @@ -878,12 +886,12 @@ void SetNodeDistanceWithParents(tNode* startNode, ushort dist) parent >>= 1; } - heap[pnode] = *startNode; + heap[pnode] = *node; numHeapEntries++; } // [A] -void ComputeDistanceFromSearchTarget(tNode* startNode) +void pushSeedNode(tNode* startNode) { u_short dist; int i, dx, dz; @@ -896,7 +904,7 @@ void ComputeDistanceFromSearchTarget(tNode* startNode) dist = SquareRoot0( dx * dx + dz * dz ) >> 1; - SetNodeDistanceWithParents(startNode, dist); + pushNode(startNode, dist); } // [D] [T] @@ -941,14 +949,21 @@ void UpdateCopMap(void) i = 36; while (--i >= 0) - iterate(); + { + if (!iterate()) + { + pathFrames = 0; + break; + } + } DebugDisplayObstacleMap(); // remove cars addCivs(); } - else + + if(pathFrames == 0) { // restart from new search target position if (player[0].playerType == 1 && (CopsCanSeePlayer != 0 || numActiveCops == 0)) @@ -981,7 +996,7 @@ void UpdateCopMap(void) distanceCache[i] = d; } - startNode.vx = ((searchTarget.vx + (searchTarget.vz >> 1 & 0x1ffU)) >> 9) * 512 - ((searchTarget.vz & 0x200U) >> 1); + startNode.vx = ((searchTarget.vx + (searchTarget.vz >> 1 & 511)) >> 9) * 512 - ((searchTarget.vz & 512) >> 1); startNode.vz = (searchTarget.vz >> 9) << 9; startNode.vy = searchTarget.vy; @@ -1003,30 +1018,30 @@ void UpdateCopMap(void) if (dz < dx + dz / 2) { - ComputeDistanceFromSearchTarget(&startNode); + pushSeedNode(&startNode); startNode.vx += 256; startNode.vz += 512; - ComputeDistanceFromSearchTarget(&startNode); + pushSeedNode(&startNode); startNode.vx += 256; startNode.vz -= 512; - ComputeDistanceFromSearchTarget(&startNode); + pushSeedNode(&startNode); } else { - ComputeDistanceFromSearchTarget(&startNode); + pushSeedNode(&startNode); startNode.vx += 256; startNode.vz += 512; - ComputeDistanceFromSearchTarget(&startNode); + pushSeedNode(&startNode); startNode.vx -= 512; - ComputeDistanceFromSearchTarget(&startNode); + pushSeedNode(&startNode); } } diff --git a/src_rebuild/Game/C/pause.c b/src_rebuild/Game/C/pause.c index 5e40c5ac0..9242a33e6 100644 --- a/src_rebuild/Game/C/pause.c +++ b/src_rebuild/Game/C/pause.c @@ -188,32 +188,32 @@ extern void LoadSky(void); void DebugTimeOfDayDay(int direction) { - wantedTimeOfDay = 1; - gTimeOfDay = 1; + wantedTimeOfDay = TIME_DAY; + gTimeOfDay = TIME_DAY; gWantNight = 0; LoadSky(); } void DebugTimeOfDayNight(int direction) { - wantedTimeOfDay = 3; - gTimeOfDay = 3; + wantedTimeOfDay = TIME_NIGHT; + gTimeOfDay = TIME_NIGHT; gWantNight = 1; LoadSky(); } void DebugTimeOfDayDusk(int direction) { - wantedTimeOfDay = 0; - gTimeOfDay = 0; + wantedTimeOfDay = TIME_DUSK; + gTimeOfDay = TIME_DUSK; gWantNight = 0; LoadSky(); } void DebugTimeOfDayDawn(int direction) { - wantedTimeOfDay = 2; - gTimeOfDay = 2; + wantedTimeOfDay = TIME_DAWN; + gTimeOfDay = TIME_DAWN; gWantNight = 0; LoadSky(); } @@ -225,7 +225,7 @@ void DebugTimeOfDayRain(int direction) gWeather ^= 1; wantedWeather = gWeather; - if (gWeather == 1) + if (gWeather == WEATHER_RAIN) wetness = 7000; else wetness = 0; @@ -259,7 +259,6 @@ MENU_HEADER DebugJustForFunHeader = MENU_ITEM DebugOptionsItems[] = { #ifdef CUTSCENE_RECORDER - //{ gCutsceneRecorderPauseText, 5u, 2u, (pauseFunc)&NextCutsceneRecorderPlayer, MENU_QUIT_NONE, NULL }, { gCurrentChasePauseText, 5u, 2u, (pauseFunc)&CutRec_NextChase, MENU_QUIT_NONE, NULL }, #endif { "Display position", PAUSE_TYPE_FUNC, 2, SetDisplayPosition, MENU_QUIT_NONE, NULL}, @@ -1062,7 +1061,7 @@ void DrawVisibleMenus(void) if (NumPlayers > 1) { - SetFullscreenDrawing(); + SetFullscreenDrawing(2); } pActive = VisibleMenus[VisibleMenu]; diff --git a/src_rebuild/Game/C/pedest.c b/src_rebuild/Game/C/pedest.c index 61ddf2e3e..2160b00dd 100644 --- a/src_rebuild/Game/C/pedest.c +++ b/src_rebuild/Game/C/pedest.c @@ -24,6 +24,7 @@ #include "map.h" #include "system.h" #include "handling.h" +#include "draw.h" #include "ASM/rndrasm.h" @@ -1433,7 +1434,7 @@ void PingInPedestrians(void) baseLoc.vy = player[0].pos[1]; baseLoc.vz = player[0].pos[2]; - if (gWeather == 0 && FindSeated() != NULL) + if (gWeather == WEATHER_NONE && FindSeated() != NULL) return; for (i = 0; i < 50; i++) @@ -2085,8 +2086,8 @@ int CalcPedestrianDirection(int last_dir, int wx, int wz, VECTOR* target) } } - rx = wx & 0xfffffc00; - rz = wz & 0xfffffc00; + rx = wx & ~1023; + rz = wz & ~1023; if (num == 1) { @@ -2553,7 +2554,7 @@ void IHaveThePower(void) { oldWeather = gWeather; bPower = 1; - gWeather = 1; + gWeather = WEATHER_RAIN; } powerCounter++; @@ -2587,12 +2588,11 @@ void IHaveThePower(void) // [D] [T] void ProcessTannerPad(LPPEDESTRIAN pPed, u_int pad, char PadSteer, char use_analogue) { + int mapheight[2]; + sdPlane* SurfacePtr; - int direction; - VECTOR vec; - VECTOR normal; - VECTOR out; - VECTOR tVec; + int direction, diff; + VECTOR vec, normal, out, tVec; sdPlane* plane; PLAYER* lcp; @@ -2631,23 +2631,21 @@ void ProcessTannerPad(LPPEDESTRIAN pPed, u_int pad, char PadSteer, char use_anal bStopTanner = 0; - int mapheight[2]; - mapheight[0] = -130 - MapHeight(&vec); mapheight[1] = -130 - MapHeight(&tVec); - int dist = ABS(mapheight[1] - mapheight[0]); + diff = ABS(mapheight[1] - mapheight[0]); // check slope - if (dist <= 1010) + if (diff <= 1010) { SurfacePtr = sdGetCell(&tVec); if (SurfacePtr != NULL) { - dist = ABS(((SurfacePtr->b >> 2) - 2048 & 0xfff) - 2048); + diff = ABS(DIFF_ANGLES((SurfacePtr->b >> 2), 0)); //ABS(((SurfacePtr->b >> 2) - 2048 & 0xfff) - 2048); - if (dist <= 1100) + if (diff <= 1100) { switch (SurfacePtr->surface) { @@ -2656,7 +2654,7 @@ void ProcessTannerPad(LPPEDESTRIAN pPed, u_int pad, char PadSteer, char use_anal //case 6: // water. We allow to walk on water in Rio a little bit. Then he drowns case 9: // water with fade out default: - dist = -1; + diff = -1; break; } } @@ -2664,7 +2662,7 @@ void ProcessTannerPad(LPPEDESTRIAN pPed, u_int pad, char PadSteer, char use_anal } // can't walk in water - if (dist != -1) + if (diff != -1) bStopTanner = 1; if (pPed->type != PED_ACTION_SIT && !bStopTanner) @@ -2765,10 +2763,11 @@ void ProcessTannerPad(LPPEDESTRIAN pPed, u_int pad, char PadSteer, char use_anal { FindSurfaceD2((VECTOR*)lcp->pos, &normal, &out, &plane); - if (plane->surface != -1 && plane->surface < 32 && (plane->surface & 0x10)) + if (plane && plane->surface != -1 && plane->surface < 32 && (plane->surface & 16)) { pPed->position.vx += (normal.vx >> 6); pPed->position.vz += (normal.vz >> 6); + pPed->position.vy = mapheight[0]; } } } diff --git a/src_rebuild/Game/C/players.c b/src_rebuild/Game/C/players.c index 18111b519..1ef118c0f 100644 --- a/src_rebuild/Game/C/players.c +++ b/src_rebuild/Game/C/players.c @@ -148,11 +148,13 @@ void ChangeCarPlayerToPed(int playerID) if (CarHasSiren(lcp->ap.model)) locPlayer->horn.on = 0; +#if ENABLE_MISSION_FIXES // [A] carry over felony from car to Tanner if cops see player // don't clear player felony in Destroy the Yard if (CopsCanSeePlayer || gCurrentMissionNumber == 30) pedestrianFelony = lcp->felonyRating; else +#endif pedestrianFelony = 0; } @@ -207,10 +209,12 @@ void ChangePedPlayerToCar(int playerID, CAR_DATA *newCar) newCar->ai.padid = &lPlayer->padid; newCar->hndType = 0; - if (playerID == 0 && - !(newCar->controlFlags & CONTROL_FLAG_PLAYER_START_CAR)) // [A] bug fix: don't give felony if player owns his cop car + if (playerID == 0 +#if ENABLE_GAME_FIXES + && !(newCar->controlFlags & CONTROL_FLAG_PLAYER_START_CAR) // [A] bug fix: don't give felony if player owns his cop car +#endif + ) { - // [A] Rev 1.1 removes felony override for "Steal the cop car" if (gCurrentMissionNumber != 32 && MissionHeader->residentModels[newCar->ap.model] == 0) { NoteFelony(&felonyData, 11, 4096); @@ -230,12 +234,14 @@ void ChangePedPlayerToCar(int playerID, CAR_DATA *newCar) HaveCarSoundStraightAway(playerID); // [A] carry over felony from Tanner to car if cops see player. Force in Destroy the yard +#if ENABLE_MISSION_FIXES if (CopsCanSeePlayer || gCurrentMissionNumber == 30) { if (newCar->felonyRating < pedestrianFelony) newCar->felonyRating = pedestrianFelony; } else +#endif pedestrianFelony = 0; } diff --git a/src_rebuild/Game/C/pres.c b/src_rebuild/Game/C/pres.c index 928fed71c..d266cc840 100644 --- a/src_rebuild/Game/C/pres.c +++ b/src_rebuild/Game/C/pres.c @@ -3,6 +3,318 @@ #include "system.h" #include "texture.h" +extern int gShowMap; + +#ifndef PSX + +#include "PsyX/PsyX_render.h" +#include "../utils/targa.h" +#include "../utils/hqfont.h" + +#define HIRES_FONTS + +struct FONT_QUAD +{ + float x0, y0, s0, t0; // top-left + float x1, y1, s1, t1; // bottom-right +}; + +TextureID gHiresFontTexture = 0; +TextureID gHiresDigitsTexture = 0; + +OUT_FN2RANGE gHiresFontRanges[4]; +OUT_FN2INFO gHiresFontCharData[4][224]; +int gHiresFontRangeCount = 0; + +void InitHiresFonts() +{ + char namebuffer[64]; + u_char* data; + + // init digits + if(!gHiresDigitsTexture) + { + int width, height, bpp; + + sprintf(namebuffer, "%s%s", gDataFolder, "GFX\\HQ\\digits.tga"); + FS_FixPathSlashes(namebuffer); + + if (LoadTGAImage(namebuffer, &data, width, height, bpp)) + { + if (bpp == 32) + { + gHiresDigitsTexture = GR_CreateRGBATexture(width, height, data); + } + free(data); + data = NULL; + } + } + + // init font2 + if(!gHiresFontTexture) + { + gHiresFontRangeCount = 0; + + int width, height, bpp; + int x, y; + int size; + FILE* fp; + sprintf(namebuffer, "%s%s", gDataFolder, "GFX\\HQ\\font2.fn2"); + FS_FixPathSlashes(namebuffer); + + fp = fopen(namebuffer, "rb"); + if (fp) + { + int i; + + // read fn2 step by step + OUT_FN2HEADER fn2hdr; + fread(&fn2hdr, sizeof(fn2hdr), 1, fp); + + gHiresFontRangeCount = fn2hdr.range_count; + for (i = 0; i < fn2hdr.range_count; ++i) + { + fread(&gHiresFontRanges[i], sizeof(gHiresFontRanges[i]), 1, fp); + fread(gHiresFontCharData[i], sizeof(OUT_FN2INFO), gHiresFontRanges[i].count, fp); + } + + fclose(fp); + } + + // load TGA file + sprintf(namebuffer, "%s%s", gDataFolder, "GFX\\HQ\\font2.tga"); + FS_FixPathSlashes(namebuffer); + + if (LoadTGAImage(namebuffer, &data, width, height, bpp)) + { + if (bpp == 32) + { + gHiresFontTexture = GR_CreateRGBATexture(HIRES_FONT_SIZE_W, HIRES_FONT_SIZE_H, data); + } + free(data); + } + } +} + +void SetHiresFontTexture(int enabled) +{ + if (gHiresFontTexture == 0) + { + return; + } + + DR_PSYX_TEX* tex = (DR_PSYX_TEX*)current->primptr; + if (enabled) + SetPsyXTexture(tex, gHiresFontTexture, 255, 255); + else + SetPsyXTexture(tex, 0, 0, 0); + + if (gShowMap == 0) + { + addPrim(current->ot, tex); + current->primptr += sizeof(DR_PSYX_TEX); + } + else + { + DrawPrim(tex); + } +} + +void SetHiresDigitsTexture(int enabled) +{ + if (gHiresDigitsTexture == 0) + { + return; + } + + DR_PSYX_TEX* tex = (DR_PSYX_TEX*)current->primptr; + if (enabled) + SetPsyXTexture(tex, gHiresDigitsTexture, 96, 59); + else + SetPsyXTexture(tex, 0, 0, 0); + + addPrim(current->ot, tex); + current->primptr += sizeof(DR_PSYX_TEX); +} + +void GetHiresBakedQuad(int char_index, float* xpos, float* ypos, FONT_QUAD* q) +{ + float ipw = 1.0f / (float)HIRES_FONT_SIZE_W; + float iph = 1.0f / (float)HIRES_FONT_SIZE_H; + + const OUT_FN2INFO* b = gHiresFontCharData[0] + char_index - gHiresFontRanges[0].start; + + float scale = 0.275f; + + float s_x = b->x1 - b->x0; + float s_y = b->y1 - b->y0; + + q->x0 = *xpos + b->xoff * scale; + q->y0 = *ypos + b->yoff * scale; + q->x1 = (b->xoff2 - b->xoff) * scale; + q->y1 = (b->yoff2 - b->yoff) * scale; + + q->s0 = b->x0 * 255.0f * ipw; + q->t0 = b->y0 * 255.0f * iph; + q->s1 = s_x * 255.0f * ipw; + q->t1 = s_y * 255.0f * iph; + + q->y0 += 14.0f; + + *xpos += b->xadvance * scale; +} + +int StrighWidthHires(char* string) +{ + u_char chr; + float width; + width = 0; + + while ((chr = *string++) != 0) + { + if (chr >= 128 && chr <= 138) + { + width += 24; + continue; + } + + chr = (chr >= 32 && chr < 128 || chr > 138) ? chr : '?'; + + float fx, fy; + fx = 0.0f; + fy = 0.0f; + FONT_QUAD q; + GetHiresBakedQuad(chr, &fx, &fy, &q); + width += fx; + } + return width; +} + +extern CVECTOR gFontColour; +extern short fontclutid; +extern short fonttpage; + +int PrintStringHires(char* string, int x, int y) +{ + u_char chr; + float width; + u_int index; + int showMap; + + showMap = gShowMap; + width = x; + + SetHiresFontTexture(showMap); + + while ((chr = *string++) != 0) + { + if (chr >= 128 && chr <= 138) + { + if(showMap) + SetHiresFontTexture(0); + else + SetHiresFontTexture(1); + + current->primptr = (char*)DrawButton(chr, current->primptr, width, y); + + if (showMap) + SetHiresFontTexture(1); + else + SetHiresFontTexture(0); + + width += 24; + x += 24; + continue; + } + + chr = (chr >= 32 && chr < 128 || chr > 138) ? chr : '?'; + + POLY_FT4* fontFT4; + POLY_FT4* shadowFT4; + float fx, fy; + fx = width; + fy = y; + FONT_QUAD q; + GetHiresBakedQuad(chr, &fx, &fy, &q); + + fontFT4 = (POLY_FT4*)current->primptr; + + setPolyFT4(fontFT4); + setSemiTrans(fontFT4, 1); + + setUVWH(fontFT4, q.s0, q.t0, q.s1, q.t1); + + fontFT4->clut = fontclutid; + fontFT4->tpage = fonttpage; + + if (showMap == 0) + { + setRGB0(fontFT4, gFontColour.r, gFontColour.g, gFontColour.b); + setXYWH(fontFT4, q.x0, q.y0, q.x1, q.y1); + + addPrim(current->ot, fontFT4); + current->primptr += sizeof(POLY_FT4); + + shadowFT4 = (POLY_FT4*)current->primptr; + + // add shadow poly + memcpy(shadowFT4, fontFT4, sizeof(POLY_FT4)); + setRGB0(shadowFT4, 0, 0, 0); + setXYWH(shadowFT4, q.x0 + 0.5f, q.y0 + 0.5f, q.x1, q.y1); + + addPrim(current->ot, shadowFT4); + current->primptr += sizeof(POLY_FT4); + } + else + { + setRGB0(fontFT4, 0, 0, 0); + setXYWH(fontFT4, q.x0 + 0.5f, q.y0 + 0.5f, q.x1, q.y1); + DrawPrim(fontFT4); + + setRGB0(fontFT4, gFontColour.r, gFontColour.g, gFontColour.b); + setXYWH(fontFT4, q.x0, q.y0, q.x1, q.y1); + DrawPrim(fontFT4); + } + + width += fx - width; + } + + SetHiresFontTexture(showMap == 0); + if(showMap) + DrawSync(0); + + return width; +} + +void PrintStringBoxedHires(char* string, int ix, int iy) +{ + char word[32]; + int x, y; + int wordcount; + + wordcount = 1; + + x = ix; + y = iy; + + while (*string) + { + string = GetNextWord(string, word); + + if (x + StringWidth(word) > 308 && (wordcount != 1 || *string != 0)) + { + x = ix; + y += 14; + } + + x = PrintStringHires(word, x, y); + + wordcount++; + } +} + +#endif + extern TEXTURE_DETAILS digit_texture; struct FONT_DIGIT @@ -61,6 +373,13 @@ void SetTextColour(u_char Red, u_char Green, u_char Blue) // [D] [T] int StringWidth(char *pString) { +#ifdef HIRES_FONTS + if (gHiresFontTexture) + { + return StrighWidthHires(pString); + } +#endif + u_char let; int w; @@ -166,14 +485,18 @@ void LoadFont(char *buffer) fonttpage = GetTPage(0,0, dest.x, dest.y); - LoadImage(&fontclutpos, (u_long *)clut); // upload clut - LoadImage(&dest, (u_long *)(file + 32)); // upload font image + LoadImage(&fontclutpos, (u_long*)clut); // upload clut + LoadImage(&dest, (u_long*)(file + 32)); // upload font image + +#ifdef HIRES_FONTS + InitHiresFonts(); +#endif DrawSync(0); } // [D] [T] -void StoreClut2(u_long *pDest, int x, int y) +void StoreClut2(u_long* pDest, int x, int y) { RECT16 rect; @@ -196,7 +519,7 @@ void SetCLUT16Flags(ushort clutID, ushort mask, char transparent) x = (clutID & 63) * 16; y = (clutID >> 6); - StoreClut2((ulong *)buffer, x, y); + StoreClut2((u_long*)buffer, x, y); pCurrent = buffer; ctr = 1; @@ -217,9 +540,6 @@ void SetCLUT16Flags(ushort clutID, ushort mask, char transparent) LoadClut2((u_long*)buffer, x,y); } -// MAP.C ???? -extern int gShowMap; - // [D] [T] int PrintString(char *string, int x, int y) { @@ -233,10 +553,19 @@ int PrintString(char *string, int x, int y) if (current == NULL) return -1; +#ifdef HIRES_FONTS + if (gHiresFontTexture) + { + return PrintStringHires(string, x, y); + } +#endif + font = (SPRT *)current->primptr; if (showMap != 0) - font = (SPRT *)SetFontTPage(font); + { + font = (SPRT*)SetFontTPage(font); + } width = x; @@ -247,7 +576,7 @@ int PrintString(char *string, int x, int y) width += 4; continue; } - + if (chr < 32 || chr > 138 || chr < 128) { if (AsciiTable[chr] == -1) @@ -287,18 +616,24 @@ int PrintString(char *string, int x, int y) else { if (showMap == 0) - font = (SPRT *)SetFontTPage(font); + { + font = (SPRT*)SetFontTPage(font); + } font = (SPRT *)DrawButton(chr, font, width, y); width += 24; if (showMap != 0) - font = (SPRT *)SetFontTPage(font); + { + font = (SPRT*)SetFontTPage(font); + } } } if (showMap == 0) - current->primptr = (char *)SetFontTPage(font); + { + current->primptr = (char*)SetFontTPage(font); + } else DrawSync(0); @@ -316,6 +651,10 @@ short PrintDigit(int x, int y, char *string) int fixedWidth; char vOff, h; +#ifdef HIRES_FONTS + SetHiresDigitsTexture(0); +#endif + width = x; font = (SPRT *)current->primptr; @@ -356,8 +695,18 @@ short PrintDigit(int x, int y, char *string) font->x0 = width + (fixedWidth - pDigit->width) / 2; font->y0 = y; - font->u0 = digit_texture.coords.u0 + pDigit->xOffset; - font->v0 = vOff + digit_texture.coords.v0; +#ifdef HIRES_FONTS + if (gHiresDigitsTexture) + { + font->u0 = pDigit->xOffset; + font->v0 = vOff; + } + else +#endif + { + font->u0 = digit_texture.coords.u0 + pDigit->xOffset; + font->v0 = digit_texture.coords.v0 + vOff; + } font->w = pDigit->width; font->h = h; @@ -388,6 +737,10 @@ short PrintDigit(int x, int y, char *string) addPrim(current->ot, null); current->primptr += sizeof(POLY_FT3); +#ifdef HIRES_FONTS + SetHiresDigitsTexture(1); +#endif + return width; } @@ -408,6 +761,14 @@ void PrintStringBoxed(char *string, int ix, int iy) int index; int wordcount; +#ifdef HIRES_FONTS + if (gHiresFontTexture) + { + PrintStringBoxedHires(string, ix, iy); + return; + } +#endif + font = (SPRT *)current->primptr; wordcount = 1; @@ -508,10 +869,16 @@ int PrintScaledString(int y, char *string, int scale) int height; u_char vOff; +#ifdef HIRES_FONTS + SetHiresDigitsTexture(0); +#endif + font = (POLY_FT4 *)current->primptr; if (gShowMap != 0) - font = (POLY_FT4 *)SetFontTPage(font); + { + font = (POLY_FT4*)SetFontTPage(font); + } width = StringWidth(string) * scale; x = (320 - (width / 16)) / 2; @@ -548,6 +915,7 @@ int PrintScaledString(int y, char *string, int scale) x1 = x + width; setPolyFT4(font); + setSemiTrans(font, 1); setRGB0(font, gFontColour.r, gFontColour.g, gFontColour.b); font->x0 = x; // [A] no suitable macro in libgpu @@ -559,7 +927,16 @@ int PrintScaledString(int y, char *string, int scale) font->x3 = x1; font->y3 = y1; - setUVWH(font, digit_texture.coords.u0 + pDigit->xOffset, digit_texture.coords.v0 + vOff, pDigit->width, height); +#ifdef HIRES_FONTS + if (gHiresDigitsTexture) + { + setUVWH(font, pDigit->xOffset, vOff, pDigit->width, height); + } + else +#endif + { + setUVWH(font, digit_texture.coords.u0 + pDigit->xOffset, digit_texture.coords.v0 + vOff, pDigit->width, height); + } font->clut = digit_texture.clutid; font->tpage = digit_texture.tpageid; @@ -574,6 +951,10 @@ int PrintScaledString(int y, char *string, int scale) current->primptr = (char*)font; +#ifdef HIRES_FONTS + SetHiresDigitsTexture(1); +#endif + return x; } @@ -605,7 +986,7 @@ void* DrawButton(u_char button, void *prim, int x, int y) SPRT* sprt; POLY_FT3* null; - btn = &button_textures[button - 0x80]; + btn = &button_textures[button - 128]; sprt = (SPRT*)prim; setSprt(sprt); diff --git a/src_rebuild/Game/C/pres.h b/src_rebuild/Game/C/pres.h index 821814c09..4c95890f6 100644 --- a/src_rebuild/Game/C/pres.h +++ b/src_rebuild/Game/C/pres.h @@ -3,12 +3,9 @@ struct OUT_FONTINFO { - u_char x; - u_char y; - char offx; - char offy; - u_char width; - u_char height; + u_char x, y; + char offx, offy; + u_char width, height; u_short pad; }; diff --git a/src_rebuild/Game/C/replays.c b/src_rebuild/Game/C/replays.c index 945882825..b69a720c8 100644 --- a/src_rebuild/Game/C/replays.c +++ b/src_rebuild/Game/C/replays.c @@ -135,7 +135,7 @@ int SaveReplayToBuffer(char *buffer) header->wantedCar[0] = wantedCar[0]; header->wantedCar[1] = wantedCar[1]; - memcpy((u_char*)&header->SavedData, (u_char*)&MissionEndData, sizeof(MISSION_DATA)); + header->SavedData = MissionEndData; // write each stream data for (int i = 0; i < NumPlayers; i++) @@ -146,7 +146,7 @@ int SaveReplayToBuffer(char *buffer) REPLAY_STREAM* srcStream = &ReplayStreams[i]; // copy source type - memcpy((u_char*)&sheader->SourceType, (u_char*)&srcStream->SourceType, sizeof(STREAM_SOURCE)); + sheader->SourceType = srcStream->SourceType; sheader->Size = srcStream->padCount * sizeof(PADRECORD); // srcStream->PadRecordBufferEnd - srcStream->InitialPadRecordBuffer; sheader->Length = srcStream->length; @@ -214,7 +214,7 @@ int LoadReplayFromBuffer(char *buffer) wantedCar[0] = header->wantedCar[0]; wantedCar[1] = header->wantedCar[1]; - memcpy((u_char*)&MissionEndData, (u_char*)&header->SavedData, sizeof(MISSION_DATA)); + MissionEndData = header->SavedData; pt = (char*)(header+1); @@ -227,7 +227,7 @@ int LoadReplayFromBuffer(char *buffer) REPLAY_STREAM* destStream = &ReplayStreams[i]; // copy source type - memcpy((u_char*)&destStream->SourceType, (u_char*)&sheader->SourceType, sizeof(STREAM_SOURCE)); + destStream->SourceType = sheader->SourceType; int size = (sheader->Size + sizeof(PADRECORD)) & -4; @@ -278,50 +278,11 @@ int LoadReplayFromBuffer(char *buffer) return 1; } -#ifndef PSX -int LoadUserAttractReplay(int mission, int userId) -{ - char customFilename[64]; - - if (userId >= 0 && userId < gNumUserChases) - { - sprintf(customFilename, "REPLAYS\\User\\%s\\ATTRACT.%d", gUserReplayFolderList[userId], mission); - - if (FileExists(customFilename)) - { - if (Loadfile(customFilename, (char*)_other_buffer)) - return LoadReplayFromBuffer((char*)_other_buffer); - } - } - - return 0; -} -#endif - // [D] [T] int LoadAttractReplay(int mission) { char filename[32]; -#ifndef PSX - int userId = -1; - - // [A] REDRIVER2 PC - custom attract replays - if (gNumUserChases) - { - userId = rand() % (gNumUserChases + 1); - - if (userId == gNumUserChases) - userId = -1; - } - - if (LoadUserAttractReplay(mission, userId)) - { - printInfo("Loaded custom attract replay (%d) by %s\n", mission, gUserReplayFolderList[userId]); - return 1; - } -#endif - sprintf(filename, "REPLAYS\\ATTRACT.%d", mission); if (!FileExists(filename)) diff --git a/src_rebuild/Game/C/shadow.c b/src_rebuild/Game/C/shadow.c index 6d3aee030..66336ec0f 100644 --- a/src_rebuild/Game/C/shadow.c +++ b/src_rebuild/Game/C/shadow.c @@ -66,7 +66,7 @@ void ResetTyreTracks(CAR_DATA* cp, int player_id) // [D] [T] void GetTyreTrackPositions(CAR_DATA *cp, int player_id) { - int loop, track, steps; + int loop, track; CAR_COSMETICS *car_cos; VECTOR WheelPos; VECTOR CarPos; @@ -78,9 +78,8 @@ void GetTyreTrackPositions(CAR_DATA *cp, int player_id) car_cos = cp->ap.carCos; SetRotMatrix(&cp->hd.where); - steps = 4 / MAX_TYRE_TRACK_WHEELS; - - for (loop = 0; loop < 4; loop += steps) + track = 0; + for (loop = (2 / MAX_TYRE_TRACK_WHEELS); loop < 4; loop += (4 / MAX_TYRE_TRACK_WHEELS), track++) { WheelPos.vx = car_cos->wheelDisp[loop].vx; if (loop & 2) @@ -89,7 +88,7 @@ void GetTyreTrackPositions(CAR_DATA *cp, int player_id) WheelPos.vx -= 17; WheelPos.vy = 0; - WheelPos.vz = car_cos->wheelDisp[loop + 1 & 3].vz; + WheelPos.vz = car_cos->wheelDisp[loop].vz; _MatrixRotate(&WheelPos); @@ -97,8 +96,6 @@ void GetTyreTrackPositions(CAR_DATA *cp, int player_id) WheelPos.vy = CarPos.vy; WheelPos.vz += CarPos.vz; - track = loop / steps; - tyre_new_positions[player_id][track].vx = WheelPos.vx; tyre_new_positions[player_id][track].vz = WheelPos.vz; tyre_new_positions[player_id][track].vy = MapHeight(&WheelPos); @@ -442,7 +439,7 @@ void InitShadow(void) } // [D] [A] - this is a fuckery -void SubdivShadow(long z0, long z1, long z2, long z3, POLY_FT4 *sps) +void SubdivShadow(int z0, int z1, int z2, int z3, POLY_FT4 *sps) { // [A] we already have better car shadow code. This is UNUSED anyway POLY_FT4 *spd; @@ -493,8 +490,8 @@ void PlaceShadowForCar(VECTOR *shadowPoints, int subdiv, int zOfs, int flag) POLYFT4* pft4 = &shadowpoly; - plotContext.clut = (u_int)(*plotContext.ptexture_cluts)[pft4->texture_set][pft4->texture_id] << 0x10; - plotContext.tpage = ((u_int)(*plotContext.ptexture_pages)[pft4->texture_set] | 0x40) << 0x10; + plotContext.clut = (u_int)(*plotContext.ptexture_cluts)[pft4->texture_set][pft4->texture_id]; + plotContext.tpage = ((u_int)(*plotContext.ptexture_pages)[pft4->texture_set] | 0x40); copyVector(&subdivVerts[0][0], &points[pft4->v0]); subdivVerts[0][0].uv.val = *(ushort*)&pft4->uv0; @@ -656,7 +653,7 @@ void clippedPoly(void) int iVar6; SVECTOR *pSVar7; short *psVar8; - ulong *in_a1; + uint *in_a1; short *psVar9; int iVar10; int iVar11; @@ -665,7 +662,7 @@ void clippedPoly(void) pPVar4 = spolys; iVar11 = numcv + -1; if (iVar11 != -1) { - in_a1 = (ulong *)0xffffffff; + in_a1 = (uint *)0xffffffff; psVar8 = &(&cv)[lastcv].vz; do { iVar11 = iVar11 + -1; @@ -677,7 +674,7 @@ void clippedPoly(void) if (2 < numcv) { iVar11 = numcv + -1; if (numcv != 0) { - in_a1 = (ulong *)0xffffffff; + in_a1 = (uint *)0xffffffff; pSVar7 = &cv + lastcv; do { iVar11 = iVar11 + -1; @@ -689,7 +686,7 @@ void clippedPoly(void) if (2 < numcv) { iVar11 = numcv + -1; if (numcv != 0) { - in_a1 = (ulong *)&(&cv)[lastcv].vy; + in_a1 = (uint *)&(&cv)[lastcv].vy; do { iVar11 = iVar11 + -1; *(short *)((int)in_a1 + 2) = @@ -701,7 +698,7 @@ void clippedPoly(void) if (2 < numcv) { iVar11 = numcv + -1; if (numcv != 0) { - in_a1 = (ulong *)0xffffffff; + in_a1 = (uint *)0xffffffff; psVar8 = &(&cv)[lastcv].vy; do { iVar11 = iVar11 + -1; @@ -713,7 +710,7 @@ void clippedPoly(void) if (2 < numcv) { iVar11 = numcv + -1; if (numcv != 0) { - in_a1 = (ulong *)0xffffffff; + in_a1 = (uint *)0xffffffff; psVar8 = &(&cv)[lastcv].vy; do { iVar11 = iVar11 + -1; diff --git a/src_rebuild/Game/C/shadow.h b/src_rebuild/Game/C/shadow.h index 629a6338e..e0cbf4cb9 100644 --- a/src_rebuild/Game/C/shadow.h +++ b/src_rebuild/Game/C/shadow.h @@ -16,7 +16,7 @@ extern void AddTyreTrack(int wheel, int tracksAndSmoke, int player_id, int conti extern void DrawTyreTracks(); // 0x000759E0 -extern void SubdivShadow(long z0, long z1, long z2, long z3, POLY_FT4 *sps); // 0x00076108 +extern void SubdivShadow(int z0, int z1, int z2, int z3, POLY_FT4 *sps); // 0x00076108 extern void PlaceShadowForCar(VECTOR *shadowPoints, int subdiv, int zOfs, int flag); // 0x000766CC extern void sQuad(SVECTOR *v0, SVECTOR *v1, SVECTOR *v2, SVECTOR *v3, CVECTOR* light_col, int LightSortCorrect); // 0x00077138 diff --git a/src_rebuild/Game/C/sky.c b/src_rebuild/Game/C/sky.c index 90bfd64d3..65cf854ef 100644 --- a/src_rebuild/Game/C/sky.c +++ b/src_rebuild/Game/C/sky.c @@ -8,13 +8,11 @@ #include "main.h" #include "debris.h" #include "players.h" +#include "draw.h" struct RGB16 { - short r; - short g; - short b; - short pad; + short r, g, b, pad; }; struct FLAREREC @@ -259,20 +257,20 @@ void LoadSky(void) if (gWeather - 1U < 2) { - if (gTimeOfDay == 3) + if (gTimeOfDay == TIME_NIGHT) offset = 0x10000; else offset = 0x20000; } else { - if (gTimeOfDay == 0) + if (gTimeOfDay == TIME_DAWN) offset = 0x30000; - else if (gTimeOfDay == 1) + else if (gTimeOfDay == TIME_DAY) offset = 0; - else if (gTimeOfDay == 2) + else if (gTimeOfDay == TIME_DUSK) offset = 0x40000; - else if (gTimeOfDay == 3) + else if (gTimeOfDay == TIME_NIGHT) offset = 0x10000; } @@ -287,7 +285,7 @@ void LoadSky(void) } // [D] [T] -#ifdef USE_PGXP +#if USE_PGXP void DisplaySun(DVECTORF* pos, CVECTOR* col, int flare_col) #else void DisplaySun(DVECTOR* pos, CVECTOR* col, int flare_col) @@ -395,7 +393,7 @@ void DisplaySun(DVECTOR* pos, CVECTOR* col, int flare_col) } // [D] [T] -#ifdef USE_PGXP +#if USE_PGXP void DisplayMoon(DVECTORF* pos, CVECTOR* col, int flip) #else void DisplayMoon(DVECTOR* pos, CVECTOR* col, int flip) @@ -494,7 +492,7 @@ void DrawLensFlare(void) int haze_col; -#ifdef USE_PGXP +#if USE_PGXP DVECTORF sun_pers_conv_position; #else DVECTOR sun_pers_conv_position; @@ -505,10 +503,10 @@ void DrawLensFlare(void) source = sun_source; - if (gWeather - 1U <= 1 || gTimeOfDay == 0 || gTimeOfDay == 2) + if (gWeather - 1U <= 1 || (M_BIT(gTimeOfDay) & (M_BIT(TIME_DAWN) | M_BIT(TIME_DUSK)))) return; - if (gTimeOfDay == 3) + if (gTimeOfDay == TIME_NIGHT) col.r = 128; else col.r = 254; @@ -519,7 +517,7 @@ void DrawLensFlare(void) col.b = col.r; // get the sun brightness from framebuffer copy - if (gTimeOfDay != 3 && last_attempt_failed == 0) + if (gTimeOfDay != TIME_NIGHT && last_attempt_failed == 0) { pwBuffer = buffer; StoreImage(&source, (u_long*)buffer); @@ -548,7 +546,7 @@ void DrawLensFlare(void) gte_SetRotMatrix(&inv_camera_matrix); gte_SetTransVector(&dummy); - if (gTimeOfDay == 3) + if (gTimeOfDay == TIME_NIGHT) { gte_ldv0(&moon_position[GameLevel]); } @@ -570,7 +568,7 @@ void DrawLensFlare(void) distance_to_sun = SquareRoot0(xgap * xgap + ygap * ygap); - if (gTimeOfDay == 3) + if (gTimeOfDay == TIME_NIGHT) { if (distance_to_sun < 500) { @@ -646,7 +644,7 @@ void DrawLensFlare(void) } } -#ifdef USE_PGXP +#if USE_PGXP // remap PsyX_GetPSXWidescreenMappedViewport(&viewp); sun_pers_conv_position.vx = RemapVal(sun_pers_conv_position.vx, float(viewp.x), float(viewp.w), 0.0f, 320.0f); @@ -687,8 +685,6 @@ void DrawLensFlare(void) int gTunnelNum = -1; int skyFade; -RGB16 skycolor = { 128,128,128 }; - // [D] [T] void TunnelSkyFade(void) { @@ -697,9 +693,7 @@ void TunnelSkyFade(void) VECTOR* v2; VECTOR* v1; int tun; - - if (gTunnelNum == -1) - return; + int px, pz; if (GameLevel != 3 && gTunnelNum < 3) tun = gTunnelNum; @@ -718,13 +712,16 @@ void TunnelSkyFade(void) if (DIFF_ANGLES(camera_angle.vy, tunnelDir[tun][1]) + 1247 < 2495) // (((tunnelDir[tun][1] - camera_angle.vy) + 2048 & 4095) - 801 < 2495) v2 = &tunnelPos[tun][1]; + px = player[0].pos[0]; + pz = player[0].pos[2]; + if(v1 && v2) { - dX = (v1->vx - player[0].pos[0]) >> 5; // [A] smooth sky fade - dZ = (v1->vz - player[0].pos[2]) >> 5; + dX = (v1->vx - px) >> 5; // [A] smooth sky fade + dZ = (v1->vz - pz) >> 5; - diffX = (v2->vx - player[0].pos[0]) >> 5; - diffZ = (v2->vz - player[0].pos[2]) >> 5; + diffX = (v2->vx - px) >> 5; + diffZ = (v2->vz - pz) >> 5; len = (dX * dX + dZ * dZ); l2 = (diffX * diffX + diffZ * diffZ); @@ -734,15 +731,15 @@ void TunnelSkyFade(void) } else if(v2) { - diffX = (v2->vx - player[0].pos[0]) >> 5; - diffZ = (v2->vz - player[0].pos[2]) >> 5; + diffX = (v2->vx - px) >> 5; + diffZ = (v2->vz - pz) >> 5; len = (diffX * diffX + diffZ * diffZ); } else if(v1) { - diffX = (v1->vx - player[0].pos[0]) >> 5; - diffZ = (v1->vz - player[0].pos[2]) >> 5; + diffX = (v1->vx - px) >> 5; + diffZ = (v1->vz - pz) >> 5; len = (diffX * diffX + diffZ * diffZ); } @@ -762,41 +759,41 @@ void TunnelSkyFade(void) } // [D] [T] -void calc_sky_brightness(void) +void calc_sky_brightness(RGB16* skycolor) { int dawn; dawn = DawnCount >> 5; - if(gTimeOfDay == 0 || gTimeOfDay == 2) + if(M_BIT(gTimeOfDay) & (M_BIT(TIME_DAWN) | M_BIT(TIME_DUSK))) { - if (gTimeOfDay == 0) + if (gTimeOfDay == TIME_DAWN) { - skycolor.r = dawn + 41; - skycolor.b = dawn + 28; + skycolor->r = dawn + 41; + skycolor->b = dawn + 28; } - else if (gTimeOfDay == 2) + else if (gTimeOfDay == TIME_DUSK) { - skycolor.r = 143 - dawn; - skycolor.b = 128 - dawn; + skycolor->r = 143 - dawn; + skycolor->b = 128 - dawn; } - if (skycolor.r < 26) - skycolor.r = 26; - else if (skycolor.r > 128) - skycolor.r = 128; + if (skycolor->r < 26) + skycolor->r = 26; + else if (skycolor->r > 128) + skycolor->r = 128; - if (skycolor.b < 26) - skycolor.b = 26; - else if (skycolor.b > 128) - skycolor.b = 128; + if (skycolor->b < 26) + skycolor->b = 26; + else if (skycolor->b > 128) + skycolor->b = 128; - skycolor.g = skycolor.b; + skycolor->g = skycolor->b; } else { - skycolor.b = 128; - skycolor.g = 128; - skycolor.r = 128; + skycolor->b = 128; + skycolor->g = 128; + skycolor->r = 128; } if (gTunnelNum == -1 || @@ -808,20 +805,21 @@ void calc_sky_brightness(void) TunnelSkyFade(); - if (skycolor.r > skyFade) - skycolor.r = skyFade; + if (skycolor->r > skyFade) + skycolor->r = skyFade; - if (skycolor.g > skyFade) - skycolor.g = skyFade; + if (skycolor->g > skyFade) + skycolor->g = skyFade; - if (skycolor.b > skyFade) - skycolor.b = skyFade; + if (skycolor->b > skyFade) + skycolor->b = skyFade; } -#ifdef USE_PGXP +#if USE_PGXP DVECTORF scratchPad_skyVertices[35]; // 1f800044 #else #define scratchPad_skyVertices ((DVECTOR*)getScratchAddr(0x11)) // 1f800044 +static_assert(0x11 + sizeof(DVECTOR) * 35 < 1024 - sizeof(_pct), "scratchpad overflow"); #endif // [D] [T] @@ -833,7 +831,7 @@ void PlotSkyPoly(POLYFT4* polys, int skytexnum, unsigned char r, unsigned char g src = polys; poly = (POLY_FT4*)current->primptr; -#ifdef USE_PGXP +#if USE_PGXP DVECTORF* outpoints = scratchPad_skyVertices; #else DVECTOR* outpoints = scratchPad_skyVertices; @@ -866,7 +864,7 @@ void PlotSkyPoly(POLYFT4* polys, int skytexnum, unsigned char r, unsigned char g addPrim(current->ot + OTSIZE - 1, poly); -#if defined(USE_PGXP) && defined(USE_EXTENDED_PRIM_POINTERS) +#if USE_PGXP && USE_EXTENDED_PRIM_POINTERS poly->pgxp_index = outpoints[src->v0].pgxp_index; #endif @@ -875,11 +873,11 @@ void PlotSkyPoly(POLYFT4* polys, int skytexnum, unsigned char r, unsigned char g } // [D] [T] -void PlotHorizonMDL(MODEL* model, int horizontaboffset) +void PlotHorizonMDL(MODEL* model, int horizontaboffset, RGB16* skycolor) { SVECTOR* verts; -#ifdef USE_PGXP +#if USE_PGXP DVECTORF* dv; #else DVECTOR* dv; @@ -895,7 +893,7 @@ void PlotHorizonMDL(MODEL* model, int horizontaboffset) dv = scratchPad_skyVertices; count = model->num_vertices; -#ifdef USE_PGXP +#if USE_PGXP PGXP_SetZOffsetScale(0.0f, 256.0f); #endif @@ -908,17 +906,17 @@ void PlotHorizonMDL(MODEL* model, int horizontaboffset) if(count == 15) gte_stszotz(&z); -#ifdef USE_PGXP +#if USE_PGXP // store PGXP index // HACK: -1 is needed here for some reason - dv[0].pgxp_index = dv[1].pgxp_index = dv[2].pgxp_index = PGXP_GetIndex() - 1; + dv[0].pgxp_index = dv[1].pgxp_index = dv[2].pgxp_index = PGXP_GetIndex(0) - 1; #endif dv += 3; verts += 3; count -= 3; } while (count); -#ifdef USE_PGXP +#if USE_PGXP PGXP_SetZOffsetScale(0.0f, 1.0f); #endif @@ -929,9 +927,9 @@ void PlotHorizonMDL(MODEL* model, int horizontaboffset) polys = (unsigned char*)model->poly_block; polySize = PolySizes[*polys]; - red = skycolor.r; - green = skycolor.g; - blue = skycolor.b; + red = skycolor->r; + green = skycolor->g; + blue = skycolor->b; // draw sky count = model->num_polys; @@ -957,33 +955,34 @@ void PlotHorizonMDL(MODEL* model, int horizontaboffset) // [D] [T] void DrawSkyDome(void) { + RGB16 skycolor = { 128,128,128 }; VECTOR skyOfs = dummy; - skyOfs.vy = sky_y_offset[GameLevel]; + calc_sky_brightness(&skycolor); + + skyOfs.vy = sky_y_offset[GameLevel]; gte_SetRotMatrix(&inv_camera_matrix); gte_SetTransVector(&skyOfs); - - calc_sky_brightness(); #ifdef PSX // FIXME: use frustrum angle instead? if (((camera_angle.vy - 1450U) & 4095) > 2250) - PlotHorizonMDL(modelpointers[0], HorizonLookup[GameLevel][0]); + PlotHorizonMDL(modelpointers[0], HorizonLookup[GameLevel][0], &skycolor); if (((camera_angle.vy - 651U) & 4095) < 1799) - PlotHorizonMDL(modelpointers[2], HorizonLookup[GameLevel][1]); + PlotHorizonMDL(modelpointers[2], HorizonLookup[GameLevel][1], &skycolor); if (((camera_angle.vy - 1701U) & 4095) < 1749) - PlotHorizonMDL(modelpointers[3], HorizonLookup[GameLevel][2]); + PlotHorizonMDL(modelpointers[3], HorizonLookup[GameLevel][2], &skycolor); if (((camera_angle.vy - 400U) & 4095) > 2300) - PlotHorizonMDL(modelpointers[1], HorizonLookup[GameLevel][3]); + PlotHorizonMDL(modelpointers[1], HorizonLookup[GameLevel][3], &skycolor); #else // draw full sky - no need in frustrum culling - PlotHorizonMDL(modelpointers[0], HorizonLookup[GameLevel][0]); - PlotHorizonMDL(modelpointers[2], HorizonLookup[GameLevel][1]); - PlotHorizonMDL(modelpointers[3], HorizonLookup[GameLevel][2]); - PlotHorizonMDL(modelpointers[1], HorizonLookup[GameLevel][3]); + PlotHorizonMDL(modelpointers[0], HorizonLookup[GameLevel][0], &skycolor); + PlotHorizonMDL(modelpointers[2], HorizonLookup[GameLevel][1], &skycolor); + PlotHorizonMDL(modelpointers[3], HorizonLookup[GameLevel][2], &skycolor); + PlotHorizonMDL(modelpointers[1], HorizonLookup[GameLevel][3], &skycolor); #endif } diff --git a/src_rebuild/Game/C/sound.c b/src_rebuild/Game/C/sound.c index c1e5b641e..42724cb85 100644 --- a/src_rebuild/Game/C/sound.c +++ b/src_rebuild/Game/C/sound.c @@ -52,6 +52,7 @@ int gMasterVolume = 0; int gMusicVolume = -4000; int Song_ID = -1; +int Song_SetPos = -1; int VABID = -1; int gSoundMode = 1; // mono or stereo @@ -67,6 +68,7 @@ int vblcounter = 0; // vblank counter // [D] [T] void SoundHandler(void) { + CHANNEL_DATA* c; int ct; int off; @@ -77,11 +79,16 @@ void SoundHandler(void) for (ct = 0; ct < MAX_SFX_CHANNELS; ct++) { - if (channels[ct].loop || channels[ct].time == 0) + c = &channels[ct]; + if ((c->flags & CHAN_LOOP) || c->time == 0) + { continue; + } - if (--channels[ct].time == 0) + if (--c->time == 0) + { off |= SPU_KEYCH(ct); + } } if (off) @@ -94,7 +101,15 @@ void VsyncProc(void) vblcounter++; if (Song_ID != -1) + { + if (Song_SetPos != -1) + { + XM_SetSongPos(Song_ID, Song_SetPos); + Song_SetPos = -1; + } + XM_Update(); + } SoundHandler(); } @@ -140,8 +155,7 @@ void ClearChannelFields(int channel) chan = &channels[channel]; - chan->loop = 0; - chan->locked = 0; + chan->flags = 0; chan->time = 0; chan->samplerate = 0; chan->srcvolume = -10000; @@ -320,9 +334,6 @@ int CalculateVolume(int channel) return volume - 10000; } - -int gSurround = 0; - // [D] [T] void UpdateVolumeAttributesS(int channel, int proximity) { @@ -352,7 +363,7 @@ void UpdateVolumeAttributesS(int channel, int proximity) vol = (vol + vol / 2 + vol / 8 + vol / 128) * master_volume / 16384; chan->attr.volume.left = vol; - chan->attr.volume.right = gSurround ? -vol : vol; // HMM: why like that? + chan->attr.volume.right = vol; if (vol == 0) return; @@ -426,7 +437,7 @@ void UpdateVolumeAttributesS(int channel, int proximity) if (proximity > 0) damp = damp * dist / 12000; - chan->attr.volume.right = gSurround ? -MAX(0, vol - damp) : MAX(0, vol - damp); + chan->attr.volume.right = MAX(0, vol - damp); } } @@ -455,45 +466,52 @@ int CompleteSoundSetup(int channel, int bank, int sample, int pitch, int proximi int bpf; int rate; CHANNEL_DATA* chan; + SAMPLE_DATA* samp; - rate = samples[bank][sample].samplerate * pitch; + samp = &samples[bank][sample]; + + rate = samp->samplerate * pitch; +#ifdef PAL_VERSION bpf = (rate / 4096) / 50; +#else + bpf = (rate / 4096) / 60; +#endif if (bpf == 0) { - channel = -1; + return -1; } - else - { - chan = &channels[channel]; - - if (gSoundMode == 1 && proximity != -1) - UpdateVolumeAttributesS(channel, proximity); - else - UpdateVolumeAttributesM(channel); - stop_sound_handler = 1; + chan = &channels[channel]; + + if (gSoundMode == 1 && proximity != -1) + UpdateVolumeAttributesS(channel, proximity); + else + UpdateVolumeAttributesM(channel); - chan->attr.mask = SPU_VOICE_VOLL | SPU_VOICE_VOLR | SPU_VOICE_VOLMODEL | SPU_VOICE_VOLMODER | SPU_VOICE_PITCH | SPU_VOICE_WDSA; - chan->attr.addr = samples[bank][sample].address; - chan->attr.pitch = MIN(rate / 44100, 16383); - chan->time = (samples[bank][sample].length / bpf) * 2 + 2; - chan->loop = samples[bank][sample].loop; + stop_sound_handler = 1; - chan->samplerate = samples[bank][sample].samplerate; + chan->attr.mask = SPU_VOICE_VOLL | SPU_VOICE_VOLR | SPU_VOICE_VOLMODEL | SPU_VOICE_VOLMODER | SPU_VOICE_PITCH | SPU_VOICE_WDSA; + chan->attr.addr = samp->address; + chan->attr.pitch = MIN(rate / 44100, 16383); + chan->time = (samp->length / bpf) * 2 + 2; - if (sound_paused != 0) - { - chan->attr.volume.left = 0; - chan->attr.volume.right = 0; - } + chan->flags &= ~CHAN_LOOP; + chan->flags |= samp->loop ? CHAN_LOOP : 0; - SpuSetVoiceAttr(&chan->attr); - SpuSetKey(1, chan->attr.voice); + chan->samplerate = samp->samplerate; - stop_sound_handler = 0; + if (sound_paused != 0) + { + chan->attr.volume.left = 0; + chan->attr.volume.right = 0; } + SpuSetVoiceAttr(&chan->attr); + SpuSetKey(1, chan->attr.voice); + + stop_sound_handler = 0; + return channel; } @@ -505,7 +523,7 @@ void ComputeDoppler(CHANNEL_DATA *ch) int seperationrate; VECTOR *srcPos; - long* srcVel; // LONGVECTOR3 + int* srcVel; // LONGVECTOR3 PLAYER *pl; int dx, dy, dz; @@ -518,7 +536,7 @@ void ComputeDoppler(CHANNEL_DATA *ch) return; } - srcVel = (long*)ch->srcvelocity; + srcVel = (int*)ch->srcvelocity; pl = &player[ch->player]; @@ -528,9 +546,9 @@ void ComputeDoppler(CHANNEL_DATA *ch) dist = jsqrt(dx * dx + dy * dy + dz * dz); - dx = (srcPos->vx - pl->cameraPos.vx) + FIXEDH(srcVel[0] - pl->camera_vel[0]); - dy = (srcPos->vy + pl->cameraPos.vy) + FIXEDH(srcVel[1] - pl->camera_vel[1]); - dz = (srcPos->vz - pl->cameraPos.vz) + FIXEDH(srcVel[2] - pl->camera_vel[2]); + dx += FIXEDH(srcVel[0] - pl->camera_vel[0]); + dy += FIXEDH(srcVel[1] - pl->camera_vel[1]); + dz += FIXEDH(srcVel[2] - pl->camera_vel[2]); seperationrate = jsqrt(dx * dx + dy * dy + dz * dz); @@ -577,6 +595,9 @@ int Start3DTrackingSound(int channel, int bank, int sample, VECTOR *position, LO channel = CompleteSoundSetup(channel, bank, sample, 4096, 0); + if (channel < 0) + return -1; + ComputeDoppler(&channels[channel]); SetChannelPitch(channel, 4096); @@ -605,6 +626,9 @@ int Start3DSoundVolPitch(int channel, int bank, int sample, int x, int y, int z, channel = CompleteSoundSetup(channel, bank, sample, pitch, 0); + if (channel < 0) + return -1; + ComputeDoppler(&channels[channel]); SetChannelPitch(channel, pitch); @@ -848,29 +872,34 @@ void UnPauseSound(void) // [D] [T] void StopChannel(int channel) { + CHANNEL_DATA* c; u_char lock; int vsync; if (channel < 0 || channel >= MAX_SFX_CHANNELS) return; - lock = channels[channel].locked; + c = &channels[channel]; + vsync = VSync(-1); - SpuSetKey(0, channels[channel].attr.voice); + SpuSetKey(0, c->attr.voice); // PSX SPU keyoff sometimes does not work for first time due to it's nature do { - if (SpuGetKeyStatus(channels[channel].attr.voice) == 0) + if (SpuGetKeyStatus(c->attr.voice) == 0) break; #ifdef __EMSCRIPTEN__ emscripten_sleep(0); #endif } while (VSync(-1) - vsync < 8); + // save lock status + lock = c->flags & CHAN_LOCKED; + ClearChannelFields(channel); - channels[channel].locked = lock; + c->flags = lock; } // [D] [T] @@ -891,7 +920,7 @@ void LockChannel(int channel) if (channel < 0 || channel >= MAX_SFX_CHANNELS) // [A] return; - channels[channel].locked = 1; + channels[channel].flags |= CHAN_LOCKED; } // [D] [T] @@ -900,7 +929,7 @@ void UnlockChannel(int c) if (c < 0 || c >= MAX_SFX_CHANNELS) // [A] return; - channels[c].locked = 0; + channels[c].flags &= ~CHAN_LOCKED; } // [D] [T] @@ -923,7 +952,7 @@ int LoadSoundBank(char *address, int length, int bank) SpuSetTransferMode(SPU_TRANSFER_BY_DMA); SpuSetTransferStartAddr(spuaddress); - SpuWrite((unsigned char*)address + slength, length - slength); + SpuWrite((u_char*)address + slength, length - slength); SpuIsTransferCompleted(SPU_TRANSFER_WAIT); @@ -1001,7 +1030,7 @@ int LoadSoundBankDynamic(char *address, int length, int dbank) SpuSetTransferMode(SPU_TRANSFER_BY_DMA); SpuSetTransferStartAddr(lsbTabs.addr); - SpuWrite((unsigned char*)address + slength, length - slength); + SpuWrite((u_char*)address + slength, length - slength); SpuIsTransferCompleted(SPU_TRANSFER_WAIT); @@ -1057,6 +1086,7 @@ void StopXM(void) // [D] [T] int GetFreeChannel(int force) { + CHANNEL_DATA* c; int channel; int it; int least; @@ -1068,7 +1098,7 @@ int GetFreeChannel(int force) channel = 0; // find free channel - while (channel < MAX_SFX_CHANNELS && (channels[channel].locked || status[channel] != SPU_OFF && status[channel] != SPU_RESET)) + while (channel < MAX_SFX_CHANNELS && ((channels[channel].flags & CHAN_LOCKED) || status[channel] != SPU_OFF && status[channel] != SPU_RESET)) { channel++; } @@ -1083,15 +1113,16 @@ int GetFreeChannel(int force) channel = -1; least = 0; - for (it = 0; it < MAX_SFX_CHANNELS; it++) + c = &channels[0]; + for (it = 0; it < MAX_SFX_CHANNELS; it++, c++) { - if (channels[it].locked || channels[it].loop) + if (c->flags & (CHAN_LOCKED | CHAN_LOOP)) continue; - if (channel == -1 || channels[it].time < least) + if (channel == -1 || c->time < least) { channel = it; - least = channels[it].time; + least = c->time; } } @@ -1102,7 +1133,7 @@ int GetFreeChannel(int force) } // [D] [T] -void AllocateReverb(long mode, long depth) +void AllocateReverb(int mode, int depth) { SpuReverbAttr r_attr; diff --git a/src_rebuild/Game/C/sound.h b/src_rebuild/Game/C/sound.h index 01d96e382..ee22c2750 100644 --- a/src_rebuild/Game/C/sound.h +++ b/src_rebuild/Game/C/sound.h @@ -1,28 +1,31 @@ #ifndef SOUND_H #define SOUND_H +enum ChanFlags +{ + CHAN_LOOP = (1 << 0), + CHAN_LOCKED = (1 << 1) +}; + struct CHANNEL_DATA { SpuVoiceAttr attr; - - u_char loop; - u_char locked; - u_short time; + VECTOR* srcposition; + VECTOR position; + LONGVECTOR3* srcvelocity; int samplerate; - char player; int srcvolume; int volumeScale; + int cameradist; + int lastcameradist; u_short srcpitch; u_short dopplerScale; - int cameradist; - int lastcameradist; - - VECTOR* srcposition; - VECTOR position; - LONGVECTOR3* srcvelocity; + u_short time; + u_char flags; + char player; }; extern CHANNEL_DATA channels[16]; @@ -32,13 +35,14 @@ extern int gMusicVolume; extern int gSoundMode; extern int Song_ID; +extern int Song_SetPos; extern int VABID; extern int music_paused; extern void InitSound(); // 0x000790E4 extern void ResetSound(); // 0x00079250 -extern void AllocateReverb(long mode, long depth); // 0x0007AB3C +extern void AllocateReverb(int mode, int depth); // 0x0007AB3C extern void SetReverbState(int on); // 0x0007A6C0 extern void SetReverbInGameState(int on); // 0x0007A704 extern int SetReverbChannelState(int ch, int on); // 0x0007A75C diff --git a/src_rebuild/Game/C/spool.c b/src_rebuild/Game/C/spool.c index 6eec9cddf..02f81b5c8 100644 --- a/src_rebuild/Game/C/spool.c +++ b/src_rebuild/Game/C/spool.c @@ -24,6 +24,13 @@ #include "camera.h" #include "dr2roads.h" +#define SPOOL_REGION 0 +#define SPOOL_TPAGE 1 +#define SPOOL_SOUNDBANK 2 +#define SPOOL_MISC 3 + +typedef void(*spooledFuncPtr)(); + struct SPOOLQ { u_char type; @@ -73,12 +80,14 @@ int SpecialByRegion[4][20] = { {2, 5, 4, 1, 2, 1, 4, 3, 2, 2, 2, 2, 3, 3, 3, 0, 0, 0, 0, 0}, }; +SPOOLQ spooldata[48]; + char* model_spool_buffer = NULL; int cell_objects_add[5]; int cell_slots_add[5]; - -typedef void(*spooledFuncPtr)(); +short loading_region[4]; +int regions_unpacked[4]; SXYPAIR* Music_And_AmbientOffsets; @@ -89,28 +98,19 @@ int NumAreas; char* RegionSpoolInfo; unsigned short *spoolinfo_offsets; -char* specmallocptr; -char *specLoadBuffer; - int doSpooling = 1; -short loading_region[4]; -int regions_unpacked[4]; - int spool_regioncounter; int spoolerror; // UNUSED int spool_regionpos; +int spoolcounter; volatile int spoolactive; // volatile is required at least for PC volatile int quickSpool; -int models_ready; - -short specspooldata[3] = { 20, 10, 4 }; +int models_ready; int tsetcounter; int tsetpos; -int spoolcounter; - int loadbank_read; int loadbank_write; @@ -118,12 +118,6 @@ volatile int spoolpos; volatile int spoolpos_reading; volatile int spoolpos_writing; -int allowSpecSpooling; -int startSpecSpool; - -int unpack_roadmap_flag; -int unpack_cellptr_flag; - char *packed_cell_pointers; SPL_REGIONINFO spool_regioninfo[8]; @@ -140,8 +134,6 @@ static volatile int current_sector; static volatile int switch_spooltype; static volatile int endchunk; -int send_bank; -int sample_chunk; volatile int chunk_complete; @@ -151,7 +143,12 @@ int new_area_location; int LoadingArea = 0; unsigned short *newmodels = NULL; -SPOOLQ spooldata[48]; +#define CHECK_SWITCHSPOOL() \ + if (switch_spooltype) \ + { \ + changemode(&spooldata[spoolpos_writing]); \ + return; \ + } \ // configure spooler #if USE_PC_FILESYSTEM @@ -450,9 +447,7 @@ void InitSpooling(void) spoolcounter = 0; loadbank_read = 0; spoolpos = 0; - unpack_roadmap_flag = 0; loadbank_write = 0; - unpack_cellptr_flag = 0; } int tsetinfo[32]; @@ -476,6 +471,7 @@ void SendTPage(void) if (nTPchunks == 0) { + // Send palettes if (slot != tpageloaded[tpage2send] - 1) { npalettes = *(int *)(model_spool_buffer + 0xE000); @@ -492,7 +488,7 @@ void SendTPage(void) cluts.h = npalettes / 4 + 1; - LoadImage(&cluts, (u_long *)(model_spool_buffer + 0xE000 + 4)); + LoadImage(&cluts, (u_long*)(model_spool_buffer + 0xE000 + 4)); clutptr = (u_int*)(texture_cluts[tpage2send]); i = 0; @@ -516,7 +512,7 @@ void SendTPage(void) { if (slot != tpageloaded[tpage2send] - 1) { - LoadImage(&tpage, (u_long *)(model_spool_buffer + 0xA000 + (loadbank_write % 2) * 256 * 32)); + LoadImage(&tpage, (u_long*)(model_spool_buffer + 0xA000 + (loadbank_write & 1) * 256 * 32)); tpage.y = tpage.y + tpage.h; } @@ -619,7 +615,7 @@ void LoadInAreaTSets(int area) tsetinfo[tsetcounter * 2 + 1] = availableslots[slot]; tsetinfo[tsetcounter * 2] = tpages[i]; - RequestSpool(1, 0, offset, 17, loadaddr, SendTPage); + RequestSpool(SPOOL_TPAGE, 0, offset, 17, loadaddr, SendTPage); offset += 17; tsetcounter++; @@ -658,11 +654,16 @@ void init_spooled_models(void) size = *(int *)addr; model = (MODEL *)(addr + 4); + modelpointers[model_number] = model; + pLodModels[model_number] = model; + lod = Low2LowerDetailTable[model_number]; if (lod != 0xffff && lod != model_number) pLodModels[model_number] = modelpointers[lod]; + ProcessModel(model_number); + if (model->instance_number == -1) { if ((uint)model->collision_block != 0) @@ -689,10 +690,6 @@ void init_spooled_models(void) } model->poly_block += (int)model; - - modelpointers[model_number] = model; - pLodModels[model_number] = model; - addr += size + 4; } @@ -714,6 +711,7 @@ void CheckValidSpoolData(void) if (models_ready) init_spooled_models(); +#ifdef PSX if (spoolactive && check_regions_present()) { stopgame(); @@ -726,6 +724,7 @@ void CheckValidSpoolData(void) startgame(); } +#endif // PSX } // [D] [T] @@ -741,7 +740,7 @@ void LoadInAreaModels(int area) int length = AreaData[area].model_size; newmodels = (ushort *)(model_spool_buffer + (length-1) * 2048); - RequestSpool(3, 0, AreaData[area].model_offset, length, model_spool_buffer, SetupModels); + RequestSpool(SPOOL_MISC, 0, AreaData[area].model_offset, length, model_spool_buffer, SetupModels); } // [D] [T] @@ -758,14 +757,6 @@ void CheckLoadAreaData(int cellx, int cellz) spoolptr = (Spool *)(RegionSpoolInfo + spoolinfo_offsets[current_region]); -#ifndef PSX - // [A] this fixes spooling not activated bug (reversing bug?) - if (LoadedArea != spoolptr->super_region && spoolptr->super_region != 0xFF && old_region != -1) - { - LoadedArea = spoolptr->super_region; - } - else -#endif if (old_region == -1 && spoolptr->super_region != 0xFF) { // just load the area if no @@ -777,7 +768,7 @@ void CheckLoadAreaData(int cellx, int cellz) if (old_region == -1) LoadedArea = -1; - else if (/*spoolptr->super_region == 0xFF ||*/ nAreas == 0) + else if (spoolptr->super_region == 0xFF && nAreas == 0) return; #define BOUNDARY_MIN 15 @@ -852,7 +843,7 @@ void ClearRegion(int target_region) ClearMem(PVS_Buffers[target_region]-4, pvsSize[target_region]); - RoadMapDataRegions[target_region] = (short*)PVS_Buffers[0]; + RoadMapDataRegions[target_region] = (short*)PVS_Buffers[target_region]; } inline int _getIntAdv(char** ptr) @@ -998,134 +989,131 @@ void changemode(SPOOLQ *current); // [D] [T] void data_cb_textures(void) { - if (chunk_complete) - { - chunk_complete = 0; - nTPchunks = nTPchunks_writing; + if (chunk_complete == 0) + return; - SendTPage(); + chunk_complete = 0; + nTPchunks = nTPchunks_writing; - if (nTPchunks != 0) - loadbank_write++; + SendTPage(); - nTPchunks_writing++; + if (nTPchunks != 0) + loadbank_write++; - if (nTPchunks_writing == 5) - { - nTPchunks_writing = 0; - spoolpos_writing++; + nTPchunks_writing++; - if (ntpages == 0) - { - if (!switch_spooltype) - { - CdDataCallback(NULL); - - if (spoolpos_writing == spoolcounter) - { - SPOOL_WARNING("All SPOOL requests (%d) completed successfully on TEXTURES\n", spoolcounter); // [A] - - spoolcounter = 0; - spoolpos_writing = 0; - spoolpos_reading = 0; - spoolactive = 0; - } - else - { - UpdateSpool(); - } - } - else - { - changemode(&spooldata[spoolpos_writing]); - } - } - } + if (nTPchunks_writing != 5) + return; + + nTPchunks_writing = 0; + spoolpos_writing++; + + if (ntpages != 0) + return; + + CHECK_SWITCHSPOOL(); + + CdDataCallback(NULL); + + if (spoolpos_writing == spoolcounter) + { + SPOOL_WARNING("All SPOOL requests (%d) completed successfully on TEXTURES\n", spoolcounter); // [A] + + spoolcounter = 0; + spoolpos_writing = 0; + spoolpos_reading = 0; + spoolactive = 0; + } + else + { + UpdateSpool(); } } // [D] [T] void ready_cb_textures(unsigned char intr, unsigned char *result) { - if (intr == 1) + if (intr != 1) { - CdGetSector(target_address, SECTOR_SIZE); + FoundError("ready_cb_textures", intr, result); + return; + } - target_address += CDSECTOR_SIZE; - sectors_this_chunk--; - current_sector++; - sectors_to_read--; + CdGetSector(target_address, SECTOR_SIZE); - if (sectors_this_chunk == 0) - { - if (nTPchunks_reading) - loadbank_read++; + target_address += CDSECTOR_SIZE; + sectors_this_chunk--; + current_sector++; + sectors_to_read--; - nTPchunks_reading++; - chunk_complete = intr; + if (sectors_this_chunk != 0) + return; - if (sectors_to_read == 0) - { - ntpages--; + if (nTPchunks_reading) + loadbank_read++; - if (ntpages == 0) - { - spoolpos_reading++; - test_changemode(); - } - else - { - nTPchunks_reading = 0; - sectors_to_read = 17; + nTPchunks_reading++; + chunk_complete = intr; - target_address = spooldata[spoolpos_reading].addr + 0x4000; - spoolpos_reading++; + if (sectors_to_read == 0) + { + ntpages--; - sectors_this_chunk = intr; - } - } - else - { - sectors_this_chunk = 4; - target_address = spooldata[spoolpos_reading].addr + (loadbank_read & 1U) * 0x2000; - } + if (ntpages == 0) + { + spoolpos_reading++; + test_changemode(); + } + else + { + nTPchunks_reading = 0; + sectors_to_read = 17; + + target_address = spooldata[spoolpos_reading].addr + 0x4000; + spoolpos_reading++; + + sectors_this_chunk = intr; } } else - FoundError("ready_cb_textures", intr, result); + { + sectors_this_chunk = 4; + target_address = spooldata[spoolpos_reading].addr + (loadbank_read & 1U) * 0x2000; + } } // [D] [T] void ready_cb_regions(unsigned char intr, unsigned char *result) { - if (intr == 1) + if (intr != 1) { - CdGetSector(target_address, SECTOR_SIZE); + FoundError("ready_cb_regions", intr, result); + return; + } - target_address += CDSECTOR_SIZE; - sectors_this_chunk--; - current_sector++; - sectors_to_read--; + CdGetSector(target_address, SECTOR_SIZE); - if (sectors_this_chunk == 0) - { - spoolpos_reading++; - chunk_complete = intr; + target_address += CDSECTOR_SIZE; + sectors_this_chunk--; + current_sector++; + sectors_to_read--; - if (sectors_to_read == 0) - { - endchunk = intr; - test_changemode(); - } - else - { - target_address = spooldata[spoolpos_reading].addr; - sectors_this_chunk = spooldata[spoolpos_reading].nsectors; - } + if (sectors_this_chunk == 0) + { + spoolpos_reading++; + chunk_complete = intr; + + if (sectors_to_read == 0) + { + endchunk = intr; + test_changemode(); + } + else + { + target_address = spooldata[spoolpos_reading].addr; + sectors_this_chunk = spooldata[spoolpos_reading].nsectors; } } - else - FoundError("ready_cb_regions", intr, result); } // [D] [T] @@ -1133,40 +1121,35 @@ void data_cb_regions(void) { SPOOLQ* current = &spooldata[spoolpos_writing]; - if (chunk_complete) - { - chunk_complete = 0; + if (chunk_complete == 0) + return; - if (current->func != NULL) - (current->func)(); + chunk_complete = 0; - spoolpos_writing++; + if (current->func != NULL) + (current->func)(); - if (endchunk) - { - if (!switch_spooltype) - { - CdDataCallback(NULL); + spoolpos_writing++; - if (spoolpos_writing == spoolcounter) - { - SPOOL_WARNING("All SPOOL requests (%d) completed successfully on REGIONS\n", spoolcounter); // [A] + if (endchunk == 0) + return; - spoolcounter = 0; - spoolpos_writing = 0; - spoolpos_reading = 0; - spoolactive = 0; - } - else - { - UpdateSpool(); - } - } - else - { - changemode(&spooldata[spoolpos_writing]); - } - } + CHECK_SWITCHSPOOL(); + + CdDataCallback(NULL); + + if (spoolpos_writing == spoolcounter) + { + SPOOL_WARNING("All SPOOL requests (%d) completed successfully on REGIONS\n", spoolcounter); // [A] + + spoolcounter = 0; + spoolpos_writing = 0; + spoolpos_reading = 0; + spoolactive = 0; + } + else + { + UpdateSpool(); } } @@ -1176,37 +1159,32 @@ void data_cb_misc(void) { SPOOLQ *current = &spooldata[spoolpos_writing]; - if (chunk_complete) - { - chunk_complete = 0; + if (chunk_complete == 0) + return; - if (current->func != NULL) - (*current->func)(); + chunk_complete = 0; - spoolpos_writing++; + if (current->func != NULL) + (*current->func)(); - if (!switch_spooltype) - { - CdDataCallback(NULL); + spoolpos_writing++; + + CHECK_SWITCHSPOOL(); + + CdDataCallback(NULL); - if (spoolpos_writing == spoolcounter) - { - SPOOL_WARNING("All SPOOL requests (%d) completed successfully on MISC\n", spoolcounter); // [A] + if (spoolpos_writing == spoolcounter) + { + SPOOL_WARNING("All SPOOL requests (%d) completed successfully on MISC\n", spoolcounter); // [A] - spoolcounter = 0; - spoolpos_writing = 0; - spoolpos_reading = 0; - spoolactive = 0; - } - else - { - UpdateSpool(); - } - } - else - { - changemode(&spooldata[spoolpos_writing]); - } + spoolcounter = 0; + spoolpos_writing = 0; + spoolpos_reading = 0; + spoolactive = 0; + } + else + { + UpdateSpool(); } } @@ -1348,7 +1326,6 @@ void unpack_cellpointers(int region_to_unpack, int target_barrel_region, char* c int packtype; - unpack_cellptr_flag = 0; packtype = *(int *)(cell_addr + 4); source_packed_data = (ushort *)(cell_addr + 8); @@ -1406,8 +1383,6 @@ void unpack_cellpointers(int region_to_unpack, int target_barrel_region, char* c printError("BAD PACKED CELL POINTER DATA, region = %d, packtype = %d\n", region_to_unpack, packtype); D_CHECK_ERROR(true, "Bad cell pointer data"); - - unpack_cellptr_flag = 0; } } @@ -1505,7 +1480,6 @@ int UpdateSpoolPC(void) break; case 1: // textures - // read cluts nTPchunks = 0; SPL_READ(current->addr + CDSECTOR_SIZE * 2 * 4, 1); @@ -1516,9 +1490,10 @@ int UpdateSpoolPC(void) // read tpage (4 sectors 4 times = 16) for (int i = 0; i < 4; i++) { - SPL_READ(current->addr + (loadbank_write & 1U) * 256 * 32, 4); + SPL_READ(current->addr + (loadbank_write & 1) * 256 * 32, 4); SendTPage(); + loadbank_write++; nTPchunks++; } @@ -1618,31 +1593,31 @@ int LoadRegionData(int region, int target_region) #ifndef PSX if (gDemoLevel) { - RequestSpool(0, 0, offset, spoolptr->roadm_size, PVS_Buffers[target_region], NULL); + RequestSpool(SPOOL_REGION, 0, offset, spoolptr->roadm_size, PVS_Buffers[target_region], NULL); offset += spoolptr->roadm_size; - RequestSpool(0, 0, offset, spoolptr->cell_data_size[1], cell_buffer, NULL); + RequestSpool(SPOOL_REGION, 0, offset, spoolptr->cell_data_size[1], cell_buffer, NULL); offset += spoolptr->cell_data_size[1]; - RequestSpool(0, 0, offset, spoolptr->cell_data_size[0], (char *)(cells + cell_slots_add[target_region]), NULL); + RequestSpool(SPOOL_REGION, 0, offset, spoolptr->cell_data_size[0], (char *)(cells + cell_slots_add[target_region]), NULL); offset += spoolptr->cell_data_size[0]; - RequestSpool(0, 0, offset, spoolptr->cell_data_size[2], (char *)(cell_objects + num_straddlers + cell_objects_add[target_region]), GotRegion); + RequestSpool(SPOOL_REGION, 0, offset, spoolptr->cell_data_size[2], (char *)(cell_objects + num_straddlers + cell_objects_add[target_region]), GotRegion); offset += spoolptr->cell_data_size[2]; } else #endif { - RequestSpool(0, 0, offset, spoolptr->cell_data_size[1], cell_buffer, NULL); + RequestSpool(SPOOL_REGION, 0, offset, spoolptr->cell_data_size[1], cell_buffer, NULL); offset += spoolptr->cell_data_size[1]; - RequestSpool(0, 0, offset, spoolptr->cell_data_size[0], (char *)(cells + cell_slots_add[target_region]), NULL); + RequestSpool(SPOOL_REGION, 0, offset, spoolptr->cell_data_size[0], (char *)(cells + cell_slots_add[target_region]), NULL); offset += spoolptr->cell_data_size[0]; - RequestSpool(0, 0, offset, spoolptr->cell_data_size[2], (char *)(cell_objects + num_straddlers + cell_objects_add[target_region]), NULL); + RequestSpool(SPOOL_REGION, 0, offset, spoolptr->cell_data_size[2], (char *)(cell_objects + num_straddlers + cell_objects_add[target_region]), NULL); offset += spoolptr->cell_data_size[2]; - RequestSpool(0, 0, offset, spoolptr->roadm_size, PVS_Buffers[target_region] - 4, GotRegion); + RequestSpool(SPOOL_REGION, 0, offset, spoolptr->roadm_size, PVS_Buffers[target_region] - 4, GotRegion); offset += spoolptr->roadm_size; } @@ -1675,6 +1650,45 @@ void UnpackRegion(int region_to_unpack, int target_barrel_region) //--------------------------------------------------------------------- +// Special car spooling + +enum SpecialSpoolState +{ + SpecSpool_None = 0, + + SpecSpool_Tpage1, + SpecSpool_Tpage2, + SpecSpool_Cluts, + SpecSpool_Cosmetics, + + SpecSpool_CleanSpool, + SpecSpool_LowSpool, + + SpecSpool_CleanModel, + SpecSpool_DamagedModel, + SpecSpool_LowModel +}; + +int specialState = SpecSpool_None; + +int allowSpecSpooling; +int startSpecSpool; +char* specmallocptr; +char* specLoadBuffer; +short specSpoolModelIndex = 4; + +int* modelMemory; + +int lengthLowBlock; +int lastCleanBlock; +int lengthDamBlock; + +int firstLowBlock; +int firstDamBlock; + +int specBlocksToLoad = 0; +char specModelValid = 1; +int specSpoolComplete; void SpecialStartNextBlock(void); @@ -1694,7 +1708,7 @@ void SpecClutsSpooled(void) for (int i = 0; i < 2; i++) { int index = specialSlot + i; - int tpage = specTpages[GameLevel][(specspooldata[2]-1) * 2 + i]; + int tpage = specTpages[GameLevel][(specSpoolModelIndex-1) * 2 + i]; carTpages[GameLevel][i + 6] = tpageslots[index] = tpage; tpageloaded[tpage] = specialSlot + i + 1; @@ -1721,38 +1735,19 @@ void SpecClutsSpooled(void) } #endif } - - if (quickSpool != 1) - DrawSyncCallback(SpecialStartNextBlock); } -int *modelMemory; - -int lengthLowBlock; -int lastCleanBlock; -int lengthDamBlock; - -int firstLowBlock; -int firstDamBlock; - -int specBlocksToLoad = 0; -int specialState = 0; -char specModelValid = 1; -int specSpoolComplete; - // [D] [T] void CleanModelSpooled(void) { - int *loadaddr; - int *mem; + int* loadaddr; + int* polyBlock; loadaddr = (int *)specLoadBuffer; if (specBlocksToLoad == lastCleanBlock-1) { loadaddr = (int *)(specLoadBuffer + 12); - - modelMemory = (int *)specmallocptr; gCarCleanModelPtr[4] = (MODEL *)modelMemory; } @@ -1760,20 +1755,12 @@ void CleanModelSpooled(void) while (loadaddr < (int*)(specLoadBuffer + CDSECTOR_SIZE)) *modelMemory++ = *loadaddr++; - mem = (int*)((int)gCarCleanModelPtr[4] + gCarCleanModelPtr[4]->poly_block); // [A] pls check, might be invalid + polyBlock = (int*)((int)gCarCleanModelPtr[4] + gCarCleanModelPtr[4]->poly_block); // [A] pls check, might be invalid - if (specBlocksToLoad == 0 || mem < modelMemory) - { -#if USE_PC_FILESYSTEM - if (gContentOverride) - { - // [A] vertices - LoadCarModelFromFile((char*)gCarCleanModelPtr[4], MissionHeader->residentModels[4], CAR_MODEL_CLEAN); - } -#endif - + if (specBlocksToLoad == 0 || modelMemory > polyBlock) + { specBlocksToLoad = 0; - modelMemory = mem; + modelMemory = polyBlock; gCarCleanModelPtr[4]->vertices += (int)gCarCleanModelPtr[4]; gCarCleanModelPtr[4]->normals += (int)gCarCleanModelPtr[4]; @@ -1793,15 +1780,14 @@ int damOffset; // [D] [T] void DamagedModelSpooled(void) { - int *loadaddr; - int *mem; + int* loadaddr; + int* polyBlock; loadaddr = (int *)specLoadBuffer; if (specBlocksToLoad == lengthDamBlock-1) { loadaddr = (int *)(specLoadBuffer + damOffset); - gCarDamModelPtr[4] = (MODEL *)modelMemory; } @@ -1809,20 +1795,12 @@ void DamagedModelSpooled(void) while (loadaddr < (int*)(specLoadBuffer + CDSECTOR_SIZE)) *modelMemory++ = *loadaddr++; - mem = (int*)((int)gCarDamModelPtr[4] + gCarDamModelPtr[4]->poly_block); // [A] pls check, might be invalid + polyBlock = (int*)((int)gCarDamModelPtr[4] + gCarDamModelPtr[4]->poly_block); // [A] pls check, might be invalid - if (specBlocksToLoad == 0 || mem < modelMemory) - { -#if USE_PC_FILESYSTEM - if (gContentOverride) - { - // [A] vertices - LoadCarModelFromFile((char*)gCarDamModelPtr[4], MissionHeader->residentModels[4], CAR_MODEL_DAMAGED); - } -#endif - + if (specBlocksToLoad == 0 || modelMemory > polyBlock) + { specBlocksToLoad = 0; - modelMemory = mem; + modelMemory = polyBlock; gCarDamModelPtr[4]->vertices += (int)gCarDamModelPtr[4]; gCarDamModelPtr[4]->normals += (int)gCarDamModelPtr[4]; @@ -1832,7 +1810,6 @@ void DamagedModelSpooled(void) if (quickSpool != 1) DrawSyncCallback(SpecialStartNextBlock); - } int lowOffset; @@ -1840,15 +1817,14 @@ int lowOffset; // [D] [T] void LowModelSpooled(void) { - int *loadaddr; - int *mem; + int* loadaddr; + int* polyBlock; loadaddr = (int *)specLoadBuffer; if (specBlocksToLoad == lengthLowBlock - 1) { loadaddr = (int *)(specLoadBuffer + lowOffset); - gCarLowModelPtr[4] = (MODEL *)modelMemory; } @@ -1856,20 +1832,12 @@ void LowModelSpooled(void) while (loadaddr < (int*)(specLoadBuffer + CDSECTOR_SIZE)) *modelMemory++ = *loadaddr++; - mem = (int*)((int)gCarLowModelPtr[4] + gCarLowModelPtr[4]->poly_block); // [A] pls check, might be invalid + polyBlock = (int*)((int)gCarLowModelPtr[4] + gCarLowModelPtr[4]->poly_block); // [A] pls check, might be invalid - if (specBlocksToLoad == 0 || mem < modelMemory) + if (specBlocksToLoad == 0 || modelMemory > polyBlock) { -#if USE_PC_FILESYSTEM - if (gContentOverride) - { - // [A] vertices - LoadCarModelFromFile((char*)gCarLowModelPtr[4], MissionHeader->residentModels[4], CAR_MODEL_LOWDETAIL); - } -#endif - specBlocksToLoad = 0; - modelMemory = mem; + modelMemory = polyBlock; gCarLowModelPtr[4]->vertices += (int)gCarLowModelPtr[4]; gCarLowModelPtr[4]->normals += (int)gCarLowModelPtr[4]; @@ -1890,56 +1858,100 @@ void CleanSpooled(void) { MODEL *model; + // first block is model sizes if (specBlocksToLoad == 6) { + int lastDamBlock; + int lastLowBlock; + // [A] for readability sake - int size_1 = ((int *)specmallocptr)[0]; - int size_2 = ((int *)specmallocptr)[1]; - int size_3 = ((int *)specmallocptr)[2]; + int cleanModelOfs = ((int *)specmallocptr)[0]; + int damModelOfs = ((int *)specmallocptr)[1]; + int lowModelOfs = ((int *)specmallocptr)[2]; - lastCleanBlock = size_1 + CDSECTOR_SIZE + 11; - lastCleanBlock >>= 11; + lastCleanBlock = cleanModelOfs + CDSECTOR_SIZE + 11; + firstDamBlock = cleanModelOfs + 12; + firstLowBlock = cleanModelOfs + damModelOfs + 12; - firstDamBlock = size_1 + 12; + // to sector index + lastCleanBlock >>= 11; firstDamBlock >>= 11; - - firstLowBlock = size_1 + size_2 + 12; firstLowBlock >>= 11; - int lastDamBlock = size_1 + size_2 + CDSECTOR_SIZE + 11; - lastDamBlock >>= 11; + lastDamBlock = cleanModelOfs + damModelOfs + CDSECTOR_SIZE + 11; + lastLowBlock = cleanModelOfs + damModelOfs + lowModelOfs + CDSECTOR_SIZE + 11; - lengthDamBlock = lastDamBlock - firstDamBlock; - damOffset = size_1 - (firstDamBlock * CDSECTOR_SIZE - 12); - - int lastLowBlock = size_1 + size_2 + size_3 + CDSECTOR_SIZE + 11; + // to sector index + lastDamBlock >>= 11; lastLowBlock >>= 11; + lengthDamBlock = lastDamBlock - firstDamBlock; lengthLowBlock = lastLowBlock - firstLowBlock; - lowOffset = size_1 + size_2 - (firstLowBlock * CDSECTOR_SIZE - 12); - } - model = (MODEL *)(specmallocptr + 12); + lowOffset = cleanModelOfs + damModelOfs - (firstLowBlock * CDSECTOR_SIZE - 12); + damOffset = cleanModelOfs - (firstDamBlock * CDSECTOR_SIZE - 12); + + // mark the memory start for all special models + modelMemory = (int*)specmallocptr; - if (specBlocksToLoad == 7-lastCleanBlock) - { #if USE_PC_FILESYSTEM if (gContentOverride) { - // [A] vertices - LoadCarModelFromFile((char*)model, MissionHeader->residentModels[4], CAR_MODEL_CLEAN); + char* mem; + + if (mem = LoadCarModelFromFile((char*)modelMemory, MissionHeader->residentModels[4], CAR_MODEL_CLEAN)) + { + gCarCleanModelPtr[4] = (MODEL*)modelMemory; + + model = GetCarModel(mem, (char**)&modelMemory, 1); + + whichCP = baseSpecCP; + buildNewCarFromModel(&NewCarModel[4], model, 0); + + specBlocksToLoad = 0; + specialState = SpecSpool_CleanModel; + } + + if (mem = LoadCarModelFromFile((char*)modelMemory, MissionHeader->residentModels[4], CAR_MODEL_DAMAGED)) + { + gCarDamModelPtr[4] = (MODEL*)modelMemory; + + model = GetCarModel(mem, (char**)&modelMemory, 0); + + specBlocksToLoad = 0; + specialState = SpecSpool_DamagedModel; + } + + if (mem = LoadCarModelFromFile((char*)modelMemory, MissionHeader->residentModels[4], CAR_MODEL_LOWDETAIL)) + { + gCarLowModelPtr[4] = (MODEL*)modelMemory; + + model = GetCarModel(mem, (char**)&modelMemory, 1); + + buildNewCarFromModel(&NewLowCarModel[4], model, 0); + + specBlocksToLoad = 0; + specialState = SpecSpool_LowModel; + } } #endif - + } + + // Spooled clean car models are guaranteed to be split in two instances + // this one is polygons only + if (specBlocksToLoad == 7 - lastCleanBlock) + { + specBlocksToLoad = 0; + + model = (MODEL*)(specmallocptr + sizeof(int) * 3); + model->vertices += (int)model; model->poly_block += (int)model; model->normals += (int)model; model->point_normals += (int)model; whichCP = baseSpecCP; - buildNewCarFromModel(&NewCarModel[4], model, 0); - specBlocksToLoad = 0; } if (quickSpool != 1) @@ -1955,14 +1967,6 @@ void LowSpooled(void) if (specBlocksToLoad == 0) { model = (MODEL *)(specmallocptr + lowOffset); - -#if USE_PC_FILESYSTEM - if (gContentOverride) - { - // [A] vertices - LoadCarModelFromFile((char*)model, MissionHeader->residentModels[4], CAR_MODEL_LOWDETAIL); - } -#endif model->vertices += (int)model; model->normals += (int)model; @@ -1983,51 +1987,31 @@ void Tada(void) int spec_tpage; RECT16 tpagerect; - if (specialState == 2) + switch (specialState) { - tpagerect.w = 64; - tpagerect.h = 16; + case SpecSpool_Tpage1: + case SpecSpool_Tpage2: + spec_tpage = specialSlot + (specialState - 1); + tpagerect.w = 64; + tpagerect.h = 16; - spec_tpage = specialSlot + 1; - tpagerect.x = tpagepos[spec_tpage].x; - tpagerect.y = tpagepos[spec_tpage].y + (15 - specBlocksToLoad) * 16; + tpagerect.x = tpagepos[spec_tpage].x; + tpagerect.y = tpagepos[spec_tpage].y + (15 - specBlocksToLoad) * 16; - if (specBlocksToLoad == 15) - update_slotinfo(specTpages[GameLevel][specspooldata[2]-1], specialSlot, &tpagerect); - - LoadImage(&tpagerect, (u_long *)specLoadBuffer); - } - else - { - if (specialState > 2) - { - if (specialState != 4) - return; + if (specBlocksToLoad == 15) + update_slotinfo(specTpages[GameLevel][specSpoolModelIndex-1], specialSlot, &tpagerect); + LoadImage(&tpagerect, (u_long*)specLoadBuffer); + break; + case SpecSpool_Cluts: + SpecClutsSpooled(); + break; + case SpecSpool_Cosmetics: SetupSpecCosmetics(specLoadBuffer); SetupSpecDenting(specLoadBuffer + sizeof(CAR_COSMETICS)); - - if (quickSpool == 1) - return; - - DrawSyncCallback(SpecialStartNextBlock); - return; - } - - if (specialState != 1) - return; - - spec_tpage = specialSlot; - tpagerect.w = 64; - tpagerect.h = 16; - - tpagerect.x = tpagepos[spec_tpage].x; - tpagerect.y = tpagepos[spec_tpage].y + (15 - specBlocksToLoad) * 16; - - if (specBlocksToLoad == 15) - update_slotinfo(specTpages[GameLevel][specspooldata[2]-1], spec_tpage, &tpagerect); - - LoadImage(&tpagerect, (u_long *)specLoadBuffer); + break; + default: + printWarning("Tada: incorrect special state %d", specialState); } if (quickSpool != 1) @@ -2042,79 +2026,78 @@ void SpecialStartNextBlock(void) spooledFuncPtr spoolFunc; loadaddr = specLoadBuffer; - DrawSyncCallback(0); + DrawSyncCallback(NULL); // if previous state blocks are fully loaded, start next state if (specBlocksToLoad == 0) { + specialState++; switch (specialState) { - case 0: - case 1: + case SpecSpool_Tpage1: + case SpecSpool_Tpage2: specBlocksToLoad = 16; break; - case 2: - case 3: + case SpecSpool_Cluts: + case SpecSpool_Cosmetics: specBlocksToLoad = 1; break; - case 4: + case SpecSpool_CleanSpool: specBlocksToLoad = 7; break; - case 5: - case 8: + case SpecSpool_LowSpool: + case SpecSpool_LowModel: specBlocksToLoad = lengthLowBlock; break; - case 6: + case SpecSpool_CleanModel: specBlocksToLoad = lastCleanBlock; break; - case 7: + case SpecSpool_DamagedModel: specBlocksToLoad = lengthDamBlock; break; default: specBlocksToLoad = 0; } - - specialState++; } - fileSector = 0x1400 + (citystart[GameLevel] - SpoolLumpOffset / CDSECTOR_SIZE) + (specspooldata[2]-1) * 42; + fileSector = 0x1400 + (citystart[GameLevel] - SpoolLumpOffset / CDSECTOR_SIZE) + (specSpoolModelIndex-1) * 42; switch (specialState) { - case 1: + case SpecSpool_Tpage1: spoolFunc = Tada; fileSector += (17 - specBlocksToLoad); break; - case 2: + case SpecSpool_Tpage2: spoolFunc = Tada; fileSector += (33 - specBlocksToLoad); break; - case 3: - spoolFunc = SpecClutsSpooled; + case SpecSpool_Cluts: + spoolFunc = Tada; break; - case 4: + case SpecSpool_Cosmetics: fileSector += 33; spoolFunc = Tada; break; - case 5: + case SpecSpool_CleanSpool: spoolFunc = CleanSpooled; fileSector += (41 - specBlocksToLoad); loadaddr = specmallocptr + (7 - specBlocksToLoad) * CDSECTOR_SIZE; break; - case 6: + case SpecSpool_LowSpool: spoolFunc = LowSpooled; fileSector += ((firstLowBlock + lengthLowBlock) - specBlocksToLoad) + 34; loadaddr = specmallocptr + (lengthLowBlock - specBlocksToLoad) * CDSECTOR_SIZE; break; - case 7: + case SpecSpool_CleanModel: spoolFunc = CleanModelSpooled; fileSector += (lastCleanBlock - specBlocksToLoad) + 34; break; - case 8: + case SpecSpool_DamagedModel: spoolFunc = DamagedModelSpooled; fileSector += ((firstDamBlock + lengthDamBlock) - specBlocksToLoad) + 34; break; - case 9: + case SpecSpool_LowModel: spoolFunc = LowModelSpooled; fileSector += ((firstLowBlock + lengthLowBlock) - specBlocksToLoad) + 34; break; @@ -2122,7 +2105,7 @@ void SpecialStartNextBlock(void) if (quickSpool == 1) { specModelValid = 1; - specialState = 0; + specialState = SpecSpool_None; quickSpool = 0; specSpoolComplete = 0; return; @@ -2132,7 +2115,7 @@ void SpecialStartNextBlock(void) return; } - RequestSpool(3, 0, fileSector, 1, loadaddr, spoolFunc); + RequestSpool(SPOOL_MISC, 0, fileSector, 1, loadaddr, spoolFunc); specBlocksToLoad--; } @@ -2156,7 +2139,7 @@ void CheckSpecialSpool(void) } specModelValid = 1; - specialState = 0; + specialState = SpecSpool_None; specBlocksToLoad = 0; specSpoolComplete = 0; startSpecSpool = -1; @@ -2164,6 +2147,7 @@ void CheckSpecialSpool(void) if (allowSpecSpooling && specSpoolComplete != 1 && + specialState == SpecSpool_None && GameType != GAME_PURSUIT && LoadedArea != -1 && SpecialByRegion[GameLevel][LoadedArea] != MissionHeader->residentModels[4]-7) @@ -2187,11 +2171,10 @@ void CheckSpecialSpool(void) gCarCleanModelPtr[4] = NULL; gCarLowModelPtr[4] = NULL; - specspooldata[2] = SpecialByRegion[GameLevel][LoadedArea]; + specSpoolModelIndex = SpecialByRegion[GameLevel][LoadedArea]; MissionHeader->residentModels[4] = SpecialByRegion[GameLevel][LoadedArea] + 7; - if (specialState == 0) - SpecialStartNextBlock(); + SpecialStartNextBlock(); } } @@ -2199,14 +2182,14 @@ void CheckSpecialSpool(void) void QuickSpoolSpecial(void) { quickSpool = 1; - specialState = 0; + specialState = SpecSpool_None; specBlocksToLoad = 0; gCarCleanModelPtr[4] = NULL; gCarDamModelPtr[4] = NULL; gCarLowModelPtr[4] = NULL; - specspooldata[2] = MissionHeader->residentModels[4]-7; + specSpoolModelIndex = MissionHeader->residentModels[4]-7; do { SpoolSYNC(); @@ -2226,11 +2209,11 @@ void PrepareSecretCar(void) gCarCleanModelPtr[4] = NULL; gCarLowModelPtr[4] = NULL; - specspooldata[2] = 5; + specSpoolModelIndex = 5; specModelValid = 0; startSpecSpool = CameraCnt; MissionHeader->residentModels[4] = 12; - specialState = 0; + specialState = SpecSpool_None; specBlocksToLoad = 0; SpecialStartNextBlock(); } @@ -2327,7 +2310,7 @@ void InitSpecSpool(void) #endif specModelValid = 1; - specialState = 0; + specialState = SpecSpool_None; specBlocksToLoad = 0; quickSpool = 0; specSpoolComplete = 0; diff --git a/src_rebuild/Game/C/sysclock.c b/src_rebuild/Game/C/sysclock.c index dd5799e3d..5f299eed7 100644 --- a/src_rebuild/Game/C/sysclock.c +++ b/src_rebuild/Game/C/sysclock.c @@ -8,12 +8,12 @@ int timerflag = 0; int timerhz; int reentryflag; -long tickset; -long tickval; -long ticks; -long libticks = 0; -long g_currentthread = 1; -long timerevent; +int tickset; +int tickval; +int ticks; +int libticks = 0; +int g_currentthread = 1; +int timerevent; void resettick() { @@ -30,7 +30,7 @@ void savegp(long* st) { } -void restoregp(long gp) +void restoregp(int gp) { } @@ -64,9 +64,9 @@ long tmrint(void) } /* -void timedwait(long ms) +void timedwait(int ms) { - long oldTick; + int oldTick; oldTick = gettick(); @@ -109,9 +109,9 @@ void inittimer(int hz) { if (hz == 0) hz = 100; - +#ifdef PSX EnterCriticalSection(); - +#endif if (timerflag == 0) { memset((u_char*)&tmrsub, 0, sizeof(tmrsub)); @@ -138,7 +138,9 @@ void inittimer(int hz) SetRCnt(RCntCNT2, (4233600 / hz), EvSpTRAP); StartRCnt(RCntCNT2); +#ifdef PSX ExitCriticalSection(); +#endif resettick(); //addexit(restoretimer); diff --git a/src_rebuild/Game/C/system.c b/src_rebuild/Game/C/system.c index ac185f3d6..505d5c563 100644 --- a/src_rebuild/Game/C/system.c +++ b/src_rebuild/Game/C/system.c @@ -58,7 +58,7 @@ volatile char* _replay_buffer = NULL; // 0x1FABBC #if defined(USE_CRT_MALLOC) -char* mallocptr = NULL; +volatile char* mallocptr = NULL; volatile char* malloctab = NULL; void* g_dynamicAllocs[1024] = { 0 }; @@ -627,7 +627,6 @@ void DisableDisplay(void) SetDispMask(0); } -int DoNotSwap = 0; DB* MPlast[2]; DB* MPcurrent[2]; @@ -643,13 +642,7 @@ void SwapDrawBuffers(void) { DrawSync(0); - if (DoNotSwap == 0) - { - PutDispEnv(¤t->disp); - } - - DoNotSwap = 0; - + PutDispEnv(¤t->disp); PutDrawEnv(¤t->draw); DrawOTag((u_long*)(current->ot + OTSIZE-1)); @@ -664,7 +657,8 @@ void SwapDrawBuffers(void) last = &MPBuff[0][0]; } - ClearCurrentDrawBuffers(); + ClearOTagR((u_long*)current->ot, OTSIZE); + current->primptr = current->primtab; } // [D] [T] @@ -680,17 +674,17 @@ void SwapDrawBuffers2(int player) } PutDrawEnv(¤t->draw); - DrawOTag((u_long*)(current->ot + OTSIZE - 1)); + DrawOTag((u_long*)(current->ot + OTSIZE-1)); if (player == 1) { toggle = FrameCnt & 1; // [A] i guess it should work as intended - MPcurrent[0] = &MPBuff[0][-toggle + 1]; + MPcurrent[0] = &MPBuff[0][1-toggle]; MPlast[0] = &MPBuff[0][toggle]; - MPcurrent[1] = &MPBuff[1][-toggle + 1]; + MPcurrent[1] = &MPBuff[1][1-toggle]; MPlast[1] = &MPBuff[1][toggle]; } @@ -734,6 +728,8 @@ void SetupDrawBuffers(void) MPBuff[0][1].disp.screen.h = SCREEN_H; MPBuff[0][0].disp.screen.x = draw_mode.framex; MPBuff[0][1].disp.screen.x = draw_mode.framex; + MPBuff[0][0].disp.screen.y = draw_mode.framey; + MPBuff[0][1].disp.screen.y = draw_mode.framey; if (NoPlayerControl == 0) SetupDrawBufferData(NumPlayers); @@ -795,26 +791,26 @@ void SetupDrawBufferData(int num_players) { for (j = 0; j < num_players; j++) { - u_long* otpt; + u_int* otpt; u_char* primpt; if (toggle) { - otpt = (u_long*)_OT2; + otpt = (u_int*)_OT2; primpt = (u_char*)_primTab2; // _primTab1 + PRIMTAB_SIZE } else { - otpt = (u_long*)_OT1; + otpt = (u_int*)_OT1; primpt = (u_char*)_primTab1; } toggle ^= 1; InitaliseDrawEnv(MPBuff[j], x[j], y[j], 320, height); - MPBuff[i][j].primtab = (char*)primpt; - MPBuff[i][j].primptr = (char*)primpt; - MPBuff[i][j].ot = (OTTYPE*)otpt; + MPBuff[j][i].primtab = (char*)primpt; + MPBuff[j][i].primptr = (char*)primpt; + MPBuff[j][i].ot = (OTTYPE*)otpt; } } @@ -830,17 +826,8 @@ void SetupDrawBufferData(int num_players) // [D] [T] void InitaliseDrawEnv(DB* pBuff, int x, int y, int w, int h) { -#ifdef PSX -#define DB1 pBuff[0] -#define DB2 pBuff[1] -#else -// on PsyX we have to prevent flicker -#define DB1 pBuff[1] -#define DB2 pBuff[0] -#endif - - SetDefDrawEnv(&DB1.draw, x, y, w, h); - SetDefDrawEnv(&DB2.draw, x, y + 256, w, h); + SetDefDrawEnv(&pBuff[0].draw, x, y, w, h); + SetDefDrawEnv(&pBuff[1].draw, x, y + 256, w, h); pBuff[0].id = 0; pBuff[0].draw.dfe = 1; @@ -848,7 +835,8 @@ void InitaliseDrawEnv(DB* pBuff, int x, int y, int w, int h) pBuff[1].id = 1; pBuff[1].draw.dfe = 1; -#ifdef USE_PGXP +#if USE_PGXP + // extend clip rectangles for widscreen if(NumPlayers == 2) { pBuff[0].draw.clip.x -= 256; diff --git a/src_rebuild/Game/C/system.h b/src_rebuild/Game/C/system.h index 5ab055e34..477c4b77d 100644 --- a/src_rebuild/Game/C/system.h +++ b/src_rebuild/Game/C/system.h @@ -59,7 +59,7 @@ extern void sys_freeall(); #define D_TEMPALLOC(size) sys_tempalloc(size) #ifdef _DEBUG -#define D_MALLOC(size) sys_malloc(size, __FUNCTION__, __LINE__) +#define D_MALLOC(size) sys_malloc(size, FUNCNAME, __LINE__) #else #define D_MALLOC(size) sys_malloc(size, NULL, 0) @@ -85,13 +85,13 @@ extern void sys_freeall(); #define D_MALLOC_END() \ D_TEMPFREE();\ if(mallocptr > _oldmalloc)\ - DMalloc_DebugPrint("malloc(%d) in %s, line %d. Malloc usage: %d\n", mallocptr-_oldmalloc, __FUNCTION__, __LINE__, (mallocptr-malloctab));\ + DMalloc_DebugPrint("malloc(%d) in %s, line %d. Malloc usage: %d\n", mallocptr-_oldmalloc, FUNCNAME, __LINE__, (mallocptr-malloctab));\ } // D_MALLOC_BEGIN block #else #define D_MALLOC_END() \ D_TEMPFREE();\ if(mallocptr > _oldmalloc)\ - DMalloc_DebugPrint("malloc(%d) in " __FUNCTION__ ", line %d. Malloc usage: %d\n", mallocptr-_oldmalloc, __LINE__, (mallocptr-malloctab));\ + DMalloc_DebugPrint("malloc(%d) in " FUNCNAME ", line %d. Malloc usage: %d\n", mallocptr-_oldmalloc, __LINE__, (mallocptr-malloctab));\ } // D_MALLOC_BEGIN block #endif @@ -139,7 +139,7 @@ extern DRAW_MODE draw_mode_ntsc; #define OTSIZE 0x2000 #endif -#ifdef USE_EXTENDED_PRIM_POINTERS +#if USE_EXTENDED_PRIM_POINTERS # define PRIMTAB_SIZE 0x50000 #else # define PRIMTAB_SIZE 0x1e000 diff --git a/src_rebuild/Game/C/targets.c b/src_rebuild/Game/C/targets.c index 952c94683..c8f0722e6 100644 --- a/src_rebuild/Game/C/targets.c +++ b/src_rebuild/Game/C/targets.c @@ -281,7 +281,7 @@ void DrawStopZone(VECTOR *pPosition) SVECTOR temp; long p; long flag; - long sz; + int sz; int flash; int height; @@ -326,7 +326,7 @@ void DrawStopZone(VECTOR *pPosition) RotTransPers(&temp, pOut, &p, &flag); - if (pOut == (long*)&pPoly->x0) + if (pOut == (long*)&pPoly->x0) pOut = (long*)&pPoly->x1; else if (pOut == (long*)&pPoly->x1) pOut = (long*)&pPoly->x2; @@ -347,7 +347,7 @@ void DrawStopZone(VECTOR *pPosition) *(ushort*)&pPoly->u2 = *(ushort*)&light_texture.coords.u2; *(ushort*)&pPoly->u3 = *(ushort*)&light_texture.coords.u3; - if (gTimeOfDay == 3) + if (gTimeOfDay == TIME_NIGHT) pPoly->tpage = light_texture.tpageid | 0x20; else pPoly->tpage = light_texture.tpageid | 0x40; diff --git a/src_rebuild/Game/C/texture.c b/src_rebuild/Game/C/texture.c index 36658873f..bdc4e8699 100644 --- a/src_rebuild/Game/C/texture.c +++ b/src_rebuild/Game/C/texture.c @@ -193,7 +193,7 @@ void LoadTPageFromTIMs(int tpage2send) tmptpage.w = timData->width; tmptpage.h = timData->height; - LoadImage(&tmptpage, (u_long *)((char*)timData + sizeof(TIMIMAGEHDR))); + LoadImage(&tmptpage, (u_long*)((char*)timData + sizeof(TIMIMAGEHDR))); // get through all it's CLUTs // and replace @@ -225,7 +225,7 @@ void LoadTPageFromTIMs(int tpage2send) tmpclut.w = 16; tmpclut.h = 1; - LoadImage(&tmpclut, (u_long *)((char*)timClut + sizeof(TIMIMAGEHDR) + j * 32)); + LoadImage(&tmpclut, (u_long*)((char*)timClut + sizeof(TIMIMAGEHDR) + j * 32)); } } } @@ -245,7 +245,7 @@ int LoadTPageAndCluts(RECT16 *tpage, RECT16 *cluts, int tpage2send, char *tpagea for (i = 0; i < npalettes; i++) { - LoadImage(cluts, (u_long *)tpageaddress); + LoadImage(cluts, (u_long*)tpageaddress); tpageaddress += 32; texture_cluts[tpage2send][i] = GetClut(cluts->x, cluts->y); @@ -259,7 +259,7 @@ int LoadTPageAndCluts(RECT16 *tpage, RECT16 *cluts, int tpage2send, char *tpagea temptpage.h = 256; decomp_asm((char*)_other_buffer, tpageaddress); - LoadImage(&temptpage, (u_long *)_other_buffer); + LoadImage(&temptpage, (u_long*)_other_buffer); texture_pages[tpage2send] = GetTPage(0, 0, tpage->x, tpage->y); IncrementTPageNum(tpage); diff --git a/src_rebuild/Game/C/tile.c b/src_rebuild/Game/C/tile.c index a655df4d5..3533ae8c4 100644 --- a/src_rebuild/Game/C/tile.c +++ b/src_rebuild/Game/C/tile.c @@ -8,6 +8,117 @@ #include "ASM/rndrasm.h" +#ifdef PSX +#pragma GCC optimization ("O3") +#endif + +#ifdef DYNAMIC_LIGHTING +void Tile1x1Lit(MODEL* model) +{ + int opz, Z; + int ofse; + PL_POLYFT4* polys; + int i; + u_char ptype; + POLY_GT4* prims; + SVECTOR* srcVerts; + + srcVerts = (SVECTOR*)model->vertices; + polys = (PL_POLYFT4*)model->poly_block; + + // grass should be under pavements and other things + if ((model->shape_flags & SHAPE_FLAG_WATER) || (model->flags2 & MODEL_FLAG_GRASS)) + ofse = 229; + else + ofse = 133; + +#if USE_PGXP + PGXP_SetZOffsetScale(0.0f, ofse > 200 ? 1.005f : 0.995f); +#endif + + i = model->num_polys; + while (i-- > 0) + { + // iterate through polygons + // with skipping + ptype = polys->id & 0x1f; + + // perform transform + gte_ldv3(&srcVerts[polys->v0], &srcVerts[polys->v1], &srcVerts[polys->v3]); + gte_rtpt(); + + // get culling value + gte_nclip(); + gte_stopz(&opz); + + if (opz > 0) + { + prims = (POLY_GT4*)plotContext.primptr; + + *(uint*)&prims->r0 = plotContext.colour; + *(uint*)&prims->r1 = plotContext.colour; + *(uint*)&prims->r2 = plotContext.colour; + *(uint*)&prims->r3 = plotContext.colour; + setPolyGT4(prims); + + // retrieve first three verts + gte_stsxy3(&prims->x0, &prims->x1, &prims->x2); + + // translate 4th vert and get OT Z value + gte_ldv0(&srcVerts[polys->v2]); + gte_rtps(); + gte_avsz4(); + + gte_stotz(&Z); + + gte_stsxy(&prims->x3); + + prims->tpage = (*plotContext.ptexture_pages)[polys->texture_set]; + prims->clut = (*plotContext.ptexture_cluts)[polys->texture_set][polys->texture_id]; + + *(ushort*)&prims->u0 = *(ushort*)&polys->uv0; + *(ushort*)&prims->u1 = *(ushort*)&polys->uv1; + *(ushort*)&prims->u2 = *(ushort*)&polys->uv3; + *(ushort*)&prims->u3 = *(ushort*)&polys->uv2; + + SVECTOR tmpPos; + gte_ldv0(&srcVerts[polys->v0]); + gte_rtps(); + gte_stsv(&tmpPos); + GetDLightLevel(&tmpPos, (u_int*)&prims->r0); + + gte_ldv0(&srcVerts[polys->v1]); + gte_rtps(); + gte_stsv(&tmpPos); + GetDLightLevel(&tmpPos, (u_int*)&prims->r1); + + gte_ldv0(&srcVerts[polys->v3]); + gte_rtps(); + gte_stsv(&tmpPos); + GetDLightLevel(&tmpPos, (u_int*)&prims->r2); + + gte_ldv0(&srcVerts[polys->v2]); + gte_rtps(); + gte_stsv(&tmpPos); + GetDLightLevel(&tmpPos, (u_int*)&prims->r3); + + addPrim(plotContext.ot + (Z >> 1) + ofse, prims); + + plotContext.primptr += sizeof(POLY_GT4); + } + + polys = (PL_POLYFT4*)((char*)polys + plotContext.polySizes[ptype]); + } + +#if USE_PGXP + PGXP_SetZOffsetScale(0.0f, 1.0f); +#endif + + // done + plotContext.current->primptr = plotContext.primptr; +} +#endif + // [D] [T] [A] void Tile1x1(MODEL *model) { @@ -28,7 +139,7 @@ void Tile1x1(MODEL *model) else ofse = 133; -#ifdef USE_PGXP +#if USE_PGXP PGXP_SetZOffsetScale(0.0f, ofse > 200 ? 1.005f : 0.995f); #endif @@ -51,9 +162,9 @@ void Tile1x1(MODEL *model) { prims = (POLY_FT4*)plotContext.primptr; + *(uint*)&prims->r0 = plotContext.colour; setPolyFT4(prims); - *(ulong*)&prims->r0 = plotContext.colour; - + // retrieve first three verts gte_stsxy3(&prims->x0, &prims->x1, &prims->x2); @@ -82,7 +193,7 @@ void Tile1x1(MODEL *model) polys = (PL_POLYFT4*)((char*)polys + plotContext.polySizes[ptype]); } -#ifdef USE_PGXP +#if USE_PGXP PGXP_SetZOffsetScale(0.0f, 1.0f); #endif @@ -90,6 +201,19 @@ void Tile1x1(MODEL *model) plotContext.current->primptr = plotContext.primptr; } +// performs division by 3 or by 5 depending on t +inline int fst_div_lut_3_5(int x, int t) +{ + static int mul[] = { 171, 205 }; + return x * mul[t] >> (9 + t); +} + +inline int fst_div_3(int x) +{ + return x * 171 >> 9; +} + + // [D] [T] void DrawTILES(PACKED_CELL_OBJECT** tiles, int tile_amount) { @@ -101,13 +225,20 @@ void DrawTILES(PACKED_CELL_OBJECT** tiles, int tile_amount) if (gTimeOfDay > -1) { - if (gTimeOfDay < 3) + int combo = combointensity; + if (gTimeOfDay < TIME_NIGHT) { - plotContext.colour = combointensity & 0xffffffU | 0x2C000000; + plotContext.colour = combo & 0xffffffU | 0x2C000000; } - else if (gTimeOfDay == 3) + else if (gTimeOfDay == TIME_NIGHT) { - plotContext.colour = ((combointensity >> 16 & 255) / 3) << 16 | ((combointensity >> 8 & 255) / 3) << 8 | (combointensity & 255) / 3 | 0x2C000000U; +#ifdef DYNAMIC_LIGHTING + int t; + t = GameLevel == 2 && gEnableDlights == 1; + plotContext.colour = fst_div_lut_3_5(combo >> 16 & 255, t) << 16 | fst_div_lut_3_5(combo >> 8 & 255, t) << 8 | fst_div_lut_3_5(combo & 255, t) | 0x2C000000U; +#else + plotContext.colour = fst_div_3(combo >> 16 & 255) << 16 | fst_div_3(combo >> 8 & 255) << 8 | fst_div_3(combo & 255) | 0x2C000000U; +#endif } } @@ -116,7 +247,7 @@ void DrawTILES(PACKED_CELL_OBJECT** tiles, int tile_amount) if (gWeather - 1U < 2) { u_int col; - col = plotContext.colour >> 2 & 0x3f; + col = plotContext.colour >> 2 & 63; plotContext.colour = col * 0x30000 | col * 0x300 | col * 3 | 0x2C000000; } @@ -125,7 +256,6 @@ void DrawTILES(PACKED_CELL_OBJECT** tiles, int tile_amount) plotContext.primptr = current->primptr; plotContext.ptexture_pages = (ushort(*)[128])texture_pages; plotContext.ptexture_cluts = (ushort(*)[128][32])texture_cluts; - plotContext.lastTexInfo = 0x18273472; plotContext.flags = 0; plotContext.polySizes = PolySizes; @@ -135,20 +265,20 @@ void DrawTILES(PACKED_CELL_OBJECT** tiles, int tile_amount) { ppco = *tilePointers++; - plotContext.f4colourTable[6] = ppco->pos.vx; - plotContext.f4colourTable[7] = (ppco->pos.vy << 0x10) >> 0x11; - plotContext.f4colourTable[8] = ppco->pos.vz; + plotContext.scribble[0] = ppco->pos.vx; + plotContext.scribble[1] = (ppco->pos.vy << 0x10) >> 0x11; + plotContext.scribble[2] = ppco->pos.vz; yang = ppco->value & 0x3f; model_number = (ppco->value >> 6) | (ppco->pos.vy & 1) << 10; if (previous_matrix == yang) { - Z = Apply_InvCameraMatrixSetTrans((VECTOR_NOPAD *)(plotContext.f4colourTable + 6)); + Z = Apply_InvCameraMatrixSetTrans((VECTOR_NOPAD *)plotContext.scribble); } else { - Z = Apply_InvCameraMatrixAndSetMatrix((VECTOR_NOPAD *)(plotContext.f4colourTable + 6), &CompoundMatrix[previous_matrix = yang]); + Z = Apply_InvCameraMatrixAndSetMatrix((VECTOR_NOPAD *)plotContext.scribble, &CompoundMatrix[previous_matrix = yang]); } if (Z <= DRAW_LOD_DIST_HIGH) @@ -160,14 +290,18 @@ void DrawTILES(PACKED_CELL_OBJECT** tiles, int tile_amount) if (Z < 2000) TileNxN(pModel, 4, 75); - else + else TileNxN(pModel, 2, 35); } else { pModel = Z > DRAW_LOD_DIST_LOW ? pLodModels[model_number] : modelpointers[model_number]; +#ifdef DYNAMIC_LIGHTING + (gEnableDlights ? Tile1x1Lit : Tile1x1)(pModel); +#else Tile1x1(pModel); +#endif // DYNAMIC_LIGHTING } } current->primptr = plotContext.primptr; @@ -206,31 +340,31 @@ void makeMesh(MVERTEX(*VSP)[5][5], int m, int n) v4 = (*VSP)[0][2]; VecSubtract(&e1, &v2, &v1); // plane[1] - plane[0]; - e1.uv.s.u0 = (v2.uv.s.u0 - v1.uv.s.u0) / 2; - e1.uv.s.v0 = (v2.uv.s.v0 - v1.uv.s.v0) / 2; + e1.uv.s.u0 = (v2.uv.s.u0 - v1.uv.s.u0) >> 1; + e1.uv.s.v0 = (v2.uv.s.v0 - v1.uv.s.v0) >> 1; VecSubtract(&e2, &v3, &v4); // plane[2] - plane[3]; - e2.uv.s.u0 = (v3.uv.s.u0 - v4.uv.s.u0) / 2; - e2.uv.s.v0 = (v3.uv.s.v0 - v4.uv.s.v0) / 2; + e2.uv.s.u0 = (v3.uv.s.u0 - v4.uv.s.u0) >> 1; + e2.uv.s.v0 = (v3.uv.s.v0 - v4.uv.s.v0) >> 1; VecSubtract(&e3, &v4, &v1); // plane[3] - plane[0]; - e3.uv.s.u0 = (v4.uv.s.u0 - v1.uv.s.u0) / 2; - e3.uv.s.v0 = (v4.uv.s.v0 - v1.uv.s.v0) / 2; + e3.uv.s.u0 = (v4.uv.s.u0 - v1.uv.s.u0) >> 1; + e3.uv.s.v0 = (v4.uv.s.v0 - v1.uv.s.v0) >> 1; VecSubtract(&e4, &v3, &v2); // plane[2] - plane[1]; - e4.uv.s.u0 = (v3.uv.s.u0 - v2.uv.s.u0) / 2; - e4.uv.s.v0 = (v3.uv.s.v0 - v2.uv.s.v0) / 2; + e4.uv.s.u0 = (v3.uv.s.u0 - v2.uv.s.u0) >> 1; + e4.uv.s.v0 = (v3.uv.s.v0 - v2.uv.s.v0) >> 1; //----------- // half them all - SetVec(&e1, e1.vx / 2, e1.vy / 2, e1.vz / 2); + SetVec(&e1, e1.vx >> 1, e1.vy >> 1, e1.vz >> 1); - SetVec(&e2, e2.vx / 2, e2.vy / 2, e2.vz / 2); + SetVec(&e2, e2.vx >> 1, e2.vy >> 1, e2.vz >> 1); - SetVec(&e3, e3.vx / 2, e3.vy / 2, e3.vz / 2); + SetVec(&e3, e3.vx >> 1, e3.vy >> 1, e3.vz >> 1); - SetVec(&e4, e4.vx / 2, e4.vy / 2, e4.vz / 2); + SetVec(&e4, e4.vx >> 1, e4.vy >> 1, e4.vz >> 1); //----------- @@ -253,10 +387,10 @@ void makeMesh(MVERTEX(*VSP)[5][5], int m, int n) //----------- VecSubtract(&e5, &p2, &p1); // p2 - p1; - e5.uv.s.u0 = (p2.uv.s.u0 - p1.uv.s.u0) / 2; - e5.uv.s.v0 = (p2.uv.s.v0 - p1.uv.s.v0) / 2; + e5.uv.s.u0 = (p2.uv.s.u0 - p1.uv.s.u0) >> 1; + e5.uv.s.v0 = (p2.uv.s.v0 - p1.uv.s.v0) >> 1; - SetVec(&e5, e5.vx / 2, e5.vy / 2, e5.vz / 2); + SetVec(&e5, e5.vx >> 1, e5.vy >> 1, e5.vz >> 1); VecAdd(&p5, &e5, &p1); // e5 * 0.5f + p1; @@ -309,7 +443,7 @@ void drawMesh(MVERTEX(*VSP)[5][5], int m, int n, _pct *pc) for (int index = 0; index < numPolys; index++) { setPolyFT4(prim); - *(ulong*)&prim->r0 = pc->colour; // FIXME: semiTransparency support + *(uint*)&prim->r0 = pc->colour; // FIXME: semiTransparency support // test gte_ldv3(&(*VSP)[index][0], &(*VSP)[index][1], &(*VSP)[index][2]); @@ -336,7 +470,6 @@ void drawMesh(MVERTEX(*VSP)[5][5], int m, int n, _pct *pc) gte_ldv0(&(*VSP)[index][3]); gte_rtps(); - gte_stsxy(&prim->x3); *(ushort*)&prim->u0 = (*VSP)[index][0].uv.val; @@ -344,8 +477,8 @@ void drawMesh(MVERTEX(*VSP)[5][5], int m, int n, _pct *pc) *(ushort*)&prim->u2 = (*VSP)[index][2].uv.val; *(ushort*)&prim->u3 = (*VSP)[index][3].uv.val; - prim->clut = pc->clut >> 0x10; - prim->tpage = pc->tpage >> 0x10; + prim->clut = pc->clut; + prim->tpage = pc->tpage; addPrim(pc->ot + (z >> 1), prim); @@ -356,6 +489,102 @@ void drawMesh(MVERTEX(*VSP)[5][5], int m, int n, _pct *pc) pc->primptr = (char*)prim; } +#ifdef DYNAMIC_LIGHTING +void drawMeshLit(MVERTEX(*VSP)[5][5], int m, int n, _pct* pc) +{ + POLY_GT4* prim; + int z, opz; + + prim = (POLY_GT4*)pc->primptr; + + int numPolys = 4; + + if (n < 2) + numPolys = 1; + +#if 0 + // no need to subdivide! + if (g_pgxpZBuffer) + numPolys = 1; +#endif + + for (int index = 0; index < numPolys; index++) + { + setPolyGT4(prim); + + // test + gte_ldv3(&(*VSP)[index][0], &(*VSP)[index][1], &(*VSP)[index][2]); + gte_rtpt(); + gte_nclip(); + gte_stopz(&opz); + + gte_avsz3(); + + gte_stotz(&z); + + if (pc->flags & (PLOT_NO_CULL | PLOT_INV_CULL)) + { + if (pc->flags & PLOT_NO_CULL) + opz = 1; // no culling + else // PLOT_FRONT_CULL + opz = -opz; // front face + } + + if (opz > 0 && z > 5) + { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + + gte_ldv0(&(*VSP)[index][3]); + gte_rtps(); + + gte_stsxy(&prim->x3); + + *(uint*)&prim->r0 = plotContext.colour; + *(uint*)&prim->r1 = plotContext.colour; + *(uint*)&prim->r2 = plotContext.colour; + *(uint*)&prim->r3 = plotContext.colour; + + SVECTOR tmpPos; + gte_ldv0(&(*VSP)[index][0]); + gte_rtps(); + gte_stsv(&tmpPos); + GetDLightLevel(&tmpPos, (u_int*)&prim->r0); + + gte_ldv0(&(*VSP)[index][1]); + gte_rtps(); + gte_stsv(&tmpPos); + GetDLightLevel(&tmpPos, (u_int*)&prim->r1); + + gte_ldv0(&(*VSP)[index][2]); + gte_rtps(); + gte_stsv(&tmpPos); + GetDLightLevel(&tmpPos, (u_int*)&prim->r2); + + gte_ldv0(&(*VSP)[index][3]); + gte_rtps(); + gte_stsv(&tmpPos); + GetDLightLevel(&tmpPos, (u_int*)&prim->r3); + + setPolyGT4(prim); + + * (ushort*)&prim->u0 = (*VSP)[index][0].uv.val; + *(ushort*)&prim->u1 = (*VSP)[index][1].uv.val; + *(ushort*)&prim->u2 = (*VSP)[index][2].uv.val; + *(ushort*)&prim->u3 = (*VSP)[index][3].uv.val; + + prim->clut = pc->clut; + prim->tpage = pc->tpage; + + addPrim(pc->ot + (z >> 1), prim); + + prim++; + } + } + + pc->primptr = (char*)prim; +} +#endif // DYNAMIC_LIGHTING + // [A] custom implemented function void SubdivNxM(char *polys, int n, int m, int ofse) { @@ -365,8 +594,8 @@ void SubdivNxM(char *polys, int n, int m, int ofse) POLYFT4* pft4 = (POLYFT4*)polys; - plotContext.clut = (u_int)(*plotContext.ptexture_cluts)[pft4->texture_set][pft4->texture_id] << 0x10; - plotContext.tpage = (u_int)(*plotContext.ptexture_pages)[pft4->texture_set] << 0x10; + plotContext.clut = (u_int)(*plotContext.ptexture_cluts)[pft4->texture_set][pft4->texture_id]; + plotContext.tpage = (u_int)(*plotContext.ptexture_pages)[pft4->texture_set]; copyVector(&subdivVerts[0][0], &verts[pft4->v0]); subdivVerts[0][0].uv.val = *(ushort*)&pft4->uv0; @@ -383,7 +612,11 @@ void SubdivNxM(char *polys, int n, int m, int ofse) plotContext.ot += ofse; makeMesh((MVERTEX(*)[5][5])subdivVerts, m, n); +#ifdef DYNAMIC_LIGHTING + (gEnableDlights ? drawMeshLit : drawMesh)((MVERTEX(*)[5][5])subdivVerts, m, n, &plotContext); +#else drawMesh((MVERTEX(*)[5][5])subdivVerts, m, n, &plotContext); +#endif plotContext.ot -= ofse; } @@ -419,7 +652,7 @@ void TileNxN(MODEL *model, int levels, int Dofse) ttype = 0; while (i--) { -#ifdef USE_PGXP +#if USE_PGXP switch (ttype) { case 0: @@ -443,7 +676,7 @@ void TileNxN(MODEL *model, int levels, int Dofse) polys += plotContext.polySizes[*polys]; } -#ifdef USE_PGXP +#if USE_PGXP PGXP_SetZOffsetScale(0.0f, 1.0f); #endif } diff --git a/src_rebuild/Game/C/wheelforces.c b/src_rebuild/Game/C/wheelforces.c index 1be49ff73..f6abdb75e 100644 --- a/src_rebuild/Game/C/wheelforces.c +++ b/src_rebuild/Game/C/wheelforces.c @@ -12,6 +12,7 @@ #include "sound.h" #include "glaunch.h" #include "system.h" +#include "cutscene.h" #define GRAVITY_FORCE (-7456) // D1 has -10922 @@ -199,21 +200,18 @@ void AddWheelForcesDriver1(CAR_DATA* cp, CAR_LOCALS* cl) int chan; WHEEL* wheel; int friction_coef; - int oldSpeed; - int wheelspd; - LONGVECTOR4 wheelPos; - LONGVECTOR4 surfacePoint; - LONGVECTOR4 surfaceNormal; + int oldSpeed, wheelspd; + LONGVECTOR4 wheelPos, surfacePoint, surfaceNormal; VECTOR force; LONGVECTOR4 pointVel; - int frontFS; - int rearFS; + int frontFS, rearFS; sdPlane* SurfacePtr; int i; int cdx, cdz; int sdx, sdz; CAR_COSMETICS* car_cos; int player_id; + int oldCutRoughness; oldSpeed = cp->hd.speed * 3 >> 1; @@ -232,6 +230,7 @@ void AddWheelForcesDriver1(CAR_DATA* cp, CAR_LOCALS* cl) player_id = GetPlayerId(cp); car_cos = &car_cosmetics[cp->ap.model]; + oldCutRoughness = gInGameCutsceneActive && gCurrentMissionNumber == 23 && gInGameCutsceneID == 0; // [A] hack moved here GetFrictionScalesDriver1(cp, cl, &frontFS, &rearFS); cp->hd.front_vel = 0; @@ -248,9 +247,23 @@ void AddWheelForcesDriver1(CAR_DATA* cp, CAR_LOCALS* cl) gte_rtv0tr(); gte_stlvnl(wheelPos); - newCompression = FindSurfaceD2((VECTOR*)&wheelPos, (VECTOR*)&surfaceNormal, (VECTOR*)&surfacePoint, &SurfacePtr); + FindSurfaceD2((VECTOR*)&wheelPos, (VECTOR*)&surfaceNormal, (VECTOR*)&surfacePoint, &SurfacePtr); - friction_coef = (newCompression * (32400 - wetness) >> 15) + 500; + if (SurfacePtr && SurfacePtr->surface == SURF_GRASS) + { + int roughness; + roughness = RSIN((surfacePoint[0] + surfacePoint[2]) * 2) >> 8; + + surfacePoint[1] += oldCutRoughness ? (roughness >> 1) : (roughness / 3); + + forcefac = 2048; + } + else + { + forcefac = 4096; + } + + friction_coef = (forcefac * (32400 - wetness) >> 15) + 500; if (SurfacePtr != NULL) wheel->onGrass = SurfacePtr->surface == SURF_GRASS; @@ -603,6 +616,11 @@ void StepOneCar(CAR_DATA* cp) FindSurfaceD2((VECTOR*)pointPos, (VECTOR*)surfaceNormal, (VECTOR*)&surfacePoint, &SurfacePtr); + if (SurfacePtr && SurfacePtr->surface == SURF_GRASS) + { + surfacePoint[1] += (RSIN((surfacePoint[0] + surfacePoint[2]) * 2) >> 8) / 3; + } + if ((surfacePoint[1] - pointPos[1]) - 1U < 799) { int newLift; diff --git a/src_rebuild/Game/C/xaplay.c b/src_rebuild/Game/C/xaplay.c index 9edf613f1..c8e972172 100644 --- a/src_rebuild/Game/C/xaplay.c +++ b/src_rebuild/Game/C/xaplay.c @@ -50,7 +50,7 @@ struct XA_TRACK XA_TRACK XAMissionMessages[4]; -static unsigned long finished_count = 0; +static unsigned int finished_count = 0; static int gPlaying = 0; unsigned short gChannel = 0; static int xa_prepared = 0; @@ -123,7 +123,7 @@ int gNumXASubtitles = 0; int gXASubtitleTime = 0; int gXASubtitlePauseTime = 0; -void PrintXASubtitles() +void PrintXASubtitles(int yPos) { if (gSubtitles == 0 || pauseflag) return; @@ -144,7 +144,7 @@ void PrintXASubtitles() if(curTime >= subStartFrame && curTime <= subEndFrame) { SetTextColour(120, 120, 120); - PrintStringCentred(sub->text, 200); + PrintStringCentred(sub->text, yPos); } } } diff --git a/src_rebuild/Game/C/xaplay.h b/src_rebuild/Game/C/xaplay.h index effc1cb1a..fee156d96 100644 --- a/src_rebuild/Game/C/xaplay.h +++ b/src_rebuild/Game/C/xaplay.h @@ -1,7 +1,7 @@ #ifndef XAPLAY_H #define XAPLAY_H -extern void PrintXASubtitles(); +extern void PrintXASubtitles(int yPos); extern void GetMissionXAData(int number); // 0x00082EC0 extern void GetXAData(int number); // 0x00082D60 diff --git a/src_rebuild/Game/C/xmplay.c b/src_rebuild/Game/C/xmplay.c index 8b7399c65..c0d95611a 100644 --- a/src_rebuild/Game/C/xmplay.c +++ b/src_rebuild/Game/C/xmplay.c @@ -20,7 +20,7 @@ XMPLAY.C SpuVoiceAttr xm_g_s_attr; /* Structure for individual voice attributes*/ SpuVoiceAttr uxm_g_s_attr; /* Structure for individual voice attributes*/ SpuReverbAttr xm_r_attr; /* Structure for reverb */ -unsigned long xm_l_vag_spu_addr[8][128]; /* Address in memory for first sound file */ +unsigned int xm_l_vag_spu_addr[8][128]; /* Address in memory for first sound file */ /**** XM SPECIFIC ****/ @@ -232,7 +232,7 @@ short Interpolate(short p, short p1, short p2, short v1, short v2) dp = p2 - p1; di = p - p1; - return v1 + ((long)(di*dv) / dp); + return v1 + ((int)(di*dv) / dp); } @@ -423,7 +423,7 @@ int InitXMData(u_char *mpp, int XM_ID, int S3MPan) int a; int c; - u_long b; + u_int b; u_short b2; @@ -456,9 +456,9 @@ int InitXMData(u_char *mpp, int XM_ID, int S3MPan) for (t = 0; t < c; t++) { - mhu->JAP_PAT_ADDR[t] = (u_long*)(mpp + a); /* Store Pattern Addr's */ + mhu->JAP_PAT_ADDR[t] = (u_int*)(mpp + a); /* Store Pattern Addr's */ b = GetLong(mpp + a); - mhu->JAP_PAT_ADDR2[t] = (u_long*)(mpp + a + b); /* Store Pattern Addr's */ + mhu->JAP_PAT_ADDR2[t] = (u_int*)(mpp + a + b); /* Store Pattern Addr's */ b2 = getWord(mpp + a + 7); b += b2; a += b; @@ -471,16 +471,16 @@ int InitXMData(u_char *mpp, int XM_ID, int S3MPan) b2 = getWord(mpp + a + 27); if (b2 != 0) { - mhu->JAP_SampAddr[t] = (u_long*)(mpp + 29 + a); - mhu->JAP_SampHdrAddr[t] = (u_long*)(mpp + b + a); + mhu->JAP_SampAddr[t] = (u_int*)(mpp + 29 + a); + mhu->JAP_SampHdrAddr[t] = (u_int*)(mpp + b + a); b += (40 * b2); } else { - mhu->JAP_SampAddr[t] = (u_long*)0xcdcdcdcd; //(u_long*)(mpp+29+a); - mhu->JAP_SampHdrAddr[t] = (u_long*)0x01234567; //(u_long*)(mpp+b+a); + mhu->JAP_SampAddr[t] = (u_int*)0xcdcdcdcd; //(u_int *)(mpp+29+a); + mhu->JAP_SampHdrAddr[t] = (u_int*)0x01234567; //(u_int *)(mpp+b+a); } - mhu->JAP_InstrumentOffset[t] = (u_long*)(mpp + a); /* Store Instrument Addr's*/ + mhu->JAP_InstrumentOffset[t] = (u_int*)(mpp + a); /* Store Instrument Addr's*/ a += b; } return(mhu->XMPSXChannels); @@ -489,16 +489,16 @@ int InitXMData(u_char *mpp, int XM_ID, int S3MPan) /***************************************************************************** GetLong - Returns a long from given address + Returns a int from given address *****************************************************************************/ -u_long GetLong(u_char *mpp) +u_int GetLong(u_char *mpp) { u_char a; u_char b; u_char c; u_char d; - u_long e; + u_int e; a = *(mpp); b = *(mpp + 1); @@ -1306,7 +1306,7 @@ SetInstr void SetInstr(u_char inst) { - u_long *j; + u_int *j; u_char j2; short ddd; @@ -1344,7 +1344,7 @@ void SetInstr(u_char inst) */ j = mh->JAP_SampHdrAddr[inst]; - if (j == (u_long*)0x01234567) + if (j == (u_int*)0x01234567) return; j2 = *((u_char*)j + 12); @@ -1373,7 +1373,7 @@ SetPer void SetPer(void) { - u_long *j; + u_int *j; u_short period; u_char a; if (CurrentCh > 23) @@ -1749,7 +1749,7 @@ DoVol c Instrument volume *****************************************************************************/ -short DoVol(u_long a,short b,short c) +short DoVol(u_int a,short b,short c) { a*=b; a*=c; @@ -2494,10 +2494,10 @@ GetFreq2 int JPPer = 7350; //6578 -long GetFreq2(long period) +int GetFreq2(int period) { int okt; - long frequency; + int frequency; //FntPrint("period %d\n",period); period = JPPer - period; @@ -2525,7 +2525,7 @@ short ProcessEnvelope(short v,u_char keyon,int JSmp) u_char a2,b; u_short p; -u_long* j; +u_int* j; short apos; short aval; short bpos; @@ -2597,7 +2597,7 @@ short ProcessPanEnvelope(short v, u_char keyon, int JSmp) u_char a2, b; u_short p; - u_long* j; + u_int* j; short apos; short aval; short bpos; diff --git a/src_rebuild/Game/C/xmplay.h b/src_rebuild/Game/C/xmplay.h index 668822ffa..41d5ca130 100644 --- a/src_rebuild/Game/C/xmplay.h +++ b/src_rebuild/Game/C/xmplay.h @@ -45,11 +45,11 @@ typedef struct _XMHEADER { u_short bpm; u_short XMChannels; u_short XMPSXChannels; - u_long *JAP_PAT_ADDR[256]; /* Pattern Start Addresses (maxpatt*chnls)*/ - u_long *JAP_PAT_ADDR2[256]; /* Pattern Start Addresses (maxpatt*chnls)*/ - u_long *JAP_InstrumentOffset[128]; - u_long *JAP_SampAddr[128]; - u_long *JAP_SampHdrAddr[128]; + u_int *JAP_PAT_ADDR[256]; /* Pattern Start Addresses (maxpatt*chnls)*/ + u_int *JAP_PAT_ADDR2[256]; /* Pattern Start Addresses (maxpatt*chnls)*/ + u_int *JAP_InstrumentOffset[128]; + u_int *JAP_SampAddr[128]; + u_int *JAP_SampHdrAddr[128]; u_char jorders[256]; /* list of patterns */ int S3MPanning; } XMHEADER; @@ -63,8 +63,8 @@ typedef struct _XMCHANNEL { short OldRVol; u_short OldPeriod; u_char OldSample; - u_long OldSOff; - u_long SOffset; + u_int OldSOff; + u_int SOffset; u_char nothing; u_char ChDead; u_char panenvflg; /* envelope flag */ @@ -100,7 +100,7 @@ typedef struct _XMCHANNEL { u_char kick2; /* if 1=sample has to be restarted */ u_char sample; /* which sample number (0-31) */ short handle; /* which sample-handle */ - u_long start; /* The start byte index in the sample */ + u_int start; /* The start byte index in the sample */ u_char panning; /* panning position */ u_char pansspd; /* panslide speed */ u_char volume; /* volume (0 - 64) to play the sample at */ @@ -163,8 +163,8 @@ typedef struct _XMSONG { int JBPM; int PCounter; u_short PatSize; - u_long *PatAdr; - u_long *PatAdr2; + u_int *PatAdr; + u_int *PatAdr2; int PlayMask; int SFXNum; XMCHANNEL XM_Chnl[24]; /* max 32 channels per song*/ @@ -231,7 +231,7 @@ typedef struct _XM_HeaderInfo typedef struct _XM_VABInfo { u_char* Address; - u_long Size; + u_int Size; } XM_VABInfo; typedef struct _XM_Feedback @@ -273,7 +273,7 @@ void XM_PlayStop(int Song_ID); // 0x00085DC8 int InitXMData(u_char *mpp, int XM_ID, int S3MPan); // 0x000831B0 -unsigned long GetLong(u_char *mpp); // 0x000869C0 +unsigned int GetLong(u_char *mpp); // 0x000869C0 void XM_OnceOffInit(int PAL); // 0x00085F7C @@ -309,7 +309,7 @@ void DoTremolo(); // 0x000849F0 short DoPan(short envpan, short pan); // 0x00086560 -short DoVol(unsigned long a, short b, short c); // 0x00086538 +short DoVol(unsigned int a, short b, short c); // 0x00086538 void UpdateXMData(); // 0x00086484 @@ -337,7 +337,7 @@ void UpdateHardware(); // 0x00085448 int IntVols(int Vol1, int Vol2); // 0x00086F70 -long GetFreq2(long period); // 0x00086AF0 +int GetFreq2(int period); // 0x00086AF0 short ProcessEnvelope(short v, u_char keyon, int JSmp); // 0x000857A4 diff --git a/src_rebuild/Game/Frontend/FEmain.c b/src_rebuild/Game/Frontend/FEmain.c index 43b107b30..1e1b9e38a 100644 --- a/src_rebuild/Game/Frontend/FEmain.c +++ b/src_rebuild/Game/Frontend/FEmain.c @@ -22,6 +22,259 @@ #include "C/spool.h" #include "C/state.h" +#ifndef PSX + +#include "PsyX/PsyX_render.h" +#include "../utils/targa.h" +#include "../utils/hqfont.h" + +#define HIRES_FONTS + +struct FEFONT_QUAD +{ + float x0, y0, s0, t0; // top-left + float x1, y1, s1, t1; // bottom-right +}; + +TextureID gHiresFEFontTexture = 0; +OUT_FN2RANGE gHiresFEFontRanges[4]; +OUT_FN2INFO gHiresFEFontCharData[4][224]; +int gHiresFEFontRangeCount = 0; + +void InitHiresFEFont() +{ + char namebuffer[64]; + u_char* data; + + // init font2 + if (!gHiresFEFontTexture) + { + gHiresFEFontRangeCount = 0; + + int width, height, bpp; + int x, y; + int size; + FILE* fp; + sprintf(namebuffer, "%s%s", gDataFolder, "GFX\\HQ\\fefont.fn2"); + FS_FixPathSlashes(namebuffer); + + fp = fopen(namebuffer, "rb"); + if (fp) + { + int i; + + // read fn2 step by step + OUT_FN2HEADER fn2hdr; + fread(&fn2hdr, sizeof(fn2hdr), 1, fp); + + gHiresFEFontRangeCount = fn2hdr.range_count; + for (i = 0; i < fn2hdr.range_count; ++i) + { + fread(&gHiresFEFontRanges[i], sizeof(gHiresFEFontRanges[i]), 1, fp); + fread(gHiresFEFontCharData[i], sizeof(OUT_FN2INFO), gHiresFEFontRanges[i].count, fp); + } + + fclose(fp); + } + + // load TGA file + sprintf(namebuffer, "%s%s", gDataFolder, "GFX\\HQ\\fefont.tga"); + FS_FixPathSlashes(namebuffer); + + if (LoadTGAImage(namebuffer, &data, width, height, bpp)) + { + if (bpp == 32) + { + gHiresFEFontTexture = GR_CreateRGBATexture(HIRES_FONT_SIZE_W, HIRES_FONT_SIZE_H, data); + } + free(data); + } + } +} + +void FEGetHiresBakedQuad(int char_index, float scale, float* xpos, float* ypos, FEFONT_QUAD* q) +{ + float ipw = 1.0f / (float)HIRES_FONT_SIZE_W; + float iph = 1.0f / (float)HIRES_FONT_SIZE_H; + + const OUT_FN2INFO* b = gHiresFEFontCharData[0] + char_index - gHiresFEFontRanges[0].start; + + float fscale = 0.5f * scale; + + float s_x = b->x1 - b->x0; + float s_y = b->y1 - b->y0; + + q->x0 = *xpos + b->xoff * fscale; + q->y0 = *ypos + b->yoff * fscale; + q->x1 = (b->xoff2 - b->xoff) * fscale; + q->y1 = (b->yoff2 - b->yoff) * fscale; + + q->s0 = b->x0 * 255.0f * ipw; + q->t0 = b->y0 * 255.0f * iph; + q->s1 = s_x * 255.0f * ipw; + q->t1 = s_y * 255.0f * iph; + + q->y0 += 32.0f; + + *xpos += b->xadvance * fscale; +} + +void SetHiresFEFontTexture(int enabled) +{ + if (gHiresFEFontTexture == 0) + { + return; + } + + DR_PSYX_TEX* tex = (DR_PSYX_TEX*)current->primptr; + if (enabled) + SetPsyXTexture(tex, gHiresFEFontTexture, 255, 255); + else + SetPsyXTexture(tex, 0, 0, 0); + + addPrim(current->ot + 1, tex); + current->primptr += sizeof(DR_PSYX_TEX); +} + +int FEStringWidthHires(char* string) +{ + char* pString = string; + u_char c = 0; + + int w = 0; + + while ((c = *pString++) != 0) + { + float fx, fy; + fx = 0; + fy = 0; + FEFONT_QUAD q; + FEGetHiresBakedQuad(c, 1.0f, &fx, &fy, &q); + + w += fx; + } + + return w; +} + +int FEPrintStringSizedHires(char* string, int x, int y, int scale, int transparent, int r, int g, int b) +{ + if (current == NULL || string == NULL) + return -1; + + POLY_FT4* shadow; + POLY_FT4* font; + u_char let; + int w; + int h; + + SetHiresFEFontTexture(0); + font = (POLY_FT4*)current->primptr; + + while ((let = *string++) != 0) + { + if (let == '\n') + continue; + + float fx, fy; + fx = x; + fy = y; + FEFONT_QUAD q; + FEGetHiresBakedQuad(let, scale / 4096.0f, &fx, &fy, &q); + + setPolyFT4(font); + setSemiTrans(font, 1); + + setRGB0(font, r, g, b); + setUVWH(font, q.s0, q.t0, q.s1, q.t1); + setXYWH(font, q.x0, q.y0, q.x1, q.y1); + + font->clut = 0; + font->tpage = 0; + + addPrim(current->ot + 1, font); + shadow = font + 1; + + // add shadow poly + memcpy(shadow, font, sizeof(POLY_FT4)); + setRGB0(shadow, 10, 10, 10); + setXYWH(shadow, q.x0 + 1.0f, q.y0 + 1.0f, q.x1, q.y1); + + addPrim(current->ot + 1, shadow); + font += 2; + + // make room for next character + x += fx - x; + } + + // set tail + current->primptr = (char*)font; + SetHiresFEFontTexture(1); + + return x; +} + +int FEPrintStringHires(char* string, float x, float y, int justification, int r, int g, int b) +{ + POLY_FT4* shadow; + POLY_FT4* font; + u_char let; + + if (justification & 4) + { + x -= FEStringWidthHires(string); + } + + SetHiresFEFontTexture(0); + font = (POLY_FT4*)current->primptr; + + int counter = 0; + + while ((let = *string++) != 0) + { + float fx, fy; + fx = x; + fy = y; + FEFONT_QUAD q; + FEGetHiresBakedQuad(let, 1.0f, &fx, &fy, &q); + + setPolyFT4(font); + setSemiTrans(font, 1); + + setRGB0(font, r, g, b); + setUVWH(font, q.s0, q.t0, q.s1, q.t1); + setXYWH(font, q.x0, q.y0, q.x1, q.y1); + + font->clut = 0; + font->tpage = 0; + + addPrim(current->ot + 1, font); + shadow = font + 1; + + // add shadow poly + memcpy(shadow, font, sizeof(POLY_FT4)); + setRGB0(shadow, 10, 10, 10); + setXYWH(shadow, q.x0 + 1.0f, q.y0 + 1.0f, q.x1, q.y1); + + addPrim(current->ot + 1, shadow); + font += 2; + + // add space for next character + x += fx - x; + + if (++counter >= 32) + break; + } + + // set tail + current->primptr = (char *)font; + + SetHiresFEFontTexture(1); + + return x; +} + +#endif // PSX struct PSXBUTTON { @@ -331,8 +584,8 @@ char carNumLookup[4][10] = { int minmaxSelections[4][2] = { { 0, 8 }, - { 8, 17 }, - { 18, 27 }, + { 8, 18 }, + { 18, 28 }, { 28, 37 } }; @@ -566,16 +819,17 @@ void SetVariable(int var) GameType = GAME_IDLEDEMO; gCurrentMissionNumber = (value + 400); break; +#if ENABLE_BONUS_CONTENT case 14: // [A] { ShowBonusGallery(); - LoadFrontendScreens(0); } - case 15: + case 15: // [A] mini cars cheat { ActiveCheats.cheat13 = value; } +#endif } } @@ -702,7 +956,7 @@ void DisplayOnScreenText(void) text = "Incompatible controller in Port 1"; } - FEPrintStringSized(text, 40, 400, 0xc00, transparent, 64, 64, 64); + FEPrintStringSized(text, 40, 400, 3072, transparent, 64, 64, 64); } else { @@ -716,14 +970,14 @@ void DisplayOnScreenText(void) strcat(ScreenTitle, ScreenNames[i]); } - FEPrintStringSized(ScreenTitle, 40, 400, 0xc00, 1, 64, 64, 64); + FEPrintStringSized(ScreenTitle, 40, 400, 3072, 1, 64, 64, 64); } if (iScreenSelect == SCREEN_CUTSCENE) { text = GET_MISSION_TXT(CutSceneNames[cutSelection + CutAmountsTotal[currCity]]); - FEPrintStringSized(text, 100, 226, 0xc00, 1, 64, 64, 64); + FEPrintStringSized(text, 100, 226, 3072, 1, 64, 64, 64); } } } @@ -933,7 +1187,7 @@ void SetupExtraPoly(char *fileName, int offset, int offset2) rect.w = 64; rect.h = 219; - LoadImage(&rect, (u_long *)(_frontend_buffer + offset2 + offset * 0x8000)); + LoadImage(&rect, (u_long*)(_frontend_buffer + offset2 + offset * 0x8000)); DrawSync(0); VSync(0); @@ -1059,20 +1313,21 @@ void LoadFrontendScreens(int full) #ifndef PSX // [A] SCREEN HACKS - + // Time of day extended screen + PsxScreens[3].userFunctionNum = 22; // TimeOfDaySelectScreen +#endif // for web demo content (or empty SCRS.BIN) - if(PsxScreens[0].userFunctionNum == 128) + if (PsxScreens[0].userFunctionNum == 128) PsxScreens[0].userFunctionNum = 23; // DemoScreen - // Time of day extended screen - PsxScreens[3].userFunctionNum = 22; //TimeOfDaySelectScreen - +#ifndef PSX // replay theater PsxScreens[4].buttons[0].action = FE_MAKEVAR(BTN_NEXT_SCREEN, 39); PsxScreens[4].buttons[0].var = -1; - PsxScreens[39].userFunctionNum = 21; // UserReplaySelectScreen +#endif // PSX +#if ENABLE_BONUS_CONTENT // make mini cars cheat screen PsxScreens[40] = PsxScreens[31]; PsxScreens[40].userFunctionNum = 24; // MiniCarsOnOffScreen @@ -1175,7 +1430,7 @@ void ReInitScreens(int returnToMain) NumPlayers = 1; gWantNight = 0; gNight = 0; - gTimeOfDay = 1; + gTimeOfDay = TIME_DAY; gSubGameNumber = 0; feVariableSave[3] = -1; @@ -1197,7 +1452,7 @@ void ReInitScreens(int returnToMain) pCurrScreen = pScreenStack[ScreenDepth]; pNewButton = pButtonStack[ScreenDepth]; - // [A] state hack + // [A] state hack for demo if (pCurrScreen == NULL) pCurrScreen = &PsxScreens[0]; @@ -1552,6 +1807,9 @@ void SetFEDrawMode(void) void InitFrontend(void) { InitCdIcon(); +#ifdef HIRES_FONTS + InitHiresFEFont(); +#endif ResetGraph(1); SetDispMask(0); @@ -1704,9 +1962,15 @@ int FEStringWidth(char* string) { char* pString = string; u_char c = 0; - int w = 0; +#ifdef HIRES_FONTS + if (gHiresFEFontTexture) + { + return FEStringWidthHires(string); + } +#endif + while ((c = *pString++) != 0) { if (c == ' ') @@ -1724,30 +1988,24 @@ int FEPrintString(char *string, int x, int y, int justification, int r, int g, i if (current == NULL || string == NULL) return -1; +#ifdef HIRES_FONTS + if (gHiresFEFontTexture) + { + return FEPrintStringHires(string, x, y, justification, r, g, b); + } +#endif + FE_CHARDATA *pFontInfo; SPRT *font; u_char let; - font = (SPRT *)current->primptr; - if (justification & 4) { - char *pString = string; - u_char c = 0; - - int w = 0; - - while ((c = *pString++) != 0) - { - if (c == ' ') - w += 4; - else - w += feFont.CharInfo[c].w; - } - - x -= w; + x -= FEStringWidth(string); } + font = (SPRT*)current->primptr; + int counter = 0; while ((let = *string++) != 0) @@ -1805,6 +2063,13 @@ int FEPrintStringSized(char *string, int x, int y, int scale, int transparent, i if (current == NULL || string == NULL) return -1; +#ifdef HIRES_FONTS + if (gHiresFEFontTexture) + { + return FEPrintStringSizedHires(string, x, y, scale, transparent, r, g, b); + } +#endif + POLY_FT4 *font; FE_CHARDATA *pFontInfo; u_char let; @@ -1873,10 +2138,10 @@ int CentreScreen(int bSetup) char text[32]; sprintf(text, "X1: %d, Y1: %d", current->disp.screen.x, current->disp.screen.y); - FEPrintStringSized(text, 25, 50, 0xC00, 0, 128, 0, 0); + FEPrintStringSized(text, 25, 50, 3072, 0, 128, 0, 0); sprintf(text, "X2: %d, Y2: %d", last->disp.screen.x, last->disp.screen.y); - FEPrintStringSized(text, 25, 75, 0xC00, 0, 128, 0, 0); + FEPrintStringSized(text, 25, 75, 3072, 0, 128, 0, 0); #endif if (feNewPad & MPAD_CROSS) @@ -1973,9 +2238,17 @@ int CarSelectScreen(int bSetup) { for (int i = 4; i < 9; i++) { +#if USE_PC_FILESYSTEM + // [A] minimal requirement for this car to be selectable + extern int gContentOverride; + if (i == 8) + CarAvailability[0][i] = gContentOverride && FileExists("LEVELS\\CHICAGO\\CARMODEL_11_clean.MDL"); // remove truck + else + CarAvailability[0][i] = 1; +#else if (i != 8) CarAvailability[0][i] = 1; // remove truck - +#endif CarAvailability[1][i] = 1; CarAvailability[2][i] = 1; CarAvailability[3][i] = 1; @@ -2113,7 +2386,7 @@ int CarSelectScreen(int bSetup) } rect = extraRect; - LoadImage(&rect, (u_long *)(_frontend_buffer + carSelection * 0x8000)); + LoadImage(&rect, (u_long*)(_frontend_buffer + carSelection * 0x8000)); DrawSync(0); #ifdef PSX @@ -2223,7 +2496,7 @@ int MissionSelectScreen(int bSetup) for (i = 0; (i < 4) && !done; i++) { if (botch[currMission + i].missNum > gFurthestMission || - currMission + i > minmaxSelections[currCity][1] || + currMission + i >= minmaxSelections[currCity][1] || currMission + i > 36) { done = true; @@ -2505,7 +2778,7 @@ int MissionCityScreen(int bSetup) } rect = extraRect; - LoadImage(&rect, (u_long *)(_frontend_buffer + currCity * 0x8000)); + LoadImage(&rect, (u_long*)(_frontend_buffer + currCity * 0x8000)); DrawSync(0); #ifdef PSX @@ -2725,9 +2998,9 @@ int CutSceneCitySelectScreen(int bSetup) bDrawExtra = 1; if (currCity == 4) - LoadImage(&rect, (u_long *)_frontend_buffer); + LoadImage(&rect, (u_long*)_frontend_buffer); else - LoadImage(&rect, (u_long *)(_frontend_buffer + currCity * 0x8000)); + LoadImage(&rect, (u_long*)(_frontend_buffer + currCity * 0x8000)); DrawSync(0); } @@ -2785,9 +3058,9 @@ int CutSceneCitySelectScreen(int bSetup) rect = extraRect; if (GameLevel != 4) - LoadImage(&rect, (u_long *)(_frontend_buffer + GameLevel * 0x8000)); + LoadImage(&rect, (u_long*)(_frontend_buffer + GameLevel * 0x8000)); else - LoadImage(&rect, (u_long *)_frontend_buffer); + LoadImage(&rect, (u_long*)_frontend_buffer); DrawSync(0); @@ -3182,7 +3455,7 @@ int CityCutOffScreen(int bSetup) bDrawExtra = 1; RECT16 rect = extraRect; - LoadImage(&rect, (u_long *)(_frontend_buffer + currCity * 0x8000)); + LoadImage(&rect, (u_long*)(_frontend_buffer + currCity * 0x8000)); DrawSync(0); } #endif @@ -3231,7 +3504,7 @@ int CityCutOffScreen(int bSetup) } RECT16 rect = extraRect; - LoadImage(&rect, (u_long *)(_frontend_buffer + currCity * 0x8000)); + LoadImage(&rect, (u_long*)(_frontend_buffer + currCity * 0x8000)); DrawSync(0); #endif return 0; @@ -3298,8 +3571,8 @@ static char* cheatText[] = G_LTXT_ID(GTXT_Circuit), G_LTXT_ID(GTXT_Invincibility), G_LTXT_ID(GTXT_Immunity), - G_LTXT_ID(GTXT_MiniCars), // [A] - G_LTXT_ID(GTXT_BonusGallery) // [A] + G_LTXT_ID(GTXT_MiniCars), // [A] + G_LTXT_ID(GTXT_BonusGallery) // [A] }; // [D] [T] @@ -3335,7 +3608,7 @@ int CheatScreen(int bSetup) } if (gFurthestMission == 40) - numOpen = 6; // [A] now 5 elements as "Bonus gallery" is open + numOpen = 4 + ENABLE_BONUS_CONTENT * 2; // [A] now 5 elements as "Bonus gallery" is open else numOpen = AvailableCheats.cheat1 + AvailableCheats.cheat2 + AvailableCheats.cheat3 + AvailableCheats.cheat4; @@ -3472,7 +3745,7 @@ int CheatScreen(int bSetup) pCurrScreen->buttons[4].u = 4; } - // [A] adding bonus gallery + // [A] adding mini cars cheat if (numOpen >= 6) { pCurrScreen->buttons[5].action = hackLookup1[cheatOn[5]]; @@ -3645,7 +3918,8 @@ int BuildButtonsVertical(int count, int xStart, int yStart) #ifndef PSX #include "../utils/fs.h" -char gFEReplayList[8][20]; +#define MAX_REPLAY_NAME_LENGTH 48 +char gFEReplayList[8][MAX_REPLAY_NAME_LENGTH]; int gFEReplayCount = 0; #endif @@ -3664,9 +3938,9 @@ int UserReplaySelectScreen(int bSetup) while (filename) { - strncpy(gFEReplayList[gFEReplayCount], filename, 16); + strncpy(gFEReplayList[gFEReplayCount], filename, MAX_REPLAY_NAME_LENGTH); tmp = strchr(filename, '.'); - gFEReplayList[gFEReplayCount][19] = 0; + gFEReplayList[gFEReplayCount][MAX_REPLAY_NAME_LENGTH-1] = 0; gFEReplayCount++; filename = FS_FindNext(fd); @@ -3690,7 +3964,7 @@ int UserReplaySelectScreen(int bSetup) for (int i = 0; i < numButtons; i++) { PSXBUTTON& btn = pCurrScreen->buttons[i]; - strcpy(btn.Name, gFEReplayList[i]); + strncpy(btn.Name, gFEReplayList[i], sizeof(btn.Name)); tmp = strchr(btn.Name, '.'); @@ -3768,7 +4042,7 @@ int TimeOfDaySelectScreen(int bSetup) { // setup time we want to reset it wantedWeather = 0; - wantedTimeOfDay = 1; + wantedTimeOfDay = TIME_DAY; numButtons = BuildButtonsVertical(3, 168, 208); @@ -3828,7 +4102,7 @@ int TimeOfDaySelectScreen(int bSetup) wantedTimeOfDay += dir; wantedTimeOfDay &= 3; - gWantNight = wantedTimeOfDay == 2 || wantedTimeOfDay == 3; + gWantNight = wantedTimeOfDay == TIME_DUSK || wantedTimeOfDay == TIME_NIGHT; } else if (i == 1) { diff --git a/src_rebuild/Game/dr2limits.h b/src_rebuild/Game/dr2limits.h index 2670d0112..c0361c401 100644 --- a/src_rebuild/Game/dr2limits.h +++ b/src_rebuild/Game/dr2limits.h @@ -4,6 +4,12 @@ // DRIVER 2 game engine limits // please populate this file only with engine limits during refactoring + +#ifndef PSX +#define DYNAMIC_LIGHTING +#define MAX_DLIGHTS 32 +#endif + #ifndef PSX #define MAX_PLAYERS 16 // used for replay streams mostly #else diff --git a/src_rebuild/Game/dr2locale.h b/src_rebuild/Game/dr2locale.h index 6653f7d43..9b351252c 100644 --- a/src_rebuild/Game/dr2locale.h +++ b/src_rebuild/Game/dr2locale.h @@ -68,6 +68,7 @@ enum GameStrId GTXT_FilmDirector, GTXT_QuickReplay, GTXT_Exit, + GTXT_Back, GTXT_Rotation, GTXT_Move, GTXT_SkipCutscene, diff --git a/src_rebuild/Game/dr2math.h b/src_rebuild/Game/dr2math.h index 1cbed3991..eb1567dec 100644 --- a/src_rebuild/Game/dr2math.h +++ b/src_rebuild/Game/dr2math.h @@ -29,7 +29,6 @@ extern short rcossin_tbl[8192]; // math constants #define FixHalfRound(x, bits) (((x) + (1 << (bits-1))) >> bits) -#define FixFloorSigned(x, bits) ((x) / (1 << bits)) // in disassembly: ((int(x) < 0 ? int(x) + (1 << bits)-1 : int(x)) >> bits) #define FIXEDH(a) FixHalfRound(a, ONE_BITS) // Fixed Half Round number #define FIXED(a) ((a) >> ONE_BITS) // Fixed number (unsigned) @@ -40,8 +39,12 @@ extern short rcossin_tbl[8192]; #define isin(a) (rcossin_tbl[( ( a ) & 4095) * 2]) #define icos(a) (rcossin_tbl[((( a )+1024) & 4095) * 2]) -#define DIFF_ANGLES( A, B ) \ - (((((B) - (A)) + 2048) & 4095) - 2048) +#define RAND(seed) ((seed) * 0x19660D + 0x3C6EF35F) + +#define DIFF_ANGLES_R( A, B, RANGE ) \ + (((((B) - (A)) + (RANGE>>1)) & RANGE-1) - (RANGE>>1)) + +#define DIFF_ANGLES( A, B ) DIFF_ANGLES_R(A, B, 4096) // Remap a value in the range [A,B] to [C,D]. #define RemapVal( val, A, B, C, D) \ @@ -133,6 +136,14 @@ extern short rcossin_tbl[8192]; #define MAX(a,b) fst_max(a,b) #endif +#ifdef PSX +#undef ABS // don't use PsyQ's silly ABS that might not be optimized +#endif + +#ifndef ABS +#define ABS(a) fst_abs(a) +#endif + inline int fst_min(int a, int b) { int diff = a - b; @@ -147,4 +158,10 @@ inline int fst_max(int a, int b) return a - (diff & dsgn); } +inline int fst_abs(int x) +{ + int mask = x >> 31; + return (x ^ mask) - mask; +} + #endif // DR2MATH_H \ No newline at end of file diff --git a/src_rebuild/Game/dr2types.h b/src_rebuild/Game/dr2types.h index eba624f9e..390def56a 100644 --- a/src_rebuild/Game/dr2types.h +++ b/src_rebuild/Game/dr2types.h @@ -9,10 +9,10 @@ typedef short SHORTVECTOR4[4]; -typedef long LONGVECTOR3[3]; -typedef long LONGVECTOR4[4]; +typedef int LONGVECTOR3[3]; +typedef int LONGVECTOR4[4]; -typedef long LONGQUATERNION[4]; +typedef int LONGQUATERNION[4]; struct VECTOR2 { @@ -291,16 +291,6 @@ struct HUBCAP int Duration; }; -struct BOUND_BOX -{ - int x0; - int y0; - int z0; - int x1; - int y1; - int z1; -}; - typedef struct _HANDLING_TYPE { char frictionScaleRatio; @@ -362,7 +352,7 @@ typedef struct _HANDLING_DATA union RigidBodyState { - long v[13]; + int v[13]; struct { LONGVECTOR3 fposition; LONGQUATERNION orientation; @@ -762,7 +752,7 @@ typedef struct _TARGET int eventId; // data 3 union { - VECTOR* eventPos; // data 4 + int eventPos; // data 4 int unused[10]; }; int loseMessage; // data 14 @@ -772,7 +762,7 @@ typedef struct _TARGET }; } MS_TARGET; -assert_sizeof(MS_TARGET, 64); +static_assert(sizeof(MS_TARGET) == 64, "MS_TARGET size is not correct"); //--------------------------------------------------------------------------------------- @@ -1155,24 +1145,6 @@ struct ROADBLOCK //--------------------------------------------------------------------------------------- // TODO: OBJCOLL.H -struct tRay -{ - LONGVECTOR4 org; - LONGVECTOR4 dir; -}; - -struct tRange -{ - int lower; - int upper; -}; - -struct tAABB -{ - tRange slab[3]; -}; - - struct TestResult { int depth; @@ -1346,4 +1318,21 @@ enum GAMEMODE GAMEMODE_DEMO = 6, }; +//--------------------------------------------------- + +enum TIMEOFDAY +{ + TIME_DAWN = 0, + TIME_DAY = 1, + TIME_DUSK = 2, + TIME_NIGHT = 3 +}; + +enum WEATHER +{ + WEATHER_NONE = 0, + WEATHER_RAIN = 1, + WEATHER_WET = 2 +}; + #endif // DR2TYPES_H \ No newline at end of file diff --git a/src_rebuild/Game/driver2.h b/src_rebuild/Game/driver2.h index 0cd5668ee..e9fc5ce6f 100644 --- a/src_rebuild/Game/driver2.h +++ b/src_rebuild/Game/driver2.h @@ -26,8 +26,12 @@ #include "platform.h" -#define USE_PC_FILESYSTEM (!defined(PSX)) // PC filesystem is prioritized over CD -#define USE_CD_FILESYSTEM 1 // use always +#define USE_PC_FILESYSTEM (!defined(PSX)) // PC filesystem is prioritized over CD +#define USE_CD_FILESYSTEM 1 // use always +#define ENABLE_MISSION_FIXES 1 +#define ENABLE_GAME_FIXES 1 +#define ENABLE_GAME_ENCHANCEMENTS 1 +#define ENABLE_BONUS_CONTENT 1 #ifdef PSX // TODO: Include PSX STUFF diff --git a/src_rebuild/Game/engine/mdl.h b/src_rebuild/Game/engine/mdl.h index 9ee02a21a..8d552de82 100644 --- a/src_rebuild/Game/engine/mdl.h +++ b/src_rebuild/Game/engine/mdl.h @@ -160,26 +160,6 @@ struct MODEL int normals; int point_normals; int collision_block; - - SVECTOR* pVertex(int i) const - { - return (SVECTOR *)(((u_char *)this) + vertices) + i; - } - - SVECTOR* pNormal(int i) const - { - return (SVECTOR *)(((u_char *)this) + point_normals) + i; - } - - COLLISION_PACKET* pCollisionPacket(int i) const - { - return (COLLISION_PACKET *)(((u_char *)this) + collision_block) + i; - } - - char* pPolyAt(int ofs) const - { - return (char *)(((u_char *)this) + poly_block + ofs); - } }; diff --git a/src_rebuild/Game/psyx_compat.h b/src_rebuild/Game/psyx_compat.h index 9aa47c7f5..f487223b5 100644 --- a/src_rebuild/Game/psyx_compat.h +++ b/src_rebuild/Game/psyx_compat.h @@ -7,6 +7,8 @@ #ifndef PSX #include "PsyX/PsyX_public.h" #include "PsyX/PsyX_globals.h" +#include "psx/libgte.h" +#include "psx/libgpu.h" #endif // Necessary types diff --git a/src_rebuild/Game/reversing.h b/src_rebuild/Game/reversing.h index fb4759602..09ed9489a 100644 --- a/src_rebuild/Game/reversing.h +++ b/src_rebuild/Game/reversing.h @@ -14,6 +14,8 @@ #define M_INT_4(a,b,c,d) (((a) << 24) | ((b) << 16) | ((c) << 8) | (d) ) #define M_INT_4R(a,b,c,d) M_INT_4(d, c, b, a) +#define M_BIT(x) (1 << (x)) + //--------------------------------------- #if !defined( __TYPEINFOGEN__ ) && !defined( _lint ) && defined(_WIN32) // pcLint has problems with assert_offsetof() diff --git a/src_rebuild/Game/version.h b/src_rebuild/Game/version.h index 88dbe0192..9087f4a4b 100644 --- a/src_rebuild/Game/version.h +++ b/src_rebuild/Game/version.h @@ -1,9 +1,9 @@ #ifndef GAME_VERSION_N -#define GAME_VERSION_N "7.4" +#define GAME_VERSION_N "7.8" #endif #ifndef GAME_VERSION_RES -#define GAME_VERSION_RES 7,4 +#define GAME_VERSION_RES 7,8 #endif #define GAME_TITLE "REDRIVER2" diff --git a/src_rebuild/PsyCross b/src_rebuild/PsyCross index 502f700c5..b086bba2a 160000 --- a/src_rebuild/PsyCross +++ b/src_rebuild/PsyCross @@ -1 +1 @@ -Subproject commit 502f700c5ba88b1ee0f7bca5cdd8ea66b833417a +Subproject commit b086bba2a20a6208e8f5272b38357717de01eb17 diff --git a/src_rebuild/platform/Emscripten/shell.html b/src_rebuild/platform/Emscripten/shell.html index df805bb6f..0716477a5 100644 --- a/src_rebuild/platform/Emscripten/shell.html +++ b/src_rebuild/platform/Emscripten/shell.html @@ -86,9 +86,8 @@
-
-
emscripten
+
Downloading...
@@ -107,97 +106,88 @@ - + - {{{ SCRIPT }}} + {{{ SCRIPT }}} \ No newline at end of file diff --git a/src_rebuild/premake5.lua b/src_rebuild/premake5.lua index ab56f11b7..95bf16521 100644 --- a/src_rebuild/premake5.lua +++ b/src_rebuild/premake5.lua @@ -3,6 +3,8 @@ require "premake_modules/usage" require "premake_modules/emscripten" +IS_ANDROID = (_ACTION == "androidndk") + ------------------------------------------ newoption { @@ -19,7 +21,7 @@ SDL2_DIR = os.getenv("SDL2_DIR") or "dependencies/SDL2" OPENAL_DIR = os.getenv("OPENAL_DIR") or "dependencies/openal-soft" JPEG_DIR = os.getenv("JPEG_DIR") or "dependencies/jpeg" -WEBDEMO_DIR = os.getenv("WEBDEMO_DIR") or "../../../content/web_demo@/" -- FIXME: make it better +WEBDEMO_DIR = os.getenv("WEBDEMO_DIR") or "../../../../content/web_demo@/" -- FIXME: make it better RED2_DIR = os.getenv("RED2_DIR") or "../../data@/" WEBSHELL_PATH = "../platform/Emscripten" -- must be relative to makefile path (SADLY) @@ -58,6 +60,7 @@ workspace "REDRIVER2" "-Wno-parentheses", "-Wno-format", } + linkoptions { "-s TOTAL_MEMORY=1073741824", "-s USE_SDL=2", @@ -80,6 +83,51 @@ workspace "REDRIVER2" "{COPY} " .. WEBSHELL_PATH .. "/lsfs.js %{cfg.buildtarget.directory}" } + elseif IS_ANDROID then + system "android" + shortcommands "On" + + platforms { + "android-arm", "android-arm64" + } + + disablewarnings { + "c++11-narrowing", + "constant-conversion", + "writable-strings", + "unused-value", + "switch", + "shift-op-parentheses", + "parentheses", + "format", + } + + buildoptions { + "-fpermissive", + "-fexceptions", + "-pthread", + } + + linkoptions { + "--no-undefined", + "-fexceptions", + "-pthread", + + "-mfloat-abi=softfp", -- force NEON to be used + "-mfpu=neon" + } + + filter "platforms:*-x86" + architecture "x86" + + filter "platforms:*-x86_64" + architecture "x86_64" + + filter "platforms:*-arm" + architecture "arm" + + filter "platforms:*-arm64" + architecture "arm64" else platforms { "x86" } --, "x86_64" } end @@ -134,9 +182,16 @@ if os.target() == "windows" or os.target() == "emscripten" then include "premake_libjpeg.lua" end +-- font tool +if os.target() ~= "emscripten" then + include "premake5_font_tool.lua" +end + -- Psy-Cross layer include "premake5_psycross.lua" + + -- game iteslf project "REDRIVER2" kind "WindowedApp" @@ -175,7 +230,6 @@ project "REDRIVER2" "utils/**.cpp", "utils/**.c", "redriver2_psxpc.cpp", - "DebugOverlay.cpp", } filter "platforms:emscripten" diff --git a/src_rebuild/premake5_font_tool.lua b/src_rebuild/premake5_font_tool.lua new file mode 100644 index 000000000..823c24dfa --- /dev/null +++ b/src_rebuild/premake5_font_tool.lua @@ -0,0 +1,33 @@ +-- Font generator tool +project "FontTool" + kind "ConsoleApp" + language "C++" + targetdir "bin/%{cfg.buildcfg}" + + files { + "tools/font_tool/**.h", + "tools/font_tool/**.H", + "tools/font_tool/**.c", + "tools/font_tool/**.C", + "tools/font_tool/**.cpp", + "tools/font_tool/**.CPP", + "utils/stb_truetype.*", + "utils/targa.*", + } + + defines { } + + includedirs { + "utils", + "PsyCross/include/psx" + } + + filter "system:Windows" + defines { "_WINDOWS" } + + filter "configurations:Release" + optimize "Speed" + + filter "configurations:Release_dev" + optimize "Speed" + diff --git a/src_rebuild/premake_modules/androidndk/LICENSE.txt b/src_rebuild/premake_modules/androidndk/LICENSE.txt new file mode 100644 index 000000000..b83a92132 --- /dev/null +++ b/src_rebuild/premake_modules/androidndk/LICENSE.txt @@ -0,0 +1,27 @@ +Copyright (c) 2021 Vitaliy Triang3l Kuzmin. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + 3. Neither the name of premake-androidndk nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src_rebuild/premake_modules/androidndk/README.md b/src_rebuild/premake_modules/androidndk/README.md new file mode 100644 index 000000000..4e0183026 --- /dev/null +++ b/src_rebuild/premake_modules/androidndk/README.md @@ -0,0 +1,541 @@ +# premake-androidndk + +A module for generation of ndk-build Android.mk files for [Premake 5](https://premake.github.io/). + +* Providing the same level of **multi-ABI building capabilities** as hand-written Android.mk files, allowing for building for multiple ABIs from one ndk-build invocation and supporting ABI filtering for most of the project settings. +* Supporting all languages accepted by ndk-build — C, C++, the GNU Assembler, Yasm, RenderScript, as well as prebuilt libraries and external Android.mk files. +* Focused on supporting as many Premake project settings as possible, with attention to coverage of edge cases such as allowed characters. +* Preferring exposing Android.mk settings through existing Premake project settings over adding new ones where semantically appropriate to allow porting of projects from other targets with minimal changes. + +Available under the [Unlicense](UNLICENSE.txt), or, similar to Premake, the [BSD 3-Clause “New†or “Revised†License](LICENSE.txt). + +## Usage + +### Generating Android.mk files + +Clone or download the module, and `require("path/to/premake-androidndk/androidndk")` in your workspace's Premake script. Use the `androidndk` action to perform generation. + +All projects — which represent ndk-build modules — in the application must be located in a single workspace, as a workspace corresponds to an Application.mk file and the root Android.mk. + +Add Android platform definitions to the `platforms` of your workspace or the projects in it. This is especially important in two cases — if your workspace targets other operating systems alongside Android, and if you want to specify ABI-specific settings. Using `configurations` for this purpose also works, but generally it's recommended to use the configuration axis for build flavors such as debug/release, while doing all platform and architecture filtering via the platform axis — and due to the nature of specifying platforms and configurations to be built by ndk-build for Android.mk files generated by this module, mixing the two may result in complicated setup and unnecessary duplication of arguments you'll need to pass to ndk-build. + +**Specify the `system` as `"android"`** in your Android platforms and configurations. While this module doesn't check the value of `system` anywhere internally, if it's not specified, Premake will assume that the host OS is the target, which may be undesirable — for instance, build libraries may not have the `"lib"` prefix if the `system` is selected as `"windows"`. + +If you need to specify settings that apply only to certain ABIs, you can specify separate `platforms` (or `configurations`) with different `architecture` values. The supported architectures are `"ARM"` (corresponds to the `armeabi-v7a` ABI), `"ARM64"` (`arm64-v8a` ABI), `"x86"` (`x86` ABI) and `"x86_64"` (`x86_64` ABI). + +For example: + +```lua +configurations({"Debug", "Release") +platforms({"Windows-x86_64", "Android-ARM64", "Android-x86_64"}) +filter({"platforms:Windows-*"}) + system("windows") + systemversion("10.0") +filter({"platforms:Android-*"}) + system("android") + systemversion("24") + cppstl("c++") +filter({"platforms:*-x86_64"}) + architecture("x86_64") +filter({"platforms:*-ARM64"}) + architecture("ARM64") +filter({}) +``` + +It's also possible to specify both architecture-specific and architecture-agnostic (with a nil or `"universal"` architecture) platforms in the same project. In this case, the architecture specialization will be chosen when ndk-build is building for an ABI for which one available, and the architecture-independent settings will be used as a fallback if one is not: + +```lua +platforms({"AndroidOther", "AndroidARMv7", "AndroidARMv8"}) +filter({"platforms:AndroidARMv7"}) + architecture("ARM") +filter({"platforms:AndroidARMv8"}) + architecture("ARM64") +filter({"architecture:ARM or ARM64"}) + files({"neonmath.cpp"}) +filter({}) +-- `armeabi-v7a` ABI will use the AndroidARMv7 platform with neonmath.cpp. +-- `arm64-v8a` ABI will use the AndroidARMv8 platform with neonmath.cpp. +-- `x86` and `x86_64` ABIs will use the AndroidOther platform without neonmath.cpp. +``` + +Note that deprecated ABIs such as `armeabi`, `armeabi-v7a-hard`, `mips`, `mips64` are not supported. `armeabi-v7a` builds by default will have NEON enabled and will use the Thumb instruction set for release configurations, this can be changed via settings listed in the section below. + +It is important to consider that **Premake settings such as `architecture` are assigned to configuration–platform pairs**. So, if you want to use `filter({"architecture:"})`, you **must create a platform or a configuration for that architecture**, otherwise it will not work. + +To exclude a project from building for certain ABIs, you can set its `kind` to `None` with the required filter. + +Most settings allow GNU make variable expansion and function call passthrough, so you can use environment variables, or variables and functions provided by ndk-build like `$(TARGET_ARCH_ABI)`, in your projects, and they will be expanded at build time. You need to be careful not to reference variables or functions that may result in disallowed characters in the specific context, such as whitespaces in settings that end up being written to a list variable, however — it's not possible for the module to validate the value in this case. To use the dollar sign as a character, specify `$$` instead. Paths starting with a dollar sign (even if it's `$$` denoting an escaped raw `$` character) are treated as absolute by Premake, allowing for the usage of paths relative to directories such as `$(NDK_ROOT)`. If you want to override this behavior, explicitly prefix the path with `./`. + +#### Usage of prebuilt libraries and external Android.mk files + +Normally, this module provides functionality for building of projects from source code. + +However, it also exposes the prebuilt library functionality of ndk-build. This is the recommended way of including non-system library binaries in the workspace and linking projects against them, as it lets ndk-build properly ensure that the library is copied into the destination directory properly and that it's loaded from the correct path. ndk-build will generate warnings if the project is linked against non-system libraries via system `links` or via `linkoptions`. + +In addition, it's also possible to create a project that will use an external Android.mk file instead of its own settings (certain settings still must be correctly specified for linkage purposes, however). + +To create a project using a prebuilt library or an external Android.mk file, specify **just one `.a`, `.so` or `.mk` file** and **no other source files** in the `files` setting for the desired configurations/platforms. Also, you **must specify the correct `kind`** of the project — `"SharedLib"` for `.so`, `"StaticLib"` for `.a`, or the kind that matches the actual build script included in the external Android.mk. + +Projects using an external Android.mk files have special rules regarding their usage, specifically: + +- The external Android.mk must contain settings for only at most one project (module). +- The `kind` of the project must match the build script used in the external Android.mk — `"SharedLib"` for `BUILD_SHARED_LIBRARY` or `PREBUILT_SHARED_LIBRARY`, `"StaticLib"` for `BUILD_STATIC_LIBRARY` or `PREBUILT_STATIC_LIBRARY`, `"ConsoleApp"` for `BUILD_EXECUTABLE`. **Do not use the `"Makefile"` kind** — it has a completely different purpose, and is not supported by this module. The module must know the actual `kind` for linkage. +- The name of the project must be the same as `LOCAL_MODULE` in the external Android.mk. + +The following settings have effect when used in a prebuilt library project or an external Android.mk project: + +- `configurations` +- `flags` + - `"ExcludeFromBuild"` + - `"LinkTimeOptimization"` (for static libraries, if any object files in it are built with `-flto`) +- `linkoptions` (the only supported options are `-u` or `-Wl,--undefined` exported from static libraries) +- `links` (must match `LOCAL_SHARED_LIBRARIES` and `LOCAL_STATIC_LIBRARIES`/`LOCAL_WHOLE_STATIC_LIBRARIES` for external Android.mk projects referencing other Premake projects, external or not; also, for static libraries, must include the system libraries from `LOCAL_EXPORT_LDLIBS`) +- `location` (or `basedir` if not provided) +- `kind` +- `platforms` +- `project` (must match `LOCAL_MODULE` in the external Android.mk) +- `wholelib` + +For example, to add the Android Native App Glue to your workspace, you can create a project with the following settings: + +```lua +project("android_native_app_glue") + kind("StaticLib") + files({"$(NDK_ROOT)/sources/android/native_app_glue/Android.mk"}) + links({"log", "android"}) + linkoptions({"-u ANativeActivity_onCreate"}) +``` + +#### RenderScript library linkage + +To link projects with RenderScript sources, you may need to add some of these prebuilt library projects to your workspace (most importantly `"RScpp_static"`) and to specify them in `links` of the projects that use RenderScript: + +```lua +project("RSSupport") + kind("SharedLib") + files({"$(RENDERSCRIPT_TOOLCHAIN_PREBUILT_ROOT)/platform/$(TARGET_ARCH)/libRSSupport.so"}) +project("RSSupportIO") + kind("SharedLib") + files({"$(RENDERSCRIPT_TOOLCHAIN_PREBUILT_ROOT)/platform/$(TARGET_ARCH)/libRSSupportIO.so"}) +project("blasV8") + kind("SharedLib") + files({"$(RENDERSCRIPT_TOOLCHAIN_PREBUILT_ROOT)/platform/$(TARGET_ARCH)/libblasV8.so"}) +project("RScpp_static") + kind("StaticLib") + files({"$(RENDERSCRIPT_TOOLCHAIN_PREBUILT_ROOT)/platform/$(TARGET_ARCH)/libRScpp_static.a"}) +``` + +### Invoking ndk-build + +#### Setting up applicaton settings + +The basic setup is different depending on whether you're using the `externalNativeBuild` functionality of Gradle to build the application. + +The module generates an Application.mk file containing basic settings for the entire application, with some support for Premake configuration and platform filtering that may be useful, for instance, for choosing `APP_DEBUG` and `APP_OPTIM` based on the selected configuration. However, as ABI filtering is not available in Application.mk, it will be more coarse than for settings that go to Android.mk — it is assumed that all Application.mk-level settings should not depend on the target ABI, you can see the exact list of the settings that go to Application.mk in the list of supported project settings. + +If you're launching ndk-build as a custom task, or not using Gradle at all, you need to specify the path to the Application.mk in the ndk-build command arguments, as `NDK_APPLICATION_MK:=path/to/WORKSPACE_NAME.Application.mk`, where `WORKSPACE_NAME` is the name of the Premake workspace for the application. The Application.mk file is written to the `location` directory of the workspace. + +**Gradle's `externalNativeBuild`, however, causes most Application.mk settings to be ignored and overridden by the settings specified in the Gradle script itself.** Because of this, using the Application.mk file generated by this module may not be absolutely necessary, but for correctness, you should still set the `NDK_APPLICATION_MK` variable in ndk-build arguments, otherwise it will try to use the Application.mk from the default path — it's better to express the intentions explicitly than to rely on the file not currently existing. However, you'll need to specify the correct settings in the Gradle script manually anyway. Because Gradle scripts contain settings not only for NDK, but also for all aspects of the application, and since the layout of configurations — build types and product flavors — may vary greatly between applications, Gradle setup is out of the scope of this module. + +Here is an example of configuring the application settings relevant to the usage of files generated by this module: + +```groovy +android { + // The Android NDK version to build the application with. + // If omitted, the latest installed version will be used. + ndkVersion '23.0.7599858' + + // Per-configuration Gradle settings. + // May be specified for defaultConfig, as well as for buildTypes and productFlavors. + defaultConfig { + // Minimum Android SDK version supported by the application. + // Corresponds to APP_PLATFORM := android- in Application.mk (Premake `systemversion`). + minSdkVersion 14 + + externalNativeBuild { + ndkBuild { + // See com.android.build.api.dsl.ExternalNativeNdkBuildOptions documentation. + + // Application.mk path - specifying the correct one is recommended. + arguments 'NDK_APPLICATION_MK:=path/to/WORKSPACE_NAME.Application.mk', + // Premake platforms to build for - see the section about configuration and platform selection below. + 'PREMAKE_ANDROIDNDK_PLATFORMS:=Android-ARM', + 'PREMAKE_ANDROIDNDK_PLATFORMS+=Android-ARM64' + } + } + + ndk { + // See com.android.build.api.dsl.Ndk documentation for more useful options such as `jobs`. + + // The list of the ABIs to build this application for. + // Corresponds to APP_ABI in Application.mk. + // Should match the actual list of ABIs supported by at least some of the projects in the workspace. + // If omitted, ndk-build will try to build for all ABIs supported by the used NDK version. + // In this case, you should provide an architecture-agnostic platform/configuration for the projects. + // It may be fine not to specify this if you target only a subset of APIs, but ndk-build will be emitting warnings. + abiFilters 'armeabi-v7a', 'arm64-v8a' + + // The C++ STL to use. + // Corresponds to APP_STL in Application.mk. + // If omitted, APP_STL (Premake `cppstl` and `staticruntime`) from the Application.mk will be used. + // If that's not specified too, the default STL for the current API version will be selected. + stl 'c++_static' + } + } + + buildTypes { + release { + debuggable false + + externalNativeBuild { + ndkBuild { + // Premake configurations to build for - see the section about configuration and platform selection below. + arguments 'PREMAKE_ANDROIDNDK_CONFIGURATIONS:=Release' + } + } + } + + debug { + debuggable true + + externalNativeBuild { + ndkBuild { + // Premake configurations to build for - see the section about configuration and platform selection below. + arguments 'PREMAKE_ANDROIDNDK_CONFIGURATIONS:=Debug' + } + } + } + } + + externalNativeBuild { + ndkBuild { + // See com.android.build.api.dsl.NdkBuild documentation. + // This is required to initiate native code building at all. + + // The Android.mk file to use - the generated workspace Android.mk in the `location` of the workspace. + // Corresponds to APP_BUILD_SCRIPT in Application.mk. + path file('path/to/WORKSPACE_NAME.wks.Android.mk') + } + } +} +``` + +#### Specifying Premake configurations and platforms to build + +Because a single ndk-build invocation builds for multiple ABIs, the usage of the generated files is slightly unusual compared to most build systems targeted by Premake where only a single configuration and platform pair is selected for building. + +Instead of letting you select just one configuration and platform, this module allows you to execute ndk-build for **multiple platforms and configurations**. + +For this purpose, the module provides two variables that you need to set in the arguments of the ndk-build invocation (`externalNativeBuild.ndkBuild.arguments` for a Gradle build configuration): + +* `PREMAKE_ANDROIDNDK_CONFIGURATIONS` — Premake configurations to build, **at least one must be specified**. +* `PREMAKE_ANDROIDNDK_PLATFORMS` — Premake platforms to build for. Optional, if none are specified, will be building for all platforms specified in any project. + +To set a variable to just one value, you can use the `VARIABLE:=value` syntax of the ndk-build argument. For multiple values, you need to provide each in a separate argument as `VARIABLE+=value` (preferably the first with `:=` still to ensure it's overwritten if there's an environment variable with the same name). The Gradle example above shows how you can enable building for multiple platforms representing different ABIs, and for a configuration depending on the Gradle build type. + +This, however, does not mean that you can create debug and release builds at once — rather, this architecture is designed specifically to provide a way for building for multiple ABIs as usual while being able to use an `architecture` filter in Premake. Because of this, **you must ensure** that **for every ABI, only at most one configuration–platform pair will be selected** for every project. An ambiguous selection of platforms and configurations will result in an undefined behavior. + +Platform-agnostic projects — those that have no `platforms` inherited from the workspace or defined — will be built regardless of the value of `PREMAKE_ANDROIDNDK_PLATFORMS` (only configuration selection will be used for them). + +## Supported settings + +Please carefully verify that the settings you're using in your projects are handled in a way that's supported by this module and by ndk-build itself. Most importantly, please **check the settings that are listed as per-file or per-extension here**, especially if you have source files of different languages in your projects. + +### Per-file or per-configuration/per-platform for a project, ABI-filtered + +There is a small number of settings for which it's possible to specify the value for individual source files using the `files` filter. + +- **`armisa`** (new) = `"A32"` / `"T32"` (default) + - ARMv7 instruction set to use. By default, Thumb (`"T32"`) will be used for release builds. Debug builds, however, always use A32. + - At the project configuration scope, it controls `LOCAL_ARM_MODE`. + - At the file scope, it can be used to build the specified files for A32 in a Thumb project, **but not vice versa**, by adding the `.arm` suffix to the file in `LOCAL_SRC_FILES`. Alternatively, you can use the `.arm` suffix (if needed, as a part of `.arm.neon`, but not `.neon.arm`) directly in the `files` setting, which will take precedence. +- **`flags`** + - `ExcludeFromBuild` + - Also supported for excluding entire projects on specified configurations/platforms. +- **`vectorextensions`** (for the `"ARM"` architecture) = `"ARMv7"` (new) / `"NEON"` (default, new) + - Whether to allow the compiler to generate instructions from the NEON SIMD instruction set as part of its optimizations. Specifying `"ARMv7"` (analogous to `"IA32"` on x86) disables NEON, specifying `"NEON"` enables it. By default, for consistency with NDK versions starting with r21, NEON will be enabled, even if the project doesn't explicitly specify `vectorextensions` (so on pre-r21, NEON will be used for Premake projects too). + - At the project configuration scope, it controls `LOCAL_ARM_NEON`. + - At the file scope, it can be used to build the specified files with NEON code generation in a project with NEON disabled, **but not vice versa**, by adding the `.neon` suffix to the file in `LOCAL_SRC_FILES`. Alternatively, you can use the `.neon` suffix (if needed, as a part of `.arm.neon`, but not `.neon.arm`) directly in the `files` setting, which will take precedence. + - For the allowed values on other architectures, see the per-configuration/per-platform settings section. + +### Per-language or per-configuration/per-platform for a project, ABI-filtered + +While ndk-build doesn't provide a way to specify compiler flags and most other settings for individual source files, it supports multiple source languages with different compilers, which accept different build options. Premake, however, exposes most build settings under language-agnostic names. + +To specify which compiler should receive the needed value, you can use file extension filters. + +Because the module supports `.arm`, `.neon` and `.arm.neon` suffixes, as well as many C++ extensions, it's not recommended to list file extensions manually. Instead, built-in filter constants provided by the module should be preferred, as they include the ARM suffixes and all the supported extensions. + +For including a specific language, the following variables are available (note that they must be used directly, and if needed, via `..` concatenation — **`%{}` string interpolation will not locate them**): + +- `premake.modules.androidndk.filefilters.as` for the GNU Assembler (GAS) +- `premake.modules.androidndk.filefilters.asm` for Yasm +- `premake.modules.androidndk.filefilters.c` for C +- `premake.modules.androidndk.filefilters.cpp` for C++ +- `premake.modules.androidndk.filefilters.rs` for RenderScript + +Each of those filters is a single string which already has the `"files:"` prefix (for instance, `premake.modules.androidndk.filefilters.c` is `"files:**.c or files:**.c.arm or files:**.c.neon or files:**.c.arm.neon"`). + +To exclude a language from a file filter, `filefilters` also includes filters prefixed with `not`, such as `premake.modules.androidndk.filefilters.notc`. Unlike the inclusive filters, the exclusive are tables, because Premake exposes the logical `and` as separate table elements. `premake.modules.androidndk.filefilters.notc` is `{ "files:not **.c", "files:not **.c.arm", "files:not **.c.neon", "files:not **.c.arm.neon" }`, for example. Premake filter tables can be nested, you don't need to perform flattening manually if you want to combine the language filters with additional terms. + +In addition, raw combinations of extensions and ARM suffixes are available in `filefilters` with the `extensions` suffix, such as `premake.modules.androidndk.filefilters.cextensions`, which is `{ ".c", ".c.arm", ".c.neon", ".c.arm.neon" }` (without the `**` recursive wildcard). You can do your own processing of them using functions like `table.translate` and `table.concat`, for instance, to attach all the possible extensions to a specific file name. + +Note that ndk-build treats the uppercase `.C` extension as C++. To avoid imposing additional constraints, this module allows using the `.C` extension for C++, even though other Premake actions may treat it as C, especially considering that `path.hasextension` in Premake is case-insensitive. However, **Premake filters are case-insensitive, and `"files:**.c"` or `"files:**.C"` will match *both* C `.c` and C++ `.C` sources**, leading to the specified settings being used for both C and C++. Therefore, **it's recommended to completely avoid using the uppercase `.C` extension**. The `.C` extension is also not included in the `premake.modules.androidndk.filefilters` constants for this reason. + +It is important that **you must not try to derive a `language` filter from an extension filter** because the `language` setting has a `"project"` scope in Premake rather than `"config"`, and thus it doesn't support file-specific or even configuration- or platform-specific overrides, in addition to not having allowed values for GAS, Yasm and RenderScript, so **the following will not work**: + +```lua +filter(premake.modules.androidndk.cfilefilter) + language("C") -- This WILL NOT WORK! +filter(premake.modules.androidndk.cppfilefilter) + language("C++") -- This WILL NOT WORK! +filter("language:C") + defines({"MY_NULL=((void *)0)"}) +filter("language:C++") + defines({"MY_NULL=0"}) +``` + +In addition, if the `NoPCH` flag is not enabled in the project for the configuration–platform pair, and the file specified in `pchheader` is also listed in `files`, any file-filtered settings for it will be treated as C++ settings. + +With some exceptions, most language-specific settings can be set independently for each language supported by this module. + +- **`buildoptions`** (C, C++, GAS, Yasm, RenderScript) + - One build option may contain one or multiple compiler arguments separated with whitespaces. + - Double quotation marks (`"`) may be used to specify a single compiler argument containing whitespaces. + - If double quotation marks need to be escaped, they need to be prefixed with `\`, as `"\\\""` according to Lua string literal escaping rules. + - The backslash character (`\`) itself also needs to be escaped with another `\`, as `"\\\\"`, so the module can distinguish between a backslash used for escaping the double quote character and an actual backslash character. + - All other characters after GNU make `$` variable or function reference expansion are allowed, and other shell-interpreted characters will be escaped automatically at build time. + - Because of the way the values are gathered from the used filters, duplicate `buildoptions` are eliminated. For this reason, always specify multiple-argument options as `{ "-prefix value1", "-prefix value2" }`, not `{ "-prefix", "value1", "-prefix", "value2" }` (as the latter will become `"-prefix value1 value2"`). + - See the documentation for the `"LinkTimeOptimization"` flag here for details about the handling of `"-flto=…"`. +- **`cdialect`** (C) +- **`cppdialect`** (C++) +- **`flags`** + - `"FatalCompileWarnings"` (C, C++, Yasm) + - `"NoBufferSecurityCheck"` (C, C++) + - `"ShadowedVariables"` (C, C++) + - `"UndefinedIdentifiers"` (C, C++) +- **`defines`** (C, C++, Yasm) + - All `defines` are passed to the compiler before `undefines` even if they're specified both within and without an extension filter. +- **`disablewarnings`** (C, C++, Yasm) +- **`enablewarnings`** (C, C++, Yasm) +- **`fatalwarnings`** (C, C++, Yasm) + - For Yasm, this is treated as `enablewarnings`. +- **`floatingpoint`** (C, C++) + - `"Strict"` is treated as `"Default"`. +- **`floatingpointexceptions`** (C, C++) +- **`forceincludes`** (C, C++, Yasm) + - For C and C++, double quotation mark characters (`"`) are disallowed, as they would terminate the `#include "path"` statement inserted by the compiler. + - For Yasm, `"`, `'`, `;` characters are disallowed. +- **`includedirs`** (**C, C++, GAS and Yasm *combined***, RenderScript separately) + - C, C++, GAS and Yasm use **the same** list of include directories (that is written to `LOCAL_C_INCLUDES`). + - All `includedirs` are passed to the compiler before `sysincludedirs`. + - Whitespaces and `#`, `$` (after GNU make reference expansion) characters are disallowed. +- **`inlinesvisibility`** (C, C++) + - Unlike in other Premake actions using `premake.clang` or `premake.gcc`, this is also supported for C, not only C++. +- **`omitframepointer`** (C, C++) + - To use the Address Sanitizer, this needs to be set to `Off` — see the [Address Sanitizer](https://developer.android.com/ndk/guides/asan) guide on Android Developers. +- **`strictaliasing`** (C, C++) +- **`sysincludedirs`** (**C, C++, GAS and Yasm *combined***, RenderScript separately) + - See `includedirs`. +- **`undefines`** (C, C++, Yasm) + - All `defines` are passed to the compiler before `undefines` even if they're specified both within and without an extension filter. +- **`unsignedchar`** (C, C++) +- **`visibility`** (C, C++) + - Unlike in other Premake actions using `premake.clang` or `premake.gcc`, this is also supported for C, not only C++. +- **`warnings`** (C, C++, Yasm) + - For Yasm, only `"Off"` has effect — other values are treated as warnings enabled as normal. + +### Per-configuration/per-platform for a project, ABI-filtered + +- **`architecture`** + - If an architecture is specified, the settings for this configuration–platform pair will be used will be used while building for the respective ABI if the configuration and the platform are selected for building via `PREMAKE_ANDROIDNDK_CONFIGURATIONS` and `PREMAKE_ANDROIDNDK_PLATFORMS`. In this case, the `architecture` filter may be used to provide ABI-specific settings. + - `"ARM"` for the `armeabi-v7a` ABI. + - `"ARM64"` for the `arm64-v8a` ABI. + - `"x86"` for the `x86` ABI. + - `"x86_64"` for the `x86_64` ABI. + - Leave unspecified or set to `"universal"` to provide fallback settings that will be used for the project for ABIs without a specialization. + - Generally `architecture` should be specified under a `platforms` filter, or, depending on your configuration structure, under a `configurations` one. See the “Usage†section for more information ABI filtering of settings. +- **`exceptionhandling`** = `"Off"` / `"On"` (default) +- **`files`** + - For a built Premake project: + - The following languages are supported: + - C (`.c`) + - C++ (`.cc`, `.cp`, `.cxx`, `.cpp`, `.CPP`, `.c++`, `.C`) + - The list of allowed extensions is broader than the default for Premake v5.0.0-alpha16, but matches the default for the NDK r23). + - Note that while ndk-build (and therefore this setting) is case-sensitive, Premake filters are not. Therefore, a `files:**.c` or a `files:**.C` filter will cause both C `.c` and C++ `.C` to pass, and extension-filtered language-specific settings such as `buildoptions` intended only for C++ will be used for C as well if both are present in the project, for example — see the section about per-language settings. It's better to avoid using the `.C` extension completely. + - GNU Assembler (`.s`, `.S`) + - Yasm (`.asm`) + - `"x86"` and `"x86_64"` architectures only — will be ignored automatically while building for other ABIs, including in architecture-agnostic projects. + - RenderScript (`.rs`, `.fs`) + - `.arm`, `.neon` and `.arm.neon` (but not `.neon.arm`) suffixes are allowed — see the documentation for per-file `armisa` and `vectorextensions` settings, which can be used as an alternative (using file-filtered `armisa` and `vectorextensions` is recommended instead so filters like `"files:**.c"` can still be used rather than `"files:**.c or **.c.arm or **.c.neon or **.c.arm.neon"`, however — though for extension filtering, constants like `premake.modules.androidndk.filefilters.c` are recommended, which include the `.arm` and `.neon` suffixes). + - Whitespaces, backtick, and `"`, `#`, `$` (after GNU make reference expansion), `&`, `'`, `(`, `)`, `,`, `;`, `<`, `>`, `|` characters are disallowed. The backslash is allowed as a path separator on Windows, but disallowed on Linux. + - To create a prebuilt library project or to use an external Android.mk file, specify **only one** `.so` (for the `"SharedLib"` kind), `.a` (for the `"StaticLib"` kind) or `.mk` file, and **no other source files**. See the “Usage†section for more information about the required and supported settings for prebuilt and external projects. + - In prebuilt library paths, whitespaces, and `"`, `#`, `$` (after GNU make reference expansion), `%`, `&`, `*`, `;`, `<`, `>`, `?`, `|` characters are disallowed on Windows. On other operating systems, backtick, `'`, `(`, `)`, `\` characters are not supported. + - In external Android.mk paths, whitespaces are not disallowed. + - All irrelevant file types (such as headers) will be ignored automatically. +- **`flags`** + - `"ExcludeFromBuild"` + - Also supported for excluding individual source files from building. + - `"FatalLinkWarnings"` + - For shared libraries and executables only. + - Unlike in ndk-build itself, fatal link warnings are disabled by default (`LOCAL_DISABLE_FATAL_LINKER_WARNINGS := true`). + - `"LinkTimeOptimization"` + - If used in a static library, shared libraries and executables linked against it will also be linked with link-time optimization (the transitivity ensured for this options, however, doesn't apply to building of source files). + - It's possible to override the default link-time optimization type by specifying the desired link-time optimizations type, such as `-flto=thin`, in **both** `buildoptions` and `linkoptions`. The specific `-flto=` type will also be propagated from `linkoptions` of static library dependencies (though in this case, all linked static libraries must have the same LTO type, and static library dependency analysis only checks `linkoptions`, not `buildoptions`). + - `"NoPCH"` + - Causes `pchheader` to be ignored — see `pchheader` for more information. +- **`formatstringchecks`** (new) = `"Off"` / `"On"` (default) + - Corresponds to `LOCAL_DISABLE_FORMAT_STRING_CHECKS` (but enabling instead of disabling), whether to check `printf` format strings in the sources. +- **`isaextensions`** + - This should be used only in libraries with code used conditionally based on the information provided by `cpuid`, or for emulation purposes, otherwise it may prevent the app from working on physical devices. + - All `"x86"` and `"x86_64"` architecture `isaextensions` normally supported by Premake actions targeting Clang are supported. +- **`libdirs`** + - Search paths for library binaries specific directly via `links`. + - Not recommended as using library binaries directly via linker arguments is discouraged by ndk-build — prefer using prebuilt library projects, and see `links` for more information. + - All `libdirs` gathered from the project and its static library dependencies are passed to the linker before `syslibdirs`. +- **`linkoptions`** + - Most options are supported only in shared libraries and executables, but there are some exceptions which are exported from static libraries as well. + - See `buildoptions` for information about passing multiple arguments, characters that need to be escaped manually, deduplication, disallowed characters. + - Going to Clang++ rather than directly to LLD — `-Wl` may be needed for many options. + - Unlike in other Premake actions, some parsing is done: + - Options prefixed with `-l`, `-L` (with or without `-Wl`), `-Wl,--library`, `-Wl,--library-path` will go to `LOCAL_LDLIBS` instead of `LOCAL_LDFLAGS`, and will be exported from static libraries to dependent shared libraries, similarly to `links`. This may be useful if you have a project named the same as a system library, but you want to link against that system library (for instance, `-llog` if there is a project named `"log"`). See `links` and `libdirs` for more information. + - Options prefixed with `-u` (with or without `-Wl`) or `-Wl,--undefined` will also be exported from static libraries transitively to allow preserving “unused†symbols imported from static libraries in the dependent shared libraries, primarily JNI native function implementations in static libraries — similar to `wholelib`, but more granular, for individual symbols. + - See the documentation for the `"LinkTimeOptimization"` flag here for details about the handling of `"-flto=…"`. +- **`links`** + - Premake projects and system libraries to link against — usable in all kinds of projects (in shared libraries and executables, as well as in static libraries for transitive linkage). + - If a shared library or an executable is linked against a static library project, it will automatically be linked against all system libraries referenced by its static library dependencies, even if static library dependencies are chained via `links`. Transitivity across shared libraries is not provided, however, as it's not necessary and may be undesirable — instead of using `LOCAL_EXPORT_LDLIBS`, the module traverses the dependency tree by itself. This is done for consistency with other Premake actions such as Visual Studio. + - See `wholelib` and `wholelibs` for information about importing static libraries as whole archives. + - If you have a project named the same as a system library, but you still want to link against the system library, you can use a `buildoptions` entry with the `-l` prefix (for instance, `-llog` if there is a project named `"log"`). + - Generally shouldn't be used for anything but linking to other projects and to the NDK system libraries — ndk-build will generate warnings for non-system libraries. Prebuilt library projects (see the “Usage†section) should be preferred for linking against non-system library binaries directly. However, the module still provides the functionality for linking against arbitrary library files: + - Full paths, `libdirs` and `syslibdirs` can be used for specifying search paths (however, there's no way to link against two libraries with the same name under different paths). + - Both short library names and full file names are allowed. If the file name ends with `.a` or `.so`, it's treated as a full name — the `"lib"` prefix must be explicitly specified if needed. + - The `":"` prefix can be added to the file name explicitly to force handling of it as a full file name. +- **`kind`** + - `"SharedLib"` or `"StaticLib"` for a built or a prebuilt shared or static library. + - `"ConsoleApp"` for an executable. + - `"None"` to exclude the project from building for the current configuration/platform. + - Note that `"Makefile"` is not a supported option — an external Android.mk file must be used through `files`, and the project for it must have the `kind` that matches the actual ndk-build script included by it. + - For more information about the usage of prebuilt libraries and external Android.mk files, see the section about them in “Usageâ€. +- **`renderscriptcompatibility`** (new) = `"Off"` (default) / `"On"` + - Corresponds to `LOCAL_RENDERSCRIPT_COMPATIBILITY`. +- **`renderscriptincludedirsoverride`** (new) = `"Off"` (default) / `"On"` + - Corresponds to the usage of `LOCAL_RENDERSCRIPT_INCLUDES_OVERRIDE` instead of `LOCAL_RENDERSCRIPT_INCLUDES`, whether to ignore the NDK built-in platform and toolchain RenderScript include search paths. +- **`pchheader`** + - Suffixes such as `.arm` and `.neon` are not supported — handling of ARMv7 ISA overrides is performed internally in ndk-build. + - Whitespaces, backtick, and `"`, `#`, `$` (after GNU make reference expansion), `&`, `'`, `(`, `)`, `,`, `;`, `<`, `=`, `>`, `|` characters are disallowed (same as in `files` for sources, but additionally `=`). The backslash is allowed as a path separator on Windows, but disallowed on Linux. + - ndk-build treats the precompiled header as C++ code. The module will configure variables necessary for building C++ code (such as `LOCAL_CPPFLAGS`) if the precompiled header is present, however, if you have any C++-specific settings with a file extension filter, you need to provide at least one source file with the respective extension, otherwise this module will not be able to access those settings. If the file specified in `pchheader` is listed among `files`, any file-filtered settings applied to it will also be treated as C++ settings. + - Ignored if the `"NoPCH"` flag is enabled for the project configuration/platform. +- **`rtti`** = `"Off"` / `"On"` (default) +- **`targetprefix`**, **`targetname`**, **`targetsuffix`** + - Combined into `LOCAL_MODULE_FILENAME`. + - Overriding the binary path or extension is not supported. + - Whitespaces and `#`, `$` (after GNU make reference expansion), `%`, `/`, `;`, `|` characters are disallowed. + - Only removing the `lib` prefix works when invoking ndk-build from Gradle as of Gradle 7.0.2 because it gathers the names of the targets to build from `LOCAL_MODULE_FILENAME` instead of `LOCAL_MODULE`. If executing ndk-build manually without a list of targets, or with a correctly specified list of them, other values are supported. +- **`undefinedsymbols`** (new) = `"Off"` (default) / `"On"` + - For shared libraries and executables only. + - Corresponds to `LOCAL_ALLOW_UNDEFINED_SYMBOLS`, if enabled, errors will not be thrown for undefined symbols, instead, the symbols will be resolved at runtime. +- **`shortcommands`** (new) = `"Off"` (default) / `"On"` + - Corresponds to `LOCAL_SHORT_COMMANDS`, whether to execute build commands from a file rather than directly via the shell, as if the command line is too long, it may not fit in the 8191 character limit on Windows. +- **`syslibdirs`** + - See `libdirs`. +- **`system`** + - Must be `"android"` for the correct library name prefix (`"lib"`). +- **`thinarchive`** (new) = `"Off"` (default) / `"On"` + - For static libraries only. + - Corresponds to `LOCAL_THIN_ARCHIVE`, whether to store only object file paths instead of the object themselves in the static library being built. +- **`vectorextensions`** (for the `"x86"` and `"x86_64"` architectures) + - This should be used only in libraries with code used conditionally based on the information provided by `cpuid`, or for emulation purposes, otherwise it may prevent the app from working on physical devices. + - Allowed values on the `"x86"` architecture: `"SSE4.1"`, `"SSE4.2"` (new), `"AVX"`, `"AVX2"` (SSSE3 and below are required by Android and are always supported). + - Allowed values on the `"x86_64"` architecture: `"AVX"`, `"AVX2"` (SSE4.2 and below are required by Android and are always supported). + - For the allowed values on the `"ARM"` architecture, see the per-file settings section. +- **`wholelib`** (new) = `"Off"` (default) / `"On"` + - For static libraries only. + - Whether all object files from this static library should be included in the shared libraries (but not executables) referencing it (and this library will be added to `LOCAL_WHOLE_STATIC_LIBRARIES` instead of `LOCAL_STATIC_LIBRARIES`). This is useful, for instance, when exporting JNI native function implementations from a static library. + - This setting is for **exporting** the requirement that the static library must be linked as a whole archive — for the equivalent for importing, see `wholelibs` (a static library will be linked via `LOCAL_WHOLE_STATIC_LIBRARIES` rather than `LOCAL_STATIC_LIBRARIES` if it enables `wholelib` for itself, or if it's specified in `wholelibs` of the project importing it — you only need either to specify `wholelib` in the dependency or to add it to `wholelibs` of the dependent projects; though specifying both the same time is fine, that's redundant). +- **`wholelibs`** (new) + - List of static library project names among the `links` of the project to link as whole archives. + - For more information about whole static libraries, see `wholelib` — this setting provides the same functionality, but for **importing** static library dependencies as whole archives (only one of `wholelib` or `wholelibs` is enough). + - If a project is listed in `wholelibs`, it still needs to be added to `links` to be linked — this setting is merely a modifier, and therefore it's fine to just list all static library projects that need to be linked as whole archives once for the entire workspace (though `wholelib` is more suited for this purpose). + +### Per-configuration/per-platform for a workspace, not ABI-filtered + +These settings have effect on `APP` variables in the Application.mk rather than `LOCAL` variables in projects Android.mk files. + +There is a single Application.mk file for the entire workspace, and the same values of the `APP` variables are used regardless of the current target ABI. Thus, **for all configurations and platforms selected in `PREMAKE_ANDROIDNDK_CONFIGURATIONS` and `PREMAKE_ANDROIDNDK_PLATFORMS`, the values of these settings must be the same for**: + +- All projects in the workspace; +- All target ABIs (`architecture`s). + +For all selected configurations (which are visited in an undefined order), the module tries to locate the value in: + +1. The workspace itself. +2. The `startproject`. +3. For all selected platforms, visited in an undefined order, platform-specific projects, also in an undefined order. +4. Platform-agnostic projects, in an undefined order. + +You can use, for instance, `optimize("Off")` in the `"Debug"` configuration, and `optimize("On")` in the `"Release"` one, as long as you build the workspace with **either** `PREMAKE_ANDROIDNDK_CONFIGURATIONS:=Debug` or `PREMAKE_ANDROIDNDK_CONFIGURATIONS:=Release`, but not both at once. However, you can't disable optimizations for specific projects in the `"Release"` configuration in this case. + +- **`cppstl`** (new) = `"Default"` / `"c++"` / `"none"` / `"system"` + - The C++ standard library to use. + - NDK versions before r18 also support `"gabi++"`, `"gnustl"`, `"stlport"`. + - Use `staticruntime` to specify whether to link against the static standard library or against the shared one. +- **`optimize`** + - Handled in a way that approximates the behavior of other Premake actions. + - Unspecified, `"Off"`, `"Debug"` result in `APP_OPTIM := debug`. + - Anything else (`"Size"`, `"Speed"`, `"Full"`) results in `APP_OPTIM := release`, and also force-disables `APP_DEBUG`. +- **`symbols`** + - Handled in a way that approximates the behavior of other Premake actions. + - Ignored if `optimize` is set to a value that results in optimization enabled. + - Unspecified, `"Off"`, `"Default"` result in `APP_DEBUG := true`. + - Anything else (`"On"`, `"FastLink"`, `"Full"`) results in `APP_DEBUG := false`. +- **`staticruntime`** = `"Default"` / `"Off"` / `"On"` (default) + - Whether to use the static C++ standard library (`cppstl`) as opposed to a shared one. + - Static by default, consistent with the default CMake behavior (where `c++_static` is the default standard library). + - Ignored for `"none"` and `"system"` standard libraries. +- **`systemversion`** + - Minimum Android SDK version required by the application, as a string representation of a number (corresponds to `APP_PLATFORM := android-systemversion`). + +### Per-project or per-workspace + +- **`basedir`** +- **`configurations`** + - Whitespaces and `%`, `(`, `)`, `,` characters are disallowed (configuration and platform names are used in GNU make list function calls). +- **`location`** + - The directory where the Android.mk file for the project or the Application.mk and the root Android.mk for the workspace will be placed (if provided — otherwise `basedir` will be used). + - For projects, it is heavily recommended to set this to a folder that doesn't contain header files. This is the path that will be used as `LOCAL_PATH`, and ndk-build internally adds `LOCAL_PATH` as the last include directory, causing `LOCAL_PATH` to be treated as a more “system†include search path than the system paths themselves. This causes issues when the `LOCAL_PATH` directory contains header files named the same as C and C++ standard library headers — the LLVM libc++ uses `#include_next` in a few files (such as `math.h`), causing project's files to included from system headers; this is especially dangerous if the local `"math.h"` itself `#include`s ``. +- **`platforms`** + - See `configurations`. +- **`project`** + - Whitespaces and `#`, `$`, `%`, `/`, `:`, `;`, `=`, `|` characters are disallowed. + - GNU make variable or function references (`$`) are disallowed. +- **`startproject`** + - The start project will have a higher priority when locating setting values for Application.mk variables. +- **`workspace`** + - Whitespaces and `#`, `$` characters are disallowed. + - GNU make variable or function references (`$`) are disallowed. + +## Future work, contributing and omissions + +Several features were left temporarily or intentionally unimplemented in the current version of the module, however, they may be useful in certain cases. + +If your project depends on something missing from the module, feel free to open an issue, and we'll discuss how the needed functionality can be added to the module in a convenient and flexible way — or submit a pull request with the implementation! + +Note that the general preference is to use existing Premake settings wherever possible — potentially twisting them and adding the necessary constraints while still trying to keep the original semantic concepts — instead of adding new ones. Don't hesitate to exploit filters, to interpret a variable in different ways depending on the target ABI and overall context, to use any opportunity to make the module more compatible with existing projects for other targets. However, if new settings need to be added, prefer using `kind = "boolean"` settings rather than flags as the former have three states, making it easier to provide default behavior, especially if the new settings are promoted to the Premake core or used in other modules in the future, potentially on platforms with different defaults. + +If you're adding functionality that involves strings in a new place in the generated Android.mk and Application.mk files, make sure to check which non-letter ASCII characters, including different types of whitespaces, are supported by ndk-build in the specific context, especially if it's a file name or a path, on both Linux (which allows any characters in paths) and Windows (including UNC paths), with attention to backslash-escaping of shell-interpreted characters (see `androidndk.shellEscapedCharactersPostQuotes`), but not only them. All user input must be passed through `p.esc`, preferably early (and functions expecting output of `p.esc` as arguments must be named with the `PostEsc` suffix), to ensure the integrity of GNU make variable or function references (also make sure that paths including `$` references are handled correctly as absolute or as relative depending on the context, especially when joining — usually paths beginning with `$` should be considered absolute, and that's how Premake itself treats them, while a relative path needs to start with `./$` in this case) and of escaping of the comment character `#`. After `p.esc`, the invalid character check may be done for the non-reference part of the string if needed — see the usage `androidndk.staticallyHasPatternPostEsc` (build-time validation would be an overkill if there are references in the string, but to catch potential errors when porting projects from other platforms, generation-time checks are useful). In many places, strings are used in shell commands invoked by ndk-build — see how checkers and escapers starting with `androidndk.shellEscape` are used in the module; also shell character escaping needs to be done at build time, not at Premake invocation time, since it must be performed after expanding GNU make references. Also, if you're passing user input to a GNU make function, use an intermediate variable (see `androidndk.getTemporaryVariableReference`), as the user-provided string may contain brackets or commas, which would break the parsing of the function call. + +The following functionality has been omitted currently, but may be nice to have for reasons like compatibility: + +- Unit tests. This is a large module with complex input handling with a large number of edge cases for many settings that, in some cases, depend on the kind of the project, the target ABIs, source languages used in the project — written in a dynamically-typed language. Many errors and typos may occur. Tests verifying both supported and disallowed input need to be added — covering platform-dependent and platform-agnostic projects, ABI-dependent and ABI-agnostic projects, different kinds of binaries, prebuilt libraries, external Android.mk projects, source files written in various languages with extension filters and `.arm` and `.neon` suffixes, link settings and transitivity of them across chains of static libraries where needed, GNU make variable and function references, disallowed characters, shell-interpreted character escaping, and other cases handled by this module. +- Building for deprecated ABIs removed from the NDK — `armeabi`, `armeabi-v7a-hard`, `mips`, `mips64`. While simply adding a new `architecture` is trivial (though for choosing `armeabi-v7a-hard`, a special setting for the floating-point argument convention may be more suitable), that wouldn't imply complete support for the details of them — such as instruction set extensions (like the MXU vector extensions for MIPS), hardware floating-point unit support. MIPS-powered Android devices are extremely rare, and ARMv5 deprecation began in Android 4.0, having been completed in Android 4.4. +- Support for obsolete toolchains removed from the NDK, such as GCC, as well as toolchain version selection (as of r23, the NDK only offers one version of the Clang toolchain). +- Clang-Tidy — needs to be integrated into the architecture of Premake itself. +- Post-processing of the generated machine code and assembly source files via `LOCAL_FILTER_ASM` — needs to be added in a way friendly to the Premake architecture. +- Compatibility with the Visual Studio actions for Android (the built-in `android` module). It defines many settings in ways that aren't consistent with the Premake core itself, as well as adding redundant options: + - Values are generally lowercase, while the Premake core uses PascalCase internally. + - The additions to the list of the allowed `architecture` values are chaotic. For `armeabi`, it allows both `"arm"` and `"armv5"` (which are registered as separate options rather than aliases of each other), while for `armeabi-v7a`, it uses `"armv7"`. Premake itself provides an `"ARM"` option, which, given the removal of ARMv5 support from the NDK, is more likely to be expected to correspond to ARMv7 as opposed to ARMv5. `arm64-v8a` is exposed as `"aarch64"` instead of `"ARM64"` used in the Premake core. + - ARMv5 and ARMv7 instruction sets are presented as two options: a `"Thumb"` flag, and a `"thumbmode"` setting, with one of the allowed values being `"disabled"` rather than `"Default"` commonly used in Premake. + - `stl` is a new setting in it, but it uses misleading names. The `"gnustl"` and `"c++"` standard libraries provided by ndk-build are called `"gnu"` and `"libc++"` instead, which is likely not what users expect. For `"system"`, the value `"none"` is used — however, the NDK has both `"system"` (which is deprecated) and `"none"`, the difference between the two being `new` and `delete` being supported in `"system"`, while `"none"` providing no C++ standard library functionality at all. The name `stl` also doesn't imply C++ directly, which may cause issues if Premake gets support for a different language that also has the standard library referred to by the same acronym — unlike settings like `buildoptions`, this may be even workspace-level (and in ndk-build, it is), and thus the value is not file- or extension-filterable. + - `androidapilevel` is redundant due to the existence of `systemversion`. + - The `"posix"` system tag is not added for Android. +- The “call array†architecture is not used in the code as certain settings are handled in multiple places in different ways, and various kinds of preprocessing are done. Replacing parts of the generation code in project scripts is unlikely to be useful or convenient for this reason. diff --git a/src_rebuild/premake_modules/androidndk/UNLICENSE.txt b/src_rebuild/premake_modules/androidndk/UNLICENSE.txt new file mode 100644 index 000000000..68a49daad --- /dev/null +++ b/src_rebuild/premake_modules/androidndk/UNLICENSE.txt @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/src_rebuild/premake_modules/androidndk/_manifest.lua b/src_rebuild/premake_modules/androidndk/_manifest.lua new file mode 100644 index 000000000..01def5e8b --- /dev/null +++ b/src_rebuild/premake_modules/androidndk/_manifest.lua @@ -0,0 +1,11 @@ +-- premake-androidndk - a multi-ABI, multi-language ndk-build Android.mk generator for Premake. +-- Originally written by Vitaliy Kuzmin. +-- See README.md for detailed usage and design information. +-- Available under the Unlicense (see UNLICENSE.txt) or the BSD 3-Clause "New" or "Revised" License (see LICENSE.txt). + +return { + "_preload.lua", + "androidndk.lua", + "androidndk_project.lua", + "androidndk_workspace.lua", +}; diff --git a/src_rebuild/premake_modules/androidndk/_preload.lua b/src_rebuild/premake_modules/androidndk/_preload.lua new file mode 100644 index 000000000..e66cfbfb1 --- /dev/null +++ b/src_rebuild/premake_modules/androidndk/_preload.lua @@ -0,0 +1,110 @@ +-- premake-androidndk - a multi-ABI, multi-language ndk-build Android.mk generator for Premake. +-- Originally written by Vitaliy Kuzmin. +-- See README.md for detailed usage and design information. +-- Available under the Unlicense (see UNLICENSE.txt) or the BSD 3-Clause "New" or "Revised" License (see LICENSE.txt). + +local p = premake; + +p.api.addAllowed("system", p.ANDROID); +-- NEON is enabled by default, like in NDK r21. +-- Similar to how Premake defines "IA32" for x86, use "ARMv7" to disable NEON (and use the .neon suffix where needed). +p.api.addAllowed("vectorextensions", { "ARMv7", "NEON", "SSE4.2" }); +if os.systemTags[p.ANDROID] == nil then + os.systemTags[p.ANDROID] = { "android", "posix", "mobile" }; +end + +p.api.register({ + name = "armisa", + scope = "config", + kind = "string", + allowed = { + p.DEFAULT, + "A32", + "T32", + }, +}); + +p.api.register({ + name = "cppstl", + scope = "config", + kind = "string", + allowed = { + p.DEFAULT, + -- As of NDK r18, only none, c++ and system are available, with the latter being deprecated. + -- Supporting the older STLs is trivial, however. + "c++", + "gabi++", + "gnustl", + "none", + "stlport", + "system", + }, +}); + +p.api.register({ + name = "formatstringchecks", + scope = "config", + kind = "boolean", +}); + +p.api.register({ + name = "renderscriptcompatibility", + scope = "config", + kind = "boolean", +}); + +p.api.register({ + name = "renderscriptincludedirsoverride", + scope = "config", + kind = "boolean", +}); + +p.api.register({ + name = "shortcommands", + scope = "config", + kind = "boolean", +}); + +p.api.register({ + name = "thinarchive", + scope = "config", + kind = "boolean", +}); + +p.api.register({ + name = "undefinedsymbols", + scope = "config", + kind = "boolean", +}); + +-- Whether this static library project should be linked as a whole, even if not specified in `wholelibs` of dependent projects. +p.api.register({ + name = "wholelib", + scope = "config", + kind = "boolean", +}); + +-- Subset of `links` which need to be linked as a whole regardless of their `wholelib` setting. +p.api.register({ + name = "wholelibs", + scope = "config", + kind = "list:mixed", + tokens = true, +}); + +newaction({ + trigger = "androidndk", + shortname = "Android ndk-build", + description = "Generate Android ndk-build makefiles", + targetos = p.ANDROID, + valid_kinds = { p.CONSOLEAPP, p.SHAREDLIB, p.STATICLIB }, + valid_languages = { p.C, p.CPP }, + -- Premake workspace and project names can include periods, so using suffixes for both. + onWorkspace = p.modules.androidndk.onWorkspace, + onProject = p.modules.androidndk.onProject, +}); + +-- Decide when the full module should be loaded. +return function(config) + return _ACTION == "androidndk"; +end; diff --git a/src_rebuild/premake_modules/androidndk/androidndk.lua b/src_rebuild/premake_modules/androidndk/androidndk.lua new file mode 100644 index 000000000..549e99150 --- /dev/null +++ b/src_rebuild/premake_modules/androidndk/androidndk.lua @@ -0,0 +1,465 @@ +-- premake-androidndk - a multi-ABI, multi-language ndk-build Android.mk generator for Premake. +-- Originally written by Vitaliy Kuzmin. +-- See README.md for detailed usage and design information. +-- Available under the Unlicense (see UNLICENSE.txt) or the BSD 3-Clause "New" or "Revised" License (see LICENSE.txt). + +local p = premake; +p.modules.androidndk = {}; +local androidndk = p.modules.androidndk; +androidndk._VERSION = "1.0.0"; + +include("androidndk_project.lua"); +include("androidndk_workspace.lua"); + +include("_preload.lua"); + +-- Inserts one or multiple values into a table. +-- Useful for inserting compiler flags from built-in Premake toolsets. +function androidndk.insertKeyedFlat(tbl, value) + -- Skip if the value is nil, for instance, to quickly ignore irrelevant flags or values of settings without a mapping. + if value == nil then + return; + end + if type(value) == "table" then + for i, element in ipairs(value) do + androidndk.insertKeyedFlat(tbl, element); + end + return; + end + if tbl[value] ~= nil then + return; + end + table.insert(tbl, value); + tbl[value] = value; +end + +-- Rules regarding special characters in the input and how they're handled in this module (as of NDK r23, checked with `$(warning "$(VARIABLE)")`): +-- * Whitespaces (tab, vertical tab, form feed, space) - defining the word "whitespace" as all of these, unlike just "space": +-- * Horizontal tab being the first on a line defines a recipe. +-- * Otherwise, all whitespace characters behave like whitespaces, at least in the syntax of GNU make itself. +-- * Trimming behavior in various contexts relevant to Android.mk: +-- * All sides of a variable assignment - left-hand, the operator itself, right-hand - have leading whitespaces trimmed. +-- * In the right-hand side of a variable assignment, each line (separated with a \-escaped newline) is treated as a list element, like a complete token. +-- Any amount (including zero) of whitespaces or escaped newlines between two non-empty elements is collapsed into a single " " space. +-- However, if the last non-whitespace character in an element is $ (not $$, but $ alone), no space is inserted, they're just removed (including the $). +-- This rule, however, is directly applicable only to inner newline separators, not the beginning or the end of the whole right-hand side. +-- Whitespaces and newlines before the first non-empty element are trimmed (as leading whitespaces in general). +-- Behavior for the last element is less consistent. +-- If the last non-empty element is followed only by whitespaces, all those trailing whitespaces are preserved as their original encoding. +-- If there's a \-escaped newline after the last non-empty element, however, all trailing whitespaces and newlines are collapsed into one " " space. +-- Within the boundaries of one list element, however, all original whitespaces are preserved. +-- * Function call argument lists (as a whole, not individual arguments) have leading whitespaces trimmed. +-- * $() can be prepended or appended to include leading or trailing whitespaces in an element or the first function argument. +-- * Resolution: +-- * Pass through directly. +-- * Single-line variable generation: +-- * Preserve project-provided leading whitespaces for the whole value (or the first table element) using $(). +-- * Multiline list variable generation: +-- * Preserve project-provided leading and trailing whitespaces for each table element using $(), though not necessary for the last. +-- * Both "\" and " \" are fine for separating table elements. +-- However, using " \" is consistent with the general recipe line splitting rules of GNU make for joining with a space. +-- * Newline characters (carriage return, line feed): +-- * Start a new statement. +-- * Can be escaped with a backslash, but then the newline itself may be trimmed according to the whitespace trimming rules. +-- * True newlines can be kept in variable declarations using define/endef. +-- * Resolution: +-- * Replace unconditionally with spaces. +-- * Escaping handled separately. +-- * Not using define/endef until a real use in ndk-build scripts is found. +-- * Backslash: +-- * If the last character before a newline, behaves as a line split, trimmed according to the whitespace trimming rules. +-- * Escapes the number sign which starts a comment. +-- * In other cases, passed as a raw character. +-- * Resolution: +-- * Pass through directly. +-- * If the last character on a line and provided by the project, append $(). +-- * Dollar sign: +-- * A single dollar sign is a prefix for a variable or a function reference. +-- * $(name arguments) and ${name arguments} are the regular syntax. +-- * If neither () nor {} are used, one character immediately following the $ is used as the variable name. +-- * $ alone directly in the end of a variable assignment is treated as a raw $ character. +-- * The effect of a trailing $ on variable assignments split into multiple lines is described in the section about whitespaces. +-- * $$ results in a raw $ character. +-- * Resolution: +-- * Pass through directly to allow using environment variables and built-in variables like TARGET_ARCH_ABI in scripts, as well as functions. +-- * Prevent unclosed variable and function references with an undefined, but safe result. +-- The easiest way is to close them - doesn't require insertion into the middle - with a special case for the trailing $ (convert to $$). +-- * Number sign: +-- * Starts a comment. +-- * Escaped with a backslash for passing as raw data, but the backslash itself may be escaped as well. +-- * Backslashes preceding a # are treated as n/2 backslashes ((n-1)/2 if there is an odd number of them) in other cases. +-- * An even number of backslashes before the # in the original file causes the comment not to be escaped. +-- * Resolution: +-- * Escape with a single backslash and a terminator to prevent dependence on the number of backslashes, as $()\#. +-- * This makes concatenation of a string ending with \ and one starting with an escaped # safe. + +androidndk.commentEscapeString = "$()\\#"; +androidndk.commentUnescapePattern = string.escapepattern(androidndk.commentEscapeString); + +androidndk.whitespacesNonNewline = { + ["\t"] = "\t", + ["\v"] = "\v", + ["\f"] = "\f", + [" "] = " ", +}; + +-- Due to escaping of #, this function can't be called again on its result. +function androidndk.esc(value) + value = string.gsub(value, "#", androidndk.commentEscapeString); + -- Replace newlines with safer spaces. + value = string.gsub(value, "\r\n", " "); + value = string.gsub(value, "\n", " "); + value = string.gsub(value, "\r", " "); + -- Make sure there are no unclosed variable and function references. + -- Close $() and ${} with a bracket, and escape the final $. + -- As a result, all references will be closed and balanced. + local unclosedReferenceTerminators = {}; + local afterReferenceCharacter = false; + -- Need a while loop as the for loop counter can't be modified from the body. + local i = 1; + while i <= string.len(value) do + local character = string.sub(value, i, i); + if character == "\\" and string.sub(value, i + 1, i + 1) == "#" then + -- Escaped comment character. + i = i + 1; + character = "#"; + end + if afterReferenceCharacter then + -- The character after the $. + -- Options: $ (escaped raw $), an opening bracket (opening a reference), any other character (single-character reference). + if character == "(" then + table.insert(unclosedReferenceTerminators, ")"); + elseif character == "{" then + table.insert(unclosedReferenceTerminators, "}"); + end + afterReferenceCharacter = false; + else + -- Not the character after the $. + -- Options: $, a reference-closing bracket, any other raw character. + if character == "$" then + afterReferenceCharacter = true; + elseif #unclosedReferenceTerminators > 0 and character == unclosedReferenceTerminators[#unclosedReferenceTerminators] then + unclosedReferenceTerminators[#unclosedReferenceTerminators] = nil; + end + end + i = i + 1; + end + local referencesClosed = { value }; + -- Escape the incomplete single-character reference as a raw $. + if afterReferenceCharacter then + table.insert(referencesClosed, "$"); + end + -- Close the unclosed references. + for i2 = 1, #unclosedReferenceTerminators do + table.insert(referencesClosed, unclosedReferenceTerminators[#unclosedReferenceTerminators - i + 1]); + end + return table.concat(referencesClosed); +end + +function androidndk.setupGeneration() + -- The tab character is the recipe line prefix in makefiles. + p.indent(" "); + p.escaper(androidndk.esc); + p.eol("\n"); +end + +-- Whether the string contains GNU make variable or function references. +-- p.esc should be done prior to this. +-- Empty references, that are $() or ${}, are not treated as references, merely as delimiters. +-- $() is used in comment sign escaping, for example. +function androidndk.hasNonEmptyMakeReferencesPostEsc(value) + -- For strictness and simplicity, the lone trailing $ is treated as a reference too (can't be a result of p.esc anyway). + local i = 0; + while true do + i = string.find(value, "%$", i + 1); + if i == nil then + return false; + end + if string.sub(value, i, i + 1) == "$$" then + i = i + 1; + else + local potentiallyEmptyReference = string.sub(value, i, i + 2); + if potentiallyEmptyReference == "$()" or potentiallyEmptyReference == "${}" then + i = i + 2; + else + return true; + end + end + end +end + +-- Removes variable and function references and reverts p.esc's escaping of # and $ from a GNU make variable value (but not multiline). +-- Useful for coarse validation of string contents, whether there are unsupported characters. +-- What the references expand to isn't known at Premake time, but static value validation may be good to catch common issues. +-- To get a value that can be passed back to GNU make, do string.gsub of the "%$" pattern to "$$", and then p.esc. +-- For strictness and simplicity, the lone trailing $ is treated as a reference too (can't be a result of p.esc anyway). +function androidndk.unescapeAndRemoveMakeReferencesPostEsc(value) + -- Unescape p.esc's escaping of the comment character. + value = string.gsub(value, androidndk.commentUnescapePattern, "#"); + local newValue = {}; + local unclosedReferenceTerminators = {}; + local afterReferenceCharacter = false; + for i = 1, string.len(value) do + local character = string.sub(value, i, i); + if afterReferenceCharacter then + -- The character after the $. + -- Options: $ (escaped raw $), an opening bracket (opening a reference), any other character (single-character reference). + if character == "$" then + if not (#unclosedReferenceTerminators > 0) then + -- Escaped $. + table.insert(newValue, "$"); + end + elseif character == "(" then + table.insert(unclosedReferenceTerminators, ")"); + elseif character == "{" then + table.insert(unclosedReferenceTerminators, "}"); + end + afterReferenceCharacter = false; + else + -- Not the character after the $. + -- Options: $, a reference-closing bracket, any other raw character. + if character == "$" then + afterReferenceCharacter = true; + else + if #unclosedReferenceTerminators > 0 then + if character == unclosedReferenceTerminators[#unclosedReferenceTerminators] then + unclosedReferenceTerminators[#unclosedReferenceTerminators] = nil; + end + else + -- A raw character outside references. + table.insert(newValue, character); + end + end + end + end + return table.concat(newValue); +end + +function androidndk.staticallyHasPatternPostEsc(value, pattern) + return string.find(androidndk.unescapeAndRemoveMakeReferencesPostEsc(value, true), pattern) ~= nil; +end + +-- Characters that can be escaped with a backslash in Windows shell commands, except for \ itself and ". +-- \ and " should be escaped before these inside individual arguments as they may be wrapped in " by the module itself to include whitespaces. +-- On Windows, before other characters (except for \ and "), a \ is kept and treated as a directory separator instead. +-- Extracted by using all characters 33-126 (with special rules for #) prefixed with \ in a -Define and seeing the Clang error for its usage. +-- Matches Windows sh_chars_sh in GNU make, with the addition of a single quote. +-- On Linux, where \ is not a path separator, prefixing any character with \ results in it being escaped in the shell. +androidndk.shellEscapedCharactersPostQuotes = { + "#", "$", "&", "'", "(", ")", "*", ";", "<", ">", "?", "[", "]", "^", "`", "{", "|", "}" +}; + +-- Checkers are meant to be used in assignToVariablePostEsc calls, and assume the input is pre-escaped and doesn't contain non-empty references. + +function androidndk.shellEscapePostQuotesChecker(value) + -- Comments are escaped as $()\# - contains an empty reference. + -- However, this still includes the # itself, which needs to be escaped with a backslash in a shell command. + -- Raw dollar signs ($$ representing $) need to be escaped in a shell command too. + if string.find(value, "#") ~= nil or string.find(value, "%$%$") ~= nil then + return true; + end + -- Remove empty references to check if the value contains (){}. + -- It's assumed that these are the only kind of references that may be passed to checkers. + value = string.gsub(string.gsub(value, "%$%(%)", ""), "%${}", ""); + return string.find(value, "[&'%(%)%*;<>%?%[%]%^`{|}]") ~= nil; +end + +function androidndk.shellEscapeChecker(value) + -- The only two usages of a backslash after androidndk.esc are a raw backslash and a # escape sequence. + -- Both \ and # need to be escaped with \ in the shell. + -- So, it's okay to return true if there is any \ at all. + if string.find(value, "[\\\"]") ~= nil then + return true; + end + return androidndk.shellEscapePostQuotesChecker(value); +end + +function androidndk.shellEscapeModuleFileNameChecker(value) + -- : must be \-escaped on Linux. + -- It's a drive letter separator on Windows, however, so it's disallowed in LOCAL_MODULE_FILENAME there (it's a name, not a path). + if string.find(value, "[:]") ~= nil then + return true; + end + return androidndk.shellEscapeChecker(value); +end + +function androidndk.shellEscapeSrcFilesChecker(value) + -- : must be \-escaped on Linux, but cannot be escaped on non-Cygwin Windows, and is a drive letter separator there supported directly. + -- The differences are handled at build time according to $(HOST_OS). + if string.find(value, "[:]") ~= nil then + return true; + end + return androidndk.shellEscapeChecker(value); +end + +-- Using UPPER_CASE names because ndk-build reserves all lower-case names. +androidndk.shellEscapePreQuotesMakeCall = "PREMAKE_ANDROIDNDK_SHELL_ESCAPE_PRE_QUOTES"; +androidndk.shellEscapePostQuotesMakeCall = "PREMAKE_ANDROIDNDK_SHELL_ESCAPE_POST_QUOTES"; +androidndk.shellEscapeMakeCall = "PREMAKE_ANDROIDNDK_SHELL_ESCAPE"; +androidndk.shellEscapeModuleFileNameMakeCall = "PREMAKE_ANDROIDNDK_SHELL_ESCAPE_MODULE_FILENAME"; +androidndk.shellEscapeSrcFilesMakeCall = "PREMAKE_ANDROIDNDK_SHELL_ESCAPE_SRC_FILES"; + +androidndk.temporaryVariablePrefix = "PREMAKE_ANDROIDNDK_TEMPORARY_"; + +-- Gets or adds a temporary intermediate GNU make variable. +-- Useful for wrapping strings in GNU make function calls, for safety if they contain the closing bracket. +function androidndk.getTemporaryVariableReference(temporaryVariables, value) + local index = temporaryVariables[value]; + if index == nil then + index = #temporaryVariables + 1; + temporaryVariables[index] = value; + temporaryVariables[value] = index; + end + return "$(" .. androidndk.temporaryVariablePrefix .. index .. ")"; +end + +function androidndk.getCaseElse(anyCasesWritten) + if anyCasesWritten then + return "else "; + end + return ""; +end + +-- ABIs that be set by a combination of settings that can be expressed in a project. +-- With the architecture set to "universal" or nil, projects can be built for ABIs not in this list though. +-- If new ABIs are added to the NDK, they can be supported without updating this module at least in ABI-agnostic projects. +-- Sorted alphabetically. +androidndk.knownAbis = { + "arm64-v8a", + -- "armeabi" (ARMv5) was removed in NDK r17. + "armeabi-v7a", + -- "armeabi-v7a-hard" was removed in NDK r12. + -- Building for both calling conventions at once (if passing this table to APP_ABI) would be pointless anyway. + -- "mips" and "mips64" were removed in NDK r17. + -- Premake has no built-in architecture values for MIPS. + "x86", + "x86_64", +}; + +androidndk.ABI_ALL = "all"; + +androidndk.architectureAbis = { + [p.ARM] = "armeabi-v7a", + [p.ARM64] = "arm64-v8a", + [p.X86] = "x86", + [p.X86_64] = "x86_64", +}; + +-- Returns: +-- * The ABI name for supported configurations. +-- * "all" (like the "all" that can be passed to APP_ABI) if the configuration doesn't have an ABI filter. +-- * nil for unsupported configurations. +function androidndk.getConfigAbi(config) + -- While armeabi (ARMv5) and armeabi-v7a (ARMv7) may be both considered "ARM", ARMv5 is different enough to have its own `architecture` option. + -- Support for ARMv5, however, was removed in NDK r17. + -- If needed, it can be exposed via a separate configuration variable. + -- MIPS support was also removed in r17. + if config.architecture == nil or config.architecture == p.UNIVERSAL then + return androidndk.ABI_ALL; + end + return androidndk.architectureAbis[config.architecture]; +end + +function androidndk.preventWhitespaceTrimming(value) + if androidndk.whitespacesNonNewline[string.sub(value, 1, 1)] ~= nil then + -- Prevent trimming of the leading whitespaces. + value = "$()" .. value; + end + local trailingCharacter = string.sub(value, -1, -1); + if trailingCharacter == "\\" or androidndk.whitespacesNonNewline[trailingCharacter] ~= nil then + -- Don't treat the final backslash (like in a directory path) as line splitting. + -- Also prevent trimming of the trailing whitespaces. + value = value .. "$()"; + end + return value; +end + +-- Returns whether the post-processing call is needed for any element. +function androidndk.assignToVariablePostEsc(name, value, multiline, postProcessChecker, operator) + if operator == nil then + operator = ":="; + end + if type(value) == "string" then + value = { value }; + end + value = table.filterempty(value); + if not (#value > 0) then + return false; + end + local postProcessNeeded = false; + if multiline then + p.push("%s %s \\", name, operator); + for i, element in ipairs(value) do + local boundedLine = androidndk.preventWhitespaceTrimming(element); + if i >= #value then + p.w(boundedLine); + else + p.w("%s \\", boundedLine); + end + -- Post-processing is always needed if there are references because they are expanded to anything at runtime. + -- However, if not needed, don't waste build time doing unnecessary substitutions. + if postProcessChecker ~= nil and not postProcessNeeded and (androidndk.hasNonEmptyMakeReferencesPostEsc(element) or postProcessChecker(element)) then + postProcessNeeded = true; + end + end + p.pop(); + else + -- Inner whitespaces are preserved within the right-hand side of an assignment, safe to concatenate. + p.w("%s %s %s", name, operator, androidndk.preventWhitespaceTrimming(table.concat(value, " "))); + end + return postProcessNeeded; +end + +function androidndk.writePostProcessCall(variableName, makeCall) + p.w("%s := $(call %s,$(%s))", variableName, makeCall, variableName); +end + +function androidndk.isConfigSupported(config, reportWarnings) + if config.flags.ExcludeFromBuild then + -- Intentionally excluded. + -- Warnings specific to this module shouldn't be reported either as the configuration may not be targeting this module at all. + return false; + end + if config.project ~= nil then + assert(androidndk.isProjectSupported(config.project, false), "Usage of configurations for unsupported projects must not be attempted at all"); + end + -- Wrong kind or ABI are a normal situation when the project has both Android and non-Android configs. + -- Not reporting warnings for them (or any warnings as for non-Android targets these issues may be non-existent). + if androidndk.kindScripts[config.kind] == nil or androidndk.getConfigAbi(config) == nil then + return false; + end + local warningLocation; + if reportWarnings then + if config.project ~= nil then + warningLocation = string.format("workspace '%s' project '%s' configuration '%s'", config.workspace.name, config.project.name, config.name); + else + warningLocation = string.format("workspace '%s' configuration '%s'", config.workspace.name, config.name); + end + end + local configSupported = true; + -- Whitespaces because configurations are placed in a whitespace-separated list. + -- % because configurations and platforms are used in filter and filter-out, where % is a wildcard. + -- It can be escaped with \, but then the case when the % is preceded by other backslashes is complicated to handle. + -- The rest are disallowed due to usage of the names in GNU make function calls. + local disallowedCharacters = "%(),"; + local disallowedPattern = "[%s%%%(%),]"; + if androidndk.staticallyHasPatternPostEsc(p.esc(config.buildcfg), disallowedPattern) then + configSupported = false; + if reportWarnings then + p.warn( + "Skipping %s with configuration name containing whitespaces or disallowed characters %s", + warningLocation, disallowedCharacters); + end + end + if config.platform ~= nil and androidndk.staticallyHasPatternPostEsc(p.esc(config.platform), disallowedPattern) then + configSupported = false; + if reportWarnings then + p.warn( + "Skipping %s with platform name containing whitespaces or disallowed characters %s", + warningLocation, disallowedCharacters); + end + end + return configSupported; +end + +return androidndk; diff --git a/src_rebuild/premake_modules/androidndk/androidndk_project.lua b/src_rebuild/premake_modules/androidndk/androidndk_project.lua new file mode 100644 index 000000000..b310ecadc --- /dev/null +++ b/src_rebuild/premake_modules/androidndk/androidndk_project.lua @@ -0,0 +1,1743 @@ +-- premake-androidndk - a multi-ABI, multi-language ndk-build Android.mk generator for Premake. +-- Originally written by Vitaliy Kuzmin. +-- See README.md for detailed usage and design information. +-- Available under the Unlicense (see UNLICENSE.txt) or the BSD 3-Clause "New" or "Revised" License (see LICENSE.txt). + +local p = premake; +local androidndk = p.modules.androidndk; + +androidndk.kindScripts = { + [p.CONSOLEAPP] = "BUILD_EXECUTABLE", + [p.SHAREDLIB] = "BUILD_SHARED_LIBRARY", + [p.STATICLIB] = "BUILD_STATIC_LIBRARY", +}; + +androidndk.prebuiltKinds = { + [p.SHAREDLIB] = { + extension = ".so", + script = "PREBUILT_SHARED_LIBRARY", + }, + [p.STATICLIB] = { + extension = ".a", + script = "PREBUILT_STATIC_LIBRARY", + }, +}; + +function androidndk.isProjectSupported(project, reportWarnings) + -- Not allowing references or escaped $ in project names as they complicate path building as well as including project Android.mk files. + -- Premake treats any path beginning with $ as absolute. + -- LOCAL_MODULE and LOCAL_STATIC_LIBRARIES sweeping result: + -- Allowed directly on Linux: !+,-./@[]^_{}~ + -- Allowed if \-escaped on Linux: "` + -- Allowed if \-escaped on Linux, misleadingly displayed with \ in the terminal: &'()*<>? + -- Allowed if \\-escaped on Linux: \ + -- Disallowed: whitespaces, #$%:;=| + -- Stricter than LOCAL_MODULE_FILENAME derived from it by default for : and = (no action required). + -- However, more relaxed for / - to avoid issues, disallowing / too. + if string.find(project.name, "%$") ~= nil or androidndk.staticallyHasPatternPostEsc(p.esc(project.name), "[%s#%$%%/:;=|]") then + if reportWarnings then + p.warn( + "Skipping workspace '%s' project '%s' with name containing GNU make references, " .. + "whitespaces or characters disallowed in an ndk-build module name #$%%/:;=|", + project.workspace.name, project.name); + end + return false; + end + return true; +end + +function androidndk.getConfigCondition(buildcfg, platform, abi) + -- All requirements must be met by what is provided by ndk-build and the user for a configuration to be selected. + -- This is done by removing (filtering out) the current state from the set of the requirements. + -- If the resulting set is empty, the current state matches all the requirements. + + -- It's important that either all requirements are checked at once (like here), or the ABI check is the outermost ifeq. + -- PREMAKE_ANDROIDNDK_CONFIGURATIONS and _PLATFORMS include _all_ needed configurations and are the same throughout the ndk-build execution. + -- With nested buildcfg > platform > ABI conditionals, all ABIs would be entering the same buildcfg, and then the same platform within it. + -- However, the ABI is the primary criteria for selecting the platform (and the buildcfg if it depends on it). + -- The ABI should be narrowing down the selection either to one config, or to skipping the module. + -- The result for multiple configurations for one ABI is undefined. + + local state = {}; + local requirements = {}; + + -- Configs in projects are usually sorted by ABI (so ABI-agnostic fallbacks can be provided), then (by Premake) buildcfg, then platform. + -- So, using this order as well for better readability of generated makefiles. + + -- ABI filtering is done only when the ABI specified. + if abi ~= nil then + if abi ~= androidndk.ABI_ALL then + table.insert(state, "ABI=$(TARGET_ARCH_ABI)"); + table.insert(requirements, "ABI=" .. abi); + end + end + + -- At least one build configuration is required by Premake. + table.insert(state, "$(PREMAKE_ANDROIDNDK_CONFIGURATIONS_PREFIXED)"); + table.insert(requirements, "CONFIGURATION=" .. p.esc(buildcfg)); + + -- Platforms may be unspecified, in this case, configurations will have a nil platform. + if platform ~= nil then + table.insert(state, "$(PREMAKE_ANDROIDNDK_PLATFORMS_PREFIXED)"); + table.insert(requirements, "PLATFORM=" .. p.esc(platform)); + end + + return "ifeq ($(filter-out " .. table.concat(state, " ") .. "," .. table.concat(requirements, " ") .. "),)"; +end + +function androidndk.configUsesA32(config) + -- LOCAL_ARM_MODE and the .arm suffix have no effect on ABIs where they're not relevant - no need to check the ABI. + -- NDK uses T32 by default, and always switches to A32 for debug builds, but it's handled implicitly in it. + return config.armisa == "A32"; +end + +function androidndk.configUsesNeon(config) + -- LOCAL_ARM_NEON and the .neon suffix have no effect on ABIs where they're not relevant - no need to check the ABI. + -- On NDK r21, NEON is enabled by default. + -- Requiring setting vectorextensions to "ARMv7" (similar to "IA32") explicitly for disabling. + return config.vectorextensions ~= "ARMv7"; +end + +function androidndk.shellEscapePreQuotesPostEsc(argument, temporaryVariables) + if androidndk.hasNonEmptyMakeReferencesPostEsc(argument) then + -- Escape while building, after expanding variable and function references. + -- Can't use the text directly in a function call because it may contain brackets. + return "$(call " .. androidndk.shellEscapePreQuotesMakeCall .. "," .. androidndk.getTemporaryVariableReference(temporaryVariables, argument) .. ")"; + end + -- Unescape comment characters, which have \ in their escape sequence. + argument = string.gsub(argument, androidndk.commentUnescapePattern, "#"); + -- Pre-escape. + argument = string.gsub(string.gsub(argument, "\\", "\\\\"), "\"", "\\\""); + -- Re-escape comment characters. + argument = string.gsub(argument, "#", androidndk.commentEscapeString); + return argument; +end + +-- For calling with unescaped paths, otherwise paths starting with # will be considered absolute due to $()\# escaping. +-- In addition, may need to escape characters inserted by path.getrelative itself. +-- Unfortunately, if path.getrelative inserts any $ by itself while joining, they will be treated as references. +-- They wouldn't be able to be distinguished from references specified in the project itself. +function androidndk.getEscapedProjectRelativePath(project, file, joinWithLocalPath) + -- Must be synchronized with LOCAL_PATH setting. + -- Based on p.filename logic (relative to the location of the project's Android.mk). + -- If a path starts with a GNU make variable or function reference, it's assumed to be absolute, to allow using paths like $(NDK_ROOT). + -- Premake already treats paths starting with $ as absolute in path.getrelative, among other functions. + -- Relative paths starting with $ must be prefixed with ./. + local relativePath = path.getrelative(project.location or project.basedir, file); + -- For something like ./$(VARIABLE) (relative), path.getrelative can possibly return $(VARIABLE), which will be treated as absolute. + -- Premake treats all paths beginning with $ as absolute. + -- In this case, the path must still be written as relative to $(LOCAL_PATH), but path.join won't work. + -- path.join doesn't even work if the trailing part begins with ./$(VARIABLE). + -- Using concatenation for this purpose instead. + if joinWithLocalPath and (not path.isabsolute(relativePath) or (string.sub(relativePath, 1, 1) == "$" and string.sub(file, 1, 1) ~= "$")) then + relativePath = "$(LOCAL_PATH)/" .. relativePath; + end + return p.esc(relativePath); +end + +-- Excluding the .arm and .neon suffixes. +androidndk.asmExtension = ".asm"; +androidndk.cExtension = ".c"; +androidndk.sourceExtensions = { androidndk.asmExtension, androidndk.cExtension }; +androidndk.compilerFlagsVariables = { + [androidndk.asmExtension] = "ASMFLAGS", + [androidndk.cExtension] = "CONLYFLAGS", +}; +-- NDK r23 default-c++-extensions. +-- Strictly broader than path.iscppfile as of premake-core v5.0.0-alpha16. +-- path.iscppfile is not usable directly conveniently, however, as it also includes assembly and Objective-C/C++. +-- Do not use path.hasextension (or path.iscppfile) as it's case-insensitive unlike ndk-build, and will treat .C as C instead of C++. +-- Indexed for concatenation purposes. +androidndk.cppExtensions = { ".cc", ".cp", ".cxx", ".cpp", ".CPP", ".c++", ".C" }; +table.foreachi(androidndk.cppExtensions, function(extension) + -- Build a hash set for lookup in cppExtensions. + androidndk.cppExtensions[extension] = extension; + -- Register the extension as a C++ one. + table.insert(androidndk.sourceExtensions, extension); + androidndk.compilerFlagsVariables[extension] = "CPPFLAGS"; +end); +-- NDK r23 default-rs-extensions. +androidndk.rsExtensions = { ".rs", ".fs" }; +table.foreachi(androidndk.rsExtensions, function(extension) + -- Build a hash set for lookup in rsExtensions. + androidndk.rsExtensions[extension] = extension; + -- Register the extension as a RenderScript one. + table.insert(androidndk.sourceExtensions, extension); + androidndk.compilerFlagsVariables[extension] = "RENDERSCRIPT_FLAGS"; +end); +androidndk.asExtensions = { ".s", ".S" }; +table.foreachi(androidndk.asExtensions, function(extension) + -- Build a hash set for lookup in asExtensions. + androidndk.asExtensions[extension] = extension; + -- Register the extension as a GAS one. + table.insert(androidndk.sourceExtensions, extension); + androidndk.compilerFlagsVariables[extension] = "ASFLAGS"; +end); +-- Build a hash set for lookup in sourceExtensions. +table.foreachi(androidndk.sourceExtensions, function(extension) + androidndk.sourceExtensions[extension] = extension; +end); + +-- Define variables usable by projects for language filtering via extension filters. +-- Lowercase because it's the public interface for projects. +androidndk.filefilters = {}; +function androidndk.buildExtensionFileFilterTerms(extensions) + local extensionsCaseInsensitive = {}; + -- Premake filters are case-insensitive - don't add the same extension twice. + for i, extension in ipairs(extensions) do + table.insertkeyed(extensionsCaseInsensitive, string.lower(extension)); + end + -- Build filters including subdirectories, adding all possible ARM suffixes. + local extensionFilters = {}; + for i, extension in ipairs(extensionsCaseInsensitive) do + local extensionFilter = "**" .. extension; + table.insert(extensionFilters, extensionFilter); + table.insert(extensionFilters, extensionFilter .. ".arm"); + table.insert(extensionFilters, extensionFilter .. ".neon"); + table.insert(extensionFilters, extensionFilter .. ".arm.neon"); + end + return extensionFilters; +end +-- Initializing public helpers (and thus lowercase names) - don't remove even though they are not referenced within the module itself! +(function() + local languageFilterVariables = { + { + language = "as", + extensions = androidndk.asExtensions, + }, + { + language = "asm", + extensions = { androidndk.asmExtension }, + }, + { + language = "c", + extensions = { androidndk.cExtension }, + }, + { + language = "cpp", + -- Premake filters are case-insensitive, so C sources would pass **.C as well - skip it in the C++ filter. + -- It's not recommended to use the .C extension at all for this reason. + extensions = table.filter( + androidndk.cppExtensions, + function(extension) + return extension ~= ".C"; + end), + }, + { + language = "rs", + extensions = androidndk.rsExtensions, + }, + }; + for i, languageFilterVariable in ipairs(languageFilterVariables) do + local extensionsCaseInsensitive = {}; + -- Premake filters are case-insensitive - don't add the same extension twice. + for i2, extension in ipairs(languageFilterVariable.extensions) do + table.insertkeyed(extensionsCaseInsensitive, string.lower(extension)); + end + -- Build arrays of raw extensions with all possible ARM suffixes: + -- androidndk.prefixfilefilterextensions = { ".ext", ".ext.arm", ".ext.neon", ".ext.arm.neon"... } + -- This allows arbitrary usage of table.translate, including to prepend a specific file name. + local extensionsWithSuffixes = {}; + for i2, extension in ipairs(extensionsCaseInsensitive) do + table.insert(extensionsWithSuffixes, extension); + table.insert(extensionsWithSuffixes, extension .. ".arm"); + table.insert(extensionsWithSuffixes, extension .. ".neon"); + table.insert(extensionsWithSuffixes, extension .. ".arm.neon"); + end + androidndk.filefilters[languageFilterVariable.language .. "extensions"] = extensionsWithSuffixes; + -- Build inclusive filters for all subdirectories as a string. + -- androidndk.filefilters.language = "files:**.ext or files:**.ext.arm or files:**.ext.neon or files:**.ext.arm.neon..." + androidndk.filefilters[languageFilterVariable.language] = + table.concat( + table.translate( + extensionsWithSuffixes, + function(extension) + return "files:**" .. extension; + end), + " or "); + -- Build exclusive filters for all subdirectories as a table. + -- androidndk.filefilters.notlanguage = { "files:not **.ext", "files:not **.ext.arm", "files:not **.ext.neon", "files:not **.ext.arm.neon"... } + androidndk.filefilters["not" .. languageFilterVariable.language] = + table.translate( + extensionsWithSuffixes, + function(extension) + return "files:not **" .. extension; + end); + end +end)(); + +-- In this module, flags are gathered from both the project configuration and per-file configuration. +-- ndk-build, however, doesn't support per-file flags. +-- Per-file configuration is needed though because compilers for different languages may need different flags. +-- The `language` setting is a whole-project setting, it alone can't be used in a filter to specify which compiler should receive the flags. +-- Therefore, the file extension must be used as a filter. +-- This leads of a lot of duplication - every file in the project has project-level flags as well as extension-specific flags. +-- For this reason, each flag is added only once. +-- Because of this, a flag may contain one or multiple whitespace-separated compiler argument. +-- "-include a.h -include b.h -include \"c d.h\" -include \"c d.h\"" must NOT be deduplicated as: +-- { "-include", "a.h", "b.h", "\"c d.h\"" } +-- or as: +-- { "-include", "a.h", "b.h", "\"c", "d.h\"" } +-- It must be: +-- { "-include a.h", "-include b.h", "-include \"c d.h\"" } + +-- Built-in flags (derived from from boolean or enumeration settings) here are added to the flags table directly as p.esc(value). +-- Most shell characters in them will be escaped at build time, however, " and \ will not. +-- So, it's possible to have built-in flags corresponding to one (possibly with quoted whitespaces) or multiple (with unquoted whitespaces) compiler arguments. +-- If a double quote character or a backslash needs to be used as a raw character, it needs to be pre-escaped manually in the flag literal here. +-- GNU make variable and function references should be used in built-in flags very carefully for the same reason. +-- Ordering of the flags is preserved only for their first occurrences in each of the LOCAL_languageFLAGS. +-- In Clang, if there are conflicting flags (such as -f and -fno), the ones later in the list take precedence. +-- Per-extension flags are therefore added after project-scope flags so the former can override the latter. + +-- Makes the value a single command line arguments passed to the compiler with a prefix, which may be in the same or in a separate argument. +-- The value must be pre-escaped with p.esc. +-- The prefix (not pre-escaped) will be handled similar to built-in flags, and may contain unquoted whitespaces for separate command line arguments. +-- Examples of the output: +-- * -DCSTRING=\"Hello\" +-- * -D"CSTRING=\"Hello, world!\"" +-- * -D "YASMSTRING=\"Hello, world!\"" +-- * -include header.h +-- * -include "header file.h" +-- * PREMAKE_ANDROIDNDK_TEMPORARY_1 := $(ENVIRONMENT_VARIABLE)/New folder (2)/header file.h +-- -include "$(call PREMAKE_ANDROIDNDK_SHELL_ESCAPE_PRE_QUOTES,$(PREMAKE_ANDROIDNDK_TEMPORARY_1))" +function androidndk.getSingleArgumentPrefixedFlagPostValueEsc(value, prefix, temporaryVariables) + local flagParts = { p.esc(prefix) }; + -- While it's completely safe to add excessive quotes in general, in some cases ndk-build doesn't expect them. + -- One example being LOCAL_LDLIBS, which is designed only for linking to Android system libraries. + -- ndk-build checks each entry against a list of known Android system libraries, and throws a warning if any non-system ones are specified. + -- The intended usage pattern of LOCAL_LDLIBS `LOCAL_LDLIBS := -landroid -llog`, not ``LOCAL_LDLIBS := -l"android" -l"log"`. + -- So, adding quotes only if there are whitespaces or references (for which it's not known whether they will be expanded to something containing whitespaces). + local isQuoted = androidndk.hasNonEmptyMakeReferencesPostEsc(value) or androidndk.staticallyHasPatternPostEsc(value, "%s"); + if isQuoted then + table.insert(flagParts, "\""); + end + table.insert(flagParts, androidndk.shellEscapePreQuotesPostEsc(value, temporaryVariables)); + if isQuoted then + table.insert(flagParts, "\""); + end + return table.concat(flagParts); +end + +-- System include directories are normally placed after the local ones, including in ndk-build itself. +androidndk.includeDirsConfigSettings = { "includedirs", "sysincludedirs" }; + +function androidndk.addIncludeDirs(includeDirs, config) + -- In LOCAL_C_INCLUDES (tested on it, but LOCAL_RENDERSCRIPT_INCLUDES goes to the host-c-includes call too): + -- Allowed directly on Linux: !%*+,-./:=?@[]^_{}~ + -- Allowed if \-escaped on Linux: "&'();<>\`| + -- Escaped UNC paths (beginning with \\\\) are supported on Windows. + -- Disallowed: whitespaces, #$ + -- In the NDK itself, either absolute or relative to the NDK root, not to the local path - need $(LOCAL_PATH) explicitly. + for i, settingName in ipairs(androidndk.includeDirsConfigSettings) do + local settingOutput = includeDirs[settingName]; + if settingOutput == nil then + settingOutput = {}; + includeDirs[settingName] = settingOutput; + end + local settingInput = config[settingName]; + for i2, includeDir in ipairs(settingInput) do + local includeDirPath = androidndk.getEscapedProjectRelativePath(config.project, includeDir, true); + if androidndk.staticallyHasPatternPostEsc(includeDirPath, "[%s#%$]") then + p.warn( + "Skipping workspace '%s' project '%s' configuration '%s' include directory '%s' with whitespaces or disallowed characters #$", + config.workspace.name, config.project.name, config.name, includeDir); + else + table.insert(settingOutput, includeDirPath); + end + end + end +end + +function androidndk.writeProjectIncludeDirs(includeDirsForAllVariables, variable, escapeShellCharacters) + local includeDirsForVariable = includeDirsForAllVariables[variable]; + if includeDirsForVariable == nil then + return false; + end + -- Merge all config settings the include directories are gathered from in the correct order. + local includeDirsForVariableMerged = {}; + for i, configSettingName in ipairs(androidndk.includeDirsConfigSettings) do + if includeDirsForVariable[configSettingName] ~= nil then + for i2, includeDir in ipairs(includeDirsForVariable[configSettingName]) do + table.insertkeyed(includeDirsForVariableMerged, includeDir); + end + end + end + local localVariable = "LOCAL_" .. variable; + if androidndk.assignToVariablePostEsc(localVariable, includeDirsForVariableMerged, true, androidndk.shellEscapeChecker) then + if escapeShellCharacters then + androidndk.writePostProcessCall(localVariable, androidndk.shellEscapeMakeCall); + end + return true; + end + return false; +end + +-- Assuming Clang - GCC was removed in NDK r13. +-- RenderScript is based on C99, but for writing compute kernels. + +-- Supported values are built-in flags as single strings and tables of strings (will be separate flags table elements). + +androidndk.asmFlagsSettingFlags = { + { key = "FatalCompileWarnings", value = "-Werror" }, +}; + +androidndk.cCppFlagsSettingFlags = { + { key = "FatalCompileWarnings", value = "-Werror" }, + -- LinkTimeOptimization is per-project, not per-extension, as it needs to be easily queryable in static library dependency analysis. + -- NoBufferSecurityCheck is C++-only in p.clang, but nothing precludes its usage in C. + -- Android uses -fstack-protector-strong by default as of NDK r23. + { key = "NoBufferSecurityCheck", value = "-fno-stack-protector" }, + { key = "ShadowedVariables", value = "-Wshadow" }, + { key = "UndefinedIdentifiers", value = "-Wundef" }, +}; + +androidndk.rsFlagsSettingFlags = { + { key = "FatalCompileWarnings", value = "-Werror" }, + { key = "ShadowedVariables", value = "-Wshadow" }, + { key = "UndefinedIdentifiers", value = "-Wundef" }, +}; + +-- Instead of using p.clang and mapFlags (with all its complexity), listing only what's relevant to ndk-build. +-- Supported keys are only strings and booleans (as p.OFF and p.ON), no tables (to avoid the complexity of keeping a deterministic order). +-- Supported values are built-in flags as single strings and tables of strings (will be separate flags table elements). +-- Values may contain unquoted (for multiple options) or quoted whitespaces, or \-escaped " and \, but p.esc will be done before writing. + +androidndk.asmSettingValueFlags = { + { + key = "warnings", + values = { + [p.OFF] = "-w", + }, + }, +}; + +androidndk.cCppSettingValueFlags = { + -- architecture is chosen via APP_ABI. + -- flags are handled separately to avoid ensuring deterministic table ordering. + { + key = "floatingpoint", + values = { + Fast = "-ffast-math", + }, + }, + -- floatingpointexceptions not present in p.clang as of v5.0.0-alpha16, added here. + { + key = "floatingpointexceptions", + values = { + [p.ON] = "-ftrapping-math", + [p.OFF] = "-fno-trapping-math", + }, + }, + { + key = "strictaliasing", + values = { + [p.OFF] = "-fno-strict-aliasing", + Level1 = { "-fstrict-aliasing", "-Wstrict-aliasing=1" }, + Level2 = { "-fstrict-aliasing", "-Wstrict-aliasing=2" }, + Level3 = { "-fstrict-aliasing", "-Wstrict-aliasing=3" }, + }, + }, + -- optimize is chosen via APP_OPTIM. + -- pic is always enabled by ndk-build and is required on Android. + -- vectorextensions are per-architecture. + -- isaextensions are per-architecture. + { + key = "warnings", + values = { + [p.OFF] = "-w", + High = "-Wall", + Extra = { "-Wall", "-Wextra" }, + Everything = "-Weverything", + }, + }, + -- symbols are chosen via APP_DEBUG. + { + key = "unsignedchar", + values = { + [p.ON] = "-funsigned-char", + [p.OFF] = "-fno-unsigned-char", + }, + }, + -- omitframepointer Off is needed for the Address Sanitizer, so exposed here. + -- More info about omitframepointer on Android: https://github.com/android/ndk/issues/824 + { + key = "omitframepointer", + values = { + [p.ON] = "-fomit-frame-pointer", + [p.OFF] = "-fno-omit-frame-pointer", + }, + }, + -- visibility and inlinesvisibility are C++-only in p.clang in v5.0.0-alpha16, but nothing precludes their usage in C. + { + key = "visibility", + values = { + Default = "-fvisibility=default", + Hidden = "-fvisibility=hidden", + Internal = "-fvisibility=internal", + Protected = "-fvisibility=protected", + }, + }, + { + key = "inlinesvisibility", + values = { + Hidden = "-fvisibility-inlines-hidden", + }, + }, +}; + +androidndk.cSettingValueFlags = { + { + key = "cdialect", + values = { + ["C89"] = "-std=c89", + ["C90"] = "-std=c90", + ["C99"] = "-std=c99", + ["C11"] = "-std=c11", + ["gnu89"] = "-std=gnu89", + ["gnu90"] = "-std=gnu90", + ["gnu99"] = "-std=gnu99", + ["gnu11"] = "-std=gnu11", + }, + }, +} + +androidndk.cppSettingValueFlags = { + -- exceptionhandling is chosen via LOCAL_CPP_FEATURES. + { + key = "cppdialect", + values = { + ["C++98"] = "-std=c++98", + ["C++0x"] = "-std=c++0x", + ["C++11"] = "-std=c++11", + ["C++1y"] = "-std=c++1y", + ["C++14"] = "-std=c++14", + ["C++1z"] = "-std=c++1z", + ["C++17"] = "-std=c++17", + ["C++2a"] = "-std=c++2a", + ["C++20"] = "-std=c++20", + ["gnu++98"] = "-std=gnu++98", + ["gnu++0x"] = "-std=gnu++0x", + ["gnu++11"] = "-std=gnu++11", + ["gnu++1y"] = "-std=gnu++1y", + ["gnu++14"] = "-std=gnu++14", + ["gnu++1z"] = "-std=gnu++1z", + ["gnu++17"] = "-std=gnu++17", + ["gnu++2a"] = "-std=gnu++2a", + ["gnu++20"] = "-std=gnu++20", + ["C++latest"] = "-std=c++20", + }, + }, + -- rtti is chosen via LOCAL_CPP_FEATURES. +}; + +androidndk.rsSettingValueFlags = { + { + key = "warnings", + values = { + [p.OFF] = "-w", + High = "-Wall", + Extra = { "-Wall", "-Wextra" }, + Everything = "-Weverything", + }, + }, + -- No other cCppFlagsSettingFlags and cSettingValueFlags are supported. +}; + +androidndk.asmWarningFlags = { + { key = "enablewarnings", prefix = "-W" }, + { key = "disablewarnings", prefix = "-Wno-" }, + -- No -Werror= on YASM, pick the closest to the intention. + { key = "fatalwarnings", prefix = "-W" }, +}; + +androidndk.cCppWarningFlags = { + { key = "enablewarnings", prefix = "-W" }, + { key = "disablewarnings", prefix = "-Wno-" }, + { key = "fatalwarnings", prefix = "-Werror=" }, +}; + +function androidndk.addCompilerFlagsForExtension(flagsForExtension, extension, config, temporaryVariables) + -- Make sure the table for generic flags exists. + if flagsForExtension.buildoptions == nil then + flagsForExtension.buildoptions = {}; + end + + local isCCpp = extension == androidndk.cExtension or androidndk.cppExtensions[extension] ~= nil; + + local languageDefinesPrefix = nil; + local languageUndefinesPrefix = nil; + local languageFlagsSettingFlags = nil; + local languageSettingValueFlagsTables = {}; + local languageWarningFlags = nil; + if extension == androidndk.asmExtension then + -- The defines prefix is separated into a different argument. + languageDefinesPrefix = "-D "; + languageUndefinesPrefix = "-U "; + languageFlagsSettingFlags = androidndk.asmFlagsSettingFlags; + table.insert(languageSettingValueFlagsTables, androidndk.asmSettingValueFlags); + languageWarningFlags = androidndk.asmWarningFlags; + elseif isCCpp then + -- The defines prefix is in the same argument (though separation also works, but ndk-build itself doesn't separate). + languageDefinesPrefix = "-D"; + languageUndefinesPrefix = "-U"; + -- Currently only shared between C and C++. + languageFlagsSettingFlags = androidndk.cCppFlagsSettingFlags; + table.insert(languageSettingValueFlagsTables, androidndk.cCppSettingValueFlags); + if androidndk.cppExtensions[extension] ~= nil then + table.insert(languageSettingValueFlagsTables, androidndk.cppSettingValueFlags); + else + table.insert(languageSettingValueFlagsTables, androidndk.cSettingValueFlags); + end + languageWarningFlags = androidndk.cCppWarningFlags; + elseif androidndk.rsExtensions[extension] ~= nil then + -- -D and -U are not supported. + languageFlagsSettingFlags = androidndk.rsFlagsSettingFlags; + table.insert(languageSettingValueFlagsTables, androidndk.rsSettingValueFlags); + languageWarningFlags = androidndk.cCppWarningFlags; + end + + -- Preprocessor definitions (not supported by the GNU assembler). + -- Potentially with whitespaces, but need to go to a single (quoted) argument. + -- Defines before undefines for consistency with other Premake actions such as Visual Studio. + -- Placing them in separate tables so they're not mixed if both defines are undefines are provided by both the project and the extension filter. + -- Not doing the same for -D and -U provided via raw buildoptions though to avoid interfering with the project-provided order. + if languageDefinesPrefix ~= nil then + if flagsForExtension.defines == nil then + flagsForExtension.defines = {}; + end + for i, define in ipairs(config.defines) do + table.insertkeyed( + flagsForExtension.defines, + androidndk.getSingleArgumentPrefixedFlagPostValueEsc(p.esc(define), languageDefinesPrefix, temporaryVariables)); + end + end + if languageUndefinesPrefix ~= nil then + if flagsForExtension.undefines == nil then + flagsForExtension.undefines = {}; + end + for i, undefine in ipairs(config.undefines) do + table.insertkeyed( + flagsForExtension.undefines, + androidndk.getSingleArgumentPrefixedFlagPostValueEsc(p.esc(undefine), languageUndefinesPrefix, temporaryVariables)); + end + end + + -- Forced include paths (not supported by the GNU assembler). + if extension == androidndk.asmExtension then + for i, forceInclude in ipairs(config.forceincludes) do + local forceIncludePath = androidndk.getEscapedProjectRelativePath(config.project, forceInclude, true); + if androidndk.staticallyHasPatternPostEsc(forceIncludePath, "[\"';]") then + p.warn( + "Skipping workspace '%s' project '%s' configuration '%s' Yasm force include '%s' with path containing disallowed characters \"';", + config.workspace.name, config.project.name, config.name, forceInclude); + else + table.insertkeyed( + flagsForExtension.buildoptions, + androidndk.getSingleArgumentPrefixedFlagPostValueEsc(forceIncludePath, "-P ", temporaryVariables)); + end + end + elseif isCCpp then + -- -include is not supported by RenderScript. + for i, forceInclude in ipairs(config.forceincludes) do + local forceIncludePath = androidndk.getEscapedProjectRelativePath(config.project, forceInclude, true); + -- -include %s becomes #include "%s", the inner " is considered a string terminator. + if androidndk.staticallyHasPatternPostEsc(forceIncludePath, "[\"]") then + p.warn( + "Skipping workspace '%s' project '%s' configuration '%s' C/C++ force include '%s' with path containing a disallowed double quote character", + config.workspace.name, config.project.name, config.name, forceInclude); + else + table.insertkeyed( + flagsForExtension.buildoptions, + androidndk.getSingleArgumentPrefixedFlagPostValueEsc(forceIncludePath, "-include ", temporaryVariables)); + end + end + end + + -- Flags for configuration flags setting. + if languageFlagsSettingFlags ~= nil then + for i, flagsSettingKeyValue in ipairs(languageFlagsSettingFlags) do + if config.flags[flagsSettingKeyValue.key] then + local flagsSettingValue = flagsSettingKeyValue.value; + if type(flagsSettingValue) ~= "table" then + flagsSettingValue = { flagsSettingValue }; + end + for i2, flagsSettingValueBuildOption in ipairs(flagsSettingValue) do + table.insertkeyed(flagsForExtension.buildoptions, p.esc(flagsSettingValueBuildOption)); + end + end + end + end + + -- Flags for setting values. + for i, languageSettingValueFlags in ipairs(languageSettingValueFlagsTables) do + for i2, languageSettingValueKeyValues in ipairs(languageSettingValueFlags) do + local settingValue = config[languageSettingValueKeyValues.key]; + if settingValue ~= nil then + -- Convert boolean to off/on. + if settingValue == false then + settingValue = p.OFF; + elseif settingValue == true then + settingValue = p.ON; + end + local settingValueFlags = languageSettingValueKeyValues.values[settingValue]; + if settingValueFlags ~= nil then + if type(settingValueFlags) ~= "table" then + settingValueFlags = { settingValueFlags }; + end + for i3, settingValueFlag in ipairs(settingValueFlags) do + table.insertkeyed(flagsForExtension.buildoptions, p.esc(settingValueFlag)); + end + end + end + end + end + + -- Individual warnings. + -- After generic setting flags which include the default warning behavior so -Wname can override -w. + if languageWarningFlags ~= nil then + for i, warningKeyPrefix in ipairs(languageWarningFlags) do + for i2, warning in ipairs(config[warningKeyPrefix.key]) do + table.insertkeyed( + flagsForExtension.buildoptions, + androidndk.getSingleArgumentPrefixedFlagPostValueEsc(p.esc(warning), warningKeyPrefix.prefix, temporaryVariables)); + end + end + end + + -- Add raw project-specified flags, as single or multiple arguments, after everything (so they can cancel internal flags). + -- Quoting, or escaping of quotes, must be done by the project instead, as a single build option may contain multiple arguments. + -- Build options are platform-dependent anyway - the module is free to establish any rules. + for i, buildOption in ipairs(config.buildoptions) do + local buildOptionEscaped = p.esc(buildOption); + table.insertkeyed(flagsForExtension.buildoptions, buildOptionEscaped); + if isCCpp and androidndk.isFltoOption(buildOptionEscaped) then + -- Store the last (thus the highest-priority) -flto type used by the language. + -- RenderScript doesn't support -flto. + flagsForExtension.flto = buildOptionEscaped; + end + end +end + +-- Defines before undefines for consistency with other Premake actions such as Visual Studio. +-- Everything special before everything generic (including raw project buildoptions) for the same reason. +-- Separate to make sure -D -U -D -U instead of -D -D -U -U doesn't happen if both are provided by both the project and the extension filter. +androidndk.projectCompilerFlagsOrder = { "defines", "undefines", "buildoptions" }; + +function androidndk.writeProjectCompilerFlags(flagsForAllVariables, variable, escapeShellCharacters) + local flagsForVariable = flagsForAllVariables[variable]; + if flagsForVariable == nil then + return false; + end + local flagsForVariableMerged = {}; + -- Merge all sources of flags in the correct order. + for i, flagsTableName in ipairs(androidndk.projectCompilerFlagsOrder) do + if flagsForVariable[flagsTableName] ~= nil then + for i2, flag in ipairs(flagsForVariable[flagsTableName]) do + table.insertkeyed(flagsForVariableMerged, flag); + end + end + end + local localVariable = "LOCAL_" .. variable; + if androidndk.assignToVariablePostEsc(localVariable, flagsForVariableMerged, true, androidndk.shellEscapePostQuotesChecker) then + if escapeShellCharacters then + androidndk.writePostProcessCall(localVariable, androidndk.shellEscapePostQuotesMakeCall); + end + return true; + end + return false; +end + +-- Built-in flags for ISA and vector extensions. + +androidndk.abiIsaExtensions = { + ["x86"] = { + MOVBE = "-mmovbe", + POPCNT = "-mpopcnt", + PCLMUL = "-mpclmul", + LZCNT = "-mlzcnt", + BMI = "-mbmi", + BMI2 = "-mbmi2", + F16C = "-mf16c", + AES = "-maes", + FMA = "-mfma", + FMA4 = "-mfma4", + RDRND = "-mrdrnd", + }, +}; +androidndk.abiIsaExtensions["x86_64"] = androidndk.abiIsaExtensions["x86"]; + +androidndk.abiVectorExtensions = { + ["x86"] = { + -- Up to SSSE3 are required. + ["SSE4.1"] = "-msse4.1", + ["SSE4.2"] = "-msse4.2", + ["AVX"] = "-mavx", + ["AVX2"] = "-mavx2", + }, + ["x86_64"] = { + -- Up to SSE4.2 are required. + ["AVX"] = "-mavx", + ["AVX2"] = "-mavx2", + }, +}; + +-- Escaped or not doesn't matter. +function androidndk.isFltoOption(option) + -- Remove quotes (so "-flto..." is handled like -flto...). + -- Escaped or not doesn't matter as the pattern being checked doesn't include a backslash. + option = string.gsub(option, "\"", ""); + -- Let -flto alone, or -flto=..., pass. + return string.find(option, "^%s*%-flto%s$") ~= nil or string.find(option, "^%s*%-flto=") ~= nil; +end + +-- ldFlagsUndefined receives -u and --undefined from `linkoptions`. +-- ldLibsDirFlags receives -L from `linkoptions` and -L for `links`. +-- ldLibsSysDirFlags receives -L for `syslibdirs`. +-- ldLibsLibFlags receives -l from `linkoptions` and -l for `links`. +-- ldFlags receives the rest of `linkoptions`. +-- Any output table can be nil. +-- In this case, only libraries to link to will be obtained and written to ldLibs*Flags. +-- Returns the last encountered (thus highest-priority) -flto option if any is passed (so non-default LTO types can be used with transitivity). +function androidndk.addSingleConfigLinkerFlags(config, ldFlagsUndefined, ldFlags, ldLibsDirFlags, ldLibsSysDirFlags, ldLibsLibFlags, temporaryVariables) + -- NDK passes LDFLAGS before LDLIBS. + -- It also placed local LDLIBS before imported LDLIBS. + -- Entries for the system libraries (`syslibdirs` here specifically) should be the last according to this ordering also. + -- The system can be considered the deepest import of everything. + -- For LDFLAGS, it's the reverse, however - imported LDFLAGS are before local LDFLAGS. + + -- This module also distributes `linkoptions` between LDFLAGS and LDLIBS instead of passing all to LDFLAGS. + -- LDLIBS is what's designed to hold the -l flags for the system libraries to link to. + -- While LDFLAGS can contain -l too, it's not the intended way of passing them. + -- The reason why this is taken into consideration, and `linkoptions` are parsed at all, is the special case of name collision resolution. + -- A workspace may contain its own project, for example, named "log", and in this case a "log" link would be considered a sibling link, not a system one. + -- An alternative would be to link to ":liblog.so". + -- However, this will result in a warning as ndk-build uses simpler logic for checking whether only system libraries are specified which fails for that. + -- Therefore, this module offers a way to pass "-llog" via `linkoptions`. + + -- Normally though -l should be used only for a small fixed set of Android NDK system libraries. + -- All this logic is therefore a massive overkill. + -- Prebuilt library projects normally should be used instead of -L and -l. + -- However, for the ease of porting, and to avoid adding constraints, letting projects link directly to libraries via raw linker arguments anyway. + -- Specifying anything but -l would throw a warning though. + + -- Search paths (-L) apply to all -l options regardless of the order. + -- They can be written to either LDFLAGS or LDLIBS. + -- This module implements dependency transitivity a custom way, stopping iterating at shared libraries (unlike NDK's internal exports). + -- For this purpose, it only imports everything that goes to LDLIBS, but not to LDFLAGS - assuming it contains everything needed for library linkage. + -- So, always writing search paths to LDLIBS here, not to LDFLAGS. + + -- Handling `linkoptions` before `links` and `syslibdirs` because the latter is conceptually closer to LDFLAGS, and the latter two to LDLIBS. + -- Considering -L specified in `linkoptions` as the more generic `links` (which are placed before `syslibdirs`) for the same reason as well. + + local flto = nil; + local longLinkOptions = { + { + name = "--library", + destination = ldLibsLibFlags, + }, + { + name = "--library-path", + destination = ldLibsDirFlags, + }, + { + name = "--undefined", + destination = ldFlagsUndefined, + }, + }; + for i, linkOptionUnescaped in ipairs(config.linkoptions) do + local linkOption = p.esc(linkOptionUnescaped); + -- Check which output table should receive the option. + local linkOptionDestination = ldFlags; + -- -flto is a Clang++ option, not an LLD one - don't check it if there's -Wl. + -- Also, it's either a full option, or an option with a value - check it in a special way for this reason as well. + if androidndk.isFltoOption(linkOption) then + flto = linkOption; + else + -- Trim whitespaces (until the first quote if present). + local linkOptionFirstNonWhitespace = string.find(linkOption, "[%S]"); + if linkOptionFirstNonWhitespace ~= nil then + -- Remove quotes (so "-l..." is handled like -l...). + -- Escaped or not doesn't matter as none of the checked prefixes include a backslash. + local linkOptionForPrefix = string.gsub(string.sub(linkOption, linkOptionFirstNonWhitespace, -1), "\"", ""); + -- ndk-build passes the flags to Clang++, not to LLD directly. + -- Therefore, most of linker options must be passed via -Wl, and for those that don't, the presence of -Wl doesn't matter. + -- Specifically, -l and -L can be passed to both Clang++ and LLD, but --library and --library-path can't (and require -Wl). + -- -l and -L can be either attached directly to the name or placed in a separate (whitespace-separated) argument. + -- --library and --library-path can be either attached with a = or placed in a separate argument. + -- Since --library and --library-path require -Wl, arguments are comma-separate rather than whitespace-separated. + local wlPrefix = "-Wl,"; + local wlPrefixLength = string.len(wlPrefix); + local hasWl = string.sub(linkOption, 1, string.len(wlPrefixLength)) == wlPrefix; + if hasWl then + linkOptionForPrefix = string.sub(linkOptionForPrefix, wlPrefixLength + 1, -1); + end + local linkOptionSingleCharacterPrefix = string.sub(linkOptionForPrefix, 1, 2); + if linkOptionSingleCharacterPrefix == "-u" then + linkOptionDestination = ldFlagsUndefined; + elseif linkOptionSingleCharacterPrefix == "-l" then + linkOptionDestination = ldLibsLibFlags; + elseif linkOptionSingleCharacterPrefix == "-L" then + linkOptionDestination = ldLibsDirFlags; + else + if hasWl then + -- Check if --Wl,--option=value or --Wl,--option,value is used. + for i2, longLinkOption in ipairs(longLinkOptions) do + local longLinkOptionPrefixSub = string.sub(linkOptionForPrefix, 1, string.len(longLinkOption.name)); + if longLinkOption.name == (longLinkOptionPrefixSub .. "=") or longLinkOption.name == (longLinkOptionPrefixSub .. ",") then + linkOptionDestination = longLinkOption.destination; + break; + end + end + end + end + end + end + -- Add the flag to the needed table if adding to one was requested at all. + -- Quoting, or escaping of quotes, must be done by the project instead, as a single link option may contain multiple arguments. + -- Link options are platform-dependent anyway - the module is free to establish any rules. + if linkOptionDestination ~= nil then + table.insertkeyed(linkOptionDestination, linkOption); + end + end + + -- Local search paths (`libdirs` and `links`). + if ldLibsDirFlags ~= nil then + for i, libDir in ipairs(p.config.getlinks(config, "system", "directory")) do + local libDirRelativePath = androidndk.getEscapedProjectRelativePath(config.project, libDir, true); + table.insertkeyed( + ldLibsDirFlags, + androidndk.getSingleArgumentPrefixedFlagPostValueEsc(libDirRelativePath, "-L", temporaryVariables)); + end + end + + -- System search paths. + if ldLibsSysDirFlags ~= nil then + for i, sysLibDir in ipairs(config.syslibdirs) do + local sysLibDirRelativePath = androidndk.getEscapedProjectRelativePath(config.project, sysLibDir, true); + table.insertkeyed( + ldLibsSysDirFlags, + androidndk.getSingleArgumentPrefixedFlagPostValueEsc(sysLibDirRelativePath, "-L", temporaryVariables)); + end + end + + -- Local links. + -- Specifying the : prefix directly in `links` is allowed to force treatment of the name as a full file name instead of a short name. + -- However, just specifying the full file name is also allowed for simplicity of the usage (especially of the usage of full paths). + -- libname.so will be treated as libname.so, not as liblibname.so.a or liblibname.so.so. + -- However, something like name.thumb will be treated as libname.thumb.a/libname.thumb.so still. + -- Making no special assumptions about GNU make references (such as environment variables). + -- The intended usage of LOCAL_LDLIBS is purely for linking to the libraries provided by the Android NDK via their short names anyway. + -- To use, for instance, a full path from an environment variable (or a different kind of a GNU make reference), specify: + -- links({ "$(dir $(VARIABLE))/:$(notdir $(VARIABLE))" }) + if ldLibsLibFlags ~= nil then + for i, libName in ipairs(p.config.getlinks(config, "system", "name")) do + local libNamePrefix = ""; + if string.sub(libName, 1, 1) ~= ":" then + -- Full name not forced. + -- Check if using the full name anyway. + local libExtension = path.getextension(libName); + if libExtension == ".a" or libExtension == ".so" then + libNamePrefix = ":"; + end + end + table.insertkeyed( + ldLibsLibFlags, + androidndk.getSingleArgumentPrefixedFlagPostValueEsc(p.esc(libNamePrefix .. libName), "-l", temporaryVariables)); + end + end + + return flto; +end + +function androidndk.generateProjectConfig(config) + local temporaryVariables = {}; + + local configAbi = androidndk.getConfigAbi(config); + assert(configAbi ~= nil, "Configurations with invalid ABIs must be skipped in isConfigSupported"); + + local configUsesA32 = androidndk.configUsesA32(config); + local configUsesNeon = androidndk.configUsesNeon(config); + + -- Get the precompiler header path. + -- Handling of the ARM instruction set and NEON is done by ndk-build implicitly, not possible and not needed to specify suffixes. + -- All the inclusion of the built header is done by ndk-build itself implicitly. + -- At this point, it's needed for getting the file configuration for it so, if needed, C++-specific options can be extracted from it. + local pch = nil; + if not config.flags.NoPCH and config.pchheader ~= nil then + pch = androidndk.getEscapedProjectRelativePath(config.project, config.pchheader, false); + -- LOCAL_PCH is involved a value that goes to LOCAL_SRC_FILES internally in ndk-build, so the rules are the same. + -- However, = is also not allowed. + if androidndk.staticallyHasPatternPostEsc(pch, "[%s\"#%$&'%(%),;<=>`|]") then + p.warn( + "Skipping workspace '%s' project '%s' configuration '%s' precompiled header '%s' " .. + "with path containing whitespaces or disallowed characters \"#$&'(),;<=>`|", + config.workspace.name, config.project.name, config.name, pch); + pch = nil; + end + end + local pchFileConfig = nil; + + -- Gather source files not excluded from build. + local sourceTree = p.project.getsourcetree(config.project); + local configAbiSupportsYasm = configAbi == "x86" or configAbi == "x86_64" or configAbi == androidndk.ABI_ALL; + -- External Android.mk files and prebuilt libraries. + local sourcePrecreatedFiles = {}; + -- Keys - sequential indexes, paths without .arm and .neon suffixes. + -- Values - paths with .arm and .neon suffixes. + local sourceFiles = {}; + local sourceFilesYasmConditional = {}; + -- includeDirs[variable] and compilerFlags[variable] may be nil if no source files contributing to them have been found yet. + -- Each includeDirs element optionally contains includedirs = {}, sysincludedirs = {}. + local includeDirs = {}; + local renderScriptIncludeDirsVariable = iif(config.renderscriptincludedirsoverride == true, "RENDERSCRIPT_INCLUDES_OVERRIDE", "RENDERSCRIPT_INCLUDES"); + -- Each compilerFlags element optionally contains defines = {}, undefines = {}, buildoptions (used for the rest of flags) = {}, flto = "...". + -- flto, if present, contains the last -flto flag encountered for this extension. + local compilerFlags = {}; + -- For simplicity, for project-defined flags, using separate CONLYFLAGS and CPPFLAGS. + -- Using LOCAL_CFLAGS is also undesirable because they go not only to C and C++, but to GNU assembly as well. + -- See the definition of ev-compile-s-source in ndk-build. + local configIncludeDirsAdded = {}; + local configCompilerFlagsAdded = {}; + p.tree.traverse(sourceTree, { + onleaf = function(fileNode, depth) + local fileConfig = p.fileconfig.getconfig(fileNode, config); + -- Check if the file is present at all in the current configuration and not explicitly excluded with a file filter. + if fileConfig ~= nil then + -- abspath must be used, as with relpath or path, a path relative to the Android.mk location is written in the end, not to LOCAL_PATH. + local sourceOriginalRelativePath = androidndk.getEscapedProjectRelativePath(config.project, fileNode.abspath, false); + -- If this is the precompiled header, store the file configuration for it so it can be used later to obtain C++-specific settings. + -- ExcludeFromBuild has no effect on whether the precompiled header is used in other Premake actions. + if sourceOriginalRelativePath == pch then + pchFileConfig = fileConfig; + end + if not fileConfig.flags.ExcludeFromBuild then + -- Handle according to the extension. + -- Supporting external Android.mk files, prebuilt libraries, source files (possibly with .arm and .neon suffixes). + -- Unlike path.hasextension, ndk-build is case-sensitive (.C is treated as C++ by ndk-build by default). + -- Therefore, using == for path.getextension instead of path.hasextension as the latter is case-insensitive. + local sourceOriginalExtension = path.getextension(sourceOriginalRelativePath); + local sourceOriginalBaseName = path.getbasename(sourceOriginalRelativePath); + -- Check if the file name is not empty. + if sourceOriginalBaseName ~= "" then + if sourceOriginalExtension == ".mk" then + -- Need the full path, not a relative path like in LOCAL_SRC_FILES. + local sourceAndroidMkPath = androidndk.getEscapedProjectRelativePath(config.project, fileNode.abspath, true); + -- Whitespaces in include statements are supported, but need to be escaped with \ which themselves are escapable in this case. + -- Meaning, n/2 backslashes will be passed, and escaping will be done only by an odd number of backslashes. + -- This makes it hard to handle cases of backslashes before whitespaces in the original path. + -- Backslashes not before spaces are treated as raw characters, however - can't replace all with two backslashes. + if androidndk.staticallyHasPatternPostEsc(sourceAndroidMkPath, "[%s]") then + p.warn( + "Skipping workspace '%s' project '%s' configuration '%s' file '%s' " .. + "with path containing whitespaces, which are not supported", + config.workspace.name, config.project.name, config.name, sourceAndroidMkPath); + else + table.insertkeyed(sourcePrecreatedFiles, sourceAndroidMkPath); + end + elseif sourceOriginalExtension == ".a" or sourceOriginalExtension == ".so" then + -- LOCAL_SRC_FILES limitations for prebuilt libraries: + -- Backslash escaping of shell characters is NOT supported on Windows. + -- Escaping with a backslash works on Linux, however, but it's not needed for any of the allowed characters there. + -- On Linux, the path goes to `cp`. + -- Disallowed characters on Linux: whitespaces, "#$%&'()*:;<>?\`| + -- * is treated as a wildcard, so it can't truly be a part of a file name either. + -- \ is allowed on Windows, however, as a path separator. + -- On Windows, the path goes to `copy` after replacing / with \. + -- Disallowed characters on Windows: whitespaces, "#$%&*;<>?| + -- : is allowed on Windows as a drive letter separator. + -- Therefore, on Windows, strictly more different characters are allowed. + -- The user may, however, possibly want to use the generated scripts on just Windows. + -- For this reason, only paths with characters disallowed on both (not either) hosts must be filtered out here. + -- There's no need to impose overly strict constraints, this check acts as a portability aid. + if androidndk.staticallyHasPatternPostEsc(sourceOriginalRelativePath, "[%s\"#%$%%&%*;<>%?|]") then + -- % needs to be escaped as %% in printf format strings. + p.warn( + "Skipping workspace '%s' project '%s' configuration '%s' file '%s' " .. + "with path containing whitespaces or disallowed characters \"#$%%&*;<>?|", + config.workspace.name, config.project.name, config.name, sourceOriginalRelativePath); + else + table.insertkeyed(sourcePrecreatedFiles, sourceOriginalRelativePath); + end + else + -- .arm and .neon suffixes internally enable the A32 instruction set and NEON respectively for the base file. + -- They're also supported on ABIs other than armeabi-v7a, having no effect in this case, the file is built as usual. + -- With LOCAL_ARM_MODE := arm and LOCAL_ARM_NEON := true respectively, they have no effect also. + -- These suffixes are allowed for all types of source files, even x86 assembly. + local sourceRelativePath = sourceOriginalRelativePath; + local sourceExtension = sourceOriginalExtension; + -- Only .arm, .neon and .arm.neon combinations are supported - not .neon.arm. + local sourceHasNeonForced = sourceExtension == ".neon"; + if sourceHasNeonForced then + sourceRelativePath = path.removeextension(sourceRelativePath); + sourceExtension = path.getextension(sourceRelativePath); + end + local sourceHasA32Forced = sourceExtension == ".arm"; + if sourceHasA32Forced then + sourceRelativePath = path.removeextension(sourceRelativePath); + sourceExtension = path.getextension(sourceRelativePath); + end + if sourceExtension == ".arm" or sourceExtension == ".neon" then + p.warn( + "Workspace '%s' project '%s' config '%s' file '%s' has an incorrect armeabi suffix - " .. + "only .arm, .neon and .arm.neon are allowed by the NDK", + config.workspace.name, config.project.name, config.name, fileNode.abspath); + else + local sourceBaseName = path.getbasename(sourceRelativePath); + -- Check if the file name is not empty. + if sourceBaseName ~= "" then + -- Silently skipping files in the project unrelated to ndk-build, as well as headers. + if androidndk.sourceExtensions[sourceExtension] ~= nil then + -- Is a source file. + + -- LOCAL_SRC_FILES limitations for source files: + -- Allowed directly on Linux: !%+-./=?@[]^_{}~ + -- Allowed if \-escaped on Linux: * + -- (* works correctly on Linux, not as a wildcard.) + -- : is allowed if escaped on Linux, but is the drive separator on Windows, and is not escapable there. + -- Absolute paths are okay, so escaping should be disabled dynamically on Windows. + -- Allowed and escapable on Windows, disallowed on Linux: \ + -- UNC paths are not supported on Windows. + -- Disallowed: whitespaces, "#$&'(),;<>`| + if androidndk.staticallyHasPatternPostEsc(sourceOriginalRelativePath, "[%s\"#%$&'%(%),;<>`|]") then + p.warn( + "Skipping workspace '%s' project '%s' configuration '%s' file '%s' " .. + "with path containing whitespaces or disallowed characters \"#$&'(),;<>`|", + config.workspace.name, config.project.name, config.name, sourceOriginalRelativePath); + else + -- Try adding the file to source files. + local sourceFileTable = sourceFiles; + if sourceExtension == androidndk.asmExtension then + -- .asm (YASM) files can be assembled only for x86 and x86_64, giving warnings on other architectures. + -- YASM files are explicitly added only for x86 and x86_64 as otherwise ndk-build will be throwing warnings. + if configAbiSupportsYasm then + if configAbi == androidndk.ABI_ALL then + -- Add YASM sources conditionally for multi-ABI configurations. + sourceFileTable = sourceFilesYasmConditional; + else + -- Add YASM sources directly to the main LOCAL_SRC_FILES assignment for x86 and x86_64. + sourceFileTable = sourceFiles; + end + else + sourceFileTable = nil; + end + end + if sourceFileTable ~= nil and sourceFileTable[sourceRelativePath] == nil then + -- New source file. + + -- Add the file to the table. + -- Allowing A32 and NEON to be enabled for files using either the suffixes or file filters. + -- Considering the suffixes more important as they're always specified for individual files, while filters can be less granular. + local sourceRelativePathWithSuffixesParts = { sourceRelativePath }; + if not configUsesA32 and not sourceHasA32Forced then + sourceHasA32Forced = androidndk.configUsesA32(fileConfig); + end + if sourceHasA32Forced then + table.insert(sourceRelativePathWithSuffixesParts, ".arm"); + end + if not configUsesNeon and not sourceHasNeonForced then + sourceHasNeonForced = androidndk.configUsesNeon(fileConfig); + end + if sourceHasNeonForced then + table.insert(sourceRelativePathWithSuffixesParts, ".neon"); + end + local sourceRelativePathWithSuffixes = table.concat(sourceRelativePathWithSuffixesParts); + sourceFileTable[sourceRelativePath] = sourceRelativePathWithSuffixes; + table.insert(sourceFileTable, sourceRelativePathWithSuffixes); + + -- Gather include directories. + -- C_INCLUDES is used for C, C++, GAS and YASM (all non-RenderScript source types supported), but not for RenderScript. + local includeDirsVariable = iif(androidndk.rsExtensions[sourceExtension] ~= nil, renderScriptIncludeDirsVariable, "C_INCLUDES"); + local includeDirsForExtension = includeDirs[includeDirsVariable]; + if includeDirsForExtension == nil then + includeDirsForExtension = {}; + includeDirs[includeDirsVariable] = includeDirsForExtension; + end + if not configIncludeDirsAdded[includeDirsVariable] then + androidndk.addIncludeDirs(includeDirsForExtension, config); + end + -- Add extension-specific include directories. + -- ndk-build doesn't support per-file include directories, but C/C++/GAS/YASM and RenderScript need to be distinguished from each other. + androidndk.addIncludeDirs(includeDirsForExtension, fileConfig); + + -- Gather compiler flags. + local compilerFlagsVariable = androidndk.compilerFlagsVariables[sourceExtension]; + if compilerFlagsVariable ~= nil then + local compilerFlagsForExtension = compilerFlags[compilerFlagsVariable]; + if compilerFlagsForExtension == nil then + compilerFlagsForExtension = {}; + compilerFlags[compilerFlagsVariable] = compilerFlagsForExtension; + end + -- Add non-extension-specific compiler options from the project configuration if hasn't done already. + -- It's fine not to do filtering if the project, for example, contains only C or C++ files, but no assembly. + if not configCompilerFlagsAdded[compilerFlagsVariable] then + configCompilerFlagsAdded[compilerFlagsVariable] = true; + androidndk.addCompilerFlagsForExtension(compilerFlagsForExtension, sourceExtension, config, temporaryVariables); + end + -- Add extension-specific compiler options. + -- ndk-build doesn't support per-file flags, but C, C++, GAS and YASM need to be distinguished from each other. + -- Doing this after the project-level flags so per-extension flags can override them (provide a -fno- for an -f or the opposite). + androidndk.addCompilerFlagsForExtension(compilerFlagsForExtension, sourceExtension, fileConfig, temporaryVariables); + end + end + end + end + end + end + end + end + end + end + end + }); + + -- Get the extension, or nil if not available, for the special case of precreated project files. + -- These can be external Android.mk files and prebuilt libraries. + -- To use one, it must be the only source file in the project. + local precreatedExtension = nil; + if #sourcePrecreatedFiles == 1 and not (#sourceFiles > 0 or #sourceFilesYasmConditional > 0) then + precreatedExtension = path.getextension(sourcePrecreatedFiles[1]); + end + + if precreatedExtension ~= nil then + -- Check the special case of an external Android.mk file. + -- Using Android.mk files not generated by Premake is allowed as long as: + -- * Only one project is specified in it. + -- * The project name, after all escaping and reference expansion, is the same as LOCAL_MODULE in it. + -- * The kind matches the one used in it. + if precreatedExtension == ".mk" then + p.w("include %s", androidndk.preventWhitespaceTrimming(sourcePrecreatedFiles[1])); + return; + end + + -- Check the special case of a prebuilt library. + local prebuiltKind = androidndk.prebuiltKinds[config.kind]; + if prebuiltKind ~= nil and precreatedExtension == prebuiltKind.extension then + -- Backslash escaping is not supported for prebuilt library paths on Windows. + androidndk.assignToVariablePostEsc("LOCAL_SRC_FILES", sourcePrecreatedFiles[1], false); + p.w("include $(%s)", prebuiltKind.script); + return; + end + end + + -- Warn if there are any external Android.mk files or prebuilt libraries in the projects, but they aren't used for some reason. + -- It may be a mistake, the user might have wanted to use one, but has possibly configured the filters so that other files are still included. + for i, file in ipairs(sourcePrecreatedFiles) do + p.warn( + "Workspace '%s' project '%s' configuration '%s' contains an external Android.mk or a prebuilt library '%s' among its source files, " .. + "but it's not the only source file in the project, or it doesn't match the kind of the project", + config.workspace.name, config.project.name, config.name, file); + end + + -- Some variables may have values that are relevant only to a subset of the ABIs. + -- Examples are LOCAL_ARM_MODE, LOCAL_ARM_NEON, .arm/.neon/.arm.neon and .asm in LOCAL_SRC_FILES. + -- The general rule here is that most of the details of handling those are left to ndk-build itself. + -- This is done to avoid introducing additional interference and constraints, as well as to simplify the code. + -- ABI conditionals are added only when not having them would cause errors or warnings (like x86-specific CFLAGS on ARM). + -- If the default behavior of ndk-build needs to be overriden, the project should explicitly use the needed filters. + + -- Gather C++ settings if the precompiled header is used, from the project and, if available, the file. + -- The precompiled header is always compiled as C++ by ndk-build. + -- Treat it as potentially encountering a C++ file for the first time, so it's compiled with the needed include directories and flags. + -- Ignoring ExcludeFromBuild as it doesn't have effect on pchheader in other Premake actions. + if pch ~= nil then + if not configIncludeDirsAdded["C_INCLUDES"] then + configIncludeDirsAdded["C_INCLUDES"] = true; + local cIncludes = includeDirs["C_INCLUDES"]; + if cIncludes == nil then + cIncludes = {}; + includeDirs["C_INCLUDES"] = cIncludes; + end + androidndk.addIncludeDirs(cIncludes, config); + if pchFileConfig ~= nil then + androidndk.addIncludeDirs(cIncludes, pchFileConfig); + end + end + if not configCompilerFlagsAdded["CPPFLAGS"] then + configCompilerFlagsAdded["CPPFLAGS"] = true; + local cppFlags = compilerFlags["CPPFLAGS"]; + if cppFlags == nil then + cppFlags = {}; + compilerFlags["CPPFLAGS"] = cppFlags; + end + androidndk.addCompilerFlagsForExtension(cppFlags, androidndk.cppExtensions[1], config, temporaryVariables); + if pchFileConfig ~= nil then + androidndk.addCompilerFlagsForExtension(cppFlags, androidndk.cppExtensions[1], pchFileConfig, temporaryVariables); + end + end + end + + -- If the LinkTimeOptimization flag is enabled, apply the LTO type requested by the project explicitly via `buildoptions`, or the default "-flto". + -- Also verify link-time optimization type consistency. + local fltoCOverride = nil; + if compilerFlags["CONLYFLAGS"] ~= nil then + fltoCOverride = compilerFlags["CONLYFLAGS"].flto; + end + local fltoCppOverride = nil; + if compilerFlags["CPPFLAGS"] ~= nil then + fltoCppOverride = compilerFlags["CPPFLAGS"].flto; + end + -- This must not be used by the link options - they should be only verified against it. + -- Static library analysis doesn't scan `buildoptions`, only `linkoptions`. + local fltoBuildOption = fltoCppOverride or fltoCOverride or "-flto"; + if fltoCOverride ~= nil and fltoCOverride ~= fltoBuildOption then + p.warn( + "Workspace '%s' project '%s' configuration '%s' uses LTO type '%s' for C, while it uses '%s' for another language", + config.workspace.name, config.project.name, config.name, fltoCOverride, fltoBuildOption); + end + if fltoCppOverride ~= nil and fltoCppOverride ~= fltoBuildOption then + p.warn( + "Workspace '%s' project '%s' configuration '%s' uses LTO type '%s' for C++, while it uses '%s' for another language", + config.workspace.name, config.project.name, config.name, fltoCppOverride, fltoBuildOption); + end + if config.flags.LinkTimeOptimization then + if fltoCOverride == nil then + if compilerFlags["CONLYFLAGS"] == nil then + compilerFlags["CONLYFLAGS"] = {} + end + if compilerFlags["CONLYFLAGS"].buildoptions == nil then + compilerFlags["CONLYFLAGS"].buildoptions = {} + end + table.insertkeyed(compilerFlags["CONLYFLAGS"].buildoptions, fltoBuildOption); + end + if fltoCppOverride == nil then + if compilerFlags["CPPFLAGS"] == nil then + compilerFlags["CPPFLAGS"] = {} + end + if compilerFlags["CPPFLAGS"].buildoptions == nil then + compilerFlags["CPPFLAGS"].buildoptions = {} + end + table.insertkeyed(compilerFlags["CPPFLAGS"].buildoptions, fltoBuildOption); + end + end + + -- Gather linker options (LDFLAGS and LDLIBS) from the module and its static library dependencies. + -- Importing manually instead of using LOCAL_EXPORT_LDFLAGS/LDLIBS so there's no transitivity across shared libraries. + -- This is consistent with the behavior of Visual Studio - only static libraries export their dependencies. + -- Also it keeps syslibdirs of all static library dependencies last, after all local library directories. + -- ndk-build puts imported LDFLAGS first, but imported LDLIBS last. + -- For -u, which is usually passed via LDFLAGS, the order of local vs. imported options doesn't matter, however. + -- all_depends in ndk-build is generated via a breadth-first walk of the dependency graph. + local isLinked = config.kind == p.CONSOLEAPP or config.kind == p.SHAREDLIB; + local ldFlags = {}; + local ldLibs = {}; + if isLinked then + local ldFlagsUndefined = {}; + local ldFlagsLocal = {}; + local ldLibsDirFlags = {}; + local ldLibsSysDirFlags = {}; + local ldLibsLibFlags = {}; + local ldLinkTimeOptimization = config.flags.LinkTimeOptimization; + local ldFlagsExplicitFlto = nil; + -- Start recursion from the current project config. + -- It will also result in local LDLIBS parts being added before the imports. + local ldDependencies = { config, [config.project.name] = config }; + local ldDependencyIndex = 1; + while ldDependencyIndex <= #ldDependencies do + -- Gather -u LDFLAGS as well as LDLIBS parts for the project currently being processed. + -- -u may be used to keep certain exports from static libraries in shared libraries, in a way more granular than whole static libraries. + -- It is used, for example, to prevent stripping ANativeActivity_onCreate from the Android Native App Glue. + local ldDependency = ldDependencies[ldDependencyIndex]; + local ldDependencyExplicitFlto = + androidndk.addSingleConfigLinkerFlags( + ldDependency, ldFlagsUndefined, nil, ldLibsDirFlags, ldLibsSysDirFlags, ldLibsLibFlags, temporaryVariables); + -- The last -flto has the highest priority. + -- However, -flto for the project itself is actually added after all dependencies here, not alongside them. + if ldDependency ~= config and ldDependencyExplicitFlto ~= nil then + if ldFlagsExplicitFlto ~= nil and ldFlagsExplicitFlto ~= ldDependencyExplicitFlto then + p.warn( + "Workspace '%s' project '%s' configuration '%s' has mismatching -flto link options " .. + "among its static library dependencies ('%s' in project '%s' and '%s' in another project)", + config.workspace.name, config.project.name, config.name, + ldDependencyExplicitFlto, ldDependency.project.name, ldFlagsExplicitFlto); + end + ldFlagsExplicitFlto = ldDependencyExplicitFlto; + end + for i, ldDependencySibling in ipairs(p.config.getlinks(ldDependency, "siblings", "object")) do + -- Only gather -u exports and as LDLIBS from static libraries, as shared libraries have already been linked as a complete object. + -- Also check if not already handled, including to prevent circular dependencies. + if ldDependencySibling.kind == p.STATICLIB and ldDependencies[ldDependencySibling.project.name] == nil then + if androidndk.isProjectSupported(ldDependencySibling.project, false) then + if androidndk.isConfigSupported(ldDependencySibling, false) then + table.insert(ldDependencies, ldDependencySibling); + ldDependencies[ldDependencySibling.project.name] = ldDependencySibling; + if ldDependencySibling.flags.LinkTimeOptimization then + ldLinkTimeOptimization = true; + end + end + end + end + end + ldDependencyIndex = ldDependencyIndex + 1; + end + -- Merge LDLIBS parts. + local ldLibsFlagsTables = { ldLibsDirFlags, ldLibsSysDirFlags, ldLibsLibFlags }; + for i, ldLibsFlags in ipairs(ldLibsFlagsTables) do + for i2, ldLibsFlag in ipairs(ldLibsFlags) do + table.insertkeyed(ldLibs, ldLibsFlag); + end + end + -- Gather local LDFLAGS other than -u after the imports. + -- For LDFLAGS, the order mostly shouldn't matter though. + -- Doing this just in a way consistent with how ndk-build itself gathers exported LDLIBS. + local ldLocalFlagsExplicitFlto = androidndk.addSingleConfigLinkerFlags(config, nil, ldFlagsLocal, nil, nil, nil, temporaryVariables); + if ldLocalFlagsExplicitFlto ~= nil then + -- The last -flto has the highest priority. + if ldFlagsExplicitFlto ~= nil and ldFlagsExplicitFlto ~= ldLocalFlagsExplicitFlto then + p.warn( + "Workspace '%s' project '%s' configuration '%s' has a -flto link option '%s' " .. + "that doesn't match its value used by static library dependencies '%s'", + config.workspace.name, config.project.name, config.name, + ldLocalFlagsExplicitFlto, ldFlagsExplicitFlto); + end + ldFlagsExplicitFlto = ldLocalFlagsExplicitFlto; + end + if ldFlagsExplicitFlto ~= nil then + ldLinkTimeOptimization = true; + end + -- If the project itself, or any of its static libraries, is compiled with LTO, link with LTO. + -- Also, if the project or any of its static libraries has an explicit -flto option (possibly with a non-default type), use it instead. + local ldFlagsFlto = ldFlagsExplicitFlto or "-flto"; + -- Checking if the build -flto is not nil because the project may, for instance, only link against static libraries with LTO. + if fltoBuildOption ~= nil and fltoBuildOption ~= ldFlagsFlto then + if ldFlagsFlto ~= nil then + p.warn( + "Workspace '%s' project '%s' configuration '%s' uses a -flto build option '%s' " .. + "that differs from the value '%s' that will be used by the linker", + config.workspace.name, config.project.name, config.name, fltoBuildOption, ldFlagsFlto); + else + p.warn( + "Workspace '%s' project '%s' configuration '%s' uses a -flto build option '%s', " .. + "but doesn't have the LinkTimeOptimization flag enabled or an -flto in its link options", + config.workspace.name, config.project.name, config.name, fltoBuildOption); + end + end + if ldLinkTimeOptimization then + table.insertkeyed(ldFlags, ldFlagsFlto); + end + -- Merge LDFLAGS parts. + local ldFlagsTables = { ldFlagsUndefined, ldFlagsLocal }; + for i, ldFlagsTable in ipairs(ldFlagsTables) do + for i2, ldFlag in ipairs(ldFlagsTable) do + table.insertkeyed(ldFlags, ldFlag); + end + end + end + + -- Gather module dependencies for the current build configuration and platform. + local sharedLibraries = {}; + local staticLibraries = {}; + local wholeStaticLibraries = {}; + for i, siblingLinkProjectConfig in ipairs(p.config.getlinks(config, "siblings", "object")) do + if androidndk.isProjectSupported(siblingLinkProjectConfig.project, false) then + if androidndk.isConfigSupported(siblingLinkProjectConfig, false) then + local siblingLinksTable = nil; + if siblingLinkProjectConfig.kind == p.SHAREDLIB then + siblingLinksTable = sharedLibraries; + elseif siblingLinkProjectConfig.kind == p.STATICLIB then + if siblingLinkProjectConfig.wholelib == true or config.wholelibs[siblingLinkProjectConfig.project.name] ~= nil then + siblingLinksTable = wholeStaticLibraries; + else + siblingLinksTable = staticLibraries; + end + end + if siblingLinksTable ~= nil then + table.insert(siblingLinksTable, siblingLinkProjectConfig.project.name); + end + end + end + end + + -- Assign the values to the variables. + + -- Write assignments to referenced intermediate variables. + for i, temporaryVariable in ipairs(temporaryVariables) do + androidndk.assignToVariablePostEsc(androidndk.temporaryVariablePrefix .. i, temporaryVariable, false); + end + + -- LOCAL_ALLOW_UNDEFINED_SYMBOLS + if isLinked and config.undefinedsymbols == true then + p.w("LOCAL_ALLOW_UNDEFINED_SYMBOLS := true"); + end + + -- LOCAL_ASFLAGS + androidndk.writeProjectCompilerFlags(compilerFlags, "ASFLAGS", true); + + -- LOCAL_ASMFLAGS + androidndk.writeProjectCompilerFlags(compilerFlags, "ASMFLAGS", true); + + -- LOCAL_ARM_MODE + -- NDK uses T32 by default, and always switches to A32 for debug builds, but it's handled implicitly in it. + if configUsesA32 then + p.w("LOCAL_ARM_MODE := arm"); + end + + -- LOCAL_ARM_NEON + -- On NDK r21, NEON is enabled by default. + -- Since that wasn't the case on older versions, both enabling and disabling it explicitly anyway. + p.w("LOCAL_ARM_NEON := %s", iif(configUsesNeon, "true", "false")); + + -- LOCAL_CONLYFLAGS + -- Potentially will be appending ISA and vector extensions, post-process later. + local cOnlyFlagsNeedPostProcess = androidndk.writeProjectCompilerFlags(compilerFlags, "CONLYFLAGS", false); + + -- LOCAL_CPPFLAGS + -- Potentially will be appending ISA and vector extensions, post-process later. + local cppFlagsNeedPostProcess = androidndk.writeProjectCompilerFlags(compilerFlags, "CPPFLAGS", false); + + -- After writing LOCAL_CONLYFLAGS and LOCAL_CPPFLAGS, add ABI-specific C and C++ flags (not using LOCAL_CFLAGS at all). + local cFlagsAbis; + if configAbi == androidndk.ABI_ALL then + cFlagsAbis = androidndk.knownAbis; + else + cFlagsAbis = { configAbi }; + end + local abiCFlagsConditionalOpen = false; + for i, cFlagsAbi in ipairs(cFlagsAbis) do + local abiCFlags = {}; + -- vectorextensions + if config.vectorextensions ~= nil then + local abiVectorExtensions = androidndk.abiVectorExtensions[cFlagsAbi]; + if abiVectorExtensions ~= nil then + local abiConfigVectorExtensions = abiVectorExtensions[config.vectorextensions]; + if abiConfigVectorExtensions ~= nil then + table.insertkeyed(abiCFlags, p.esc(abiConfigVectorExtensions)); + end + end + end + -- isaextensions + local abiIsaExtensions = androidndk.abiIsaExtensions[cFlagsAbi]; + if abiIsaExtensions ~= nil then + for i2, extension in ipairs(config.isaextensions) do + local abiIsaExtension = abiIsaExtensions[extension]; + if abiIsaExtension ~= nil then + table.insertkeyed(abiCFlags, p.esc(abiIsaExtension)); + end + end + end + -- Write the conditional and the extensions. + if #abiCFlags > 0 then + if configAbi == androidndk.ABI_ALL then + p.push("%sifeq ($(TARGET_ARCH_ABI),%s)", androidndk.getCaseElse(abiCFlagsConditionalOpen), cFlagsAbi); + abiCFlagsConditionalOpen = true; + end + -- Safe to += even if no other CONLYFLAGS and CPPFLAGS were specified, set to an empty value by CLEAR_VARS. + if androidndk.assignToVariablePostEsc("LOCAL_CONLYFLAGS", abiCFlags, true, androidndk.shellEscapePostQuotesChecker, "+=") then + cOnlyFlagsNeedPostProcess = true; + end + if androidndk.assignToVariablePostEsc("LOCAL_CPPFLAGS", abiCFlags, true, androidndk.shellEscapePostQuotesChecker, "+=") then + cppFlagsNeedPostProcess = true; + end + if configAbi == androidndk.ABI_ALL then + p.pop(); + end + end + end + if abiCFlagsConditionalOpen then + assert(configAbi == androidndk.ABI_ALL, "Can have ABI conditionals for ABI-specific C flags only for ABI-agnostic configurations"); + p.w("endif"); + end + + if cOnlyFlagsNeedPostProcess then + androidndk.writePostProcessCall("LOCAL_CONLYFLAGS", androidndk.shellEscapePostQuotesMakeCall); + end + if cppFlagsNeedPostProcess then + androidndk.writePostProcessCall("LOCAL_CPPFLAGS", androidndk.shellEscapePostQuotesMakeCall); + end + + -- LOCAL_CPP_FEATURES + -- For Clang, Premake core modules check if these are not "Off" (rather than if they are "On"). + local cppFeatures = {}; + if config.exceptionhandling ~= p.OFF then + table.insert(cppFeatures, "exceptions"); + end + if config.rtti ~= p.OFF then + table.insert(cppFeatures, "rtti"); + end + if #cppFeatures > 0 then + androidndk.assignToVariablePostEsc("LOCAL_CPP_FEATURES", cppFeatures, false); + end + + -- LOCAL_C_INCLUDES + androidndk.writeProjectIncludeDirs(includeDirs, "C_INCLUDES", true); + + -- LOCAL_DISABLE_FATAL_LINKER_WARNINGS + -- Different than the Android.mk default (fatal by default), but for compatibility with other Premake actions. + if isLinked and not config.flags.FatalLinkWarnings then + p.w("LOCAL_DISABLE_FATAL_LINKER_WARNINGS := true"); + end + + -- LOCAL_DISABLE_FORMAT_STRING_CHECKS + -- Enabling format string checks by default (if nil). + if config.formatstringchecks == false then + p.w("LOCAL_DISABLE_FORMAT_STRING_CHECKS := true"); + end + + -- LOCAL_LDFLAGS + if isLinked then + if androidndk.assignToVariablePostEsc("LOCAL_LDFLAGS", ldFlags, true, androidndk.shellEscapePostQuotesChecker) then + androidndk.writePostProcessCall("LOCAL_LDFLAGS", androidndk.shellEscapePostQuotesMakeCall); + end + end + + -- LOCAL_LDLIBS + if isLinked then + if androidndk.assignToVariablePostEsc("LOCAL_LDLIBS", ldLibs, true, androidndk.shellEscapePostQuotesChecker) then + androidndk.writePostProcessCall("LOCAL_LDLIBS", androidndk.shellEscapePostQuotesMakeCall); + end + end + + -- LOCAL_MODULE_FILENAME + -- Allowed directly on Linux: !+,-.=@[]^_{}~ + -- Allowed if \-escaped on Linux: "\` + -- Allowed if \-escaped on Linux, misleadingly displayed with \ in the terminal: &'()*<>? + -- : is allowed if escaped on Linux (displayed with \), disallowed on Windows (drive letter separator - only the name, not a path, in this variable). + -- Disallowed: whitespaces, #$%/;| + local moduleFileName = p.esc(config.buildtarget.prefix) .. p.esc(config.buildtarget.basename) .. p.esc(config.buildtarget.suffix); + if androidndk.staticallyHasPatternPostEsc(moduleFileName, "[%s#%$%%/;|]") then + -- % needs to be escaped as %% in printf format strings. + p.warn( + "Using the default build target name for workspace '%s' project '%s' configuration '%s' " .. + "instead of '%s' (displayed as escaped) with whitespaces or disallowed characters #$%%/:;|", + config.workspace.name, config.project.name, config.name, moduleFileName); + else + if androidndk.assignToVariablePostEsc("LOCAL_MODULE_FILENAME", moduleFileName, false, androidndk.shellEscapeModuleFileNameChecker) then + androidndk.writePostProcessCall("LOCAL_MODULE_FILENAME", androidndk.shellEscapeModuleFileNameMakeCall); + end + end + + -- LOCAL_PCH + if pch ~= nil then + if androidndk.assignToVariablePostEsc("LOCAL_PCH", pch, false, androidndk.shellEscapeChecker) then + androidndk.writePostProcessCall("LOCAL_PCH", androidndk.shellEscapeMakeCall); + end + end + + -- LOCAL_RENDERSCRIPT_COMPATIBILITY + if config.renderscriptcompatibility == true then + p.w("LOCAL_RENDERSCRIPT_COMPATIBILITY := true"); + end + + -- LOCAL_RENDERSCRIPT_FLAGS + androidndk.writeProjectCompilerFlags(compilerFlags, "RENDERSCRIPT_FLAGS", true); + + -- LOCAL_RENDERSCRIPT_INCLUDES or LOCAL_RENDERSCRIPT_INCLUDES_OVERRIDE + androidndk.writeProjectIncludeDirs(includeDirs, renderScriptIncludeDirsVariable, true); + + -- LOCAL_SHARED_LIBRARIES + -- See LOCAL_MODULE for character rules. + if androidndk.assignToVariablePostEsc("LOCAL_SHARED_LIBRARIES", p.esc(sharedLibraries), true, androidndk.shellEscapeChecker) then + androidndk.writePostProcessCall("LOCAL_SHARED_LIBRARIES", androidndk.shellEscapeMakeCall); + end + + -- LOCAL_SHORT_COMMANDS + if config.shortcommands == true then + p.w("LOCAL_SHORT_COMMANDS := true"); + end + + -- LOCAL_SRC_FILES + -- See the validation of source files for information about disallowed characters. + local sourceFilesNeedPostProcess = false; + if androidndk.assignToVariablePostEsc("LOCAL_SRC_FILES", sourceFiles, true, androidndk.shellEscapeSrcFilesChecker) then + sourceFilesNeedPostProcess = true; + end + -- Only add .asm files for x86 and x86_64 as otherwise the NDK will be throwing warnings. + -- Single-ABI configurations are already handled while gathering the source file nodes. + -- Write the assignment for multi-ABI configurations. + if #sourceFilesYasmConditional > 0 then + p.push("ifneq ($(filter x86 x86_64,$(TARGET_ARCH_ABI)),)"); + if androidndk.assignToVariablePostEsc("LOCAL_SRC_FILES", sourceFilesYasmConditional, true, androidndk.shellEscapeSrcFilesChecker, "+=") then + sourceFilesNeedPostProcess = true; + end + p.pop("endif"); + end + if sourceFilesNeedPostProcess then + androidndk.writePostProcessCall("LOCAL_SRC_FILES", androidndk.shellEscapeSrcFilesMakeCall); + end + + -- LOCAL_STATIC_LIBRARIES + -- See LOCAL_MODULE for character rules. + if androidndk.assignToVariablePostEsc("LOCAL_STATIC_LIBRARIES", p.esc(staticLibraries), true, androidndk.shellEscapeChecker) then + androidndk.writePostProcessCall("LOCAL_STATIC_LIBRARIES", androidndk.shellEscapeMakeCall); + end + + -- LOCAL_THIN_ARCHIVE + if config.kind == p.STATICLIB and config.thinarchive == true then + p.w("LOCAL_THIN_ARCHIVE := true"); + end + + -- LOCAL_WHOLE_STATIC_LIBRARIES + -- See LOCAL_MODULE for character rules. + if androidndk.assignToVariablePostEsc("LOCAL_WHOLE_STATIC_LIBRARIES", p.esc(wholeStaticLibraries), true, androidndk.shellEscapeChecker) then + androidndk.writePostProcessCall("LOCAL_WHOLE_STATIC_LIBRARIES", androidndk.shellEscapeMakeCall); + end + + -- Invoke the build script for the specified kind. + local kindScript = androidndk.kindScripts[config.kind]; + assert(kindScript ~= nil, "Configurations with invalid kinds must be skipped in isConfigSupported"); + p.w("include $(%s)", kindScript); +end + +function androidndk.onProject(project) + if not androidndk.isProjectSupported(project, true) then + return; + end + androidndk.setupGeneration(); + p.generate(project, ".prj.Android.mk", function(project) + -- Must be synchronized with getEscapedProjectRelativePath. + -- Using the Android.mk directory (location), not the base directory, as it's less likely to contain header files. + -- There's undesirable behavior in NDK regarding the order of include directories. + -- LOCAL_PATH is always implicitly an include directory, and the -I for it is passed after the STL include directories. + -- That causes #include_next in C standard library headers to locate app headers with the same in LOCAL_PATH. + -- https://github.com/android/ndk/issues/1582 + p.w("LOCAL_PATH := $(call my-dir)"); + p.w("include $(CLEAR_VARS)"); + -- Workaround for LOCAL_RENDERSCRIPT_COMPATIBILITY not being cleared in NDK r23 as it's not listed in modules-LOCALS. + -- Clearing similar to clear-vars as of NDK r23, by defining as empty, not via `undefine`. + p.w("LOCAL_RENDERSCRIPT_COMPATIBILITY :="); + -- Project name already verified by androidndk.isProjectSupported. + if androidndk.assignToVariablePostEsc("LOCAL_MODULE", p.esc(project.name), false, androidndk.shellEscapeChecker) then + androidndk.writePostProcessCall("LOCAL_MODULE", androidndk.shellEscapeMakeCall); + end + -- Specify all extensions assumed to be C++ and RenderScript by this module. + -- This allows for using extensions different than the default in this module if needed. + -- Additionally, it ensures forward compatibility if the defaults in ndk-build are changed. + androidndk.assignToVariablePostEsc("LOCAL_CPP_EXTENSION", table.concat(androidndk.cppExtensions, " "), false); + androidndk.assignToVariablePostEsc("LOCAL_RS_EXTENSION", table.concat(androidndk.rsExtensions, " "), false); + -- Gather valid configurations for each ABI. + -- ABI-agnostic configurations are checked last. + -- This allows both ABI specializations and ABI-agnostic fallbacks to be specified for building at the same time. + -- Without this, the ABI-agnostic fallback would've been chosen over specializations depending on the order of the platforms table. + local abisConfigs = {}; + for config in p.project.eachconfig(project) do + if androidndk.isConfigSupported(config, true) then + local configAbi = androidndk.getConfigAbi(config); + if abisConfigs[configAbi] == nil then + abisConfigs[configAbi] = { abi = configAbi, configs = {} }; + -- The ABI-agnostic configurations will be used as fallbacks when no ABI specialization is available. + -- So, they need to be generated last. + if configAbi ~= androidndk.ABI_ALL then + table.insert(abisConfigs, abisConfigs[configAbi]); + end + end + table.insert(abisConfigs[configAbi].configs, config); + end + end + if abisConfigs[androidndk.ABI_ALL] ~= nil then + table.insert(abisConfigs, abisConfigs[androidndk.ABI_ALL]); + end + local anyConfigWritten = false; + for i, abiConfigs in ipairs(abisConfigs) do + for i2, abiConfig in ipairs(abiConfigs.configs) do + local configCondition = androidndk.getConfigCondition(abiConfig.buildcfg, abiConfig.platform, abiConfigs.abi); + p.push("%s%s", androidndk.getCaseElse(anyConfigWritten), configCondition); + androidndk.generateProjectConfig(abiConfig); + p.pop(); + anyConfigWritten = true; + end + end + if anyConfigWritten then + p.w("endif"); + end + end); +end diff --git a/src_rebuild/premake_modules/androidndk/androidndk_workspace.lua b/src_rebuild/premake_modules/androidndk/androidndk_workspace.lua new file mode 100644 index 000000000..1f737045f --- /dev/null +++ b/src_rebuild/premake_modules/androidndk/androidndk_workspace.lua @@ -0,0 +1,513 @@ +-- premake-androidndk - a multi-ABI, multi-language ndk-build Android.mk generator for Premake. +-- Originally written by Vitaliy Kuzmin. +-- See README.md for detailed usage and design information. +-- Available under the Unlicense (see UNLICENSE.txt) or the BSD 3-Clause "New" or "Revised" License (see LICENSE.txt). + +local p = premake; +local androidndk = p.modules.androidndk; + +-- Projects can add their own configurations and platforms. +-- So, the use of workspace.configurations, workspace.projects, and their product workspace.configs, should be avoided. +-- Instead, configs should be gathered from all projects. + +-- Writes the setup of the variables used in configuration conditionals. +-- Required in both Application.mk and the workspace Android.mk because with Gradle, the Application.mk is optional. +function androidndk.setupConfigConditionalMakeVariables(usedBuildcfgs, usedPlatforms) + -- Display generated workspace usage information. + -- Using warning/error directly, not the __ndk_* counterparts. + -- __ndk_* functions are internal, and it's more important to show this to the user than to respect NDK_NO_*. + -- $(info) is not displayed in the build output in Android Studio, using $(warning) for everything. + p.push("ifeq ($(filter %s,$(PREMAKE_ANDROIDNDK_CONFIGURATIONS)),)", table.concat(p.esc(usedBuildcfgs), " ")); + p.w("$(warning No configurations to build are specified in PREMAKE_ANDROIDNDK_CONFIGURATIONS.)"); + p.w("$(warning Specify one or multiple \"PREMAKE_ANDROIDNDK_CONFIGURATIONS+=configuration\" in the ndk-build arguments to build those configurations.)"); + p.w("$(warning For workspaces with multiple target platforms, you can also provide the list of the platforms to build via PREMAKE_ANDROIDNDK_PLATFORMS.)"); + p.w("$(warning If PREMAKE_ANDROIDNDK_PLATFORMS is not specified, the projects in this workspace will be built for all platforms they're targeting.)"); + p.w("$(warning Note that configuration and platform names are case-sensitive in this script.)"); + p.w("$(warning )"); + p.w("$(warning It's heavily recommended that only at most one configuration and platform specified corresponds to each targeted ABI.)"); + p.w("$(warning Otherwise, which set of project settings will actually be chosen for building for each ABI will be undefined.)"); + p.w("$(warning )"); + p.w("$(warning The recommended approach is to use configurations for the optimization mode, and, if needed, platforms for ABI filtering.)"); + p.w("$(warning This can be done by specifying different values of the Premake architecture setting for each platform using a platform filter.)"); + p.w("$(warning In this case, each ABI will unambiguously correspond to only one configuration and platform pair.)"); + p.w("$(warning )"); + p.w("$(warning Configurations for this workspace:)"); + for i, buildcfg in ipairs(usedBuildcfgs) do + p.x("$(warning $() %s)", buildcfg); + end + p.w("$(warning )"); + if #usedPlatforms > 0 then + p.w("$(warning Platforms for this workspace:)"); + for i, platform in ipairs(usedPlatforms) do + p.x("$(warning $() %s)", platform); + end + else + p.w("$(warning This workspace is platform-agnostic.)"); + end + p.w("$(error Aborting.)"); + p.pop("endif"); + + -- Generate variables used for configuration selection. + -- As Application.mk variables are preserved in Android.mk, += may result in duplication. + -- Only using := for this reason, and also not modifying the original PREMAKE_ANDROIDNDK_CONFIGURATIONS/PLATFORMS. + p.w("PREMAKE_ANDROIDNDK_CONFIGURATIONS_PREFIXED := $(addprefix CONFIGURATION=,$(PREMAKE_ANDROIDNDK_CONFIGURATIONS))"); + if #usedPlatforms > 0 then + p.push("ifneq ($(PREMAKE_ANDROIDNDK_PLATFORMS),)"); + p.w("PREMAKE_ANDROIDNDK_PLATFORMS_PREFIXED := $(addprefix PLATFORM=,$(PREMAKE_ANDROIDNDK_PLATFORMS))"); + p.pop(); + p.push("else"); + local usedPlatformsPrefixed = {}; + for i, platform in ipairs(usedPlatforms) do + table.insert(usedPlatformsPrefixed, "PLATFORM=" .. p.esc(platform)); + end + p.w("PREMAKE_ANDROIDNDK_PLATFORMS_PREFIXED := %s", table.concat(usedPlatformsPrefixed, " ")); + p.pop("endif"); + end +end + +function androidndk.findConfigForAppSetting(workspace, buildcfg, platform, predicate) + -- In none of these checks should completely unrequested build configurations or platforms pass. + -- This may lead, for instance, to systemversion for a totally different OS to be selected as a fallback. + + -- Considering the workspace (and, if it doesn't provide the needed value, the start project) highest-priority. + -- Even when requesting platform-specific settings, but the start project is platform-agnostic (nil .platform), still prefer the start project. + -- This makes settings more controllable than when some project can override the setting just because it has a platform specialization. + + -- Try to find in the workspace itself. + for config in p.workspace.eachconfig(workspace) do + if androidndk.isConfigSupported(config, false) then + if config.buildcfg == buildcfg and (config.platform == nil or config.platform == platform) and predicate(config) then + return config; + end + end + end + + -- If didn't find in the workspace itself, try the main project in the workspace - the start project. + if workspace.startproject ~= nil then + local startProject = p.workspace.findproject(workspace, workspace.startproject); + if startProject ~= nil and androidndk.isProjectSupported(startProject, false) then + for config in p.project.eachconfig(startProject) do + if androidndk.isConfigSupported(config, false) then + if config.buildcfg == buildcfg and (config.platform == nil or config.platform == platform) and predicate(config) then + return config; + end + end + end + end + end + + -- This configuration may be used by just a subset of the projects. + -- Platform-specific specializations and platform-agnostic fallbacks are obtained via separate findConfigForAppSetting calls in this case. + -- ?= for platform-agnostic variables is done even if platform conditions pass - no need to look for them here for non-nil platforms. + for project in p.workspace.eachproject(workspace) do + if androidndk.isProjectSupported(project, false) then + for config in p.project.eachconfig(project) do + if androidndk.isConfigSupported(config, false) then + if config.buildcfg == buildcfg and config.platform == platform and predicate(config) then + return config; + end + end + end + end + end + + -- Found nothing - don't add the setting. + return nil; +end + +-- Returns a table of { variable = "APP_...", value = ... }, with the variable values escaped. +-- Expecting the outer code to set APP_DEBUG to false if APP_OPTIM is release in the end to match Premake optimize/symbols logic. +function androidndk.gatherEscapedAppVariables(workspace, buildcfg, platform) + -- If the value for a Premake setting is not specified by the projects for this configuration, the default must not be added to the table. + -- This would make it impossible for another configuration, where the setting is explicitly defined, to override the value using ?=. + -- Only returning variables corresponding to settings explicitly defined for this configuration. + + -- ndk-build defaults to non-debug (potentially no symbols) and optimized. + -- Premake doesn't define a default for symbols, but unoptimized by default. + -- Replicating the p.config.isDebugBuild and p.config.isOptimizedBuild logic. + -- An optimized build is not considered a debug one at all, regardless of whether symbols are needed. + local optimizeConfig = androidndk.findConfigForAppSetting(workspace, buildcfg, platform, function(config) + return config.optimize ~= nil; + end); + -- optimizeConfig.optimize ~= nil checked previously. + local isOptimized = optimizeConfig ~= nil and optimizeConfig.optimize ~= p.OFF and optimizeConfig.optimize ~= "Debug"; + local appOptim = nil; + if optimizeConfig ~= nil then + -- The configuration explicitly wants to enable or to disable optimizations. + appOptim = iif(isOptimized, "release", "debug"); + end + -- Expecting the outer code to set APP_DEBUG to false if APP_OPTIM is release. + -- Thus, not explicitly returning APP_DEBUG ?= false for release APP_OPTIM. + local appDebug = nil; + if not isOptimized then + local symbolsConfig = androidndk.findConfigForAppSetting(workspace, buildcfg, platform, function(config) + return config.symbols ~= nil; + end); + if symbolsConfig ~= nil then + -- p.config.isOptimizedBuild checked previously. + appDebug = iif(symbolsConfig.symbols ~= p.OFF and symbolsConfig.symbols ~= p.DEFAULT, "true", "false"); + end + end + + local appVariables = {}; + + -- APP_DEBUG + if appDebug ~= nil then + table.insert(appVariables, { variable = "APP_DEBUG", value = appDebug }); + end + + -- APP_OPTIM + if appOptim ~= nil then + table.insert(appVariables, { variable = "APP_OPTIM", value = appOptim }); + end + + -- APP_PLATFORM + local systemVersionConfig = androidndk.findConfigForAppSetting(workspace, buildcfg, platform, function(config) + return p.project.systemversion(config) ~= nil; + end); + if systemVersionConfig ~= nil then + table.insert(appVariables, { variable = "APP_PLATFORM", value = "android-" .. p.esc(p.project.systemversion(systemVersionConfig)) }); + end + + -- PREMAKE_ANDROIDNDK_APP_STL_RUNTIME (part of APP_STL) + local stlConfig = androidndk.findConfigForAppSetting(workspace, buildcfg, platform, function(config) + return config.cppstl ~= nil; + end); + if stlConfig ~= nil then + -- The default, which in ndk-build is "none", is explicitly requested for this configuration. + local stl = iif(stlConfig.cppstl == p.DEFAULT, "none", stlConfig.cppstl); + table.insert(appVariables, { variable = "PREMAKE_ANDROIDNDK_APP_STL_RUNTIME", value = p.esc(stl) }); + end + + -- PREMAKE_ANDROIDNDK_APP_STL_LINKAGE (part of APP_STL) + -- While for ndk-build, the default STL is "none", which doesn't have a linkage suffix, for CMake it's "c++_static". + -- Therefore, considering static the default (treating explicitly specified "Default" as "On"). + local staticRuntimeConfig = androidndk.findConfigForAppSetting(workspace, buildcfg, platform, function(config) + return config.staticruntime ~= nil; + end); + if staticRuntimeConfig ~= nil then + local stlLinkage = iif(staticRuntimeConfig.staticruntime ~= p.OFF, "_static", "_shared"); + table.insert(appVariables, { variable = "PREMAKE_ANDROIDNDK_APP_STL_LINKAGE", value = stlLinkage }); + end + + return appVariables; +end + +function androidndk.onWorkspace(workspace) + -- Not allowing references or escaped $ in workspace names as they complicate path building as well as specifying APP_BUILD_SCRIPT. + -- Premake treats any path beginning with $ as absolute. + -- APP_BUILD_SCRIPT also doesn't allow #$ and whitespaces. + if string.find(workspace.name, "%$") ~= nil or androidndk.staticallyHasPatternPostEsc(p.esc(workspace.name), "[%s#%$]") then + p.warn("Skipping workspace '%s' with name containing GNU make references, whitespaces or disallowed characters #$", workspace.name); + return; + end + + androidndk.setupGeneration(); + + -- Gather build configurations and platforms used by all projects for generating conditionals for application settings. + -- Projects may add their configurations not present in the workspace itself, and they won't appear in workspace.configs. + -- Some projects may have no platforms specified. + -- In this case, they're built regardless of the platform, and also if some completely different platform is specified. + -- At least one build configuration is required in a project, however. + local usedBuildcfgs = {}; + local usedPlatforms = {}; + for project in p.workspace.eachproject(workspace) do + if androidndk.isProjectSupported(project, false) then + for config in p.project.eachconfig(project) do + if androidndk.isConfigSupported(config, false) then + table.insertkeyed(usedBuildcfgs, config.buildcfg); + if config.platform ~= nil then + table.insertkeyed(usedPlatforms, config.platform); + end + end + end + end + end + + -- Application.mk. + p.generate(workspace, ".Application.mk", function(workspace) + -- Verify the correctness of the arguments, and set up the variables used for filtering in Application.mk. + androidndk.setupConfigConditionalMakeVariables(usedBuildcfgs, usedPlatforms); + + -- Gather Application.mk variable values for selected configurations and platforms. + -- As this is Application.mk, ABI filtering is not possible. + -- Therefore, selection of the required build configurations and platforms will work differently than in projects. + -- All selected build configurations and platforms must have the same values of settings written here. + -- For example, APP_OPTIM may be wrong with `PREMAKE_ANDROIDNDK_CONFIGURATIONS := Debug Release` if they have different `optimize`. + -- If one selected configuration-platform defines a value, while another doesn't care, pick from the one where it's explicitly set. + -- So, using if/if and ?= instead of if/elseif and :=, unlike in projects (where only configuration-platform pair is supposed to be chosen). + -- Most APP variables accept only one value - using ?= for them. + -- APP_ABI, however, accumulates the needed ABIs (in this case, for selected configurations and platforms), so using += for it. + + -- First, clear values that will be set - they might have been set externally while invoking ndk-build, override them. + -- Setting them to an empty value is not sufficient - `undefine` is required for ?= to work. + -- clear-vars is called for known APP variables before including Application.mk, which, as of NDK r23, defines the values as empty, however. + -- For this reason, if no value is provided, defining each of these variables as something (at least as empty). + p.w("undefine APP_ABI"); + p.w("undefine APP_DEBUG"); + p.w("undefine APP_OPTIM"); + p.w("undefine APP_PLATFORM"); + p.w("undefine PREMAKE_ANDROIDNDK_APP_STL_RUNTIME"); + p.w("undefine PREMAKE_ANDROIDNDK_APP_STL_LINKAGE"); + + -- Gather ABIs the projects are built for, for each configuration and platform for it. + -- Can't just collect all ABIs used by all platforms into a single APP_ABI assignment. + -- The reason is that some configurations may not even be targeting Android. + -- For instance, may only want ARM builds for Android, but x86 builds only for Windows. + -- If "all" is present in any platform-specific or platform-agnostic table, the whole platform must be treated as using "all". + local buildcfgsAbis = { + -- [1...] = [buildcfg] = { + -- platformSpecific = { + -- [1...] = [platform] = { + -- [1...] = [abi] = abi, + -- } + -- }, + -- platformAgnostic = { + -- [1...] = [abi] = abi, + -- }, + -- } + }; + local abiAgnosticConfigsUsed = false; + for project in p.workspace.eachproject(workspace) do + if androidndk.isProjectSupported(project, false) then + for config in p.project.eachconfig(project) do + if androidndk.isConfigSupported(config, false) then + local buildcfgAbis = buildcfgsAbis[config.buildcfg]; + if buildcfgAbis == nil then + buildcfgAbis = { platformSpecific = {}, platformAgnostic = {} }; + buildcfgsAbis[config.buildcfg] = buildcfgAbis; + end + local platformAbis; + if config.platform ~= nil then + platformAbis = buildcfgAbis.platformSpecific[config.platform]; + if platformAbis == nil then + platformAbis = {}; + buildcfgAbis.platformSpecific[config.platform] = platformAbis; + end + else + platformAbis = buildcfgAbis.platformAgnostic; + end + local abi = androidndk.getConfigAbi(config); + table.insertkeyed(platformAbis, abi); + if abi == androidndk.ABI_ALL then + abiAgnosticConfigsUsed = true; + end + end + end + end + end + + -- Gather the variables, and write the ABIs and variables. + for i, buildcfg in ipairs(usedBuildcfgs) do + local buildcfgConditionalOpen = false; + + local buildcfgAbis = buildcfgsAbis[buildcfg]; + -- Skip ABIs needed by platform-agnostic projects with this configuration from platform specializations. + -- ABIs needed by platform-agnostic projects will be added regardless of the platform. + local platformAgnosticAbis = nil; + local platformAgnosticAbisAll = false; + if buildcfgAbis ~= nil then + platformAgnosticAbis = buildcfgAbis.platformAgnostic; + platformAgnosticAbisAll = platformAgnosticAbis[androidndk.ABI_ALL] ~= nil; + end + + -- Gather and write platform-specific ABIs and platform specializations of APP variables. + for i2, platform in ipairs(usedPlatforms) do + -- Gather ABIs that any projects, for this buildcfg-platform pair, need to be built for. + -- Skip ABIs needed by platform-agnostic projects with this configuration from platform specializations. + -- ABIs needed by platform-agnostic projects will be added regardless of the platform. + local platformNewAbis = {}; + if buildcfgAbis ~= nil and not platformAgnosticAbisAll then + if buildcfgAbis ~= nil then + local platformAbisUnfiltered = buildcfgAbis.platformSpecific[platform]; + if platformAbisUnfiltered ~= nil then + for i3, abi in ipairs(platformAbisUnfiltered) do + if abi == androidndk.ABI_ALL then + platformNewAbis = { androidndk.ABI_ALL }; + break; + end + if platformAgnosticAbis == nil or platformAgnosticAbis[abi] == nil then + table.insert(platformNewAbis, abi); + end + end + -- Sort alphabetically for stable output. + table.sort(platformNewAbis); + end + end + end + -- Gather specializations of the APP variables for this platform. + local platformAppVariables = androidndk.gatherEscapedAppVariables(workspace, buildcfg, platform); + -- Write ABI additions and variable specializations for this platform. + if #platformNewAbis > 0 or #platformAppVariables > 0 then + if not buildcfgConditionalOpen then + buildcfgConditionalOpen = true; + p.push("ifneq ($(filter CONFIGURATION=%s,$(PREMAKE_ANDROIDNDK_CONFIGURATIONS_PREFIXED)),)", p.esc(buildcfg)); + end + p.push("ifneq ($(filter PLATFORM=%s,$(PREMAKE_ANDROIDNDK_PLATFORMS_PREFIXED)),)", p.esc(platform)); + androidndk.assignToVariablePostEsc("APP_ABI", platformNewAbis, false, nil, "+="); + for i3, variable in ipairs(platformAppVariables) do + androidndk.assignToVariablePostEsc(variable.variable, variable.value, false, nil, "?="); + end + -- Close the platform conditional. + p.pop("endif"); + end + end + + -- Write ABIs for platform-agnostic projects and APP variable values used when no platform specialization has been chosen. + local platformAgnosticAbisSorted = nil; + if platformAgnosticAbis ~= nil then + if platformAgnosticAbisAll then + platformAgnosticAbisSorted = { androidndk.ABI_ALL }; + else + platformAgnosticAbisSorted = table.arraycopy(platformAgnosticAbis); + -- Sort alphabetically for stable output. + table.sort(platformAgnosticAbisSorted); + end + end + local platformAgnosticAppVariables = androidndk.gatherEscapedAppVariables(workspace, buildcfg, nil); + if (platformAgnosticAbisSorted ~= nil and #platformAgnosticAbisSorted > 0) or #platformAgnosticAppVariables > 0 then + if not buildcfgConditionalOpen then + buildcfgConditionalOpen = true; + p.push("ifneq ($(filter CONFIGURATION=%s,$(PREMAKE_ANDROIDNDK_CONFIGURATIONS_PREFIXED)),)", p.esc(buildcfg)); + end + if platformAgnosticAbisSorted ~= nil then + androidndk.assignToVariablePostEsc("APP_ABI", platformAgnosticAbisSorted, false, nil, "+="); + end + for i2, variable in ipairs(platformAgnosticAppVariables) do + androidndk.assignToVariablePostEsc(variable.variable, variable.value, false, nil, "?="); + end + end + + if buildcfgConditionalOpen then + p.pop("endif"); + end + end + + -- Apply defaults if no configuration-specific overrides have been chosen, and normalize variables. + -- Also set variables that don't depend on the configuration. + + -- APP_ABI + -- Setting to "all" by default is not needed - empty already means `all`. + -- Also, it can happen only when no configuration with projects to build is selected. + -- Remove duplicates (sorting does that implicitly). + p.w("APP_ABI := $(sort $(APP_ABI))"); + -- If "all" was set for any configuration, and += of any other ABIs happened, make APP_ABI just "all". + if abiAgnosticConfigsUsed then + p.push("ifneq ($(filter " .. androidndk.ABI_ALL .. ",$(APP_ABI)),)"); + p.w("APP_ABI := " .. androidndk.ABI_ALL); + p.pop("endif"); + end + + -- APP_BUILD_SCRIPT + -- In the same directory, no need for p.filename followed by making it relative. + -- Also for this reason, no need for path.join, which may treat the name as an absolute path (for instance, if it begins with a $()\#-escaped #). + p.w("APP_BUILD_SCRIPT := %s", "$(call my-dir)/" .. p.esc(workspace.name .. ".wks.Android.mk")); + + -- APP_DEBUG and APP_OPTIM + -- Handling according to p.config.isDebugBuild and p.config.isOptimizedBuild logic (unoptimized by default, no symbols by default). + -- p.config.isDebugBuild always returns false (no symbols) for optimized builds. + -- For more information, see APP_DEBUG and APP_OPTIM gathering. + p.w("APP_OPTIM ?= debug"); + p.push("ifeq ($(APP_OPTIM),release)"); + p.w("APP_DEBUG := false"); + p.pop(); + p.push("else"); + p.w("APP_DEBUG ?= false"); + p.pop("endif"); + + -- APP_PLATFORM + -- Reverting the `undefine` because as of NDK r23, ndk-build sets all APP variables to empty values. + p.w("APP_PLATFORM ?="); + + -- APP_STL + -- The default for ndk-build is "none". Also, since the default for CMake is "c++_static", assuming static linkage by default. + p.w("PREMAKE_ANDROIDNDK_APP_STL_RUNTIME ?= none"); + p.w("PREMAKE_ANDROIDNDK_APP_STL_LINKAGE ?= _static"); + -- "none" and "system" don't have linkage suffixes. + p.push("ifneq ($(filter none system,$(PREMAKE_ANDROIDNDK_APP_STL_RUNTIME)),)"); + p.w("PREMAKE_ANDROIDNDK_APP_STL_LINKAGE :="); + p.pop("endif"); + p.w("APP_STL := $(PREMAKE_ANDROIDNDK_APP_STL_RUNTIME)$(PREMAKE_ANDROIDNDK_APP_STL_LINKAGE)"); + end); + + -- Android.mk. + p.generate(workspace, ".wks.Android.mk", function(workspace) + -- my-dir returns the directory containing the latest included file, not the directory with the current file. + -- Therefore, if project makefiles are written to other directories, calling it every time will result in incorrect paths. + p.w("PREMAKE_ANDROIDNDK_WORKSPACE_DIR := $(call my-dir)"); + + -- Verify the correctness of the arguments, and set up the variables used for filtering in Android.mk. + -- This needs to be in Android.mk even though it inherits Application.mk variables because with Gradle, Application.mk is optional. + androidndk.setupConfigConditionalMakeVariables(usedBuildcfgs, usedPlatforms); + + -- Setup post-expansion escaping in text passed to command execution via the shell. + -- In some variables - LOCAL_CFLAGS/LOCAL_LDFLAGS, LOCAL_C_INCLUDES, LOCAL_MODULE_FILENAME - path requirements are pretty relaxed. + -- On Linux, file names may contain " or \, but they need to be escaped with \ prefix as otherwise they're interpreted by the shell. + -- On Windows, " is not allowed in file names, but a single \ is treated as a path separator - no escaping required. + -- Function for calling inside quoted arguments (to be followed by the function for a list of arguments). + p.w("%s = $(subst \",\\\",$(subst \\,\\\\,$(1)))", androidndk.shellEscapePreQuotesMakeCall); + -- Function for calling for lists of quoted arguments. + local escapeFunction = { androidndk.shellEscapePostQuotesMakeCall, "_NO_BRACKETS = " }; + local bracketsEncountered = 0; + for i = #androidndk.shellEscapedCharactersPostQuotes, 1, -1 do + local character = androidndk.shellEscapedCharactersPostQuotes[i]; + if character == "(" or character == ")" then + -- Brackets must be used in a ${} call, not $(). + bracketsEncountered = bracketsEncountered + 1; + else + if character == "#" then + -- Escape the comment character similar to the way androidndk.esc does that, breaking the dependency on the number of backslashes. + character = androidndk.commentEscapeString; + elseif character == "$" then + character = "$$"; + end + table.insert(escapeFunction, "$(subst "); + table.insert(escapeFunction, character); + table.insert(escapeFunction, ",\\"); + table.insert(escapeFunction, character); + table.insert(escapeFunction, ","); + end + end + table.insert(escapeFunction, "$(1)"); + table.insert(escapeFunction, string.rep(")", #androidndk.shellEscapedCharactersPostQuotes - bracketsEncountered)); + p.w(table.concat(escapeFunction)); + -- Escape the brackets as well. + p.w( + "%s = ${subst ),\\),${subst (,\\(,${call %s_NO_BRACKETS,${1}}}}", + androidndk.shellEscapePostQuotesMakeCall, + androidndk.shellEscapePostQuotesMakeCall); + -- Function for raw text without arguments containing whitespaces that need to be wrapped in true, unescaped quotes. + p.w( + "%s = $(call %s,$(call %s,$(1)))", + androidndk.shellEscapeMakeCall, + androidndk.shellEscapePostQuotesMakeCall, + androidndk.shellEscapePreQuotesMakeCall); + -- Function for LOCAL_MODULE_FILENAME. + -- : must be \-escaped on Linux. + -- It's a drive letter separator on Windows, however, so it's disallowed in LOCAL_MODULE_FILENAME there (it's a name, not a path). + -- Therefore, there's no need to handle it separately on Windows and Linux - just \-escape : unconditionally. + p.w("%s = $(subst :,\\:,$(call %s,$(1)))", androidndk.shellEscapeModuleFileNameMakeCall, androidndk.shellEscapeMakeCall); + -- Function for LOCAL_SRC_FILES and LOCAL_PCH. + -- : must be \-escaped on Linux, but cannot be escaped on non-Cygwin Windows, and is a drive letter separator there supported directly. + p.push("ifeq ($(HOST_OS),windows)"); + p.w("%s = $(call %s,$(1))", androidndk.shellEscapeSrcFilesMakeCall, androidndk.shellEscapeMakeCall); + p.pop(); + p.push("else"); + p.w("%s = $(subst :,\\:,$(call %s,$(1)))", androidndk.shellEscapeSrcFilesMakeCall, androidndk.shellEscapeMakeCall); + p.pop("endif"); + + -- Include makefiles of all projects. + -- Same path logic as in p.generate. + local workspaceDirectory = path.getdirectory(p.filename(workspace, ".wks.Android.mk")); + for project in p.workspace.eachproject(workspace) do + if androidndk.isProjectSupported(project, false) then + p.w( + "include %s", + androidndk.preventWhitespaceTrimming( + path.join( + "$(PREMAKE_ANDROIDNDK_WORKSPACE_DIR)", + p.esc(path.getrelative(workspaceDirectory, p.filename(project, ".prj.Android.mk")))))); + end + end + end); +end diff --git a/src_rebuild/redriver2_psxpc.cpp b/src_rebuild/redriver2_psxpc.cpp index d635ebe6f..bb0f5ca78 100644 --- a/src_rebuild/redriver2_psxpc.cpp +++ b/src_rebuild/redriver2_psxpc.cpp @@ -19,6 +19,7 @@ #include "C/overlay.h" #include "C/players.h" #include "C/time.h" +#include "C/draw.h" #include "utils/ini.h" @@ -560,12 +561,9 @@ int main(int argc, char** argv) extern int gContentOverride; int newScrZ = gCameraDefaultScrZ; const char* dataFolderStr = ini_get(config, "fs", "dataFolder"); - const char* userReplaysStr = ini_get(config, "game", "userChases"); if(!cdImageFileName) cdImageFileName = ini_get(config, "cdfs", "image"); - - InitUserReplays(userReplaysStr); // configure Psy-X pads ini_sget(config, "pad", "pad1device", "%d", &g_cfg_controllerToSlotMapping[0]); @@ -584,6 +582,9 @@ int main(int argc, char** argv) // configure host game ini_sget(config, "game", "drawDistance", "%d", &gDrawDistance); +#ifdef DYNAMIC_LIGHTING + ini_sget(config, "game", "dynamicLights", "%d", &gEnableDlights); +#endif ini_sget(config, "game", "disableChicagoBridges", "%d", &gDisableChicagoBridges); ini_sget(config, "game", "fieldOfView", "%d", &newScrZ); ini_sget(config, "game", "freeCamera", "%d", &enableFreecamera); diff --git a/src_rebuild/tools/font_tool/font_tool_main.cpp b/src_rebuild/tools/font_tool/font_tool_main.cpp new file mode 100644 index 000000000..407d29161 --- /dev/null +++ b/src_rebuild/tools/font_tool/font_tool_main.cpp @@ -0,0 +1,156 @@ +#include +#include +#include +#include +#include + +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" +#include "targa.h" + +#include "hqfont.h" + +struct FN2RangeInfo +{ + OUT_FN2RANGE hdr; + OUT_FN2INFO* chars; +}; + +static FN2RangeInfo fontRanges[4]; +static int fontRangeCount = 0; + +void Usage() +{ + printf("example: FontTool -i -o \n\nAdditional arguments:"); + printf("\t-r : add range of characters"); +} + +int main(int argc, char** argv) +{ + if (argc < 2) + { + Usage(); + return 0; + } + + { + FN2RangeInfo& firstRange = fontRanges[0]; + + firstRange.hdr.start = 32; + firstRange.hdr.count = 224; + firstRange.chars = new OUT_FN2INFO[firstRange.hdr.count]; + ++fontRangeCount; + } + + const char* inputFilename = nullptr; + const char* outpitFilename = nullptr; + + for (int i = 0; i < argc; ++i) + { + if (!strcmp(argv[i], "-i") && i + 1 < argc) + { + inputFilename = argv[i+1]; + } + else if (!strcmp(argv[i], "-o") && i + 1 < argc) + { + outpitFilename = argv[i + 1]; + } + else if (!strcmp(argv[i], "-r") && i + 2 < argc) + { + FN2RangeInfo& newRange = fontRanges[fontRangeCount]; + + newRange.hdr.start = atoi(argv[i + 1]); + newRange.hdr.count = atoi(argv[i + 2]); + newRange.chars = new OUT_FN2INFO[newRange.hdr.count]; + ++fontRangeCount; + } + } + + if (!inputFilename) + { + Usage(); + return 0; + } + + if (!outpitFilename) + { + Usage(); + return 0; + } + + FILE* fp = fopen(inputFilename, "rb"); + if (!fp) + { + printf("Cannot open %s\n", inputFilename); + return -1; + } + + // read whole file + fseek(fp, 0, SEEK_END); + const long size = ftell(fp); + fseek(fp, 0, SEEK_SET); + u_char* data = (u_char*)malloc(size); + fread(data, 1, size, fp); + fclose(fp); + + // gen font + u_char* tmpBitmap = (u_char*)malloc(HIRES_FONT_SIZE_W * HIRES_FONT_SIZE_H); + u_int* bitmapRGBA = (u_int*)malloc(HIRES_FONT_SIZE_W * HIRES_FONT_SIZE_H * 4); + + stbtt_pack_context pc; + stbtt_PackBegin(&pc, tmpBitmap, HIRES_FONT_SIZE_W, HIRES_FONT_SIZE_H, 0, 2, NULL); + stbtt_PackSetOversampling(&pc, 1, 1); + stbtt_PackSetSkipMissingCodepoints(&pc, 1); + + for (int i = 0; i < fontRangeCount; ++i) + { + FN2RangeInfo& range = fontRanges[i]; + stbtt_PackFontRange(&pc, data, 0, 65.0f, range.hdr.start, range.hdr.count, (stbtt_packedchar*)range.chars); + } + + stbtt_PackEnd(&pc); + + for (int x = 0; x < HIRES_FONT_SIZE_W; ++x) + { + for (int y = 0; y < HIRES_FONT_SIZE_H; ++y) + { + bitmapRGBA[x + y * HIRES_FONT_SIZE_W] = tmpBitmap[x + y * HIRES_FONT_SIZE_W] << 24 | 0xffffff; + } + } + + { + char tgaFileName[256]; + strcpy(tgaFileName, outpitFilename); + strcat(tgaFileName, ".tga"); + + SaveTGAImage(tgaFileName, (u_char*)bitmapRGBA, HIRES_FONT_SIZE_W, HIRES_FONT_SIZE_H, 32); + } + + { + char fntFileName[256]; + strcpy(fntFileName, outpitFilename); + strcat(fntFileName, ".fn2"); + + FILE* fntFp = fopen(fntFileName, "wb"); + if (fntFp) + { + OUT_FN2HEADER fn2hdr; + fn2hdr.version = FN2_VERSION; + fn2hdr.range_count = fontRangeCount; + fwrite(&fn2hdr, sizeof(fn2hdr), 1, fntFp); + + for (int i = 0; i < fontRangeCount; ++i) + { + FN2RangeInfo& range = fontRanges[i]; + fwrite(&range.hdr, sizeof(OUT_FN2RANGE), 1, fntFp); + fwrite(range.chars, sizeof(OUT_FN2INFO), range.hdr.count, fntFp); + + delete[] range.chars; + } + } + } + + free(bitmapRGBA); + free(tmpBitmap); + free(data); +} \ No newline at end of file diff --git a/src_rebuild/DebugOverlay.cpp b/src_rebuild/utils/DebugOverlay.cpp similarity index 88% rename from src_rebuild/DebugOverlay.cpp rename to src_rebuild/utils/DebugOverlay.cpp index bd4a63777..a8b67c53d 100644 --- a/src_rebuild/DebugOverlay.cpp +++ b/src_rebuild/utils/DebugOverlay.cpp @@ -1,21 +1,19 @@ -#include "Game/driver2.h" - -#include "Game/C/mission.h" -#include "Game/C/convert.h" -#include "Game/C/camera.h" -#include "Game/C/dr2roads.h" -#include "Game/C/system.h" -#include "Game/C/pres.h" -#include "Game/C/spool.h" -#include "Game/C/cars.h" -#include "Game/C/draw.h" -#include "Game/C/players.h" -#include "Game/C/glaunch.h" - -#include - +#include "driver2.h" + +#include "C/mission.h" +#include "C/convert.h" +#include "C/camera.h" +#include "C/dr2roads.h" +#include "C/system.h" +#include "C/pres.h" +#include "C/spool.h" +#include "C/cars.h" +#include "C/draw.h" +#include "C/players.h" +#include "C/glaunch.h" #include "C/felony.h" +#include int gDisplayDrawStats = 0; @@ -160,13 +158,12 @@ void DrawDebugOverlays() int lane = GetLaneByPositionOnRoad(&roadInfo, carPos); - sprintf(tempBuf, "%s %d flg %d%d%d spd %d len %d", + sprintf(tempBuf, "%s %d PRK(%d-%d) SPD(%d) LEN(%d)", roadInfo.straight ? "STR" : "CRV", roadInfo.surfId, - (roadInfo.NumLanes & 0x20) > 0, // flag 0 - first lane? - (roadInfo.NumLanes & 0x40) > 0, // flag 1 - leftmost park - (roadInfo.NumLanes & 0x80) > 0, // flag 2 - rightmost park - ROAD_SPEED_LIMIT(&roadInfo), // speed limit id + ROAD_IS_LEFTMOST_LANE_PARKING(&roadInfo), + ROAD_IS_RIGHTMOST_LANE_PARKING(&roadInfo), + ROAD_SPEED_LIMIT(&roadInfo), segLen ); @@ -183,7 +180,7 @@ void DrawDebugOverlays() PrintString(tempBuf, 10, 195); sprintf(tempBuf, "c %d %d %d %d", - (int)(*roadInfo.ConnectIdx)[0], (int)(*roadInfo.ConnectIdx)[1], (int)(*roadInfo.ConnectIdx)[2], (int)(*roadInfo.ConnectIdx)[3]); + (int)roadInfo.ConnectIdx[0], (int)roadInfo.ConnectIdx[1], (int)roadInfo.ConnectIdx[2], (int)roadInfo.ConnectIdx[3]); PrintString(tempBuf, 10, 205); } @@ -191,10 +188,16 @@ void DrawDebugOverlays() { DRIVER2_JUNCTION* junc = GET_JUNCTION(roadInfo.surfId); - sprintf(tempBuf, "JUN %d flg %d - c %d %d %d %d",roadInfo.surfId, junc->flags, - (int)(*roadInfo.ConnectIdx)[0], (int)(*roadInfo.ConnectIdx)[1], (int)(*roadInfo.ConnectIdx)[2], (int)(*roadInfo.ConnectIdx)[3]); + sprintf(tempBuf, "JUN %d TL(%d) YLD(%d)", + roadInfo.surfId, + (junc->flags & 1), (junc->flags & 2)); PrintString(tempBuf, 10, 180); + + sprintf(tempBuf, "c %d %d %d %d", + (int)roadInfo.ConnectIdx[0], (int)roadInfo.ConnectIdx[1], (int)roadInfo.ConnectIdx[2], (int)roadInfo.ConnectIdx[3]); + + PrintString(tempBuf, 10, 205); } @@ -238,7 +241,7 @@ void Debug_Line2D(SXYPAIR& pointA, SXYPAIR& pointB, CVECTOR& color) line->g0 = color.g; line->b0 = color.b; -#if defined(USE_PGXP) && defined(USE_EXTENDED_PRIM_POINTERS) +#if USE_PGXP && USE_EXTENDED_PRIM_POINTERS line->pgxp_index = 0xFFFF; #endif diff --git a/src_rebuild/utils/hqfont.h b/src_rebuild/utils/hqfont.h new file mode 100644 index 000000000..0c1250964 --- /dev/null +++ b/src_rebuild/utils/hqfont.h @@ -0,0 +1,27 @@ +#ifndef HQFONT_H +#define HQFONT_H + +#define FN2_VERSION 1 +#define HIRES_FONT_SIZE_W 768 +#define HIRES_FONT_SIZE_H 768 + +struct OUT_FN2INFO +{ + u_short x0, y0, x1, y1; + float xoff, yoff, xadvance; + float xoff2, yoff2; +}; + +struct OUT_FN2RANGE +{ + u_short start; + u_short count; +}; + +struct OUT_FN2HEADER +{ + u_short version; + u_short range_count; +}; + +#endif // HQFONT_H \ No newline at end of file diff --git a/src_rebuild/utils/stb_truetype.h b/src_rebuild/utils/stb_truetype.h new file mode 100644 index 000000000..643d37899 --- /dev/null +++ b/src_rebuild/utils/stb_truetype.h @@ -0,0 +1,5085 @@ +// [DEAR IMGUI] +// This is a slightly modified version of stb_truetype.h 1.26. +// Mostly fixing for compiler and static analyzer warnings. +// Grep for [DEAR IMGUI] to find the changes. + +// stb_truetype.h - v1.26 - public domain +// authored from 2009-2021 by Sean Barrett / RAD Game Tools +// +// ======================================================================= +// +// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES +// +// This library does no range checking of the offsets found in the file, +// meaning an attacker can use it to read arbitrary memory. +// +// ======================================================================= +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe +// Cass Everitt Martins Mozeiko github:aloucks +// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam +// Brian Hook Omar Cornut github:vassvik +// Walter van Niftrik Ryan Griege +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. Brian Costabile +// Ken Voskuil (kaesve) +// +// VERSION HISTORY +// +// 1.26 (2021-08-28) fix broken rasterizer +// 1.25 (2021-07-11) many fixes +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places need to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to . I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// to = 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); +// Query the font vertical metrics without having to create a font first. + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); +// If skip != 0, this tells stb_truetype to skip any codepoints for which +// there is no corresponding glyph. If skip=0, which is the default, then +// codepoints without a glyph recived the font's "missing character" glyph, +// typically an empty box by convention. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + int skip_missing; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publicly so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. +// Returns 0 if the character codepoint is not defined in the font. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); +// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 +// table (specific to MS/Windows TTF files). +// +// Returns 1 on success (table present), 0 on failure. + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + +typedef struct stbtt_kerningentry +{ + int glyph1; // use stbtt_FindGlyphIndex + int glyph2; + int advance; +} stbtt_kerningentry; + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); +// Retrieves a complete list of all of the kerning pairs provided by the font +// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. +// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of contours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +STBTT_DEF unsigned char *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl); +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); +// fills svg with the character's SVG data. +// returns data size or 0 if SVG not found. + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + + + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +// since most people won't use this, find this table the first time it's needed +static int stbtt__get_svg(stbtt_fontinfo *info) +{ + stbtt_uint32 t; + if (info->svg < 0) { + t = stbtt__find_table(info->data, info->fontstart, "SVG "); + if (t) { + stbtt_uint32 offset = ttULONG(info->data + t + 2); + info->svg = t + offset; + } else { + info->svg = 0; + } + } + return info->svg; +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + info->svg = -1; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start, last; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + last = ttUSHORT(data + endCount + 2*item); + if (unicode_codepoint < start || unicode_codepoint > last) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours < 0) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0 && vertices) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // FALLTHROUGH + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && b0 < 32) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) +{ + stbtt_uint8 *data = info->data + info->kern; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + return ttUSHORT(data+10); +} + +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) +{ + stbtt_uint8 *data = info->data + info->kern; + int k, length; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + length = ttUSHORT(data+10); + if (table_length < length) + length = table_length; + + for (k = 0; k < length; k++) + { + table[k].glyph1 = ttUSHORT(data+18+(k*6)); + table[k].glyph2 = ttUSHORT(data+20+(k*6)); + table[k].advance = ttSHORT(data+22+(k*6)); + } + + return length; +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch (coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + break; + } + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + break; + } + + default: return -1; // unsupported + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch (classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + break; + } + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + break; + } + + default: + return -1; // Unsupported definition type, return an error. + } + + // "All glyphs not assigned to a class fall into class 0". (OpenType spec) + return 0; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i, sti; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i= pairSetCount) return 0; + + needle=glyph2; + r=pairValueCount-1; + l=0; + + // Binary search. + while (l <= r) { + stbtt_uint16 secondGlyph; + stbtt_uint8 *pairValue; + m = (l + r) >> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } else + return 0; + break; + } + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + if (valueFormat1 == 4 && valueFormat2 == 0) { // Support more formats? + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + stbtt_uint8 *class1Records, *class2Records; + stbtt_int16 xAdvance; + + if (glyph1class < 0 || glyph1class >= class1Count) return 0; // malformed + if (glyph2class < 0 || glyph2class >= class2Count) return 0; // malformed + + class1Records = table + 16; + class2Records = class1Records + 2 * (glyph1class * class2Count); + xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } else + return 0; + break; + } + + default: + return 0; // Unsupported position format + } + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + else if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) +{ + int i; + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); + + int numEntries = ttUSHORT(svg_doc_list); + stbtt_uint8 *svg_docs = svg_doc_list + 2; + + for(i=0; i= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) + return svg_doc; + } + return 0; +} + +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) +{ + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc; + + if (info->svg == 0) + return 0; + + svg_doc = stbtt_FindSVGDoc(info, gl); + if (svg_doc != NULL) { + *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); + return ttULONG(svg_doc + 8); + } else { + return 0; + } +} + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) +{ + return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static float stbtt__sized_trapezoid_area(float height, float top_width, float bottom_width) +{ + STBTT_assert(top_width >= 0); + STBTT_assert(bottom_width >= 0); + return (top_width + bottom_width) / 2.0f * height; +} + +static float stbtt__position_trapezoid_area(float height, float tx0, float tx1, float bx0, float bx1) +{ + return stbtt__sized_trapezoid_area(height, tx1 - tx0, bx1 - bx0); +} + +static float stbtt__sized_triangle_area(float height, float width) +{ + return height * width / 2; +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = (sy1 - sy0) * e->direction; + STBTT_assert(x >= 0 && x < len); + scanline[x] += stbtt__position_trapezoid_area(height, x_top, x+1.0f, x_bottom, x+1.0f); + scanline_fill[x] += height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, y_final, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + STBTT_assert(dy >= 0); + STBTT_assert(dx >= 0); + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = y_top + dy * (x1+1 - x0); + + // compute intersection with y axis at x2 + y_final = y_top + dy * (x2 - x0); + + // x1 x_top x2 x_bottom + // y_top +------|-----+------------+------------+--------|---+------------+ + // | | | | | | + // | | | | | | + // sy0 | Txxxxx|............|............|............|............| + // y_crossing | *xxxxx.......|............|............|............| + // | | xxxxx..|............|............|............| + // | | /- xx*xxxx........|............|............| + // | | dy < | xxxxxx..|............|............| + // y_final | | \- | xx*xxx.........|............| + // sy1 | | | | xxxxxB...|............| + // | | | | | | + // | | | | | | + // y_bottom +------------+------------+------------+------------+------------+ + // + // goal is to measure the area covered by '.' in each pixel + + // if x2 is right at the right edge of x1, y_crossing can blow up, github #1057 + // @TODO: maybe test against sy1 rather than y_bottom? + if (y_crossing > y_bottom) + y_crossing = y_bottom; + + sign = e->direction; + + // area of the rectangle covered from sy0..y_crossing + area = sign * (y_crossing-sy0); + + // area of the triangle (x_top,sy0), (x1+1,sy0), (x1+1,y_crossing) + scanline[x1] += stbtt__sized_triangle_area(area, x1+1 - x_top); + + // check if final y_crossing is blown up; no test case for this + if (y_final > y_bottom) { + int denom = (x2 - (x1+1)); + y_final = y_bottom; + if (denom != 0) { // [DEAR IMGUI] Avoid div by zero (https://github.com/nothings/stb/issues/1316) + dy = (y_final - y_crossing ) / denom; // if denom=0, y_final = y_crossing, so y_final <= y_bottom + } + } + + // in second pixel, area covered by line segment found in first pixel + // is always a rectangle 1 wide * the height of that line segment; this + // is exactly what the variable 'area' stores. it also gets a contribution + // from the line segment within it. the THIRD pixel will get the first + // pixel's rectangle contribution, the second pixel's rectangle contribution, + // and its own contribution. the 'own contribution' is the same in every pixel except + // the leftmost and rightmost, a trapezoid that slides down in each pixel. + // the second pixel's contribution to the third pixel will be the + // rectangle 1 wide times the height change in the second pixel, which is dy. + + step = sign * dy * 1; // dy is dy/dx, change in y for every 1 change in x, + // which multiplied by 1-pixel-width is how much pixel area changes for each step in x + // so the area advances by 'step' every time + + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; // area of trapezoid is 1*step/2 + area += step; + } + STBTT_assert(STBTT_fabs(area) <= 1.01f); // accumulated error from area += step unless we round step down + STBTT_assert(sy1 > y_final-0.01f); + + // area covered in the last pixel is the rectangle from all the pixels to the left, + // plus the trapezoid filled by the line segment in this pixel all the way to the right edge + scanline[x2] += area + sign * stbtt__position_trapezoid_area(sy1-y_final, (float) x2, x2+1.0f, x_bottom, x2+1.0f); + + // the rest of the line is filled based on the total height of the line segment in this pixel + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + // note though that this does happen some of the time because + // x_top and x_bottom can be extrapolated at the top & bottom of + // the shape and actually lie outside the bounding box + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + if (j == 0 && off_y != 0) { + if (z->ey < scan_y_top) { + // this can happen due to subpixel positioning and some kind of fp rounding error i think + z->ey = scan_y_top; + } + } + STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + spc->skip_missing = 0; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) +{ + spc->skip_missing = skip; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + int missing_glyph_added = 0; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { + rects[k].w = rects[k].h = 0; + } else { + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + if (glyph == 0) + missing_glyph_added = 1; + } + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, missing_glyph = -1, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed && r->w != 0 && r->h != 0) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + + if (glyph == 0) + missing_glyph = j; + } else if (spc->skip_missing) { + return_value = 0; + } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { + ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i, j, n, return_value; // [DEAR IMGUI] removed = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) +{ + int i_ascent, i_descent, i_lineGap; + float scale; + stbtt_fontinfo info; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); + *ascent = (float) i_ascent * scale; + *descent = (float) i_descent * scale; + *lineGap = (float) i_lineGap * scale; +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + + orig[0] = x; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + a*x^2 + b*x + c = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + if (scale == 0) return NULL; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + if (verts[i].type == STBTT_vline && precompute[i] != 0.0f) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + float dist,dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3] = {0.f,0.f,0.f}; + float px,py,t,it,dist2; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.25 (2021-07-11) many fixes +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/src_rebuild/utils/targa.cpp b/src_rebuild/utils/targa.cpp index 24d45df13..ffecd628c 100644 --- a/src_rebuild/utils/targa.cpp +++ b/src_rebuild/utils/targa.cpp @@ -1,6 +1,7 @@ #include "targa.h" #include #include +#include bool LoadTGAImage(const char* filename, u_char** data, int& width, int& height, int& bpp) { @@ -32,7 +33,7 @@ bool LoadTGAImage(const char* filename, u_char** data, int& width, int& height, fread(palette, sizeof(palette), 1, fp); // Read the file data - fBuffer = new u_char[size - sizeof(header) - palLength]; + fBuffer = (u_char*)malloc(size - sizeof(header) - palLength); fread(fBuffer, size - sizeof(header) - palLength, 1, fp); fclose(fp); @@ -43,7 +44,7 @@ bool LoadTGAImage(const char* filename, u_char** data, int& width, int& height, { uint c, count; - tempBuffer = new u_char[size]; + tempBuffer = (u_char*)malloc(size); dest = tempBuffer; src = fBuffer; @@ -95,7 +96,7 @@ bool LoadTGAImage(const char* filename, u_char** data, int& width, int& height, if (palLength > 0) { bpp = 24; - dest = *data = new u_char[width * height * 3]; + dest = *data = (u_char*)malloc(width * height * 3); for (y = 0; y < height; y++) { for (x = 0; x < width; x++) @@ -111,7 +112,7 @@ bool LoadTGAImage(const char* filename, u_char** data, int& width, int& height, else { bpp = 8; - dest = *data = new u_char[width * height]; + dest = *data = (u_char*)malloc(width * height); for (y = 0; y < height; y++) { memcpy(dest, src, width); @@ -122,7 +123,7 @@ bool LoadTGAImage(const char* filename, u_char** data, int& width, int& height, break; case 16: bpp = 32; - dest = *data = new u_char[width * height * 4]; + dest = *data = (u_char*)malloc(width * height * 4); for (y = 0; y < height; y++) { for (x = 0; x < width; x++) @@ -141,7 +142,7 @@ bool LoadTGAImage(const char* filename, u_char** data, int& width, int& height, break; case 24: bpp = 24; - dest = *data = new u_char[width * height * 3]; + dest = *data = (u_char*)malloc(width * height * 3); for (y = 0; y < height; y++) { @@ -157,7 +158,7 @@ bool LoadTGAImage(const char* filename, u_char** data, int& width, int& height, break; case 32: bpp = 32; - dest = *data = new u_char[width * height * 4]; + dest = *data = (u_char*)malloc(width * height * 4); for (y = 0; y < height; y++) { for (x = 0; x < width; x++) @@ -173,8 +174,64 @@ bool LoadTGAImage(const char* filename, u_char** data, int& width, int& height, break; } - delete[] tempBuffer; - delete[] fBuffer; + free(tempBuffer); + free(fBuffer); + + return true; +} + +bool SaveTGAImage(const char* filename, u_char* data, int width, int height, int bpp) +{ + TGAHeader tgaHeader; + + // Initialize the Targa header + tgaHeader.descriptionlen = 0; + tgaHeader.cmaptype = 0; + tgaHeader.imagetype = 2; + tgaHeader.cmapstart = 0; + tgaHeader.cmapentries = 0; + tgaHeader.cmapbits = 0; + tgaHeader.xoffset = 0; + tgaHeader.yoffset = 0; + tgaHeader.width = width; + tgaHeader.height = height; + tgaHeader.bpp = bpp; + tgaHeader.attrib = 0; + + int imageSize = width * height * (bpp / 8); + + FILE* fp = fopen(filename, "wb"); + if (!fp) + return false; + + // Write the header + fwrite(&tgaHeader, sizeof(TGAHeader), 1, fp); + + // Write the image data + u_char* src = data + (bpp / 8) * width * (height - 1); + + switch (bpp) + { + case 32: + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + u_char pixel[4]; + pixel[0] = src[2]; + pixel[1] = src[1]; + pixel[2] = src[0]; + pixel[3] = src[3]; + + fwrite(pixel, sizeof(pixel), 1, fp); + src += 4; + } + src -= 8 * width; + } + break; + } + + fclose(fp); return true; } \ No newline at end of file diff --git a/src_rebuild/utils/targa.h b/src_rebuild/utils/targa.h index 0183663aa..8190f9031 100644 --- a/src_rebuild/utils/targa.h +++ b/src_rebuild/utils/targa.h @@ -24,5 +24,6 @@ struct TGAHeader #pragma pack (pop) bool LoadTGAImage(const char* filename, u_char** data, int& width, int& height, int& bpp); +bool SaveTGAImage(const char* filename, u_char* data, int width, int height, int bpp); #endif // TARGA_H \ No newline at end of file diff --git a/src_rebuild/utils/video_source/VideoPlayer.cpp b/src_rebuild/utils/video_source/VideoPlayer.cpp index 4f0b2b0d2..666b105e4 100644 --- a/src_rebuild/utils/video_source/VideoPlayer.cpp +++ b/src_rebuild/utils/video_source/VideoPlayer.cpp @@ -23,7 +23,7 @@ void InitFMVFont() RECT16 fontRect; DrawSync(0); - LoadClut((u_long *)(fmvFont + 20),960,72); + LoadClut((u_long*)(fmvFont + 20),960,72); DrawSync(0); fontRect.x = 960; @@ -31,7 +31,7 @@ void InitFMVFont() fontRect.w = 64; fontRect.h = 72; - LoadImage2(&fontRect,(u_long *)(fmvFont + 64)); + LoadImage2(&fontRect,(u_long*)(fmvFont + 64)); DrawSync(0); } @@ -49,7 +49,7 @@ void PrintFMVText(char *str, int x, short y, int brightness) OTTYPE ot; POLY_FT4* poly; - ClearOTagR((ulong*)&ot, 1); + ClearOTagR((u_long*)&ot, 1); poly = fmvTextPolys; str_w = 0; @@ -119,7 +119,7 @@ void PrintFMVText(char *str, int x, short y, int brightness) } } - DrawOTag((ulong*)&ot); + DrawOTag((u_long*)&ot); } int UnpackJPEG(unsigned char* src_buf, unsigned src_length, unsigned bpp, unsigned char* dst_buf, int& width, int& height) @@ -180,7 +180,7 @@ void SetupMovieRectangle(int image_w, int image_h) u_char r = 1; u_char b = 1; -#ifdef USE_PGXP +#if USE_PGXP GR_SetViewPort(0, 0, windowWidth, windowHeight); GrVertex blit_vertices[] =