This commit is contained in:
Nahuel Rocchetti 2023-08-02 21:21:43 -03:00
commit 26efd8281b
7 changed files with 4960 additions and 27 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,5 @@
using OpenTS2.Content.DBPF.Effects.Types;
using System;
using OpenTS2.Content.DBPF.Effects.Types;
using OpenTS2.Files.Formats.DBPF.Types;
using OpenTS2.Files.Utils;
using UnityEngine;
@ -76,11 +77,29 @@ namespace OpenTS2.Content.DBPF.Effects
RandomWalkTurnX = randomWalkTurnX;
RandomWalkTurnY = randomWalkTurnY;
}
public bool IsFlagSet(ParticleFlagBits flag)
{
var mask = 1UL << (int)flag;
return (Flags & mask) != 0;
}
}
public enum ParticleFlagBits
{
/// <summary>
/// This flag is set if the particle emitter should be an ellipsoid shape.
/// </summary>
EmitterIsEllipsoid = 8,
}
public struct ParticleLife
{
/// <summary>
/// Particles get a lifetime in a random range between this vector's start and end.
/// </summary>
public Vector2 Life { get; }
public float LifePreRoll { get; }
public ParticleLife(Vector2 life, float lifePreRoll)
@ -97,7 +116,16 @@ namespace OpenTS2.Content.DBPF.Effects
public BoundingBox EmitDirection { get; }
public Vector2 EmitSpeed { get; }
/// <summary>
/// The volume of the emitter defined by its corners. This can either be a cuboid shape, an ellipsoid or a
/// torus depending on the flags and EmitTorusWidth.
/// </summary>
public BoundingBox EmitVolume { get; }
/// <summary>
/// If set to a value greater than 0, emitter is a torus shape.
/// </summary>
public float EmitTorusWidth { get; }
public FloatCurve RateCurve { get; }
@ -152,6 +180,61 @@ namespace OpenTS2.Content.DBPF.Effects
Colors = colors;
ColorVary = colorVary;
}
/// <summary>
/// This returns the range of colors to use for start of the particle.
/// </summary>
public readonly (Color, Color) GetStartingColorRange()
{
var minimum = new Color(Colors[0].x - ColorVary[0],
Colors[0].y - ColorVary[1],
Colors[0].z - ColorVary[2],
Math.Max(AlphaCurve.Curve[0] - AlphaVary, 0));
var maximum = new Color(Colors[0].x + ColorVary[0],
Colors[0].y + ColorVary[1],
Colors[0].z + ColorVary[2],
Math.Min(AlphaCurve.Curve[0] + AlphaVary, 1));
return (minimum, maximum);
}
public readonly (Gradient, Gradient) GetColorGradientsOverTime()
{
var minColorKeys = new GradientColorKey[Colors.Length];
var maxColorKeys = new GradientColorKey[Colors.Length];
// Unity supports a maximum of 8 keys.
Debug.Assert(Colors.Length <= 8);
for (var i = 0; i < Colors.Length; i++)
{
var time = ((float)i) / Colors.Length;
var color = Colors[i];
minColorKeys[i] = new GradientColorKey(
new Color(color.x - ColorVary[0], color.y - ColorVary[1], color.z - ColorVary[2]), time);
maxColorKeys[i] = new GradientColorKey(
new Color(color.x + ColorVary[0], color.y + ColorVary[1], color.z + ColorVary[2]), time);
}
// Unity supports a maximum of 8 keys.
Debug.Assert(AlphaCurve.Curve.Length <= 8);
var minAlphaKeys = new GradientAlphaKey[AlphaCurve.Curve.Length];
var maxAlphaKeys = new GradientAlphaKey[AlphaCurve.Curve.Length];
for (var i = 0; i < AlphaCurve.Curve.Length; i++)
{
var time = ((float)i) / AlphaCurve.Curve.Length;
var alpha = AlphaCurve.Curve[i];
minAlphaKeys[i] = new GradientAlphaKey(Math.Max(alpha - AlphaVary, 0), time);
maxAlphaKeys[i] = new GradientAlphaKey(Math.Min(alpha + AlphaVary, 1), time);
}
var minGradient = new Gradient();
minGradient.SetKeys(minColorKeys, minAlphaKeys);
var maxGradient = new Gradient();
maxGradient.SetKeys(maxColorKeys, maxAlphaKeys);
return (minGradient, maxGradient);
}
}
public struct ParticleDrawing
@ -225,7 +308,9 @@ namespace OpenTS2.Content.DBPF.Effects
}
// These are methods common to both Particle and MetaParticles.
#region Particle and MetaParticle common readers
public static class ParticleHelper
{
internal static ParticleLife ReadParticleLife(IoBuffer reader)
@ -288,5 +373,6 @@ namespace OpenTS2.Content.DBPF.Effects
);
}
}
#endregion
}

