Skip to content

Commit 00b46c3

Browse files
authored
Generator rework (#2456)
1 parent 0c1f213 commit 00b46c3

File tree

11 files changed

+505
-424
lines changed

11 files changed

+505
-424
lines changed

OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs

Lines changed: 19 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ public sealed class ClientDreamParticlesSystem : SharedDreamParticlesSystem {
1515
[Dependency] private readonly ClientAppearanceSystem _appearanceSystem = default!;
1616
[Dependency] private readonly IDreamInterfaceManager _dreamInterfaceManager = default!;
1717
[Dependency] private readonly IClyde _clyde = default!;
18+
[Dependency] private readonly IRobustRandom _random = default!;
1819

1920
private RenderTargetPool _renderTargetPool = default!;
20-
private readonly Random _random = new();
2121

2222
//used for icon GetTexture(), never needs anything but default settings
2323
private readonly RendererMetaData _defaultRenderMetaData = new();
@@ -56,25 +56,31 @@ private ParticleSystemArgs GetParticleSystemArgs(DreamParticlesComponent compone
5656
textureFunc = () => _random.Pick(icons).GetTexture(null!, null!, _defaultRenderMetaData, null, null);
5757
}
5858

59-
var result = new ParticleSystemArgs(textureFunc, new Vector2i(component.Width, component.Height), (uint)component.Count, component.Spawning) {
60-
Lifespan = GetGeneratorFloat(component.LifespanLow, component.LifespanHigh, component.LifespanDist),
61-
Fadein = GetGeneratorFloat(component.FadeInLow, component.FadeInHigh, component.FadeInDist),
62-
Fadeout = GetGeneratorFloat(component.FadeOutLow, component.FadeOutHigh, component.FadeOutDist),
59+
var perTick = (1f / 10f); // "Tick" refers to a BYOND standard tick of 0.1s. --DM Reference
60+
var result = new ParticleSystemArgs(textureFunc, new Vector2i(component.Width, component.Height), (uint)component.Count, component.Spawning / perTick) {
61+
Lifespan = () => (component.Lifespan?.Generate(_random) ?? 1f) * perTick,
62+
Fadein = () => (component.FadeIn?.Generate(_random) ?? 0f) * perTick,
63+
Fadeout = () => (component.FadeOut?.Generate(_random) ?? 0f) * perTick,
6364
Color = component.Gradient.Length > 0
6465
? lifetime => {
6566
var colorIndex = (int)(lifetime * component.Gradient.Length);
6667
colorIndex = Math.Clamp(colorIndex, 0, component.Gradient.Length - 1);
6768
return component.Gradient[colorIndex];
6869
}
6970
: _ => Color.White,
70-
Acceleration = (_ , velocity) => GetGeneratorVector3(component.AccelerationLow, component.AccelerationHigh, component.AccelerationType, component.AccelerationDist)() + GetGeneratorVector3(component.DriftLow, component.DriftHigh, component.DriftType, component.DriftDist)() - velocity*GetGeneratorVector3(component.FrictionLow, component.FrictionHigh, component.FrictionType, component.FrictionDist)(),
71-
SpawnPosition = GetGeneratorVector3(component.SpawnPositionLow, component.SpawnPositionHigh, component.SpawnPositionType, component.SpawnPositionDist),
72-
SpawnVelocity = GetGeneratorVector3(component.SpawnVelocityLow, component.SpawnVelocityHigh, component.SpawnVelocityType, component.SpawnVelocityDist),
73-
Transform = _ => {
74-
var scale = GetGeneratorVector2(component.ScaleLow, component.ScaleHigh, component.ScaleType, component.ScaleDist)();
75-
var rotation = GetGeneratorFloat(component.RotationLow, component.RotationHigh, component.RotationDist)();
76-
var growth = GetGeneratorVector2(component.GrowthLow, component.GrowthHigh, component.GrowthType, component.GrowthDist)();
77-
var spin = GetGeneratorFloat(component.SpinLow, component.SpinHigh, component.SpinDist)();
71+
Acceleration = (_, velocity) => { // TODO: Acceleration needs to only update every tick
72+
var drift = (component.Drift?.GenerateVector3(_random) ?? Vector3.Zero);
73+
var friction = (component.Friction?.GenerateVector3(_random) ?? Vector3.Zero); // TODO: Only calculated once per particle
74+
75+
return drift - (velocity * friction);
76+
},
77+
SpawnPosition = () => component.SpawnPosition?.GenerateVector3(_random) ?? Vector3.Zero,
78+
SpawnVelocity = () => component.SpawnVelocity?.GenerateVector3(_random) ?? Vector3.Zero,
79+
Transform = _ => { // TODO: Needs to only be performed every tick
80+
var scale = component.Scale.GenerateVector2(_random);
81+
var rotation = component.Rotation?.Generate(_random) ?? 0f;
82+
var growth = component.Growth?.GenerateVector2(_random) ?? Vector2.Zero;
83+
var spin = component.Spin?.Generate(_random) ?? 0f;
7884
return Matrix3x2.CreateScale(scale.X + growth.X, scale.Y + growth.Y) *
7985
Matrix3x2.CreateRotation(rotation + spin);
8086
},
@@ -83,88 +89,4 @@ private ParticleSystemArgs GetParticleSystemArgs(DreamParticlesComponent compone
8389

8490
return result;
8591
}
86-
87-
private Func<float> GetGeneratorFloat(float low, float high, GeneratorDistribution distribution) {
88-
switch (distribution) {
89-
case GeneratorDistribution.Constant:
90-
return () => high;
91-
case GeneratorDistribution.Uniform:
92-
return () => _random.NextFloat(low, high);
93-
case GeneratorDistribution.Normal:
94-
return () => (float)Math.Clamp(_random.NextGaussian((low + high) / 2, (high - low) / 6), low, high);
95-
case GeneratorDistribution.Linear:
96-
return () => MathF.Sqrt(_random.NextFloat(0, 1)) * (high - low) + low;
97-
case GeneratorDistribution.Square:
98-
return () => MathF.Cbrt(_random.NextFloat(0, 1)) * (high - low) + low;
99-
default:
100-
throw new NotImplementedException();
101-
}
102-
}
103-
104-
private Func<Vector2> GetGeneratorVector2(Vector2 low, Vector2 high, GeneratorOutputType type, GeneratorDistribution distribution){
105-
switch (type) {
106-
case GeneratorOutputType.Num:
107-
return () => new Vector2(GetGeneratorFloat(low.X, high.X, distribution)(), GetGeneratorFloat(low.Y, high.Y, distribution)());
108-
case GeneratorOutputType.Vector:
109-
return () => Vector2.Lerp(low, high, GetGeneratorFloat(0, 1, distribution)());
110-
case GeneratorOutputType.Box:
111-
return () => new Vector2(GetGeneratorFloat(low.X, high.X, distribution)(), GetGeneratorFloat(low.Y, high.Y, distribution)());
112-
case GeneratorOutputType.Circle:
113-
var theta = _random.NextFloat(0, 360);
114-
//polar -> cartesian, radius between low and high, angle uniform sample
115-
return () => new Vector2(MathF.Cos(theta) * GetGeneratorFloat(low.X, high.X, distribution)(), MathF.Sin(theta) * GetGeneratorFloat(low.Y, high.Y, distribution)());
116-
case GeneratorOutputType.Square:
117-
return () => {
118-
var x = GetGeneratorFloat(-high.X, high.X, distribution)();
119-
var y = GetGeneratorFloat(-high.Y, high.Y, distribution)();
120-
if (MathF.Abs(x) < low.X)
121-
y = _random.NextByte() > 128
122-
? GetGeneratorFloat(-high.Y, -low.Y, distribution)()
123-
: GetGeneratorFloat(low.Y, high.Y, distribution)();
124-
return new(x, y);
125-
};
126-
default:
127-
throw new NotImplementedException($"Unimplemented generator output type {type}");
128-
}
129-
}
130-
131-
private Func<Vector3> GetGeneratorVector3(Vector3 low, Vector3 high, GeneratorOutputType type, GeneratorDistribution distribution){
132-
switch (type) {
133-
case GeneratorOutputType.Num:
134-
return () => new Vector3(GetGeneratorFloat(low.X, high.X, distribution)(), GetGeneratorFloat(low.Y, high.Y, distribution)(), GetGeneratorFloat(low.Z, high.Z, distribution)());
135-
case GeneratorOutputType.Vector:
136-
return () => Vector3.Lerp(low, high, GetGeneratorFloat(0, 1, distribution)());
137-
case GeneratorOutputType.Box:
138-
return () => new Vector3(GetGeneratorFloat(low.X, high.X, distribution)(), GetGeneratorFloat(low.Y, high.Y, distribution)(), GetGeneratorFloat(low.Z, high.Z, distribution)());
139-
case GeneratorOutputType.Sphere:
140-
var theta = _random.NextFloat(0, 360);
141-
var phi = _random.NextFloat(0, 180);
142-
//3d polar -> cartesian, radius between low and high, angle uniform sample
143-
return () => new Vector3(
144-
MathF.Cos(theta) * MathF.Sin(phi) * GetGeneratorFloat(low.X, high.X, distribution)(),
145-
MathF.Sin(theta) * MathF.Sin(phi) * GetGeneratorFloat(low.Y, high.Y, distribution)(),
146-
MathF.Cos(phi) * GetGeneratorFloat(low.Z, high.Z, distribution)()
147-
);
148-
case GeneratorOutputType.Cube:
149-
return () => {
150-
var x = GetGeneratorFloat(-high.X, high.X, distribution)();
151-
var y = GetGeneratorFloat(-high.Y, high.Y, distribution)();
152-
var z = GetGeneratorFloat(-high.Z, high.Z, distribution)();
153-
if (MathF.Abs(x) < low.X)
154-
y = _random.NextByte() > 128
155-
? GetGeneratorFloat(-high.Y, -low.Y, distribution)()
156-
: GetGeneratorFloat(low.Y, high.Y, distribution)();
157-
if (MathF.Abs(y) < low.Y)
158-
z = _random.NextByte() > 128
159-
? GetGeneratorFloat(-high.Z, -low.Z, distribution)()
160-
: GetGeneratorFloat(low.Z, high.Z, distribution)();
161-
return new(x, y, z);
162-
};
163-
case GeneratorOutputType.Circle:
164-
case GeneratorOutputType.Square:
165-
return () => new Vector3(GetGeneratorVector2(new(low.X, low.Y), new(high.X, high.Y), type, distribution)(), 0);
166-
default:
167-
throw new NotImplementedException($"Unimplemented generator output type {type}");
168-
}
169-
}
17092
}

OpenDreamClient/Rendering/Particles/ParticlesManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ public void FrameUpdate(FrameEventArgs args) {
179179
_particles[i].Lifetime += args.DeltaSeconds;
180180
_particles[i].Transform = _baseTransform * _transform(_particles[i].Lifetime);
181181
_particles[i].Color = _color(_particles[i].Lifetime);
182-
_particles[i].Velocity += _acceleration(_particles[i].Lifetime, _particles[i].Velocity) * args.DeltaSeconds;
182+
_particles[i].Velocity += _acceleration(_particles[i].Lifetime, _particles[i].Velocity);
183183
_particles[i].Position += _particles[i].Velocity*args.DeltaSeconds;
184184
if(_particles[i].Fadein > _particles[i].Lifetime)
185185
_particles[i].Color.A = Math.Clamp(_particles[i].Lifetime/_particles[i].Fadein, 0, 1);

OpenDreamRuntime/Objects/DreamObjectTree.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,6 @@ public DreamObject CreateObject(TreeEntry type) {
180180
return new DreamObjectArea(type.ObjectDefinition);
181181
if (type.ObjectDefinition.IsSubtypeOf(Atom))
182182
return new DreamObjectAtom(type.ObjectDefinition);
183-
if (type.ObjectDefinition.IsSubtypeOf(Generator))
184-
throw new Exception("Cannot create objects of type /generator without the generator() proc");
185183
if (type.ObjectDefinition.IsSubtypeOf(Particles))
186184
return new DreamObjectParticles(type.ObjectDefinition);
187185
if (type.ObjectDefinition.IsSubtypeOf(Client))
@@ -194,6 +192,8 @@ public DreamObject CreateObject(TreeEntry type) {
194192
return new DreamObjectCallee(type.ObjectDefinition);
195193
if (type.ObjectDefinition.IsSubtypeOf(Vector))
196194
return new DreamObjectVector(type.ObjectDefinition);
195+
if (type.ObjectDefinition.IsSubtypeOf(Generator))
196+
return new DreamObjectGenerator(type.ObjectDefinition);
197197

198198
return new DreamObject(type.ObjectDefinition);
199199
}

OpenDreamRuntime/Objects/Types/DreamObjectAtom.cs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using OpenDreamRuntime.Procs;
2-
3-
namespace OpenDreamRuntime.Objects.Types;
1+
namespace OpenDreamRuntime.Objects.Types;
42

53
[Virtual]
64
public class DreamObjectAtom : DreamObject {
@@ -149,12 +147,28 @@ protected override void SetVar(string varName, DreamValue value) {
149147
case "filters": {
150148
Filters.Cut();
151149

152-
if (value.TryGetValueAsDreamList(out var valueList)) { // filters = list("type"=...)
153-
var filterObject = DreamObjectFilter.TryCreateFilter(ObjectTree, valueList);
154-
if (filterObject == null) // list() with invalid "type" is ignored
155-
break;
156-
157-
Filters.AddValue(new(filterObject));
150+
// filters = list("type"=...) or list(filter(...), filter(...))
151+
if (value.TryGetValueAsDreamList(out var valueList)) {
152+
if (valueList.GetValue(new("type")) != DreamValue.Null) { // It's a single filter
153+
var filterObject = DreamObjectFilter.TryCreateFilter(ObjectTree, valueList);
154+
if (filterObject == null) // list() with invalid "type" is ignored
155+
break;
156+
157+
Filters.AddValue(new(filterObject));
158+
} else { // It's a list of filters
159+
foreach (var filter in valueList.EnumerateValues()) {
160+
if (!filter.TryGetValueAsDreamObject<DreamObjectFilter>(out var filterObject)) {
161+
if (!filter.TryGetValueAsDreamList(out var filterValues))
162+
continue;
163+
164+
filterObject = DreamObjectFilter.TryCreateFilter(ObjectTree, filterValues);
165+
if (filterObject == null)
166+
continue;
167+
}
168+
169+
Filters.AddValue(new(filterObject));
170+
}
171+
}
158172
} else if (!value.IsNull) {
159173
Filters.AddValue(value);
160174
}
Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,84 @@
1-
using OpenDreamShared.Rendering;
1+
using OpenDreamRuntime.Procs;
2+
using OpenDreamShared.Dream;
23

34
namespace OpenDreamRuntime.Objects.Types;
45

5-
public sealed class DreamObjectGenerator(DreamObjectDefinition objectDefinition, DreamValue a, DreamValue b, GeneratorOutputType outputType, GeneratorDistribution distribution) : DreamObject(objectDefinition) {
6-
public DreamValue A { get; private set; } = a;
7-
public DreamValue B { get; private set; } = b;
8-
public GeneratorOutputType OutputType { get; private set; } = outputType;
9-
public GeneratorDistribution Distribution { get; private set; } = distribution;
6+
public sealed class DreamObjectGenerator(DreamObjectDefinition objectDefinition) : DreamObject(objectDefinition) {
7+
public IGenerator Generator { get; private set; } = default!;
8+
9+
public override void Initialize(DreamProcArguments args) {
10+
var type = args.GetArgument(0);
11+
if (!type.TryGetValueAsString(out var typeStr))
12+
throw new Exception($"Invalid generator type {type}");
13+
14+
var a = args.GetArgument(1);
15+
var b = args.GetArgument(2);
16+
var distribution = DistributionNumberToEnum((int)args.GetArgument(3).UnsafeGetValueAsFloat());
17+
18+
switch (typeStr) {
19+
case "num":
20+
case "circle":
21+
case "sphere": {
22+
var low = a.UnsafeGetValueAsFloat();
23+
var high = (b.Type == DreamValue.DreamValueType.Float) ? b.UnsafeGetValueAsFloat() : 1f;
24+
25+
Generator = typeStr switch {
26+
"num" => new GeneratorNum(low, high, distribution),
27+
"circle" => new GeneratorCircle(low, high, distribution),
28+
"sphere" => new GeneratorSphere(low, high, distribution),
29+
_ => throw new ArgumentOutOfRangeException()
30+
};
31+
32+
break;
33+
}
34+
case "vector":
35+
case "box": {
36+
var low = DreamObjectVector.CreateFromValue(a, ObjectTree);
37+
var high = DreamObjectVector.CreateFromValue(b, ObjectTree);
38+
39+
if (low.Is3D || high.Is3D)
40+
Generator = typeStr == "vector"
41+
? new GeneratorVector3(low.AsVector3, high.AsVector3, distribution)
42+
: new GeneratorBox3(low.AsVector3, high.AsVector3, distribution);
43+
else
44+
Generator = typeStr == "vector"
45+
? new GeneratorVector2(low.AsVector2, high.AsVector2, distribution)
46+
: new GeneratorBox2(low.AsVector2, high.AsVector2, distribution);
47+
break;
48+
}
49+
case "square":
50+
case "cube": {
51+
var low = DreamObjectVector.CreateFromValue(a, ObjectTree);
52+
var high = DreamObjectVector.CreateFromValue(b, ObjectTree);
53+
54+
Generator = typeStr switch {
55+
"square" => new GeneratorSquare(low.AsVector2, high.AsVector2, distribution),
56+
"cube" => new GeneratorCube(low.AsVector3, high.AsVector3, distribution),
57+
_ => throw new ArgumentOutOfRangeException()
58+
};
59+
60+
break;
61+
}
62+
default:
63+
throw new Exception($"Invalid generator type {type}");
64+
}
65+
}
66+
67+
public T RequireType<T>() where T : IGenerator {
68+
if (Generator is not T casted)
69+
throw new Exception($"Expected generator type {typeof(T)} but got {Generator.GetType()}");
70+
71+
return casted;
72+
}
73+
74+
private GeneratorDistribution DistributionNumberToEnum(int number) {
75+
return number switch {
76+
0 => GeneratorDistribution.Uniform,
77+
1 => GeneratorDistribution.Normal,
78+
2 => GeneratorDistribution.Linear,
79+
3 => GeneratorDistribution.Square,
80+
_ => GeneratorDistribution.Uniform // Default to UNIFORM_RAND
81+
};
82+
}
1083
}
1184

OpenDreamRuntime/Objects/Types/DreamObjectImage.cs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -197,12 +197,28 @@ protected override void SetVar(string varName, DreamValue value) {
197197

198198
_filters.Cut();
199199

200+
// filters = list("type"=...) or list(filter(...), filter(...))
200201
if (valueList != null) { // filters = list("type"=...)
201-
var filterObject = DreamObjectFilter.TryCreateFilter(ObjectTree, valueList);
202-
if (filterObject == null) // list() with invalid "type" is ignored
203-
break;
204-
205-
_filters.AddValue(new(filterObject));
202+
if (valueList.GetValue(new("type")) != DreamValue.Null) { // It's a single filter
203+
var filterObject = DreamObjectFilter.TryCreateFilter(ObjectTree, valueList);
204+
if (filterObject == null) // list() with invalid "type" is ignored
205+
break;
206+
207+
_filters.AddValue(new(filterObject));
208+
} else { // It's a list of filters
209+
foreach (var filter in valueList.EnumerateValues()) {
210+
if (!filter.TryGetValueAsDreamObject<DreamObjectFilter>(out var filterObject)) {
211+
if (!filter.TryGetValueAsDreamList(out var filterValues))
212+
continue;
213+
214+
filterObject = DreamObjectFilter.TryCreateFilter(ObjectTree, filterValues);
215+
if (filterObject == null)
216+
continue;
217+
}
218+
219+
_filters.AddValue(new(filterObject));
220+
}
221+
}
206222
} else if (!value.IsNull) {
207223
_filters.AddValue(value);
208224
}

0 commit comments

Comments
 (0)