Compare commits

...

72 Commits

Author SHA1 Message Date
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
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
dda988db17 Quick bugfix 2020-11-29 16:31:41 -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
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
a6de537e4a ScaleRange change 2020-11-16 15:44:40 -08:00
33b0307aa2 Lower text decal default size 2020-11-16 15:39:41 -08:00
121428414c Add input locks for text entry 2020-11-16 15:34:10 -08:00
98f774b2ae More font fixes and UI changes
- Lowered step size for decal size and depth to 1cm.
 - Changed default max size to 10m.
 - Text decals now show as a circle if they contain only whitespace.
 - Fixed font name in config and defaulted to Calibri if its missing
2020-11-16 15:05:55 -08:00
d31576fb0b Fix font saving and rendering 2020-11-16 14:44:07 -08:00
c9853049c5 Clear rendertex because KSP wont 2020-11-16 14:01:10 -08:00
40d4e2cc8a Update changelog 2020-11-14 21:26:16 -08:00
6f0c1f8ff5 Update Travis 2020-11-14 21:19:26 -08:00
4cf6de9693 Add defaults to text decal config 2020-11-14 21:17:54 -08:00
06137357c1 Close windows when text part is destroyed 2020-11-14 21:15:43 -08:00
dbbc621181 Fix text decal saving and loading
Closes #25
2020-11-14 21:11:16 -08:00
c6b9812fe3 Fix MM patch 2020-11-13 03:20:51 -08:00
8accabe9e5 Fix FAR incompatability 2020-11-13 02:22:56 -08:00
981a167864 Revert "Add fallbacks to all fonts, even squad ones"
This reverts commit ac1289a46e.
2020-11-13 01:55:42 -08:00
d4978b1b3c Update changelog 2020-11-13 01:44:52 -08:00
c42e443b4b Add minimum size for text decals 2020-11-13 01:43:08 -08:00
a6e2edc475 use URL-style string escaping 2020-11-13 01:42:55 -08:00
e82b02b0e5 Fix overlapping text and add text escaping 2020-11-12 21:25:24 -08:00
ea8c069d68 Fix issues with fallback fonts
Fixed TMP subobjects being deleted, causing fallback fonts to fail in some situations.
closes #24
2020-11-12 20:10:40 -08:00
ac1289a46e Add fallbacks to all fonts, even squad ones 2020-11-12 14:55:04 -08:00
e6288942ea Cleanup and fix onVariantApplied 2020-11-09 19:34:34 -08:00
16ef53ea65
remove lfs stuff from travis config 2020-10-14 20:08:25 -07:00
7b01848acc Merge branch 'release' into main 2020-10-11 16:55:18 -07:00
f37c3d57b7 For real this time 2020-10-11 16:49:59 -07:00
a937888c0d Fix deploy script 2020-10-11 16:48:10 -07:00
5065a8abf3 Merge branch 'main' into release 2020-10-11 16:21:34 -07:00
1ebed608a8 Fix corrupted text rendering
Fix text corruption when rendering text right after a scene change by delaying it by a single frame in OnLoad
2020-10-11 03:05:07 -07: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
880f463ee0 Merge branch 'master' into release 2020-10-04 16:15:45 -07:00
ea08bd84f0 Merge pull request #21 from drewcassidy/master
Release v0.2.0
2020-09-30 13:53:11 -07:00
3e157faae3 Merge branch 'master' into release 2020-07-01 15:55:31 -07:00
a569eaec51 Merge pull request #19 from drewcassidy/master
release v0.1.3
2020-06-20 00:12:55 -07:00
6360d222b0 Merge pull request #18 from drewcassidy/master
Release v0.1.2
2020-06-18 14:19:05 -07:00
6d33d6e524 Merge pull request #17 from drewcassidy/master
Release v0.1.1
2020-06-17 14:22:25 -07:00
e9f593cb0e Merge pull request #16 from drewcassidy/master
Release v0.1.1
2020-06-17 14:07:36 -07:00
682c831c7c Merge pull request #15 from drewcassidy/master
v0.1.0: take 3
2020-06-16 21:35:19 -07:00
6ab699fb23 Merge pull request #14 from drewcassidy/master
Version 0.1.0: take 2
2020-06-16 21:27:19 -07:00
443df8170e Merge pull request #13 from drewcassidy/master
Version 0.1.0
2020-06-16 21:14:08 -07:00
33 changed files with 1084 additions and 961 deletions

3
.gitignore vendored
View File

@ -49,4 +49,7 @@ Source/ConformalDecals/bin
.ds_store .ds_store
*.sublime* *.sublime*
.idea .idea
.vs
obj obj
*.swp
Source/.editorconfig

View File

@ -3,15 +3,13 @@ python:
- 3.6 - 3.6
before_install: before_install:
- echo -e "machine github.com\n login $GITHUB_OAUTH_TOKEN" > ~/.netrc - echo -e "machine github.com\n login $GITHUB_OAUTH_TOKEN" > ~/.netrc
- git lfs pull
- git lfs fetch --all
install: install:
- pip install awscli boto3 requests - pip install awscli boto3 requests
branches: branches:
only: only:
- release - release
script: script:
- git clone https://github.com/post-kerbin-mining-corporation/build-deploy.git - git clone https://github.com/post-kerbin-mining-corporation/build-deploy.git
- cd build-deploy - cd build-deploy
- git checkout master - git checkout master
- cd .. - cd ..
@ -24,4 +22,4 @@ deploy:
script: python build-deploy/src/deploy.py --f ".mod_data.yml" # Deploy package to spacedock, curse, github script: python build-deploy/src/deploy.py --f ".mod_data.yml" # Deploy package to spacedock, curse, github
skip_cleanup: true skip_cleanup: true
on: on:
branch: release branch: release

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

@ -52,11 +52,17 @@ PART
name = ModuleConformalText name = ModuleConformalText
text = Text text = Text
fontName = Calibri SDF
fillColor = #000000FF
outlineColor = #FFFFFFFF
fillEnabled = true
outlineEnabled = false
shader = ConformalDecals/Decal/Text shader = ConformalDecals/Decal/Text
useBaseNormal = true useBaseNormal = true
scaleMode = MINIMUM scaleMode = MINIMUM
defaultScale = 0.2
defaultDepth = 0.2 defaultDepth = 0.2
defaultCutoff = 0.5 defaultCutoff = 0.5
} }

View File

@ -0,0 +1,17 @@
// Decals are just paint, so they shouldnt affect a vessel's aerodynamics at all
@PART[*]:HAS[@MODULE[ModuleConformalDecal]]:After[FerramAerospaceResearch]
{
!MODULE[GeometryPartModule] {}
}
@PART[*]:HAS[@MODULE[ModuleConformalFlag]]:After[FerramAerospaceResearch]
{
!MODULE[GeometryPartModule] {}
}
@PART[*]:HAS[@MODULE[ModuleConformalText]]:After[FerramAerospaceResearch]
{
!MODULE[GeometryPartModule] {}
}

View File

@ -6,14 +6,14 @@
{ {
"MAJOR":0, "MAJOR":0,
"MINOR":2, "MINOR":2,
"PATCH":1, "PATCH":7,
"BUILD":0 "BUILD":0
}, },
"KSP_VERSION": "KSP_VERSION":
{ {
"MAJOR":1, "MAJOR":1,
"MINOR":10, "MINOR":11,
"PATCH":1 "PATCH":0
}, },
"KSP_VERSION_MIN":{ "KSP_VERSION_MIN":{
"MAJOR":1, "MAJOR":1,
@ -22,7 +22,7 @@
}, },
"KSP_VERSION_MAX":{ "KSP_VERSION_MAX":{
"MAJOR":1, "MAJOR":1,
"MINOR":10, "MINOR":11,
"PATCH":99 "PATCH":99
} }
} }

View File