View file

@ -16,14 +16,14 @@ namespace OpenTS2.Content.DBPF.Effects
public struct EffectDescription
{
public string EffectName { get; }
public byte BlockType { get; }
public int BlockIndex { get; }
public byte EffectType { get; }
public int EffectIndex { get; }
private EffectDescription(string effectName, byte blockType, int blockIndex)
private EffectDescription(string effectName, byte effectType, int effectIndex)
{
EffectName = effectName;
BlockType = blockType;
BlockIndex = blockIndex;
EffectType = effectType;
EffectIndex = effectIndex;
}
public static EffectDescription Deserialize(IoBuffer reader, ushort version)

View file

@ -15,7 +15,7 @@ namespace OpenTS2.Content.DBPF.Effects.Types
Curve = curve;
}
public AnimationCurve ToUnityCurve()
public ParticleSystem.MinMaxCurve ToUnityCurve()
{
var keyframes = new Keyframe[Curve.Length];
for (var i = 0; i < Curve.Length; i++)
@ -23,7 +23,21 @@ namespace OpenTS2.Content.DBPF.Effects.Types
keyframes[i] = new Keyframe(i, Curve[i]);
}
return new AnimationCurve(keyframes);
return new ParticleSystem.MinMaxCurve(1.0f, new AnimationCurve(keyframes));
}
public ParticleSystem.MinMaxCurve ToUnityCurveWithVariance(float vary)
{
var lowerKeyframes = new Keyframe[Curve.Length];
var upperKeyframes = new Keyframe[Curve.Length];
for (var i = 0; i < Curve.Length; i++)
{
lowerKeyframes[i] = new Keyframe(i, Curve[i] - vary);
upperKeyframes[i] = new Keyframe(i, Curve[i] + vary);
}
return new ParticleSystem.MinMaxCurve(1.0f,
new AnimationCurve(lowerKeyframes), new AnimationCurve(upperKeyframes));
}
public static FloatCurve Deserialize(IoBuffer reader)

View file

@ -72,12 +72,12 @@ namespace OpenTS2.Content.DBPF
public IBaseEffect GetEffectFromVisualEffectDescription(EffectDescription description)
{
return description.BlockType switch
return description.EffectType switch
{
1 => ParticleEffects[description.BlockIndex],
6 => SequenceEffects[description.BlockIndex],
1 => ParticleEffects[description.EffectIndex],
6 => SequenceEffects[description.EffectIndex],
_ => throw new NotImplementedException(
$"Block type {description.BlockType} in VisualEffect not supported")
$"Block type {description.EffectType} in VisualEffect not supported")
};
}
}

View file

