Various dead-end attempts at font loading and rendering

feature-better-tweakables
Andrew Cassidy 4 years ago
parent ecb3eccfe5
commit f9d9c228ae

@ -0,0 +1,57 @@
PART
{
name = conformaldecals-text
module = Part
author = Andrew Cassidy
MODEL
{
model = ConformalDecals/Assets/decal-blank
scale = 1.0, 1.0, 1.0
}
rescaleFactor = 1
// Attachment
attachRules = 1,1,0,0,1
node_attach = 0.0, 0.0, 0.1, 0.0, 0.0, -1.0
// Tech
TechRequired = start
// Info
cost = 75
category = Structural
// CDL-F Flag Decal
title = Conformal Text
// Peel-N-Stik Adhesive Decals
manufacturer = #LOC_ConformalDecals_agent-peel-n-stick_title
// A simple switchable flag. Can either use the mission flag or select a specific flag to use.
description = #LOC_ConformalDecals_flag-description
// conformal decal sticker flag
tags = #LOC_ConformalDecals_flag-tags
bulkheadProfiles = srf
// Parameters
mass = 0.0005
dragModel = NONE
angularDrag = 0.0
crashTolerance = 10
maxTemp = 2000
breakingForce = 350
breakingTorque = 150
physicalSignificance = NONE
MODULE
{
name = ModuleConformalText
useBaseNormal = true
defaultDepth = 0.2
defaultCutoff = 0
}
}

@ -43,6 +43,9 @@
<Reference Include="UnityEngine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>dlls\UnityEngine.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.AssetBundleModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>dlls\UnityEngine.AssetBundleModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>dlls\UnityEngine.CoreModule.dll</HintPath>
</Reference>
@ -60,9 +63,13 @@
<Compile Include="MaterialModifiers\MaterialPropertyCollection.cs" />
<Compile Include="MaterialModifiers\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="Util\Logging.cs" />
<Compile Include="Util\OrientedBounds.cs" />
<Compile Include="Util\TextureUtils.cs" />

