sm64coopdx/autogen/convert_constants.py
djoslin0 82ca45eeb6
Lighting engine improvements (#870)
* Lighting engine improvements

Now objects will be affected by the lighting engine. This is
accomplished by passing the renderer the object's model matrix
(uncombined with the view or projection). You can now setup the
lighting engine mode to affect all shaded surfaces, lighting can
be affected by surface normals, and you can control what type of
tone mapping is applied.

added le_set_mode(mode)
    By default we retain the previous behavior.

    When set to LE_MODE_AFFECT_ALL_SHADED the lighting engine will
    affect every shaded material.

    This way we don't have to recompile every object and level that
    we want shaded with special coop-specific commands

added le_get_mode()

added le_set_tone_mapping(toneMapping)
    Tone mapping is what happens when a color value exceeds its 0-255
    range.

    By default we retain the current tone mapping (called
    LE_TONE_MAPPING_TOTAL_WEIGHTED).

    LE_TONE_MAPPING_WEIGHTED is now accessible, it was the tone
    mapping that was previously left out of the compile through ifdefs.

    LE_TONE_MAPPING_CLAMP is just simple additive with a clamp at a
    color value of 255.

    LE_TONE_MAPPING_REINHARD is reinhard tone mapping
    (vout = (vin + 1) / vin).

added le_set_light_use_surface_normals(id, useSurfaceNormals)
    By default lights retain their previous behavior (of ignoring
    surface normals).

    When enabled lights cast on one side of the object will not
    appear on the other side of the object.

    It is kind of like backface culling, but for lights.

added le_calculate_lighting_color_with_normal(pos, normal, outColor, lightIntensityScalar)
    It's just like le_calculate_lighting_color(), but you can pass
    in normals now.

* Removed normal calculation from vertex colored surfaces - they don't have normals

* Use packed normals correctly

* made LE_MODE_AFFECT_ALL_SHADED the default

* made useSurfaceNormals the default for lights

* Set ambient color, performed le_is_enabled() checks

The ambient color was black, which is why everything was dark by default.
If we set ambient to white then people will never see the effects of their
lights unless they set ambient to a lower value. So I added checks for
if a light has ever been added. The alternative would be to have something
like le_set_enabled()

* Rewrite how we obtain the model matrix - invert the camera

* run autogen

* Change default tonemapper to weighted, make setting ambient enable LE, fix null deref

* Address Peachy's comments

---------

Co-authored-by: MysterD <myster@d>
2025-06-29 17:46:35 +02:00

568 lines
17 KiB
Python

from common import *
from extract_constants import *
from vec_types import *
import sys
in_filename = 'autogen/lua_constants/built-in.lua'
deprecated_filename = 'autogen/lua_constants/deprecated.lua'
out_filename = 'src/pc/lua/smlua_constants_autogen.c'
out_filename_docs = 'docs/lua/constants.md'
out_filename_defs = 'autogen/lua_definitions/constants.lua'
in_files = [
"include/types.h",
"include/sm64.h",
"src/pc/lua/smlua_hooks.h",
"src/game/area.h",
"src/game/camera.h",
"include/mario_animation_ids.h",
"include/sounds.h",
"src/game/characters.h",
"src/pc/network/network.h",
"src/pc/network/network_player.h",
"include/PR/os_cont.h",
"src/game/interaction.c",
"src/game/interaction.h",
"src/pc/djui/djui_hud_utils.h",
"src/pc/controller/controller_mouse.h",
"include/behavior_table.h",
"src/pc/lua/utils/smlua_model_utils.h",
"src/pc/lua/utils/smlua_misc_utils.h",
"include/object_constants.h",
"include/mario_geo_switch_case_ids.h",
"src/game/object_list_processor.h",
"src/engine/graph_node.h",
"levels/level_defines.h",
"src/game/obj_behaviors.c",
"src/game/save_file.h",
"src/game/obj_behaviors_2.h",
"include/dialog_ids.h",
"include/seq_ids.h",
"include/surface_terrains.h",
"src/game/level_update.h",
"src/pc/network/version.h",
"include/geo_commands.h",
"include/level_commands.h",
"src/audio/external.h",
"src/game/envfx_snow.h",
"src/pc/mods/mod_storage.h",
"src/game/first_person_cam.h",
"src/pc/djui/djui_console.h",
"src/game/player_palette.h",
"src/pc/network/lag_compensation.h",
"src/pc/djui/djui_panel_menu.h",
"src/engine/lighting_engine.h",
"include/PR/gbi.h"
]
exclude_constants = {
"*": [ "^MAXCONTROLLERS$", "^AREA_[^T].*", "^AREA_T[HTO]", "^CONT_ERR.*", "^READ_MASK$", "^SIGN_RANGE$", ],
"include/sm64.h": [ "END_DEMO" ],
"include/types.h": [ "GRAPH_NODE_GUARD" ],
"src/audio/external.h": [ "DS_DIFF" ],
"src/game/save_file.h": [ "EEPROM_SIZE" ],
"src/game/obj_behaviors.c": [ "^o$" ],
"src/pc/djui/djui_console.h": [ "CONSOLE_MAX_TMP_BUFFER" ],
"src/pc/lua/smlua_hooks.h": [ "MAX_HOOKED_MOD_MENU_ELEMENTS", "^HOOK_RETURN_.*", "^ACTION_HOOK_.*", "^MOD_MENU_ELEMENT_.*" ],
"src/pc/djui/djui_panel_menu.h": [ "RAINBOW_TEXT_LEN" ]
}
include_constants = {
"include/geo_commands.h": [ "BACKGROUND" ],
"include/level_commands.h": [ "WARP_CHECKPOINT", "WARP_NO_CHECKPOINT" ],
"src/audio/external.h": [ "SEQ_PLAYER", "DS_" ],
"src/pc/mods/mod_storage.h": [ "MAX_KEYS", "MAX_KEY_VALUE_LENGTH" ],
"include/PR/gbi.h": [
"^G_NOOP$",
"^G_SETOTHERMODE_H$",
"^G_SETOTHERMODE_L$",
"^G_ENDDL$",
"^G_DL$",
"^G_MOVEMEM$",
"^G_MOVEWORD$",
"^G_MTX$",
"^G_GEOMETRYMODE$",
"^G_POPMTX$",
"^G_TEXTURE$",
"^G_COPYMEM$",
"^G_VTX$",
"^G_TRI1$",
"^G_TRI2$",
"^G_SETCIMG$",
"^G_SETZIMG$",
"^G_SETTIMG$",
"^G_SETCOMBINE$",
"^G_SETENVCOLOR$",
"^G_SETPRIMCOLOR$",
"^G_SETBLENDCOLOR$",
"^G_SETFOGCOLOR$",
"^G_SETFILLCOLOR$",
"^G_FILLRECT$",
"^G_SETTILE$",
"^G_LOADTILE$",
"^G_LOADBLOCK$",
"^G_SETTILESIZE$",
"^G_LOADTLUT$",
"^G_SETSCISSOR$",
"^G_TEXRECTFLIP$",
"^G_TEXRECT$",
]
}
# Constants that exist in the source code but should not appear
# in the documentation or VSCode autocomplete
hide_constants = {
"interaction.h": [ "INTERACT_UNKNOWN_08" ],
}
pretend_find = [
"SOUND_ARG_LOAD"
]
############################################################################
seen_constants = []
totalConstants = 0
verbose = len(sys.argv) > 1 and (sys.argv[1] == "-v" or sys.argv[1] == "--verbose")
overrideConstant = {
'VERSION_REGION': '"US"',
}
defined_values = {
'VERSION_US': True,
'VERSION_EU': False,
'VERSION_JP': False,
'VERSION_SH': False,
'F3DEX_GBI_2': True,
}
############################################################################
def validate_identifiers(built_files):
files = ''
for f in built_files.splitlines():
if f.startswith('#'):
continue
files += f + '\n'
all_identifiers = [x.group()[1:] for x in re.finditer(r'[(, ][A-Z_][A-Z0-9_]*', files)]
all_identifiers = set(all_identifiers)
for ident in all_identifiers:
if ident in pretend_find:
continue
if ident + '=' not in built_files:
print('COULD NOT FIND ' + ident)
############################################################################
def saw_constant(identifier, inIfBlock):
if inIfBlock:
return False
if identifier in seen_constants:
print("SAW DUPLICATE CONSTANT: " + identifier)
return True
else:
seen_constants.append(identifier)
return False
def allowed_identifier(filename, ident):
exclude_list = exclude_constants['*']
if filename in exclude_constants:
exclude_list.extend(exclude_constants[filename])
for exclude in exclude_list:
if re.search(exclude, ident) != None:
return False
if filename in include_constants:
for include in include_constants[filename]:
if re.search(include, ident) != None:
return True
return False
if ident in overrideConstant:
return False
return True
def process_enum(filename, line, inIfBlock):
_, ident, val = line.split(' ', 2)
if '{' not in val or '}' not in val:
#print('UNRECOGNIZED ENUM: ' + line)
return None
# grab inside body
val = val.split('{', 1)[-1].rsplit('}', 1)[0]
ret = {}
ret['identifier'] = ident
constants = []
set_to = None
index = 0
fields = val.split(',')
for field in fields:
field = field.strip()
if len(field) == 0:
continue
if '=' in field:
ident, val = field.split('=', 2)
constants.append([ident.strip(), val.strip()])
set_to = ident
index = 1
continue
if set_to is not None:
constants.append([field, '((%s) + %d)' % (set_to, index)])
index += 1
continue
if allowed_identifier(filename, field):
constants.append([field, str(index)])
if saw_constant(field, inIfBlock):
print('>>> ' + line)
index += 1
ret['constants'] = constants
return ret
def process_define(filename, line, inIfBlock):
_, ident, val = line.split(' ', 2)
val = val.replace('(u8)', '')
val = val.replace('(u64)', '')
val = re.sub(r'\.\d+f', '', val)
for p in val.split(' '):
if p.startswith('0x'):
continue
p = re.sub(r'0x[a-fA-F0-9]+', '', p)
if re.search(r'[a-z]', p) != None and "VERSION_TEXT" not in line and "SM64COOPDX_VERSION" not in line:
if 'gCurrentObject' not in line and verbose:
print('UNRECOGNIZED DEFINE: ' + line)
return None
if not allowed_identifier(filename, ident):
return None
if saw_constant(ident, inIfBlock):
print('>>> ' + line)
return [ident, val]
def process_line(filename, line, inIfBlock):
if line.startswith('enum '):
return process_enum(filename, line, inIfBlock)
elif line.startswith('#define '):
return process_define(filename, line, inIfBlock)
else:
print("UNRECOGNIZED LINE: " + line)
return None
def process_file(filename):
processed_file = {}
processed_file['filename'] = filename.replace('\\', '/').split('/')[-1]
constants = []
lines = extract_constants(get_path(filename)).splitlines()
block_stack = None
for line in lines:
if line.startswith('#if'):
if not block_stack: block_stack = []
block_stack.append({
'if_line': line,
'then': [],
'else': None,
'else_line': None,
'ignore': line.endswith('_H') or line.endswith('_H_')
})
elif line.startswith("#else"):
if not block_stack: continue
current = block_stack[-1]
if current['ignore']: continue
current['else_line'] = line
current['else'] = []
elif line.startswith("#endif"):
if not block_stack: continue
block = block_stack.pop()
if not block['ignore'] and len(block['then']) > 0 and block['else']:
block['then'].append([block['else_line'] + ' // ' + block['if_line']]) # append else line
block['else'].append([line + ' // ' + block['if_line']]) # append endif line
constants.append([block['if_line']])
constants.extend(block['then'])
if block['else']:
constants.extend(block['else'])
else:
c = process_line(filename, line, block_stack is not None)
if c is not None:
if block_stack and not block_stack[-1]['ignore']:
current = block_stack[-1]
if current['else'] is not None:
current['else'].append(c)
else:
current['then'].append(c)
else:
constants.append(c)
processed_file['constants'] = constants
return processed_file
def process_files():
seen_constants = []
processed_files = []
files = sorted(in_files, key=lambda d: d.split('/')[-1])
for f in files:
processed_files.append(process_file(f))
for key, item in overrideConstant.items():
processed_files[0]['constants'].append([key, item])
return processed_files
############################################################################
def build_constant(processed_constant, skip_constant):
constants = processed_constant
s = ''
is_enum = 'identifier' in processed_constant
if is_enum:
constants = processed_constant['constants']
else:
constants = [processed_constant]
for c in constants:
if c[0].startswith('#'):
if c[0].startswith('#ifdef'):
skip_constant = not defined_values[c[0].split()[1]]
elif c[0].startswith('#else'):
skip_constant = not skip_constant
elif c[0].startswith('#endif'):
skip_constant = False
continue
if skip_constant:
continue
s += '%s=%s\n' % (c[0], c[1].replace('"', "'"))
return s, skip_constant
def build_file(processed_file):
s = ''
skip_constant = False
for c in processed_file['constants']:
cs, skip_constant = build_constant(c, skip_constant)
s += cs
return s
def build_files(processed_files):
s = ''
for file in processed_files:
s += build_file(file)
return s
def build_vec_type_constant(type_name, vec_type, constant, values):
txt = 'g%s%s = create_read_only_table({' % (type_name, constant)
txt += ','.join([
'%s=%s' % (lua_field, str(values[i]))
for i, lua_field in enumerate(vec_type["fields_mapping"])
])
txt += '})'
return txt
def build_to_c(built_files):
txt = ''
# Built-in and deprecated
for filename in [in_filename, deprecated_filename]:
with open(get_path(filename), 'r') as f:
for line in f.readlines():
txt += line.strip() + '\n'
# Vec types constants
for type_name, vec_type in VEC_TYPES.items():
for constant, values in vec_type.get("constants", {}).items():
txt += build_vec_type_constant(type_name, vec_type, constant, values) + '\n'
# Source files
txt += '\n' + built_files
while ('\n\n' in txt):
txt = txt.replace('\n\n', '\n')
lines = txt.splitlines()
txt = 'char gSmluaConstants[] = ""\n'
for line in lines:
if line.startswith("#"):
txt += '%s\n' % line
continue
txt += '"%s\\n"\n' % line
txt += ';'
return txt
############################################################################
def doc_should_document(fname, identifier):
if fname in hide_constants:
for pattern in hide_constants[fname]:
if re.search(pattern, identifier) != None:
return False
return True
def doc_constant_index(processed_files):
s = '# Supported Constants\n'
for processed_file in processed_files:
s += '- [%s](#%s)\n' % (processed_file['filename'], processed_file['filename'].replace('.', ''))
constants = [x for x in processed_file['constants'] if 'identifier' in x]
for c in constants:
s += ' - [enum %s](#enum-%s)\n' % (c['identifier'], c['identifier'])
s += '\n<br />\n\n'
return s
def doc_constant(fname, processed_constant):
constants = processed_constant
s = ''
is_enum = 'identifier' in processed_constant
if is_enum:
constants = processed_constant['constants']
if len(constants) == 0:
return ''
enum = 'enum ' + processed_constant['identifier']
s += '\n### [%s](#%s)\n' % (enum, processed_constant['identifier'])
s += '| Identifier | Value |\n'
s += '| :--------- | :---- |\n'
for c in constants:
s += '| %s | %s |\n' % (c[0], c[1])
return s
for c in [processed_constant]:
if c[0].startswith('#'):
continue
if not doc_should_document(fname, c[0]):
continue
s += '- %s\n' % (c[0])
return s
def doc_file(processed_file):
s = '## [%s](#%s)\n' % (processed_file['filename'], processed_file['filename'])
constants = processed_file['constants']
for c in constants:
s += doc_constant(processed_file['filename'], c)
s += '\n[:arrow_up_small:](#)\n'
s += '\n<br />\n\n'
return s
def doc_files(processed_files):
s = '## [:rewind: Lua Reference](lua.md)\n\n'
s += doc_constant_index(processed_files)
for file in processed_files:
s += doc_file(file)
return s
############################################################################
def def_constant(fname, processed_constant, skip_constant):
global totalConstants
constants = processed_constant
s = ''
is_enum = 'identifier' in processed_constant
if is_enum:
constants = processed_constant['constants']
if len(constants) == 0:
return '', skip_constant
id = translate_to_def(processed_constant['identifier'])
klen = 0
vlen = 0
s += '\n'
for c in constants:
klen = max(klen, len(c[0]))
vlen = max(vlen, len(c[1]))
for c in constants:
s += c[0].ljust(klen) + ' = ' + c[1].rjust(vlen) + ' --- @type %s\n' % id
totalConstants += 1
s += '\n--- @alias %s\n' % id
for c in constants:
s += '--- | `%s`\n' % c[0]
return s, skip_constant
for c in [processed_constant]:
if c[0].startswith('#'):
if c[0].startswith('#ifdef'):
skip_constant = not defined_values[c[0].split()[1]]
elif c[0].startswith('#else'):
skip_constant = not skip_constant
elif c[0].startswith('#endif'):
skip_constant = False
continue
if skip_constant:
continue
if not doc_should_document(fname, c[0]):
continue
if '"' in c[1]:
s += '\n--- @type string\n'
else:
s += '\n--- @type integer\n'
s += '%s = %s\n' % (c[0], c[1])
totalConstants += 1
return s, skip_constant
def build_to_def(processed_files):
s = '-- AUTOGENERATED FOR CODE EDITORS --\n\n'
with open(get_path(in_filename), 'r') as f:
s += f.read()
s += '\n'
s += '\n\n-------------------------\n'
s += '-- vec types constants --\n'
s += '-------------------------\n\n'
for type_name, vec_type in VEC_TYPES.items():
for constant, values in vec_type.get("constants", {}).items():
s += '--- @type %s\n' % (type_name)
s += build_vec_type_constant(type_name, vec_type, constant, values) + '\n\n'
for file in processed_files:
constants = file['constants']
skip_constant = False
for c in constants:
cs, skip_constant = def_constant(file['filename'], c, skip_constant)
s += cs
return s
############################################################################
def main():
processed_files = process_files()
built_files = build_files(processed_files)
validate_identifiers(built_files)
built_c = build_to_c(built_files)
with open(get_path(out_filename), 'w', encoding='utf-8', newline='\n') as out:
out.write(built_c)
doc = doc_files(processed_files)
with open(get_path(out_filename_docs), 'w', encoding='utf-8', newline='\n') as out:
out.write(doc)
defs = build_to_def(processed_files)
with open(get_path(out_filename_defs), 'w', encoding='utf-8', newline='\n') as out:
out.write(defs)
global totalConstants
print("Total constants: " + str(totalConstants))
main()