@ -1,5 +1,5 @@
# Conformal Decals v0.2.1 # Conformal Decals v0.2.7
[![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) [![Build Status](https://travis-ci.com/drewcassidy/KSP-Conformal-Decals.svg?branch=release)](https://travis-ci.com/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)
![Screenshot](http://pileof.rocks/KSP/images/ConformalDecalsHeader.png) ![Screenshot](http://pileof.rocks/KSP/images/ConformalDecalsHeader.png)

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,116 +1,58 @@
<?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>0.2.7</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\DecalTextStyle.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'" />
</Target>
</Project> </Project>

View File

@ -3,7 +3,6 @@ using System.Collections.Generic;
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 {
@ -50,8 +49,6 @@ 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);
@ -92,12 +89,12 @@ namespace ConformalDecals {
_shaderBlacklist.Add(shaderName); _shaderBlacklist.Add(shaderName);
} }
} }
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 +111,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;
@ -133,7 +126,7 @@ namespace ConformalDecals {
var configs = GameDatabase.Instance.GetConfigs("CONFORMALDECALS"); var configs = GameDatabase.Instance.GetConfigs("CONFORMALDECALS");
if (configs.Length > 0) { if (configs.Length > 0) {
Logging.Log("loading config"); Logging.Log("Loading config");
foreach (var config in configs) { foreach (var config in configs) {
ParseConfig(config.config); ParseConfig(config.config);
} }

View File

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

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);
} }
} }

View File

