diff --git a/.agents/skills/code_style b/.agents/skills/code_style new file mode 120000 index 000000000..edb444fb7 --- /dev/null +++ b/.agents/skills/code_style @@ -0,0 +1 @@ +../../.github/skills/code_style \ No newline at end of file diff --git a/.agents/skills/execute b/.agents/skills/execute new file mode 120000 index 000000000..ff61f8ce2 --- /dev/null +++ b/.agents/skills/execute @@ -0,0 +1 @@ +../../.github/skills/execute \ No newline at end of file diff --git a/.agents/skills/ghidra b/.agents/skills/ghidra new file mode 120000 index 000000000..e348149ed --- /dev/null +++ b/.agents/skills/ghidra @@ -0,0 +1 @@ +../../.github/skills/ghidra \ No newline at end of file diff --git a/.agents/skills/implement b/.agents/skills/implement new file mode 120000 index 000000000..609ac0075 --- /dev/null +++ b/.agents/skills/implement @@ -0,0 +1 @@ +../../.github/skills/implement \ No newline at end of file diff --git a/.agents/skills/line_lookup b/.agents/skills/line_lookup new file mode 120000 index 000000000..98bcd185e --- /dev/null +++ b/.agents/skills/line_lookup @@ -0,0 +1 @@ +../../.github/skills/line_lookup \ No newline at end of file diff --git a/.agents/skills/lookup b/.agents/skills/lookup new file mode 120000 index 000000000..e0466a56a --- /dev/null +++ b/.agents/skills/lookup @@ -0,0 +1 @@ +../../.github/skills/lookup \ No newline at end of file diff --git a/.agents/skills/refiner b/.agents/skills/refiner new file mode 120000 index 000000000..d61e56ea1 --- /dev/null +++ b/.agents/skills/refiner @@ -0,0 +1 @@ +../../.github/skills/refiner \ No newline at end of file diff --git a/.agents/skills/scaffold b/.agents/skills/scaffold new file mode 120000 index 000000000..5684eddc2 --- /dev/null +++ b/.agents/skills/scaffold @@ -0,0 +1 @@ +../../.github/skills/scaffold \ No newline at end of file diff --git a/.claude b/.claude new file mode 120000 index 000000000..c0ca46856 --- /dev/null +++ b/.claude @@ -0,0 +1 @@ +.agents \ No newline at end of file diff --git a/.github/skills/code_style/SKILL.md b/.github/skills/code_style/SKILL.md index 5218fb40b..f5455ba9a 100644 --- a/.github/skills/code_style/SKILL.md +++ b/.github/skills/code_style/SKILL.md @@ -31,7 +31,7 @@ python tools/code_style.py audit --base origin/main - `audit` also checks touched `class` / `struct` declarations against known header declarations and, when no header exists, against the PS2 visibility rule. - `audit` warns on touched local forward declarations when the repo already has a header for that type. - `audit` warns on touched type members that look like invented padding or placeholder names such as `pad`, `unk`, or `field_1234`. -- `audit` also checks touched style-guide rules that clang-format cannot enforce for you, such as cast spacing, `using namespace`, `NULL`, and missing `EA_PRAGMA_ONCE_SUPPORTED` guard blocks when a header's guard region is touched. +- `audit` also checks touched style-guide rules that clang-format cannot enforce for you, such as cast spacing, `using namespace`, `NULL`, bare `#if MACRO` presence checks, recovered layout members that still use raw `unsigned char` / `unsigned short`, and missing or misordered `EA_PRAGMA_ONCE_SUPPORTED` guard blocks when a header's prologue is touched. - `audit` groups repeated findings by file so branch-wide output stays readable. - Use `audit --category safe-cpp` when you want a smaller Frontend/FEng-focused subset and `audit --category match-sensitive-cpp` when you want a conservative review queue for decomp code. - `format --check` is an opt-in wrapper around the repo's `.clang-format`, and by default it targets eligible changed C/C++ files, including match-sensitive code. @@ -95,7 +95,14 @@ Foo::Foo() - Use `nullptr` exclusively for null pointers. - Prefer `if (ptr)` / `if (!ptr)` over explicit null comparisons when the change is local and verified safe. - When a match-sensitive TU has many explicit `nullptr` checks and you decide to normalize them, prefer one mechanical full-TU pass over piecemeal cleanup. Rebuild the unit and re-check its status before keeping the rewrite. +- When a helper is doing address arithmetic, prefer `intptr_t` / `uintptr_t` or byte-pointer (`reinterpret_cast`) math over plain `int` parameters or integerized pointer subtraction. - Inline assembly is acceptable when it is needed to preserve dead-code compares, ordering, or other compiler behavior that source alone cannot reproduce. +- In low-level list / node / allocator code, prefer existing helper methods such as `AddBefore`, `AddAfter`, `Remove`, `GetPrev`, `GetNext`, or typed accessors over open-coding link rewiring once the helper exists. + +### Header prologues and preprocessor checks + +- In headers, keep the guard / `EA_PRAGMA_ONCE_SUPPORTED` block before any project `#include`; do not place includes ahead of `#pragma once`. +- Use `#ifdef MACRO` / `#ifndef MACRO` for presence checks. Reserve bare `#if MACRO` for cases where you really need the macro's numeric value. ### Forward declarations and local prototypes @@ -103,6 +110,7 @@ Foo::Foo() - If the repo already has a header declaration/definition for a type, include that header instead of redeclaring the type locally. - If the repo only has an empty or stub owner header, and line info / surrounding source clearly points at that header's subsystem, prefer populating that owner header over leaving a recovered project type declaration inside a `.cpp`. - Only keep a local forward declaration when no canonical repo header exists yet and you have verified that the ownership is still unresolved. +- Likewise for project free functions: if a declaration is shared across translation units, move it into the owning header instead of leaving ad-hoc local prototypes in `.cpp` files. - Prefer moving helper template declarations next to their real use site instead of leaving them in an unrelated file. ### Pointer style @@ -114,13 +122,18 @@ Foo::Foo() - Use the repo's header guard form when writing headers: `#ifndef` / `#define` plus the `#ifdef EA_PRAGMA_ONCE_SUPPORTED` / `#pragma once` block. - Keep member layout comments aligned and intact in decomp headers. +- When writing a recovered layout, start from a pasted GC DWARF dump instead of hand-reconstructing a cleaner version. Treat the dump as source-of-truth data entry, then make only small verified fixes from PS2 or existing headers. - Preserve the original `class` / `struct` kind from existing headers or Dwarf / PS2 evidence; do not treat it as a cosmetic style choice. - Treat header declarations as the repo source of truth. If the repo only has local `.cpp` partial declarations, verify the kind with the PS2 dump instead of copying them blindly. - Even forward declarations and local partial declarations should use the accurate keyword when known. +- Keep the `// total size: 0x...` comment above the recovered type declaration instead of burying it inside the body. +- When a recovered type is a `class`, keep explicit access sections and put the method/accessor block before the member layout block unless existing repo evidence shows otherwise. - Preserve the member naming style that DWARF shows. Some types use `mMember`, others use `m_member`; do not normalize them. - Preserve recovered member names, types, order, and offset comments. Do not invent placeholder members named `pad`, `unk`, `unknown`, or `field_XXXX` for game code just to make a layout compile. +- Preserve the dumped declaration order too. Do not regroup methods, helpers, enums, or fields for readability unless an existing repo header or PS2 evidence proves the original order differs. - If a member is genuinely unknown, stop and verify it with `find-symbol.py`, GC Dwarf, and PS2 data. If the layout is still incomplete, add a short TODO above the type instead of burying uncertainty in fake member names. - Add offset / size comments when you are writing recovered type layouts from DWARF. +- In recovered layouts, prefer explicit-width aliases such as `uint8` / `uint16` when the field width is known. Use plain `char` for text / byte buffers and `signed char` when the field is a signed 8-bit counter. - Define inline member functions in headers only when DWARF shows that they are genuinely inlined in the binary. - Use `struct` for POD-like data carriers with public fields; use `class` for behavior-heavy types only when that matches the recovered type information. - Keep tiny placeholder methods as concise inline bodies when that is already the local pattern. @@ -134,13 +147,28 @@ Foo::Foo() ### Dense local code - Expand dense one-line helper structs, declaration blocks, and function bodies in non-match-sensitive files into normal multiline formatting. +- In low-level headers, prefer normal multi-line bodies for touched inline operators and accessors instead of stacking `{ return ...; }` on one line, unless the surrounding file clearly uses intentional placeholder one-liners. - Prefer readable blocks over stacked one-line statements when behavior does not depend on exact source shape. +- In touched validation/parsing code, prefer explicit min/max or boundary checks over equivalent magic-constant arithmetic when the clearer form still compiles to the verified result. +- In parser/state-table code, prefer named enums and enum-typed state variables over anonymous integer state codes when that rewrite is verified safe. +- In touched parser/script code, prefer keeping DWARF-listed consumed-token locals such as `region`, `section`, or `option` instead of chaining and discarding `GetNextArgument*` calls inline. When the original code used a small nested parse block, preserving that block shape can be necessary for exact DWARF even when objdiff already matches. + +### Recovery markers + +- Remove stale recovery markers such as `// TODO`, `// UNSOLVED`, or `// STRIPPED` when the touched code is now implemented or understood. +- If a marker still needs to stay, give it short context such as ownership uncertainty, a Dwarf caveat, a platform/config note, or a scratch/link reference. Avoid bare marker-only comments. +- Do not leave `// TODO` hanging off a declaration or helper you just implemented; either finish the thought or remove the marker. ### Uncertain ownership - If a declaration or global clearly compiles but its original home is uncertain, add a short TODO comment instead of inventing structure you cannot justify yet. - When ownership matters, verify it with `decomp-workflow.py`, `decomp-context.py`, and `line-lookup` before moving code. +### Readable helper extraction + +- When touched recovered code repeats the same pointer/boundary arithmetic, prefer a short named helper or accessor such as `GetTop`, `GetBot`, `GetNext`, `GetPrev`, `GetStringTableStart`, or `GetStringTableEnd` if that shape is already supported by Dwarf/inlining evidence. +- Prefer call sites that use those helpers or existing container APIs over re-encoding the same arithmetic or link manipulation inline. + ## Phase 3: Things Not To "Clean Up" Blindly - Do not move an inline method out of a header just because it looks cleaner. @@ -172,3 +200,11 @@ Keep the cleanup only if the build succeeds and the relevant match status is unc - The trailing `//` initializer-list markers are an intentional repo convention, not noise to remove. - Small `if (ptr)` cleanup batches can be kept in match-sensitive code, but only after rebuilding the affected unit. - Dense frontend shim files benefit from multiline struct/prototype/function formatting. +- Header prologues should keep the `EA_PRAGMA_ONCE_SUPPORTED` block ahead of includes, not after them. +- Bare `#if MACRO` presence checks are review bait; use `#ifdef` / `#ifndef` unless you are intentionally testing a numeric config value. +- Reviewed recovered headers tend to keep total-size comments above the type, methods before fields, explicit access sections, and fixed-width aliases for width-known narrow integer members. +- Recent `zMisc` review cleanup also showed that hand-reconstructed structs and reordered declarations create avoidable churn; copy recovered layouts from DWARF into the owner header first and keep the dumped order unless PS2/header evidence proves a correction. +- Reviewed fixups also remove stale bare recovery markers or replace them with context, and prefer existing list/node helpers over hand-written pointer/link rewiring. +- Some reviewed fixups improved readability without losing match by replacing opaque range-check arithmetic with explicit bounds and by moving repeated pointer/boundary math behind short named helpers. +- Other recurring review churn came from plain-`int` address helpers, stray local `.cpp` prototypes for shared functions, and integer-coded parser states where named enums were clearer but still matched. +- Recent `zTrack` polish also showed that parser-like code should keep DWARF-listed consumed-token locals and the original nested block shape instead of compressing everything into inline/discarded `GetNextArgument*` calls. diff --git a/.github/skills/execute/SKILL.md b/.github/skills/execute/SKILL.md index 7abf9cb3f..ea6fef41b 100644 --- a/.github/skills/execute/SKILL.md +++ b/.github/skills/execute/SKILL.md @@ -11,6 +11,9 @@ the produced C++ compiles to byte-identical object code against the original ret For each function, "done" means both objdiff and normalized DWARF are exact. +Human review is not a substitute for running `dwarf compare`. Each function should hit +its own `verify` gate before you treat it as ready to hand off, commit, or move past. + ## Overview This workflow combines several smaller workflows: @@ -88,6 +91,10 @@ definition does not yet exist in the project, follow the scaffold workflow in `.github/skills/scaffold/SKILL.md` to create the needed header/source definitions before moving on. +Treat recovered types here as copied reference data, not as hand-designed headers. Copy +the GC DWARF type body into the canonical owner header first and preserve its declaration +order unless PS2 or existing repo-header evidence proves a specific correction. + ## Phase 3: Implement Functions ### 3a. Get the updated function list @@ -113,6 +120,9 @@ For each missing or nonmatching function, follow the implementation workflow in - **One at a time.** Keep the tree in a coherent state as you work through the list. - **Balance new vs fixing.** Don't get stuck on one stubborn function — sometimes implementing the next function reveals patterns that make the previous one click. +- **Recovered types are not freeform.** If a function forces you to add or fix a type, + copy the DWARF layout into the owner header first. Do not sketch structs/classes from + use sites or reorder declarations just to make the header look nicer. - **Mismatch triage:** - `@stringBase0` offset mismatches often resolve as more string literals are added - If you need to inspect the original string or rodata at a virtual address, use `python tools/elf_lookup.py 0xADDR` @@ -152,6 +162,10 @@ python tools/decomp-workflow.py verify -u main/Path/To/TU -f FunctionName If it fails, follow up with `decomp-workflow.py diff` and `decomp-workflow.py dwarf` until both checks pass. +Do not queue up several "probably done" functions and leave the DWARF check for later. +Close the `verify` gate per function before moving on whenever feasible; otherwise the +reviewer ends up doing avoidable DWARF triage. + ### 3g. Periodic reassessment After every few functions, re-run the full status check: @@ -189,6 +203,8 @@ For any remaining nonmatching functions, make one final pass using the implement or refiner workflow with all context accumulated during the session. Do not report a function as complete unless its per-function `verify` check also passes. +Do not hand a function to review as "done except maybe DWARF" — either resolve the DWARF +failure yourself or explicitly call out the blocker and why it remains. ## Phase 5: Report diff --git a/.github/skills/implement/SKILL.md b/.github/skills/implement/SKILL.md index 25f56b926..8915ac246 100644 --- a/.github/skills/implement/SKILL.md +++ b/.github/skills/implement/SKILL.md @@ -9,6 +9,11 @@ Your goal is to decompile a specific function: writing C++ source that compiles A function is not done until it is exact in both objdiff and normalized DWARF. +Reviewers should not be spending their time rediscovering DWARF mismatches. Before you +report progress, ask for review, hand the function off, or switch to another target, you +must run the per-function verification gate yourself and treat any DWARF failure as your +next task, not as review debt. + ## Phase 1: Gather Context Collect data from **all** of these sources in parallel where possible. @@ -85,6 +90,8 @@ Reference the skill for the usage. It gives info based on the virtual address of - If a repo header already exists for the type, include that header instead of introducing a local forward declaration. - Preserve the original `class` vs `struct` kind. If the existing header is missing or incomplete, verify the type kind from GC Dwarf and PS2 info before writing a local declaration. - Preserve real member names and field types too. Do not introduce `pad`, `unk`, or `field_XXXX` members as placeholders for guessed layout; verify the member list from GC Dwarf / PS2 data and leave a TODO when something is still uncertain. +- When a type is missing or incomplete, dump the full class/struct body from GC DWARF and paste that as the starting point. Do not reconstruct the layout from one function's field accesses or from guessed semantics. +- Preserve the dumped declaration order as well as the member order. Do not re-sort methods, group fields by guessed meaning, or otherwise "clean up" the layout unless an existing repo header or PS2 evidence proves a specific correction. ### 1e. Assembly reference @@ -125,6 +132,8 @@ and assembly: Utilize the dwarf information that you get from the lookup skill heavily. +For any recovered type you touch while implementing the function, treat the DWARF body as source material to copy, not prose to paraphrase. Start from the dumped layout in the canonical owner header, then make only the minimal verified fixes. + Don't add explanatory comments during implementation unless you need to document a remaining DWARF mismatch. Don't use any temporary local variables that don't exist in the dwarf. @@ -156,6 +165,16 @@ python tools/decomp-workflow.py verify -u main/Path/To/TU -f FunctionName If the build fails, fix compilation errors first. +As soon as you have a compiling draft, run the combined verification gate immediately: + +```sh +python tools/decomp-workflow.py verify -u main/Path/To/TU -f FunctionName +``` + +Do this before you spend a long time polishing late instruction mismatches. If `verify` +already shows a DWARF failure, fix that first so you are not polishing code the reviewer +will bounce anyway. + ### Check the diff ```sh @@ -203,6 +222,17 @@ debug-line owner files for each DWARF `// Range:` block, which makes it much eas spot inlines that are coming from the wrong header or owner file. Exact line-number agreement is a useful secondary hint, but file ownership is the first thing to check. +Use this as the default loop when the function compiles but `verify` is still failing: + +1. Run `verify`. +2. If DWARF fails, run `dwarf`. +3. Fix the structural issue the DWARF diff is pointing at first: missing/extra locals, + wrong qualifiers or parameter types, wrong inline ownership, wrong helper/header owner, + or a source shape that outlined something that should be inlined. +4. Rebuild and rerun `verify`. +5. Only return to instruction-by-instruction cleanup once the remaining failures are no + longer obvious DWARF-compare issues. + Manual fallback: After writing your code, you can also run the dwarf dump on the compiled output and then query your output dump with lookup.py to compare your decompiled functions against the originals. Since the address of the function you're working on can keep changing @@ -233,6 +263,9 @@ Every mismatched instruction is a signal — don't settle for "close enough". Reaching 100% instruction matching status is not enough. Stay in the loop until `verify` passes, which means the DWARF of the function also matches after normalization. +Do not leave a function in a "review-ready" or "good enough for now" state with a known +DWARF failure unless you are explicitly blocked and you document that blocker clearly. + ## Phase 5: Report Summarize: diff --git a/.github/skills/refiner/SKILL.md b/.github/skills/refiner/SKILL.md index a6aeb2125..0054e6e18 100644 --- a/.github/skills/refiner/SKILL.md +++ b/.github/skills/refiner/SKILL.md @@ -15,9 +15,25 @@ approaches that were tried before — instead, apply systematic lateral analysis - A diff is available (`decomp-diff.py -u -d `). - The "obvious" translation from Ghidra has been attempted. - You have been given the current source code and the diff. +- You have already run the per-function `verify` gate and know whether the remaining work + is still structural DWARF cleanup or true late-stage instruction cleanup. + +Refiner is not the place to dump unresolved DWARF debt on a reviewer. If `verify` or +`dwarf` is still showing obvious structural mismatches (missing locals, wrong types, +wrong inline ownership, wrong helper/header owner), fix those first or drop back to the +implementer workflow before doing late instruction polish. ## Phase 1: Read the full diff without collapsing +Before you start a refiner pass, confirm the gate status: + +```sh +python tools/decomp-workflow.py verify -u main/Path/To/TU -f FunctionName +``` + +If the combined gate is failing for reasons that are still clearly visible in the DWARF +diff, address those first instead of treating them as reviewer follow-up. + Preferred shortcut: ```sh @@ -151,6 +167,9 @@ DWARF mismatches to watch for: - Wrong return type - Missing inlined function records (means an inline call was outlined) +If these mismatches are still present, you are not in pure refiner territory yet. Resolve +them before you ask a reviewer to spend time on the function. + ## Phase 4: Report Summarize: diff --git a/.github/skills/scaffold/SKILL.md b/.github/skills/scaffold/SKILL.md index a2f062f50..52cdf0e9a 100644 --- a/.github/skills/scaffold/SKILL.md +++ b/.github/skills/scaffold/SKILL.md @@ -29,7 +29,14 @@ Collect data from **all** of these sources in parallel where possible: ## Phase 2: Setup class -Copy and cleanup the header that you got from running the `lookup` skill using the `symbols/Dwarf` folder. Fix visibility, function order and vtable related things based on using `lookup` on the PS2 types. +Copy the header/type body that you got from running the `lookup` skill using the +`symbols/Dwarf` folder into the canonical owner header first. Do not retype or +reconstruct the layout from memory, from scattered callsites, or from guessed +semantics. + +Then do the minimum cleanup backed by evidence: fix visibility, function order and +vtable related things based on using `lookup` on the PS2 types, and clean up duplicated +inline copies when the DWARF emitted both versions. For formatting and local cleanup while writing the header, consult `.github/skills/code_style/SKILL.md`. Use it for member-comment alignment, declaration @@ -39,6 +46,9 @@ Preserve the real `class` / `struct` kind while scaffolding. Check existing head then use Dwarf plus PS2 visibility / vtable info to decide the type kind. Even temporary forward declarations should match the known original kind. +Keep the header prologue in repo order: header guard, `EA_PRAGMA_ONCE_SUPPORTED` block, +then includes. Do not drop project includes ahead of `#pragma once`. + If the repo already has a header for a type you need, include that header instead of adding a new local forward declaration. Only forward-declare when no canonical repo header exists yet and you have verified that the ownership is still unresolved. @@ -47,11 +57,31 @@ Preserve real member names, types, order, and offset comments while scaffolding. fill gaps with invented `pad`, `unk`, or `field_XXXX` members for game types; verify the layout from Dwarf / PS2 data and leave a TODO over the type if a field is still uncertain. +Preserve the declaration order from the dumped type body as well, not just the member +order. Do not regroup methods, fields, enums, or helper declarations for readability +unless an existing repo header or PS2 evidence proves the original owner header used a +different order. + +Keep the `// total size: 0x...` comment above the recovered type declaration. When the +recovered type is a `class`, keep explicit access sections and prefer putting methods / +accessors before the member layout block unless existing repo evidence says otherwise. + +When a recovered field width is known, prefer explicit-width aliases such as `uint8` / +`uint16` over raw `unsigned char` / `unsigned short`. Use plain `char` for string or byte +buffers and `signed char` when the field is a signed 8-bit counter. + +If a recovered type repeatedly walks neighbors, boundaries, or in-object offsets, prefer +small named helpers such as `GetTop`, `GetBot`, `GetNext`, `GetPrev`, or boundary getters +instead of repeating raw pointer arithmetic at each call site. + +When those helpers operate on addresses or byte offsets, prefer `intptr_t` / `uintptr_t` +or explicit byte-pointer arithmetic instead of plain `int` address parameters. + Only create headers if it's really necessary (the struct doesn't have inlines so you can't determine in which header file it goes and it's thematically very different from the other structs that use it), otherwise put it into the one you determined to be correct. The dwarf often has duplicated inlines, clean those up according to the order in the PS2 info. -Write a TODO comment over the struct/class if you aren't 100% sure that it belongs to the correct header. +Write a TODO comment over the struct/class if you aren't 100% sure that it belongs to the correct header, and say why (ownership uncertainty, circular dependency, dwarf caveat, etc.) instead of leaving a bare marker. ## Phase 3: Add needed files to jumbo file and compile diff --git a/.gitignore b/.gitignore index 547d049c6..1e78f634d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,12 @@ .idea/ .vs/ +# macOS +.DS_Store +.AppleDouble +.LSOverride +._* + # Caches __pycache__ .mypy_cache diff --git a/AGENTS.md b/AGENTS.md index d367fc237..2ce218626 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,8 +12,16 @@ ninja all_source # build all objects ninja # build all objects, hash check and progress report ninja baseline # generates baseline report for regression checking ninja changes # check for regressions after code changes (empty = no regressions) +python tools/build_matrix.py # sequential full `ninja` across GC/Xbox/PS2, then restore GOWE69 +python tools/build_matrix.py --all-source # sequential compile-only smoke check across GC/Xbox/PS2 ``` +Use `python tools/build_matrix.py` when you want one command that verifies the current +worktree across all supported platforms. It runs `configure.py --version ...` and the +selected ninja target sequentially, writes per-platform logs under `build//logs/`, +prints failure tails with the exact failing command, and restores the worktree to +`GOWE69` by default when it finishes. + ## Project Layout ``` @@ -31,16 +39,7 @@ objdiff.json Generated build/diff configuration ## Sub-Agent Usage -Sub-agents are allowed only for **read-only exploration** tasks such as: - -- searching the codebase for symbols, call sites, or include relationships -- inspecting decomp output, assembly, DWARF, PS2 dumps, or line mappings -- gathering context from Ghidra, `tools/decomp-workflow.py`, `lookup.py`, `decomp-diff.py`, or similar tools -- summarizing findings that help the main worker decide what to change - -Sub-agents must **not** write or edit code files, headers, configs, or other repository files. -All persistent file changes, decomp implementations, scaffolding, and follow-up fixes must be -done by the main worker after reviewing the read-only findings. +Sub-agents are **strictly prohibited**. Do not use sub-agents for any tasks (whether read-only exploration or editing). All work must be performed by the main worker directly. ## Forbidden Changes @@ -343,8 +342,15 @@ This is a **C++98** codebase compiled with ProDG GC 3.9.3 (GCC 2.95 under the ho - Inline assembly is acceptable when needed to reproduce dead code or compiler scheduling that source alone cannot express cleanly - Preserve the original `class` vs `struct` kind. Check existing headers first, then Dwarf / PS2 info when needed. Even forward declarations and local partial declarations should use the accurate keyword when known. - Prefer including the real repo header over introducing a local forward declaration for a project type. If a type already has a header in `src/`, include it instead of redeclaring it locally. -- If a subsystem already has a stub owner header and the debug line info points back at that subsystem, fill the owner header instead of keeping a recovered project type declaration in a `.cpp`. +- If a subsystem already has a stub or umbrella owner header and the debug line info points back at that subsystem, fill the owner header instead of keeping a recovered project type declaration in a `.cpp` or spinning up a one-off micro-header just for that type. +- Apply the same owner-header rule to shared enums, globals, callback typedefs, and free functions. If multiple TUs need the declaration, put it in the canonical owner header once and include that header instead of duplicating enum bodies or `extern` blocks across `.cpp`s. - Preserve original member names, types, order, and proven layout comments. Do not invent `pad`, `unk`, or `field_XXXX` members just to satisfy a guessed size or offset; verify the real members with `find-symbol.py`, GC Dwarf, and PS2 data, and leave a short TODO if a layout detail is still uncertain. +- When recovering a type, start by copying the GC DWARF struct/class body into the canonical owner header. Treat that dump as the source of truth for declaration order too; only apply targeted fixes that are backed by existing repo headers or PS2 data, such as visibility, virtual/function order, duplicate-inline cleanup, or owner-header placement. +- Do not hand-reconstruct recovered layouts from scattered field accesses, guessed semantics, or a "cleaned up" reordering. If you do not have enough evidence to paste the type confidently, stop and gather more DWARF / PS2 info first. +- Preserve the original scope and nesting of recovered declarations too. Keep class-owned enums/types nested when the original did, and move subsystem/global enums into their real owner header instead of flattening or duplicating them near one caller. +- Use the narrowest correct home for recovered declarations: shared project-facing types in headers, TU-private helper structs/classes and allocator metadata in the `.cpp`. Do not dump implementation-only helpers into public headers just because they were convenient to write there. +- Prefer real subsystem or vendor headers over ad-hoc local typedef/prototype blocks. If an external API is shared and the project is missing the proper header, add that header in the correct subtree instead of stashing declarations in an unrelated gameplay file. +- Do not leave repeated `// TODO move`, `// TODO where should this go`, or "I just made this up" markers around declarations. Either move the declaration to its owner now or leave one short targeted TODO above the owner declaration if ownership is still genuinely unresolved. - Follow DWARF member naming exactly (`mMember` vs `m_member`) instead of normalizing names - Omit the `this` pointer. - Use `nullptr` and `override`. If they are missing, you need to include `types.h`. @@ -363,44 +369,26 @@ python tools/decomp-status.py --unit main/Path/To/TU Commit whenever the match percentage increases (e.g. you matched a new function). Use this format for the commit message: ``` -n.n%: short description of what was matched or changed +n.n[n]%: short description of what was matched or changed ``` Examples: - `42.1%: match UpdateCamera` -- `78.5%: match PlayerController constructor and destructor` +- `78.56%: match PlayerController constructor and destructor` - `100.0%: full match for zAnim` Do not batch up multiple percentage milestones into one commit — commit as each improvement lands. -## Parallel Sub-Agent Matching - -When working on a translation unit with multiple non-matching functions, use sub-agents selectively for **read-only exploration** around individual functions. Each sub-agent should focus on **exactly one function** — do not assign a sub-agent more than one function at a time. - -**Limit: never run more than 5 sub-agents concurrently.** Spawning too many at once causes resource contention and makes it harder to reason about progress. - -Guidelines: - -- Prefer solving difficult matching work in the main worker. Use sub-agents to inspect one function's context, diff, DWARF, or related call paths without editing files. -- Spawn a sub-agent per function only when the functions are independent (no shared edits to the same source lines). -- Sub-agents stay read-only. Let them inspect existing diff/context output rather than compiling or rebuilding. -- Do not sit idle waiting for sub-agents to finish. Continue with other independent investigation while they run. -- After a useful result lands and you make a real improvement, check the updated match percentage and commit if it improved. - ## Matching Philosophy You should take the Ghidra decompiler output for the initial translation step, get it to compile, make sure that the dwarf of the function matches and only then look for binary matching problems in the assembly. Be aware Ghidra usually gets the order of branches incorrect in if statements (it inverts the logic and the two bodies are swapped), this needs to be fixed to achieve bytematching status. -You may use sub-agents to gather read-only context during this process, but they must not -edit files. Treat their output as analysis input for the main worker, not as a path to -delegate source changes. - A function is only done when both objdiff and normalized DWARF are exact. Treat a 100% instruction match with a DWARF mismatch as unfinished work, not a near-complete result. -The dwarf of your structs doesn't have to neccessarily match the original due to various reasons, just make sure that you copied everything correctly. +The DWARF of your structs does not always compare cleanly in every detail, but the recovery process still starts by copying the dumped layout correctly. Do not freehand-reconstruct a struct from call sites or guessed semantics; paste the DWARF body into the real owner header first, then make only the minimal PS2/header-backed fixes such as visibility, function order, vtable order, or duplicate-inline cleanup. Never dismiss a diff as "close enough" or "just register allocation." Every mismatched instruction is a signal that the source doesn't perfectly represent the original. Even @@ -436,6 +424,8 @@ Virtual table layout is also missing from the dwarf but there on PS2. Be aware t The inline information in the dwarf is incredibly useful. When you encounter one, you should look up its body in the project. If it doesn't exist yet, deduce how the code should look like and add it to the correct header (you can use your address lookup skill or if that doesn't succeed and the inline is a member function, just find the corresponding class in the project). +For recovered structs and classes, treat DWARF as copied source material rather than a loose sketch. Paste the dumped type into the owner header first and keep its declaration/member order unless PS2 or an existing repo header proves a specific correction. + It's very important that you use math inlines from bMath and UMath as shown in the dwarf. UVector inlines use temporaries that the compiler couldn't optimize out. You can see in the dwarf on which stack address they are and deduce final destination they are copied to. ### Store instruction order hints @@ -481,6 +471,24 @@ register assignments but does NOT affect integer register assignments (and vice Every local that is NOT in the DWARF is a spurious temporary — remove it. - Every local that IS in the DWARF must exist in the source, even if you don't use the name. Name it exactly as the DWARF shows. +- When objdiff is already exact but a local only differs by lexical scope, try an equivalent + loop form that keeps the temporary inside the same block as the original DWARF. In practice, + changing a `for (...; ...; x = next)` into a `while (...) { T *next = ...; ...; x = next; }` + can fix DWARF-only scope mismatches without changing codegen. +- In parser/script code, do not collapse DWARF-listed consumed tokens into chained discarded + calls. If the original debug info shows locals such as `region`, `section`, or `option`, + keep those named locals and the small nested parse block; that can preserve exact DWARF + while leaving objdiff unchanged. + +### Slot-pooled delete paths + +- If a recovered local/project type participates in `delete` paths or container/list teardown, + check whether the original type exposed inline `operator new` / `operator delete`. Missing + slot-pool-backed operators often makes GCC emit `__builtin_delete` instead of the original + allocator/free path and can also move destructor/delete DWARF ownership out of the TU. +- This applies even when the TU mostly allocates the type manually through `bOMalloc` or a + pool helper. Restoring the inline operators can still be necessary so `delete` expressions + and synthesized cleanup paths match the original code and DWARF. ### Virtual vs direct calls diff --git a/src/Speed/Indep/SourceLists/zEagl4Anim.cpp b/src/Speed/Indep/SourceLists/zEagl4Anim.cpp index b14224997..84c0ddbc0 100644 --- a/src/Speed/Indep/SourceLists/zEagl4Anim.cpp +++ b/src/Speed/Indep/SourceLists/zEagl4Anim.cpp @@ -34,8 +34,12 @@ #include "Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp" +#include "Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp" + #include "Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp" +#include "Speed/Indep/Src/EAGL4Anim/eagl4runtimetransform.cpp" + #include "Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp" #include "Speed/Indep/Src/EAGL4Anim/FnRawPoseChannel.cpp" @@ -68,4 +72,6 @@ #include "Speed/Indep/Src/EAGL4Anim/StatelessQ.cpp" +#include "Speed/Indep/Src/EAGL4Anim/SystemCmn.cpp" + #include "Speed/Indep/Src/EAGL4Anim/system.cpp" diff --git a/src/Speed/Indep/SourceLists/zTrack.cpp b/src/Speed/Indep/SourceLists/zTrack.cpp index e69de29bb..b627cab70 100644 --- a/src/Speed/Indep/SourceLists/zTrack.cpp +++ b/src/Speed/Indep/SourceLists/zTrack.cpp @@ -0,0 +1,25 @@ +#include "Speed/Indep/Src/World/Skids.cpp" + +#include "Speed/Indep/Src/World/Clans.cpp" + +#include "Speed/Indep/Src/World/Track.cpp" + +#include "Speed/Indep/Src/World/TrackPositionMarker.cpp" + +#include "Speed/Indep/Src/World/TrackInfo.cpp" + +#include "Speed/Indep/Src/World/TrackPath.cpp" + +#include "Speed/Indep/Src/World/TrackStreamer.cpp" + +#include "Speed/Indep/Src/World/Scenery.cpp" + +#include "Speed/Indep/Src/World/VisibleSection.cpp" + +#include "Speed/Indep/Src/World/WeatherMan.cpp" + +#include "Speed/Indep/Src/World/ScreenEffects.cpp" + +#include "Speed/Indep/Src/World/EventManager.cpp" + +#include "Speed/Indep/Src/World/ParameterMaps.cpp" diff --git a/src/Speed/Indep/Src/Camera/CameraMover.hpp b/src/Speed/Indep/Src/Camera/CameraMover.hpp index e2ef17946..673d751be 100644 --- a/src/Speed/Indep/Src/Camera/CameraMover.hpp +++ b/src/Speed/Indep/Src/Camera/CameraMover.hpp @@ -40,6 +40,10 @@ enum CameraMoverTypes { // total size: 0x124 class CameraAnchor { public: + bVector3 *GetGeometryPosition() { + return &mGeomPos; + } + unsigned int GetWorldID() const { return mWorldID; } diff --git a/src/Speed/Indep/Src/EAGL4Anim/AnimUtil.h b/src/Speed/Indep/Src/EAGL4Anim/AnimUtil.h index f7f01f8e8..034996e7b 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/AnimUtil.h +++ b/src/Speed/Indep/Src/EAGL4Anim/AnimUtil.h @@ -19,6 +19,41 @@ inline float FastSqrt(float x) { return sqrtf(x); } +inline void EulToQuat(const float *eulData, float *quatData) { + float ss; + float sc; + float cs; + float cc; + float sh; + float sj; + float si; + float ch; + float cj; + float ci; + float th; + float tj; + float ti; + + ti = eulData[0] * 0.5f; + tj = eulData[1] * 0.5f; + th = eulData[2] * 0.5f; + ci = cosf(ti); + cj = cosf(tj); + ch = cosf(th); + si = sinf(ti); + sj = sinf(tj); + sh = sinf(th); + cc = ci * cj; + cs = ci * sj; + sc = si * cj; + ss = si * sj; + + quatData[0] = sc * ch - cs * sh; + quatData[1] = cs * ch + sc * sh; + quatData[2] = cc * sh - ss * ch; + quatData[3] = cc * ch + ss * sh; +} + inline intptr_t AlignSize16(intptr_t size) { return (size + 15) & ~15; } @@ -58,6 +93,13 @@ inline void FastQuatBlendF4(float w, const float *d0, const float *d1, float *ou out[3] *= s; } +inline void QuatMult(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result) { + result.x = (a.x * b.w - a.y * b.z) + a.z * b.y + a.w * b.x; + result.y = ((a.x * b.z + a.y * b.w) - a.z * b.x) + a.w * b.y; + result.z = ((-a.x) * b.y + a.y * b.x) + a.z * b.w + a.w * b.z; + result.w = ((-a.x * b.x - a.y * b.y) - a.z * b.z) + a.w * b.w; +} + }; // namespace EAGL4Anim #endif diff --git a/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp index af2ce0242..f6786a9e5 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.cpp @@ -520,29 +520,4 @@ bool FnKeyQuatChan::EvalSQTMask(float currTime, float *sqt, const BoneMask *bone return true; } -// TODO move -float qt0[7]; - -// TODO move -inline void QuatF4(float *&data, float *output) { - output[0] = *data++; - output[1] = *data++; - output[2] = *data++; - output[3] = *data++; -} - -// TODO inline and move -void TranF3(float *&data, float *output) { - output[4] = *data++; - output[5] = *data++; - output[6] = *data++; -} - -// TODO inline and move -void QuatF4Interp(float w, float *&data0, float *&data1, float *output) { - QuatF4(data0, qt0); - QuatF4(data1, output); - FastQuatBlendF4(w, qt0, output, output); -} - }; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.h b/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.h index 94da14798..cd72222e7 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.h +++ b/src/Speed/Indep/Src/EAGL4Anim/DeltaChan.h @@ -136,9 +136,6 @@ class FnDeltaLerpChan : public FnDeltaChan { mType = AnimTypeId::ANIM_DELTALERP; } - // Overrides: FnAnimSuper - ~FnDeltaLerpChan() override {} - // Overrides: FnAnim void Eval(float prevTime, float currTime, float *evalBuffer) override; @@ -180,9 +177,6 @@ class FnDeltaQuatChan : public FnDeltaChan { mType = AnimTypeId::ANIM_DELTAQUAT; } - // Overrides: FnAnimSuper - ~FnDeltaQuatChan() override {} - // Overrides: FnAnim void Eval(float prevTime, float currTime, float *evalBuffer) override; @@ -356,9 +350,6 @@ class FnKeyLerpChan : public FnKeyDeltaChan { mType = AnimTypeId::ANIM_KEYLERP; } - // Overrides: FnAnimSuper - ~FnKeyLerpChan() override {} - // Overrides: FnAnim void Eval(float prevTime, float currTime, float *evalBuffer) override; @@ -394,9 +385,6 @@ class FnKeyQuatChan : public FnKeyDeltaChan { mType = AnimTypeId::ANIM_KEYQUAT; } - // Overrides: FnAnimSuper - ~FnKeyQuatChan() override {} - // Overrides: FnAnim void Eval(float prevTime, float currTime, float *evalBuffer) override; diff --git a/src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h b/src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h index 718216a3f..300f7e939 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h +++ b/src/Speed/Indep/Src/EAGL4Anim/DeltaQ.h @@ -10,6 +10,8 @@ namespace EAGL4Anim { +inline void DeltaQRecoverW(int signBit, UMath::Vector4 &q); + // total size: 0x18 struct DeltaQMinRangef { UMath::Vector3 mMin; // offset 0x0, size 0xC @@ -18,7 +20,16 @@ struct DeltaQMinRangef { // total size: 0xC struct DeltaQMinRange { - void UnQuantize(DeltaQMinRangef &minRangef) {} + void UnQuantize(DeltaQMinRangef &minRangef) { + const float RangeScale16Bit = 3.0518044e-5f; + + minRangef.mMin.x = mMin[0] * RangeScale16Bit - 1.0f; + minRangef.mMin.y = mMin[1] * RangeScale16Bit - 1.0f; + minRangef.mMin.z = mMin[2] * RangeScale16Bit - 1.0f; + minRangef.mRange.x = mRange[0] * 9.6119827e-7f; + minRangef.mRange.y = mRange[1] * 4.7871444e-7f; + minRangef.mRange.z = mRange[2] * 4.7871444e-7f; + } unsigned short mMin[3]; // offset 0x0, size 0x6 unsigned short mRange[3]; // offset 0x6, size 0x6 @@ -26,7 +37,11 @@ struct DeltaQMinRange { // total size: 0x3 struct DeltaQDelta { - void UnQuantize(const DeltaQMinRangef &minRangef, UMath::Vector4 &q) {} + void UnQuantize(const DeltaQMinRangef &minRangef, UMath::Vector4 &q) { + q.x = minRangef.mMin.x + minRangef.mRange.x * mX; + q.y = minRangef.mMin.y + minRangef.mRange.y * mY; + q.z = minRangef.mMin.z + minRangef.mRange.z * mZ; + } unsigned char mX : 7; // offset 0x0, size 0x1 unsigned char mW : 1; // offset 0x0, size 0x1 @@ -37,8 +52,13 @@ struct DeltaQDelta { // total size: 0x6 struct DeltaQPhysical { void UnQuantize(UMath::Vector4 &q) { - // const float RangeScale15Bit; - // const float RangeScale16Bit; + const float RangeScale16Bit = 3.0518044e-5f; + const float RangeScale15Bit = 6.1037019e-5f; + + q.x = mX * RangeScale15Bit - 1.0f; + q.y = mY * RangeScale16Bit - 1.0f; + q.z = mZ * RangeScale16Bit - 1.0f; + DeltaQRecoverW(mW, q); } unsigned short mX : 15; // offset 0x0, size 0x2 @@ -53,6 +73,45 @@ inline void DeltaQRecoverW(int signBit, UMath::Vector4 &q) { { float len; } + + ndotn = q.x * q.x + q.y * q.y + q.z * q.z; + if (ndotn <= 1.0f) { + q.w = FastSqrt(1.0f - ndotn); + if (signBit > 0) { + q.w = -q.w; + } + } else { + float len = FastSqrt(ndotn); + + q.x /= len; + q.y /= len; + q.z /= len; + q.w = 0.0f; + } +} + +inline void FastPolarizedQuatBlend(float t, const UMath::Vector4 &q0, const UMath::Vector4 &q1, UMath::Vector4 &result) { + float s; + UMath::Vector4 temp; + + if (q0.x * q1.x + q0.y * q1.y + q0.z * q1.z + q0.w * q1.w > 0.0f) { + temp = q1; + } else { + temp.x = -q1.x; + temp.y = -q1.y; + temp.z = -q1.z; + temp.w = -q1.w; + } + + result.x = t * (temp.x - q0.x) + q0.x; + result.y = t * (temp.y - q0.y) + q0.y; + result.z = t * (temp.z - q0.z) + q0.z; + result.w = t * (temp.w - q0.w) + q0.w; + s = 1.0f / FastSqrt(result.x * result.x + result.y * result.y + result.z * result.z + result.w * result.w); + result.x *= s; + result.y *= s; + result.z *= s; + result.w *= s; } // total size: 0x14 @@ -84,15 +143,21 @@ struct DeltaQ : public AnimMemoryMap { return result; } - void GetArrays(DeltaQMinRange *&minRanges, unsigned char *&binStart, unsigned char *&constBoneIndices, DeltaQPhysical *&constPhysical) {} + void GetArrays(DeltaQMinRange *&minRanges, unsigned char *&binStart, unsigned char *&constBoneIndices, DeltaQPhysical *&constPhysical) { + unsigned char *memBytes = reinterpret_cast(this) + 0x12; + + minRanges = reinterpret_cast(memBytes); + binStart = &memBytes[mNumBones * sizeof(DeltaQMinRange)]; + constBoneIndices = GetConstBoneIdx(); + constPhysical = GetConstPhysical(); + } int GetBinSize() const { - // TODO - return AlignSize2(3 * ((GetBinLength() - 1) * mNumBones)); + return AlignSize2(mNumBones * (((GetBinLength() - 1) * sizeof(DeltaQDelta)) + sizeof(DeltaQPhysical))); } DeltaQMinRange *GetMinRange() { - unsigned char *memBytes = reinterpret_cast(&this[1]); + unsigned char *memBytes = reinterpret_cast(this) + 0x12; return reinterpret_cast(memBytes); } @@ -106,27 +171,13 @@ struct DeltaQ : public AnimMemoryMap { return reinterpret_cast(binData); } - DeltaQDelta *GetDelta(unsigned char *binData, int deltaIdx) {} - - unsigned short *GetConstBoneIdx() { - unsigned int binLen = GetBinLength(); - const int binSize = GetBinSize(); - // TODO it's out of line - // int numBins = mNumFrames >> GetBinLengthPower(); // r8 - // // get to the end of the bins - // unsigned char *s = &GetBin(0)[binSize * numBins]; // r11 - // int r = mNumFrames & GetBinLengthModMask(); // r31 - - // if (r > 0) { - // s = reinterpret_cast(AlignSize2((intptr_t)s + mNumBones * 2 + (r - 1) * GetFrameDeltaSize())); - // } - - return reinterpret_cast(nullptr); + DeltaQDelta *GetDelta(unsigned char *binData, int deltaIdx) { + return reinterpret_cast(&binData[mNumBones * sizeof(DeltaQPhysical) + deltaIdx * mNumBones * sizeof(DeltaQDelta)]); } - float *GetConstPhysical() { - // return reinterpret_cast(AlignSize4(reinterpret_cast(&GetConstBoneIdx()[mNumConstBones]))); - } + unsigned char *GetConstBoneIdx(); + + DeltaQPhysical *GetConstPhysical(); unsigned short mNumKeys; // offset 0x4, size 0x2 unsigned char mNumBones; // offset 0x6, size 0x1 diff --git a/src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h b/src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h index 04999bb76..6e8afbbf7 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h +++ b/src/Speed/Indep/Src/EAGL4Anim/DeltaQFast.h @@ -27,7 +27,14 @@ struct DeltaQFastMinRange { // total size: 0x6 struct DeltaQFastPhysical { - void UnQuantize(UMath::Vector4 &q) const {} + void UnQuantize(UMath::Vector4 &q) const { + unsigned short w = static_cast((mW0 << 8) | (mW1 << 4) | mW2); + + q.x = static_cast(mX) * 4.8840049e-4f - 1.0002443f; + q.y = static_cast(mY) * 4.8840049e-4f - 1.0002443f; + q.z = static_cast(mZ) * 4.8840049e-4f - 1.0002443f; + q.w = static_cast(w) * 4.8840049e-4f - 1.0002443f; + } unsigned short mX : 12; // offset 0x0, size 0x2 unsigned short mW0 : 4; // offset 0x0, size 0x2 @@ -39,7 +46,14 @@ struct DeltaQFastPhysical { // total size: 0x3 struct DeltaQFastDelta { - void UnQuantize(const DeltaQFastMinRangef &minRangef, UMath::Vector4 &q) const {} + void UnQuantize(const DeltaQFastMinRangef &minRangef, UMath::Vector4 &q) const { + unsigned char w = static_cast((mW0 << 4) | (mW1 << 2) | mW2); + + q.x = minRangef.mMin.x + minRangef.mRange.x * static_cast(mX) * 1.5873017e-2f; + q.y = minRangef.mMin.y + minRangef.mRange.y * static_cast(mY) * 1.5873017e-2f; + q.z = minRangef.mMin.z + minRangef.mRange.z * static_cast(mZ) * 1.5873017e-2f; + q.w = minRangef.mMin.w + minRangef.mRange.w * static_cast(w) * 1.5873017e-2f; + } unsigned char mX : 6; // offset 0x0, size 0x1 unsigned char mW0 : 2; // offset 0x0, size 0x1 @@ -69,7 +83,7 @@ struct DeltaQFast : public AnimMemoryMap { return 1 << mBinLengthPower; } - unsigned char GetBinLengthPower() const { + unsigned int GetBinLengthPower() const { return mBinLengthPower; } @@ -78,11 +92,15 @@ struct DeltaQFast : public AnimMemoryMap { return result; } - void GetArrays(DeltaQFastMinRange *&minRanges, unsigned char *&binStart, unsigned char *&constBoneIndices, DeltaQFastPhysical *&constPhysical) {} + void GetArrays(DeltaQFastMinRange *&minRanges, unsigned char *&binStart, unsigned char *&constBoneIndices, DeltaQFastPhysical *&constPhysical) { + minRanges = GetMinRange(); + binStart = GetBin(0); + constBoneIndices = reinterpret_cast(GetConstBoneIdx()); + constPhysical = reinterpret_cast(GetConstPhysical()); + } int GetBinSize() const { - // TODO - return AlignSize2(3 * ((GetBinLength() - 1) * mNumBones)); + return AlignSize2(mNumBones * (6 + ((GetBinLength() - 1) * 3))); } DeltaQFastMinRange *GetMinRange() { @@ -100,27 +118,13 @@ struct DeltaQFast : public AnimMemoryMap { return reinterpret_cast(binData); } - DeltaQFastDelta *GetDelta(unsigned char *binData, int deltaIdx) {} - - unsigned short *GetConstBoneIdx() { - unsigned int binLen = GetBinLength(); - const int binSize = GetBinSize(); - // TODO it's out of line - // int numBins = mNumFrames >> GetBinLengthPower(); // r8 - // // get to the end of the bins - // unsigned char *s = &GetBin(0)[binSize * numBins]; // r11 - // int r = mNumFrames & GetBinLengthModMask(); // r31 - - // if (r > 0) { - // s = reinterpret_cast(AlignSize2((intptr_t)s + mNumBones * 2 + (r - 1) * GetFrameDeltaSize())); - // } - - return reinterpret_cast(nullptr); + DeltaQFastDelta *GetDelta(unsigned char *binData, int deltaIdx) { + return reinterpret_cast(&binData[mNumBones * 6 + deltaIdx * mNumBones * 3]); } - float *GetConstPhysical() { - // return reinterpret_cast(AlignSize4(reinterpret_cast(&GetConstBoneIdx()[mNumConstBones]))); - } + unsigned char *GetConstBoneIdx(); + + DeltaQFastPhysical *GetConstPhysical(); unsigned short mNumKeys; // offset 0x4, size 0x2 unsigned char mNumBones; // offset 0x6, size 0x1 diff --git a/src/Speed/Indep/Src/EAGL4Anim/DeltaSingleQ.h b/src/Speed/Indep/Src/EAGL4Anim/DeltaSingleQ.h index 0a3b3f91c..fcd2babe8 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/DeltaSingleQ.h +++ b/src/Speed/Indep/Src/EAGL4Anim/DeltaSingleQ.h @@ -22,9 +22,21 @@ struct DeltaSingleQMinRangef { // total size: 0xE struct DeltaSingleQMinRange { - void GetAlignment(float &c0, float &c1) {} + void GetAlignment(float &c0, float &c1) { + c0 = mConst0 * 9.5875265e-5f + -3.1415927f; + c1 = mConst1 * 9.5875265e-5f + -3.1415927f; + } + + void UnQuantize(DeltaSingleQMinRangef &minRangef) { + const float RangeScale16Bit = 3.0518044e-5f; - void UnQuantize(DeltaSingleQMinRangef &minRangef) {} + minRangef.mMin[0] = mMin[0] * RangeScale16Bit - 1.0f; + minRangef.mMin[1] = mMin[1] * RangeScale16Bit - 1.0f; + minRangef.mRange[0] = mRange[0] * 8.1381455e-6f; + minRangef.mRange[1] = mRange[1] * 8.1381455e-6f; + minRangef.mIndex = static_cast(mIndex); + GetAlignment(minRangef.mConst0, minRangef.mConst1); + } unsigned short mConst0; // offset 0x0, size 0x2 unsigned short mConst1; // offset 0x2, size 0x2 @@ -35,7 +47,22 @@ struct DeltaSingleQMinRange { // total size: 0x2 struct DeltaSingleQPhysical { - void UnQuantize(int index, UMath::Vector4 &q) const {} + void UnQuantize(int index, UMath::Vector4 &q) const { + const float RangeScale8Bit = 7.8431377e-3f; + + q.z = 0.0f; + q.y = 0.0f; + q.x = 0.0f; + + if (index == 0) { + q.x = mV * RangeScale8Bit - 1.0f; + } else if (index == 1) { + q.y = mV * RangeScale8Bit - 1.0f; + } else { + q.z = mV * RangeScale8Bit - 1.0f; + } + q.w = mW * RangeScale8Bit - 1.0f; + } unsigned char mV; // offset 0x0, size 0x1 unsigned char mW; // offset 0x1, size 0x1 @@ -43,7 +70,20 @@ struct DeltaSingleQPhysical { // total size: 0x1 struct DeltaSingleQDelta { - void UnQuantize(const DeltaSingleQMinRangef &minRangef, UMath::Vector4 &q) {} + void UnQuantize(const DeltaSingleQMinRangef &minRangef, UMath::Vector4 &q) { + q.z = 0.0f; + q.y = 0.0f; + q.x = 0.0f; + + if (minRangef.mIndex == 0) { + q.x = minRangef.mRange[0] * mV + minRangef.mMin[0]; + } else if (minRangef.mIndex == 1) { + q.y = minRangef.mRange[0] * mV + minRangef.mMin[0]; + } else { + q.z = minRangef.mRange[0] * mV + minRangef.mMin[0]; + } + q.w = minRangef.mRange[1] * mW + minRangef.mMin[1]; + } unsigned char mV : 4; // offset 0x0, size 0x1 unsigned char mW : 4; // offset 0x0, size 0x1 @@ -78,11 +118,16 @@ struct DeltaSingleQ : public AnimMemoryMap { return result; } - void GetArrays(DeltaSingleQMinRange *&minRanges, unsigned char *&binStart) {} + void GetArrays(DeltaSingleQMinRange *&minRanges, unsigned char *&binStart) { + unsigned char *memBytes = reinterpret_cast(&this[1]); + + minRanges = reinterpret_cast(memBytes); + binStart = &memBytes[mNumBones * sizeof(DeltaSingleQMinRange)]; + } int GetBinSize() const { - // TODO - // return AlignSize2(3 * ((GetBinLength() - 1) * mNumBones)); + return static_cast(AlignSize2((mNumBones * sizeof(DeltaSingleQPhysical)) + + ((GetBinLength() - 1) * mNumBones * sizeof(DeltaSingleQDelta)))); } DeltaSingleQMinRange *GetMinRange() { @@ -91,16 +136,19 @@ struct DeltaSingleQ : public AnimMemoryMap { } unsigned char *GetBin(int binIdx) { - // const int bs = GetBinSize(); - // unsigned char *memPos = &reinterpret_cast(GetMinRange())[mNumBones * sizeof(DeltaSingleQMinRange)]; - // return &memPos[binIdx * bs]; + const int bs = GetBinSize(); + unsigned char *memPos = &reinterpret_cast(GetMinRange())[mNumBones * sizeof(DeltaSingleQMinRange)]; + return &memPos[binIdx * bs]; } DeltaSingleQPhysical *GetPhysical(unsigned char *binData) { return reinterpret_cast(binData); } - DeltaSingleQDelta *GetDelta(unsigned char *binData, int deltaIdx) {} + DeltaSingleQDelta *GetDelta(unsigned char *binData, int deltaIdx) { + return reinterpret_cast(&binData[mNumBones * sizeof(DeltaSingleQPhysical) + + (deltaIdx * mNumBones * sizeof(DeltaSingleQDelta))]); + } unsigned short *GetConstBoneIdx() { unsigned int binLen = GetBinLength(); diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnCycle.h b/src/Speed/Indep/Src/EAGL4Anim/FnCycle.h index 37366dac1..8ecfde059 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnCycle.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnCycle.h @@ -1,11 +1,12 @@ #ifndef EAGL4ANIM_FNCYCLE_H #define EAGL4ANIM_FNCYCLE_H -#include "Speed/Indep/Src/EAGL4Anim/eagl4supportdef.h" #ifdef EA_PRAGMA_ONCE_SUPPORTED #pragma once #endif +#include "Speed/Indep/Src/EAGL4Anim/eagl4supportdef.h" +#include "AnimUtil.h" #include "FnAnim.h" namespace EAGL4Anim { @@ -42,30 +43,61 @@ class FnCycle : public FnAnim { mpAnim = anim; mStartTime = startTime; mEndTime = endTime; + mLength = endTime - startTime; } FnAnim *GetAnim() { return mpAnim; } - float GetStartTime() const {} + float GetStartTime() const { + return mStartTime; + } - float GetEndTime() const {} + float GetEndTime() const { + return mEndTime; + } - // Overrides: FnAnim - void Eval(float previousTime, float currentTime, float *dofs) override {} + private: + float GetInRangeTime(float t) const { + float tmp; + int n; + + if (t < mStartTime) { + tmp = t - mStartTime; + n = FloatToInt(tmp / mLength); + return mEndTime - (tmp - static_cast(n) * mLength); + } + + if (t <= mEndTime) { + return t; + } + + tmp = t - mEndTime; + n = FloatToInt(tmp / mLength); + return mStartTime + (tmp - static_cast(n) * mLength); + } + public: // Overrides: FnAnim - bool EvalEvent(float previousTime, float currentTime, EventHandler **eventHandlers, void *extraData) override {} + void Eval(float previousTime, float currentTime, float *dofs) override { + mpAnim->Eval(GetInRangeTime(previousTime), GetInRangeTime(currentTime), dofs); + } // Overrides: FnAnim - bool EvalSQT(float currentTime, float *sqt, const BoneMask *boneMask) override {} + bool EvalEvent(float previousTime, float currentTime, EventHandler **eventHandlers, void *extraData) override { + return mpAnim->EvalEvent(GetInRangeTime(previousTime), GetInRangeTime(currentTime), eventHandlers, extraData); + } // Overrides: FnAnim - bool EvalPhase(float currentTime, PhaseValue &phase) override {} + bool EvalSQT(float currentTime, float *sqt, const BoneMask *boneMask) override { + return mpAnim->EvalSQT(GetInRangeTime(currentTime), sqt, boneMask); + } - private: - float GetInRangeTime(float t) const {} + // Overrides: FnAnim + bool EvalPhase(float currentTime, PhaseValue &phase) override { + return mpAnim->EvalPhase(GetInRangeTime(currentTime), phase); + } float mStartTime; // offset 0xC, size 0x4 float mEndTime; // offset 0x10, size 0x4 diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp index 10528beb1..b688c9f5a 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.cpp @@ -6,6 +6,69 @@ namespace EAGL4Anim { +unsigned char *DeltaQ::GetConstBoneIdx() { + unsigned int binLen = GetBinLength(); + const int binSize = GetBinSize(); + unsigned char *s = GetBin(0); + int r = mNumKeys % binLen; + + s += binSize * (mNumKeys / binLen); + + if (r != 0) { + s += mNumBones * (((r - 1) * sizeof(DeltaQDelta)) + sizeof(DeltaQPhysical)); + } + + return s; +} + +DeltaQPhysical *DeltaQ::GetConstPhysical() { + unsigned int numBones = mNumBones; + unsigned int binLength = 1u << mBinLengthPower; + unsigned int numBins = mNumKeys / binLength; + unsigned int remainder = mNumKeys - numBins * binLength; + int s = reinterpret_cast(this) + 0x12; + + s += numBones * sizeof(DeltaQMinRange); + s += AlignSize2(numBones * (((binLength - 1) * sizeof(DeltaQDelta)) + sizeof(DeltaQPhysical))) * numBins; + + if (remainder != 0) { + s += numBones * (((remainder - 1) * sizeof(DeltaQDelta)) + sizeof(DeltaQPhysical)); + } + + return reinterpret_cast(AlignSize2(s + mNumConstBones)); +} + +namespace { + +static const float kFloatZero = 0.0f; +static const float kFloatOne = 1.0f; + +static inline DeltaQMinRange *GetMinRanges(DeltaQ *deltaQ) { + return deltaQ->GetMinRange(); +} + +static inline unsigned char *GetBinStart(DeltaQ *deltaQ) { + return &reinterpret_cast(GetMinRanges(deltaQ))[deltaQ->mNumBones * sizeof(DeltaQMinRange)]; +} + +static inline unsigned char *GetBin(DeltaQ *deltaQ, int binIdx) { + return &GetBinStart(deltaQ)[binIdx * deltaQ->GetBinSize()]; +} + +static inline DeltaQPhysical *GetPhysical(unsigned char *binData) { + return reinterpret_cast(binData); +} + +static inline DeltaQDelta *GetDelta(DeltaQ *deltaQ, unsigned char *binData, int deltaIdx) { + return deltaQ->GetDelta(binData, deltaIdx); +} + +static inline float *GetOutputQuat(float *sqt, unsigned char boneIdx) { + return &sqt[boneIdx * 12 + 4]; +} + +} // namespace + FnDeltaQ::FnDeltaQ() : mPrevKey(-1), // mConstPhysical(nullptr), // @@ -22,6 +85,401 @@ void FnDeltaQ::Eval(float prevTime, float currTime, float *sqt) { EvalSQTMasked(currTime, nullptr, sqt); } +bool FnDeltaQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { + if (boneMask) { + return EvalSQTMasked(currTime, boneMask, sqt); + } + + if (!mBins) { + InitBuffersAsRequired(); + } + DeltaQ *deltaQ = reinterpret_cast(mpAnim); + + if (!deltaQ->mNumBones) { + return true; + } + int floorTime = FloatToInt(currTime); + int floorKey; + + if (!deltaQ->mTimes) { + if (floorTime < 0) { + floorKey = 0; + } else if (floorTime >= deltaQ->mNumKeys) { + floorKey = deltaQ->mNumKeys - 1; + } else { + floorKey = floorTime; + } + } else if (floorTime < deltaQ->mTimes[0]) { + floorKey = 0; + } else { + int timeIndex; + + if (mPrevKey < 1) { + timeIndex = 0; + } else { + timeIndex = mPrevKey - 1; + } + if (deltaQ->mTimes[timeIndex] <= floorTime) { + while (timeIndex < deltaQ->mNumKeys - 2 && deltaQ->mTimes[timeIndex + 1] <= floorTime) { + timeIndex++; + } + } else { + while (timeIndex > 0 && deltaQ->mTimes[timeIndex] > floorTime) { + timeIndex--; + } + } + + floorKey = timeIndex + 1; + } + + unsigned int binLenPower = deltaQ->GetBinLengthPower(); + unsigned int binLenModMask = deltaQ->GetBinLengthModMask(); + + int floorBinIdx = floorKey >> binLenPower; + int floorDeltaIdx = floorKey & binLenModMask; + int prevBinIdx = mPrevKey >> binLenPower; + int prevDeltaIdx; + unsigned char *binData = &mBins[floorBinIdx * mBinSize]; + DeltaQPhysical *floorPhys = GetPhysical(binData); + unsigned char *boneIdxs = deltaQ->mBoneIdxs; + + bool preventReverse = false; + + if (floorKey < mPrevKey) { + preventReverse = !IsReverseDeltaSumEnabled(); + } + + if (mPrevKey != -1 && floorBinIdx == prevBinIdx && floorDeltaIdx != 0 && !preventReverse) { + prevDeltaIdx = mPrevKey & binLenModMask; + } else { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + floorPhys[ibone].UnQuantize(mPrevQs[ibone]); + } + prevDeltaIdx = 0; + } + + if (prevDeltaIdx < floorDeltaIdx) { + for (int iframe = prevDeltaIdx; iframe < floorDeltaIdx; iframe++) { + DeltaQDelta *floorDelta = GetDelta(deltaQ, binData, iframe); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + UMath::Vector4 deltaf; + DeltaQMinRangef minRangef; + + mMinRanges[ibone].UnQuantize(minRangef); + floorDelta->UnQuantize(minRangef, deltaf); + mPrevQs[ibone].x += deltaf.x; + mPrevQs[ibone].y += deltaf.y; + mPrevQs[ibone].z += deltaf.z; + floorDelta++; + } + } + } else if (prevDeltaIdx > floorDeltaIdx) { + for (int iframe = prevDeltaIdx - 1; iframe >= floorDeltaIdx; iframe--) { + DeltaQDelta *floorDelta = GetDelta(deltaQ, binData, iframe); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + UMath::Vector4 deltaf; + DeltaQMinRangef minRangef; + + mMinRanges[ibone].UnQuantize(minRangef); + floorDelta->UnQuantize(minRangef, deltaf); + mPrevQs[ibone].x -= deltaf.x; + mPrevQs[ibone].y -= deltaf.y; + mPrevQs[ibone].z -= deltaf.z; + floorDelta++; + } + } + } + + if (floorDeltaIdx == 0) { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + DeltaQRecoverW(floorPhys[ibone].mW, mPrevQs[ibone]); + } + } else { + DeltaQDelta *floorDelta = GetDelta(deltaQ, binData, floorDeltaIdx - 1); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + DeltaQRecoverW(floorDelta->mW, mPrevQs[ibone]); + floorDelta++; + } + } + mPrevKey = floorKey; + + int ceilKey = floorKey + 1; + float scale = 1.0f; + bool slerpReqd; + + if (!deltaQ->mTimes) { + slerpReqd = currTime != floorTime; + if (slerpReqd) { + scale = currTime - floorTime; + } + } else if (floorKey == 0) { + slerpReqd = currTime != 0.0f; + if (slerpReqd) { + float ceilKeyTime = static_cast(deltaQ->mTimes[floorKey]); + scale = currTime / ceilKeyTime; + } + } else { + float floorKeyTime = deltaQ->mTimes[floorKey - 1]; + slerpReqd = currTime != floorKeyTime; + if (slerpReqd) { + float ceilKeyTime = static_cast(deltaQ->mTimes[floorKey]); + scale = (currTime - floorKeyTime) / (ceilKeyTime - floorKeyTime); + } + } + + if (slerpReqd && floorKey < deltaQ->mNumKeys - 1) { + int ceilBinIdx = ceilKey >> binLenPower; + DeltaQPhysical *ceilPhys = GetPhysical(&mBins[ceilBinIdx * mBinSize]); + + if (ceilBinIdx != floorBinIdx) { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + UMath::Vector4 ceilq; + UMath::Vector4 &out = *reinterpret_cast(GetOutputQuat(sqt, boneIdxs[ibone])); + + ceilPhys[ibone].UnQuantize(ceilq); + FastPolarizedQuatBlend(scale, mPrevQs[ibone], ceilq, out); + } + } else { + DeltaQDelta *ceilDelta = GetDelta(deltaQ, binData, floorDeltaIdx); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + UMath::Vector4 ceilq; + UMath::Vector4 &out = *reinterpret_cast(GetOutputQuat(sqt, boneIdxs[ibone])); + DeltaQMinRangef minRangef; + + mMinRanges[ibone].UnQuantize(minRangef); + ceilDelta->UnQuantize(minRangef, ceilq); + ceilq.x += mPrevQs[ibone].x; + ceilq.y += mPrevQs[ibone].y; + ceilq.z += mPrevQs[ibone].z; + DeltaQRecoverW(ceilDelta->mW, ceilq); + FastPolarizedQuatBlend(scale, mPrevQs[ibone], ceilq, out); + ceilDelta++; + } + } + } else { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + *reinterpret_cast(GetOutputQuat(sqt, boneIdxs[ibone])) = mPrevQs[ibone]; + } + } + + if (deltaQ->mNumConstBones != 0) { + for (int ibone = 0; ibone < deltaQ->mNumConstBones; ibone++) { + UMath::Vector4 constq; + + mConstPhysical[ibone].UnQuantize(constq); + *reinterpret_cast(GetOutputQuat(sqt, mConstBoneIdxs[ibone])) = constq; + } + } + + return true; +} + +bool FnDeltaQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sqt) { + if (!mBins) { + InitBuffersAsRequired(); + } + DeltaQ *deltaQ = reinterpret_cast(mpAnim); + unsigned char numBones = deltaQ->mNumBones; + unsigned char *boneIdxs = deltaQ->mBoneIdxs; + + if (!numBones) { + return true; + } + int floorTime = FloatToInt(currTime); + int floorKey; + + if (!deltaQ->mTimes) { + if (floorTime < 0) { + floorKey = 0; + } else if (floorTime >= deltaQ->mNumKeys) { + floorKey = deltaQ->mNumKeys - 1; + } else { + floorKey = floorTime; + } + } else if (floorTime < deltaQ->mTimes[0]) { + floorKey = 0; + } else { + int timeIndex; + + if (mPrevKey < 1) { + timeIndex = 0; + } else { + timeIndex = mPrevKey - 1; + } + if (deltaQ->mTimes[timeIndex] <= floorTime) { + while (timeIndex < deltaQ->mNumKeys - 2 && deltaQ->mTimes[timeIndex + 1] <= floorTime) { + timeIndex++; + } + } else { + while (timeIndex > 0 && deltaQ->mTimes[timeIndex] > floorTime) { + timeIndex--; + } + } + + floorKey = timeIndex + 1; + } + + unsigned int binLenPower = deltaQ->GetBinLengthPower(); + unsigned int binLenModMask = deltaQ->GetBinLengthModMask(); + + int floorBinIdx = floorKey >> binLenPower; + int floorDeltaIdx = floorKey & binLenModMask; + int prevBinIdx = mPrevKey >> binLenPower; + int prevDeltaIdx; + unsigned char *binData = &mBins[floorBinIdx * mBinSize]; + DeltaQPhysical *floorPhys = GetPhysical(binData); + bool preventReverse = floorKey < mPrevKey && !IsReverseDeltaSumEnabled(); + if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorDeltaIdx == 0 || preventReverse) { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + floorPhys[ibone].UnQuantize(mPrevQs[ibone]); + } + } + prevDeltaIdx = 0; + } else { + prevDeltaIdx = mPrevKey & binLenModMask; + } + + if (prevDeltaIdx < floorDeltaIdx) { + for (int iframe = prevDeltaIdx; iframe < floorDeltaIdx; iframe++) { + DeltaQDelta *floorDelta = GetDelta(deltaQ, binData, iframe); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector4 deltaf; + DeltaQMinRangef minRangef; + + mMinRanges[ibone].UnQuantize(minRangef); + floorDelta->UnQuantize(minRangef, deltaf); + mPrevQs[ibone].x += deltaf.x; + mPrevQs[ibone].y += deltaf.y; + mPrevQs[ibone].z += deltaf.z; + } + floorDelta++; + } + } + } else if (prevDeltaIdx > floorDeltaIdx) { + for (int iframe = prevDeltaIdx - 1; iframe >= floorDeltaIdx; iframe--) { + DeltaQDelta *floorDelta = GetDelta(deltaQ, binData, iframe); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector4 deltaf; + DeltaQMinRangef minRangef; + + mMinRanges[ibone].UnQuantize(minRangef); + floorDelta->UnQuantize(minRangef, deltaf); + mPrevQs[ibone].x -= deltaf.x; + mPrevQs[ibone].y -= deltaf.y; + mPrevQs[ibone].z -= deltaf.z; + } + floorDelta++; + } + } + } + + if (floorDeltaIdx == 0) { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + DeltaQRecoverW(floorPhys[ibone].mW, mPrevQs[ibone]); + } + } + } else { + DeltaQDelta *floorDelta = GetDelta(deltaQ, binData, floorDeltaIdx - 1); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + DeltaQRecoverW(floorDelta->mW, mPrevQs[ibone]); + } + floorDelta++; + } + } + mPrevKey = floorKey; + + int ceilKey = floorKey + 1; + float scale = 1.0f; + bool slerpReqd; + + if (!deltaQ->mTimes) { + slerpReqd = currTime != floorTime; + if (slerpReqd) { + scale = currTime - floorTime; + } + } else if (floorKey == 0) { + slerpReqd = currTime != 0.0f; + if (slerpReqd) { + float ceilKeyTime = static_cast(deltaQ->mTimes[floorKey]); + scale = currTime / ceilKeyTime; + } + } else { + float floorKeyTime = deltaQ->mTimes[floorKey - 1]; + slerpReqd = currTime != floorKeyTime; + if (slerpReqd) { + float ceilKeyTime = static_cast(deltaQ->mTimes[floorKey]); + scale = (currTime - floorKeyTime) / (ceilKeyTime - floorKeyTime); + } + } + + if (slerpReqd && floorKey < deltaQ->mNumKeys - 1) { + int ceilBinIdx = ceilKey >> binLenPower; + DeltaQPhysical *ceilPhys = GetPhysical(&mBins[ceilBinIdx * mBinSize]); + + if (ceilBinIdx != floorBinIdx) { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector4 ceilq; + UMath::Vector4 &out = *reinterpret_cast(GetOutputQuat(sqt, boneIdxs[ibone])); + + ceilPhys[ibone].UnQuantize(ceilq); + FastPolarizedQuatBlend(scale, mPrevQs[ibone], ceilq, out); + } + } + } else { + DeltaQDelta *ceilDelta = GetDelta(deltaQ, binData, floorDeltaIdx); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector4 ceilq; + UMath::Vector4 &out = *reinterpret_cast(GetOutputQuat(sqt, boneIdxs[ibone])); + DeltaQMinRangef minRangef; + + mMinRanges[ibone].UnQuantize(minRangef); + ceilDelta->UnQuantize(minRangef, ceilq); + ceilq.x += mPrevQs[ibone].x; + ceilq.y += mPrevQs[ibone].y; + ceilq.z += mPrevQs[ibone].z; + DeltaQRecoverW(ceilDelta->mW, ceilq); + FastPolarizedQuatBlend(scale, mPrevQs[ibone], ceilq, out); + } + ceilDelta++; + } + } + } else { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + *reinterpret_cast(GetOutputQuat(sqt, boneIdxs[ibone])) = mPrevQs[ibone]; + } + } + } + + if (deltaQ->mNumConstBones != 0) { + for (int ibone = 0; ibone < deltaQ->mNumConstBones; ibone++) { + if (boneMask->GetBone(mConstBoneIdxs[ibone])) { + UMath::Vector4 constq; + + mConstPhysical[ibone].UnQuantize(constq); + *reinterpret_cast(GetOutputQuat(sqt, mConstBoneIdxs[ibone])) = constq; + } + } + } + + return true; +} + bool FnDeltaQ::EvalWeights(float currTime, float *weights) { Eval(currTime, currTime, weights); return true; diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.h b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.h index 80a3abe6e..5ce31f3f2 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQ.h @@ -73,7 +73,19 @@ class FnDeltaQ : public FnAnimMemoryMap { protected: virtual bool EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sqt); - void InitBuffersAsRequired() {} + void InitBuffersAsRequired() { + DeltaQ *deltaQ = reinterpret_cast(mpAnim); + DeltaQMinRange *minRanges; + + deltaQ->GetArrays(minRanges, mBins, mConstBoneIdxs, mConstPhysical); + mBinSize = deltaQ->GetBinSize(); + + if (deltaQ->mNumBones != 0) { + mPrevQBlock = MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs)); + mPrevQs = reinterpret_cast(mPrevQBlock); + mMinRanges = minRanges; + } + } DeltaQMinRange *mMinRanges; // offset 0x10, size 0x4 unsigned char *mBins; // offset 0x14, size 0x4 diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp index e69de29bb..52c406e0e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.cpp @@ -0,0 +1,738 @@ +#include "FnDeltaQFast.h" + +#include "AnimTypeId.h" +#include "AnimUtil.h" + + +static void QuatMultXxYxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, const UMath::Vector4 &c, UMath::Vector4 &result); +static void QuatMultXxQ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result); +static void QuatMultQxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result); + +namespace EAGL4Anim { + +unsigned char *DeltaQFast::GetConstBoneIdx() { + unsigned int binLen = GetBinLength(); + const int binSize = GetBinSize(); + unsigned char *s = GetBin(0); + int r = mNumKeys % binLen; + + s += binSize * (mNumKeys / binLen); + + if (r > 0) { + s += mNumBones * (((r - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical)); + } + + return s; +} + +DeltaQFastPhysical *DeltaQFast::GetConstPhysical() { + unsigned int numBones = mNumBones; + unsigned int binLength = 1u << mBinLengthPower; + unsigned int numBins = mNumKeys / binLength; + int remainder = mNumKeys - numBins * binLength; + int s = reinterpret_cast(this) + 0x12; + + s += numBones * sizeof(DeltaQFastMinRange); + s += AlignSize2(numBones * (((binLength - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical))) * numBins; + + if (remainder > 0) { + s += numBones * (((remainder - 1) * sizeof(DeltaQFastDelta)) + sizeof(DeltaQFastPhysical)); + } + + return reinterpret_cast(AlignSize2(s + mNumConstBones)); +} + +namespace { + +static const float kQFastMinScale16 = 3.0518044e-5f; +static const float kQFastDeltaScale6 = 1.5873017e-2f; +static const float kQFastPhysicalScale12 = 4.8840049e-4f; +static const float kQFastPhysicalBias12 = 1.0002443f; + +static inline float *GetQFastOutputQuat(float *sqt, unsigned char boneIdx) { + return &sqt[boneIdx * 12 + 4]; +} + +static inline unsigned char *GetQFastBin(unsigned char *bins, int binSize, int binIdx) { + return &bins[binIdx * binSize]; +} + +static inline DeltaQFastPhysical *GetQFastPhysical(unsigned char *binData) { + return reinterpret_cast(binData); +} + +static inline unsigned char *GetQFastDeltaData(DeltaQFast *deltaQ, unsigned char *binData, int deltaIdx) { + return &binData[deltaQ->mNumBones * 6 + deltaIdx * deltaQ->mNumBones * 3]; +} + +static inline void DecodeQFastPhysical(const DeltaQFastPhysical &physical, UMath::Vector4 &q) { + unsigned short xBits = physical.mX; + unsigned short yBits = physical.mY; + unsigned short zBits = physical.mZ; + unsigned short wBits = static_cast((physical.mW0 << 8) | (physical.mW1 << 4) | physical.mW2); + + q.x = xBits * kQFastPhysicalScale12 - kQFastPhysicalBias12; + q.y = yBits * kQFastPhysicalScale12 - kQFastPhysicalBias12; + q.z = zBits * kQFastPhysicalScale12 - kQFastPhysicalBias12; + q.w = wBits * kQFastPhysicalScale12 - kQFastPhysicalBias12; +} + +static inline void DecodeQFastDelta(const DeltaQFastMinRangef &minRangef, const unsigned char *deltaData, UMath::Vector4 &q) { + unsigned int xBits = deltaData[0] >> 2; + unsigned int yBits = deltaData[1] >> 2; + unsigned int zBits = deltaData[2] >> 2; + unsigned int wBits = ((deltaData[0] & 3) << 4) | ((deltaData[1] & 3) << 2) | (deltaData[2] & 3); + + q.x = minRangef.mMin.x + minRangef.mRange.x * (xBits * kQFastDeltaScale6); + q.y = minRangef.mMin.y + minRangef.mRange.y * (yBits * kQFastDeltaScale6); + q.z = minRangef.mMin.z + minRangef.mRange.z * (zBits * kQFastDeltaScale6); + q.w = minRangef.mMin.w + minRangef.mRange.w * (wBits * kQFastDeltaScale6); +} + +static inline int FindQFastFloorKey(int prevKey, DeltaQFast *deltaQ, float currTime) { + int floorTime = FloatToInt(currTime); + + if (!deltaQ->mTimes) { + if (floorTime < 0) { + return 0; + } + if (deltaQ->mNumKeys > floorTime) { + return floorTime; + } + return deltaQ->mNumKeys - 1; + } + if (floorTime < deltaQ->mTimes[0]) { + return 0; + } + + int timeIndex = prevKey < 1 ? 0 : prevKey - 1; + + if (deltaQ->mTimes[timeIndex] <= floorTime) { + while (timeIndex < deltaQ->mNumKeys - 2 && deltaQ->mTimes[timeIndex + 1] <= floorTime) { + timeIndex++; + } + } else { + while (timeIndex > 0 && deltaQ->mTimes[timeIndex] > floorTime) { + timeIndex--; + } + } + + return timeIndex + 1; +} + +static inline float ComputeQFastBlendT(DeltaQFast *deltaQ, int floorKey, int ceilKey, float currTime) { + if (ceilKey <= floorKey) { + return 0.0f; + } + if (!deltaQ->mTimes) { + return currTime - static_cast(floorKey); + } + + float floorTime = static_cast(deltaQ->mTimes[floorKey - 1]); + float ceilTime = static_cast(deltaQ->mTimes[ceilKey - 1]); + + if (floorKey == 0) { + floorTime = 0.0f; + } + if (ceilTime == floorTime) { + return 0.0f; + } + + return (currTime - floorTime) / (ceilTime - floorTime); +} + +} // namespace + +FnDeltaQFast::FnDeltaQFast() + : mMinRangesf(nullptr), // + mBins(nullptr), // + mBinSize(-1), // + mPrevKey(-1), // + mPrevQBlock(nullptr), // + mPrevQs(nullptr), // + mNextKey(-1), // + mNextQBlock(nullptr), // + mNextQs(nullptr), // + mConstBoneIdxs(nullptr), // + mConstPhysical(nullptr), // + mBoneMask(nullptr) { + mType = AnimTypeId::ANIM_DELTAQFAST; +} + +FnDeltaQFast::~FnDeltaQFast() { + if (mMinRangesf) { + MemoryPoolManager::DeleteBlock(mMinRangesf); + } +} + +void FnDeltaQFast::SetAnimMemoryMap(AnimMemoryMap *anim) { + DeltaQFast *deltaQ = reinterpret_cast(anim); + + mpAnim = anim; + unsigned char numBones = deltaQ->mNumBones; + DeltaQFastMinRange *minRange = reinterpret_cast(reinterpret_cast(deltaQ) + 0x12); + mBins = reinterpret_cast(minRange) + numBones * sizeof(DeltaQFastMinRange); + mConstBoneIdxs = reinterpret_cast(deltaQ->GetConstBoneIdx()); + mConstPhysical = reinterpret_cast(deltaQ->GetConstPhysical()); + mBinSize = AlignSize2(deltaQ->mNumBones * (((1 << deltaQ->mBinLengthPower) - 1) * 3 + 6)); + + if (deltaQ->mNumBones != 0) { + unsigned int qBlockOffset = numBones << 5; + unsigned int nextQBlockOffset = numBones << 4; + unsigned char *block = + reinterpret_cast(MemoryPoolManager::NewBlock(nextQBlockOffset + nextQBlockOffset + qBlockOffset)); + unsigned char *qBlock = block + qBlockOffset; + unsigned char *nextQBlock = qBlock + nextQBlockOffset; + + mMinRangesf = reinterpret_cast(block); + mPrevQBlock = qBlock; + mPrevQs = reinterpret_cast(qBlock); + mNextQBlock = nextQBlock; + mNextQs = reinterpret_cast(mNextQBlock); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + unsigned short *minRangeValues = reinterpret_cast(&minRange[ibone]); + float *minRangefValues = reinterpret_cast(&mMinRangesf[ibone]); + + minRangefValues[0] = minRangeValues[0] * kQFastMinScale16 - 1.0f; + minRangefValues[1] = minRangeValues[1] * kQFastMinScale16 - 1.0f; + minRangefValues[2] = minRangeValues[2] * kQFastMinScale16 - 1.0f; + minRangefValues[3] = minRangeValues[3] * kQFastMinScale16 - 1.0f; + minRangefValues[4] = minRangeValues[4] * kQFastMinScale16; + minRangefValues[5] = minRangeValues[5] * kQFastMinScale16; + minRangefValues[6] = minRangeValues[6] * kQFastMinScale16; + minRangefValues[7] = minRangeValues[7] * kQFastMinScale16; + } + } + + mBoneMask = nullptr; + mNextKey = -1; + mPrevKey = -1; +} + +bool FnDeltaQFast::GetLength(float &timeLength) const { + DeltaQFast *deltaQ = reinterpret_cast(mpAnim); + + timeLength = static_cast(deltaQ->GetNumFrames()); + return true; +} + +void FnDeltaQFast::Eval(float prevTime, float currTime, float *sqt) { + EvalSQT(currTime, sqt, nullptr); +} + +void FnDeltaQFast::InitBuffers() { + if (mpAnim) { + SetAnimMemoryMap(mpAnim); + } +} + +inline void FnDeltaQFast::AddDelta(DeltaQFastPhysical *floorPhys, DeltaQFast *deltaQ, int prevDeltaIdx, int floorDeltaIdx, + UMath::Vector4 *prevQs) { + unsigned char *binData = reinterpret_cast(floorPhys); + + for (int iframe = prevDeltaIdx; iframe < floorDeltaIdx; iframe++) { + DeltaQFastDelta *floorDelta = reinterpret_cast(GetQFastDeltaData(deltaQ, binData, iframe)); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + UMath::Vector4 delta; + + floorDelta->UnQuantize(mMinRangesf[ibone], delta); + prevQs[ibone].x += delta.x; + prevQs[ibone].y += delta.y; + prevQs[ibone].z += delta.z; + prevQs[ibone].w += delta.w; + floorDelta++; + } + } +} + +inline void FnDeltaQFast::SubDelta(DeltaQFastPhysical *floorPhys, DeltaQFast *deltaQ, int prevDeltaIdx, int floorDeltaIdx, + UMath::Vector4 *prevQs) { + unsigned char *binData = reinterpret_cast(floorPhys); + + for (int iframe = prevDeltaIdx - 1; iframe >= floorDeltaIdx; iframe--) { + DeltaQFastDelta *floorDelta = reinterpret_cast(GetQFastDeltaData(deltaQ, binData, iframe)); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + UMath::Vector4 delta; + + floorDelta->UnQuantize(mMinRangesf[ibone], delta); + prevQs[ibone].x -= delta.x; + prevQs[ibone].y -= delta.y; + prevQs[ibone].z -= delta.z; + prevQs[ibone].w -= delta.w; + floorDelta++; + } + } +} + +void FnDeltaQFast::UpdateNextQs(DeltaQFast *deltaQ, int ceilKey, int floorBinIdx, int floorDeltaIdx) { + if (ceilKey == mNextKey) { + return; + } + + int ceilBinIdx = ceilKey >> deltaQ->mBinLengthPower; + unsigned char *binData = &mBins[ceilBinIdx * mBinSize]; + + if (ceilBinIdx != floorBinIdx) { + unsigned char numBones = deltaQ->mNumBones; + + for (int ibone = 0; ibone < numBones; ibone++) { + float *nextQ = reinterpret_cast(&mNextQs[ibone]); + DeltaQFastPhysical *physical = reinterpret_cast(&binData[ibone * 6]); + + nextQ[0] = static_cast(physical->mX) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[1] = static_cast(physical->mY) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[2] = static_cast(physical->mZ) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[3] = + static_cast(static_cast((physical->mW0 << 8) | (physical->mW1 << 4) | physical->mW2)) * kQFastPhysicalScale12 - + kQFastPhysicalBias12; + } + } else { + unsigned int numBones = deltaQ->mNumBones; + int ceilDeltaIdx = floorDeltaIdx; + DeltaQFastDelta *ceilDelta = reinterpret_cast(&binData[numBones * 6 + ceilDeltaIdx * numBones * 3]); + + for (int ibone = 0; ibone < static_cast(numBones); ibone++) { + UMath::Vector4 ceilq; + + ceilDelta->UnQuantize(mMinRangesf[ibone], ceilq); + mNextQs[ibone].x = ceilq.x + mPrevQs[ibone].x; + mNextQs[ibone].y = ceilq.y + mPrevQs[ibone].y; + mNextQs[ibone].z = ceilq.z + mPrevQs[ibone].z; + mNextQs[ibone].w = ceilq.w + mPrevQs[ibone].w; + ceilDelta++; + } + } + + mNextKey = ceilKey; +} + +void FnDeltaQFast::AddDeltaMask(DeltaQFastPhysical *floorPhys, DeltaQFast *deltaQ, int prevDeltaIdx, int floorDeltaIdx, UMath::Vector4 *prevQs, + const BoneMask *boneMask) { + unsigned char *binData = reinterpret_cast(floorPhys); + unsigned char *boneIdxs = deltaQ->mBoneIdxs; + unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, prevDeltaIdx); + + for (int iframe = prevDeltaIdx; iframe < floorDeltaIdx; iframe++) { + unsigned char numBones = deltaQ->mNumBones; + DeltaQFastDelta *floorDelta = reinterpret_cast(deltaData); + + for (int ibone = 0; ibone < numBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector4 delta; + + floorDelta->UnQuantize(mMinRangesf[ibone], delta); + prevQs[ibone].x += delta.x; + prevQs[ibone].y += delta.y; + prevQs[ibone].z += delta.z; + prevQs[ibone].w += delta.w; + } + floorDelta++; + } + deltaData = reinterpret_cast(floorDelta); + } +} + +void FnDeltaQFast::SubDeltaMask(DeltaQFastPhysical *floorPhys, DeltaQFast *deltaQ, int prevDeltaIdx, int floorDeltaIdx, UMath::Vector4 *prevQs, + const BoneMask *boneMask) { + unsigned char *binData = reinterpret_cast(floorPhys); + unsigned char *boneIdxs = deltaQ->mBoneIdxs; + unsigned char *deltaData = GetQFastDeltaData(deltaQ, binData, prevDeltaIdx - 1) + (deltaQ->mNumBones - 1) * 3; + + for (int iframe = prevDeltaIdx - 1; iframe >= floorDeltaIdx; iframe--) { + unsigned char numBones = deltaQ->mNumBones; + DeltaQFastDelta *floorDelta = reinterpret_cast(deltaData); + + for (int ibone = numBones - 1; ibone >= 0; ibone--) { + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector4 delta; + + floorDelta->UnQuantize(mMinRangesf[ibone], delta); + prevQs[ibone].x -= delta.x; + prevQs[ibone].y -= delta.y; + prevQs[ibone].z -= delta.z; + prevQs[ibone].w -= delta.w; + } + floorDelta--; + } + deltaData = reinterpret_cast(floorDelta); + } +} + +void FnDeltaQFast::UpdateNextQsMask(DeltaQFast *deltaQ, int ceilKey, int floorBinIdx, int floorDeltaIdx, const BoneMask *boneMask) { + if (ceilKey == mNextKey) { + return; + } + + int ceilBinIdx = ceilKey >> deltaQ->mBinLengthPower; + unsigned char *binData = &mBins[ceilBinIdx * mBinSize]; + unsigned char *boneIdxs = deltaQ->mBoneIdxs; + + if (ceilBinIdx != floorBinIdx) { + unsigned char numBones = deltaQ->mNumBones; + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + unsigned char boneIdx = boneIdxs[ibone]; + if (boneMask->GetBone(boneIdx)) { + float *nextQ = reinterpret_cast(&mNextQs[ibone]); + DeltaQFastPhysical *physical = reinterpret_cast(&binData[ibone * 6]); + + nextQ[0] = static_cast(physical->mX) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[1] = static_cast(physical->mY) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[2] = static_cast(physical->mZ) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + nextQ[3] = + static_cast(static_cast((physical->mW0 << 8) | (physical->mW1 << 4) | physical->mW2)) * + kQFastPhysicalScale12 - + kQFastPhysicalBias12; + } + } + } else { + unsigned int numBones = deltaQ->mNumBones; + DeltaQFastDelta *ceilDelta = reinterpret_cast(&binData[numBones * 6 + floorDeltaIdx * numBones * 3]); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + unsigned char boneIdx = boneIdxs[ibone]; + if (boneMask->GetBone(boneIdx)) { + UMath::Vector4 ceilq; + + ceilDelta->UnQuantize(mMinRangesf[ibone], ceilq); + mNextQs[ibone].x = ceilq.x + mPrevQs[ibone].x; + mNextQs[ibone].y = ceilq.y + mPrevQs[ibone].y; + mNextQs[ibone].z = ceilq.z + mPrevQs[ibone].z; + mNextQs[ibone].w = ceilq.w + mPrevQs[ibone].w; + } + ceilDelta++; + } + } + + mNextKey = ceilKey; +} + +bool FnDeltaQFast::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { + if (!boneMask) { + if (mBoneMask) { + mBoneMask = boneMask; + mPrevKey = -1; + mNextKey = -1; + } + + DeltaQFast *deltaQ = reinterpret_cast(mpAnim); + float *quatBase = sqt + 4; + unsigned char *boneIdxs = deltaQ->mBoneIdxs; + + if (deltaQ->mNumBones) { + int floorTime = FloatToInt(currTime); + unsigned short *times = deltaQ->mTimes; + int floorKey = FindQFastFloorKey(mPrevKey, deltaQ, currTime); + unsigned int floorDeltaIdx; + int floorBinIdx; + unsigned int binLengthMask; + + floorBinIdx = static_cast(floorKey) >> deltaQ->mBinLengthPower; + binLengthMask = 0x7FFFFFFFU >> (0x1F - deltaQ->mBinLengthPower); + floorDeltaIdx = floorKey & binLengthMask; + + if (mNextKey == floorKey) { + UMath::Vector4 *swapQs = mNextQs; + + mNextQs = mPrevQs; + mPrevQs = swapQs; + mNextKey = mPrevKey; + } else { + int binData = reinterpret_cast(mBins) + floorBinIdx * mBinSize; + int prevDeltaIdx = mPrevKey; + bool preventReverse = false; + + if (mPrevKey == static_cast(floorKey + 1)) { + for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { + mPrevQs[ibone] = mNextQs[ibone]; + } + + mNextKey = mPrevKey; + } + + if (floorKey < mPrevKey) { + preventReverse = !IsReverseDeltaSumEnabled(); + } + + if (prevDeltaIdx != -1 && floorBinIdx == (mPrevKey >> deltaQ->mBinLengthPower) && floorDeltaIdx != 0 && !preventReverse) { + prevDeltaIdx &= static_cast(binLengthMask); + } else { + for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + DeltaQFastPhysical *physical = reinterpret_cast(binData + ibone * 6); + + prevQ[0] = static_cast(physical->mX) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + prevQ[1] = static_cast(physical->mY) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + prevQ[2] = static_cast(physical->mZ) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + prevQ[3] = + static_cast(static_cast((physical->mW0 << 8) | (physical->mW1 << 4) | physical->mW2)) * + kQFastPhysicalScale12 - + kQFastPhysicalBias12; + } + + prevDeltaIdx = 0; + } + + if (prevDeltaIdx < static_cast(floorDeltaIdx)) { + AddDelta(reinterpret_cast(binData), deltaQ, prevDeltaIdx, floorDeltaIdx, mPrevQs); + } else if (static_cast(floorDeltaIdx) < prevDeltaIdx) { + SubDelta(reinterpret_cast(binData), deltaQ, prevDeltaIdx, floorDeltaIdx, mPrevQs); + } + } + + mPrevKey = floorKey; + times = deltaQ->mTimes; + bool slerpReqd = false; + float t = 0.0f; + + if (!times) { + float floorTimef = static_cast(floorTime); + + slerpReqd = currTime != floorTimef; + if (slerpReqd) { + t = currTime - floorTimef; + } + } else if (floorKey == 0) { + slerpReqd = currTime != 0.0f; + if (slerpReqd) { + t = currTime / static_cast(times[0]); + } + } else { + float floorTimef = static_cast(times[floorKey - 1]); + + slerpReqd = currTime != floorTimef; + if (slerpReqd) { + t = (currTime - floorTimef) / (static_cast(times[floorKey]) - floorTimef); + } + } + + if (slerpReqd && static_cast(floorKey) < deltaQ->mNumKeys - 1) { + UpdateNextQs(deltaQ, floorKey + 1, floorBinIdx, floorDeltaIdx); + + for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + float *nextQ = reinterpret_cast(&mNextQs[ibone]); + float x = t * (nextQ[0] - prevQ[0]) + prevQ[0]; + unsigned char boneIdx = boneIdxs[ibone]; + float y = t * (nextQ[1] - prevQ[1]) + prevQ[1]; + float z = t * (nextQ[2] - prevQ[2]) + prevQ[2]; + float w = t * (nextQ[3] - prevQ[3]) + prevQ[3]; + float *out = reinterpret_cast(boneIdx * 0x30 + reinterpret_cast(quatBase)); + float invNorm = 1.0f / sqrtf(x * x + y * y + z * z + w * w); + + out[0] = x * invNorm; + out[1] = y * invNorm; + out[2] = z * invNorm; + out[3] = w * invNorm; + } + + goto finish_const_bones; + } + + for (int ibone = 0; ibone < static_cast(deltaQ->mNumBones); ibone++) { + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + float *out = reinterpret_cast(boneIdxs[ibone] * 0x30 + reinterpret_cast(quatBase)); + + out[0] = prevQ[0]; + out[1] = prevQ[1]; + out[2] = prevQ[2]; + out[3] = prevQ[3]; + } + } + + finish_const_bones: + for (int ibone = 0; ibone < static_cast(deltaQ->mNumConstBones); ibone++) { + DeltaQFastPhysical *physical = reinterpret_cast(reinterpret_cast(mConstPhysical) + ibone * 6); + float *out = reinterpret_cast(mConstBoneIdxs[ibone] * 0x30 + reinterpret_cast(quatBase)); + + out[0] = static_cast(physical->mX) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + out[1] = static_cast(physical->mY) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + out[2] = static_cast(physical->mZ) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + out[3] = static_cast(static_cast((physical->mW0 << 8) | (physical->mW1 << 4) | physical->mW2)) * + kQFastPhysicalScale12 - + kQFastPhysicalBias12; + } + + return true; + } + return EvalSQTMask(currTime, sqt, boneMask); +} + +bool FnDeltaQFast::EvalSQTMask(float currTime, float *sqt, const BoneMask *boneMask) { + if (boneMask != mBoneMask) { + mBoneMask = boneMask; + mPrevKey = -1; + mNextKey = -1; + } + + DeltaQFast *deltaQ = reinterpret_cast(mpAnim); + float *q = sqt + 4; + unsigned char *boneIdxs = deltaQ->mBoneIdxs; + + if (!deltaQ->mNumBones) { + return true; + } + + int floorTime = FloatToInt(currTime); + int floorKey; + + if (!deltaQ->mTimes) { + if (floorTime < 0) { + floorKey = 0; + } else if (floorTime >= deltaQ->mNumKeys) { + floorKey = deltaQ->mNumKeys - 1; + } else { + floorKey = floorTime; + } + } else if (floorTime < deltaQ->mTimes[0]) { + floorKey = 0; + } else { + int timeIndex = mPrevKey < 1 ? 0 : mPrevKey - 1; + + if (deltaQ->mTimes[timeIndex] <= floorTime) { + while (timeIndex < deltaQ->mNumKeys - 2 && deltaQ->mTimes[timeIndex + 1] <= floorTime) { + timeIndex++; + } + } else { + while (timeIndex > 0 && deltaQ->mTimes[timeIndex] > floorTime) { + timeIndex--; + } + } + + floorKey = timeIndex + 1; + } + + int floorBinIdx = floorKey >> deltaQ->GetBinLengthPower(); + int floorDeltaIdx = floorKey & deltaQ->GetBinLengthModMask(); + int prevBinIdx = mPrevKey >> deltaQ->GetBinLengthPower(); + bool preventReverse = floorKey < mPrevKey && !IsReverseDeltaSumEnabled(); + unsigned char *binData = &mBins[floorBinIdx * mBinSize]; + DeltaQFastPhysical *floorPhys = reinterpret_cast(binData); + int prevDeltaIdx; + + if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorDeltaIdx == 0 || preventReverse) { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (!boneMask->GetBone(boneIdxs[ibone])) { + continue; + } + + DeltaQFastPhysical *physical = &floorPhys[ibone]; + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + + prevQ[0] = static_cast(physical->mX) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + prevQ[1] = static_cast(physical->mY) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + prevQ[2] = static_cast(physical->mZ) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + prevQ[3] = static_cast(static_cast((physical->mW0 << 8) | (physical->mW1 << 4) | physical->mW2)) * + kQFastPhysicalScale12 - + kQFastPhysicalBias12; + } + prevDeltaIdx = 0; + } else { + prevDeltaIdx = mPrevKey & deltaQ->GetBinLengthModMask(); + } + + if (prevDeltaIdx < floorDeltaIdx) { + AddDeltaMask(floorPhys, deltaQ, prevDeltaIdx, floorDeltaIdx, mPrevQs, boneMask); + } else if (prevDeltaIdx > floorDeltaIdx) { + SubDeltaMask(floorPhys, deltaQ, prevDeltaIdx, floorDeltaIdx, mPrevQs, boneMask); + } + + int ceilKey = floorKey + 1; + bool slerpReqd = false; + float t = 0.0f; + + if (ceilKey >= deltaQ->mNumKeys) { + ceilKey = floorKey; + } + + if (!deltaQ->mTimes) { + float floorTimef = static_cast(floorKey); + + slerpReqd = currTime != floorTimef; + if (slerpReqd) { + t = currTime - floorTimef; + } + } else if (floorKey == 0) { + slerpReqd = currTime != 0.0f; + if (slerpReqd) { + float ceilKeyTime = static_cast(deltaQ->mTimes[0]); + + t = currTime / ceilKeyTime; + } + } else { + float floorKeyTime = static_cast(deltaQ->mTimes[floorKey - 1]); + + slerpReqd = currTime != floorKeyTime; + if (slerpReqd) { + float ceilKeyTime = static_cast(deltaQ->mTimes[floorKey]); + + t = (currTime - floorKeyTime) / (ceilKeyTime - floorKeyTime); + } + } + + if (slerpReqd && ceilKey > floorKey) { + UpdateNextQsMask(deltaQ, ceilKey, floorBinIdx, floorDeltaIdx, boneMask); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + unsigned char boneIdx = boneIdxs[ibone]; + + if (!boneMask->GetBone(boneIdx)) { + continue; + } + + float *prevQ = reinterpret_cast(&mPrevQs[ibone]); + float *nextQ = reinterpret_cast(&mNextQs[ibone]); + float *out = &q[boneIdx * 12]; + float x = t * (nextQ[0] - prevQ[0]) + prevQ[0]; + float y = t * (nextQ[1] - prevQ[1]) + prevQ[1]; + float z = t * (nextQ[2] - prevQ[2]) + prevQ[2]; + float w = t * (nextQ[3] - prevQ[3]) + prevQ[3]; + float invNorm = 1.0f / sqrtf(x * x + y * y + z * z + w * w); + + out[0] = x * invNorm; + out[1] = y * invNorm; + out[2] = z * invNorm; + out[3] = w * invNorm; + } + } else { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + unsigned char boneIdx = boneIdxs[ibone]; + + if (!boneMask->GetBone(boneIdx)) { + continue; + } + + float *out = &q[boneIdx * 12]; + + out[0] = mPrevQs[ibone].x; + out[1] = mPrevQs[ibone].y; + out[2] = mPrevQs[ibone].z; + out[3] = mPrevQs[ibone].w; + } + } + + for (int ibone = 0; ibone < deltaQ->mNumConstBones; ibone++) { + unsigned char boneIdx = mConstBoneIdxs[ibone]; + + if (!boneMask->GetBone(boneIdx)) { + continue; + } + + DeltaQFastPhysical *physical = &mConstPhysical[ibone]; + float *out = &q[boneIdx * 12]; + + out[0] = static_cast(physical->mX) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + out[1] = static_cast(physical->mY) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + out[2] = static_cast(physical->mZ) * kQFastPhysicalScale12 - kQFastPhysicalBias12; + out[3] = static_cast(static_cast((physical->mW0 << 8) | (physical->mW1 << 4) | physical->mW2)) * + kQFastPhysicalScale12 - + kQFastPhysicalBias12; + } + + return true; +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.h b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.h index eaeba781a..75d0cd29e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaQFast.h @@ -37,34 +37,26 @@ class FnDeltaQFast : public FnAnimMemoryMap { FnDeltaQFast(); // Overrides: FnAnimSuper - ~FnDeltaQFast() override { - if (mPrevQBlock) { - MemoryPoolManager::DeleteBlock(mPrevQBlock); - } - } + ~FnDeltaQFast() override; // Overrides: FnAnimMemoryMap - void SetAnimMemoryMap(AnimMemoryMap *anim) override { - mpAnim = anim; - } + void SetAnimMemoryMap(AnimMemoryMap *anim) override; // Overrides: FnAnim - bool GetLength(float &timeLength) const override { - DeltaQFast *deltaQ = reinterpret_cast(mpAnim); + bool GetLength(float &timeLength) const override; - timeLength = static_cast(deltaQ->GetNumFrames()); - return true; - } + // Overrides: FnAnim + void Eval(float prevTime, float currTime, float *sqt) override; // Overrides: FnAnim bool EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) override; protected: - void InitBuffers() {} + void InitBuffers(); - void AddDelta(DeltaQFastPhysical *floorPhys, DeltaQFast *deltaQ, int prevDeltaIdx, int floorDeltaIdx, UMath::Vector4 *prevQs) {} + void AddDelta(DeltaQFastPhysical *floorPhys, DeltaQFast *deltaQ, int prevDeltaIdx, int floorDeltaIdx, UMath::Vector4 *prevQs); - void SubDelta(DeltaQFastPhysical *floorPhys, DeltaQFast *deltaQ, int prevDeltaIdx, int floorDeltaIdx, UMath::Vector4 *prevQs) {} + void SubDelta(DeltaQFastPhysical *floorPhys, DeltaQFast *deltaQ, int prevDeltaIdx, int floorDeltaIdx, UMath::Vector4 *prevQs); void UpdateNextQs(DeltaQFast *deltaQ, int ceilKey, int floorBinIdx, int floorDeltaIdx); diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp index e69de29bb..d2bd951af 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.cpp @@ -0,0 +1,578 @@ +#include "FnDeltaSingleQ.h" +#include "AnimTypeId.h" +#include "MemoryPoolManager.h" +#include "Speed/Indep/Src/EAGL4Anim/AnimTypeId.h" +#include "Speed/Indep/Src/EAGL4Anim/AnimUtil.h" + +static void QuatMultXxYxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, const UMath::Vector4 &c, UMath::Vector4 &result) { + UMath::Vector4 q; + + q.x = a.x * b.w; + q.y = a.w * b.y; + q.w = a.w * b.w; + q.z = (-a.x) * b.y; + + result.x = q.x * c.w - q.y * c.z; + result.y = q.x * c.z + q.y * c.w; + result.z = q.z * c.w + q.w * c.z; + result.w = -q.z * c.z + q.w * c.w; +} + +static void QuatMultXxQ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result) { + result.x = a.x * b.w + a.w * b.x; + result.y = a.x * b.z + a.w * b.y; + result.z = (-a.x) * b.y + a.w * b.z; + result.w = (-a.x) * b.x + a.w * b.w; +} + +static void QuatMultQxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result) { + result.x = a.x * b.w - a.y * b.z; + result.y = a.x * b.z + a.y * b.w; + result.z = a.z * b.w + a.w * b.z; + result.w = (-a.z) * b.z + a.w * b.w; +} + +namespace EAGL4Anim { + +namespace { + +static const float kSingleQFloatZero = 0.0f; +static const float kSingleQFloatOne = 1.0f; +static const float kSingleQHalf = 0.5f; +static const float kSingleQPi = 3.1415927f; +static const float kSingleQAngleScale16Bit = 9.5875265e-5f; +static const float kSingleQRangeScale16Bit = 3.0518044e-5f; +static const float kSingleQRangeScale8Bit = 7.8431377e-3f; +static const float kSingleQRangeScale4Bit = 0.13333334f; +static const float kSingleQRangeScale16To4Bit = 8.1381455e-6f; + +static inline int GetSingleQFrameDeltaSize(const DeltaSingleQ *deltaQ) { + return deltaQ->mNumBones * sizeof(DeltaSingleQDelta); +} + +static inline int GetSingleQBinSize(const DeltaSingleQ *deltaQ) { + return static_cast(AlignSize2((deltaQ->mNumBones * sizeof(DeltaSingleQPhysical)) + + ((deltaQ->GetBinLength() - 1) * GetSingleQFrameDeltaSize(deltaQ)))); +} + +static inline DeltaSingleQMinRange *GetSingleQMinRanges(DeltaSingleQ *deltaQ) { + return deltaQ->GetMinRange(); +} + +static inline unsigned char *GetSingleQBinStart(DeltaSingleQ *deltaQ) { + return &reinterpret_cast(GetSingleQMinRanges(deltaQ))[deltaQ->mNumBones * sizeof(DeltaSingleQMinRange)]; +} + +static inline unsigned char *GetSingleQBin(DeltaSingleQ *deltaQ, int binIdx) { + return &GetSingleQBinStart(deltaQ)[binIdx * GetSingleQBinSize(deltaQ)]; +} + +static inline DeltaSingleQPhysical *GetSingleQPhysical(unsigned char *binData) { + return reinterpret_cast(binData); +} + +static inline DeltaSingleQDelta *GetSingleQDelta(DeltaSingleQ *deltaQ, unsigned char *binData, int deltaIdx) { + return reinterpret_cast(&binData[deltaQ->mNumBones * sizeof(DeltaSingleQPhysical) + + (deltaIdx * GetSingleQFrameDeltaSize(deltaQ))]); +} + +static inline void NormalizeSingleQQuat(UMath::Vector4 &q) { + float s = kSingleQFloatOne / FastSqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w); + + q.x *= s; + q.y *= s; + q.z *= s; + q.w *= s; +} + +static inline void SingleQQuatMultXxYxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, const UMath::Vector4 &c, UMath::Vector4 &result) { + float awby = a.w * b.y; + float axbw = a.x * b.w; + float awbw = a.w * b.w; + float naxby = -(a.x * b.y); + + result.x = axbw * c.w - awby * c.z; + result.y = axbw * c.z + awby * c.w; + result.z = naxby * c.w + awbw * c.z; + result.w = -naxby * c.z + awbw * c.w; +} + +static inline void SingleQQuatMultXxQ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result) { + result.x = a.w * b.x + a.x * b.w; + result.y = a.w * b.y + a.x * b.z; + result.z = -(a.x * b.y) + a.w * b.z; + result.w = -(a.x * b.x) + a.w * b.w; +} + +static inline void SingleQQuatMultQxZ(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result) { + result.x = a.x * b.w - a.y * b.z; + result.y = a.x * b.z + a.y * b.w; + result.z = a.z * b.w + a.w * b.z; + result.w = -(a.z * b.z) + a.w * b.w; +} + +static inline void DecodeSingleQMinRange(const DeltaSingleQMinRange &minRange, DeltaSingleQMinRangef &minRangef) { + minRangef.mMin[0] = minRange.mMin[0] * kSingleQRangeScale16Bit - kSingleQFloatOne; + minRangef.mMin[1] = minRange.mMin[1] * kSingleQRangeScale16Bit - kSingleQFloatOne; + minRangef.mRange[0] = minRange.mRange[0] * kSingleQRangeScale16To4Bit; + minRangef.mRange[1] = minRange.mRange[1] * kSingleQRangeScale16To4Bit; + minRangef.mIndex = static_cast(minRange.mIndex); + minRangef.mConst0 = minRange.mConst0 * kSingleQAngleScale16Bit + -kSingleQPi; + minRangef.mConst1 = minRange.mConst1 * kSingleQAngleScale16Bit + -kSingleQPi; +} + +static inline void DecodeSingleQPhysical(const DeltaSingleQPhysical &physical, int index, UMath::Vector4 &q) { + q.z = kSingleQFloatZero; + q.y = kSingleQFloatZero; + q.x = kSingleQFloatZero; + + if (index == 0) { + q.x = physical.mV * kSingleQRangeScale8Bit - kSingleQFloatOne; + } else if (index == 1) { + q.y = physical.mV * kSingleQRangeScale8Bit - kSingleQFloatOne; + } else { + q.z = physical.mV * kSingleQRangeScale8Bit - kSingleQFloatOne; + } + q.w = physical.mW * kSingleQRangeScale8Bit - kSingleQFloatOne; +} + +static inline void DecodeSingleQDelta(const DeltaSingleQMinRange &minRange, const DeltaSingleQDelta &delta, UMath::Vector4 &q) { + DeltaSingleQMinRangef minRangef; + float v = minRangef.mMin[0]; + float w = minRangef.mMin[1]; + unsigned char index; + + DecodeSingleQMinRange(minRange, minRangef); + + v = minRangef.mRange[0] * delta.mV + minRangef.mMin[0]; + w = minRangef.mRange[1] * delta.mW + minRangef.mMin[1]; + index = minRangef.mIndex; + + q.z = kSingleQFloatZero; + q.y = kSingleQFloatZero; + q.x = kSingleQFloatZero; + + if (index == 0) { + q.x = v; + } else if (index == 1) { + q.y = v; + } else { + q.z = v; + } + q.w = w; +} + +static inline void ComposeSingleQQuat(unsigned short index, const UMath::Vector4 &pre, const UMath::Vector4 &mid, + const UMath::Vector4 &post, UMath::Vector4 &result) { + if (index == 0) { + QuatMultXxQ(mid, post, result); + } else if (index == 1) { + QuatMultXxYxZ(pre, mid, post, result); + } else { + QuatMultQxZ(pre, mid, result); + } +} + +static inline float *GetSingleQOutputQuat(float *sqt, unsigned char boneIdx) { + return &sqt[boneIdx * 12 + 4]; +} + +} // namespace + +FnDeltaSingleQ::FnDeltaSingleQ() + : mMinRanges(nullptr), // + mBins(nullptr), // + mBinSize(-1), // + mPrevKey(-1), // + mPrevQBlock(nullptr), // + mPrevQs(nullptr), // + mPreMultQs(nullptr), // + mPostMultQs(nullptr) { + mType = AnimTypeId::ANIM_DELTASINGLEQ; +} + +void FnDeltaSingleQ::Eval(float prevTime, float currTime, float *sqt) { + EvalSQTMasked(currTime, nullptr, sqt); +} + +inline void FnDeltaSingleQ::InitBuffersAsRequired() { + DeltaSingleQ *deltaQ = reinterpret_cast(mpAnim); + DeltaSingleQMinRange *minRanges; + int ibone; + float eul[3]; + DeltaSingleQMinRangef *pMinRangef; + + deltaQ->GetArrays(minRanges, mBins); + mBinSize = deltaQ->GetBinSize(); + mPrevQBlock = MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPrevQs)); + mPrevQs = reinterpret_cast(mPrevQBlock); + mMinRanges = minRanges; + mPreMultQs = reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPreMultQs))); + mPostMultQs = reinterpret_cast(MemoryPoolManager::NewBlock(deltaQ->mNumBones * sizeof(*mPostMultQs))); + + for (ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + DeltaSingleQMinRangef minRangef; + + pMinRangef = &minRangef; + mMinRanges[ibone].UnQuantize(*pMinRangef); + + if (pMinRangef->mIndex == 0) { + mPreMultQs[ibone].x = kSingleQFloatZero; + mPreMultQs[ibone].y = kSingleQFloatZero; + mPreMultQs[ibone].z = kSingleQFloatZero; + mPreMultQs[ibone].w = kSingleQFloatOne; + eul[0] = kSingleQFloatZero; + eul[1] = pMinRangef->mConst0; + eul[2] = pMinRangef->mConst1; + EulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); + } else if (pMinRangef->mIndex == 1) { + eul[0] = pMinRangef->mConst0; + eul[1] = kSingleQFloatZero; + eul[2] = kSingleQFloatZero; + EulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); + + eul[0] = kSingleQFloatZero; + eul[1] = kSingleQFloatZero; + eul[2] = pMinRangef->mConst1; + EulToQuat(eul, reinterpret_cast(&mPostMultQs[ibone])); + } else { + mPostMultQs[ibone].x = kSingleQFloatZero; + mPostMultQs[ibone].y = kSingleQFloatZero; + mPostMultQs[ibone].z = kSingleQFloatZero; + mPostMultQs[ibone].w = kSingleQFloatOne; + eul[0] = pMinRangef->mConst0; + eul[1] = pMinRangef->mConst1; + eul[2] = kSingleQFloatZero; + EulToQuat(eul, reinterpret_cast(&mPreMultQs[ibone])); + } + } +} + +bool FnDeltaSingleQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { + if (boneMask) { + return EvalSQTMasked(currTime, boneMask, sqt); + } + DeltaSingleQ *deltaQ = reinterpret_cast(mpAnim); + + if (!mPrevQs) { + InitBuffersAsRequired(); + } + int floorTime = FloatToInt(currTime); + int floorKey; + + if (!deltaQ->mTimes) { + if (floorTime < 0) { + floorKey = 0; + } else if (floorTime >= deltaQ->mNumKeys) { + floorKey = deltaQ->mNumKeys - 1; + } else { + floorKey = floorTime; + } + } else if (floorTime < deltaQ->mTimes[0]) { + floorKey = 0; + } else { + int timeIndex; + + if (mPrevKey < 1) { + timeIndex = 0; + } else { + timeIndex = mPrevKey - 1; + } + if (deltaQ->mTimes[timeIndex] <= floorTime) { + while (timeIndex < deltaQ->mNumKeys - 2 && deltaQ->mTimes[timeIndex + 1] <= floorTime) { + timeIndex++; + } + } else { + while (timeIndex > 0 && deltaQ->mTimes[timeIndex] > floorTime) { + timeIndex--; + } + } + + floorKey = timeIndex + 1; + } + + unsigned int binLenPower = deltaQ->GetBinLengthPower(); + unsigned int binLenModMask = deltaQ->GetBinLengthModMask(); + + int floorBinIdx = floorKey >> binLenPower; + int floorDeltaIdx = floorKey & binLenModMask; + int prevBinIdx = mPrevKey >> binLenPower; + int prevDeltaIdx; + unsigned char *binData = &mBins[floorBinIdx * mBinSize]; + DeltaSingleQPhysical *floorPhys = GetSingleQPhysical(binData); + unsigned char *boneIdxs = deltaQ->mBoneIdxs; + + if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorKey < mPrevKey) { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + floorPhys[ibone].UnQuantize(mMinRanges[ibone].mIndex, mPrevQs[ibone]); + } + prevDeltaIdx = 0; + } else { + prevDeltaIdx = mPrevKey & binLenModMask; + } + + if (prevDeltaIdx < floorDeltaIdx) { + for (int iframe = prevDeltaIdx; iframe < floorDeltaIdx; iframe++) { + DeltaSingleQDelta *floorDelta = GetSingleQDelta(deltaQ, binData, iframe); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + UMath::Vector4 deltaf; + DeltaSingleQMinRangef minRangef; + + mMinRanges[ibone].UnQuantize(minRangef); + floorDelta[ibone].UnQuantize(minRangef, deltaf); + mPrevQs[ibone].x += deltaf.x; + mPrevQs[ibone].y += deltaf.y; + mPrevQs[ibone].z += deltaf.z; + mPrevQs[ibone].w += deltaf.w; + } + } + } + mPrevKey = floorKey; + + int ceilKey = floorKey + 1; + float scale = 1.0f; + bool slerpReqd; + + if (!deltaQ->mTimes) { + slerpReqd = currTime != floorTime; + if (slerpReqd) { + scale = currTime - floorTime; + } + } else if (floorKey == 0) { + slerpReqd = currTime != 0.0f; + if (slerpReqd) { + float ceilKeyTime = static_cast(deltaQ->mTimes[floorKey]); + scale = currTime / ceilKeyTime; + } + } else { + float floorKeyTime = deltaQ->mTimes[floorKey - 1]; + slerpReqd = currTime != floorKeyTime; + if (slerpReqd) { + float ceilKeyTime = static_cast(deltaQ->mTimes[floorKey]); + scale = (currTime - floorKeyTime) / (ceilKeyTime - floorKeyTime); + } + } + + if (slerpReqd && floorKey < deltaQ->mNumKeys - 1) { + int ceilBinIdx = ceilKey >> binLenPower; + DeltaSingleQPhysical *ceilPhys = GetSingleQPhysical(&mBins[ceilBinIdx * mBinSize]); + + if (ceilBinIdx != floorBinIdx) { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + UMath::Vector4 ceilq; + UMath::Vector4 interpq; + + ceilPhys[ibone].UnQuantize(mMinRanges[ibone].mIndex, ceilq); + interpq.x = scale * (ceilq.x - mPrevQs[ibone].x) + mPrevQs[ibone].x; + interpq.y = scale * (ceilq.y - mPrevQs[ibone].y) + mPrevQs[ibone].y; + interpq.z = scale * (ceilq.z - mPrevQs[ibone].z) + mPrevQs[ibone].z; + interpq.w = scale * (ceilq.w - mPrevQs[ibone].w) + mPrevQs[ibone].w; + NormalizeSingleQQuat(interpq); + + ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], + *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone]))); + } + } else { + DeltaSingleQDelta *ceilDelta = GetSingleQDelta(deltaQ, binData, floorDeltaIdx); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + UMath::Vector4 ceilq; + UMath::Vector4 interpq; + DeltaSingleQMinRangef minRangef; + + mMinRanges[ibone].UnQuantize(minRangef); + ceilDelta[ibone].UnQuantize(minRangef, ceilq); + ceilq.x += mPrevQs[ibone].x; + ceilq.y += mPrevQs[ibone].y; + ceilq.z += mPrevQs[ibone].z; + ceilq.w += mPrevQs[ibone].w; + + interpq.x = scale * (ceilq.x - mPrevQs[ibone].x) + mPrevQs[ibone].x; + interpq.y = scale * (ceilq.y - mPrevQs[ibone].y) + mPrevQs[ibone].y; + interpq.z = scale * (ceilq.z - mPrevQs[ibone].z) + mPrevQs[ibone].z; + interpq.w = scale * (ceilq.w - mPrevQs[ibone].w) + mPrevQs[ibone].w; + NormalizeSingleQQuat(interpq); + + ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], + *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone]))); + } + } + } else { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], mPrevQs[ibone], mPostMultQs[ibone], + *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone]))); + } + } + + return true; +} + +bool FnDeltaSingleQ::EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sqt) { + DeltaSingleQ *deltaQ = reinterpret_cast(mpAnim); + + if (!mPrevQs) { + InitBuffersAsRequired(); + } + int floorTime = FloatToInt(currTime); + int floorKey; + + if (!deltaQ->mTimes) { + if (floorTime < 0) { + floorKey = 0; + } else if (floorTime >= deltaQ->mNumKeys) { + floorKey = deltaQ->mNumKeys - 1; + } else { + floorKey = floorTime; + } + } else if (floorTime < deltaQ->mTimes[0]) { + floorKey = 0; + } else { + int timeIndex; + + if (mPrevKey < 1) { + timeIndex = 0; + } else { + timeIndex = mPrevKey - 1; + } + if (deltaQ->mTimes[timeIndex] <= floorTime) { + while (timeIndex < deltaQ->mNumKeys - 2 && deltaQ->mTimes[timeIndex + 1] <= floorTime) { + timeIndex++; + } + } else { + while (timeIndex > 0 && deltaQ->mTimes[timeIndex] > floorTime) { + timeIndex--; + } + } + + floorKey = timeIndex + 1; + } + + unsigned int binLenPower = deltaQ->GetBinLengthPower(); + unsigned int binLenModMask = deltaQ->GetBinLengthModMask(); + + int floorBinIdx = floorKey >> binLenPower; + int floorDeltaIdx = floorKey & binLenModMask; + int prevBinIdx = mPrevKey >> binLenPower; + int prevDeltaIdx; + unsigned char *binData = &mBins[floorBinIdx * mBinSize]; + DeltaSingleQPhysical *floorPhys = GetSingleQPhysical(binData); + unsigned char *boneIdxs = deltaQ->mBoneIdxs; + + if (mPrevKey == -1 || floorBinIdx != prevBinIdx || floorKey < mPrevKey) { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + floorPhys[ibone].UnQuantize(mMinRanges[ibone].mIndex, mPrevQs[ibone]); + } + } + prevDeltaIdx = 0; + } else { + prevDeltaIdx = mPrevKey & binLenModMask; + } + + if (prevDeltaIdx < floorDeltaIdx) { + for (int iframe = prevDeltaIdx; iframe < floorDeltaIdx; iframe++) { + DeltaSingleQDelta *floorDelta = GetSingleQDelta(deltaQ, binData, iframe); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector4 deltaf; + DeltaSingleQMinRangef minRangef; + + mMinRanges[ibone].UnQuantize(minRangef); + floorDelta[ibone].UnQuantize(minRangef, deltaf); + mPrevQs[ibone].x += deltaf.x; + mPrevQs[ibone].y += deltaf.y; + mPrevQs[ibone].z += deltaf.z; + mPrevQs[ibone].w += deltaf.w; + } + } + } + } + mPrevKey = floorKey; + + int ceilKey = floorKey + 1; + float scale = 1.0f; + bool slerpReqd; + + if (!deltaQ->mTimes) { + slerpReqd = currTime != floorTime; + if (slerpReqd) { + scale = currTime - floorTime; + } + } else if (floorKey == 0) { + slerpReqd = currTime != 0.0f; + if (slerpReqd) { + float ceilKeyTime = static_cast(deltaQ->mTimes[floorKey]); + scale = currTime / ceilKeyTime; + } + } else { + float floorKeyTime = deltaQ->mTimes[floorKey - 1]; + slerpReqd = currTime != floorKeyTime; + if (slerpReqd) { + float ceilKeyTime = static_cast(deltaQ->mTimes[floorKey]); + scale = (currTime - floorKeyTime) / (ceilKeyTime - floorKeyTime); + } + } + + if (slerpReqd && floorKey < deltaQ->mNumKeys - 1) { + int ceilBinIdx = ceilKey >> binLenPower; + DeltaSingleQPhysical *ceilPhys = GetSingleQPhysical(&mBins[ceilBinIdx * mBinSize]); + + if (ceilBinIdx != floorBinIdx) { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector4 ceilq; + UMath::Vector4 interpq; + + ceilPhys[ibone].UnQuantize(mMinRanges[ibone].mIndex, ceilq); + interpq.x = scale * (ceilq.x - mPrevQs[ibone].x) + mPrevQs[ibone].x; + interpq.y = scale * (ceilq.y - mPrevQs[ibone].y) + mPrevQs[ibone].y; + interpq.z = scale * (ceilq.z - mPrevQs[ibone].z) + mPrevQs[ibone].z; + interpq.w = scale * (ceilq.w - mPrevQs[ibone].w) + mPrevQs[ibone].w; + NormalizeSingleQQuat(interpq); + + ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], + *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone]))); + } + } + } else { + DeltaSingleQDelta *ceilDelta = GetSingleQDelta(deltaQ, binData, floorDeltaIdx); + + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector4 ceilq; + UMath::Vector4 interpq; + DeltaSingleQMinRangef minRangef; + + mMinRanges[ibone].UnQuantize(minRangef); + ceilDelta[ibone].UnQuantize(minRangef, ceilq); + ceilq.x += mPrevQs[ibone].x; + ceilq.y += mPrevQs[ibone].y; + ceilq.z += mPrevQs[ibone].z; + ceilq.w += mPrevQs[ibone].w; + + interpq.x = scale * (ceilq.x - mPrevQs[ibone].x) + mPrevQs[ibone].x; + interpq.y = scale * (ceilq.y - mPrevQs[ibone].y) + mPrevQs[ibone].y; + interpq.z = scale * (ceilq.z - mPrevQs[ibone].z) + mPrevQs[ibone].z; + interpq.w = scale * (ceilq.w - mPrevQs[ibone].w) + mPrevQs[ibone].w; + NormalizeSingleQQuat(interpq); + + ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], interpq, mPostMultQs[ibone], + *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone]))); + } + } + } + } else { + for (int ibone = 0; ibone < deltaQ->mNumBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + ComposeSingleQQuat(mMinRanges[ibone].mIndex, mPreMultQs[ibone], mPrevQs[ibone], mPostMultQs[ibone], + *reinterpret_cast(GetSingleQOutputQuat(sqt, boneIdxs[ibone]))); + } + } + } + + return true; +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.h b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.h index a4e3dfa16..a165ed88c 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnDeltaSingleQ.h @@ -40,6 +40,8 @@ class FnDeltaSingleQ : public FnAnimMemoryMap { ~FnDeltaSingleQ() override { if (mPrevQBlock) { MemoryPoolManager::DeleteBlock(mPrevQBlock); + MemoryPoolManager::DeleteBlock(mPreMultQs); + MemoryPoolManager::DeleteBlock(mPostMultQs); } } @@ -56,13 +58,16 @@ class FnDeltaSingleQ : public FnAnimMemoryMap { return true; } + // Overrides: FnAnim + void Eval(float prevTime, float currTime, float *sqt) override; + // Overrides: FnAnim bool EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) override; protected: virtual bool EvalSQTMasked(float currTime, const BoneMask *boneMask, float *sqt); - void InitBuffersAsRequired() {} + void InitBuffersAsRequired(); DeltaSingleQMinRange *mMinRanges; // offset 0x10, size 0x4 unsigned char *mBins; // offset 0x14, size 0x4 diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp index e69de29bb..163c11f4e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.cpp @@ -0,0 +1,31 @@ +#include "FnEventBlender.h" + + +namespace EAGL4Anim { + +void FnEventBlender::Eval(float previousTime, float currentTime, float *data) { + if (!(currentTime > mStartTransTime)) { + mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); + return; + } + + if (!(currentTime < mEndTransTime)) { + mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], data); + return; + } + + switch (mTriggerType) { + case FIRST_ONLY: + mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); + return; + case SECOND_ONLY: + mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], data); + return; + default: + mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], data); + mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], data); + return; + } +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.h b/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.h index 374581e32..4526c5775 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnEventBlender.h @@ -6,6 +6,7 @@ #endif #include "FnAnim.h" +#include "eagl4supportdef.h" namespace EAGL4Anim { @@ -30,7 +31,9 @@ class FnEventBlender : public FnAnim { // void *operator new(size_t size, const char *msg) {} - // void operator delete(void *ptr, size_t size) {} + void operator delete(void *ptr, size_t size) { + EAGL4Internal::EAGL4Free(ptr, size); + } // void *operator new[](size_t size) {} diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp index e69de29bb..535875a49 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.cpp @@ -0,0 +1,146 @@ +#include "FnPoseAnim.h" + +#include "AnimTypeId.h" +#include "AnimUtil.h" +#include "PoseAnim.h" + + +namespace EAGL4Anim { + +FnPoseAnim::FnPoseAnim() { + mType = AnimTypeId::ANIM_POSEANIM; + mPrevKey = 0; +} + +void FnPoseAnim::SetAnimMemoryMap(AnimMemoryMap *anim) { + mpAnim = anim; +} + +bool FnPoseAnim::GetLength(float &timeLength) const { + int zero = 0; + + timeLength = static_cast(zero); + return true; +} + +bool FnPoseAnim::EvalPose(float currTime, const PosePaletteBank *paletteBank, float *sqt) { + PoseAnim *poseAnim = reinterpret_cast(mpAnim); + unsigned short *times = poseAnim->mTimes; + unsigned int key = 0; + bool interp = false; + float t = 0.0f; + int floorTime = FloatToInt(currTime); + const PosePalette *const *palettes = paletteBank->GetPalettes(); + + if (currTime > static_cast(times[0])) { + unsigned int numKeys = poseAnim->mNumKeys; + + if (currTime >= static_cast(times[numKeys - 1])) { + key = numKeys - 1; + } else { + key = mPrevKey; + if (times[key] <= floorTime) { + if (key < numKeys - 1) { + if (times[key + 1] <= floorTime) { + do { + key++; + } while (key < numKeys - 2 && times[key + 1] <= floorTime); + } + } + } else if (key != 0) { + do { + key--; + } while (key > 0 && times[key] > floorTime); + } + + if (numKeys != 0) { + float floorTime = static_cast(times[key]); + + interp = currTime != floorTime; + if (interp) { + t = (currTime - floorTime) / (static_cast(times[key + 1]) - floorTime); + } + } + } + } + + mPrevKey = static_cast(key); + + const PosePalette *palette = palettes[poseAnim->mPaletteIndex]; + float *poseData = palette->GetPoseData(); + unsigned short *dofIndices = palette->GetDofIndices(); + unsigned int numQ = static_cast(palette->GetNumQs()); + unsigned int numT = static_cast(palette->GetNumTs()); + unsigned int poseIdx = poseAnim->mPoseIndices[key]; + + if (interp) { + int stride = (numQ + numT) * 4; + int src0 = (poseIdx + 1) * stride - 4; + int src1 = (poseAnim->mPoseIndices[key + 1] + 1) * stride - 4; + int transIdx = static_cast(numT) - 1; + + t = (3.0f - (t + t)) * t * t; + + while (transIdx >= 0) { + float *from = &poseData[src0]; + float *to = &poseData[src1]; + float *out = &sqt[dofIndices[numQ + transIdx]]; + + out[0] = from[0] + (to[0] - from[0]) * t; + out[1] = from[1] + (to[1] - from[1]) * t; + out[2] = from[2] + (to[2] - from[2]) * t; + + src0 -= 4; + src1 -= 4; + transIdx--; + } + + int quatIdx = static_cast(numQ) - 1; + + while (quatIdx >= 0) { + FastQuatBlendF4(t, &poseData[src0], &poseData[src1], &sqt[dofIndices[quatIdx]]); + src0 -= 4; + src1 -= 4; + quatIdx--; + } + } else { + int stride = (numQ + numT) * 4; + int src = (poseIdx + 1) * stride - 4; + int transIdx = static_cast(numT) - 1; + + while (transIdx >= 0) { + float *from = &poseData[src]; + float *out = &sqt[dofIndices[numQ + transIdx]]; + + out[0] = from[0]; + out[1] = from[1]; + out[2] = from[2]; + + src -= 4; + transIdx--; + } + + int quatIdx = static_cast(numQ) - 1; + + while (quatIdx >= 0) { + float *from = &poseData[src]; + float *out = &sqt[dofIndices[quatIdx]]; + + out[0] = from[0]; + out[1] = from[1]; + out[2] = from[2]; + out[3] = from[3]; + + src -= 4; + quatIdx--; + } + } + + return true; +} + +unsigned short FnPoseAnim::GetTargetCheckSum() const { + return mpAnim->GetTargetCheckSum(); +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.h b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.h index 63c95b2e8..7df7ee9b3 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseAnim.h @@ -7,6 +7,7 @@ #include "FnAnimMemoryMap.h" #include "PosePalette.h" +#include "eagl4supportdef.h" namespace EAGL4Anim { @@ -17,7 +18,9 @@ class FnPoseAnim : public FnAnimMemoryMap { // void *operator new(size_t size, const char *msg) {} - // void operator delete(void *ptr, size_t size) {} + void operator delete(void *ptr, size_t size) { + EAGL4Internal::EAGL4Free(ptr, size); + } // void *operator new[](size_t size) {} @@ -29,9 +32,6 @@ class FnPoseAnim : public FnAnimMemoryMap { return ptr; } - // Overrides: FnAnimSuper - ~FnPoseAnim() override {} - static void PatchVtbl(FnPoseAnim *poseAnim) {} FnPoseAnim(); @@ -46,7 +46,7 @@ class FnPoseAnim : public FnAnimMemoryMap { bool EvalPose(float currTime, const PosePaletteBank *paletteBank, float *sqt) override; // Overrides: FnAnim - unsigned short GetTargetCheckSum() const override {} + unsigned short GetTargetCheckSum() const override; protected: unsigned short mPrevKey; // offset 0x10, size 0x2 diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp index e69de29bb..2566dd1cc 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.cpp @@ -0,0 +1,297 @@ +#include "FnPoseBlender.h" + +#include "AnimUtil.h" + +static inline void LinearBlendF3(float w, const float *d0, const float *d1, float *out) { + out[0] = d0[0] + w * (d1[0] - d0[0]); + out[1] = d0[1] + w * (d1[1] - d0[1]); + out[2] = d0[2] + w * (d1[2] - d0[2]); +} + +namespace EAGL4Anim { + +namespace { + +static inline void ApplyAlignedRootTransform(EAGL4::Transform &tmpTransform, const EAGL4::Transform &alignMatrix, + float *pose, int boneIdx) { + UMath::Vector4 quat; + UMath::Vector4 trans; + float *bonePose = &pose[boneIdx * 12]; + + tmpTransform.BuildSQT(bonePose[0], bonePose[1], bonePose[2], bonePose[4], bonePose[5], bonePose[6], bonePose[7], + bonePose[8], bonePose[9], bonePose[10]); + tmpTransform.PostMult(alignMatrix); + tmpTransform.ExtractQuatTrans(&quat, &trans); + + bonePose[4] = reinterpret_cast(&quat)[0]; + bonePose[5] = reinterpret_cast(&quat)[1]; + bonePose[6] = reinterpret_cast(&quat)[2]; + bonePose[7] = reinterpret_cast(&quat)[3]; + bonePose[8] = reinterpret_cast(&trans)[0]; + bonePose[9] = reinterpret_cast(&trans)[1]; + bonePose[10] = reinterpret_cast(&trans)[2]; +} + +static inline void BlendRootTranslation(float w, const float *pose0, const float *pose1, float *out, int boneIdx) { + int transIdx = boneIdx * 12 + 8; + + out[transIdx + 0] = pose0[transIdx + 0] + w * (pose1[transIdx + 0] - pose0[transIdx + 0]); + out[transIdx + 1] = pose0[transIdx + 1] + w * (pose1[transIdx + 1] - pose0[transIdx + 1]); + out[transIdx + 2] = pose0[transIdx + 2] + w * (pose1[transIdx + 2] - pose0[transIdx + 2]); +} + +}; // namespace + +FnPoseBlender::~FnPoseBlender() {} + +void FnPoseBlender::operator delete(void *ptr, size_t size) { + EAGL4Internal::EAGL4Free(ptr, size); +} + +void FnPoseBlender::Blend(int numBones, float w, const float *pose0, const float *pose1, float *result, + const BoneMask *boneMask) { + int i; + int j; + + if (!boneMask) { + j = 0; + + for (i = 0; i < numBones; ++i) { + j += 4; + FastQuatBlendF4(w, &pose0[j], &pose1[j], &result[j]); + j += 4; + ::LinearBlendF3(w, &pose0[j], &pose1[j], &result[j]); + j += 4; + } + } else { + j = 0; + + for (i = 0; i < numBones; ++i) { + j += 4; + if (boneMask->GetBone(i)) { + FastQuatBlendF4(w, &pose0[j], &pose1[j], &result[j]); + j += 4; + ::LinearBlendF3(w, &pose0[j], &pose1[j], &result[j]); + j += 4; + } else { + j += 8; + } + } + } +} + +bool FnPoseBlender::EvalSQT(float currentTime, float *sqtBuffer, const BoneMask *boneMask) { + if (currentTime <= mStartTransTime) { + return mAnim[0]->EvalSQT(currentTime - mTimeOffset[0], sqtBuffer, boneMask); + } + + if (currentTime >= mEndTransTime) { + bool result = mAnim[1]->EvalSQT(currentTime - mTimeOffset[1], sqtBuffer, boneMask); + + if (result) { + if (mAlignRootBoneIdx >= 0 && (!boneMask || boneMask->GetBone(mAlignRootBoneIdx))) { + float *sqt1 = &sqtBuffer[mAlignRootBoneIdx * 12]; + EAGL4::Transform boneMat; + UMath::Vector4 quat; + UMath::Vector4 trans; + + boneMat.BuildSQT(sqt1[0], sqt1[1], sqt1[2], sqt1[4], sqt1[5], sqt1[6], sqt1[7], sqt1[8], sqt1[9], sqt1[10]); + boneMat.PostMult(mAlignMatrix); + boneMat.ExtractQuatTrans(&quat, &trans); + + sqt1[4] = quat.x; + sqt1[5] = quat.y; + sqt1[6] = quat.z; + sqt1[7] = quat.w; + sqt1[8] = trans.x; + sqt1[9] = trans.y; + sqt1[10] = trans.z; + } + return true; + } + return false; + } + + float w = (currentTime - mStartTransTime) / mDuration; + + if (mResetBuffers[0]) { + mpSkel->GetStillPose(mPose[0], boneMask); + } + + if (mResetBuffers[1]) { + mpSkel->GetStillPose(mPose[1], boneMask); + } + + if (!mAnim[0]->EvalSQT(currentTime - mTimeOffset[0], mPose[0], boneMask)) { + return false; + } + + if (mAnim[1]->EvalSQT(currentTime - mTimeOffset[1], mPose[1], boneMask)) { + if (!boneMask) { + if (mAlignRootBoneIdx >= 0) { + for (int boneIdx = mpSkel->GetNumBones() - 1; boneIdx >= 0; --boneIdx) { + if (boneIdx == mAlignRootBoneIdx) { + float *sqt1 = &mPose[1][mAlignRootBoneIdx * 12]; + EAGL4::Transform boneMat; + UMath::Vector4 quat; + UMath::Vector4 trans; + + boneMat.BuildSQT(sqt1[0], sqt1[1], sqt1[2], sqt1[4], sqt1[5], sqt1[6], sqt1[7], sqt1[8], sqt1[9], + sqt1[10]); + boneMat.PostMult(mAlignMatrix); + boneMat.ExtractQuatTrans(&quat, &trans); + + sqt1[4] = quat.x; + sqt1[5] = quat.y; + sqt1[6] = quat.z; + sqt1[7] = quat.w; + sqt1[8] = trans.x; + sqt1[9] = trans.y; + sqt1[10] = trans.z; + ::LinearBlendF3(w, &mPose[0][mAlignRootBoneIdx * 12 + 8], &mPose[1][mAlignRootBoneIdx * 12 + 8], + &sqtBuffer[mAlignRootBoneIdx * 12 + 8]); + } + + int poseIdx = boneIdx * 12; + + FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &sqtBuffer[poseIdx + 4]); + } + } else { + for (int boneIdx = mpSkel->GetNumBones() - 1; boneIdx >= 0; --boneIdx) { + int poseIdx = boneIdx * 12; + + FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &sqtBuffer[poseIdx + 4]); + } + + BlendRootTranslation(w, mPose[0], mPose[1], sqtBuffer, 0); + } + } else if (mAlignRootBoneIdx >= 0) { + for (int boneIdx = mpSkel->GetNumBones() - 1; boneIdx >= 0; --boneIdx) { + if (boneMask->GetBone(boneIdx)) { + if (boneIdx == mAlignRootBoneIdx) { + float *sqt1 = &mPose[1][mAlignRootBoneIdx * 12]; + EAGL4::Transform boneMat; + UMath::Vector4 quat; + UMath::Vector4 trans; + + boneMat.BuildSQT(sqt1[0], sqt1[1], sqt1[2], sqt1[4], sqt1[5], sqt1[6], sqt1[7], sqt1[8], sqt1[9], + sqt1[10]); + boneMat.PostMult(mAlignMatrix); + boneMat.ExtractQuatTrans(&quat, &trans); + + sqt1[4] = quat.x; + sqt1[5] = quat.y; + sqt1[6] = quat.z; + sqt1[7] = quat.w; + sqt1[8] = trans.x; + sqt1[9] = trans.y; + sqt1[10] = trans.z; + ::LinearBlendF3(w, &mPose[0][mAlignRootBoneIdx * 12 + 8], &mPose[1][mAlignRootBoneIdx * 12 + 8], + &sqtBuffer[mAlignRootBoneIdx * 12 + 8]); + } + + int poseIdx = boneIdx * 12; + + FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &sqtBuffer[poseIdx + 4]); + } + } + } else { + for (int boneIdx = mpSkel->GetNumBones() - 1; boneIdx >= 0; --boneIdx) { + if (boneMask->GetBone(boneIdx)) { + int poseIdx = boneIdx * 12; + + FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &sqtBuffer[poseIdx + 4]); + } + } + + if (boneMask->GetBone(0)) { + BlendRootTranslation(w, mPose[0], mPose[1], sqtBuffer, 0); + } + } + return true; + } + + return false; +} + +void FnPoseBlender::Eval(float previousTime, float currentTime, float *outputPose) { + if (currentTime <= mStartTransTime) { + mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], outputPose); + return; + } + + if (currentTime >= mEndTransTime) { + mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], outputPose); + + if (mAlignRootBoneIdx >= 0) { + float *sqt1 = &outputPose[mAlignRootBoneIdx * 12]; + EAGL4::Transform boneMat; + UMath::Vector4 quat; + UMath::Vector4 trans; + + boneMat.BuildSQT(sqt1[0], sqt1[1], sqt1[2], sqt1[4], sqt1[5], sqt1[6], sqt1[7], sqt1[8], sqt1[9], sqt1[10]); + boneMat.PostMult(mAlignMatrix); + boneMat.ExtractQuatTrans(&quat, &trans); + + sqt1[4] = quat.x; + sqt1[5] = quat.y; + sqt1[6] = quat.z; + sqt1[7] = quat.w; + sqt1[8] = trans.x; + sqt1[9] = trans.y; + sqt1[10] = trans.z; + } + + return; + } + + float w = (currentTime - mStartTransTime) / mDuration; + + if (mResetBuffers[0]) { + mpSkel->GetStillPose(mPose[0], 0); + } + + if (mResetBuffers[1]) { + mpSkel->GetStillPose(mPose[1], 0); + } + + mAnim[0]->Eval(previousTime - mTimeOffset[0], currentTime - mTimeOffset[0], mPose[0]); + mAnim[1]->Eval(previousTime - mTimeOffset[1], currentTime - mTimeOffset[1], mPose[1]); + + if (mAlignRootBoneIdx >= 0) { + for (int boneIdx = mpSkel->GetNumBones() - 1; boneIdx >= 0; --boneIdx) { + if (boneIdx == mAlignRootBoneIdx) { + float *sqt1 = &mPose[1][mAlignRootBoneIdx * 12]; + EAGL4::Transform boneMat; + UMath::Vector4 quat; + UMath::Vector4 trans; + + boneMat.BuildSQT(sqt1[0], sqt1[1], sqt1[2], sqt1[4], sqt1[5], sqt1[6], sqt1[7], sqt1[8], sqt1[9], sqt1[10]); + boneMat.PostMult(mAlignMatrix); + boneMat.ExtractQuatTrans(&quat, &trans); + + sqt1[4] = quat.x; + sqt1[5] = quat.y; + sqt1[6] = quat.z; + sqt1[7] = quat.w; + sqt1[8] = trans.x; + sqt1[9] = trans.y; + sqt1[10] = trans.z; + ::LinearBlendF3(w, &mPose[0][mAlignRootBoneIdx * 12 + 8], &mPose[1][mAlignRootBoneIdx * 12 + 8], + &outputPose[mAlignRootBoneIdx * 12 + 8]); + } + + int poseIdx = boneIdx * 12; + + FastQuatBlendF4(w, &mPose[0][poseIdx + 4], &mPose[1][poseIdx + 4], &outputPose[poseIdx + 4]); + } + } else { + for (int poseIdx = mpSkel->GetNumBones() * 12 - 8; poseIdx > 3; poseIdx -= 12) { + FastQuatBlendF4(w, &mPose[0][poseIdx], &mPose[1][poseIdx], &outputPose[poseIdx]); + } + + BlendRootTranslation(w, mPose[0], mPose[1], outputPose, 0); + } +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.h b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.h index 82406c4f9..76db1da86 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnPoseBlender.h @@ -22,13 +22,13 @@ class FnPoseBlender : public FnAnim { } // Overrides: FnAnimSuper - ~FnPoseBlender() override {} + ~FnPoseBlender() override; // void *operator new(size_t size) {} // void *operator new(size_t size, const char *msg) {} - // void operator delete(void *ptr, size_t size) {} + void operator delete(void *ptr, size_t size); // void *operator new[](size_t size) {} diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRawLinearChannel.h b/src/Speed/Indep/Src/EAGL4Anim/FnRawLinearChannel.h index 08a87ddea..c5c9ec22f 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRawLinearChannel.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRawLinearChannel.h @@ -56,7 +56,9 @@ class FnRawLinearChannel : public FnAnimMemoryMap { } // Overrides: FnAnim - void Eval(float, float currentTime, float *output) override {} + void Eval(float, float currentTime, float *output) override { + GetRawLinearChannel()->Eval(currentTime, output, mInterp); + } // Overrides: FnAnim bool GetLength(float &l) const override { diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRawPoseChannel.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRawPoseChannel.cpp index e69de29bb..da50119d1 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRawPoseChannel.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRawPoseChannel.cpp @@ -0,0 +1,15 @@ +#include "FnRawPoseChannel.h" + + +namespace EAGL4Anim { + +void FnRawPoseChannel::Eval(float prevTime, float currentTime, float *outputPose) { + GetRawPoseChannel()->Eval(currentTime, outputPose, mInterp, nullptr); +} + +bool FnRawPoseChannel::EvalSQT(float currentTime, float *outputPose, const BoneMask *boneMask) { + GetRawPoseChannel()->Eval(currentTime, outputPose, mInterp, boneMask); + return true; +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRawPoseChannel.h b/src/Speed/Indep/Src/EAGL4Anim/FnRawPoseChannel.h index 274d00576..fe9194309 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRawPoseChannel.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRawPoseChannel.h @@ -5,6 +5,7 @@ #pragma once #endif +#include "eagl4supportdef.h" #include "FnAnimMemoryMap.h" #include "RawPoseChannel.h" @@ -24,7 +25,9 @@ class FnRawPoseChannel : public FnAnimMemoryMap { // void *operator new(size_t size, const char *msg) {} - // void operator delete(void *ptr, size_t size) {} + void operator delete(void *ptr, size_t size) { + EAGL4Internal::EAGL4Free(ptr, size); + } // void *operator new[](size_t size) {} @@ -36,20 +39,35 @@ class FnRawPoseChannel : public FnAnimMemoryMap { return ptr; } - RawPoseChannel *GetRawPoseChannel() {} + RawPoseChannel *GetRawPoseChannel() { + return reinterpret_cast(mpAnim); + } - const RawPoseChannel *GetRawPoseChannel() const {} + const RawPoseChannel *GetRawPoseChannel() const { + return reinterpret_cast(mpAnim); + } - void SetInterp(bool state) {} + void SetInterp(bool state) { + mInterp = state; + } - bool IsInterp() const {} + bool IsInterp() const { + return mInterp; + } // Overrides: FnAnim - bool GetLength(float &l) const override {} + bool GetLength(float &l) const override { + l = static_cast(GetRawPoseChannel()->GetNumFrames()); + return true; + } - int GetNumFrames() const {} + int GetNumFrames() const { + return GetRawPoseChannel()->GetNumFrames(); + } - int GetNumBones() const {} + int GetNumBones() const { + return GetRawPoseChannel()->GetNumBones(); + } // Overrides: FnAnim void Eval(float, float currentTime, float *outputPose) override; diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp index e69de29bb..88b4b1b3a 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.cpp @@ -0,0 +1,531 @@ +#include "FnRunBlender.h" + +#include "AnimUtil.h" +#include "FnPoseBlender.h" +#include "MemoryPoolManager.h" +#include "PhaseChan.h" +#include "ScratchBuffer.h" + +#include + +float length(float *v) { + return sqrtf(v[0] * v[0] + v[1] * v[1]); +} + + +namespace EAGL4Anim { + +static const UMath::Vector4 kFacingAxis = {0.0f, 1.0f, 0.0f, 1.0f}; + +static inline void QuatTransformPoint(const UMath::Vector4 &q, const UMath::Vector4 &p, UMath::Vector4 &result) { + float s; + float xs; + float ys; + float zs; + float wx; + float wy; + float wz; + float xx; + float xy; + float xz; + float yy; + float yz; + float zz; + + s = 2.0f; + xs = q.x * s; + ys = q.y * s; + zs = q.z * s; + wx = q.w * xs; + wy = q.w * ys; + wz = q.w * zs; + xx = q.x * xs; + xy = q.x * ys; + xz = q.x * zs; + yy = q.y * ys; + yz = q.y * zs; + zz = q.z * zs; + result.x = p.x * (1.0f - (yy + zz)) + p.y * (xy - wz) + p.z * (xz + wy); + result.y = p.x * (xy + wz) + p.y * (1.0f - (xx + zz)) + p.z * (yz - wx); + result.z = p.x * (xz - wy) + p.y * (yz + wx) + p.z * (1.0f - (xx + yy)); + result.w = 1.0f; +} + +FnRunBlender::FnRunBlender() + : mFreq(1.0f), // + mOffset(0.0f), // + mCycleIdx(-100) { + mFnVelAnims[1] = nullptr; + mType = AnimTypeId::ANIM_RUNBLENDER; + mAnims = nullptr; + mPhases = nullptr; + mVels = nullptr; + mWeight = 0.0f; + mNumAnims = 0; + mIdx = -100; + mPrevTime = 0.0f; + mInit = false; + mSpeed = nullptr; + mFnAnims[0] = nullptr; + mFnAnims[1] = nullptr; + mFnVelAnims[0] = nullptr; +} + +FnRunBlender::~FnRunBlender() { + if (mFnAnims[0]) { + MemoryPoolManager::DeleteFnAnim(mFnAnims[0]); + } + if (mFnAnims[1]) { + MemoryPoolManager::DeleteFnAnim(mFnAnims[1]); + } + if (mFnVelAnims[0]) { + MemoryPoolManager::DeleteFnAnim(mFnVelAnims[0]); + } + if (mFnVelAnims[1]) { + MemoryPoolManager::DeleteFnAnim(mFnVelAnims[1]); + } + if (mNumAnims) { + ScratchBuffer::GetScratchBuffer(0).FreeBuffer(); + } + if (mAnims) { + MemoryPoolManager::DeleteBlock(mAnims); + } + if (mPhases) { + MemoryPoolManager::DeleteBlock(const_cast(mPhases)); + } + if (mVels) { + MemoryPoolManager::DeleteBlock(mVels); + } + if (mSpeed) { + MemoryPoolManager::DeleteBlock(mSpeed); + } +} + +bool FnRunBlender::EvalSQT(float currTime, float *sqtBuffer, const BoneMask *boneMask) { + mPrevTime = currTime; + + if (!mFnAnims[0]) { + SetWeight(0.0f); + } + + currTime += mOffset; + float t0 = mFreq * mCycles[0] * currTime + mAlignFrame[0]; + float t1 = mFreq * mCycles[1] * currTime + mAlignFrame[1]; + int cIdx = ComputeCycleIdx(t0, 0.0f, static_cast(mPhases[mIdx]->mNumFrames - 1)); + t0 = CycleTime(t0, 0.0f, static_cast(mPhases[mIdx]->mNumFrames - 1)); + t1 = CycleTime(t1, 0.0f, static_cast(mPhases[mIdx + 1]->mNumFrames - 1)); + + mSkeleton->GetStillPose(sqtBuffer, 0); + if (!mFnAnims[0]->EvalSQT(t0, sqtBuffer, 0)) { + return false; + } + + if (mWeight != 0.0f) { + float *buffer = reinterpret_cast(ScratchBuffer::GetScratchBuffer(0).GetBuffer()); + + mSkeleton->GetStillPose(buffer, 0); + if (!mFnAnims[1]->EvalSQT(t1, buffer, 0)) { + return false; + } + + FnPoseBlender::Blend(mSkeleton->GetNumBones(), mWeight, sqtBuffer, buffer, sqtBuffer, nullptr); + } + + AlignCycleBeginEnd(cIdx); + AlignRootQ(sqtBuffer); + return true; +} + +void FnRunBlender::Eval(float prevTime, float currTime, float *pose) { + EvalSQT(currTime, pose, nullptr); +} + +void FnRunBlender::SetWeight(float w) { + int i = FloatToInt(w); + float prevFreq; + + if (i < 0) { + i = 0; + } + if (i >= mNumAnims - 1) { + i = mNumAnims - 2; + } + + mWeight = w - static_cast(i); + + if (i == mIdx) { + prevFreq = mFreq; + + mFreq = mWeight / mCycles[1] + (1.0f - mWeight) / mCycles[0]; + mOffset = (prevFreq / mFreq) * (mPrevTime + mOffset) - mPrevTime; + return; + } + + if (i == mIdx + 1) { + MemoryPoolManager::DeleteFnAnim(mFnAnims[0]); + mFnAnims[0] = mFnAnims[1]; + mFnAnims[1] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mAnims[i + 1]))); + } else if (i == mIdx - 1) { + MemoryPoolManager::DeleteFnAnim(mFnAnims[1]); + mFnAnims[1] = mFnAnims[0]; + mFnAnims[0] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mAnims[i]))); + } else { + if (mFnAnims[0]) { + MemoryPoolManager::DeleteFnAnim(mFnAnims[0]); + } + if (mFnAnims[1]) { + MemoryPoolManager::DeleteFnAnim(mFnAnims[1]); + } + + mFnAnims[0] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mAnims[i]))); + mFnAnims[1] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mAnims[i + 1]))); + } + + if (mVels) { + if (i == mIdx + 1) { + MemoryPoolManager::DeleteFnAnim(mFnVelAnims[0]); + mFnVelAnims[0] = mFnVelAnims[1]; + mFnVelAnims[1] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mVels[i + 1]))); + } else if (i == mIdx - 1) { + MemoryPoolManager::DeleteFnAnim(mFnVelAnims[1]); + mFnVelAnims[1] = mFnVelAnims[0]; + mFnVelAnims[0] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mVels[i]))); + } else { + if (mFnVelAnims[0]) { + MemoryPoolManager::DeleteFnAnim(mFnVelAnims[0]); + } + if (mFnVelAnims[1]) { + MemoryPoolManager::DeleteFnAnim(mFnVelAnims[1]); + } + + mFnVelAnims[0] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mVels[i]))); + mFnVelAnims[1] = reinterpret_cast(MemoryPoolManager::NewFnAnim(const_cast(mVels[i + 1]))); + } + } + + if (mPhases[i]->StartWithRight()) { + mAlignFrame[0] = static_cast(mPhases[i]->mStartTime); + } else { + mAlignFrame[0] = static_cast(mPhases[i]->mStartTime + mPhases[i]->mCycles[0]); + } + + mAlignFrame[1] = static_cast(mPhases[i + 1]->mStartTime); + if (!mPhases[i + 1]->StartWithRight()) { + mAlignFrame[1] += static_cast(static_cast(mPhases[i + 1]->mCycles[0])); + } + + mIdx = i; + prevFreq = mFreq; + mCycles[0] = static_cast(mPhases[i]->mCycles[0] + mPhases[i]->mCycles[1]) * 0.5f; + mCycles[1] = static_cast(mPhases[i + 1]->mCycles[0] + mPhases[i + 1]->mCycles[1]) * 0.5f; + mFreq = mWeight / mCycles[1] + (1.0f - mWeight) / mCycles[0]; + mOffset = (prevFreq / mFreq) * (mPrevTime + mOffset) - mPrevTime; +} + +bool FnRunBlender::EvalPhase(float currTime, PhaseValue &phase) { + return false; +} + +bool FnRunBlender::EvalVel2D(float currTime, float *vel) { + mPrevTime = currTime; + if (mVels) { + if (!mFnVelAnims[0]) { + SetWeight(0.0f); + } + + { + float t0; + float t1; + int cIdx; + + currTime += mOffset; + + t0 = mFreq * mCycles[0] * currTime + mAlignFrame[0]; + t1 = mFreq * mCycles[1] * currTime + mAlignFrame[1]; + cIdx = ComputeCycleIdx(t0, 0.0f, static_cast(mPhases[mIdx]->mNumFrames - 1)); + + t0 = CycleTime(t0, 0.0f, static_cast(mPhases[mIdx]->mNumFrames - 1)); + t1 = CycleTime(t1, 0.0f, static_cast(mPhases[mIdx + 1]->mNumFrames - 1)); + + if (!BlendVel(t0, t1, vel)) { + return false; + } + + AlignCycleBeginEnd(cIdx); + AlignVel(vel); + return true; + } + } + + return false; +} + +bool FnRunBlender::BlendVel(float t0, float t1, float *vel) const { + float v0[2]; + float v1[2]; + + if (!mFnVelAnims[0]->EvalVel2D(t0, v0)) { + return false; + } + if (mWeight != 0.0f) { + if (!mFnVelAnims[1]->EvalVel2D(t1, v1)) { + return false; + } + + float len; + float w; + + w = 1.0f - mWeight; + + vel[0] = mWeight * v1[0] + w * v0[0]; + vel[1] = mWeight * v1[1] + w * v0[1]; + + len = length(vel); + + if (len != 0.0f) { + len = (mWeight * length(v1) + w * length(v0)) / len; + vel[0] = vel[0] * len; + vel[1] = vel[1] * len; + } + } else { + vel[1] = v0[1]; + vel[0] = v0[0]; + } + + return true; +} + +bool FnRunBlender::BlendFacing(float t0, float t1, float *f) const { + float *buffer = reinterpret_cast(ScratchBuffer::GetScratchBuffer(0).GetBuffer()); + UMath::Vector4 q0; + UMath::Vector4 q1; + UMath::Vector4 q; + UMath::Vector4 xAxis; + UMath::Vector4 xAxis1; + + if (!mFnAnims[0]->EvalSQT(t0, buffer, 0)) { + return false; + } + + q0 = *reinterpret_cast(&buffer[4]); + + if (mWeight != 0.0f) { + mSkeleton->GetStillPose(buffer, 0); + if (!mFnAnims[1]->EvalSQT(t1, buffer, 0)) { + return false; + } + + q1 = *reinterpret_cast(&buffer[4]); + FastQuatBlendF4(mWeight, reinterpret_cast(&q0), reinterpret_cast(&q1), reinterpret_cast(&q)); + } else { + q = q0; + } + + xAxis = kFacingAxis; + QuatTransformPoint(q, xAxis, xAxis1); + f[1] = xAxis1.z; + f[0] = xAxis1.x; + return true; +} + +float FnRunBlender::GetFrequency() const { + return mFreq; +} + +void FnRunBlender::ComputeBeginRootQ(UMath::Vector4 &q) const { + ComputeRootQ(0.0f, 0.0f, q); +} + +void FnRunBlender::ComputeEndRootQ(UMath::Vector4 &q) const { + float t0 = static_cast(mPhases[mIdx]->mNumFrames - 1); + float t1 = static_cast(mPhases[mIdx + 1]->mNumFrames - 1); + + ComputeRootQ(t0, t1, q); +} + +void FnRunBlender::ComputeRootQ(float t0, float t1, UMath::Vector4 &q) const { + float *buffer = reinterpret_cast(ScratchBuffer::GetScratchBuffer(0).GetBuffer()); + UMath::Vector4 q0; + UMath::Vector4 q1; + + if (!mFnAnims[0]->EvalSQT(t0, buffer, 0)) { + return; + } + + q0 = *reinterpret_cast(&buffer[4]); + + if (mWeight != 0.0f) { + mSkeleton->GetStillPose(buffer, 0); + if (!mFnAnims[1]->EvalSQT(t1, buffer, 0)) { + return; + } + + q1 = *reinterpret_cast(&buffer[4]); + FastQuatBlendF4(mWeight, reinterpret_cast(&q0), reinterpret_cast(&q1), reinterpret_cast(&q)); + } else { + q = q0; + } +} + +float FnRunBlender::CycleTime(float t, float startTime, float endTime) const { + float tmp; + int n; + float len = endTime - startTime; + + if (t < startTime) { + tmp = startTime - t; + n = FloatToInt(tmp / len); + return endTime - (tmp - static_cast(n) * len); + } + if (t < endTime) { + return t; + } + tmp = t - endTime; + n = FloatToInt(tmp / len); + return startTime + (tmp - static_cast(n) * len); +} + +int FnRunBlender::ComputeCycleIdx(float t, float startTime, float endTime) const { + float length = endTime - startTime; + + if (t < startTime) { + t = startTime - t; + return static_cast(t / length); + } + if (t >= endTime) { + t -= endTime; + return static_cast(t / length) + 1; + } + return 0; +} + +void FnRunBlender::ComputeAlignQ(float *v1, float *v2, UMath::Vector4 &q) const { + float dot = v1[0] * v2[0] + v1[1] * v2[1]; + float v1Length = sqrtf(v1[0] * v1[0] + v1[1] * v1[1]); + float v2Length = sqrtf(v2[0] * v2[0] + v2[1] * v2[1]); + float norm = v1Length * v2Length; + float w = ((dot / norm) + 1.0f) * 0.5f; + + q.x = 0.0f; + q.y = sqrtf(1.0f - w); + q.z = 0.0f; + q.w = sqrtf(w); + if (0.0f < v1[0] * v2[1] - v1[1] * v2[0]) { + q.y = -q.y; + } +} + +void FnRunBlender::AlignCycleBeginEnd(int cIdx) { + float v0[2]; + float v1[2]; + + if (!mInit) { + mCycleIdx = -1; + mAlignQ.x = 0.0f; + mAlignQ.y = 0.0f; + mAlignQ.z = 0.0f; + mAlignQ.w = 1.0f; + mInit = true; + } else if (mCycleIdx != cIdx) { + UMath::Vector4 q; + UMath::Vector4 resultQ; + + BlendFacing(0.0f, 0.0f, v0); + BlendFacing(static_cast(const_cast(mPhases[mIdx])->mNumFrames - 1), + static_cast(const_cast(mPhases[mIdx + 1])->mNumFrames - 1), v1); + ComputeAlignQ(v0, v1, q); + if (mCycleIdx - 1 == cIdx) { + q.y = -q.y; + } + + QuatMult(mAlignQ, q, resultQ); + mAlignQ = resultQ; + mCycleIdx = cIdx; + } +} + +void FnRunBlender::AlignRootQ(float *sqt) const { + UMath::Vector4 result; + QuatMult(*reinterpret_cast(&sqt[4]), mAlignQ, result); + *reinterpret_cast(&sqt[4]) = result; +} + +void FnRunBlender::AlignVel(float *vel) const { + UMath::Vector4 org; + UMath::Vector4 result; + + org.x = vel[0]; + org.y = 0.0f; + org.z = vel[1]; + org.w = 1.0f; + QuatTransformPoint(mAlignQ, org, result); + vel[0] = result.x; + vel[1] = result.z; +} + +bool FnRunBlender::FindMatchTime(const MatchPhaseInput &input, float &time) const { + PhaseValue phase; + float a; + float da; + float na; + int n; + int s; + int i; + int minIdx; + float diffAngle; + float minAngle; + float angle = input.mAngle; + float dAngle = input.mDAngle; + FnRunBlender *nonConstThis = const_cast(this); + + n = FloatToInt((mWeight * (mCycles[1] - mCycles[0]) + mCycles[0]) * 2.0f); + s = 1; + + phase.mAngle = 0.0f; + if (0.0f < input.mSearchLength && input.mSearchLength < static_cast(n)) { + n = FloatToInt(input.mSearchLength) + s; + } + + nonConstThis->EvalPhase(0.0f, phase); + + a = phase.mAngle; + minAngle = angle - a; + if (minAngle < 0.0f) { + minAngle = -minAngle; + } + + minIdx = 0; + for (i = s; i < n; i++) { + nonConstThis->EvalPhase(static_cast(i), phase); + na = phase.mAngle; + + if (a <= angle && angle <= na) { + da = na - a; + + if (0.0f <= da * dAngle) { + if (da != 0.0f) { + time = ((angle - a) / da + static_cast(i - s)) * static_cast(s); + } else { + time = (static_cast(i - s) + 0.5f) * static_cast(s); + } + return true; + } + } + + diffAngle = angle - na; + if (diffAngle < 0.0f) { + diffAngle = -diffAngle; + } + if (diffAngle < minAngle) { + minIdx = i; + minAngle = diffAngle; + } + + a = na; + } + + time = static_cast(minIdx); + return true; +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.h b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.h index 52745e831..d05e7a018 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnRunBlender.h @@ -8,6 +8,7 @@ #include "AnimMemoryMap.h" #include "FnAnim.h" #include "Skeleton.h" +#include "eagl4supportdef.h" namespace EAGL4Anim { @@ -18,7 +19,9 @@ class FnRunBlender : public FnAnim { // void *operator new(size_t size, const char *msg) {} - // void operator delete(void *ptr, size_t size) {} + void operator delete(void *ptr, size_t size) { + EAGL4Internal::EAGL4Free(ptr, size); + } // void *operator new[](size_t size) {} @@ -30,11 +33,20 @@ class FnRunBlender : public FnAnim { return ptr; } - inline float GetOffset() const {} + inline float GetOffset() const { + return mOffset; + } - inline void GetAnims(int &numAnims, const AnimMemoryMap **&anims, const PhaseChan **&phases, const AnimMemoryMap **&vels) const {} + inline void GetAnims(int &numAnims, const AnimMemoryMap **&anims, const PhaseChan **&phases, const AnimMemoryMap **&vels) const { + numAnims = mNumAnims; + anims = mAnims; + phases = mPhases; + vels = mVels; + } - inline float GetWeight() const {} + inline float GetWeight() const { + return mWeight; + } FnRunBlender(); @@ -52,7 +64,7 @@ class FnRunBlender : public FnAnim { void SetAnims(Skeleton *s, int numAnims, const AnimMemoryMap **anims, const AnimMemoryMap **phases, const AnimMemoryMap **vels); // Overrides: FnAnim - bool EvalPhase(float) override; + bool EvalPhase(float currTime, PhaseValue &phase) override; // Overrides: FnAnim bool EvalVel2D(float currTime, float *vel) override; diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp index e69de29bb..904435e35 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.cpp @@ -0,0 +1,329 @@ +#include "FnStatelessF3.h" +#include "AnimTypeId.h" +#include "StatelessF3.h" + +namespace EAGL4Anim { + +namespace { + +static inline unsigned char GetStatelessF3BoneIndex(unsigned short dofIdx) { + return static_cast(dofIdx / 12); +} + +static inline void UnquantizeStatelessF3(const StatelessF3::DofInfo &dofInfo, const short *frameBuf, UMath::Vector3 &result) { + result.x = dofInfo.mRange[0] * frameBuf[0]; + result.y = dofInfo.mRange[1] * frameBuf[1]; + result.z = dofInfo.mRange[2] * frameBuf[2]; +} + +} // namespace + +FnStatelessF3::FnStatelessF3() { + mType = AnimTypeId::ANIM_STATELESSF3; +} + +FnStatelessF3::~FnStatelessF3() {} + +void FnStatelessF3::SetAnimMemoryMap(AnimMemoryMap *anim) { + mpAnim = anim; +} + +bool FnStatelessF3::GetLength(float &timeLength) const { + StatelessF3 *statelessF3 = reinterpret_cast(mpAnim); + + timeLength = static_cast(statelessF3->GetNumFrames()); + return true; +} + +void FnStatelessF3::Eval(float, float currTime, float *sqt) { + EvalSQT(currTime, sqt, nullptr); +} + +bool FnStatelessF3::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { + if (mUseFPS) { + currTime *= mFPS; + } + + StatelessF3 *statelessF3 = reinterpret_cast(mpAnim); + + int floorTime = FloatToInt(currTime); + int floorKey; + bool slerpReqd; + float scale = 0.0f; + + if (!statelessF3->mTimes) { + if (floorTime < 0) { + floorKey = 0; + } else if (floorTime >= statelessF3->mNumKeys) { + floorKey = statelessF3->mNumKeys - 1; + } else { + floorKey = floorTime; + } + + slerpReqd = currTime != floorTime; + scale = currTime - floorTime; + if (floorKey >= statelessF3->mNumKeys - 1) { + slerpReqd = false; + } + } else { + if (floorTime < statelessF3->mTimes[0]) { + floorKey = 0; + } else { + int timeIndex = 0; + + if (mPrevKey != 0) { + timeIndex = mPrevKey - 1; + } + if (statelessF3->mTimes[timeIndex] <= floorTime) { + if (timeIndex < statelessF3->mNumKeys - 2) { + while (statelessF3->mTimes[timeIndex + 1] <= floorTime) { + timeIndex++; + if (timeIndex >= statelessF3->mNumKeys - 2) { + break; + } + } + } + } else if (timeIndex > 0) { + do { + timeIndex--; + if (timeIndex < 1) { + break; + } + } while (statelessF3->mTimes[timeIndex] > floorTime); + } + + floorKey = timeIndex + 1; + } + if (floorKey == 0) { + slerpReqd = currTime != 0.0f; + if (slerpReqd) { + scale = currTime / static_cast(statelessF3->mTimes[0]); + } + } else { + float floorKeyTime = static_cast(statelessF3->mTimes[floorKey - 1]); + + slerpReqd = currTime != floorKeyTime; + if (slerpReqd) { + float ceilKeyTime = static_cast(statelessF3->mTimes[floorKey]); + scale = (currTime - floorKeyTime) / (ceilKeyTime - floorKeyTime); + } + } + if (floorKey >= statelessF3->mNumKeys - 1) { + slerpReqd = false; + } + } + + mPrevKey = static_cast(floorKey); + + if (!boneMask) { + if (mBoneMask) { + mBoneMask = nullptr; + } + + short *dataBuf = statelessF3->GetData(); + StatelessF3::DofInfo *dofInfos = statelessF3->GetDofInfo(); + unsigned short *dofIdxs = statelessF3->mDofIdxs; + short *frameData = statelessF3->GetFrameData(dataBuf, floorKey); + int nBones = statelessF3->mNumBones; + + if (slerpReqd) { + short *nextFrameData = statelessF3->GetFrameData(dataBuf, floorKey + 1); + + for (int ibone = 0; ibone < nBones; ibone++) { + UMath::Vector3 prev; + UMath::Vector3 next; + + UnquantizeStatelessF3(dofInfos[ibone], frameData, prev); + UnquantizeStatelessF3(dofInfos[ibone], nextFrameData, next); + + unsigned short index = dofIdxs[ibone]; + + sqt[index + 0] = scale * (next.x - prev.x) + prev.x; + sqt[index + 1] = scale * (next.y - prev.y) + prev.y; + sqt[index + 2] = scale * (next.z - prev.z) + prev.z; + + frameData += 3; + nextFrameData += 3; + } + } else { + for (int ibone = 0; ibone < nBones; ibone++) { + unsigned short index = dofIdxs[ibone]; + + sqt[index + 0] = dofInfos[ibone].mRange[0] * frameData[0]; + sqt[index + 1] = dofInfos[ibone].mRange[1] * frameData[1]; + sqt[index + 2] = dofInfos[ibone].mRange[2] * frameData[2]; + frameData += 3; + } + } + + if (statelessF3->mNumConstBones != 0) { + unsigned short *constIdxs = statelessF3->GetConstBoneIdx(); + float *constBuf = statelessF3->GetConstData(dataBuf); + + for (int ibone = 0; ibone < statelessF3->mNumConstBones; ibone++) { + float *constBone = &constBuf[ibone * 3]; + unsigned short index = constIdxs[ibone]; + + sqt[index + 0] = constBone[0]; + sqt[index + 1] = constBone[1]; + sqt[index + 2] = constBone[2]; + } + } + } else { + return EvalSQTMask(currTime, sqt, boneMask, slerpReqd, floorKey, scale); + } + + return true; +} + +bool FnStatelessF3::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool slerpReqd, int floorKey, float scale) { + if (boneMask != mBoneMask) { + mBoneMask = boneMask; + } + + StatelessF3 *statelessF3 = reinterpret_cast(mpAnim); + short *dataBuf = statelessF3->GetData(); + StatelessF3::DofInfo *dofInfos = statelessF3->GetDofInfo(); + unsigned short *dofIdxs = statelessF3->mDofIdxs; + short *frameData = statelessF3->GetFrameData(dataBuf, floorKey); + int nBones = statelessF3->mNumBones; + unsigned char boneIdxs[120]; + + for (int ibone = 0; ibone < nBones; ibone++) { + boneIdxs[ibone] = GetStatelessF3BoneIndex(dofIdxs[ibone]); + } + + if (slerpReqd) { + short *nextFrameData = statelessF3->GetFrameData(dataBuf, floorKey + 1); + + for (int ibone = 0; ibone < nBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector3 prev; + UMath::Vector3 next; + + UnquantizeStatelessF3(dofInfos[ibone], frameData, prev); + UnquantizeStatelessF3(dofInfos[ibone], nextFrameData, next); + + unsigned short index = dofIdxs[ibone]; + + sqt[index + 0] = scale * (next.x - prev.x) + prev.x; + sqt[index + 1] = scale * (next.y - prev.y) + prev.y; + sqt[index + 2] = scale * (next.z - prev.z) + prev.z; + } + frameData += 3; + nextFrameData += 3; + } + } else { + for (int ibone = 0; ibone < nBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector3 value; + unsigned short index = dofIdxs[ibone]; + + UnquantizeStatelessF3(dofInfos[ibone], frameData, value); + sqt[index + 0] = value.x; + sqt[index + 1] = value.y; + sqt[index + 2] = value.z; + } + frameData += 3; + } + } + + if (statelessF3->mNumConstBones != 0) { + unsigned short *constIdxs = statelessF3->GetConstBoneIdx(); + float *constBuf = statelessF3->GetConstData(dataBuf); + + for (int ibone = 0; ibone < statelessF3->mNumConstBones; ibone++) { + unsigned char boneIdx = GetStatelessF3BoneIndex(constIdxs[ibone]); + + if (boneMask->GetBone(boneIdx)) { + unsigned short index = constIdxs[ibone]; + + sqt[index + 0] = constBuf[0]; + sqt[index + 1] = constBuf[1]; + sqt[index + 2] = constBuf[2]; + } + constBuf += 3; + } + } + + return true; +} + +bool FnStatelessF3::EvalSQTfast(float currTime, float *sqt, const BoneMask *boneMask, bool slerpReqd, int floorKey, float scale) { + mPrevKey = static_cast(floorKey); + + if (!boneMask) { + StatelessF3 *statelessF3 = reinterpret_cast(mpAnim); + short *dataBuf = statelessF3->GetData(); + StatelessF3::DofInfo *dofInfos = statelessF3->GetDofInfo(); + unsigned short *dofIdxs = statelessF3->mDofIdxs; + short *frameData = statelessF3->GetFrameData(dataBuf, floorKey); + int nBones = statelessF3->mNumBones; + + if (slerpReqd) { + short *nextFrameData = statelessF3->GetFrameData(dataBuf, floorKey + 1); + + for (int ibone = 0; ibone < nBones; ibone++) { + UMath::Vector3 prev; + UMath::Vector3 next; + + UnquantizeStatelessF3(dofInfos[ibone], frameData, prev); + UnquantizeStatelessF3(dofInfos[ibone], nextFrameData, next); + + unsigned short index = dofIdxs[ibone]; + + sqt[index + 0] = scale * (next.x - prev.x) + prev.x; + sqt[index + 1] = scale * (next.y - prev.y) + prev.y; + sqt[index + 2] = scale * (next.z - prev.z) + prev.z; + + frameData += 3; + nextFrameData += 3; + } + } else { + for (int ibone = 0; ibone < nBones; ibone++) { + unsigned short index = dofIdxs[ibone]; + + sqt[index + 0] = dofInfos[ibone].mRange[0] * frameData[0]; + sqt[index + 1] = dofInfos[ibone].mRange[1] * frameData[1]; + sqt[index + 2] = dofInfos[ibone].mRange[2] * frameData[2]; + frameData += 3; + } + } + + if (statelessF3->mNumConstBones != 0) { + unsigned short *constIdxs = statelessF3->GetConstBoneIdx(); + float *constBuf = statelessF3->GetConstData(dataBuf); + + for (int ibone = 0; ibone < statelessF3->mNumConstBones; ibone++) { + float *constBone = &constBuf[ibone * 3]; + unsigned short index = constIdxs[ibone]; + + sqt[index + 0] = constBone[0]; + sqt[index + 1] = constBone[1]; + sqt[index + 2] = constBone[2]; + } + } + } else { + return EvalSQTMask(currTime, sqt, boneMask, slerpReqd, floorKey, scale); + } + + return true; +} + +void FnStatelessF3::UseFPS(bool u) { + mUseFPS = u; + if (mFPS != 0 || !mUseFPS) { + return; + } + GetAttribute(AttributeId(AttributeId::ID_FPS), mFPS); +} + +unsigned short FnStatelessF3::GetTargetCheckSum() const { + return mpAnim->GetTargetCheckSum(); +} + +const AttributeBlock *FnStatelessF3::GetAttributes() const { + return reinterpret_cast(mpAnim)->GetAttributeBlock(); +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.h b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.h index 98b55525d..e6187b4b7 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessF3.h @@ -7,6 +7,7 @@ #include "BoneMask.h" #include "FnAnimMemoryMap.h" +#include "eagl4supportdef.h" namespace EAGL4Anim { @@ -17,7 +18,9 @@ class FnStatelessF3 : public FnAnimMemoryMap { // void *operator new(size_t size, const char *msg) {} - // void operator delete(void *ptr, size_t size) {} + void operator delete(void *ptr, size_t size) { + EAGL4Internal::EAGL4Free(ptr, size); + } // void *operator new[](size_t size) {} @@ -54,10 +57,10 @@ class FnStatelessF3 : public FnAnimMemoryMap { unsigned char GetFPS() const; // Overrides: FnAnim - unsigned short GetTargetCheckSum() const override {} + unsigned short GetTargetCheckSum() const override; // Overrides: FnAnim - const AttributeBlock *GetAttributes() const override {} + const AttributeBlock *GetAttributes() const override; protected: bool EvalSQTMask(float currTime, float *sqt, const BoneMask *boneMask, bool slerpReqd, int floorKey, float scale); diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp index e69de29bb..c35e87aa5 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.cpp @@ -0,0 +1,293 @@ +#include "FnStatelessQ.h" +#include "AnimTypeId.h" +#include "FnStatelessF3.h" +#include "StatelessQ.h" + +namespace EAGL4Anim { + +namespace { + +static inline float UncompressStatelessQValue(unsigned short value) { + union { + unsigned int u; + float f; + } bits; + + bits.u = ((value & 0x8000) << 16) | ((value & 0x7FFF) << 15); + return bits.f; +} + +#define STORE_STATELESS_Q_BITS(dst, value) *reinterpret_cast(&(dst)) = (((value) & 0x8000) << 16) | (((value) & 0x7FFF) << 15) + +static inline float *GetStatelessQOutput(float *sqt, unsigned char boneIdx) { + return &sqt[boneIdx * 12 + 4]; +} + +static inline void LoadStatelessQ(unsigned short *frameData, UMath::Vector4 &q) { + q.x = UncompressStatelessQValue(frameData[0]); + q.y = UncompressStatelessQValue(frameData[1]); + q.z = UncompressStatelessQValue(frameData[2]); + q.w = UncompressStatelessQValue(frameData[3]); +} + +} // namespace + +FnStatelessQ::FnStatelessQ() { + mType = AnimTypeId::ANIM_STATELESSQ; + mPrevKey = 0; +} + +FnStatelessQ::~FnStatelessQ() {} + +void FnStatelessQ::SetAnimMemoryMap(AnimMemoryMap *anim) { + mpAnim = anim; +} + +bool FnStatelessQ::GetLength(float &timeLength) const { + StatelessQ *statelessQ = reinterpret_cast(mpAnim); + + timeLength = static_cast(statelessQ->GetNumFrames()); + return true; +} + +void FnStatelessQ::Eval(float, float currTime, float *sqt) { + EvalSQT(currTime, sqt, nullptr); +} + +bool FnStatelessQ::EvalSQT(float currTime, float *sqt, const BoneMask *boneMask) { + if (mUseFPS) { + currTime *= mFPS; + } + + StatelessQ *statelessQ = reinterpret_cast(mpAnim); + + int floorTime = FloatToInt(currTime); + int floorKey; + bool slerpReqd; + float scale = 0.0f; + + if (!statelessQ->mTimes) { + if (floorTime < 0) { + floorKey = 0; + } else if (floorTime >= statelessQ->mNumKeys) { + floorKey = statelessQ->mNumKeys - 1; + } else { + floorKey = floorTime; + } + + slerpReqd = currTime != floorTime; + scale = currTime - floorTime; + if (floorKey >= statelessQ->mNumKeys - 1) { + slerpReqd = false; + } + } else { + if (floorTime < statelessQ->mTimes[0]) { + floorKey = 0; + } else { + int timeIndex = mPrevKey != 0 ? mPrevKey - 1 : 0; + + if (statelessQ->mTimes[timeIndex] <= floorTime) { + while (timeIndex < statelessQ->mNumKeys - 2 && statelessQ->mTimes[timeIndex + 1] <= floorTime) { + timeIndex++; + } + } else { + while (timeIndex > 0 && statelessQ->mTimes[timeIndex] > floorTime) { + timeIndex--; + } + } + + floorKey = timeIndex + 1; + } + if (floorKey == 0) { + slerpReqd = currTime != 0.0f; + if (slerpReqd) { + scale = currTime / static_cast(statelessQ->mTimes[0]); + } + } else { + float floorKeyTime = static_cast(statelessQ->mTimes[floorKey - 1]); + + slerpReqd = currTime != floorKeyTime; + if (slerpReqd) { + float ceilKeyTime = static_cast(statelessQ->mTimes[floorKey]); + scale = (currTime - floorKeyTime) / (ceilKeyTime - floorKeyTime); + } + } + if (floorKey >= statelessQ->mNumKeys - 1) { + slerpReqd = false; + } + } + + mPrevKey = static_cast(floorKey); + + if (!boneMask) { + if (mBoneMask) { + mBoneMask = nullptr; + } + + float *q = sqt + 4; + unsigned short *dataBuf = statelessQ->GetData(); + unsigned short *frameData = statelessQ->GetFrameData(dataBuf, floorKey); + unsigned char *boneIdxs = statelessQ->mBoneIdxs; + int nBones = statelessQ->mNumBones; + int index; + + if (slerpReqd && floorKey < statelessQ->mNumKeys - 1) { + int nextKey = floorKey + 1; + unsigned short *nextFrameData = statelessQ->GetFrameData(dataBuf, nextKey); + + for (int ibone = 0; ibone < nBones; ibone++) { + UMath::Vector4 prevQ; + UMath::Vector4 nextQ; + + STORE_STATELESS_Q_BITS(prevQ.x, frameData[0]); + STORE_STATELESS_Q_BITS(prevQ.y, frameData[1]); + STORE_STATELESS_Q_BITS(prevQ.z, frameData[2]); + STORE_STATELESS_Q_BITS(prevQ.w, frameData[3]); + frameData += 4; + STORE_STATELESS_Q_BITS(nextQ.x, nextFrameData[0]); + STORE_STATELESS_Q_BITS(nextQ.y, nextFrameData[1]); + STORE_STATELESS_Q_BITS(nextQ.z, nextFrameData[2]); + STORE_STATELESS_Q_BITS(nextQ.w, nextFrameData[3]); + nextFrameData += 4; + index = boneIdxs[ibone] * 12; + + q[index + 0] = scale * (nextQ.x - prevQ.x) + prevQ.x; + q[index + 1] = scale * (nextQ.y - prevQ.y) + prevQ.y; + q[index + 2] = scale * (nextQ.z - prevQ.z) + prevQ.z; + q[index + 3] = scale * (nextQ.w - prevQ.w) + prevQ.w; + } + } else { + for (int ibone = 0; ibone < nBones; ibone++) { + index = boneIdxs[ibone] * 12; + + STORE_STATELESS_Q_BITS(q[index + 0], frameData[0]); + STORE_STATELESS_Q_BITS(q[index + 1], frameData[1]); + STORE_STATELESS_Q_BITS(q[index + 2], frameData[2]); + STORE_STATELESS_Q_BITS(q[index + 3], frameData[3]); + frameData += 4; + } + } + + if (statelessQ->mNumConstBones != 0) { + int numConsts = statelessQ->mNumConstBones; + unsigned char *constIdxs = statelessQ->GetConstBoneIdx(); + unsigned short *constBuf = statelessQ->GetConstData(dataBuf); + + for (int ibone = 0; ibone < numConsts; ibone++) { + index = *constIdxs++ * 12; + + STORE_STATELESS_Q_BITS(q[index + 0], constBuf[0]); + STORE_STATELESS_Q_BITS(q[index + 1], constBuf[1]); + STORE_STATELESS_Q_BITS(q[index + 2], constBuf[2]); + STORE_STATELESS_Q_BITS(q[index + 3], constBuf[3]); + constBuf += 4; + } + } + } else { + EvalSQTMask(currTime, sqt, boneMask, slerpReqd, floorKey, scale); + } + + if (statelessQ->mF3Ptr) { + reinterpret_cast(statelessQ->mF3Ptr)->EvalSQTfast(currTime, sqt, boneMask, slerpReqd, floorKey, scale); + } + + return true; +} + +bool FnStatelessQ::EvalSQTMask(float, float *sqt, const BoneMask *boneMask, bool slerpReqd, int floorKey, float scale) { + if (boneMask != mBoneMask) { + mBoneMask = boneMask; + } + + StatelessQ *statelessQ = reinterpret_cast(mpAnim); + float *q = sqt + 4; + unsigned short *dataBuf = statelessQ->GetData(); + unsigned short *frameData = statelessQ->GetFrameData(dataBuf, floorKey); + unsigned short *prevData; + unsigned short *nextData; + unsigned char *boneIdxs = statelessQ->mBoneIdxs; + int nBones = statelessQ->mNumBones; + int index; + int numConsts; + + if (slerpReqd && floorKey < statelessQ->mNumKeys - 1) { + int nextKey = floorKey + 1; + unsigned short *nextFrameData = statelessQ->GetFrameData(dataBuf, nextKey); + + for (int ibone = 0; ibone < nBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + UMath::Vector4 prevQ; + UMath::Vector4 nextQ; + prevData = &frameData[ibone * 4]; + nextData = &nextFrameData[ibone * 4]; + + STORE_STATELESS_Q_BITS(prevQ.x, prevData[0]); + STORE_STATELESS_Q_BITS(prevQ.y, prevData[1]); + STORE_STATELESS_Q_BITS(prevQ.z, prevData[2]); + STORE_STATELESS_Q_BITS(prevQ.w, prevData[3]); + STORE_STATELESS_Q_BITS(nextQ.x, nextData[0]); + STORE_STATELESS_Q_BITS(nextQ.y, nextData[1]); + STORE_STATELESS_Q_BITS(nextQ.z, nextData[2]); + STORE_STATELESS_Q_BITS(nextQ.w, nextData[3]); + index = boneIdxs[ibone] * 12; + + q[index + 0] = scale * (nextQ.x - prevQ.x) + prevQ.x; + q[index + 1] = scale * (nextQ.y - prevQ.y) + prevQ.y; + q[index + 2] = scale * (nextQ.z - prevQ.z) + prevQ.z; + q[index + 3] = scale * (nextQ.w - prevQ.w) + prevQ.w; + } + } + } else { + for (int ibone = 0; ibone < nBones; ibone++) { + if (boneMask->GetBone(boneIdxs[ibone])) { + prevData = &frameData[ibone * 4]; + + index = boneIdxs[ibone] * 12; + + STORE_STATELESS_Q_BITS(q[index + 0], prevData[0]); + STORE_STATELESS_Q_BITS(q[index + 1], prevData[1]); + STORE_STATELESS_Q_BITS(q[index + 2], prevData[2]); + STORE_STATELESS_Q_BITS(q[index + 3], prevData[3]); + } + } + } + + numConsts = statelessQ->mNumConstBones; + if (numConsts != 0) { + unsigned char *constIdxs = statelessQ->GetConstBoneIdx(); + unsigned short *constBuf = statelessQ->GetConstData(dataBuf); + + for (int ibone = 0; ibone < numConsts; ibone++) { + if (boneMask->GetBone(constIdxs[ibone])) { + prevData = &constBuf[ibone * 4]; + + index = constIdxs[ibone] * 12; + + STORE_STATELESS_Q_BITS(q[index + 0], prevData[0]); + STORE_STATELESS_Q_BITS(q[index + 1], prevData[1]); + STORE_STATELESS_Q_BITS(q[index + 2], prevData[2]); + STORE_STATELESS_Q_BITS(q[index + 3], prevData[3]); + } + } + } + + return true; +} + +void FnStatelessQ::UseFPS(bool u) { + mUseFPS = u; + if (mFPS != 0 || !mUseFPS) { + return; + } + GetAttribute(AttributeId(AttributeId::ID_FPS), mFPS); +} + +unsigned short FnStatelessQ::GetTargetCheckSum() const { + return mpAnim->GetTargetCheckSum(); +} + +const AttributeBlock *FnStatelessQ::GetAttributes() const { + return reinterpret_cast(mpAnim)->GetAttributeBlock(); +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.h b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.h index e8d0e0889..3fcf67b3a 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnStatelessQ.h @@ -7,6 +7,7 @@ #include "BoneMask.h" #include "FnAnimMemoryMap.h" +#include "eagl4supportdef.h" #include @@ -19,7 +20,9 @@ class FnStatelessQ : public FnAnimMemoryMap { // void *operator new(size_t size, const char *msg) {} - // void operator delete(void *ptr, size_t size) {} + void operator delete(void *ptr, size_t size) { + EAGL4Internal::EAGL4Free(ptr, size); + } // void *operator new[](size_t size) {} @@ -53,10 +56,10 @@ class FnStatelessQ : public FnAnimMemoryMap { unsigned char GetFPS() const; // Overrides: FnAnim - unsigned short GetTargetCheckSum() const override {} + unsigned short GetTargetCheckSum() const override; // Overrides: FnAnim - const AttributeBlock *GetAttributes() const override {} + const AttributeBlock *GetAttributes() const override; protected: // Overrides: FnAnim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp index e69de29bb..1e035ad9b 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.cpp @@ -0,0 +1,338 @@ +#include "FnTurnBlender.h" + +#include "AnimUtil.h" +#include "FnPoseBlender.h" +#include "FnRunBlender.h" +#include "ScratchBuffer.h" + +#include + +float turnLength(float *v) { + return sqrtf(v[0] * v[0] + v[1] * v[1]); +} + +namespace EAGL4Anim { + +FnTurnBlender::FnTurnBlender() + : mFreq(1.0f), // + mOffset(0.0f), // + mCycleIdx(-100) { + mFnAnims[1] = nullptr; + mType = AnimTypeId::ANIM_TURNBLENDER; + mAnims = nullptr; + mWeight = 0.0f; + mNumAnims = 0; + mIdx = -100; + mPrevTime = 0.0f; + mInit = false; + mFnAnims[0] = nullptr; +} + +FnTurnBlender::~FnTurnBlender() { + if (mNumAnims) { + ScratchBuffer::GetScratchBuffer(1).FreeBuffer(); + } + if (mAnims) { + MemoryPoolManager::DeleteBlock(mAnims); + } +} + +bool FnTurnBlender::EvalSQT(float currTime, float *sqtBuffer, const BoneMask *boneMask) { + mPrevTime = currTime; + + if (!mFnAnims[0]) { + SetWeight(0.0f); + } + + currTime += mOffset; + int cIdx = ComputeCycleIdx(currTime, 0.0f, 2.0f / mFreq); + float t0 = mFreq * mCycles[0] * currTime; + float t1 = mFreq * mCycles[1] * currTime; + + t0 = CycleTime(t0, 0.0f, mCycles[0] + mCycles[0]) - mOffsets[0]; + t1 = CycleTime(t1, 0.0f, mCycles[1] + mCycles[1]) - mOffsets[1]; + + mSkeleton->GetStillPose(sqtBuffer, 0); + if (!mFnAnims[0]->EvalSQT(t0, sqtBuffer, 0)) { + return false; + } + + if (mWeight != 0.0f) { + float *buffer = reinterpret_cast(ScratchBuffer::GetScratchBuffer(1).GetBuffer()); + + mSkeleton->GetStillPose(buffer, 0); + if (!mFnAnims[1]->EvalSQT(t1, buffer, 0)) { + return false; + } + + FnPoseBlender::Blend(mSkeleton->GetNumBones(), mWeight, sqtBuffer, buffer, sqtBuffer, nullptr); + } + + AlignCycleBeginEnd(cIdx); + AlignRootQ(sqtBuffer); + return true; +} + +void FnTurnBlender::Eval(float prevTime, float currTime, float *pose) { + EvalSQT(currTime, pose, nullptr); +} + +void FnTurnBlender::SetWeight(float w) { + float prevFreq; + int i = FloatToInt(w); + FnRunBlender *fnA; + + if (i < 0) { + i = 0; + } + if (i >= mNumAnims - 1) { + i = mNumAnims - 2; + } + + mWeight = w - static_cast(i); + if (i == mIdx) { + prevFreq = mFreq; + mFreq = mWeight / mCycles[1] + (1.0f - mWeight) / mCycles[0]; + mOffset = (prevFreq / mFreq) * (mPrevTime + mOffset) - mPrevTime; + } else { + if (i == mIdx + 1) { + mFnAnims[0] = mFnAnims[1]; + mFnAnims[1] = mAnims[i + 1]; + } else if (i == mIdx - 1) { + mFnAnims[1] = mFnAnims[0]; + mFnAnims[0] = mAnims[i]; + } else { + mFnAnims[0] = mAnims[i]; + mFnAnims[1] = mAnims[i + 1]; + } + + mIdx = i; + fnA = static_cast(mAnims[i]); + mCycles[0] = 1.0f / fnA->GetFrequency(); + mOffsets[0] = fnA->GetOffset(); + fnA = static_cast(mAnims[mIdx + 1]); + mCycles[1] = 1.0f / fnA->GetFrequency(); + prevFreq = mFreq; + mOffsets[1] = fnA->GetOffset(); + mFreq = mWeight / mCycles[1] + (1.0f - mWeight) / mCycles[0]; + mOffset = (prevFreq / mFreq) * (mPrevTime + mOffset) - mPrevTime; + } +} + +bool FnTurnBlender::EvalPhase(float currTime, PhaseValue &phase) { + return false; +} + +bool FnTurnBlender::EvalVel2D(float currTime, float *vel) { + mPrevTime = currTime; + if (!mFnAnims[0]) { + SetWeight(0.0f); + } + + float evalTime = currTime + mOffset; + int cycleIdx = ComputeCycleIdx(evalTime, 0.0f, 2.0f / mFreq); + + printf("currTime: %g offset: %g cycle: %g\n", evalTime, mOffset, 1.0f / mFreq); + printf("offset0: %g offset1: %g\n", mOffsets[0], mOffsets[1]); + printf("cycle0: %g cycle1: %g\n", mCycles[0], mCycles[1]); + + float t0 = mFreq * mCycles[0] * evalTime; + float t1 = mFreq * mCycles[1] * evalTime; + + printf("before offset t0: %g t1: %g\n", t0, t1); + if (BlendVel(CycleTime(t0, 0.0f, mCycles[0] + mCycles[0]) - mOffsets[0], CycleTime(t1, 0.0f, mCycles[1] + mCycles[1]) - mOffsets[1], vel)) { + AlignCycleBeginEnd(cycleIdx); + AlignVel(vel); + return true; + } + + return false; +} + +bool FnTurnBlender::BlendVel(float t0, float t1, float *vel) const { + float v0[2]; + float v1[2]; + + if (!mFnAnims[0]->EvalVel2D(t0, v0)) { + return false; + } + if (mWeight != 0.0f) { + if (!mFnAnims[1]->EvalVel2D(t1, v1)) { + return false; + } + + float len; + float w; + + w = 1.0f - mWeight; + + vel[0] = mWeight * v1[0] + w * v0[0]; + vel[1] = mWeight * v1[1] + w * v0[1]; + + len = length(vel); + + if (len != 0.0f) { + len = (mWeight * length(v1) + w * length(v0)) / len; + vel[0] = vel[0] * len; + vel[1] = vel[1] * len; + } + } else { + vel[1] = v0[1]; + vel[0] = v0[0]; + } + + return true; +} + +float FnTurnBlender::GetFrequency() const { + return mFreq; +} + +float FnTurnBlender::CycleTime(float t, float startTime, float endTime) const { + float tmp; + int n; + float len = endTime - startTime; + + if (t < startTime) { + tmp = startTime - t; + n = FloatToInt(tmp / len); + return endTime - (tmp - static_cast(n) * len); + } + if (t < endTime) { + return t; + } + tmp = t - endTime; + n = FloatToInt(tmp / len); + return startTime + (tmp - static_cast(n) * len); +} + +int FnTurnBlender::ComputeCycleIdx(float t, float startTime, float endTime) const { + float length = endTime - startTime; + + if (t < startTime) { + t = startTime - t; + return static_cast(t / length); + } + if (t >= endTime) { + t -= endTime; + return static_cast(t / length) + 1; + } + return 0; +} + +void FnTurnBlender::ComputeAlignQ(float *v1, float *v2, UMath::Vector4 &q) const { + float dot = v1[0] * v2[0] + v1[1] * v2[1]; + float v1Length = sqrtf(v1[0] * v1[0] + v1[1] * v1[1]); + float v2Length = sqrtf(v2[0] * v2[0] + v2[1] * v2[1]); + float norm = v1Length * v2Length; + float w = ((dot / norm) + 1.0f) * 0.5f; + + q.x = 0.0f; + q.y = sqrtf(1.0f - w); + q.z = 0.0f; + q.w = sqrtf(w); + if (0.0f < v1[0] * v2[1] - v1[1] * v2[0]) { + q.y = -q.y; + } +} + +void FnTurnBlender::AlignCycleBeginEnd(int cIdx) { + float v0[2]; + float v1[2]; + + if (!mInit) { + mCycleIdx = -1; + mAlignQ.x = 0.0f; + mAlignQ.y = 0.0f; + mAlignQ.z = 0.0f; + mAlignQ.w = 1.0f; + mInit = true; + } else if (mCycleIdx != cIdx) { + UMath::Vector4 q; + UMath::Vector4 resultQ; + static int i; + + BlendBeginFacing(v0); + BlendEndFacing(v1); + ComputeAlignQ(v0, v1, q); + if (mCycleIdx - 1 == cIdx) { + q.y = -q.y; + } + + QuatMult(mAlignQ, q, resultQ); + mAlignQ = resultQ; + mCycleIdx = cIdx; + printf("turn align[%d] Q: %g %g %g %g\n\n", i++, mAlignQ.x, mAlignQ.y, mAlignQ.z, mAlignQ.w); + } +} + +void FnTurnBlender::AlignRootQ(float *sqt) const { + UMath::Vector4 result; + QuatMult(*reinterpret_cast(&sqt[4]), mAlignQ, result); + *reinterpret_cast(&sqt[4]) = result; +} + +void FnTurnBlender::AlignVel(float *vel) const { + UMath::Vector4 org; + UMath::Vector4 result; + + org.x = vel[0]; + org.y = 0.0f; + org.z = vel[1]; + org.w = 1.0f; + QuatTransformPoint(mAlignQ, org, result); + vel[0] = result.x; + vel[1] = result.z; +} + +bool FnTurnBlender::BlendBeginFacing(float *f) const { + FnRunBlender *fnA; + UMath::Vector4 q0; + UMath::Vector4 q1; + UMath::Vector4 q; + UMath::Vector4 xAxis; + UMath::Vector4 xAxis1; + + if (!f) { + return false; + } + + fnA = reinterpret_cast(mFnAnims[0]); + fnA->ComputeBeginRootQ(q0); + fnA = reinterpret_cast(mFnAnims[1]); + fnA->ComputeBeginRootQ(q1); + FastQuatBlendF4(mWeight, reinterpret_cast(&q0), reinterpret_cast(&q1), reinterpret_cast(&q)); + xAxis = kFacingAxis; + QuatTransformPoint(q, xAxis, xAxis1); + f[1] = xAxis1.z; + f[0] = xAxis1.x; + printf("Facing: %g %g\n", xAxis1.x, xAxis1.z); + return true; +} + +bool FnTurnBlender::BlendEndFacing(float *f) const { + FnRunBlender *fnA; + UMath::Vector4 q0; + UMath::Vector4 q1; + UMath::Vector4 q; + UMath::Vector4 xAxis; + UMath::Vector4 xAxis1; + + if (!f) { + return false; + } + + fnA = reinterpret_cast(mFnAnims[0]); + fnA->ComputeEndRootQ(q0); + fnA = reinterpret_cast(mFnAnims[1]); + fnA->ComputeEndRootQ(q1); + FastQuatBlendF4(mWeight, reinterpret_cast(&q0), reinterpret_cast(&q1), reinterpret_cast(&q)); + xAxis = kFacingAxis; + QuatTransformPoint(q, xAxis, xAxis1); + f[1] = xAxis1.z; + f[0] = xAxis1.x; + printf("Facing: %g %g\n", xAxis1.x, xAxis1.z); + return true; +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.h b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.h index 1a34dd8e9..7bbab190e 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.h +++ b/src/Speed/Indep/Src/EAGL4Anim/FnTurnBlender.h @@ -7,6 +7,7 @@ #include "FnAnim.h" #include "Skeleton.h" +#include "eagl4supportdef.h" namespace EAGL4Anim { @@ -17,7 +18,9 @@ class FnTurnBlender : public FnAnim { // void *operator new(size_t size, const char *msg) {} - // void operator delete(void *ptr, size_t size) {} + void operator delete(void *ptr, size_t size) { + EAGL4Internal::EAGL4Free(ptr, size); + } // void *operator new[](size_t size) {} @@ -45,7 +48,7 @@ class FnTurnBlender : public FnAnim { void SetAnims(Skeleton *s, int numAnims, FnAnim **anims); // Overrides: FnAnim - bool EvalPhase(float) override; + bool EvalPhase(float currTime, PhaseValue &phase) override; // Overrides: FnAnim bool EvalVel2D(float currTime, float *vel) override; diff --git a/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.cpp index e69de29bb..e5e094d71 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.cpp @@ -0,0 +1,59 @@ +#include "PhaseChan.h" + + +namespace EAGL4Anim { + +bool FnPhaseChan::GetLength(float &timeLength) const { + const PhaseChan *phaseChan = reinterpret_cast(mpAnim); + + timeLength = static_cast(phaseChan->mNumFrames); + return true; +} + +void FnPhaseChan::SetAnimMemoryMap(AnimMemoryMap *anim) { + PhaseChan *phaseChan = reinterpret_cast(anim); + + mpAnim = anim; + mIdx = 0; + mCurrentFrame = phaseChan->mStartTime; + mRight = !phaseChan->StartWithRight(); + mSampleRate = phaseChan->GetAngleSampleRate(); +} + +void FnPhaseChan::Eval(float prevTime, float currTime, float *phaseValue) { + PhaseChan *phaseChan = reinterpret_cast(mpAnim); + int lastFrame = phaseChan->mNumFrames - 1; + + if (phaseChan->IsCycleAnim()) { + if (currTime < 0.0f) { + float cycleLength = static_cast(lastFrame); + + currTime = currTime - static_cast(static_cast(currTime / cycleLength) * lastFrame); + } else { + float cycleLength = static_cast(lastFrame); + + if (currTime > cycleLength) { + float wrapped = currTime - cycleLength; + + currTime = wrapped - static_cast(static_cast(wrapped / cycleLength) * lastFrame); + } + } + } + + int frame = static_cast(currTime) / static_cast(mSampleRate); + float t = (currTime - static_cast(frame * mSampleRate)) / static_cast(mSampleRate); + int numCycles = phaseChan->mNumCycles > 1 ? phaseChan->mNumCycles : 2; + float angle0 = static_cast(phaseChan->GetAngles()[numCycles + frame]) * 1.4117647f - 180.0f; + + if (frame < lastFrame / static_cast(mSampleRate) + 1) { + float angle1 = static_cast(phaseChan->GetAngles()[numCycles + frame + 1]) * 1.4117647f - 180.0f; + + *phaseValue = (1.0f - t) * angle0 + t * angle1; + } else { + float angle1 = static_cast(phaseChan->GetAngles()[numCycles + frame - 1]) * 1.4117647f - 180.0f; + + *phaseValue = (t + 1.0f) * angle0 - t * angle1; + } +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.h b/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.h index db8fd151a..47673f650 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.h +++ b/src/Speed/Indep/Src/EAGL4Anim/PhaseChan.h @@ -6,6 +6,7 @@ #endif #include "FnAnimMemoryMap.h" +#include "eagl4supportdef.h" namespace EAGL4Anim { @@ -29,7 +30,9 @@ struct PhaseChan : public AnimMemoryMap { // void *operator new(size_t size, const char *msg) {} - // void operator delete(void *ptr, size_t size) {} + void operator delete(void *ptr, size_t size) { + EAGL4Internal::EAGL4Free(ptr, size); + } // void *operator new[](size_t size) {} @@ -41,33 +44,77 @@ struct PhaseChan : public AnimMemoryMap { PhaseChan() {} - bool IsInvalid() const {} + bool IsInvalid() const { + return (mFlag & 0x4) != 0; + } - void SetInvalid(bool s) {} + void SetInvalid(bool s) { + if (s) { + mFlag |= 0x4; + } else { + mFlag &= ~0x4; + } + } - bool StartWithRight() const {} + bool StartWithRight() const { + return (mFlag & 0x1) != 0; + } - void SetStartWithRight(bool s) {} + void SetStartWithRight(bool s) { + if (s) { + mFlag |= 0x1; + } else { + mFlag &= ~0x1; + } + } - void SetCycleAnim(bool s) {} + void SetCycleAnim(bool s) { + if (s) { + mFlag |= 0x2; + } else { + mFlag &= ~0x2; + } + } - bool IsCycleAnim() const {} + bool IsCycleAnim() const { + return (mFlag & 0x2) != 0; + } static int ComputeSize(int numCycles, int numFrames, int sampleRateFlag) {} int GetSize() const {} - unsigned char *GetAngles() {} + unsigned char *GetAngles() { + return &mCycles[mNumCycles > 1 ? mNumCycles : 2]; + } - const unsigned char *GetAngles() const {} + const unsigned char *GetAngles() const { + return &mCycles[mNumCycles > 1 ? mNumCycles : 2]; + } void SetAngle(int i, float angle) {} float GetAngle(int i) const {} - int GetAngleSampleRate() const {} + int GetAngleSampleRate() const { + if ((mFlag & 0x8) != 0) { + return 1; + } + if ((mFlag & 0x10) != 0) { + return 2; + } + if ((mFlag & 0x20) != 0) { + return 4; + } + if ((mFlag & 0x40) != 0) { + return 8; + } + return 1; + } - int GetNumAngles() const {} + int GetNumAngles() const { + return mNumFrames / GetAngleSampleRate() + 1; + } bool FindMatchTime(const MatchPhaseInput &input, float &time) const; @@ -85,7 +132,9 @@ class FnPhaseChan : public FnAnimMemoryMap { // void *operator new(size_t size, const char *msg) {} - // void operator delete(void *ptr, size_t size) {} + void operator delete(void *ptr, size_t size) { + EAGL4Internal::EAGL4Free(ptr, size); + } // void *operator new[](size_t size) {} @@ -103,9 +152,6 @@ class FnPhaseChan : public FnAnimMemoryMap { mType = AnimTypeId::ANIM_PHASE; } - // Overrides: FnAnimSuper - ~FnPhaseChan() override {} - // Overrides: FnAnim bool GetLength(float &timeLength) const override; @@ -116,7 +162,9 @@ class FnPhaseChan : public FnAnimMemoryMap { void SetAnimMemoryMap(AnimMemoryMap *anim) override; // Overrides: FnAnim - const PhaseChan *GetPhaseChan() override {} + const PhaseChan *GetPhaseChan() override { + return reinterpret_cast(mpAnim); + } // Members unsigned short mIdx; // offset 0x10, size 0x2 diff --git a/src/Speed/Indep/Src/EAGL4Anim/PoseAnim.cpp b/src/Speed/Indep/Src/EAGL4Anim/PoseAnim.cpp index e69de29bb..131f72172 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/PoseAnim.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/PoseAnim.cpp @@ -0,0 +1,15 @@ +#include "PoseAnim.h" + +#include "FnPoseAnim.h" + +namespace EAGL4Anim { + +void PoseAnim::InitAnimMemoryMap(AnimMemoryMap *anim) { + PoseAnim *poseAnim = reinterpret_cast(anim); + FnPoseAnim fnPoseAnim; + FnPoseAnim *fnPoseAnimPtr = reinterpret_cast(poseAnim->GetFnLocation()); + + *reinterpret_cast(fnPoseAnimPtr) = *reinterpret_cast(&fnPoseAnim); +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/PosePalette.h b/src/Speed/Indep/Src/EAGL4Anim/PosePalette.h index 3b1ff9973..fedb873dc 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/PosePalette.h +++ b/src/Speed/Indep/Src/EAGL4Anim/PosePalette.h @@ -14,11 +14,17 @@ namespace EAGL4Anim { // total size: 0xC class PosePalette { public: - int GetNumPoses() const {} + int GetNumPoses() const { + return mNumPoses; + } - int GetNumQs() const {} + int GetNumQs() const { + return mNumQs; + } - int GetNumTs() const {} + int GetNumTs() const { + return mNumTs; + } void SetNumPoses(int n) {} @@ -26,9 +32,13 @@ class PosePalette { void SetNumTs(int n) {} - float *GetPoseData() const {} + float *GetPoseData() const { + return mPoseData; + } - unsigned short *GetDofIndices() const {} + unsigned short *GetDofIndices() const { + return mDofIndices; + } private: float *mPoseData; // offset 0x0, size 0x4 @@ -62,7 +72,9 @@ class PosePaletteBank { static int ComputeSize(int numPalettes) {} - const PosePalette **GetPalettes() const {} + const PosePalette *const *GetPalettes() const { + return reinterpret_cast(&this[1]); + } private: CheckSum mSkelCheckSum; // offset 0x0, size 0x2 diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.cpp index e69de29bb..996d733ed 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.cpp @@ -0,0 +1,76 @@ +#include "RawEventChannel.h" + +namespace EAGL4Anim { + +void RawEventChannel::Eval(float previousTime, float currentTime, int ¤tIdx, float &cacheCurrentTime, EventHandler **eventHandlers, void *extraData) { + int numEvents = GetNumEvents(); + Event *events = GetEvents(); + EventHandler *eh; + int i; + + if (previousTime < cacheCurrentTime) { + i = currentIdx; + + while (0 <= i && events[i].triggerTime > previousTime) { + i--; + } + currentIdx = i + 1; + } + + i = currentIdx; + + while (i < numEvents && events[i].triggerTime <= previousTime) { + i++; + } + + currentIdx = i; + + if (previousTime == currentTime) { + if (i >= numEvents) { + currentIdx = numEvents - 1; + } + + i = currentIdx; + + while (0 <= i && events[i].triggerTime >= currentTime) { + i--; + } + currentIdx = i + 1; + } else { + if (currentTime < previousTime) { + for (; i < numEvents; i++) { + eh = eventHandlers[events[i].eventId]; + + if (eh) { + eh->HandleEvent(currentTime, events[i], extraData); + } + } + currentIdx = 0; + } + } + + i = currentIdx; + + if (i < numEvents) { + do { + if (events[i].triggerTime > currentTime) { + break; + } + + eh = eventHandlers[events[i].eventId]; + if (eh) { + eh->HandleEvent(currentTime, events[i], extraData); + } + i++; + } while (i < numEvents); + } + + currentIdx = i; + if (i >= numEvents) { + currentIdx = numEvents - 1; + } + + cacheCurrentTime = currentTime; +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.h b/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.h index e38fae196..9f639a4e8 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.h +++ b/src/Speed/Indep/Src/EAGL4Anim/RawEventChannel.h @@ -13,15 +13,25 @@ namespace EAGL4Anim { // total size: 0x8 class RawEventChannel : public AnimMemoryMap { public: - int GetNumEvents() const {} + int GetNumEvents() const { + return mNumEvents; + } - void SetNumEvents(int n) {} + void SetNumEvents(int n) { + mNumEvents = n; + } - struct Event *GetEvents() {} + Event *GetEvents() { + return reinterpret_cast(&this[1]); + } - int GetSize() const {} + int GetSize() const { + return ComputeSize(mNumEvents); + } - static int ComputeSize(int numEvents) {} + static int ComputeSize(int numEvents) { + return sizeof(RawEventChannel) + numEvents * sizeof(Event); + } void Eval(float previousTime, float currentTime, int ¤tIdx, float &cacheCurrentTime, EventHandler **eventHandlers, void *extraData); diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h b/src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h index ca721f3e5..32b3cdc02 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h +++ b/src/Speed/Indep/Src/EAGL4Anim/RawLinearChannel.h @@ -28,30 +28,85 @@ class RawLinearChannel : public AnimMemoryMap { mNumFrames = n; } - unsigned short *GetDOFIndex() {} + unsigned short *GetDOFIndex() { + return reinterpret_cast(&this[1]); + } - const unsigned short *GetDOFIndex() const {} + const unsigned short *GetDOFIndex() const { + return reinterpret_cast(&this[1]); + } - int GetDOFIndexSize() const {} + int GetDOFIndexSize() const { + return (mNumDOFs + 1) & ~1; + } - float *GetAnimData() {} + float *GetAnimData() { + return reinterpret_cast(GetDOFIndex() + GetDOFIndexSize()); + } - const float *GetAnimData() const {} + const float *GetAnimData() const { + return reinterpret_cast(GetDOFIndex() + GetDOFIndexSize()); + } - float *GetFrame(int i) {} + float *GetFrame(int i) { + return &GetAnimData()[i * mNumDOFs]; + } - const float *GetFrame(int i) const {} + const float *GetFrame(int i) const { + return &GetAnimData()[i * mNumDOFs]; + } - int GetSize() const {} + int GetSize() const { + return ComputeSize(mNumDOFs, mNumFrames); + } - static int ComputeSize(int numDOFs, int numFrames) {} + static int ComputeSize(int numDOFs, int numFrames) { + return sizeof(RawLinearChannel) + (((numDOFs + 1) & ~1) * sizeof(unsigned short)) + numFrames * numDOFs * sizeof(float); + } - void EvalInterpFrame(float t, int frame0, int frame1, float *output) {} + void EvalInterpFrame(float t, int frame0, int frame1, float *output) { + const unsigned short *dofIndex = GetDOFIndex(); + const float *frameData0 = GetFrame(frame0); + const float *frameData1 = GetFrame(frame1); - void Eval(float frameTime, float *output, bool interp) {} + for (int i = 0; i < mNumDOFs; i++) { + float value0 = frameData0[i]; + output[dofIndex[i]] = t * (frameData1[i] - value0) + value0; + } + } private: - void EvalFrame(int frame, float *output) {} + void EvalFrame(int frame, float *output) { + const unsigned short *dofIndex = GetDOFIndex(); + const float *frameData = GetFrame(frame); + + for (int i = 0; i < mNumDOFs; i++) { + output[dofIndex[i]] = frameData[i]; + } + } + + public: + void Eval(float frameTime, float *output, bool interp) { + int frame = static_cast(frameTime); + + if (frame < 0) { + EvalFrame(0, output); + } else { + int lastFrame = mNumFrames - 1; + + if (frame >= lastFrame) { + EvalFrame(lastFrame, output); + } else { + float t = frameTime - static_cast(frame); + + if (t != 0.0f && interp) { + EvalInterpFrame(t, frame, frame + 1, output); + } else { + EvalFrame(frame, output); + } + } + } + } unsigned short mNumDOFs; // offset 0x4, size 0x2 unsigned short mNumFrames; // offset 0x6, size 0x2 diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp index e69de29bb..461761b55 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.cpp @@ -0,0 +1,138 @@ +#include "RawPoseChannel.h" + +#include + +namespace EAGL4Anim { + +float qt0[7]; + +void RawPoseChannel::InitAnimMemoryMap(AnimMemoryMap *anim) { + RawPoseChannel *rawChan = reinterpret_cast(anim); + int i = 0; + int j; + int count; + int *sig = rawChan->GetNonInterpSig(); + int sigSize = rawChan->GetSigSize(); + + while (i < sigSize) { + count = *sig++; + i++; + + for (j = 0; j < count; j++) { + int t = *sig; + + i++; + switch (t) { + case EUL: + *sig = reinterpret_cast(EulF3); + break; + case QUAT: + *sig = reinterpret_cast(QuatF4); + break; + case TRAN: + *sig = reinterpret_cast(TranF3); + break; + default: + printf("Bad signature channel type\n"); + break; + } + sig++; + } + } + + sig = rawChan->GetInterpSig(); + i = 0; + while (i < sigSize) { + count = *sig++; + i++; + + for (j = 0; j < count; j++) { + int t = *sig; + + i++; + switch (t) { + case EUL: + *sig = reinterpret_cast(EulF3Interp); + break; + case QUAT: + *sig = reinterpret_cast(QuatF4Interp); + break; + case TRAN: + *sig = reinterpret_cast(TranF3Interp); + break; + default: + printf("Bad signature channel type\n"); + break; + } + sig++; + } + } +} + +void RawPoseChannel::Eval(float frameTime, float *outputPose, bool interp, const BoneMask *boneMask) { + int frame = static_cast(frameTime); + + if (frame < 0) { + EvalFrame(0, outputPose, boneMask); + } else { + int lastFrame = mNumFrames - 1; + + if (frame >= lastFrame) { + EvalFrame(lastFrame, outputPose, boneMask); + } else { + float t = frameTime - static_cast(frame); + + if (t != 0.0f && interp) { + EvalInterpFrame(t, frame, frame + 1, outputPose, boneMask); + } else { + EvalFrame(frame, outputPose, boneMask); + } + } + } +} + +void RawPoseChannel::EvalFrame(int frame, float *outputPose, const BoneMask *boneMask) { + int count; + int *s = GetNonInterpSig(); + float *d = GetFrame(frame); + float *out = outputPose; + int *end = s + GetSigSize(); + void (*func)(float *&, float *); + int j; + + if (!boneMask) { + while (s < end) { + count = *s++; + + for (j = 0; j < count; j++) { + func = reinterpret_cast(*s++); + func(d, out + 4); + } + out += 12; + } + } else { + for (int i = 0; s < end; i++) { + count = *s++; + + if (boneMask->GetBone(i)) { + for (j = 0; j < count; j++) { + func = reinterpret_cast(*s++); + func(d, out + 4); + } + } else { + for (j = 0; j < count; j++) { + func = reinterpret_cast(*s++); + if (func == EulF3 || func == TranF3) { + d += 3; + } else if (func == QuatF4) { + d += 4; + } + } + } + + out += 12; + } + } +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.h b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.h index 8c2adfa43..016e18d70 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.h +++ b/src/Speed/Indep/Src/EAGL4Anim/RawPoseChannel.h @@ -5,11 +5,64 @@ #pragma once #endif +#include "AnimUtil.h" #include "AnimMemoryMap.h" #include "BoneMask.h" namespace EAGL4Anim { +extern float qt0[7]; + +inline float DegToRad(float deg) { + return deg * 0.017453294f; +} + +inline void QuatF4(float *&data, float *output) { + output[0] = *data++; + output[1] = *data++; + output[2] = *data++; + output[3] = *data++; +} + +inline void EulF3(float *&data, float *output) { + float eulData[3]; + + eulData[0] = DegToRad(*data++); + eulData[1] = DegToRad(*data++); + eulData[2] = DegToRad(*data++); + EulToQuat(eulData, output); +} + +inline void TranF3(float *&data, float *output) { + output[4] = *data++; + output[5] = *data++; + output[6] = *data++; +} + +inline void QuatBlendF4(float w, const float *d0, const float *d1, float *out) { + FastQuatBlendF4(w, d0, d1, out); +} + +inline void QuatF4Interp(float w, float *&data0, float *&data1, float *output) { + QuatF4(data0, qt0); + QuatF4(data1, output); + QuatBlendF4(w, qt0, output, output); +} + +inline void EulF3Interp(float w, float *&data0, float *&data1, float *output) { + EulF3(data0, qt0); + EulF3(data1, output); + QuatBlendF4(w, qt0, output, output); +} + +inline void TranF3Interp(float w, float *&data0, float *&data1, float *output) { + TranF3(data0, qt0); + TranF3(data1, output); + output[4] = qt0[4] + w * (output[4] - qt0[4]); + output[5] = qt0[5] + w * (output[5] - qt0[5]); + output[6] = qt0[6] + w * (output[6] - qt0[6]); +} + // total size: 0x10 class RawPoseChannel : public AnimMemoryMap { public: @@ -19,33 +72,55 @@ class RawPoseChannel : public AnimMemoryMap { TRAN = 2, }; - int GetSigSize() const {} + int GetSigSize() const { + return mSigSize; + } void SetSigSize(int s) {} - int GetFrameSize() const {} + int GetFrameSize() const { + return mFrameSize; + } void SetFrameSize(int s) {} - int GetNumFrames() const {} + int GetNumFrames() const { + return mNumFrames; + } void SetNumFrames(int n) {} - int *GetNonInterpSig() {} + int *GetNonInterpSig() { + return reinterpret_cast(&this[1]); + } - const int *GetNonInterpSig() const {} + const int *GetNonInterpSig() const { + return reinterpret_cast(&this[1]); + } - int *GetInterpSig() {} + int *GetInterpSig() { + return GetNonInterpSig() + GetSigSize(); + } - const int *GetInterpSig() const {} + const int *GetInterpSig() const { + return GetNonInterpSig() + GetSigSize(); + } - float *GetAnimData() {} + float *GetAnimData() { + return reinterpret_cast(GetInterpSig() + GetSigSize()); + } - const float *GetAnimData() const {} + const float *GetAnimData() const { + return reinterpret_cast(GetInterpSig() + GetSigSize()); + } - float *GetFrame(int i) {} + float *GetFrame(int i) { + return &GetAnimData()[i * GetFrameSize()]; + } - const float *GetFrame(int i) const {} + const float *GetFrame(int i) const { + return &GetAnimData()[i * GetFrameSize()]; + } int GetSize() const {} @@ -53,7 +128,49 @@ class RawPoseChannel : public AnimMemoryMap { int GetNumBones() const {} - void EvalInterpFrame(float t, int frame0, int frame1, float *outputPose, const BoneMask *boneMask) {} + void EvalInterpFrame(float t, int frame0, int frame1, float *outputPose, const BoneMask *boneMask) { + int *s = GetInterpSig(); + float *d0 = GetFrame(frame0); + float *d1 = GetFrame(frame1); + int *end = s + mSigSize; + + if (!boneMask) { + while (s < end) { + int count = *s++; + float *out = outputPose + 12; + + for (int j = 0; j < count; j++) { + reinterpret_cast(*s++)(t, d0, d1, outputPose + 4); + } + outputPose = out; + } + } else { + for (int i = 0; s < end; i++) { + int count = *s++; + + if (boneMask->GetBone(i)) { + for (int j = 0; j < count; j++) { + reinterpret_cast(*s++)(t, d0, d1, outputPose + 4); + } + } else { + for (int j = 0; j < count; j++) { + void (*func)(float, float *&, float *&, float *) = + reinterpret_cast(*s++); + + if (func == EulF3Interp || func == TranF3Interp) { + d0 += 3; + d1 += 3; + } else if (func == QuatF4Interp) { + d0 += 4; + d1 += 4; + } + } + } + + outputPose += 12; + } + } + } static void InitAnimMemoryMap(AnimMemoryMap *anim); diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp index e69de29bb..477a8d363 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.cpp @@ -0,0 +1,259 @@ +#include "RawStateChan.h" + +namespace EAGL4Anim { + +namespace { + +static unsigned char *GetRawStateKeyData(RawStateChan *rawStateChan) { + unsigned char *keyData = reinterpret_cast(rawStateChan->GetDecodeData()); + unsigned char numFields = rawStateChan->GetNumFields(); + + keyData += numFields * sizeof(unsigned short); + if ((numFields & 1) == 0) { + keyData += sizeof(unsigned short); + } + + return keyData; +} + +static const unsigned char *GetRawStateKeyData(const RawStateChan *rawStateChan) { + const unsigned char *keyData = reinterpret_cast(rawStateChan->GetDecodeData()); + unsigned char numFields = rawStateChan->GetNumFields(); + + keyData += numFields * sizeof(unsigned short); + if ((numFields & 1) == 0) { + keyData += sizeof(unsigned short); + } + + return keyData; +} + +} // namespace + +FnRawStateChan::~FnRawStateChan() {} + +bool FnRawStateChan::GetLength(float &timeLength) const { + const RawStateChan *rawStateChan = reinterpret_cast(mpAnim); + + timeLength = static_cast(rawStateChan->GetNumFrames()); + return true; +} + +void FnRawStateChan::Eval(float, float time, float *dofs) { + EvalState(time, reinterpret_cast(dofs)); +} + +void FnRawStateChan::Decode(unsigned char *src, unsigned char *dest) const { + const RawStateChan *rawStateChan = reinterpret_cast(mpAnim); + unsigned int value = 0; + unsigned int numBits = 0; + + for (int i = 0; i < rawStateChan->GetNumFields(); i++) { + unsigned short decodeData = rawStateChan->GetDecodeData()[i]; + char decodeInfo[3]; + decodeInfo[0] = static_cast(decodeData >> 13); + decodeInfo[1] = static_cast(((decodeData >> 11) & 3) + 1); + decodeInfo[2] = static_cast(decodeData); + + if (decodeInfo[0] == 2) { + goto Decode4Bits; + } else if (decodeInfo[0] > 2) { + goto DecodeWide; + } else if (decodeInfo[0] == 0) { + goto Decode1Bit; + } else if (decodeInfo[0] == 1) { + goto Decode2Bits; + } + goto DecodeDone; + + DecodeWide: + if (decodeInfo[0] == 4) { + goto DecodeU16; + } + if (decodeInfo[0] < 4) { + goto DecodeU8; + } + if (decodeInfo[0] != 5) { + goto DecodeDone; + } + value = *reinterpret_cast(src); + src += 4; + goto DecodeDone; + + DecodeU16: + value = *reinterpret_cast(src); + src += 2; + goto DecodeDone; + + DecodeU8: + value = *src; + src += 1; + goto DecodeDone; + + Decode4Bits: + numBits = (numBits + 4) & 0xFF; + value = (*src >> (8 - numBits)) & 0xF; + goto DecodeDone; + + Decode2Bits: + numBits = (numBits + 2) & 0xFF; + value = (*src >> (8 - numBits)) & 3; + goto DecodeDone; + + Decode1Bit: + numBits = (numBits + 1) & 0xFF; + value = (*src >> (8 - numBits)) & 1; + + DecodeDone: + + if (numBits > 7) { + src += 1; + numBits = 0; + } + + if (static_cast(decodeInfo[1]) == 2) { + goto Store2Bytes; + } else if (static_cast(decodeInfo[1]) > 2) { + goto StoreWide; + } else if (static_cast(decodeInfo[1]) == 1) { + goto Store1Byte; + } + goto StoreDone; + + StoreWide: + if (static_cast(decodeInfo[1]) != 4) { + goto StoreDone; + } + *reinterpret_cast(&dest[static_cast(decodeInfo[2])]) = value; + goto StoreDone; + + Store2Bytes: + *reinterpret_cast(&dest[static_cast(decodeInfo[2])]) = + static_cast(value); + goto StoreDone; + + Store1Byte: + dest[static_cast(decodeInfo[2])] = static_cast(value); + + StoreDone: + ; + } +} + +bool FnRawStateChan::EvalState(float time, State *s) { + RawStateChan *rawStateChan = reinterpret_cast(mpAnim); + unsigned char numFields = rawStateChan->GetNumFields(); + unsigned char keySize = rawStateChan->GetKeySize(); + unsigned short numKeys; + unsigned char *keyData; + int keyIdx = mKeyIdx; + unsigned char *currKey; + + if ((numFields & 1) == 0) { + keyData = reinterpret_cast(rawStateChan) + numFields * sizeof(unsigned short) + 12; + } else { + keyData = reinterpret_cast(rawStateChan) + numFields * sizeof(unsigned short) + 10; + } + + if (*reinterpret_cast(&keyData[keyIdx * keySize]) <= time) { + numKeys = rawStateChan->GetNumKeys(); + + if (keyIdx < numKeys) { + keyData = reinterpret_cast(rawStateChan) + numFields * sizeof(unsigned short) + 10; + + do { + currKey = keyData + 2; + if ((numFields & 1) != 0) { + currKey = keyData; + } + currKey += keyIdx * keySize; + + if (time < *reinterpret_cast(currKey + keySize)) { + goto KeyFound; + } + + keyIdx++; + } while (keyIdx < numKeys); + } + + if ((numFields & 1) == 0) { + keyData = reinterpret_cast(rawStateChan) + numFields * sizeof(unsigned short) + 12; + } else { + keyData = reinterpret_cast(rawStateChan) + numFields * sizeof(unsigned short) + 10; + } + + Decode(keyData + keySize * (numKeys - 1) + sizeof(float), reinterpret_cast(s)); + mKeyIdx = numKeys - 1; + } else { + keyIdx--; + + if (keyIdx > -1) { + keyData = reinterpret_cast(rawStateChan) + numFields * sizeof(unsigned short) + 10; + + do { + currKey = keyData + 2; + if ((numFields & 1) != 0) { + currKey = keyData; + } + currKey += keyIdx * keySize; + + if (*reinterpret_cast(currKey) <= time) { + goto KeyFound; + } + + keyIdx--; + } while (keyIdx > -1); + } + + if ((numFields & 1) == 0) { + keyData = reinterpret_cast(rawStateChan) + numFields * sizeof(unsigned short) + 12; + } else { + keyData = reinterpret_cast(rawStateChan) + numFields * sizeof(unsigned short) + 10; + } + + Decode(keyData + sizeof(float), reinterpret_cast(s)); + mKeyIdx = 0; + } + + return true; + +KeyFound: + Decode(currKey + sizeof(float), reinterpret_cast(s)); + mKeyIdx = keyIdx; + return true; +} + +bool FnRawStateChan::FindTime(const StateTest &test, float startTime, float &resultTime) { + const RawStateChan *rawStateChan = reinterpret_cast(mpAnim); + unsigned char stateBuffer[80]; + int keyIdx; + + for (keyIdx = 0; keyIdx < rawStateChan->GetNumKeys(); keyIdx++) { + int keyDataOffset; + const float *currKey; + float keyTime; + unsigned char numFields = rawStateChan->GetNumFields(); + + if (numFields & 1) { + keyDataOffset = rawStateChan->GetNumFields() * sizeof(unsigned short) + 10; + } else { + keyDataOffset = rawStateChan->GetNumFields() * sizeof(unsigned short) + 12; + } + + currKey = reinterpret_cast( + reinterpret_cast(rawStateChan) + keyDataOffset + keyIdx * rawStateChan->GetKeySize()); + keyTime = *currKey; + + if (keyTime > startTime) { + Decode(const_cast(reinterpret_cast(currKey + 1)), stateBuffer); + if (test.Pass(reinterpret_cast(stateBuffer))) { + resultTime = keyTime; + return true; + } + } + } + + return false; +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.h b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.h index afce8cf96..ddc00e838 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.h +++ b/src/Speed/Indep/Src/EAGL4Anim/RawStateChan.h @@ -7,6 +7,7 @@ #include "AnimMemoryMap.h" #include "FnAnimMemoryMap.h" +#include "eagl4supportdef.h" namespace EAGL4Anim { @@ -17,7 +18,9 @@ class RawStateChan : public AnimMemoryMap { // void *operator new(size_t size, const char *msg) {} - // void operator delete(void *ptr, size_t size) {} + void operator delete(void *ptr, size_t size) { + EAGL4Internal::EAGL4Free(ptr, size); + } // void *operator new[](size_t size) {} @@ -29,23 +32,35 @@ class RawStateChan : public AnimMemoryMap { void SetNumFrames(unsigned short n) {} - unsigned short GetNumFrames() const {} + unsigned short GetNumFrames() const { + return mNumFrames; + } void SetNumKeys(unsigned short n) {} - unsigned short GetNumKeys() const {} + unsigned short GetNumKeys() const { + return mNumKeys; + } void SetKeySize(unsigned char n) {} - unsigned char GetKeySize() const {} + unsigned char GetKeySize() const { + return mKeySize; + } void SetNumFields(unsigned char n) {} - unsigned char GetNumFields() const {} + unsigned char GetNumFields() const { + return mNumFields; + } - const unsigned short *GetDecodeData() const {} + const unsigned short *GetDecodeData() const { + return mDecodeData; + } - unsigned short *GetDecodeData() {} + unsigned short *GetDecodeData() { + return mDecodeData; + } int GetSize() const {} @@ -74,7 +89,9 @@ class FnRawStateChan : public FnAnimMemoryMap { // void *operator new(size_t size, const char *msg) {} - // void operator delete(void *ptr, size_t size) {} + void operator delete(void *ptr, size_t size) { + EAGL4Internal::EAGL4Free(ptr, size); + } // void *operator new[](size_t size) {} @@ -91,13 +108,13 @@ class FnRawStateChan : public FnAnimMemoryMap { } // Overrides: FnAnimSuper - ~FnRawStateChan() override {} + ~FnRawStateChan() override; // Overrides: FnAnim - bool GetLength(float &timeLength) const override {} + bool GetLength(float &timeLength) const override; // Overrides: FnAnim - void Eval(float, float time, float *dofs) override {} + void Eval(float, float time, float *dofs) override; void Decode(unsigned char *src, unsigned char *dest) const; diff --git a/src/Speed/Indep/Src/EAGL4Anim/ScratchBuffer.cpp b/src/Speed/Indep/Src/EAGL4Anim/ScratchBuffer.cpp index 7b31e139f..ae481ffa9 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/ScratchBuffer.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/ScratchBuffer.cpp @@ -1,7 +1,23 @@ #include "ScratchBuffer.h" +#include "Speed/Indep/Src/EAGL4Anim/eagl4supportdef.h" + namespace EAGL4Anim { ScratchBuffer ScratchBufferHelper::mScratchBuffers[] = {}; +void ScratchBuffer::FreeBuffer() { + int refCount = mRefCount; + + mRefCount = refCount - 1; + if (mBuffer && refCount - 1 < 1) { + EAGL4Internal::EAGL4Free(mBuffer, mSize); + mBuffer = nullptr; + } +} + +ScratchBuffer &ScratchBuffer::GetScratchBuffer(int i) { + return ScratchBufferHelper::mScratchBuffers[i]; +} + }; diff --git a/src/Speed/Indep/Src/EAGL4Anim/ScratchBuffer.h b/src/Speed/Indep/Src/EAGL4Anim/ScratchBuffer.h index cac4159d8..fedeb56a2 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/ScratchBuffer.h +++ b/src/Speed/Indep/Src/EAGL4Anim/ScratchBuffer.h @@ -28,9 +28,13 @@ class ScratchBuffer { // void *operator new(size_t, void *ptr) {} - void *GetBuffer() {} + void *GetBuffer() { + return mBuffer; + } - unsigned int GetSize() const {} + unsigned int GetSize() const { + return mSize; + } void AllocateBuffer(unsigned int bufferSize); @@ -48,6 +52,7 @@ class ScratchBuffer { class ScratchBufferHelper { private: + friend class ScratchBuffer; static ScratchBuffer mScratchBuffers[3]; // size: 0x24 }; diff --git a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp index e69de29bb..6e9b4d0c7 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/Skeleton.cpp @@ -0,0 +1,294 @@ +#include "Skeleton.h" + +static void MtxMult(EAGL4::Transform *result, const EAGL4::Transform *A, const EAGL4::Transform *B); + +namespace EAGL4Anim { + +extern void (*MatrixMultiply)(EAGL4::Transform *, const EAGL4::Transform *, const EAGL4::Transform *); + +void Skeleton::PoseSQTToGlobal(float *pose, EAGL4::Transform *output, BoneMask *mask) { + if (mask) { + int i; + int p; + float *poseData = pose; + BoneData *bones = GetBoneData(); + int n = GetNumBones(); + + for (i = 0; i < n; i++) { + if (mask->GetBone(i)) { + output[i].BuildSQT(poseData[0], poseData[1], poseData[2], poseData[4], poseData[5], poseData[6], poseData[7], poseData[8], + poseData[9], poseData[10]); + output[i].m.v0.x *= poseData[3]; + output[i].m.v1.x *= poseData[3]; + output[i].m.v2.x *= poseData[3]; + + p = bones[i].mParentIdx; + if (p >= 0) { + MatrixMultiply(&output[i], &output[p], &output[i]); + } + } + + poseData += 12; + } + } else { + int i; + int p; + float *poseData = pose; + BoneData *bones = GetBoneData(); + int n = GetNumBones(); + + for (i = 0; i < n; i++) { + output[i].BuildSQT(poseData[0], poseData[1], poseData[2], poseData[4], poseData[5], poseData[6], poseData[7], poseData[8], + poseData[9], poseData[10]); + output[i].m.v0.x *= poseData[3]; + output[i].m.v1.x *= poseData[3]; + output[i].m.v2.x *= poseData[3]; + + p = bones[i].mParentIdx; + if (p >= 0) { + MatrixMultiply(&output[i], &output[p], &output[i]); + } + + poseData += 12; + } + } +} + +void Skeleton::GetStillPose(float *pose, const BoneMask *mask) const { + int n = GetNumBones(); + int i; + + if (mask) { + if (GetInvBoneScales()) { + for (i = 0; i < n; i++) { + if (mask->GetBone(i)) { + const BoneData &bd = GetBoneData(i); + + pose[0] = bd.mS.x; + pose[1] = bd.mS.y; + pose[2] = bd.mS.z; + pose[3] = GetInvBoneScales()[i]; + pose[4] = bd.mQ.x; + pose[5] = bd.mQ.y; + pose[6] = bd.mQ.z; + pose[7] = bd.mQ.w; + pose[8] = bd.mT.x; + pose[9] = bd.mT.y; + pose[10] = bd.mT.z; + pose[11] = 1.0f; + } + pose += 12; + } + } else { + for (i = 0; i < n; i++) { + if (mask->GetBone(i)) { + const BoneData &bd = GetBoneData(i); + const float one = 1.0f; + float z; + + pose[0] = bd.mS.x; + pose[1] = bd.mS.y; + z = bd.mS.z; + pose[2] = (pose[3] = one, z); + pose[4] = bd.mQ.x; + pose[5] = bd.mQ.y; + pose[6] = bd.mQ.z; + pose[7] = bd.mQ.w; + pose[8] = bd.mT.x; + pose[9] = bd.mT.y; + pose[10] = bd.mT.z; + pose[11] = one; + } + pose += 12; + } + } + } else if (GetInvBoneScales()) { + for (i = 0; i < n; i++) { + const BoneData &bd = GetBoneData(i); + + pose[0] = bd.mS.x; + pose[1] = bd.mS.y; + pose[2] = bd.mS.z; + pose[3] = GetInvBoneScales()[i]; + pose[4] = bd.mQ.x; + pose[5] = bd.mQ.y; + pose[6] = bd.mQ.z; + pose[7] = bd.mQ.w; + pose[8] = bd.mT.x; + pose[9] = bd.mT.y; + pose[10] = bd.mT.z; + pose[11] = 1.0f; + pose += 12; + } + } else { + for (i = 0; i < n; i++) { + const BoneData &bd = GetBoneData(i); + const float one = 1.0f; + float z; + + pose[0] = bd.mS.x; + pose[1] = bd.mS.y; + z = bd.mS.z; + pose[2] = (pose[3] = one, z); + pose[4] = bd.mQ.x; + pose[5] = bd.mQ.y; + pose[6] = bd.mQ.z; + pose[7] = bd.mQ.w; + pose[8] = bd.mT.x; + pose[9] = bd.mT.y; + pose[10] = bd.mT.z; + pose[11] = one; + pose += 12; + } + } +} + +void Skeleton::MirrorPose(float *pose, float *mirrorPose, bool local, const BoneMask *mask) { + const int DOF = 12; + const int n = GetNumBones(); + int i; + float *ps; + float *pd; + + if (mask) { + if (pose == mirrorPose) { + for (i = 0; i < n; i++) { + if (mask->GetBone(i)) { + int lrIdx = GetBoneData(i).mLeftRightIdx; + + if (lrIdx > i) { + float tmp; + + pd = &mirrorPose[lrIdx * DOF]; + ps = &mirrorPose[i * DOF]; + tmp = pd[4]; + pd[4] = -ps[4]; + ps[4] = -tmp; + tmp = pd[5]; + pd[5] = -ps[5]; + ps[5] = -tmp; + tmp = pd[6]; + pd[6] = ps[6]; + ps[6] = tmp; + tmp = pd[7]; + pd[7] = ps[7]; + ps[7] = tmp; + tmp = pd[8]; + pd[8] = ps[8]; + ps[8] = tmp; + tmp = pd[9]; + pd[9] = ps[9]; + ps[9] = tmp; + tmp = pd[10]; + pd[10] = -ps[10]; + ps[10] = -tmp; + } else if (lrIdx == i) { + pd = &mirrorPose[i * DOF]; + pd[4] = -pd[4]; + pd[5] = -pd[5]; + pd[10] = -pd[10]; + } + } + } + } else { + for (i = 0; i < n; i++) { + if (mask->GetBone(i)) { + int lrIdx = GetBoneData(i).mLeftRightIdx; + + ps = &pose[i * DOF]; + pd = &mirrorPose[lrIdx * DOF]; + pd[0] = ps[0]; + pd[1] = ps[1]; + pd[2] = ps[2]; + pd[4] = -ps[4]; + pd[5] = -ps[5]; + pd[6] = ps[6]; + pd[7] = ps[7]; + pd[8] = ps[8]; + pd[9] = ps[9]; + pd[10] = -ps[10]; + } + } + } + if (!local) { + pd = mirrorPose; + UMath::Vector4 quat = *reinterpret_cast(&pd[4]); + + pd[4] = quat.z; + pd[5] = quat.w; + pd[6] = -quat.x; + pd[8] = -pd[8]; + pd[10] = -pd[10]; + pd[7] = -quat.y; + } + } else { + if (pose == mirrorPose) { + for (i = 0; i < n; i++) { + int lrIdx = GetBoneData(i).mLeftRightIdx; + + if (lrIdx > i) { + float tmp; + + pd = &mirrorPose[lrIdx * DOF]; + ps = &mirrorPose[i * DOF]; + tmp = pd[4]; + pd[4] = -ps[4]; + ps[4] = -tmp; + tmp = pd[5]; + pd[5] = -ps[5]; + ps[5] = -tmp; + tmp = pd[6]; + pd[6] = ps[6]; + ps[6] = tmp; + tmp = pd[7]; + pd[7] = ps[7]; + ps[7] = tmp; + tmp = pd[8]; + pd[8] = ps[8]; + ps[8] = tmp; + tmp = pd[9]; + pd[9] = ps[9]; + ps[9] = tmp; + tmp = pd[10]; + pd[10] = -ps[10]; + ps[10] = -tmp; + } else if (lrIdx == i) { + pd = &mirrorPose[i * DOF]; + pd[4] = -pd[4]; + pd[5] = -pd[5]; + pd[10] = -pd[10]; + } + } + } else { + for (i = 0; i < n; i++) { + int lrIdx = GetBoneData(i).mLeftRightIdx; + + ps = &pose[i * DOF]; + pd = &mirrorPose[lrIdx * DOF]; + pd[0] = ps[0]; + pd[1] = ps[1]; + pd[2] = ps[2]; + pd[4] = -ps[4]; + pd[5] = -ps[5]; + pd[6] = ps[6]; + pd[7] = ps[7]; + pd[8] = ps[8]; + pd[9] = ps[9]; + pd[10] = -ps[10]; + } + } + if (!local) { + pd = mirrorPose; + UMath::Vector4 quat = *reinterpret_cast(&pd[4]); + + pd[4] = quat.z; + pd[5] = quat.w; + pd[6] = -quat.x; + pd[8] = -pd[8]; + pd[10] = -pd[10]; + pd[7] = -quat.y; + } + } +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/StatelessF3.cpp b/src/Speed/Indep/Src/EAGL4Anim/StatelessF3.cpp index e69de29bb..bc482e6c9 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/StatelessF3.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/StatelessF3.cpp @@ -0,0 +1,16 @@ +#include "StatelessF3.h" +#include "FnStatelessF3.h" + +namespace EAGL4Anim { + +void StatelessF3::InitAnimMemoryMap(AnimMemoryMap *anim) { + StatelessF3 *statelessF3 = reinterpret_cast(anim); + FnStatelessF3 *fnStatelessF3Ptr = reinterpret_cast(statelessF3->GetFnLocation()); + + { + FnStatelessF3 fnStatelessF3; + *reinterpret_cast(fnStatelessF3Ptr) = *reinterpret_cast(&fnStatelessF3); + } +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/StatelessF3.h b/src/Speed/Indep/Src/EAGL4Anim/StatelessF3.h index 3ac7433aa..9b109fc83 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/StatelessF3.h +++ b/src/Speed/Indep/Src/EAGL4Anim/StatelessF3.h @@ -6,6 +6,7 @@ #endif #include "Speed/Indep/Src/EAGL4Anim/AnimMemoryMap.h" +#include "Speed/Indep/Src/EAGL4Anim/AnimUtil.h" namespace EAGL4Anim { @@ -17,25 +18,50 @@ struct StatelessF3 : public AnimMemoryMap { float mRange[4]; // offset 0x10, size 0x10 }; - const AttributeBlock *GetAttributeBlock() const {} + const AttributeBlock *GetAttributeBlock() const { + return mAttributeBlock; + } static int ComputeSize(int numBones, int numConst, int numKeys, bool useKeyFrames) {} - unsigned short GetNumFrames() const {} + unsigned short GetNumFrames() const { + if (!mTimes) { + return mNumKeys; + } else { + return mTimes[mNumKeys - 2] + 1; + } + } - void GetArrays(DofInfo *&dofInfo, short *&dataBuf) {} + void GetArrays(DofInfo *&dofInfo, short *&dataBuf) { + dofInfo = GetDofInfo(); + dataBuf = GetData(); + } - DofInfo *GetDofInfo() {} + DofInfo *GetDofInfo() { + return reinterpret_cast(&this[1]); + } - short *GetData() {} + short *GetData() { + return reinterpret_cast(&reinterpret_cast(GetDofInfo())[mNumBones * sizeof(DofInfo)]); + } - short *GetFrameData(short *dataBuf, int frameIdx) {} + short *GetFrameData(short *dataBuf, int frameIdx) { + return &dataBuf[frameIdx * 3 * mNumBones]; + } - float *GetConstData(short *dataBuf) {} + float *GetConstData(short *dataBuf) { + return reinterpret_cast(AlignSize4(reinterpret_cast(&dataBuf[mNumKeys * 3 * mNumBones]))); + } - unsigned short *GetConstBoneIdx() {} + unsigned short *GetConstBoneIdx() { + return &mDofIdxs[mNumBones]; + } - void UnQuantize(const DofInfo &dofInfo, const short *frameBuf, UMath::Vector3 &result) const {} + void UnQuantize(const DofInfo &dofInfo, const short *frameBuf, UMath::Vector3 &result) const { + result.x = dofInfo.mRange[0] * frameBuf[0]; + result.y = dofInfo.mRange[1] * frameBuf[1]; + result.z = dofInfo.mRange[2] * frameBuf[2]; + } static int GetFnOffset() { return 24; diff --git a/src/Speed/Indep/Src/EAGL4Anim/StatelessQ.cpp b/src/Speed/Indep/Src/EAGL4Anim/StatelessQ.cpp index e69de29bb..c64f1f05f 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/StatelessQ.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/StatelessQ.cpp @@ -0,0 +1,30 @@ +#include "StatelessQ.h" +#include "FnStatelessF3.h" +#include "FnStatelessQ.h" +#include "StatelessF3.h" + +namespace EAGL4Anim { + +void StatelessQ::InitAnimMemoryMap(AnimMemoryMap *anim) { + StatelessQ *statelessQ = reinterpret_cast(anim); + FnStatelessQ *fnStatelessQPtr = reinterpret_cast(statelessQ->GetFnLocation()); + + { + FnStatelessQ fnStatelessQ; + *reinterpret_cast(fnStatelessQPtr) = *reinterpret_cast(&fnStatelessQ); + } + + if (statelessQ->mF3Ptr) { + StatelessF3 *statelessF3 = reinterpret_cast(statelessQ->mF3Ptr); + FnStatelessF3 *fnStatelessF3Ptr = reinterpret_cast(statelessF3->GetFnLocation()); + + { + FnStatelessF3 fnStatelessF3; + *reinterpret_cast(fnStatelessF3Ptr) = *reinterpret_cast(&fnStatelessF3); + } + fnStatelessF3Ptr->SetAnimMemoryMap(statelessF3); + statelessQ->mF3Ptr = fnStatelessF3Ptr; + } +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/StatelessQ.h b/src/Speed/Indep/Src/EAGL4Anim/StatelessQ.h index 2176952e9..213f1e6b0 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/StatelessQ.h +++ b/src/Speed/Indep/Src/EAGL4Anim/StatelessQ.h @@ -11,17 +11,33 @@ namespace EAGL4Anim { // total size: 0x18 struct StatelessQ : public AnimMemoryMap { - const AttributeBlock *GetAttributeBlock() const {} + const AttributeBlock *GetAttributeBlock() const { + return mAttributeBlock; + } - int GetNumFrames() const {} + int GetNumFrames() const { + if (!mTimes) { + return mNumKeys; + } else { + return mTimes[mNumKeys - 2] + 1; + } + } - unsigned short *GetData() {} + unsigned short *GetData() { + return reinterpret_cast(&this[1]); + } - unsigned short *GetFrameData(unsigned short *dataBuf, int frameIdx) {} + unsigned short *GetFrameData(unsigned short *dataBuf, int frameIdx) { + return &dataBuf[frameIdx * 4 * mNumBones]; + } - unsigned short *GetConstData(unsigned short *dataBuf) {} + unsigned short *GetConstData(unsigned short *dataBuf) { + return &dataBuf[mNumKeys * 4 * mNumBones]; + } - unsigned char *GetConstBoneIdx() {} + unsigned char *GetConstBoneIdx() { + return &mBoneIdxs[mNumBones]; + } static int ComputeSize(int numKeys, int numBones, int numConstBones, bool useKeyFrames) {} diff --git a/src/Speed/Indep/Src/EAGL4Anim/SystemCmn.cpp b/src/Speed/Indep/Src/EAGL4Anim/SystemCmn.cpp index e69de29bb..725b621f6 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/SystemCmn.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/SystemCmn.cpp @@ -0,0 +1,36 @@ +#include "System.h" + +#include "FnAnimFactory.h" +#include "MemoryPoolManager.h" +#include "eagl4AnimBank.h" +#include "eagl4supportdlopen.h" + +#include + +namespace EAGL4Anim { + +namespace { + +struct MemoryPoolManagerAccessor : MemoryPoolManager { + static MemoryPoolManager *&GetDefaultMemoryManager() { + return gDefaultMemoryManager; + } + + static MemoryPoolManager *&GetMemoryManager() { + return gMemoryManager; + } +}; + +} // namespace + +void Initializer::InitInternal(size_t memorySize, bool) { + MemoryPoolManagerAccessor::GetDefaultMemoryManager() = + new (EAGL4Internal::EAGL4Malloc(sizeof(MemoryPoolManager), nullptr)) MemoryPoolManager; + MemoryPoolManagerAccessor::GetMemoryManager() = MemoryPoolManagerAccessor::GetDefaultMemoryManager(); + MemoryPoolManagerAccessor::GetDefaultMemoryManager()->InitAux(static_cast(memorySize)); + + FnAnimFactory::mpFactory = reinterpret_cast(EAGL4Internal::EAGL4Malloc(1, nullptr)); + EAGL4::DynamicLoader::gConsPool.AddType(EAGL4::DynamicLoader::AnimBankType, AnimBank::Constructor, AnimBank::Destructor); +} + +}; // namespace EAGL4Anim diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4runtimetransform.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4runtimetransform.cpp index e69de29bb..4380eff87 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4runtimetransform.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4runtimetransform.cpp @@ -0,0 +1,147 @@ +#include "Speed/Indep/Src/EAGL4Anim/eagl4runtimetransform.h" + +#include "AnimUtil.h" + +namespace EAGL4 { + +static inline void EAGL4m3toquat(const UMath::Matrix3 *mat, UMath::Vector4 *result) { + float s; + float trace = (*mat)[0][0] + (*mat)[1][1] + (*mat)[2][2]; + + if (trace > 0.0f) { + s = EAGL4Anim::FastSqrt(trace + 1.0f); + result->w = s * 0.5f; + s = 0.5f / s; + result->x = ((*mat)[1][2] - (*mat)[2][1]) * s; + result->y = ((*mat)[2][0] - (*mat)[0][2]) * s; + result->z = ((*mat)[0][1] - (*mat)[1][0]) * s; + } else { + unsigned long i = 0; + + if ((*mat)[1][1] > (*mat)[0][0]) { + i = 1; + } + if ((*mat)[2][2] > (*mat)[i][i]) { + i = 2; + } + + if (i == 0) { + s = EAGL4Anim::FastSqrt(((*mat)[0][0] - ((*mat)[1][1] + (*mat)[2][2])) + 1.0f); + result->x = s * 0.5f; + if (s != 0.0f) { + s = 0.5f / s; + } + result->w = ((*mat)[1][2] - (*mat)[2][1]) * s; + result->y = ((*mat)[0][1] + (*mat)[1][0]) * s; + result->z = ((*mat)[0][2] + (*mat)[2][0]) * s; + } else if (i == 1) { + s = EAGL4Anim::FastSqrt(((*mat)[1][1] - ((*mat)[2][2] + (*mat)[0][0])) + 1.0f); + result->y = s * 0.5f; + if (s != 0.0f) { + s = 0.5f / s; + } + result->w = ((*mat)[2][0] - (*mat)[0][2]) * s; + result->x = ((*mat)[1][0] + (*mat)[0][1]) * s; + result->z = ((*mat)[1][2] + (*mat)[2][1]) * s; + } else { + s = EAGL4Anim::FastSqrt(((*mat)[2][2] - ((*mat)[0][0] + (*mat)[1][1])) + 1.0f); + result->z = s * 0.5f; + if (s != 0.0f) { + s = 0.5f / s; + } + result->w = ((*mat)[0][1] - (*mat)[1][0]) * s; + result->x = ((*mat)[2][0] + (*mat)[0][2]) * s; + result->y = ((*mat)[2][1] + (*mat)[1][2]) * s; + } + } +} + +void MultMatrix(const UMath::Matrix4 *pm1, const UMath::Matrix4 *pm2, UMath::Matrix4 *presult) { + const float *m1 = pm1->GetElements(); + const float *m2 = pm2->GetElements(); + float *result = presult->GetElements(); + + result[0] = m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8] + m1[3] * m2[12]; + result[1] = m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9] + m1[3] * m2[13]; + result[2] = m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10] + m1[3] * m2[14]; + result[3] = m1[0] * m2[3] + m1[1] * m2[7] + m1[2] * m2[11] + m1[3] * m2[15]; + result[4] = m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8] + m1[7] * m2[12]; + result[5] = m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9] + m1[7] * m2[13]; + result[6] = m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10] + m1[7] * m2[14]; + result[7] = m1[4] * m2[3] + m1[5] * m2[7] + m1[6] * m2[11] + m1[7] * m2[15]; + result[8] = m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8] + m1[11] * m2[12]; + result[9] = m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9] + m1[11] * m2[13]; + result[10] = m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10] + m1[11] * m2[14]; + result[11] = m1[8] * m2[3] + m1[9] * m2[7] + m1[10] * m2[11] + m1[11] * m2[15]; + result[12] = m1[12] * m2[0] + m1[13] * m2[4] + m1[14] * m2[8] + m1[15] * m2[12]; + result[13] = m1[12] * m2[1] + m1[13] * m2[5] + m1[14] * m2[9] + m1[15] * m2[13]; + result[14] = m1[12] * m2[2] + m1[13] * m2[6] + m1[14] * m2[10] + m1[15] * m2[14]; + result[15] = m1[12] * m2[3] + m1[13] * m2[7] + m1[14] * m2[11] + m1[15] * m2[15]; +} + +void Transform::PostMult(const Transform &second, Transform *pOutput) const { + MultMatrix(&m, &second.m, &pOutput->m); +} + +void Transform::PostMult(const Transform &second) { + Transform result; + + MultMatrix(&m, &second.m, &result.m); + m = result.m; +} + +void Transform::ExtractQuatTrans(UMath::Vector4 *retQuat, UMath::Vector4 *retTrans) const { + UMath::Matrix3 m3; + + m3.v0.x = m.v0.x; + m3.v0.y = m.v0.y; + m3.v0.z = m.v0.z; + m3.v1.x = m.v1.x; + m3.v1.y = m.v1.y; + m3.v1.z = m.v1.z; + m3.v2.x = m.v2.x; + m3.v2.y = m.v2.y; + m3.v2.z = m.v2.z; + + EAGL4m3toquat(&m3, retQuat); + + retTrans->x = m.v3.x; + retTrans->y = m.v3.y; + retTrans->z = m.v3.z; + retTrans->w = m.v3.w; +} + +void Transform::BuildSQT(float sx, float sy, float sz, float qx, float qy, float qz, float qw, float tx, float ty, float tz) { + float s = 1.0f; + float xs = qx + qx; + float ys = qy + qy; + float zs = qz + qz; + float wx = qw * xs; + float wy = qw * ys; + float wz = qw * zs; + float xx = qx * xs; + float xy = qx * ys; + float xz = qx * zs; + float yy = qy * ys; + float yz = qy * zs; + float zz = qz * zs; + + m.v3.x = tx; + m.v3.y = ty; + m.v3.w = s; + m.v2.w = 0.0f; + m.v0.w = 0.0f; + m.v1.w = 0.0f; + m.v3.z = tz; + m.v0.z = sx * (xz - wy); + m.v1.z = sy * (yz + wx); + m.v2.z = sz * (s - (xx + yy)); + m.v0.x = sx * (s - (yy + zz)); + m.v1.x = sy * (xy - wz); + m.v2.x = sz * (xz + wy); + m.v0.y = sx * (xy + wz); + m.v1.y = sy * (s - (xx + zz)); + m.v2.y = sz * (yz - wx); +} + +}; // namespace EAGL4 diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportconspool.h b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportconspool.h index f2fa079a2..948070d82 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportconspool.h +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportconspool.h @@ -22,13 +22,13 @@ struct DestructorEntry { void *data; // offset 0x4, size 0x4 }; -typedef void *(*RuntimeAllocConstructor)(const char *, class DynamicLoader *, int &, bool &, const char *); +typedef void *(*RuntimeAllocConstructor)(const char *, class EAGL4::DynamicLoader *, int &, bool &, const char *); typedef void (*RuntimeAllocDestructor)(void *, int); // TODO wrong namespace // total size: 0x10 struct RuntimeAllocDestructorEntry { - // void *operator new(size_t size) {} + void *operator new(size_t size); // void *operator new(size_t size, const char *msg) {} @@ -44,10 +44,7 @@ struct RuntimeAllocDestructorEntry { // void *operator new(size_t, void *ptr) {} - RuntimeAllocDestructorEntry(RuntimeAllocDestructor d, void *data, int auxData) - : d(d), // - data(data), // - auxData(auxData) {} + RuntimeAllocDestructorEntry(RuntimeAllocDestructor d, void *data, int auxData); RuntimeAllocDestructor d; // offset 0x0, size 0x4 void *data; // offset 0x4, size 0x4 diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp index 28f90eacf..3843682e7 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.cpp @@ -5,8 +5,81 @@ #include #include +static void *dlsym(void *handle, const char *name); + +inline void *RuntimeAllocDestructorEntry::operator new(size_t size) { + return EAGL4Internal::EAGL4Malloc(size, nullptr); +} + +inline RuntimeAllocDestructorEntry::RuntimeAllocDestructorEntry(RuntimeAllocDestructor d, void *data, int auxData) + : d(d), // + data(data), // + auxData(auxData) {} + namespace EAGL4 { +namespace { + +static const char kSymTabName[] = ".symtab"; +static const char kStrTabName[] = ".strtab"; +static const char kNamespaceMarker[] = ":::"; +static const char kDynamicSymbolsMsg[] = "EAGL4::dynamic symbols"; +static const char kDynamicSymbols2Msg[] = "EAGL4::dynamic symbols 2"; +static const char gRuntimeAllocType[] = "RUNTIME_ALLOC::"; + +static inline unsigned short htotus(unsigned short s) { + return static_cast((s << 8) | (s >> 8)); +} + +static inline unsigned int htotul(unsigned int l) { + unsigned int ul; + + ul = htotus(static_cast(l)); + ul <<= 16; + ul |= htotus(static_cast(l >> 16)); + return ul; +} + +static inline unsigned int ByteSwap32(unsigned int value) { + return ((value >> 16) & 0xFF) << 8 | (value >> 24) | (((value & 0xFF) << 8 | ((value & 0xFFFF) >> 8)) << 16); +} + +} // namespace + +inline void *HashPointer::operator new(size_t size) { + return EAGL4Internal::EAGL4Malloc(size, nullptr); +} + +inline HashPointer::HashPointer(DynamicLoader *pDL) { + e = nullptr; + mpDynamicLoader = pDL; + pSearchFunction = nullptr; + next = nullptr; + prev = nullptr; + strtab = nullptr; + resolved = false; + symbols_num = 0; + symtab = nullptr; + sections = nullptr; + chain = nullptr; + isOriginal = nullptr; +} + +inline void *DynamicLoader::ELFAddr(unsigned int offset) { + if (offset < mDataLen) { + return mpData + offset; + } + if (mpReloc) { + return mpReloc + (offset - mDataLen); + } + if (mDataLen < offset) { + return nullptr; + } + return mpData + offset; +} + +static HashPointer *hashhead; + SymbolPool DynamicLoader::gSymbolPool; ConstructorPool DynamicLoader::gConsPool; RuntimeAllocConstructorPool DynamicLoader::gRuntimeAllocConsPool; @@ -62,6 +135,332 @@ bool DynamicLoader::DoVersionCheck() { return true; } +void DynamicLoader::Resolve() { + int i; + int j; + HashPointer *h; + ELFSectionHeader *sheader; + ELFHeader *e; + const int MAX_UNRESOLVED_ERRORS = 32; + bool unresolvedSymbolError; + char *unresolvedList[32]; + int numUnresolved; + + h = reinterpret_cast(handle); + if (!h || h->resolved) { + return; + } + + e = h->e; + h->resolved = true; + sheader = h->sections; + unresolvedSymbolError = false; + numUnresolved = 0; + +retry: + ; + + for (i = 0; i < e->e_shnum; i++) { + int relocations; + char *patchbase; + ELF32_Rel *r; + ELF32_Sym *symtab; + + if (sheader[i].sh_type != SHT_REL) { + continue; + } + + relocations = static_cast(sheader[i].sh_size); + if (relocations < 0) { + relocations += 7; + } + patchbase = reinterpret_cast(sheader[i].sh_vinfo); + r = reinterpret_cast(sheader[i].sh_voffset); + symtab = reinterpret_cast(sheader[i].sh_vlink); + relocations >>= 3; + + for (j = 0; j < relocations; j++) { + unsigned int baseaddr = 0; + int iIndex = j; + ELF32_Sym *sym; + unsigned int *patchaddr; + unsigned short *patchaddr16; + + r[j].r_offset = htotul(r[j].r_offset); + r[j].r_info = htotul(r[j].r_info); + sym = &symtab[r[j].r_info >> 8]; + + while ((sym->st_info & 0xF) < STT_FILE) { + if (sym->st_shndx && sym->st_shndx < e->e_shnum) { + baseaddr = reinterpret_cast(sheader[sym->st_shndx].sh_voffset); + break; + } + + HashPointer *hp; + + for (hp = hashhead; hp; hp = hp->next) { + if (hp != h) { + void *addr = dlsym(hp, &h->strtab[sym->st_name]); + + if (addr) { + sym->st_shndx = 1; + sym->st_other = 2; + baseaddr = reinterpret_cast(sheader[1].sh_voffset); + sym->st_value = reinterpret_cast(addr) - baseaddr; + break; + } + } + } + + if (hp) { + break; + } + + if (h->pSearchFunction) { + bool valid; + void *addr = h->pSearchFunction(&h->strtab[sym->st_name], valid); + + if (valid) { + sym->st_shndx = 1; + sym->st_other = 3; + baseaddr = reinterpret_cast(sheader[1].sh_voffset); + sym->st_value = reinterpret_cast(addr) - baseaddr; + break; + } + } + + { + bool valid; + void *addr = gSymbolPool.Search(&h->strtab[sym->st_name], valid); + + if (valid) { + sym->st_shndx = 1; + sym->st_other = 4; + baseaddr = reinterpret_cast(sheader[1].sh_voffset); + sym->st_value = reinterpret_cast(addr) - baseaddr; + break; + } + } + + { + Symbol s; + + s.name = &h->strtab[sym->st_name]; + s.type = s.name + strlen(s.name) + 1; + if (s.type[0] == 0x7F) { + s.type++; + } else { + s.type--; + } + if (strncmp(gRuntimeAllocType, s.name, strlen(gRuntimeAllocType)) == 0) { + const char *stripped_name; + RuntimeAllocConstructor c; + + stripped_name = s.name + strlen(gRuntimeAllocType); + c = gRuntimeAllocConsPool.FindConstructor(s.type); + + if (c) { + RuntimeAllocDestructor d = gRuntimeAllocConsPool.FindDestructor(s.type); + int auxData; + bool bCallDestructor = false; + s.data = c(stripped_name, reinterpret_cast(this), auxData, bCallDestructor, s.name); + + if (bCallDestructor && s.data) { + RuntimeAllocDestructorEntry *de = new RuntimeAllocDestructorEntry(d, s.data, auxData); + de->next = RuntimeAllocDestructors; + RuntimeAllocDestructors = de; + } + + sym->st_shndx = 1; + sym->st_other = 5; + baseaddr = reinterpret_cast(sheader[1].sh_voffset); + sym->st_value = reinterpret_cast(s.data) - baseaddr; + break; + } + } + + if (!unresolvedSymbolError) { + unresolvedSymbolError = true; + } + if (numUnresolved < MAX_UNRESOLVED_ERRORS) { + bool found = false; + + for (j = 0; j < numUnresolved; j++) { + if (&h->strtab[sym->st_name] == unresolvedList[j]) { + found = true; + break; + } + } + + if (!found) { + unresolvedList[numUnresolved++] = &h->strtab[sym->st_name]; + } + j = iIndex; + } + } + break; + } + + baseaddr += sym->st_value; + patchaddr = reinterpret_cast(patchbase + r[j].r_offset); + *patchaddr = htotul(*patchaddr); + patchaddr16 = reinterpret_cast(patchaddr); + + switch (r[j].r_info & 0xFF) { + case R_MIPS_32: + *patchaddr += baseaddr; + break; + + case R_MIPS_26: + *patchaddr = (*patchaddr & 0xFC000000) | ((baseaddr + ((*patchaddr & 0x03FFFFFF) * 4) & 0x0FFFFFFF) >> 2); + break; + + case R_MIPS_HI16: + *patchaddr16 = static_cast(*patchaddr16 + static_cast(baseaddr >> 16)); + continue; + + case R_MIPS_LO16: + *patchaddr16 = static_cast(*patchaddr16 + static_cast(baseaddr)); + continue; + } + } + } + + RunConstructors(); + mIsResolved = true; +} + +void DynamicLoader::Initialize(DynamicUserCallback pSearchFunction) { + int i; + HashPointer *pHP = new HashPointer(this); + HashPointer &h = *pHP; + ELFSectionHeader *sheader; + ELFHeader *e; + char *shstrtab; + void *p; + + e = reinterpret_cast(mpData); + h.e = e; + + e->e_type = htotus(e->e_type); + e->e_machine = htotus(e->e_machine); + e->e_version = htotul(e->e_version); + e->e_entry = htotul(e->e_entry); + e->e_phoff = htotul(e->e_phoff); + e->e_shoff = htotul(e->e_shoff); + e->e_flags = htotul(e->e_flags); + e->e_ehsize = htotus(e->e_ehsize); + e->e_phentsize = htotus(e->e_phentsize); + e->e_phnum = htotus(e->e_phnum); + e->e_shentsize = htotus(e->e_shentsize); + e->e_shnum = htotus(e->e_shnum); + e->e_shstrndx = htotus(e->e_shstrndx); + + p = ELFAddr(e->e_shoff); + h.sections = reinterpret_cast(p); + + for (i = 0; i < e->e_shnum; i++) { + sheader = &h.sections[i]; + + sheader->sh_name = htotul(sheader->sh_name); + sheader->sh_type = htotul(sheader->sh_type); + sheader->sh_flags = htotul(sheader->sh_flags); + sheader->sh_addr = htotul(sheader->sh_addr); + sheader->sh_offset = htotul(sheader->sh_offset); + sheader->sh_size = htotul(sheader->sh_size); + sheader->sh_link = htotul(sheader->sh_link); + sheader->sh_info = htotul(sheader->sh_info); + sheader->sh_addralign = htotul(sheader->sh_addralign); + sheader->sh_entsize = htotul(sheader->sh_entsize); + + p = ELFAddr(sheader->sh_offset); + sheader->sh_voffset = p; + } + + shstrtab = reinterpret_cast(h.sections[e->e_shstrndx].sh_voffset); + + for (i = 0; i < e->e_shnum; i++) { + sheader = &h.sections[i]; + + if (sheader->sh_type == SHT_STRTAB) { + sheader->sh_vlink = h.sections[sheader->sh_link].sh_voffset; + if (strcmp(kStrTabName, shstrtab + sheader->sh_name) == 0) { + char *s = reinterpret_cast(sheader->sh_voffset); + int len = sheader->sh_size; + const char *TYPE_SEPARATOR = kNamespaceMarker; + int slen; + + h.strtab = s; + while (len > 0) { + slen = strlen(s); + if (s[0] == '_' && s[1] == '_') { + char *t = strstr(s + 2, TYPE_SEPARATOR); + if (t) { + char typebuf[128]; + char *type_separator = t + strlen(TYPE_SEPARATOR); + unsigned int nameLength; + + *t = 0; + strcpy(typebuf, s + 2); + nameLength = strlen(type_separator); + memmove(s, type_separator, nameLength + 1); + s[nameLength + 1] = '\x7F'; + strcpy(s + nameLength + 2, typebuf); + } + } + s += slen + 1; + len -= slen + 1; + } + } + } else if (sheader->sh_type == SHT_SYMTAB || sheader->sh_type == SHT_STRTAB) { + sheader->sh_vlink = h.sections[sheader->sh_link].sh_voffset; + if (strcmp(kSymTabName, shstrtab + sheader->sh_name) == 0) { + h.symtab = reinterpret_cast(sheader->sh_voffset); + h.symbols_num = static_cast(sheader->sh_size >> 4); + } + } else if (sheader->sh_type == SHT_REL) { + sheader->sh_vinfo = h.sections[sheader->sh_info].sh_voffset; + sheader->sh_vlink = h.sections[sheader->sh_link].sh_voffset; + } + } + + h.pSearchFunction = pSearchFunction; + + for (i = 0; i < 0x100; i++) { + h.hash[i] = static_cast(-1); + } + + h.chain = reinterpret_cast(EAGL4Internal::EAGL4Malloc(h.symbols_num * sizeof(unsigned long), kDynamicSymbolsMsg)); + h.isOriginal = reinterpret_cast(EAGL4Internal::EAGL4Malloc(h.symbols_num * sizeof(unsigned long), kDynamicSymbols2Msg)); + + for (i = 0; i < h.symbols_num; i++) { + ELF32_Sym *sym = &h.symtab[i]; + + sym->st_name = htotul(sym->st_name); + sym->st_value = htotul(sym->st_value); + sym->st_size = htotul(sym->st_size); + sym->st_shndx = htotus(sym->st_shndx); + + unsigned long hash = elfhash(h.strtab + sym->st_name); + h.chain[i] = h.hash[hash]; + h.hash[hash] = i; + + if (sym->st_shndx == 0 || h.e->e_shnum <= sym->st_shndx) { + h.isOriginal[i] = false; + } else { + h.isOriginal[i] = true; + } + } + + h.next = hashhead; + if (hashhead) { + hashhead->prev = &h; + } + h.prev = nullptr; + hashhead = &h; + handle = &h; +} + DynamicLoader::~DynamicLoader() { Release(); RunDestructors(); @@ -71,13 +470,9 @@ DynamicLoader::~DynamicLoader() { mpPatchAddresses32 = nullptr; } -// TODO where does this go? -static HashPointer *hashhead; - void DynamicLoader::Release() { - if (handle) { - HashPointer *h = reinterpret_cast(handle); - + HashPointer *h = reinterpret_cast(handle); + if (h) { if (h->prev) { h->prev->next = h->next; } else { @@ -96,9 +491,9 @@ void DynamicLoader::Release() { EAGL4Internal::EAGL4Free(h->isOriginal, h->symbols_num * sizeof(uintptr_t)); } - // TODO how to avoid the null check? - delete h; - handle = nullptr; + EAGL4Internal::EAGL4Free(h, sizeof(HashPointer)); + h = nullptr; + handle = h; } mIsResolved = false; } @@ -170,33 +565,29 @@ int DynamicLoader::GetCount() const { // TODO DynamicLoader::Symbol DynamicLoader::GetSymbol(int i) const { DynamicLoader::Symbol r; - HashPointer *h = reinterpret_cast(handle); - if (!h) { - // r.name = nullptr; - r.type = nullptr; - // r.data = nullptr; - r.isInternalRef = false; + HashPointer *h; + ELF32_Sym *s; + int iIndex; + + r.name = nullptr; + r.data = nullptr; + if (!handle) { return r; } - ELF32_Sym *s = h->symtab; + h = reinterpret_cast(handle); + s = h->symtab; if (i < 0 || i >= h->symbols_num) { - // r.name = nullptr; - r.type = nullptr; - // r.data = nullptr; - r.isInternalRef = false; return r; } r.name = &h->strtab[s[i].st_name]; - r.type = &r.name[strlen(&h->strtab[s[i].st_name])]; - r.type++; - if (r.type[1] == 0x7F) { + r.type = &h->strtab[s[i].st_name] + strlen(r.name) + 1; + if (r.type[0] == 0x7F) { r.type++; } else { r.type--; } - r.isInternalRef = (s[i].st_other - 2) > 3; - - int iIndex = s[i].st_shndx; + r.isInternalRef = static_cast(s[i].st_other) - 2 > 3; + iIndex = s[i].st_shndx; if (s[i].st_other == 1) { r.data = reinterpret_cast(s[i].st_value); } else if (iIndex > 0 && iIndex < h->e->e_shnum) { diff --git a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.h b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.h index 5635cc2e5..eeb5bdecc 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.h +++ b/src/Speed/Indep/Src/EAGL4Anim/eagl4supportdlopen.h @@ -19,11 +19,10 @@ class DynamicLoader { public: // total size: 0x10 struct Symbol { - // TODO is this constructor correct? or is it empty maybe? - Symbol() - : name(nullptr), // - data(nullptr) // - {} + Symbol() { + name = nullptr; + data = nullptr; + } const char *name; // offset 0x0, size 0x4 const char *type; // offset 0x4, size 0x4 @@ -54,7 +53,7 @@ class DynamicLoader { // bool IsResolved() {} - // void *ELFAddr(unsigned int offset) {} + void *ELFAddr(unsigned int offset); DynamicLoader(void *d, unsigned int len, DynamicUserCallback pSearchFunction); @@ -253,7 +252,7 @@ enum ELF32_SHN_TYPES { SHN_UNDEF = 0, SHN_MIPS_ACCOMON = 65280, SHN_ABS = 65521, // total size: 0x430 struct HashPointer { - // void *operator new(size_t size) {} + void *operator new(size_t size); // void *operator new(size_t size, const char *msg) {} @@ -277,9 +276,7 @@ struct HashPointer { return *mpDynamicLoader; } - HashPointer(DynamicLoader *pDL) { - mpDynamicLoader = pDL; - } + HashPointer(DynamicLoader *pDL); ~HashPointer() {} diff --git a/src/Speed/Indep/Src/EAGL4Anim/system.cpp b/src/Speed/Indep/Src/EAGL4Anim/system.cpp index e69de29bb..9433f7fc3 100644 --- a/src/Speed/Indep/Src/EAGL4Anim/system.cpp +++ b/src/Speed/Indep/Src/EAGL4Anim/system.cpp @@ -0,0 +1,11 @@ +#include "eagl4runtimetransform.h" + +#include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "dolphin/mtx44_ext.h" + +static void MtxMult(EAGL4::Transform *result, const EAGL4::Transform *A, const EAGL4::Transform *B) { + EAGL4::Transform newMtx; + + bMulMatrix(reinterpret_cast(&newMtx), reinterpret_cast(A), reinterpret_cast(B)); + bCopy(reinterpret_cast(result), reinterpret_cast(&newMtx)); +} diff --git a/src/Speed/Indep/Src/Ecstasy/Ecstasy.hpp b/src/Speed/Indep/Src/Ecstasy/Ecstasy.hpp index 6d8c4dd38..20809cac3 100644 --- a/src/Speed/Indep/Src/Ecstasy/Ecstasy.hpp +++ b/src/Speed/Indep/Src/Ecstasy/Ecstasy.hpp @@ -184,6 +184,10 @@ struct eView : public eViewPlatInterface { this->Active = state; } + int IsActive() const { + return Active; + } + CameraMover *GetCameraMover() { if (!this->CameraMoverList.IsEmpty()) { return this->CameraMoverList.GetHead(); @@ -229,6 +233,13 @@ enum ScreenEffectControl { SEC_FRAME = 0, }; +enum ScreenEffectPalette { + EFX_CAMERA_FLASH = 0, + EFX_TUNNEL = 1, + EFX_UNIQUE = 2, + EFX_NUMBER = 3, +}; + struct ScreenEffectInf { // total size: 0xC ScreenEffectControl Controller; // offset 0x0, size 0x4 @@ -247,6 +258,14 @@ struct ScreenEffectDef { struct ScreenEffectDB *); // offset 0x4C, size 0x4 }; +struct ScreenEffectPaletteDef { + // total size: 0x10C + int NumEffects; // offset 0x0, size 0x4 + ScreenEffectType SE_type[3]; // offset 0x4, size 0xC + ScreenEffectDef SE_Def[3]; // offset 0x10, size 0xF0 + ScreenEffectControl SE_Controller[3]; // offset 0x100, size 0xC +}; + struct ScreenEffectDB { // total size: 0x1E8 eView *MyView; // offset 0x0, size 0x4 @@ -256,6 +275,18 @@ struct ScreenEffectDB { float SE_time; // offset 0x1E4, size 0x4 ScreenEffectDB(); + void Update(float deltatime); + void AddScreenEffect(ScreenEffectType type, float intensity, float r, float g, float b); + void AddScreenEffect(ScreenEffectType type, ScreenEffectDef *info, unsigned int lock, ScreenEffectControl controller); + void AddPaletteEffect(ScreenEffectPalette palette); + void AddPaletteEffect(ScreenEffectPaletteDef *palette); + float GetIntensity(ScreenEffectType type); + float GetDATA(ScreenEffectType type, int index); + void SetDATA(ScreenEffectType type, float data, int index); + + void SetController(ScreenEffectType type, ScreenEffectControl SEC) { + SE_inf[type].Controller = SEC; + } void SetMyView(eView *view) { MyView = view; @@ -271,6 +302,8 @@ struct ePoly { unsigned char flags; // offset 0x90, size 0x1 unsigned char Flailer; // offset 0x91, size 0x1 + ePoly(); + void *operator new(size_t size) {} void operator delete(void *ptr) {} @@ -312,90 +345,6 @@ enum RainWindType { POINT_WIND = 0, }; -// total size: 0x47C -struct Rain { - OnScreenRain OSrain; // offset 0x0, size 0x4 - int NoRain; // offset 0x4, size 0x4 - int NoRainAhead; // offset 0x8, size 0x4 - int inTunnel; // offset 0xC, size 0x4 - int inOverpass; // offset 0x10, size 0x4 - void *the_zone; // offset 0x14, size 0x4 - bVector3 outvex; // offset 0x18, size 0x10 - bVector2 twoDpos; // offset 0x28, size 0x8 - bVector3 CamVelLOCAL; // offset 0x30, size 0x10 - CurtainStatus IsValidRainCurtainPos; // offset 0x40, size 0x4 - int renderCount; // offset 0x44, size 0x4 - private: - eView *MyView; // offset 0x48, size 0x4 - float intensity; // offset 0x4C, size 0x4 - float CloudIntensity; // offset 0x50, size 0x4 - uint32 DesiredActive; // offset 0x54, size 0x4 - uint32 NewSwapBuffer; // offset 0x58, size 0x4 - uint32 OldSwapBuffer; // offset 0x5C, size 0x4 - int32 NumRainPoints; // offset 0x60, size 0x4 - unsigned int NumOfType[2]; // offset 0x64, size 0x8 - unsigned int DesiredNumOfType[2]; // offset 0x6C, size 0x8 - float Percentages[2]; // offset 0x74, size 0x8 - float precipWindEffect[2][2]; // offset 0x7C, size 0x10 - TextureInfo *texture_info[2]; // offset 0x8C, size 0x8 - bVector3 OldCarDirection; // offset 0x94, size 0x10 - bVector3 precipRadius[2]; // offset 0xA4, size 0x20 - bVector3 precipSpeedRange[2]; // offset 0xC4, size 0x20 - float precipZconstant[2]; // offset 0xE4, size 0x8 - RainWindType windType[2]; // offset 0xEC, size 0x8 - float CameraSpeed; // offset 0xF4, size 0x4 - bVector3 windSpeed; // offset 0xF8, size 0x10 - bVector3 DesiredwindSpeed; // offset 0x108, size 0x10 - float DesiredWindTime; // offset 0x118, size 0x4 - float windTime; // offset 0x11C, size 0x4 - uint32 fogR; // offset 0x120, size 0x4 - uint32 fogG; // offset 0x124, size 0x4 - uint32 fogB; // offset 0x128, size 0x4 - bVector2 aabbMax; // offset 0x12C, size 0x8 - bVector2 aabbMin; // offset 0x134, size 0x8 - ePoly PRECIPpoly[2]; // offset 0x13C, size 0x128 - bMatrix4 local2world; // offset 0x264, size 0x40 - bMatrix4 world2localrot; // offset 0x2A4, size 0x40 - float LenModifier; // offset 0x2E4, size 0x4 - float DesiredIntensity; // offset 0x2E8, size 0x4 - float DesiredCloudyness; // offset 0x2EC, size 0x4 - float DesiredRoadDampness; // offset 0x2F0, size 0x4 - float RoadDampness; // offset 0x2F4, size 0x4 - float percentPrecip[2]; // offset 0x2F8, size 0x8 - bVector3 PrevailingWindSpeed; // offset 0x300, size 0x10 - float WeatherTime; // offset 0x310, size 0x4 - float DesiredWeatherTime; // offset 0x314, size 0x4 - bVector3 Velocities[10][2]; // offset 0x318, size 0x140 - bVector2 ent0; // offset 0x458, size 0x8 - bVector2 ent1; // offset 0x460, size 0x8 - bVector2 ext0; // offset 0x468, size 0x8 - bVector2 ext1; // offset 0x470, size 0x8 - uint8 entFLAG; // offset 0x478, size 0x1 - uint8 extFLAG; // offset 0x479, size 0x1 - - public: - Rain(eView *view, RainType StartType); - void Init(RainType type, float percent); - - float GetRainIntensity() {} - - float GetCloudIntensity() { - return this->CloudIntensity; - } - - float GetRoadDampness() {} - - void GetPrecipFogColour(unsigned int *r, unsigned int *g, unsigned int *b) {} - - void SetPrecipFogColour(unsigned int r, unsigned int g, unsigned int b) {} - - float GetAmount(RainType type) {} - - void SetRoadDampness(float damp) {} - - bVector3 *GetWind() {} -}; - struct FacePixelation { // total size: 0xC struct eView *MyView; // offset 0x0, size 0x4 @@ -516,6 +465,7 @@ int eSmoothNormals(eSolid **solid_table, int num_solids); extern eLoadedSolidStats LoadedSolidStats; extern unsigned int eFrameCounter; +extern int WaitUntilRenderingDoneDisabled; inline unsigned int eGetFrameCounter() { return eFrameCounter; @@ -525,4 +475,12 @@ inline int eLoadStreamingSolidPack(const char *filename) { return eLoadStreamingSolidPack(filename, nullptr, nullptr, 0); } +inline void DisableWaitUntilRenderingDone() { + WaitUntilRenderingDoneDisabled = 1; +} + +inline void EnableWaitUntilRenderingDone() { + WaitUntilRenderingDoneDisabled = 0; +} + #endif diff --git a/src/Speed/Indep/Src/Ecstasy/EcstasyData.hpp b/src/Speed/Indep/Src/Ecstasy/EcstasyData.hpp index 65a4c20dc..144eb246e 100644 --- a/src/Speed/Indep/Src/Ecstasy/EcstasyData.hpp +++ b/src/Speed/Indep/Src/Ecstasy/EcstasyData.hpp @@ -195,6 +195,9 @@ struct ePositionMarker { void EndianSwap() {} }; +struct eModel; +struct eLightContext; + class eViewPlatInfo; // total size: 0x4 @@ -212,6 +215,7 @@ class eViewPlatInterface { static eViewPlatInfo *GimmeMyViewPlatInfo(int view_id); eVisibleState GetVisibleStateGB(const bVector3 *aabb_min, const bVector3 *aabb_max, bMatrix4 *local_world); eVisibleState GetVisibleStateSB(const bVector3 *aabb_min, const bVector3 *aabb_max, bMatrix4 *local_world); + void Render(eModel *model, bMatrix4 *local_to_world, eLightContext *light_context, unsigned int flags, bMatrix4 *blending_matricies); }; struct eLoadedSolidStats { diff --git a/src/Speed/Indep/Src/Misc/ResourceLoader.hpp b/src/Speed/Indep/Src/Misc/ResourceLoader.hpp index 5b5ae2cd3..07c3c9b73 100644 --- a/src/Speed/Indep/Src/Misc/ResourceLoader.hpp +++ b/src/Speed/Indep/Src/Misc/ResourceLoader.hpp @@ -187,6 +187,11 @@ void EndianSwapChunkHeader(bChunk *chunk); void EndianSwapChunkHeadersRecursive(bChunk *chunks, int sizeof_chunks); void EndianSwapChunkHeadersRecursive(bChunk *first_chunk, bChunk *last_chunk); +int ServiceResourceLoading(); +ResourceFile *CreateResourceFile(const char *filename, ResourceFileType type, int flags, int flag_offset, int file_size); +void UnloadResourceFile(ResourceFile *resource_file); +void SetDelayedResourceCallback(void (*callback)(void *), void *param); + extern int ChunkMovementOffset; // size: 0x4 inline ResourceFile *LoadResourceFile(const char *filename, ResourceFileType type, int flags) { @@ -207,4 +212,8 @@ inline int GetChunkMovementOffset() { return ChunkMovementOffset; } +inline void SetDelayedResourceCallback(void (*callback)(int), int param) { + SetDelayedResourceCallback(reinterpret_cast(callback), reinterpret_cast(param)); +} + #endif diff --git a/src/Speed/Indep/Src/World/Clans.cpp b/src/Speed/Indep/Src/World/Clans.cpp index e69de29bb..49ead4c81 100644 --- a/src/Speed/Indep/Src/World/Clans.cpp +++ b/src/Speed/Indep/Src/World/Clans.cpp @@ -0,0 +1,112 @@ +#include "Clans.hpp" + +#include "Skids.hpp" +#include "Speed/Indep/bWare/Inc/bVector.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +void bInitializeBoundingBox(bVector3 *bbox_min, bVector3 *bbox_max, const bVector3 *point); + +extern int WorldTime; + +SlotPool *ClanSlotPool = 0; +bTList ClanList; + +inline void *Clan::operator new(size_t) { + return bOMalloc(ClanSlotPool); +} + +inline void Clan::operator delete(void *ptr) { + bFree(ClanSlotPool, ptr); +} + +void InitClans() { + if (!ClanSlotPool) { + ClanSlotPool = bNewSlotPool(0x48, 0x28, "ClanSlotPool", 0); + } +} + +void FlushClans() { + while (!ClanList.IsEmpty()) { + Clan *clan = ClanList.GetHead(); + ClanList.Remove(clan); + delete clan; + } +} + +void CloseClans() { + if (ClanSlotPool) { + FlushClans(); + bDeleteSlotPool(ClanSlotPool); + ClanSlotPool = 0; + } +} + +Clan *GetClan(bVector3 *position) { + int cx = (static_cast(position->x * 65536.0f) >> 22) & 0xffff; + int cy = (static_cast(position->y * 65536.0f) >> 22) * 0x10000; + unsigned int hash = static_cast(cx + cy); + Clan *clan = ClanList.GetHead(); + + if (clan != ClanList.EndOfList() && clan->GetHash() != hash) { + do { + clan = clan->GetNext(); + } while (clan != ClanList.EndOfList() && clan->GetHash() != hash); + } + + if (clan != ClanList.EndOfList()) { + clan->SetLastUpdateTime(WorldTime); + ClanList.Remove(clan); + ClanList.AddHead(clan); + return clan; + } + + if (bIsSlotPoolFull(ClanSlotPool)) { + clan = ClanList.GetTail(); + ClanList.Remove(clan); + delete clan; + } + + clan = new Clan(position, hash); + ClanList.AddHead(clan); + return clan; +} + +void RenderClans(eView *view) { + ProfileNode profile_node("TODO", 0); + Clan *clan = ClanList.GetHead(); + while (clan != ClanList.EndOfList()) { + Clan *next_clan = clan->GetNext(); + int pixel_size = view->GetPixelSize(clan->GetBBoxMin(), clan->GetBBoxMax()); + + if (pixel_size > 10) { + eVisibleState visibility = view->GetVisibleState(clan->GetBBoxMin(), clan->GetBBoxMax(), 0); + if (visibility != 0) { + if (WorldTime - clan->GetLastUpdateTime() > 300) { + clan->SetLastUpdateTime(WorldTime); + ClanList.Remove(clan); + ClanList.AddHead(clan); + } + RenderSkids(view, clan); + } + } + clan = next_clan; + } +} + +Clan::Clan(bVector3 *position, unsigned int hash) + : Hash(hash) +{ + Position = *position; + bInitializeBoundingBox(&BBoxMin, &BBoxMax, position); +} + +Clan::~Clan() { + while (SkidSetList.GetHead() != SkidSetList.EndOfList()) { + bPNode *node = SkidSetList.GetHead(); + DeleteThisSkid(reinterpret_cast(node->GetpObject())); + } + + while (SkidSetList.GetHead() != SkidSetList.EndOfList()) { + SkidSetList.RemoveHead(); + } +} diff --git a/src/Speed/Indep/Src/World/Clans.hpp b/src/Speed/Indep/Src/World/Clans.hpp index 37eaefff4..a41151bd4 100644 --- a/src/Speed/Indep/Src/World/Clans.hpp +++ b/src/Speed/Indep/Src/World/Clans.hpp @@ -5,7 +5,56 @@ #pragma once #endif +#include "Speed/Indep/bWare/Inc/bList.hpp" +#include "Speed/Indep/bWare/Inc/bMath.hpp" + +struct SkidSet; +class eView; + +void bExpandBoundingBox(bVector3 *bbox_min, bVector3 *bbox_max, const bVector3 *bbox2_min, const bVector3 *bbox2_max); + +class Clan : public bTNode { + public: + // total size: 0x48 + bPList SkidSetList; // offset 0x8, size 0x8 + + private: + unsigned int Hash; // offset 0x10, size 0x4 + int LastUpdateTime; // offset 0x14, size 0x4 + bVector3 Position; // offset 0x18, size 0x10 + bVector3 BBoxMin; // offset 0x28, size 0x10 + bVector3 BBoxMax; // offset 0x38, size 0x10 + + public: + void *operator new(size_t size); + void operator delete(void *ptr); + + Clan(bVector3 *position, unsigned int hash); + ~Clan(); + bVector3 *GetBBoxMin() { + return &BBoxMin; + } + bVector3 *GetBBoxMax() { + return &BBoxMax; + } + int GetLastUpdateTime() { + return LastUpdateTime; + } + unsigned int GetHash() { + return Hash; + } + void SetLastUpdateTime(int time) { + LastUpdateTime = time; + } + void ExpandBoundingBox(bVector3 *bbox_min, bVector3 *bbox_max) { + bExpandBoundingBox(&BBoxMin, &BBoxMax, bbox_min, bbox_max); + } +}; + void InitClans(); +void FlushClans(); void CloseClans(); +Clan *GetClan(bVector3 *position); +void RenderClans(eView *view); #endif diff --git a/src/Speed/Indep/Src/World/EventManager.cpp b/src/Speed/Indep/Src/World/EventManager.cpp index e69de29bb..705c0b89d 100644 --- a/src/Speed/Indep/Src/World/EventManager.cpp +++ b/src/Speed/Indep/Src/World/EventManager.cpp @@ -0,0 +1,434 @@ +#include "Speed/Indep/Src/World/EventManager.hpp" + +#include "Speed/Indep/Src/Main/Event.h" +#include "Speed/Indep/Src/World/VisibleSection.hpp" +#include "Speed/Indep/bWare/Inc/bChunk.hpp" +#include "Speed/Indep/bWare/Inc/bDebug.hpp" +#include "Speed/Indep/bWare/Inc/bSlotPool.hpp" + +struct vAABB { + float PositionX; + float PositionY; + float PositionZ; + short ParentIndex; + short NumChildren; + float ExtentX; + float ExtentY; + float ExtentZ; + short ChildrenIndicies[10]; +}; + +struct vAABBTree { + vAABB *NodeArray; + short NumLeafNodes; + short NumParentNodes; + short TotalNodes; + short Depth; + int pad1; + + vAABB *QueryLeaf(float x, float y, float z); +}; + +struct EventTrigger { + unsigned int NameHash; + unsigned int EventID; + unsigned int Parameter; + int TrackDirectionMask; + float PositionX; + float PositionY; + float PositionZ; + float Radius; + + unsigned int GetEventID() { + return EventID; + } + + float GetRadius() { + return Radius; + } +}; + +struct EventTriggerPack : public bTNode { + int Version; + int ScenerySectionNumber; + int NumEventTriggers; + int EndianSwapped; + vAABBTree *EventTree; + EventTrigger *EventTriggerArray; +}; + +extern SlotPool *EventSlotPool; + +enum EVENT_ID { + TRIGGER_EVENT_CAR_ON_FERN = 65539, + TRIGGER_EVENT_VIEW_DRIVING_LINE = 65541, + TRIGGER_EVENT_ACTIVATE_TRAIN = 65542, + TRIGGER_EVENT_SOUND = 65543, + TRIGGER_EVENT_GUIDE_ARROW = 65544, + TRIGGER_EVENT_ACTIVATE_PLANE = 65545, + TRIGGER_EVENT_INITIATE_PURSUIT = 131072, + TRIGGER_EVENT_CALL_FOR_BACKUP = 131073, + TRIGGER_EVENT_CALL_FOR_ROADBLOCK = 131074, + TRIGGER_EVENT_STRATEGY_INITIATE = 131075, + TRIGGER_EVENT_COLLISION = 131076, + TRIGGER_EVENT_ANNOUNCE_ARREST = 131077, + TRIGGER_EVENT_STRATEGY_OUTCOME = 131078, + TRIGGER_EVENT_ROADBLOCK_UPDATE = 131079, + TRIGGER_EVENT_CANCEL_PURSUIT = 131080, + TRIGGER_EVENT_START_SIREN = 262144, + TRIGGER_EVENT_STOP_SIREN = 262145 +}; + +struct emEvent : public bTNode { + static void *operator new(size_t size) { + return bOMalloc(EventSlotPool); + } + + static void operator delete(void *ptr) { + bFree(EventSlotPool, ptr); + } + + emEvent() { + } + + int ReferenceCount; + unsigned int ID; + void *pEventTrigger; + Car *CarPtr; + int Parameter0; + int Parameter1; + int Parameter2; +}; + +typedef void (*EVENT_HANDLER_FUNC)(emEvent *); + +struct emEventHandler : public bTNode { + EVENT_HANDLER_FUNC HandlerFunction; + unsigned int StreamMask; + int ReferenceCount; + float TotalTime; +}; + +void bEndianSwap32(void *value); +void SwapEndian(vAABBTree *tree); +emEvent *emAddEvent(EVENT_ID event_id); + +static inline bNode *GetEventTriggerPackNode_EventManager(void *event_trigger_pack) { + return reinterpret_cast(event_trigger_pack); +} + +static inline int *GetEventTriggerPackWords_EventManager(void *event_trigger_pack) { + return reinterpret_cast(event_trigger_pack); +} + +static inline int GetEventTriggerPackType_EventManager(void *event_trigger_pack) { + return GetEventTriggerPackWords_EventManager(event_trigger_pack)[2]; +} + +static inline int GetEventTriggerPackSectionNumber_EventManager(void *event_trigger_pack) { + return GetEventTriggerPackWords_EventManager(event_trigger_pack)[3]; +} + +static inline int GetEventTriggerPackNumEvents_EventManager(void *event_trigger_pack) { + return GetEventTriggerPackWords_EventManager(event_trigger_pack)[4]; +} + +static inline int GetEventTriggerPackEndianSwapped_EventManager(void *event_trigger_pack) { + return GetEventTriggerPackWords_EventManager(event_trigger_pack)[5]; +} + +static inline void SetEventTriggerPackEndianSwapped_EventManager(void *event_trigger_pack, int swapped) { + GetEventTriggerPackWords_EventManager(event_trigger_pack)[5] = swapped; +} + +static inline void *GetEventTriggerPackTree_EventManager(void *event_trigger_pack) { + return reinterpret_cast(event_trigger_pack)[6]; +} + +static inline void SetEventTriggerPackTree_EventManager(void *event_trigger_pack, void *tree) { + reinterpret_cast(event_trigger_pack)[6] = tree; +} + +static inline void *GetEventTriggerPackData_EventManager(void *event_trigger_pack) { + return reinterpret_cast(event_trigger_pack)[7]; +} + +static inline void SetEventTriggerPackData_EventManager(void *event_trigger_pack, void *data) { + reinterpret_cast(event_trigger_pack)[7] = data; +} + +static inline VisibleSectionUserInfo **GetUserInfoTable_EventManager() { + return reinterpret_cast(reinterpret_cast(&TheVisibleSectionManager) + 0x60); +} + +emEvent *TriggerEventArray[41]; +SlotPool *EventSlotPool = 0; +SlotPool *EventHandlerSlotPool = 0; +int EventManagerStats[5]; +bTList EmptyEventTriggerPackList; +bTList EventTriggerPackList; +bTList EventHandlerList; +bTList MasterEventQueue; +bTList *CurrentEventQueue = &MasterEventQueue; +emEvent *CurrentlyHandlingEvent = 0; + +void emEventManagerInit() { + EventSlotPool = bNewSlotPool(0x24, 0x3C, "EventSlotPool", 0); + EventHandlerSlotPool = bNewSlotPool(0x18, 0x14, "EventHandlerSlotPool", 0); +} + +int LoaderEventManager(bChunk *bchunk) { + if (bchunk->GetID() != 0x80036000) { + return false; + } + + EventTriggerPack *trigger_pack = 0; + bChunk *chunk = &bchunk[1]; + bChunk *last_chunk = reinterpret_cast(reinterpret_cast(bchunk) + bchunk->Size) + 1; + for (; chunk != last_chunk; chunk = chunk->GetNext()) { + switch (chunk->GetID()) { + case 0x36001: { + trigger_pack = reinterpret_cast(chunk->GetAlignedData(0x10)); + if (trigger_pack->EndianSwapped == 0) { + bPlatEndianSwap(&trigger_pack->ScenerySectionNumber); + bPlatEndianSwap(&trigger_pack->Version); + bPlatEndianSwap(&trigger_pack->NumEventTriggers); + } + + if (trigger_pack->Version != 2) { + return true; + } + + VisibleSectionUserInfo *user_info = + TheVisibleSectionManager.AllocateUserInfo(trigger_pack->ScenerySectionNumber); + user_info->pEventTriggerPack = trigger_pack; + break; + } + + case 0x36002: + if (trigger_pack) { + trigger_pack->EventTree = reinterpret_cast(chunk->GetAlignedData(0x10)); + trigger_pack->EventTree->NodeArray = + reinterpret_cast(reinterpret_cast(trigger_pack->EventTree) + 4); + if (trigger_pack->EndianSwapped == 0) { + SwapEndian(trigger_pack->EventTree); + } + } + break; + + case 0x36003: + if (trigger_pack) { + trigger_pack->EventTriggerArray = reinterpret_cast(chunk->GetAlignedData(0x10)); + if (trigger_pack->EndianSwapped == 0) { + int num_triggers = static_cast(chunk->GetAlignedSize(0x10)) >> 5; + for (int n = 0; n < num_triggers; n++) { + EventTrigger *event_trigger = &trigger_pack->EventTriggerArray[n]; + bPlatEndianSwap(&event_trigger->NameHash); + bPlatEndianSwap(&event_trigger->EventID); + bPlatEndianSwap(&event_trigger->Parameter); + bPlatEndianSwap(&event_trigger->TrackDirectionMask); + bPlatEndianSwap(&event_trigger->PositionX); + bPlatEndianSwap(&event_trigger->PositionY); + bPlatEndianSwap(&event_trigger->PositionZ); + bPlatEndianSwap(&event_trigger->Radius); + } + } + } + break; + } + } + + trigger_pack->EndianSwapped = 1; + if (trigger_pack) { + if (trigger_pack->NumEventTriggers != 0 && trigger_pack->EventTree && trigger_pack->EventTriggerArray) { + EventTriggerPackList.AddTail(trigger_pack); + } else { + EmptyEventTriggerPackList.AddTail(trigger_pack); + } + } + + return true; +} + +int UnloaderEventManager(bChunk *bchunk) { + if (bchunk->GetID() != 0x80036000) { + return false; + } + + bChunk *chunk = bchunk->GetFirstChunk(); + bChunk *last_chunk = bchunk->GetLastChunk(); + if (chunk != last_chunk) { + do { + if (chunk->GetID() == 0x36001) { + EventTriggerPack *trigger_pack = reinterpret_cast(bchunk->GetAlignedData(0x10)); + if (trigger_pack->Version == 2) { + trigger_pack->Remove(); + } + + VisibleSectionUserInfo *user_info = TheVisibleSectionManager.GetUserInfo(trigger_pack->ScenerySectionNumber); + user_info->pEventTriggerPack = 0; + TheVisibleSectionManager.UnallocateUserInfo(trigger_pack->ScenerySectionNumber); + break; + } + + chunk = reinterpret_cast(reinterpret_cast(chunk) + chunk->Size) + 1; + } while (chunk != last_chunk); + } + + return true; +} + +int emAddHandler(EVENT_HANDLER_FUNC function, unsigned int stream_mask) { + if (function && stream_mask) { + for (emEventHandler *handler = EventHandlerList.GetHead(); handler != EventHandlerList.EndOfList(); + handler = handler->GetNext()) { + if (handler->HandlerFunction == function) { + handler->ReferenceCount += 1; + return 1; + } + } + + emEventHandler *handler = reinterpret_cast(bOMalloc(EventHandlerSlotPool)); + if (!handler) { + return 0; + } + + handler->HandlerFunction = function; + handler->StreamMask = stream_mask; + handler->ReferenceCount = 1; + EventHandlerList.AddTail(handler); + EventManagerStats[1] += 1; + if (EventManagerStats[1] > EventManagerStats[4]) { + EventManagerStats[4] = EventManagerStats[1]; + } + return 1; + } + + return 0; +} + +void emRemoveHandler(EVENT_HANDLER_FUNC function) { + for (emEventHandler *handler = EventHandlerList.GetHead(); handler != EventHandlerList.EndOfList(); + handler = handler->GetNext()) { + if (handler->HandlerFunction == function) { + int ref_count = handler->ReferenceCount - 1; + handler->ReferenceCount = ref_count; + if (ref_count == 0) { + if (handler->Remove()) { + bFree(EventHandlerSlotPool, handler); + } + EventManagerStats[1] -= 1; + } + return; + } + } +} + +emEvent *emAddEvent(EVENT_ID event_id) { + emEvent *event = new emEvent; + if (!event) { + return 0; + } + + bMemSet(event, 0, sizeof(emEvent)); + event->ReferenceCount = 0; + event->ID = event_id; + CurrentEventQueue->AddTail(event); + EventManagerStats[0] += 1; + return event; +} + +void emProcessAllEvents() { + bTList temp_event_queue; + bTList locked_event_queue; + emEvent *event; + + CurrentEventQueue = &temp_event_queue; + event = MasterEventQueue.GetHead(); + + while (event != MasterEventQueue.EndOfList()) { + emEvent *next_event = event->GetNext(); + int event_id = event->ID; + int event_handled = 0; + + CurrentlyHandlingEvent = event; + + for (emEventHandler *handler = EventHandlerList.GetHead(); handler != EventHandlerList.EndOfList(); + handler = handler->GetNext()) { + int handler_stream_mask = handler->StreamMask; + + if ((event_id & handler_stream_mask) != 0) { + unsigned int start_time = bGetTicker(); + + handler->HandlerFunction(event); + handler->TotalTime += bGetTickerDifference(start_time, bGetTicker()); + event_handled = 1; + } + } + + CurrentlyHandlingEvent = 0; + event->Remove(); + if (event->ReferenceCount == 0) { + delete event; + } else { + locked_event_queue.AddTail(event); + } + + event = next_event; + } + + while (!locked_event_queue.IsEmpty()) { + MasterEventQueue.AddTail(locked_event_queue.RemoveHead()); + } + while (!temp_event_queue.IsEmpty()) { + MasterEventQueue.AddTail(temp_event_queue.RemoveHead()); + } + + CurrentEventQueue = &MasterEventQueue; +} +emEvent **emTriggerEventsInSection(bVector3 *position, int section_number) { + emEvent **current_event = TriggerEventArray; + emEvent **sentinel_event = &TriggerEventArray[40]; + float x = position->x; + float y = position->y; + float z = position->z; + VisibleSectionUserInfo *user_info = TheVisibleSectionManager.GetUserInfo(section_number); + + if (user_info && user_info->pEventTriggerPack) { + EventTriggerPack *trigger_pack = user_info->pEventTriggerPack; + vAABBTree *tree = trigger_pack->EventTree; + vAABB *aabb = tree->QueryLeaf(x, y, z); + if (aabb) { + EventTrigger *root_event = trigger_pack->EventTriggerArray; + int num_hits = -aabb->NumChildren; + + for (int i = 0; i < num_hits && current_event < sentinel_event; i++) { + EventTrigger *event = &root_event[aabb->ChildrenIndicies[i]]; + float event_x = event->PositionX; + float event_z = event->PositionZ; + float event_y = event->PositionY; + float dz = bAbs(z - event_z); + float dy = bAbs(y - event_y); + float dx = bAbs(x - event_x); + float r2 = event->GetRadius(); + float dist2 = dz * dz + dx * dx + dy * dy; + + r2 *= r2; + if (dist2 < r2) { + emEvent *new_event = emAddEvent(static_cast(event->GetEventID())); + new_event->pEventTrigger = event; + *current_event = new_event; + current_event++; + } + } + } + } + + if (current_event == TriggerEventArray) { + return 0; + } + + *current_event = 0; + return TriggerEventArray; +} + diff --git a/src/Speed/Indep/Src/World/ParameterMaps.cpp b/src/Speed/Indep/Src/World/ParameterMaps.cpp index e69de29bb..7d3f81892 100644 --- a/src/Speed/Indep/Src/World/ParameterMaps.cpp +++ b/src/Speed/Indep/Src/World/ParameterMaps.cpp @@ -0,0 +1,4 @@ +#include "ParameterMaps.hpp" + +ParameterAccessorBlendByDistance TintSunRiseAccessor[2] = {"Screen Tint SunRise", "Screen Tint SunRise"}; +ParameterAccessorBlendByDistance TintMiddayAccessor[2] = {"Screen Tint Midday", "Screen Tint Midday"}; diff --git a/src/Speed/Indep/Src/World/ParameterMaps.hpp b/src/Speed/Indep/Src/World/ParameterMaps.hpp index 761f31502..e2999f03a 100644 --- a/src/Speed/Indep/Src/World/ParameterMaps.hpp +++ b/src/Speed/Indep/Src/World/ParameterMaps.hpp @@ -5,6 +5,69 @@ #pragma once #endif +#include "Speed/Indep/bWare/Inc/bList.hpp" +class ParameterMapLayer; + +class ParameterAccessor : public bTNode { + public: + ParameterAccessor(); + ParameterAccessor(const char *layer_name); + virtual ~ParameterAccessor(); + + virtual void CaptureData(float x, float y); + virtual void ClearData(); + virtual float GetDataFloat(int field_index); + virtual int GetDataInt(int field_index); + + protected: + virtual void SetUpForNewLayer(); + + ParameterMapLayer *Layer; + unsigned int AutoAttachLayerNamehash; + const char *DebugName; + void *CurrentParameterData; +}; + +class ParameterAccessorBlend : public ParameterAccessor { + public: + ParameterAccessorBlend(); + ParameterAccessorBlend(const char *layer_name); + virtual ~ParameterAccessorBlend(); + + virtual void CaptureData(float x, float y, float ratio); + virtual void ClearData(); + + protected: + virtual void SetUpForNewLayer(); + + void *LastData; + int HaveLastData; + + private: + virtual void CaptureData(float x, float y); +}; + +class ParameterAccessorBlendByDistance : public ParameterAccessorBlend { + public: + ParameterAccessorBlendByDistance(); + ParameterAccessorBlendByDistance(const char *layer_name); + virtual ~ParameterAccessorBlendByDistance(); + + virtual void CaptureData(float x, float y, float full_blend_distance); + + protected: + virtual void SetUpForNewLayer(); + + float last_x; + float last_y; + int HaveLastPosition; + + private: + virtual void CaptureData(float x, float y); +}; + +extern ParameterAccessorBlendByDistance TintSunRiseAccessor[2]; +extern ParameterAccessorBlendByDistance TintMiddayAccessor[2]; #endif diff --git a/src/Speed/Indep/Src/World/Rain.hpp b/src/Speed/Indep/Src/World/Rain.hpp index cd6926519..5d6334c36 100644 --- a/src/Speed/Indep/Src/World/Rain.hpp +++ b/src/Speed/Indep/Src/World/Rain.hpp @@ -7,6 +7,100 @@ #include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +// total size: 0x47C +struct Rain { + OnScreenRain OSrain; // offset 0x0, size 0x4 + int NoRain; // offset 0x4, size 0x4 + int NoRainAhead; // offset 0x8, size 0x4 + int inTunnel; // offset 0xC, size 0x4 + int inOverpass; // offset 0x10, size 0x4 + void *the_zone; // offset 0x14, size 0x4 + bVector3 outvex; // offset 0x18, size 0x10 + bVector2 twoDpos; // offset 0x28, size 0x8 + bVector3 CamVelLOCAL; // offset 0x30, size 0x10 + CurtainStatus IsValidRainCurtainPos; // offset 0x40, size 0x4 + int renderCount; // offset 0x44, size 0x4 + private: + eView *MyView; // offset 0x48, size 0x4 + float intensity; // offset 0x4C, size 0x4 + float CloudIntensity; // offset 0x50, size 0x4 + uint32 DesiredActive; // offset 0x54, size 0x4 + uint32 NewSwapBuffer; // offset 0x58, size 0x4 + uint32 OldSwapBuffer; // offset 0x5C, size 0x4 + int32 NumRainPoints; // offset 0x60, size 0x4 + unsigned int NumOfType[2]; // offset 0x64, size 0x8 + unsigned int DesiredNumOfType[2]; // offset 0x6C, size 0x8 + float Percentages[2]; // offset 0x74, size 0x8 + float precipWindEffect[2][2]; // offset 0x7C, size 0x10 + TextureInfo *texture_info[2]; // offset 0x8C, size 0x8 + bVector3 OldCarDirection; // offset 0x94, size 0x10 + bVector3 precipRadius[2]; // offset 0xA4, size 0x20 + bVector3 precipSpeedRange[2]; // offset 0xC4, size 0x20 + float precipZconstant[2]; // offset 0xE4, size 0x8 + RainWindType windType[2]; // offset 0xEC, size 0x8 + float CameraSpeed; // offset 0xF4, size 0x4 + bVector3 windSpeed; // offset 0xF8, size 0x10 + bVector3 DesiredwindSpeed; // offset 0x108, size 0x10 + float DesiredWindTime; // offset 0x118, size 0x4 + float windTime; // offset 0x11C, size 0x4 + uint32 fogR; // offset 0x120, size 0x4 + uint32 fogG; // offset 0x124, size 0x4 + uint32 fogB; // offset 0x128, size 0x4 + bVector2 aabbMax; // offset 0x12C, size 0x8 + bVector2 aabbMin; // offset 0x134, size 0x8 + ePoly PRECIPpoly[2]; // offset 0x13C, size 0x128 + bMatrix4 local2world; // offset 0x264, size 0x40 + bMatrix4 world2localrot; // offset 0x2A4, size 0x40 + float LenModifier; // offset 0x2E4, size 0x4 + float DesiredIntensity; // offset 0x2E8, size 0x4 + float DesiredCloudyness; // offset 0x2EC, size 0x4 + float DesiredRoadDampness; // offset 0x2F0, size 0x4 + float RoadDampness; // offset 0x2F4, size 0x4 + float percentPrecip[2]; // offset 0x2F8, size 0x8 + bVector3 PrevailingWindSpeed; // offset 0x300, size 0x10 + float WeatherTime; // offset 0x310, size 0x4 + float DesiredWeatherTime; // offset 0x314, size 0x4 + bVector3 Velocities[10][2]; // offset 0x318, size 0x140 + bVector2 ent0; // offset 0x458, size 0x8 + bVector2 ent1; // offset 0x460, size 0x8 + bVector2 ext0; // offset 0x468, size 0x8 + bVector2 ext1; // offset 0x470, size 0x8 + uint8 entFLAG; // offset 0x478, size 0x1 + uint8 extFLAG; // offset 0x479, size 0x1 + + public: + Rain(eView *view, RainType StartType); + void Init(RainType type, float percent); + + float GetRainIntensity() { + return intensity; + } + + float GetCloudIntensity() { + return CloudIntensity; + } + + float GetRoadDampness() { + return RoadDampness; + } + + void SetPrecipFogColour(unsigned int r, unsigned int g, unsigned int b) {} + + float GetAmount(RainType type) {} + + void SetRoadDampness(float damp) {} + + bVector3 *GetWind() {} + + void GetPrecipFogColour(unsigned int *r, unsigned int *g, unsigned int *b) { + *r = this->fogR; + *g = this->fogG; + *b = this->fogB; + } + + void AttachRainCurtain(float x0, float y0, float z0, float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3); +}; + int AmIinATunnel(eView *view, int CheckOverPass); int AmIinATunnelSlow(eView *view, int CheckOverPass); void SetRainBase(); diff --git a/src/Speed/Indep/Src/World/Scenery.cpp b/src/Speed/Indep/Src/World/Scenery.cpp index e69de29bb..a86f9dcc8 100644 --- a/src/Speed/Indep/Src/World/Scenery.cpp +++ b/src/Speed/Indep/Src/World/Scenery.cpp @@ -0,0 +1,1400 @@ +#include "Scenery.hpp" + +#include "Speed/Indep/Libs/Support/Utility/UStandard.h" +#include "VisibleSection.hpp" +#include "Speed/Indep/Src/Camera/Camera.hpp" +#include "Speed/Indep/Src/Ecstasy/eMath.hpp" +#include "Speed/Indep/Src/Ecstasy/eLight.hpp" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Src/Misc/Profiler.hpp" +#include "Speed/Indep/Src/Misc/ResourceLoader.hpp" +#include "Speed/Indep/Src/World/TrackInfo.hpp" +#include "Speed/Indep/Src/World/Rain.hpp" +#include "Speed/Indep/bWare/Inc/SpeedScript.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#include "Speed/Indep/bWare/Inc/bFunk.hpp" + +SceneryOverrideInfo *GetSceneryOverrideInfo(int override_info_number); +int LoaderSceneryGroup(bChunk *chunk); +int UnloaderSceneryGroup(bChunk *chunk); +int LoaderScenery(bChunk *chunk); +int UnloaderScenery(bChunk *chunk); + +struct _type_map; +typedef UTL::Std::map ModelHeirarchyMap; + +struct eSceneryLightContext : public eLightContext { + char Name[34]; + short LightingContextNumber; + bMatrix4 *LocalLights; + unsigned int NumLights; + + void EndianSwap() { + bPlatEndianSwap(&Type); + bPlatEndianSwap(&NumLights); + bEndianSwap16(&LightingContextNumber); + } +}; + +class PrecullerBooBooManager { + private: + unsigned char BitField[0x800]; + + public: + void Reset() { + bMemSet(this, 0, 0x800); + } + + int GetSectionNumber(bVector3 &position); + unsigned char *GetByte(int section_number); + unsigned char GetBit(int section_number); + + void Set(bVector3 &pos) { + int n = GetSectionNumber(pos); + unsigned char *p = GetByte(n); + *p |= GetBit(n); + } + + void Clr(bVector3 &pos) { + int n = GetSectionNumber(pos); + unsigned char *p = GetByte(n); + *p &= 0xFF - GetBit(n); + } + + bool IsSet(bVector3 &pos) { + int n = GetSectionNumber(pos); + unsigned char *p = GetByte(n); + return (*p & GetBit(n)) != 0; + } +}; + +struct GrandSceneryCullInfo { + // total size: 0x8E0 + SceneryCullInfo SceneryCullInfos[12]; // offset 0x0, size 0x8D0 + int NumCullInfos; // offset 0x8D0, size 0x4 + SceneryDrawInfo *pFirstDrawInfo; // offset 0x8D4, size 0x4 + SceneryDrawInfo *pCurrentDrawInfo; // offset 0x8D8, size 0x4 + SceneryDrawInfo *pTopDrawInfo; // offset 0x8DC, size 0x4 + + static SceneryDrawInfo SceneryDrawInfoTable[3500]; + + int WhatSectionsShouldWeDraw(short *sections_to_draw, int max_sections_to_draw, SceneryCullInfo *scenery_cull_info); + void CullView(SceneryCullInfo *scenery_cull_info); + void DoCulling(); + void StuffScenery(eView *view, int stuff_flags); +}; + +extern unsigned int FrameMallocFailed; +extern unsigned int FrameMallocFailAmount; +extern float EnvMapShadowExtraHeight; +extern eModel *pDebugModel; +extern PrecullerBooBooManager gPrecullerBooBooManager; +static const float EnablePrecullingSpeed = 40.0f * 0.4470272660255432f; +extern int PrecullerMode; +extern int DisablePrecullerCounter; +extern int RealTimeFrames; +extern int CurrentZoneNumber; +extern int SeeulatorToolActive; +extern int ScenerySectionToBlink; +extern int SeeulatorRefreshTrackStreamer; +extern int ShowSectionBoarder; +void RefreshTrackStreamer(); +void CreateWindRotMatrix(eView *view, bMatrix4 *matrix, int x, const bMatrix4 *world); +void RenderVisibleSectionBoundary(VisibleSectionBoundary *boundary, eView *view); +ScenerySectionHeader *GetScenerySectionHeader(int section_number); +int IsInTable(short *section_numbers, int num_sections, int section_number); +int ToggleIsInTable(short *section_numbers, int num_sections, int max_sections, int section_number); +bTList ScenerySectionHeaderList; +RegionQuery RegionInfo; +ModelHeirarchyMap HeirarchyMap; +bTList SceneryGroupList; +bChunkLoader bChunkLoaderSceneryGroup(0x34109, LoaderSceneryGroup, UnloaderSceneryGroup); +bChunkLoader bChunkLoaderScenerySection(0x80034100, LoaderScenery, UnloaderScenery); +bChunkLoader bChunkLoaderOverrideInfos(0x34108, LoaderScenery, UnloaderScenery); +bChunkLoader bChunkLoaderSceneryHeirarchy(0x8003410B, LoaderScenery, UnloaderScenery); +bChunkLoader bChunkLoaderSceneryLighting(0x80034115, LoaderScenery, UnloaderScenery); +SceneryDetailLevel ForceAllSceneryDetailLevels = SCENERY_DETAIL_NONE; +SceneryOverrideInfo *SceneryOverrideInfoTable = 0; +int NumSceneryOverrideInfos = 0; +eLight *LightTable = 0; +int MaxSceneryLightContexts = 0; +eSceneryLightContext **SceneryLightContextTable = 0; +void (*ModelConnectionCallback)(ScenerySectionHeader *, int, eModel *) = 0; +void (*ModelDisconnectionCallback)(ScenerySectionHeader *, int, eModel *) = 0; +void (*SectionConnectionCallback)(ScenerySectionHeader *) = 0; +void (*SectionDisconnectionCallback)(ScenerySectionHeader *) = 0; +eModel *pVisibleZoneBoundaryModel = 0; +short SceneryOverrideHashTable[257]; +SceneryDrawInfo GrandSceneryCullInfo::SceneryDrawInfoTable[3500]; +extern unsigned char SceneryGroupEnabledTable[0x1000]; + +inline int PrecullerBooBooManager::GetSectionNumber(bVector3 &position) { + int x_section = (static_cast(static_cast(position.x)) >> 5) & 0x7F; + return ((static_cast(position.y) & 0xFE0) << 2) | x_section; +} + +static inline int GetPrecullerSectionNumber(float x, float y) { + return ((static_cast(static_cast(x)) >> 5) & 0x1F) + (static_cast(y) & 0x3E0); +} + +unsigned char *PrecullerBooBooManager::GetByte(int section_number) { + return BitField + (section_number >> 3); +} + +unsigned char PrecullerBooBooManager::GetBit(int section_number) { + return static_cast(1 << (section_number & 7)); +} + +static inline void EndianSwapSectionHeader_Scenery(int *section_header_words) { + bEndianSwap32(reinterpret_cast(section_header_words) + 0xC); + bEndianSwap32(reinterpret_cast(section_header_words) + 0x10); + bEndianSwap32(reinterpret_cast(section_header_words) + 0x14); + bEndianSwap32(reinterpret_cast(section_header_words) + 0x38); +} + +static inline void EndianSwapSceneryInfo_Scenery(unsigned char *data) { + for (int i = 0; i < 4; i++) { + bEndianSwap32(data + 0x18 + i * 4); + } + bEndianSwap32(data + 0x38); + bEndianSwap32(data + 0x3C); + bEndianSwap32(data + 0x40); +} + +static inline void EndianSwapSceneryInstance_Scenery(SceneryInstance *instance) { + bPlatEndianSwap(&instance->ExcludeFlags); + bPlatEndianSwap(&instance->PrecullerInfoIndex); + bPlatEndianSwap(&instance->LightingContextNumber); + for (int i = 0; i < 3; i++) { + bPlatEndianSwap(&instance->Position[i]); + } + for (int i = 0; i < 9; i++) { + bPlatEndianSwap(&instance->Rotation[i]); + } + bPlatEndianSwap(&instance->SceneryInfoNumber); + for (int i = 0; i < 3; i++) { + bPlatEndianSwap(&instance->BBoxMin[i]); + bPlatEndianSwap(&instance->BBoxMax[i]); + } +} + +static inline void EndianSwapPrecullerInfo_Scenery(unsigned char *data) { + bEndianSwap32(data + 0x00); + bEndianSwap32(data + 0x04); + bEndianSwap32(data + 0x08); + bEndianSwap32(data + 0x0C); + bEndianSwap32(data + 0x10); + bEndianSwap32(data + 0x14); + bEndianSwap16(data + 0x18); + for (int i = 0; i < 5; i++) { + bEndianSwap16(data + 0x1A + i * 2); + } +} + +static inline SceneryOverrideInfo *FindMatchingOverrideInfo_Scenery(int section_number, int override_index) { + for (int i = 0; i < NumSceneryOverrideInfos; i++) { + SceneryOverrideInfo *override_info = &SceneryOverrideInfoTable[i]; + if (override_info->SectionNumber == section_number && override_info->InstanceNumber == override_index) { + return override_info; + } + } + return 0; +} + +static inline eModel *FindExistingModel_Scenery(unsigned char *scenery_info, int model_slot) { + unsigned int name_hash = *reinterpret_cast(scenery_info + 0x18 + model_slot * 4); + for (int i = 0; i < model_slot; i++) { + eModel *model = *reinterpret_cast(scenery_info + 0x28 + i * 4); + if (model && model->NameHash == name_hash) { + return model; + } + } + return 0; +} + +static inline unsigned char *GetSceneryInfo_Scenery(int *section_header_words, short scenery_info_number) { + return reinterpret_cast(section_header_words[6]) + scenery_info_number * 0x48; +} + +static inline eModel *GetSceneryModel_Scenery(unsigned char *scenery_info, int model_slot) { + return *reinterpret_cast(scenery_info + 0x28 + model_slot * 4); +} + +static inline float GetSceneryRadius_Scenery(unsigned char *scenery_info) { + return *reinterpret_cast(scenery_info + 0x38); +} + +static inline int InlinedViewGetPixelSize(SceneryCullInfo *scenery_cull_info, const bVector3 *position, float radius) { + bVector3 dir = *position - scenery_cull_info->Position; + float distance_ahead = bDot(&dir, &scenery_cull_info->Direction); + if (distance_ahead < -radius) { + return 0; + } + + float distance_away = bLength(&dir); + float pixel_size_float = scenery_cull_info->H; + float distance_minus_radius = distance_away - radius; + if (distance_minus_radius > radius) { + pixel_size_float = (radius * pixel_size_float) / distance_minus_radius; + } + return static_cast(pixel_size_float); +} + +static inline bMatrix4 *eFrameMallocMatrix(int num_matrices) { + unsigned char *address = CurrentBufferPos; + unsigned int size = num_matrices * sizeof(bMatrix4); + unsigned char *next_buffer_pos = address + size; + if (CurrentBufferEnd <= next_buffer_pos) { + FrameMallocFailed = 1; + FrameMallocFailAmount += size; + address = 0; + } else { + CurrentBufferPos = next_buffer_pos; + } + return reinterpret_cast(address); +} + +void BuildSceneryOverrideHashTable() { + int num_scenery_override_infos = NumSceneryOverrideInfos; + SceneryOverrideInfo *scenery_override_info_table = SceneryOverrideInfoTable; + int index = 0; + unsigned int i = 0; + do { + unsigned int next_i = i + 1; + SceneryOverrideHashTable[i] = static_cast(index); + while (index < num_scenery_override_infos && + (static_cast(reinterpret_cast(&scenery_override_info_table[index])[0]) & 0xFF) == i) { + index += 1; + } + i = next_i; + } while (static_cast(i) < 0x100); + SceneryOverrideHashTable[0x100] = static_cast(index); +} + +ModelHeirarchy *FindSceneryHeirarchyByName(unsigned int name_hash) { + ModelHeirarchyMap::iterator it = HeirarchyMap.find(name_hash); + if (it == HeirarchyMap.end()) { + return 0; + } + return it->second; +} + +SceneryOverrideInfo *GetSceneryOverrideInfo(int override_info_number) { + return reinterpret_cast(reinterpret_cast(SceneryOverrideInfoTable) + override_info_number * 6); +} + +void SceneryOverrideInfo::AssignOverrides(ScenerySectionHeader *section_header) { + SceneryInstance *scenery_instance = section_header->GetSceneryInstance(InstanceNumber); + + if ((scenery_instance->ExcludeFlags & 0x800000) != 0 && ((scenery_instance->ExcludeFlags ^ ExcludeFlags) & 0x400) != 0) { + bMatrix4 matrix; + bMatrix4 flip_matrix; + + scenery_instance->GetRotation(&matrix); + + bIdentity(&flip_matrix); + flip_matrix.v0.x = -1.0f; + bMulMatrix(&matrix, &matrix, &flip_matrix); + scenery_instance->GetPosition(&matrix.v3); + scenery_instance->SetMatrix(&matrix); + } + + scenery_instance->ExcludeFlags = ExcludeFlags + (scenery_instance->ExcludeFlags & 0xFFFF0000); +} + +void SceneryOverrideInfo::AssignOverrides() { + ScenerySectionHeader *section_header = GetScenerySectionHeader(SectionNumber); + if (section_header) { + AssignOverrides(section_header); + } +} + +void LoadPrecullerBooBooScript(const char *filename, bool reset) { + if (reset) { + gPrecullerBooBooManager.Reset(); + } + + SpeedScript script(filename, 1); + while (script.GetNextCommand("BOOBOO:")) { + char *region = script.GetNextArgumentString(); + + if (bStrICmp(region, TrackInfo::GetLoadedTrackInfo()->RegionName) == 0) { + { + char *section = script.GetNextArgumentString(); + char *option = script.GetNextArgumentString(); + bool set_booboo = bStrICmp(option, "SET") == 0; + bool clr_booboo = bStrICmp(option, "CLR") == 0; + + (void)section; + script.GetNextArgumentString(); + bVector3 pos = script.GetNextArgumentVector3(); + if (set_booboo) { + gPrecullerBooBooManager.Set(pos); + } else if (clr_booboo) { + gPrecullerBooBooManager.Clr(pos); + } + } + } + } +} + +void LoadPrecullerBooBooScripts() { + LoadPrecullerBooBooScript("TRACKS\\PrecullerBooBooScript.hoo", 1); +} + +int LoaderSceneryGroup(bChunk *chunk) { + if (chunk->GetID() == 0x34109) { + int chunk_size = chunk->Size; + int group_offset = 0; + if (group_offset < chunk_size) { + do { + SceneryGroup *group = reinterpret_cast(reinterpret_cast(chunk) + group_offset + 8); + SceneryGroup *head = SceneryGroupList.GetHead(); + head->Prev = group; + group->Next = head; + SceneryGroupList.HeadNode.Next = group; + group->Prev = reinterpret_cast(&SceneryGroupList); + + bEndianSwap32(&group->NameHash); + bEndianSwap16(&group->GroupNumber); + bEndianSwap16(&group->NumObjects); + for (int i = 0; i < group->NumObjects; i++) { + bEndianSwap16(&group->OverrideInfoNumbers[i]); + } + + group_offset += (group->NumObjects * sizeof(unsigned short) + 0x17U) & 0xFFFFFFFC; + } while (group_offset < chunk_size); + } + return 1; + } + + return 0; +} + +int UnloaderSceneryGroup(bChunk *chunk) { + if (chunk->GetID() == 0x34109) { + bMemSet(SceneryGroupEnabledTable, 0, 0x1000); + SceneryGroupEnabledTable[0] = 1; + SceneryGroupList.InitList(); + return 1; + } + + return 0; +} + +SceneryGroup *FindSceneryGroup(unsigned int name_hash) { + for (SceneryGroup *group = SceneryGroupList.GetHead(); group != SceneryGroupList.EndOfList(); group = group->GetNext()) { + if (group->NameHash == name_hash) { + return group; + } + } + return 0; +} + +void EnableSceneryGroup(unsigned int name_hash, bool flip_artwork) { + SceneryGroup *group = static_cast(FindSceneryGroup(name_hash)); + if (group) { + unsigned short override_flags = 0; + if (flip_artwork) { + override_flags = 0x400; + } + + for (int i = 0; i < group->NumObjects; i++) { + SceneryOverrideInfo *override_info = group->GetOverrideInfo(i); + override_info->ExcludeFlags = override_flags | (override_info->ExcludeFlags & 0xFBEF); + override_info->AssignOverrides(); + } + + SceneryGroupEnabledTable[group->GroupNumber] = 1; + if (flip_artwork) { + SceneryGroupEnabledTable[group->GroupNumber] |= 2; + } + if (group->DriveThroughBarrierFlag) { + SceneryGroupEnabledTable[group->GroupNumber] |= 4; + } + } +} + +void DisableSceneryGroup(unsigned int name_hash) { + SceneryGroup *group = static_cast(FindSceneryGroup(name_hash)); + if (group) { + for (int i = 0; i < group->NumObjects; i++) { + SceneryOverrideInfo *override_info = group->GetOverrideInfo(i); + override_info->ExcludeFlags = override_info->ExcludeFlags | 0x10; + override_info->AssignOverrides(); + } + SceneryGroupEnabledTable[group->GroupNumber] = 0; + } +} + +void DisableAllSceneryGroups() { + for (SceneryGroup *group = SceneryGroupList.GetHead(); group != SceneryGroupList.EndOfList(); group = group->GetNext()) { + if (SceneryGroupEnabledTable[group->GroupNumber]) { + for (int i = 0; i < group->NumObjects; i++) { + SceneryOverrideInfo *override_info = group->GetOverrideInfo(i); + override_info->ExcludeFlags = override_info->ExcludeFlags | 0x10; + override_info->AssignOverrides(); + } + SceneryGroupEnabledTable[group->GroupNumber] = 0; + } + } +} + +ScenerySectionHeader *GetScenerySectionHeader(int section_number) { + VisibleSectionUserInfo *user_info = TheVisibleSectionManager.GetUserInfo(section_number); + if (!user_info) { + return 0; + } + return user_info->pScenerySectionHeader; +} + +int LoaderScenery(bChunk *chunk) { + if (chunk->GetID() == 0x34108) { + SceneryOverrideInfoTable = reinterpret_cast(chunk->GetData()); + NumSceneryOverrideInfos = static_cast(chunk->Size) / 6; + for (int i = 0; i < NumSceneryOverrideInfos; i++) { + SceneryOverrideInfo *override_info = &SceneryOverrideInfoTable[i]; + override_info->EndianSwap(); + } + BuildSceneryOverrideHashTable(); + return 1; + } + + if (chunk->GetID() == 0x80034100) { + ScenerySectionHeader *section_header = 0; + bChunk *last_chunk = chunk->GetLastChunk(); + + for (bChunk *subchunk = chunk->GetFirstChunk(); subchunk != last_chunk; subchunk = subchunk->GetNext()) { + unsigned int subchunk_id = subchunk->GetID(); + if (subchunk_id == 0x34101) { + section_header = reinterpret_cast(subchunk->GetAlignedData(0x10)); + int *section_header_words = reinterpret_cast(section_header); + if (section_header_words[2] == 0) { + bEndianSwap32(reinterpret_cast(section_header_words) + 0xC); + bEndianSwap32(reinterpret_cast(section_header_words) + 0x10); + bEndianSwap32(reinterpret_cast(section_header_words) + 0x14); + bEndianSwap32(reinterpret_cast(section_header_words) + 0x38); + } + + VisibleSectionUserInfo *user_info = TheVisibleSectionManager.AllocateUserInfo(section_header_words[3]); + user_info->pScenerySectionHeader = section_header; + + if (GetScenerySectionLetter(section_header_words[3]) == 'Z') { + ScenerySectionHeaderList.AddHead(section_header); + } else { + ScenerySectionHeaderList.AddTail(section_header); + } + } else if (subchunk_id == 0x34102) { + int *section_header_words = reinterpret_cast(section_header); + section_header_words[6] = reinterpret_cast(subchunk->GetData()); + section_header_words[7] = static_cast(subchunk->Size) / 0x48; + if (section_header_words[2] == 0) { + for (int i = 0; i < section_header_words[7]; i++) { + bEndianSwap32(reinterpret_cast(section_header_words[6] + i * 0x48 + 0x40)); + for (int n = 0; n < 4; n++) { + bEndianSwap32(reinterpret_cast(section_header_words[6] + i * 0x48 + 0x18 + n * 4)); + } + bEndianSwap32(reinterpret_cast(section_header_words[6] + i * 0x48 + 0x38)); + bEndianSwap32(reinterpret_cast(section_header_words[6] + i * 0x48 + 0x3C)); + } + } + + for (int i = 0; i < section_header_words[7]; i++) { + SceneryInfo *scenery_info = reinterpret_cast(section_header_words[6]) + i; + unsigned int hierarchy_name = scenery_info->mHeirarchyNameHash; + if (hierarchy_name != 0) { + scenery_info->mHeirarchy = FindSceneryHeirarchyByName(hierarchy_name); + } + } + } else if (subchunk_id == 0x34103) { + int *section_header_words = reinterpret_cast(section_header); + section_header_words[8] = (reinterpret_cast(subchunk) + 0x17) & 0xFFFFFFF0; + section_header_words[9] = + static_cast(subchunk->Size - (section_header_words[8] - reinterpret_cast(subchunk->GetData()))) >> 6; + if (section_header_words[2] == 0) { + for (int i = 0; i < section_header_words[9]; i++) { + unsigned char *instance = reinterpret_cast(section_header_words[8] + i * 0x40); + bEndianSwap16(instance + 0x3E); + bEndianSwap32(instance + 0x18); + for (int n = 0; n < 3; n++) { + unsigned char *swap = instance + n * 4; + bEndianSwap32(swap + 0x20); + } + for (int n = 0; n < 9; n++) { + unsigned char *swap = instance + n * 2; + bEndianSwap16(swap + 0x2C); + } + bEndianSwap32(instance + 0x00); + bEndianSwap32(instance + 0x04); + bEndianSwap32(instance + 0x08); + bEndianSwap32(instance + 0x0C); + bEndianSwap32(instance + 0x10); + bEndianSwap32(instance + 0x14); + bEndianSwap16(instance + 0x1C); + bEndianSwap16(instance + 0x1E); + } + } + } else if (subchunk_id == 0x34105) { + int *section_header_words = reinterpret_cast(section_header); + section_header_words[10] = reinterpret_cast(subchunk->GetData()); + section_header_words[11] = static_cast(subchunk->Size) / 0x24; + if (section_header_words[2] == 0) { + for (int i = 0; i < section_header_words[11]; i++) { + bEndianSwap16(reinterpret_cast(section_header_words[10] + i * 0x24 + 0x18)); + bEndianSwap32(reinterpret_cast(section_header_words[10] + i * 0x24 + 0x00)); + bEndianSwap32(reinterpret_cast(section_header_words[10] + i * 0x24 + 0x04)); + bEndianSwap32(reinterpret_cast(section_header_words[10] + i * 0x24 + 0x08)); + bEndianSwap32(reinterpret_cast(section_header_words[10] + i * 0x24 + 0x0C)); + bEndianSwap32(reinterpret_cast(section_header_words[10] + i * 0x24 + 0x10)); + bEndianSwap32(reinterpret_cast(section_header_words[10] + i * 0x24 + 0x14)); + for (int n = 0; n < 5; n++) { + bEndianSwap16(reinterpret_cast(section_header_words[10] + i * 0x24 + 0x1A + n * 2)); + } + } + } + } else if (subchunk_id == 0x34106) { + int *section_header_words = reinterpret_cast(section_header); + int num_overrides = static_cast(subchunk->Size) >> 2; + unsigned short *override_data_base = reinterpret_cast(subchunk->GetData()); + for (int i = 0; i < num_overrides; i++) { + unsigned short *override_data = reinterpret_cast(reinterpret_cast(override_data_base) + i * 4); + bEndianSwap16(&override_data[0]); + bEndianSwap16(&override_data[1]); + SceneryOverrideInfo *override_info = + reinterpret_cast(reinterpret_cast(SceneryOverrideInfoTable) + override_data[1] * 6); + if (override_info->InstanceNumber == override_data[0] && override_info->SectionNumber == section_header_words[3]) { + override_info->AssignOverrides(section_header); + } + } + } else if (subchunk_id == 0x34107) { + int *section_header_words = reinterpret_cast(section_header); + section_header_words[12] = reinterpret_cast(subchunk->GetData()); + int num_override_datas = static_cast(subchunk->Size) >> 7; + section_header_words[13] = num_override_datas; + if (section_header_words[2] == 0) { + for (int i = 0; i < num_override_datas; i++) { + } + } + } + } + + if (!AreChunksBeingMoved()) { + int *section_header_words = reinterpret_cast(section_header); + SceneryInfo *scenery_infos = reinterpret_cast(section_header_words[6]); + for (int n = 0; n < section_header_words[7]; n++) { + SceneryInfo *scenery_info = &scenery_infos[n]; + for (int detail_level = 0; detail_level < 4; detail_level++) { + unsigned int name_hash = scenery_info->NameHash[detail_level]; + if (name_hash != 0 && name_hash != 0xBE43EDBB && name_hash != 0x90F70174) { + eModel *model = 0; + for (int i = 0; i < detail_level; i++) { + model = scenery_info->pModel[i]; + if (model && model->NameHash == name_hash) { + break; + } + model = 0; + } + + if (!model) { + model = reinterpret_cast(bOMalloc(eModelSlotPool)); + model->NameHash = 0; + model->Solid = 0; + model->Init(name_hash); + if (ModelConnectionCallback) { + ModelConnectionCallback(section_header, n, model); + } + } + scenery_info->pModel[detail_level] = model; + } + } + + eModel *lowest_detail_model = scenery_info->pModel[1]; + if (scenery_info->pModel[2] == 0 && lowest_detail_model != 0) { + scenery_info->pModel[2] = lowest_detail_model; + } + if (scenery_info->pModel[0] == 0 && lowest_detail_model != 0) { + scenery_info->pModel[0] = lowest_detail_model; + } + } + + if (SectionConnectionCallback) { + SectionConnectionCallback(section_header); + } + } + + reinterpret_cast(section_header)[2] = 1; + return 1; + } + + if (chunk->GetID() == 0x8003410B) { + bChunk *last_chunk = chunk->GetLastChunk(); + for (bChunk *subchunk = chunk->GetFirstChunk(); subchunk != last_chunk; subchunk = subchunk->GetNext()) { + ModelHeirarchy *mH = reinterpret_cast(subchunk->GetData()); + ModelHeirarchy::Node *node = reinterpret_cast(mH + 1); + bEndianSwap32(&mH->mNameHash); + + unsigned int num_models = mH->mNumNodes; + for (unsigned int i = 0; i < num_models; i++) { + bEndianSwap32(&node[i].mNodeName); + bEndianSwap32(&node[i].mModelHash); + } + + HeirarchyMap[mH->mNameHash] = mH; + for (unsigned int i = 0; i < num_models; i++) { + eModel *model = 0; + if (node[i].mModelHash != 0) { + model = reinterpret_cast(bOMalloc(eModelSlotPool)); + if (model) { + model->NameHash = 0; + model->Solid = 0; + model->Init(node[i].mModelHash); + } + } + node[i].mModel = model; + } + } + return 1; + } + + if (chunk->GetID() == 0x80034115) { + bChunk *last_chunk = chunk->GetLastChunk(); + for (bChunk *subchunk = chunk->GetFirstChunk(); subchunk != last_chunk; subchunk = subchunk->GetNext()) { + if (subchunk->GetID() == 0x34116) { + LightTable = reinterpret_cast(subchunk->GetData()); + } else if (subchunk->GetID() == 0x34117) { + SceneryLightContextTable = reinterpret_cast(subchunk->GetData()); + MaxSceneryLightContexts = (subchunk->Size + 3) >> 2; + } else if (subchunk->GetID() == 0x34118) { + eSceneryLightContext *light_context = reinterpret_cast(subchunk->GetAlignedData(0x10)); + bPlatEndianSwap(&light_context->Type); + bPlatEndianSwap(&light_context->NumLights); + bPlatEndianSwap(&light_context->LightingContextNumber); + light_context->LocalLights = reinterpret_cast(light_context + 1); + for (unsigned int i = 0; i < light_context->NumLights; i++) { + bPlatEndianSwap(reinterpret_cast(reinterpret_cast(light_context->LocalLights) + i * 0x40 + 0x00)); + bPlatEndianSwap(reinterpret_cast(reinterpret_cast(light_context->LocalLights) + i * 0x40 + 0x10)); + bPlatEndianSwap(reinterpret_cast(reinterpret_cast(light_context->LocalLights) + i * 0x40 + 0x20)); + bPlatEndianSwap(reinterpret_cast(reinterpret_cast(light_context->LocalLights) + i * 0x40 + 0x30)); + } + SceneryLightContextTable[light_context->LightingContextNumber] = light_context; + } + } + return 1; + } + + return 0; +} + +int UnloaderScenery(bChunk *chunk) { + unsigned int chunk_id = chunk->GetID(); + + if (chunk_id == 0x34108) { + SceneryOverrideInfoTable = 0; + NumSceneryOverrideInfos = 0; + BuildSceneryOverrideHashTable(); + return 1; + } + + if (chunk_id == 0x80034100) { + ScenerySectionHeader *section_header = reinterpret_cast(chunk->GetAlignedData(0x10)); + int *section_header_words = reinterpret_cast(section_header); + TheVisibleSectionManager.GetUserInfo(section_header_words[3])->pScenerySectionHeader = 0; + TheVisibleSectionManager.UnallocateUserInfo(section_header_words[3]); + section_header->Remove(); + + if (!AreChunksBeingMoved()) { + for (int i = 0; i < section_header_words[7]; i++) { + unsigned char *scenery_info = reinterpret_cast(section_header_words[6]) + i * 0x48; + eModel **model_slots = reinterpret_cast(scenery_info + 0x28); + for (int j = 0; j < 4; j++) { + if (AreChunksBeingMoved()) { + break; + } + + if (model_slots[j]) { + eModel *slot_model = model_slots[j]; + for (int k = j + 1; k < 4; k++) { + if (model_slots[k] == slot_model) { + model_slots[k] = 0; + } + } + if (ModelDisconnectionCallback) { + ModelDisconnectionCallback(section_header, i, model_slots[j]); + } + + eModel *model = model_slots[j]; + if (model) { + model->UnInit(); + bFree(eModelSlotPool, model); + } + model_slots[j] = 0; + } + } + } + + if (SectionDisconnectionCallback) { + SectionDisconnectionCallback(section_header); + } + } + + return 1; + } + + if (chunk_id == 0x8003410B) { + bChunk *last_chunk = chunk->GetLastChunk(); + for (bChunk *subchunk = chunk->GetFirstChunk(); subchunk != last_chunk; subchunk = subchunk->GetNext()) { + ModelHeirarchy *heirarchy = reinterpret_cast(subchunk->GetData()); + ModelHeirarchy::Node *nodes = reinterpret_cast(heirarchy + 1); + unsigned int num_models = heirarchy->mNumNodes; + + for (unsigned int i = 0; i < num_models; i++) { + eModel *model = nodes[i].mModel; + if (model) { + model->UnInit(); + bFree(eModelSlotPool, model); + nodes[i].mModel = 0; + } + } + + ModelHeirarchyMap::iterator it = HeirarchyMap.find(heirarchy->mNameHash); + if (it != HeirarchyMap.end()) { + HeirarchyMap.erase(it); + } + } + return 1; + } + + if (chunk_id == 0x80034115) { + MaxSceneryLightContexts = 0; + LightTable = 0; + SceneryLightContextTable = 0; + return 1; + } + + return 0; +} + +SceneryInfo *FindSceneryInfo(unsigned int name_hash) { + for (ScenerySectionHeader *section_header = reinterpret_cast(ScenerySectionHeaderList.GetHead()); + section_header != reinterpret_cast(ScenerySectionHeaderList.EndOfList()); + section_header = reinterpret_cast(section_header->GetNext())) { + int *section_header_words = reinterpret_cast(section_header); + for (int i = 0; i < section_header_words[7]; i++) { + SceneryInfo *scenery_info = reinterpret_cast(section_header_words[6]) + i; + eModel *model = scenery_info->pModel[0]; + if (model && model->NameHash == name_hash) { + return scenery_info; + } + } + } + return 0; +} + +SceneryInstance *FindSceneryInstance(unsigned int name_hash) { + for (ScenerySectionHeader *section_header = reinterpret_cast(ScenerySectionHeaderList.GetHead()); + section_header != reinterpret_cast(ScenerySectionHeaderList.EndOfList()); + section_header = reinterpret_cast(section_header->GetNext())) { + int *section_header_words = reinterpret_cast(section_header); + for (int i = 0; i < section_header_words[9]; i++) { + SceneryInstance *instance = reinterpret_cast(section_header_words[8]) + i; + SceneryInfo *scenery_info = reinterpret_cast(section_header_words[6]) + instance->SceneryInfoNumber; + eModel *model = scenery_info->pModel[0]; + if (model && model->NameHash == name_hash) { + return instance; + } + } + } + return 0; +} + +void RenderVisibleSectionBoundary(VisibleSectionBoundary *boundary, eView *view) { + if (boundary->NumPoints <= 0) { + return; + } + + float perimeter; + { + int n; + + for (n = 0; n < boundary->GetNumPoints(); n++) { + bVector2 *v1 = boundary->GetPoint(n); + bVector2 *v2 = boundary->GetPoint((n + 1) % boundary->GetNumPoints()); + float x = v1->x - v2->x; + float y = v1->y - v2->y; + perimeter = bSqrt(x * x + y * y); + } + } + + bVector3 position; + TopologyCoordinate topology_coordinate; + float pos = static_cast((static_cast(WorldTimer.GetSeconds() * 262144.0f) & 0xffff)) * 6.103515625e-05f; + int point_number; + + for (point_number = 0; point_number < boundary->GetNumPoints(); point_number++) { + bVector2 normal = *boundary->GetPoint((point_number + 1) % boundary->GetNumPoints()) - *boundary->GetPoint(point_number); + float length = bLength(&normal); + + bNormalize(&normal, &normal); + if (pos < length) { + do { + bScaleAdd(reinterpret_cast(&position), boundary->GetPoint(point_number), &normal, pos); + + if (topology_coordinate.HasTopology(reinterpret_cast(&position))) { + position.z = 9999.0f; + position.z = topology_coordinate.GetElevation(&position, 0, 0, 0); + int pixel_size = view->GetPixelSize(&position, 1.0f); + if (pixel_size > 0) { + unsigned char *matrix_memory = CurrentBufferPos; + unsigned char *next_buffer_pos = matrix_memory + sizeof(bMatrix4); + if (next_buffer_pos >= CurrentBufferEnd) { + FrameMallocFailed = 1; + FrameMallocFailAmount += sizeof(bMatrix4); + matrix_memory = 0; + } else { + CurrentBufferPos = next_buffer_pos; + } + + if (matrix_memory) { + bMatrix4 *matrix = reinterpret_cast(matrix_memory); + bIdentity(matrix); + bCopy(&matrix->v3, &position, 1.0f); + reinterpret_cast(view)->Render(pVisibleZoneBoundaryModel, matrix, 0, 0, 0); + } + } + } + + pos += 4.0f; + } while (pos < length); + } + + pos -= length; + } +} + +void RenderVisibleZones(eView *view) { + if (ShowSectionBoarder != 0 && pVisibleZoneBoundaryModel != 0) { + DrivableScenerySection *drivable_section = + TheVisibleSectionManager.FindDrivableSection(reinterpret_cast(view->GetCamera()->GetPosition())); + if (drivable_section) { + RenderVisibleSectionBoundary(drivable_section->pBoundary, view); + } + } +} + +void InitVisibleZones() { + if (pVisibleZoneBoundaryModel == 0) { + eModel *model = reinterpret_cast(bOMalloc(eModelSlotPool)); + unsigned int name_hash = bStringHash("MARKER_BOUNDARY"); + model->NameHash = 0; + model->Solid = 0; + model->Init(name_hash); + pVisibleZoneBoundaryModel = model; + } +} + +void CloseVisibleZones() { + eModel *model = pVisibleZoneBoundaryModel; + if (pVisibleZoneBoundaryModel) { + pVisibleZoneBoundaryModel->UnInit(); + bFree(eModelSlotPool, model); + } + pVisibleZoneBoundaryModel = 0; + if (SeeulatorToolActive) { + int data = 0; + bFunkCallASync("Seeulator", 4, &data, 4); + bFunkCallASync("Seeulator", 5, &data, 4); + bFunkCallASync("Seeulator", 6, &data, 4); + } +} + +int IsInTable(short *section_numbers, int num_sections, int section_number) { + for (int i = 0; i < num_sections; i++) { + if (section_numbers[i] == section_number) { + return i; + } + } + return -1; +} + +int ToggleIsInTable(short *section_numbers, int num_sections, int max_sections, int section_number) { + int section_index = IsInTable(section_numbers, num_sections, section_number); + if (section_index >= 0) { + section_numbers[section_index] = -1; + return num_sections; + } + + section_numbers[num_sections % max_sections] = static_cast(section_number); + return num_sections + 1; +} + +void ScenerySectionHeader::DrawAScenery(int scenery_instance_number, SceneryCullInfo *scenery_cull_info, int visibility_state) { + int *section_header_words = reinterpret_cast(this); + SceneryInstance *instance = GetSceneryInstance(scenery_instance_number); + tPrecullerInfo *preculler_info = GetPrecullerInfo(instance->PrecullerInfoIndex); + int preculler_section_number = scenery_cull_info->PrecullerSectionNumber; + if (preculler_section_number >= 0) { + int byte_number = preculler_section_number >> 3; + int bit_number = preculler_section_number & 7; + unsigned char visibility_bits = preculler_info->GetBits()[byte_number]; + int visibility_mask = 1 << bit_number; + if ((visibility_bits & visibility_mask) != 0) { + return; + } + } + + unsigned char instance_exclude_flags = instance->ExcludeFlags; + short scenery_info_number = instance->SceneryInfoNumber; + if (((instance_exclude_flags ^ 0x60) & scenery_cull_info->ExcludeFlags) != 0) { + return; + } + int pixel_size_int; + SceneryInfo *scenery_info = reinterpret_cast(GetSceneryInfo_Scenery(section_header_words, scenery_info_number)); + + if (visibility_state == EVISIBLESTATE_PARTIAL) { + bVector3 bbox_min; + bVector3 bbox_max; + instance->GetBBox(&bbox_min, &bbox_max); + visibility_state = scenery_cull_info->pView->GetVisibleState(&bbox_min, &bbox_max, 0); + if (visibility_state == EVISIBLESTATE_NOT) { + return; + } + } + + float radius = scenery_info->Radius + 6.0f; + pixel_size_int = InlinedViewGetPixelSize(scenery_cull_info, instance->GetPosition(), radius); + + if (pixel_size_int < 2) { + return; + } + unsigned int instance_flags = instance->ExcludeFlags; + if ((instance_flags & 0x2000000) != 0) { + pixel_size_int += 10; + } + + eModel *model = 0; + if ((scenery_cull_info->ExcludeFlags & 0x1800) != 0) { + if ((instance_flags & 0x80) != 0) { + if (pixel_size_int > 0x1F) { + model = scenery_info->pModel[2]; + } + } else { + if (pixel_size_int > 0x1F) { + if ((instance_flags & 0x1000100) != 0) { + model = scenery_info->pModel[0]; + } else { + model = scenery_info->pModel[3]; + } + } + } + } else if ((scenery_cull_info->ExcludeFlags & 0x20) != 0) { + if (pixel_size_int > 0x1F) { + model = scenery_info->pModel[2]; + } + } else if (eGetCurrentViewMode() > EVIEWMODE_ONE_RVM) { + if (pixel_size_int > 0x16) { + model = scenery_info->pModel[2]; + } + } else if (pixel_size_int > 0x11) { + model = scenery_info->pModel[0]; + eSolid *solid = model ? model->GetSolid() : 0; + if (solid && solid->NumPolys > 0x27) { + float lod_scale = solid->Density; + if (lod_scale < 6.0f) { + lod_scale = 6.0f; + } + if ((static_cast(pixel_size_int) / lod_scale) < 8.7f) { + model = scenery_info->pModel[2]; + } + } + } + + if (!model) { + return; + } + + if ((instance->ExcludeFlags & 0x200) != 0) { + SceneryDrawInfo *draw_info = scenery_cull_info->pCurrentDrawInfo; + if (draw_info >= scenery_cull_info->pTopDrawInfo) { + return; + } + + scenery_cull_info->pCurrentDrawInfo = draw_info + 1; + draw_info->pModel = reinterpret_cast(reinterpret_cast(model) + visibility_state); + draw_info->pMatrix = 0; + draw_info->SceneryInst = instance; + return; + } + + bMatrix4 *matrix = eFrameMallocMatrix(1); + + if (!matrix) { + return; + } + + instance->GetRotation(matrix); + bFill(&matrix->v3, instance->Position[0], instance->Position[1], instance->Position[2], 1.0f); + + if ((instance->ExcludeFlags & scenery_cull_info->ExcludeFlags & 0x100) != 0) { + matrix->v3.z += EnvMapShadowExtraHeight; + } + if ((scenery_cull_info->ExcludeFlags & 0x800) != 0) { + matrix->v2.z = -matrix->v2.z; + } + + SceneryDrawInfo *draw_info = scenery_cull_info->pCurrentDrawInfo; + if (draw_info >= scenery_cull_info->pTopDrawInfo) { + return; + } + + scenery_cull_info->pCurrentDrawInfo = draw_info + 1; + draw_info->pModel = reinterpret_cast(reinterpret_cast(model) + visibility_state); + if ((scenery_cull_info->ExcludeFlags & 0x4000) != 0 && model->GetSolid() && (model->GetSolid()->Flags & 0x80) != 0) { + bMatrix4 windrot; + int offset = static_cast(matrix->v3.x * 60.0f) % 0x168; + CreateWindRotMatrix(scenery_cull_info->pView, &windrot, offset, matrix); + bMulMatrix(matrix, matrix, &windrot); + } + + draw_info->pMatrix = matrix; + draw_info->SceneryInst = instance; + + if (scenery_cull_info->pView == eGetView(1, false) || scenery_cull_info->pView == eGetView(2, false)) { + ePositionMarker *position_marker = 0; + while ((position_marker = model->GetPostionMarker(position_marker)) != 0) { + if (model->GetSolid()) { + unsigned int exclude_view_ids = 2; + if (scenery_cull_info->pView == eGetView(1, false)) { + exclude_view_ids = 1; + } + + eLightFlare *light_flare = eGetNextLightFlareInPool(exclude_view_ids); + if (light_flare) { + bVector4 ps; + ps.x = position_marker->Matrix.v3.x - model->GetSolid()->PivotMatrix.v3.x; + ps.y = position_marker->Matrix.v3.y - model->GetSolid()->PivotMatrix.v3.y; + ps.z = position_marker->Matrix.v3.z - model->GetSolid()->PivotMatrix.v3.z; + ps.w = 1.0f; + eMulVector(&ps, draw_info->pMatrix, &ps); + + if (scenery_cull_info->pView->Precipitation && 0.0f < scenery_cull_info->pView->Precipitation->GetRoadDampness() && + (light_flare->PositionX != ps.x || light_flare->PositionY != ps.y || light_flare->PositionZ != ps.z)) { + light_flare->ReflectPosZ = 999.0f; + } + + light_flare->PositionX = ps.x; + light_flare->PositionY = ps.y; + light_flare->PositionZ = ps.z; + light_flare->Type = position_marker->iParam0 + 14; + if (static_cast(position_marker->iParam0 - 3) < 3) { + bVector2 dr; + light_flare->Flags = 4; + dr.x = ps.x - draw_info->pMatrix->v3.x; + dr.y = ps.y - draw_info->pMatrix->v3.y; + bNormalize(&dr, &dr); + light_flare->DirectionX = dr.x; + light_flare->DirectionY = dr.y; + light_flare->DirectionZ = 0.0f; + } else { + light_flare->Flags = 2; + } + } + } + } + } +} + +void ScenerySectionHeader::TreeCull(SceneryCullInfo *scenery_cull_info) { + const int max_depth = 64; + SceneryTreeNode *node_stack[max_depth]; + unsigned char visibility_state_stack[max_depth]; + SceneryTreeNode **pnode = node_stack + 1; + unsigned char *pvisibility_state = visibility_state_stack + 1; + + node_stack[0] = reinterpret_cast(reinterpret_cast(this)[10]); + visibility_state_stack[0] = 1; + while (pnode != node_stack) { + pnode -= 1; + SceneryTreeNode *node = *pnode; + pvisibility_state -= 1; + unsigned char visibility_state = *pvisibility_state; + if (visibility_state == 1) { + bVector3 bbox_min; + bVector3 bbox_max; + + node->GetBBox(&bbox_min, &bbox_max); + visibility_state = scenery_cull_info->pView->GetVisibleState(&bbox_min, &bbox_max, 0); + } + + if (visibility_state != 0) { + for (int child_number = 0; child_number < node->NumChildren; child_number++) { + // TODO + // short child_code = node->Children[child_number]; + short child_code; + if (child_code >= 0) { + DrawAScenery(child_code, scenery_cull_info, visibility_state); + } else { + int scenery_instance_number = child_code * -1; + SceneryTreeNode *child_node; + + child_node = reinterpret_cast(reinterpret_cast(reinterpret_cast(this)[10]) + + static_cast(scenery_instance_number) * 0x24); + + *pnode = child_node; + *pvisibility_state = visibility_state; + pnode += 1; + pvisibility_state += 1; + } + } + } + } +} + +int GrandSceneryCullInfo::WhatSectionsShouldWeDraw(short *sections_to_draw, int max_sections_to_draw, SceneryCullInfo *scenery_cull_info) { + short *sections = sections_to_draw; + int max_sections = max_sections_to_draw; + SceneryCullInfo *cull_info = scenery_cull_info; + DrivableScenerySection *drivable_scenery_section; + int iViewID = cull_info->pView->GetID(); + if (iViewID == EVIEW_SHADOWMAP1 || iViewID == EVIEW_SHADOWMAP2) { + int iViewPlayer = iViewID - 12; + drivable_scenery_section = + TheVisibleSectionManager.FindDrivableSection(reinterpret_cast(eViews[iViewPlayer].GetCamera()->GetPosition())); + } else { + drivable_scenery_section = + TheVisibleSectionManager.FindDrivableSection(reinterpret_cast(cull_info->pView->GetCamera()->GetPosition())); + } + + int num_sections_to_draw = 0; + if (!drivable_scenery_section) { + for (ScenerySectionHeader *section_header = reinterpret_cast(ScenerySectionHeaderList.GetHead()); + section_header != reinterpret_cast(ScenerySectionHeaderList.EndOfList()); + section_header = section_header->GetNext()) { + int section_number = section_header->GetSectionNumber(); + int subsection_number = section_number % 100; + if (subsection_number < 10 && num_sections_to_draw < max_sections) { + sections[num_sections_to_draw] = static_cast(section_number); + num_sections_to_draw += 1; + } + } + } else { + int section_number = 0xA28; + section_number_loop: + if (section_number >= 0xA8C) { + goto end_section_number_loop; + } + + if (GetScenerySectionHeader(section_number) && num_sections_to_draw < max_sections) { + sections[num_sections_to_draw] = static_cast(section_number); + num_sections_to_draw += 1; + } + section_number += 1; + goto section_number_loop; + end_section_number_loop: + + for (int i = 0; i < drivable_scenery_section->GetNumVisibleSections(); i++) { + int section_number = drivable_scenery_section->GetVisibleSection(i); + if (section_number >= 0 && GetScenerySectionHeader(section_number) && num_sections_to_draw < max_sections) { + sections[num_sections_to_draw] = static_cast(section_number); + num_sections_to_draw += 1; + } + } + } + + if (cull_info->pView->GetID() == EVIEW_PLAYER1) { + int current_zone_number = -1; + if (drivable_scenery_section) { + current_zone_number = drivable_scenery_section->GetSectionNumber(); + } + + if (current_zone_number != CurrentZoneNumber) { + CurrentZoneNumber = current_zone_number; + if (!SeeulatorToolActive) { + return num_sections_to_draw; + } + if (drivable_scenery_section) { + bFunkCallASync("Seeulator", 1, &CurrentZoneNumber, 4); + } + } + + if (SeeulatorToolActive) { + if (ScenerySectionToBlink != 0 && ((RealTimeFrames / 5) & 1U) != 0) { + num_sections_to_draw = ToggleIsInTable(sections, num_sections_to_draw, max_sections, ScenerySectionToBlink); + } + if (SeeulatorToolActive && SeeulatorRefreshTrackStreamer != 0) { + RefreshTrackStreamer(); + SeeulatorRefreshTrackStreamer = 0; + } + } + } + + return num_sections_to_draw; +} + +void GrandSceneryCullInfo::CullView(SceneryCullInfo *scenery_cull_info) { + short sections_to_draw[128]; + int num_sections = WhatSectionsShouldWeDraw(sections_to_draw, 0x80, scenery_cull_info); + + for (int i = 0; i < num_sections; i++) { + if (sections_to_draw[i] >= 0) { + ScenerySectionHeader *section_header = GetScenerySectionHeader(sections_to_draw[i]); + if (section_header && reinterpret_cast(section_header)[10] != 0) { + section_header->TreeCull(scenery_cull_info); + } + } + } +} + +void GrandSceneryCullInfo::DoCulling() { + ProfileNode profile_node("TODO", 0); + int n; + pFirstDrawInfo = SceneryDrawInfoTable; + pCurrentDrawInfo = SceneryDrawInfoTable; + pTopDrawInfo = SceneryDrawInfoTable + 3500; + + for (n = 0; n < NumCullInfos; n++) { + SceneryCullInfo *scenery_cull_info = &SceneryCullInfos[n]; + bool do_precull = true; + + scenery_cull_info->Position = *scenery_cull_info->pView->GetCamera()->GetPosition(); + scenery_cull_info->Direction = *scenery_cull_info->pView->GetCamera()->GetDirection(); + scenery_cull_info->H = scenery_cull_info->pView->H; + + if (PrecullerMode == 0) { + do_precull = false; + } else if (PrecullerMode == 2) { + int time = RealTimeFrames % 0x3D; + if (time < 0xF) { + do_precull = false; + } + } else if (PrecullerMode == 3) { + Camera *camera = scenery_cull_info->pView->GetCamera(); + float speed = bLength(reinterpret_cast(reinterpret_cast(camera) + 0x1E8)); + if (speed < EnablePrecullingSpeed) { + do_precull = false; + } + } + + if (DisablePrecullerCounter > 0 && PrecullerMode != 2) { + do_precull = false; + } + + if (do_precull) { + if (gPrecullerBooBooManager.IsSet(scenery_cull_info->Position)) { + do_precull = false; + } + } + scenery_cull_info->PrecullerSectionNumber = -1; + if (do_precull) { + scenery_cull_info->PrecullerSectionNumber = GetPrecullerSectionNumber(scenery_cull_info->Position.x, scenery_cull_info->Position.y); + } + } + + for (n = 0; n < NumCullInfos; n++) { + SceneryCullInfo *scenery_cull_info = &SceneryCullInfos[n]; + scenery_cull_info->pFirstDrawInfo = pCurrentDrawInfo; + scenery_cull_info->pCurrentDrawInfo = pCurrentDrawInfo; + scenery_cull_info->pTopDrawInfo = pTopDrawInfo; + CullView(scenery_cull_info); + pCurrentDrawInfo = scenery_cull_info->pCurrentDrawInfo; + } + + for (n = 0; n < NumCullInfos; n++) { + } +} + +void GrandSceneryCullInfo::StuffScenery(eView *view, int stuff_flags) { + unsigned int base_flags = 0; + unsigned int forbidden_flags = 0; + unsigned int required_flags = 0; + + if ((stuff_flags & 1) != 0) { + base_flags = 0x1000; + } + if ((stuff_flags & 0x400) != 0) { + required_flags = 0x100000; + } + if ((stuff_flags & 0x80) != 0) { + base_flags |= 0x100; + } + if ((stuff_flags & 0x10) != 0) { + required_flags = 0x2000; + } else if ((stuff_flags & 8) != 0) { + forbidden_flags = 0x2000; + } + if ((stuff_flags & 0x300) != 0) { + required_flags = 0x10000; + } + if ((stuff_flags & 0x800) != 0) { + forbidden_flags |= 0x400000; + } + if ((stuff_flags & 0x1000) != 0) { + required_flags = 0x1000000; + } + + for (int i = 0; i < NumCullInfos; i++) { + SceneryCullInfo *scenery_cull_info = &SceneryCullInfos[i]; + if (scenery_cull_info->pView != view) { + continue; + } + + for (SceneryDrawInfo *draw_info = scenery_cull_info->pFirstDrawInfo; draw_info < scenery_cull_info->pCurrentDrawInfo; draw_info++) { + unsigned int model_word = reinterpret_cast(draw_info->pModel); + unsigned int model_type = model_word & 3; + unsigned int exclude_flags = draw_info->SceneryInst->ExcludeFlags; + unsigned int render_flags = base_flags; + + pDebugModel = reinterpret_cast(model_word & ~3); + if ((exclude_flags & 0x80) != 0) { + render_flags |= 0x2000; + } + if ((exclude_flags & 0x1000000) != 0) { + render_flags |= 0x100000; + } + if ((exclude_flags & 0x100) != 0) { + render_flags |= 0x20000; + } + if ((exclude_flags & 0x400000) != 0) { + render_flags |= 0x40000; + } + if ((exclude_flags & 0x20000) != 0) { + render_flags |= 0x800000; + } + if ((exclude_flags & 0x80000) != 0) { + render_flags |= 0x400000; + } + if ((stuff_flags & 0x200) != 0) { + if ((exclude_flags & 0x200000) != 0 && ((exclude_flags >> 0x1A) & 1U) == 0) { + render_flags |= 0x10000; + } + if ((exclude_flags & 0x40000000) != 0) { + render_flags |= 0x10000; + } + } + if ((stuff_flags & 0x100) != 0) { + if ((exclude_flags & 0x200000) != 0) { + render_flags |= 0x10000; + } + if ((exclude_flags & 0x40000000) != 0) { + render_flags |= 0x10000; + } + } + if (model_type == 2) { + render_flags |= 4; + } + + bool required_ok = required_flags == 0 || (render_flags & required_flags) != 0; + bool forbidden_ok = forbidden_flags == 0 || (render_flags & forbidden_flags) == 0; + if (required_ok && forbidden_ok) { + bMatrix4 *matrix = draw_info->pMatrix; + if (!matrix) { + reinterpret_cast(view)->Render(pDebugModel, &eMathIdentityMatrix, 0, render_flags, 0); + } else { + reinterpret_cast(view)->Render(pDebugModel, matrix, 0, render_flags, 0); + } + pDebugModel = 0; + } + } + return; + } +} +void ServicePreculler() {} diff --git a/src/Speed/Indep/Src/World/Scenery.hpp b/src/Speed/Indep/Src/World/Scenery.hpp index 6083173f1..8d299a561 100644 --- a/src/Speed/Indep/Src/World/Scenery.hpp +++ b/src/Speed/Indep/Src/World/Scenery.hpp @@ -6,6 +6,7 @@ #endif #include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Libs/Support/Utility/UCrc.h" #include "Speed/Indep/Src/World/WeatherMan.hpp" #include "Speed/Indep/bWare/Inc/bMath.hpp" @@ -22,6 +23,11 @@ struct SceneryBoundingBox { // total size: 0x18 float BBoxMin[3]; // offset 0x0, size 0xC float BBoxMax[3]; // offset 0xC, size 0xC + + void GetBBox(bVector3 *bbox_min, bVector3 *bbox_max) { + bFill(bbox_min, BBoxMin[0], BBoxMin[1], BBoxMin[2]); + bFill(bbox_max, BBoxMax[0], BBoxMax[1], BBoxMax[2]); + } }; struct SceneryInstance : public SceneryBoundingBox { @@ -32,13 +38,53 @@ struct SceneryInstance : public SceneryBoundingBox { float Position[3]; // offset 0x20, size 0xC short Rotation[9]; // offset 0x2C, size 0x12 short SceneryInfoNumber; // offset 0x3E, size 0x2 + + void GetRotation(bMatrix4 *matrix) { + const float rotation_conversion = 0.00012207031f; + float x = static_cast(Rotation[0]) * rotation_conversion; + float y = static_cast(Rotation[1]) * rotation_conversion; + float z = static_cast(Rotation[2]) * rotation_conversion; + bFill(&matrix->v0, x, y, z, 0.0f); + x = static_cast(Rotation[3]) * rotation_conversion; + y = static_cast(Rotation[4]) * rotation_conversion; + z = static_cast(Rotation[5]) * rotation_conversion; + bFill(&matrix->v1, x, y, z, 0.0f); + x = static_cast(Rotation[6]) * rotation_conversion; + y = static_cast(Rotation[7]) * rotation_conversion; + z = static_cast(Rotation[8]) * rotation_conversion; + bFill(&matrix->v2, x, y, z, 0.0f); + bFill(&matrix->v3, 0.0f, 0.0f, 0.0f, 1.0f); + } + + void GetPosition(bVector4 *position) { + bFill(position, Position[0], Position[1], Position[2], 1.0f); + } + + bVector3 *GetPosition() { + return reinterpret_cast(Position); + } + + void SetMatrix(const bMatrix4 *matrix) { + const float rotation_conversion = 8192.0f; + Rotation[0] = static_cast(matrix->v0.x * rotation_conversion); + Rotation[1] = static_cast(matrix->v0.y * rotation_conversion); + Rotation[2] = static_cast(matrix->v0.z * rotation_conversion); + Rotation[3] = static_cast(matrix->v1.x * rotation_conversion); + Rotation[4] = static_cast(matrix->v1.y * rotation_conversion); + Rotation[5] = static_cast(matrix->v1.z * rotation_conversion); + Rotation[6] = static_cast(matrix->v2.x * rotation_conversion); + Rotation[7] = static_cast(matrix->v2.y * rotation_conversion); + Rotation[8] = static_cast(matrix->v2.z * rotation_conversion); + Position[0] = matrix->v3.x; + Position[1] = matrix->v3.y; + Position[2] = matrix->v3.z; + } }; struct SceneryDrawInfo { // total size: 0xC - eModel *pModel; // offset 0x0, size 0x4 - bMatrix4 *pMatrix; // offset 0x4, size 0x4 - char unk08[4]; + eModel *pModel; // offset 0x0, size 0x4 + bMatrix4 *pMatrix; // offset 0x4, size 0x4 SceneryInstance *SceneryInst; // offset 0x8, size 0x4 }; @@ -57,6 +103,127 @@ struct SceneryCullInfo { int PrecullerSectionNumber; // offset 0xB8, size 0x4 }; +// total size: 0x8 +struct ModelHeirarchy { + enum Flags { + F_INTERNAL = 1, + }; + // total size: 0x10 + struct Node { + UCrc32 mNodeName; // offset 0x0, size 0x4 + unsigned int mModelHash; // offset 0x4, size 0x4 + eModel *mModel; // offset 0x8, size 0x4 + unsigned char mFlags; // offset 0xC, size 0x1 + unsigned char mParent; // offset 0xD, size 0x1 + unsigned char mNumChildren; // offset 0xE, size 0x1 + unsigned char mChildIndex; // offset 0xF, size 0x1 + }; + + const Node *GetNodes() const {} + + Node *GetNodes() {} + + unsigned int GetSize() const {} + + unsigned int mNameHash; // offset 0x0, size 0x4 + unsigned char mNumNodes; // offset 0x4, size 0x1 + unsigned char mFlags; // offset 0x5, size 0x1 + unsigned short pad; // offset 0x6, size 0x2 +}; + +// total size: 0x48 +struct SceneryInfo { + // Members + char DebugName[24]; // offset 0x0, size 0x18 + unsigned int NameHash[4]; // offset 0x18, size 0x10 + eModel *pModel[4]; // offset 0x28, size 0x10 + float Radius; // offset 0x38, size 0x4 + unsigned int MeshChecksum; // offset 0x3C, size 0x4 + unsigned int mHeirarchyNameHash; // offset 0x40, size 0x4 + ModelHeirarchy *mHeirarchy; // offset 0x44, size 0x4 +}; + +class tPrecullerInfo { + public: + bool IsVisible(int preculler_section_number) { + return (VisibilityBits[preculler_section_number >> 3] & (1 << (preculler_section_number & 7))) != 0; + } + + bool IsNotVisible(int preculler_section_number) { + return !IsVisible(preculler_section_number); + } + + unsigned char *GetBits() { + return VisibilityBits; + } + + private: + unsigned char VisibilityBits[0x80]; +}; + +// total size: 0x24 +struct SceneryTreeNode : public SceneryBoundingBox { + short NumChildren; // offset 0x18, size 0x2 + short ChildCodes[5]; // offset 0x1A, size 0xA +}; + +struct ScenerySectionHeader : public bTNode { + void DrawAScenery(int scenery_instance_number, SceneryCullInfo *scenery_cull_info, int visibility_state); + + int IsVisible(SceneryCullInfo *scenery_cull_info); + + void CullBruteForce(SceneryCullInfo *scenery_cull_info); + + void TreeCull(SceneryCullInfo *scenery_cull_info); + + void CullNodeRecursive(SceneryTreeNode *node, SceneryCullInfo *scenery_cull_info, unsigned int visibility_state); + + SceneryInstance *GetSceneryInstance(int scenery_instance_number) { + return &pSceneryInstance[scenery_instance_number]; + } + + int GetSectionNumber() { + return this->SectionNumber; + } + + tPrecullerInfo *GetPrecullerInfo(int preculler_info_index) { + return &PrecullerInfoTable[preculler_info_index]; + } + + static float mLodLevelDistance[3]; // size: 0xC, address: 0xFFFFFFFF + + int ChunksLoaded; // offset 0x8, size 0x4 + int SectionNumber; // offset 0xC, size 0x4 + int NumPolygonsInMemory; // offset 0x10, size 0x4 + int NumPolygonsInWorld; // offset 0x14, size 0x4 + SceneryInfo *pSceneryInfo; // offset 0x18, size 0x4 + int NumSceneryInfo; // offset 0x1C, size 0x4 + SceneryInstance *pSceneryInstance; // offset 0x20, size 0x4 + int NumSceneryInstances; // offset 0x24, size 0x4 + SceneryTreeNode *SceneryTreeNodeTable; // offset 0x28, size 0x4 + int NumSceneryTreeNodes; // offset 0x2C, size 0x4 + tPrecullerInfo *PrecullerInfoTable; // offset 0x30, size 0x4 + int NumPrecullerInfos; // offset 0x34, size 0x4 + int ViewsVisibleThisFrame; // offset 0x38, size 0x4 +}; + +struct SceneryOverrideInfo { + void EndianSwap() { + bPlatEndianSwap(&SectionNumber); + bPlatEndianSwap(&InstanceNumber); + bPlatEndianSwap(&ExcludeFlags); + } + + void AssignOverrides(); + void AssignOverrides(ScenerySectionHeader *section_header); + + short SectionNumber; // offset 0x0, size 0x2 + short InstanceNumber; // offset 0x2, size 0x2 + unsigned short ExcludeFlags; // offset 0x4, size 0x2 +}; + +extern SceneryOverrideInfo *SceneryOverrideInfoTable; + // total size: 0x8014 struct SceneryGroup : public bTNode { // SceneryGroup(unsigned int name_hash) {} @@ -67,7 +234,9 @@ struct SceneryGroup : public bTNode { // int GetOverrideInfoNumber(int index) {} - // struct SceneryOverrideInfo *GetOverrideInfo(int index) {} + SceneryOverrideInfo *GetOverrideInfo(int index) { + return &SceneryOverrideInfoTable[OverrideInfoNumbers[index]]; + } // void EndianSwap() {} @@ -93,6 +262,6 @@ void CloseVisibleZones(); void ServicePreculler(); void LoadPrecullerBooBooScripts(); void EnableSceneryGroup(unsigned int group_name_hash, bool flip_artwork); -SceneryGroup *FindSceneryGroup(unsigned int name_hash); // TODO remove "class" +SceneryGroup *FindSceneryGroup(unsigned int name_hash); #endif diff --git a/src/Speed/Indep/Src/World/ScreenEffects.cpp b/src/Speed/Indep/Src/World/ScreenEffects.cpp index e69de29bb..83dfb7133 100644 --- a/src/Speed/Indep/Src/World/ScreenEffects.cpp +++ b/src/Speed/Indep/Src/World/ScreenEffects.cpp @@ -0,0 +1,415 @@ +#include "ScreenEffects.hpp" + +#include "Scenery.hpp" +#include "Speed/Indep/Src/Camera/Camera.hpp" +#include "Speed/Indep/Src/Camera/CameraMover.hpp" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Src/Ecstasy/EcstasyE.hpp" +#include "Speed/Indep/Src/Ecstasy/eLight.hpp" +#include "Speed/Indep/Src/Misc/GameFlow.hpp" +#include "Speed/Indep/Src/World/TrackPath.hpp" +#include "Speed/Indep/Src/World/Rain.hpp" +#include "Speed/Indep/Src/World/WCollisionMgr.h" +#include "Speed/Indep/Src/World/WWorldPos.h" +#include "Speed/Indep/Src/World/WeatherMan.hpp" +#include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +static unsigned int AccumulationBufferNeedsFlush = 0; +ScreenEffectPaletteDef SE_PaletteFile[EFX_NUMBER]; + +class eViewRenderShim : public eView { + public: + void Render(eModel *model, bMatrix4 *matrix, eLightContext *light_context, unsigned int a4, unsigned int a5, unsigned int a6); +}; + +extern eModel *pVisibleZoneBoundaryModel; +extern unsigned int FrameMallocFailed; +extern unsigned int FrameMallocFailAmount; +extern float GlareFalloff; +extern float GlareFallon; +extern float TUNHEIGHT; +extern int debugflash; +extern TrackPathZone *zoneB[2]; + +static inline UMath::Vector3 &bConvertToBond(UMath::Vector3 &dest, const bVector3 &v) { + bConvertToBond(*reinterpret_cast(&dest), v); + return dest; +} + +static int __tmp_14_27615; +static bVector3 lcamPosInside_27614[2]; +static float dataBackup_27616[2][12]; +static GenericRegion *regionB_27617[2]; +static unsigned int ticS_27592; + +class WWorldPosTopologyShim : public WWorldPos { + public: + WWorldPosTopologyShim(float yOffset) + : WWorldPos(yOffset) { + fFace.fPt0 = UMath::Vector3::kZero; + fFace.fPt1 = UMath::Vector3::kZero; + fFace.fPt2 = UMath::Vector3::kZero; + fFace.fSurface.fSurface = 0; + fFace.fSurface.fFlags = 0; + } +}; + +void InitScreenEFX(); + +enum TunnelBloomDataIndex { + kTunnelPoint0X = 0, + kTunnelPoint0Y = 1, + kTunnelPoint0Z = 2, + kTunnelPoint1X = 3, + kTunnelPoint1Y = 4, + kTunnelPoint1Z = 5, + kTunnelPoint2X = 6, + kTunnelPoint2Y = 7, + kTunnelPoint2Z = 8, + kTunnelPoint3X = 9, + kTunnelPoint3Y = 10, + kTunnelPoint3Z = 11, +}; + +ScreenEffectDB::ScreenEffectDB() { + SE_time = 0.0f; + for (int i = 0; i < SE_NUM_TYPES; i++) { + SE_inf[i].active = 0; + SE_data[i].r = 0.0f; + SE_data[i].g = 0.0f; + SE_data[i].b = 0.0f; + SE_data[i].a = 0.0f; + for (int j = 0; j < 14; j++) { + SE_data[i].data[j] = 0.0f; + } + SE_data[i].intensity = 0.0f; + SE_data[i].UpdateFnc = 0; + numType[i] = 0; + } + InitScreenEFX(); +} +void ScreenEffectDB::Update(float deltatime) { + SE_time += deltatime; + + for (int i = 0; i < SE_NUM_TYPES; i++) { + if (SE_inf[i].active == 1) { + SE_inf[i].frameNum += 1; + ScreenEffectControl controller = SE_inf[i].Controller; + if (controller == SEC_FRAME || controller == SEC_FUNCTION) { + SE_inf[i].active = 0; + numType[i] = 0; + } + } + } +} + +void ScreenEffectDB::AddScreenEffect(ScreenEffectType type, float intensity, float r, float g, float b) { + ScreenEffectDef info; + + info.intensity = intensity; + info.r = r; + info.g = g; + info.b = b; + info.UpdateFnc = 0; + AddScreenEffect(type, &info, 1, SEC_FRAME); +} + +void ScreenEffectDB::AddScreenEffect(ScreenEffectType type, ScreenEffectDef *info, unsigned int lock, + ScreenEffectControl controller) { + if (lock != 0) { + if (info) { + SE_data[type] = *info; + } + numType[type] = 1; + } else { + float influence; + float invFluence; + + numType[type] += 1; + influence = static_cast(numType[type]) / static_cast(numType[type] + 1); + invFluence = 1.0f - influence; + + SE_data[type].r = influence * SE_data[type].r + invFluence * info->r; + SE_data[type].g = influence * SE_data[type].g + invFluence * info->g; + SE_data[type].b = influence * SE_data[type].b + invFluence * info->b; + SE_data[type].a = influence * SE_data[type].a + invFluence * info->a; + SE_data[type].intensity = influence * SE_data[type].intensity + invFluence * info->intensity; + } + + SE_inf[type].active = 1; + if (SE_data[type].UpdateFnc) { + SE_data[type].UpdateFnc(type, this); + } else { + SetController(type, controller); + } + + if (SE_data[type].intensity < 0.01f) { + SE_inf[type].active = 0; + } +} + +void ScreenEffectDB::AddPaletteEffect(ScreenEffectPalette palette) { + AddPaletteEffect(&SE_PaletteFile[palette]); +} + +void ScreenEffectDB::AddPaletteEffect(ScreenEffectPaletteDef *palette) { + for (int i = 0; i < palette->NumEffects; i++) { + AddScreenEffect(palette->SE_type[i], &palette->SE_Def[i], 1, palette->SE_Controller[i]); + } +} + +void InitScreenEFX() {} + +void TickSFX() { + if (TheGameFlowManager.IsInGame()) { + if (ticS_27592 != eFrameCounter - 1) { + UpdateAllScreenEFX(); + } + ticS_27592 = eFrameCounter; + } +} + +void UpdateAllScreenEFX() { + for (int i = 1; i <= 2; i++) { + eView *view = eGetView(i, false); + if (view->IsActive()) { + eGetView(i, false)->ScreenEffects->Update(0.033333335f); + if (debugflash != 0) { + debugflash = 0; + eGetView(i, false)->ScreenEffects->AddPaletteEffect(EFX_CAMERA_FLASH); + } + } + } +} + +void FlushAccumulationBuffer() { + AccumulationBufferNeedsFlush = 1; +} + +void AccumulationBufferFlushed() { + AccumulationBufferNeedsFlush = 0; +} + +unsigned int QueryFlushAccumulationBuffer() { + return AccumulationBufferNeedsFlush; +} +void DoTinting(eView *view) { + ScreenEffectDef SE_def; + unsigned int r; + unsigned int g; + unsigned int b; + float intense; + + if (IsRainDisabled()) { + return; + } + + if (view->Precipitation) { + intense = view->Precipitation->GetCloudIntensity(); + } else { + intense = 0.0f; + } + + if (0.0f < intense) { + if (view->Precipitation) { + view->Precipitation->GetPrecipFogColour(&r, &g, &b); + } + SE_def.r = static_cast(r); + SE_def.g = static_cast(g); + SE_def.a = 128.0f; + SE_def.UpdateFnc = 0; + SE_def.intensity = intense; + SE_def.b = static_cast(b); + view->ScreenEffects->AddScreenEffect(SE_TINT, &SE_def, 1, SEC_FRAME); + } +} + +void DoTunnelBloom(eView *view) { + int vIndex = 1; + if (view->GetID() == 1) { + vIndex = 0; + } + + if (!view->IsActive()) { + return; + } + + CameraMover *camera_mover = view->GetCameraMover(); + if (!camera_mover) { + return; + } + + CameraAnchor *camera_anchor = camera_mover->GetAnchor(); + if (!camera_anchor) { + return; + } + + bVector3 *my_car_pos = camera_anchor->GetGeometryPosition(); + Camera *view_camera = view->GetCamera(); + bVector3 *camera_position = view_camera->GetPosition(); + bVector3 *camera_direction = view_camera->GetDirection(); + bVector2 twoDpos(camera_position->x, camera_position->y); + + if (!__tmp_14_27615) { + int i = 1; + do { + i -= 1; + } while (i + 1 != 0); + __tmp_14_27615 = 1; + } + + TrackPathZone *zone = 0; + bVector3 endVector; + bVector3 posScreen; + TrackPathZone *zoneBP = zoneB[vIndex]; + if (zoneBP && zoneBP->IsPointInside(&twoDpos)) { + zone = zoneB[vIndex]; + } else { + zone = TheTrackPathManager.FindZone(&twoDpos, TRACK_PATH_ZONE_TUNNEL, 0); + } + + if (zone && zone->GetElevation() > my_car_pos->z) { + lcamPosInside_27614[vIndex] = *camera_position; + float angleCos = 0.0f; + GenericRegion *end_tunnel = GetClosestRegionInView(view, &endVector, &angleCos); + if (!end_tunnel) { + return; + } + + ScreenEffectDef SE_def; + bVector2 endP(endVector.x, endVector.y); + bVector2 p0; + bVector2 p1; + float len = zone->GetSegmentNextTo(&endP, &p0, &p1); + if (len == -1.0f || len >= 40.0f) { + return; + } + + bVector3 p3(endVector); + UMath::Vector3 usPoint; + bConvertToBond(usPoint, p3); + float height = 0.0f; + WCollisionMgr(0, 3).GetWorldHeightAtPointRigorous(usPoint, height, 0); + + dataBackup_27616[vIndex][kTunnelPoint0X] = p0.x + camera_direction->x; + dataBackup_27616[vIndex][kTunnelPoint0Y] = p0.y + camera_direction->y; + dataBackup_27616[vIndex][kTunnelPoint0Z] = height + camera_direction->z; + dataBackup_27616[vIndex][kTunnelPoint1X] = p1.x + camera_direction->x; + dataBackup_27616[vIndex][kTunnelPoint1Y] = p1.y + camera_direction->y; + dataBackup_27616[vIndex][kTunnelPoint1Z] = height + camera_direction->z; + dataBackup_27616[vIndex][kTunnelPoint2X] = p0.x + camera_direction->x; + dataBackup_27616[vIndex][kTunnelPoint2Y] = p0.y + camera_direction->y; + dataBackup_27616[vIndex][kTunnelPoint2Z] = height + TUNHEIGHT + camera_direction->z; + dataBackup_27616[vIndex][kTunnelPoint3X] = p1.x + camera_direction->x; + dataBackup_27616[vIndex][kTunnelPoint3Y] = p1.y + camera_direction->y; + dataBackup_27616[vIndex][kTunnelPoint3Z] = height + TUNHEIGHT + camera_direction->z; + + SE_def.r = 128.0f; + SE_def.g = 128.0f; + SE_def.b = 128.0f; + SE_def.a = 128.0f; + SE_def.UpdateFnc = 0; + SE_def.data[0] = dataBackup_27616[vIndex][kTunnelPoint0X]; + SE_def.data[1] = dataBackup_27616[vIndex][kTunnelPoint0Y]; + SE_def.data[2] = dataBackup_27616[vIndex][kTunnelPoint0Z]; + SE_def.data[3] = dataBackup_27616[vIndex][kTunnelPoint1X]; + SE_def.data[4] = dataBackup_27616[vIndex][kTunnelPoint1Y]; + SE_def.data[5] = dataBackup_27616[vIndex][kTunnelPoint1Z]; + SE_def.data[6] = dataBackup_27616[vIndex][kTunnelPoint2X]; + SE_def.data[7] = dataBackup_27616[vIndex][kTunnelPoint2Y]; + SE_def.data[8] = dataBackup_27616[vIndex][kTunnelPoint2Z]; + SE_def.data[9] = dataBackup_27616[vIndex][kTunnelPoint3X]; + SE_def.data[10] = dataBackup_27616[vIndex][kTunnelPoint3Y]; + SE_def.data[11] = dataBackup_27616[vIndex][kTunnelPoint3Z]; + + if (regionB_27617[vIndex] != end_tunnel) { + view->ScreenEffects->SetDATA(SE_GLARE, 0.0f, 1); + } + regionB_27617[vIndex] = end_tunnel; + if (zoneB[vIndex] != zone) { + view->ScreenEffects->SetDATA(SE_GLARE, 0.0f, 1); + } + zoneB[vIndex] = zone; + + bVector2 r = p0 - twoDpos; + bVector2 v(p1.y - p0.y, p0.x - p1.x); + bNormalize(&v, &v); + float dir_dot = bAbs(bDot(&v, &r)); + if (dir_dot < 17.0f) { + SE_def.intensity = view->ScreenEffects->GetDATA(SE_GLARE, 1) * 0.05882353f * dir_dot; + } else { + SE_def.intensity = 1.0f; + if (view->ScreenEffects->GetDATA(SE_GLARE, 1) < 1.0f) { + SE_def.intensity = view->ScreenEffects->GetDATA(SE_GLARE, 1) + GlareFallon; + } + } + + view->ScreenEffects->SetDATA(SE_GLARE, SE_def.intensity, 1); + view->ScreenEffects->AddScreenEffect(SE_GLARE, &SE_def, 1, SEC_FRAME); + AccumulationBufferNeedsFlush = 1; + + if (view->Precipitation && 0.0f < view->Precipitation->GetRainIntensity()) { + view->Precipitation->IsValidRainCurtainPos = CT_OVERIDE; + view->Precipitation->AttachRainCurtain( + SE_def.data[6], + SE_def.data[7], + SE_def.data[8], + SE_def.data[9], + SE_def.data[10], + SE_def.data[11], + SE_def.data[0], + SE_def.data[1], + SE_def.data[2], + SE_def.data[3], + SE_def.data[4], + SE_def.data[5] + ); + } + return; + } + + if (0.0f < view->ScreenEffects->GetIntensity(SE_GLARE)) { + ScreenEffectDef SE_def; + bVector3 midpoint( + dataBackup_27616[vIndex][kTunnelPoint0X], + dataBackup_27616[vIndex][kTunnelPoint0Y], + dataBackup_27616[vIndex][kTunnelPoint0Z] + ); + bVector3 ToGlare; + float BaseGlare = view->ScreenEffects->GetIntensity(SE_GLARE) - GlareFalloff; + + midpoint += bVector3( + dataBackup_27616[vIndex][kTunnelPoint1X], + dataBackup_27616[vIndex][kTunnelPoint1Y], + dataBackup_27616[vIndex][kTunnelPoint1Z] + ); + midpoint *= 0.5f; + + SE_def.r = 128.0f; + SE_def.g = 128.0f; + SE_def.b = 128.0f; + SE_def.a = 128.0f; + SE_def.UpdateFnc = 0; + SE_def.intensity = BaseGlare; + ToGlare = *camera_position - lcamPosInside_27614[vIndex]; + ToGlare += *camera_direction; + + if (0.0f < BaseGlare) { + SE_def.data[0] = ToGlare.x + dataBackup_27616[vIndex][kTunnelPoint0X]; + SE_def.data[1] = ToGlare.y + dataBackup_27616[vIndex][kTunnelPoint0Y]; + SE_def.data[2] = ToGlare.z + dataBackup_27616[vIndex][kTunnelPoint0Z]; + SE_def.data[3] = ToGlare.x + dataBackup_27616[vIndex][kTunnelPoint1X]; + SE_def.data[4] = ToGlare.y + dataBackup_27616[vIndex][kTunnelPoint1Y]; + SE_def.data[5] = ToGlare.z + dataBackup_27616[vIndex][kTunnelPoint1Z]; + SE_def.data[6] = ToGlare.x + dataBackup_27616[vIndex][kTunnelPoint2X]; + SE_def.data[7] = ToGlare.y + dataBackup_27616[vIndex][kTunnelPoint2Y]; + SE_def.data[8] = ToGlare.z + dataBackup_27616[vIndex][kTunnelPoint2Z]; + SE_def.data[9] = ToGlare.x + dataBackup_27616[vIndex][kTunnelPoint3X]; + SE_def.data[10] = ToGlare.y + dataBackup_27616[vIndex][kTunnelPoint3Y]; + SE_def.data[11] = ToGlare.z + dataBackup_27616[vIndex][kTunnelPoint3Z]; + view->ScreenEffects->AddScreenEffect(SE_GLARE, &SE_def, 1, SEC_FRAME); + AccumulationBufferNeedsFlush = 1; + } + } +} diff --git a/src/Speed/Indep/Src/World/ScreenEffects.hpp b/src/Speed/Indep/Src/World/ScreenEffects.hpp index c4724a7c6..307e3aab3 100644 --- a/src/Speed/Indep/Src/World/ScreenEffects.hpp +++ b/src/Speed/Indep/Src/World/ScreenEffects.hpp @@ -5,10 +5,25 @@ #pragma once #endif +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" + +inline float ScreenEffectDB::GetIntensity(ScreenEffectType type) { + return SE_data[type].intensity; +} + +inline float ScreenEffectDB::GetDATA(ScreenEffectType type, int index) { + return SE_data[type].data[index]; +} + +inline void ScreenEffectDB::SetDATA(ScreenEffectType type, float data, int index) { + SE_data[type].data[index] = data; +} + void TickSFX(); void DoTunnelBloom(struct eView *view /* r25 */); void DoTinting(struct eView *view /* r31 */); void UpdateAllScreenEFX(); +void FlushAccumulationBuffer(); void AccumulationBufferFlushed(); unsigned int QueryFlushAccumulationBuffer(); diff --git a/src/Speed/Indep/Src/World/Skids.cpp b/src/Speed/Indep/Src/World/Skids.cpp index e69de29bb..37a2f6692 100644 --- a/src/Speed/Indep/Src/World/Skids.cpp +++ b/src/Speed/Indep/Src/World/Skids.cpp @@ -0,0 +1,348 @@ +#include "Skids.hpp" + +#include "Speed/Indep/Src/Camera/Camera.hpp" +#include "Speed/Indep/Src/Ecstasy/Texture.hpp" +#include "Speed/Indep/Src/Ecstasy/eMath.hpp" +#include "Speed/Indep/Src/Misc/Profiler.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" +#include "Speed/Indep/bWare/Inc/bVector.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +void bInitializeBoundingBox(bVector3 *bbox_min, bVector3 *bbox_max, const bVector3 *point); +void bExpandBoundingBox(bVector3 *bbox_min, bVector3 *bbox_max, const bVector3 *point, float extra_width); +void bExpandBoundingBox(bVector3 *bbox_min, bVector3 *bbox_max, const bVector3 *bbox2_min, const bVector3 *bbox2_max); +int bIsSlotPoolFull(SlotPool *slot_pool); +extern bVector3 ZeroVector; + +static const int kNumSkidSegments_Skids = 8; +static const int kNumSkidTextures_Skids = 29; +static const float kSkidSegmentScale_Skids = 64.0f; +static const float kInverseSkidSegmentScale_Skids = 1.0f / 64.0f; +static const float kSkidDirectionBreakThreshold_Skids = 0.2f; +static const float kSkidDirectionMergeThreshold_Skids = 0.002f; +static const float kSkidLengthMergeThreshold_Skids = 0.25f; +static const float kSkidLengthSplitThreshold_Skids = 3.0f; +static const float kSkidIntensityScale_Skids = 255.0f; +static const unsigned int kSkidColour_Skids = 0x80808080; + +class eViewSkidRenderShim : public eView { + public: + void Render(ePoly *poly, TextureInfo *texture_info, bMatrix4 *matrix, int flags, float z_bias); +}; + +void SkidSegment::SetPoints(bVector3 *position, bVector3 *delta_position) { + const float scale_factor = kSkidSegmentScale_Skids; + float x = position->x; + float y = position->y; + float z = position->z; + int dx = static_cast(delta_position->x * scale_factor); + int dy = static_cast(delta_position->y * scale_factor); + int dz = static_cast(delta_position->z * scale_factor); + + Position[0] = x; + Position[1] = y; + Position[2] = z; + DeltaPosition[0] = static_cast(dx); + DeltaPosition[1] = static_cast(dy); + DeltaPosition[2] = static_cast(dz); +} + +SlotPool *SkidSetSlotPool = 0; +int PlotSkidsInCaffeine = 0; +int PlotSkidPointsInCaffeine = 0; +bTList SkidSetList; +TextureInfo *SkidTextureInfo[kNumSkidTextures_Skids]; + +void SkidSegment::GetPoints(bVector3 *position, bVector3 *delta_position) { + const float scale_factor = kInverseSkidSegmentScale_Skids; + float x; + float y; + float z; + float dx; + float dy; + float dz; + + dx = static_cast(DeltaPosition[0]) * scale_factor; + dy = static_cast(DeltaPosition[1]) * scale_factor; + y = Position[1]; + dz = static_cast(DeltaPosition[2]) * scale_factor; + z = Position[2]; + x = Position[0]; + + position->x = x; + position->y = y; + position->z = z; + + if (delta_position) { + delta_position->x = dx; + delta_position->y = dy; + delta_position->z = dz; + } +} + +void SkidSegment::GetEndPoints(bVector3 *left_point, bVector3 *right_point) { + const float scale_factor = kInverseSkidSegmentScale_Skids; + float x; + float y; + float z; + float dx; + float dy; + float dz; + + x = Position[0]; + dx = static_cast(DeltaPosition[0]) * scale_factor; + y = Position[1]; + z = Position[2]; + dy = static_cast(DeltaPosition[1]) * scale_factor; + dz = static_cast(DeltaPosition[2]) * scale_factor; + left_point->x = x + dx; + left_point->y = y + dy; + left_point->z = z + dz; + right_point->x = x - dx; + right_point->y = y - dy; + right_point->z = z - dz; +} + +SkidSet::SkidSet(SkidMaker *skid_maker, bVector3 *position, bVector3 *delta_position, int terrain_type, float intensity) { + TheTerrainType = terrain_type; + NumSkidSegments = 0; + pSkidMaker = skid_maker; + Position = *position; + bInitializeBoundingBox(&BBoxMin, &BBoxMax, position); + BBoxCentre = *position; + + pClan = GetClan(position); + pClanNode = pClan->SkidSetList.AddTail(this); + + AddSegment(position, delta_position, false, intensity); +} + +SkidSet::~SkidSet() { + if (pSkidMaker) { + pSkidMaker->MakeNoSkid(); + } + + pClan->SkidSetList.Remove(pClanNode); +} + +int SkidSet::AddSegment(bVector3 *position, bVector3 *delta_position, bool skid_is_flaming, float intensity) { + (void)skid_is_flaming; + + bVector3 new_segment_forward; + float length = 0.0f; + if (NumSkidSegments > 0) { + bVector3 new_segment_normal = *position - *SkidSegments[NumSkidSegments - 1].GetPosition(); + bVector3 new_segment_delta(new_segment_normal); + length = bLength(&new_segment_delta); + bNormalize(&new_segment_forward, &new_segment_delta); + } + + int expand_last_skid_segment = 0; + if (NumSkidSegments > 1) { + float error = 1.0f - bDot(&new_segment_forward, &LastNormal); + float new_segment_length = LastSegmentLength + length; + if (error > kSkidDirectionBreakThreshold_Skids) { + FinishedAddingSkids(); + return 0; + } + + if (new_segment_length < kSkidLengthMergeThreshold_Skids) { + expand_last_skid_segment = 1; + } + if (error < kSkidDirectionMergeThreshold_Skids) { + expand_last_skid_segment = 1; + } + if (new_segment_length > kSkidLengthSplitThreshold_Skids) { + expand_last_skid_segment = 0; + } + } + + SkidSegment *skid_segment; + if (expand_last_skid_segment) { + skid_segment = &SkidSegments[NumSkidSegments - 1]; + LastSegmentLength += length; + } else if (NumSkidSegments == kNumSkidSegments_Skids) { + return 1; + } else { + skid_segment = &SkidSegments[NumSkidSegments]; + NumSkidSegments += 1; + if (NumSkidSegments > 1) { + LastNormal = new_segment_forward; + } + LastSegmentLength = length; + } + + skid_segment->SetPoints(position, delta_position); + skid_segment->SetIntensity(static_cast(intensity * kSkidIntensityScale_Skids)); + + bExpandBoundingBox(&BBoxMin, &BBoxMax, position, length); + pClan->ExpandBoundingBox(&BBoxMin, &BBoxMax); + + bAdd(&BBoxCentre, &BBoxMin, &BBoxMax); + bScale(&BBoxCentre, &BBoxCentre, 0.5f); + return 0; +} + +void SkidSet::FinishedAddingSkids() { + if (pSkidMaker) { + pSkidMaker->pSkidSet = 0; + pSkidMaker = 0; + } +} + +void SkidSet::Render(eView *view, unsigned char intensityReduction) { + if (!SkidTextureInfo[TheTerrainType]) { + return; + } + + bMatrix4 *identity_matrix = eGetIdentityMatrix(); + ePoly poly; + float extra_height = 0.05f; + + for (int n = 0; n < NumSkidSegments - 1; n++) { + SkidSegment *skid_segment = &SkidSegments[n]; + SkidSegment *next_skid_segment = &SkidSegments[n + 1]; + unsigned char alpha0; + unsigned char alpha1; + + skid_segment->GetEndPoints(&poly.Vertices[0], &poly.Vertices[3]); + next_skid_segment->GetEndPoints(&poly.Vertices[1], &poly.Vertices[2]); + poly.Vertices[0].z += extra_height; + poly.Vertices[1].z += extra_height; + poly.Vertices[2].z += extra_height; + poly.Vertices[3].z += extra_height; + + alpha0 = skid_segment->GetIntensity(); + alpha1 = next_skid_segment->GetIntensity(); + if (intensityReduction > alpha0) { + alpha0 = 0; + } else { + alpha0 -= intensityReduction; + } + if (intensityReduction > alpha1) { + alpha1 = 0; + } else { + alpha1 -= intensityReduction; + } + + *reinterpret_cast(&poly.Colours[0][0]) = kSkidColour_Skids; + *reinterpret_cast(&poly.Colours[1][0]) = kSkidColour_Skids; + *reinterpret_cast(&poly.Colours[2][0]) = kSkidColour_Skids; + *reinterpret_cast(&poly.Colours[3][0]) = kSkidColour_Skids; + poly.Colours[0][3] = alpha0; + poly.Colours[1][3] = alpha1; + poly.Colours[2][3] = alpha1; + poly.Colours[3][3] = alpha0; + reinterpret_cast(view)->Render(&poly, SkidTextureInfo[TheTerrainType], identity_matrix, 0, + 0.05f); + } +} + +SkidSet *CreateNewSkidSet(SkidMaker *skid_maker, bVector3 *position, bVector3 *delta_position, int terrain_type, float intensity) { + if (bIsSlotPoolFull(SkidSetSlotPool)) { + SkidSet *oldest_skid_set = static_cast(SkidSetList.GetTail()->Remove()); + if (oldest_skid_set) { + delete oldest_skid_set; + } + } + + SkidSet *skid_set = new SkidSet(skid_maker, position, delta_position, terrain_type, intensity); + SkidSetList.AddHead(skid_set); + return skid_set; +} + +void SkidMaker::MakeSkid(Car *pCar, bVector3 *position, bVector3 *delta_position, int terrain_type, float intensity) { + bool make_flaming_skids = false; + if (pCar) { + float distance_from_car = bDistBetween(0, position); + if (distance_from_car > 4.0f) { + return; + } + } + + if (!pSkidSet) { + pSkidSet = CreateNewSkidSet(this, position, delta_position, terrain_type, intensity); + } else if (pSkidSet->GetTerrainType() != terrain_type || + pSkidSet->AddSegment(position, delta_position, make_flaming_skids, intensity) != 0) { + bVector3 last_position; + bVector3 last_delta_position; + float last_intensity; + + pSkidSet->GetLastPoints(&last_position, &last_delta_position); + last_intensity = pSkidSet->GetLastIntensity(); + pSkidSet->FinishedAddingSkids(); + SkidSet *new_skid_set = CreateNewSkidSet(this, &last_position, &last_delta_position, terrain_type, last_intensity); + new_skid_set->AddSegment(position, delta_position, make_flaming_skids, intensity); + pSkidSet = new_skid_set; + } +} + +void SkidMaker::MakeNoSkid() { + if (pSkidSet) { + pSkidSet->FinishedAddingSkids(); + } +} + +void InitSkids(int max_skids) { + if (!SkidSetSlotPool) { + SkidSetSlotPool = bNewSlotPool(0xF0, max_skids, "SkidSetSlotPool", GetVirtualMemoryAllocParams()); + SkidSetSlotPool->Flags = static_cast(SkidSetSlotPool->Flags & ~1); + } + + for (int i = 0; i < kNumSkidTextures_Skids; i++) { + SkidTextureInfo[i] = 0; + SkidTextureInfo[i] = GetTextureInfo(bStringHash("SKID_ROAD"), 1, 0); + } + + PlotSkidsInCaffeine = 0; + PlotSkidPointsInCaffeine = 0; +} + +void CloseSkids() { + if (SkidSetSlotPool) { + bDeleteSlotPool(SkidSetSlotPool); + SkidSetSlotPool = 0; + } + + for (int n = 0; n < kNumSkidTextures_Skids; n++) { + SkidTextureInfo[n] = 0; + } +} + +void DeleteThisSkid(SkidSet *skid_set) { + SkidSetList.Remove(skid_set); + if (skid_set) { + delete skid_set; + } +} + +void DeleteAllSkids() { + while (!SkidSetList.IsEmpty()) { + delete static_cast(SkidSetList.GetTail()->Remove()); + } +} + +void RenderSkids(eView *view, Clan *clan) { + ProfileNode profile_node("TODO", 0); + + for (bPNode *p = clan->SkidSetList.GetHead(); p != clan->SkidSetList.EndOfList(); p = p->GetNext()) { + SkidSet *skid_set = reinterpret_cast(p->GetObject()); + eVisibleState visibility = view->GetVisibleState(skid_set->GetBBoxMin(), skid_set->GetBBoxMax(), 0); + if (visibility != EVISIBLESTATE_NOT) { + int pixel_size = view->GetPixelSize(bDistBetween(skid_set->GetBBoxCentre(), view->GetCamera()->GetPosition()), 1.0f); + if (4.0f < static_cast(pixel_size)) { + unsigned char intensityReduction; + if (10.0f < static_cast(pixel_size)) { + intensityReduction = 0; + } else { + intensityReduction = + static_cast(static_cast(256.0f - (pixel_size - 4.0f) * 42.666668f) & 0xff); + } + + skid_set->Render(view, intensityReduction); + SkidSetList.Remove(skid_set); + SkidSetList.AddHead(skid_set); + } + } + } +} diff --git a/src/Speed/Indep/Src/World/Skids.hpp b/src/Speed/Indep/Src/World/Skids.hpp index 0346dee8b..56ff784c7 100644 --- a/src/Speed/Indep/Src/World/Skids.hpp +++ b/src/Speed/Indep/Src/World/Skids.hpp @@ -5,7 +5,99 @@ #pragma once #endif +#include "Clans.hpp" +#include "Car.hpp" +#include "Speed/Indep/bWare/Inc/bList.hpp" +#include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "Speed/Indep/bWare/Inc/bSlotPool.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +class eView; +extern SlotPool *SkidSetSlotPool; + +class SkidSegment { + private: + // total size: 0x10 + float Position[3]; // offset 0x0, size 0xC + signed char DeltaPosition[3]; // offset 0xC, size 0x3 + unsigned char Intensity; // offset 0xF, size 0x1 + + public: + bVector3 *GetPosition() { + return reinterpret_cast(Position); + } + void SetIntensity(unsigned char intensity) { + Intensity = intensity; + } + unsigned char GetIntensity() { + return Intensity; + } + void SetPoints(bVector3 *position, bVector3 *delta_position); + void GetPoints(bVector3 *position, bVector3 *delta_position); + void GetEndPoints(bVector3 *left_point, bVector3 *right_point); +}; + +struct SkidMaker { + // total size: 0x4 + struct SkidSet *pSkidSet; // offset 0x0, size 0x4 + + void MakeSkid(Car *pCar, bVector3 *position, bVector3 *delta_position, int terrain_type, float intensity); + void MakeNoSkid(); +}; + +struct SkidSet : public bTNode { + // total size: 0xF0 + bVector3 LastNormal; // offset 0x8, size 0x10 + float LastSegmentLength; // offset 0x18, size 0x4 + Clan *pClan; // offset 0x1C, size 0x4 + bPNode *pClanNode; // offset 0x20, size 0x4 + SkidMaker *pSkidMaker; // offset 0x24, size 0x4 + int TheTerrainType; // offset 0x28, size 0x4 + bVector3 Position; // offset 0x2C, size 0x10 + bVector3 BBoxMax; // offset 0x3C, size 0x10 + bVector3 BBoxMin; // offset 0x4C, size 0x10 + SkidSegment SkidSegments[8]; // offset 0x5C, size 0x80 + int NumSkidSegments; // offset 0xDC, size 0x4 + bVector3 BBoxCentre; // offset 0xE0, size 0x10 + + void *operator new(size_t size) { + return bMalloc(SkidSetSlotPool); + } + void operator delete(void *ptr) { + bFree(SkidSetSlotPool, ptr); + } + + SkidSet(SkidMaker *skid_maker, bVector3 *position, bVector3 *delta_position, int terrain_type, float intensity); + ~SkidSet(); + + int AddSegment(bVector3 *position, bVector3 *delta_position, bool skid_is_flaming, float intensity); + void FinishedAddingSkids(); + void Render(eView *view, unsigned char alpha); + int GetTerrainType() { + return TheTerrainType; + } + bVector3 *GetBBoxMax() { + return &BBoxMax; + } + bVector3 *GetBBoxMin() { + return &BBoxMin; + } + bVector3 *GetBBoxCentre() { + return &BBoxCentre; + } + void GetLastPoints(bVector3 *position, bVector3 *delta_position) { + SkidSegments[NumSkidSegments - 1].GetPoints(position, delta_position); + } + float GetLastIntensity() { + return static_cast(SkidSegments[NumSkidSegments - 1].GetIntensity()) * (1.0f / 255.0f); + } +}; + +SkidSet *CreateNewSkidSet(SkidMaker *skid_maker, bVector3 *position, bVector3 *delta_position, int terrain_type, float intensity); void InitSkids(int max_skids); void CloseSkids(); +void DeleteThisSkid(SkidSet *skid_set); +void DeleteAllSkids(); +void RenderSkids(eView *view, Clan *clan); #endif diff --git a/src/Speed/Indep/Src/World/Track.cpp b/src/Speed/Indep/Src/World/Track.cpp index e69de29bb..1daf337db 100644 --- a/src/Speed/Indep/Src/World/Track.cpp +++ b/src/Speed/Indep/Src/World/Track.cpp @@ -0,0 +1,148 @@ +#include "Track.hpp" + +#include "Speed/Indep/Src/World/WWorldPos.h" +#include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#include "Speed/Indep/bWare/Inc/bChunk.hpp" + +void bEndianSwap32(void *value); + +static inline UMath::Vector3 &bConvertToBond_Track(UMath::Vector3 &dest, const bVector3 &v) { + bConvertToBond(*reinterpret_cast(&dest), v); + return dest; +} + +class WWorldPosTopologyShim_Track : public WWorldPos { + public: + WWorldPosTopologyShim_Track(float yOffset) + : WWorldPos(yOffset) { + fFace.fPt0 = UMath::Vector3::kZero; + fFace.fPt1 = UMath::Vector3::kZero; + fFace.fPt2 = UMath::Vector3::kZero; + fFace.fSurface.fSurface = 0; + fFace.fSurface.fFlags = 0; + } +}; + +enum TerrainType { + TERRAIN_TYPE_NONE = 0, + TERRAIN_TYPE_ROAD = 1, + TERRAIN_TYPE_ROAD_WET = 2, + TERRAIN_TYPE_ROAD_DRIFT = 3, + TERRAIN_TYPE_ROAD_SMOKE_1 = 4, + TERRAIN_TYPE_ROAD_SMOKE_2 = 5, + TERRAIN_TYPE_ROAD_SMOKE_3 = 6, + TERRAIN_TYPE_BRIDGE = 7, + TERRAIN_TYPE_DIRT = 8, + TERRAIN_TYPE_GRAVEL = 9, + TERRAIN_TYPE_ROUGH_ROAD = 10, + TERRAIN_TYPE_COBBLESTONE = 11, + TERRAIN_TYPE_STAIRS = 12, + TERRAIN_TYPE_PUDDLE = 13, + TERRAIN_TYPE_DEEP_WATER = 14, + TERRAIN_TYPE_GRASS = 15, + TERRAIN_TYPE_SIDEWALK = 16, + TERRAIN_TYPE_WOOD = 17, + TERRAIN_TYPE_PLASTIC = 18, + TERRAIN_TYPE_GLASS = 19, + TERRAIN_TYPE_SOLID_WALL = 20, + TERRAIN_TYPE_SEE_THROUGH_WALL = 21, + TERRAIN_TYPE_PLANT = 22, + TERRAIN_TYPE_POST = 23, + TERRAIN_TYPE_PILLAR = 24, + TERRAIN_TYPE_METAL_GRATE = 25, + TERRAIN_TYPE_METAL = 26, + TERRAIN_TYPE_CHAINLINK = 27, + TERRAIN_TYPE_CAR = 28, + NUM_TERRAIN_TYPES = 29, +}; + +struct TopologyCoordinate { + int HasTopology(const bVector2 *position); + float GetElevation(const bVector3 *position, enum TerrainType *type, bVector3 *normal, bool *point_valid); + + private: + int mData[2]; +}; + +// total size: 0x60 +struct TrackOBB { + inline void EndianSwap() { + bPlatEndianSwap(&TypeNameHash); + bPlatEndianSwap(&Matrix); + bPlatEndianSwap(&Dims); + } + + unsigned int TypeNameHash; + unsigned int Pad[3]; + bMatrix4 Matrix; + bVector3 Dims; +}; + +static char *TrackOBBTable = 0; +static int NumTrackOBBs = 0; +bChunkLoader bChunkLoaderTrackOBB(0x34191, LoaderTrackOBB, UnloaderTrackOBB); + +int GetNumTrackOBBs() { + return NumTrackOBBs; +} + +TrackOBB *GetTrackOBB(int index) { + return reinterpret_cast(TrackOBBTable + index * 0x60); +} + +int LoaderTrackOBB(bChunk *chunk) { + if (chunk->GetID() != 0x34191) { + return 0; + } + + NumTrackOBBs = static_cast(chunk->GetAlignedSize(16)) / 0x60u; + TrackOBBTable = chunk->GetAlignedData(16); + for (int n = 0; n < NumTrackOBBs; n++) { + TrackOBB *track_obb = reinterpret_cast(TrackOBBTable + n * 0x60); + track_obb->EndianSwap(); + } + + return 1; +} + +int UnloaderTrackOBB(bChunk *chunk) { + if (chunk->GetID() != 0x34191) { + return 0; + } + + NumTrackOBBs = 0; + TrackOBBTable = 0; + return 1; +} + +void EstablishRemoteCaffeineConnection() {} + +float TopologyCoordinate::GetElevation(const bVector3 *position, enum TerrainType *type, bVector3 *normal, bool *point_valid) { + UMath::Vector3 bond_pos; + UMath::Vector4 dummy_normal; + + (void)type; + (void)normal; + + bConvertToBond_Track(bond_pos, *position); + WWorldPosTopologyShim_Track world_pos(0.025f); + world_pos.Update(bond_pos, dummy_normal, true, 0, true); + if (point_valid) { + *point_valid = world_pos.OnValidFace(); + } + if (world_pos.OnValidFace()) { + return world_pos.HeightAtPoint(bond_pos); + } + return position->z; +} + +int TopologyCoordinate::HasTopology(const bVector2 *position) { + float test_elevation; + bVector3 test_position(position->x, position->y, 99999.1015625f); + bool point_valid; + + test_elevation = GetElevation(&test_position, 0, 0, &point_valid); + (void)test_elevation; + return point_valid; +} diff --git a/src/Speed/Indep/Src/World/Track.hpp b/src/Speed/Indep/Src/World/Track.hpp index 31877c32d..41ee4dc6a 100644 --- a/src/Speed/Indep/Src/World/Track.hpp +++ b/src/Speed/Indep/Src/World/Track.hpp @@ -5,6 +5,14 @@ #pragma once #endif +#include "Speed/Indep/bWare/Inc/bChunk.hpp" + +struct TrackOBB; + void EstablishRemoteCaffeineConnection(); +int GetNumTrackOBBs(); +TrackOBB *GetTrackOBB(int index); +int LoaderTrackOBB(bChunk *chunk); +int UnloaderTrackOBB(bChunk *chunk); #endif diff --git a/src/Speed/Indep/Src/World/TrackInfo.cpp b/src/Speed/Indep/Src/World/TrackInfo.cpp index e69de29bb..e0f8e01d9 100644 --- a/src/Speed/Indep/Src/World/TrackInfo.cpp +++ b/src/Speed/Indep/Src/World/TrackInfo.cpp @@ -0,0 +1,105 @@ +#include "TrackInfo.hpp" + +#include "Speed/Indep/bWare/Inc/bChunk.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +static TrackInfo *TrackInfoTable = 0; +static unsigned int NumTrackInfo = 0; +TrackInfo *LoadedTrackInfo = 0; +bChunkLoader bChunkLoaderTrackInfo(0x34201, TrackInfo::LoaderTrackInfo, TrackInfo::UnloaderTrackInfo); + +TrackInfo *TrackInfo::GetTrackInfo(int track_number) { + for (int n = 0; n < static_cast(NumTrackInfo); n++) { + TrackInfo *info = &TrackInfoTable[n]; + if (info->TrackNumber == track_number) { + return info; + } + } + + return 0; +} + +int TrackInfo::LoaderTrackInfo(bChunk *chunk) { + int i; + int j; + + if (chunk->GetID() == 0x34201) { + TrackInfoTable = reinterpret_cast(chunk->GetData()); + NumTrackInfo = chunk->GetSize() / sizeof(TrackInfo); + + for (int n = 0; n < static_cast(NumTrackInfo); n++) { + TrackInfo *info = &TrackInfoTable[n]; + bPlatEndianSwap(&info->LocationNumber); + bPlatEndianSwap(reinterpret_cast(&info->LocationName)); + bPlatEndianSwap(reinterpret_cast(&info->DriftType)); + bPlatEndianSwap(&info->TrackNumber); + bPlatEndianSwap(&info->SameAsTrackNumber); + bPlatEndianSwap(&info->TimeToBeatForwards_ms); + bPlatEndianSwap(&info->TimeToBeatReverse_ms); + bPlatEndianSwap(&info->ScoreToBeatForwards_DriftOnly); + bPlatEndianSwap(&info->ScoreToBeatReverse_DriftOnly); + bPlatEndianSwap(&info->SunInfoNameHash); + bPlatEndianSwap(&info->UsageFlags); + bPlatEndianSwap(&info->TrackMapCalibrationUpperLeft); + bPlatEndianSwap(&info->TrackMapCalibrationMapWidthMetres); + bPlatEndianSwap(&info->TrackMapCalibrationRotation); + bPlatEndianSwap(&info->TrackMapStartLineAngle); + bPlatEndianSwap(&info->TrackMapFinishLineAngle); + bPlatEndianSwap(reinterpret_cast(&info->ForwardDifficulty)); + bPlatEndianSwap(reinterpret_cast(&info->ReverseDifficulty)); + bPlatEndianSwap(&info->NumSecondsBeforeShortcutsAllowed); + bPlatEndianSwap(&info->nDriftSecondsMin); + bPlatEndianSwap(&info->nDriftSecondsMax); + + i = 0; + do { + j = 0; + do { + bPlatEndianSwap(&info->OverrideStartingRouteForAI[i][j]); + j += 1; + } while (j < 4); + i += 1; + } while (i < 2); + + i = 0; + do { + j = 0; + do { + bPlatEndianSwap(&info->MaxTrafficCars[i][j]); + j += 1; + } while (j < 2); + i += 1; + } while (i < 4); + + i = 0; + do { + bPlatEndianSwap(&info->TrafficAllowedNearStartLine[i]); + i += 1; + } while (i < 2); + + i = 0; + do { + bPlatEndianSwap(&info->TrafficMinInitialDistanceBetweenCars[i]); + i += 1; + } while (i < 2); + + i = 0; + do { + bPlatEndianSwap(&info->TrafficMinInitialDistanceFromStartLine[i]); + i += 1; + } while (i < 2); + } + + return 1; + } + return 0; +} + +int TrackInfo::UnloaderTrackInfo(bChunk *chunk) { + if (chunk->GetID() == 0x34201) { + TrackInfoTable = 0; + NumTrackInfo = 0; + return 1; + } + return 0; +} diff --git a/src/Speed/Indep/Src/World/TrackInfo.hpp b/src/Speed/Indep/Src/World/TrackInfo.hpp index ae235e386..d07d41e23 100644 --- a/src/Speed/Indep/Src/World/TrackInfo.hpp +++ b/src/Speed/Indep/Src/World/TrackInfo.hpp @@ -5,8 +5,12 @@ #pragma once #endif +#include "Speed/Indep/bWare/Inc/bChunk.hpp" #include "Speed/Indep/bWare/Inc/bMath.hpp" +class TrackInfo; +extern TrackInfo *LoadedTrackInfo; + enum eLocationName { UPPER_CLASS = 0, CITY_CORE = 1, @@ -41,6 +45,16 @@ class TrackInfo { return LoadedTrackInfo; } + static int LoaderTrackInfo(bChunk *chunk); + static int UnloaderTrackInfo(bChunk *chunk); + + static int GetLoadedTrackNumber() { + if (LoadedTrackInfo) { + return LoadedTrackInfo->TrackNumber; + } + return 0; + } + char Name[32]; // offset 0x0, size 0x20 char TrackDirectory[32]; // offset 0x20, size 0x20 char RegionName[8]; // offset 0x40, size 0x8 diff --git a/src/Speed/Indep/Src/World/TrackPath.cpp b/src/Speed/Indep/Src/World/TrackPath.cpp index e69de29bb..089c08374 100644 --- a/src/Speed/Indep/Src/World/TrackPath.cpp +++ b/src/Speed/Indep/Src/World/TrackPath.cpp @@ -0,0 +1,289 @@ +#include "Speed/Indep/Src/World/TrackPath.hpp" +#include "Scenery.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +BOOL bBoundingBoxIsInside(const bVector2 *bbox_min, const bVector2 *bbox_max, const bVector2 *point, float extra_width); +BOOL bBoundingBoxOverlapping(const bVector2 *bbox_min, const bVector2 *bbox_max, const bVector2 *bbox2_min, const bVector2 *bbox2_max); +bool bIsPointInPoly(const bVector2 *point, const bVector2 *points, int num_points); +void bEndianSwap16(void *value); +void bEndianSwap32(void *value); +void bPlatEndianSwap(bVector2 *value); +void NotifyGameZonesChanged(); + +static inline TrackPathZone *NextTrackPathZone(TrackPathZone *zone) { + return reinterpret_cast(reinterpret_cast(zone) + zone->MemoryImageSize); +} + +static inline char *GetTrackPathBarrierData(TrackPathBarrier *barriers, int index) { + return reinterpret_cast(barriers) + index * 0x18; +} + +TrackPathZone *zoneB[2]; +TrackPathManager TheTrackPathManager; +bChunkLoader bChunkLoaderTrackPath(0x80034147, LoaderTrackPath, UnloaderTrackPath); +bChunkLoader bChunkLoaderTrackPathBarriers(0x3414D, LoaderTrackPath, UnloaderTrackPath); + +bool DoLinesIntersect(const bVector2 &line1_start, const bVector2 &line1_end, const bVector2 &line2_start, const bVector2 &line2_end) { + float dy1 = line1_end.y - line1_start.y; + float dx2 = line2_end.x - line2_start.x; + float dx1 = line1_end.x - line1_start.x; + float dy2 = line2_end.y - line2_start.y; + float den = dx1 * dy2 - dy1 * dx2; + + if (den != 0.0f) { + float dx3 = line1_start.x - line2_start.x; + float dy3 = line1_start.y - line2_start.y; + float r = (dy3 * dx2 - dx3 * dy2) / den; + if (0.0f <= r && r <= 1.0f) { + float s = (dy3 * dx1 - dx3 * dy1) / den; + if (0.0f <= s && s <= 1.0f) { + return true; + } + } + } + + return false; +} + +void TrackPathManager::Clear() { + NumZones = 0; + SizeofZones = 0; + pZones = nullptr; + bMemSet(ZoneInfoTable, 0, sizeof(ZoneInfoTable)); + MostCachedZones = 0; + pBarriers = nullptr; + NumBarriers = 0; + zoneB[0] = 0; + zoneB[1] = 0; +} + +int TrackPathManager::Loader(bChunk *chunk) { + if (chunk->GetID() == 0x80034147) { + bChunk *last_chunk = chunk->GetLastChunk(); + + for (chunk = chunk->GetFirstChunk(); chunk != last_chunk; chunk = chunk->GetNext()) { + if (chunk->GetID() == 0x3414A) { + pZones = reinterpret_cast(chunk->GetData()); + TrackPathZone *zone = pZones; + SizeofZones = chunk->GetSize(); + NumZones = 0; + + for (; zone < GetLastZone(); zone = zone->GetMemoryImageNext()) { + bEndianSwap32(&zone->Type); + bPlatEndianSwap(&zone->Position); + bPlatEndianSwap(&zone->Direction); + bEndianSwap32(&zone->Elevation); + bEndianSwap16(&zone->VisitInfo); + bEndianSwap16(&zone->NumPoints); + bEndianSwap16(&zone->MemoryImageSize); + bPlatEndianSwap(&zone->BBoxMin); + bPlatEndianSwap(&zone->BBoxMax); + for (int n = 0; n < zone->NumPoints; n++) { + bPlatEndianSwap(&zone->Points[n]); + } + for (int n = 0; n < 4; n++) { + bEndianSwap32(reinterpret_cast(n * sizeof(int) + reinterpret_cast(zone) + 0x30)); + } + NumZones += 1; + } + } + } + BuildZoneInfoTable(); + return true; + } + + if (chunk->GetID() == 0x3414D) { + pBarriers = reinterpret_cast(chunk->GetData()); + int i; + unsigned int size = chunk->GetSize(); + NumBarriers = size / 0x18; + for (i = 0; i < NumBarriers; i++) { + pBarriers[i].EndianSwap(); + } + return true; + } + + return false; +} + +int TrackPathManager::Unloader(bChunk *chunk) { + if (chunk->GetID() == 0x80034147) { + Clear(); + NotifyGameZonesChanged(); + return true; + } + + if (chunk->GetID() == 0x3414D) { + pBarriers = nullptr; + NumBarriers = 0; + return true; + } + + return false; +} + +void TrackPathManager::DisableAllBarriers() { + for (int i = 0; i < NumBarriers; i++) { + GetTrackPathBarrierData(pBarriers, i)[0x10] = 0; + } +} + +void TrackPathManager::EnableBarriers(const char *group_name) { + unsigned int group_name_hash = bStringHash(group_name); + for (int i = 0; i < NumBarriers; i++) { + char *barrier = GetTrackPathBarrierData(pBarriers, i); + if (*reinterpret_cast(barrier + 0x14) == group_name_hash) { + barrier[0x10] = 1; + + void *scenery_group = FindSceneryGroup(group_name_hash); + barrier[0x12] = scenery_group && *reinterpret_cast(reinterpret_cast(scenery_group) + 0x11); + } + } +} + +void TrackPathManager::BuildZoneInfoTable() { + for (int type = 0; type < NUM_TRACK_PATH_ZONES; type++) { + ZoneInfo *zone_info = &ZoneInfoTable[type]; + zone_info->NumZones = 0; + + for (TrackPathZone *zone = pZones; zone < GetLastZone(); zone = zone->GetMemoryImageNext()) { + if (zone->GetType() == static_cast(type)) { + if (zone_info->NumZones == 0) { + zone_info->pFirstZone = zone; + } + zone_info->pLastZone = zone->GetMemoryImageNext(); + zone_info->NumZones += 1; + } + } + } + + NotifyGameZonesChanged(); +} + +TrackPathZone *TrackPathManager::FindZone(const bVector2 *position, eTrackPathZoneType zone_type, TrackPathZone *prev_zone) { + ZoneInfo *zone_info = &ZoneInfoTable[zone_type]; + bool cache_valid; + + if (!position) { + cache_valid = false; + } else if (bBoundingBoxIsInside(&zone_info->CachedBBoxMin, &zone_info->CachedBBoxMax, position, 0.0f)) { + cache_valid = zone_info->NumCachedZones < 9; + } else { + const float cached_radius = 64.0f; + TrackPathZone *first_zone; + TrackPathZone *last_zone = zone_info->pLastZone; + + first_zone = zone_info->pFirstZone; + + zone_info->CachedBBoxMin.x = position->x - cached_radius; + zone_info->CachedBBoxMin.y = position->y - cached_radius; + zone_info->CachedBBoxMax.x = position->x + cached_radius; + zone_info->CachedBBoxMax.y = position->y + cached_radius; + zone_info->NumCachedZones = 0; + zone_info->NumFullRebuilds += 1; + + for (TrackPathZone *zone = first_zone; zone < last_zone; zone = zone->GetMemoryImageNext()) { + if (bBoundingBoxOverlapping(&zone_info->CachedBBoxMin, &zone_info->CachedBBoxMax, &zone->BBoxMin, &zone->BBoxMax)) { + if (zone_info->NumCachedZones < 8) { + zone->CachedIndex = static_cast(zone_info->NumCachedZones); + zone_info->CachedZones[zone_info->NumCachedZones] = zone; + } + zone_info->NumCachedZones += 1; + } + } + + cache_valid = zone_info->NumCachedZones < 9; + MostCachedZones = bMax(MostCachedZones, zone_info->NumCachedZones); + } + + TrackPathZone *found_zone = 0; + if (!cache_valid) { + TrackPathZone *first_zone; + TrackPathZone *last_zone = zone_info->pLastZone; + + first_zone = zone_info->pFirstZone; + zone_info->NumCacheRebuilds += 1; + if (prev_zone) { + first_zone = prev_zone->GetMemoryImageNext(); + } + + { + TrackPathZone *zone = first_zone; + while (zone < last_zone && position && + (!bBoundingBoxIsInside(&zone->BBoxMin, &zone->BBoxMax, position, 0.0f) || !zone->IsPointInside(position))) { + zone = zone->GetMemoryImageNext(); + } + found_zone = zone; + } + } else { + int first_zone_index = 0; + zone_info->NumCacheHits += 1; + if (prev_zone) { + first_zone_index = prev_zone->CachedIndex + 1; + } + + for (int index = first_zone_index; index < zone_info->NumCachedZones; index++) { + TrackPathZone *zone = zone_info->CachedZones[index]; + if (bBoundingBoxIsInside(&zone->BBoxMin, &zone->BBoxMax, position, 0.0f) && zone->IsPointInside(position)) { + found_zone = zone; + break; + } + } + } + + return found_zone; +} + +void TrackPathManager::ResetZoneVisitInfos() { + for (TrackPathZone *zone = pZones; zone < GetLastZone(); zone = zone->GetMemoryImageNext()) { + zone->SetVisitInfo(0); + } +} + +bool TrackPathZone::IsPointInside(const bVector2 *point) { + return bIsPointInPoly(point, Points, NumPoints); +} + +void TrackPathInitRemoteCaffeineConnection() {} + +int LoaderTrackPath(bChunk *chunk) { + return TheTrackPathManager.Loader(chunk); +} + +int UnloaderTrackPath(bChunk *chunk) { + return TheTrackPathManager.Unloader(chunk); +} +float TrackPathZone::GetSegmentNextTo(bVector2 *point, bVector2 *segment_point_a, bVector2 *segment_point_b) { + int Closest0 = -1; + int Closest1 = -1; + float d0 = 1.0e30f; + float len; + + for (int n = 0; n < NumPoints; n++) { + bVector2 *p0 = &Points[n % NumPoints]; + bVector2 *p1 = &Points[(n + 1) % NumPoints]; + bVector2 v = *p0 - *point; + bVector2 r(p1->y - p0->y, p0->x - p1->x); + + bNormalize(&r, &r); + len = bDot(&r, &v); + bVector2 InPoint = r * (len * 0.999f) + *point; + bVector2 InPoint2 = r * (len * 1.001f) + *point; + len = bAbs(len); + + if (len < d0 && (IsPointInside(&InPoint) || IsPointInside(&InPoint2))) { + Closest0 = n % NumPoints; + Closest1 = (n + 1) % NumPoints; + d0 = len; + } + } + + if (Closest0 == -1 || Closest1 == -1) { + return -1.0f; + } + + bCopy(segment_point_a, &Points[Closest0]); + bCopy(segment_point_b, &Points[Closest1]); + return d0; +} diff --git a/src/Speed/Indep/Src/World/TrackPath.hpp b/src/Speed/Indep/Src/World/TrackPath.hpp index 93f225503..419daaca1 100644 --- a/src/Speed/Indep/Src/World/TrackPath.hpp +++ b/src/Speed/Indep/Src/World/TrackPath.hpp @@ -5,7 +5,9 @@ #pragma once #endif +#include "Speed/Indep/bWare/Inc/bChunk.hpp" #include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" enum eTrackPathZoneType { NUM_TRACK_PATH_ZONES = 15, @@ -26,6 +28,22 @@ enum eTrackPathZoneType { TRACK_PATH_ZONE_RESET = 0, }; +// total size: 0x18 +struct TrackPathBarrier { + bVector2 Points[2]; // offset 0x0, size 0x10 + char Enabled; // offset 0x10, size 0x1 + char Pad; // offset 0x11, size 0x1 + char PlayerBarrier; // offset 0x12, size 0x1 + char LeftHanded; // offset 0x13, size 0x1 + unsigned int GroupHash; // offset 0x14, size 0x4 + + void EndianSwap() { + bPlatEndianSwap(&Points[0]); + bPlatEndianSwap(&Points[1]); + bPlatEndianSwap(&GroupHash); + } +}; + // total size: 0x244 class TrackPathZone { public: @@ -48,6 +66,26 @@ class TrackPathZone { void GetOpposite(bVector2 *in0, bVector2 *in1, bVector2 *opp0, bVector2 *opp1); bool GetIntercept(bVector2 &InterceptPoint, const bVector2 *Start, const bVector2 *Direction); bool IsPointInside(const bVector2 *point); + float GetSegmentNextTo(bVector2 *point, bVector2 *segment_point_a, bVector2 *segment_point_b); + float GetElevation() { + return Elevation; + } + + eTrackPathZoneType GetType() { + return Type; + } + + int GetMemoryImageSize() { + return MemoryImageSize; + } + + void SetVisitInfo(int v) { + VisitInfo = v; + } + + TrackPathZone *GetMemoryImageNext() { + return reinterpret_cast(reinterpret_cast(this) + GetMemoryImageSize()); + } int GetData(int index) { return Data[index]; @@ -67,12 +105,19 @@ class TrackPathManager { bVector2 CachedBBoxMax; int NumCachedZones; int NumCacheHits; - int NumCacheRebuilds; int NumFullRebuilds; + int NumCacheRebuilds; TrackPathZone *CachedZones[8]; }; public: + TrackPathManager() { + Clear(); + } + + int Loader(bChunk *chunk); + int Unloader(bChunk *chunk); + void Clear(); void EnableBarriers(const char *group_name); void DisableAllBarriers(); void BuildZoneInfoTable(); @@ -82,19 +127,23 @@ class TrackPathManager { void Close() {} private: - // TrackPathZone *GetLastZone() {} - - int NumZones; // offset 0x0, size 0x4 - int SizeofZones; // offset 0x4, size 0x4 - TrackPathZone *pZones; // offset 0x8, size 0x4 - ZoneInfo ZoneInfoTable[15]; // offset 0xC, size 0x474 - int MostCachedZones; // offset 0x480, size 0x4 - int NumBarriers; // offset 0x484, size 0x4 - struct TrackPathBarrier *pBarriers; // offset 0x488, size 0x4 + TrackPathZone *GetLastZone() { + return reinterpret_cast(reinterpret_cast(pZones) + SizeofZones); + } + + int NumZones; // offset 0x0, size 0x4 + int SizeofZones; // offset 0x4, size 0x4 + TrackPathZone *pZones; // offset 0x8, size 0x4 + ZoneInfo ZoneInfoTable[15]; // offset 0xC, size 0x474 + int MostCachedZones; // offset 0x480, size 0x4 + int NumBarriers; // offset 0x484, size 0x4 + TrackPathBarrier *pBarriers; // offset 0x488, size 0x4 }; extern TrackPathManager TheTrackPathManager; void TrackPathInitRemoteCaffeineConnection(); +int LoaderTrackPath(bChunk *chunk); +int UnloaderTrackPath(bChunk *chunk); #endif diff --git a/src/Speed/Indep/Src/World/TrackPositionMarker.cpp b/src/Speed/Indep/Src/World/TrackPositionMarker.cpp index e69de29bb..91a7cb89f 100644 --- a/src/Speed/Indep/Src/World/TrackPositionMarker.cpp +++ b/src/Speed/Indep/Src/World/TrackPositionMarker.cpp @@ -0,0 +1,93 @@ +#include "TrackPositionMarker.hpp" + +#include "TrackInfo.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#include "Speed/Indep/bWare/Inc/bChunk.hpp" + +bTList TrackPositionMarkerList; +bChunkLoader bChunkLoaderTrackPositionMarkers(0x34146, LoaderTrackPositionMarkers, UnloaderTrackPositionMarkers); + +static void NotifyTrackMarkersChanged() {} + +int LoaderTrackPositionMarkers(bChunk *chunk) { + if (chunk->GetID() == 0x34146) { + TrackPositionMarker *marker_table = reinterpret_cast(chunk->GetAlignedData(0x10)); + int num_markers = chunk->GetAlignedSize(0x10) / sizeof(TrackPositionMarker); + + for (int n = 0; n < num_markers; n++) { + TrackPositionMarker *marker = &marker_table[n]; + bPlatEndianSwap(&marker->NameHash); + bPlatEndianSwap(&marker->Param); + bPlatEndianSwap(&marker->Position); + bPlatEndianSwap(&marker->Angle); + bPlatEndianSwap(&marker->TrackNumber); + TrackPositionMarkerList.AddTail(marker); + } + + NotifyTrackMarkersChanged(); + return 1; + } + + return 0; +} + +int UnloaderTrackPositionMarkers(bChunk *chunk) { + if (chunk->GetID() == 0x34146) { + TrackPositionMarker *marker_table = reinterpret_cast(chunk->GetAlignedData(0x10)); + int num_markers = chunk->GetAlignedSize(0x10) / sizeof(TrackPositionMarker); + + for (int n = 0; n < num_markers; n++) { + TrackPositionMarkerList.Remove(&marker_table[n]); + } + + NotifyTrackMarkersChanged(); + return 1; + } + + return 0; +} + +int GetNumTrackPositionMarkers(int track_number, unsigned int name_hash) { + int num_markers = 0; + + for (TrackPositionMarker *p = TrackPositionMarkerList.GetHead(); p != TrackPositionMarkerList.EndOfList(); + p = p->GetNext()) { + if (p->NameHash == name_hash && (track_number == 0 || p->TrackNumber == track_number)) { + num_markers += 1; + } + } + + return num_markers; +} + +void ForEachTrackPositionMarker(bool (*callback)(TrackPositionMarker *, unsigned int), unsigned int tag) { + for (TrackPositionMarker *marker = TrackPositionMarkerList.GetHead(); marker != TrackPositionMarkerList.EndOfList(); + marker = marker->GetNext()) { + if (!callback(marker, tag)) { + break; + } + } +} + +TrackPositionMarker *GetTrackPositionMarker(int track_number, unsigned int name_hash, int index) { + int num_markers = 0; + + for (TrackPositionMarker *p = TrackPositionMarkerList.GetHead(); p != TrackPositionMarkerList.EndOfList(); + p = p->GetNext()) { + if (p->NameHash == name_hash && (p->TrackNumber == 0 || p->TrackNumber == track_number)) { + if (num_markers == index) { + return p; + } + + num_markers += 1; + } + } + + return 0; +} + +TrackPositionMarker *GetTrackPositionMarker(unsigned int name_hash, int index) { + int track_number = TrackInfo::GetLoadedTrackNumber(); + + return GetTrackPositionMarker(track_number, name_hash, index); +} diff --git a/src/Speed/Indep/Src/World/TrackPositionMarker.hpp b/src/Speed/Indep/Src/World/TrackPositionMarker.hpp index f0c95d5db..bb20b48a9 100644 --- a/src/Speed/Indep/Src/World/TrackPositionMarker.hpp +++ b/src/Speed/Indep/Src/World/TrackPositionMarker.hpp @@ -5,6 +5,7 @@ #pragma once #endif +#include "Speed/Indep/bWare/Inc/bChunk.hpp" #include "Speed/Indep/bWare/Inc/bList.hpp" #include "Speed/Indep/bWare/Inc/bMath.hpp" @@ -20,7 +21,12 @@ struct TrackPositionMarker : public bTNode { int Padding3; // offset 0x2C, size 0x4 }; +int LoaderTrackPositionMarkers(bChunk *chunk); +int UnloaderTrackPositionMarkers(bChunk *chunk); +void ForEachTrackPositionMarker(bool (*callback)(TrackPositionMarker *, unsigned int), unsigned int tag); int GetNumTrackPositionMarkers(int track_number, unsigned int name_hash); +TrackPositionMarker *GetTrackPositionMarker(int track_number, unsigned int name_hash, int index); +TrackPositionMarker *GetTrackPositionMarker(unsigned int name_hash, int index); extern bTList TrackPositionMarkerList; diff --git a/src/Speed/Indep/Src/World/TrackStreamer.cpp b/src/Speed/Indep/Src/World/TrackStreamer.cpp index e69de29bb..b310dbdf4 100644 --- a/src/Speed/Indep/Src/World/TrackStreamer.cpp +++ b/src/Speed/Indep/Src/World/TrackStreamer.cpp @@ -0,0 +1,2466 @@ +#include "TrackStreamer.hpp" + +#include "TrackPath.hpp" +#include "VisibleSection.hpp" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Src/Misc/Profiler.hpp" +#include "Speed/Indep/Src/Misc/QueuedFile.hpp" +#include "Speed/Indep/Src/Misc/ResourceLoader.hpp" +#include "Speed/Indep/Src/Misc/Timer.hpp" +#include "Speed/Indep/Tools/Inc/ConversionUtil.hpp" +#include "Speed/Indep/Src/Misc/bFile.hpp" +#include "Speed/Indep/bWare/Inc/bDebug.hpp" +#include "Speed/Indep/bWare/Inc/Espresso.hpp" +#include "Speed/Indep/bWare/Inc/bPrintf.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" +#include "Speed/Indep/bWare/Inc/bFunk.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#ifdef EA_PLATFORM_GAMECUBE +#include "dolphin/PPCArch.h" +#include "dolphin/os/OSCache.h" +#endif + +#include + +extern BOOL bMemoryTracing; +extern int ScenerySectionLODOffset; +extern int SeeulatorToolActive; +extern int ScenerySectionToBlink; +extern int RealLoopCounter; +extern bool PostLoadFixupDisabled; +extern int AllowDuplicateSolids; +extern int ForceHoleFillerMethod; +extern int WaitForFrameBufferSwapDisabled; +extern int WaitUntilRenderingDoneDisabled; +extern int TrackStreamerRemoteCaffeinating; +extern unsigned int eFrameCounter; +int Get2PlayerSectionNumber(int section_number); +char *GetScenerySectionName(char *name, int section_number); +char *GetScenerySectionName(int section_number); +void PostLoadFixup(); +void SetDuplicateTextureWarning(BOOL enabled); +bool LoadTempPermChunks(bChunk **ppchunks, int *psizeof_chunks, int allocation_params, const char *debug_name); +bool DoLinesIntersect(const bVector2 &line1_start, const bVector2 &line1_end, const bVector2 &line2_start, const bVector2 &line2_end); +void eWaitUntilRenderingDone(); +void MoveChunks(bChunk *dest_chunks, bChunk *source_chunks, int sizeof_chunks, const char *debug_name); +void bSetMemoryPoolOverrideInfo(int pool_num, MemoryPoolOverrideInfo *override_info); +void UnloadChunks(bChunk *chunks, int sizeof_chunks, const char *debug_name); +void SetQueuedFileMinPriority(int priority); +void NotifySkyLoader(); +void BlockWhileQueuedFileBusy(); +int GetBoundarySectionNumber(int section_number, const char *platform_name); +int LoaderTrackStreamer(bChunk *chunk); +int UnloaderTrackStreamer(bChunk *chunk); +extern int QueuedFileDefaultPriority; + +static unsigned int prev_need_loading_bar_26275 = 0; +static const float kMaxDistance_TrackStreamer = 3.4028235e+38f; +static const float kVelocityEpsilon_TrackStreamer = 0.0f; +static const float kFuturePositionScale_TrackStreamer = 0.5f; +static const float kPredictionScaleA_TrackStreamer = 1.0f; +static const float kPredictionScaleB_TrackStreamer = 1.0f; +static const float kLoadingBarDistanceThreshold_TrackStreamer = 15.0f; +static const float kLoadingBarSpeedThreshold_TrackStreamer = 100.0f; +static const float kSwitchZoneFarLoadThreshold_TrackStreamer = 0.1f; +static const float kPredictedZoneScale_TrackStreamer = 1.5f; +static const float kPredictedZoneMaxDistance_TrackStreamer = 100.0f; +static const float kPredictedZoneStopProjectSpeed_TrackStreamer = 178.81091f; +static const float kPredictedZoneEqualEpsilon_TrackStreamer = 0.001f; +static VisibleSectionBitTable CurrentVisibleSectionTableMem; +TrackStreamer TheTrackStreamer; +bChunkLoader bChunkLoaderTrackStreamingSection(0x34110, LoaderTrackStreamer, UnloaderTrackStreamer); +bChunkLoader bChunkLoaderTrackStreamingDiscBundle(0x34113, LoaderTrackStreamer, UnloaderTrackStreamer); +bChunkLoader bChunkLoaderTrackStreamingInfo(0x34111, LoaderTrackStreamer, UnloaderTrackStreamer); +bChunkLoader bChunkLoaderTrackStreamingBarriers(0x34112, LoaderTrackStreamer, UnloaderTrackStreamer); + +static inline char GetScenerySectionLetter_TrackStreamer(int section_number) { + return static_cast(section_number / 100 + 'A' - 1); +} + +static inline bool IsRegularScenerySection_TrackStreamer(int section_number) { + char section_letter = GetScenerySectionLetter_TrackStreamer(section_number); + return section_letter >= 'A' && section_letter < 'U'; +} + +static inline bool IsTextureSection_TrackStreamer(int section_number) { + char section_letter = GetScenerySectionLetter_TrackStreamer(section_number); + return section_letter == 'Y' || section_letter == 'W'; +} + +static inline bool IsLibrarySection_TrackStreamer(int section_number) { + char section_letter = GetScenerySectionLetter_TrackStreamer(section_number); + return section_letter == 'X' || section_letter == 'U'; +} + +static inline short GetScenerySectionNumber_TrackStreamer(char section_letter, int subsection_number) { + return static_cast((section_letter - 'A' + 1) * 100 + subsection_number); +} + +static inline bool IsLODScenerySectionNumber(int section_number) { + int subsection_number = GetScenerySubsectionNumber(section_number); + return subsection_number >= ScenerySectionLODOffset && subsection_number < ScenerySectionLODOffset * 2; +} + +static inline bool IsLoadingBarSection_TrackStreamer(int section_number) { + if (!IsRegularScenerySection_TrackStreamer(section_number)) { + return false; + } + + int subsection_number = section_number % 100; + return (subsection_number > 0 && subsection_number < ScenerySectionLODOffset) || + (ScenerySectionLODOffset <= subsection_number && subsection_number < ScenerySectionLODOffset * 2); +} + +static inline void DisableWaitForFrameBufferSwap() { + WaitForFrameBufferSwapDisabled = 1; +} + +static inline void EnableWaitForFrameBufferSwap() { + WaitForFrameBufferSwapDisabled = 0; +} + +static inline void eAllowDuplicateSolids(bool enable) { + if (enable) { + AllowDuplicateSolids += 1; + } else { + AllowDuplicateSolids -= 1; + } +} + +inline bool TrackStreamingBarrier::Intersects(const bVector2 *pointa, const bVector2 *pointb) { + return DoLinesIntersect(Points[0], Points[1], *pointa, *pointb); +} + +struct bBitTableLayout_TrackStreamer { + int NumBits; + uint8 *Bits; +}; + +void bBitTable::ClearTable() { + bMemSet(Bits, 0, NumBits >> 3); +} + +struct bMemoryTraceAllocatePacket { + int PoolID; + int MemoryAddress; + int Size; + int DebugLine; + int AllocationNumber; + char DebugText[48]; +}; + +TSMemoryPool::TSMemoryPool(int address, int size, const char *debug_name, int pool_num) { + PoolNum = pool_num; + DebugName = debug_name; + TotalSize = size; + TracingEnabled = true; + Updated = false; + AllocationNumber = 0; + AmountFree = size; + LargestFree = size; + NeedToRecalcLargestFree = false; + + for (int i = 0; i < 192; i++) { + UnusedNodeList.AddTail(&MemoryNodes[i]); + } + + if (TracingEnabled && bMemoryTracing) { + bMemoryTraceNewPoolPacket packet; + packet.PoolID = reinterpret_cast(this); + bMemSet(packet.Name, 0, sizeof(packet.Name)); + bStrNCpy(packet.Name, debug_name, sizeof(packet.Name) - 1); + bFunkCallASync("CODEINE", 0x19, &packet, sizeof(packet)); + } + + GetNewNode(address, size, false, 0); + + if (TracingEnabled && bMemoryTracing) { + bMemoryTraceFreePacket packet; + bMemSet(&packet, 0, sizeof(packet)); + packet.PoolID = reinterpret_cast(this); + packet.MemoryAddress = static_cast(address); + packet.Size = size; + bFunkCallASync("CODEINE", 0x1b, &packet, sizeof(packet)); + } + + bMemSet(&OverrideInfo, 0, sizeof(OverrideInfo)); + OverrideInfo.Name = DebugName; + OverrideInfo.Pool = this; + OverrideInfo.Address = address; + OverrideInfo.Size = size; + OverrideInfo.Malloc = OverrideMalloc; + OverrideInfo.Free = OverrideFree; + OverrideInfo.GetAmountFree = OverrideGetAmountFree; + OverrideInfo.GetLargestFreeBlock = OverrideGetLargestFreeBlock; + bSetMemoryPoolOverrideInfo(PoolNum, &OverrideInfo); +} + +TSMemoryNode *TSMemoryPool::GetNewNode(int address, int size, bool allocated, const char *debug_name) { + TSMemoryNode *node = UnusedNodeList.RemoveHead(); + + node->Address = address; + node->Size = size; + node->Allocated = allocated; + bSafeStrCpy(node->DebugName, debug_name, sizeof(node->DebugName)); + return node; +} + +void TSMemoryPool::RemoveNode(TSMemoryNode *node) { + UnusedNodeList.AddTail(node); +} + +inline bool TSMemoryNode::IsFree() { + return !Allocated; +} + +inline bool TSMemoryNode::IsAllocated() { + return Allocated; +} + +inline bool TSMemoryNode::Contains(int address) { + return address >= Address && address < Address + Size; +} + +void *TSMemoryPool::Malloc(int size, const char *debug_name, bool best_fit, bool allocate_from_top, int address) { + TSMemoryNode *found_node = 0; + TSMemoryNode *new_node; + int new_bottom_size; + int new_top_size; + + size = (size + 0x7f) & ~0x7f; + + if (address != 0) { + for (TSMemoryNode *node = NodeList.GetHead(); node != NodeList.EndOfList(); node = node->GetNext()) { + if (node->IsFree() && node->Size >= size && node->Contains(address)) { + found_node = node; + break; + } + } + } else if (best_fit) { + for (TSMemoryNode *node = NodeList.GetHead(); node != NodeList.EndOfList(); node = node->GetNext()) { + if (node->IsFree() && node->Size >= size && (!found_node || found_node->Size - size > node->Size - size)) { + found_node = node; + } + } + } else if (allocate_from_top) { + for (TSMemoryNode *node = NodeList.GetHead(); node != NodeList.EndOfList(); node = node->GetNext()) { + if (node->IsFree() && node->Size >= size) { + found_node = node; + break; + } + } + } else { + for (TSMemoryNode *node = NodeList.GetTail(); node != NodeList.EndOfList(); node = node->GetPrev()) { + if (node->IsFree() && node->Size >= size) { + found_node = node; + break; + } + } + } + + if (!found_node) { + return 0; + } + + if (address == 0) { + if (allocate_from_top) { + address = found_node->Address; + } else { + address = found_node->Address + found_node->Size - size; + } + } + + AmountFree -= size; + if (found_node->Size == LargestFree) { + NeedToRecalcLargestFree = true; + } + + new_node = GetNewNode(address, size, true, debug_name); + new_node->AddAfter(found_node); + + new_bottom_size = address - found_node->Address; + new_top_size = found_node->Address + found_node->Size - (address + size); + found_node->Size = new_bottom_size; + if (new_bottom_size == 0) { + NodeList.Remove(found_node); + RemoveNode(found_node); + } + + if (new_top_size != 0) { + TSMemoryNode *top_node = GetNewNode(address + size, new_top_size, false, 0); + top_node->AddAfter(new_node); + } + + if (TracingEnabled && bMemoryTracing) { + bMemoryTraceAllocatePacket send_packet; + bMemoryTraceAllocatePacket packet; + bMemoryTraceAllocatePacket *fake_match = &packet; + int extra_len; + int n; + unsigned char *p; + + memset(fake_match, 0, sizeof(packet)); + packet.PoolID = reinterpret_cast(this); + packet.MemoryAddress = address; + packet.Size = size; + packet.AllocationNumber = AllocationNumber; + send_packet = packet; + p = reinterpret_cast(send_packet.DebugText); + n = sizeof(send_packet.DebugText); + bMemSet(p, 0, n); + if (debug_name) { + bStrNCpy(reinterpret_cast(p), debug_name, n - 1); + } + extra_len = bStrLen(reinterpret_cast(p)) + 0x15; + bFunkCallASync("CODEINE", 0x1c, &send_packet, extra_len); + } + + Updated = true; + AllocationNumber += 1; + return reinterpret_cast(address); +} + +void TSMemoryPool::Free(void *memory) { + int address = reinterpret_cast(memory); + Updated = true; + + for (TSMemoryNode *node = NodeList.GetHead(); node != NodeList.EndOfList(); node = node->GetNext()) { + if (node->Address == address) { + int size; + TSMemoryNode *prev_node = node->GetPrev(); + + node->DebugName[0] = 0; + size = node->Size; + node->Allocated = false; + if (prev_node != NodeList.EndOfList() && prev_node->IsFree()) { + node->Address = address - prev_node->Size; + node->Size = size + prev_node->Size; + NodeList.Remove(prev_node); + RemoveNode(prev_node); + } + + TSMemoryNode *next_node = node->GetNext(); + if (next_node != NodeList.EndOfList() && next_node->IsFree()) { + node->Size += next_node->Size; + NodeList.Remove(next_node); + RemoveNode(next_node); + } + + AmountFree += size; + if (node->Size > LargestFree) { + LargestFree = node->Size; + } + + if (TracingEnabled && bMemoryTracing) { + bMemoryTraceFreePacket packet; + bMemoryTraceFreePacket *packet_ptr = &packet; + memset(packet_ptr, 0, sizeof(*packet_ptr)); + packet.PoolID = reinterpret_cast(this); + packet.MemoryAddress = address; + packet.Size = size; + bFunkCallASync("CODEINE", 0x1b, packet_ptr, sizeof(*packet_ptr)); + } + return; + } + } +} + +inline void *TSMemoryPool::OverrideMalloc(void *pool, int size, const char *debug_text, int debug_line, int allocation_params) { + register int user_alignment_offset; + (void)debug_line; + user_alignment_offset = bMemoryGetAlignmentOffset(allocation_params); + + if (user_alignment_offset != 0) { + char *p = reinterpret_cast(static_cast(pool)->Malloc(size + 0x80, debug_text, bMemoryGetBestFit(allocation_params), + bMemoryGetTopBit(allocation_params), 0)); + return &p[0x80 - user_alignment_offset]; + } + + return static_cast(pool)->Malloc(size, debug_text, bMemoryGetBestFit(allocation_params), bMemoryGetTopBit(allocation_params), 0); +} + +void TSMemoryPool::OverrideFree(void *pool, void *ptr) { + static_cast(pool)->Free(reinterpret_cast(reinterpret_cast(ptr) & ~static_cast(0x7F))); +} + +int TSMemoryPool::OverrideGetAmountFree(void *pool) { + return static_cast(pool)->GetAmountFree(); +} + +int TSMemoryPool::OverrideGetLargestFreeBlock(void *pool) { + return static_cast(pool)->GetLargestFreeBlock(); +} + +int TSMemoryPool::GetAmountFree() { + int amount_free; + + (void)amount_free; + for (TSMemoryNode *node = NodeList.GetHead(); node != NodeList.EndOfList(); node = node->GetNext()) { + node->IsFree(); + } + return AmountFree; +} + +int TSMemoryPool::GetLargestFreeBlock() { + if (NeedToRecalcLargestFree) { + int largest_free = 0; + for (TSMemoryNode *node = NodeList.GetHead(); node != NodeList.EndOfList(); node = node->GetNext()) { + if (node->IsFree() && node->Size > largest_free) { + largest_free = node->Size; + } + } + LargestFree = largest_free; + NeedToRecalcLargestFree = false; + } + + int largest_free = 0; + for (TSMemoryNode *node = NodeList.GetHead(); node != NodeList.EndOfList(); node = node->GetNext()) { + if (node->IsFree() && node->Size > largest_free) { + largest_free = node->Size; + } + } + return LargestFree; +} + +TSMemoryNode *TSMemoryPool::GetNextNode(bool start_from_top, TSMemoryNode *node) { + if (start_from_top) { + if (node) { + node = node->GetNext(); + } else { + node = NodeList.GetHead(); + } + } else { + if (node) { + node = node->GetPrev(); + } else { + node = NodeList.GetTail(); + } + } + + if (node == NodeList.EndOfList()) { + return 0; + } + return node; +} + +TSMemoryNode *TSMemoryPool::GetNextFreeNode(bool start_from_top, TSMemoryNode *node) { + while ((node = GetNextNode(start_from_top, node)) != 0) { + if (!node->Allocated) { + return node; + } + } + return 0; +} + +TSMemoryNode *TSMemoryPool::GetNextAllocatedNode(bool start_from_top, TSMemoryNode *node) { + while ((node = GetNextNode(start_from_top, node)) != 0) { + if (node->Allocated) { + return node; + } + } + return 0; +} + +unsigned int TSMemoryPool::GetPoolChecksum() { + return 0; +} + +inline TSMemoryNode *TSMemoryPool::GetFirstAllocatedNode(bool start_from_top) { + return GetNextAllocatedNode(start_from_top, 0); +} + +void TSMemoryPool::DebugPrint() { + for (TSMemoryNode *node = NodeList.GetHead(); node != NodeList.EndOfList(); node = node->GetNext()) { + const char *name = node->DebugName; + node->IsAllocated(); + (void)name; + } +} + +int LoaderTrackStreamer(bChunk *chunk) { + return TheTrackStreamer.Loader(chunk); +} + +int UnloaderTrackStreamer(bChunk *chunk) { + return TheTrackStreamer.Unloader(chunk); +} + +void RefreshTrackStreamer() { + TheTrackStreamer.RefreshLoading(); +} + +TrackStreamer::TrackStreamer() { + pTrackStreamingSections = 0; + NumTrackStreamingSections = 0; + pDiscBundleSections = 0; + pLastDiscBundleSection = 0; + NumSectionsLoaded = 0; + NumSectionsLoading = 0; + NumSectionsActivated = 0; + NumSectionsOutOfMemory = 0; + NumSectionsMoved = 0; + bMemSet(StreamFilenames, 0, sizeof(StreamFilenames)); + SplitScreen = false; + PermFileLoading = false; + PermFilename = 0; + PermFileChunks = 0; + PermFileSize = 0; + NumBarriers = 0; + pBarriers = 0; + NumCurrentStreamingSections = 0; + NumHibernatingSections = 0; + CurrentZoneNeedsRefreshing = false; + ZoneSwitchingDisabled = false; + LastWaitUntilRenderingDoneFrameCount = 0; + LastPrintedFrameCount = 0; + SkipNextHandleLoad = false; + + ClearCurrentZones(); + ClearStreamingPositions(); + + pMemoryPoolMem = 0; + MemoryPoolSize = 0; + UserMemoryAllocationSize = 0; + pMemoryPool = 0; + + CurrentVisibleSectionTable.Init(CurrentVisibleSectionTableMem.Bits, 0xAF0); + CurrentVisibleSectionTable.ClearTable(); + bMemSet(KeepSectionTable, 0, sizeof(KeepSectionTable)); + pCallback = 0; + CallbackParam = 0; + MakeSpaceInPoolCallback = 0; + MakeSpaceInPoolCallbackParam = 0; + MakeSpaceInPoolSize = 0; +} + +int TrackStreamer::Loader(bChunk *chunk) { + unsigned int chunk_id = chunk->GetID(); + if (chunk_id == 0x34110) { + pTrackStreamingSections = reinterpret_cast(chunk->GetData()); + NumTrackStreamingSections = chunk->Size / sizeof(TrackStreamingSection); + for (int i = 0; i < NumTrackStreamingSections; i++) { + TrackStreamingSection *section = &pTrackStreamingSections[i]; + bEndianSwap16(§ion->SectionNumber); + bEndianSwap32(§ion->FileType); + bEndianSwap32(§ion->Status); + bEndianSwap32(§ion->FileOffset); + bEndianSwap32(§ion->Size); + bEndianSwap32(§ion->CompressedSize); + bEndianSwap32(§ion->PermSize); + bEndianSwap32(§ion->SectionPriority); + bPlatEndianSwap(§ion->Centre); + bEndianSwap32(§ion->Radius); + bEndianSwap32(§ion->Checksum); + } + + for (int i = 0; i < NumHibernatingSections; i++) { + TrackStreamingSection *src = &HibernatingSections[i]; + TrackStreamingSection *section = FindSection(src->SectionNumber); + bMemCpy(section, src, sizeof(TrackStreamingSection)); + NumSectionsLoaded += 1; + ActivateSection(section); + int current_streaming_section = NumCurrentStreamingSections; + CurrentStreamingSections[current_streaming_section] = section; + NumCurrentStreamingSections = current_streaming_section + 1; + } + + NumHibernatingSections = 0; + return 1; + } else if (chunk_id == 0x34113) { + pDiscBundleSections = reinterpret_cast(chunk->GetData()); + pLastDiscBundleSection = reinterpret_cast(reinterpret_cast(pDiscBundleSections) + chunk->Size); + for (DiscBundleSection *disc_bundle = pDiscBundleSections; disc_bundle < pLastDiscBundleSection; + disc_bundle = reinterpret_cast(reinterpret_cast(disc_bundle) + + (disc_bundle->NumMembers * sizeof(DiscBundleSectionMember) + 0x14))) { + bEndianSwap32(&disc_bundle->FileOffset); + bEndianSwap32(&disc_bundle->FileSize); + for (int i = 0; i < disc_bundle->NumMembers; i++) { + DiscBundleSectionMember *member = &disc_bundle->Members[i]; + bEndianSwap16(&member->SectionNumber); + bEndianSwap16(&member->FileOffset); + member->pSection = FindSection(member->SectionNumber); + } + } + return 1; + } else if (chunk_id == 0x34111) { + pInfo = reinterpret_cast(chunk->GetData()); + for (int i = 0; i < 2; i++) { + bEndianSwap32(&pInfo->FileSize[i]); + } + return 1; + } else if (chunk_id == 0x34112) { + pBarriers = reinterpret_cast(chunk->GetData()); + NumBarriers = chunk->Size / sizeof(TrackStreamingBarrier); + for (int i = 0; i < NumBarriers; i++) { + TrackStreamingBarrier *barrier = &pBarriers[i]; + bPlatEndianSwap(&barrier->Points[0]); + bPlatEndianSwap(&barrier->Points[1]); + } + return 1; + } else { + return 0; + } +} + +int TrackStreamer::Unloader(bChunk *chunk) { + unsigned int chunk_id = chunk->GetID(); + if (chunk_id == 0x34110) { + UnloadEverything(); + pTrackStreamingSections = 0; + NumTrackStreamingSections = 0; + return 1; + } + + if (chunk_id == 0x34113) { + pDiscBundleSections = 0; + pLastDiscBundleSection = 0; + return 1; + } + + if (chunk_id == 0x34111) { + pInfo = 0; + return 1; + } + + if (chunk_id == 0x34112) { + pBarriers = 0; + NumBarriers = 0; + return 1; + } + + return 0; +} + +void TrackStreamer::ClearCurrentZones() { + for (int position_number = 0; position_number < 2; position_number++) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + position_entry->CurrentZone = 0; + position_entry->BeginLoadingTime = 0.0f; + position_entry->BeginLoadingPosition.x = 0.0f; + position_entry->BeginLoadingPosition.y = 0.0f; + position_entry->NumSectionsToLoad = 0; + position_entry->NumSectionsLoaded = 0; + position_entry->AmountToLoad = 0; + position_entry->AmountLoaded = 0; + } + + CurrentZoneFarLoad = true; + StartLoadingTime = 0.0f; + NumJettisonedSections = 0; + LoadingPhase = LOADING_IDLE; + LoadingBacklog = StartLoadingTime; + CurrentZoneOutOfMemory = false; + CurrentZoneAllocatedButIncomplete = false; + CurrentZoneNonReplayLoad = false; + CurrentZoneName[0] = 0; + MemorySafetyMargin = 0; + AmountJettisoned = 0; + bMemSet(JettisonedSections, 0, sizeof(JettisonedSections)); + RemoveCurrentStreamingSections(); +} + +void TrackStreamer::InitMemoryPool(int size) { + MemoryPoolSize = size; +#ifdef MILESTONE_OPT + pMemoryPoolMem = bMalloc(size, "Track Streaming", 0, 0x2000); +#else + pMemoryPoolMem = bMalloc(size, 0x2000); +#endif + pMemoryPool = new TSMemoryPool(reinterpret_cast(pMemoryPoolMem), MemoryPoolSize, "Track Streaming", 7); +} + +int TrackStreamer::GetMemoryPoolSize() { + if (pMemoryPool->IsUpdated()) { + UserMemoryAllocationSize = CountUserAllocations(0); + } + return MemoryPoolSize - UserMemoryAllocationSize; +} + +int TrackStreamer::CountUserAllocations(const char **pfragmented_user_allocation) { + int num_fragmented_user_allocations; + + if (pfragmented_user_allocation) { + *pfragmented_user_allocation = 0; + } + + num_fragmented_user_allocations = 0; + int user_allocation_size = 0; + bool start_from_top = false; + TSMemoryNode *node = pMemoryPool->GetFirstAllocatedNode(start_from_top); + while (node) { + TrackStreamingSection *section = FindSectionByAddress(node->Address); + if (!section) { + user_allocation_size += node->Size; + if (pMemoryPool->GetNextFreeNode(start_from_top, node) && pMemoryPool->GetNextFreeNode(!start_from_top, node) && + pfragmented_user_allocation) { + *pfragmented_user_allocation = node->DebugName; + num_fragmented_user_allocations += 1; + } + } + + node = pMemoryPool->GetNextAllocatedNode(start_from_top, node); + } + + (void)num_fragmented_user_allocations; + return user_allocation_size; +} + +TrackStreamingSection *TrackStreamer::FindSection(int section_number) { + for (int n = 0; n < NumTrackStreamingSections; n++) { + TrackStreamingSection *section = &pTrackStreamingSections[n]; + if (section->SectionNumber == static_cast(section_number)) { + return section; + } + } + return 0; +} + +TrackStreamingSection *TrackStreamer::FindSectionByAddress(int address) { + int n; + + { + TrackStreamingSection *section; + for (n = 0; n < NumCurrentStreamingSections; n++) { + section = CurrentStreamingSections[n]; + if (section->pMemory == reinterpret_cast(address)) { + return section; + } + } + } + + { + TrackStreamingSection *section; + for (n = 0; n < NumTrackStreamingSections; n++) { + section = &pTrackStreamingSections[n]; + if (section->pMemory == reinterpret_cast(address)) { + return section; + } + } + } + + return 0; +} + +int TrackStreamer::GetCombinedSectionNumber(int section_number) { + bool use_combined_section = false; + if ((static_cast(section_number / 100 - 1) & 0xFF) < 0x14) { + int subsection_number = section_number % 100; + use_combined_section = subsection_number > 0 && subsection_number < ScenerySectionLODOffset; + } + + if (use_combined_section) { + int combined_section_number = section_number + ScenerySectionLODOffset; + TrackStreamingSection *section = FindSection(section_number); + if (!section) { + section = FindSection(combined_section_number); + if (section) { + return combined_section_number; + } + } + } + + return section_number; +} + +void TrackStreamer::InitRegion(const char *region_stream_filename, bool split_screen) { + bool flush_hibernating_sections = false; + + if (SplitScreen != split_screen) { + SplitScreen = split_screen; + flush_hibernating_sections = true; + } + if (!bStrEqual(StreamFilenames[1], region_stream_filename)) { + flush_hibernating_sections = true; + bStrCpy(StreamFilenames[1], region_stream_filename); + } + if (flush_hibernating_sections) { + FlushHibernatingSections(); + } + if (PermFileLoading) { + BlockWhileQueuedFileBusy(); + } + + ClearCurrentZones(); + ClearStreamingPositions(); + + { + int position_number = 0; + do { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + + position_entry->AudioBlockingPosition.x = 0.0f; + position_entry->PredictedZone = 0; + position_entry->PredictedZoneValidTime = 0; + position_entry->AudioReading = false; + position_entry->AudioReadingTime = 0.0f; + position_entry->AudioReadingPosition.x = 0.0f; + position_entry->AudioReadingPosition.y = 0.0f; + position_entry->AudioBlocking = false; + position_entry->AudioBlockingTime = 0.0f; + position_entry->AudioBlockingPosition.y = 0.0f; + position_number += 1; + } while (position_number < 2); + } + + int n = 0; + while (n < NumTrackStreamingSections) { + TrackStreamingSection *section = &pTrackStreamingSections[n]; + int boundary_section_number = GetBoundarySectionNumber(static_cast(section->SectionNumber), bGetPlatformName()); + VisibleSectionBoundary *boundary = TheVisibleSectionManager.FindBoundary(boundary_section_number); + + section->pBoundary = boundary; + n += 1; + } + + EmptyCaffeineLayers(); +} + +void TrackStreamer::HibernateStreamingSections() { + int sections_to_hibernate[5]; + int n; + int section_number; + TrackStreamingSection *section; + TrackStreamingSection *hibernating_section; + + (void)sections_to_hibernate; + (void)n; + (void)section_number; + (void)section; + (void)hibernating_section; + return; +} + +void TrackStreamer::FlushHibernatingSections() { + for (int n = 0; n < NumHibernatingSections; n++) { + TrackStreamingSection *section = &HibernatingSections[n]; + bFree(section->pMemory); + } + NumHibernatingSections = 0; +} + +void *TrackStreamer::AllocateMemory(TrackStreamingSection *section, int allocation_params) { + void *buf = bMalloc(section->Size, section->SectionName, 0, allocation_params | 0x2007); + if (!buf) { + bBreak(); + } + return buf; +} + +void TrackStreamer::LoadDiscBundle(DiscBundleSection *disc_bundle) { + void *memory = 0; + for (int i = 0; i < disc_bundle->NumMembers; i++) { + DiscBundleSectionMember *member = &disc_bundle->Members[i]; + TrackStreamingSection *section = member->pSection; + if (i == 0) { + memory = section->pMemory; + } + section->Status = TrackStreamingSection::LOADING; + } + + NumSectionsLoading += 1; + AddQueuedFile(memory, StreamFilenames[1], disc_bundle->FileOffset, disc_bundle->FileSize, DiscBundleLoadedCallback, + reinterpret_cast(disc_bundle), 0); +} + +void TrackStreamer::DiscBundleLoadedCallback(int param, int error_status) { + (void)error_status; + TheTrackStreamer.DiscBundleLoadedCallback(reinterpret_cast(param)); +} + +void TrackStreamer::DiscBundleLoadedCallback(DiscBundleSection *disc_bundle) { + NumSectionsLoading += -1 + disc_bundle->NumMembers; + for (int i = 0; i < disc_bundle->NumMembers; i++) { + DiscBundleSectionMember *member = &disc_bundle->Members[i]; + TrackStreamingSection *section = member->pSection; + section->pDiscBundle = 0; + SectionLoadedCallback(section); + } +} + +void TrackStreamer::LoadSection(TrackStreamingSection *section) { + NumSectionsLoading += 1; + section->Status = TrackStreamingSection::LOADING; + + if (section->CompressedSize == section->Size) { + AddQueuedFile(section->pMemory, StreamFilenames[section->FileType], section->FileOffset, section->CompressedSize, SectionLoadedCallback, + reinterpret_cast(section), 0); + } else { + QueuedFileParams params; + params.BlockSize = 0x7ffffff; + params.Priority = QueuedFileDefaultPriority; + params.Compressed = false; + params.Compressed = true; + params.UncompressedSize = section->Size; + AddQueuedFile(section->pMemory, StreamFilenames[section->FileType], section->FileOffset, section->CompressedSize, SectionLoadedCallback, + reinterpret_cast(section), ¶ms); + } +} + +void TrackStreamer::ActivateSection(TrackStreamingSection *section) { + ProfileNode profile_node(section->SectionName, 0); + int allocation_params = 0x2087; + NumSectionsActivated += 1; + eAllowDuplicateSolids(true); + SetDuplicateTextureWarning(false); + + bChunk *chunks = reinterpret_cast(section->pMemory); + int sizeof_chunks = section->LoadedSize; + LoadTempPermChunks(&chunks, &sizeof_chunks, allocation_params, section->SectionName); + + section->pMemory = chunks; + section->LoadedSize = sizeof_chunks; + section->Status = TrackStreamingSection::ACTIVATED; + section->LoadedTime = 0; + eAllowDuplicateSolids(false); + SetDuplicateTextureWarning(true); +} + +void TrackStreamer::UnactivateSection(TrackStreamingSection *section) { + ProfileNode profile_node(section->SectionName, 0); + section->UnactivatedFrameCount = 0; + DisableWaitUntilRenderingDone(); + section->UnactivatedFrameCount = eGetFrameCounter(); + UnloadChunks(reinterpret_cast(section->pMemory), section->LoadedSize, section->SectionName); + EnableWaitUntilRenderingDone(); + NumSectionsActivated -= 1; + section->Status = TrackStreamingSection::LOADED; +} + +bool TrackStreamer::WillUnloadBlock(TrackStreamingSection *section) { + unsigned int frame = section->UnactivatedFrameCount; + if ((frame != 0) && (frame == eFrameCounter)) { + if (LastWaitUntilRenderingDoneFrameCount != frame) { + return true; + } + } + return false; +} + +void TrackStreamer::UnloadSection(TrackStreamingSection *section) { + ProfileNode profile_node(section->SectionName, 0); + if (section->Status == TrackStreamingSection::ACTIVATED) { + UnactivateSection(section); + } + + if (section->Status == TrackStreamingSection::LOADED) { + if (WillUnloadBlock(section)) { + DisableWaitForFrameBufferSwap(); + eWaitUntilRenderingDone(); + EnableWaitForFrameBufferSwap(); + LastWaitUntilRenderingDoneFrameCount = eGetFrameCounter(); + } + + section->UnactivatedFrameCount = 0; + bFree(section->pMemory); + section->LoadedTime = 0; + section->pMemory = 0; + section->Status = TrackStreamingSection::UNLOADED; + NumSectionsLoaded -= 1; + } +} + +bool TrackStreamer::NeedsGameStateActivation(TrackStreamingSection *section) { + return false; + + if (IsRegularScenerySection(section->SectionNumber) && IsLODScenerySectionNumber(section->SectionNumber)) { + return true; + } +} + +void TrackStreamer::SectionLoadedCallback(int param, int error_status) { + (void)error_status; + TheTrackStreamer.SectionLoadedCallback(reinterpret_cast(param)); +} + +void TrackStreamer::SectionLoadedCallback(TrackStreamingSection *section) { + section->Status = TrackStreamingSection::LOADED; + section->LoadedSize = section->Size; + EndianSwapChunkHeadersRecursive(reinterpret_cast(section->pMemory), section->Size); + NumSectionsLoading -= 1; + NumSectionsLoaded += 1; + section->LoadedTime = RealTimeFrames; + + if (section->CurrentlyVisible && !NeedsGameStateActivation(section)) { + ActivateSection(section); + } + + for (int position_number = 0; position_number < 2; position_number++) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + if (((section->CurrentlyVisible >> position_number) & 1) != 0) { + position_entry->NumSectionsLoaded += 1; + position_entry->AmountLoaded += section->Size; + } + } + + CalculateLoadingBacklog(); +#ifdef EA_PLATFORM_GAMECUBE + DCStoreRange(section->pMemory, section->LoadedSize); +#endif +} + +void TrackStreamer::EmptyCaffeineLayers() { + TrackStreamerRemoteCaffeinating = 0; +} + +void TrackStreamer::SetLoadingPhase(eLoadingPhase phase) { + LoadingPhase = phase; + if (phase == LOADING_IDLE || phase == LOADING_REGULAR_SECTIONS) { + SetQueuedFileMinPriority(0); + } else { + SetQueuedFileMinPriority(QueuedFileDefaultPriority); + } +} + +int TrackStreamer::UnloadLeastRecentlyUsedSection() { + TrackStreamingSection *best_section = 0; + + for (int n = 0; n < NumTrackStreamingSections; n++) { + TrackStreamingSection *section = &pTrackStreamingSections[n]; + if (section->Status == TrackStreamingSection::LOADED && !section->CurrentlyVisible && + (!best_section || section->LastNeededTimestamp < best_section->LastNeededTimestamp)) { + best_section = section; + } + } + + if (!best_section) { + return 0; + } + + UnloadSection(best_section); + return best_section->LoadedSize; +} + +void TrackStreamer::JettisonSection(TrackStreamingSection *section) { + AmountJettisoned += section->Size; + JettisonedSections[NumJettisonedSections] = section; + NumJettisonedSections += 1; + + if (section->Status == TrackStreamingSection::ACTIVATED) { + UnactivateSection(section); + } + if (section->Status == TrackStreamingSection::LOADED) { + UnloadSection(section); + } + + section->CurrentlyVisible = false; + + int index = 0; + while (CurrentStreamingSections[index] != section) { + index += 1; + } + + while (index < NumCurrentStreamingSections - 1) { + CurrentStreamingSections[index] = CurrentStreamingSections[index + 1]; + index += 1; + } + + NumCurrentStreamingSections -= 1; +} + +bool TrackStreamer::JettisonLeastImportantSection() { + TrackStreamingSection *best_section = ChooseSectionToJettison(); + if (best_section) { + JettisonSection(best_section); + return true; + } + return false; +} + +TrackStreamingSection *TrackStreamer::ChooseSectionToJettison() { + TrackStreamingSection *best_section = 0; + int best_discard_priority = 0; + static int last_jettison_print; + bool print_jettison_this_frame = false; + + last_jettison_print = RealLoopCounter; + for (int i = 0; i < NumCurrentStreamingSections; i++) { + int discard_priority = 0; + TrackStreamingSection *section = CurrentStreamingSections[i]; + + if (IsTextureSection(section->SectionNumber) || IsLibrarySection(section->SectionNumber)) { + discard_priority = 2; + if (section->SectionNumber == GetScenerySectionNumber('Y', 0) || section->SectionNumber == GetScenerySectionNumber('W', 0) || + section->SectionNumber == GetScenerySectionNumber('X', 0)) { + discard_priority = 1; + } else if (LoadingPhase == ALLOCATING_TEXTURE_SECTIONS && IsTextureSection(section->SectionNumber) && + section->Status == TrackStreamingSection::ACTIVATED && !SplitScreen) { + discard_priority = 10000; + } else if (LoadingPhase == ALLOCATING_GEOMETRY_SECTIONS && IsLibrarySection(section->SectionNumber) && + section->Status == TrackStreamingSection::ACTIVATED && !SplitScreen) { + discard_priority = 10000; + } + } else if (IsRegularScenerySection(section->SectionNumber)) { + int loading_priority = GetLoadingPriority(section, &StreamingPositionEntries[0], true); + if (SplitScreen) { + int loading_priority2 = GetLoadingPriority(section, &StreamingPositionEntries[1], true); + if (loading_priority2 < loading_priority) { + loading_priority = loading_priority2; + } + } + discard_priority = loading_priority * 10 + 100; + } + + if (discard_priority != 0) { + if (static_cast(section->Status - TrackStreamingSection::LOADED) > 1) { + discard_priority += 1; + } + } + if (discard_priority > best_discard_priority) { + best_section = section; + best_discard_priority = discard_priority; + } + } + + return best_section; +} + +void TrackStreamer::UnJettisonSections() { + for (int n = 0; n < NumJettisonedSections; n++) { + TrackStreamingSection *section = JettisonedSections[n]; + CurrentStreamingSections[NumCurrentStreamingSections++] = section; + section->CurrentlyVisible = true; + } + NumJettisonedSections = 0; + AmountJettisoned = 0; +} + +int TrackStreamer::BuildHoleMovements(HoleMovement *hole_movements, int max_movements, int filler_method, int largest_free, int *pamount_moved, + int max_amount_to_move) { + ProfileNode profile_node("TODO", 0); + int ticks = bGetTicker(); + unsigned int checksum = pMemoryPool->GetPoolChecksum(); + bool failed; + int num_movements; + int amount_moved; + int total_needing_allocation; + + pMemoryPool->EnableTracing(false); + total_needing_allocation = -1; + failed = false; + num_movements = 0; + amount_moved = 0; + while (true) { + if (largest_free >= 0) { + if (pMemoryPool->GetLargestFreeBlock() >= largest_free) { + break; + } + } else { + int out_of_memory_size = AllocateSectionMemory(&total_needing_allocation); + FreeSectionMemory(); + if (out_of_memory_size == 0) { + break; + } + } + + if (filler_method != 0) { + if (pMemoryPool->GetAmountFree() == pMemoryPool->GetLargestFreeBlock()) { + break; + } + } + + if (num_movements == max_movements) { + break; + } + + HoleMovement *movement = &hole_movements[num_movements]; + movement->Address = 0; + + if (filler_method == 2 || filler_method == 0 || filler_method == 3) { + bool start_from_top = filler_method == 3; + TSMemoryNode *free_node = pMemoryPool->GetFirstFreeNode(start_from_top); + TSMemoryNode *node = pMemoryPool->GetNextAllocatedNode(start_from_top, free_node); + if (filler_method == 0 && !node) { + break; + } + + if (node && free_node) { + movement->Size = node->Size; + movement->Address = node->Address; + movement->NewAddress = free_node->GetAddress(start_from_top, movement->Size); + if (filler_method == 0 && !FindSectionByAddress(movement->Address)) { + break; + } + } + } else if (filler_method == 4 || filler_method == 5) { + bool start_from_top = filler_method == 5; + TSMemoryNode *free_node = pMemoryPool->GetFirstFreeNode(start_from_top); + int best_hole_size = 0; + bool first = true; + + for (TSMemoryNode *next_node = pMemoryPool->GetNextAllocatedNode(start_from_top, free_node); next_node; + next_node = pMemoryPool->GetNextAllocatedNode(start_from_top, next_node)) { + TSMemoryNode *next_free = pMemoryPool->GetNextFreeNode(start_from_top, next_node); + if (!next_free) { + continue; + } + if (first || next_node->Size <= free_node->Size) { + TSMemoryNode *node1 = pMemoryPool->GetNextNode(!start_from_top, next_node); + TSMemoryNode *node2 = pMemoryPool->GetNextNode(start_from_top, next_node); + int hole_size = next_node->Size; + if (node1 && node1->IsFree()) { + hole_size += node1->Size; + } + if (node2 && node2->IsFree()) { + hole_size += node2->Size; + } + if (hole_size > best_hole_size) { + best_hole_size = hole_size; + movement->Size = next_node->Size; + movement->Address = next_node->Address; + movement->NewAddress = free_node->GetAddress(start_from_top, movement->Size); + first = false; + } + } + } + } else if (filler_method == 1) { + bool done = false; + bool found_one = false; + bool found_big_enough = false; + TSMemoryNode *largest_allocated = 0; + int current_best = 0; + int current_best_middle_memory = 0x3E8000; + int best_address = 0; + bool first_pass = true; + TSMemoryNode *top_free_top = 0; + + do { + if (first_pass) { + first_pass = false; + top_free_top = pMemoryPool->GetFirstNode(true); + } else { + top_free_top = pMemoryPool->GetNextFreeNode(true, top_free_top); + } + + if (!top_free_top) { + done = true; + } else { + int top_free_memory = top_free_top->Size; + TSMemoryNode *bottom_free_top = pMemoryPool->GetNextFreeNode(true, top_free_top); + if (!bottom_free_top) { + done = true; + } else { + TSMemoryNode *top_allocated = pMemoryPool->GetNextNode(true, top_free_top); + pMemoryPool->GetNextNode(false, bottom_free_top); + + int middle_allocated_memory = top_allocated->Size; + int total_free_memory = top_free_memory + bottom_free_top->Size; + int size_checking[32]; + int i = 0; + do { + size_checking[i] = 0; + i += 1; + } while (i < 32); + + size_checking[0] = top_allocated->Size; + + TSMemoryNode *largest_allocated_here = top_allocated; + TSMemoryNode *cursor = top_allocated; + int found_nodes = 1; + while ((cursor = pMemoryPool->GetNextNode(true, top_allocated)) != bottom_free_top && top_allocated && found_nodes < 32) { + top_allocated = pMemoryPool->GetNextNode(true, top_allocated); + if (top_allocated) { + size_checking[found_nodes] = top_allocated->Size; + middle_allocated_memory += top_allocated->Size; + found_nodes += 1; + if (top_allocated->Size > largest_allocated_here->Size) { + largest_allocated_here = top_allocated; + } + } + } + + int free_gap = total_free_memory - middle_allocated_memory; + if ((!found_big_enough && current_best < free_gap) || + (found_big_enough && total_free_memory + middle_allocated_memory >= total_needing_allocation && + middle_allocated_memory < current_best_middle_memory)) { + std::sort(size_checking, size_checking + found_nodes); + int evaluated_best_address = 0; + bool largest_flag = false; + int nodes_to_move = 0; + int position = 0; + + TSMemoryNode *evaluated_top_free = pMemoryPool->GetFirstFreeNode(true); + while (found_nodes > nodes_to_move && evaluated_top_free) { + bool skip_flag = false; + int target_index = found_nodes - nodes_to_move - 1; + for (int i = 0; i < found_nodes; i++) { + if (size_checking[target_index] == position) { + skip_flag = true; + } + } + if (evaluated_top_free == top_free_top || evaluated_top_free == bottom_free_top) { + skip_flag = true; + } + if (!skip_flag && evaluated_top_free->Size >= size_checking[target_index]) { + size_checking[target_index] = position; + nodes_to_move += 1; + if (!largest_flag) { + evaluated_best_address = evaluated_top_free->Address; + largest_flag = true; + } + evaluated_top_free = pMemoryPool->GetNextFreeNode(true, 0); + } + evaluated_top_free = pMemoryPool->GetNextFreeNode(true, evaluated_top_free); + position += 1; + } + + if (nodes_to_move >= found_nodes) { + current_best = free_gap; + best_address = evaluated_best_address; + found_one = true; + largest_allocated = largest_allocated_here; + if (total_free_memory + middle_allocated_memory >= total_needing_allocation) { + found_big_enough = true; + current_best_middle_memory = middle_allocated_memory; + } + } + } + } + } + } while (!done); + + if (found_one && largest_allocated && FindSectionByAddress(largest_allocated->Address)) { + movement->Size = largest_allocated->Size; + movement->Address = largest_allocated->Address; + movement->NewAddress = best_address; + } + } + + if (movement->Address == 0) { + failed = true; + break; + } + + num_movements += 1; + movement->Checksum = pMemoryPool->GetPoolChecksum(); + pMemoryPool->Free(reinterpret_cast(movement->Address)); + pMemoryPool->Malloc(movement->Size, "HoleMovement", false, false, movement->NewAddress); + amount_moved += movement->Size; + if (max_amount_to_move < amount_moved) { + failed = true; + break; + } + } + + for (int n = num_movements - 1; n >= 0; n--) { + HoleMovement *movement = &hole_movements[n]; + pMemoryPool->Free(reinterpret_cast(movement->NewAddress)); + TrackStreamingSection *section = FindSectionByAddress(movement->Address); + char *debug_name; + if (section) { + debug_name = section->SectionName; + } else { + debug_name = "UndoHoleMovement"; + } + pMemoryPool->Malloc(movement->Size, debug_name, false, false, movement->Address); + } + + pMemoryPool->EnableTracing(true); + if (pamount_moved) { + *pamount_moved = amount_moved; + } + if (failed) { + return -1; + } + return num_movements; +} + +int TrackStreamer::DoHoleFilling(int largest_free) { + ProfileNode profile_node("TODO", 0); + const char *fragmented_user_allocation; + HoleMovement hole_movement_table[128]; + + CountUserAllocations(&fragmented_user_allocation); + if (fragmented_user_allocation) { + pMemoryPool->DebugPrint(); + return 0; + } + + int best_method = -1; + int forced_hole_filler_method = ForceHoleFillerMethod; + if (forced_hole_filler_method >= 0) { + int num_hole_movements = BuildHoleMovements(hole_movement_table, 0x80, forced_hole_filler_method, largest_free, 0, 0x7FFFFFFF); + if (num_hole_movements > 0) { + best_method = forced_hole_filler_method; + } + } else { + int best_amount_moved = 0x7FFFFFFF; + for (int filler_method = 1; filler_method < 6; filler_method++) { + int amount_moved; + int num_hole_movements = BuildHoleMovements(hole_movement_table, 0x80, filler_method, largest_free, &amount_moved, best_amount_moved); + if (num_hole_movements > 0 && amount_moved < best_amount_moved) { + best_method = filler_method; + best_amount_moved = amount_moved; + } + } + } + + if (best_method < 0) { + return 0; + } + + int num_hole_movements = BuildHoleMovements(hole_movement_table, 0x80, best_method, largest_free, 0, 0x7FFFFFFF); + for (int n = 0; n < num_hole_movements; n++) { + ProfileNode profile_node("TODO", 0); + HoleMovement *movement = &hole_movement_table[n]; + TrackStreamingSection *section = FindSectionByAddress(movement->Address); + if (LastWaitUntilRenderingDoneFrameCount != eGetFrameCounter()) { + int start_ticks = bGetTicker(); + DisableWaitForFrameBufferSwap(); + eWaitUntilRenderingDone(); + LastWaitUntilRenderingDoneFrameCount = eGetFrameCounter(); + EnableWaitForFrameBufferSwap(); + float time = bGetTickerDifference(start_ticks); + (void)time; + } + + int start_ticks = bGetTicker(); + void *new_memory = reinterpret_cast(movement->NewAddress); + pMemoryPool->Free(reinterpret_cast(movement->Address)); + pMemoryPool->Malloc(movement->Size, section->SectionName, false, false, movement->NewAddress); + if (section->Status == TrackStreamingSection::ACTIVATED) { + eAllowDuplicateSolids(true); + SetDuplicateTextureWarning(false); + MoveChunks(reinterpret_cast(new_memory), reinterpret_cast(section->pMemory), section->LoadedSize, + section->SectionName); +#ifdef EA_PLATFORM_GAMECUBE + DCStoreRangeNoSync(new_memory, section->LoadedSize); +#endif + eAllowDuplicateSolids(false); + SetDuplicateTextureWarning(true); + } else { + eWaitUntilRenderingDone(); + bOverlappedMemCpy(new_memory, section->pMemory, section->LoadedSize); + } + section->pMemory = new_memory; + float move_time = bGetTickerDifference(start_ticks); + (void)move_time; + NumSectionsMoved += 1; +#ifdef EA_PLATFORM_GAMECUBE + PPCSync(); +#endif + } + + return 1; +} + +void TrackStreamer::SetStreamingPosition(int position_number, const bVector3 *position) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + position_entry->Position.x = position->x; + position_entry->Position.y = position->y; + position_entry->PredictedZone = 0; + position_entry->Elevation = position->z; + position_entry->Direction.y = 0.0f; + position_entry->PredictedZoneValidTime = -1; + position_entry->Velocity.x = 0.0f; + position_entry->Velocity.y = 0.0f; + position_entry->Direction.x = 0.0f; + position_entry->PositionSet = true; + position_entry->FollowingCar = false; + CurrentZoneNeedsRefreshing = true; +} + +void TrackStreamer::PredictStreamingPosition(int position_number, const bVector3 *position, const bVector3 *velocity, const bVector3 *direction, + bool following_car) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + position_entry->Position.x = position->x; + position_entry->Position.y = position->y; + position_entry->Elevation = position->z; + position_entry->Velocity.x = velocity->x; + position_entry->Velocity.y = velocity->y; + position_entry->Direction.x = direction->x; + float direction_y = direction->y; + position_entry->FollowingCar = following_car; + position_entry->Direction.y = direction_y; + position_entry->PositionSet = true; +} + +void TrackStreamer::ReadyToMakeSpaceInPoolBridge(int param) { + reinterpret_cast(param)->ReadyToMakeSpaceInPool(); +} + +short TrackStreamer::GetPredictedZone(StreamingPositionEntry *position_entry) { + float speed = bLength(&position_entry->Velocity); + int predicted_zone = 0; + bool found_predicted_zone = false; + TrackPathZone *zone = 0; + bVector2 predict_position; + + while ((zone = TheTrackPathManager.FindZone(&position_entry->Position, TRACK_PATH_ZONE_STREAMER_PREDICTION, zone))) { + float elevation = zone->GetElevation(); + if ((0.0f < elevation && position_entry->Elevation < elevation) || (elevation < 0.0f && -elevation < position_entry->Elevation)) { + continue; + } + + float max_speed = kPredictedZoneStopProjectSpeed_TrackStreamer * kPredictedZoneScale_TrackStreamer; + float distance = speed * kPredictedZoneScale_TrackStreamer; + DrivableScenerySection *scenery_section; + if (max_speed < speed) { + predict_position = position_entry->Position; + } else if (kPredictedZoneMaxDistance_TrackStreamer < distance) { + bScaleAdd(&predict_position, &position_entry->Position, &position_entry->Velocity, kPredictedZoneMaxDistance_TrackStreamer / speed); + } else { + bScaleAdd(&predict_position, &position_entry->Position, &position_entry->Velocity, kPredictedZoneScale_TrackStreamer); + } + + scenery_section = TheVisibleSectionManager.FindDrivableSection(&predict_position); + if (scenery_section && zone->Data[0] != 0) { + short section_number = scenery_section->SectionNumber; + for (int i = 0; i <= 3; i++) { + if (zone->Data[i] == 0) { + break; + } + if (zone->Data[i] == section_number) { + found_predicted_zone = true; + predicted_zone = section_number; + break; + } + } + } + } + + if (found_predicted_zone) { + if (!bEqual(&predict_position, &position_entry->Position, kPredictedZoneEqualEpsilon_TrackStreamer)) { + for (int barrier_num = 0; barrier_num < NumBarriers; barrier_num++) { + TrackStreamingBarrier *barrier = &pBarriers[barrier_num]; + if (barrier->Intersects(&position_entry->Position, &predict_position)) { + found_predicted_zone = false; + predicted_zone = 0; + } + } + } + + if (found_predicted_zone) { + return predicted_zone; + } + } + + DrivableScenerySection *scenery_section = TheVisibleSectionManager.FindDrivableSection(&position_entry->Position); + if (scenery_section) { + predicted_zone = scenery_section->SectionNumber; + } + return predicted_zone; +} + +void TrackStreamer::ClearStreamingPositions() { + for (int position_number = 0; position_number < 2; position_number++) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + position_entry->PositionSet = false; + position_entry->FollowingCar = false; + } +} + +void TrackStreamer::RemoveCurrentStreamingSections() { + for (int i = 0; i < NumCurrentStreamingSections; i++) { + CurrentStreamingSections[i]->CurrentlyVisible = 0; + } + + NumCurrentStreamingSections = 0; + bBitTableLayout_TrackStreamer *layout = reinterpret_cast(&CurrentVisibleSectionTable); + bMemSet(layout->Bits, 0, layout->NumBits >> 3); +} + +void TrackStreamer::AddCurrentStreamingSections(short *section_numbers, int num_sections, int position_number) { + int i = 0; + if (i < num_sections) { + StreamingPositionEntry *streaming_position = &StreamingPositionEntries[position_number]; + unsigned int position_bit = 1 << position_number; + do { + short §ion_number = section_numbers[i]; + CurrentVisibleSectionTable.Set(section_number); + if (SplitScreen) { + section_number = static_cast(Get2PlayerSectionNumber(section_number)); + } + + TrackStreamingSection *section = FindSection(section_number); + if (!section) { + continue; + } + + section->LastNeededTimestamp = RealTimeFrames; + if (!section->CurrentlyVisible) { + CurrentStreamingSections[NumCurrentStreamingSections++] = section; + } + + if ((((static_cast(section->CurrentlyVisible) >> position_number) ^ 1U) & 1) != 0) { + section->CurrentlyVisible |= static_cast(position_bit); + if (section->Status < TrackStreamingSection::LOADED) { + streaming_position->NumSectionsToLoad += 1; + streaming_position->AmountToLoad += section->Size; + } + } + i += 1; + } while (i < num_sections); + } +} + +void TrackStreamer::DetermineStreamingSections() { + const int max_sections_to_load = 0x180; + short sections_to_load[384]; + int num_sections_to_load = 3; + unsigned short section_number; + + RemoveCurrentStreamingSections(); + sections_to_load[0] = GetScenerySectionNumber_TrackStreamer('Y', 0); + sections_to_load[1] = GetScenerySectionNumber_TrackStreamer('X', 0); + sections_to_load[2] = GetScenerySectionNumber_TrackStreamer('Z', 0); + + { + short *sections_to_load_ptr = sections_to_load; + if (SeeulatorToolActive && ScenerySectionToBlink != 0) { + num_sections_to_load = 4; + sections_to_load_ptr[3] = static_cast(ScenerySectionToBlink); + } + + for (int n = 0; n < 4; n++) { + section_number = KeepSectionTable[n]; + if (section_number != 0) { + sections_to_load_ptr[num_sections_to_load] = section_number; + num_sections_to_load += 1; + } + } + + AddCurrentStreamingSections(sections_to_load, num_sections_to_load, 0); + AddCurrentStreamingSections(sections_to_load, num_sections_to_load, 1); + int position_number = 0; + do { + { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + if (position_entry->CurrentZone > 0) { + { + LoadingSection *loading_section = TheVisibleSectionManager.FindLoadingSection(position_entry->CurrentZone); + if (!loading_section) { + { + DrivableScenerySection *drivable_section = TheVisibleSectionManager.FindDrivableSection(position_entry->CurrentZone); + num_sections_to_load = 0; + for (int i = 0; i < drivable_section->GetNumVisibleSections(); i++) { + { + int section_number = drivable_section->GetVisibleSection(i); + sections_to_load_ptr[num_sections_to_load] = section_number; + num_sections_to_load += 1; + } + } + } + } else { + num_sections_to_load = + TheVisibleSectionManager.GetSectionsToLoad(loading_section, sections_to_load_ptr, max_sections_to_load); + } + } + + AddCurrentStreamingSections(sections_to_load, num_sections_to_load, position_number); + } + } + position_number += 1; + } while (position_number < 2); + } +} + +int TrackStreamer::AllocateSectionMemory(int *ptotal_needing_allocation) { + ProfileNode profile_node("TODO", 0); + int out_of_memory_size = 0; + int total_needing_allocation = 0; + int num_sections_allocated = 0; + + if (LoadingPhase == ALLOCATING_REGULAR_SECTIONS && pDiscBundleSections < pLastDiscBundleSection) { + for (DiscBundleSection *disc_bundle = pDiscBundleSections; disc_bundle < pLastDiscBundleSection; + disc_bundle = disc_bundle->GetMemoryImageNext()) { + int i = 0; + if (disc_bundle->NumMembers > 0) { + do { + DiscBundleSectionMember *member = &disc_bundle->Members[i]; + TrackStreamingSection *section = member->pSection; + if (!section->CurrentlyVisible || section->Status != TrackStreamingSection::UNLOADED) { + break; + } + i += 1; + } while (i < disc_bundle->NumMembers); + } + + if (i == disc_bundle->NumMembers) { + if (disc_bundle->FileSize <= pMemoryPool->GetLargestFreeBlock()) { + unsigned char *pmemory = + static_cast(pMemoryPool->Malloc(disc_bundle->FileSize, disc_bundle->SectionName, true, false, 0)); + pMemoryPool->Free(pmemory); + + for (i = 0; i < disc_bundle->NumMembers; i++) { + DiscBundleSectionMember *member = &disc_bundle->Members[i]; + TrackStreamingSection *section = member->pSection; + void *realloc_mem = pmemory + member->FileOffset * 0x80; + + num_sections_allocated += 1; + section->pDiscBundle = disc_bundle; + section->Status = TrackStreamingSection::ALLOCATED; + section->pMemory = realloc_mem; + pMemoryPool->Malloc(section->Size, disc_bundle->SectionName, false, false, reinterpret_cast(realloc_mem)); + } + + total_needing_allocation += disc_bundle->FileSize; + } + } + } + } + + for (int i = 0; i < NumCurrentStreamingSections; i++) { + TrackStreamingSection *section = CurrentStreamingSections[i]; + if (section->Status != TrackStreamingSection::UNLOADED) { + continue; + } + + if (section->SectionNumber != GetScenerySectionNumber('Y', 0) && section->SectionNumber != GetScenerySectionNumber('X', 0) && + section->SectionNumber != GetScenerySectionNumber('W', 0) && section->SectionNumber != GetScenerySectionNumber('U', 0) && + section->SectionNumber != GetScenerySectionNumber('Z', 0)) { + if (LoadingPhase == ALLOCATING_TEXTURE_SECTIONS) { + if (!IsTextureSection(section->SectionNumber)) { + continue; + } + } + + if (LoadingPhase == ALLOCATING_GEOMETRY_SECTIONS) { + if (!IsLibrarySection(section->SectionNumber)) { + continue; + } + } + } + + total_needing_allocation += section->Size; + if (bLargestMalloc(7) < section->Size) { + out_of_memory_size += section->Size; + NumSectionsOutOfMemory += 1; + } else { + num_sections_allocated += 1; + section->pMemory = AllocateMemory(section, 0x80); + section->Status = TrackStreamingSection::ALLOCATED; + if (num_sections_allocated > 99999) { + CurrentZoneAllocatedButIncomplete = true; + return out_of_memory_size; + } + } + } + + CurrentZoneAllocatedButIncomplete = false; + *ptotal_needing_allocation = total_needing_allocation; + return out_of_memory_size; +} + +void TrackStreamer::FreeSectionMemory() { + NumSectionsOutOfMemory = 0; + for (int n = 0; n < NumTrackStreamingSections; n++) { + TrackStreamingSection *section = &pTrackStreamingSections[n]; + if (section->Status == TrackStreamingSection::ALLOCATED) { + bFree(section->pMemory); + section->pDiscBundle = 0; + section->pMemory = 0; + section->Status = TrackStreamingSection::UNLOADED; + } + } +} + +bool TrackStreamer::HandleMemoryAllocation() { + int out_of_memory_size; + int total_amount_unloaded = 0; + int total_amount_hole_filled = 0; + + { + int total_needing_allocation; + int amount_unloaded; + + do { + amount_unloaded = UnloadLeastRecentlyUsedSection(); + } while (amount_unloaded != 0); + + NumSectionsMoved = 0; + + do { + do { + FreeSectionMemory(); + out_of_memory_size = AllocateSectionMemory(&total_needing_allocation); + if (out_of_memory_size == 0) { + goto done; + } + if (total_amount_unloaded > 0x7FFFFFFF || total_amount_hole_filled > 0x200000) { + return false; + } + if (NumSectionsMoved > 15) { + return false; + } + + FreeSectionMemory(); + amount_unloaded = UnloadLeastRecentlyUsedSection(); + total_amount_unloaded += amount_unloaded; + } while (amount_unloaded > 0); + + { + int amount_hole_filled; + int threshold = total_needing_allocation + 0x4000; + + while (bCountFreeMemory(7) < threshold) { + CurrentZoneOutOfMemory = true; + if (!JettisonLeastImportantSection()) { + break; + } + AllocateSectionMemory(&total_needing_allocation); + FreeSectionMemory(); + threshold = total_needing_allocation + 0x4000; + } + + amount_hole_filled = DoHoleFilling(0); + total_amount_hole_filled += amount_hole_filled; + if (amount_hole_filled != 0) { + continue; + } + } + + CurrentZoneOutOfMemory = true; + } while (JettisonLeastImportantSection()); + + out_of_memory_size = AllocateSectionMemory(&total_needing_allocation); + } + +done: + MemorySafetyMargin = 0; + + { + int amount_jettisoned = AmountJettisoned; + int free_memory = bCountFreeMemory(7) - (out_of_memory_size + amount_jettisoned); + { + int n = 0; + int num_track_streaming_sections = NumTrackStreamingSections; + + if (num_track_streaming_sections > 0) { + do { + TrackStreamingSection *section = &pTrackStreamingSections[n]; + if (!section->CurrentlyVisible && section->Status != TrackStreamingSection::UNLOADED) { + free_memory += section->PermSize; + } + n += 1; + } while (n < num_track_streaming_sections); + } + } + MemorySafetyMargin = free_memory; + } + + if (out_of_memory_size + AmountJettisoned != 0) { + { + int n = 0; + + if (NumJettisonedSections > 0) { + do { + n += 1; + } while (n < NumJettisonedSections); + } + } + + if (LoadingPhase == LOADING_REGULAR_SECTIONS) { + int i = 0; + + if (NumCurrentStreamingSections > 0) { + do { + TrackStreamingSection *section; + i += 1; + } while (i < NumCurrentStreamingSections); + } + } + } + + return true; +} + +void *TrackStreamer::AllocateUserMemory(int size, const char *debug_name, int offset) { +#ifndef MILESTONE_OPT + (void)debug_name; +#endif + + int allocation_params; + if (size > bLargestMalloc(7)) { + allocation_params = (offset & 0x1FFC) << 17 | 0x2000; + } else { + allocation_params = (offset & 0x1FFC) << 17 | 0x2047; + } +#ifdef MILESTONE_OPT + return bMalloc(size, debug_name, 0, allocation_params); +#else + return bMalloc(size, allocation_params); +#endif +} + +void TrackStreamer::FreeUserMemory(void *mem) { + int free_before = pMemoryPool->GetAmountFree(); + bFree(mem); + int size = pMemoryPool->GetAmountFree(); + (void)free_before; + (void)size; +} + +bool TrackStreamer::IsUserMemory(void *mem) { + int pos = static_cast(mem) - static_cast(pMemoryPoolMem); + return pMemoryPoolMem && pos >= 0 && pos < MemoryPoolSize; +} + +bool TrackStreamer::MakeSpaceInPool(int size, bool force_unloading) { + WaitForCurrentLoadingToComplete(); + while (bCountFreeMemory(7) < size) { + int amount_unloaded = UnloadLeastRecentlyUsedSection(); + if (amount_unloaded == 0 && (!force_unloading || !JettisonLeastImportantSection())) { + break; + } + } + + ForceHoleFillerMethod = 0; + DoHoleFilling(0x7FFFFFFF); + ForceHoleFillerMethod = -1; + return size <= bLargestMalloc(7); +} + +void TrackStreamer::MakeSpaceInPool(int size, void (*callback)(int), int param) { + if (LoadingPhase == LOADING_IDLE) { + IsLoadingInProgress(); + } + + if (!IsLoadingInProgress()) { + MakeSpaceInPool(size, true); + callback(param); + } else { + MakeSpaceInPoolSize = size; + MakeSpaceInPoolCallback = callback; + MakeSpaceInPoolCallbackParam = param; + pCallback = ReadyToMakeSpaceInPoolBridge; + CallbackParam = reinterpret_cast(this); + } +} + +void TrackStreamer::ReadyToMakeSpaceInPool() { + MakeSpaceInPool(MakeSpaceInPoolSize, true); + + void (*callback)(int) = MakeSpaceInPoolCallback; + int param = MakeSpaceInPoolCallbackParam; + MakeSpaceInPoolCallback = 0; + MakeSpaceInPoolCallbackParam = 0; + MakeSpaceInPoolSize = 0; + callback(param); +} + +bool TrackStreamer::DetermineCurrentZones(short *current_zones) { + bool changed = false; + for (int position_number = 0; position_number < 2; position_number++) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + short current_zone = -1; + if (position_entry->PositionSet) { + current_zone = GetPredictedZone(position_entry); + } + + if (current_zone == position_entry->PredictedZone) { + position_entry->PredictedZoneValidTime += 1; + } else if (position_entry->PredictedZoneValidTime == -1) { + position_entry->PredictedZone = current_zone; + position_entry->PredictedZoneValidTime = 1000; + } else { + position_entry->PredictedZone = current_zone; + position_entry->PredictedZoneValidTime = 1; + } + + short section_number = position_entry->CurrentZone; + if (current_zone != section_number) { + if (position_entry->PredictedZoneValidTime < 0) { + current_zone = section_number; + } + if (current_zone != section_number) { + changed = true; + } + } + + current_zones[position_number] = current_zone; + } + + return changed || CurrentZoneNeedsRefreshing; +} + +void TrackStreamer::ServiceGameState() { + float start_time = GetDebugRealTime(); + HandleZoneSwitching(); + HandleSectionActivation(); + float time = GetDebugRealTime(); + (void)start_time; + (void)time; + + AmountNotRendered = 0; + for (int n = 0; n < NumCurrentStreamingSections; n++) { + TrackStreamingSection *section = CurrentStreamingSections[n]; + if (!section->WasRendered && IsRegularScenerySection(section->SectionNumber)) { + AmountNotRendered += section->Size; + } + section->WasRendered = 0; + } +} + +void TrackStreamer::ServiceNonGameState() { + ProfileNode profile_node("TODO", 0); + float start_time = GetDebugRealTime(); + HandleLoading(); + float time = GetDebugRealTime(); + (void)start_time; + (void)time; +} + +void TrackStreamer::BlockUntilLoadingComplete() { + RefreshLoading(); + WaitForCurrentLoadingToComplete(); +} + +void TrackStreamer::WaitForCurrentLoadingToComplete() { + while (!AreAllSectionsActivated()) { + HandleLoading(); + short section_to_activate = static_cast(GetSectionToActivate(0)); + if (section_to_activate != 0) { + ActivateSection(FindSection(section_to_activate)); + } + ServiceResourceLoading(); + bThreadYield(8); + } +} + +bool TrackStreamer::IsLoadingInProgress() { + bool loading_in_progress = !AreAllSectionsActivated(); + + if (!loading_in_progress && !AreAllSectionsActivated()) { + while (!AreAllSectionsActivated()) { + ServiceResourceLoading(); + ServiceNonGameState(); + } + } + + return loading_in_progress; +} + +bool TrackStreamer::AreAllSectionsActivated() { + bool all_sections_activated = false; + if (LoadingPhase == LOADING_IDLE) { + all_sections_activated = NumCurrentStreamingSections <= NumSectionsActivated + NumSectionsOutOfMemory; + } + return all_sections_activated; +} + +void TrackStreamer::RefreshLoading() { + CurrentZoneNeedsRefreshing = true; + for (int position_number = 0; position_number < 2; position_number++) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + position_entry->PredictedZoneValidTime = -1; + } + HandleZoneSwitching(); +} + +void TrackStreamer::HandleZoneSwitching() { + ProfileNode profile_node("TODO", 0); + short current_zones[2]; + bool current_zones_different; + if (!ZoneSwitchingDisabled && pMemoryPoolMem) { + current_zones_different = DetermineCurrentZones(current_zones); + if (current_zones_different) { + SwitchZones(current_zones); + } + } +} +void TrackStreamer::SwitchZones(short *current_zones) { + StartLoadingTime = GetDebugRealTime(); + CurrentZoneNeedsRefreshing = false; + + for (int position_number = 0; position_number < 2; position_number++) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + int zone_number = current_zones[position_number]; + if (position_entry->CurrentZone != zone_number) { + PlotLoadingMarker(position_entry); + + VisibleSectionBoundary *boundary1 = TheVisibleSectionManager.FindBoundary(position_entry->CurrentZone); + VisibleSectionBoundary *boundary2 = TheVisibleSectionManager.FindBoundary(zone_number); + float best_distance = kMaxDistance_TrackStreamer; + if (boundary1 && boundary2) { + for (int n = 0; n < boundary1->GetNumPoints(); n++) { + float distance = boundary2->GetDistanceOutside(boundary1->GetPoint(n), kMaxDistance_TrackStreamer); + best_distance = bMin(best_distance, distance); + } + } + + if (kSwitchZoneFarLoadThreshold_TrackStreamer < best_distance) { + CurrentZoneFarLoad = true; + } + + position_entry->CurrentZone = zone_number; + position_entry->BeginLoadingPosition = position_entry->Position; + position_entry->BeginLoadingTime = GetDebugRealTime(); + position_entry->NumSectionsToLoad = 0; + position_entry->NumSectionsLoaded = 0; + position_entry->AmountToLoad = 0; + position_entry->AmountLoaded = 0; + } + + if (position_number == 0) { + GetScenerySectionName(CurrentZoneName, zone_number); + } else if (zone_number > 0) { + bSPrintf(CurrentZoneName, "%s - %s", CurrentZoneName, GetScenerySectionName(zone_number)); + } + } + + int num_sections_unactivated = 0; + DetermineStreamingSections(); + PostLoadFixupDisabled = true; + for (int n = 0; n < NumTrackStreamingSections; n++) { + TrackStreamingSection *section = &pTrackStreamingSections[n]; + if (section->Status == TrackStreamingSection::ACTIVATED && !section->CurrentlyVisible) { + if (!IsTextureSection(section->SectionNumber) && !IsLibrarySection(section->SectionNumber)) { + UnactivateSection(section); + num_sections_unactivated += 1; + } + } + } + PostLoadFixupDisabled = false; + + if (num_sections_unactivated > 0) { + PostLoadFixup(); + SkipNextHandleLoad = true; + } + + FreeSectionMemory(); + SetLoadingPhase(ALLOCATING_TEXTURE_SECTIONS); + NumJettisonedSections = 0; + AmountJettisoned = 0; + CurrentZoneOutOfMemory = false; + CurrentZoneAllocatedButIncomplete = false; + MemorySafetyMargin = 0; + bMemSet(JettisonedSections, 0, sizeof(JettisonedSections)); + AssignLoadingPriority(); + CalculateLoadingBacklog(); +} + +void TrackStreamer::HandleLoading() { + if (SkipNextHandleLoad) { + SkipNextHandleLoad = false; + } else { + if (LoadingPhase != LOADING_IDLE) { + if ((LoadingPhase == LOADING_TEXTURE_SECTIONS || LoadingPhase == LOADING_GEOMETRY_SECTIONS || LoadingPhase == LOADING_REGULAR_SECTIONS) && + (StartLoadingSections(), NumSectionsLoading == 0)) { + if (CurrentZoneAllocatedButIncomplete) { + SetLoadingPhase(static_cast(LoadingPhase - 1)); + } else { + if (LoadingPhase == LOADING_REGULAR_SECTIONS) { + FinishedLoading(); + return; + } + SetLoadingPhase(static_cast(LoadingPhase + 1)); + } + } + + if ((LoadingPhase == ALLOCATING_TEXTURE_SECTIONS || LoadingPhase == ALLOCATING_GEOMETRY_SECTIONS || + LoadingPhase == ALLOCATING_REGULAR_SECTIONS) && + NumSectionsLoading < 1) { + int num_sections_unactivated = 0; + for (int n = 0; n < NumTrackStreamingSections; n++) { + TrackStreamingSection *section = &pTrackStreamingSections[n]; + if (section->Status == TrackStreamingSection::ACTIVATED && !section->CurrentlyVisible) { + if (LoadingPhase == ALLOCATING_GEOMETRY_SECTIONS && IsTextureSection(section->SectionNumber)) { + num_sections_unactivated += 1; + UnactivateSection(section); + } else if (LoadingPhase == ALLOCATING_REGULAR_SECTIONS && IsLibrarySection(section->SectionNumber)) { + num_sections_unactivated += 1; + UnactivateSection(section); + } + } + } + + if (num_sections_unactivated > 0) { + SkipNextHandleLoad = true; + } else { + PostLoadFixupDisabled = true; + int did_allocate = HandleMemoryAllocation(); + PostLoadFixupDisabled = false; + if (NumSectionsMoved > 0) { + PostLoadFixup(); + } + if (did_allocate != 0) { + SetLoadingPhase(static_cast(LoadingPhase + 1)); + if (LoadingPhase == LOADING_TEXTURE_SECTIONS || LoadingPhase == LOADING_GEOMETRY_SECTIONS) { + UnJettisonSections(); + } + HandleLoading(); + } + } + } + } + } +} + +int TrackStreamer::GetLoadingPriority(TrackStreamingSection *section, StreamingPositionEntry *position_entry, bool calculating_jettison) { + if (!section->pBoundary) { + return 0; + } + + float speed = bLength(position_entry->Velocity); + if (calculating_jettison) { + speed = 100.0f; + } + + if (speed < 1.0f) { + return 0; + } + + bVector2 predict_pos = position_entry->Position + position_entry->Velocity * 1.0f; + VisibleSectionBoundary *boundary = section->pBoundary; + float distance = boundary->GetDistanceOutside(&predict_pos, 999.0f); + + bVector2 direction; + if (calculating_jettison) { + bNormalize(&direction, &position_entry->Direction); + } else { + bNormalize(&direction, &position_entry->Velocity); + } + + bVector2 v = section->Centre - predict_pos; + v = bNormalize(v); + float dot = bDot(&direction, &v); + float speed_factor = bMin(speed * 0.016666668f, 1.0f); + float angle = bAngToDeg(bACos(dot)); + float angle_factor = bClamp(angle, 20.0f, 90.0f); + float adjusted_distance = distance * (1.0f - (90.0f - angle_factor) * 0.014285714f * speed_factor * 0.66999996f); + int priority = static_cast(adjusted_distance * 0.013333334f); + + return bClamp(priority, 0, 2); +} + +void TrackStreamer::AssignLoadingPriority() { + espEmptyLayer(0); + + { + int priority; + + { + char layer_name[32]; + + espEmptyLayer(layer_name); + } + } + + for (int n = 0; n < NumCurrentStreamingSections; n++) { + TrackStreamingSection *section = CurrentStreamingSections[n]; + int best_priority = 99; + for (int position_number = 0; position_number < 2; position_number++) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + if (((section->CurrentlyVisible >> position_number) & 1U) != 0) { + { + int priority = GetLoadingPriority(section, position_entry, false); + if (priority < best_priority) { + best_priority = priority; + } + } + } + } + + section->LoadingPriority = best_priority * 100000 + section->SectionPriority; + } +} + +void TrackStreamer::CalculateLoadingBacklog() { + float loading_backlog = 0.0f; + for (int i = 0; i < NumCurrentStreamingSections; i++) { + TrackStreamingSection *section = CurrentStreamingSections[i]; + if (section->CurrentlyVisible && section->Status != TrackStreamingSection::LOADED && section->Status != TrackStreamingSection::ACTIVATED) { + int rounded_size = section->Size; + if (rounded_size < 0) { + rounded_size += 0x3ff; + } + + float section_backlog = static_cast(rounded_size >> 10) * 0.0004f + 0.2f; + if (section->BaseLoadingPriority == 1) { + section_backlog *= 0.4f; + } + if (section->BaseLoadingPriority == 2) { + section_backlog *= 0.2f; + } + loading_backlog += section_backlog; + } + } + + LoadingBacklog = loading_backlog; +} + +void TrackStreamer::StartLoadingSections() { + bool something_to_load = true; + while (NumSectionsLoading < 2 && something_to_load) { + int best_priority = 0x7FFFFFFF; + TrackStreamingSection *best_section = 0; + for (int i = 0; i < NumCurrentStreamingSections; i++) { + TrackStreamingSection *section = CurrentStreamingSections[i]; + if (section->Status == TrackStreamingSection::ALLOCATED) { + int priority = section->LoadingPriority; + if (section->pDiscBundle) { + priority = -1; + } + if (priority < best_priority) { + best_priority = priority; + best_section = section; + } + } else if (section->Status == TrackStreamingSection::LOADED && !NeedsGameStateActivation(section)) { + TheTrackStreamer.ActivateSection(section); + } + } + + if (!best_section) { + something_to_load = false; + } else { + if (best_section->pDiscBundle) { + DiscBundleSection *disc_bundle = best_section->pDiscBundle; + LoadDiscBundle(disc_bundle); + } else { + LoadSection(best_section); + } + } + } +} + +void TrackStreamer::FinishedLoading() { + { + float load_time; + int position_number; + StreamingPositionEntry *position_entry; + (void)load_time; + (void)position_number; + (void)position_entry; + } + + LoadingPhase = LOADING_IDLE; + CurrentZoneNonReplayLoad = false; + CurrentZoneFarLoad = false; + NotifySkyLoader(); + + for (int position_number = 0; position_number < 2; position_number++) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + if (position_entry->BeginLoadingTime != 0.0f) { + PlotLoadingMarker(position_entry); + } + position_entry->BeginLoadingTime = 0.0f; + } + + if (pCallback) { + SetDelayedResourceCallback(pCallback, CallbackParam); + pCallback = 0; + CallbackParam = 0; + } +} + +void TrackStreamer::PlotLoadingMarker(StreamingPositionEntry *streaming_position) { + char stack[0x20]; + (void)stack; +} + +bool TrackStreamer::CheckLoadingBar() { + ProfileNode profile_node("TODO", 0); + float closest_distance = kMaxDistance_TrackStreamer; + TrackStreamingSection *closest_section; + StreamingPositionEntry *closest_position_entry; + float closest_approach_speed; + bool need_loading_bar; + + for (int position_number = 0; position_number < 2; position_number++) { + StreamingPositionEntry *position_entry = &StreamingPositionEntries[position_number]; + float speed; + float max_speed; + float prediction_scale_a = kPredictionScaleA_TrackStreamer; + float prediction_scale_b = kPredictionScaleB_TrackStreamer; + + if (!IsLoadingInProgress()) { + break; + } + + if (IsFarLoadingInProgress() || !position_entry->PositionSet || !position_entry->FollowingCar) { + break; + } + + speed = bLength(&position_entry->Velocity); + max_speed = MPH2MPS(kLoadingBarSpeedThreshold_TrackStreamer); + if (speed > max_speed) { + break; + } + + for (int n = 0; n < NumCurrentStreamingSections; n++) { + TrackStreamingSection *section = CurrentStreamingSections[n]; + VisibleSectionBoundary *boundary = section->pBoundary; + + if (boundary) { + bool may_contain_road = false; + if (IsRegularScenerySection(section->SectionNumber)) { + if (IsScenerySectionDrivable(section->SectionNumber) || IsLODScenerySectionNumber(section->SectionNumber)) { + may_contain_road = true; + } + } + + if (may_contain_road && section->Status != TrackStreamingSection::ACTIVATED) { + const float small_test_time = kFuturePositionScale_TrackStreamer; + bVector2 test_pos = position_entry->Position + position_entry->Velocity * small_test_time; + float distance1 = boundary->GetDistanceOutside(&position_entry->Position, kMaxDistance_TrackStreamer); + float distance2 = boundary->GetDistanceOutside(&test_pos, kMaxDistance_TrackStreamer); + float approach_speed = (distance1 - distance2) * prediction_scale_a * prediction_scale_b; + float distance = distance1 - approach_speed; + if (distance < closest_distance) { + closest_distance = distance; + closest_section = section; + closest_position_entry = position_entry; + closest_approach_speed = approach_speed; + } + } + } + } + } + + need_loading_bar = closest_distance < kLoadingBarDistanceThreshold_TrackStreamer; + prev_need_loading_bar_26275 = need_loading_bar; + return need_loading_bar; +} + +int TrackStreamer::GetSectionToActivate(int activation_delay) { + if (NumSectionsActivated < NumCurrentStreamingSections) { + for (int n = 0; n < NumCurrentStreamingSections; n++) { + TrackStreamingSection *section = CurrentStreamingSections[n]; + if (section->Status == TrackStreamingSection::LOADED && TheTrackStreamer.NeedsGameStateActivation(section) && + RealTimeFrames - section->LoadedTime >= activation_delay) { + return section->SectionNumber; + } + } + } + + return 0; +} + +void TrackStreamer::HandleSectionActivation() { + ProfileNode profile_node("TODO", 0); + int activation_delay; + short section_to_activate = static_cast(GetSectionToActivate(0)); + (void)activation_delay; + if (section_to_activate != 0) { + TrackStreamingSection *section = FindSection(section_to_activate); + if (section->Status != TrackStreamingSection::ACTIVATED) { + if (section->Status != TrackStreamingSection::LOADED) { + if (!section->CurrentlyVisible) { + return; + } + + do { + HandleLoading(); + ServiceResourceLoading(); + } while (section->Status != TrackStreamingSection::LOADED); + } + ActivateSection(section); + } + } +} + +void TrackStreamer::UnloadEverything() { + while (NumSectionsLoading != 0) { + ServiceResourceLoading(); + } + + for (int n = 0; n < NumTrackStreamingSections; n++) { + TrackStreamingSection *section = &pTrackStreamingSections[n]; + if (static_cast(section->Status - TrackStreamingSection::LOADED) < 2U) { + UnloadSection(section); + } + } + + FreeSectionMemory(); + ClearCurrentZones(); +} diff --git a/src/Speed/Indep/Src/World/TrackStreamer.hpp b/src/Speed/Indep/Src/World/TrackStreamer.hpp index 868d05048..c861d34b8 100644 --- a/src/Speed/Indep/Src/World/TrackStreamer.hpp +++ b/src/Speed/Indep/Src/World/TrackStreamer.hpp @@ -24,6 +24,14 @@ struct DiscBundleSectionMember { }; struct DiscBundleSection { + int GetMemoryImageSize() { + return NumMembers * sizeof(DiscBundleSectionMember) + 0x14; + } + + DiscBundleSection *GetMemoryImageNext() { + return reinterpret_cast(reinterpret_cast(this) + GetMemoryImageSize()); + } + // total size: 0x114 int FileOffset; // offset 0x0, size 0x4 int FileSize; // offset 0x4, size 0x4 @@ -98,12 +106,59 @@ class TSMemoryNode : public bTNode { int Size; // offset 0xC, size 0x4 bool Allocated; // offset 0x10, size 0x1 char DebugName[32]; // offset 0x14, size 0x20 + + bool IsAllocated(); + + bool IsFree(); + + bool Contains(int address); + + int GetAddress(bool start_from_top, int size) { + if (start_from_top) { + return Address; + } + return Address + Size - size; + } }; // total size: 0x2754 class TSMemoryPool { public: + TSMemoryPool(int address, int size, const char *debug_name, int pool_num); + void *Malloc(int size, const char *debug_name, bool best_fit, bool allocate_from_top, int address); + void Free(void *memory); + int GetAmountFree(); + int GetLargestFreeBlock(); + TSMemoryNode *GetNextNode(bool start_from_top, TSMemoryNode *node = 0); + TSMemoryNode *GetFirstNode(bool start_from_top) { + return GetNextNode(start_from_top, 0); + } + TSMemoryNode *GetNextFreeNode(bool start_from_top, TSMemoryNode *node = 0); + TSMemoryNode *GetFirstFreeNode(bool start_from_top) { + return GetNextFreeNode(start_from_top, 0); + } + TSMemoryNode *GetNextAllocatedNode(bool start_from_top, TSMemoryNode *node = 0); + TSMemoryNode *GetFirstAllocatedNode(bool start_from_top); + bool IsUpdated() { + bool updated = Updated; + Updated = false; + return updated; + } + unsigned int GetPoolChecksum(); + void EnableTracing(bool enabled) { + TracingEnabled = enabled; + } + void DebugPrint(); + private: + static void *OverrideMalloc(void *pool, int size, const char *debug_text, int debug_line, int allocation_params); + static void OverrideFree(void *pool, void *ptr); + static int OverrideGetAmountFree(void *pool); + static int OverrideGetLargestFreeBlock(void *pool); + + TSMemoryNode *GetNewNode(int address, int size, bool allocated, const char *debug_name); + void RemoveNode(TSMemoryNode *node); + int PoolNum; // offset 0x0, size 0x4 const char *DebugName; // offset 0x4, size 0x4 int TotalSize; // offset 0x8, size 0x4 @@ -127,14 +182,23 @@ struct TrackStreamingInfo { struct TrackStreamingBarrier { // void EndianSwap() {} - // bool Intersects(const bVector2 *pointa, const bVector2 *pointb) {} + bool Intersects(const bVector2 *pointa, const bVector2 *pointb); bVector2 Points[2]; // offset 0x0, size 0x10 }; +struct HoleMovement { + // total size: 0x10 + int Address; // offset 0x0, size 0x4 + int NewAddress; // offset 0x4, size 0x4 + int Size; // offset 0x8, size 0x4 + unsigned int Checksum; // offset 0xC, size 0x4 +}; + // total size: 0x888 class TrackStreamer { public: + TrackStreamer(); enum eLoadingPhase { LOADING_IDLE = 0, ALLOCATING_TEXTURE_SECTIONS = 1, @@ -150,8 +214,6 @@ class TrackStreamer { int Unloader(bChunk *chunk); - void ClearCurrentZones(); - void InitMemoryPool(int size); void CloseMemoryPool(); @@ -168,8 +230,6 @@ class TrackStreamer { TrackStreamingSection *FindSectionByAddress(int address); - int GetCombinedSectionNumber(int section_number); - void InitRegion(const char *region_stream_filename, bool split_screen); void StartPermFileLoading(const char *filename); @@ -182,14 +242,6 @@ class TrackStreamer { void *AllocateMemory(TrackStreamingSection *section, int allocation_params); - void LoadDiscBundle(DiscBundleSection *disc_bundle); - - static void DiscBundleLoadedCallback(int param, int error_status); - - void DiscBundleLoadedCallback(DiscBundleSection *disc_bundle); - - void LoadSection(TrackStreamingSection *section); - void ActivateSection(TrackStreamingSection *section); void UnactivateSection(TrackStreamingSection *section); @@ -200,10 +252,6 @@ class TrackStreamer { bool NeedsGameStateActivation(TrackStreamingSection *section); - static void SectionLoadedCallback(int param, int error_status); - - void SectionLoadedCallback(TrackStreamingSection *section); - void EmptyCaffeineLayers(); static char *GetLoadingPhaseName(enum eLoadingPhase phase); @@ -222,7 +270,7 @@ class TrackStreamer { void UnJettisonSections(); - int BuildHoleMovements(struct HoleMovement *hole_movements, int max_movements, int filler_method, int largest_free, int *pamount_moved, + int BuildHoleMovements(HoleMovement *hole_movements, int max_movements, int filler_method, int largest_free, int *pamount_moved, int max_amount_to_move); int DoHoleFilling(int largest_free); @@ -262,8 +310,6 @@ class TrackStreamer { void ReadyToMakeSpaceInPool(); - bool DetermineCurrentZones(short *current_zones); - void ServiceGameState(); void PrintMemoryPool(); @@ -282,8 +328,6 @@ class TrackStreamer { void SetLoadingCallback(void (*callback)(int), int param); - void HandleZoneSwitching(); - void SwitchZones(short *current_zones); void HandleLoading(); @@ -310,6 +354,10 @@ class TrackStreamer { void ForceSectionToUnload(int section_number); + bool IsFarLoadingInProgress() { + return CurrentZoneFarLoad && IsLoadingInProgress(); + } + void DisableZoneSwitching() { ZoneSwitchingDisabled = true; } @@ -323,6 +371,18 @@ class TrackStreamer { } private: + void ClearCurrentZones(); + bool DetermineCurrentZones(short *current_zones); + void HandleZoneSwitching(); + int GetCombinedSectionNumber(int section_number); + static void DiscBundleLoadedCallback(int param, int error_status); + static void ReadyToMakeSpaceInPoolBridge(int param); + void DiscBundleLoadedCallback(DiscBundleSection *disc_bundle); + void LoadDiscBundle(DiscBundleSection *disc_bundle); + void LoadSection(TrackStreamingSection *section); + static void SectionLoadedCallback(int param, int error_status); + void SectionLoadedCallback(TrackStreamingSection *section); + TrackStreamingSection *pTrackStreamingSections; // offset 0x0, size 0x4 int NumTrackStreamingSections; // offset 0x4, size 0x4 DiscBundleSection *pDiscBundleSections; // offset 0x8, size 0x4 diff --git a/src/Speed/Indep/Src/World/VisibleSection.cpp b/src/Speed/Indep/Src/World/VisibleSection.cpp index 935d9a975..8923f63de 100644 --- a/src/Speed/Indep/Src/World/VisibleSection.cpp +++ b/src/Speed/Indep/Src/World/VisibleSection.cpp @@ -1,3 +1,923 @@ #include "VisibleSection.hpp" +#include "Scenery.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +void RefreshTrackStreamer(); +BOOL bBoundingBoxIsInside(const bVector2 *bbox_min, const bVector2 *bbox_max, const bVector2 *point, float extra_width); +float bDistToLine(const bVector2 *point, const bVector2 *pointa, const bVector2 *pointb); + +struct SectionRemapper { + short SectionNumber; + short SectionNumber2P; +}; + +extern SectionRemapper SectionRemapperTable_Gamecube[129]; +extern SectionRemapper SectionRemapperTable[134]; +extern VisibleSectionManager TheVisibleSectionManager; + +static bool initialized_VisibleSection = false; +static int map_table_VisibleSection[2800]; +static int counter_VisibleSection = 0; +static char text_VisibleSection[4][16]; + +int Get2PlayerSectionNumber(int section_number, const char *build_platform) { + if (bStrICmp(build_platform, "PC") != 0) { + char section_letter = GetScenerySectionLetter(section_number); + if (section_letter == 'Y') { + return static_cast(section_number % 100 + 0x8FC); + } + + if (section_letter == 'X') { + return static_cast(section_number % 100 + 0x834); + } + + SectionRemapper *remap_table = SectionRemapperTable; + int table_size = 134; + if (bStrICmp(build_platform, "GAMECUBE") == 0) { + table_size = 129; + remap_table = SectionRemapperTable_Gamecube; + } + + for (int n = 0; n < table_size; n++) { + if (remap_table[n].SectionNumber == section_number) { + return remap_table[n].SectionNumber2P; + } + } + } + + return section_number; +} + +int Get2PlayerSectionNumber(int section_number) { + return Get2PlayerSectionNumber(section_number, bGetPlatformName()); +} + +int Get1PlayerSectionNumber(int section_number_2p, const char *build_platform) { + if (!initialized_VisibleSection) { + initialized_VisibleSection = true; + bMemSet(map_table_VisibleSection, 0, sizeof(map_table_VisibleSection)); + for (int sec_1p = 0; sec_1p < 2800; sec_1p++) { + int sec_2p = Get2PlayerSectionNumber(sec_1p, build_platform); + if (sec_2p != sec_1p) { + map_table_VisibleSection[sec_2p] = sec_1p; + } + } + } + + if (map_table_VisibleSection[section_number_2p] != 0) { + return map_table_VisibleSection[section_number_2p]; + } + return section_number_2p; +} + +int GetBoundarySectionNumber(int section_number, const char *platform_name) { + int boundary_section_number = Get1PlayerSectionNumber(section_number, platform_name); + int subsection_number = boundary_section_number % 100; + int is_boundary_section = 0; + + if (subsection_number >= ScenerySectionLODOffset) { + is_boundary_section = subsection_number < ScenerySectionLODOffset * 2; + } + + if (is_boundary_section) { + boundary_section_number -= ScenerySectionLODOffset; + } + + return boundary_section_number; +} + +int LoaderVisibleSections(bChunk *chunk) { + return TheVisibleSectionManager.Loader(chunk); +} + +int UnloaderVisibleSections(bChunk *chunk) { + return TheVisibleSectionManager.Unloader(chunk); +} + +char *GetScenerySectionName(char *name, int section_number) { + if (section_number < 1) { + name[0] = '-'; + name[1] = '-'; + name[2] = '\0'; + } else { + bSPrintf(name, "%c%d", GetScenerySectionLetter(section_number), section_number % 100); + } + + return name; +} + +char *GetScenerySectionName(int section_number) { + unsigned int index = static_cast(counter_VisibleSection) & 3; + counter_VisibleSection += 1; + return GetScenerySectionName(text_VisibleSection[index], section_number); +} + +static bool MyIsPointInPoly(const bVector2 *point, const bVector2 *points, int num_points) { + float x = point->x; + float y = point->y; + bool inside = false; + int j = num_points - 1; + + for (int i = 0; i < num_points; i++) { + float point_y = points[i].y; + if (((point_y <= y && y < points[j].y) || (points[j].y <= y && y < point_y)) && + x < ((points[j].x - points[i].x) * (y - point_y)) / (points[j].y - point_y) + points[i].x) { + inside = !inside; + } + + j = i; + } + + return inside; +} + +static inline bool PointInBBox(const bVector2 *point, const bVector2 *bbox_min, const bVector2 *bbox_max) { + return point->x >= bbox_min->x && point->x <= bbox_max->x && point->y >= bbox_min->y && point->y <= bbox_max->y; +} + +bool VisibleSectionBoundary::IsPointInside(const bVector2 *point) { + if (!bBoundingBoxIsInside(&BBoxMin, &BBoxMax, point, 0.0f)) { + return false; + } + + return MyIsPointInPoly(point, Points, NumPoints); +} + +float VisibleSectionBoundary::GetDistanceOutside(const bVector2 *point, float max_distance) { + if (!bBoundingBoxIsInside(&BBoxMin, &BBoxMax, point, max_distance)) { + return max_distance; + } + + if (IsPointInside(point)) { + return 0.0f; + } + + float closest_distance = max_distance; + { + int point_number = 0; + while (point_number < NumPoints) { + int next = point_number + 1; + bVector2 *point1 = GetPoint(point_number); + bVector2 *point2 = GetPoint(next - (next / NumPoints) * NumPoints); + float distance = bDistToLine(point, point1, point2); + if (distance < closest_distance) { + closest_distance = distance; + } + point_number = next; + } + } + + return closest_distance; +} + +void DrivableScenerySection::AddVisibleSection(int section_number) { + if (NumVisibleSections < MaxVisibleSections && !IsSectionVisible(section_number)) { + short num_visible_sections = NumVisibleSections; + NumVisibleSections = num_visible_sections + 1; + VisibleSections[num_visible_sections] = static_cast(section_number); + if (MostVisibleSections < NumVisibleSections) { + MostVisibleSections = NumVisibleSections; + } + } +} + +int DrivableScenerySection::IsSectionVisible(int section_number) { + for (int i = 0; i < NumVisibleSections; i++) { + if (VisibleSections[i] == section_number) { + return 1; + } + } + return 0; +} + +void DrivableScenerySection::RemoveVisibleSection(int section_number) { + { + int n = 0; + if (n >= NumVisibleSections) { + return; + } + + do { + if (VisibleSections[n] == section_number) { + { + int i = n; + + if (i < NumVisibleSections - 1) { + do { + VisibleSections[i] = VisibleSections[i + 1]; + i++; + } while (i < NumVisibleSections - 1); + } + } + + NumVisibleSections--; + VisibleSections[NumVisibleSections] = 0; + return; + } + + n++; + } while (n < NumVisibleSections); + } +} + +void DrivableScenerySection::SortVisibleSections() { + bool swap; + do { + swap = false; + if (NumVisibleSections - 1 > 0) { + for (int i = 0; i < NumVisibleSections - 1; i++) { + short a = VisibleSections[i]; + short b = VisibleSections[i + 1]; + if (b < a) { + VisibleSections[i + 1] = a; + swap = true; + VisibleSections[i] = b; + } + } + } + } while (swap); +} + +LoadingSection *VisibleSectionManager::FindLoadingSection(int section_number) { + for (LoadingSection *loading_section = LoadingSectionList.GetHead(); loading_section != LoadingSectionList.EndOfList(); + loading_section = loading_section->GetNext()) { + short target_section_number = static_cast(section_number); + short *drivable_sections = loading_section->DrivableSections; + int num_drivable_sections = loading_section->NumDrivableSections; + + for (int i = 0; i < num_drivable_sections; i++) { + if (drivable_sections[i] == target_section_number) { + return loading_section; + } + } + } + + return 0; +} + +int VisibleSectionManager::GetSectionsToLoad(LoadingSection *loading_section, short *section_numbers, int max_sections) { + if (!loading_section) { + return 0; + } + + int num_sections = 0; + for (int n = 0; n < loading_section->NumDrivableSections; n++) { + DrivableScenerySection *drivable_section = FindDrivableSection(loading_section->DrivableSections[n]); + if (!drivable_section) { + continue; + } + + for (int i = 0; i < drivable_section->GetNumVisibleSections(); i++) { + int section_number = drivable_section->GetVisibleSection(i); + if (!HasSection(section_numbers, num_sections, static_cast(section_number)) && num_sections < max_sections) { + section_numbers[num_sections] = section_number; + num_sections += 1; + } + } + } + + for (int i = 0; i < loading_section->NumExtraSections; i++) { + int section_number = loading_section->ExtraSections[i]; + if (!HasSection(section_numbers, num_sections, static_cast(section_number)) && num_sections < max_sections) { + section_numbers[num_sections] = section_number; + num_sections += 1; + } + + if (IsScenerySectionDrivable(section_number)) { + section_number = GetLODScenerySectionNumber(section_number); + if (!HasSection(section_numbers, num_sections, static_cast(section_number)) && num_sections < max_sections) { + section_numbers[num_sections] = section_number; + num_sections += 1; + } + } + } + + return num_sections; +} + +VisibleGroupInfo VisibleGroupInfoTable[5] = { + {"BARRIER_", 1}, + {"BARRIERS_", 1}, + {"PLAYER_BARRIERS_", 1}, + {"SCENERY_GROUP_", 1}, + {"FREE_ROAM", 0}, +}; +SectionRemapper SectionRemapperTable_Gamecube[129] = { + {GetScenerySectionNumber('D', 7), GetScenerySectionNumber('K', 1)}, + {GetScenerySectionNumber('D', 47), GetScenerySectionNumber('K', 41)}, + {GetScenerySectionNumber('D', 21), GetScenerySectionNumber('K', 2)}, + {GetScenerySectionNumber('D', 61), GetScenerySectionNumber('K', 42)}, + {GetScenerySectionNumber('D', 29), GetScenerySectionNumber('K', 3)}, + {GetScenerySectionNumber('D', 69), GetScenerySectionNumber('K', 43)}, + {GetScenerySectionNumber('D', 35), GetScenerySectionNumber('K', 4)}, + {GetScenerySectionNumber('D', 75), GetScenerySectionNumber('K', 44)}, + {GetScenerySectionNumber('D', 38), GetScenerySectionNumber('K', 5)}, + {GetScenerySectionNumber('D', 78), GetScenerySectionNumber('K', 45)}, + {GetScenerySectionNumber('F', 4), GetScenerySectionNumber('K', 6)}, + {GetScenerySectionNumber('F', 44), GetScenerySectionNumber('K', 46)}, + {GetScenerySectionNumber('F', 11), GetScenerySectionNumber('K', 7)}, + {GetScenerySectionNumber('F', 51), GetScenerySectionNumber('K', 47)}, + {GetScenerySectionNumber('F', 13), GetScenerySectionNumber('K', 8)}, + {GetScenerySectionNumber('F', 53), GetScenerySectionNumber('K', 48)}, + {GetScenerySectionNumber('G', 2), GetScenerySectionNumber('K', 9)}, + {GetScenerySectionNumber('G', 42), GetScenerySectionNumber('K', 49)}, + {GetScenerySectionNumber('G', 3), GetScenerySectionNumber('K', 10)}, + {GetScenerySectionNumber('G', 43), GetScenerySectionNumber('K', 50)}, + {GetScenerySectionNumber('G', 4), GetScenerySectionNumber('K', 11)}, + {GetScenerySectionNumber('G', 44), GetScenerySectionNumber('K', 51)}, + {GetScenerySectionNumber('G', 8), GetScenerySectionNumber('K', 12)}, + {GetScenerySectionNumber('G', 48), GetScenerySectionNumber('K', 52)}, + {GetScenerySectionNumber('G', 9), GetScenerySectionNumber('K', 13)}, + {GetScenerySectionNumber('G', 49), GetScenerySectionNumber('K', 53)}, + {GetScenerySectionNumber('G', 10), GetScenerySectionNumber('K', 14)}, + {GetScenerySectionNumber('G', 50), GetScenerySectionNumber('K', 54)}, + {GetScenerySectionNumber('G', 12), GetScenerySectionNumber('K', 15)}, + {GetScenerySectionNumber('G', 52), GetScenerySectionNumber('K', 55)}, + {GetScenerySectionNumber('G', 14), GetScenerySectionNumber('K', 16)}, + {GetScenerySectionNumber('G', 54), GetScenerySectionNumber('K', 56)}, + {GetScenerySectionNumber('G', 15), GetScenerySectionNumber('K', 17)}, + {GetScenerySectionNumber('G', 55), GetScenerySectionNumber('K', 57)}, + {GetScenerySectionNumber('G', 20), GetScenerySectionNumber('K', 18)}, + {GetScenerySectionNumber('G', 60), GetScenerySectionNumber('K', 58)}, + {GetScenerySectionNumber('G', 22), GetScenerySectionNumber('K', 19)}, + {GetScenerySectionNumber('G', 62), GetScenerySectionNumber('K', 59)}, + {GetScenerySectionNumber('G', 29), GetScenerySectionNumber('K', 20)}, + {GetScenerySectionNumber('G', 69), GetScenerySectionNumber('K', 60)}, + {GetScenerySectionNumber('G', 30), GetScenerySectionNumber('K', 21)}, + {GetScenerySectionNumber('G', 70), GetScenerySectionNumber('K', 61)}, + {GetScenerySectionNumber('H', 2), GetScenerySectionNumber('K', 22)}, + {GetScenerySectionNumber('H', 42), GetScenerySectionNumber('K', 62)}, + {GetScenerySectionNumber('H', 3), GetScenerySectionNumber('K', 23)}, + {GetScenerySectionNumber('H', 43), GetScenerySectionNumber('K', 63)}, + {GetScenerySectionNumber('H', 4), GetScenerySectionNumber('K', 24)}, + {GetScenerySectionNumber('H', 44), GetScenerySectionNumber('K', 64)}, + {GetScenerySectionNumber('H', 10), GetScenerySectionNumber('K', 25)}, + {GetScenerySectionNumber('H', 50), GetScenerySectionNumber('K', 65)}, + {GetScenerySectionNumber('H', 11), GetScenerySectionNumber('K', 26)}, + {GetScenerySectionNumber('H', 51), GetScenerySectionNumber('K', 66)}, + {GetScenerySectionNumber('H', 13), GetScenerySectionNumber('K', 27)}, + {GetScenerySectionNumber('H', 53), GetScenerySectionNumber('K', 67)}, + {GetScenerySectionNumber('H', 15), GetScenerySectionNumber('K', 28)}, + {GetScenerySectionNumber('H', 55), GetScenerySectionNumber('K', 68)}, + {GetScenerySectionNumber('H', 18), GetScenerySectionNumber('K', 29)}, + {GetScenerySectionNumber('H', 58), GetScenerySectionNumber('K', 69)}, + {GetScenerySectionNumber('I', 7), GetScenerySectionNumber('K', 30)}, + {GetScenerySectionNumber('I', 47), GetScenerySectionNumber('K', 70)}, + {GetScenerySectionNumber('I', 13), GetScenerySectionNumber('K', 31)}, + {GetScenerySectionNumber('I', 53), GetScenerySectionNumber('K', 71)}, + {GetScenerySectionNumber('I', 19), GetScenerySectionNumber('K', 32)}, + {GetScenerySectionNumber('I', 59), GetScenerySectionNumber('K', 72)}, + {GetScenerySectionNumber('I', 20), GetScenerySectionNumber('K', 33)}, + {GetScenerySectionNumber('I', 60), GetScenerySectionNumber('K', 73)}, + {GetScenerySectionNumber('I', 22), GetScenerySectionNumber('K', 34)}, + {GetScenerySectionNumber('I', 62), GetScenerySectionNumber('K', 74)}, + {GetScenerySectionNumber('I', 31), GetScenerySectionNumber('K', 35)}, + {GetScenerySectionNumber('I', 71), GetScenerySectionNumber('K', 75)}, + {GetScenerySectionNumber('I', 32), GetScenerySectionNumber('K', 36)}, + {GetScenerySectionNumber('I', 72), GetScenerySectionNumber('K', 76)}, + {GetScenerySectionNumber('J', 9), GetScenerySectionNumber('K', 37)}, + {GetScenerySectionNumber('J', 49), GetScenerySectionNumber('K', 77)}, + {GetScenerySectionNumber('J', 10), GetScenerySectionNumber('K', 38)}, + {GetScenerySectionNumber('J', 50), GetScenerySectionNumber('K', 78)}, + {GetScenerySectionNumber('O', 9), GetScenerySectionNumber('K', 39)}, + {GetScenerySectionNumber('O', 49), GetScenerySectionNumber('K', 79)}, + {GetScenerySectionNumber('P', 4), GetScenerySectionNumber('N', 1)}, + {GetScenerySectionNumber('P', 44), GetScenerySectionNumber('N', 41)}, + {GetScenerySectionNumber('P', 5), GetScenerySectionNumber('N', 2)}, + {GetScenerySectionNumber('P', 45), GetScenerySectionNumber('N', 42)}, + {GetScenerySectionNumber('P', 7), GetScenerySectionNumber('N', 3)}, + {GetScenerySectionNumber('P', 47), GetScenerySectionNumber('N', 43)}, + {GetScenerySectionNumber('P', 10), GetScenerySectionNumber('N', 4)}, + {GetScenerySectionNumber('P', 50), GetScenerySectionNumber('N', 44)}, + {GetScenerySectionNumber('P', 17), GetScenerySectionNumber('N', 5)}, + {GetScenerySectionNumber('P', 57), GetScenerySectionNumber('N', 45)}, + {GetScenerySectionNumber('P', 22), GetScenerySectionNumber('N', 6)}, + {GetScenerySectionNumber('P', 62), GetScenerySectionNumber('N', 46)}, + {GetScenerySectionNumber('P', 33), GetScenerySectionNumber('N', 7)}, + {GetScenerySectionNumber('P', 73), GetScenerySectionNumber('N', 47)}, + {GetScenerySectionNumber('Q', 3), GetScenerySectionNumber('N', 8)}, + {GetScenerySectionNumber('Q', 43), GetScenerySectionNumber('N', 48)}, + {GetScenerySectionNumber('Q', 5), GetScenerySectionNumber('N', 9)}, + {GetScenerySectionNumber('Q', 45), GetScenerySectionNumber('N', 49)}, + {GetScenerySectionNumber('Q', 10), GetScenerySectionNumber('N', 10)}, + {GetScenerySectionNumber('Q', 50), GetScenerySectionNumber('N', 50)}, + {GetScenerySectionNumber('Q', 12), GetScenerySectionNumber('N', 11)}, + {GetScenerySectionNumber('Q', 52), GetScenerySectionNumber('N', 51)}, + {GetScenerySectionNumber('Q', 16), GetScenerySectionNumber('N', 12)}, + {GetScenerySectionNumber('Q', 56), GetScenerySectionNumber('N', 52)}, + {GetScenerySectionNumber('Q', 23), GetScenerySectionNumber('N', 13)}, + {GetScenerySectionNumber('Q', 63), GetScenerySectionNumber('N', 53)}, + {GetScenerySectionNumber('R', 1), GetScenerySectionNumber('N', 14)}, + {GetScenerySectionNumber('R', 41), GetScenerySectionNumber('N', 54)}, + {GetScenerySectionNumber('R', 3), GetScenerySectionNumber('N', 15)}, + {GetScenerySectionNumber('R', 43), GetScenerySectionNumber('N', 55)}, + {GetScenerySectionNumber('R', 4), GetScenerySectionNumber('N', 16)}, + {GetScenerySectionNumber('R', 44), GetScenerySectionNumber('N', 56)}, + {GetScenerySectionNumber('R', 7), GetScenerySectionNumber('N', 17)}, + {GetScenerySectionNumber('R', 47), GetScenerySectionNumber('N', 57)}, + {GetScenerySectionNumber('R', 16), GetScenerySectionNumber('N', 18)}, + {GetScenerySectionNumber('R', 56), GetScenerySectionNumber('N', 58)}, + {GetScenerySectionNumber('R', 18), GetScenerySectionNumber('N', 19)}, + {GetScenerySectionNumber('R', 58), GetScenerySectionNumber('N', 59)}, + {GetScenerySectionNumber('R', 26), GetScenerySectionNumber('N', 20)}, + {GetScenerySectionNumber('R', 66), GetScenerySectionNumber('N', 60)}, + {GetScenerySectionNumber('R', 33), GetScenerySectionNumber('N', 21)}, + {GetScenerySectionNumber('R', 73), GetScenerySectionNumber('N', 61)}, + {GetScenerySectionNumber('V', 1), GetScenerySectionNumber('V', 99)}, + {GetScenerySectionNumber('V', 4), GetScenerySectionNumber('V', 98)}, + {GetScenerySectionNumber('V', 6), GetScenerySectionNumber('V', 97)}, + {GetScenerySectionNumber('V', 14), GetScenerySectionNumber('V', 96)}, + {GetScenerySectionNumber('V', 19), GetScenerySectionNumber('V', 95)}, + {GetScenerySectionNumber('V', 22), GetScenerySectionNumber('V', 94)}, + {GetScenerySectionNumber('V', 59), GetScenerySectionNumber('V', 93)}, + {GetScenerySectionNumber('V', 71), GetScenerySectionNumber('V', 92)}, + {GetScenerySectionNumber('V', 70), GetScenerySectionNumber('V', 91)}, +}; +SectionRemapper SectionRemapperTable[134] = { + {GetScenerySectionNumber('Q', 3), GetScenerySectionNumber('Q', 39)}, + {GetScenerySectionNumber('Q', 43), GetScenerySectionNumber('Q', 79)}, + {GetScenerySectionNumber('G', 8), GetScenerySectionNumber('G', 39)}, + {GetScenerySectionNumber('G', 48), GetScenerySectionNumber('G', 79)}, + {GetScenerySectionNumber('F', 11), GetScenerySectionNumber('F', 39)}, + {GetScenerySectionNumber('F', 51), GetScenerySectionNumber('F', 79)}, + {GetScenerySectionNumber('H', 10), GetScenerySectionNumber('H', 39)}, + {GetScenerySectionNumber('H', 50), GetScenerySectionNumber('H', 79)}, + {GetScenerySectionNumber('Q', 12), GetScenerySectionNumber('Q', 38)}, + {GetScenerySectionNumber('Q', 52), GetScenerySectionNumber('Q', 78)}, + {GetScenerySectionNumber('G', 12), GetScenerySectionNumber('G', 38)}, + {GetScenerySectionNumber('G', 52), GetScenerySectionNumber('G', 78)}, + {GetScenerySectionNumber('H', 4), GetScenerySectionNumber('H', 38)}, + {GetScenerySectionNumber('H', 44), GetScenerySectionNumber('H', 78)}, + {GetScenerySectionNumber('G', 10), GetScenerySectionNumber('G', 37)}, + {GetScenerySectionNumber('G', 50), GetScenerySectionNumber('G', 77)}, + {GetScenerySectionNumber('G', 20), GetScenerySectionNumber('G', 36)}, + {GetScenerySectionNumber('G', 60), GetScenerySectionNumber('G', 76)}, + {GetScenerySectionNumber('P', 17), GetScenerySectionNumber('Q', 37)}, + {GetScenerySectionNumber('P', 57), GetScenerySectionNumber('Q', 77)}, + {GetScenerySectionNumber('P', 4), GetScenerySectionNumber('Q', 33)}, + {GetScenerySectionNumber('P', 44), GetScenerySectionNumber('Q', 73)}, + {GetScenerySectionNumber('P', 5), GetScenerySectionNumber('Q', 34)}, + {GetScenerySectionNumber('P', 45), GetScenerySectionNumber('Q', 74)}, + {GetScenerySectionNumber('P', 7), GetScenerySectionNumber('Q', 35)}, + {GetScenerySectionNumber('P', 47), GetScenerySectionNumber('Q', 75)}, + {GetScenerySectionNumber('P', 10), GetScenerySectionNumber('Q', 36)}, + {GetScenerySectionNumber('P', 50), GetScenerySectionNumber('Q', 76)}, + {GetScenerySectionNumber('D', 35), GetScenerySectionNumber('D', 39)}, + {GetScenerySectionNumber('D', 75), GetScenerySectionNumber('D', 79)}, + {GetScenerySectionNumber('D', 16), GetScenerySectionNumber('C', 38)}, + {GetScenerySectionNumber('D', 56), GetScenerySectionNumber('C', 78)}, + {GetScenerySectionNumber('D', 21), GetScenerySectionNumber('C', 37)}, + {GetScenerySectionNumber('D', 61), GetScenerySectionNumber('C', 77)}, + {GetScenerySectionNumber('T', 6), GetScenerySectionNumber('T', 34)}, + {GetScenerySectionNumber('T', 46), GetScenerySectionNumber('T', 74)}, + {GetScenerySectionNumber('T', 8), GetScenerySectionNumber('T', 35)}, + {GetScenerySectionNumber('T', 48), GetScenerySectionNumber('T', 75)}, + {GetScenerySectionNumber('T', 9), GetScenerySectionNumber('T', 36)}, + {GetScenerySectionNumber('T', 49), GetScenerySectionNumber('T', 76)}, + {GetScenerySectionNumber('T', 13), GetScenerySectionNumber('T', 37)}, + {GetScenerySectionNumber('T', 53), GetScenerySectionNumber('T', 77)}, + {GetScenerySectionNumber('T', 17), GetScenerySectionNumber('T', 38)}, + {GetScenerySectionNumber('T', 57), GetScenerySectionNumber('T', 78)}, + {GetScenerySectionNumber('T', 22), GetScenerySectionNumber('T', 39)}, + {GetScenerySectionNumber('T', 62), GetScenerySectionNumber('T', 79)}, + {GetScenerySectionNumber('R', 16), GetScenerySectionNumber('S', 36)}, + {GetScenerySectionNumber('R', 56), GetScenerySectionNumber('S', 76)}, + {GetScenerySectionNumber('R', 26), GetScenerySectionNumber('S', 38)}, + {GetScenerySectionNumber('R', 66), GetScenerySectionNumber('S', 78)}, + {GetScenerySectionNumber('R', 17), GetScenerySectionNumber('S', 37)}, + {GetScenerySectionNumber('R', 57), GetScenerySectionNumber('S', 77)}, + {GetScenerySectionNumber('R', 18), GetScenerySectionNumber('S', 39)}, + {GetScenerySectionNumber('R', 58), GetScenerySectionNumber('S', 79)}, + {GetScenerySectionNumber('R', 3), GetScenerySectionNumber('S', 35)}, + {GetScenerySectionNumber('R', 43), GetScenerySectionNumber('S', 75)}, + {GetScenerySectionNumber('R', 7), GetScenerySectionNumber('S', 34)}, + {GetScenerySectionNumber('R', 47), GetScenerySectionNumber('S', 74)}, + {GetScenerySectionNumber('R', 33), GetScenerySectionNumber('S', 33)}, + {GetScenerySectionNumber('R', 73), GetScenerySectionNumber('S', 73)}, + {GetScenerySectionNumber('R', 1), GetScenerySectionNumber('S', 28)}, + {GetScenerySectionNumber('R', 41), GetScenerySectionNumber('S', 68)}, + {GetScenerySectionNumber('R', 2), GetScenerySectionNumber('S', 27)}, + {GetScenerySectionNumber('R', 42), GetScenerySectionNumber('S', 67)}, + {GetScenerySectionNumber('R', 14), GetScenerySectionNumber('S', 26)}, + {GetScenerySectionNumber('R', 54), GetScenerySectionNumber('S', 66)}, + {GetScenerySectionNumber('R', 20), GetScenerySectionNumber('S', 25)}, + {GetScenerySectionNumber('R', 60), GetScenerySectionNumber('S', 65)}, + {GetScenerySectionNumber('R', 21), GetScenerySectionNumber('S', 24)}, + {GetScenerySectionNumber('R', 61), GetScenerySectionNumber('S', 64)}, + {GetScenerySectionNumber('R', 27), GetScenerySectionNumber('S', 23)}, + {GetScenerySectionNumber('R', 67), GetScenerySectionNumber('S', 63)}, + {GetScenerySectionNumber('R', 28), GetScenerySectionNumber('L', 36)}, + {GetScenerySectionNumber('R', 68), GetScenerySectionNumber('L', 76)}, + {GetScenerySectionNumber('O', 35), GetScenerySectionNumber('S', 32)}, + {GetScenerySectionNumber('O', 75), GetScenerySectionNumber('S', 72)}, + {GetScenerySectionNumber('O', 9), GetScenerySectionNumber('S', 31)}, + {GetScenerySectionNumber('O', 49), GetScenerySectionNumber('S', 71)}, + {GetScenerySectionNumber('O', 26), GetScenerySectionNumber('S', 30)}, + {GetScenerySectionNumber('O', 66), GetScenerySectionNumber('S', 70)}, + {GetScenerySectionNumber('O', 32), GetScenerySectionNumber('S', 29)}, + {GetScenerySectionNumber('O', 72), GetScenerySectionNumber('S', 69)}, + {GetScenerySectionNumber('O', 35), GetScenerySectionNumber('L', 37)}, + {GetScenerySectionNumber('O', 75), GetScenerySectionNumber('L', 77)}, + {GetScenerySectionNumber('I', 2), GetScenerySectionNumber('I', 39)}, + {GetScenerySectionNumber('I', 42), GetScenerySectionNumber('I', 79)}, + {GetScenerySectionNumber('I', 3), GetScenerySectionNumber('I', 38)}, + {GetScenerySectionNumber('I', 43), GetScenerySectionNumber('I', 78)}, + {GetScenerySectionNumber('I', 1), GetScenerySectionNumber('I', 37)}, + {GetScenerySectionNumber('I', 41), GetScenerySectionNumber('I', 77)}, + {GetScenerySectionNumber('I', 20), GetScenerySectionNumber('I', 36)}, + {GetScenerySectionNumber('I', 60), GetScenerySectionNumber('I', 76)}, + {GetScenerySectionNumber('I', 22), GetScenerySectionNumber('I', 35)}, + {GetScenerySectionNumber('I', 62), GetScenerySectionNumber('I', 75)}, + {GetScenerySectionNumber('I', 31), GetScenerySectionNumber('L', 38)}, + {GetScenerySectionNumber('I', 71), GetScenerySectionNumber('L', 78)}, + {GetScenerySectionNumber('I', 32), GetScenerySectionNumber('L', 39)}, + {GetScenerySectionNumber('I', 72), GetScenerySectionNumber('L', 79)}, + {GetScenerySectionNumber('J', 9), GetScenerySectionNumber('J', 39)}, + {GetScenerySectionNumber('J', 49), GetScenerySectionNumber('J', 79)}, + {GetScenerySectionNumber('J', 10), GetScenerySectionNumber('J', 38)}, + {GetScenerySectionNumber('J', 50), GetScenerySectionNumber('J', 78)}, + {GetScenerySectionNumber('F', 6), GetScenerySectionNumber('F', 38)}, + {GetScenerySectionNumber('F', 46), GetScenerySectionNumber('F', 78)}, + {GetScenerySectionNumber('F', 13), GetScenerySectionNumber('F', 37)}, + {GetScenerySectionNumber('F', 53), GetScenerySectionNumber('F', 77)}, + {GetScenerySectionNumber('H', 2), GetScenerySectionNumber('H', 37)}, + {GetScenerySectionNumber('H', 42), GetScenerySectionNumber('H', 77)}, + {GetScenerySectionNumber('H', 3), GetScenerySectionNumber('H', 36)}, + {GetScenerySectionNumber('H', 43), GetScenerySectionNumber('H', 76)}, + {GetScenerySectionNumber('H', 11), GetScenerySectionNumber('H', 35)}, + {GetScenerySectionNumber('H', 51), GetScenerySectionNumber('H', 75)}, + {GetScenerySectionNumber('H', 15), GetScenerySectionNumber('H', 34)}, + {GetScenerySectionNumber('H', 55), GetScenerySectionNumber('H', 74)}, + {GetScenerySectionNumber('G', 17), GetScenerySectionNumber('G', 35)}, + {GetScenerySectionNumber('G', 57), GetScenerySectionNumber('G', 75)}, + {GetScenerySectionNumber('V', 14), GetScenerySectionNumber('V', 99)}, + {GetScenerySectionNumber('V', 4), GetScenerySectionNumber('V', 98)}, + {GetScenerySectionNumber('V', 41), GetScenerySectionNumber('V', 97)}, + {GetScenerySectionNumber('V', 31), GetScenerySectionNumber('V', 96)}, + {GetScenerySectionNumber('V', 55), GetScenerySectionNumber('V', 95)}, + {GetScenerySectionNumber('E', 2), GetScenerySectionNumber('E', 39)}, + {GetScenerySectionNumber('E', 42), GetScenerySectionNumber('E', 79)}, + {GetScenerySectionNumber('E', 3), GetScenerySectionNumber('E', 38)}, + {GetScenerySectionNumber('E', 43), GetScenerySectionNumber('E', 78)}, + {GetScenerySectionNumber('E', 5), GetScenerySectionNumber('E', 37)}, + {GetScenerySectionNumber('E', 45), GetScenerySectionNumber('E', 77)}, + {GetScenerySectionNumber('E', 8), GetScenerySectionNumber('E', 36)}, + {GetScenerySectionNumber('E', 48), GetScenerySectionNumber('E', 76)}, + {GetScenerySectionNumber('E', 1), GetScenerySectionNumber('E', 35)}, + {GetScenerySectionNumber('E', 41), GetScenerySectionNumber('E', 75)}, + {GetScenerySectionNumber('M', 12), GetScenerySectionNumber('M', 39)}, + {GetScenerySectionNumber('M', 52), GetScenerySectionNumber('M', 79)}, + {GetScenerySectionNumber('C', 9), GetScenerySectionNumber('C', 39)}, +}; + VisibleSectionManager TheVisibleSectionManager; // size: 0x6830 +int ScenerySectionLODOffset = 0; +DrivableScenerySection *pSectionD9 = 0; +DrivableScenerySection *pSectionC14 = 0; +char *bGetPlatformName(); +int bStrNICmp(const char *s1, const char *s2, int n); + +VisibleSectionManager::VisibleSectionManager() { + pBoundaryChunks = 0; + pInfo = 0; + pActiveOverlay = 0; + pUndoOverlay = 0; + + bMemSet(UserInfoTable, 0, sizeof(UserInfoTable)); + NumAllocatedUserInfo = 0; + + bNode *head = &UnallocatedUserInfoList.HeadNode; + for (int i = 0; i < 512; i++) { + VisibleSectionUserInfo *user_info = &UserInfoStorageTable[i]; + bNode *tail = head->Prev; + + tail->Next = reinterpret_cast(user_info); + head->Prev = reinterpret_cast(user_info); + reinterpret_cast(user_info)->Prev = tail; + reinterpret_cast(user_info)->Next = head; + } + + VisibleBitTables = 0; + bMemSet(EnabledGroups, 0, sizeof(EnabledGroups)); +} + +VisibleSectionUserInfo *VisibleSectionManager::AllocateUserInfo(int section_number) { + VisibleSectionUserInfo *user_info = UserInfoTable[section_number]; + if (!user_info) { + user_info = reinterpret_cast(UnallocatedUserInfoList.HeadNode.Next); + NumAllocatedUserInfo += 1; + + bNode *next = reinterpret_cast(user_info)->Next; + bNode *prev = reinterpret_cast(user_info)->Prev; + prev->Next = next; + next->Prev = prev; + + bMemSet(user_info, 0, sizeof(VisibleSectionUserInfo)); + UserInfoTable[section_number] = user_info; + } + + user_info->ReferenceCount += 1; + return user_info; +} + +void VisibleSectionManager::UnallocateUserInfo(int section_number) { + VisibleSectionUserInfo *user_info = UserInfoTable[section_number]; + if (!user_info) { + return; + } + + int ref_count = user_info->ReferenceCount - 1; + user_info->ReferenceCount = ref_count; + if (ref_count != 0) { + return; + } + + bNode *tail = UnallocatedUserInfoList.HeadNode.Prev; + NumAllocatedUserInfo -= 1; + tail->Next = reinterpret_cast(user_info); + UnallocatedUserInfoList.HeadNode.Prev = reinterpret_cast(user_info); + reinterpret_cast(user_info)->Prev = tail; + reinterpret_cast(user_info)->Next = &UnallocatedUserInfoList.HeadNode; + UserInfoTable[section_number] = 0; +} + +void VisibleSectionManager::ActivateOverlay(const char *name) { + VisibleSectionOverlay *overlay = OverlayList.GetHead(); + while (overlay != OverlayList.EndOfList()) { + if (bStrICmp(overlay->Name, name) == 0) { + break; + } + overlay = overlay->GetNext(); + } + + if (overlay == OverlayList.EndOfList() || overlay == pActiveOverlay) { + return; + } + + if (pActiveOverlay) { + UnactivateOverlay(); + } + + DisablePrecullerCounter += 1; + pActiveOverlay = overlay; + VisibleSectionOverlay *undo_overlay = new VisibleSectionOverlay; + undo_overlay->NumEntries = 0; + bMemSet(undo_overlay->Name, 0, sizeof(undo_overlay->Name)); + bSafeStrCpy(undo_overlay->Name, "Undo", sizeof(undo_overlay->Name)); + pUndoOverlay = undo_overlay; + ActivateOverlay(overlay, undo_overlay); + RefreshTrackStreamer(); +} + +void VisibleSectionManager::ActivateOverlay(VisibleSectionOverlay *overlay, VisibleSectionOverlay *undo_overlay) { + for (int i = 0; i < overlay->NumEntries; i++) { + OverlayEntry *entry = &overlay->EntryTable[i]; + DrivableScenerySection *section = FindDrivableSection(entry->DrivableSectionNumber); + if (!section) { + continue; + } + + bool changed = false; + if (entry->AddRemove == 0) { + if (section->IsSectionVisible(entry->SectionNumber)) { + changed = true; + section->RemoveVisibleSection(entry->SectionNumber); + } + } else if (!section->IsSectionVisible(entry->SectionNumber)) { + changed = true; + section->AddVisibleSection(entry->SectionNumber); + } + + if (changed) { + section->SortVisibleSections(); + if (undo_overlay) { + OverlayEntry *undo_entry = &undo_overlay->EntryTable[undo_overlay->NumEntries]; + undo_overlay->NumEntries += 1; + *undo_entry = *entry; + undo_entry->AddRemove = entry->AddRemove == 0; + } + } + } +} + +void VisibleSectionManager::UnactivateOverlay() { + if (pActiveOverlay) { + DisablePrecullerCounter -= 1; + ActivateOverlay(pUndoOverlay, 0); + if (pUndoOverlay) { + delete pUndoOverlay; + } + pActiveOverlay = 0; + } +} + +int VisibleSectionManager::Loader(bChunk *chunk) { + if (chunk->GetID() == 0x80034150) { + bChunk *first_chunk = chunk->GetFirstChunk(); + bChunk *current_chunk = first_chunk; + bChunk *last_chunk = chunk->GetLastChunk(); + + while (current_chunk < last_chunk) { + unsigned int current_chunk_id = current_chunk->GetID(); + if (current_chunk_id == 0x34152) { + VisibleSectionBoundary *boundary = reinterpret_cast(current_chunk->GetData()); + VisibleSectionBoundary *last_boundary = reinterpret_cast(current_chunk->GetLastChunk()); + + if (boundary < last_boundary) { + do { + boundary->EndianSwap(); + if (IsScenerySectionDrivable(boundary->GetSectionNumber())) { + DrivableBoundaryList.AddTail(boundary); + } else { + NonDrivableBoundaryList.AddTail(boundary); + } + + boundary = reinterpret_cast( + reinterpret_cast(boundary) + boundary->GetMemoryImageSize()); + } while (boundary < last_boundary); + } + } else if (current_chunk_id == 0x34153) { + DrivableScenerySection *section = reinterpret_cast(current_chunk->GetData()); + DrivableScenerySection *last_section = reinterpret_cast(current_chunk->GetLastChunk()); + + if (section < last_section) { + do { + DrivableSectionList.AddTail(section); + section->EndianSwap(); + section->pBoundary = FindBoundary(section->GetSectionNumber()); + int section_size = 0xA4 - (0x48 - section->MaxVisibleSections) * sizeof(short); + section = reinterpret_cast( + reinterpret_cast(section) + section_size); + } while (section < last_section); + } + + pSectionD9 = FindDrivableSection(0x199); + pSectionC14 = FindDrivableSection(0x13A); + } else if (current_chunk_id == 0x34151) { + pInfo = reinterpret_cast(current_chunk->GetData()); + pInfo->EndianSwap(); + ScenerySectionLODOffset = pInfo->LODOffset; + } else if (current_chunk_id == 0x34154) { + } else if (current_chunk_id == 0x34155) { + LoadingSection *loading_sections = reinterpret_cast(current_chunk->GetData()); + int num_loading_sections = current_chunk->Size / sizeof(LoadingSection); + int n = 0; + while (n < num_loading_sections) { + LoadingSection *section = &loading_sections[n]; + LoadingSectionList.AddTail(section); + section->EndianSwap(); + n += 1; + } + } + + current_chunk = current_chunk->GetNext(); + } + + InitVisibleZones(); + RefreshTrackStreamer(); + return 1; + } + + if (chunk->GetID() == 0x34158) { + VisibleSectionOverlay *overlay = reinterpret_cast(chunk->GetData()); + OverlayList.AddTail(overlay); + overlay->EndianSwap(); + return 1; + } + + return 0; +} +int VisibleSectionManager::Unloader(bChunk *chunk) { + if (chunk->GetID() == 0x80034150) { + pInfo = 0; + DrivableBoundaryList.InitList(); + NonDrivableBoundaryList.InitList(); + LoadingSectionList.InitList(); + DrivableSectionList.InitList(); + return 1; + } + + if (chunk->GetID() == 0x34158) { + reinterpret_cast(chunk->GetData())->Remove(); + return 1; + } + + return 0; +} + +VisibleSectionBoundary *VisibleSectionManager::FindBoundary(int section_number) { + for (VisibleSectionBoundary *boundary = DrivableBoundaryList.GetHead(); boundary != DrivableBoundaryList.EndOfList(); + boundary = boundary->GetNext()) { + if (boundary->SectionNumber == section_number) { + return boundary; + } + } + + for (VisibleSectionBoundary *boundary = NonDrivableBoundaryList.GetHead(); boundary != NonDrivableBoundaryList.EndOfList(); + boundary = boundary->GetNext()) { + if (boundary->SectionNumber == section_number) { + return boundary; + } + } + + return 0; +} + +VisibleSectionBoundary *VisibleSectionManager::FindClosestBoundary(const bVector2 *point, float *distance) { + float closest_distance = 9999999.0f; + VisibleSectionBoundary *closest_boundary = 0; + + for (VisibleSectionBoundary *boundary = DrivableBoundaryList.GetHead(); boundary != DrivableBoundaryList.EndOfList(); + boundary = boundary->GetNext()) { + if (boundary->IsPointInside(point)) { + closest_distance = 0.0f; + DrivableBoundaryList.Remove(boundary); + DrivableBoundaryList.AddHead(boundary); + closest_boundary = boundary; + break; + } + + float boundary_distance = boundary->GetDistanceOutside(point, closest_distance); + if (!closest_boundary || boundary_distance < closest_distance || + (boundary_distance == closest_distance && boundary->SectionNumber < closest_boundary->SectionNumber)) { + closest_distance = boundary_distance; + closest_boundary = boundary; + } + } + + if (distance) { + *distance = closest_distance; + } + + return closest_boundary; +} + +VisibleSectionBoundary *VisibleSectionManager::FindBoundary(const bVector2 *point) { + for (VisibleSectionBoundary *boundary = DrivableBoundaryList.GetHead(); boundary != DrivableBoundaryList.EndOfList(); + boundary = boundary->GetNext()) { + if (boundary->IsPointInside(point)) { + DrivableBoundaryList.Remove(boundary); + DrivableBoundaryList.AddHead(boundary); + return boundary; + } + } + + float distance_to_boundary; + VisibleSectionBoundary *boundary = FindClosestBoundary(point, &distance_to_boundary); + if (distance_to_boundary >= 0.1f) { + return 0; + } + + return boundary; +} + +DrivableScenerySection *VisibleSectionManager::FindDrivableSection(const bVector2 *point) { + for (DrivableScenerySection *section = DrivableSectionList.GetHead(); section != DrivableSectionList.EndOfList(); section = section->GetNext()) { + if (section->pBoundary->IsPointInside(point)) { + DrivableSectionList.Remove(section); + DrivableSectionList.AddHead(section); + return section; + } + } + + float distance; + VisibleSectionBoundary *boundary = FindClosestBoundary(point, &distance); + if (distance < 0.1f) { + return FindDrivableSection(boundary->SectionNumber); + } + + return 0; +} + +DrivableScenerySection *VisibleSectionManager::FindDrivableSection(int section_number) { + for (DrivableScenerySection *section = DrivableSectionList.GetHead(); section != DrivableSectionList.EndOfList(); section = section->GetNext()) { + if (section->SectionNumber == section_number) { + return section; + } + } + + return 0; +} + +VisibleGroupInfo *VisibleSectionManager::GetGroupInfo(const char *selection_set_name) { + VisibleGroupInfo *group_info = VisibleGroupInfoTable; + for (int i = 0; i < 5; i++) { + int name_length = bStrLen(group_info->SelectionSetName); + if (bStrNICmp(selection_set_name, group_info->SelectionSetName, name_length) == 0) { + return group_info; + } + group_info += 1; + } + return 0; +} + +void VisibleSectionManager::EnableGroup(unsigned int group_name) { + for (int i = 0; i < 0x100; i++) { + if (EnabledGroups[i] == 0) { + EnabledGroups[i] = group_name; + return; + } + } +} diff --git a/src/Speed/Indep/Src/World/VisibleSection.hpp b/src/Speed/Indep/Src/World/VisibleSection.hpp index 074d852b9..860d9f08b 100644 --- a/src/Speed/Indep/Src/World/VisibleSection.hpp +++ b/src/Speed/Indep/Src/World/VisibleSection.hpp @@ -9,6 +9,67 @@ #include "Speed/Indep/bWare/Inc/Strings.hpp" #include "Speed/Indep/bWare/Inc/bList.hpp" #include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +extern int ScenerySectionLODOffset; + +static inline char GetScenerySectionLetter(int section_number) { + return static_cast(section_number / 100 + 'A' - 1); +} + +static inline bool IsRegularScenerySection(int section_number) { + char section_letter = GetScenerySectionLetter(section_number); + return section_letter >= 'A' && section_letter < 'U'; +} + +static inline int GetScenerySubsectionNumber(int section_number) { + return section_number % 100; +} + +static inline short GetScenerySectionNumber(char section_letter, int subsection_number) { + return static_cast((section_letter - 'A' + 1) * 100 + subsection_number); +} + +static inline bool IsTextureSection(int section_number) { + char section_letter = GetScenerySectionLetter(section_number); + return section_letter == 'Y' || section_letter == 'W'; +} + +static inline bool IsLibrarySection(int section_number) { + char section_letter = GetScenerySectionLetter(section_number); + return section_letter == 'X' || section_letter == 'U'; +} + +static inline bool IsScenerySectionDrivable(int section_number) { + if (!IsRegularScenerySection(section_number)) { + return false; + } + + int subsection_number = GetScenerySubsectionNumber(section_number); + return subsection_number > 0 && subsection_number < ScenerySectionLODOffset; +} + +static inline int GetLODScenerySectionNumber(int drivable_section_number) { + return drivable_section_number + ScenerySectionLODOffset; +} + +static inline int bGetTablePos(short *table, int num_elements, short element) { + for (int n = 0; n < num_elements; n++) { + if (table[n] == element) { + return n; + } + } + + return -1; +} + +static inline bool bIsInTable(short *table, int num_elements, short element) { + return bGetTablePos(table, num_elements, element) >= 0; +} + +static inline bool HasSection(short *section_table, int num_sections, short section_number) { + return bIsInTable(section_table, num_sections, section_number); +} struct VisibleSectionBoundary : public bTNode { // total size: 0xA4 @@ -19,6 +80,20 @@ struct VisibleSectionBoundary : public bTNode { bVector2 BBoxMax; // offset 0x14, size 0x8 bVector2 Centre; // offset 0x1C, size 0x8 bVector2 Points[16]; // offset 0x24, size 0x80 + + void EndianSwap(); + bool IsPointInside(const bVector2 *point); + float GetDistanceOutside(const bVector2 *point, float max_distance); + int GetSectionNumber(); + int GetMemoryImageSize(); + + int GetNumPoints() { + return NumPoints; + } + + bVector2 *GetPoint(int n) { + return &Points[n]; + } }; struct VisibleSectionCoordinate { @@ -38,6 +113,20 @@ struct DrivableScenerySection : public bTNode { short VisibleSections[72]; // offset 0x12, size 0x90 short Padding; // offset 0xA2, size 0x2 + void EndianSwap(); + int GetSectionNumber() { + return SectionNumber; + } + int GetMemoryImageSize(); + void AddVisibleSection(int section_number); + int IsSectionVisible(int section_number); + void RemoveVisibleSection(int section_number); + void SortVisibleSections(); + + int GetNumVisibleSections() { + return this->NumVisibleSections; + } + int GetVisibleSection(int i) { return this->VisibleSections[i]; } @@ -47,6 +136,8 @@ struct DrivableSectionsInRegion { // total size: 0x324 int NumSections; // offset 0x0, size 0x4 short Sections[400]; // offset 0x4, size 0x320 + + void EndianSwap(); }; struct VisibleTextureSection : public bTNode { @@ -66,6 +157,8 @@ struct LoadingSection : public bTNode { short DrivableSections[16]; // offset 0x1A, size 0x20 short NumExtraSections; // offset 0x3A, size 0x2 short ExtraSections[8]; // offset 0x3C, size 0x10 + + void EndianSwap(); }; struct SuperScenerySection : public bTNode { @@ -112,14 +205,83 @@ struct VisibleSectionOverlay : public bTNode { char Name[40]; // offset 0x8, size 0x28 int NumEntries; // offset 0x30, size 0x4 OverlayEntry EntryTable[4096]; // offset 0x34, size 0x6000 + + void EndianSwap(); }; struct VisibleSectionManagerInfo { // total size: 0x328 int LODOffset; // offset 0x0, size 0x4 DrivableSectionsInRegion TheDrivableSectionsInRegion; // offset 0x4, size 0x324 + + void EndianSwap(); }; +inline void VisibleSectionBoundary::EndianSwap() { + bPlatEndianSwap(&SectionNumber); + bPlatEndianSwap(reinterpret_cast(&NumPoints)); + bPlatEndianSwap(reinterpret_cast(&PanoramaBoundary)); + bPlatEndianSwap(&BBoxMin); + bPlatEndianSwap(&BBoxMax); + for (int i = 0; i < NumPoints; i++) { + bPlatEndianSwap(&Points[i]); + } +} + +inline int VisibleSectionBoundary::GetSectionNumber() { + return SectionNumber; +} + +inline int VisibleSectionBoundary::GetMemoryImageSize() { + return 0xA4 - (16 - NumPoints) * sizeof(bVector2); +} + +inline void DrivableSectionsInRegion::EndianSwap() { + bPlatEndianSwap(&NumSections); + for (int i = 0; i < NumSections; i++) { + bPlatEndianSwap(&Sections[i]); + } +} + +inline void DrivableScenerySection::EndianSwap() { + bPlatEndianSwap(&SectionNumber); + bPlatEndianSwap(reinterpret_cast(&MostVisibleSections)); + bPlatEndianSwap(reinterpret_cast(&MaxVisibleSections)); + bPlatEndianSwap(&NumVisibleSections); + for (int i = 0; i < NumVisibleSections; i++) { + bPlatEndianSwap(&VisibleSections[i]); + } +} + +inline int DrivableScenerySection::GetMemoryImageSize() { + return 0x12 + NumVisibleSections * sizeof(short) + sizeof(Padding); +} + +inline void LoadingSection::EndianSwap() { + bPlatEndianSwap(&NumDrivableSections); + for (int i = 0; i < NumDrivableSections; i++) { + bPlatEndianSwap(&DrivableSections[i]); + } + bPlatEndianSwap(&NumExtraSections); + for (int i = 0; i < NumExtraSections; i++) { + bPlatEndianSwap(&ExtraSections[i]); + } +} + +inline void VisibleSectionOverlay::EndianSwap() { + bPlatEndianSwap(&NumEntries); + for (int n = 0; n < NumEntries; n++) { + OverlayEntry *entry = &EntryTable[n]; + bPlatEndianSwap(&entry->DrivableSectionNumber); + bPlatEndianSwap(&entry->SectionNumber); + } +} + +inline void VisibleSectionManagerInfo::EndianSwap() { + bPlatEndianSwap(&LODOffset); + TheDrivableSectionsInRegion.EndianSwap(); +} + struct OverrideSectionObject : public bTNode { // total size: 0x4C short SectionNumber; // offset 0x8, size 0x2 @@ -141,20 +303,63 @@ struct VisibleSectionUserInfo { struct UnallocatedVisibleSectionUserInfo {}; struct VisibleGroupInfo { + // total size: 0x8 char *SelectionSetName; // offset 0x0, size 0x4 bool UsedForTopology; // offset 0x4, size 0x1 }; -// total size: 0x6830 class VisibleSectionManager { public: static VisibleGroupInfo *GetGroupInfo(const char *selection_set_name); + VisibleTextureSection *FindVisibleTextureSection(int section_number); + + LoadingSection *FindLoadingSection(int drivable_section_number); + + LoadingSection *FindLoadingSection(const char *name); + + int GetSectionsToLoad(LoadingSection *loading_section, short *section_numbers, int max_sections); + + OverrideSectionObject *FindOverrideSectionObject(const char *name, OverrideSectionObject *prev_object, bool partial_compare); + + VisibleSectionManager(); + + ~VisibleSectionManager(); + + VisibleSectionUserInfo *AllocateUserInfo(int section_number); + void UnallocateUserInfo(int section_number); + + void ActivateOverlay(const char *name); + + void ActivateOverlay(VisibleSectionOverlay *overlay, VisibleSectionOverlay *undo_overlay); + + void UnactivateOverlay(); + + int Loader(bChunk *chunk); + + int Unloader(bChunk *chunk); + + VisibleSectionBoundary *FindBoundary(int section_number); + + VisibleSectionBoundary *FindClosestBoundary(const bVector2 *point, float *distance_outside); + + VisibleSectionBoundary *FindBoundary(const bVector2 *point); + + int FindCloseBoundaries(VisibleSectionBoundary **boundaries, int max_boundaries, const bVector2 *point, float distance_outside); + DrivableScenerySection *FindDrivableSection(const bVector2 *point); + DrivableScenerySection *FindDrivableSection(int section_number); + + unsigned int GetVisibleSectionChecksum(int section_number); + + bool IsGroupEnabled(unsigned int group_name_hash); + void EnableGroup(unsigned int group_name_hash); + void DisableGroup(unsigned int group_name_hash); + void DisableAllGroups() { bMemSet(EnabledGroups, 0, 0x400); } diff --git a/src/Speed/Indep/Src/World/WWorldPos.h b/src/Speed/Indep/Src/World/WWorldPos.h index b1c11652d..f2805c27a 100644 --- a/src/Speed/Indep/Src/World/WWorldPos.h +++ b/src/Speed/Indep/Src/World/WWorldPos.h @@ -44,7 +44,9 @@ class WWorldPos { // bool OffEdge() const {} - // bool OnValidFace() const {} + bool OnValidFace() const { + return fFaceValid; + } void ForceFaceValidity() {} diff --git a/src/Speed/Indep/Src/World/WeatherMan.cpp b/src/Speed/Indep/Src/World/WeatherMan.cpp index e69de29bb..61ba74e79 100644 --- a/src/Speed/Indep/Src/World/WeatherMan.cpp +++ b/src/Speed/Indep/Src/World/WeatherMan.cpp @@ -0,0 +1,275 @@ +#include "WeatherMan.hpp" + +#include "Speed/Indep/Src/Camera/Camera.hpp" +#include "Speed/Indep/Src/Camera/CameraMover.hpp" +#include "Speed/Indep/bWare/Inc/bMath.hpp" + +bTList RegionLists[NUM_REGION_TYPES]; +int RegionCount[NUM_REGION_TYPES]; +bVector3 cPos; +extern float BaseWeatherFogStart; +extern float BaseWeatherFog; +extern float BaseFogFalloffY; +extern float BaseFogFalloffX; +extern float BaseFogFalloff; +extern float DAT_80409c34; +extern float DAT_80409c38; +extern float DAT_80409c3c; +extern float DAT_80409c40; +extern float DAT_80409c44; +extern int FogControlOverRide; +extern int BaseWeatherFogColourR; +extern int BaseWeatherFogColourG; +extern int BaseWeatherFogColourB; +extern float oldDistFogStart_27399; +extern float oldDistFogPower_27398; +extern unsigned int oldDistFogColour_27397; + +int LoaderWeatherMan(bChunk *bchunk) { + if (bchunk->GetID() != 0x34250) { + return 0; + } + + unsigned char *data = reinterpret_cast(bchunk->GetAlignedData(0x10)); + bEndianSwap32(data + 8); + bEndianSwap32(data + 0xC); + if (*reinterpret_cast(data + 8) == 2) { + int num_regions = *reinterpret_cast(data + 0xC); + unsigned char *region_data = data + 0x10; + for (int i = 0; i < num_regions; i++) { + bEndianSwap32(region_data + 0x84); + bEndianSwap32(region_data + 0x88); + bEndianSwap32(region_data + 0x48); + bEndianSwap32(region_data + 0x4C); + bEndianSwap32(region_data + 0x50); + bEndianSwap32(region_data + 0x54); + bEndianSwap32(region_data + 0x58); + bEndianSwap32(region_data + 0x5C); + bEndianSwap32(region_data + 0x60); + bEndianSwap32(region_data + 0x64); + bEndianSwap32(region_data + 0x68); + bEndianSwap32(region_data + 0x6C); + bEndianSwap32(region_data + 0x70); + bEndianSwap32(region_data + 0x74); + bEndianSwap32(region_data + 0x78); + bEndianSwap32(region_data + 0x8C); + bEndianSwap32(region_data + 0x90); + bEndianSwap32(region_data + 0x94); + AddRegion(reinterpret_cast(region_data)); + region_data += sizeof(GenericRegion); + } + } + + return 1; +} + +int UnloaderWeatherMan(bChunk *bchunk) { + if (bchunk->GetID() != 0x34250) { + return 0; + } + + unsigned char *data = reinterpret_cast(bchunk->GetAlignedData(0x10)); + int version = *reinterpret_cast(data + 8); + if (version == 2) { + GenericRegion *region = reinterpret_cast(data + 0x10); + int num_regions = *reinterpret_cast(data + 0xC); + for (int i = 0; i < num_regions; i++) { + RemoveRegion(region); + region += 1; + } + } + + return 1; +} + +void AddRegion(GenericRegion *region) { + unsigned int region_type = static_cast(region->Type); + if (region_type == REGION_RAIN && region->Intensity == 0.0f) { + region->Type = REGION_TUNNEL; + region_type = REGION_TUNNEL; + } + + if (region_type < NUM_REGION_TYPES) { + RegionLists[region_type].AddTail(region); + RegionCount[region_type] += 1; + } +} + +void RemoveRegion(GenericRegion *region) { + region->Remove(); +} + +int DepthRegion(GenericRegion *before, GenericRegion *after) { + bVector3 Position(before->PositionX, before->PositionY, before->PositionZ); + bVector3 Delta = Position - cPos; + float distB = bLength(Delta); + Position = bVector3(after->PositionX, after->PositionY, after->PositionZ); + Delta = Position - cPos; + float distA = bLength(Delta); + return distB <= distA; +} + +int RegionQuery::CalculateRegionInfo(eView *view, RegionType regionKind, int InFE) { + unsigned int colr_r = 0; + unsigned int colr_g = 0; + unsigned int colr_b = 0; + bVector3 cPos(*view->GetCamera()->GetPosition()); + + (void)InFE; + + if (FogControlOverRide) { + unsigned int retcol; + unsigned int fog_colour = BaseWeatherFogColourB << 16 | BaseWeatherFogColourG << 8 | BaseWeatherFogColourR; + + FogFalloff = BaseFogFalloff; + FogFalloffX = BaseFogFalloffX; + FogFalloffY = BaseFogFalloffY; + DistFogStart = BaseWeatherFogStart; + DistFogPower = BaseWeatherFog; + retcol = fog_colour | 0x80000000; + DistFogColour = retcol; + if (oldDistFogColour_27397 == retcol) { + if (oldDistFogPower_27398 == DistFogPower) { + if (oldDistFogStart_27399 == DistFogStart) { + return 0; + } + } + } + } else { + DistFogPower = DAT_80409c34; + DistFogStart = DAT_80409c34; + FogFalloffY = DAT_80409c34; + FogFalloffX = DAT_80409c34; + FogFalloff = DAT_80409c34; + float smallest = DAT_80409c38; + bTList *region_list = &RegionLists[static_cast(regionKind)]; + + for (GenericRegion *region = region_list->GetHead(); region != region_list->EndOfList(); region = region->GetNext()) { + bVector4 direction; + direction.x = region->PositionX - cPos.x; + direction.y = region->PositionY - cPos.y; + float distanceSq = direction.x * direction.x + direction.y * direction.y; + + if (distanceSq < region->Radius * region->Radius) { + float distance = DAT_80409c34; + if (DAT_80409c3c < distanceSq) { + distance = bSqrt(distanceSq); + } + + float blend; + if (distance < region->FarFalloffStart) { + blend = DAT_80409c34; + } else { + blend = (distance - region->FarFalloffStart) / (region->Radius - region->FarFalloffStart); + } + + region->effect = DAT_80409c44 - blend; + if (blend == DAT_80409c34) { + region->inFlags = 1; + if (region->Radius < smallest) { + smallest = region->Radius; + } + } else { + region->inFlags = 2; + } + } else { + region->inFlags = 4; + region->effect = DAT_80409c34; + } + } + + float totaleffex = DAT_80409c34; + for (GenericRegion *region = region_list->GetHead(); region != region_list->EndOfList(); region = region->GetNext()) { + region->effect = region->effect * region->modifier; + totaleffex += region->effect; + } + + for (GenericRegion *region = region_list->GetHead(); region != region_list->EndOfList(); region = region->GetNext()) { + if (totaleffex == DAT_80409c34) { + region->effect = DAT_80409c34; + } else { + region->effect = region->effect / totaleffex; + } + + if (region->effect != DAT_80409c34) { + FogFalloff += region->FogFalloff * region->effect; + FogFalloffX += region->FogFalloffX * region->effect; + FogFalloffY += region->FogFalloffY * region->effect; + DistFogStart += region->FogStart * region->effect; + DistFogPower += region->Intensity * region->effect; + + unsigned int fog_colour = region->FogColour; + unsigned int fog_colour_r = static_cast(fog_colour); + unsigned int fog_colour_g = static_cast(fog_colour >> 8); + unsigned int fog_colour_b = static_cast(fog_colour >> 16); + + colr_r += static_cast(fog_colour_r * region->effect); + colr_g += static_cast(fog_colour_g * region->effect); + colr_b += static_cast(fog_colour_b * region->effect); + } + } + + unsigned int retcol = colr_r | colr_g << 8 | colr_b << 16 | 0x80000000; + DistFogColour = retcol; + if (oldDistFogColour_27397 == retcol) { + if (oldDistFogPower_27398 == DistFogPower) { + if (oldDistFogStart_27399 == DistFogStart) { + return 0; + } + } + } + } + + oldDistFogColour_27397 = DistFogColour; + oldDistFogPower_27398 = DistFogPower; + oldDistFogStart_27399 = DistFogStart; + return 1; +} + +GenericRegion *GetClosestRegionInView(eView *view, bVector3 *endVector, float *angleCos) { + cPos = *view->GetCamera()->GetPosition(); + bVector3 cDir(*view->GetCamera()->GetDirection()); + bTList *region_list = &RegionLists[REGION_BLOOM]; + region_list->Sort(DepthRegion); + bVector3 posScreen; + + CameraMover *cameraMover = view->GetCameraMover(); + if (!cameraMover) { + return 0; + } + + CameraAnchor *cameraAnchor = cameraMover->GetAnchor(); + if (!cameraAnchor) { + return 0; + } + + bVector3 MyCarPos(*cameraAnchor->GetGeometryPosition()); + float maxDist = 99999.0f; + float angleCOS; + GenericRegion *ClosestRegion = 0; + + for (GenericRegion *region = region_list->GetHead(); region != region_list->EndOfList(); region = region->GetNext()) { + bVector3 Position(region->PositionX, region->PositionY, region->PositionZ); + bVector3 Delta = Position - cPos; + bVector3 Delta2(Delta); + bNormalize(&Delta2, &Delta); + + angleCOS = bDot(Delta2, cDir); + if (0.0f < angleCOS) { + float dist = bLength(Delta); + if (dist < maxDist) { + *angleCos = angleCOS; + ClosestRegion = region; + maxDist = dist; + } + } + } + + if (ClosestRegion) { + endVector->x = ClosestRegion->PositionX; + endVector->y = ClosestRegion->PositionY; + endVector->z = ClosestRegion->PositionZ; + return ClosestRegion; + } + return 0; +} diff --git a/src/Speed/Indep/Src/World/WeatherMan.hpp b/src/Speed/Indep/Src/World/WeatherMan.hpp index f0d76ccbf..7f38f3c03 100644 --- a/src/Speed/Indep/Src/World/WeatherMan.hpp +++ b/src/Speed/Indep/Src/World/WeatherMan.hpp @@ -66,4 +66,10 @@ struct RegionQuery { int CalculateRegionInfo(eView *view, RegionType regionKind, int InFE); }; +void AddRegion(GenericRegion *region); +void RemoveRegion(GenericRegion *region); +int DepthRegion(GenericRegion *before, GenericRegion *after); +GenericRegion *GetClosestRegionInView(eView *view, bVector3 *endVector, float *angleCos); +int UnloaderWeatherMan(bChunk *bchunk); + #endif diff --git a/src/Speed/Indep/bWare/Inc/Espresso.hpp b/src/Speed/Indep/bWare/Inc/Espresso.hpp new file mode 100644 index 000000000..ae3da5637 --- /dev/null +++ b/src/Speed/Indep/bWare/Inc/Espresso.hpp @@ -0,0 +1,14 @@ +#ifndef BWARE_ESPRESSO_H +#define BWARE_ESPRESSO_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +inline void espEmptyLayer(const char *layername) {} + +inline int espGetLayerState(const char *layername) { + return 0; +} + +#endif diff --git a/src/Speed/Indep/bWare/Inc/bList.hpp b/src/Speed/Indep/bWare/Inc/bList.hpp index 70079e26d..35b702e57 100644 --- a/src/Speed/Indep/bWare/Inc/bList.hpp +++ b/src/Speed/Indep/bWare/Inc/bList.hpp @@ -36,8 +36,8 @@ class bNode { } bNode *AddAfter(bNode *insert_point) { - bNode *new_next = insert_point->Next; bNode *new_prev = this->Prev; // unused + bNode *new_next = insert_point->Next; insert_point->Next = this; new_next->Prev = this; this->Prev = insert_point; @@ -315,8 +315,10 @@ class bPNode : public bTNode { this->Object = object; } - bPNode *GetObject() { - return reinterpret_cast(Object); + ~bPNode() {} + + void *GetObject() { + return Object; } void *GetpObject() { @@ -347,7 +349,7 @@ template class bPList : public bTList { void Remove(bNode *node) { bList::Remove(node); - delete node; + delete reinterpret_cast(node); } void RemoveHead() { diff --git a/src/Speed/Indep/bWare/Inc/bMath.hpp b/src/Speed/Indep/bWare/Inc/bMath.hpp index 67c82ebef..ed40fd9a8 100644 --- a/src/Speed/Indep/bWare/Inc/bMath.hpp +++ b/src/Speed/Indep/bWare/Inc/bMath.hpp @@ -243,7 +243,7 @@ struct bVector2 { int operator==(const bVector2 &v); - // bVector2 &operator=(const bVector2 &v) {} // compiler generated? shown in dwarf + bVector2 &operator=(const bVector2 &v); // bVector2(const bVector2 &v) {} // compiler generated @@ -262,6 +262,18 @@ inline bVector2 *bFill(bVector2 *dest, float x, float y) { return dest; } +inline bVector2 *bCopy(bVector2 *dest, const bVector2 *v) { + float x = v->x; + float y = v->y; + bFill(dest, x, y); + return dest; +} + +inline bVector2 &bVector2::operator=(const bVector2 &v) { + bCopy(this, &v); + return *this; +} + inline bVector2::bVector2(float _x, float _y) { bFill(this, _x, _y); } @@ -286,12 +298,35 @@ inline bVector2 bVector2::operator-(const bVector2 &v) const { return bVector2(_x, _y); } +inline bVector2 *bScale(bVector2 *dest, const bVector2 *v, float scale) { + float x = v->x; + float y = v->y; + + dest->x = x * scale; + dest->y = y * scale; + return dest; +} + +inline bVector2 bScale(const bVector2 &v, float scale) { + bVector2 dest; + bScale(&dest, &v, scale); + return dest; +} + +inline bVector2 bVector2::operator*(float f) const { + return bScale(*this, f); +} + inline float bLength(const bVector2 *v) { float x = v->x; float y = v->y; return bSqrt(x * x + y * y); } +inline float bLength(const bVector2 &v) { + return bLength(&v); +} + inline bVector2 bNormalize(const bVector2 &v) { bVector2 dest; bNormalize(&dest, &v); @@ -304,15 +339,6 @@ inline float bDot(const bVector2 *v1, const bVector2 *v2) { return v1->x * v2->x + v1->y * v2->y; } -inline bVector2 *bScale(bVector2 *dest, const bVector2 *v, float scale) { - float x = v->x; - float y = v->y; - - dest->x = x * scale; - dest->y = y * scale; - return dest; -} - struct ALIGN_16 bVector3 { // total size: 0x10 float x; // offset 0x0, size 0x4 @@ -930,15 +956,25 @@ struct bQuaternion { class bBitTable { public: - // bBitTable() {} + bBitTable() { + Bits = 0; + NumBits = 0; + } - // bBitTable(void *mem, int num_bits) {} + bBitTable(void *mem, int num_bits) { + Init(mem, num_bits); + } - // void Init(void *mem, int num_bits) {} + void Init(void *mem, int num_bits) { + Bits = reinterpret_cast(mem); + NumBits = num_bits; + } - // void ClearTable() {} + void ClearTable(); - // void Set(int bit) {} + void Set(int bit) { + Bits[bit >> 3] |= static_cast(1 << (bit & 7)); + } // void Clear(int bit) {} diff --git a/src/Speed/Indep/bWare/Inc/bMemory.hpp b/src/Speed/Indep/bWare/Inc/bMemory.hpp index 5be513d4d..fccc94a4a 100644 --- a/src/Speed/Indep/bWare/Inc/bMemory.hpp +++ b/src/Speed/Indep/bWare/Inc/bMemory.hpp @@ -147,18 +147,4 @@ void bMemorySetOverflowPoolNumber(int pool_num, int overflow_pool_number); void *bWareMalloc(int size, const char *debug_text, int debug_line, int allocation_params); -inline int bMemoryGetPoolNum(int allocation_params) { - return allocation_params & 0xf; -} - -inline int bMemoryGetAlignment(int allocation_params) { - int alignment = allocation_params >> 6 & 0x1ffc; - - return alignment; -} - -inline int bMemoryGetAlignmentOffset(int allocation_params) { - return (allocation_params >> 17) & 0x1ffc; -} - #endif diff --git a/src/Speed/Indep/bWare/Inc/bWare.hpp b/src/Speed/Indep/bWare/Inc/bWare.hpp index 28202994b..7f74728d5 100644 --- a/src/Speed/Indep/bWare/Inc/bWare.hpp +++ b/src/Speed/Indep/bWare/Inc/bWare.hpp @@ -133,4 +133,26 @@ inline int bIsBFunkAvailable() { unsigned int bCalculateCrc32(const void *data, int size, unsigned int prev_crc32); +inline int bMemoryGetPoolNum(int allocation_params) { + return allocation_params & 0xf; +} + +inline int bMemoryGetAlignment(int allocation_params) { + int alignment = allocation_params >> 6 & 0x1ffc; + + return alignment; +} + +inline int bMemoryGetBestFit(int allocation_params) { + return allocation_params & 0x80; +} + +inline int bMemoryGetTopBit(int allocation_params) { + return allocation_params & 0x40; +} + +inline int bMemoryGetAlignmentOffset(int allocation_params) { + return (allocation_params >> 17) & 0x1ffc; +} + #endif diff --git a/tools/build_matrix.py b/tools/build_matrix.py new file mode 100644 index 000000000..135bce18c --- /dev/null +++ b/tools/build_matrix.py @@ -0,0 +1,307 @@ +#!/usr/bin/env python3 + +""" +Run sequential build checks across supported platforms. + +Examples: + python tools/build_matrix.py + python tools/build_matrix.py --version GOWE69 --version SLES-53558-A124 + python tools/build_matrix.py --all-source +""" + +import argparse +import os +import subprocess +import sys +import time +from dataclasses import dataclass +from typing import List, Optional, Sequence + + +SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) +ROOT_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "..")) +DEFAULT_RESTORE_VERSION = "GOWE69" + + +@dataclass(frozen=True) +class PlatformCheck: + version: str + label: str + required_assets: Sequence[str] + + +@dataclass +class StepResult: + name: str + command: List[str] + returncode: int + elapsed: float + log_path: str + output: str + + @property + def ok(self) -> bool: + return self.returncode == 0 + + +@dataclass +class PlatformResult: + platform: PlatformCheck + configure: Optional[StepResult] = None + build: Optional[StepResult] = None + preflight_error: Optional[str] = None + + @property + def ok(self) -> bool: + return ( + self.preflight_error is None + and self.configure is not None + and self.configure.ok + and self.build is not None + and self.build.ok + ) + + +PLATFORMS = ( + PlatformCheck( + version="GOWE69", + label="GameCube", + required_assets=("orig/GOWE69/NFSMWRELEASE.ELF",), + ), + PlatformCheck( + version="EUROPEGERMILESTONE", + label="Xbox 360", + required_assets=("orig/EUROPEGERMILESTONE/NfsMWEuropeGerMilestone.xex",), + ), + PlatformCheck( + version="SLES-53558-A124", + label="PS2", + required_assets=("orig/SLES-53558-A124/NFS.ELF",), + ), +) + +PLATFORM_BY_VERSION = {platform.version: platform for platform in PLATFORMS} + + +def print_section(title: str) -> None: + print(f"\n== {title} ==", flush=True) + + +def tail_lines(text: str, count: int) -> str: + lines = text.rstrip().splitlines() + if len(lines) <= count: + return "\n".join(lines) + return "\n".join(lines[-count:]) + + +def run_logged(command: List[str], log_path: str) -> StepResult: + start = time.monotonic() + try: + completed = subprocess.run( + command, + cwd=ROOT_DIR, + capture_output=True, + text=True, + errors="replace", + ) + output = completed.stdout + if completed.stderr: + if output and not output.endswith("\n"): + output += "\n" + output += completed.stderr + returncode = completed.returncode + except OSError as exc: + output = str(exc) + returncode = 127 + elapsed = time.monotonic() - start + + os.makedirs(os.path.dirname(log_path), exist_ok=True) + with open(log_path, "w", encoding="utf-8") as log_file: + log_file.write(output) + + return StepResult( + name=os.path.basename(log_path), + command=command, + returncode=returncode, + elapsed=elapsed, + log_path=log_path, + output=output, + ) + + +def missing_assets(platform: PlatformCheck) -> List[str]: + missing = [] + for rel_path in platform.required_assets: + abs_path = os.path.join(ROOT_DIR, rel_path) + if not os.path.exists(abs_path): + missing.append(rel_path) + return missing + + +def describe_failure(step: StepResult, tail_count: int) -> None: + print(f"FAIL {step.name}: exit {step.returncode} in {step.elapsed:.2f}s") + print(f"Command: {' '.join(step.command)}") + print(f"Log: {step.log_path}") + if step.output.strip(): + print("--- output tail ---") + print(tail_lines(step.output, tail_count)) + + +def run_platform( + platform: PlatformCheck, build_target: Optional[str], jobs: int, tail_count: int +) -> PlatformResult: + result = PlatformResult(platform=platform) + logs_dir = os.path.join(ROOT_DIR, "build", platform.version, "logs") + + print_section(f"{platform.label} ({platform.version})") + + missing = missing_assets(platform) + if missing: + result.preflight_error = ( + "Missing required assets: " + + ", ".join(missing) + + " (hint: seed shared assets or run worktree bootstrap first)" + ) + print(f"FAIL preflight: {result.preflight_error}") + return result + + configure_cmd = [sys.executable, "configure.py", "--version", platform.version] + configure_log = os.path.join(logs_dir, "build-matrix-configure.log") + print(f"RUN configure: {' '.join(configure_cmd)}") + result.configure = run_logged(configure_cmd, configure_log) + if result.configure.ok: + print(f"OK configure: {result.configure.elapsed:.2f}s ({configure_log})") + else: + describe_failure(result.configure, tail_count) + return result + + build_cmd = ["ninja", "-j", str(jobs)] + if build_target is not None: + build_cmd.append(build_target) + build_name = build_target or "default" + build_log = os.path.join(logs_dir, f"build-matrix-{build_name}.log") + print(f"RUN build: {' '.join(build_cmd)}") + result.build = run_logged(build_cmd, build_log) + if result.build.ok: + print(f"OK build: {result.build.elapsed:.2f}s ({build_log})") + else: + describe_failure(result.build, tail_count) + + return result + + +def restore_version(version: str, tail_count: int) -> bool: + print_section(f"Restore {version}") + log_path = os.path.join(ROOT_DIR, "build", version, "logs", "build-matrix-restore.log") + step = run_logged([sys.executable, "configure.py", "--version", version], log_path) + if step.ok: + print(f"OK restore: {step.elapsed:.2f}s ({log_path})") + return True + + describe_failure(step, tail_count) + return False + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Check sequential builds across all supported platforms." + ) + parser.add_argument( + "--version", + dest="versions", + action="append", + choices=sorted(PLATFORM_BY_VERSION.keys()), + help="Limit the run to one or more versions (default: all platforms).", + ) + parser.add_argument( + "--all-source", + action="store_true", + help="Run `ninja all_source` instead of the default full `ninja`.", + ) + parser.add_argument( + "--jobs", + type=int, + default=1, + help="Parallelism passed to ninja (default: 1).", + ) + parser.add_argument( + "--tail", + type=int, + default=40, + help="How many output lines to print when a step fails (default: 40).", + ) + parser.add_argument( + "--restore-version", + default=DEFAULT_RESTORE_VERSION, + choices=sorted(PLATFORM_BY_VERSION.keys()), + help=f"Version to restore at the end (default: {DEFAULT_RESTORE_VERSION}).", + ) + parser.add_argument( + "--no-restore", + action="store_true", + help="Leave the worktree configured for the last checked version.", + ) + return parser.parse_args() + + +def print_summary( + results: Sequence[PlatformResult], restore_version_name: str, restore_ok: Optional[bool] +) -> None: + print_section("Summary") + for result in results: + if result.preflight_error is not None: + print(f"FAIL {result.platform.version}: {result.preflight_error}") + continue + if result.configure is None or not result.configure.ok: + assert result.configure is not None + print( + f"FAIL {result.platform.version}: configure exit {result.configure.returncode} " + f"({result.configure.elapsed:.2f}s)" + ) + continue + if result.build is None or not result.build.ok: + assert result.build is not None + print( + f"FAIL {result.platform.version}: build exit {result.build.returncode} " + f"({result.build.elapsed:.2f}s)" + ) + continue + total = result.configure.elapsed + result.build.elapsed + print(f"OK {result.platform.version}: {total:.2f}s") + + if restore_ok is not None: + status = "OK" if restore_ok else "FAIL" + print(f"{status:4} restore: {restore_version_name}") + + +args = parse_args() + + +def main() -> int: + selected_versions = args.versions or [platform.version for platform in PLATFORMS] + platforms = [PLATFORM_BY_VERSION[version] for version in selected_versions] + build_target = "all_source" if args.all_source else None + results: List[PlatformResult] = [] + restore_ok: Optional[bool] = None + + print(f"Root: {ROOT_DIR}") + print(f"Build target: {build_target or 'ninja default'}") + + try: + for platform in platforms: + results.append(run_platform(platform, build_target, args.jobs, args.tail)) + finally: + if not args.no_restore: + restore_ok = restore_version(args.restore_version, args.tail) + + print_summary(results, args.restore_version, restore_ok) + + if restore_ok is False: + return 1 + if any(not result.ok for result in results): + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/code_style.py b/tools/code_style.py index ecb85f713..3a1129963 100644 --- a/tools/code_style.py +++ b/tools/code_style.py @@ -84,6 +84,7 @@ class Finding: ) USING_NAMESPACE_PATTERN = re.compile(r"^\s*using\s+namespace\b") NULL_PATTERN = re.compile(r"\bNULL\b") +BARE_PRESENCE_IF_PATTERN = re.compile(r"^\s*#if\s+([A-Za-z_][A-Za-z0-9_]*)\s*$") HEADER_GUARD_IFNDEF_PATTERN = re.compile(r"^\s*#ifndef\s+[A-Za-z0-9_]+\s*$", re.MULTILINE) HEADER_GUARD_DEFINE_PATTERN = re.compile(r"^\s*#define\s+[A-Za-z0-9_]+\s*$", re.MULTILINE) EA_PRAGMA_BLOCK_PATTERN = re.compile( @@ -92,6 +93,16 @@ class Finding: r".*?^\s*#endif\s*$", re.MULTILINE | re.DOTALL, ) +EA_PRAGMA_IFDEF_PATTERN = re.compile( + r"^\s*#ifdef\s+EA_PRAGMA_ONCE_SUPPORTED\s*$", re.MULTILINE +) +RECOVERED_LAYOUT_COMMENT_PATTERN = re.compile( + r"//\s*offset 0x[0-9A-Fa-f]+,\s*size 0x[0-9A-Fa-f]+" +) +RECOVERED_NARROW_UNSIGNED_PATTERN = re.compile(r"\bunsigned\s+(char|short)\b") +BARE_RECOVERY_MARKER_PATTERN = re.compile( + r"//\s*(TODO|UNSOLVED|STRIPPED)\b(?:\s*[.:,-]*)?\s*$" +) SUSPICIOUS_MEMBER_PATTERN = re.compile( r"^(?:" r"_?pad(?:ding)?[0-9A-Fa-f_]*" @@ -405,6 +416,7 @@ def audit_forward_declarations( path: str, text: str, touched_lines: Optional[Set[int]] ) -> List[Finding]: findings: List[Finding] = [] + current_rel = relpath(path) for idx, line in enumerate(text.splitlines(), 1): if touched_lines is not None and idx not in touched_lines: continue @@ -413,7 +425,11 @@ def audit_forward_declarations( continue name = match.group(2) - headers = header_declaration_paths(name, path, idx) + headers = [ + header + for header in header_declaration_paths(name, path, idx) + if header != current_rel + ] if not headers: continue @@ -441,6 +457,16 @@ def audit_style_guide_rules( if touched_lines is not None and idx not in touched_lines: continue stripped = line.strip() + bare_recovery_marker_match = BARE_RECOVERY_MARKER_PATTERN.search(line) + if bare_recovery_marker_match is not None: + findings.append( + Finding( + path, + idx, + "INFO", + f"`// {bare_recovery_marker_match.group(1)}` has no context; add a short reason or remove the stale recovery marker", + ) + ) if stripped.startswith("//"): continue @@ -471,9 +497,35 @@ def audit_style_guide_rules( "use `nullptr` instead of `NULL`", ) ) + bare_presence_if_match = BARE_PRESENCE_IF_PATTERN.match(line) + if bare_presence_if_match is not None: + findings.append( + Finding( + path, + idx, + "WARN", + f"bare `#if {bare_presence_if_match.group(1)}` looks like a presence check; prefer `#ifdef {bare_presence_if_match.group(1)}` unless a numeric test is intentional", + ) + ) + narrow_type_match = RECOVERED_NARROW_UNSIGNED_PATTERN.search(line) + if ( + narrow_type_match is not None + and RECOVERED_LAYOUT_COMMENT_PATTERN.search(line) is not None + ): + preferred = "uint8" if narrow_type_match.group(1) == "char" else "uint16" + findings.append( + Finding( + path, + idx, + "INFO", + f"recovered layout member uses `{narrow_type_match.group(0)}`; prefer explicit-width `{preferred}` when the field width is known", + ) + ) if ext in HEADER_EXTS: - should_check_guard = touched_lines is None or any(line_no <= 8 for line_no in touched_lines) + should_check_guard = touched_lines is None or any( + line_no <= 12 for line_no in touched_lines + ) if should_check_guard: has_ifndef = HEADER_GUARD_IFNDEF_PATTERN.search(text) is not None has_define = HEADER_GUARD_DEFINE_PATTERN.search(text) is not None @@ -487,6 +539,20 @@ def audit_style_guide_rules( "header guard should use `#ifndef` / `#define` plus the `EA_PRAGMA_ONCE_SUPPORTED` `#pragma once` block", ) ) + pragma_ifdef_match = EA_PRAGMA_IFDEF_PATTERN.search(text) + if pragma_ifdef_match is not None: + pragma_ifdef_line = text[: pragma_ifdef_match.start()].count("\n") + 1 + for idx, line in enumerate(text.splitlines(), 1): + if line.strip().startswith("#include ") and idx < pragma_ifdef_line: + findings.append( + Finding( + path, + idx, + "WARN", + "header include appears before the `EA_PRAGMA_ONCE_SUPPORTED` block; keep the guard / pragma block ahead of includes", + ) + ) + break return findings diff --git a/tools/decomp-workflow.py b/tools/decomp-workflow.py index 84f2f83d5..78e7fee04 100644 --- a/tools/decomp-workflow.py +++ b/tools/decomp-workflow.py @@ -16,12 +16,15 @@ python tools/decomp-workflow.py function -u main/Speed/Indep/SourceLists/zCamera -f UpdateAll --no-source python tools/decomp-workflow.py diff -u main/Speed/Indep/SourceLists/zCamera -d UpdateAll --reloc-diffs all python tools/decomp-workflow.py dwarf -u main/Speed/Indep/SourceLists/zCamera -f UpdateAll + python tools/decomp-workflow.py dwarf-scan -u main/Speed/Indep/SourceLists/zCamera + python tools/decomp-workflow.py dwarf-scan -u main/Speed/Indep/SourceLists/zCamera --objdiff-status match python tools/decomp-workflow.py dwarf -u main/Speed/Indep/SourceLists/zAttribSys -f 'Attrib::Class::RemoveCollection(Attrib::Collection *)' --full-diff python tools/decomp-workflow.py verify -u main/Speed/Indep/SourceLists/zCamera -f UpdateAll python tools/decomp-workflow.py unit -u main/Speed/Indep/SourceLists/zCamera """ import argparse +import difflib import json import re import os @@ -45,6 +48,8 @@ make_abs, run_objdiff_json, ) +from lookup import _candidate_func_names, _sig_contains_name, read_text, split_functions +from split_dwarf_info import apply_umath_fixups SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) @@ -56,24 +61,51 @@ PS2_SYMBOLS = os.path.join(ROOT_DIR, "config", "SLES-53558-A124", "symbols.txt") GC_DWARF = os.path.join(ROOT_DIR, "symbols", "Dwarf") DEBUG_LINES = os.path.join(ROOT_DIR, "symbols", "debug_lines.txt") +X360_COMPILER_DIR = os.path.join(ROOT_DIR, "build", "compilers", "X360", "14.00.2110") +PS2_COMPILER_DIR = os.path.join(ROOT_DIR, "build", "compilers", "PS2", "ee-gcc2.9-991111") +MIPS_BINUTILS_DIR = os.path.join(ROOT_DIR, "build", "mips_binutils") DEFAULT_SMOKE_UNIT = "main/Speed/Indep/SourceLists/zCamera" DEBUG_SYMBOL_PROBE_MANGLED = "UpdateAll__6Cameraf" DEBUG_SYMBOL_PROBE_DEMANGLED = "Camera::UpdateAll(float)" DEBUG_SYMBOL_PROBE_GC_ADDR = "0x80065A84" REBUILT_DEBUG_LINE_RE = re.compile(r"^\s*([0-9A-Fa-f]+)\s*:") +DWARF_HEX_RE = re.compile(r"0x[0-9A-Fa-f]+") LOW_MATCH_PRIORITY_THRESHOLD = 60.0 VERY_LOW_MATCH_PRIORITY_THRESHOLD = 40.0 HIGH_MATCH_CLEANUP_THRESHOLD = 85.0 VERY_HIGH_MATCH_CLEANUP_THRESHOLD = 95.0 +FunctionBlock = Tuple[str, str, str, str] SHARED_ASSET_REQUIREMENTS = [ (os.path.join("build", "tools"), "downloaded tooling"), (os.path.join("orig", "GOWE69", "NFSMWRELEASE.ELF"), "GameCube original ELF"), + ( + os.path.join("orig", "EUROPEGERMILESTONE", "NfsMWEuropeGerMilestone.xex"), + "Xbox original XEX", + ), (os.path.join("orig", "SLES-53558-A124", "NFS.ELF"), "PS2 original ELF"), (os.path.join("symbols", "Dwarf"), "DWARF dump"), ] +PLATFORM_BUILD_REQUIREMENTS = [ + ( + "x360-compiler", + X360_COMPILER_DIR, + "missing (seed build/compilers in this worktree for Xbox builds)", + ), + ( + "ps2-compiler", + PS2_COMPILER_DIR, + "missing (seed build/compilers in this worktree for PS2 builds)", + ), + ( + "ps2-binutils", + MIPS_BINUTILS_DIR, + "missing (seed build/mips_binutils in this worktree for PS2 builds)", + ), +] + class WorkflowError(RuntimeError): pass @@ -292,6 +324,14 @@ def choose_objdiff_row(unit_name: str, function_name: str, reloc_diffs: str = "n return matches[0] +def resolve_exact_function_name( + unit_name: str, function_name: str, reloc_diffs: str = "none" +) -> str: + return str( + choose_objdiff_row(unit_name, function_name, reloc_diffs=reloc_diffs)["name"] + ) + + def load_dwarf_report( unit_name: str, function_name: str, @@ -307,6 +347,355 @@ def load_dwarf_report( raise WorkflowError(f"dwarf-compare.py returned invalid JSON: {e}") +def load_dwarf_blocks( + path: str, folder_mode: bool, apply_split_fixups_in_ram: bool = False +) -> List[FunctionBlock]: + if folder_mode: + text = read_text(os.path.join(path, "functions.nothpp")) + else: + text = read_text(path) + if apply_split_fixups_in_ram: + text = apply_umath_fixups(text) + return split_functions(text) + + +def find_dwarf_function_blocks( + funcs: Sequence[FunctionBlock], query: str +) -> List[FunctionBlock]: + candidates = _candidate_func_names(query) + exact_matches: List[FunctionBlock] = [] + fuzzy_matches: List[FunctionBlock] = [] + + for func in funcs: + sig_line = func[2] + if sig_line == query: + exact_matches.append(func) + elif any(_sig_contains_name(sig_line, candidate) for candidate in candidates): + fuzzy_matches.append(func) + + if exact_matches: + return exact_matches + return fuzzy_matches + + +def choose_dwarf_function_block( + funcs: Sequence[FunctionBlock], query: str, label: str +) -> FunctionBlock: + matches = find_dwarf_function_blocks(funcs, query) + if not matches: + raise WorkflowError(f"{label}: function '{query}' not found.") + if len(matches) > 1: + preview = "\n".join(f" - {match[2]}" for match in matches[:8]) + extra = "" + if len(matches) > 8: + extra = f"\n ... {len(matches) - 8} more" + raise WorkflowError( + f"{label}: function query '{query}' matched multiple DWARF blocks.\n" + f"Use a more specific function name.\n{preview}{extra}" + ) + return matches[0] + + +def normalize_dwarf_line(line: str) -> str: + stripped = line.rstrip("\n").rstrip() + if stripped.startswith("// Range:"): + return "// Range: " + return DWARF_HEX_RE.sub("0xADDR", stripped) + + +def normalize_dwarf_block(block: str) -> List[str]: + return [normalize_dwarf_line(line) for line in block.splitlines()] + + +def count_dwarf_opcodes( + opcodes: Sequence[Tuple[str, int, int, int, int]] +) -> Dict[str, int]: + matching = 0 + original_only = 0 + rebuilt_only = 0 + changed_groups = 0 + for tag, i1, i2, j1, j2 in opcodes: + if tag == "equal": + matching += i2 - i1 + continue + changed_groups += 1 + if tag in ("replace", "delete"): + original_only += i2 - i1 + if tag in ("replace", "insert"): + rebuilt_only += j2 - j1 + return { + "matching_lines": matching, + "original_only_lines": original_only, + "rebuilt_only_lines": rebuilt_only, + "changed_groups": changed_groups, + } + + +def build_dwarf_scan_row( + row: Dict[str, Any], + original_funcs: Sequence[FunctionBlock], + rebuilt_funcs: Sequence[FunctionBlock], +) -> Dict[str, Any]: + function_name = str(row["name"]) + result: Dict[str, Any] = { + "function": function_name, + "symbol_name": row["symbol_name"], + "objdiff_status": row["status"], + "objdiff_match_percent": row["match_percent"], + "unmatched_bytes_est": row["unmatched_bytes_est"], + "size": row["size"], + } + + try: + original_block = choose_dwarf_function_block( + original_funcs, function_name, "original DWARF" + ) + rebuilt_block = choose_dwarf_function_block( + rebuilt_funcs, function_name, "rebuilt DWARF" + ) + original_lines = normalize_dwarf_block(original_block[3]) + rebuilt_lines = normalize_dwarf_block(rebuilt_block[3]) + matcher = difflib.SequenceMatcher(a=original_lines, b=rebuilt_lines) + counts = count_dwarf_opcodes(matcher.get_opcodes()) + total_lines = max(len(original_lines), len(rebuilt_lines), 1) + result.update( + { + "dwarf_status": "exact" + if original_lines == rebuilt_lines + else "mismatch", + "dwarf_match_percent": 100.0 * counts["matching_lines"] / total_lines, + "changed_groups": counts["changed_groups"], + "matching_lines": counts["matching_lines"], + "total_lines": total_lines, + "original_line_count": len(original_lines), + "rebuilt_line_count": len(rebuilt_lines), + "signature_match": normalize_dwarf_line(original_block[2]) + == normalize_dwarf_line(rebuilt_block[2]), + } + ) + except WorkflowError as e: + result.update( + { + "dwarf_status": "error", + "dwarf_match_percent": None, + "changed_groups": None, + "matching_lines": None, + "total_lines": None, + "original_line_count": None, + "rebuilt_line_count": None, + "signature_match": None, + "error": str(e), + } + ) + return result + + +def filter_dwarf_scan_rows( + rows: Sequence[Dict[str, Any]], dwarf_status: str +) -> List[Dict[str, Any]]: + if dwarf_status == "all": + return list(rows) + if dwarf_status == "problem": + return [row for row in rows if row["dwarf_status"] in ("mismatch", "error")] + return [row for row in rows if row["dwarf_status"] == dwarf_status] + + +def filter_dwarf_signature_rows( + rows: Sequence[Dict[str, Any]], signature_status: str +) -> List[Dict[str, Any]]: + if signature_status == "all": + return list(rows) + want_match = signature_status == "match" + return [ + row + for row in rows + if row.get("signature_match") is not None + and bool(row["signature_match"]) == want_match + ] + + +def sort_dwarf_scan_rows(rows: List[Dict[str, Any]]) -> None: + status_rank = {"error": 0, "mismatch": 1, "exact": 2} + rows.sort( + key=lambda row: ( + status_rank.get(str(row["dwarf_status"]), 3), + row["dwarf_match_percent"] + if row["dwarf_match_percent"] is not None + else -1.0, + 0 + if row.get("signature_match") is True + else 1 + if row.get("signature_match") is False + else 2, + -(row["changed_groups"] or 0), + -(row["unmatched_bytes_est"] or 0), + row["objdiff_match_percent"] + if row["objdiff_match_percent"] is not None + else -1.0, + row["function"].lower(), + ) + ) + + +def command_dwarf_scan(args: argparse.Namespace) -> None: + ensure_decomp_prereqs() + if not args.json: + print_section(f"DWARF Scan: {args.unit}") + ensure_shared_unit_output(args.unit) + + rebuilt_dwarf_path = ( + os.path.abspath(args.rebuilt_dwarf_file) if args.rebuilt_dwarf_file else None + ) + cleanup_rebuilt_dwarf = False + try: + if not rebuilt_dwarf_path: + rebuilt_dwarf_path = dtk_dwarf_dump(get_unit_build_output(args.unit)) + cleanup_rebuilt_dwarf = True + + data = run_objdiff_json( + OBJDIFF_CLI, + args.unit, + reloc_diffs=args.reloc_diffs, + root_dir=ROOT_DIR, + ) + rows = [ + row + for row in build_objdiff_symbol_rows(data) + if row["type"] == "function" and row["side"] == "left" + ] + if args.objdiff_status != "all": + rows = [row for row in rows if row["status"] == args.objdiff_status] + if args.search: + rows = [ + row + for row in rows + if fuzzy_match(args.search, row["name"]) + or fuzzy_match(args.search, row["symbol_name"]) + ] + if not rows: + raise WorkflowError("No functions match the given filters.") + + original_funcs = load_dwarf_blocks(GC_DWARF, folder_mode=True) + rebuilt_funcs = load_dwarf_blocks( + rebuilt_dwarf_path, folder_mode=False, apply_split_fixups_in_ram=True + ) + scan_rows = [ + build_dwarf_scan_row(row, original_funcs, rebuilt_funcs) for row in rows + ] + + summary = { + "scanned_functions": len(scan_rows), + "exact_functions": sum( + 1 for row in scan_rows if row["dwarf_status"] == "exact" + ), + "mismatch_functions": sum( + 1 for row in scan_rows if row["dwarf_status"] == "mismatch" + ), + "error_functions": sum( + 1 for row in scan_rows if row["dwarf_status"] == "error" + ), + "byte_matched_dwarf_problems": sum( + 1 + for row in scan_rows + if row["objdiff_status"] == "match" + and row["dwarf_status"] in ("mismatch", "error") + ), + "signature_mismatch_functions": sum( + 1 for row in scan_rows if row.get("signature_match") is False + ), + } + + filtered_rows = filter_dwarf_scan_rows(scan_rows, args.dwarf_status) + filtered_rows = filter_dwarf_signature_rows( + filtered_rows, args.signature_status + ) + sort_dwarf_scan_rows(filtered_rows) + if args.limit is not None: + filtered_rows = filtered_rows[: args.limit] + + if args.json: + print( + json.dumps( + { + "unit": args.unit, + "summary": summary, + "rows": filtered_rows, + }, + indent=2, + ) + ) + return + + print( + f"Scanned {summary['scanned_functions']} function(s): " + f"{summary['exact_functions']} exact, " + f"{summary['mismatch_functions']} mismatched, " + f"{summary['error_functions']} errors." + ) + print( + "Byte-matched but DWARF-problem functions: " + f"{summary['byte_matched_dwarf_problems']}" + ) + print( + "Signature-mismatch functions: " + f"{summary['signature_mismatch_functions']}" + ) + + if not filtered_rows: + print("No functions match the given filters.") + return + + print() + print( + f"{'DSTAT':<8} {'DWARF':>7} {'SIG':>3} {'CHG':>4} {'OBJ':>7} {'OSTAT':<10} {'UNM':>6} FUNCTION" + ) + print("-" * 120) + for row in filtered_rows: + dwarf_percent = ( + f"{row['dwarf_match_percent']:.1f}%" + if row["dwarf_match_percent"] is not None + else "ERR" + ) + objdiff_percent = ( + f"{row['objdiff_match_percent']:.1f}%" + if row["objdiff_match_percent"] is not None + else "-" + ) + changed_groups = ( + str(row["changed_groups"]) if row["changed_groups"] is not None else "-" + ) + signature_state = ( + "yes" + if row.get("signature_match") is True + else "no" + if row.get("signature_match") is False + else "-" + ) + print( + f"{row['dwarf_status']:<8} {dwarf_percent:>7} {signature_state:>3} {changed_groups:>4} " + f"{objdiff_percent:>7} {row['objdiff_status']:<10} " + f"{row['unmatched_bytes_est']:>5}B {row['function']}" + ) + if args.show_errors and row.get("error"): + first_line = str(row["error"]).splitlines()[0] + print(f" error: {first_line}") + + print() + print( + "Tip: focus matched-byte functions first with " + "`python tools/decomp-workflow.py dwarf-scan " + f"-u {shlex.quote(args.unit)} --objdiff-status match`" + ) + if summary["signature_mismatch_functions"]: + print( + "Tip: add `--signature-status match` to focus body/local DWARF mismatches " + "instead of signature-only trouble." + ) + finally: + if cleanup_rebuilt_dwarf: + maybe_remove(rebuilt_dwarf_path) + + def lookup_symbol_address(symbols_file: str, mangled_name: str) -> Optional[str]: if not os.path.exists(symbols_file): return None @@ -395,6 +784,14 @@ def build_shared_unit_cached(unit: str) -> str: except WorkflowError as e: report(False, "ghidra", str(e)) + print_section("Platform Build Inputs") + for label, abs_path, missing_detail in PLATFORM_BUILD_REQUIREMENTS: + report( + os.path.exists(abs_path), + label, + describe_path(abs_path) if os.path.exists(abs_path) else missing_detail, + ) + print_section("Debug Symbol Checks") try: gc_addr = lookup_symbol_address(GC_SYMBOLS, DEBUG_SYMBOL_PROBE_MANGLED) @@ -642,6 +1039,9 @@ def command_function(args: argparse.Namespace) -> None: ensure_decomp_prereqs() print_section(f"Function Workflow: {args.function}") ensure_shared_unit_output(args.unit) + resolved_function_name = resolve_exact_function_name( + args.unit, args.function, reloc_diffs=args.reloc_diffs + ) cmd = python_tool("decomp-context.py", "-u", args.unit, "-f", args.function) if args.no_source: cmd.append("--no-source") @@ -661,9 +1061,14 @@ def command_function(args: argparse.Namespace) -> None: print(flush=True) print( "Required completion check: python tools/decomp-workflow.py verify " - f"-u {shlex.quote(args.unit)} -f {shlex.quote(args.function)}", + f"-u {shlex.quote(args.unit)} -f {shlex.quote(resolved_function_name)}", flush=True, ) + if resolved_function_name != args.function: + print( + f"(Resolved exact function name for DWARF-safe follow-up: {resolved_function_name})", + flush=True, + ) def command_unit(args: argparse.Namespace) -> None: @@ -810,8 +1215,11 @@ def command_dwarf(args: argparse.Namespace) -> None: print_section(f"DWARF Workflow: {args.unit} / {args.function}") if not args.rebuilt_dwarf_file: ensure_shared_unit_output(args.unit) + resolved_function_name = resolve_exact_function_name(args.unit, args.function) - cmd: List[str] = python_tool("dwarf-compare.py", "-u", args.unit, "-f", args.function) + cmd: List[str] = python_tool( + "dwarf-compare.py", "-u", args.unit, "-f", resolved_function_name + ) if args.summary: cmd.append("--summary") if args.json: @@ -833,18 +1241,24 @@ def command_verify(args: argparse.Namespace) -> None: ensure_shared_unit_output(args.unit) objdiff_row = choose_objdiff_row(args.unit, args.function, reloc_diffs=args.reloc_diffs) - dwarf_report = load_dwarf_report( - args.unit, - args.function, - rebuilt_dwarf_file=args.rebuilt_dwarf_file, - ) + resolved_function_name = str(objdiff_row["name"]) + dwarf_load_error: Optional[str] = None + dwarf_report: Optional[Dict[str, Any]] = None + try: + dwarf_report = load_dwarf_report( + args.unit, + resolved_function_name, + rebuilt_dwarf_file=args.rebuilt_dwarf_file, + ) + except WorkflowError as e: + dwarf_load_error = str(e) objdiff_exact = ( objdiff_row["status"] == "match" and objdiff_row["match_percent"] is not None and float(objdiff_row["match_percent"]) >= 100.0 ) - dwarf_exact = bool(dwarf_report["normalized_exact_match"]) + dwarf_exact = bool(dwarf_report["normalized_exact_match"]) if dwarf_report else False overall_ok = objdiff_exact and dwarf_exact objdiff_percent = ( @@ -852,34 +1266,56 @@ def command_verify(args: argparse.Namespace) -> None: if objdiff_row["match_percent"] is not None else "-" ) - dwarf_percent = f"{float(dwarf_report['match_percent']):.1f}%" + dwarf_percent = ( + f"{float(dwarf_report['match_percent']):.1f}%" if dwarf_report else "-" + ) print( f"objdiff: {'PASS' if objdiff_exact else 'FAIL'} | " f"{objdiff_percent} | status={objdiff_row['status']} | " f"unmatched~{objdiff_row['unmatched_bytes_est']}B" ) - print( - f"DWARF: {'PASS' if dwarf_exact else 'FAIL'} | " - f"{dwarf_percent} | normalized exact={'yes' if dwarf_exact else 'no'} | " - f"change groups={dwarf_report['changed_groups']}" - ) + if dwarf_report: + print( + f"DWARF: {'PASS' if dwarf_exact else 'FAIL'} | " + f"{dwarf_percent} | normalized exact={'yes' if dwarf_exact else 'no'} | " + f"change groups={dwarf_report['changed_groups']}" + ) + else: + print("DWARF: FAIL | unable to compare rebuilt vs original DWARF", flush=True) + if resolved_function_name != args.function: + print(f"Resolved DWARF symbol: {resolved_function_name}") print(f"Overall: {'PASS' if overall_ok else 'FAIL'}") print("Done means both objdiff and normalized DWARF are exact for the function.") if overall_ok: return + if dwarf_load_error: + print(flush=True) + print("DWARF compare could not complete:", flush=True) + print(dwarf_load_error, flush=True) + if ( + objdiff_row["status"] == "missing" + and "rebuilt DWARF: function" in dwarf_load_error + and "not found" in dwarf_load_error + ): + print( + "Hint: the rebuilt object does not contain this function yet. " + "Implement the function or fix its ownership/signature first, then rerun verify.", + flush=True, + ) + print(flush=True) print("Follow-up commands:", flush=True) print( f" python tools/decomp-workflow.py diff -u {shlex.quote(args.unit)} " - f"-d {shlex.quote(args.function)}", + f"-d {shlex.quote(resolved_function_name)}", flush=True, ) print( f" python tools/decomp-workflow.py dwarf -u {shlex.quote(args.unit)} " - f"-f {shlex.quote(args.function)}", + f"-f {shlex.quote(resolved_function_name)}", flush=True, ) raise WorkflowError( @@ -897,7 +1333,7 @@ def build_parser() -> argparse.ArgumentParser: health = subparsers.add_parser( "health", - help="Check whether the current worktree is ready for GC and PS2 decomp work", + help="Check whether the current worktree is ready for GC, Xbox, and PS2 work", ) health.add_argument( "--full", @@ -941,7 +1377,12 @@ def build_parser() -> argparse.ArgumentParser: help="Run decomp-context.py for one function", ) function.add_argument("-u", "--unit", required=True, help="Translation unit name") - function.add_argument("-f", "--function", required=True, help="Function name to inspect") + function.add_argument( + "-f", + "--function", + required=True, + help="Function name to inspect (full name or a unique substring)", + ) function.add_argument( "--no-source", action="store_true", @@ -1086,7 +1527,12 @@ def build_parser() -> argparse.ArgumentParser: help="Compare original vs rebuilt DWARF for one function", ) dwarf.add_argument("-u", "--unit", required=True, help="Translation unit name") - dwarf.add_argument("-f", "--function", required=True, help="Function name to compare") + dwarf.add_argument( + "-f", + "--function", + required=True, + help="Function name to compare (full name or a unique substring)", + ) dwarf.add_argument( "--summary", action="store_true", @@ -1122,12 +1568,72 @@ def build_parser() -> argparse.ArgumentParser: ) dwarf.set_defaults(func=command_dwarf) + dwarf_scan = subparsers.add_parser( + "dwarf-scan", + help="Scan one translation unit and rank per-function DWARF problem areas", + ) + dwarf_scan.add_argument("-u", "--unit", required=True, help="Translation unit name") + dwarf_scan.add_argument( + "--search", + help="Only include functions whose name or symbol contains this text", + ) + dwarf_scan.add_argument( + "--objdiff-status", + choices=["all", "match", "nonmatching", "missing"], + default="all", + help="Filter functions by objdiff status before scanning (default: all)", + ) + dwarf_scan.add_argument( + "--dwarf-status", + choices=["all", "problem", "exact", "mismatch", "error"], + default="problem", + help="Filter scan results by DWARF outcome after scanning (default: problem)", + ) + dwarf_scan.add_argument( + "--signature-status", + choices=["all", "match", "mismatch"], + default="all", + help="Filter scan results by whether the DWARF signature already matches (default: all)", + ) + dwarf_scan.add_argument( + "--limit", + type=int, + default=20, + help="Maximum rows to print after sorting (default: 20)", + ) + dwarf_scan.add_argument( + "--json", + action="store_true", + help="Print the scan summary and rows as JSON", + ) + dwarf_scan.add_argument( + "--show-errors", + action="store_true", + help="Print one-line error details under rows that could not be compared", + ) + dwarf_scan.add_argument( + "--reloc-diffs", + choices=RELOC_DIFF_CHOICES, + default="none", + help="Pass through objdiff relocation diff mode when loading unit symbols", + ) + dwarf_scan.add_argument( + "--rebuilt-dwarf-file", + help="Use an existing rebuilt DWARF dump instead of dumping the unit object", + ) + dwarf_scan.set_defaults(func=command_dwarf_scan) + verify = subparsers.add_parser( "verify", help="Fail unless one function matches in both objdiff and DWARF", ) verify.add_argument("-u", "--unit", required=True, help="Translation unit name") - verify.add_argument("-f", "--function", required=True, help="Function name to verify") + verify.add_argument( + "-f", + "--function", + required=True, + help="Function name to verify (full name or a unique substring)", + ) verify.add_argument( "--reloc-diffs", choices=RELOC_DIFF_CHOICES, diff --git a/tools/share_worktree_assets.py b/tools/share_worktree_assets.py index 374d168e5..4bfdcf055 100644 --- a/tools/share_worktree_assets.py +++ b/tools/share_worktree_assets.py @@ -13,6 +13,8 @@ python tools/share_worktree_assets.py status --all python tools/share_worktree_assets.py link --all python tools/share_worktree_assets.py bootstrap + python tools/share_worktree_assets.py bootstrap --version EUROPEGERMILESTONE --xbox-xex /path/to/NfsMWEuropeGerMilestone.xex + python tools/share_worktree_assets.py bootstrap --version SLES-53558-A124 --ps2-toolchain-zip /path/to/PS2.zip """ import argparse @@ -21,6 +23,7 @@ import shutil import subprocess import sys +import zipfile from dataclasses import dataclass from typing import Dict, Iterable, List, Optional, Set @@ -39,11 +42,16 @@ class AssetSpec: FIXED_ASSETS = ( AssetSpec(os.path.join("orig", "GOWE69", "NFSMWRELEASE.ELF"), "file"), + AssetSpec( + os.path.join("orig", "EUROPEGERMILESTONE", "NfsMWEuropeGerMilestone.xex"), + "file", + ), AssetSpec(os.path.join("orig", "SLES-53558-A124", "NFS.ELF"), "file"), AssetSpec(os.path.join("orig", "SLES-53558-A124", "NFS.MAP"), "file"), AssetSpec(os.path.join("build", "tools"), "dir"), AssetSpec(os.path.join("build", "compilers"), "dir"), AssetSpec(os.path.join("build", "ppc_binutils"), "dir"), + AssetSpec(os.path.join("build", "mips_binutils"), "dir"), ) @@ -120,6 +128,85 @@ def ensure_parent(path: str) -> None: os.makedirs(parent, exist_ok=True) +def seed_shared_file(shared_path: str, source_path: str, description: str) -> None: + ensure_parent(shared_path) + if os.path.isfile(shared_path) and not os.path.islink(shared_path): + if not filecmp.cmp(shared_path, source_path, shallow=False): + raise RuntimeError( + f"Refusing to replace existing shared {description}: {shared_path}" + ) + return + if os.path.islink(shared_path): + if not same_symlink(shared_path, source_path): + raise RuntimeError( + f"Refusing to replace existing shared {description}: {shared_path}" + ) + os.unlink(shared_path) + elif lexists(shared_path): + raise RuntimeError( + f"Refusing to replace existing shared {description}: {shared_path}" + ) + shutil.copy2(source_path, shared_path) + + +def extract_zip_into(zip_path: str, output_dir: str) -> None: + os.makedirs(output_dir, exist_ok=True) + with zipfile.ZipFile(zip_path) as archive: + for member in archive.infolist(): + member_name = member.filename.rstrip("/") + if not member_name: + continue + + output_path = os.path.join(output_dir, *member_name.split("/")) + if member.is_dir(): + os.makedirs(output_path, exist_ok=True) + continue + + ensure_parent(output_path) + if os.path.exists(output_path): + continue + + with archive.open(member) as src, open(output_path, "wb") as dst: + shutil.copyfileobj(src, dst) + os.chmod(output_path, 0o755) + + +def seed_bootstrap_assets( + shared_root: str, xbox_xex: Optional[str], ps2_toolchain_zip: Optional[str] +) -> None: + if xbox_xex: + if not os.path.isfile(xbox_xex): + raise RuntimeError(f"Xbox XEX not found: {xbox_xex}") + seed_shared_file( + os.path.join( + shared_root, + "orig", + "EUROPEGERMILESTONE", + "NfsMWEuropeGerMilestone.xex", + ), + xbox_xex, + "Xbox XEX", + ) + + if ps2_toolchain_zip: + if not os.path.isfile(ps2_toolchain_zip): + raise RuntimeError(f"PS2 toolchain zip not found: {ps2_toolchain_zip}") + extract_zip_into(ps2_toolchain_zip, os.path.join(shared_root, "build", "compilers")) + expected_ee_gcc = os.path.join( + shared_root, + "build", + "compilers", + "PS2", + "ee-gcc2.9-991111", + "bin", + "ee-gcc.exe", + ) + if not os.path.isfile(expected_ee_gcc): + raise RuntimeError( + "PS2 toolchain zip did not produce build/compilers/PS2/ee-gcc2.9-991111/bin/ee-gcc.exe" + ) + + def merge_file(src: str, dst: str, relpath: str) -> None: ensure_parent(dst) if not os.path.exists(dst): @@ -342,18 +429,23 @@ def bootstrap_generated_files(worktree: str, version: str) -> None: objdiff_json = os.path.join(worktree, "objdiff.json") compile_commands = os.path.join(worktree, "compile_commands.json") config_target = os.path.join("build", version, "config.json") + configure_cmd = [sys.executable, "configure.py", "--version", version] - print(f"{worktree}: running configure.py") - run_command([sys.executable, "configure.py"], worktree, "configure.py") + print(f"{worktree}: running {' '.join(configure_cmd)}") + run_command(configure_cmd, worktree, "configure.py") if not os.path.isfile(build_ninja): raise RuntimeError(f"{worktree}: configure.py did not create build.ninja") - if not os.path.isfile(objdiff_json) or not os.path.isfile(compile_commands): + if ( + not os.path.isfile(config_target) + or not os.path.isfile(objdiff_json) + or not os.path.isfile(compile_commands) + ): print(f"{worktree}: generating {config_target} for local objdiff metadata") run_command(["ninja", config_target], worktree, f"ninja {config_target}") - print(f"{worktree}: rerunning configure.py") - run_command([sys.executable, "configure.py"], worktree, "configure.py") + print(f"{worktree}: rerunning {' '.join(configure_cmd)}") + run_command(configure_cmd, worktree, "configure.py") missing = [] if not os.path.isfile(objdiff_json): @@ -373,7 +465,10 @@ def bootstrap_worktrees( version: str, run_health: bool, smoke_build: Optional[str], + xbox_xex: Optional[str], + ps2_toolchain_zip: Optional[str], ) -> int: + seed_bootstrap_assets(shared_root, xbox_xex, ps2_toolchain_zip) link_assets(target_worktrees, seed_worktrees, shared_root) for worktree in target_worktrees: bootstrap_generated_files(worktree, version) @@ -418,6 +513,16 @@ def main() -> int: metavar="UNIT", help="Also run `decomp-workflow.py health --smoke-build UNIT` after bootstrap.", ) + parser.add_argument( + "--xbox-xex", + metavar="PATH", + help="Seed the shared Xbox XEX from a local file before linking/bootstrap.", + ) + parser.add_argument( + "--ps2-toolchain-zip", + metavar="PATH", + help="Extract a local PS2 EE-GCC zip into shared build/compilers before linking/bootstrap.", + ) args = parser.parse_args() common_dir = git_common_dir(root_dir) @@ -437,6 +542,8 @@ def main() -> int: args.version, args.health, args.smoke_build, + args.xbox_xex, + args.ps2_toolchain_zip, ) except RuntimeError as e: print(f"Error: {e}", file=sys.stderr)