Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Particles #2219

Draft
wants to merge 32 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
b38e410
hacky but kinda working
amylizzle Feb 5, 2025
1abb28f
render target
amylizzle Feb 6, 2025
b697f68
fix offset
amylizzle Feb 6, 2025
6db9202
manager rendering
amylizzle Feb 17, 2025
0129924
Component system
amylizzle Feb 17, 2025
9138e9c
sandbox
amylizzle Feb 17, 2025
3d37193
TODO: stop taking on enormous projects when tired
amylizzle Feb 17, 2025
1334241
jesus christ this is so much shit
amylizzle Feb 18, 2025
82b6b86
generator
amylizzle Feb 18, 2025
b9b9142
more
amylizzle Feb 18, 2025
582f7d5
done! with this part.
amylizzle Feb 19, 2025
dda9e5e
closee
amylizzle Feb 19, 2025
2b51722
list, appearance, bugfixes
amylizzle Feb 19, 2025
a08e3b1
so close
amylizzle Feb 19, 2025
608cfc3
mark component as dirty
amylizzle Feb 19, 2025
0311905
I'm tired and it doesn't work and I don't know why
amylizzle Feb 19, 2025
ee38d34
split components bad
amylizzle Feb 20, 2025
47dc471
it *still* doesn't work and I *still* have no idea why
amylizzle Feb 20, 2025
32f9e1f
manual state
amylizzle Feb 20, 2025
2aebbc9
drawing
amylizzle Feb 20, 2025
572791b
whoops
amylizzle Feb 20, 2025
fb02985
working!!!!
amylizzle Feb 20, 2025
4f2e9a2
clean up generator
amylizzle Feb 20, 2025
58950e8
only one particle
amylizzle Feb 20, 2025
22c4f3d
Fix autogenerated component state stuff
amylizzle Feb 20, 2025
de1ba9f
friction and drift
amylizzle Feb 20, 2025
e5c568b
render above
amylizzle Feb 20, 2025
45e7f16
cleanup
amylizzle Feb 20, 2025
51638ab
Merge remote-tracking branch 'upstream/master' into particles_od
amylizzle Feb 20, 2025
dceeccd
Merge branch 'master' into particles_od
amylizzle Feb 20, 2025
007360d
viewport changes
amylizzle Feb 23, 2025
6587932
Merge branch 'master' into particles_od
amylizzle Feb 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DMCompiler/DMStandard/Types/Atoms/Movable.dm
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
//Undocumented var. "[x],[y]" or "[x],[y] to [x2],[y2]" based on bound_* vars
var/bounds as opendream_unimplemented

var/particles/particles as opendream_unimplemented
var/particles/particles

proc/Bump(atom/Obstacle)

Expand Down
48 changes: 24 additions & 24 deletions DMCompiler/DMStandard/Types/Particles.dm
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,34 @@
/particles
parent_type = /datum
//Particle vars that affect the entire set (generators are not allowed for these)
var/width = 100 as opendream_unimplemented //null defaults to 0. width is the size of the particle "image" ie particles within this width image will be rendered, if they are partially in they get partially cut. if they reenter this area after leaving it they reapper. image is centered on particle owner.
var/height = 100 as opendream_unimplemented //ditto
var/count = 100 as opendream_unimplemented // if null, uses the last set value. is checked BEFORE lifespan so (count 10, lifespan 10, spawning 1) will skip a pixel every 10 pixels
var/spawning = 1 as opendream_unimplemented // null is treated as 0
var/bound1 = -1000 as opendream_unimplemented // Usually list but if a number treated as list(bound1, bound1, bound1). if particles go above/below bound they will get immediately deleted regardless of lifespan. null is treated as the default value (-1000 and 1000)(this could be treated as infinity as well but 1000 is so large its hard to tell)
var/bound2 = 1000 as opendream_unimplemented // Ditto!
var/gravity as opendream_unimplemented // Usually list but if a number treated as list(gravity, gravity, gravity).
var/list/gradient = null as opendream_unimplemented // not cast as a list on byond as of 514.1580 despite only being able to be a list
var/transform as opendream_unimplemented // matrix or list. list can be simple matrix, complex matrix or projection matrix. thus: list(a, b, c, d, e, f) OR list(xx,xy,xz, yx,yy,yz, zx,zy,zz) OR list(xx,xy,xz, yx,yy,yz, zx,zy,zz, cx,cy,cz) OR list(xx,xy,xz,xw, yx,yy,yz,yw, zx,zy,zz,zw, wx,wy,wz,ww)
var/width = 100 //null defaults to 0. width is the size of the particle "image" ie particles within this width image will be rendered, if they are partially in they get partially cut. if they reenter this area after leaving it they reapper. image is centered on particle owner.
var/height = 100 //ditto
var/count = 100 // if null, uses the last set value. is checked BEFORE lifespan so (count 10, lifespan 10, spawning 1) will skip a pixel every 10 pixels
var/spawning = 1 // null is treated as 0
var/bound1 = -1000 // Usually list but if a number treated as list(bound1, bound1, bound1). if particles go above/below bound they will get immediately deleted regardless of lifespan. null is treated as the default value (-1000 and 1000)(this could be treated as infinity as well but 1000 is so large its hard to tell)
var/bound2 = 1000 // Ditto!
var/gravity // Usually list but if a number treated as list(gravity, gravity, gravity).
var/list/gradient = null // not cast as a list on byond as of 514.1580 despite only being able to be a list
var/transform // matrix or list. list can be simple matrix, complex matrix or projection matrix. thus: list(a, b, c, d, e, f) OR list(xx,xy,xz, yx,yy,yz, zx,zy,zz) OR list(xx,xy,xz, yx,yy,yz, zx,zy,zz, cx,cy,cz) OR list(xx,xy,xz,xw, yx,yy,yz,yw, zx,zy,zz,zw, wx,wy,wz,ww)

