Files
ChestPreview/ChestPreview/ChestPreview/Rendering/WorldBillboardRenderer.cs

215 lines
7.2 KiB
C#

using ChestPreview.Configs;
using ChestPreview.Models;
using System;
using System.Collections.Generic;
using Vintagestory.API.Client;
using Vintagestory.API.Common;
using Vintagestory.API.MathTools;
namespace ChestPreview.Rendering;
internal class WorldBillboardRenderer(ICoreClientAPI api, Config config, CardRenderer cardRenderer) : IRenderer
{
private static float card_cell_padding => 2f;
private static float card_cell_width => 72f;
private static float card_cell_gap => 4f;
private static float billboard_brightness => 1.18f;
private readonly ICoreClientAPI api = api;
private readonly List<PreviewTarget> frameTargets = [];
private readonly CardRenderer cardRenderer = cardRenderer;
private readonly PreviewTargetProvider targetProvider = new(api, config);
private readonly WorldBillboardPresenter worldBillboardPresenter = new(api, config);
private readonly MeshRef quadMeshRef = api.Render.UploadMesh(
QuadMeshUtil.GetCustomQuadModelData(
1f,
v: 1f,
0f,
v2: 0f,
dx: -1f,
dy: -1f,
dw: 2f,
dh: 2f,
r: byte.MaxValue,
g: byte.MaxValue,
b: byte.MaxValue,
a: byte.MaxValue
)
);
private bool disposed;
public ICoreClientAPI Api { get; } = api;
public Config Config { get; } = config;
public double RenderOrder => 0.61;
public int RenderRange => 24;
public void OnRenderFrame(float deltaTime, EnumRenderStage stage)
{
if (disposed || stage != EnumRenderStage.AfterOIT)
return;
frameTargets.Clear();
targetProvider.CollectTargets(deltaTime, frameTargets);
worldBillboardPresenter.PrepareFrame(frameTargets);
if (worldBillboardPresenter.FrameBillboards.Count == 0)
return;
api.Render.GlDisableCullFace();
api.Render.GLEnableDepthTest();
api.Render.GlToggleBlend(true, EnumBlendMode.PremultipliedAlpha);
try
{
foreach (BillboardTarget billboardTarget in worldBillboardPresenter.FrameBillboards)
{
int maxColumns = GetBillboardMaxColumns(billboardTarget);
if (!cardRenderer.TryGetOrCreateCardTexture(billboardTarget.PreviewTarget, maxColumns, out LoadedTexture texture))
continue;
RenderBillboard(billboardTarget, texture, maxColumns);
}
}
finally
{
StopShader(api.Render.CurrentActiveShader);
api.Render.GlToggleBlend(false, EnumBlendMode.PremultipliedAlpha);
api.Render.GLDisableDepthTest();
api.Render.GlEnableCullFace();
}
}
public void Dispose()
{
if (disposed)
return;
disposed = true;
frameTargets.Clear();
worldBillboardPresenter.Clear();
targetProvider.Dispose();
quadMeshRef.Dispose();
}
private void RenderBillboard(BillboardTarget billboardTarget, LoadedTexture texture, int maxColumns)
{
EntityPlayer? playerEntity = api.World.Player?.Entity;
if (playerEntity?.CameraPos == null)
return;
float referenceWidthPx = GetCardWidthPx(maxColumns);
float widthScale = texture.Width / Math.Max(1f, referenceWidthPx);
float textureAspect = texture.Height / (float)System.Math.Max(1, texture.Width);
float scaledWidth;
float textureHeight;
Vec3d center;
if (billboardTarget.IsFrontPlacement)
{
scaledWidth = billboardTarget.Width * widthScale;
textureHeight = scaledWidth * textureAspect;
if (scaledWidth > billboardTarget.Width)
{
scaledWidth = billboardTarget.Width;
textureHeight = scaledWidth * textureAspect;
}
if (textureHeight > billboardTarget.Height)
{
textureHeight = billboardTarget.Height;
scaledWidth = textureHeight / Math.Max(0.0001f, textureAspect);
}
center = billboardTarget.Center;
}
else
{
scaledWidth = billboardTarget.Width * widthScale;
textureHeight = scaledWidth * textureAspect;
center = new Vec3d(
billboardTarget.PreviewTarget.Anchor.X,
billboardTarget.PreviewTarget.Anchor.Y + textureHeight / 2f,
billboardTarget.PreviewTarget.Anchor.Z
);
}
float[] modelMatrix = CreateModelMatrix(billboardTarget, center, scaledWidth, textureHeight, playerEntity.CameraPos);
float brightness = billboard_brightness;
IStandardShaderProgram shader = api.Render.PreparedStandardShader(
billboardTarget.PreviewTarget.BlockEntity.Pos.X,
billboardTarget.PreviewTarget.BlockEntity.Pos.Y,
billboardTarget.PreviewTarget.BlockEntity.Pos.Z,
new Vec4f(brightness, brightness, brightness, 1f)
);
shader.Tex2D = texture.TextureId;
shader.AlphaTest = 0.01f;
shader.RgbaAmbientIn = new Vec3f(brightness, brightness, brightness);
shader.RgbaLightIn = new Vec4f(brightness, brightness, brightness, 1f);
shader.RgbaGlowIn = new Vec4f(0f, 0f, 0f, 0f);
shader.RgbaTint = new Vec4f(brightness, brightness, brightness, 1f);
shader.ModelMatrix = modelMatrix;
shader.ViewMatrix = api.Render.CameraMatrixOriginf;
shader.ProjectionMatrix = api.Render.CurrentProjectionMatrix;
api.Render.BindTexture2d(texture.TextureId);
api.Render.RenderMesh(quadMeshRef);
}
private static void StopShader(IShaderProgram? shader)
{
if (shader == null)
return;
try
{
shader.Stop();
}
catch
{
}
}
private static float GetCardWidthPx(int columns)
{
int safeColumns = Math.Max(1, columns);
return card_cell_padding * 2f + safeColumns * card_cell_width + (safeColumns - 1) * card_cell_gap;
}
private int GetBillboardMaxColumns(BillboardTarget billboardTarget)
{
int columnsPerBlock = Math.Max(1, Config.BillboardColumnsPerBlock <= 0 ? 1 : Config.BillboardColumnsPerBlock);
int blockWidth = Math.Max(1, (int)MathF.Round(billboardTarget.Width));
return columnsPerBlock * blockWidth;
}
private static float[] CreateModelMatrix(BillboardTarget billboardTarget, Vec3d center, float width, float height, Vec3d cameraPos)
{
float halfWidth = width / 2f;
float halfHeight = height / 2f;
return
[
billboardTarget.Right.X * halfWidth,
billboardTarget.Right.Y * halfWidth,
billboardTarget.Right.Z * halfWidth,
0f,
billboardTarget.Up.X * halfHeight,
billboardTarget.Up.Y * halfHeight,
billboardTarget.Up.Z * halfHeight,
0f,
billboardTarget.Forward.X,
billboardTarget.Forward.Y,
billboardTarget.Forward.Z,
0f,
(float)(center.X - cameraPos.X),
(float)(center.Y - cameraPos.Y),
(float)(center.Z - cameraPos.Z),
1f
];
}
}