105 Commits

Author SHA1 Message Date
5a7f101155 Nuke UpdateTexture
• It never gets called except with UpdateMaterial, and UpdateMaterial isnt very heavy
• Also add a check for if the text changed in ModuleTextDecal to avoid calling a bunch of code unnecessarily. Only really affects editor performance when adjusting sliders but nice to have anyways
2022-03-21 22:24:15 -07:00
39555d92d1 Clean up things broken by the merge
And refactor flag module yet again
2022-03-18 21:35:25 -07:00
7a909c18bc Merge branch 'main' into feature-better-tweakables
Probably breaks everything
2022-03-14 21:48:48 -07:00
d714c498fb Apply stash 2022-03-14 21:36:50 -07:00
fa4b799788 Run on create 2022-03-13 19:12:18 -07:00
970a69be11 Release 0.2.10
### Fixed

- Fixed decals not projecting on loading prefabs

### Changed

- Re-enabled projecting onto TransparentFX layer

### Added

- Allowed for regular expressions to be used when blacklisting shaders
- Added all Waterfall shaders to the shader blacklist when Waterfall is present
2022-03-13 18:45:14 -07:00
dddb7f09f3 make log message make a bit more sense 2022-03-13 18:43:00 -07:00
3cd229bc23 Fix decals not projecting when loading prefabs
• Add support for regex shader blacklisting
• Re-allow projecting onto transparentFX
2022-03-13 14:02:39 -07:00
4948818065 Remove Travis badge 2022-03-11 23:10:20 -08:00
036b084d34 Fix remote version file url 2022-03-11 22:49:03 -08:00
1e3addeb4d Include Harmony in build 2022-03-11 22:42:31 -08:00
47c70d3071 Fix actions 2022-03-11 22:36:19 -08:00
9955b9ce30 Don't version the Readme 2022-03-11 22:33:05 -08:00
32ddc54019 Release 0.2.9
### Fixed

- Fixed text decals breaking when used in symmetry
- Fixed decals projecting onto the TransparentFX layer, such as Waterfall plumes
2022-03-11 22:29:14 -08:00
ff262d55c8 Automatically fast-forward the release branch on release 2022-03-11 22:28:15 -08:00
d1f3d1fa55 Merge branch 'release' 2022-03-11 22:10:05 -08:00
2965633235 Update changelog 2022-03-11 22:02:10 -08:00
a82369e595 Merge branch 'hotfix' 2022-03-11 21:57:18 -08:00
16c4fa2b96 Github mandates a .txt extension for text files??? 2022-03-11 21:31:45 -08:00
591ec0aa74 Setup version URL from releases
this is going to 404 until the next release but oh well
2022-03-11 21:28:14 -08:00
210d326469 Last touches 2022-03-10 19:31:16 -08:00
dd405a21db Fix speeling 2022-03-10 19:05:26 -08:00
c40f700235 Fix both deployment steps
thanks @DasSkelett!
2022-03-10 18:39:36 -08:00
36cc896b2a Fix file 2022-03-09 22:07:32 -08:00
4c02e91dfd Fix spacedock login 2022-03-09 22:05:26 -08:00
41b15477fb More shenanigans 2022-03-09 22:03:06 -08:00
b61f71bc00 github why 2022-03-09 21:53:46 -08:00
74fe42a7ff why 2022-03-09 21:37:22 -08:00
7fa8969d5b wtf 2022-03-09 21:36:33 -08:00
21f8332d66 Fix publish and artifact handling 2022-03-09 21:22:05 -08:00
cb219a52c0 Don't try to validate other mods version info 2022-03-09 21:03:38 -08:00
f2f2100334 fix spelling 2022-03-09 21:00:27 -08:00
9bdb95527a Fix python dependencies 2022-03-09 20:58:17 -08:00
ad504ce4cc Download dependencies and simple deploy script 2022-03-09 20:57:10 -08:00
18ebbd7b4c Pesky commas 2022-03-08 23:54:23 -08:00
ba189f18c4 Clean up version file and add some logging 2022-03-08 23:52:40 -08:00
322aaa613e Add script for updating version 2022-03-08 23:43:33 -08:00
5443377bfe Descriptive artifact name 2022-03-07 00:29:39 -08:00
1ed6dc0f68 Fix path 2022-03-07 00:28:00 -08:00
90dec1b42a Simplify uploading and ensure Plugin directory exists 2022-03-07 00:24:46 -08:00
80da98d95a Fix DLL copy snippet 2022-03-07 00:17:21 -08:00
76016fbbf1 Case sensitivity is evil 2022-03-07 00:10:10 -08:00
53657b8fad Fix zip command and name the artefact 2022-03-07 00:08:43 -08:00
9d7502091c Upload build output 2022-03-07 00:06:16 -08:00
f788c25837 Remove DLL from repo 2022-03-07 00:06:07 -08:00
017110738b correct dll location 2022-03-06 23:52:03 -08:00
fd7134ece9 Actually use the right command 2022-03-06 23:52:03 -08:00
68ceacfef4 URL is case sensitive 2022-03-06 23:52:03 -08:00
a452844cd1 Start on build script 2022-03-06 23:52:03 -08:00
4b8d11e1de Use markdown changelog 2022-03-06 23:52:03 -08:00
8f888ee552 Remove travis config 2022-03-06 23:52:03 -08:00
95b99d1d42 Clean up text update function 2022-03-06 00:01:23 -08:00
ba6676b625 Hide NRE warning in Rider
its terrible but I've never seen it NRE so 🤷‍♂️
2022-03-05 23:44:59 -08:00
495441be06 Fix text not updating correctly in symmetry 2022-03-05 23:38:12 -08:00
8025a0a418 Ignore thumbnails 2022-02-24 23:33:34 -08:00
760609fae2 Refactor flag decal
Not exhaustively tested but shouldnt be different from before
2022-02-24 23:33:07 -08:00
ecc60751f7 Don't project onto TransparentFX layer 2021-12-25 23:15:15 -08:00
e10b7e4b51 Merge branch 'main' into release 2021-03-18 04:27:11 -07:00
a91a83a280 Update B9PartSwitch 2021-03-18 04:26:45 -07:00
316f92df2c Update shabby to use Harmony 2 2021-03-18 04:22:13 -07:00
f20119565f Run some logic only at startup 2021-01-07 02:45:58 -08:00
6ee52ac18e Fix some projection logic 2021-01-06 01:09:43 -08:00
67ccaa1dae Fix textures not updating sometimes 2021-01-05 19:02:43 -08:00
c33f5bacdd Fix symmetry 2021-01-05 16:23:15 -08:00
03d2e66543 simplify opacity cutoff and wear by using material properties 2021-01-05 16:01:26 -08:00
be3b7f791a Remove debug logging statements 2021-01-05 00:44:02 -08:00
3effae6ecd Merge branch 'feature-multiProject' into develop 2021-01-05 00:40:55 -08:00
7406fa613b Clear targets on attach 2021-01-05 00:40:30 -08:00
7b9ed99325 Cleanup of abandoned saving code 2021-01-01 14:28:30 -08:00
e069f85e56 More tweaks 2021-01-01 14:02:46 -08:00
2f2b6fb692 Get multi-projection to work 2020-12-31 21:05:30 -08:00
169f2e9bf1 Add uint parsing function 2020-12-21 03:30:36 -08:00
eb033113b3 Merge branch 'main' into feature-multiProject 2020-12-20 01:46:16 -08:00
938babb41b Merge branch 'main' into release 2020-12-19 17:08:13 -08:00
3173dd914a Update version and remove debug statement 2020-12-19 17:06:52 -08:00
5f6712f476 Revert changes to ModuleConformalDecal.cs 2020-12-19 16:58:15 -08:00
6b7996fdd7 Text render simplification and small optimizations 2020-12-19 16:11:56 -08:00
5feb16dcfb Only update text once per frame
Fixed text re-rendering several times in a single frame when pasting in text
2020-12-18 21:14:34 -08:00
bf8e98caf0 Fix text rendering for some non-ascii strings 2020-12-17 16:14:33 -08:00
b634eb1e8e Update version and changelog 2020-12-16 13:04:46 -08:00
dadf38acd5 go back to using temporary rendertexs 2020-12-16 01:28:57 -08:00
f42e0d78d6 Don't reuse textures, and don't keep them in RAM
Hopefully fixes #28
2020-12-16 01:11:32 -08:00
227c259e51 Cleanup and migrate to using VS 2020-12-08 16:06:01 -08:00
fc6820d73b make normal map gen less dumb 2020-12-04 17:12:41 -08:00
9dc98a6f9d Fix flag aspect ratio and font instantiation 2020-12-04 17:05:37 -08:00
35fce78616 fix flag decal NRE 2020-12-04 16:16:11 -08:00
84611a26e8 Remove workaround for outdated msbuild 2020-12-02 12:39:17 -08:00
a61a2b81a1 Cleanup refactor 2020-12-02 01:40:46 -08:00
e37cf03f7b Merge branch 'main' into feature-multiProject
# Conflicts:
#	GameData/ConformalDecals/Plugins/ConformalDecals.dll
#	Source/ConformalDecals/ModuleConformalDecal.cs
#	Source/ConformalDecals/ModuleConformalText.cs
2020-11-30 00:46:03 -08:00
1e7c6d81c9 Merge branch 'main' into release 2020-11-29 16:31:55 -08:00
dda988db17 Quick bugfix 2020-11-29 16:31:41 -08:00
900061f7f6 Merge branch 'main' into release 2020-11-29 16:09:03 -08:00
e56278c6cb Fix stock flags being stretched 2020-11-29 16:02:45 -08:00
2793f5fcb1 Fix DLL copying 2020-11-26 19:17:31 -08:00
833ec43a52 Update project files 2020-11-26 17:47:42 -08:00
e6856124e7 Another attempt at fixing the planet glitch 2020-11-25 02:44:04 -08:00
8ed7a130ab Alternate rendertex handling 2020-11-22 20:18:16 -08:00
d8d3d4ed92 Merge branch 'main' into release 2020-11-16 16:49:01 -08:00
334af786f6 Version change 2020-11-16 16:48:45 -08:00
17ef93bb6e Copy other text parameters to symmetry counterparts 2020-11-16 16:45:54 -08:00
e6288942ea Cleanup and fix onVariantApplied 2020-11-09 19:34:34 -08:00
62bdd151e1 Refactor to rearrange initialization code
• Add matrix parsing code
• Add target serialization
• Rearrange initialization to allow loading targets from config soon
2020-10-09 19:30:59 -07:00
f5af8a4d53 Merge branch 'develop' into feature-multiProject 2020-10-09 17:28:00 -07:00
df95601416 Allow toggling multiprojection in editor 2020-10-05 23:30:22 -07:00
596675ad8d Add naive implementation of multi-projection 2020-10-05 22:42:01 -07:00
45 changed files with 1567 additions and 1072 deletions

23
.github/workflows/ff-release.yml vendored Normal file
View File

@ -0,0 +1,23 @@
name: Fast-Forward Release Branch
on:
release:
types: [created]
jobs:
fast-forward:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: release
fetch-depth: 0
- name: Merge into Release
run: git merge ${{github.ref_name}} --ff-only
- name: Push Changes
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: release

139
.github/workflows/ksp-publish.yml vendored Normal file
View File

