Skip to content

Commit fe819e2

Browse files
authored
[OoT] Auto detect non-literal actor scale (Fast-64#716)
non-literal scale import fix
1 parent 30db460 commit fe819e2

1 file changed

Lines changed: 132 additions & 7 deletions

File tree

fast64_internal/z64/f3d_writer.py

Lines changed: 132 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from ..utility import CData, getGroupIndexFromname, readFile, writeFile
88
from ..f3d.flipbook import flipbook_to_c, flipbook_2d_to_c, flipbook_data_to_c
99
from ..f3d.f3d_material import createF3DMat, F3DMaterial_UpdateLock, update_preset_manual
10-
from .utility import replaceMatchContent, getOOTScale
10+
from .utility import replaceMatchContent, getOOTScale, ootStripComments
1111
from .texture_array import TextureFlipbook
1212

1313
from ..f3d.f3d_writer import (
@@ -331,6 +331,131 @@ def writeTextureArraysExisting2D(data: str, flipbook: TextureFlipbook, flipbookA
331331
return newData
332332

333333

334+
def ootParseScaleLiteral(scaleExpr: str) -> Optional[float]:
335+
scaleExpr = scaleExpr.strip().removesuffix("f")
336+
337+
try:
338+
return float(scaleExpr)
339+
except ValueError:
340+
return None
341+
342+
343+
def ootSplitTopLevel(expr: str) -> list[str]:
344+
entries = []
345+
start = 0
346+
depth = 0
347+
348+
for i, char in enumerate(expr):
349+
if char in "({[":
350+
depth += 1
351+
elif char in ")}]":
352+
depth -= 1
353+
elif char == "," and depth == 0:
354+
entries.append(expr[start:i].strip())
355+
start = i + 1
356+
357+
finalEntry = expr[start:].strip()
358+
if finalEntry != "":
359+
entries.append(finalEntry)
360+
361+
return entries
362+
363+
364+
def ootResolveStructArrayFieldScale(actorData: str, scaleExpr: str) -> Optional[float]:
365+
match = re.fullmatch(r"([A-Za-z_]\w*)\s*\[\s*[^\]]+\s*\]\s*\.\s*([A-Za-z_]\w*)", scaleExpr.strip())
366+
if match is None:
367+
return None
368+
369+
tableName, fieldName = match.groups()
370+
actorData = ootStripComments(actorData)
371+
372+
tableMatch = re.search(
373+
rf"(?:static\s+)?(?P<struct_name>[A-Za-z_]\w*)\s+{re.escape(tableName)}\s*\[\s*\]\s*=\s*\{{",
374+
actorData,
375+
re.DOTALL,
376+
)
377+
if tableMatch is None:
378+
return None
379+
380+
structMatch = re.search(
381+
rf"typedef\s+struct(?:\s+{re.escape(tableMatch.group('struct_name'))})?\s*\{{(?P<body>.*?)\}}\s*{re.escape(tableMatch.group('struct_name'))}\s*;",
382+
actorData,
383+
re.DOTALL,
384+
)
385+
if structMatch is None:
386+
return None
387+
388+
fieldNames = [
389+
fieldMatch.group(1)
390+
for fieldMatch in re.finditer(
391+
r"[A-Za-z_]\w*(?:\s*\*+)?\s+([A-Za-z_]\w*)\s*(?:\[[^\]]+\])?\s*;", structMatch.group("body")
392+
)
393+
]
394+
if fieldName not in fieldNames:
395+
return None
396+
397+
fieldIndex = fieldNames.index(fieldName)
398+
values = set()
399+
depth = 0
400+
entryStart = None
401+
402+
for i in range(tableMatch.end() - 1, len(actorData)):
403+
char = actorData[i]
404+
if char == "{":
405+
if depth == 1:
406+
entryStart = i + 1
407+
depth += 1
408+
elif char == "}":
409+
depth -= 1
410+
if depth == 1 and entryStart is not None:
411+
entryValues = ootSplitTopLevel(actorData[entryStart:i])
412+
if fieldIndex >= len(entryValues):
413+
return None
414+
415+
value = ootParseScaleLiteral(entryValues[fieldIndex])
416+
if value is None:
417+
return None
418+
419+
values.add(value)
420+
if len(values) > 1:
421+
return None
422+
entryStart = None
423+
elif depth == 0:
424+
break
425+
426+
return values.pop() if len(values) == 1 else None
427+
428+
429+
def ootResolveScaleExpression(actorData: str, scaleExpr: str) -> Optional[float]:
430+
scaleExpr = scaleExpr.strip()
431+
literalValue = ootParseScaleLiteral(scaleExpr)
432+
if literalValue is not None:
433+
return literalValue
434+
435+
while scaleExpr.startswith("(") and scaleExpr.endswith(")"):
436+
scaleExpr = scaleExpr[1:-1].strip()
437+
literalValue = ootParseScaleLiteral(scaleExpr)
438+
if literalValue is not None:
439+
return literalValue
440+
441+
depth = 0
442+
for i, char in enumerate(scaleExpr):
443+
if char in "({[":
444+
depth += 1
445+
elif char in ")}]":
446+
depth -= 1
447+
elif depth == 0 and char in "*/":
448+
leftValue = ootResolveScaleExpression(actorData, scaleExpr[:i])
449+
rightValue = ootResolveScaleExpression(actorData, scaleExpr[i + 1 :])
450+
if leftValue is None or rightValue is None:
451+
return None
452+
if char == "*":
453+
return leftValue * rightValue
454+
return None if rightValue == 0 else leftValue / rightValue
455+
456+
return ootResolveStructArrayFieldScale(actorData, scaleExpr)
457+
458+
334459
# Note this does not work well with actors containing multiple "parts". (z_en_honotrap)
335460
def ootReadActorScale(basePath: str, overlayName: str, isLink: bool) -> Optional[float]:
336461
if not isLink:
@@ -341,16 +466,16 @@ def ootReadActorScale(basePath: str, overlayName: str, isLink: bool) -> Optional
341466
chainInitMatch = re.search(r"CHAIN_VEC3F_DIV1000\s*\(\s*scale\s*,\s*(.*?)\s*,", actorData, re.DOTALL)
342467
if chainInitMatch is not None:
343468
scale = chainInitMatch.group(1).strip()
344-
if scale[-1] == "f":
345-
scale = scale[:-1]
346-
return getOOTScale(1 / (float(scale) / 1000))
469+
resolvedScale = ootResolveScaleExpression(actorData, scale)
470+
if resolvedScale is not None:
471+
return getOOTScale(1 / (resolvedScale / 1000))
347472

348473
actorScaleMatch = re.search(r"Actor\_SetScale\s*\(.*?,\s*(.*?)\s*\)", actorData, re.DOTALL)
349474
if actorScaleMatch is not None:
350475
scale = actorScaleMatch.group(1).strip()
351-
if scale[-1] == "f":
352-
scale = scale[:-1]
353-
return getOOTScale(1 / float(scale))
476+
resolvedScale = ootResolveScaleExpression(actorData, scale)
477+
if resolvedScale is not None:
478+
return getOOTScale(1 / resolvedScale)
354479

355480
if isLink:
356481
return getOOTScale(100.0)

0 commit comments

Comments
 (0)