//Vars that apply when a particle spawns
var/lifespan as opendream_unimplemented // actual time a particle exists is fadein + lifespan + fade. thus this just the time it spends fully faded in. null is treated as
var/fade as opendream_unimplemented // null treated as 0
var/fadein as opendream_unimplemented // null treated as 0
var/icon as opendream_unimplemented // either icon or list(icon = weightofthisicon, icon = weightofthisicon) if null defaults to a 1x1 white pixel
var/icon_state as opendream_unimplemented // either string or list(string = weightofthisiconstate, string = weightofthisiconstate) if null defaults to a 1x1 white pixel
var/color as opendream_unimplemented // null treated as 0
var/color_change as opendream_unimplemented // null treated as 0
var/position as opendream_unimplemented // Usually list but if a number treated as list(position, position, position). null is treated as 0
var/velocity as opendream_unimplemented // Usually list but if a number treated as list(velocity, velocity, velocity). null is treated as 0
var/scale as opendream_unimplemented // if null defaults to 1, if number treated as list(scale, scale)
var/grow as opendream_unimplemented // if null defaults to 0, if number treated as list(grow, grow)
var/rotation as opendream_unimplemented // null treated as 0
var/spin as opendream_unimplemented // null treated as 0
var/friction as opendream_unimplemented // null treated as 0, numbers below 0 treated as 0
var/lifespan // actual time a particle exists is fadein + lifespan + fade. thus this just the time it spends fully faded in. null is treated as
var/fade // null treated as 0
var/fadein // null treated as 0
var/icon // either icon or list(icon = weightofthisicon, icon = weightofthisicon) if null defaults to a 1x1 white pixel
var/icon_state // either string or list(string = weightofthisiconstate, string = weightofthisiconstate) if null defaults to a 1x1 white pixel
var/color // null treated as 0
var/color_change // null treated as 0
var/position // Usually list but if a number treated as list(position, position, position). null is treated as 0
var/velocity // Usually list but if a number treated as list(velocity, velocity, velocity). null is treated as 0
var/scale // if null defaults to 1, if number treated as list(scale, scale)
var/grow // if null defaults to 0, if number treated as list(grow, grow)
var/rotation // null treated as 0
var/spin // null treated as 0
var/friction // null treated as 0, numbers below 0 treated as 0

//Vars that are evaluated every tick
var/drift as opendream_unimplemented // Usually list but if a number treated as list(drift, drift, drift)
var/drift // Usually list but if a number treated as list(drift, drift, drift)