@ -19,7 +19,7 @@ namespace OpenTS2.Scenes
private EffectsAsset _effects;
private List<Material> _particleMaterials = new List<Material>();
private void Start()
private void Awake()
{
var contentProvider = ContentProvider.Get();
// Load effects package.
@ -27,6 +27,11 @@ namespace OpenTS2.Scenes
Filesystem.GetPackagesInDirectory(Filesystem.GetDataPathForProduct(ProductFlags.BaseGame) + "/Res/Effects"));
_effects = contentProvider.GetAsset<EffectsAsset>(new ResourceKey(instanceID: 1, groupID: GroupIDs.Effects, typeID: TypeIDs.EFFECTS));
Debug.Assert(_effects != null, "Couldn't find effects");
// Apply a sims to unity space transformation.
GetComponent<ParticleSystem>().transform.Rotate(-90, 0, 0);
GetComponent<ParticleSystem>().transform.localScale = new Vector3(1, -1, 1);
// Disable emission on the base particle system, only children will emit.
var emissionModule = GetComponent<ParticleSystem>().emission;
emissionModule.enabled = false;
@ -53,7 +58,7 @@ namespace OpenTS2.Scenes
{
case ParticleEffect e:
var subSystem = CreateForParticleEffect(effectDescription, e);
subSystem.transform.parent = unityParticleSystem.transform;
subSystem.transform.SetParent(unityParticleSystem.transform, worldPositionStays:false);
break;
default:
throw new NotImplementedException($"Effect type {effect} not supported");
@ -70,30 +75,71 @@ namespace OpenTS2.Scenes
system.Stop(withChildren:true, ParticleSystemStopBehavior.StopEmittingAndClear);
var emission = system.emission;
var emissionRateOverTime = emission.rateOverTime;
emissionRateOverTime.curve = effect.Emission.RateCurve.ToUnityCurve();
emission.rateOverTime = effect.Emission.RateCurve.ToUnityCurve();
// Set the emitter shape and drection.
var shape = system.shape;
if (effect.Emission.EmitTorusWidth > 0)
{
shape.shapeType = ParticleSystemShapeType.Donut;
}
else if (effect.IsFlagSet(ParticleFlagBits.EmitterIsEllipsoid))
{
shape.shapeType = ParticleSystemShapeType.Sphere;
}
else
{
shape.shapeType = ParticleSystemShapeType.Box;
}
var main = system.main;
main.duration = effect.Life.Life[0];
main.startSize = effect.Size.SizeCurve.ToUnityCurveWithVariance(effect.Size.SizeVary);
main.startSpeed =
new ParticleSystem.MinMaxCurve(min: effect.Emission.EmitSpeed[0], max: effect.Emission.EmitSpeed[1]);
main.startLifetime = new ParticleSystem.MinMaxCurve(min:effect.Life.Life[0], max:effect.Life.Life[1]);
// Set colors.
var (minColor, maxColor) = effect.Color.GetStartingColorRange();
main.startColor = new ParticleSystem.MinMaxGradient(minColor, maxColor);
var colorOverLifetime = system.colorOverLifetime;
var (minColorGradient, maxColorGradient) = effect.Color.GetColorGradientsOverTime();
colorOverLifetime.color = new ParticleSystem.MinMaxGradient(minColorGradient, maxColorGradient);
colorOverLifetime.enabled = true;
if (effect.Drawing.MaterialName != "")
{
var textureAsset = ContentProvider.Get().GetAsset<ScenegraphTextureAsset>(new ResourceKey(
$"{effect.Drawing.MaterialName}_txtr", GroupIDs.Scenegraph, TypeIDs.SCENEGRAPH_TXTR));
// TODO: temporarily using the standard shader here.
var material = new Material(Shader.Find("OpenTS2/StandardMaterial/AlphaBlended"))
{
mainTexture = textureAsset.GetSelectedImageAsUnityTexture(ContentProvider.Get())
};
var material = MakeParticleMaterial(textureAsset.GetSelectedImageAsUnityTexture(ContentProvider.Get()));
_particleMaterials.Add(material);
system.GetComponent<Renderer>().sharedMaterial = material;
Debug.Log($"material: {effect.Drawing.MaterialName}");
}
Debug.Log($"effect idx: {description.EffectIndex}");
return particleObject;
}
private Material MakeParticleMaterial(Texture texture)
{
var material = new Material(Shader.Find("Particles/Standard Surface"))
{
mainTexture = texture
};
// These are jacked from https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/Inspector/StandardParticlesShaderGUI.cs#L637
// for now, may want to do our own shader in the future.
material.SetOverrideTag("RenderType", "Transparent");
material.SetFloat("_BlendOp", (float)UnityEngine.Rendering.BlendOp.Add);
material.SetFloat("_SrcBlend", (float)UnityEngine.Rendering.BlendMode.SrcAlpha);
material.SetFloat("_DstBlend", (float)UnityEngine.Rendering.BlendMode.One);
material.SetFloat("_ZWrite", 0.0f);
material.DisableKeyword("_ALPHATEST_ON");
material.EnableKeyword("_ALPHABLEND_ON");
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
material.DisableKeyword("_ALPHAMODULATE_ON");
material.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent;
return material;
}
}
}

View file

@ -164,6 +164,6 @@ public class EffectsCodecTest
var description = visualEffect.Descriptions[0];
Assert.That(description.EffectName, Is.EqualTo("construction_cursor_dust"));
Assert.That(description.BlockIndex, Is.EqualTo(0));
Assert.That(description.EffectIndex, Is.EqualTo(0));
}
}