Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 Pack3r.Console/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;
using DotMake.CommandLine;
using Pack3r.Extensions;
Expand Down Expand Up @@ -27,6 +26,7 @@ internal static IConfiguration SetupDI()
.Bind<IReferenceParser>(1).To<AseParser>()
.Bind<IReferenceParser>(2).To<Md3Parser>()
.Bind<IReferenceParser>(3).To<SkinParser>()
.Bind<IReferenceParser>(4).To<ObjParser>()
.Bind<IResourceRefParser>().To<ResourceRefParser>()
.Bind<IAssetService>().To<AssetService>()
.Bind<LoggerBase>().To<LoggerBase>()
Expand Down
7 changes: 6 additions & 1 deletion Pack3r.Core/Models/Shader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public sealed class Shader(
public bool HasLightStyles { get; set; }

/// <summary>Shader includes references to any files needed in pk3</summary>
public bool NeededInPk3 => Resources.Count > 0 || Shaders.Count > 0 || ImplicitMapping.HasValue;
public bool NeededInPk3 => ForcePk3Include || Resources.Count > 0 || Shaders.Count > 0 || ImplicitMapping.HasValue;

public string GetAbsolutePath() => Path.Combine(Source.RootPath, DestinationPath).NormalizePath();

Expand All @@ -41,6 +41,11 @@ public sealed class Shader(
/// </summary>
public QPath? ImplicitMapping { get; set; }

/// <summary>
/// Whether the shader has a $whiteimage or $lightmap stage, fogparms, or other shader directives that require it to be included.
/// </summary>
public bool ForcePk3Include { get; set; }

public bool Equals(Shader? other)
{
return ReferenceEquals(this, other);
Expand Down
17 changes: 11 additions & 6 deletions Pack3r.Core/Parsers/AseParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,26 @@ public partial class AseParser(

await foreach (var line in reader.ReadLines(asset, cancellationToken))
{
if (Comment().IsMatch(line.Value.Span))
ReadOnlySpan<char> span = line.Value.Span;

if (Comment().IsMatch(span))
{
if (!line.Value.Span.Contains("Generated by Q3Map2", StringComparison.Ordinal))
if (!span.Contains("Generated by Q3Map2", StringComparison.Ordinal))
{
logger.Warn($"Unsupported .ASE model (not generated by Q3Map2): '{asset.FullPath}'");
return null;
logger.Warn($"Potentially unsupported .ASE model (not generated by Q3Map2): '{asset.FullPath}'");
}
}
else if (line.Value.Span.StartsWithF("*MAP_NAME"))
else if (Bitmap().IsMatch(span))
{
if (line.Value.TryReadPastWhitespace(out var token) &&
!token.EqualsF("\"NOSHADER\"") &&
!token.EqualsF("\"default\"") &&
!token.EqualsF("\"textures/common/nodraw\""))
{
resources.Add(Resource.Shader(token.Trim('"'), in line));
}
}
else if (line.Value.Span.StartsWithF("*GEOMOBJECT"))
else if (span.StartsWithF("*GEOMOBJECT"))
{
break;
}
Expand All @@ -48,4 +50,7 @@ public partial class AseParser(

[GeneratedRegex("""\*COMMENT\s""", RegexOptions.Singleline, 1000)]
private static partial Regex Comment();

[GeneratedRegex("""\*BITMAP\s""", RegexOptions.Singleline, 1000)]
private static partial Regex Bitmap();
}
35 changes: 35 additions & 0 deletions Pack3r.Core/Parsers/ObjParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Pack3r.Extensions;
using Pack3r.IO;
using Pack3r.Models;

namespace Pack3r.Parsers;

public partial class ObjParser(
ILineReader reader) : IReferenceParser
{
public string Description => "model";

public bool CanParse(ReadOnlyMemory<char> resource) => resource.EndsWithF(".obj");

public async Task<ResourceList?> Parse(IAsset asset, CancellationToken cancellationToken)
{
ResourceList resources = [];

await foreach (var line in reader.ReadLines(asset, cancellationToken))
{
const string UseMaterial = "usemtl ";

if (line.HasValue && line.Value.Span.StartsWithF(UseMaterial))
{
QString materialName = ((QString)line.Value[UseMaterial.Length..].Trim()).TrimTextureExtension();

if (!materialName.IsEmpty)
{
resources.Add(Resource.Shader(materialName, in line));
}
}
}

return resources;
}
}
26 changes: 19 additions & 7 deletions Pack3r.Core/Parsers/ShaderParser.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Pack3r.Extensions;
Expand Down Expand Up @@ -440,12 +439,6 @@ public async IAsyncEnumerable<Shader> Parse(
continue;
}

// only map, animmap, clampmap and videomap are valid
if ((line.FirstChar | 0x20) is not ('m' or 'a' or 'c' or 'v'))
{
continue;
}

if (line.MatchKeyword("map", out token) ||
line.MatchKeyword("clampMap", out token))
{
Expand All @@ -454,6 +447,10 @@ public async IAsyncEnumerable<Shader> Parse(
{
shader.Resources.Add(token);
}
else
{
shader.ForcePk3Include = true;
}
}
else if (line.MatchKeyword("animMap", out token))
{
Expand Down Expand Up @@ -484,6 +481,10 @@ static void AddFrames(List<QPath> list, ReadOnlyMemory<char> source)
{
shader.Resources.Add(token);
}
else if (!shader.ForcePk3Include && _forceIncludeKeywords.Contains(token))
{
shader.ForcePk3Include = true;
}

continue;
}
Expand Down Expand Up @@ -555,4 +556,15 @@ private enum State : byte
/// <summary>In a stage e.g. map $lightmap</summary>
Stage = 3,
}

private static readonly HashSet<QString> _forceIncludeKeywords = new(QString.Comparer)
{
"portal",
"fogvars",
"fogparms",
"skyfogvars",
"waterfogvars",
"lightgridmulamb",
"lightgridmuldir",
};
}
18 changes: 18 additions & 0 deletions Pack3r.Core/Services/Packager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,25 @@ void AddCompileFile(string absolutePath)
void AddShaderFile(Shader shader, Resource resource)
{
if (shader.Source.NotPacked)
{
handledFiles.Add(shader.DestinationPath);
return;
}

foreach (var source in map.AssetSources)
{
if (source.NotPacked &&
source.Assets.ContainsKey(shader.DestinationPath))
{
if (options.ShaderDebug)
{
logger.Debug($"Shader not packed, also present in {source.Name}: '{shader.GetAbsolutePath()}'");
}

handledFiles.Add(shader.DestinationPath);
return;
}
}

if (TryAddFileFromSource(shader.Source, shader.DestinationPath.AsMemory(), resource, shader))
return;
Expand Down
105 changes: 105 additions & 0 deletions Pack3r.Tests/AseParserTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@

using Pack3r.Logging;
using Pack3r.Parsers;

namespace Pack3r.Tests;

public static class AseParserTests
{
[Fact]
public static async Task Should_Parse()
{
var reader = new StringLineReader(Data);
var parser = new AseParser(NullLogger<AseParser>.Instance, reader);

Assert.True(parser.CanParse("model.ase".AsMemory()));
Assert.False(parser.CanParse("model.obj".AsMemory()));

var result = await parser.Parse(new MockAsset(), default);

Assert.NotNull(result);
Assert.Equal(
[
"textures/venice_ase_c1/box_m02",
"textures/venice_ase_c1/box_m03",
"textures/venice_ase_c1/box_m01",
], result.Select(s => s.Value.ToString()));
}

private const string Data =
"""
*3DSMAX_ASCIIEXPORT 200
*COMMENT "Generated by Q3Map2 (ydnar) -convert -format ase"
*SCENE {
*SCENE_FILENAME "box_m02.bsp"
*SCENE_FIRSTFRAME 0
*SCENE_LASTFRAME 100
*SCENE_FRAMESPEED 30
*SCENE_TICKSPERFRAME 160
*SCENE_BACKGROUND_STATIC 0.0000 0.0000 0.0000
*SCENE_AMBIENT_STATIC 0.0000 0.0000 0.0000
}
*MATERIAL_LIST {
*MATERIAL_COUNT 3
*MATERIAL 0 {
*MATERIAL_NAME "textures/venice_ase_c1/box_m02"
*MATERIAL_CLASS "Standard"
*MATERIAL_DIFFUSE 1.000000 0.880850 0.571197
*MATERIAL_SHADING Phong
*MAP_DIFFUSE {
*MAP_NAME "textures/venice_ase_c1/box_m02"
*MAP_CLASS "Bitmap"
*MAP_SUBNO 1
*MAP_AMOUNT 1.0
*MAP_TYPE Screen
*BITMAP "textures/venice_ase_c1/box_m02"
*BITMAP_FILTER Pyramidal
}
}
*MATERIAL 1 {
*MATERIAL_NAME "textures/venice_ase_c1/box_m03"
*MATERIAL_CLASS "Standard"
*MATERIAL_DIFFUSE 1.000000 0.886761 0.619810
*MATERIAL_SHADING Phong
*MAP_DIFFUSE {
*MAP_NAME "textures/venice_ase_c1/box_m03"
*MAP_CLASS "Bitmap"
*MAP_SUBNO 1
*MAP_AMOUNT 1.0
*MAP_TYPE Screen
*BITMAP "textures/venice_ase_c1/box_m03"
*BITMAP_FILTER Pyramidal
}
}
*MATERIAL 2 {
*MATERIAL_NAME "textures/venice_ase_c1/box_m01"
*MATERIAL_CLASS "Standard"
*MATERIAL_DIFFUSE 1.000000 0.887614 0.607439
*MATERIAL_SHADING Phong
*MAP_DIFFUSE {
*MAP_NAME "textures/venice_ase_c1/box_m01"
*MAP_CLASS "Bitmap"
*MAP_SUBNO 1
*MAP_AMOUNT 1.0
*MAP_TYPE Screen
*BITMAP "textures/venice_ase_c1/box_m01"
*BITMAP_FILTER Pyramidal
}
}
}
*GEOMOBJECT {
*NODE_NAME "mat2model0surf0"
*NODE_TM {
*NODE_NAME "mat2model0surf0"
*INHERIT_POS 0 0 0
*INHERIT_ROT 0 0 0
*INHERIT_SCL 0 0 0
*TM_ROW0 1.0 0 0
*TM_ROW1 0 1.0 0
*TM_ROW2 0 0 1.0
*TM_ROW3 0 0 0
*TM_POS 0.000000 0.000000 0.000000
}
}
""";
}
Loading