//misc notes
// particle image height/width is not considered for TILE_BOUND-less atoms
Expand Down
2 changes: 0 additions & 2 deletions DMCompiler/DMStandard/UnsortedAdditions.dm
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
set opendream_unimplemented = TRUE
/proc/findtextEx_char(Haystack,Needle,Start=1,End=0)
set opendream_unimplemented = TRUE
/proc/generator(type, A, B, rand)
set opendream_unimplemented = TRUE
/proc/load_resource(File)
set opendream_unimplemented = TRUE
proc/missile(Type, Start, End)
Expand Down
1 change: 1 addition & 0 deletions DMCompiler/DMStandard/_Standard.dm
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ proc/flist(Path) as /list
proc/floor(A) as num
proc/fract(n) as num
proc/ftime(File, IsCreationTime = 0) as num
proc/generator(type, A, B, rand) as /generator
proc/get_step_to(Ref, Trg, Min=0) as num
proc/get_steps_to(Ref, Trg, Min=0) as /list
proc/gradient(A, index)
Expand Down
4 changes: 2 additions & 2 deletions OpenDreamClient/Interface/Controls/UI/ScalingViewport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ protected override void KeyBindUp(GUIBoundKeyEventArgs args) {
_inputManager.ViewportKeyEvent(this, args);
}