@ -0,0 +1,139 @@
name: Build and Release
on: [ push ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Setup .NET Core SDK
uses: actions/setup-dotnet@v1.7.2
with:
dotnet-version: '6.0.x'
- name: Install Python Tools
run: |
python -m pip install --upgrade pip
python -m pip install yaclog yaclog-ksp
- name: Generate Version Info
run: |
echo "VERSION_TITLE=$(yaclog show -n)" >> $GITHUB_ENV
python Scripts/version.py
yaclog-ksp -n "Conformal Decals"
- name: Validate Version Info
uses: DasSkelett/AVC-VersionFileValidator@master
- name: Download DLL Dependencies
working-directory: Source
run: |
wget --user drewcassidy --password ${{ secrets.PILE_OF_ROCKS_PASS }} https://pileof.rocks/Secret/conformal-decals-dependencies-1.zip
unzip conformal-decals-dependencies-*.zip -d ConformalDecals/dlls
- name: Build DLL
working-directory: Source
run: |
mkdir -p ../GameData/ConformalDecals/Plugins
dotnet build --configuration Release ConformalDecals.sln
- name: Download KSP Dependencies
run: |
wget http://pileof.rocks/KSP/Shabby_v0.2.0.zip
wget https://ksp.sarbian.com/jenkins/job/ModuleManager/161/artifact/ModuleManager.4.2.1.dll
wget https://github.com/blowfishpro/B9PartSwitch/releases/download/v2.19.0/B9PartSwitch_v2.19.0.zip
wget https://github.com/KSPModdingLibs/HarmonyKSP/releases/download/2.0.4.0/HarmonyKSP_2.0.4.0_for_KSP1.8+.zip
unzip -d Shabby Shabby*.zip
unzip -d B9PartSwitch B9PartSwitch*.zip
unzip -d HarmonyKSP HarmonyKSP*.zip
mv Shabby/GameData/Shabby GameData/
mv ModuleManager*.dll GameData/
mv B9PartSwitch/GameData/B9PartSwitch GameData/
mv HarmonyKSP/GameData/000_Harmony GameData/
- name: Upload Unbundled Build
uses: actions/upload-artifact@v3
with:
name: ConformalDecals-unbundled
path: |
GameData/ConformalDecals
README.md
CHANGELOG.md
LICENSE-ART.md
LICENSE-SOURCE.md
- name: Upload Bundled Build
uses: actions/upload-artifact@v3
with:
name: ConformalDecals
path: |
GameData
README.md
CHANGELOG.md
LICENSE-ART.md
LICENSE-SOURCE.md
deploy:
needs: build
runs-on: ubuntu-latest
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install Python Tools
run: |
python -m pip install --upgrade pip
python -m pip install yaclog
- name: Get version name and body
run: |
echo "VERSION_TITLE=$(yaclog show -n)" >> $GITHUB_ENV
echo "$(yaclog show -mb)" >> RELEASE.md
- name: Download Build Artifacts
uses: actions/download-artifact@v3
- name: Zip Download Packages
run: |
mkdir bundled
mkdir unbundled
zip -r bundled/ConformalDecals-$VERSION_TITLE.zip ConformalDecals/*
zip -r unbundled/ConformalDecals-$VERSION_TITLE.zip ConformalDecals-unbundled/*
ls
- name: Publish to Spacedock
run: |
curl -F "username=drewcassidy" -F "password=${{ secrets.SPACEDOCK_PASS }}" \
-c ./cookies "https://spacedock.info/api/login"
curl -c ./cookies -b ./cookies \
-F "version=$VERSION_TITLE" \
-F "changelog=$(yaclog show -mb)" \
-F "game-version=1.12.3" \
-F "notify-followers=yes" \
-F "zipball=@bundled/ConformalDecals-$VERSION_TITLE.zip" \
"https://spacedock.info/api/mod/2451/update"
- name: Publish to Github
uses: softprops/action-gh-release@v1
with:
files: |
bundled/ConformalDecals-*.zip
ConformalDecals/GameData/ConformalDecals/Versioning/ConformalDecals.version
name: Conformal Decals ${{ env.VERSION_TITLE }}
body_path: RELEASE.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

6
.gitignore vendored
View File

@ -16,6 +16,9 @@ Temp/
GameData/ConformalDecals/Resources/Resources GameData/ConformalDecals/Resources/Resources
GameData/ConformalDecals/Resources/*.manifest GameData/ConformalDecals/Resources/*.manifest
# DLLs
GameData/ConformalDecals/Plugins
# Unity Project Files # Unity Project Files
PartTools.cfg PartTools.cfg
*.meta *.meta
@ -49,4 +52,7 @@ Source/ConformalDecals/bin
.ds_store .ds_store
*.sublime* *.sublime*
.idea .idea
.vs
obj obj
*.swp
@thumbs

View File

@ -16,11 +16,15 @@ dependencies: # Configure dependencies
zip: false zip: false
B9PartSwitch: B9PartSwitch:
location: url location: url
url: http://pileof.rocks/KSP/B9PartSwitch-v2.16.0.zip url: http://pileof.rocks/KSP/B9PartSwitch-v2.18.0.zip
zip: true zip: true
Shabby: Shabby:
location: url location: url
url: http://taniwha.org/~bill/Shabby_v0.1.2.zip url: http://pileof.rocks/KSP/Shabby_v0.2.0.zip
zip: true
HarmonyKSP:
location: url
url: https://github.com/KSPModdingLibs/HarmonyKSP/releases/download/2.0.4.0/HarmonyKSP_2.0.4.0_for_KSP1.8+.zip
zip: true zip: true
deploy: deploy:
SpaceDock: SpaceDock:

View File

@ -1,25 +0,0 @@
language: python
python:
- 3.6
before_install:
- echo -e "machine github.com\n login $GITHUB_OAUTH_TOKEN" > ~/.netrc
install:
- pip install awscli boto3 requests
branches:
only:
- release
script:
- git clone https://github.com/post-kerbin-mining-corporation/build-deploy.git
- cd build-deploy
- git checkout master
- cd ..
- pytest -s --testpath "GameData/" build-deploy/src/tests/ # run the deploy tests
- python build-deploy/src/package.py --f ".mod_data.yml" # Build package
before_deploy:
- python build-deploy/src/stage.py --f ".mod_data.yml" # Run the staging script
deploy:
- provider: script
script: python build-deploy/src/deploy.py --f ".mod_data.yml" # Deploy package to spacedock, curse, github
skip_cleanup: true
on:
branch: release

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

193
CHANGELOG.md Normal file
View File

@ -0,0 +1,193 @@
# Changelog
All notable changes to this project will be documented in this file
| modName | Conformal Decals |
| ------- |:-------------------------------------------------------------------------------------|
| license | CC-By-SA & GPL3 |
| website | https://forum.kerbalspaceprogram.com/index.php?/topic/194802-18-111-conformal-decals |
| author | Andrew Cassidy |
## 0.2.10 - 2022-03-14
### Fixed
- Fixed decals not projecting on loading prefabs
### Changed
- Re-enabled projecting onto TransparentFX layer
### Added
- Allowed for regular expressions to be used when blacklisting shaders
- Added all Waterfall shaders to the shader blacklist when Waterfall is present
## 0.2.9 - 2022-03-12
### Fixed
- Fixed text decals breaking when used in symmetry
- Fixed decals projecting onto the TransparentFX layer, such as Waterfall plumes
## 0.2.8
- Update bundled Shabby to support Harmony 2 for compatibility with other mods
- Update bundled B9PartSwitch to 2.18.0
## 0.2.7
- Supported KSP versions: 1.8.x to 1.11.x
### Notes:
- Attaching decal parts in flight using engineer kerbals is not supported.
### Fixed:
- Fixed certain non-ascii strings not rendering correctly under certain circumstances.
- Yet another attempted fix for the planet text glitch.
## 0.2.6
### Fixed:
- Fixed stock flags appearing stretched by forcing their aspect ratio to be correct.
- Another attempted fix for the planet text glitch.
## 0.2.5
### Fixed:
- Fixed line spacing, character spacing, and vertical settings not applying to symmetry counterparts
## 0.2.4
### Fixed:
- Fixed red text appearing on planets due to KSP bug by clearing render textures afterwards.
- Fixed fonts not saving correctly.
### Changed:
- Lowered step size for decal size and depth to 1cm.
- Changed default max size to 5m.
- Changed default text decal size to 0.2m
- Text decals now show as a circle if they contain only whitespace.
## 0.2.3
### Fixed:
- Fixed TMP subobjects being deleted, causing fallback fonts to fail in some situations.
- Started using URL-style encoding for text decals behind the scenes to prevent issues with certain characters.
- Fixed text decals having zero size when they had only whitespace or an empty string.
- Fixed decals having drag and causing issues when using FAR.
- Fixed broken saving of text decals in certain circumstances.
## 0.2.2
### Fixed:
- Fixed corrupted text rendering when a vessel loads during a scene change.
## 0.2.1
### Changed:
- Pressing enter in the text entry window now types a newline.
### Fixed:
- Renamed font assetbundle. The old extension was causing the game to try to load it twice on Windows due to legacy compatability features.
- Fixed text rendering on DirectX resulting in black boxes by using ARGB32 instead of RG16 for the render texture in DirectX.
## 0.2.0
### New Parts:
- CDL-3 Surface Base Decal: A set of conformal decals based on the symbols from the movie Moon (2009) designed by Gavin Rothery
- CDL-T Custom Text Decal: A customizable text decal with a variety of fonts
### Changed:
- New ModuleConformalText module for customizable text
- Text, font, and style can all be customized, as well as text fill and outline colors and widths
- Same projection and opacity options as other conformal decals
- New StandardText decal shader supporting the text module
- Unified all decal shaders into a single "StandardDecal" shader with variants supporting any combination of bump, specular and emissive maps, plus SDF alphas.
- Old shaders are remapped to Standard shader plus keywords automatically.
- New SDF-based antialiasing for when decals extend to their borders, e.g. on opaque flags.
- New "KEYWORD" material modifier, allowing for shader features to be enabled and disabled.
- Material modifiers can now be removed in variants by setting `remove = true` inside them.
### Fixed:
- Fixed WIDTH and HEIGHT scale modes being flipped
- Removed some debug log statements
- Dependencies:
- Updated ModuleManager to version 4.1.4
## 0.1.4
Supported KSP versions: 1.8.x to 1.10.x
### Fixed:
- Fixed decals rendering onto disabled B9PS part variants
- Decals will still not update whan their parent part's B9PS variant is changed, both in flight and in the editor. This is known and awaiting a change to B9PS to be fixed.
- Fixed decal bounds rendering as dark cubes when shadowed by EVE clouds.
- Fixed decals being shadowed by EVE clouds, causing the part underneath to appear overly dark.
## 0.1.3
### Fixe:
- Fixed decals being able to be scaled down to 0
### Changed:
- Made decal bounds no longer collide in flight, this is done by repurposing layer 31 (which is configurable in the ConformalDecals.cfg file)
- Decals will now be unselectable in flight by default. This can be disabled with the `selectableInFlight` value in ConformalDecals.cfg, or in the module config itself.
- Decal parts will now destroy themselves automatically when the parent part is destroyed
- Small refactor of node parsing code
- Colors can now be specified in hex (#RGB, #RGBA, #RRGGBB, or #RRGGBBAA) or using the colors specified in the XKCDColors class
## 0.1.2
### Fixed:
- Disabled writing to the zbuffer in the decal bounds shader. Should fix any issues with Scatterer or EVE.
## 0.1.1
### Fixed:
- Fixed flag decal not adjusting to new texture sizes immediately.
- Fixed decal bounds being visible on launch.
- Fixed decal bounds being visible in the part icon.
## 0.1.0
Initial release!
### New Parts:
- CDL-F Flag Decal: Conformal flag decal, which uses either the mission flag or a flag of your choosing.
- CDL-1 Generic Decal: A set of conformal generic decals for planes and rockets
- CDL-2 Semiotic Standard Decal: A set of conformal decals based on the Semiotic Standard for All Commercial Trans-Stellar Utility Lifter and Transport Spacecraft designed by Ron Cobb for the movie Alien

View File

@ -15,7 +15,7 @@ Localization
#LOC_ConformalDecals_gui-opacity = Opacity #LOC_ConformalDecals_gui-opacity = Opacity
#LOC_ConformalDecals_gui-cutoff = Cutoff #LOC_ConformalDecals_gui-cutoff = Cutoff
#LOC_ConformalDecals_gui-wear = Edge Wear #LOC_ConformalDecals_gui-wear = Edge Wear
#LOC_ConformalDecals_gui-aspectratio = Aspect Ratio #LOC_ConformalDecals_gui-multiproject = Project onto Multiple
#LOC_ConformalDecals_gui-select-flag = Select Flag #LOC_ConformalDecals_gui-select-flag = Select Flag
#LOC_ConformalDecals_gui-reset-flag = Reset Flag #LOC_ConformalDecals_gui-reset-flag = Reset Flag
#LOC_ConformalDecals_gui-set-text = Set Text #LOC_ConformalDecals_gui-set-text = Set Text

View File

@ -98,7 +98,7 @@ PART
MODULE { MODULE {
IDENTIFIER { name = ModuleConformalDecal } IDENTIFIER { name = ModuleConformalDecal }
DATA { DATA {
shader = ConformalDecals/Paint/DiffuseSDF KEYWORD { name = DECAL_SDF_ALPHA }
tile = 0, 2, 128, 112 tile = 0, 2, 128, 112
} }
} }

View File

@ -0,0 +1,7 @@
// Prevent projection onto Waterfall plumes
CONFORMALDECALS:NEEDS[Waterfall] {
SHADERBLACKLIST {
shaderRegex = Waterfall/.*
}
}

View File

@ -4,21 +4,13 @@ CONFORMALDECALS {
SHADERBLACKLIST { SHADERBLACKLIST {
shader = DepthMask shader = DepthMask
shader = KSP/Alpha/Cutoff
shader = KSP/Alpha/Cutoff Bumped
shader = KSP/Alpha/Translucent
shader = KSP/Alpha/Translucent Additive
shader = KSP/Alpha/Translucent Specular
shader = KSP/Alpha/Unlit Transparent
shader = KSP/Bumped Specular (Transparent) shader = KSP/Bumped Specular (Transparent)
shader = KSP/FX/ScrollingUnlit shader = KSP/FX/ScrollingUnlit
shader = KSP/Particles/Additive
shader = KSP/Particles/Additive (Soft)
shader = KSP/Particles/Alpha Blended
shader = KSP/Particles/Alpha Blended Emissive Cutout
shader = KSP/Specular (Cutoff) shader = KSP/Specular (Cutoff)
shader = KSP/Specular (Transparent) shader = KSP/Specular (Transparent)
shader = Solid Color (Alpha) shader = Solid Color (Alpha)
shaderRegex = KSP/Alpha/.*
shaderRegex = KSP/Particles/.*
} }
FONT { FONT {

View File

@ -1,28 +1,24 @@
{ {
"NAME":"ConformalDecals", "NAME": "ConformalDecals",
"URL":"https://raw.githubusercontent.com/drewcassidy/KSP-Conformal-Decals/release/GameData/ConformalDecals/Versioning/ConformalDecals.version", "URL": "https://github.com/drewcassidy/KSP-Conformal-Decals/releases/latest/download/ConformalDecals.version",
"DOWNLOAD":"https://github.com/drewcassidy/KSP-Conformal-Decals/releases", "DOWNLOAD": "https://github.com/drewcassidy/KSP-Conformal-Decals/releases/latest",
"VERSION": "CHANGE_LOG_URL": "https://raw.githubusercontent.com/drewcassidy/KSP-Conformal-Decals/release/CHANGELOG.md",
{ "VERSION": {
"MAJOR":0, "MAJOR": 9,
"MINOR":2, "MINOR": 9,
"PATCH":4, "PATCH": 9,
"BUILD":0 "BUILD": 100000
}, },
"KSP_VERSION": "KSP_VERSION": {
{ "MAJOR": 1,
"MAJOR":1, "MINOR": 12
"MINOR":10,
"PATCH":1
}, },
"KSP_VERSION_MIN":{ "KSP_VERSION_MIN": {
"MAJOR":1, "MAJOR": 1,
"MINOR":8, "MINOR": 8
"PATCH":0
}, },
"KSP_VERSION_MAX":{ "KSP_VERSION_MAX": {
"MAJOR":1, "MAJOR": 1,
"MINOR":10, "MINOR": 12
"PATCH":99
} }
} }

View File

@ -1,5 +1,5 @@
# Conformal Decals v0.2.4 # Conformal Decals
[![Build Status](https://travis-ci.org/drewcassidy/KSP-Conformal-Decals.svg?branch=release)](https://travis-ci.org/drewcassidy/KSP-Conformal-Decals) [![Art: CC BY-SA 4.0](https://img.shields.io/badge/Art%20License-CC%20BY--SA%204.0-orange.svg)](https://creativecommons.org/licenses/by-sa/4.0/) [![Code: GPL v3](https://img.shields.io/badge/Code%20License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Art: CC BY-SA 4.0](https://img.shields.io/badge/Art%20License-CC%20BY--SA%204.0-orange.svg)](https://creativecommons.org/licenses/by-sa/4.0/) [![Code: GPL v3](https://img.shields.io/badge/Code%20License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
![Screenshot](http://pileof.rocks/KSP/images/ConformalDecalsHeader.png) ![Screenshot](http://pileof.rocks/KSP/images/ConformalDecalsHeader.png)
@ -9,9 +9,10 @@ Conformal Decals adds a set of decal stickers to KSP, as well as providing a fra
Required: Required:
- KSP (1.8.x to 1.10.x) - KSP (1.8.x to 1.10.x)
- B9 Part Switch (2.16.0). Bundled with release. - B9 Part Switch (2.18.0). Bundled with release.
- ModuleManager (4.1.4). Bundled with release. - ModuleManager (4.1.4). Bundled with release.
- Shabby (0.1.2). Bundled with release. - Shabby (0.2.0 unofficial build). Bundled with release.
- HarmonyKSP (2.0.4.0). Bundled with release.
Optional: Optional:
- Wild Blue Tools. For custom decals category in the VAB and SPH. - Wild Blue Tools. For custom decals category in the VAB and SPH.

61
Scripts/version.py Normal file
View File

@ -0,0 +1,61 @@
import yaclog
import yaclog.version
import git as gp
import os
import xml.dom.minidom as minidom
import json
def run():
repo = gp.Repo(os.curdir)
cl = yaclog.Changelog('CHANGELOG.md')
version = str(cl.current_version(released=True).version)
release = False
for tag in repo.tags:
if tag.commit == repo.head.commit:
release = True
build = 100000
break
if not release:
build = int.from_bytes(repo.head.commit.binsha[0:2], byteorder='big')
version = yaclog.version.increment_version(version, 2)
print(f'Setting up version {version} build {build}')
version_path = 'GameData/ConformalDecals/Versioning/ConformalDecals.version'
with open(version_path, 'r+') as version_file:
print('Updating version file')
segments = version.split('.')
# print(version_file.read())
decoded = json.load(version_file)
decoded['VERSION']['MAJOR'] = int(segments[0])
decoded['VERSION']['MINOR'] = int(segments[1])
decoded['VERSION']['PATCH'] = int(segments[2])
decoded['VERSION']['BUILD'] = build
version_file.seek(0)
json.dump(decoded, version_file, indent=4)
version_file.truncate()
project_path = 'Source/ConformalDecals/ConformalDecals.csproj'
with open(project_path, 'r+') as project_file:
print('Updating csproj file')
segments = version.split('.')
decoded = minidom.parse(project_file)
version_node = decoded.getElementsByTagName('AssemblyVersion')[0]
if release:
version_node.firstChild.nodeValue = f'{version}'
else:
version_node.firstChild.nodeValue = f'{version}.{build}'
# version_node.value = f'{version}.{build}'
project_file.seek(0)
decoded.writexml(project_file)
project_file.truncate()
print('Done!')
if __name__ == '__main__':
run()

View File

@ -1,6 +1,12 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConformalDecals", "ConformalDecals/ConformalDecals.csproj", "{1ea983f9-42e5-494e-9683-fdac9c9121f4}" # Visual Studio Version 16
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConformalDecals", "ConformalDecals\ConformalDecals.csproj", "{1EA983F9-42E5-494E-9683-FDAC9C9121F4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C2564402-B081-479B-B723-D5C065BC884E}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -8,9 +14,33 @@ Global
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1ea983f9-42e5-494e-9683-fdac9c9121f4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1EA983F9-42E5-494E-9683-FDAC9C9121F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1ea983f9-42e5-494e-9683-fdac9c9121f4}.Debug|Any CPU.Build.0 = Debug|Any CPU {1EA983F9-42E5-494E-9683-FDAC9C9121F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1ea983f9-42e5-494e-9683-fdac9c9121f4}.Release|Any CPU.ActiveCfg = Release|Any CPU {1EA983F9-42E5-494E-9683-FDAC9C9121F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1ea983f9-42e5-494e-9683-fdac9c9121f4}.Release|Any CPU.Build.0 = Release|Any CPU {1EA983F9-42E5-494E-9683-FDAC9C9121F4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
Policies = $0
$0.DotNetNamingPolicy = $1
$1.DirectoryNamespaceAssociation = PrefixedHierarchical
$0.TextStylePolicy = $2
$2.FileWidth = 80
$2.TabsToSpaces = True
$2.scope = text/x-csharp
$2.NoTabsAfterNonTabs = True
$2.EolMarker = Unix
$0.CSharpFormattingPolicy = $3
$3.NewLinesForBracesInTypes = False
$3.NewLinesForBracesInMethods = False
$3.NewLinesForBracesInProperties = False
$3.NewLinesForBracesInAccessors = False
$3.NewLinesForBracesInAnonymousMethods = False
$3.NewLinesForBracesInControlBlocks = False
$3.NewLinesForBracesInAnonymousTypes = False
$3.NewLinesForBracesInObjectCollectionArrayInitializers = False
$3.NewLinesForBracesInLambdaExpressionBody = False
$3.scope = text/x-csharp
$3.SpaceAfterCast = True
$0.StandardHeader = $4
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View File

@ -1,115 +1,58 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?><Project Sdk="Microsoft.NET.Sdk">
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)/$(MSBuildToolsVersion)/Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)/$(MSBuildToolsVersion)/Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <TargetFramework>net48</TargetFramework>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{1ea983f9-42e5-494e-9683-fdac9c9121f4}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<LangVersion>8</LangVersion> <LangVersion>8</LangVersion>
<RootNamespace>ConformalDecals</RootNamespace> <IsPackable>false</IsPackable>
</PropertyGroup> <PlatformTarget>x64</PlatformTarget>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <NoWarn>1701;1702;CS0649;CS1591</NoWarn>
<DebugSymbols>true</DebugSymbols> <AssemblyVersion>9.9.9</AssemblyVersion>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin/Debug/</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin/Release/</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin/Release/ConformalDecals.xml</DocumentationFile>
<NoWarn>CS1591,CS0649</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"> <Reference Include="Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>dlls/Assembly-CSharp.dll</HintPath> <HintPath>dlls\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="KSPAssets, Version=1.4.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>dlls\KSPAssets.dll</HintPath>
</Reference> </Reference>
<Reference Include="Shabby, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"> <Reference Include="Shabby, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>dlls/Shabby.dll</HintPath> <HintPath>dlls\Shabby.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" />
<Reference Include="UnityEngine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"> <Reference Include="UnityEngine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>dlls/UnityEngine.dll</HintPath> <HintPath>dlls\UnityEngine.dll</HintPath>
</Reference> </Reference>
<Reference Include="UnityEngine.AssetBundleModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"> <Reference Include="UnityEngine.AssetBundleModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>dlls/UnityEngine.AssetBundleModule.dll</HintPath> <HintPath>dlls\UnityEngine.AssetBundleModule.dll</HintPath>
</Reference> </Reference>
<Reference Include="UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"> <Reference Include="UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>dlls/UnityEngine.CoreModule.dll</HintPath> <HintPath>dlls\UnityEngine.CoreModule.dll</HintPath>
</Reference> </Reference>
<Reference Include="UnityEngine.PhysicsModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"> <Reference Include="UnityEngine.PhysicsModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>dlls/UnityEngine.PhysicsModule.dll</HintPath> <HintPath>dlls\UnityEngine.PhysicsModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.TextCoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>dlls/UnityEngine.TextCoreModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.TextRenderingModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>dlls/UnityEngine.TextRenderingModule.dll</HintPath>
</Reference> </Reference>
<Reference Include="UnityEngine.UI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"> <Reference Include="UnityEngine.UI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>dlls/UnityEngine.UI.dll</HintPath> <HintPath>dlls\UnityEngine.UI.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UIElementsModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>dlls/UnityEngine.UIElementsModule.dll</HintPath>
</Reference> </Reference>
<Reference Include="UnityEngine.UIModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"> <Reference Include="UnityEngine.UIModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>dlls/UnityEngine.UIModule.dll</HintPath> <HintPath>dlls\UnityEngine.UIModule.dll</HintPath>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="DecalConfig.cs" /> <Compile Remove="dlls\**" />
<Compile Include="DecalIconFixer.cs" />
<Compile Include="DecalPropertyIDs.cs" />
<Compile Include="MaterialProperties/MaterialColorProperty.cs" />
<Compile Include="MaterialProperties/MaterialFloatProperty.cs" />
<Compile Include="MaterialProperties/MaterialKeywordProperty.cs" />
<Compile Include="MaterialProperties/MaterialProperty.cs" />
<Compile Include="MaterialProperties/MaterialPropertyCollection.cs" />
<Compile Include="MaterialProperties/MaterialTextureProperty.cs" />
<Compile Include="ModuleConformalFlag.cs" />
<Compile Include="ModuleConformalText.cs" />
<Compile Include="ProjectionTarget.cs" />
<Compile Include="ModuleConformalDecal.cs" />
<Compile Include="Properties/AssemblyInfo.cs" />
<Compile Include="Text/DecalFont.cs" />
<Compile Include="Text/FontLoader.cs" />
<Compile Include="Text/TextRenderer.cs" />
<Compile Include="Text/DecalText.cs" />
<Compile Include="Text\TextRenderOutput.cs" />
<Compile Include="Text\TextRenderJob.cs" />
<Compile Include="UI/ColorPickerController.cs" />
<Compile Include="UI/FontMenuController.cs" />
<Compile Include="UI/FontMenuItem.cs" />
<Compile Include="UI/TextEntryController.cs" />
<Compile Include="UI/UILoader.cs" />
<Compile Include="UI/UITag.cs" />
<Compile Include="UI\ColorBoxSlider.cs" />
<Compile Include="UI\ColorChannelSlider.cs" />
<Compile Include="Util/Logging.cs" />
<Compile Include="Util/OrientedBounds.cs" />
<Compile Include="Util/ParseUtil.cs" />
<Compile Include="UI/BoxSlider.cs" />
<Compile Include="Util\ColorHSL.cs" />
<Compile Include="Util\ColorHSV.cs" />
<Compile Include="Util\ColorUtil.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)/Microsoft.CSharp.targets" />
<PropertyGroup> <ItemGroup>
<PostBuildEvent>sh -e -c "cp -v '$(TargetPath)' '$(SolutionDir)/../GameData/ConformalDecals/Plugins'"</PostBuildEvent> <EmbeddedResource Remove="dlls\**" />
</PropertyGroup> </ItemGroup>
<ItemGroup>
<None Remove="dlls\**" />
</ItemGroup>
<ItemGroup>
<None Include="..\.editorconfig" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="/bin/cp -v '$(OutDir)ConformalDecals.dll' '$(SolutionDir)../GameData/ConformalDecals/Plugins/ConformalDecals.dll'" IgnoreExitCode="true" />
<!--Fuck you MSBuild stop trying to run CMD.exe on macOS-->
</Target>
</Project> </Project>

View File

@ -1,15 +1,16 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.RegularExpressions;
using ConformalDecals.Text; using ConformalDecals.Text;
using ConformalDecals.Util; using ConformalDecals.Util;
using TMPro; using TMPro;
using UniLinq;
using UnityEngine; using UnityEngine;
namespace ConformalDecals { namespace ConformalDecals {
public static class DecalConfig { public static class DecalConfig {
private static Texture2D _blankNormal; private static Texture2D _blankNormal;
private static List<string> _shaderBlacklist; private static List<string> _shaderBlacklist;
private static List<Regex> _shaderRegexBlacklist;
private static Dictionary<string, DecalFont> _fontList; private static Dictionary<string, DecalFont> _fontList;
private static int _decalLayer = 31; private static int _decalLayer = 31;
private static bool _selectableInFlight; private static bool _selectableInFlight;
@ -50,15 +51,23 @@ namespace ConformalDecals {
public static bool SelectableInFlight => _selectableInFlight; public static bool SelectableInFlight => _selectableInFlight;
public static IEnumerable<DecalFont> Fonts => _fontList.Values; public static IEnumerable<DecalFont> Fonts => _fontList.Values;
public static DecalFont FallbackFont { get; private set; }
public static bool IsBlacklisted(Shader shader) { public static bool IsBlacklisted(Shader shader) {
return IsBlacklisted(shader.name); return IsBlacklisted(shader.name);
} }
public static bool IsBlacklisted(string shaderName) { public static bool IsBlacklisted(string shaderName) {
return _shaderBlacklist.Contains(shaderName); if (_shaderBlacklist.Contains(shaderName)) return true;
foreach (var regex in _shaderRegexBlacklist) {
if (regex.IsMatch(shaderName)) {
_shaderBlacklist.Add(shaderName);
Logging.Log($"Caching blacklisted shader name '{shaderName}' which matches '{regex}'");
return true;
}
}
return false;
} }
public static bool IsLegacy(string shaderName, out string newShader, out string[] keywords) { public static bool IsLegacy(string shaderName, out string newShader, out string[] keywords) {
@ -91,13 +100,17 @@ namespace ConformalDecals {
foreach (var shaderName in blacklist.GetValuesList("shader")) { foreach (var shaderName in blacklist.GetValuesList("shader")) {
_shaderBlacklist.Add(shaderName); _shaderBlacklist.Add(shaderName);
} }
foreach (var shaderRegex in blacklist.GetValuesList("shaderRegex")) {
_shaderRegexBlacklist.Add(new Regex(shaderRegex));
}
} }
var allFonts = Resources.FindObjectsOfTypeAll<TMP_FontAsset>(); var allFonts = Resources.FindObjectsOfTypeAll<TMP_FontAsset>();
foreach (var fontNode in node.GetNodes("FONT")) { foreach (var fontNode in node.GetNodes("FONT")) {
try { try {
var font = new DecalFont(fontNode, allFonts); var font = DecalFont.Parse(fontNode, allFonts);
_fontList.Add(font.Name, font); _fontList.Add(font.Name, font);
} }
catch (Exception e) { catch (Exception e) {
@ -114,12 +127,8 @@ namespace ConformalDecals {
var colors = new[] {color, color, color, color}; var colors = new[] {color, color, color, color};
var tex = new Texture2D(width, height, TextureFormat.RGBA32, false); var tex = new Texture2D(width, height, TextureFormat.RGBA32, false);
for (var x = 0; x <= width; x++) {
for (var y = 0; y < height; y++) {
tex.SetPixels32(colors);
}
}
tex.SetPixels32(colors);
tex.Apply(); tex.Apply();
return tex; return tex;
@ -128,13 +137,14 @@ namespace ConformalDecals {
// ReSharper disable once UnusedMember.Global // ReSharper disable once UnusedMember.Global
public static void ModuleManagerPostLoad() { public static void ModuleManagerPostLoad() {
_shaderBlacklist = new List<string>(); _shaderBlacklist = new List<string>();
_shaderRegexBlacklist = new List<Regex>();
_fontList = new Dictionary<string, DecalFont>(); _fontList = new Dictionary<string, DecalFont>();
var configs = GameDatabase.Instance.GetConfigs("CONFORMALDECALS"); var configs = GameDatabase.Instance.GetConfigs("CONFORMALDECALS");
if (configs.Length > 0) { if (configs.Length > 0) {
Logging.Log("loading config");
foreach (var config in configs) { foreach (var config in configs) {
Logging.Log($"loading config node '{config.url}'");
ParseConfig(config.config); ParseConfig(config.config);
} }
} }
@ -150,4 +160,4 @@ namespace ConformalDecals {
_blankNormal = MakeBlankNormal(); _blankNormal = MakeBlankNormal();
} }
} }
} }

View File

@ -8,7 +8,7 @@ namespace ConformalDecals.MaterialProperties {
public override void ParseNode(ConfigNode node) { public override void ParseNode(ConfigNode node) {
base.ParseNode(node); base.ParseNode(node);
ParseUtil.ParseBoolIndirect(ref value, node, "value"); value = ParseUtil.ParseBool(node, "value", true, true);
} }
public override void Modify(Material material) { public override void Modify(Material material) {

View File

@ -95,7 +95,7 @@ namespace ConformalDecals.MaterialProperties {
var property = MaterialProperty.Instantiate(_serializedProperties[i]); var property = MaterialProperty.Instantiate(_serializedProperties[i]);
_materialProperties.Add(_serializedNames[i], property); _materialProperties.Add(_serializedNames[i], property);
if (property is MaterialTextureProperty textureProperty && textureProperty.isMain) { if (property is MaterialTextureProperty {isMain: true} textureProperty) {
_mainTexture = textureProperty; _mainTexture = textureProperty;
} }
} }
@ -105,6 +105,28 @@ namespace ConformalDecals.MaterialProperties {
_materialProperties ??= new Dictionary<string, MaterialProperty>(); _materialProperties ??= new Dictionary<string, MaterialProperty>();
} }
public void Load(ConfigNode node) {
// add keyword nodes
foreach (var keywordNode in node.GetNodes("KEYWORD")) {
ParseProperty<MaterialKeywordProperty>(keywordNode);
}
// add texture nodes
foreach (var textureNode in node.GetNodes("TEXTURE")) {
ParseProperty<MaterialTextureProperty>(textureNode);
}
// add float nodes
foreach (var floatNode in node.GetNodes("FLOAT")) {
ParseProperty<MaterialTextureProperty>(floatNode);
}
// add color nodes
foreach (var colorNode in node.GetNodes("COLOR")) {
ParseProperty<MaterialColorProperty>(colorNode);
}
}
public void OnDestroy() { public void OnDestroy() {
if (_decalMaterial != null) Destroy(_decalMaterial); if (_decalMaterial != null) Destroy(_decalMaterial);
if (_previewMaterial != null) Destroy(_previewMaterial); if (_previewMaterial != null) Destroy(_previewMaterial);
@ -192,7 +214,7 @@ namespace ConformalDecals.MaterialProperties {
var newProperty = AddOrGetProperty<T>(propertyName); var newProperty = AddOrGetProperty<T>(propertyName);
newProperty.ParseNode(node); newProperty.ParseNode(node);
if (newProperty is MaterialTextureProperty textureProperty && textureProperty.isMain) { if (newProperty is MaterialTextureProperty {isMain: true} textureProperty) {
_mainTexture = textureProperty; _mainTexture = textureProperty;
} }
@ -230,7 +252,7 @@ namespace ConformalDecals.MaterialProperties {
public void UpdateScale(Vector2 scale) { public void UpdateScale(Vector2 scale) {
foreach (var entry in _materialProperties) { foreach (var entry in _materialProperties) {
if (entry.Value is MaterialTextureProperty textureProperty && textureProperty.autoScale) { if (entry.Value is MaterialTextureProperty {autoScale: true} textureProperty) {
textureProperty.SetScale(scale); textureProperty.SetScale(scale);
} }
} }
@ -241,7 +263,7 @@ namespace ConformalDecals.MaterialProperties {
var mainTexSize = _mainTexture.Dimensions; var mainTexSize = _mainTexture.Dimensions;
foreach (var entry in _materialProperties) { foreach (var entry in _materialProperties) {
if (entry.Value is MaterialTextureProperty textureProperty && textureProperty.autoTile) { if (entry.Value is MaterialTextureProperty {autoTile: true} textureProperty) {
textureProperty.SetTile(tile, mainTexSize); textureProperty.SetTile(tile, mainTexSize);
} }
} }
@ -258,20 +280,6 @@ namespace ConformalDecals.MaterialProperties {
UpdateTile(tile); UpdateTile(tile);
} }
public void SetOpacity(float opacity) {
DecalMaterial.SetFloat(DecalPropertyIDs._DecalOpacity, opacity);
PreviewMaterial.SetFloat(DecalPropertyIDs._DecalOpacity, opacity);
}
public void SetCutoff(float cutoff) {
DecalMaterial.SetFloat(DecalPropertyIDs._Cutoff, cutoff);
PreviewMaterial.SetFloat(DecalPropertyIDs._Cutoff, cutoff);
}
public void SetWear(float wear) {
DecalMaterial.SetFloat(DecalPropertyIDs._EdgeWearStrength, wear);
}
public void UpdateMaterials() { public void UpdateMaterials() {
foreach (var material in Materials) { foreach (var material in Materials) {
UpdateMaterial(material); UpdateMaterial(material);

View File

@ -27,6 +27,7 @@ namespace ConformalDecals.MaterialProperties {
public string TextureUrl { public string TextureUrl {
get => _textureUrl; get => _textureUrl;
set { set {
if (_textureUrl == value) return; // URL hasnt changed
_texture = LoadTexture(value, isNormal); _texture = LoadTexture(value, isNormal);
_textureUrl = value; _textureUrl = value;
} }
@ -41,7 +42,13 @@ namespace ConformalDecals.MaterialProperties {
public Vector2 Dimensions => new Vector2(_texture.width, _texture.height); public Vector2 Dimensions => new Vector2(_texture.width, _texture.height);
public Vector2 MaskedDimensions => _hasTile ? _tileRect.size : Dimensions; public Vector2 MaskedDimensions => _hasTile ? _tileRect.size : Dimensions;
public float AspectRatio => MaskedHeight / (float) MaskedWidth; public float AspectRatio {
get {
if (_texture == null) return 1;
if (_textureUrl?.Contains("Squad/Flags") == true) return 0.625f; // squad flags are slightly stretched, so unstretch them
return MaskedHeight / (float) MaskedWidth;
}
}
public override void ParseNode(ConfigNode node) { public override void ParseNode(ConfigNode node) {
base.ParseNode(node); base.ParseNode(node);

View File

@ -2,7 +2,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using ConformalDecals.MaterialProperties; using ConformalDecals.MaterialProperties;
using ConformalDecals.Targets;
using ConformalDecals.Util; using ConformalDecals.Util;
using UniLinq;
using UnityEngine; using UnityEngine;
namespace ConformalDecals { namespace ConformalDecals {
@ -71,15 +73,23 @@ namespace ConformalDecals {
UI_FloatRange()] UI_FloatRange()]
public float opacity = 1.0f; public float opacity = 1.0f;
private MaterialFloatProperty _opacityProperty;
[KSPField(guiName = "#LOC_ConformalDecals_gui-cutoff", guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "P0"), [KSPField(guiName = "#LOC_ConformalDecals_gui-cutoff", guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "P0"),
UI_FloatRange()] UI_FloatRange()]
public float cutoff = 0.5f; public float cutoff = 0.5f;
private MaterialFloatProperty _cutoffProperty;
[KSPField(guiName = "#LOC_ConformalDecals_gui-wear", guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "F0"), [KSPField(guiName = "#LOC_ConformalDecals_gui-wear", guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "F0"),
UI_FloatRange()] UI_FloatRange()]
public float wear = 100; public float wear = 100;
[KSPField(isPersistant = true)] public bool projectMultiple; // reserved for future features. do not modify private MaterialFloatProperty _wearProperty;
[KSPField(guiName = "#LOC_ConformalDecals_gui-multiproject", guiActive = false, guiActiveEditor = true, isPersistant = true),
UI_Toggle()]
public bool projectMultiple = true;
[KSPField] public MaterialPropertyCollection materialProperties; [KSPField] public MaterialPropertyCollection materialProperties;
@ -96,7 +106,7 @@ namespace ConformalDecals {
private const int DecalQueueMax = 2400; private const int DecalQueueMax = 2400;
private static int _decalQueueCounter = -1; private static int _decalQueueCounter = -1;
private List<ProjectionTarget> _targets; private readonly Dictionary<Part, ProjectionPartTarget> _targets = new Dictionary<Part, ProjectionPartTarget>();
private bool _isAttached; private bool _isAttached;
private Matrix4x4 _orthoMatrix; private Matrix4x4 _orthoMatrix;
@ -116,22 +126,255 @@ namespace ConformalDecals {
} }
} }
// EVENTS
/// <inheritdoc /> /// <inheritdoc />
public override void OnAwake() { public override void OnAwake() {
base.OnAwake(); base.OnAwake();
if (materialProperties == null) { if (materialProperties == null) {
materialProperties = ScriptableObject.CreateInstance<MaterialPropertyCollection>(); materialProperties = ScriptableObject.CreateInstance<MaterialPropertyCollection>();
} } else {
else {
materialProperties = ScriptableObject.Instantiate(materialProperties); materialProperties = ScriptableObject.Instantiate(materialProperties);
} }
_opacityProperty = materialProperties.AddOrGetProperty<MaterialFloatProperty>("_DecalOpacity");
_cutoffProperty = materialProperties.AddOrGetProperty<MaterialFloatProperty>("_Cutoff");
_wearProperty = materialProperties.AddOrGetProperty<MaterialFloatProperty>("_EdgeWearStrength");
} }
/// <inheritdoc /> /// <inheritdoc />
public override void OnLoad(ConfigNode node) { public override void OnLoad(ConfigNode node) {
// Load
try { try {
// SETUP TRANSFORMS LoadDecal(node);
} catch (Exception e) {
this.LogException("Error loading decal", e);
}
// Setup
try {
SetupDecal();
} catch (Exception e) {
this.LogException("Error setting up decal", e);
}
}
/// <inheritdoc />
public override void OnIconCreate() {
UpdateAll();
}
/// <inheritdoc />
public override void OnStart(StartState state) {
materialProperties.RenderQueue = DecalQueue;
_boundsRenderer = decalProjectorTransform.GetComponent<MeshRenderer>();
// handle tweakables
if (HighLogic.LoadedSceneIsEditor) {
GameEvents.onEditorPartEvent.Add(OnEditorEvent);
GameEvents.onVariantApplied.Add(OnVariantApplied);
UpdateTweakables();
}
}
/// Called after OnStart is finished for all parts
/// This is mostly used to make sure all B9 variants are already in place for the rest of the vessel
public override void OnStartFinished(StartState state) {
// handle game events
if (HighLogic.LoadedSceneIsGame) {
// set initial attachment state
if (part.parent == null) {
OnDetach();
} else {
OnAttach();
}
}
// handle flight events
if (HighLogic.LoadedSceneIsFlight) {
GameEvents.onPartWillDie.Add(OnPartWillDie);
if (part.parent == null) part.explode();
Part.layerMask |= 1 << DecalConfig.DecalLayer;
decalColliderTransform.gameObject.layer = DecalConfig.DecalLayer;
if (!selectableInFlight || !DecalConfig.SelectableInFlight) {
decalColliderTransform.GetComponent<Collider>().enabled = false;
_boundsRenderer.enabled = false;
}
}
}
/// Called by Unity when the decal is destroyed
public virtual void OnDestroy() {
// remove GameEvents
if (HighLogic.LoadedSceneIsEditor) {
GameEvents.onEditorPartEvent.Remove(OnEditorEvent);
GameEvents.onVariantApplied.Remove(OnVariantApplied);
}
if (HighLogic.LoadedSceneIsFlight) {
GameEvents.onPartWillDie.Remove(OnPartWillDie);
}
// remove from preCull delegate
Camera.onPreCull -= Render;
// destroy material properties object
Destroy(materialProperties);
}
/// Called when the decal's projection and scale is modified through a tweakable
protected void OnProjectionTweakEvent(BaseField field, object obj) {
// scale or depth values have been changed, so update scale
// and update projection matrices if attached
UpdateProjection();
foreach (var counterpart in part.symmetryCounterparts) {
var decal = counterpart.GetComponent<ModuleConformalDecal>();
decal.UpdateProjection();
}
}
/// Called when the decal's material is modified through a tweakable
protected void OnMaterialTweakEvent(BaseField field, object obj) {
UpdateMaterials();
foreach (var counterpart in part.symmetryCounterparts) {
var decal = counterpart.GetComponent<ModuleConformalDecal>();
decal.UpdateMaterials();
}
}
/// Called by KSP when a new variant is applied in the editor
protected void OnVariantApplied(Part eventPart, PartVariant variant) {
if (_isAttached && eventPart != null && (projectMultiple || eventPart == part.parent)) {
_targets.Remove(eventPart);
UpdateProjection();
}
}
/// Called by KSP when an editor event occurs
protected void OnEditorEvent(ConstructionEventType eventType, Part eventPart) {
switch (eventType) {
case ConstructionEventType.PartAttached:
OnPartAttached(eventPart);
break;
case ConstructionEventType.PartDetached:
OnPartDetached(eventPart);
break;
case ConstructionEventType.PartOffsetting:
case ConstructionEventType.PartRotating:
OnPartTransformed(eventPart);
break;
}
}
/// Called when a part is transformed in the editor
protected void OnPartTransformed(Part eventPart, bool firstcall = true) {
if (part == eventPart || (firstcall && part.symmetryCounterparts.Contains(eventPart))) {
// if this is the top-level call (original event part is a decal) then update symmetry counterparts, otherwise just update this
UpdateProjection();
} else if (_isAttached) {
UpdatePartTarget(eventPart, _boundsRenderer.bounds);
// recursively call for child parts
foreach (var child in eventPart.children) {
OnPartTransformed(child, false);
}
}
}
/// Called when a part is attached in the editor
protected void OnPartAttached(Part eventPart, bool firstcall = true) {
if (part == eventPart || (firstcall && part.symmetryCounterparts.Contains(eventPart))) {
// if this is the top-level call (original event part is a decal) then update symmetry counterparts, otherwise just update this
OnAttach();
} else {
UpdatePartTarget(eventPart, _boundsRenderer.bounds);
// recursively call for child parts
foreach (var child in eventPart.children) {
OnPartAttached(child, false);
}
}
}
/// Called when a part is detached in the editor
protected void OnPartDetached(Part eventPart, bool firstcall = true) {
if (part == eventPart || (firstcall && part.symmetryCounterparts.Contains(eventPart))) {
// if this is the top-level call (original event part is a decal) then update symmetry counterparts, otherwise just update this
OnDetach();
} else if (_isAttached) {
_targets.Remove(eventPart);
// recursively call for child parts
foreach (var child in eventPart.children) {
OnPartDetached(child, false);
}
}
}
/// Called when part `willDie` will be destroyed
protected void OnPartWillDie(Part willDie) {
if (willDie == part.parent && willDie != null) {
this.Log("Parent part about to be destroyed! Killing decal part.");
part.Die();
} else if (_isAttached && projectMultiple) {
_targets.Remove(willDie);
}
}
/// Called when decal is attached to a new part
protected virtual void OnAttach() {
if (_isAttached) return;
if (part.parent == null) {
this.LogError("Attach function called but part has no parent!");
_isAttached = false;
return;
}
_isAttached = true;
_targets.Clear();
// hide model
decalModelTransform.gameObject.SetActive(false);
// unhide projector
decalProjectorTransform.gameObject.SetActive(true);
// add to preCull delegate
Camera.onPreCull += Render;
UpdateMaterials();
UpdateProjection();
}
/// Called when decal is detached from its parent part
protected virtual void OnDetach() {
if (!_isAttached) return;
_isAttached = false;
// unhide model
decalModelTransform.gameObject.SetActive(true);
// hide projector
decalProjectorTransform.gameObject.SetActive(false);
// remove from preCull delegate
Camera.onPreCull -= Render;
UpdateMaterials();
UpdateProjection();
}
// FUNCTIONS
/// Load any settings from the decal config
protected virtual void LoadDecal(ConfigNode node) {
// PARSE TRANSFORMS
if (!HighLogic.LoadedSceneIsGame) {
decalFrontTransform = part.FindModelTransform(decalFront); decalFrontTransform = part.FindModelTransform(decalFront);
if (decalFrontTransform == null) throw new FormatException($"Could not find decalFront transform: '{decalFront}'."); if (decalFrontTransform == null) throw new FormatException($"Could not find decalFront transform: '{decalFront}'.");
@ -153,351 +396,62 @@ namespace ConformalDecals {
if (backRenderer == null) { if (backRenderer == null) {
this.LogError($"Specified decalBack transform {decalBack} has no renderer attached! Setting updateBackScale to false."); this.LogError($"Specified decalBack transform {decalBack} has no renderer attached! Setting updateBackScale to false.");
updateBackScale = false; updateBackScale = false;
} } else {
else {
backMaterial = backRenderer.material; backMaterial = backRenderer.material;
if (backMaterial == null) { if (backMaterial == null) {
this.LogError($"Specified decalBack transform {decalBack} has a renderer but no material! Setting updateBackScale to false."); this.LogError($"Specified decalBack transform {decalBack} has a renderer but no material! Setting updateBackScale to false.");
updateBackScale = false; updateBackScale = false;
} } else {
else {
if (backTextureBaseScale == default) backTextureBaseScale = backMaterial.GetTextureScale(PropertyIDs._MainTex); if (backTextureBaseScale == default) backTextureBaseScale = backMaterial.GetTextureScale(PropertyIDs._MainTex);
} }
} }
} }
// PARSE MATERIAL PROPERTIES
// set shader
materialProperties.SetShader(shader);
materialProperties.AddOrGetProperty<MaterialKeywordProperty>("DECAL_BASE_NORMAL").value = useBaseNormal;
// add keyword nodes
foreach (var keywordNode in node.GetNodes("KEYWORD")) {
materialProperties.ParseProperty<MaterialKeywordProperty>(keywordNode);
}
// add texture nodes
foreach (var textureNode in node.GetNodes("TEXTURE")) {
materialProperties.ParseProperty<MaterialTextureProperty>(textureNode);
}
// add float nodes
foreach (var floatNode in node.GetNodes("FLOAT")) {
materialProperties.ParseProperty<MaterialTextureProperty>(floatNode);
}
// add color nodes
foreach (var colorNode in node.GetNodes("COLOR")) {
materialProperties.ParseProperty<MaterialColorProperty>(colorNode);
}
// handle texture tiling parameters
var tileString = node.GetValue("tile");
if (!string.IsNullOrEmpty(tileString)) {
var tileValid = ParseExtensions.TryParseRect(tileString, out tileRect);
if (!tileValid) throw new FormatException($"Invalid rect value for tile '{tileString}'");
}
if (tileRect.x >= 0) {
materialProperties.UpdateTile(tileRect);
}
else if (tileIndex >= 0) {
materialProperties.UpdateTile(tileIndex, tileSize);
}
}
catch (Exception e) {
this.LogException("Exception parsing partmodule", e);
} }
UpdateMaterials(); // PARSE MATERIAL PROPERTIES
// set shader
materialProperties.SetShader(shader);
materialProperties.AddOrGetProperty<MaterialKeywordProperty>("DECAL_BASE_NORMAL").value = useBaseNormal;
materialProperties.Load(node);
foreach (var keyword in _decalMaterial.shaderKeywords) { // handle texture tiling parameters
this.Log($"keyword: {keyword}"); var tileString = node.GetValue("tile");
if (!string.IsNullOrEmpty(tileString)) {
var tileValid = ParseExtensions.TryParseRect(tileString, out tileRect);
if (!tileValid) throw new FormatException($"Invalid rect value for tile '{tileString}'");
} }
if (tileRect.x >= 0) {
materialProperties.UpdateTile(tileRect);
} else if (tileIndex >= 0) {
materialProperties.UpdateTile(tileIndex, tileSize);
}
}
/// Setup decal by calling update functions relevent for the current situation
protected virtual void SetupDecal() {
if (HighLogic.LoadedSceneIsEditor) { if (HighLogic.LoadedSceneIsEditor) {
// Update tweakables in editor mode
UpdateTweakables(); UpdateTweakables();
} }
if (HighLogic.LoadedSceneIsGame) { if (HighLogic.LoadedSceneIsGame) {
UpdateScale(); UpdateAll();
} } else {
else {
scale = defaultScale; scale = defaultScale;
depth = defaultDepth; depth = defaultDepth;
opacity = defaultOpacity; opacity = defaultOpacity;
cutoff = defaultCutoff; cutoff = defaultCutoff;
wear = defaultWear; wear = defaultWear;
UpdateAll();
// QUEUE PART FOR ICON FIXING IN VAB // QUEUE PART FOR ICON FIXING IN VAB
DecalIconFixer.QueuePart(part.name); DecalIconFixer.QueuePart(part.name);
} }
} }
/// <inheritdoc /> /// Update decal editor tweakables
public override void OnIconCreate() {
UpdateScale();
}
/// <inheritdoc />
public override void OnStart(StartState state) {
materialProperties.RenderQueue = DecalQueue;
_boundsRenderer = decalProjectorTransform.GetComponent<MeshRenderer>();
// handle tweakables
if (HighLogic.LoadedSceneIsEditor) {
GameEvents.onEditorPartEvent.Add(OnEditorEvent);
GameEvents.onVariantApplied.Add(OnVariantApplied);
UpdateTweakables();
}
}
public override void OnStartFinished(StartState state) {
// handle game events
if (HighLogic.LoadedSceneIsGame) {
// set initial attachment state
if (part.parent == null) {
OnDetach();
}
else {
OnAttach();
}
}
// handle flight events
if (HighLogic.LoadedSceneIsFlight) {
GameEvents.onPartWillDie.Add(OnPartWillDie);
if (part.parent == null) part.explode();
Part.layerMask |= 1 << DecalConfig.DecalLayer;
decalColliderTransform.gameObject.layer = DecalConfig.DecalLayer;
if (!selectableInFlight || !DecalConfig.SelectableInFlight) {
decalColliderTransform.GetComponent<Collider>().enabled = false;
_boundsRenderer.enabled = false;
}
}
}
public virtual void OnDestroy() {
// remove GameEvents
if (HighLogic.LoadedSceneIsEditor) {
GameEvents.onEditorPartEvent.Remove(OnEditorEvent);
GameEvents.onVariantApplied.Remove(OnVariantApplied);
}
if (HighLogic.LoadedSceneIsFlight) {
GameEvents.onPartWillDie.Remove(OnPartWillDie);
}
// remove from preCull delegate
Camera.onPreCull -= Render;
// destroy material properties object
Destroy(materialProperties);
}
protected void OnSizeTweakEvent(BaseField field, object obj) {
// scale or depth values have been changed, so update scale
// and update projection matrices if attached
UpdateScale();
foreach (var counterpart in part.symmetryCounterparts) {
var decal = counterpart.GetComponent<ModuleConformalDecal>();
decal.UpdateScale();
}
}
protected void OnMaterialTweakEvent(BaseField field, object obj) {
materialProperties.SetOpacity(opacity);
materialProperties.SetCutoff(cutoff);
if (useBaseNormal) {
materialProperties.SetWear(wear);
}
foreach (var counterpart in part.symmetryCounterparts) {
var decal = counterpart.GetComponent<ModuleConformalDecal>();
decal.materialProperties.SetOpacity(opacity);
decal.materialProperties.SetCutoff(cutoff);
if (useBaseNormal) {
decal.materialProperties.SetWear(wear);
}
}
}
protected void OnVariantApplied(Part eventPart, PartVariant variant) {
if (_isAttached && eventPart == part.parent) {
UpdateTargets();
}
}
protected void OnEditorEvent(ConstructionEventType eventType, Part eventPart) {
if (this.part != eventPart && !part.symmetryCounterparts.Contains(eventPart)) return;
switch (eventType) {
case ConstructionEventType.PartAttached:
OnAttach();
break;
case ConstructionEventType.PartDetached:
OnDetach();
break;
case ConstructionEventType.PartOffsetting:
case ConstructionEventType.PartRotating:
UpdateScale();
break;
}
}
protected void OnPartWillDie(Part willDie) {
if (willDie == part.parent) {
this.Log("Parent part about to be destroyed! Killing decal part.");
part.Die();
}
}
protected virtual void OnAttach() {
if (part.parent == null) {
this.LogError("Attach function called but part has no parent!");
_isAttached = false;
return;
}
_isAttached = true;
// hide model
decalModelTransform.gameObject.SetActive(false);
// unhide projector
decalProjectorTransform.gameObject.SetActive(true);
// add to preCull delegate
Camera.onPreCull += Render;
UpdateMaterials();
UpdateTargets();
UpdateScale();
}
protected virtual void OnDetach() {
_isAttached = false;
// unhide model
decalModelTransform.gameObject.SetActive(true);
// hide projector
decalProjectorTransform.gameObject.SetActive(false);
// remove from preCull delegate
Camera.onPreCull -= Render;
UpdateMaterials();
UpdateScale();
}
protected void UpdateScale() {
scale = Mathf.Max(0.01f, scale);
depth = Mathf.Max(0.01f, depth);
var aspectRatio = materialProperties.AspectRatio;
Vector2 size;
switch (scaleMode) {
default:
case DecalScaleMode.HEIGHT:
size = new Vector2(scale / aspectRatio, scale);
break;
case DecalScaleMode.WIDTH:
size = new Vector2(scale, scale * aspectRatio);
break;
case DecalScaleMode.AVERAGE:
var width1 = 2 * scale / (1 + aspectRatio);
size = new Vector2(width1, width1 * aspectRatio);
break;
case DecalScaleMode.AREA:
var width2 = Mathf.Sqrt(scale / aspectRatio);
size = new Vector2(width2, width2 * aspectRatio);
break;
case DecalScaleMode.MINIMUM:
if (aspectRatio > 1) goto case DecalScaleMode.WIDTH;
else goto case DecalScaleMode.HEIGHT;
case DecalScaleMode.MAXIMUM:
if (aspectRatio > 1) goto case DecalScaleMode.HEIGHT;
else goto case DecalScaleMode.WIDTH;
}
// update material scale
materialProperties.UpdateScale(size);
if (_isAttached) {
// update orthogonal matrix
_orthoMatrix = Matrix4x4.identity;
_orthoMatrix[0, 3] = 0.5f;
_orthoMatrix[1, 3] = 0.5f;
decalProjectorTransform.localScale = new Vector3(size.x, size.y, depth);
// update projection
foreach (var target in _targets) {
target.Project(_orthoMatrix, decalProjectorTransform, _boundsRenderer.bounds, useBaseNormal);
}
}
else {
// rescale preview model
decalModelTransform.localScale = new Vector3(size.x, size.y, (size.x + size.y) / 2);
// update back material scale
if (updateBackScale) {
backMaterial.SetTextureScale(PropertyIDs._MainTex, new Vector2(size.x * backTextureBaseScale.x, size.y * backTextureBaseScale.y));
}
}
}
protected virtual void UpdateMaterials() {
materialProperties.UpdateMaterials();
materialProperties.SetOpacity(opacity);
materialProperties.SetCutoff(cutoff);
if (useBaseNormal) {
materialProperties.SetWear(wear);
}
_decalMaterial = materialProperties.DecalMaterial;
_previewMaterial = materialProperties.PreviewMaterial;
if (!_isAttached) decalFrontTransform.GetComponent<MeshRenderer>().material = _previewMaterial;
}
protected void UpdateTargets() {
if (_targets == null) {
_targets = new List<ProjectionTarget>();
}
else {
_targets.Clear();
}
// find all valid renderers
var renderers = part.parent.FindModelComponents<MeshRenderer>();
foreach (var renderer in renderers) {
// skip disabled renderers
if (renderer.gameObject.activeInHierarchy == false) continue;
// skip blacklisted shaders
if (DecalConfig.IsBlacklisted(renderer.material.shader)) continue;
var meshFilter = renderer.GetComponent<MeshFilter>();
if (meshFilter == null) continue; // object has a meshRenderer with no filter, invalid
var mesh = meshFilter.mesh;
if (mesh == null) continue; // object has a null mesh, invalid
// create new ProjectionTarget to represent the renderer
var target = new ProjectionTarget(renderer, mesh);
// add the target to the list
_targets.Add(target);
}
}
protected virtual void UpdateTweakables() { protected virtual void UpdateTweakables() {
// setup tweakable fields // setup tweakable fields
var scaleField = Fields[nameof(scale)]; var scaleField = Fields[nameof(scale)];
@ -505,6 +459,7 @@ namespace ConformalDecals {
var opacityField = Fields[nameof(opacity)]; var opacityField = Fields[nameof(opacity)];
var cutoffField = Fields[nameof(cutoff)]; var cutoffField = Fields[nameof(cutoff)];
var wearField = Fields[nameof(wear)]; var wearField = Fields[nameof(wear)];
var multiprojectField = Fields[nameof(projectMultiple)];
scaleField.guiActiveEditor = scaleAdjustable; scaleField.guiActiveEditor = scaleAdjustable;
depthField.guiActiveEditor = depthAdjustable; depthField.guiActiveEditor = depthAdjustable;
@ -522,7 +477,7 @@ namespace ConformalDecals {
scaleEditor.minValue = minValue; scaleEditor.minValue = minValue;
scaleEditor.maxValue = maxValue; scaleEditor.maxValue = maxValue;
scaleEditor.stepIncrement = 0.01f; //1cm scaleEditor.stepIncrement = 0.01f; //1cm
scaleEditor.onFieldChanged = OnSizeTweakEvent; scaleEditor.onFieldChanged = OnProjectionTweakEvent;
} }
if (depthAdjustable) { if (depthAdjustable) {
@ -533,7 +488,7 @@ namespace ConformalDecals {
depthEditor.minValue = minValue; depthEditor.minValue = minValue;
depthEditor.maxValue = maxValue; depthEditor.maxValue = maxValue;
depthEditor.stepIncrement = 0.01f; //1cm depthEditor.stepIncrement = 0.01f; //1cm
depthEditor.onFieldChanged = OnSizeTweakEvent; depthEditor.onFieldChanged = OnProjectionTweakEvent;
} }
if (opacityAdjustable) { if (opacityAdjustable) {
@ -570,13 +525,127 @@ namespace ConformalDecals {
wearEditor.stepIncrement = (maxValue - minValue) / steps; wearEditor.stepIncrement = (maxValue - minValue) / steps;
wearEditor.onFieldChanged = OnMaterialTweakEvent; wearEditor.onFieldChanged = OnMaterialTweakEvent;
} }
var multiprojectEditor = (UI_Toggle) multiprojectField.uiControlEditor;
multiprojectEditor.onFieldChanged = OnProjectionTweakEvent;
} }
/// Updates materials, scale and targets
protected virtual void UpdateAll() {
UpdateMaterials();
UpdateProjection();
}
/// Update decal materials
protected virtual void UpdateMaterials() {
_opacityProperty.value = opacity;
_cutoffProperty.value = cutoff;
_wearProperty.value = wear;
materialProperties.UpdateMaterials();
_decalMaterial = materialProperties.DecalMaterial;
_previewMaterial = materialProperties.PreviewMaterial;
if (!_isAttached) decalFrontTransform.GetComponent<MeshRenderer>().material = _previewMaterial;
}
/// Update decal scale and projection
protected void UpdateProjection() {
// Update scale and depth
scale = Mathf.Max(0.01f, scale);
depth = Mathf.Max(0.01f, depth);
var aspectRatio = Mathf.Max(0.01f, materialProperties.AspectRatio);
Vector2 size;
switch (scaleMode) {
default:
case DecalScaleMode.HEIGHT:
size = new Vector2(scale / aspectRatio, scale);
break;
case DecalScaleMode.WIDTH:
size = new Vector2(scale, scale * aspectRatio);
break;
case DecalScaleMode.AVERAGE:
var width1 = 2 * scale / (1 + aspectRatio);
size = new Vector2(width1, width1 * aspectRatio);
break;
case DecalScaleMode.AREA:
var width2 = Mathf.Sqrt(scale / aspectRatio);
size = new Vector2(width2, width2 * aspectRatio);
break;
case DecalScaleMode.MINIMUM:
if (aspectRatio > 1) goto case DecalScaleMode.WIDTH;
else goto case DecalScaleMode.HEIGHT;
case DecalScaleMode.MAXIMUM:
if (aspectRatio > 1) goto case DecalScaleMode.HEIGHT;
else goto case DecalScaleMode.WIDTH;
}
// update material scale
materialProperties.UpdateScale(size);
decalProjectorTransform.localScale = new Vector3(size.x, size.y, depth);
if (_isAttached) {
// update orthogonal matrix
_orthoMatrix = Matrix4x4.identity;
_orthoMatrix[0, 3] = 0.5f;
_orthoMatrix[1, 3] = 0.5f;
var projectionBounds = _boundsRenderer.bounds;
// disable all targets
foreach (var target in _targets.Values) {
target.enabled = false;
}
// collect list of potential targets
IEnumerable<Part> targetParts;
if (projectMultiple) {
targetParts = HighLogic.LoadedSceneIsFlight ? part.vessel.parts : EditorLogic.fetch.ship.parts;
} else {
targetParts = new[] {part.parent};
}
foreach (var targetPart in targetParts) {
UpdatePartTarget(targetPart, projectionBounds);
}
} else {
// rescale preview model
decalModelTransform.localScale = new Vector3(size.x, size.y, (size.x + size.y) / 2);
// update back material scale
if (updateBackScale) {
backMaterial.SetTextureScale(PropertyIDs._MainTex, new Vector2(size.x * backTextureBaseScale.x, size.y * backTextureBaseScale.y));
}
}
}
protected void UpdatePartTarget(Part targetPart, Bounds projectionBounds) {
if (targetPart.GetComponent<ModuleConformalDecal>() != null) return; // skip other decals
if (!_targets.TryGetValue(targetPart, out var target)) {
var rendererList = targetPart.FindModelComponents<MeshRenderer>();
if (rendererList.Any(o => projectionBounds.Intersects(o.bounds))) {
target = new ProjectionPartTarget(targetPart, useBaseNormal);
_targets.Add(targetPart, target);
} else {
return;
}
}
target.Project(_orthoMatrix, decalProjectorTransform, projectionBounds);
}
/// Render the decal
public void Render(Camera camera) { public void Render(Camera camera) {
if (!_isAttached) return; if (!_isAttached) return;
// render on each target object // render on each target object
foreach (var target in _targets) { foreach (var target in _targets.Values) {
target.Render(_decalMaterial, part.mpb, camera); target.Render(_decalMaterial, part.mpb, camera);
} }
} }

View File

@ -1,4 +1,3 @@
using ConformalDecals.Util;
using UnityEngine; using UnityEngine;
namespace ConformalDecals { namespace ConformalDecals {
@ -19,92 +18,103 @@ namespace ConformalDecals {
return string.IsNullOrEmpty(part.flagURL) ? HighLogic.CurrentGame.flagURL : part.flagURL; return string.IsNullOrEmpty(part.flagURL) ? HighLogic.CurrentGame.flagURL : part.flagURL;
} }
// If we are not in game, use the default flag (for icon rendering)
return DefaultFlag; return DefaultFlag;
} }
} }
public override void OnLoad(ConfigNode node) {
base.OnLoad(node);
if (useCustomFlag) {
SetFlag(flagUrl);
}
else {
SetFlag(MissionFlagUrl);
}
}
public override void OnStart(StartState state) { public override void OnStart(StartState state) {
base.OnStart(state);
if (HighLogic.LoadedSceneIsGame) {
GameEvents.onMissionFlagSelect.Add(OnEditorFlagSelected);
}
if (HighLogic.LoadedSceneIsEditor) { if (HighLogic.LoadedSceneIsEditor) {
Events[nameof(ResetFlag)].guiActiveEditor = useCustomFlag; // Register flag change event
GameEvents.onMissionFlagSelect.Add(OnEditorFlagSelected);
// Register reset button event
Events[nameof(ResetFlagButton)].guiActiveEditor = useCustomFlag;
} }
if (useCustomFlag) { base.OnStart(state);
SetFlag(flagUrl);
}
else {
SetFlag(MissionFlagUrl);
}
} }
public override void OnDestroy() { public override void OnDestroy() {
GameEvents.onMissionFlagSelect.Remove(SetFlag); if (HighLogic.LoadedSceneIsEditor) {
// Unregister flag change event
GameEvents.onMissionFlagSelect.Remove(OnEditorFlagSelected);
}
base.OnDestroy(); base.OnDestroy();
} }
[KSPEvent(guiActive = false, guiActiveEditor = true, guiName = "#LOC_ConformalDecals_gui-select-flag")] [KSPEvent(guiActive = false, guiActiveEditor = true, guiName = "#LOC_ConformalDecals_gui-select-flag")]
public void SelectFlag() { public void SelectFlagButton() {
// Button for selecting a flag
// This is a bit of a hack to bring up the stock flag selection menu
// When its done, it calls OnCustomFlagSelected()
// ReSharper disable once PossibleNullReferenceException
var flagBrowser = (Instantiate((Object) (new FlagBrowserGUIButton(null, null, null, null)).FlagBrowserPrefab) as GameObject).GetComponent<FlagBrowser>(); var flagBrowser = (Instantiate((Object) (new FlagBrowserGUIButton(null, null, null, null)).FlagBrowserPrefab) as GameObject).GetComponent<FlagBrowser>();
flagBrowser.OnFlagSelected = OnCustomFlagSelected; flagBrowser.OnFlagSelected = OnCustomFlagSelected;
} }
[KSPEvent(guiActive = false, guiActiveEditor = true, guiName = "#LOC_ConformalDecals_gui-reset-flag")] [KSPEvent(guiActive = false, guiActiveEditor = true, guiName = "#LOC_ConformalDecals_gui-reset-flag")]
public void ResetFlag() { public void ResetFlagButton() {
SetFlag(MissionFlagUrl); // we are no longer using a custom flag, so instead use the mission or agency flag
SetFlagSymmetryCounterparts(MissionFlagUrl); SetFlag("Mission", false, true);
useCustomFlag = false; // disable the reset button, since it no longer makes sense
Events[nameof(ResetFlag)].guiActiveEditor = false; Events[nameof(ResetFlagButton)].guiActiveEditor = false;
} }
private void OnCustomFlagSelected(FlagBrowser.FlagEntry newFlagEntry) { private void OnCustomFlagSelected(FlagBrowser.FlagEntry newFlagEntry) {
SetFlag(newFlagEntry.textureInfo.name); // Callback for when a flag is selected in the menu spawned by SelectFlag()
SetFlagSymmetryCounterparts(newFlagEntry.textureInfo.name);
useCustomFlag = true; // we are now using a custom flag with the URL of the new flag entry
Events[nameof(ResetFlag)].guiActiveEditor = true; SetFlag(newFlagEntry.textureInfo.name, true, true);
// make sure the reset button is now available
Events[nameof(ResetFlagButton)].guiActiveEditor = true;
} }
private void OnEditorFlagSelected(string newFlagUrl) { private void OnEditorFlagSelected(string newFlagUrl) {
// Callback for when a new mission flag is selected in the editor
// Since this callback is called for all modules, we only need to update this module
// Updating symmetry counterparts would be redundent
// if we are using the mission flag, update it. otherwise ignore the call
if (!useCustomFlag) { if (!useCustomFlag) {
SetFlag(newFlagUrl); SetFlag(newFlagUrl, false, false);
SetFlagSymmetryCounterparts(newFlagUrl);
} }
} }
private void SetFlag(string newFlagUrl) { private void SetFlag(string newFlagUrl, bool isCustom, bool recursive) {
this.Log($"Loading flag texture '{newFlagUrl}'."); // Function to set the flag URL, the custom flag
// Set values
flagUrl = newFlagUrl; flagUrl = newFlagUrl;
materialProperties.AddOrGetTextureProperty("_Decal", true).TextureUrl = newFlagUrl; useCustomFlag = isCustom;
UpdateMaterials(); // Update material and projection
UpdateScale(); UpdateAll();
// Update symmetry counterparts if called to
if (recursive) {
foreach (var counterpart in part.symmetryCounterparts) {
var decal = counterpart.GetComponent<ModuleConformalFlag>();
decal.SetFlag(newFlagUrl, isCustom, false);
}
}
} }
private void SetFlagSymmetryCounterparts(string newFlagUrl) { protected override void UpdateMaterials() {
foreach (var counterpart in part.symmetryCounterparts) { // get the decal material property for the decal texture
var decal = counterpart.GetComponent<ModuleConformalFlag>(); var textureProperty = materialProperties.AddOrGetTextureProperty("_Decal", true);
decal.SetFlag(newFlagUrl); if (useCustomFlag) { // set the texture to the custom flag
decal.useCustomFlag = useCustomFlag; textureProperty.TextureUrl = flagUrl;
} else { // set the texture to the mission flag
textureProperty.TextureUrl = MissionFlagUrl;
} }
base.UpdateMaterials();
} }
} }
} }

View File

@ -5,6 +5,7 @@ using ConformalDecals.Text;
using ConformalDecals.UI; using ConformalDecals.UI;
using ConformalDecals.Util; using ConformalDecals.Util;
using TMPro; using TMPro;
using UniLinq;
using UnityEngine; using UnityEngine;
namespace ConformalDecals { namespace ConformalDecals {
@ -28,8 +29,7 @@ namespace ConformalDecals {
public void SetText() { public void SetText() {
if (_textEntryController == null) { if (_textEntryController == null) {
_textEntryController = TextEntryController.Create(text, font, style, vertical, lineSpacing, charSpacing, lineSpacingRange, charSpacingRange, OnTextUpdate); _textEntryController = TextEntryController.Create(text, font, style, vertical, lineSpacing, charSpacing, lineSpacingRange, charSpacingRange, OnTextUpdate);
} } else {
else {
_textEntryController.Close(); _textEntryController.Close();
} }
} }
@ -46,8 +46,7 @@ namespace ConformalDecals {
public void SetFillColor() { public void SetFillColor() {
if (_fillColorPickerController == null) { if (_fillColorPickerController == null) {
_fillColorPickerController = ColorPickerController.Create(fillColor, OnFillColorUpdate); _fillColorPickerController = ColorPickerController.Create(fillColor, OnFillColorUpdate);
} } else {
else {
_fillColorPickerController.Close(); _fillColorPickerController.Close();
} }
} }
@ -69,8 +68,7 @@ namespace ConformalDecals {
public void SetOutlineColor() { public void SetOutlineColor() {
if (_outlineColorPickerController == null) { if (_outlineColorPickerController == null) {
_outlineColorPickerController = ColorPickerController.Create(outlineColor, OnOutlineColorUpdate); _outlineColorPickerController = ColorPickerController.Create(outlineColor, OnOutlineColorUpdate);
} } else {
else {
_outlineColorPickerController.Close(); _outlineColorPickerController.Close();
} }
} }
@ -89,42 +87,11 @@ namespace ConformalDecals {
private MaterialColorProperty _outlineColorProperty; private MaterialColorProperty _outlineColorProperty;
private MaterialFloatProperty _outlineWidthProperty; private MaterialFloatProperty _outlineWidthProperty;
private TextRenderJob _currentJob; private DecalText _currentText;
private DecalText _currentText;
public override void OnLoad(ConfigNode node) { // EVENTS
base.OnLoad(node);
string textRaw = "";
if (ParseUtil.ParseStringIndirect(ref textRaw, node, "text")) {
text = WebUtility.UrlDecode(textRaw);
}
string fontName = "";
if (ParseUtil.ParseStringIndirect(ref fontName, node, "fontName")) {
font = DecalConfig.GetFont(fontName);
}
else if (font == null) font = DecalConfig.GetFont("Calibri SDF");
int styleInt = 0;
if (ParseUtil.ParseIntIndirect(ref styleInt, node, "style")) {
style = (FontStyles) styleInt;
}
ParseUtil.ParseColor32Indirect(ref fillColor, node, "fillColor");
ParseUtil.ParseColor32Indirect(ref outlineColor, node, "outlineColor");
if (HighLogic.LoadedSceneIsGame) {
// For some reason, rendering doesnt work right on the first frame a scene is loaded
// So delay any rendering until the next frame when called in OnLoad
// This is probably a problem with Unity, not KSP
StartCoroutine(UpdateTextLate());
}
else {
UpdateText();
}
}
/// <inheritdoc />
public override void OnSave(ConfigNode node) { public override void OnSave(ConfigNode node) {
node.AddValue("text", WebUtility.UrlEncode(text)); node.AddValue("text", WebUtility.UrlEncode(text));
node.AddValue("fontName", font.Name); node.AddValue("fontName", font.Name);
@ -148,13 +115,23 @@ namespace ConformalDecals {
} }
public void OnTextUpdate(string newText, DecalFont newFont, FontStyles newStyle, bool newVertical, float newLineSpacing, float newCharSpacing) { public void OnTextUpdate(string newText, DecalFont newFont, FontStyles newStyle, bool newVertical, float newLineSpacing, float newCharSpacing) {
this.text = newText; text = newText;
this.font = newFont; font = newFont;
this.style = newStyle; style = newStyle;
this.vertical = newVertical; vertical = newVertical;
this.lineSpacing = newLineSpacing; lineSpacing = newLineSpacing;
this.charSpacing = newCharSpacing; charSpacing = newCharSpacing;
UpdateTextRecursive(); UpdateAll();
foreach (var decal in part.symmetryCounterparts.Select(o => o.GetComponent<ModuleConformalText>())) {
decal.text = newText;
decal.font = newFont;
decal.style = newStyle;
decal.vertical = newVertical;
decal.lineSpacing = newLineSpacing;
decal.charSpacing = newCharSpacing;
decal.UpdateAll();
}
} }
public void OnFillColorUpdate(Color rgb, Util.ColorHSV hsv) { public void OnFillColorUpdate(Color rgb, Util.ColorHSV hsv) {
@ -241,49 +218,58 @@ namespace ConformalDecals {
base.OnDetach(); base.OnDetach();
} }
private void UpdateTextRecursive() { // FUNCTIONS
UpdateText();
foreach (var counterpart in part.symmetryCounterparts) { protected override void LoadDecal(ConfigNode node) {
var decal = counterpart.GetComponent<ModuleConformalText>(); base.LoadDecal(node);
decal.text = text;
decal.font = font;
decal.style = style;
decal._currentJob = _currentJob; string textRaw = "";
decal._currentText = _currentText; if (ParseUtil.ParseStringIndirect(ref textRaw, node, "text")) {
decal.UpdateText(); text = WebUtility.UrlDecode(textRaw);
}
string fontName = "";
if (ParseUtil.ParseStringIndirect(ref fontName, node, "fontName")) {
font = DecalConfig.GetFont(fontName);
} else if (font == null) font = DecalConfig.GetFont("Calibri SDF");
int styleInt = 0;
if (ParseUtil.ParseIntIndirect(ref styleInt, node, "style")) {
style = (FontStyles) styleInt;
}
ParseUtil.ParseColor32Indirect(ref fillColor, node, "fillColor");
ParseUtil.ParseColor32Indirect(ref outlineColor, node, "outlineColor");
}
protected override void SetupDecal() {
if (HighLogic.LoadedSceneIsGame) {
// For some reason text rendering fails on the first frame of a scene, so this is my workaround
StartCoroutine(UpdateTextLate());
} else {
base.SetupDecal();
} }
} }
private IEnumerator UpdateTextLate() { private IEnumerator UpdateTextLate() {
yield return null; yield return null;
UpdateText(); UpdateAll();
}
private void UpdateText() {
// Render text
var newText = new DecalText(text, font, style, vertical, lineSpacing, charSpacing);
var output = TextRenderer.UpdateTextNow(_currentText, newText);
_currentText = newText;
UpdateTexture(output);
// TODO: ASYNC RENDERING
// var newText = new DecalText(text, _font, _style);
// _currentJob = TextRenderer.UpdateText(_currentText, newText, UpdateTexture);
// _currentText = newText;
}
public void UpdateTexture(TextRenderOutput output) {
_decalTextureProperty.Texture = output.Texture;
_decalTextureProperty.SetTile(output.Window);
UpdateMaterials();
UpdateScale();
} }
protected override void UpdateMaterials() { protected override void UpdateMaterials() {
// Render text
var newText = new DecalText(text, font, style, vertical, lineSpacing, charSpacing);
if (newText != _currentText) {
var output = TextRenderer.UpdateText(_currentText, newText);
// update the _currentText state variable
// this is the ONLY place this variable should be set! otherwise the current state is lost
_currentText = newText;
_decalTextureProperty.Texture = output.Texture;
_decalTextureProperty.SetTile(output.Window);
}
_fillEnabledProperty.value = fillEnabled; _fillEnabledProperty.value = fillEnabled;
_fillColorProperty.color = fillColor; _fillColorProperty.color = fillColor;

View File

@ -1,68 +0,0 @@
using UnityEngine;
using UnityEngine.Rendering;
namespace ConformalDecals {
public class ProjectionTarget {
// Target object data
public readonly Transform target;
private readonly Renderer _targetRenderer;
private readonly Mesh _targetMesh;
private bool _projectionEnabled;
// property block
private readonly MaterialPropertyBlock _decalMPB;
public ProjectionTarget(MeshRenderer targetRenderer, Mesh targetMesh) {
target = targetRenderer.transform;
_targetRenderer = targetRenderer;
_targetMesh = targetMesh;
_decalMPB = new MaterialPropertyBlock();
}
public void Project(Matrix4x4 orthoMatrix, Transform projector, Bounds projectorBounds, bool useBaseNormal) {
if (projectorBounds.Intersects(_targetRenderer.bounds)) {
_projectionEnabled = true;
var targetMaterial = _targetRenderer.sharedMaterial;
var projectorToTargetMatrix = target.worldToLocalMatrix * projector.localToWorldMatrix;
var projectionMatrix = orthoMatrix * projectorToTargetMatrix.inverse;
var decalNormal = projectorToTargetMatrix.MultiplyVector(Vector3.back).normalized;
var decalTangent = projectorToTargetMatrix.MultiplyVector(Vector3.right).normalized;
_decalMPB.SetMatrix(DecalPropertyIDs._ProjectionMatrix, projectionMatrix);
_decalMPB.SetVector(DecalPropertyIDs._DecalNormal, decalNormal);
_decalMPB.SetVector(DecalPropertyIDs._DecalTangent, decalTangent);
if (useBaseNormal && targetMaterial.HasProperty(DecalPropertyIDs._BumpMap)) {
_decalMPB.SetTexture(DecalPropertyIDs._BumpMap, targetMaterial.GetTexture(DecalPropertyIDs._BumpMap));
var normalScale = targetMaterial.GetTextureScale(DecalPropertyIDs._BumpMap);
var normalOffset = targetMaterial.GetTextureOffset(DecalPropertyIDs._BumpMap);
_decalMPB.SetVector(DecalPropertyIDs._BumpMap_ST, new Vector4(normalScale.x, normalScale.y, normalOffset.x, normalOffset.y));
}
else {
_decalMPB.SetTexture(DecalPropertyIDs._BumpMap, DecalConfig.BlankNormal);
}
}
else {
_projectionEnabled = false;
}
}
public bool Render(Material decalMaterial, MaterialPropertyBlock partMPB, Camera camera) {
if (_projectionEnabled) {
_decalMPB.SetFloat(PropertyIDs._RimFalloff, partMPB.GetFloat(PropertyIDs._RimFalloff));
_decalMPB.SetColor(PropertyIDs._RimColor, partMPB.GetColor(PropertyIDs._RimColor));
Graphics.DrawMesh(_targetMesh, target.localToWorldMatrix, decalMaterial, 0, camera, 0, _decalMPB, ShadowCastingMode.Off, true);
return true;
}
return false;
}
}
}

View File

@ -1,38 +1 @@
using System.Reflection; [assembly: KSPAssembly("ConformalDecals", 0, 2)]
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ConformalDecals")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Cineboxandrew")]
[assembly: AssemblyProduct("ConformalDecals")]
[assembly: AssemblyCopyright("Copyright © Andrew Cassidy 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("1ea983f9-42e5-494e-9683-fdac9c9121f4")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: KSPAssembly("ConformalDecals", 0, 1, 0)]

View File

@ -0,0 +1,8 @@
using UnityEngine;
namespace ConformalDecals.Targets {
public interface IProjectionTarget {
bool Project(Matrix4x4 orthoMatrix, Transform projector, Bounds projectionBounds);
void Render(Material decalMaterial, MaterialPropertyBlock partMPB, Camera camera);
}
}

View File

@ -0,0 +1,93 @@
using UnityEngine;
using UnityEngine.Rendering;
namespace ConformalDecals.Targets {
public class ProjectionMeshTarget : IProjectionTarget {
public const string NodeName = "MESH_TARGET";
// enabled flag
public bool enabled;
// Target object data
public readonly Transform target;
public readonly Transform root;
public readonly Mesh mesh;
public readonly MeshRenderer renderer;
// Projection data
private Matrix4x4 _decalMatrix;
private Vector3 _decalNormal;
private Vector3 _decalTangent;
// property block
private readonly MaterialPropertyBlock _decalMPB;
public ProjectionMeshTarget(Transform target, Transform root, MeshRenderer renderer, Mesh mesh, bool useBaseNormal) {
this.root = root;
this.target = target;
this.renderer = renderer;
this.mesh = mesh;
_decalMPB = new MaterialPropertyBlock();
SetNormalMap(renderer.sharedMaterial, useBaseNormal);
}
private void SetNormalMap(Material targetMaterial, bool useBaseNormal) {
if (useBaseNormal && targetMaterial.HasProperty(DecalPropertyIDs._BumpMap)) {
_decalMPB.SetTexture(DecalPropertyIDs._BumpMap, targetMaterial.GetTexture(DecalPropertyIDs._BumpMap));
var normalScale = targetMaterial.GetTextureScale(DecalPropertyIDs._BumpMap);
var normalOffset = targetMaterial.GetTextureOffset(DecalPropertyIDs._BumpMap);
_decalMPB.SetVector(DecalPropertyIDs._BumpMap_ST, new Vector4(normalScale.x, normalScale.y, normalOffset.x, normalOffset.y));
}
else {
_decalMPB.SetTexture(DecalPropertyIDs._BumpMap, DecalConfig.BlankNormal);
}
}
public bool Project(Matrix4x4 orthoMatrix, Transform projector, Bounds projectionBounds) {
if (projectionBounds.Intersects(renderer.bounds)) {
enabled = true;
var projectorToTargetMatrix = target.worldToLocalMatrix * projector.localToWorldMatrix;
_decalMatrix = orthoMatrix * projectorToTargetMatrix.inverse;
_decalNormal = projectorToTargetMatrix.MultiplyVector(Vector3.back).normalized;
_decalTangent = projectorToTargetMatrix.MultiplyVector(Vector3.right).normalized;
_decalMPB.SetMatrix(DecalPropertyIDs._ProjectionMatrix, _decalMatrix);
_decalMPB.SetVector(DecalPropertyIDs._DecalNormal, _decalNormal);
_decalMPB.SetVector(DecalPropertyIDs._DecalTangent, _decalTangent);
}
else {
enabled = false;
}
return enabled;
}
public void Render(Material decalMaterial, MaterialPropertyBlock partMPB, Camera camera) {
if (!enabled) return;
_decalMPB.SetFloat(PropertyIDs._RimFalloff, partMPB.GetFloat(PropertyIDs._RimFalloff));
_decalMPB.SetColor(PropertyIDs._RimColor, partMPB.GetColor(PropertyIDs._RimColor));
Graphics.DrawMesh(mesh, target.localToWorldMatrix, decalMaterial, 0, camera, 0, _decalMPB, ShadowCastingMode.Off, true);
}
public static bool ValidateTarget(Transform target, MeshRenderer renderer, MeshFilter filter) {
if (renderer == null) return false;
if (filter == null) return false;
if (!target.gameObject.activeInHierarchy) return false;
var material = renderer.material;
if (material == null) return false;
if (DecalConfig.IsBlacklisted(material.shader)) return false;
if (filter.sharedMesh == null) return false;
return true;
}
}
}

View File

@ -0,0 +1,50 @@
using System.Collections.Generic;
using UnityEngine;
namespace ConformalDecals.Targets {
public class ProjectionPartTarget : IProjectionTarget {
public const string NodeName = "PART_TARGET";
// enabled flag
public bool enabled;
public readonly Part part;
public readonly List<ProjectionMeshTarget> meshTargets = new List<ProjectionMeshTarget>();
public ProjectionPartTarget(Part part, bool useBaseNormal) {
this.part = part;
foreach (var renderer in part.FindModelComponents<MeshRenderer>()) {
var target = renderer.transform;
var filter = target.GetComponent<MeshFilter>();
// check if the target has any missing data
if (!ProjectionMeshTarget.ValidateTarget(target, renderer, filter)) continue;
// create new ProjectionTarget to represent the renderer
var projectionTarget = new ProjectionMeshTarget(target, part.transform, renderer, filter.sharedMesh, useBaseNormal);
// add the target to the list
meshTargets.Add(projectionTarget);
}
}
public bool Project(Matrix4x4 orthoMatrix, Transform projector, Bounds projectionBounds) {
enabled = false;
foreach (var meshTarget in meshTargets) {
enabled |= meshTarget.Project(orthoMatrix, projector, projectionBounds);
}
return enabled;
}
public void Render(Material decalMaterial, MaterialPropertyBlock partMPB, Camera camera) {
if (!enabled) return;
foreach (var target in meshTargets) {
target.Render(decalMaterial, partMPB, camera);
}
}
}
}

View File

@ -46,21 +46,25 @@ namespace ConformalDecals.Text {
public bool SmallCapsMask => (_fontStyleMask & FontStyles.SmallCaps) != 0; public bool SmallCapsMask => (_fontStyleMask & FontStyles.SmallCaps) != 0;
public DecalFont(ConfigNode node, IEnumerable<TMP_FontAsset> fontAssets) { public static DecalFont Parse(ConfigNode node, IEnumerable<TMP_FontAsset> fontAssets) {
if (node == null) throw new ArgumentNullException(nameof(node)); if (node == null) throw new ArgumentNullException(nameof(node));
if (fontAssets == null) throw new ArgumentNullException(nameof(fontAssets)); if (fontAssets == null) throw new ArgumentNullException(nameof(fontAssets));
var font = ScriptableObject.CreateInstance<DecalFont>();
var name = ParseUtil.ParseString(node, "name"); var name = ParseUtil.ParseString(node, "name");
_fontAsset = fontAssets.First(o => o.name == name); var fontAsset = fontAssets.First(o => o.name == name);
if (FontAsset == null) { if (fontAsset == null) {
throw new FormatException($"Could not find font asset named {name}"); throw new FormatException($"Could not find font asset named {name}");
} }
_title = ParseUtil.ParseString(node, "title", true, name); font._fontAsset = fontAsset;
_fontStyle = (FontStyles) ParseUtil.ParseInt(node, "style", true); font._title = ParseUtil.ParseString(node, "title", true, name);
_fontStyleMask = (FontStyles) ParseUtil.ParseInt(node, "styleMask", true); font._fontStyle = (FontStyles) ParseUtil.ParseInt(node, "style", true);
} font._fontStyleMask = (FontStyles) ParseUtil.ParseInt(node, "styleMask", true);
return font;
}
public void SetupSample(TMP_Text tmp) { public void SetupSample(TMP_Text tmp) {
if (tmp == null) throw new ArgumentNullException(nameof(tmp)); if (tmp == null) throw new ArgumentNullException(nameof(tmp));
@ -105,5 +109,9 @@ namespace ConformalDecals.Text {
public void OnBeforeSerialize() { } public void OnBeforeSerialize() { }
public void OnAfterDeserialize() { } public void OnAfterDeserialize() { }
public override string ToString() {
return _title;
}
} }
} }

View File

@ -86,5 +86,9 @@ namespace ConformalDecals.Text {
public static bool operator !=(DecalText left, DecalText right) { public static bool operator !=(DecalText left, DecalText right) {
return !Equals(left, right); return !Equals(left, right);
} }
public override string ToString() {
return $"{nameof(_text)}: {_text}, {nameof(_font)}: {_font}, {nameof(_style)}: {_style}, {nameof(_vertical)}: {_vertical}, {nameof(_lineSpacing)}: {_lineSpacing}, {nameof(_charSpacing)}: {_charSpacing}";
}
} }
} }

View File

@ -1,6 +1,5 @@
using System.IO; using System.IO;
using System.Collections; using System.Collections;
using System.Collections.Generic;
using ConformalDecals.Util; using ConformalDecals.Util;
using TMPro; using TMPro;
using UniLinq; using UniLinq;

View File

@ -1,35 +0,0 @@
using System;
using UnityEngine.Events;
namespace ConformalDecals.Text {
public class TextRenderJob {
public DecalText OldText { get; }
public DecalText NewText { get; }
public bool Needed { get; private set; }
public bool IsStarted { get; private set; }
public bool IsDone { get; private set; }
public readonly TextRenderer.TextRenderEvent onRenderFinished = new TextRenderer.TextRenderEvent();
public TextRenderJob(DecalText oldText, DecalText newText, UnityAction<TextRenderOutput> renderFinishedCallback) {
OldText = oldText;
NewText = newText ?? throw new ArgumentNullException(nameof(newText));
Needed = true;
if (renderFinishedCallback != null) onRenderFinished.AddListener(renderFinishedCallback);
}
public void Cancel() {
Needed = false;
}
public void Start() {
IsStarted = true;
}
public void Finish(TextRenderOutput output) {
IsDone = true;
onRenderFinished.Invoke(output);
}
}
}

View File

@ -9,7 +9,7 @@ namespace ConformalDecals.Text {
/// The rectangle that the rendered text takes up within the texture /// The rectangle that the rendered text takes up within the texture
public Rect Window { get; private set; } public Rect Window { get; private set; }
/// The number of users for this render output. If 0, it can be discarded from the cache and the texture reused /// The number of users for this render output. If 0, it can be discarded from the cache
public int UserCount { get; set; } public int UserCount { get; set; }
public TextRenderOutput(Texture2D texture, Rect window) { public TextRenderOutput(Texture2D texture, Rect window) {

View File

@ -3,16 +3,12 @@ using System.Collections.Generic;
using ConformalDecals.Util; using ConformalDecals.Util;
using TMPro; using TMPro;
using UnityEngine; using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Rendering; using UnityEngine.Rendering;
using Object = UnityEngine.Object;
namespace ConformalDecals.Text { namespace ConformalDecals.Text {
// TODO: Testing shows the job system is unnecessary, so remove job system code.
/// Class handing text rendering. /// Class handing text rendering.
/// Is a singleton referencing a single gameobject in the scene which contains the TextMeshPro component public static class TextRenderer {
[KSPAddon(KSPAddon.Startup.Instantly, true)]
public class TextRenderer : MonoBehaviour {
/// Texture format used for returned textures. /// Texture format used for returned textures.
/// Unfortunately due to how Unity textures work, this cannot be R8 or Alpha8, /// Unfortunately due to how Unity textures work, this cannot be R8 or Alpha8,
/// so theres always a superfluous green channel using memory /// so theres always a superfluous green channel using memory
@ -22,163 +18,73 @@ namespace ConformalDecals.Text {
/// Overriden below to be ARGB32 on DirectX because DirectX is dumb /// Overriden below to be ARGB32 on DirectX because DirectX is dumb
public static RenderTextureFormat textRenderTextureFormat = RenderTextureFormat.R8; public static RenderTextureFormat textRenderTextureFormat = RenderTextureFormat.R8;
/// The text renderer object within the scene which contains the TextMeshPro component used for rendering.
public static TextRenderer Instance {
get {
if (!_instance._isSetup) {
_instance.Setup();
}
return _instance;
}
}
/// Text Render unityevent, used with the job system to signal render completion
[Serializable]
public class TextRenderEvent : UnityEvent<TextRenderOutput> { }
private const string ShaderName = "ConformalDecals/Text Blit"; private const string ShaderName = "ConformalDecals/Text Blit";
private const int MaxTextureSize = 4096; private const int MaxTextureSize = 4096;
private const float FontSize = 100; private const float FontSize = 100;
private const float PixelDensity = 5; private const float PixelDensity = 5;
private static TextRenderer _instance; private static Shader _blitShader;
private static Texture2D _blankTexture;
private bool _isSetup;
private TextMeshPro _tmp;
private Shader _blitShader;
private static readonly Dictionary<DecalText, TextRenderOutput> RenderCache = new Dictionary<DecalText, TextRenderOutput>(); private static readonly Dictionary<DecalText, TextRenderOutput> RenderCache = new Dictionary<DecalText, TextRenderOutput>();
private static readonly Queue<TextRenderJob> RenderJobs = new Queue<TextRenderJob>();
/// Update text using the job queue
public static TextRenderJob UpdateText(DecalText oldText, DecalText newText, UnityAction<TextRenderOutput> renderFinishedCallback) {
if (newText == null) throw new ArgumentNullException(nameof(newText));
var job = new TextRenderJob(oldText, newText, renderFinishedCallback);
RenderJobs.Enqueue(job);
return job;
}
/// Update text immediately without using job queue /// Update text immediately without using job queue
public static TextRenderOutput UpdateTextNow(DecalText oldText, DecalText newText) { public static TextRenderOutput UpdateText(DecalText oldText, DecalText newText) {
if (newText == null) throw new ArgumentNullException(nameof(newText)); if (newText == null) throw new ArgumentNullException(nameof(newText));
return Instance.RunJob(new TextRenderJob(oldText, newText, null), out _); if (!(oldText is null)) UnregisterText(oldText);
// now that all old references are handled, begin rendering the new output
if (!RenderCache.TryGetValue(newText, out var renderOutput)) {
renderOutput = RenderText(newText);
RenderCache.Add(newText, renderOutput);
}
renderOutput.UserCount++;
return renderOutput;
} }
/// Unregister a user of a piece of text /// Unregister a user of a piece of text
public static void UnregisterText(DecalText text) { public static void UnregisterText(DecalText text) {
if (text == null) throw new ArgumentNullException(nameof(text));
if (RenderCache.TryGetValue(text, out var renderedText)) { if (RenderCache.TryGetValue(text, out var renderedText)) {
renderedText.UserCount--; renderedText.UserCount--;
if (renderedText.UserCount <= 0) { if (renderedText.UserCount <= 0) {
RenderCache.Remove(text); RenderCache.Remove(text);
Destroy(renderedText.Texture); var texture = renderedText.Texture;
if (texture != _blankTexture) Object.Destroy(texture);
} }
} }
} }
private void Start() {
if (_instance != null) {
Logging.LogError("Duplicate TextRenderer created???");
}
Logging.Log("Creating TextRenderer Object");
_instance = this;
DontDestroyOnLoad(gameObject);
if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Direct3D11 || SystemInfo.graphicsDeviceType == GraphicsDeviceType.Direct3D12) {
textRenderTextureFormat = RenderTextureFormat.ARGB32; // DirectX is dumb
}
if (!SystemInfo.SupportsTextureFormat(textTextureFormat)) {
Logging.LogError($"Text texture format {textTextureFormat} not supported on this platform.");
}
if (!SystemInfo.SupportsRenderTextureFormat(textRenderTextureFormat)) {
Logging.LogError($"Text texture format {textRenderTextureFormat} not supported on this platform.");
}
}
/// Setup this text renderer instance for rendering
private void Setup() {
if (_isSetup) return;
Logging.Log("Setting Up TextRenderer Object");
_tmp = gameObject.AddComponent<TextMeshPro>();
_tmp.renderer.enabled = false; // dont automatically render
_blitShader = Shabby.Shabby.FindShader(ShaderName);
if (_blitShader == null) Logging.LogError($"Could not find text blit shader named '{ShaderName}'");
_isSetup = true;
}
/// Run a text render job
private TextRenderOutput RunJob(TextRenderJob job, out bool renderNeeded) {
if (!job.Needed) {
renderNeeded = false;
return null;
}
job.Start();
Texture2D texture = null;
if (job.OldText != null && RenderCache.TryGetValue(job.OldText, out var oldRender)) {
// old output still exists
oldRender.UserCount--;
if (oldRender.UserCount <= 0) {
// this is the only usage of this output, so we are free to re-render into the texture
texture = oldRender.Texture;
RenderCache.Remove(job.OldText);
}
}
// now that all old references are handled, begin rendering the new output
if (RenderCache.TryGetValue(job.NewText, out var renderOutput)) {
renderNeeded = false;
}
else {
renderNeeded = true;
renderOutput = RenderText(job.NewText, texture);
RenderCache.Add(job.NewText, renderOutput);
}
renderOutput.UserCount++;
job.Finish(renderOutput);
return renderOutput;
}
/// Render a piece of text to a given texture /// Render a piece of text to a given texture
public TextRenderOutput RenderText(DecalText text, Texture2D texture) { public static TextRenderOutput RenderText(DecalText text) {
if (text == null) throw new ArgumentNullException(nameof(text)); if (text == null) throw new ArgumentNullException(nameof(text));
if (_tmp == null) throw new InvalidOperationException("TextMeshPro object not yet created.");
var tmpObject = new GameObject("Text Mesh Pro renderer");
var tmp = tmpObject.AddComponent<TextMeshPro>();
// SETUP TMP OBJECT FOR RENDERING // SETUP TMP OBJECT FOR RENDERING
_tmp.text = text.FormattedText; tmp.text = text.FormattedText;
_tmp.font = text.Font.FontAsset; tmp.font = text.Font.FontAsset;
_tmp.fontStyle = text.Style | text.Font.FontStyle; tmp.fontStyle = text.Style | text.Font.FontStyle;
_tmp.lineSpacing = text.LineSpacing; tmp.lineSpacing = text.LineSpacing;
_tmp.characterSpacing = text.CharSpacing; tmp.characterSpacing = text.CharSpacing;
_tmp.extraPadding = true; tmp.extraPadding = true;
_tmp.enableKerning = true; tmp.enableKerning = true;
_tmp.enableWordWrapping = false; tmp.enableWordWrapping = false;
_tmp.overflowMode = TextOverflowModes.Overflow; tmp.overflowMode = TextOverflowModes.Overflow;
_tmp.alignment = TextAlignmentOptions.Center; tmp.alignment = TextAlignmentOptions.Center;
_tmp.fontSize = FontSize; tmp.fontSize = FontSize;
// GENERATE MESH // GENERATE MESH
_tmp.ClearMesh(false); tmp.ClearMesh(false);
_tmp.ForceMeshUpdate(); tmp.ForceMeshUpdate();
var meshFilters = gameObject.GetComponentsInChildren<MeshFilter>(); var meshFilters = tmpObject.GetComponentsInChildren<MeshFilter>();
var meshes = new Mesh[meshFilters.Length]; var meshes = new Mesh[meshFilters.Length];
var materials = new Material[meshFilters.Length]; var materials = new Material[meshFilters.Length];
@ -189,9 +95,9 @@ namespace ConformalDecals.Text {
var renderer = meshFilters[i].gameObject.GetComponent<MeshRenderer>(); var renderer = meshFilters[i].gameObject.GetComponent<MeshRenderer>();
meshes[i] = meshFilters[i].mesh; meshes[i] = meshFilters[i].mesh;
if (i == 0) meshes[i] = _tmp.mesh; if (i == 0) meshes[i] = tmp.mesh;
materials[i] = Instantiate(renderer.material); materials[i] = Object.Instantiate(renderer.material);
materials[i].shader = _blitShader; materials[i].shader = _blitShader;
if (renderer == null) throw new FormatException($"Object {meshFilters[i].gameObject.name} has filter but no renderer"); if (renderer == null) throw new FormatException($"Object {meshFilters[i].gameObject.name} has filter but no renderer");
@ -209,15 +115,16 @@ namespace ConformalDecals.Text {
var size = bounds.size * PixelDensity; var size = bounds.size * PixelDensity;
size.x = Mathf.Max(size.x, 0.1f); size.x = Mathf.Max(size.x, 0.1f);
size.y = Mathf.Max(size.y, 0.1f); size.y = Mathf.Max(size.y, 0.1f);
var textureSize = new Vector2Int { var textureSize = new Vector2Int {
x = Mathf.NextPowerOfTwo((int) size.x), x = Mathf.NextPowerOfTwo((int) size.x),
y = Mathf.NextPowerOfTwo((int) size.y) y = Mathf.NextPowerOfTwo((int) size.y)
}; };
if (textureSize.x == 0 || textureSize.y == 0) { if (textureSize.x == 0 || textureSize.y == 0) {
Logging.LogWarning("No text present or error in texture size calculation. Aborting."); Logging.LogError("No text present or error in texture size calculation. Aborting.");
return new TextRenderOutput(Texture2D.blackTexture, Rect.zero); Object.Destroy(tmpObject);
return new TextRenderOutput(_blankTexture, Rect.zero);
} }
// make sure texture isnt too big, scale it down if it is // make sure texture isnt too big, scale it down if it is
@ -242,12 +149,7 @@ namespace ConformalDecals.Text {
}; };
// SETUP TEXTURE // SETUP TEXTURE
if (texture == null) { var texture = new Texture2D(textureSize.x, textureSize.y, textTextureFormat, false);
texture = new Texture2D(textureSize.x, textureSize.y, textTextureFormat, true);
}
else if (texture.width != textureSize.x || texture.height != textureSize.y || texture.format != textTextureFormat) {
texture.Resize(textureSize.x, textureSize.y, textTextureFormat, true);
}
// GENERATE PROJECTION MATRIX // GENERATE PROJECTION MATRIX
var halfSize = (Vector2) textureSize / PixelDensity / 2 / sizeRatio; var halfSize = (Vector2) textureSize / PixelDensity / 2 / sizeRatio;
@ -255,8 +157,7 @@ namespace ConformalDecals.Text {
bounds.center.y - halfSize.y, bounds.center.y + halfSize.y, -1, 1); bounds.center.y - halfSize.y, bounds.center.y + halfSize.y, -1, 1);
// GET RENDERTEX // GET RENDERTEX
var renderTex = RenderTexture.GetTemporary(textureSize.x, textureSize.y, 0, textRenderTextureFormat, RenderTextureReadWrite.Linear, 1); var renderTex = RenderTexture.GetTemporary(textureSize.x, textureSize.y, 0, textRenderTextureFormat, RenderTextureReadWrite.Linear);
renderTex.autoGenerateMips = false;
// RENDER // RENDER
Graphics.SetRenderTarget(renderTex); Graphics.SetRenderTarget(renderTex);
@ -272,26 +173,42 @@ namespace ConformalDecals.Text {
} }
} }
// COPY TEXTURE BACK INTO RAM // COPY RENDERTEX INTO TEXTURE
var prevRT = RenderTexture.active;
RenderTexture.active = renderTex; RenderTexture.active = renderTex;
texture.ReadPixels(new Rect(0, 0, textureSize.x, textureSize.y), 0, 0, true); texture.ReadPixels(new Rect(0, 0, textureSize.x, textureSize.y), 0, 0, false);
texture.Apply(); texture.Apply(false, true);
RenderTexture.active = prevRT;
GL.Clear(false, true, Color.black); //KSP doesnt clear render textures before using them so we need to clear afterwards, as well. Thanks Squad.
GL.PopMatrix(); GL.PopMatrix();
// RELEASE RENDERTEX // RELEASE RENDERTEX
RenderTexture.ReleaseTemporary(renderTex); RenderTexture.ReleaseTemporary(renderTex);
// CLEAR SUBMESHES // DESTROY THE RENDERER OBJECT
_tmp.text = ""; Object.Destroy(tmpObject);
for (int i = 0; i < transform.childCount; i++) {
var child = transform.GetChild(i);
Destroy(child.gameObject);
}
return new TextRenderOutput(texture, window); return new TextRenderOutput(texture, window);
} }
/// Setup shader and texture
public static void ModuleManagerPostLoad() {
if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Direct3D11 || SystemInfo.graphicsDeviceType == GraphicsDeviceType.Direct3D12) {
textRenderTextureFormat = RenderTextureFormat.ARGB32; // DirectX is dumb
}
if (!SystemInfo.SupportsTextureFormat(textTextureFormat)) {
Logging.LogError($"Text texture format {textTextureFormat} not supported on this platform.");
}
if (!SystemInfo.SupportsRenderTextureFormat(textRenderTextureFormat)) {
Logging.LogError($"Text texture format {textRenderTextureFormat} not supported on this platform.");
}
_blankTexture = Texture2D.blackTexture;
_blitShader = Shabby.Shabby.FindShader(ShaderName);
if (_blitShader == null) Logging.LogError($"Could not find text blit shader named '{ShaderName}'");
}
} }
} }

View File

@ -0,0 +1,33 @@
using System;
using ConformalDecals.Util;
using UnityEngine;
namespace ConformalDecals.Tweakables {
[AttributeUsage(AttributeTargets.Field)]
public abstract class TweakableData : System.Attribute, ISerializationCallbackReceiver {
public string name;
public bool adjustable = true;
public string adjustableKey;
// public string fieldChangedCallback;
public bool useSymmetry = true;
protected TweakableData(string name) {
this.name = name;
adjustableKey = name + "Adjustable";
}
public virtual void Load(ConfigNode node) {
ParseUtil.ParseBoolIndirect(ref adjustable, node, adjustableKey);
}
public virtual void Apply(BaseField baseField, PartModule module) {
baseField.guiActiveEditor = adjustable;
}
public void OnBeforeSerialize() { }
public void OnAfterDeserialize() { }
}
}

View File

@ -0,0 +1,30 @@
using System.Collections;
using System.Collections.Generic;
using UniLinq;
using UnityEngine;
namespace ConformalDecals.Tweakables {
public class TweakableDataCollection : IEnumerable<TweakableData>, ISerializationCallbackReceiver {
public readonly Dictionary<string, TweakableData> tweakables = new Dictionary<string, TweakableData>();
[SerializeField] private TweakableData[] _serializedTweakables;
public IEnumerator<TweakableData> GetEnumerator() {
return tweakables.Values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
public void OnBeforeSerialize() {
_serializedTweakables = tweakables.Values.ToArray();
}
public void OnAfterDeserialize() {
foreach (var tweakable in _serializedTweakables) {
tweakables.Add(tweakable.name, tweakable);
}
}
}
}

View File

@ -0,0 +1,56 @@
using System;
using ConformalDecals.Util;
using UnityEngine;
namespace ConformalDecals.Tweakables {
[AttributeUsage(AttributeTargets.Field)]
public class TweakableSlider : TweakableData {
// The default value for the slider
public float defaultValue;
public string defaultValueKey;
// The range of the slider as a vector of <min, max>
public float min = 0;
public float max = 1;
public string rangeKey;
// The step size of the slider
public float step;
public string stepKey;
public TweakableSlider(string name) : base(name) {
defaultValueKey = name + "Default";
rangeKey = name + "Range";
stepKey = name + "Step";
}
public override void Load(ConfigNode node) {
base.Load(node);
var range = new Vector2(min, max);
ParseUtil.ParseVector2Indirect(ref range, node, rangeKey);
min = Mathf.Max(Mathf.Epsilon, range.x);
max = Mathf.Max(min, range.y);
ParseUtil.ParseFloatIndirect(ref step, node, stepKey);
if (!HighLogic.LoadedSceneIsEditor && !HighLogic.LoadedSceneIsFlight) {
ParseUtil.ParseFloatIndirect(ref defaultValue, node, defaultValueKey);
}
}
public override void Apply(BaseField baseField, PartModule module) {
base.Apply(baseField, module);
var uiControlEditor = (UI_FloatRange) baseField.uiControlEditor;
uiControlEditor.minValue = min;
uiControlEditor.maxValue = max;
uiControlEditor.stepIncrement = step;
// Set the default value on first load
if (!HighLogic.LoadedSceneIsEditor && !HighLogic.LoadedSceneIsFlight) {
baseField.FieldInfo.SetValue(module, defaultValue);
}
}
}
}

View File

@ -0,0 +1,34 @@
using System;
using System.Reflection;
using ConformalDecals.Util;
namespace ConformalDecals.Tweakables {
[AttributeUsage(AttributeTargets.Field)]
public class TweakableToggle : TweakableData {
// The default value for the toggle
public bool defaultValue;
public string defaultValueKey;
public TweakableToggle(string name) : base(name) {
defaultValueKey = name + "Default";
}
public override void Load(ConfigNode node) {
base.Load(node);
// Set the default value on first load
if (!HighLogic.LoadedSceneIsEditor && !HighLogic.LoadedSceneIsFlight) {
ParseUtil.ParseBoolIndirect(ref defaultValue, node, defaultValueKey);
}
}
public override void Apply(BaseField baseField, PartModule module) {
base.Apply(baseField, module);
// Set the default value on first load
if (!HighLogic.LoadedSceneIsEditor && !HighLogic.LoadedSceneIsFlight) {
baseField.FieldInfo.SetValue(module, defaultValue);
}
}
}
}

View File

@ -3,7 +3,6 @@ using ConformalDecals.Text;
using ConformalDecals.Util; using ConformalDecals.Util;
using TMPro; using TMPro;
using UnityEngine; using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI; using UnityEngine.UI;
namespace ConformalDecals.UI { namespace ConformalDecals.UI {
@ -35,14 +34,15 @@ namespace ConformalDecals.UI {
private Vector2 _lineSpacingRange; private Vector2 _lineSpacingRange;
private Vector2 _charSpacingRange; private Vector2 _charSpacingRange;
private TMP_InputField _textBoxTMP; private TMP_InputField _textBoxTMP;
private FontMenuController _fontMenu;
private TextUpdateDelegate _onValueChanged; private TextUpdateDelegate _onValueChanged;
private FontMenuController _fontMenu; private static int _lockCounter;
private bool _ignoreUpdates;
private bool _isLocked; private bool _isLocked;
private string _lockString; private string _lockString;
private static int _lockCounter; private bool _ignoreUpdates;
private bool _textUpdated;
public static TextEntryController Create( public static TextEntryController Create(
string text, DecalFont font, FontStyles style, bool vertical, float linespacing, float charspacing, string text, DecalFont font, FontStyles style, bool vertical, float linespacing, float charspacing,
@ -74,7 +74,7 @@ namespace ConformalDecals.UI {
public void SetControlLock(string value = null) { public void SetControlLock(string value = null) {
if (_isLocked) return; if (_isLocked) return;
InputLockManager.SetControlLock(_lockString); InputLockManager.SetControlLock(ControlTypes.EDITOR_UI, _lockString);
_isLocked = true; _isLocked = true;
} }
@ -86,8 +86,7 @@ namespace ConformalDecals.UI {
public void OnTextUpdate(string newText) { public void OnTextUpdate(string newText) {
this._text = newText; this._text = newText;
_textUpdated = true;
OnValueChanged();
} }
public void OnFontMenu() { public void OnFontMenu() {
@ -105,7 +104,7 @@ namespace ConformalDecals.UI {
_textBoxTMP.fontAsset = _font.FontAsset; _textBoxTMP.fontAsset = _font.FontAsset;
UpdateStyleButtons(); UpdateStyleButtons();
OnValueChanged(); _textUpdated = true;
} }
public void OnLineSpacingUpdate(float value) { public void OnLineSpacingUpdate(float value) {
@ -114,7 +113,7 @@ namespace ConformalDecals.UI {
_lineSpacing = Mathf.Lerp(_lineSpacingRange.x, _lineSpacingRange.y, value); _lineSpacing = Mathf.Lerp(_lineSpacingRange.x, _lineSpacingRange.y, value);
UpdateLineSpacing(); UpdateLineSpacing();
OnValueChanged(); _textUpdated = true;
} }
public void OnLineSpacingUpdate(string text) { public void OnLineSpacingUpdate(string text) {
@ -128,7 +127,7 @@ namespace ConformalDecals.UI {
} }
UpdateLineSpacing(); UpdateLineSpacing();
OnValueChanged(); _textUpdated = true;
} }
public void OnCharSpacingUpdate(float value) { public void OnCharSpacingUpdate(float value) {
@ -137,7 +136,7 @@ namespace ConformalDecals.UI {
_charSpacing = Mathf.Lerp(_charSpacingRange.x, _charSpacingRange.y, value); _charSpacing = Mathf.Lerp(_charSpacingRange.x, _charSpacingRange.y, value);
UpdateCharSpacing(); UpdateCharSpacing();
OnValueChanged(); _textUpdated = true;
} }
public void OnCharSpacingUpdate(string text) { public void OnCharSpacingUpdate(string text) {
@ -151,7 +150,7 @@ namespace ConformalDecals.UI {
} }
UpdateCharSpacing(); UpdateCharSpacing();
OnValueChanged(); _textUpdated = true;
} }
public void OnBoldUpdate(bool state) { public void OnBoldUpdate(bool state) {
@ -163,7 +162,7 @@ namespace ConformalDecals.UI {
_style &= ~FontStyles.Bold; _style &= ~FontStyles.Bold;
_textBoxTMP.textComponent.fontStyle = _style | _font.FontStyle & ~_font.FontStyleMask; _textBoxTMP.textComponent.fontStyle = _style | _font.FontStyle & ~_font.FontStyleMask;
OnValueChanged(); _textUpdated = true;
} }
public void OnItalicUpdate(bool state) { public void OnItalicUpdate(bool state) {
@ -175,7 +174,7 @@ namespace ConformalDecals.UI {
_style &= ~FontStyles.Italic; _style &= ~FontStyles.Italic;
_textBoxTMP.textComponent.fontStyle = _style | _font.FontStyle & ~_font.FontStyleMask; _textBoxTMP.textComponent.fontStyle = _style | _font.FontStyle & ~_font.FontStyleMask;
OnValueChanged(); _textUpdated = true;
} }
public void OnUnderlineUpdate(bool state) { public void OnUnderlineUpdate(bool state) {
@ -187,7 +186,7 @@ namespace ConformalDecals.UI {
_style &= ~FontStyles.Underline; _style &= ~FontStyles.Underline;
_textBoxTMP.textComponent.fontStyle = _style | _font.FontStyle & ~_font.FontStyleMask; _textBoxTMP.textComponent.fontStyle = _style | _font.FontStyle & ~_font.FontStyleMask;
OnValueChanged(); _textUpdated = true;
} }
public void OnSmallCapsUpdate(bool state) { public void OnSmallCapsUpdate(bool state) {
@ -199,19 +198,19 @@ namespace ConformalDecals.UI {
_style &= ~FontStyles.SmallCaps; _style &= ~FontStyles.SmallCaps;
_textBoxTMP.textComponent.fontStyle = _style | _font.FontStyle & ~_font.FontStyleMask; _textBoxTMP.textComponent.fontStyle = _style | _font.FontStyle & ~_font.FontStyleMask;
OnValueChanged(); _textUpdated = true;
} }
public void OnVerticalUpdate(bool state) { public void OnVerticalUpdate(bool state) {
if (_ignoreUpdates) return; if (_ignoreUpdates) return;
_vertical = state; _vertical = state;
OnValueChanged(); _textUpdated = true;
} }
private void Start() { private void Start() {
_lockString = $"ConformalDecals_TextEditor_{_lockCounter++}"; _lockString = $"ConformalDecals_TextEditor_{_lockCounter++}";
_textBoxTMP = ((TMP_InputField) _textBox); _textBoxTMP = ((TMP_InputField) _textBox);
_textBoxTMP.text = _text; _textBoxTMP.text = _text;
_textBoxTMP.textComponent.fontStyle = _style | _font.FontStyle & ~_font.FontStyleMask; _textBoxTMP.textComponent.fontStyle = _style | _font.FontStyle & ~_font.FontStyleMask;
@ -229,9 +228,12 @@ namespace ConformalDecals.UI {
private void OnDestroy() { private void OnDestroy() {
RemoveControlLock(); RemoveControlLock();
} }
private void OnValueChanged() { private void LateUpdate() {
_onValueChanged(_text, _font, _style, _vertical, _lineSpacing, _charSpacing); if (_textUpdated) {
_onValueChanged(_text, _font, _style, _vertical, _lineSpacing, _charSpacing);
_textUpdated = false;
}
} }
private void UpdateStyleButtons() { private void UpdateStyleButtons() {

View File

@ -31,7 +31,7 @@ namespace ConformalDecals.Util {
public static string ParseString(ConfigNode node, string valueName, bool isOptional = false, string defaultValue = "") { public static string ParseString(ConfigNode node, string valueName, bool isOptional = false, string defaultValue = "") {
if (node.HasValue(valueName)) return node.GetValue(valueName); if (node.HasValue(valueName)) return node.GetValue(valueName);
if (isOptional) { if (isOptional) {
return defaultValue; return defaultValue;
} }
@ -74,6 +74,14 @@ namespace ConformalDecals.Util {
return ParseValueIndirect(ref value, node, valueName, int.TryParse); return ParseValueIndirect(ref value, node, valueName, int.TryParse);
} }
public static uint ParseUint(ConfigNode node, string valueName, bool isOptional = false, uint defaultValue = 0) {
return ParseValue(node, valueName, uint.TryParse, isOptional, defaultValue);
}
public static bool ParseUintIndirect(ref uint value, ConfigNode node, string valueName) {
return ParseValueIndirect(ref value, node, valueName, uint.TryParse);
}
public static Color32 ParseColor32(ConfigNode node, string valueName, bool isOptional = false, Color32 defaultValue = default) { public static Color32 ParseColor32(ConfigNode node, string valueName, bool isOptional = false, Color32 defaultValue = default) {
return ParseValue(node, valueName, TryParseColor32, isOptional, defaultValue); return ParseValue(node, valueName, TryParseColor32, isOptional, defaultValue);
} }
@ -106,6 +114,14 @@ namespace ConformalDecals.Util {
return ParseValueIndirect(ref value, node, valueName, ParseExtensions.TryParseVector3); return ParseValueIndirect(ref value, node, valueName, ParseExtensions.TryParseVector3);
} }
public static Matrix4x4 ParseMatrix4x4(ConfigNode node, string valueName, bool isOptional = false, Matrix4x4 defaultValue = default) {
return ParseValue(node, valueName, ParseUtil.TryParseMatrix4x4, isOptional, defaultValue);
}
public static bool ParseMatrix4x4Indirect(ref Matrix4x4 value, ConfigNode node, string valueName) {
return ParseValueIndirect(ref value, node, valueName, ParseUtil.TryParseMatrix4x4);
}
public static T ParseValue<T>(ConfigNode node, string valueName, TryParseDelegate<T> tryParse, bool isOptional = false, T defaultValue = default) { public static T ParseValue<T>(ConfigNode node, string valueName, TryParseDelegate<T> tryParse, bool isOptional = false, T defaultValue = default) {
string valueString = node.GetValue(valueName); string valueString = node.GetValue(valueName);
@ -145,6 +161,26 @@ namespace ConformalDecals.Util {
throw new FormatException($"Improperly formatted {typeof(T)} value for {valueName} : '{valueString}"); throw new FormatException($"Improperly formatted {typeof(T)} value for {valueName} : '{valueString}");
} }
public static bool TryParseMatrix4x4(string valueString, out Matrix4x4 value) {
value = new Matrix4x4();
var split = valueString.Split(Separator, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < split.Length; i++) {
split[i] = split[i].Trim();
}
if (split.Length != 16) return false;
int index = 0;
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
if (!float.TryParse(split[index], out float component)) return false;
value[row, col] = component;
}
}
return true;
}
public static bool TryParseHexColor(string valueString, out Color32 value) { public static bool TryParseHexColor(string valueString, out Color32 value) {
value = new Color32(0, 0, 0, byte.MaxValue); value = new Color32(0, 0, 0, byte.MaxValue);

View File

@ -1,95 +0,0 @@
v0.2.4
------
- Fixes:
- Fixed red text appearing on planets due to KSP bug by clearing render textures afterwards.
- Fixed fonts not saving correctly.
- Changes:
- Lowered step size for decal size and depth to 1cm.
- Changed default max size to 5m.
- Changed default text decal size to 0.2m
- Text decals now show as a circle if they contain only whitespace.
v0.2.3
------
- Fixes:
- Fixed TMP subobjects being deleted, causing fallback fonts to fail in some situations.
- Started using URL-style encoding for text decals behind the scenes to prevent issues with certain characters.
- Fixed text decals having zero size when they had only whitespace or an empty string.
- Fixed decals having drag and causing issues when using FAR.
- Fixed broken saving of text decals in certain circumstances.
v0.2.2
------
- Fixes:
- Fixed corrupted text rendering when a vessel loads during a scene change.
v0.2.1
------
- Changes:
- Pressing enter in the text entry window now types a newline.
- Fixes:
- Renamed font assetbundle. The old extension was causing the game to try to load it twice on Windows due to legacy compatability features.
- Fixed text rendering on DirectX resulting in black boxes by using ARGB32 instead of RG16 for the render texture in DirectX.
v0.2.0
------
- New Parts:
- CDL-3 Surface Base Decal: A set of conformal decals based on the symbols from the movie Moon (2009) designed by Gavin Rothery
- CDL-T Custom Text Decal: A customizable text decal with a variety of fonts
- Changes:
- New ModuleConformalText module for customizable text
- Text, font, and style can all be customized, as well as text fill and outline colors and widths
- Same projection and opacity options as other conformal decals
- New StandardText decal shader supporting the text module
- Unified all decal shaders into a single "StandardDecal" shader with variants supporting any combination of bump, specular and emissive maps, plus SDF alphas.
- Old shaders are remapped to Standard shader plus keywords automatically.
- New SDF-based antialiasing for when decals extend to their borders, e.g. on opaque flags.
- New "KEYWORD" material modifier, allowing for shader features to be enabled and disabled.
- Material modifiers can now be removed in variants by setting `remove = true` inside them.
- Fixes:
- Fixed WIDTH and HEIGHT scale modes being flipped
- Removed some debug log statements
- Dependencies:
- Updated ModuleManager to version 4.1.4
v0.1.4
------
- Supported KSP versions: 1.8.x to 1.10.x
- Fixes:
- Fixed decals rendering onto disabled B9PS part variants
- Decals will still not update whan their parent part's B9PS variant is changed, both in flight and in the editor. This is known and awaiting a change to B9PS to be fixed.
- Fixed decal bounds rendering as dark cubes when shadowed by EVE clouds.
- Fixed decals being shadowed by EVE clouds, causing the part underneath to appear overly dark.
v0.1.3
------
Fixes:
- Fixed decals being able to be scaled down to 0
Changes:
- Made decal bounds no longer collide in flight, this is done by repurposing layer 31 (which is configurable in the ConformalDecals.cfg file)
- Decals will now be unselectable in flight by default. This can be disabled with the `selectableInFlight` value in ConformalDecals.cfg, or in the module config itself.
- Decal parts will now destroy themselves automatically when the parent part is destroyed
- Small refactor of node parsing code
- Colors can now be specified in hex (#RGB, #RGBA, #RRGGBB, or #RRGGBBAA) or using the colors specified in the XKCDColors class
v0.1.2
------
Fixes:
- Disabled writing to the zbuffer in the decal bounds shader. Should fix any issues with Scatterer or EVE.
v0.1.1
------
Fixes:
- Fixed flag decal not adjusting to new texture sizes immediately.
- Fixed decal bounds being visible on launch.
- Fixed decal bounds being visible in the part icon.
v0.1.0
------
Initial release!
New parts:
- CDL-F Flag Decal: Conformal flag decal, which uses either the mission flag or a flag of your choosing.
- CDL-1 Generic Decal: A set of conformal generic decals for planes and rockets
- CDL-2 Semiotic Standard Decal: A set of conformal decals based on the Semiotic Standard for All Commercial Trans-Stellar Utility Lifter and Transport Spacecraft designed by Ron Cobb for the movie Alien