@ -41,7 +41,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

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using ConformalDecals.MaterialProperties; using ConformalDecals.MaterialProperties;
using ConformalDecals.Util; using ConformalDecals.Util;
using UniLinq;
using UnityEngine; using UnityEngine;
namespace ConformalDecals { namespace ConformalDecals {
@ -31,7 +32,7 @@ namespace ConformalDecals {
[KSPField] public bool scaleAdjustable = true; [KSPField] public bool scaleAdjustable = true;
[KSPField] public float defaultScale = 1; [KSPField] public float defaultScale = 1;
[KSPField] public Vector2 scaleRange = new Vector2(0, 4); [KSPField] public Vector2 scaleRange = new Vector2(0, 5);
[KSPField] public DecalScaleMode scaleMode = DecalScaleMode.HEIGHT; [KSPField] public DecalScaleMode scaleMode = DecalScaleMode.HEIGHT;
@ -60,26 +61,28 @@ namespace ConformalDecals {
// INTERNAL VALUES // INTERNAL VALUES
[KSPField(guiName = "#LOC_ConformalDecals_gui-scale", guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "F2", guiUnits = "m"), [KSPField(guiName = "#LOC_ConformalDecals_gui-scale", guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "F2", guiUnits = "m"),
UI_FloatRange(stepIncrement = 0.05f)] UI_FloatRange()]
public float scale = 1.0f; public float scale = 1.0f;
[KSPField(guiName = "#LOC_ConformalDecals_gui-depth", guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "F2", guiUnits = "m"), [KSPField(guiName = "#LOC_ConformalDecals_gui-depth", guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "F2", guiUnits = "m"),
UI_FloatRange(stepIncrement = 0.02f)] UI_FloatRange()]
public float depth = 0.2f; public float depth = 0.2f;
[KSPField(guiName = "#LOC_ConformalDecals_gui-opacity", guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "P0"), [KSPField(guiName = "#LOC_ConformalDecals_gui-opacity", guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "P0"),
UI_FloatRange(stepIncrement = 0.05f)] UI_FloatRange()]
public float opacity = 1.0f; public float opacity = 1.0f;
[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(stepIncrement = 0.05f)] UI_FloatRange()]
public float cutoff = 0.5f; public float cutoff = 0.5f;
[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 [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 +99,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,6 +119,8 @@ namespace ConformalDecals {
} }
} }
// EVENTS
/// <inheritdoc /> /// <inheritdoc />
public override void OnAwake() { public override void OnAwake() {
base.OnAwake(); base.OnAwake();
@ -130,114 +135,27 @@ namespace ConformalDecals {
/// <inheritdoc /> /// <inheritdoc />
public override void OnLoad(ConfigNode node) { public override void OnLoad(ConfigNode node) {
// Load
try { try {
// SETUP TRANSFORMS LoadDecal(node);
decalFrontTransform = part.FindModelTransform(decalFront);
if (decalFrontTransform == null) throw new FormatException($"Could not find decalFront transform: '{decalFront}'.");
decalBackTransform = part.FindModelTransform(decalBack);
if (decalBackTransform == null) throw new FormatException($"Could not find decalBack transform: '{decalBack}'.");
decalModelTransform = part.FindModelTransform(decalModel);
if (decalModelTransform == null) throw new FormatException($"Could not find decalModel transform: '{decalModel}'.");
decalProjectorTransform = part.FindModelTransform(decalProjector);
if (decalProjectorTransform == null) throw new FormatException($"Could not find decalProjector transform: '{decalProjector}'.");
decalColliderTransform = part.FindModelTransform(decalCollider);
if (decalColliderTransform == null) throw new FormatException($"Could not find decalCollider transform: '{decalCollider}'.");
// SETUP BACK MATERIAL
if (updateBackScale) {
var backRenderer = decalBackTransform.GetComponent<MeshRenderer>();
if (backRenderer == null) {
this.LogError($"Specified decalBack transform {decalBack} has no renderer attached! Setting updateBackScale to false.");
updateBackScale = false;
}
else {
backMaterial = backRenderer.material;
if (backMaterial == null) {
this.LogError($"Specified decalBack transform {decalBack} has a renderer but no material! Setting updateBackScale to false.");
updateBackScale = false;
}
else {
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) { catch (Exception e) {
this.LogException("Exception parsing partmodule", e); this.LogException("Error loading decal", e);
} }
UpdateMaterials(); // Setup
try {
foreach (var keyword in _decalMaterial.shaderKeywords) { SetupDecal();
this.Log($"keyword: {keyword}");
} }
catch (Exception e) {
if (HighLogic.LoadedSceneIsEditor) { this.LogException("Error setting up decal", e);
UpdateTweakables();
}
if (HighLogic.LoadedSceneIsGame) {
UpdateScale();
}
else {
scale = defaultScale;
depth = defaultDepth;
opacity = defaultOpacity;
cutoff = defaultCutoff;
wear = defaultWear;
// QUEUE PART FOR ICON FIXING IN VAB
DecalIconFixer.QueuePart(part.name);
} }
} }
/// <inheritdoc /> /// <inheritdoc />
public override void OnIconCreate() { public override void OnIconCreate() {
UpdateScale(); UpdateTextures();
UpdateProjection();
} }
/// <inheritdoc /> /// <inheritdoc />
@ -255,6 +173,8 @@ namespace ConformalDecals {
} }
} }
/// 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) { public override void OnStartFinished(StartState state) {
// handle game events // handle game events
if (HighLogic.LoadedSceneIsGame) { if (HighLogic.LoadedSceneIsGame) {
@ -283,6 +203,7 @@ namespace ConformalDecals {
} }
} }
/// Called when the decal is destroyed
public virtual void OnDestroy() { public virtual void OnDestroy() {
// remove GameEvents // remove GameEvents
if (HighLogic.LoadedSceneIsEditor) { if (HighLogic.LoadedSceneIsEditor) {
@ -301,17 +222,21 @@ namespace ConformalDecals {
Destroy(materialProperties); Destroy(materialProperties);
} }
protected void OnSizeTweakEvent(BaseField field, object obj) { /// 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 // scale or depth values have been changed, so update scale
// and update projection matrices if attached // and update projection matrices if attached
UpdateScale(); UpdateProjection();
UpdateTargets();
foreach (var counterpart in part.symmetryCounterparts) { foreach (var counterpart in part.symmetryCounterparts) {
var decal = counterpart.GetComponent<ModuleConformalDecal>(); var decal = counterpart.GetComponent<ModuleConformalDecal>();
decal.UpdateScale(); decal.UpdateProjection();
decal.UpdateTargets();
} }
} }
/// Called when the decal's material is modified through a tweakable
protected void OnMaterialTweakEvent(BaseField field, object obj) { protected void OnMaterialTweakEvent(BaseField field, object obj) {
materialProperties.SetOpacity(opacity); materialProperties.SetOpacity(opacity);
materialProperties.SetCutoff(cutoff); materialProperties.SetCutoff(cutoff);
@ -329,35 +254,85 @@ namespace ConformalDecals {
} }
} }
/// Called when a new variant is applied in the editor
protected void OnVariantApplied(Part eventPart, PartVariant variant) { protected void OnVariantApplied(Part eventPart, PartVariant variant) {
if (_isAttached && eventPart == part.parent) { if (_isAttached && eventPart != null && (!projectMultiple || eventPart == part.parent)) {
_targets.Remove(eventPart);
UpdateTargets(); UpdateTargets();
} }
} }
/// Called when an editor event occurs
protected void OnEditorEvent(ConstructionEventType eventType, Part eventPart) { protected void OnEditorEvent(ConstructionEventType eventType, Part eventPart) {
if (this.part != eventPart && !part.symmetryCounterparts.Contains(eventPart)) return;
switch (eventType) { switch (eventType) {
case ConstructionEventType.PartAttached: case ConstructionEventType.PartAttached:
OnAttach(); OnPartAttached(eventPart);
break; break;
case ConstructionEventType.PartDetached: case ConstructionEventType.PartDetached:
OnDetach(); OnPartDetached(eventPart);
break; break;
case ConstructionEventType.PartOffsetting: case ConstructionEventType.PartOffsetting:
case ConstructionEventType.PartRotating: case ConstructionEventType.PartRotating:
UpdateScale(); OnPartTransformed(eventPart);
break; break;
} }
} }
/// Called when a part is transformed in the editor
protected void OnPartTransformed(Part eventPart) {
if (this.part == eventPart) {
UpdateProjection();
UpdateTargets();
}
else if (_isAttached && projectMultiple) {
UpdatePartTarget(eventPart, _boundsRenderer.bounds);
// recursively call for child parts
foreach (var child in eventPart.children) {
OnPartTransformed(child);
}
}
}
/// Called when a part is attached in the editor
protected void OnPartAttached(Part eventPart) {
if (this.part == eventPart) {
OnAttach();
}
else if (_isAttached && projectMultiple) {
UpdatePartTarget(eventPart, _boundsRenderer.bounds);
// recursively call for child parts
foreach (var child in eventPart.children) {
OnPartAttached(child);
}
}
}
/// Called when a part is detached in the editor
protected void OnPartDetached(Part eventPart) {
if (this.part == eventPart) {
OnDetach();
}
else if (_isAttached && projectMultiple) {
_targets.Remove(eventPart);
// recursively call for child parts
foreach (var child in eventPart.children) {
OnPartDetached(child);
}
}
}
/// Called when part `willDie` will be destroyed
protected void OnPartWillDie(Part willDie) { protected void OnPartWillDie(Part willDie) {
if (willDie == part.parent) { if (willDie == part.parent) {
this.Log("Parent part about to be destroyed! Killing decal part."); this.Log("Parent part about to be destroyed! Killing decal part.");
part.Die(); part.Die();
} }
else if (_isAttached && projectMultiple) {
_targets.Remove(willDie);
}
} }
/// Called when decal is attached to a new part
protected virtual void OnAttach() { protected virtual void OnAttach() {
if (part.parent == null) { if (part.parent == null) {
this.LogError("Attach function called but part has no parent!"); this.LogError("Attach function called but part has no parent!");
@ -366,6 +341,7 @@ namespace ConformalDecals {
} }
_isAttached = true; _isAttached = true;
_targets.Clear();
// hide model // hide model
decalModelTransform.gameObject.SetActive(false); decalModelTransform.gameObject.SetActive(false);
@ -377,10 +353,11 @@ namespace ConformalDecals {
Camera.onPreCull += Render; Camera.onPreCull += Render;
UpdateMaterials(); UpdateMaterials();
UpdateProjection();
UpdateTargets(); UpdateTargets();
UpdateScale();
} }
/// Called when decal is detached from its parent part
protected virtual void OnDetach() { protected virtual void OnDetach() {
_isAttached = false; _isAttached = false;
@ -394,110 +371,94 @@ namespace ConformalDecals {
Camera.onPreCull -= Render; Camera.onPreCull -= Render;
UpdateMaterials(); UpdateMaterials();
UpdateScale(); UpdateProjection();
} }
protected void UpdateScale() { // FUNCTIONS
scale = Mathf.Max(0.01f, scale);
depth = Mathf.Max(0.01f, depth);
var aspectRatio = materialProperties.AspectRatio;
Vector2 size;
switch (scaleMode) { /// Load any settings from the decal config
default: protected virtual void LoadDecal(ConfigNode node) {
case DecalScaleMode.HEIGHT: // PARSE TRANSFORMS
size = new Vector2(scale / aspectRatio, scale); decalFrontTransform = part.FindModelTransform(decalFront);
break; if (decalFrontTransform == null) throw new FormatException($"Could not find decalFront transform: '{decalFront}'.");
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 decalBackTransform = part.FindModelTransform(decalBack);
materialProperties.UpdateScale(size); if (decalBackTransform == null) throw new FormatException($"Could not find decalBack transform: '{decalBack}'.");
if (_isAttached) { decalModelTransform = part.FindModelTransform(decalModel);
// update orthogonal matrix if (decalModelTransform == null) throw new FormatException($"Could not find decalModel transform: '{decalModel}'.");
_orthoMatrix = Matrix4x4.identity;
_orthoMatrix[0, 3] = 0.5f;
_orthoMatrix[1, 3] = 0.5f;
decalProjectorTransform.localScale = new Vector3(size.x, size.y, depth); decalProjectorTransform = part.FindModelTransform(decalProjector);
if (decalProjectorTransform == null) throw new FormatException($"Could not find decalProjector transform: '{decalProjector}'.");
// update projection decalColliderTransform = part.FindModelTransform(decalCollider);
foreach (var target in _targets) { if (decalColliderTransform == null) throw new FormatException($"Could not find decalCollider transform: '{decalCollider}'.");
target.Project(_orthoMatrix, decalProjectorTransform, _boundsRenderer.bounds, useBaseNormal);
// SETUP BACK MATERIAL
if (updateBackScale) {
var backRenderer = decalBackTransform.GetComponent<MeshRenderer>();
if (backRenderer == null) {
this.LogError($"Specified decalBack transform {decalBack} has no renderer attached! Setting updateBackScale to false.");
updateBackScale = false;
}
else {
backMaterial = backRenderer.material;
if (backMaterial == null) {
this.LogError($"Specified decalBack transform {decalBack} has a renderer but no material! Setting updateBackScale to false.");
updateBackScale = false;
}
else {
if (backTextureBaseScale == default) backTextureBaseScale = backMaterial.GetTextureScale(PropertyIDs._MainTex);
}
} }
} }
else {
// rescale preview model
decalModelTransform.localScale = new Vector3(size.x, size.y, (size.x + size.y) / 2);
// update back material scale // PARSE MATERIAL PROPERTIES
if (updateBackScale) { // set shader
backMaterial.SetTextureScale(PropertyIDs._MainTex, new Vector2(size.x * backTextureBaseScale.x, size.y * backTextureBaseScale.y)); materialProperties.SetShader(shader);
} materialProperties.AddOrGetProperty<MaterialKeywordProperty>("DECAL_BASE_NORMAL").value = useBaseNormal;
materialProperties.Load(node);
// 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);
} }
} }
protected virtual void UpdateMaterials() { /// Setup decal by calling update functions relevent for the current situation
materialProperties.UpdateMaterials(); protected virtual void SetupDecal() {
materialProperties.SetOpacity(opacity); if (HighLogic.LoadedSceneIsEditor) {
materialProperties.SetCutoff(cutoff); // Update tweakables in editor mode
if (useBaseNormal) { UpdateTweakables();
materialProperties.SetWear(wear);
} }
_decalMaterial = materialProperties.DecalMaterial; if (HighLogic.LoadedSceneIsGame) {
_previewMaterial = materialProperties.PreviewMaterial; UpdateAll();
if (!_isAttached) decalFrontTransform.GetComponent<MeshRenderer>().material = _previewMaterial;
}
protected void UpdateTargets() {
if (_targets == null) {
_targets = new List<ProjectionTarget>();
} }
else { else {
_targets.Clear(); scale = defaultScale;
} depth = defaultDepth;
opacity = defaultOpacity;
cutoff = defaultCutoff;
wear = defaultWear;
// find all valid renderers UpdateAll();
var renderers = part.parent.FindModelComponents<MeshRenderer>();
foreach (var renderer in renderers) {
// skip disabled renderers
if (renderer.gameObject.activeInHierarchy == false) continue;
// skip blacklisted shaders // QUEUE PART FOR ICON FIXING IN VAB
if (DecalConfig.IsBlacklisted(renderer.material.shader)) continue; DecalIconFixer.QueuePart(part.name);
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);
} }
} }
/// Update decal editor tweakables
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 +466,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;
@ -512,7 +474,7 @@ namespace ConformalDecals {
cutoffField.guiActiveEditor = cutoffAdjustable; cutoffField.guiActiveEditor = cutoffAdjustable;
wearField.guiActiveEditor = useBaseNormal; wearField.guiActiveEditor = useBaseNormal;
var steps = 40; var steps = 20;
if (scaleAdjustable) { if (scaleAdjustable) {
var minValue = Mathf.Max(Mathf.Epsilon, scaleRange.x); var minValue = Mathf.Max(Mathf.Epsilon, scaleRange.x);
@ -521,8 +483,8 @@ namespace ConformalDecals {
var scaleEditor = (UI_FloatRange) scaleField.uiControlEditor; var scaleEditor = (UI_FloatRange) scaleField.uiControlEditor;
scaleEditor.minValue = minValue; scaleEditor.minValue = minValue;
scaleEditor.maxValue = maxValue; scaleEditor.maxValue = maxValue;
scaleEditor.stepIncrement = (maxValue - minValue) / steps; scaleEditor.stepIncrement = 0.01f; //1cm
scaleEditor.onFieldChanged = OnSizeTweakEvent; scaleEditor.onFieldChanged = OnProjectionTweakEvent;
} }
if (depthAdjustable) { if (depthAdjustable) {
@ -532,8 +494,8 @@ namespace ConformalDecals {
var depthEditor = (UI_FloatRange) depthField.uiControlEditor; var depthEditor = (UI_FloatRange) depthField.uiControlEditor;
depthEditor.minValue = minValue; depthEditor.minValue = minValue;
depthEditor.maxValue = maxValue; depthEditor.maxValue = maxValue;
depthEditor.stepIncrement = (maxValue - minValue) / steps; depthEditor.stepIncrement = 0.01f; //1cm
depthEditor.onFieldChanged = OnSizeTweakEvent; depthEditor.onFieldChanged = OnProjectionTweakEvent;
} }
if (opacityAdjustable) { if (opacityAdjustable) {
@ -570,13 +532,140 @@ 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 textures, materials, scale and targets
protected virtual void UpdateAll() {
UpdateTextures();
UpdateMaterials();
UpdateProjection();
UpdateTargets();
}
/// Update decal textures
protected virtual void UpdateTextures() { }
/// Update decal materials
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;
}
/// 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);
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);
}
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));
}
}
}
/// Called when updating decal targets
protected void UpdateTargets() {
if (!_isAttached) return;
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);
}
}
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,5 @@
using ConformalDecals.Util; using ConformalDecals.MaterialProperties;
using UniLinq;
using UnityEngine; using UnityEngine;
namespace ConformalDecals { namespace ConformalDecals {
@ -9,6 +10,8 @@ namespace ConformalDecals {
[KSPField(isPersistant = true)] public bool useCustomFlag; [KSPField(isPersistant = true)] public bool useCustomFlag;
private MaterialTextureProperty _flagTextureProperty;
public string MissionFlagUrl { public string MissionFlagUrl {
get { get {
if (HighLogic.LoadedSceneIsEditor) { if (HighLogic.LoadedSceneIsEditor) {
@ -23,17 +26,6 @@ namespace ConformalDecals {
} }
} }
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); base.OnStart(state);
@ -44,66 +36,60 @@ namespace ConformalDecals {
if (HighLogic.LoadedSceneIsEditor) { if (HighLogic.LoadedSceneIsEditor) {
Events[nameof(ResetFlag)].guiActiveEditor = useCustomFlag; Events[nameof(ResetFlag)].guiActiveEditor = useCustomFlag;
} }
if (useCustomFlag) {
SetFlag(flagUrl);
}
else {
SetFlag(MissionFlagUrl);
}
} }
public override void OnDestroy() { public override void OnDestroy() {
GameEvents.onMissionFlagSelect.Remove(SetFlag); 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 SelectFlag() {
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; if (flagBrowser is { }) 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 ResetFlag() {
SetFlag(MissionFlagUrl);
SetFlagSymmetryCounterparts(MissionFlagUrl);
useCustomFlag = false;
Events[nameof(ResetFlag)].guiActiveEditor = false; Events[nameof(ResetFlag)].guiActiveEditor = false;
} flagUrl = MissionFlagUrl;
useCustomFlag = false;
private void OnCustomFlagSelected(FlagBrowser.FlagEntry newFlagEntry) { UpdateAll();
SetFlag(newFlagEntry.textureInfo.name); foreach (var decal in part.symmetryCounterparts.Select(o => o.GetComponent<ModuleConformalFlag>())) {
SetFlagSymmetryCounterparts(newFlagEntry.textureInfo.name); decal.Events[nameof(ResetFlag)].guiActiveEditor = false;
decal.flagUrl = flagUrl;
useCustomFlag = true; decal.useCustomFlag = false;
Events[nameof(ResetFlag)].guiActiveEditor = true; decal.UpdateAll();
}
private void OnEditorFlagSelected(string newFlagUrl) {
if (!useCustomFlag) {
SetFlag(newFlagUrl);
SetFlagSymmetryCounterparts(newFlagUrl);
} }
} }
private void SetFlag(string newFlagUrl) { private void OnCustomFlagSelected(FlagBrowser.FlagEntry newFlagEntry) {
this.Log($"Loading flag texture '{newFlagUrl}'."); Events[nameof(ResetFlag)].guiActiveEditor = true;
flagUrl = newFlagEntry.textureInfo.name;
useCustomFlag = true;
UpdateAll();
flagUrl = newFlagUrl; foreach (var decal in part.symmetryCounterparts.Select(o => o.GetComponent<ModuleConformalFlag>())) {
materialProperties.AddOrGetTextureProperty("_Decal", true).TextureUrl = newFlagUrl; decal.Events[nameof(ResetFlag)].guiActiveEditor = true;
decal.flagUrl = flagUrl;
UpdateMaterials(); decal.useCustomFlag = true;
UpdateScale(); decal.UpdateAll();
}
} }
private void SetFlagSymmetryCounterparts(string newFlagUrl) { private void OnEditorFlagSelected(string newFlagUrl) {
foreach (var counterpart in part.symmetryCounterparts) { if (!useCustomFlag) UpdateAll();
var decal = counterpart.GetComponent<ModuleConformalFlag>(); }
decal.SetFlag(newFlagUrl); protected override void UpdateTextures() {
decal.useCustomFlag = useCustomFlag; _flagTextureProperty ??= materialProperties.AddOrGetTextureProperty("_Decal", true);
base.UpdateTextures();
if (useCustomFlag) {
_flagTextureProperty.TextureUrl = flagUrl;
}
else {
_flagTextureProperty.TextureUrl = MissionFlagUrl;
} }
} }
} }

View File

@ -1,32 +1,34 @@
using System.Collections;
using System.Net;
using ConformalDecals.MaterialProperties; using ConformalDecals.MaterialProperties;
using ConformalDecals.Text; 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 {
public class ModuleConformalText : ModuleConformalDecal, ISerializationCallbackReceiver { public class ModuleConformalText : ModuleConformalDecal {
[KSPField(isPersistant = true)] public string text = "Text";
[KSPField] public Vector2 lineSpacingRange = new Vector2(-50, 50); [KSPField] public Vector2 lineSpacingRange = new Vector2(-50, 50);
[KSPField] public Vector2 charSpacingRange = new Vector2(-50, 50); [KSPField] public Vector2 charSpacingRange = new Vector2(-50, 50);
// serialization-only fields. do not use except in serialization functions [KSPField(isPersistant = true)] public bool vertical;
[KSPField(isPersistant = true)] public string fontName = "Calibri SDF"; [KSPField(isPersistant = true)] public float lineSpacing;
[KSPField(isPersistant = true)] public int style; [KSPField(isPersistant = true)] public float charSpacing;
[KSPField(isPersistant = true)] public bool vertical;
[KSPField(isPersistant = true)] public float lineSpacing; [KSPField] public string text;
[KSPField(isPersistant = true)] public float charSpacing; [KSPField] public DecalFont font;
[KSPField(isPersistant = true)] public string fillColor = "000000FF"; [KSPField] public FontStyles style;
[KSPField(isPersistant = true)] public string outlineColor = "FFFFFFFF"; [KSPField] public Color32 fillColor = Color.black;
[KSPField] public Color32 outlineColor = Color.white;
// KSP TWEAKABLES // KSP TWEAKABLES
[KSPEvent(guiName = "#LOC_ConformalDecals_gui-set-text", guiActive = false, guiActiveEditor = true)] [KSPEvent(guiName = "#LOC_ConformalDecals_gui-set-text", guiActive = false, guiActiveEditor = true)]
public void SetText() { public void SetText() {
if (_textEntryController == null) { if (_textEntryController == null) {
_textEntryController = TextEntryController.Create(text, _font, _style, lineSpacingRange, charSpacingRange, OnTextUpdate); _textEntryController = TextEntryController.Create(text, font, style, vertical, lineSpacing, charSpacing, lineSpacingRange, charSpacingRange, OnTextUpdate);
} }
else { else {
_textEntryController.Close(); _textEntryController.Close();
@ -44,7 +46,7 @@ namespace ConformalDecals {
guiActive = false, guiActiveEditor = true)] guiActive = false, guiActiveEditor = true)]
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();
@ -67,17 +69,13 @@ namespace ConformalDecals {
guiActive = false, guiActiveEditor = true)] guiActive = false, guiActiveEditor = true)]
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();
} }
} }
private DecalTextStyle _style;
private DecalFont _font;
private Color32 _fillColor;
private Color32 _outlineColor;
private TextEntryController _textEntryController; private TextEntryController _textEntryController;
private ColorPickerController _fillColorPickerController; private ColorPickerController _fillColorPickerController;
@ -92,27 +90,20 @@ 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);
OnAfterDeserialize();
UpdateTextRecursive();
}
/// <inheritdoc />
public override void OnSave(ConfigNode node) { public override void OnSave(ConfigNode node) {
OnBeforeSerialize(); node.AddValue("text", WebUtility.UrlEncode(text));
node.AddValue("fontName", font.Name);
node.AddValue("style", (int) style);
node.AddValue("fillColor", fillColor.ToHexString());
node.AddValue("outlineColor", outlineColor.ToHexString());
base.OnSave(node); base.OnSave(node);
} }
public override void OnStart(StartState state) {
base.OnStart(state);
UpdateTextRecursive();
}
public override void OnAwake() { public override void OnAwake() {
base.OnAwake(); base.OnAwake();
@ -126,31 +117,44 @@ namespace ConformalDecals {
_outlineWidthProperty = materialProperties.AddOrGetProperty<MaterialFloatProperty>("_OutlineWidth"); _outlineWidthProperty = materialProperties.AddOrGetProperty<MaterialFloatProperty>("_OutlineWidth");
} }
public void OnTextUpdate(string newText, DecalFont newFont, DecalTextStyle newStyle) { public void OnTextUpdate(string newText, DecalFont newFont, FontStyles newStyle, bool newVertical, float newLineSpacing, float newCharSpacing) {
text = newText; text = newText;
_font = newFont; font = newFont;
_style = newStyle; style = newStyle;
UpdateTextRecursive(); vertical = newVertical;
lineSpacing = newLineSpacing;
charSpacing = newCharSpacing;
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) {
_fillColor = rgb; fillColor = rgb;
UpdateMaterials(); UpdateMaterials();
foreach (var counterpart in part.symmetryCounterparts) { foreach (var counterpart in part.symmetryCounterparts) {
var decal = counterpart.GetComponent<ModuleConformalText>(); var decal = counterpart.GetComponent<ModuleConformalText>();
decal._fillColor = _fillColor; decal.fillColor = fillColor;
decal.UpdateMaterials(); decal.UpdateMaterials();
} }
} }
public void OnOutlineColorUpdate(Color rgb, Util.ColorHSV hsv) { public void OnOutlineColorUpdate(Color rgb, Util.ColorHSV hsv) {
_outlineColor = rgb; outlineColor = rgb;
UpdateMaterials(); UpdateMaterials();
foreach (var counterpart in part.symmetryCounterparts) { foreach (var counterpart in part.symmetryCounterparts) {
var decal = counterpart.GetComponent<ModuleConformalText>(); var decal = counterpart.GetComponent<ModuleConformalText>();
decal._outlineColor = _outlineColor; decal.outlineColor = outlineColor;
decal.UpdateMaterials(); decal.UpdateMaterials();
} }
} }
@ -197,34 +201,14 @@ namespace ConformalDecals {
} }
} }
public void OnBeforeSerialize() {
fontName = _font.Name;
style = (int) _style.FontStyle;
vertical = _style.Vertical;
lineSpacing = _style.LineSpacing;
charSpacing = _style.CharSpacing;
fillColor = _fillColor.ToHexString();
outlineColor = _outlineColor.ToHexString();
}
public void OnAfterDeserialize() {
_font = DecalConfig.GetFont(fontName);
_style = new DecalTextStyle((FontStyles) style, vertical, lineSpacing, charSpacing);
if (!ParseUtil.TryParseColor32(fillColor, out _fillColor)) {
Logging.LogWarning($"Improperly formatted color value for fill: '{fillColor}'");
_fillColor = Color.magenta;
}
if (!ParseUtil.TryParseColor32(outlineColor, out _outlineColor)) {
Logging.LogWarning($"Improperly formatted color value for outline: '{outlineColor}'");
_outlineColor = Color.magenta;
}
}
public override void OnDestroy() { public override void OnDestroy() {
if (HighLogic.LoadedSceneIsGame && _currentText != null) TextRenderer.UnregisterText(_currentText); if (HighLogic.LoadedSceneIsGame && _currentText != null) TextRenderer.UnregisterText(_currentText);
// close all UIs
if (_textEntryController != null) _textEntryController.Close();
if (_fillColorPickerController != null) _fillColorPickerController.Close();
if (_outlineColorPickerController != null) _outlineColorPickerController.Close();
base.OnDestroy(); base.OnDestroy();
} }
@ -237,49 +221,78 @@ 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.LoadedSceneIsEditor) {
// Update tweakables in editor mode
UpdateTweakables();
}
if (HighLogic.LoadedSceneIsGame) {
// For some reason text rendering fails on the first frame of a scene, so this is my workaround
StartCoroutine(UpdateTextLate());
}
else {
scale = defaultScale;
depth = defaultDepth;
opacity = defaultOpacity;
cutoff = defaultCutoff;
wear = defaultWear;
UpdateTextures();
UpdateMaterials();
UpdateProjection();
// QUEUE PART FOR ICON FIXING IN VAB
DecalIconFixer.QueuePart(part.name);
} }
} }
private void UpdateText() { private IEnumerator UpdateTextLate() {
// Render text yield return null;
var newText = new DecalText(text, _font, _style); UpdateAll();
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) { protected override void UpdateTextures() {
// Render text
var newText = new DecalText(text, font, style, vertical, lineSpacing, charSpacing);
var output = TextRenderer.UpdateText(_currentText, newText);
_currentText = newText;
_decalTextureProperty.Texture = output.Texture; _decalTextureProperty.Texture = output.Texture;
_decalTextureProperty.SetTile(output.Window); _decalTextureProperty.SetTile(output.Window);
UpdateMaterials();
UpdateScale();
} }
protected override void UpdateMaterials() { protected override void UpdateMaterials() {
_fillEnabledProperty.value = fillEnabled; _fillEnabledProperty.value = fillEnabled;
_fillColorProperty.color = _fillColor; _fillColorProperty.color = fillColor;
_outlineEnabledProperty.value = outlineEnabled; _outlineEnabledProperty.value = outlineEnabled;
_outlineColorProperty.color = _outlineColor; _outlineColorProperty.color = outlineColor;
_outlineWidthProperty.value = outlineWidth; _outlineWidthProperty.value = outlineWidth;
base.UpdateMaterials(); base.UpdateMaterials();

View File

@ -0,0 +1,93 @@
using UnityEngine;
using UnityEngine.Rendering;
namespace ConformalDecals {
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 {
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

@ -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

@ -3,58 +3,68 @@ using System.Collections.Generic;
using ConformalDecals.Util; using ConformalDecals.Util;
using TMPro; using TMPro;
using UniLinq; using UniLinq;
using UnityEngine;
namespace ConformalDecals.Text { namespace ConformalDecals.Text {
public class DecalFont : IEquatable<DecalFont> { public class DecalFont : ScriptableObject, ISerializationCallbackReceiver, IEquatable<DecalFont> {
[SerializeField] private string _title;
[SerializeField] private TMP_FontAsset _fontAsset;
[SerializeField] private FontStyles _fontStyle;
[SerializeField] private FontStyles _fontStyleMask;
/// Human-readable name for the font /// Human-readable name for the font
public string Title { get; } public string Title => _title;
/// Internal name for the font /// Internal name for the font
public string Name => FontAsset.name; public string Name => _fontAsset.name;
/// The font asset itself /// The font asset itself
public TMP_FontAsset FontAsset { get; } public TMP_FontAsset FontAsset => _fontAsset;
/// Styles that are forced on for this font, /// Styles that are forced on for this font,
/// e.g. smallcaps for a font without lower case characters /// e.g. smallcaps for a font without lower case characters
public FontStyles FontStyle { get; } public FontStyles FontStyle => _fontStyle;
public bool Bold => (FontStyle & FontStyles.Bold) != 0; public bool Bold => (_fontStyle & FontStyles.Bold) != 0;
public bool Italic => (FontStyle & FontStyles.Italic) != 0; public bool Italic => (_fontStyle & FontStyles.Italic) != 0;
public bool Underline => (FontStyle & FontStyles.Underline) != 0; public bool Underline => (_fontStyle & FontStyles.Underline) != 0;
public bool SmallCaps => (FontStyle & FontStyles.SmallCaps) != 0; public bool SmallCaps => (_fontStyle & FontStyles.SmallCaps) != 0;
/// Styles that are forced off for this font, /// Styles that are forced off for this font,
/// e.g. underline for a font with no underscore character /// e.g. underline for a font with no underscore character
public FontStyles FontStyleMask { get; } public FontStyles FontStyleMask => _fontStyleMask;
public bool BoldMask => (FontStyleMask & FontStyles.Bold) != 0; public bool BoldMask => (_fontStyleMask & FontStyles.Bold) != 0;
public bool ItalicMask => (FontStyleMask & FontStyles.Italic) != 0; public bool ItalicMask => (_fontStyleMask & FontStyles.Italic) != 0;
public bool UnderlineMask => (FontStyleMask & FontStyles.Underline) != 0; public bool UnderlineMask => (_fontStyleMask & FontStyles.Underline) != 0;
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));
@ -95,5 +105,13 @@ namespace ConformalDecals.Text {
public static bool operator !=(DecalFont left, DecalFont right) { public static bool operator !=(DecalFont left, DecalFont right) {
return !Equals(left, right); return !Equals(left, right);
} }
public void OnBeforeSerialize() { }
public void OnAfterDeserialize() { }
public override string ToString() {
return _title;
}
} }
} }

View File

@ -1,21 +1,39 @@
using System; using System;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using TMPro;
namespace ConformalDecals.Text { namespace ConformalDecals.Text {
public class DecalText : IEquatable<DecalText> { public class DecalText : IEquatable<DecalText> {
private readonly string _text;
private readonly DecalFont _font;
private readonly FontStyles _style;
private readonly bool _vertical;
private readonly float _lineSpacing;
private readonly float _charSpacing;
/// Raw text contents /// Raw text contents
public string Text { get; } public string Text => _text;
/// Font asset used by this text snippet /// Font asset used by this text snippet
public DecalFont Font { get; } public DecalFont Font => _font;
/// Style used by this text snippet /// Style used by this text snippet
public DecalTextStyle Style { get; } public FontStyles Style => _style;
/// If this text snippet is vertical
public bool Vertical => _vertical;
/// The text snippet's line spacing
public float LineSpacing => _lineSpacing;
/// The text snippet's character spacing
public float CharSpacing => _charSpacing;
/// The text formatted with newlines for vertical text /// The text formatted with newlines for vertical text
public string FormattedText { public string FormattedText {
get { get {
if (Style.Vertical) { if (string.IsNullOrWhiteSpace(Text)) return "•";
if (Vertical) {
return Regex.Replace(Text, @"(.)", "$1\n"); return Regex.Replace(Text, @"(.)", "$1\n");
} }
else { else {
@ -24,17 +42,22 @@ namespace ConformalDecals.Text {
} }
} }
public DecalText(string text, DecalFont font, DecalTextStyle style) {
public DecalText(string text, DecalFont font, FontStyles style, bool vertical, float linespacing, float charspacing) {
if (font == null) throw new ArgumentNullException(nameof(font)); if (font == null) throw new ArgumentNullException(nameof(font));
Text = text; _text = text;
Font = font; _font = font;
Style = style; _style = style;
_vertical = vertical;
_lineSpacing = linespacing;
_charSpacing = charspacing;
} }
public bool Equals(DecalText other) { public bool Equals(DecalText other) {
if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true; if (ReferenceEquals(this, other)) return true;
return Text == other.Text && Equals(Font, other.Font) && Style.Equals(other.Style); return _text == other._text && Equals(_font, other._font) && _style == other._style && _vertical == other._vertical && _lineSpacing.Equals(other._lineSpacing) &&
_charSpacing.Equals(other._charSpacing);
} }
public override bool Equals(object obj) { public override bool Equals(object obj) {
@ -46,9 +69,12 @@ namespace ConformalDecals.Text {
public override int GetHashCode() { public override int GetHashCode() {
unchecked { unchecked {
var hashCode = (Text != null ? Text.GetHashCode() : 0); var hashCode = (_text != null ? _text.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (Font != null ? Font.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (_font != null ? _font.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ Style.GetHashCode(); hashCode = (hashCode * 397) ^ (int) _style;
hashCode = (hashCode * 397) ^ _vertical.GetHashCode();
hashCode = (hashCode * 397) ^ _lineSpacing.GetHashCode();
hashCode = (hashCode * 397) ^ _charSpacing.GetHashCode();
return hashCode; return hashCode;
} }
} }
@ -60,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,101 +0,0 @@
using System;
using TMPro;
using UnityEngine;
// ReSharper disable NonReadonlyMemberInGetHashCode
namespace ConformalDecals.Text {
public struct DecalTextStyle : IEquatable<DecalTextStyle> {
private FontStyles _fontStyle;
private bool _vertical;
private float _lineSpacing;
private float _charSpacing;
public FontStyles FontStyle {
get => _fontStyle;
set => _fontStyle = value;
}
public bool Bold {
get => (FontStyle & FontStyles.Bold) != 0;
set {
if (value) FontStyle |= FontStyles.Bold;
else FontStyle &= ~FontStyles.Bold;
}
}
public bool Italic {
get => (FontStyle & FontStyles.Italic) != 0;
set {
if (value) FontStyle |= FontStyles.Italic;
else FontStyle &= ~FontStyles.Italic;
}
}
public bool Underline {
get => (FontStyle & FontStyles.Underline) != 0;
set {
if (value) FontStyle |= FontStyles.Underline;
else FontStyle &= ~FontStyles.Underline;
}
}
public bool SmallCaps {
get => (FontStyle & FontStyles.SmallCaps) != 0;
set {
if (value) FontStyle |= FontStyles.SmallCaps;
else FontStyle &= ~FontStyles.SmallCaps;
}
}
public bool Vertical {
get => _vertical;
set => _vertical = value;
}
public float LineSpacing {
get => _lineSpacing;
set => _lineSpacing = value;
}
public float CharSpacing {
get => _charSpacing;
set => _charSpacing = value;
}
public DecalTextStyle(FontStyles fontStyle, bool vertical, float lineSpacing, float charSpacing) {
_fontStyle = fontStyle;
_vertical = vertical;
_lineSpacing = lineSpacing;
_charSpacing = charSpacing;
}
public bool Equals(DecalTextStyle other) {
return FontStyle == other.FontStyle && Vertical == other.Vertical &&
Mathf.Approximately(LineSpacing, other.LineSpacing) &&
Mathf.Approximately(CharSpacing, other.CharSpacing);
}
public override bool Equals(object obj) {
return obj is DecalTextStyle other && Equals(other);
}
public override int GetHashCode() {
unchecked {
var hashCode = (int) FontStyle;
hashCode = (hashCode * 397) ^ Vertical.GetHashCode();
hashCode = (hashCode * 397) ^ LineSpacing.GetHashCode();
hashCode = (hashCode * 397) ^ CharSpacing.GetHashCode();
return hashCode;
}
}
public static bool operator ==(DecalTextStyle left, DecalTextStyle right) {
return left.Equals(right);
}
public static bool operator !=(DecalTextStyle left, DecalTextStyle right) {
return !left.Equals(right);
}
}
}

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,161 +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.FontStyle | text.Font.FontStyle; tmp.fontStyle = text.Style | text.Font.FontStyle;
_tmp.lineSpacing = text.Style.LineSpacing; tmp.lineSpacing = text.LineSpacing;
_tmp.characterSpacing = text.Style.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];
@ -187,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");
@ -205,14 +113,18 @@ namespace ConformalDecals.Text {
// CALCULATE SIZES // CALCULATE SIZES
var size = bounds.size * PixelDensity; var size = bounds.size * PixelDensity;
size.x = Mathf.Max(size.x, 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
@ -237,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;
@ -250,13 +157,13 @@ 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);
GL.PushMatrix(); GL.PushMatrix();
GL.LoadProjectionMatrix(matrix); GL.LoadProjectionMatrix(matrix);
GL.LoadIdentity();
GL.Clear(false, true, Color.black); GL.Clear(false, true, Color.black);
for (var i = 0; i < meshes.Length; i++) { for (var i = 0; i < meshes.Length; i++) {
@ -266,22 +173,42 @@ namespace ConformalDecals.Text {
} }
} }
GL.PopMatrix(); // COPY RENDERTEX INTO TEXTURE
var prevRT = RenderTexture.active;
// COPY TEXTURE BACK INTO RAM
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.PopMatrix();
// RELEASE RENDERTEX // RELEASE RENDERTEX
RenderTexture.ReleaseTemporary(renderTex); RenderTexture.ReleaseTemporary(renderTex);
// CLEAR SUBMESHES // DESTROY THE RENDERER OBJECT
for (int i = 0; i < transform.childCount; i++) { Object.Destroy(tmpObject);
Destroy(transform.GetChild(i).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

@ -3,15 +3,12 @@ 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 {
public class TextEntryController : MonoBehaviour { public class TextEntryController : MonoBehaviour {
[Serializable] [Serializable]
public class TextUpdateEvent : UnityEvent<string, DecalFont, DecalTextStyle> { } public delegate void TextUpdateDelegate(string newText, DecalFont newFont, FontStyles style, bool vertical, float linespacing, float charspacing);
[SerializeField] public TextUpdateEvent onValueChanged = new TextUpdateEvent();
[SerializeField] private Selectable _textBox; [SerializeField] private Selectable _textBox;
[SerializeField] private Button _fontButton; [SerializeField] private Button _fontButton;
@ -28,21 +25,29 @@ namespace ConformalDecals.UI {
[SerializeField] private Toggle _smallCapsButton; [SerializeField] private Toggle _smallCapsButton;
[SerializeField] private Toggle _verticalButton; [SerializeField] private Toggle _verticalButton;
private string _text; private string _text;
private DecalFont _font; private DecalFont _font;
private DecalTextStyle _style; private FontStyles _style;
private Vector2 _lineSpacingRange; private bool _vertical;
private Vector2 _charSpacingRange; private float _lineSpacing;
private TMP_InputField _textBoxTMP; private float _charSpacing;
private Vector2 _lineSpacingRange;
private Vector2 _charSpacingRange;
private TMP_InputField _textBoxTMP;
private FontMenuController _fontMenu; private FontMenuController _fontMenu;
private TextUpdateDelegate _onValueChanged;
private bool _ignoreUpdates; private static int _lockCounter;
private bool _isLocked;
private string _lockString;
private bool _ignoreUpdates;
private bool _textUpdated;
public static TextEntryController Create( public static TextEntryController Create(
string text, DecalFont font, DecalTextStyle style, string text, DecalFont font, FontStyles style, bool vertical, float linespacing, float charspacing,
Vector2 lineSpacingRange, Vector2 charSpacingRange, Vector2 lineSpacingRange, Vector2 charSpacingRange,
UnityAction<string, DecalFont, DecalTextStyle> textUpdateCallback) { TextUpdateDelegate textUpdateCallback) {
var window = Instantiate(UILoader.TextEntryPrefab, MainCanvasUtil.MainCanvas.transform, true); var window = Instantiate(UILoader.TextEntryPrefab, MainCanvasUtil.MainCanvas.transform, true);
window.AddComponent<DragPanel>(); window.AddComponent<DragPanel>();
@ -52,9 +57,12 @@ namespace ConformalDecals.UI {
controller._text = text; controller._text = text;
controller._font = font; controller._font = font;
controller._style = style; controller._style = style;
controller._vertical = vertical;
controller._lineSpacing = linespacing;
controller._charSpacing = charspacing;
controller._lineSpacingRange = lineSpacingRange; controller._lineSpacingRange = lineSpacingRange;
controller._charSpacingRange = charSpacingRange; controller._charSpacingRange = charSpacingRange;
controller.onValueChanged.AddListener(textUpdateCallback); controller._onValueChanged = textUpdateCallback;
return controller; return controller;
} }
@ -64,10 +72,21 @@ namespace ConformalDecals.UI {
Destroy(gameObject); Destroy(gameObject);
} }
public void SetControlLock(string value = null) {
if (_isLocked) return;
InputLockManager.SetControlLock(ControlTypes.EDITOR_UI, _lockString);
_isLocked = true;
}
public void RemoveControlLock(string value = null) {
if (!_isLocked) return;
InputLockManager.RemoveControlLock(_lockString);
_isLocked = false;
}
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() {
@ -81,104 +100,123 @@ namespace ConformalDecals.UI {
font.SetupSample(_fontButton.GetComponentInChildren<TextMeshProUGUI>()); font.SetupSample(_fontButton.GetComponentInChildren<TextMeshProUGUI>());
_textBoxTMP.text = _text; _textBoxTMP.text = _text;
_textBoxTMP.textComponent.fontStyle = _style.FontStyle | _font.FontStyle & ~_font.FontStyleMask; _textBoxTMP.textComponent.fontStyle = _style | _font.FontStyle & ~_font.FontStyleMask;
_textBoxTMP.fontAsset = _font.FontAsset; _textBoxTMP.fontAsset = _font.FontAsset;
UpdateStyleButtons(); UpdateStyleButtons();
OnValueChanged(); _textUpdated = true;
} }
public void OnLineSpacingUpdate(float value) { public void OnLineSpacingUpdate(float value) {
if (_ignoreUpdates) return; if (_ignoreUpdates) return;
_style.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) {
if (_ignoreUpdates) return; if (_ignoreUpdates) return;
if (float.TryParse(text, out var value)) { if (float.TryParse(text, out var value)) {
_style.LineSpacing = Mathf.Clamp(value, _lineSpacingRange.x, _lineSpacingRange.y); _lineSpacing = Mathf.Clamp(value, _lineSpacingRange.x, _lineSpacingRange.y);
} }
else { else {
Logging.LogWarning("Line spacing value '{text}' could not be parsed."); Logging.LogWarning("Line spacing value '{text}' could not be parsed.");
} }
UpdateLineSpacing(); UpdateLineSpacing();
OnValueChanged(); _textUpdated = true;
} }
public void OnCharSpacingUpdate(float value) { public void OnCharSpacingUpdate(float value) {
if (_ignoreUpdates) return; if (_ignoreUpdates) return;
_style.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) {
if (_ignoreUpdates) return; if (_ignoreUpdates) return;
if (float.TryParse(text, out var value)) { if (float.TryParse(text, out var value)) {
_style.CharSpacing = Mathf.Clamp(value, _charSpacingRange.x, _charSpacingRange.y); _charSpacing = Mathf.Clamp(value, _charSpacingRange.x, _charSpacingRange.y);
} }
else { else {
Logging.LogWarning("Char spacing value '{text}' could not be parsed."); Logging.LogWarning("Char spacing value '{text}' could not be parsed.");
} }
UpdateCharSpacing(); UpdateCharSpacing();
OnValueChanged(); _textUpdated = true;
} }
public void OnBoldUpdate(bool state) { public void OnBoldUpdate(bool state) {
if (_ignoreUpdates) return; if (_ignoreUpdates) return;
_style.Bold = state; if (state)
_textBoxTMP.textComponent.fontStyle = _style.FontStyle | _font.FontStyle & ~_font.FontStyleMask; _style |= FontStyles.Bold;
OnValueChanged(); else
_style &= ~FontStyles.Bold;
_textBoxTMP.textComponent.fontStyle = _style | _font.FontStyle & ~_font.FontStyleMask;
_textUpdated = true;
} }
public void OnItalicUpdate(bool state) { public void OnItalicUpdate(bool state) {
if (_ignoreUpdates) return; if (_ignoreUpdates) return;
_style.Italic = state; if (state)
_textBoxTMP.textComponent.fontStyle = _style.FontStyle | _font.FontStyle & ~_font.FontStyleMask; _style |= FontStyles.Italic;
OnValueChanged(); else
_style &= ~FontStyles.Italic;
_textBoxTMP.textComponent.fontStyle = _style | _font.FontStyle & ~_font.FontStyleMask;
_textUpdated = true;
} }
public void OnUnderlineUpdate(bool state) { public void OnUnderlineUpdate(bool state) {
if (_ignoreUpdates) return; if (_ignoreUpdates) return;
_style.Underline = state; if (state)
_textBoxTMP.textComponent.fontStyle = _style.FontStyle | _font.FontStyle & ~_font.FontStyleMask; _style |= FontStyles.Underline;
OnValueChanged(); else
_style &= ~FontStyles.Underline;
_textBoxTMP.textComponent.fontStyle = _style | _font.FontStyle & ~_font.FontStyleMask;
_textUpdated = true;
} }
public void OnSmallCapsUpdate(bool state) { public void OnSmallCapsUpdate(bool state) {
if (_ignoreUpdates) return; if (_ignoreUpdates) return;
_style.SmallCaps = state; if (state)
_textBoxTMP.textComponent.fontStyle = _style.FontStyle | _font.FontStyle & ~_font.FontStyleMask; _style |= FontStyles.SmallCaps;
OnValueChanged(); else
_style &= ~FontStyles.SmallCaps;
_textBoxTMP.textComponent.fontStyle = _style | _font.FontStyle & ~_font.FontStyleMask;
_textUpdated = true;
} }
public void OnVerticalUpdate(bool state) { public void OnVerticalUpdate(bool state) {
if (_ignoreUpdates) return; if (_ignoreUpdates) return;
_style.Vertical = state; _vertical = state;
OnValueChanged(); _textUpdated = true;
} }
private void Start() { private void Start() {
_lockString = $"ConformalDecals_TextEditor_{_lockCounter++}";
_textBoxTMP = ((TMP_InputField) _textBox); _textBoxTMP = ((TMP_InputField) _textBox);
_textBoxTMP.text = _text; _textBoxTMP.text = _text;
_textBoxTMP.textComponent.fontStyle = _style.FontStyle | _font.FontStyle & ~_font.FontStyleMask; _textBoxTMP.textComponent.fontStyle = _style | _font.FontStyle & ~_font.FontStyleMask;
_textBoxTMP.fontAsset = _font.FontAsset; _textBoxTMP.fontAsset = _font.FontAsset;
_textBoxTMP.onSelect.AddListener(SetControlLock);
_textBoxTMP.onDeselect.AddListener(RemoveControlLock);
_font.SetupSample(_fontButton.GetComponentInChildren<TextMeshProUGUI>()); _font.SetupSample(_fontButton.GetComponentInChildren<TextMeshProUGUI>());
@ -187,8 +225,15 @@ namespace ConformalDecals.UI {
UpdateCharSpacing(); UpdateCharSpacing();
} }
private void OnValueChanged() { private void OnDestroy() {
onValueChanged.Invoke(_text, _font, _style); RemoveControlLock();
}
private void LateUpdate() {
if (_textUpdated) {
_onValueChanged(_text, _font, _style, _vertical, _lineSpacing, _charSpacing);
_textUpdated = false;
}
} }
private void UpdateStyleButtons() { private void UpdateStyleButtons() {
@ -204,7 +249,7 @@ namespace ConformalDecals.UI {
} }
else { else {
_boldButton.interactable = true; _boldButton.interactable = true;
_boldButton.isOn = _style.Bold; _boldButton.isOn = (_style & FontStyles.Bold) != 0;
} }
if (_font.Italic) { if (_font.Italic) {
@ -217,7 +262,7 @@ namespace ConformalDecals.UI {
} }
else { else {
_italicButton.interactable = true; _italicButton.interactable = true;
_italicButton.isOn = _style.Italic; _italicButton.isOn = (_style & FontStyles.Italic) != 0;
} }
if (_font.Underline) { if (_font.Underline) {
@ -230,7 +275,7 @@ namespace ConformalDecals.UI {
} }
else { else {
_underlineButton.interactable = true; _underlineButton.interactable = true;
_underlineButton.isOn = _style.Underline; _underlineButton.isOn = (_style & FontStyles.Underline) != 0;
} }
if (_font.SmallCaps) { if (_font.SmallCaps) {
@ -243,10 +288,10 @@ namespace ConformalDecals.UI {
} }
else { else {
_smallCapsButton.interactable = true; _smallCapsButton.interactable = true;
_smallCapsButton.isOn = _style.SmallCaps; _smallCapsButton.isOn = (_style & FontStyles.SmallCaps) != 0;
} }
_verticalButton.isOn = _style.Vertical; _verticalButton.isOn = _vertical;
_ignoreUpdates = false; _ignoreUpdates = false;
} }
@ -254,8 +299,8 @@ namespace ConformalDecals.UI {
private void UpdateLineSpacing() { private void UpdateLineSpacing() {
_ignoreUpdates = true; _ignoreUpdates = true;
_lineSpacingSlider.value = Mathf.InverseLerp(_lineSpacingRange.x, _lineSpacingRange.y, _style.LineSpacing); _lineSpacingSlider.value = Mathf.InverseLerp(_lineSpacingRange.x, _lineSpacingRange.y, _lineSpacing);
((TMP_InputField) _lineSpacingTextBox).text = $"{_style.LineSpacing:F1}"; ((TMP_InputField) _lineSpacingTextBox).text = $"{_lineSpacing:F1}";
_ignoreUpdates = false; _ignoreUpdates = false;
} }
@ -263,8 +308,8 @@ namespace ConformalDecals.UI {
private void UpdateCharSpacing() { private void UpdateCharSpacing() {
_ignoreUpdates = true; _ignoreUpdates = true;
_charSpacingSlider.value = Mathf.InverseLerp(_charSpacingRange.x, _charSpacingRange.y, _style.CharSpacing); _charSpacingSlider.value = Mathf.InverseLerp(_charSpacingRange.x, _charSpacingRange.y, _charSpacing);
((TMP_InputField) _charSpacingTextBox).text = $"{_style.CharSpacing:F1}"; ((TMP_InputField) _charSpacingTextBox).text = $"{_charSpacing:F1}";
_ignoreUpdates = false; _ignoreUpdates = false;
} }

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using UniLinq;
using UnityEngine; using UnityEngine;
namespace ConformalDecals.Util { namespace ConformalDecals.Util {
@ -30,9 +31,15 @@ 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)) throw new FormatException($"Missing value for {valueName}"); if (node.HasValue(valueName)) return node.GetValue(valueName);
if (isOptional) {
return defaultValue;
}
else {
throw new FormatException($"Missing value for {valueName}");
}
return node.GetValue(valueName);
} }
public static bool ParseStringIndirect(ref string value, ConfigNode node, string valueName) { public static bool ParseStringIndirect(ref string value, ConfigNode node, string valueName) {
@ -68,6 +75,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);
} }
@ -100,6 +115,15 @@ 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);
@ -139,6 +163,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,8 +1,53 @@
v0.2.7
------
- Supported KSP versions: 1.8.x to 1.11.x
- Notes:
- Attaching decal parts in flight using engineer kerbals is not supported.
- Fixes:
- Fixed certain non-ascii strings not rendering correctly under certain circumstances.
- Yet another attempted fix for the planet text glitch.
v0.2.6
------
- Fixes:
- Fixed stock flags appearing stretched by forcing their aspect ratio to be correct.
- Another attempted fix for the planet text glitch.
v0.2.5
------
- Fixes:
- Fixed line spacing, character spacing, and vertical settings not applying to symmetry counterparts
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 v0.2.1
------ ------
- Changes - Changes:
- Pressing enter in the text entry window now types a newline. - Pressing enter in the text entry window now types a newline.
- Fixes - Fixes:
- Renamed font assetbundle. The old extension was causing the game to try to load it twice on Windows due to legacy compatability features. - 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. - Fixed text rendering on DirectX resulting in black boxes by using ARGB32 instead of RG16 for the render texture in DirectX.