protected override void Draw(IRenderHandle handle) {
protected override void Draw(DrawingHandleScreen handle) {
EnsureViewportCreated();

DebugTools.AssertNotNull(_viewport);
Expand All @@ -133,7 +133,7 @@ protected override void Draw(IRenderHandle handle) {
var drawBox = GetDrawBox();
var drawBoxGlobal = drawBox.Translated(GlobalPixelPosition);
_viewport.RenderScreenOverlaysBelow(handle, this, drawBoxGlobal);
handle.DrawingHandleScreen.DrawTextureRect(_viewport.RenderTarget.Texture, drawBox);
handle.DrawTextureRect(_viewport.RenderTarget.Texture, drawBox);
_viewport.RenderScreenOverlaysAbove(handle, this, drawBoxGlobal);
}

Expand Down
114 changes: 114 additions & 0 deletions OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using JetBrains.Annotations;
using OpenDreamShared.Rendering;
using Robust.Client.Graphics;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Vector3 = Robust.Shared.Maths.Vector3;

namespace OpenDreamClient.Rendering;

[UsedImplicitly]
public sealed class ClientDreamParticlesSystem : SharedDreamParticlesSystem
{
[Dependency] private readonly ParticlesManager _particlesManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly ClientAppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly IClyde _clyde = default!;
public RenderTargetPool RenderTargetPool = default!;
private Random random = new();
private RendererMetaData defaultRenderMetaData = new(); //used for icon GetTexture(), never needs anything but default settings

public override void Initialize() {
base.Initialize();
SubscribeLocalEvent<DreamParticlesComponent, AfterAutoHandleStateEvent>(OnDreamParticlesComponentChange);
SubscribeLocalEvent<DreamParticlesComponent, ComponentRemove>(HandleComponentRemove);
RenderTargetPool = new(_clyde);
}

private void OnDreamParticlesComponentChange(EntityUid uid, DreamParticlesComponent component, ref AfterAutoHandleStateEvent args)
{
if(_particlesManager.TryGetParticleSystem(uid, out var system))
system.UpdateSystem(GetParticleSystemArgs(component));
else
_particlesManager.CreateParticleSystem(uid, GetParticleSystemArgs(component));
}
private void HandleComponentRemove(EntityUid uid, DreamParticlesComponent component, ref ComponentRemove args)
{
_particlesManager.DestroyParticleSystem(uid);
}

private ParticleSystemArgs GetParticleSystemArgs(DreamParticlesComponent component){
Func<Texture> textureFunc;
if(component.TextureList is null || component.TextureList.Length == 0)
textureFunc = () => Texture.White;
else{
List<DreamIcon> icons = new(component.TextureList.Length);
foreach(var appearance in component.TextureList){
DreamIcon icon = new DreamIcon(RenderTargetPool, _gameTiming, _clyde, _appearanceSystem);
icon.SetAppearance(appearance.MustGetId());
icons.Add(icon);
}
textureFunc = () => random.Pick(icons).GetTexture(null!, null!, defaultRenderMetaData, null) ?? Texture.White; //oh god, so hacky
}
var result = new ParticleSystemArgs(textureFunc, new Vector2i(component.Width, component.Height), (uint)component.Count, component.Spawning);
GeneratorFloat lifespan = new();
result.Lifespan = GetGeneratorFloat(component.LifespanLow, component.LifespanHigh, component.LifespanType);
result.Fadein = GetGeneratorFloat(component.FadeInLow, component.FadeInHigh, component.FadeInType);
result.Fadeout = GetGeneratorFloat(component.FadeOutLow, component.FadeOutHigh, component.FadeOutType);
if(component.Gradient.Length > 0)
result.Color = (float lifetime) => {
var colorIndex = (int)(lifetime * component.Gradient.Length);
colorIndex = Math.Clamp(colorIndex, 0, component.Gradient.Length - 1);
return component.Gradient[colorIndex];
};
else
result.Color = (float lifetime) => Color.White;
result.Acceleration = (float _ , Vector3 velocity) => GetGeneratorVector3(component.AccelerationLow, component.AccelerationHigh, component.AccelerationType)() + GetGeneratorVector3(component.DriftLow, component.DriftHigh, component.DriftType)() - velocity*GetGeneratorVector3(component.FrictionLow, component.FrictionHigh, component.FrictionType)();
result.SpawnPosition = GetGeneratorVector3(component.SpawnPositionLow, component.SpawnPositionHigh, component.SpawnPositionType);
result.SpawnVelocity = GetGeneratorVector3(component.SpawnVelocityLow, component.SpawnVelocityHigh, component.SpawnVelocityType);
result.Transform = (float lifetime) => {
var scale = GetGeneratorVector2(component.ScaleLow, component.ScaleHigh, component.ScaleType)();
var rotation = GetGeneratorFloat(component.RotationLow, component.RotationHigh, component.RotationType)();
var growth = GetGeneratorVector2(component.GrowthLow, component.GrowthHigh, component.GrowthType)();
var spin = GetGeneratorFloat(component.SpinLow, component.SpinHigh, component.SpinType)();
return Matrix3x2.CreateScale(scale.X + growth.X, scale.Y + growth.Y) *
Matrix3x2.CreateRotation(rotation + spin);
};
result.BaseTransform = Matrix3x2.Identity;

return result;
}

private Func<float> GetGeneratorFloat(float low, float high, ParticlePropertyType type){
switch (type) {
case ParticlePropertyType.HighValue:
return () => high;
case ParticlePropertyType.RandomUniform:
return () => random.NextFloat(low, high);
default:
throw new NotImplementedException();
}
}

private Func<Vector2> GetGeneratorVector2(Vector2 low, Vector2 high, ParticlePropertyType type){
switch (type) {
case ParticlePropertyType.HighValue:
return () => high;
case ParticlePropertyType.RandomUniform:
return () => new Vector2(random.NextFloat(low.X, high.X), random.NextFloat(low.Y, high.Y));
default:
throw new NotImplementedException();
}
}

private Func<Vector3> GetGeneratorVector3(Vector3 low, Vector3 high, ParticlePropertyType type){
switch (type) {
case ParticlePropertyType.HighValue:
return () => high;
case ParticlePropertyType.RandomUniform:
return () => new Vector3(random.NextFloat(low.X, high.X), random.NextFloat(low.Y, high.Y), random.NextFloat(low.Z, high.Z));
default:
throw new NotImplementedException();
}
}
}
14 changes: 13 additions & 1 deletion OpenDreamClient/Rendering/DreamViewOverlay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ internal sealed partial class DreamViewOverlay : Overlay {
[Dependency] private readonly IDreamInterfaceManager _interfaceManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly ParticlesManager _particlesManager = default!;

[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
Expand Down Expand Up @@ -373,7 +375,10 @@ private void ProcessIconComponents(DreamIcon icon, Vector2 position, EntityUid u
result.Add(maptext);
}

//TODO particles - colour and transform don't apply?
//query entity for particles component - check for parent to make sure this is the top level entity
if(parentIcon is null && _particlesManager.TryGetParticleSystem(uid, out var particlesSystem)){
current.Particles = particlesSystem;
}

//flatten KeepTogetherGroup. Done here so we get implicit recursive iteration down the tree.
if (current.KeepTogetherGroup?.Count > 0) {
Expand Down Expand Up @@ -462,6 +467,11 @@ public void DrawIcon(DrawingHandleWorld handle, Vector2i renderTargetSize, Rende

handle.SetTransform(CalculateDrawingMatrix(iconMetaData.TransformToApply, pixelPosition, frame.Size, renderTargetSize));
handle.DrawTextureRect(frame, Box2.FromDimensions(Vector2.Zero, frame.Size));

if(iconMetaData.Particles is not null) {
handle.UseShader(GetBlendAndColorShader(iconMetaData, ignoreColor: true));
iconMetaData.Particles.Draw(handle, CalculateDrawingMatrix(iconMetaData.TransformToApply, pixelPosition, iconMetaData.Particles.RenderSize, renderTargetSize));
}
}

/// <summary>
Expand Down Expand Up @@ -799,6 +809,7 @@ internal sealed class RendererMetaData : IComparable<RendererMetaData> {
public Texture? TextureOverride;
public string? Maptext;
public Vector2i? MaptextSize;
public ParticleSystem? Particles;

public bool IsPlaneMaster => (AppearanceFlags & AppearanceFlags.PlaneMaster) != 0;
public bool HasRenderSource => !string.IsNullOrEmpty(RenderSource);
Expand Down Expand Up @@ -830,6 +841,7 @@ public void Reset() {
TextureOverride = null;
Maptext = null;
MaptextSize = null;
Particles = null;
}

public Texture? GetTexture(DreamViewOverlay viewOverlay, DrawingHandleWorld handle) {
Expand Down
1 change: 1 addition & 0 deletions OpenDreamRuntime/Objects/DreamObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public class DreamObject {
protected PvsOverrideSystem? PvsOverrideSystem => ObjectDefinition.PvsOverrideSystem;
protected MetaDataSystem? MetaDataSystem => ObjectDefinition.MetaDataSystem;
protected ServerVerbSystem? VerbSystem => ObjectDefinition.VerbSystem;
protected ServerDreamParticlesSystem? ParticlesSystem => ObjectDefinition.ParticlesSystem;

protected Dictionary<string, DreamValue>? Variables;
//handle to the list of vars on this object so that it's only created once and refs to object.vars are consistent
Expand Down
5 changes: 4 additions & 1 deletion OpenDreamRuntime/Objects/DreamObjectDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public sealed class DreamObjectDefinition {
public readonly PvsOverrideSystem? PvsOverrideSystem;
public readonly MetaDataSystem? MetaDataSystem;
public readonly ServerVerbSystem? VerbSystem;
public readonly ServerDreamParticlesSystem? ParticlesSystem;

public readonly TreeEntry TreeEntry;
public string Type => TreeEntry.Path;
Expand Down Expand Up @@ -74,6 +75,7 @@ public DreamObjectDefinition(DreamObjectDefinition copyFrom) {
PvsOverrideSystem = copyFrom.PvsOverrideSystem;
MetaDataSystem = copyFrom.MetaDataSystem;
VerbSystem = copyFrom.VerbSystem;
ParticlesSystem = copyFrom.ParticlesSystem;

TreeEntry = copyFrom.TreeEntry;
InitializationProc = copyFrom.InitializationProc;
Expand All @@ -88,7 +90,7 @@ public DreamObjectDefinition(DreamObjectDefinition copyFrom) {
Verbs = new List<int>(copyFrom.Verbs);
}

public DreamObjectDefinition(DreamManager dreamManager, DreamObjectTree objectTree, AtomManager atomManager, IDreamMapManager dreamMapManager, IMapManager mapManager, DreamResourceManager dreamResourceManager, WalkManager walkManager, IEntityManager entityManager, IPlayerManager playerManager, ISerializationManager serializationManager, ServerAppearanceSystem? appearanceSystem, TransformSystem? transformSystem, PvsOverrideSystem? pvsOverrideSystem, MetaDataSystem? metaDataSystem, ServerVerbSystem? verbSystem, TreeEntry? treeEntry) {
public DreamObjectDefinition(DreamManager dreamManager, DreamObjectTree objectTree, AtomManager atomManager, IDreamMapManager dreamMapManager, IMapManager mapManager, DreamResourceManager dreamResourceManager, WalkManager walkManager, IEntityManager entityManager, IPlayerManager playerManager, ISerializationManager serializationManager, ServerAppearanceSystem? appearanceSystem, TransformSystem? transformSystem, PvsOverrideSystem? pvsOverrideSystem, MetaDataSystem? metaDataSystem, ServerVerbSystem? verbSystem, ServerDreamParticlesSystem? particlesSystem, TreeEntry? treeEntry) {
DreamManager = dreamManager;
ObjectTree = objectTree;
AtomManager = atomManager;
Expand All @@ -104,6 +106,7 @@ public DreamObjectDefinition(DreamManager dreamManager, DreamObjectTree objectTr
PvsOverrideSystem = pvsOverrideSystem;
MetaDataSystem = metaDataSystem;
VerbSystem = verbSystem;
ParticlesSystem = particlesSystem;

TreeEntry = treeEntry;

Expand Down
Loading
Loading