@ -18,7 +18,10 @@ namespace ConformalDecals.MaterialModifiers {
[SerializeField] private Vector2 _textureOffset;
[SerializeField] private Vector2 _textureScale = Vector2.one;
public Texture2D Texture => _texture;
public Texture2D Texture {
get => _texture;
set => _texture = value;
}
public string TextureUrl {
get => _textureUrl;

@ -0,0 +1,41 @@
using ConformalDecals.Util;
using TMPro;
using UnityEngine;
namespace ConformalDecals {
public class ModuleConformalText: ModuleConformalDecal {
private const string DefaultFlag = "Squad/Flags/default";
[KSPField(isPersistant = true)] public string text = "Hello World!";
public override void OnLoad(ConfigNode node) {
base.OnLoad(node);
SetText(text);
}
public override void OnStart(StartState state) {
base.OnStart(state);
SetText(text);
}
private void SetText(string newText) {
if (!HighLogic.LoadedSceneIsEditor) return;
this.Log("Rendering text for part");
var fonts = Resources.FindObjectsOfTypeAll<TMP_FontAsset>();
foreach (var font in fonts) {
this.Log($"Font: {font.name}");
foreach (var fallback in font.fallbackFontAssets) {
this.Log($" Fallback: {fallback.name}");
}
}
materialProperties.AddOrGetTextureProperty("_Decal", true).Texture = TextRenderer.RenderToTexture(fonts[0], newText);
UpdateMaterials();
}
}
}

@ -0,0 +1,8 @@
using UnityEngine;
namespace ConformalDecals.Text {
public class DecalFont : ScriptableObject {
[SerializeField] public string foo1;
[SerializeField] public string foo2;
}
}

@ -0,0 +1,36 @@
using System.IO;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
namespace ConformalDecals.Text {
[DatabaseLoaderAttrib(new[] {"kspfont"})]
public class FontLoader : DatabaseLoader<GameDatabase.TextureInfo> {
public static List<TMP_FontAsset> fonts;
public override IEnumerator Load(UrlDir.UrlFile urlFile, FileInfo fileInfo) {
fonts ??= new List<TMP_FontAsset>();
Debug.Log($"[ConformalDecals] '{urlFile.fullPath}'");
var bundle = AssetBundle.LoadFromFile(urlFile.fullPath);
if (!bundle) {
Debug.Log($"[ConformalDecals] could not load font asset {urlFile.fullPath}");
}
else {
var loadedFoo = bundle.LoadAllAssets<DecalFont>();
Debug.Log(loadedFoo[0].foo1);
Debug.Log(loadedFoo[0].foo2);
var loadedFonts = bundle.LoadAllAssets<TMP_FontAsset>();
foreach (var font in loadedFonts) {
Debug.Log($"[ConformalDecals] adding font {font.name}" );
fonts.Add(font);
Debug.Log($"ConformalDecals] isReadable: {font.atlas.isReadable}");
}
}
yield break;
}
}
}

@ -0,0 +1,146 @@
using System;
using ConformalDecals.Util;
using TMPro;
using UnityEngine;
namespace ConformalDecals {
public class TextRenderer {
private struct GlyphInfo {
public TMP_Glyph glyph;
public Vector2Int size;
public Vector2Int position;
public int fontIndex;
public bool needsResample;
}
private struct FontInfo {
public TMP_FontAsset font;
public Texture2D fontAtlas;
public Color32[] fontAtlasColors;
}
public static Texture2D RenderToTexture(TMP_FontAsset font, string text) {
Debug.Log($"Rendering text: {text}");
var charArray = text.ToCharArray();
var glyphInfoArray = new GlyphInfo[charArray.Length];
var fontInfoArray = new FontInfo[charArray.Length];
var baseScale = font.fontInfo.Scale;
var padding = (int) font.fontInfo.Padding;
var ascender = (int) font.fontInfo.Ascender;
var descender = (int) font.fontInfo.Descender;
var baseline = (int) baseScale * (descender + padding);
Debug.Log($"baseline: {baseline}");
Debug.Log($"ascender: {ascender}");
Debug.Log($"descender: {descender}");
Debug.Log($"baseScale: {baseScale}");
fontInfoArray[0].font = font;
int xAdvance = 0;
for (var i = 0; i < charArray.Length; i++) {
var glyphFont = TMP_FontUtilities.SearchForGlyph(font, charArray[i], out var glyph);
if (glyphFont == font) {
glyphInfoArray[i].fontIndex = 0;
}
else {
for (int f = 1; i < charArray.Length; i++) {
if (fontInfoArray[f].font == null) {
fontInfoArray[f].font = glyphFont;
glyphInfoArray[i].fontIndex = f;
break;
}
if (fontInfoArray[f].font == glyphFont) {
glyphInfoArray[i].fontIndex = f;
break;
}
}
}
Debug.Log($"getting font info for character: '{charArray[i]}'");
Debug.Log($"character font: {glyphFont.name}");
glyphInfoArray[i].glyph = glyph;
glyphInfoArray[i].needsResample = false;
float elementScale = glyph.scale;
if (glyphFont == font) {
if (!Mathf.Approximately(elementScale, 1)) {
glyphInfoArray[i].needsResample = true;
}
elementScale *= baseScale;
}
else {
var fontScale = glyphFont.fontInfo.Scale / glyphFont.fontInfo.PointSize;
if (!Mathf.Approximately(fontScale, baseScale)) {
glyphInfoArray[i].needsResample = true;
}
elementScale *= fontScale;
}
Debug.Log($"character scale: {glyphFont.fontInfo.Scale / glyphFont.fontInfo.PointSize}");
Debug.Log($"character needs resampling: {glyphInfoArray[i].needsResample}");
glyphInfoArray[i].size.x = (int) ((glyph.width + (padding * 2)) * elementScale);
glyphInfoArray[i].size.y = (int) ((glyph.height + (padding * 2)) * elementScale);
glyphInfoArray[i].position.x = (int) ((xAdvance + glyph.xOffset - padding) * elementScale);
glyphInfoArray[i].position.y = (int) ((baseline + glyph.yOffset - padding) * elementScale);
Debug.Log($"character size: {glyphInfoArray[i].size}");
Debug.Log($"character position: {glyphInfoArray[i].position}");
}
// calculate texture bounds
int xOffset = glyphInfoArray[0].position.x;
var textureWidth = (glyphInfoArray[charArray.Length - 1].position.x + glyphInfoArray[charArray.Length - 1].size.x) - xOffset;
var textureHeight = (int) baseScale * (ascender + descender + padding * 2);
// ensure texture sizes are powers of 2
textureWidth = Mathf.NextPowerOfTwo(textureWidth);
textureHeight = Mathf.NextPowerOfTwo(textureHeight);
Debug.Log($"texture is {textureWidth} x {textureHeight}");
var texture = new Texture2D(textureWidth, textureHeight, TextureFormat.Alpha8, true);
var colors = new Color32[textureWidth * textureHeight];
for (var i = 0; i < fontInfoArray.Length; i++) {
if (fontInfoArray[i].font == null) break;
fontInfoArray[i].fontAtlas = fontInfoArray[i].font.atlas;
fontInfoArray[i].fontAtlasColors = fontInfoArray[i].fontAtlas.GetPixels32();
}
for (int i = 0; i < charArray.Length; i++) {
var glyphInfo = glyphInfoArray[i];
var glyph = glyphInfo.glyph;
var fontInfo = fontInfoArray[glyphInfo.fontIndex];
var srcPos = new Vector2Int((int) glyph.x, (int) glyph.y);
var dstPos = glyphInfo.position;
dstPos.x += xOffset;
var dstSize = glyphInfo.size;
Debug.Log($"rendering character number {i}");
if (glyphInfo.needsResample) {
var srcSize = new Vector2(glyph.width, glyph.height);
TextureUtils.BlitRectBilinearAlpha(fontInfo.fontAtlas, srcPos, srcSize, texture, colors, dstPos, dstSize, TextureUtils.BlitMode.Add);
}
else {
TextureUtils.BlitRectAlpha(fontInfo.fontAtlas, fontInfo.fontAtlasColors, srcPos, texture, colors, dstPos, dstSize, TextureUtils.BlitMode.Add);
}
}
texture.Apply(true);
return texture;
}
}
}

@ -2,11 +2,183 @@ using UnityEngine;
namespace ConformalDecals.Util {
public static class TextureUtils {
public enum BlitMode {
Set,
Add,
}
public static Color32 AddColor32(Color32 color1, Color32 color2) {
return new Color32((byte) (color1.r + color2.r), (byte) (color1.g + color2.g), (byte) (color1.b + color2.b), (byte) (color1.a + color2.a));
}
public static Color32 AddColor32Clamped(Color32 color1, Color32 color2) {
var r = color1.r + color2.r;
var g = color1.g + color2.g;
var b = color1.b + color2.b;
var a = color1.a + color2.a;
if (r > byte.MaxValue) r = byte.MaxValue;
if (g > byte.MaxValue) g = byte.MaxValue;
if (b > byte.MaxValue) b = byte.MaxValue;
if (a > byte.MaxValue) a = byte.MaxValue;
return new Color32((byte) r, (byte) g, (byte) b, (byte) a);
}
public static void ClearTexture(Color32[] colors, Color32 clearColor = default) {
for (var i = 0; i < colors.Length; i++) {
colors[i] = clearColor;
}
}
public static void BlitRectAlpha(
Texture2D src, Color32[] srcColors, Vector2Int srcPos,
Texture2D dst, Color32[] dstColors, Vector2Int dstPos,
Vector2Int size, BlitMode mode) {
ClipRect(src, ref srcPos, dst, ref dstPos, ref size);
if (size.x <= 0 || size.y <= 0) return;
int srcIndex = srcPos.x + srcPos.y * src.width;
int dstIndex = dstPos.x + dstPos.y * dst.width;
for (int dy = size.y - 1; dy >= 0; dy--) {
for (int dx = size.x - 1; dx >= 0; dx--) {
switch (mode) {
case BlitMode.Set:
dstColors[dstIndex + dx].a = srcColors[srcIndex + dx].a;
break;
case BlitMode.Add:
var s = srcColors[srcIndex + dx].a;
var d = dstColors[dstIndex + dx].a;
var sum = s + d;
if (sum > byte.MaxValue) sum = byte.MaxValue;
dstColors[dstIndex + dx].a = (byte) sum;
break;
}
}
srcIndex += src.width;
dstIndex += dst.width;
}
}
public static void BlitRect(
Texture2D src, Color32[] srcColors, Vector2Int srcPos,
Texture2D dst, Color32[] dstColors, Vector2Int dstPos,
Vector2Int size) {
Vector2Int size, BlitMode mode) {
ClipRect(src, ref srcPos, dst, ref dstPos, ref size);
if (size.x <= 0 || size.y <= 0) return;
int srcIndex = srcPos.x + srcPos.y * src.width;
int dstIndex = dstPos.x + dstPos.y * dst.width;
for (int dy = 0; dy < size.y; dy++) {
for (int dx = 0; dx < size.x; dx++) {
switch (mode) {
case BlitMode.Set:
dstColors[dstIndex + dx] = srcColors[srcIndex + dx];
break;
case BlitMode.Add:
dstColors[dstIndex + dx] = AddColor32Clamped(srcColors[srcIndex + dx], dstColors[dstIndex + dx]);
break;
}
}
srcIndex += src.width;
dstIndex += dst.width;
}
}
public static void BlitRectBilinearAlpha(
Texture2D src, Vector2Int srcPos, Vector2 srcSize,
Texture2D dst, Color32[] dstColors, Vector2Int dstPos, Vector2Int dstSize,
BlitMode mode) {
var sizeRatio = dstSize / srcSize;
ClipRect(src, ref srcPos, dst, ref dstPos, ref srcSize, ref dstSize);
if (dstSize.x <= 0 || dstSize.y <= 0) return;
var srcPixel = new Vector2(1.0f / src.width, 1.0f / src.height);
var srcStart = (srcPos * srcPixel) + (srcPixel / 2);
var srcStep = sizeRatio * srcPixel;
var srcY = srcStart.y;
int dstIndex = dstPos.x + dstPos.y * dst.width;
for (int dy = 0;
dy < dstSize.y;
dy++) {
var srcX = srcStart.x;
for (int dx = 0; dx < dstSize.x; dx++) {
switch (mode) {
case BlitMode.Set:
dstColors[dstIndex + dx].a = (byte) (src.GetPixelBilinear(srcX, srcY).a * byte.MaxValue);
break;
case BlitMode.Add:
var s = (byte) (src.GetPixelBilinear(srcX, srcY).a * byte.MaxValue);
var d = dstColors[dstIndex + dx].a;
var sum = s + d;
if (sum > byte.MaxValue) sum = byte.MaxValue;
dstColors[dstIndex + dx].a = (byte) sum;
break;
}
srcX += srcStep.x;
}
srcY += srcStep.y;
dstIndex += dst.width;
}
}
public static void BlitRectBilinear(
Texture2D src, Vector2Int srcPos, Vector2 srcSize,
Texture2D dst, Color32[] dstColors, Vector2Int dstPos, Vector2Int dstSize,
BlitMode mode) {
var sizeRatio = dstSize / srcSize;
ClipRect(src, ref srcPos, dst, ref dstPos, ref srcSize, ref dstSize);
if (dstSize.x <= 0 || dstSize.y <= 0) return;
var srcPixel = new Vector2(1.0f / src.width, 1.0f / src.height);
var srcStart = (srcPos * srcPixel) + (srcPixel / 2);
var srcStep = sizeRatio * srcPixel;
var srcY = srcStart.y;
int dstIndex = dstPos.x + dstPos.y * dst.width;
for (int dy = 0;
dy < dstSize.y;
dy++) {
var srcX = srcStart.x;
for (int dx = 0; dx < dstSize.x; dx++) {
switch (mode) {
case BlitMode.Set:
dstColors[dstIndex + dx] = src.GetPixelBilinear(srcX, srcY);
break;
case BlitMode.Add:
dstColors[dstIndex + dx] = AddColor32Clamped(src.GetPixelBilinear(srcX, srcY), dstColors[dstIndex + dx]);
break;
}
srcX += srcStep.x;
}
srcY += srcStep.y;
dstIndex += dst.width;
}
}
private static void ClipRect(Texture2D src, ref Vector2Int srcPos, Texture2D dst, ref Vector2Int dstPos, ref Vector2Int size) {
if (srcPos.x < 0) {
size.x += srcPos.x;
dstPos.x -= srcPos.x;
@ -35,30 +207,10 @@ namespace ConformalDecals.Util {
if (srcPos.y + size.y > src.height) size.y = src.height - srcPos.y;
if (dstPos.x + size.x > dst.width) size.x = dst.width - srcPos.x;
if (dstPos.y + size.y > dst.height) size.y = dst.height - srcPos.y;
if (size.x <= 0) return;
if (size.y <= 0) return;
int srcIndex = srcPos.x + srcPos.y * src.width;
int dstIndex = dstPos.x + dstPos.y * dst.width;
for (int dy = 0; dy < size.y; dy++) {
for (int dx = 0; dx < size.x; dx++) {
dstColors[dstIndex + dx] = srcColors[srcIndex + dx];
}
srcIndex += src.width;
dstIndex += dst.width;
}
}
public static void BlitRectBilinear(
Texture2D src, Vector2Int srcPos, Vector2 srcSize,
Texture2D dst, Color32[] dstColors, Vector2Int dstPos, Vector2Int dstSize) {
private static void ClipRect(Texture2D src, ref Vector2Int srcPos, Texture2D dst, ref Vector2Int dstPos, ref Vector2 srcSize, ref Vector2Int dstSize) {
var sizeRatio = dstSize / srcSize;
if (srcPos.x < 0) {
dstSize.x += (int) (srcPos.x * sizeRatio.x);
dstPos.x -= (int) (srcPos.x * sizeRatio.x);
@ -106,26 +258,6 @@ namespace ConformalDecals.Util {
dstSize.y = dst.height - srcPos.y;
srcSize.y = (int) (dstSize.y / sizeRatio.y);
}
var srcPixel = new Vector2(1.0f / src.width, 1.0f / src.height);
var srcStart = (srcPos * srcPixel) + (srcPixel / 2);
var srcStep = sizeRatio * srcPixel;
var srcY = srcStart.y;
int dstIndex = dstPos.x + dstPos.y * dst.width;
for (int dy = 0; dy < dstSize.y; dy++) {
var srcX = srcStart.x;
for (int dx = 0; dx < dstSize.x; dx++) {
dstColors[dstIndex + dx] = src.GetPixelBilinear(srcX, srcY);
srcX += srcStep.x;
}
srcY += srcStep.y;
dstIndex += dst.width;
}
}
}
}
Loading…
Cancel
Save