core/player/PlayerIsoCamera.cs
using Sandbox.Rendering;
public class PlayerIsoCamera : Component {
[Property, ReadOnly] public BasePlayer Owner {get; set;}
[Property, ReadOnly] public Vector2Int RenderSize {get; set;} = new(640,480);
[Property, ReadOnly] public CameraComponent RenderCamera {get; set;}
[Property, ReadOnly] public CameraComponent DrawCamera {get; set;}
[Property, ReadOnly] public LightingRigs Lighting {get; set;}
[Property, ReadOnly] public Vector2 DitherOffset {get; set;}
[Property, ReadOnly] public Texture Background {get; set;}
[Property, ReadOnly] public Texture BackgroundDepth {get; set;}
[Property, ReadOnly] public Texture EntityShadow {get; set;}
[Property, ReadOnly] public Texture EntityShadowDepth {get; set;}
[Property, ReadOnly] public Texture EntityLighting {get; set;}
[Property, ReadOnly] public Texture WorldText {get; set;}
[Property, ReadOnly] public Texture Mask {get; set;}
[Property, ReadOnly] public Texture Composite {get; set;}
[Property, ReadOnly] public Texture Snow {get; set;}
[Property, ReadOnly] public Texture Waypoint {get; set;}
[Property, ReadOnly] public Texture WaypointMask {get; set;}
[Property, ReadOnly] public Texture Final {get; set;}
protected override void OnAwake() {
LocalRotation = Rotation.FromYaw(60f) * Rotation.FromPitch(30f);
LocalPosition = LocalRotation. Backward * 1024f;
Lighting = Scene.CreateObject().Components.Create<LightingRigs>();
Spawn.SetOwner(this);
Spawn.Component(nameof(RenderCamera));
RenderCamera.Orthographic = true;
RenderCamera.BackgroundColor = Color.Black;
RenderCamera.ZFar = 2048;
Spawn.Component(nameof(DrawCamera));
DrawCamera.RenderTags.Add("null");
DrawCamera.BackgroundColor = Color.Black;
DrawCamera.Priority = 1000;
base.OnAwake();
}
public Vector2 PointToRenderPixels(Vector3 point) {
var uv = RenderCamera.PointToScreenNormal(point);
uv -= 0.5f;
uv /= RenderSize / Screen.Size;
uv += 0.5f;
uv *= RenderSize;
uv.x = uv.x.Floor();
uv.y = uv.y.Floor();
return uv;
}
public Vector3 MouseTarget() => ScreenPixelsToPoint(Mouse.Position);
public Vector3 ScreenPixelsToPoint(Vector2 position) {
var uv = position / Screen.Size;
uv -= 0.5f;
uv *= RenderSize / Screen.Size;
uv.x *= Screen.Aspect;
uv.x /= (float)RenderSize.x / RenderSize.y;
uv += 0.5f;
return new Plane(Vector3.Zero, Vector3.Up).Trace(RenderCamera.ScreenNormalToRay(uv)).Value;
}
private TimeUntil NextFullRender = 0;
public void Update() {
UpdateZoom();
ResetTextures();
Mouse.Visibility = MouseVisibility.Visible;
if (Owner.Mouse.InteractMode == PlayerMouse.Mode.Move && Owner.Mouse.TargetLayer != PlayerMouse.Layer.Interface)
Mouse.CursorType = "None";
else if (Owner.Mouse.InteractMode == PlayerMouse.Mode.Shoot)
Mouse.CursorType = "iso2_crosshair";
else
Mouse.CursorType = "iso2_pointer";
UpdateMouseDrag();
if (NextFullRender)
FullRender();
InterfaceRender();
}
[Property] public float ZoomScale {get; set;} = 0.25f;
private void UpdateZoom() {
if (Input.MouseWheel.y == 0)
return;
var startscale = ZoomScale;
if (Input.MouseWheel.y > 0)
ZoomScale /= 2f;
else
ZoomScale *= 2f;
ZoomScale = ZoomScale.Clamp(0.25f, 0.5f);
if (startscale != ZoomScale) {
LastSize = Vector2.Zero;
NextFullRender = 0f;
}
}
private bool IsDragging = false;
private Vector3 StartPosition;
private Vector2 MouseStart;
private Vector2 DitherStart;
private void UpdateMouseDrag() {
if (!IsDragging && Input.Pressed("drag_camera")) {
IsDragging = true;
StartPosition = Owner.WorldPosition;
DitherStart = DitherOffset;
MouseStart = Mouse.Position;
}
if (IsDragging && Input.Released("drag_camera"))
IsDragging = false;
if (IsDragging) {
var mouse = ((Mouse.Position - MouseStart) * (RenderSize / Screen.Size)).SnapToGrid(1);
var delta = ScreenPixelsToPoint(mouse) - ScreenPixelsToPoint(0);
delta *= new Vector3(Screen.Size / RenderSize, 0f);
Owner.WorldPosition = StartPosition - delta;
DitherOffset = DitherStart - mouse;
if (!mouse.IsNearZeroLength)
FullRender();
}
}
private Vector2 LastSize = Vector2.Zero;
private void ResetTextures() {
if (LastSize == Screen.Size)
return;
LastSize = Screen.Size;
RenderSize = (Vector2Int)(Screen.Size * ZoomScale);
Background?.Dispose();
Background = Texture.CreateRenderTarget().WithSize(RenderSize).Create();
BackgroundDepth?.Dispose();
BackgroundDepth = Texture.CreateRenderTarget().WithSize(RenderSize).WithFormat(ImageFormat.RGBA32323232F).Create();
EntityShadow?.Dispose();
EntityShadow = Texture.CreateRenderTarget().WithSize(RenderSize).Create();
EntityShadowDepth?.Dispose();
EntityShadowDepth = Texture.CreateRenderTarget().WithSize(RenderSize).WithFormat(ImageFormat.RGBA32323232F).Create();
EntityLighting?.Dispose();
EntityLighting = Texture.CreateRenderTarget().WithSize(RenderSize).Create();
Composite?.Dispose();
Composite = Texture.CreateRenderTarget().WithUAVBinding().WithSize(RenderSize).Create();
Snow?.Dispose();
Snow = Texture.CreateRenderTarget().WithUAVBinding().WithSize(RenderSize).Create();
Waypoint?.Dispose();
Waypoint = Texture.CreateRenderTarget().WithSize(RenderSize).Create();
WaypointMask?.Dispose();
WaypointMask = Texture.CreateRenderTarget().WithSize(RenderSize).Create();
Final?.Dispose();
Final = Texture.CreateRenderTarget().WithUAVBinding().WithSize(RenderSize).Create();
DrawCamera.ClearCommandLists();
var cmds = new CommandList();
cmds.Attributes.Set("Frame", Final);
cmds.Attributes.Set("Interface", Owner.Inventory.Interface);
cmds.Attributes.SetCombo("D_DRAW_INTERFACE", true);
cmds.Blit(Material.FromShader(Shader.Load("shaders/post_draw.shader")));
DrawCamera.AddCommandList(cmds, Stage.AfterPostProcess);
FullRender();
}
private Vector2 LastEntityRenderOffset {get; set;}
public void FullRender() {
RenderCamera.OrthographicHeight = 1024 * (Screen.Height / 1024);
RenderBackground();
if (NextFullRender) {
RenderShadows();
RenderEntities();
LastEntityRenderOffset = DitherOffset;
}
RenderWorldText();
//RenderMask();
DoComposite();
if (NextFullRender)
NextFullRender = 1f/8f;
}
private void RenderBackground() { //areas of static land behind the characters
RenderCamera.RenderExcludeTags.Add("entity");
RenderCamera.RenderExcludeTags.Add("flame");
RenderCamera.RenderExcludeTags.Add("alpha_zero_depth_floor");
RenderCamera.RenderExcludeTags.Add("iso_waypoint");
TerrainSuperBlendController.Set(Scene, Scene.RenderAttributes);
RenderCamera.RenderToTexture(Background);
Scene.RenderAttributes.Clear();
Scene.RenderAttributes.SetCombo("D_MANUAL_DEPTH", true);
RenderCamera.RenderExcludeTags.Add("light");
RenderCamera.RenderExcludeTags.Add("flame");
//RenderCamera.RenderExcludeTags.Add("no_depth");
RenderCamera.RenderToTexture(BackgroundDepth);
Scene.RenderAttributes.SetCombo("D_MANUAL_DEPTH", false);
RenderCamera.RenderExcludeTags.RemoveAll();
}
private void RenderShadows() {
RenderCamera.RenderTags.Add("entity");
RenderCamera.RenderTags.Add("entity_shadows");
Lighting.EntityShadows.GameObject.Enabled = true;
Scene.RenderAttributes.SetCombo("D_SHADOW_PASS", true);
RenderCamera.RenderToTexture(EntityShadow);
Scene.RenderAttributes.SetCombo("D_MANUAL_DEPTH", true);
RenderCamera.RenderExcludeTags.Add("light");
RenderCamera.RenderExcludeTags.Add("iso_waypoint");
RenderCamera.RenderTags.Add("flame");
RenderCamera.RenderToTexture(EntityShadowDepth);
RenderCamera.RenderTags.RemoveAll();
RenderCamera.RenderExcludeTags.RemoveAll();
Lighting.EntityShadows.GameObject.Enabled = false;
Scene.RenderAttributes.SetCombo("D_MANUAL_DEPTH", false);
Scene.RenderAttributes.SetCombo("D_SHADOW_PASS", false);
}
private void RenderEntities() { //areas of static land behind the characters
RenderCamera.RenderTags.Add("entity");
RenderCamera.RenderTags.Add("flame");
RenderCamera.RenderTags.Add("light_directional");
RenderCamera.RenderToTexture(EntityLighting);
RenderCamera.RenderTags.RemoveAll();
}
private void RenderWorldText() {
WorldText?.Dispose();
var tex = new Bitmap(RenderSize.x, RenderSize.y);
foreach (var talker in Scene.GetAllComponents<NpcTalker>()) {
if (!talker.Message.HasValue)
continue;
tex.SetPen(Color.Yellow, 1);
var center = new Rect(PointToRenderPixels(talker.GetCaptionPosition()), 0);
center = center.Grow(Graphics.MeasureText(center, talker.Message.Value).Width * 0.5f, 8);
center.Top = center.Top.Floor();
center.Left = center.Left.Floor();
center.Width = MathF.Ceiling(center.Width);
tex.DrawText(talker.Message.Value, center, TextFlag.Center);
}
WorldText = tex.ToTexture();
tex.Dispose();
}
private void RenderMask() {
Mask?.Dispose();
var tex = new Bitmap(RenderSize.x, RenderSize.y);
var any = false;
foreach (var bounds in Scene.GetAllComponents<IsoCropBounds>()) {
var bbox = bounds.BBox;
if (!any)
tex.Clear(Color.Black);
any = true;
tex.SetPen(Color.White,0);
tex.SetFill(Color.White);
var top = PointToRenderPixels(bounds.WorldPosition + WorldRotation.Left * bounds.Bounds.x * 0.5f + WorldRotation.Up * bounds.Bounds.y * 0.5f).Clamp(0f, RenderSize);
var bot = PointToRenderPixels(bounds.WorldPosition + WorldRotation.Left *-bounds.Bounds.x * 0.5f + WorldRotation.Up *-bounds.Bounds.y * 0.5f).Clamp(0f, RenderSize) - top;
if (bot.IsNearZeroLength)
continue;
tex.DrawRect(new(top,bot));
}
if (!any)
tex.Clear(Color.White);
Mask = tex.ToTexture();
tex.Dispose();
}
private void DoComposite() { //combine world renders, not including ui
var shader = new ComputeShader("shaders/cs_iso_composite_a.shader");
shader.Attributes.Set("Background", Background);
shader.Attributes.Set("BackgroundDepth", BackgroundDepth);
shader.Attributes.Set("EntityShadow", EntityShadow);
shader.Attributes.Set("EntityShadowDepth", EntityShadowDepth);
shader.Attributes.Set("EntityLighting", EntityLighting);
shader.Attributes.Set("WorldText", WorldText);
shader.Attributes.Set("Mask", Mask);
shader.Attributes.Set("Snow", Snow);
shader.Attributes.Set("EntityOffset", DitherOffset - LastEntityRenderOffset);
shader.Attributes.Set("DitherOffset", DitherOffset);
shader.Attributes.Set("RenderSize", Composite.Size);
shader.Attributes.Set("Result", Composite);
shader.Dispatch(Composite.Width, Composite.Height, 1);
}
public void InterfaceRender() {
RenderSnow();
RenderWaypoint();
DoFinalComposite();
}
private TimeUntil SnowUpdateTime {get; set;}
private Vector2 LastDitherOffset {get; set;}
private GpuBuffer<Vector4> SnowPositionBuffer {get; set;}
private void RenderSnow() {
if (SnowPositionBuffer == null)
SnowPositionBuffer = new(2048);
if (SnowUpdateTime || DitherOffset != LastDitherOffset) {
var update = new ComputeShader("shaders/cs_snow_update.shader");
update.Attributes.Set("PositionBuffer", SnowPositionBuffer);
update.Attributes.Set("DeltaTime", SnowUpdateTime.Passed);
update.Attributes.Set("TimeNow", Time.Now);
update.Attributes.Set("DitherOffset", DitherOffset);
update.Attributes.Set("MoveDelta", (DitherOffset - LastDitherOffset) / Snow.Size);
update.Attributes.Set("Update", (bool)SnowUpdateTime);
update.Dispatch(SnowPositionBuffer.ElementCount, 1, 1);
LastDitherOffset = DitherOffset;
if (SnowUpdateTime)
SnowUpdateTime = 1f/20f;
}
var shader = new ComputeShader("shaders/cs_snow_draw.shader");
shader.Attributes.Set("PositionBuffer", SnowPositionBuffer);
shader.Attributes.Set("BufferSize", SnowPositionBuffer.ElementCount);
shader.Attributes.Set("RenderSize", Snow.Size);
shader.Attributes.Set("ZoomScale", ZoomScale);
shader.Attributes.Set("Result", Snow);
shader.Dispatch(Snow.Width, Snow.Height, 1);
}
private void RenderWaypoint() {
RenderCamera.BackgroundColor = Color.Transparent;
RenderCamera.RenderTags.Add("light_directional");
if (Owner.Mouse.InteractMode == PlayerMouse.Mode.Move && Owner.Mouse.TargetLayer != PlayerMouse.Layer.Interface && !Input.Down("drag_camera"))
RenderCamera.RenderTags.Add("iso_waypoint");
RenderCamera.RenderToTexture(Waypoint);
RenderCamera.RenderTags.RemoveAll();
RenderCamera.RenderExcludeTags.Add("no_depth");
RenderCamera.RenderExcludeTags.Add("iso_waypoint");
RenderCamera.RenderExcludeTags.Add("ground");
RenderCamera.RenderExcludeTags.Add("dynamic");
RenderCamera.RenderExcludeTags.Add("flame");
Scene.RenderAttributes.SetCombo("D_MANUAL_DEPTH", true);
RenderCamera.RenderToTexture(WaypointMask);
Scene.RenderAttributes.SetCombo("D_MANUAL_DEPTH", false);
RenderCamera.RenderExcludeTags.RemoveAll();
RenderCamera.BackgroundColor = Color.Black;
}
[Property, Range(-1,1)] public float Fade {get; set;}
private void DoFinalComposite() { //combine world renders, not including ui
var shader = new ComputeShader("shaders/cs_iso_composite_b.shader");
shader.Attributes.Set("Composite", Composite);
shader.Attributes.Set("Snow", Snow);
shader.Attributes.Set("Waypoint", Waypoint);
shader.Attributes.Set("WaypointMask", WaypointMask);
shader.Attributes.Set("EntityShadow", EntityShadow);
shader.Attributes.Set("EntityOffset", DitherOffset - LastEntityRenderOffset);
shader.Attributes.Set("BackgroundDepth", BackgroundDepth);
shader.Attributes.Set("Freeze", Owner.Inventory.Temperature.Remap(-20,0,1,0).SnapToGrid(0.05f));
shader.Attributes.Set("FreezeOverlay", Texture.Load("materials/interface/freeze.vtex"));
shader.Attributes.Set("Fade", Fade);
shader.Attributes.Set("RenderSize", Final.Size);
shader.Attributes.Set("Result", Final);
shader.Dispatch(Final.Width, Final.Height, 1);
}
}
public class IsoCropBounds : Component {
[Property] public Vector2 Bounds {get; set;}
public BBox BBox => new(new Vector3(100, Bounds.x, Bounds.y) *-0.5f, new Vector3(100, Bounds.x, Bounds.y) * 0.5f);
protected override void DrawGizmos() {
Gizmo.Transform = Gizmo.Transform.WithRotation(Rotation.FromYaw(60f) * Rotation.FromPitch(30f));
Vector3 vec = 0.5f;
vec.x = 1000;
vec.y *= Bounds.x;
vec.z *= Bounds.y;
Gizmo.Draw.Color = Color.Black;
Gizmo.Draw.LineThickness = 5;
Gizmo.Draw.LineBBox(new(-vec, vec));
base.DrawGizmos();
}
}