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..15f4779af 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,27 @@ 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. + +### 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 +199,10 @@ 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. 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..771ecd9a7 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,20 @@ 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. + +### 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/config/GOWE69/config.yml b/config/GOWE69/config.yml index 525eaaf36..209733637 100644 --- a/config/GOWE69/config.yml +++ b/config/GOWE69/config.yml @@ -34,3 +34,13 @@ block_relocations: - source: .text:0x80047c1c end: .text:0x80047c28 + +# LoaderParameterMaps: chunk ID constant, not a relocation +- source: .text:0x802E63EC + end: .text:0x802E63F0 +- source: .text:0x802E63F4 + end: .text:0x802E63F8 + +# UnloaderParameterMaps: chunk ID constant, not a relocation +- source: .text:0x802E650C + end: .text:0x802E6514 diff --git a/src/Speed/Indep/Libs/Support/Miscellaneous/CARP.h b/src/Speed/Indep/Libs/Support/Miscellaneous/CARP.h index 57c0e79c8..08bbec3c5 100644 --- a/src/Speed/Indep/Libs/Support/Miscellaneous/CARP.h +++ b/src/Speed/Indep/Libs/Support/Miscellaneous/CARP.h @@ -5,4 +5,10 @@ #pragma once #endif +struct UGroup; + +namespace CARP { +unsigned int ResolveTagReferences(const UGroup *g, unsigned int deltaAddress); +} + #endif diff --git a/src/Speed/Indep/Libs/Support/Utility/UBitArray.h b/src/Speed/Indep/Libs/Support/Utility/UBitArray.h new file mode 100644 index 000000000..87094efde --- /dev/null +++ b/src/Speed/Indep/Libs/Support/Utility/UBitArray.h @@ -0,0 +1,56 @@ +#ifndef UTILITY_UBITARRAY_H +#define UTILITY_UBITARRAY_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +template +struct BitArray { + static const int kBitsPerWord = sizeof(T) * 8; + static const int kWordCount = (N + kBitsPerWord - 1) / kBitsPerWord; + + T Words[kWordCount]; + + BitArray() { + for (int i = 0; i < kWordCount; i++) { + Words[i] = 0; + } + } + + const BitArray &operator=(const BitArray &src) { + for (unsigned int i = 0; i < static_cast(kWordCount); i++) { + Words[i] = src.Words[i]; + } + return *this; + } + + bool operator!=(const BitArray &other) const { + for (int i = 0; i < kWordCount; i++) { + if (Words[i] != other.Words[i]) { + return true; + } + } + return false; + } + + bool Test(unsigned int index) const { + return (Words[index / kBitsPerWord] >> (index % kBitsPerWord)) & 1; + } + + void Set(unsigned int index) { + Words[index / kBitsPerWord] |= static_cast(1) << (index % kBitsPerWord); + } + + void Clear(unsigned int index) { + Words[index / kBitsPerWord] &= ~(static_cast(1) << (index % kBitsPerWord)); + } + + void Clear() { + for (int i = 0; i < kWordCount; i++) { + Words[i] = 0; + } + } +}; + +#endif diff --git a/src/Speed/Indep/Libs/Support/Utility/UCOM.h b/src/Speed/Indep/Libs/Support/Utility/UCOM.h index 511c88f54..507463d34 100644 --- a/src/Speed/Indep/Libs/Support/Utility/UCOM.h +++ b/src/Speed/Indep/Libs/Support/Utility/UCOM.h @@ -139,6 +139,7 @@ template class Factory { Prototype(const _PRODUCT_SIGNATURE &classsig, _CONSTRUCTOR constructor) { mSignature = classsig; mConstructor = constructor; + mTail = mHead; mHead = this; } diff --git a/src/Speed/Indep/Libs/Support/Utility/UGroup.hpp b/src/Speed/Indep/Libs/Support/Utility/UGroup.hpp index d64b05b67..f1f27c69d 100644 --- a/src/Speed/Indep/Libs/Support/Utility/UGroup.hpp +++ b/src/Speed/Indep/Libs/Support/Utility/UGroup.hpp @@ -5,6 +5,18 @@ #pragma once #endif +inline unsigned int UDataGroupTag(unsigned int type, unsigned int index) { + return (type << 16) | index; +} + +inline unsigned int UDataGroupType(unsigned int tag) { + return tag >> 16; +} + +inline unsigned int UDataGroupIndex(unsigned int tag) { + return tag & 0xFFFF; +} + struct TagStruct { unsigned int tag; // offset 0x0, size 0x4 unsigned int data[3]; // offset 0x4, size 0xC @@ -24,6 +36,20 @@ class UData { void * fPointer; // offset 0x0, size 0x4 unsigned int fOffset; // offset 0x0, size 0x4 }; // offset 0xC, size 0x4 + + unsigned int DataCount() const { + return fCount; + } + + const void *GetDataConst() const { + const void *data; + if (fEmbedded) { + data = reinterpret_cast(this) + fOffset; + } else { + data = fPointer; + } + return data; + } }; struct UGroup { @@ -51,14 +77,39 @@ struct UGroup { void * fPointer; // offset 0x0, size 0x4 unsigned int fOffset; // offset 0x0, size 0x4 }; // offset 0xC, size 0x4 -}; -inline unsigned int UDataGroupType(unsigned int tag) { - return tag >> 16; -} + static const UGroup *Deserialize(unsigned int numParts, const unsigned int *dataLengths, const void **serializedData, unsigned int deltaAddress); + unsigned int GroupCountType(unsigned int type) const; + const UGroup *GroupLocateFirst(unsigned int type, unsigned int baseIndex, unsigned int maxIndex) const; + const UGroup *GroupLocateTag(unsigned int typeIndexTag) const; + unsigned int DataCountType(unsigned int type) const; + const UData *DataLocateFirst(unsigned int type, unsigned int baseIndex, unsigned int maxIndex) const; + const UData *DataLocateTag(unsigned int typeIndexTag) const; + const void *GetArray() const; -inline unsigned int UDataGroupIndex(unsigned int tag) { - return tag & 0xFFFF; -} + const UGroup *GroupBegin() const { + return static_cast< const UGroup * >(GetArray()); + } + + const UGroup *GroupEnd() const { + return GroupBegin() + fGroupCount; + } + + const UData *DataBegin() const { + return reinterpret_cast< const UData * >(GroupEnd()); + } + + const UData *DataEnd() const { + return DataBegin() + fDataCount; + } + + const UGroup *GroupLocate(unsigned int type, unsigned int index) const { + return GroupLocateTag(UDataGroupTag(type, index)); + } + + const UData *DataLocate(unsigned int type, unsigned int index) const { + return DataLocateTag(UDataGroupTag(type, index)); + } +}; #endif diff --git a/src/Speed/Indep/Libs/Support/Utility/UListable.h b/src/Speed/Indep/Libs/Support/Utility/UListable.h index c307298c5..af7adea1d 100644 --- a/src/Speed/Indep/Libs/Support/Utility/UListable.h +++ b/src/Speed/Indep/Libs/Support/Utility/UListable.h @@ -21,6 +21,7 @@ template class Listable { typedef value_type *pointer; typedef value_type const *const_pointer; +#if MILESTONE_OPT class List : public FixedVector { public: typedef T value_type; @@ -28,11 +29,25 @@ template class Listable { typedef value_type const *const_pointer; // List(const List &); - List(); - virtual ~List(); + List() {} + ~List() override {} // List &operator=(List &); }; +#else + class List : public _Storage { + public: + typedef T value_type; + typedef value_type *pointer; + typedef value_type const *const_pointer; + + // List(const List &); + List() { this->reserve(U); } + ~List() override {} + + // List &operator=(List &); + }; +#endif typedef void (*ForEachFunc)(pointer); typedef bool (*ComparisonFunc)(pointer, pointer); diff --git a/src/Speed/Indep/Libs/Support/Utility/UMath.h b/src/Speed/Indep/Libs/Support/Utility/UMath.h index 76c15b209..3e442ef33 100644 --- a/src/Speed/Indep/Libs/Support/Utility/UMath.h +++ b/src/Speed/Indep/Libs/Support/Utility/UMath.h @@ -36,6 +36,16 @@ inline float Cosr(const float a) { return VU0_Cos(RAD2ANGLE(a) * (float)M_TWOPI); } +#ifndef EA_PLATFORM_PLAYSTATION2 +inline float ASinr(const float x) { + return ANGLE2RAD(VU0_ASin(x)); +} +#else +inline float ASinr(const float x) { + return asinf(x); +} +#endif + void BuildRotate(Matrix4 &m, float r, float x, float y, float z); float Ceil(const float x); @@ -57,7 +67,11 @@ inline float DistanceSquare(const Vector3 &a, const Vector3 &b) { } inline float DistanceSquarexz(const Vector3 &a, const Vector3 &b) { +#ifdef EA_PLATFORM_PLAYSTATION2 return VU0_v3distancesquare(a, b); +#else + return VU0_v3distancesquarexz(a, b); +#endif } inline void Clear(Vector3 &r) { @@ -172,6 +186,10 @@ inline void Add(const Vector3 &a, const Vector3 &b, Vector3 &r) { #endif } +inline void Add(const Vector4 &a, const Vector4 &b, Vector4 &r) { + VU0_v4add(a, b, r); +} + inline void Scale(const Vector3 &a, const Vector3 &b, Vector3 &r) { #ifdef EA_PLATFORM_XENON r.x = a.x * b.x; @@ -224,6 +242,11 @@ inline void ScaleAdd(const Vector4 &a, const float s, const Vector4 &b, Vector4 VU0_v4scaleadd(a, s, b, r); } +inline void ScaleAdd(const Vector2 &a, const float s, const Vector2 &b, Vector2 &r) { + r.x = a.x + s * b.x; + r.y = a.y + s * b.y; +} + inline void ScaleAddxyz(const Vector4 &a, const float s, const Vector4 &b, Vector4 &r) { VU0_v4scaleaddxyz(a, s, b, r); } @@ -249,6 +272,32 @@ inline void Subxyz(const Vector4 &a, const Vector4 &b, Vector4 &r) { VU0_v4subxyz(a, b, r); } +inline void Addxyz(const Vector4 &a, const Vector4 &b, Vector4 &r) { + VU0_v4addxyz(a, b, r); +} + +inline void Scalexyz(const Vector4 &a, const float s, Vector4 &r) { + VU0_v4scalexyz(a, s, r); +} + +inline void Scalexyz(const Vector4 &a, const Vector4 &b, Vector4 &r) { + VU0_v4scalexyz(a, b, r); +} + +inline void Negatexyz(Vector4 &r) { + VU0_v4negatexyz(r); +} + +inline float DistanceSquarexyz(const Vector4 &a, const Vector4 &b) { + return VU0_v4distancesquarexyz(a, b); +} + +inline float Distancexyz(const Vector4 &a, const Vector4 &b) { + Vector4 temp; + VU0_v4subxyz(a, b, temp); + return VU0_sqrt(VU0_v4lengthsquarexyz(temp)); +} + inline void SetYRot(Matrix4 &r, float a) { VU0_MATRIX4setyrot(r, a); } @@ -282,6 +331,10 @@ inline void Rotate(const Vector3 &a, const Matrix4 &m, Vector3 &r) { #endif } +inline void Rotate(const Vector4 &a, const Matrix4 &m, Vector4 &r) { + VU0_MATRIX3x4_vect4mult(a, m, r); +} + inline float Dot(const Vector3 &a, const Vector3 &b) { #ifdef EA_PLATFORM_XENON return a.x * b.x + a.y * b.y + a.z * b.z; @@ -294,6 +347,16 @@ inline float Dot(const Vector2 &a, const Vector2 &b) { return a.x * b.x + a.y * b.y; } +inline void Scale(Vector2 &r, const float s) { + r.x *= s; + r.y *= s; +} + +inline void Scale(const Vector2 &a, const float s, Vector2 &r) { + r.x = a.x * s; + r.y = a.y * s; +} + inline void Dot(const Vector3 &a, const Matrix4 &b, Vector3 &r) { #ifdef EA_PLATFORM_XENON r.x = Dot(a, UMath::Vector4To3(b.v0)); @@ -326,6 +389,7 @@ inline void UnitCross(const Vector3 &a, const Vector3 &b, Vector3 &r) { } #endif + inline float Normalize(Vector3 &r) { float m = VU0_v3length(r); if (m != 0.0f) { @@ -334,6 +398,14 @@ inline float Normalize(Vector3 &r) { return m; } +inline float Normalize(Vector4 &r) { + float m = VU0_v4length(r); + if (m != 0.0f) { + VU0_v4scale(r, 1.0f / m, r); + } + return m; +} + inline void Direction(const UMath::Vector3 &a, const UMath::Vector3 &b, UMath::Vector3 &r) { VU0_v3sub(a, b, r); VU0_v3unit(r, r); @@ -375,6 +447,16 @@ inline float Sqrt(const float f) { #endif } +inline float Normalize(Vector2 &r) { + float h = r.x * r.x + r.y * r.y; + float l = Sqrt(h); + float c = 1.0f / l; + r.x *= c; + r.y *= c; + float ret = l; + return ret; +} + inline float Length(const Vector3 &a) { #ifdef EA_PLATFORM_XENON return Sqrt(LengthSquare(a)); @@ -412,6 +494,12 @@ inline float Lerp(const float a, const float b, const float t) { return a + (b - a) * t; } +inline void Lerp(const Vector2 &a, const Vector2 &b, const float t, Vector2 &r) { + float u = 1.0f - t; + r.x = a.x * u + b.x * t; + r.y = a.y * u + b.y * t; +} + inline void Negate(Vector3 &r) { VU0_v3negate(r); } @@ -464,4 +552,49 @@ inline float Limit(const float a, const float l) { } // namespace UMath +struct UQuat : public UMath::Vector4 { + UQuat() { + *static_cast(this) = UMath::Vector4::kIdentity; + } + + UQuat(const UMath::Vector4 &From) { + x = From.x; + y = From.y; + z = From.z; + w = From.w; + } + + const UQuat &operator=(const UMath::Vector4 &From) { + x = From.x; + y = From.y; + z = From.z; + w = From.w; + return *this; + } + + void BuildDeltaAxis(const UMath::Vector3 &normal1, const UMath::Vector3 &normal2) { + const float angle = UMath::Dot(normal1, normal2); + if (angle > 0.999f) { + *this = UMath::Vector4::kIdentity; + return; + } + UMath::Vector3 axis; + UMath::Cross(normal1, normal2, axis); + if (angle < -0.999f) { + x = axis.x; + y = axis.y; + z = axis.z; + w = 0.0f; + UMath::Normalize(*static_cast(this)); + } else { + const float s = UMath::Sqrt(2.0f * (1.0f + angle)); + const float invs = 1.0f / s; + x = axis.x * invs; + y = axis.y * invs; + z = axis.z * invs; + w = s * 0.5f; + } + } +}; + #endif diff --git a/src/Speed/Indep/Libs/Support/Utility/USpline.h b/src/Speed/Indep/Libs/Support/Utility/USpline.h index 16adc292b..bd342c8b9 100644 --- a/src/Speed/Indep/Libs/Support/Utility/USpline.h +++ b/src/Speed/Indep/Libs/Support/Utility/USpline.h @@ -20,6 +20,14 @@ class USpline { OVERHAUSER_LINE = 0, }; + USpline(); + ~USpline(); + + void BuildSplineEx(const UMath::Vector3 &start, const UMath::Vector3 &startControl, const UMath::Vector3 &end, const UMath::Vector3 &endControl); + void EvaluateSpline(float t, UMath::Vector4 &result); + void EvaluateTangent(float t, UMath::Vector4 &tangent); + float EvaluateCurvatureXZ(float t); + static const UMath::Matrix4 &GetBasisMatrix(SplineType splineType); // total size: 0x6C diff --git a/src/Speed/Indep/Libs/Support/Utility/UTLFastVector.h b/src/Speed/Indep/Libs/Support/Utility/UTLFastVector.h new file mode 100644 index 000000000..0d55b08c5 --- /dev/null +++ b/src/Speed/Indep/Libs/Support/Utility/UTLFastVector.h @@ -0,0 +1 @@ +#include "Speed/Indep/Libs/Support/Utility/UTLVector.h" diff --git a/src/Speed/Indep/Libs/Support/Utility/UTLVector.h b/src/Speed/Indep/Libs/Support/Utility/UTLVector.h index 861aed70f..a4c1b7071 100644 --- a/src/Speed/Indep/Libs/Support/Utility/UTLVector.h +++ b/src/Speed/Indep/Libs/Support/Utility/UTLVector.h @@ -5,11 +5,16 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/FastMem.h" #include "Speed/Indep/Libs/Support/Utility/UMath.h" #include namespace UTL { +#if MILESTONE_OPT template class Vector { +#else +template class Vector { +#endif public: typedef T value_type; typedef value_type *pointer; @@ -59,6 +64,10 @@ template class Vector { return mBegin + mSize; } + reference operator[](size_type idx) { + return mBegin[idx]; + } + void push_back(value_type const &val) { if (size() >= capacity()) { reserve(GetGrowSize(size() + 1)); @@ -150,12 +159,11 @@ template class Vector { virtual void FreeVectorSpace(pointer buffer, size_type num) {} virtual size_type GetGrowSize(size_type minSize) const { - return UMath::Max(minSize, mCapacity + ((mCapacity + 1) >> 1)); // TODO is this right? + return UMath::Max(mCapacity + ((mCapacity + 1) >> 1), minSize); } - // Unfinished virtual size_type GetMaxCapacity() const { - return 0; + return 0x7FFFFFFF; } virtual void OnGrowRequest(size_type newSize) {} @@ -167,7 +175,11 @@ template class Vector { size_type mSize; // offset 0x8, size 0x4 }; +#if MILESTONE_OPT template class FixedVector : public Vector { +#else +template class FixedVector : public Vector { +#endif public: FixedVector() {} @@ -179,21 +191,18 @@ template class Fixed // TODO also put the typedefs here according to the dwarf? protected: - // Unfinished virtual std::size_t GetGrowSize(std::size_t minSize) const { - return 0; + return Size; } - // Unfinished virtual typename Vector::pointer AllocVectorSpace(std::size_t num, unsigned int alignment) { - return nullptr; + return reinterpret_cast::pointer>(mVectorSpace); } virtual void FreeVectorSpace(typename Vector::pointer buffer, std::size_t) {} - // Unfinished virtual std::size_t GetMaxCapacity() const { - return 0; + return Size; } private: @@ -201,6 +210,29 @@ template class Fixed int mVectorSpace[(sizeof(typename Vector::value_type) * Size) / sizeof(int)]; }; + +#if MILESTONE_OPT +template class FastVector : public Vector { +#else +template class FastVector : public Vector { +#endif + public: + FastVector() {} + + ~FastVector() override { + Vector::clear(); + } + + protected: + typename Vector::pointer AllocVectorSpace(std::size_t num, unsigned int alignment) override { + return static_cast::pointer>( + gFastMem.Alloc(num * sizeof(T), nullptr)); + } + + void FreeVectorSpace(typename Vector::pointer buffer, std::size_t num) override { + gFastMem.Free(buffer, num * sizeof(T), nullptr); + } +}; }; // namespace UTL #endif diff --git a/src/Speed/Indep/Libs/Support/Utility/UTypes.h b/src/Speed/Indep/Libs/Support/Utility/UTypes.h index 44208af2a..cffc9e745 100644 --- a/src/Speed/Indep/Libs/Support/Utility/UTypes.h +++ b/src/Speed/Indep/Libs/Support/Utility/UTypes.h @@ -123,6 +123,10 @@ inline Vector2 Vector2Make(float x, float y) { return c; } +inline float Cross(const Vector2 &a, const Vector2 &b) { + return a.x * b.y - a.y * b.x; +} + inline UMath::Vector3 Vector3Make(float x, float y, float z) { Vector3 c; @@ -133,6 +137,15 @@ inline UMath::Vector3 Vector3Make(float x, float y, float z) { return c; } +inline Vector4 Vector4Make(float x, float y, float z, float w) { + Vector4 c; + c.x = x; + c.y = y; + c.z = z; + c.w = w; + return c; +} + // TODO PS2 inline Vector4 Vector4Make(const Vector3 &c, float w) { Vector4 res; @@ -147,4 +160,9 @@ typedef Vector4 Quaternion; } // namespace UMath +inline UMath::Vector3 &bConvertToBond(UMath::Vector3 &dest, const bVector3 &v) { + bConvertToBond(reinterpret_cast(dest), v); + return dest; +} + #endif diff --git a/src/Speed/Indep/Libs/Support/Utility/UVectorMath.h b/src/Speed/Indep/Libs/Support/Utility/UVectorMath.h index 87987e57a..abce69e4d 100644 --- a/src/Speed/Indep/Libs/Support/Utility/UVectorMath.h +++ b/src/Speed/Indep/Libs/Support/Utility/UVectorMath.h @@ -39,8 +39,13 @@ void VU0_v4subxyz(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vecto float VU0_v4dotprodxyz(const UMath::Vector4 &a, const UMath::Vector4 &b); void VU0_v4scale(const UMath::Vector4 &a, const float scaleby, UMath::Vector4 &result); void VU0_v4scalexyz(const UMath::Vector4 &a, const float scaleby, UMath::Vector4 &result); +void VU0_v4scalexyz(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result); +void VU0_v4add(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result); float VU0_v4distancesquarexyz(const UMath::Vector4 &p1, const UMath::Vector4 &p2); +void VU0_v4addxyz(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result); +void VU0_v4negatexyz(UMath::Vector4 &result); void VU0_MATRIX3x4_vect3mult(const UMath::Vector3 &v, const UMath::Matrix4 &m, UMath::Vector3 &result); +void VU0_MATRIX3x4_vect4mult(const UMath::Vector4 &v, const UMath::Matrix4 &m, UMath::Vector4 &result); void VU0_qmul(const UMath::Vector4 &b, const UMath::Vector4 &a, UMath::Vector4 &dest); void VU0_v3quatrotate(const UMath::Vector4 &q, const UMath::Vector3 &v, UMath::Vector3 &result); @@ -211,6 +216,10 @@ inline void VU0_v4scalexyz(const UMath::Vector4 &a, const float scaleby, UMath:: inline float VU0_v4distancesquarexyz(const UMath::Vector4 &p1, const UMath::Vector4 &p2) {} +inline void VU0_v4addxyz(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result) {} + +inline void VU0_v4negatexyz(UMath::Vector4 &result) {} + inline void VU0_MATRIX3x4_vect3mult(const UMath::Vector3 &v, const UMath::Matrix4 &m, UMath::Vector3 &result) { asm __volatile__("lqc2 vf1, %1\n" "lqc2 vf2, 0x0(%2)\n" @@ -251,6 +260,10 @@ inline float VU0_Cos(float x) { return cosf(x); } +inline float VU0_ASin(float x) { + return asinf(x) / (float)M_TWOPI; +} + #endif inline float VU0_fabs(const float a) { @@ -621,6 +634,10 @@ inline float VU0_v4lengthxyz(const UMath::Vector4 &a) { return VU0_sqrt(VU0_v4lengthsquarexyz(a)); } +inline float VU0_v4length(const UMath::Vector4 &a) { + return VU0_sqrt(VU0_v4lengthsquare(a)); +} + inline void VU0_v3unit(const UMath::Vector3 &a, UMath::Vector3 &result) { #ifdef EA_PLATFORM_PLAYSTATION2 u_long128 _t0; 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/SourceLists/zWorld.cpp b/src/Speed/Indep/SourceLists/zWorld.cpp index 7875479af..fa38719a2 100644 --- a/src/Speed/Indep/SourceLists/zWorld.cpp +++ b/src/Speed/Indep/SourceLists/zWorld.cpp @@ -11,3 +11,41 @@ #include "Speed/Indep/Src/World/SmackableRender.cpp" #include "Speed/Indep/Src/World/CarRender.cpp" + +#include "Speed/Indep/Src/World/SpaceNode.cpp" + +#include "Speed/Indep/Src/World/WorldModel.cpp" + +#include "Speed/Indep/Src/World/SkyRender.cpp" + +#include "Speed/Indep/Src/World/rain.cpp" + +#include "Speed/Indep/Src/World/FacePixelate.cpp" + +#include "Speed/Indep/Src/World/VehicleFragmentConn.cpp" + +#include "Speed/Indep/Src/World/VehicleRenderConn.cpp" + +#include "Speed/Indep/Src/World/CarRenderConn.cpp" +#include "Speed/Indep/Src/World/HeliRenderConn.cpp" +#include "Speed/Indep/Src/World/VehiclePartDamage.cpp" + +#include "Speed/Indep/Src/World/CarInfo.cpp" + +#include "Speed/Indep/Src/World/CarPartNames.cpp" + +#include "Speed/Indep/Src/World/CarLoader.cpp" + +#include "Speed/Indep/Src/World/CarSkin.cpp" + +#include "Speed/Indep/Src/World/NeuQuant.cpp" + +#include "Speed/Indep/Src/World/HeliSheet.cpp" + +#include "Speed/Indep/Src/World/SimpleModelAnim.cpp" + +#include "Speed/Indep/Src/World/ParameterMaps.cpp" + +#include "Speed/Indep/Src/World/VisualTreatment.cpp" + +#include "Speed/Indep/Src/World/VehicleFX.cpp" diff --git a/src/Speed/Indep/SourceLists/zWorld2.cpp b/src/Speed/Indep/SourceLists/zWorld2.cpp index e69de29bb..55775fcbe 100644 --- a/src/Speed/Indep/SourceLists/zWorld2.cpp +++ b/src/Speed/Indep/SourceLists/zWorld2.cpp @@ -0,0 +1,17 @@ +#include "Speed/Indep/Src/World/Common/WCollider.cpp" +#include "Speed/Indep/Src/World/Common/WCollisionAssets.cpp" +#include "Speed/Indep/Src/World/Common/WCollisionPack.cpp" +#include "Speed/Indep/Src/World/Common/WCollisionMgr.cpp" +#include "Speed/Indep/Src/World/Common/WCollisionTri.cpp" +#include "Speed/Indep/Src/World/Common/WGrid.cpp" +#include "Speed/Indep/Src/World/Common/WGridManagedDynamicElem.cpp" +#include "Speed/Indep/Src/World/Common/WGridNode.cpp" +#include "Speed/Indep/Src/World/Common/WPathFinder.cpp" +#include "Speed/Indep/Src/World/Common/WRoadNetwork.cpp" +#include "Speed/Indep/Src/World/Common/WTrigger.cpp" +#include "Speed/Indep/Src/World/Common/WWorld.cpp" +#include "Speed/Indep/Src/World/Common/WWorldMath.cpp" +#include "Speed/Indep/Src/World/Common/WWorldPos.cpp" +#include "Speed/Indep/Src/World/WorldConn.cpp" +#include "Speed/Indep/Src/World/DamageZones.cpp" +#include "Speed/Indep/Src/World/TimeOfDay.cpp" diff --git a/src/Speed/Indep/Src/AI/Common/AIVehicle.cpp b/src/Speed/Indep/Src/AI/Common/AIVehicle.cpp index 084503e6c..070593875 100644 --- a/src/Speed/Indep/Src/AI/Common/AIVehicle.cpp +++ b/src/Speed/Indep/Src/AI/Common/AIVehicle.cpp @@ -341,7 +341,7 @@ AIVehicle::AIVehicle(const BehaviorParams &bp, float update_rate, float stagger, WRoadNav::EPathType path_type = WRoadNav::kPathNone; if (v->GetVehicleClass() == VehicleClass::CHOPPER) { - path_type = WRoadNav::kPathPathy; + path_type = WRoadNav::kPathChopper; } else { switch (GetVehicle()->GetDriverClass()) { case DRIVER_COP: diff --git a/src/Speed/Indep/Src/Camera/CameraMover.hpp b/src/Speed/Indep/Src/Camera/CameraMover.hpp index e2ef17946..c6a56e69e 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; } @@ -86,6 +90,13 @@ class CameraMover : public bTNode, public WCollisionMgr::ICollision return pCamera->GetPosition(); } + float GetDistanceTo(bVector3 *to) { + bVector3 rel; + + bSub(&rel, this->GetPosition(), to); + return bLength(&rel); + } + // Virtual methods virtual ~CameraMover(); @@ -112,6 +123,8 @@ class CameraMover : public bTNode, public WCollisionMgr::ICollision virtual void ResetState() {} + virtual bool IsHoodCamera() {} + // ICollisionHandler bool OnWCollide(const WCollisionMgr::WorldCollisionInfo &cInfo, const UMath::Vector3 &cPoint, void *userdata) override; diff --git a/src/Speed/Indep/Src/Ecstasy/Ecstasy.hpp b/src/Speed/Indep/Src/Ecstasy/Ecstasy.hpp index 6d8c4dd38..ca5dcf160 100644 --- a/src/Speed/Indep/Src/Ecstasy/Ecstasy.hpp +++ b/src/Speed/Indep/Src/Ecstasy/Ecstasy.hpp @@ -56,6 +56,10 @@ struct eModel : public bTNode { eSolid *GetSolid() { return Solid; } + + int HasSolid() { + return this->Solid != 0; + } }; class eSolidPlatInterface { @@ -184,6 +188,10 @@ struct eView : public eViewPlatInterface { this->Active = state; } + int IsActive() const { + return Active; + } + CameraMover *GetCameraMover() { if (!this->CameraMoverList.IsEmpty()) { return this->CameraMoverList.GetHead(); @@ -207,6 +215,10 @@ inline void eSwizzleWorldVector(const bVector3 &inVec, bVector3 &outVec) { bConvertFromBond(outVec, inVec); } +inline void eSwizzleWorldMatrix(const bMatrix4 &inMat, bMatrix4 &outMat) { + bConvertFromBond(outMat, inMat); +} + inline void eUnSwizzleWorldVector(const bVector3 &inVec, bVector3 &outVec) { bConvertToBond(outVec, inVec); } @@ -229,6 +241,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 +266,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 +283,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 +310,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) {} @@ -288,6 +329,8 @@ struct OnScreenRain { // total size: 0x4 int NumOnScreen; // offset 0x0, size 0x4 + OnScreenRain(); + int GetNumOnScreen() { return NumOnScreen; } @@ -376,8 +419,12 @@ struct Rain { public: Rain(eView *view, RainType StartType); void Init(RainType type, float percent); + 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); - float GetRainIntensity() {} + float GetRainIntensity() { + return this->intensity; + } float GetCloudIntensity() { return this->CloudIntensity; @@ -391,9 +438,11 @@ struct Rain { float GetAmount(RainType type) {} - void SetRoadDampness(float damp) {} + void SetRoadDampness(float damp) { + this->RoadDampness = damp; + } - bVector3 *GetWind() {} + bVector3 *GetWind(); }; struct FacePixelation { @@ -516,6 +565,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 +575,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/Ecstasy/EmitterSystem.h b/src/Speed/Indep/Src/Ecstasy/EmitterSystem.h index 96c443c1b..7e48ae2f9 100644 --- a/src/Speed/Indep/Src/Ecstasy/EmitterSystem.h +++ b/src/Speed/Indep/Src/Ecstasy/EmitterSystem.h @@ -272,6 +272,8 @@ struct Emitter : public bTNode { this->mLocalWorld = *local_world; } + void SetIntensity(float intensity) {} + void SetIntensityRange(float min, float max) { this->mMinIntensity = min; this->mMaxIntensity = max; @@ -327,6 +329,7 @@ class EmitterGroup : public bTNode { uint32 NumEmitters() const; bool MakeOneShot(bool force_all); void SetInheritVelocity(const bVector3 *vel); + void SetIntensity(float intensity) { mIntensity = intensity; } void Enable(); void Disable(); void SubscribeToDeletion(void *subscriber, void (*callback)(void *, struct EmitterGroup *)); diff --git a/src/Speed/Indep/Src/Frontend/Database/VehicleDB.hpp b/src/Speed/Indep/Src/Frontend/Database/VehicleDB.hpp index 006e3698f..728007b4b 100644 --- a/src/Speed/Indep/Src/Frontend/Database/VehicleDB.hpp +++ b/src/Speed/Indep/Src/Frontend/Database/VehicleDB.hpp @@ -23,6 +23,10 @@ struct FECarRecord { // total size: 0x198 struct FECustomizationRecord { + bool IsPreset() const { + return this->Preset != 0; + } + short InstalledPartIndices[139]; // offset 0x0, size 0x116 Physics::Upgrades::Package InstalledPhysics; // offset 0x118, size 0x20 Physics::Tunings Tunings[3]; // offset 0x138, size 0x54 diff --git a/src/Speed/Indep/Src/Gameplay/GMarker.h b/src/Speed/Indep/Src/Gameplay/GMarker.h index 793b7f037..5c7850259 100644 --- a/src/Speed/Indep/Src/Gameplay/GMarker.h +++ b/src/Speed/Indep/Src/Gameplay/GMarker.h @@ -5,6 +5,18 @@ #pragma once #endif +#include "Speed/Indep/Src/Gameplay/GRuntimeInstance.h" + +struct GMarker : public GRuntimeInstance { + GMarker(const unsigned int &markerKey); + + const UMath::Vector3 &GetPosition() const { return mPosition; } + const UMath::Vector3 &GetDirection() const { return mDirection; } + + UMath::Vector3 mPosition; // offset 0x28, size 0xC + UMath::Vector3 mDirection; // offset 0x34, size 0xC +}; + #endif diff --git a/src/Speed/Indep/Src/Gameplay/GRaceStatus.h b/src/Speed/Indep/Src/Gameplay/GRaceStatus.h index efbb40541..70fcbbb04 100644 --- a/src/Speed/Indep/Src/Gameplay/GRaceStatus.h +++ b/src/Speed/Indep/Src/Gameplay/GRaceStatus.h @@ -472,6 +472,10 @@ class GRaceStatus : public UTL::COM::Object, public IVehicleCache { return Exists() && Get().GetRaceType() == GRace::kRaceType_Challenge; } + static bool IsDragRace() { + return Exists() && Get().GetRaceType() == GRace::kRaceType_Drag; + } + PlayMode GetPlayMode() { return mPlayMode; } diff --git a/src/Speed/Indep/Src/Gameplay/GRuntimeInstance.h b/src/Speed/Indep/Src/Gameplay/GRuntimeInstance.h index cae688d12..5f5a4af99 100644 --- a/src/Speed/Indep/Src/Gameplay/GRuntimeInstance.h +++ b/src/Speed/Indep/Src/Gameplay/GRuntimeInstance.h @@ -11,6 +11,7 @@ class GRuntimeInstance : public Attrib::Gen::gameplay { public: GRuntimeInstance(const Attrib::Key &key, GameplayObjType type); + virtual ~GRuntimeInstance(); private: unsigned short mFlags; // offset 0x14, size 0x2 diff --git a/src/Speed/Indep/Src/Generated/AttribSys/Classes/ecar.h b/src/Speed/Indep/Src/Generated/AttribSys/Classes/ecar.h index 6094b012b..84a838ff3 100644 --- a/src/Speed/Indep/Src/Generated/AttribSys/Classes/ecar.h +++ b/src/Speed/Indep/Src/Generated/AttribSys/Classes/ecar.h @@ -73,6 +73,10 @@ struct ecar : Instance { Change(FindCollection(ClassKey(), collectionkey)); } + void ChangeWithDefault(Key collectionkey) { + Change(FindCollectionWithDefault(ClassKey(), collectionkey)); + } + static Key ClassKey() { return 0xa5b543b7; } @@ -291,6 +295,14 @@ struct ecar : Instance { return *resultptr; } + const bool &IsSkinned() const { + const bool *resultptr = reinterpret_cast(this->GetAttributePointer(0xd9102c65, 0)); + if (!resultptr) { + resultptr = reinterpret_cast(DefaultDataArea(sizeof(bool))); + } + return *resultptr; + } + const RefSpec &EngineBlownEffect(unsigned int index) const { const RefSpec *resultptr = reinterpret_cast(this->GetAttributePointer(0xd9cca9a3, index)); if (!resultptr) { diff --git a/src/Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h b/src/Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h index 3e03054bf..00f51b8b4 100644 --- a/src/Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h +++ b/src/Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h @@ -613,8 +613,8 @@ struct pvehicle : Instance { return reinterpret_cast<_LayoutStruct *>(GetLayoutPointer())->DefaultPresetRide; } - const char *CollectionName() const { - return reinterpret_cast<_LayoutStruct *>(GetLayoutPointer())->CollectionName; + const char *const &CollectionName() const { + return *reinterpret_cast(&reinterpret_cast(GetLayoutPointer())->CollectionName); } const int &engine_upgrades() const { diff --git a/src/Speed/Indep/Src/Generated/AttribSys/Classes/simsurface.h b/src/Speed/Indep/Src/Generated/AttribSys/Classes/simsurface.h index 33121cf27..b145fdd7d 100644 --- a/src/Speed/Indep/Src/Generated/AttribSys/Classes/simsurface.h +++ b/src/Speed/Indep/Src/Generated/AttribSys/Classes/simsurface.h @@ -22,6 +22,7 @@ struct TireEffectRecord { }; struct RoadNoiseRecord { + RoadNoiseRecord() : Frequency(0.0f), Amplitude(0.0f), MinSpeed(0.0f), MaxSpeed(0.0f) {} // float Frequency; float Amplitude; float MinSpeed; diff --git a/src/Speed/Indep/Src/Generated/AttribSys/Classes/tires.h b/src/Speed/Indep/Src/Generated/AttribSys/Classes/tires.h index 415603bf6..d66590420 100644 --- a/src/Speed/Indep/Src/Generated/AttribSys/Classes/tires.h +++ b/src/Speed/Indep/Src/Generated/AttribSys/Classes/tires.h @@ -67,6 +67,12 @@ struct tires : Instance { return 0xbd38d1ca; } + Instance &GetBase() { + return *this; + } + + const tires &operator=(const Instance &rhs); + const float &YAW_CONTROL(unsigned int index) const { const _LayoutStruct *lp = reinterpret_cast<_LayoutStruct *>(this->GetLayoutPointer()); if (index < lp->_Array_YAW_CONTROL.GetLength()) { diff --git a/src/Speed/Indep/Src/Generated/AttribSys/Classes/world.h b/src/Speed/Indep/Src/Generated/AttribSys/Classes/world.h index 1e5c7ab37..44939b675 100644 --- a/src/Speed/Indep/Src/Generated/AttribSys/Classes/world.h +++ b/src/Speed/Indep/Src/Generated/AttribSys/Classes/world.h @@ -24,11 +24,9 @@ struct world : Instance { } world(Key collectionKey, unsigned int msgPort, UTL::COM::IUnknown *owner) : Instance(FindCollection(ClassKey(), collectionKey), msgPort, owner) { - this->SetDefaultLayout(sizeof(_LayoutStruct)); } world(const Collection *collection, unsigned int msgPort, UTL::COM::IUnknown *owner) : Instance(collection, msgPort, owner) { - this->SetDefaultLayout(sizeof(_LayoutStruct)); } ~world() {} diff --git a/src/Speed/Indep/Src/Interfaces/Simables/IAI.h b/src/Speed/Indep/Src/Interfaces/Simables/IAI.h index 0ecefb290..7a8699684 100644 --- a/src/Speed/Indep/Src/Interfaces/Simables/IAI.h +++ b/src/Speed/Indep/Src/Interfaces/Simables/IAI.h @@ -70,9 +70,6 @@ class IRoadBlock : public UTL::COM::IUnknown, public UTL::Collections::Listable< }; class IVehicleAI : public UTL::COM::IUnknown { - protected: - ~IVehicleAI() override {} - public: static HINTERFACE _IHandle() { return (HINTERFACE)_IHandle; @@ -80,6 +77,8 @@ class IVehicleAI : public UTL::COM::IUnknown { IVehicleAI(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + virtual ~IVehicleAI() {} + virtual ISimable *GetSimable() const; virtual IVehicle *GetVehicle() const; virtual const struct AISplinePath *GetSplinePath(); diff --git a/src/Speed/Indep/Src/Main/Event.h b/src/Speed/Indep/Src/Main/Event.h index 16461c9b3..ce6db8939 100644 --- a/src/Speed/Indep/Src/Main/Event.h +++ b/src/Speed/Indep/Src/Main/Event.h @@ -10,6 +10,11 @@ #include "Speed/Indep/Src/Misc/Hermes.h" +namespace CARP { +struct EventStaticData; +struct EventList; +} + extern char *gCreationPoint; extern char *gDeletionPoint; @@ -44,11 +49,11 @@ class EventManager { static void RunEvents(); - static void FireEventList(const struct EventList *eventList, bool verbose); + static void FireEventList(const CARP::EventList *eventList, bool verbose); - static void FireOneEvent(const struct EventList *eventList, unsigned int eventToFire, bool verbose); + static void FireOneEvent(const CARP::EventList *eventList, unsigned int eventToFire, bool verbose); - static bool ListHasEvent(const struct EventList *eventList, unsigned int eventID, const struct EventStaticData **foundEvent); + static bool ListHasEvent(const CARP::EventList *eventList, unsigned int eventID, const CARP::EventStaticData **foundEvent); static void AbortCurrentEventList(); diff --git a/src/Speed/Indep/Src/Main/EventSequencer.h b/src/Speed/Indep/Src/Main/EventSequencer.h index 56bc5950c..f9c3e0c6a 100644 --- a/src/Speed/Indep/Src/Main/EventSequencer.h +++ b/src/Speed/Indep/Src/Main/EventSequencer.h @@ -13,6 +13,8 @@ // TODO move? struct EventDynamicData { // total size: 0x64 + void Clear() { memset(this, 0, sizeof(EventDynamicData)); } + UMath::Vector4 fPosition; // offset 0x0, size 0x10 UMath::Vector4 fVector; // offset 0x10, size 0x10 UMath::Vector4 fVelocity; // offset 0x20, size 0x10 diff --git a/src/Speed/Indep/Src/Misc/CookieTrail.h b/src/Speed/Indep/Src/Misc/CookieTrail.h index 918da5f2a..a929ae8a8 100644 --- a/src/Speed/Indep/Src/Misc/CookieTrail.h +++ b/src/Speed/Indep/Src/Misc/CookieTrail.h @@ -7,10 +7,11 @@ #include +#include "Speed/Indep/bWare/Inc/bMath.hpp" #include "Speed/Indep/Libs/Support/Utility/UMath.h" // total size: 0x810 -template class CookieTrail { +template class CookieTrail { int mCount; // offset 0x0, size 0x4 int mLast; // offset 0x4, size 0x4 const int mCapacity; // offset 0x8, size 0x4 @@ -19,6 +20,51 @@ template class CookieTrail { public: CookieTrail() : mCount(0), mLast(-1), mCapacity(U) {} + + void Clear() { + mCount = 0; + mLast = -1; + } + + int Count() const { + return mCount; + } + + T *GetData() { + return mData; + } + + T &Newest() { + return mData[mLast]; + } + + const T &Newest() const { + return mData[mLast]; + } + + int Capacity() { + return mCapacity; + } + + void AddNew(const T &t) { + int cap = mCapacity; + mLast = (mLast + 1) - ((mLast + 1) / cap) * cap; + if (mCount < cap) { + mCount++; + } + mData[mLast] = t; + } + + T &NthOldest(int n) { + T *data = mData; + int idx; + if (mCount < mCapacity) { + idx = n % mCount; + } else { + idx = (mLast + (n + 1)) % mCapacity; + } + return data[idx]; + } }; // TODO move? @@ -37,6 +83,14 @@ struct NavCookie { short SegmentParameter; // offset 0x3C, size 0x2 unsigned short SegmentNumber : 15; // offset 0x3E, size 0x2 unsigned short SegmentNodeInd : 1; // offset 0x3E, size 0x2 + + void SetSegmentParameter(float t) { + SegmentParameter = static_cast< short >(bClamp(t, 0.0f, 1.0f) * 65535.0f); + } + + float GetSegmentParameter() const { + return static_cast< float >(SegmentParameter) / 65535.0f; + } }; struct ResetCookie { 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/Physics/Bounds.h b/src/Speed/Indep/Src/Physics/Bounds.h index 191aee250..fd405fc3d 100644 --- a/src/Speed/Indep/Src/Physics/Bounds.h +++ b/src/Speed/Indep/Src/Physics/Bounds.h @@ -34,6 +34,12 @@ struct _V3c { short x; // offset 0x0, size 0x2 short y; // offset 0x2, size 0x2 short z; // offset 0x4, size 0x2 + + void Decompress(UMath::Vector3 &to) const { + to.x = static_cast(x) * 0.001f; + to.y = static_cast(y) * 0.001f; + to.z = static_cast(z) * 0.001f; + } }; struct _Q4c { @@ -76,7 +82,9 @@ struct Bounds { UCrc32 fNameHash; // offset 0x28, size 0x4 Collection *fCollection; // offset 0x2C, size 0x4 - void GetPivot(UMath::Vector3 &to) const {} + void GetPivot(UMath::Vector3 &to) const { + fPivot.Decompress(to); + } }; class IBoundable : public UTL::COM::IUnknown { diff --git a/src/Speed/Indep/Src/Physics/Dynamics/Collision.h b/src/Speed/Indep/Src/Physics/Dynamics/Collision.h index aa03a5c4b..5843a4cf9 100644 --- a/src/Speed/Indep/Src/Physics/Dynamics/Collision.h +++ b/src/Speed/Indep/Src/Physics/Dynamics/Collision.h @@ -19,15 +19,15 @@ struct CollisionSurface { struct CollisionObject { // total size: 0x70 - bVector4 fPosRadius; // offset 0x0, size 0x10 - bVector4 fDimensions; // offset 0x10, size 0x10 + UMath::Vector4 fPosRadius; // offset 0x0, size 0x10 + UMath::Vector4 fDimensions; // offset 0x10, size 0x10 unsigned char fType; // offset 0x20, size 0x1 unsigned char fShape; // offset 0x21, size 0x1 unsigned short fFlags; // offset 0x22, size 0x2 unsigned short fRenderInstanceInd; // offset 0x24, size 0x2 CollisionSurface fSurface; // offset 0x26, size 0x2 float fPAD[2]; // offset 0x28, size 0x8 - bMatrix4 fMat; // offset 0x30, size 0x40 + UMath::Matrix4 fMat; // offset 0x30, size 0x40 }; // TODO move to CARP? @@ -70,6 +70,7 @@ class Geometry { static bool FindIntersection(const Geometry *A, const Geometry *B, Geometry *result); Geometry(); + Geometry(const UMath::Matrix4 &orient, const UMath::Vector3 &position, const UMath::Vector3 &dimension, Shape shape, const UMath::Vector3 &delta); void Set(const UMath::Matrix4 &orient, const UMath::Vector3 &position, const UMath::Vector3 &dimension, Shape shape, const UMath::Vector3 &delta); const UMath::Vector3 &GetPosition() const { @@ -100,6 +101,12 @@ class Geometry { return mOverlap; } + Shape GetShape() const { + return static_cast(mShape); + } + + void Move(const UMath::Vector3 &deltaP); + private: UMath::Vector4 mPosition; // offset 0x0, size 0x10 UMath::Vector4 mNormal[3]; // offset 0x10, size 0x30 diff --git a/src/Speed/Indep/Src/Render/RenderConn.h b/src/Speed/Indep/Src/Render/RenderConn.h index df2cd16c9..d9aeed981 100644 --- a/src/Speed/Indep/Src/Render/RenderConn.h +++ b/src/Speed/Indep/Src/Render/RenderConn.h @@ -30,6 +30,24 @@ class Pkt_Smackable_Service : public Sim::Packet { this->mChildVisibility = 0xFFFFFF; } + UCrc32 ConnectionClass() override { + static UCrc32 hash("SmackableRenderConn"); + return hash; + } + + unsigned int Size() override { + return 0x10; + } + + static unsigned int SType() { + static UCrc32 hash("Pkt_Smackable_Service"); + return hash.GetValue(); + } + + unsigned int Type() override { + return SType(); + } + // total size: 0x10 bool mVisible; // offset 0x4, size 0x1 float mDistanceToView; // offset 0x8, size 0x4 diff --git a/src/Speed/Indep/Src/Sim/SimServer.h b/src/Speed/Indep/Src/Sim/SimServer.h index 4c8b50589..d8a6e76d2 100644 --- a/src/Speed/Indep/Src/Sim/SimServer.h +++ b/src/Speed/Indep/Src/Sim/SimServer.h @@ -32,6 +32,10 @@ class Connection : public UTL::COM::Factory + +static inline int CarLoader_GetMemoryPoolSize(CarLoader *car_loader) { + return *reinterpret_cast(reinterpret_cast(car_loader) + 0x64); +} + +struct CarPartAttribute; +struct CarPartModelTable; + +struct CarPartPackListCtor { + CarPartPackListCtor *Next; + CarPartPackListCtor *Prev; + + CarPartPackListCtor() + : Next(this), // + Prev(this) {} +}; + +struct CarPartPackLayout { + CarPartPackLayout *Next; + CarPartPackLayout *Prev; + unsigned int Version; + const char *StringTable; + unsigned int StringTableSize; + short *AttributeTableTable; + unsigned int NumAttributeTables; + void *AttributesTable; + unsigned int NumAttributes; + unsigned int *TypeNameTable; + unsigned int NumTypeNames; + void *ModelTable; + unsigned int NumModelTables; + CarPart *PartsTable; + int NumParts; +}; + +struct CarPartAttributeLayout { + unsigned int NameHash; + + union { + float fParam; + int iParam; + unsigned int uParam; + } Params; +}; + +struct CarPartIndexCtor { + CarPart *Part; + int NumParts; + + CarPartIndexCtor() + : Part(0), // + NumParts(0) {} +}; + +struct CarPartDatabase { + CarPartPackListCtor CarPartPackList; + int NumPacks; + int NumParts; + int NumBytes; + CarPartIndexCtor PaintPart_Gloss[3]; + CarPartIndexCtor PaintPart_Metallic[3]; + CarPartIndexCtor PaintPart_Pearl[3]; + CarPartIndexCtor PaintPart_Vinyl[3]; + CarPartIndexCtor PaintPart_Rims[3]; + CarPartIndexCtor PaintPart_Caliper[3]; + CarPartIndexCtor VinylPart_All[3]; + CarPartIndexCtor VinylPart_Body[3]; + CarPartIndexCtor VinylPart_Hood[3]; + CarPartIndexCtor VinylPart_Side[3]; + CarPartIndexCtor VinylPart_Manufacturer[3]; + + CarPartDatabase(); + CarType GetCarType(unsigned int car_type_name_hash); + int GetPartIndex(CarPart *car_part); + CarPart *GetCarPartByIndex(int index); + int NewGetNumCarParts(CarType car_type, int car_slot_id, unsigned int car_part_namehash, int upgrade_level); + CarPart *NewGetCarPart(CarType car_type, int car_slot_id, unsigned int car_part_namehash, CarPart *prev_part, int upgrade_level); + CarPart *NewGetFirstCarPart(CarType car_type, int car_slot_id, unsigned int car_part_namehash, int upgrade_level); + CarPart *NewGetNextCarPart(CarPart *car_part, CarType car_type, int car_slot_id, unsigned int car_part_namehash, int upgrade_level); +}; +struct MissingCarPart { + short CarType; + short Slot; + unsigned int PartNameHash; +}; +struct PresetCar : public bTNode { + char CarTypeName[0x20]; + char PresetName[0x38]; + unsigned int PartNameHashes[139]; +}; +struct CarSlotTypeOverride; +struct SlotTypeOverrideLayout { + unsigned int CarType; + unsigned int SlotId; + unsigned int LookupType[2]; +}; +struct PresetCarListHead : public bTList {}; + +CarTypeInfo *CarTypeInfoArray; +CarPartDatabase CarPartDB; +extern int iRam8047ff04; +extern MissingCarPart MissingCarPartTable[0x14A]; +extern const char *CarPartStringTable; +extern unsigned int *CarPartTypeNameHashTable; +extern unsigned int CarPartTypeNameHashTableSize; +extern CAR_PART_ID CarPartSlotMap[]; +extern CarPartPackLayout *MasterCarPartPackLayout asm("MasterCarPartPack"); +extern unsigned int *DefaultSlotTypeNameTable; +extern CarSlotTypeOverride *SlotTypeOverrideTable; +extern int NumSlotTypeOverrides; +unsigned int TempSlotTable[2]; +PresetCarListHead PresetCarList; + +int UsedCarTextureAddToTable(unsigned int *table, int num_used, int max_textures, unsigned int texture_hash = 0); +int GetTempCarSkinTextures(unsigned int *table, int num_used, int max_textures, RideInfo *ride_info); +int GetIsCollectorsEdition(); +void bMemCpy(void *dest, const void *src, unsigned int numbytes); +unsigned int *GetTypesFromSlot(CAR_SLOT_ID slot, CarType car_type); +unsigned char MapCarTypeNameHashToIndex(unsigned int car_type_namehash); +void *ScanHashTableKey8(unsigned char key_value, void *table_start, int table_length, int entry_key_offset, int entry_size); +unsigned int CarPartModelTable_GetModelNameHash(CarPartModelTable *model_table, unsigned int base_namehash, int model_num, int lod) + asm("GetModelNameHash__17CarPartModelTableUiii"); +CAR_PART_ID GetCarPartFromSlot(CAR_SLOT_ID slot); +CarPart *FindPartWithLevel(CarType type, CAR_SLOT_ID slot, int upg_level); +int GetNumCarSlotIDNames(); +const char *GetCarTypeName(CarType car_type); +CarTypeInfo *GetCarTypeInfoFromHash(unsigned int car_type_hash); +PresetCar *FindFEPresetCar(unsigned int preset_name_hash); +const char *GetCarSlotNameFromID(int car_slot_id); + +struct UsedCarTextureInfoLayout { + unsigned int TexturesToLoadPerm[87]; + unsigned int TexturesToLoadTemp[87]; + int NumTexturesToLoadPerm; + int NumTexturesToLoadTemp; + unsigned int MappedSkinHash; + unsigned int MappedSkinBHash; + unsigned int MappedGlobalHash; + unsigned int MappedWheelHash; + unsigned int MappedSpinnerHash; + unsigned int MappedBadging; + unsigned int MappedSpoilerHash; + unsigned int MappedRoofScoopHash; + unsigned int MappedDashSkinHash; + unsigned int MappedLightHash[11]; + unsigned int MappedTireHash; + unsigned int MappedRimHash; + unsigned int MappedRimBlurHash; + unsigned int MappedLicensePlateHash; + unsigned int ReplaceSkinHash; + unsigned int ReplaceSkinBHash; + unsigned int ReplaceGlobalHash; + unsigned int ReplaceWheelHash; + unsigned int ReplaceSpinnerHash; + unsigned int ReplaceSpoilerHash; + unsigned int ReplaceRoofScoopHash; + unsigned int ReplaceDashSkinHash; + unsigned int ReplaceHeadlightHash[3]; + unsigned int ReplaceHeadlightGlassHash[3]; + unsigned int ReplaceBrakelightHash[3]; + unsigned int ReplaceBrakelightGlassHash[3]; + unsigned int ReplaceReverselightHash[3]; + unsigned int ShadowHash; +}; + +CarPartDatabase::CarPartDatabase() {} + +int UsedCarTextureAddToTable(unsigned int *table, int num_used, int max_textures, unsigned int texture_hash) { + if (texture_hash == 0) { + return 0; + } + + for (int i = 0; i < num_used; i++) { + if (table[i] == texture_hash) { + return 0; + } + } + + if (num_used >= max_textures) { + return num_used; + } + + table[num_used] = texture_hash; + return 1; +} + +int GetNumCarPartIDNames() { + return CARPARTID_NUM; +} + +int GetNumCarSlotIDNames() { + return CARSLOTID_NUM; +} + +CarPartAttribute *CarPart::GetAttribute(unsigned int namehash, CarPartAttribute *prev_attribute) { + unsigned short attribute_table_index = *reinterpret_cast(reinterpret_cast(this) + 0xA); + + if (attribute_table_index == 0xFFFF) { + return 0; + } + + short *attribute_table = MasterCarPartPackLayout->AttributeTableTable + attribute_table_index; + CarPartAttributeLayout *attributes = reinterpret_cast(MasterCarPartPackLayout->AttributesTable); + int num_attributes = static_cast(attribute_table[0]); + int i = 0; + + if (prev_attribute != 0) { + do { + if (num_attributes <= i) { + break; + } + + i++; + } while (prev_attribute != reinterpret_cast(attributes + attribute_table[i])); + + if (i == num_attributes) { + return 0; + } + } + + while (i < num_attributes) { + CarPartAttributeLayout *attribute = attributes + attribute_table[i + 1]; + + if (namehash == 0 || attribute->NameHash == namehash) { + return reinterpret_cast(attribute); + } + + i++; + } + + return 0; +} + +CarPartAttribute *CarPart::GetFirstAppliedAttribute(unsigned int namehash) { + return GetNextAppliedAttribute(namehash, 0); +} + +CarPartAttribute *CarPart::GetNextAppliedAttribute(unsigned int namehash, CarPartAttribute *prev_attribute) { + return GetAttribute(namehash, prev_attribute); +} + +char *CarPart::GetName() { + return const_cast(CarPartStringTable + *reinterpret_cast(reinterpret_cast(this) + 8) * 4); +} + +unsigned int CarPart::GetCarTypeNameHash() { + return CarPartTypeNameHashTable[*reinterpret_cast(reinterpret_cast(this) + 7)]; +} + +CAR_PART_ID GetCarPartFromSlot(CAR_SLOT_ID slot) { + return CarPartSlotMap[slot]; +} + +unsigned int *GetTypesFromSlot(CAR_SLOT_ID slot, CarType car_type) { + const char *car_type_name = GetCarTypeName(car_type); + unsigned int car_type_namehash = bStringHash(car_type_name); + + { + int i = 0; + + if (i < NumSlotTypeOverrides) { + do { + SlotTypeOverrideLayout *slot_type_override = &reinterpret_cast(SlotTypeOverrideTable)[i]; + + if (slot_type_override->CarType == car_type_namehash && slot_type_override->SlotId == slot) { + return slot_type_override->LookupType; + } + i++; + } while (i < NumSlotTypeOverrides); + } + } + + bMemCpy(&TempSlotTable, DefaultSlotTypeNameTable + slot * 2, 8); + + { + int i = 0; + + do { + if (TempSlotTable[i] == 0xFFFFFFFF) { + TempSlotTable[i] = car_type_namehash; + } + i++; + } while (i < 2); + } + + return TempSlotTable; +} + +unsigned char MapCarTypeNameHashToIndex(unsigned int namehash) { + for (unsigned int i = 0; i < static_cast(CarPartTypeNameHashTableSize); i++) { + if (CarPartTypeNameHashTable[i] == namehash) { + return static_cast(i); + } + } + + return 0xFF; +} + +unsigned int CarPart::GetModelNameHash(int model_num, int lod) { + if (*reinterpret_cast(reinterpret_cast(this) + 0xC) == 0xFFFF) { + return 0; + } + + CarPartModelTable *model_table = + reinterpret_cast( + reinterpret_cast(MasterCarPartPackLayout->ModelTable) + + *reinterpret_cast(reinterpret_cast(this) + 0xC) * 0x18); + unsigned int base_namehash; + + if (*reinterpret_cast(reinterpret_cast(this) + 6) == 0) { + base_namehash = 0xFFFFFFFF; + } else if (*reinterpret_cast(reinterpret_cast(this) + 6) == 1) { + base_namehash = GetCarTypeNameHash(); + } else { + base_namehash = GetBrandNameHash(); + } + + return CarPartModelTable_GetModelNameHash(model_table, base_namehash, model_num, lod); +} + +int CarPart::HasAppliedAttribute(unsigned int namehash) { + return GetFirstAppliedAttribute(namehash) != 0; +} + +const char *CarPart::GetAppliedAttributeString(unsigned int namehash, const char *default_string) { + CarPartAttribute *attribute = GetFirstAppliedAttribute(namehash); + return attribute != 0 ? CarPartStringTable + attribute->GetUParam() * 4 : default_string; +} + +int CarPart::GetAppliedAttributeIParam(unsigned int namehash, int default_value) { + CarPartAttributeLayout *attribute = reinterpret_cast(GetFirstAppliedAttribute(namehash)); + + if (attribute != 0) { + return attribute->Params.iParam; + } + + return default_value; +} + +unsigned int CarPart::GetAppliedAttributeUParam(unsigned int namehash, unsigned int default_value) { + CarPartAttributeLayout *attribute = reinterpret_cast(GetFirstAppliedAttribute(namehash)); + + if (attribute != 0) { + return attribute->Params.uParam; + } + + return default_value; +} + +CarType CarPartDatabase::GetCarType(unsigned int car_type_name_hash) { + int i = 0; + + do { + CarTypeInfo *car_type_info = reinterpret_cast(reinterpret_cast(CarTypeInfoArray) + i * sizeof(CarTypeInfo)); + + if (bStringHash(car_type_info->BaseModelName) != car_type_name_hash) { + i++; + } else { + return car_type_info->Type; + } + } while (i < 0x54); + + return static_cast(0x54); +} + +int CarPartDatabase::GetPartIndex(CarPart *car_part) { + CarPartPackLayout *car_part_pack = reinterpret_cast(this->CarPartPackList.Next); + int index = 0; + + while (true) { + if (car_part_pack == reinterpret_cast(&this->CarPartPackList)) { + return -1; + } + + int part_index = + static_cast(reinterpret_cast(car_part) - reinterpret_cast(car_part_pack->PartsTable)) * -0x49249249 >> + 1; + + if (part_index >= 0 && part_index < car_part_pack->NumParts) { + return index + part_index; + } + + index += car_part_pack->NumParts; + car_part_pack = car_part_pack->Next; + } +} + +CarPart *CarPartDatabase::GetCarPartByIndex(int index) { + if (index > -1) { + CarPartPackLayout *car_part_pack = reinterpret_cast(this->CarPartPackList.Next); + + while (true) { + if (car_part_pack != reinterpret_cast(&this->CarPartPackList)) { + if (index < car_part_pack->NumParts) { + return reinterpret_cast(reinterpret_cast(car_part_pack->PartsTable) + index * 0xE); + } + + index -= car_part_pack->NumParts; + car_part_pack = car_part_pack->Next; + } else { + break; + } + } + } + + return 0; +} + +const char *GetCarTypeName(CarType car_type) { + const char *car_type_name = CarTypeInfoArray[car_type].CarTypeName; + + if (car_type_name != 0) { + return car_type_name; + } + + return 0; +} + +CarTypeInfo *GetCarTypeInfoFromHash(unsigned int car_type_hash) { + for (int i = 0; i < 0x54; i++) { + CarTypeInfo *car_type_info = reinterpret_cast(i * sizeof(CarTypeInfo) + reinterpret_cast(CarTypeInfoArray)); + + if (car_type_info->CarTypeNameHash == car_type_hash) { + return car_type_info; + } + } + + return 0; +} + +int CarInfo_GetMaxCompositingBufferSize() { + return 0; +} + +unsigned int CarInfo_GetResourcePool(bool needs_compositing) { + if (needs_compositing != 0) { + return CarLoader_GetMemoryPoolSize(&TheCarLoader) - CarInfo_GetMaxCompositingBufferSize(); + } + + return CarLoader_GetMemoryPoolSize(&TheCarLoader); +} + +unsigned int RideInfo::GetSkinNameHash() { + CarPart *skin_base; + + if (this->IsUsingCompositeSkin() == 0) { + skin_base = this->GetPart(0x4C); + + if (skin_base == 0) { + return 0; + } + + return skin_base->GetTextureNameHash(); + } + + return this->mCompositeSkinHash; +} + +void RideInfo::Init(CarType type, CarRenderUsage usage, int has_dash, int can_be_vertex_damaged) { + this->Type = type; + this->HasDash = has_dash; + this->CanBeVertexDamaged = can_be_vertex_damaged; + this->SkinType = 0; + this->mSpecialLODBehavior = 0; + this->InstanceIndex = 0; + this->mCompositeSkinHash = 0; + this->mMyCarLoaderHandle = 0; + this->mMyCarRenderUsage = usage; + + if (usage < 2) { + this->mMinLodLevel = CARPART_LOD_A; + this->mMaxLodLevel = CARPART_LOD_A; + this->mMinFELodLevel = CARPART_LOD_A; + this->mMaxFELodLevel = CARPART_LOD_A; + } else { + this->mMinLodLevel = CARPART_LOD_B; + this->mMaxLodLevel = CARPART_LOD_D; + this->mMinFELodLevel = CARPART_LOD_A; + this->mMaxFELodLevel = CARPART_LOD_D; + } + + if (this->Type != CARTYPE_NONE && CarTypeInfoArray != 0) { + CarTypeInfo *info = &CarTypeInfoArray[this->Type]; + + if (info != 0 && info->UsageType == CAR_USAGE_TYPE_TRAFFIC) { + this->mMinFELodLevel = CARPART_LOD_A; + this->mMaxFELodLevel = CARPART_LOD_D; + this->mMinLodLevel = CARPART_LOD_B; + this->mMaxLodLevel = CARPART_LOD_D; + } + } + + if (Sim::GetUserMode() == Sim::USER_SPLIT_SCREEN) { + this->mMaxLodLevel = CARPART_LOD_B; + this->mMinLodLevel = CARPART_LOD_B; + } + + this->mMaxBrakeLodLevel = CARPART_LOD_C; + this->mMinReflectionLodLevel = CARPART_LOD_D; + this->mMaxLicenseLodLevel = CARPART_LOD_C; + this->mMinTrafficDiffuseLodLevel = CARPART_LOD_C; + this->mMinShadowLodLevel = CARPART_LOD_B; + this->mMaxShadowLodLevel = CARPART_LOD_C; + this->mMaxTireLodLevel = CARPART_LOD_C; + this->mMaxSpoilerLodLevel = CARPART_LOD_D; + this->mMaxRoofScoopLodLevel = CARPART_LOD_D; + + if (usage == carRenderUsage_NISCar) { + this->mSpecialLODBehavior = 2; + this->mMaxTireLodLevel = CARPART_LOD_OFF; + this->mMinLodLevel = CARPART_LOD_OFF; + this->mMaxLodLevel = CARPART_LOD_OFF; + } + + bMemSet(this->mPartsTable, 0, sizeof(this->mPartsTable)); + bMemSet(this->mPartsEnabled, 1, sizeof(this->mPartsEnabled)); + this->PreviewPart = 0; +} + +int RideInfo::GetSpecialLODRangeForCarSlot(int slot_id, CARPART_LOD *special_minimum, CARPART_LOD *special_maximum, bool in_front_end) { + switch (slot_id) { + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + *special_minimum = CARPART_LOD_OFF; + *special_maximum = CARPART_LOD_OFF; + return 1; + + case CARSLOTID_INTERIOR: + case CARSLOTID_DRIVER: + if (this->mSpecialLODBehavior == 2) { + *special_minimum = CARPART_LOD_OFF; + *special_maximum = CARPART_LOD_OFF; + } else { + if (this->mSpecialLODBehavior == 1) { + *special_minimum = CARPART_LOD_OFF; + } else { + *special_minimum = in_front_end ? CARPART_LOD_A : CARPART_LOD_B; + } + + *special_maximum = slot_id == CARSLOTID_DRIVER ? CARPART_LOD_B : CARPART_LOD_D; + } + + return 1; + + default: + return 0; + } +} + +void RideInfo::SetCompositeNameHash(int skin_number) { + char temp[64]; + unsigned int dummy_skin_hash; + unsigned int dummy_wheel_hash; + unsigned int dummy_spinner_hash; + + if (static_cast(skin_number - 1) < 4) { + this->SkinType = 1; + } else if (static_cast(skin_number - 5) < 8) { + this->SkinType = 2; + } else { + this->SkinType = 0; + } + + bSPrintf(temp, "DUMMY_SKIN%d", skin_number); + dummy_skin_hash = bStringHash(temp); + this->SetCompositeSkinNameHash(dummy_skin_hash); + + bSPrintf(temp, "DUMMY_WHEEL%d", skin_number); + dummy_wheel_hash = bStringHash(temp); + this->SetCompositeWheelNameHash(dummy_wheel_hash); + + bSPrintf(temp, "DUMMY_SPINNER%d", skin_number); + dummy_spinner_hash = bStringHash(temp); + this->SetCompositeSpinnerNameHash(dummy_spinner_hash); +} + +unsigned int RideInfo::GetCompositeSkinNameHash() { + return this->mCompositeSkinHash; +} + +void RideInfo::SetCompositeSkinNameHash(unsigned int namehash) { + CarTypeInfo *type_info = &CarTypeInfoArray[this->Type]; + + if (type_info->Skinnable != 0) { + this->mCompositeSkinHash = namehash; + } else { + this->mCompositeSkinHash = 0; + } +} + +unsigned int RideInfo::GetCompositeWheelNameHash() { + return this->mCompositeWheelHash; +} + +void RideInfo::SetCompositeWheelNameHash(unsigned int namehash) { + this->mCompositeWheelHash = namehash; +} + +unsigned int RideInfo::GetCompositeSpinnerNameHash() { + return this->mCompositeSpinnerHash; +} + +void RideInfo::SetCompositeSpinnerNameHash(unsigned int namehash) { + this->mCompositeSpinnerHash = namehash; +} + +int RideInfo::IsUsingCompositeSkin() { + return this->GetCompositeSkinNameHash() != 0; +} + +void RideInfo::SetUpgradePart(CAR_SLOT_ID car_slot_id, int upg_level) { + CAR_PART_ID car_part_id = GetCarPartFromSlot(car_slot_id); + CarPart *part; + + (void)car_part_id; + part = FindPartWithLevel(this->Type, car_slot_id, upg_level); + if (part != 0) { + this->SetPart(car_slot_id, part, true); + } +} + +CarPart *FindPartWithLevel(CarType car_type, CAR_SLOT_ID slot_id, int level) { + CarPart *part = CarPartDB.NewGetCarPart(car_type, slot_id, 0, 0, -1); + + while (part != 0) { + if ((reinterpret_cast(part)[5] >> 5) == static_cast(level)) { + return part; + } + part = CarPartDB.NewGetNextCarPart(part, car_type, slot_id, 0, -1); + } + + return 0; +} + +void RideInfo::SetStockParts() { + unsigned int stock_vinyl_colours[4]; + unsigned int stock_vinyl_hashes[4]; + + for (int car_slot_id = 0; car_slot_id <= CARSLOTID_MISC; car_slot_id++) { + if (((this->Type != static_cast(4)) || (car_slot_id != CARSLOTID_ATTACHMENT6)) && car_slot_id != CARSLOTID_VINYL_LAYER0 && + (static_cast(car_slot_id - CARSLOTID_DECAL_FRONT_WINDOW_TEX0) > 0x2F)) { + if (car_slot_id == CARSLOTID_HUD_BACKING_COLOUR) { + goto set_hud_backing_colour; + } else if (car_slot_id <= CARSLOTID_HUD_BACKING_COLOUR) { + if (car_slot_id == CARSLOTID_BASE_PAINT) { + goto set_base_paint; + } else { + goto set_upgrade_part; + } + } else { + if (car_slot_id == CARSLOTID_HUD_NEEDLE_COLOUR) { + goto set_hud_needle_colour; + } else if (car_slot_id == CARSLOTID_HUD_CHARACTER_COLOUR) { + goto set_hud_character_colour; + } else { + goto set_upgrade_part; + } + } + } + + continue; + + set_base_paint: { + CarTypeInfo *type_info = &CarTypeInfoArray[this->Type]; + CarPart *paint_part = + CarPartDB.NewGetCarPart(this->Type, car_slot_id, static_cast(type_info->DefaultBasePaint), 0, -1); + + if (paint_part != 0) { + this->SetPart(car_slot_id, paint_part, true); + } + continue; + } + + set_hud_backing_colour: { + CarPart *hud_part = CarPartDB.NewGetCarPart(this->Type, car_slot_id, bStringHash("ORANGE"), 0, -1); + + if (hud_part != 0) { + this->SetPart(car_slot_id, hud_part, true); + } else { + this->SetUpgradePart(static_cast(car_slot_id), 0); + } + continue; + } + + set_hud_needle_colour: { + CarPart *hud_part = CarPartDB.NewGetCarPart(this->Type, car_slot_id, bStringHash("ORANGE"), 0, -1); + + if (hud_part != 0) { + this->SetPart(car_slot_id, hud_part, true); + } else { + this->SetUpgradePart(static_cast(car_slot_id), 0); + } + continue; + } + + set_hud_character_colour: { + CarPart *hud_part = CarPartDB.NewGetCarPart(this->Type, car_slot_id, bStringHash("WHITE"), 0, -1); + + if (hud_part != 0) { + this->SetPart(car_slot_id, hud_part, true); + } else { + this->SetUpgradePart(static_cast(car_slot_id), 0); + } + continue; + } + + set_upgrade_part: + this->SetUpgradePart(static_cast(car_slot_id), 0); + } + + stock_vinyl_hashes[0] = bStringHash("VINYL_L1_COLOR01"); + stock_vinyl_hashes[1] = bStringHash("VINYL_L1_COLOR03"); + stock_vinyl_hashes[2] = bStringHash("VINYL_L2_COLOR11"); + stock_vinyl_hashes[3] = bStringHash("VINYL_L1_COLOR01"); + stock_vinyl_colours[0] = stock_vinyl_hashes[0]; + stock_vinyl_colours[1] = stock_vinyl_hashes[1]; + stock_vinyl_colours[2] = stock_vinyl_hashes[2]; + stock_vinyl_colours[3] = stock_vinyl_hashes[3]; + + for (int j = 0; j < 4; j++) { + int car_slot_id = j + CARSLOTID_VINYL_COLOUR0_0; + CarPart *vinyl_paint_part = CarPartDB.NewGetCarPart(this->Type, car_slot_id, stock_vinyl_colours[j], 0, -1); + + this->SetPart(car_slot_id, vinyl_paint_part, true); + } +} + +void RideInfo::SetRandomPart(CAR_SLOT_ID slot, int upgrade_level) { + int num_parts = CarPartDB.NewGetNumCarParts(this->Type, slot, 0, upgrade_level); + CarPart *part = 0; + bool foundModel = false; + + for (int attempt = 1; (foundModel == false) && (attempt <= 0xF); attempt++) { + int part_number = bRandom(num_parts); + + for (int i = 0; i < part_number + 1; i++) { + part = CarPartDB.NewGetNextCarPart(part, this->Type, slot, 0, upgrade_level); + } + + if (part != 0) { + if (slot == CARSLOTID_VINYL_LAYER0) { + if (part->GetGroupNumber() != 8) { + unsigned int brand_name_hash = part->GetBrandNameHash(); + if (brand_name_hash != bStringHash("CEO") || GetIsCollectorsEdition() != 0) { + goto found_part; + } + } + } else { + found_part: + if (slot != CARSLOTID_BASE_PAINT || this->SkinType != 2 || part->GetBrandNameHash() != 0x03797533) { + bool part_missing = false; + + for (int n = 0; n < 0x14A; n++) { + MissingCarPart *missing_part = &MissingCarPartTable[n]; + + if (missing_part->CarType == this->Type && missing_part->Slot == slot && + missing_part->PartNameHash == part->GetPartNameHash()) { + part_missing = true; + break; + } + } + + if (!part_missing) { + foundModel = true; + this->SetPart(slot, part, true); + } + } + } + } + } +} + +void RideInfo::SetRandomParts() { + int randomset; + + this->SetStockParts(); + randomset = bRandom(3); + + switch (randomset) { + case 0: + this->SetRandomPart(CARSLOTID_BODY, -1); + this->SetRandomPart(CARSLOTID_SPOILER, -1); + this->SetRandomPart(CARSLOTID_BASE_PAINT, -1); + this->SetRandomPart(CARSLOTID_FRONT_WHEEL, -1); + this->SetRandomPart(CARSLOTID_PAINT_RIM, -1); + break; + case 1: + this->SetRandomPart(CARSLOTID_BASE_PAINT, -1); + this->SetRandomPart(CARSLOTID_VINYL_LAYER0, -1); + this->SetRandomPart(CARSLOTID_VINYL_COLOUR0_0, -1); + this->SetRandomPart(CARSLOTID_VINYL_COLOUR0_1, -1); + this->SetRandomPart(CARSLOTID_VINYL_COLOUR0_2, -1); + this->SetRandomPart(CARSLOTID_VINYL_COLOUR0_3, -1); + break; + case 2: + this->SetRandomPart(CARSLOTID_BODY, -1); + this->SetRandomPart(CARSLOTID_SPOILER, -1); + this->SetRandomPart(CARSLOTID_FRONT_WHEEL, -1); + this->SetRandomPart(CARSLOTID_PAINT_RIM, -1); + this->SetRandomPart(CARSLOTID_WINDOW_TINT, -1); + this->SetRandomPart(CARSLOTID_ROOF, -1); + this->SetRandomPart(CARSLOTID_HOOD, -1); + this->SetRandomPart(CARSLOTID_BASE_PAINT, -1); + this->SetRandomPart(CARSLOTID_VINYL_LAYER0, -1); + this->SetRandomPart(CARSLOTID_VINYL_COLOUR0_0, -1); + this->SetRandomPart(CARSLOTID_VINYL_COLOUR0_1, -1); + this->SetRandomPart(CARSLOTID_VINYL_COLOUR0_2, -1); + this->SetRandomPart(CARSLOTID_VINYL_COLOUR0_3, -1); + break; + } +} + +void RideInfo::SetRandomPaint() { + int num_paints; + CarPart *paint_part = 0; + int paint_number; + + num_paints = CarPartDB.NewGetNumCarParts(this->Type, CARSLOTID_BASE_PAINT, 0, -1); + paint_number = bRandom(num_paints) + 1; + for (int i = 0; i < paint_number; i++) { + paint_part = CarPartDB.NewGetNextCarPart(paint_part, this->Type, CARSLOTID_BASE_PAINT, 0, -1); + } + + this->SetPart(CARSLOTID_BASE_PAINT, paint_part, true); + for (int i = CARSLOTID_VINYL_LAYER0; i < CARSLOTID_PAINT_RIM; i++) { + this->SetPart(i, 0, true); + } +} + +CarPart *RideInfo::GetPart(int car_slot_id) const { + return this->mPartsTable[car_slot_id]; +} + +CarPart *RideInfo::SetPart(int car_slot_id, CarPart *car_part, bool update_enabled) { + CarPart *previous_part; + + if (car_slot_id <= 0x33) { + if (car_slot_id > 0x2D) { + return this->mPartsTable[car_slot_id]; + } + + if (car_slot_id == CARSLOTID_BODY) { + if (car_part == 0) { + this->mPartsTable[CARSLOTID_DAMAGE0_FRONT] = 0; + this->mPartsTable[CARSLOTID_DAMAGE0_REAR] = 0; + this->mPartsTable[CARSLOTID_DAMAGE0_FRONTLEFT] = 0; + this->mPartsTable[CARSLOTID_DAMAGE0_FRONTRIGHT] = 0; + this->mPartsTable[CARSLOTID_DAMAGE0_REARLEFT] = 0; + this->mPartsTable[CARSLOTID_DAMAGE0_REARRIGHT] = 0; + } else { + int kit_number = car_part->GetAppliedAttributeIParam(0x796C0CB0, 0); + char buffer[64]; + unsigned int base_hash; + + bSPrintf(buffer, "%s_KIT%02d_", GetCarTypeName(this->Type), kit_number); + base_hash = bStringHash(buffer); + this->mPartsTable[CARSLOTID_DAMAGE0_FRONT] = + CarPartDB.NewGetCarPart(this->Type, CARSLOTID_DAMAGE0_FRONT, bStringHash("DAMAGE0_FRONT", base_hash), 0, -1); + this->mPartsTable[CARSLOTID_DAMAGE0_REAR] = + CarPartDB.NewGetCarPart(this->Type, CARSLOTID_DAMAGE0_REAR, bStringHash("DAMAGE0_REAR", base_hash), 0, -1); + this->mPartsTable[CARSLOTID_DAMAGE0_FRONTLEFT] = + CarPartDB.NewGetCarPart(this->Type, CARSLOTID_DAMAGE0_FRONTLEFT, bStringHash("DAMAGE0_FRONTLEFT", base_hash), 0, -1); + this->mPartsTable[CARSLOTID_DAMAGE0_FRONTRIGHT] = + CarPartDB.NewGetCarPart(this->Type, CARSLOTID_DAMAGE0_FRONTRIGHT, bStringHash("DAMAGE0_FRONTRIGHT", base_hash), 0, -1); + this->mPartsTable[CARSLOTID_DAMAGE0_REARLEFT] = + CarPartDB.NewGetCarPart(this->Type, CARSLOTID_DAMAGE0_REARLEFT, bStringHash("DAMAGE0_REARLEFT", base_hash), 0, -1); + this->mPartsTable[CARSLOTID_DAMAGE0_REARRIGHT] = + CarPartDB.NewGetCarPart(this->Type, CARSLOTID_DAMAGE0_REARRIGHT, bStringHash("DAMAGE0_REARRIGHT", base_hash), 0, -1); + } + + if (car_part == 0) { + this->mPartsTable[CARSLOTID_DECAL_LEFT_DOOR] = 0; + this->mPartsTable[CARSLOTID_DECAL_RIGHT_DOOR] = 0; + this->mPartsTable[CARSLOTID_DECAL_LEFT_QUARTER] = 0; + this->mPartsTable[CARSLOTID_DECAL_RIGHT_QUARTER] = 0; + } else { + int kit_number = car_part->GetAppliedAttributeIParam(0x796C0CB0, 0); + char buffer[64]; + unsigned int base_hash; + CarPart *left_door_decal_part; + CarPart *right_door_decal_part; + CarPart *left_quarter_decal_part; + CarPart *right_quarter_decal_part; + + bSPrintf(buffer, "%s_KIT%02d_", GetCarTypeName(this->Type), kit_number); + base_hash = bStringHash(buffer); + left_door_decal_part = + CarPartDB.NewGetCarPart(this->Type, CARSLOTID_DECAL_LEFT_DOOR, bStringHash("DECAL_LEFT_DOOR_RECT_MEDIUM", base_hash), 0, + -1); + right_door_decal_part = + CarPartDB.NewGetCarPart(this->Type, CARSLOTID_DECAL_RIGHT_DOOR, bStringHash("DECAL_RIGHT_DOOR_RECT_MEDIUM", base_hash), 0, + -1); + left_quarter_decal_part = CarPartDB.NewGetCarPart(this->Type, CARSLOTID_DECAL_LEFT_QUARTER, + bStringHash("DECAL_LEFT_QUARTER_RECT_MEDIUM", base_hash), 0, -1); + right_quarter_decal_part = CarPartDB.NewGetCarPart(this->Type, CARSLOTID_DECAL_RIGHT_QUARTER, + bStringHash("DECAL_RIGHT_QUARTER_RECT_MEDIUM", base_hash), 0, -1); + this->mPartsTable[CARSLOTID_DECAL_LEFT_DOOR] = left_door_decal_part; + this->mPartsTable[CARSLOTID_DECAL_RIGHT_DOOR] = right_door_decal_part; + this->mPartsTable[CARSLOTID_DECAL_LEFT_QUARTER] = left_quarter_decal_part; + this->mPartsTable[CARSLOTID_DECAL_RIGHT_QUARTER] = right_quarter_decal_part; + } + } + } else if (car_slot_id == CARSLOTID_REAR_WHEEL) { + return this->mPartsTable[car_slot_id]; + } else if (static_cast(car_slot_id - 0x48) <= 3) { + return this->mPartsTable[car_slot_id]; + } + + previous_part = this->mPartsTable[car_slot_id]; + this->mPartsTable[car_slot_id] = car_part; + + if (update_enabled) { + this->UpdatePartsEnabled(); + } + + return previous_part; +} + +void RideInfo::UpdatePartsEnabled() { + bMemSet(this->mPartsEnabled, 1, 0x8B); + int i = 0; + GetNumCarSlotIDNames(); + do { + if (i == CARSLOTID_FRONT_WHEEL) { + unsigned int brake_paint_hash = bStringHash("CALIPER"); + CarPart *part = this->PreviewPart; + bool is_part_brake_paint = false; + if (part) { + if (part->GetPartID() == 'L') { + is_part_brake_paint = part->GetBrandNameHash() == brake_paint_hash; + } + part = this->PreviewPart; + if (part && (part->GetPartID() == 'B' || is_part_brake_paint)) { + this->mPartsEnabled[i] = 0; + } + } + } + i++; + } while (i <= 0x8a); +} + +int RideInfo::IsPartEnabled(int car_part_id) { + return this->mPartsEnabled[car_part_id]; +} + +void RideInfo::DumpForPreset(FECarRecord *car) { + (void)car; + + for (int i = 0; i < CARSLOTID_NUM; i++) { + const char *slot_name = GetCarSlotNameFromID(i); + CarPart *part = this->mPartsTable[i]; + + (void)slot_name; + if (part != 0) { + const char *display_name = part->GetName(); + (void)display_name; + } + } +} + +int LoaderFEPresetCars(bChunk *chunk) { + if (chunk->GetID() == 0x30220) { + int *chunk_words = reinterpret_cast(chunk); + int *preset_words = chunk_words + 2; + int num_presets = static_cast(chunk_words[1]) / 0x290; + int preset_count = num_presets - 1; + + if (num_presets != 0) { + do { + preset_count--; + + int j = 0; + int k; + do { + k = j + 1; + bEndianSwap32(preset_words + j + 0x18); + j = k; + } while (k < 0x8B); + + bEndianSwap64(preset_words + 0x14); + bEndianSwap64(preset_words + 0x12); + bEndianSwap32(preset_words + 0x17); + bEndianSwap32(preset_words + 0x16); + + PresetCar *preset = reinterpret_cast(preset_words); + + PresetCarList.AddTail(preset); + preset_words += 0xA4; + } while (preset_count != -1); + } + + return 1; + } + + return 0; +} + +int UnloaderFEPresetCars(bChunk *chunk) { + if (chunk->GetID() == 0x30220) { + while (!PresetCarList.IsEmpty()) { + PresetCarList.RemoveHead(); + } + + return 1; + } + + return 0; +} + +int GetNumPresetCars() { + return PresetCarList.CountElements(); +} + +PresetCar *GetPresetCarAt(int index) { + return PresetCarList.GetNode(index); +} + +PresetCar *FindFEPresetCar(unsigned int hash) { + for (PresetCar *p = PresetCarList.GetHead(); p != PresetCarList.EndOfList(); p = p->GetNext()) { + if (hash == FEHashUpper(p->PresetName)) { + return p; + } + } + + return 0; +} + +void RideInfo::FillWithPreset(unsigned int preset_name_hash) { + PresetCar *preset = FindFEPresetCar(preset_name_hash); + + if (preset != 0) { + this->SkinType = 1; + CarTypeInfo *cti = GetCarTypeInfoFromHash(FEHashUpper(preset->CarTypeName)); + CarType type = cti->Type; + + if (type != this->Type) { + this->Init(type, CarRenderUsage_Player, 0, 0); + this->SetStockParts(); + } + + for (int i = 0; i < CARSLOTID_NUM; i++) { + unsigned int part_name_hash = preset->PartNameHashes[i]; + + if (part_name_hash != 0) { + if (part_name_hash != 1) { + CarPart *part = CarPartDB.NewGetCarPart(type, i, part_name_hash, 0, -1); + this->SetPart(i, part, true); + } + } else { + this->SetPart(i, 0, true); + } + } + } +} + +void GetUsedCarTextureInfo(UsedCarTextureInfo *used_texture_info, RideInfo *ride_info, int front_end_only) { + UsedCarTextureInfoLayout *info = reinterpret_cast(used_texture_info); + CarTypeInfo *car_type_info = &CarTypeInfoArray[ride_info->Type]; + char *car_base_name = car_type_info->BaseModelName; + int max_perm_textures = 0x57; + int max_temp_textures = 0x57; + int num_perm_textures = 0; + int num_temp_textures; + int debug_print; + char buffer[64]; + CarPart *wheel_part; + unsigned int composite_skin_hash; + unsigned int composite_wheel_hash; + unsigned int composite_spinner_hash; + unsigned int skin2_namehash; + unsigned int skin3_namehash; + unsigned int skin4_namehash; + CarPart *carpart_headlight; + CarPart *carpart_brakelight; + unsigned int base_headlight_hash; + unsigned int base_brakelight_hash; + unsigned int misc_namehash; + unsigned int misc_n_namehash; + unsigned int interior_namehash; + unsigned int interior_n_namehash; + unsigned int badging_namehash; + unsigned int badging_n_namehash; + unsigned int driver_namehash; + unsigned int engine_namehash; + unsigned int badging_eu_namehash; + unsigned int license_plate_namehash; + unsigned int tire_n_namehash; + unsigned int tread_namehash; + unsigned int tread_n_namehash; + unsigned int size_hash; + unsigned int shape_hash; + unsigned int size_hashes[3]; + unsigned int shape_hashes[3]; + + bMemSet(info, 0, sizeof(*info)); + + bSPrintf(buffer, "%s_SKIN1", car_base_name); + info->MappedSkinHash = bStringHash(buffer); + bSPrintf(buffer, "%s_SKIN1B", car_base_name); + info->MappedSkinBHash = bStringHash(buffer); + info->MappedGlobalHash = bStringHash("GLOBAL_SKIN1"); + wheel_part = ride_info->GetPart(0x42); + + if (wheel_part != 0) { + info->MappedWheelHash = bStringHash("_WHEEL", wheel_part->GetAppliedAttributeUParam(0x10C98090, 0)); + composite_spinner_hash = wheel_part->GetAppliedAttributeUParam(bStringHash("SPINNER_TEXTURE"), 0); + if (composite_spinner_hash != 0) { + info->MappedSpinnerHash = composite_spinner_hash; + } else { + info->MappedSpinnerHash = 0; + } + } else { + info->MappedSpinnerHash = 0; + info->MappedWheelHash = 0; + } + + info->MappedSpoilerHash = bStringHash("SPOILER_SKIN1"); + info->MappedRoofScoopHash = bStringHash("ROOF_SKIN"); + + if (ride_info->IsUsingCompositeSkin() != 0) { + info->ReplaceSkinHash = ride_info->GetSkinNameHash(); + info->ReplaceWheelHash = ride_info->GetCompositeWheelNameHash(); + info->ReplaceSpinnerHash = ride_info->GetCompositeSpinnerNameHash(); + info->ReplaceSpoilerHash = ride_info->GetSkinNameHash(); + info->ReplaceRoofScoopHash = ride_info->GetSkinNameHash(); + } else { + unsigned int mapped_skin_hash = info->MappedSkinHash; + + info->ReplaceSkinHash = mapped_skin_hash; + info->ReplaceSpoilerHash = mapped_skin_hash; + info->ReplaceRoofScoopHash = mapped_skin_hash; + info->ReplaceWheelHash = 0; + info->ReplaceSpinnerHash = 0; + } + + info->ReplaceSkinBHash = 0; + info->ReplaceGlobalHash = info->ReplaceSkinHash; + + composite_skin_hash = ride_info->GetCompositeSkinNameHash(); + if (composite_skin_hash != 0) { + num_perm_textures = UsedCarTextureAddToTable(reinterpret_cast(info), 0, max_perm_textures, composite_skin_hash); + } else { + if (info->ReplaceSkinHash != 0) { + num_perm_textures = + UsedCarTextureAddToTable(reinterpret_cast(info), 0, max_perm_textures, info->ReplaceSkinHash); + } + if (info->ReplaceSkinBHash != 0) { + num_perm_textures += UsedCarTextureAddToTable( + reinterpret_cast(info), num_perm_textures, max_perm_textures, info->ReplaceSkinBHash); + } + } + + composite_wheel_hash = ride_info->GetCompositeWheelNameHash(); + if (composite_wheel_hash != 0) { + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, composite_wheel_hash); + } else if (info->ReplaceWheelHash != 0) { + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, info->ReplaceWheelHash); + } + + composite_spinner_hash = ride_info->GetCompositeSpinnerNameHash(); + if (composite_spinner_hash != 0) { + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, composite_spinner_hash); + } else if (info->ReplaceSpinnerHash != 0) { + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, info->ReplaceSpinnerHash); + } + + num_temp_textures = GetTempCarSkinTextures(info->TexturesToLoadTemp, 0, max_temp_textures, ride_info); + + bSPrintf(buffer, "%s_SKIN2", car_base_name); + skin2_namehash = bStringHash(buffer); + bSPrintf(buffer, "%s_SKIN3", car_base_name); + skin3_namehash = bStringHash(buffer); + bSPrintf(buffer, "%s_SKIN4", car_base_name); + skin4_namehash = bStringHash(buffer); + + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, skin2_namehash); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, skin3_namehash); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, skin4_namehash); + + carpart_headlight = ride_info->GetPart(0x40); + carpart_brakelight = ride_info->GetPart(0x41); + + bSPrintf(buffer, "%s_KIT00_HEADLIGHT", car_base_name); + base_headlight_hash = bStringHash(buffer); + bSPrintf(buffer, "%s_KIT00_BRAKELIGHT", car_base_name); + base_brakelight_hash = bStringHash(buffer); + + if (carpart_headlight != 0 && carpart_headlight->GetAppliedAttributeUParam(0x10C98090, 0) != 0) { + base_headlight_hash = carpart_headlight->GetAppliedAttributeUParam(0x10C98090, 0); + } + + if (carpart_brakelight != 0 && carpart_brakelight->GetAppliedAttributeUParam(0x10C98090, 0) != 0) { + base_brakelight_hash = carpart_brakelight->GetAppliedAttributeUParam(0x10C98090, 0); + } + + info->MappedLightHash[0] = bStringHash("HEADLIGHT_LEFT"); + info->MappedLightHash[1] = bStringHash("HEADLIGHT_RIGHT"); + info->MappedLightHash[5] = bStringHash("HEADLIGHT_GLASS_LEFT"); + info->MappedLightHash[6] = bStringHash("HEADLIGHT_GLASS_RIGHT"); + info->MappedLightHash[2] = bStringHash("BRAKELIGHT_LEFT"); + info->MappedLightHash[3] = bStringHash("BRAKELIGHT_RIGHT"); + info->MappedLightHash[4] = bStringHash("BRAKELIGHT_CENTRE"); + info->MappedLightHash[7] = bStringHash("BRAKELIGHT_GLASS_LEFT"); + info->MappedLightHash[8] = bStringHash("BRAKELIGHT_GLASS_RIGHT"); + info->MappedLightHash[9] = bStringHash("BRAKELIGHT_GLASS_CENTRE"); + info->MappedLightHash[10] = 0; + + info->ReplaceHeadlightHash[0] = bStringHash("_OFF", base_headlight_hash); + info->ReplaceHeadlightHash[1] = bStringHash("_ON", base_headlight_hash); + info->ReplaceHeadlightHash[2] = bStringHash("_DAMAGE0", base_headlight_hash); + info->ReplaceHeadlightGlassHash[0] = bStringHash("_GLASS_OFF", base_headlight_hash); + info->ReplaceHeadlightGlassHash[1] = bStringHash("_GLASS_ON", base_headlight_hash); + info->ReplaceHeadlightGlassHash[2] = bStringHash("_GLASS_DAMAGE0", base_headlight_hash); + info->ReplaceBrakelightHash[0] = bStringHash("_OFF", base_brakelight_hash); + info->ReplaceBrakelightHash[1] = bStringHash("_ON", base_brakelight_hash); + info->ReplaceBrakelightHash[2] = bStringHash("_DAMAGE0", base_brakelight_hash); + info->ReplaceBrakelightGlassHash[0] = bStringHash("_GLASS_OFF", base_brakelight_hash); + info->ReplaceBrakelightGlassHash[1] = bStringHash("_GLASS_ON", base_brakelight_hash); + info->ReplaceBrakelightGlassHash[2] = bStringHash("_GLASS_DAMAGE0", base_brakelight_hash); + info->ReplaceReverselightHash[0] = 0; + info->ReplaceReverselightHash[1] = 0; + info->ReplaceReverselightHash[2] = 0; + + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, info->ReplaceHeadlightHash[0]); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, info->ReplaceHeadlightHash[1]); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, info->ReplaceHeadlightGlassHash[0]); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, info->ReplaceHeadlightGlassHash[1]); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, info->ReplaceBrakelightHash[0]); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, info->ReplaceBrakelightHash[1]); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, info->ReplaceBrakelightGlassHash[0]); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, info->ReplaceBrakelightGlassHash[1]); + + bSPrintf(buffer, "%s_MISC", car_base_name); + misc_namehash = bStringHash(buffer); + bSPrintf(buffer, "%s_MISC_N", car_base_name); + misc_n_namehash = bStringHash(buffer); + bSPrintf(buffer, "%s_INTERIOR", car_base_name); + interior_namehash = bStringHash(buffer); + bSPrintf(buffer, "%s_INTERIOR_N", car_base_name); + interior_n_namehash = bStringHash(buffer); + bSPrintf(buffer, "%s_BADGING", car_base_name); + badging_namehash = bStringHash(buffer); + bSPrintf(buffer, "%s_BADGING_N", car_base_name); + badging_n_namehash = bStringHash(buffer); + bSPrintf(buffer, "%s_DRIVER", car_base_name); + driver_namehash = bStringHash(buffer); + bSPrintf(buffer, "%s_ENGINE", car_base_name); + engine_namehash = bStringHash(buffer); + bSPrintf(buffer, "%s_BADGING_EU", car_base_name); + badging_eu_namehash = bStringHash(buffer); + bSPrintf(buffer, "%s_LICENSE_PLATE", car_base_name); + license_plate_namehash = bStringHash(buffer); + info->MappedLicensePlateHash = license_plate_namehash; + info->MappedBadging = badging_namehash; + + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, misc_namehash); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, misc_n_namehash); + + if (front_end_only != 0 || strstr(car_base_name, "COP") != 0) { + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, interior_namehash); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, interior_n_namehash); + } + + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, driver_namehash); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, engine_namehash); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, badging_namehash); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, badging_n_namehash); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, badging_eu_namehash); + + bSPrintf(buffer, "%s_SIDELIGHT", car_base_name); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, bStringHash(buffer)); + bSPrintf(buffer, "%s_DOOR_HANDLE", car_base_name); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, bStringHash(buffer)); + bSPrintf(buffer, "%s_LOGO", car_base_name); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, bStringHash(buffer)); + + if (iRam8047ff04 != 6) { + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, bStringHash("AUDIO_SKIN")); + } + + bSPrintf(buffer, "%s_SHADOW%s", car_base_name, front_end_only != 0 ? "FE" : "IG"); + info->ShadowHash = bStringHash(buffer); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, info->ShadowHash); + + bSPrintf(buffer, "%s_NEON", car_base_name); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, bStringHash(buffer)); + + bSPrintf(buffer, "%s_TIRE", car_base_name); + info->MappedTireHash = bStringHash(buffer); + bSPrintf(buffer, "%s_TIRE_N", car_base_name); + tire_n_namehash = bStringHash(buffer); + bSPrintf(buffer, "%s_RIM", car_base_name); + info->MappedRimHash = bStringHash(buffer); + bSPrintf(buffer, "%s_RIM_BLUR", car_base_name); + info->MappedRimBlurHash = bStringHash(buffer); + bSPrintf(buffer, "%s_TREAD", car_base_name); + tread_namehash = bStringHash(buffer); + bSPrintf(buffer, "%s_TREAD_N", car_base_name); + tread_n_namehash = bStringHash(buffer); + + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, info->MappedTireHash); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, info->MappedRimHash); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, info->MappedRimBlurHash); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, tire_n_namehash); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, tread_namehash); + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, tread_n_namehash); + + size_hash = bStringHash("SIZE"); + shape_hash = bStringHash("SHAPE"); + size_hashes[0] = bStringHash("SQUARE"); + size_hashes[1] = bStringHash("RECT"); + size_hashes[2] = bStringHash("WIDE"); + shape_hashes[0] = size_hashes[0]; + shape_hashes[1] = size_hashes[1]; + shape_hashes[2] = size_hashes[2]; + + for (int i = 0x46; i <= 0x4B; i++) { + CarPart *decal_model_part = ride_info->GetPart(i); + + if (decal_model_part != 0 && decal_model_part->HasAppliedAttribute(size_hash) != 0 && + decal_model_part->HasAppliedAttribute(shape_hash) != 0) { + unsigned int decal_size = decal_model_part->GetAppliedAttributeUParam(size_hash, 0); + unsigned int decal_shape = decal_model_part->GetAppliedAttributeUParam(shape_hash, 0); + int num_decal_slots = 8; + int first_tex_part = i * num_decal_slots - 0x1DD; + + for (int j = 0; j < num_decal_slots; j++) { + CarPart *decal_texture_part = ride_info->GetPart(first_tex_part + j); + + if (decal_texture_part != 0) { + unsigned int texture_hash = decal_texture_part->GetAppliedAttributeUParam(bStringHash("NAME"), 0); + + if (decal_shape == shape_hashes[0]) { + texture_hash = bStringHash("_SQUARE", texture_hash); + } else if (decal_shape == shape_hashes[1]) { + texture_hash = bStringHash("_RECT", texture_hash); + } else if (decal_shape == shape_hashes[2]) { + texture_hash = bStringHash("_WIDE", texture_hash); + } + + num_perm_textures += UsedCarTextureAddToTable(reinterpret_cast(info), num_perm_textures, max_perm_textures, + texture_hash); + } + } + + (void)decal_size; + } + } + + info->NumTexturesToLoadPerm = num_perm_textures; + info->NumTexturesToLoadTemp = num_temp_textures; +} + +int CarPartDatabase::NewGetNumCarParts(CarType car_type, int car_slot_id, unsigned int car_part_namehash, int upgrade_level) { + int num_parts = 0; + CarPart *part = 0; + + while (true) { + part = this->NewGetCarPart(car_type, car_slot_id, car_part_namehash, part, upgrade_level); + if (part == 0) { + break; + } + + num_parts++; + } + + return num_parts; +} + +CarPart *CarPartDatabase::NewGetCarPart(CarType car_type, int car_slot_id, unsigned int car_part_namehash, CarPart *prev_part, int upgrade_level) { + unsigned int *car_type_namehashes = GetTypesFromSlot(static_cast(car_slot_id), car_type); + int previous_type_index = 0; + CAR_PART_ID car_part_id = GetCarPartFromSlot(static_cast(car_slot_id)); + int car_type_index; + unsigned int car_type_namehash; + CarPartPackLayout *car_part_pack; + int num_parts; + CarPart *part; + CarPart *end_part; + unsigned char car_type_key; + + if (prev_part != 0) { + while (previous_type_index < 2 && prev_part->GetCarTypeNameHash() != car_type_namehashes[previous_type_index]) { + previous_type_index++; + } + + if (previous_type_index == 2) { + return 0; + } + } + + for (car_type_index = previous_type_index; car_type_index <= 1; car_type_index++) { + car_type_namehash = car_type_namehashes[car_type_index]; + + if (car_type_namehash != 0) { + car_part_pack = reinterpret_cast(this->CarPartPackList.Next); + while (car_part_pack != reinterpret_cast(&this->CarPartPackList)) { + num_parts = car_part_pack->NumParts; + part = car_part_pack->PartsTable; + end_part = reinterpret_cast(reinterpret_cast(part) + num_parts * 0xE); + + if (prev_part != 0 && car_type_index == previous_type_index) { + if (prev_part < part || prev_part >= end_part) { + car_part_pack = car_part_pack->Next; + continue; + } + + part = reinterpret_cast(reinterpret_cast(prev_part) + 0xE); + } else { + car_type_key = MapCarTypeNameHashToIndex(car_type_namehash); + part = static_cast(ScanHashTableKey8(car_type_key, part, num_parts, 7, 0xE)); + if (part == 0) { + car_part_pack = car_part_pack->Next; + continue; + } + } + + while (part < end_part) { + if (static_cast(static_cast(reinterpret_cast(part)[4])) == car_part_id && + part->GetCarTypeNameHash() == car_type_namehash && + (car_part_namehash == 0 || + (static_cast(reinterpret_cast(part)[1]) << 16 | + static_cast(reinterpret_cast(part)[0])) == car_part_namehash)) { + if ((reinterpret_cast(part)[5] >> 5) == static_cast(upgrade_level)) { + return part; + } + + if (upgrade_level == -1) { + return part; + } + } + + part = reinterpret_cast(reinterpret_cast(part) + 0xE); + } + + car_part_pack = car_part_pack->Next; + } + } + } + + return 0; +} + +CarPart *CarPartDatabase::NewGetFirstCarPart(CarType car_type, int car_slot_id, unsigned int car_part_namehash, int upgrade_level) { + return this->NewGetCarPart(car_type, car_slot_id, car_part_namehash, 0, upgrade_level); +} + +CarPart *CarPartDatabase::NewGetNextCarPart(CarPart *car_part, CarType car_type, int car_slot_id, unsigned int car_part_namehash, + int upgrade_level) { + return this->NewGetCarPart(car_type, car_slot_id, car_part_namehash, car_part, upgrade_level); +} + +bool CarInfo_IsSkinned(CarType type) { + CarTypeInfo *info = &CarTypeInfoArray[type]; + + if (info != 0) { + Attrib::Gen::ecar ecar(Attrib::StringToLowerCaseKey(info->GetCarTypeName()), 0, 0); + + return ecar.IsSkinned(); + } + + return false; +} + +void GenerateMissingCarParts() { + char stack_space[0xE8]; + (void)stack_space; +} diff --git a/src/Speed/Indep/Src/World/CarInfo.hpp b/src/Speed/Indep/Src/World/CarInfo.hpp index 28e8872fd..4fb818304 100644 --- a/src/Speed/Indep/Src/World/CarInfo.hpp +++ b/src/Speed/Indep/Src/World/CarInfo.hpp @@ -6,6 +6,7 @@ #include "Speed/Indep/Src/Interfaces/Simables/IVehicle.h" #include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" // TODO this stuff may or may not go here. enum CarUsageType { @@ -24,6 +25,239 @@ enum CARPART_LOD { CARPART_LOD_E, CARPART_LOD_NUM, }; +enum CAR_SLOT_ID { + CARSLOTID_BASE = 0, + CARSLOTID_DAMAGE_FRONT_WINDOW = 1, + CARSLOTID_DAMAGE_BODY = 2, + CARSLOTID_DAMAGE_COP_LIGHTS = 3, + CARSLOTID_DAMAGE_COP_SPOILER = 4, + CARSLOTID_DAMAGE_FRONT_WHEEL = 5, + CARSLOTID_DAMAGE_LEFT_BRAKELIGHT = 6, + CARSLOTID_DAMAGE_RIGHT_BRAKELIGHT = 7, + CARSLOTID_DAMAGE_LEFT_HEADLIGHT = 8, + CARSLOTID_DAMAGE_RIGHT_HEADLIGHT = 9, + CARSLOTID_DAMAGE_HOOD = 10, + CARSLOTID_DAMAGE_BUSHGUARD = 11, + CARSLOTID_DAMAGE_FRONT_BUMPER = 12, + CARSLOTID_DAMAGE_RIGHT_DOOR = 13, + CARSLOTID_DAMAGE_RIGHT_REAR_DOOR = 14, + CARSLOTID_DAMAGE_TRUNK = 15, + CARSLOTID_DAMAGE_REAR_BUMPER = 16, + CARSLOTID_DAMAGE_REAR_LEFT_WINDOW = 17, + CARSLOTID_DAMAGE_FRONT_LEFT_WINDOW = 18, + CARSLOTID_DAMAGE_FRONT_RIGHT_WINDOW = 19, + CARSLOTID_DAMAGE_REAR_RIGHT_WINDOW = 20, + CARSLOTID_DAMAGE_LEFT_DOOR = 21, + CARSLOTID_DAMAGE_LEFT_REAR_DOOR = 22, + CARSLOTID_BODY = 23, + CARSLOTID_FRONT_BRAKE = 24, + CARSLOTID_FRONT_LEFT_WINDOW = 25, + CARSLOTID_FRONT_RIGHT_WINDOW = 26, + CARSLOTID_FRONT_WINDOW = 27, + CARSLOTID_INTERIOR = 28, + CARSLOTID_LEFT_BRAKELIGHT = 29, + CARSLOTID_LEFT_BRAKELIGHT_GLASS = 30, + CARSLOTID_LEFT_HEADLIGHT = 31, + CARSLOTID_LEFT_HEADLIGHT_GLASS = 32, + CARSLOTID_LEFT_SIDE_MIRROR = 33, + CARSLOTID_REAR_BRAKE = 34, + CARSLOTID_REAR_LEFT_WINDOW = 35, + CARSLOTID_REAR_RIGHT_WINDOW = 36, + CARSLOTID_REAR_WINDOW = 37, + CARSLOTID_RIGHT_BRAKELIGHT = 38, + CARSLOTID_RIGHT_BRAKELIGHT_GLASS = 39, + CARSLOTID_RIGHT_HEADLIGHT = 40, + CARSLOTID_RIGHT_HEADLIGHT_GLASS = 41, + CARSLOTID_RIGHT_SIDE_MIRROR = 42, + CARSLOTID_DRIVER = 43, + CARSLOTID_SPOILER = 44, + CARSLOTID_UNIVERSAL_SPOILER_BASE = 45, + CARSLOTID_DAMAGE0_FRONT = 46, + CARSLOTID_DAMAGE0_FRONTLEFT = 47, + CARSLOTID_DAMAGE0_FRONTRIGHT = 48, + CARSLOTID_DAMAGE0_REAR = 49, + CARSLOTID_DAMAGE0_REARLEFT = 50, + CARSLOTID_DAMAGE0_REARRIGHT = 51, + CARSLOTID_ATTACHMENT0 = 52, + CARSLOTID_ATTACHMENT1 = 53, + CARSLOTID_ATTACHMENT2 = 54, + CARSLOTID_ATTACHMENT3 = 55, + CARSLOTID_ATTACHMENT4 = 56, + CARSLOTID_ATTACHMENT5 = 57, + CARSLOTID_ATTACHMENT6 = 58, + CARSLOTID_ATTACHMENT7 = 59, + CARSLOTID_ATTACHMENT8 = 60, + CARSLOTID_ATTACHMENT9 = 61, + CARSLOTID_ROOF = 62, + CARSLOTID_HOOD = 63, + CARSLOTID_HEADLIGHT = 64, + CARSLOTID_BRAKELIGHT = 65, + CARSLOTID_FRONT_WHEEL = 66, + CARSLOTID_REAR_WHEEL = 67, + CARSLOTID_SPINNER = 68, + CARSLOTID_LICENSE_PLATE = 69, + CARSLOTID_DECAL_FRONT_WINDOW = 70, + CARSLOTID_DECAL_REAR_WINDOW = 71, + CARSLOTID_DECAL_LEFT_DOOR = 72, + CARSLOTID_DECAL_RIGHT_DOOR = 73, + CARSLOTID_DECAL_LEFT_QUARTER = 74, + CARSLOTID_DECAL_RIGHT_QUARTER = 75, + CARSLOTID_BASE_PAINT = 76, + CARSLOTID_VINYL_LAYER0 = 77, + CARSLOTID_PAINT_RIM = 78, + CARSLOTID_VINYL_COLOUR0_0 = 79, + CARSLOTID_VINYL_COLOUR0_1 = 80, + CARSLOTID_VINYL_COLOUR0_2 = 81, + CARSLOTID_VINYL_COLOUR0_3 = 82, + CARSLOTID_DECAL_FRONT_WINDOW_TEX0 = 83, + CARSLOTID_DECAL_FRONT_WINDOW_TEX1 = 84, + CARSLOTID_DECAL_FRONT_WINDOW_TEX2 = 85, + CARSLOTID_DECAL_FRONT_WINDOW_TEX3 = 86, + CARSLOTID_DECAL_FRONT_WINDOW_TEX4 = 87, + CARSLOTID_DECAL_FRONT_WINDOW_TEX5 = 88, + CARSLOTID_DECAL_FRONT_WINDOW_TEX6 = 89, + CARSLOTID_DECAL_FRONT_WINDOW_TEX7 = 90, + CARSLOTID_DECAL_REAR_WINDOW_TEX0 = 91, + CARSLOTID_DECAL_REAR_WINDOW_TEX1 = 92, + CARSLOTID_DECAL_REAR_WINDOW_TEX2 = 93, + CARSLOTID_DECAL_REAR_WINDOW_TEX3 = 94, + CARSLOTID_DECAL_REAR_WINDOW_TEX4 = 95, + CARSLOTID_DECAL_REAR_WINDOW_TEX5 = 96, + CARSLOTID_DECAL_REAR_WINDOW_TEX6 = 97, + CARSLOTID_DECAL_REAR_WINDOW_TEX7 = 98, + CARSLOTID_DECAL_LEFT_DOOR_TEX0 = 99, + CARSLOTID_DECAL_LEFT_DOOR_TEX1 = 100, + CARSLOTID_DECAL_LEFT_DOOR_TEX2 = 101, + CARSLOTID_DECAL_LEFT_DOOR_TEX3 = 102, + CARSLOTID_DECAL_LEFT_DOOR_TEX4 = 103, + CARSLOTID_DECAL_LEFT_DOOR_TEX5 = 104, + CARSLOTID_DECAL_LEFT_DOOR_TEX6 = 105, + CARSLOTID_DECAL_LEFT_DOOR_TEX7 = 106, + CARSLOTID_DECAL_RIGHT_DOOR_TEX0 = 107, + CARSLOTID_DECAL_RIGHT_DOOR_TEX1 = 108, + CARSLOTID_DECAL_RIGHT_DOOR_TEX2 = 109, + CARSLOTID_DECAL_RIGHT_DOOR_TEX3 = 110, + CARSLOTID_DECAL_RIGHT_DOOR_TEX4 = 111, + CARSLOTID_DECAL_RIGHT_DOOR_TEX5 = 112, + CARSLOTID_DECAL_RIGHT_DOOR_TEX6 = 113, + CARSLOTID_DECAL_RIGHT_DOOR_TEX7 = 114, + CARSLOTID_DECAL_LEFT_QUARTER_TEX0 = 115, + CARSLOTID_DECAL_LEFT_QUARTER_TEX1 = 116, + CARSLOTID_DECAL_LEFT_QUARTER_TEX2 = 117, + CARSLOTID_DECAL_LEFT_QUARTER_TEX3 = 118, + CARSLOTID_DECAL_LEFT_QUARTER_TEX4 = 119, + CARSLOTID_DECAL_LEFT_QUARTER_TEX5 = 120, + CARSLOTID_DECAL_LEFT_QUARTER_TEX6 = 121, + CARSLOTID_DECAL_LEFT_QUARTER_TEX7 = 122, + CARSLOTID_DECAL_RIGHT_QUARTER_TEX0 = 123, + CARSLOTID_DECAL_RIGHT_QUARTER_TEX1 = 124, + CARSLOTID_DECAL_RIGHT_QUARTER_TEX2 = 125, + CARSLOTID_DECAL_RIGHT_QUARTER_TEX3 = 126, + CARSLOTID_DECAL_RIGHT_QUARTER_TEX4 = 127, + CARSLOTID_DECAL_RIGHT_QUARTER_TEX5 = 128, + CARSLOTID_DECAL_RIGHT_QUARTER_TEX6 = 129, + CARSLOTID_DECAL_RIGHT_QUARTER_TEX7 = 130, + CARSLOTID_WINDOW_TINT = 131, + CARSLOTID_CUSTOM_HUD = 132, + CARSLOTID_HUD_BACKING_COLOUR = 133, + CARSLOTID_HUD_NEEDLE_COLOUR = 134, + CARSLOTID_HUD_CHARACTER_COLOUR = 135, + CARSLOTID_CV = 136, + CARSLOTID_WHEEL_MANUFACTURER = 137, + CARSLOTID_MISC = 138, + CARSLOTID_NUM = 139, +}; +enum CAR_PART_ID { + CARPARTID_INVALID = -1, + CARPARTID_BASE = 0, + CARPARTID_DAMAGE_FRONT_WINDOW = 1, + CARPARTID_DAMAGE_BODY = 2, + CARPARTID_DAMAGE_COP_LIGHTS = 3, + CARPARTID_DAMAGE_COP_SPOILER = 4, + CARPARTID_DAMAGE_FRONT_WHEEL = 5, + CARPARTID_DAMAGE_LEFT_BRAKELIGHT = 6, + CARPARTID_DAMAGE_RIGHT_BRAKELIGHT = 7, + CARPARTID_DAMAGE_LEFT_HEADLIGHT = 8, + CARPARTID_DAMAGE_RIGHT_HEADLIGHT = 9, + CARPARTID_DAMAGE_HOOD = 10, + CARPARTID_DAMAGE_BUSHGUARD = 11, + CARPARTID_DAMAGE_FRONT_BUMPER = 12, + CARPARTID_DAMAGE_RIGHT_DOOR = 13, + CARPARTID_DAMAGE_RIGHT_REAR_DOOR = 14, + CARPARTID_DAMAGE_TRUNK = 15, + CARPARTID_DAMAGE_REAR_BUMPER = 16, + CARPARTID_DAMAGE_REAR_LEFT_WINDOW = 17, + CARPARTID_DAMAGE_FRONT_LEFT_WINDOW = 18, + CARPARTID_DAMAGE_FRONT_RIGHT_WINDOW = 19, + CARPARTID_DAMAGE_REAR_RIGHT_WINDOW = 20, + CARPARTID_DAMAGE_LEFT_DOOR = 21, + CARPARTID_DAMAGE_LEFT_REAR_DOOR = 22, + CARPARTID_BODY = 23, + CARPARTID_FRONT_BRAKE = 24, + CARPARTID_FRONT_LEFT_WINDOW = 25, + CARPARTID_FRONT_RIGHT_WINDOW = 26, + CARPARTID_FRONT_WINDOW = 27, + CARPARTID_INTERIOR = 28, + CARPARTID_LEFT_BRAKELIGHT = 29, + CARPARTID_LEFT_BRAKELIGHT_GLASS = 30, + CARPARTID_LEFT_HEADLIGHT = 31, + CARPARTID_LEFT_HEADLIGHT_GLASS = 32, + CARPARTID_LEFT_SIDE_MIRROR = 33, + CARPARTID_REAR_BRAKE = 34, + CARPARTID_REAR_LEFT_WINDOW = 35, + CARPARTID_REAR_RIGHT_WINDOW = 36, + CARPARTID_REAR_WINDOW = 37, + CARPARTID_RIGHT_BRAKELIGHT = 38, + CARPARTID_RIGHT_BRAKELIGHT_GLASS = 39, + CARPARTID_RIGHT_HEADLIGHT = 40, + CARPARTID_RIGHT_HEADLIGHT_GLASS = 41, + CARPARTID_RIGHT_SIDE_MIRROR = 42, + CARPARTID_DRIVER = 43, + CARPARTID_SPOILER = 44, + CARPARTID_UNIVERSAL_SPOILER_BASE = 45, + CARPARTID_DAMAGE0_FRONT = 46, + CARPARTID_DAMAGE0_FRONTLEFT = 47, + CARPARTID_DAMAGE0_FRONTRIGHT = 48, + CARPARTID_DAMAGE0_REAR = 49, + CARPARTID_DAMAGE0_REARLEFT = 50, + CARPARTID_DAMAGE0_REARRIGHT = 51, + CARPARTID_ATTACHMENT0 = 52, + CARPARTID_ATTACHMENT1 = 53, + CARPARTID_ATTACHMENT2 = 54, + CARPARTID_ATTACHMENT3 = 55, + CARPARTID_ATTACHMENT4 = 56, + CARPARTID_ATTACHMENT5 = 57, + CARPARTID_ATTACHMENT6 = 58, + CARPARTID_ATTACHMENT7 = 59, + CARPARTID_ATTACHMENT8 = 60, + CARPARTID_ATTACHMENT9 = 61, + CARPARTID_ROOF = 62, + CARPARTID_HOOD = 63, + CARPARTID_HEADLIGHT = 64, + CARPARTID_BRAKELIGHT = 65, + CARPARTID_BRAKE = 66, + CARPARTID_WHEEL = 67, + CARPARTID_SPINNER = 68, + CARPARTID_LICENSE_PLATE = 69, + CARPARTID_DECAL_FRONT_WINDOW = 70, + CARPARTID_DECAL_REAR_WINDOW = 71, + CARPARTID_DECAL_LEFT_DOOR = 72, + CARPARTID_DECAL_RIGHT_DOOR = 73, + CARPARTID_DECAL_LEFT_QUARTER = 74, + CARPARTID_DECAL_RIGHT_QUARTER = 75, + CARPARTID_PAINT = 76, + CARPARTID_VINYL_PAINT = 77, + CARPARTID_RIM_PAINT = 78, + CARPARTID_VINYL = 79, + CARPARTID_DECAL_TEXTURE = 80, + CARPARTID_WINDOW_TINT = 81, + CARPARTID_CUSTOM_HUD = 82, + CARPARTID_CUSTOM_HUD_PAINT = 83, + CARPARTID_CV = 84, + CARPARTID_WHEEL_MANUFACTURER = 85, + CARPARTID_MISC = 86, + CARPARTID_NUM = 87, +}; enum CarRenderUsage { CarRenderUsage_Player, CarRenderUsage_RemotePlayer, @@ -35,17 +269,147 @@ enum CarRenderUsage { CarRenderUsage_Invalid, }; +struct CarPartAttribute { + unsigned int NameHash; + union { + float fParam; + int iParam; + unsigned int uParam; + } Params; + + unsigned int GetUParam() { + return this->Params.uParam; + } + + void EndianSwap() { + bPlatEndianSwap(&this->Params.iParam); + bPlatEndianSwap(&this->NameHash); + } +}; + +struct CarPart { + CarPartAttribute *GetAttribute(unsigned int namehash, CarPartAttribute *prev_attribute); + CarPartAttribute *GetFirstAppliedAttribute(unsigned int namehash); + CarPartAttribute *GetNextAppliedAttribute(unsigned int namehash, CarPartAttribute *prev_attribute); + char *GetName(); + unsigned int GetCarTypeNameHash(); + unsigned int GetModelNameHash(int model, int lod); + unsigned int GetAppliedAttributeUParam(unsigned int namehash, unsigned int default_value); + int GetAppliedAttributeIParam(unsigned int namehash, int default_value); + const char *GetAppliedAttributeString(unsigned int namehash, const char *default_string); + int HasAppliedAttribute(unsigned int namehash); + char GetPartID() { + return *(reinterpret_cast(this) + 4); + } + + char GetUpgradeLevel() { + return (static_cast(*(reinterpret_cast(this) + 5)) >> 5) - 1; + } + + char GetGroupNumber() { + return *(reinterpret_cast(this) + 5) & 0x1F; + } + + void EndianSwap() { + bPlatEndianSwap(reinterpret_cast(reinterpret_cast(this) + 0)); + bPlatEndianSwap(reinterpret_cast(reinterpret_cast(this) + 2)); + bPlatEndianSwap(reinterpret_cast(reinterpret_cast(this) + 8)); + bPlatEndianSwap(reinterpret_cast(reinterpret_cast(this) + 10)); + bPlatEndianSwap(reinterpret_cast(reinterpret_cast(this) + 12)); + } + + unsigned int GetPartNameHash() { + return (static_cast(*(reinterpret_cast(this) + 1)) << 16) | + *reinterpret_cast(this); + } + + unsigned int GetTextureNameHash() { + return GetAppliedAttributeUParam(0x10C98090, 0); + } + + unsigned int GetBrandNameHash() { + return GetAppliedAttributeUParam(0xEBB03E66, 0); + } +}; + // total size: 0x310 +struct FECarRecord; class RideInfo { public: void Init(CarType type, CarRenderUsage usage, int has_dash, int can_be_vertex_damaged); + struct CarPart *SetPart(int car_slot_id, struct CarPart *car_part, bool update_enabled); + void SetStockParts(); + void SetRandomPart(CAR_SLOT_ID slot, int upgrade_level); + void SetRandomPaint(); + void SetRandomParts(); + void SetUpgradePart(CAR_SLOT_ID car_slot_id, int upg_level); + void UpdatePartsEnabled(); struct CarPart *GetPart(int car_slot_id) const; + int IsPartEnabled(int car_part_id); + void DumpForPreset(FECarRecord *car); + void FillWithPreset(unsigned int preset_name_hash); int GetSpecialLODRangeForCarSlot(int slot_id, CARPART_LOD *special_minimum, CARPART_LOD *special_maximum, bool in_front_end); + unsigned int GetSkinNameHash(); + void SetCompositeNameHash(int skin_number); + unsigned int GetCompositeSkinNameHash(); + void SetCompositeSkinNameHash(unsigned int namehash); + unsigned int GetCompositeWheelNameHash(); + void SetCompositeWheelNameHash(unsigned int namehash); + unsigned int GetCompositeSpinnerNameHash(); + void SetCompositeSpinnerNameHash(unsigned int namehash); + int IsUsingCompositeSkin(); + struct CarPart *GetPreviewPart() { + return this->PreviewPart; + } RideInfo() { Init(CARTYPE_NONE, CarRenderUsage_Player, 0, 0); } + void SetCarLoaderHandle(int car_loader_handle) { + this->mMyCarLoaderHandle = car_loader_handle; + } + + int GetCarLoaderHandle() { + return this->mMyCarLoaderHandle; + } + + CarRenderUsage GetCarRenderUsage() { + return this->mMyCarRenderUsage; + } + + CARPART_LOD GetMinLodLevel() const { + return this->mMinLodLevel; + } + + CARPART_LOD GetMaxLodLevel() const { + return this->mMaxLodLevel; + } + + CARPART_LOD GetMinFELodLevel() const { + return this->mMinFELodLevel; + } + + CARPART_LOD GetMaxFELodLevel() const { + return this->mMaxFELodLevel; + } + + CARPART_LOD GetMaxTireLodLevel() const { + return this->mMaxTireLodLevel; + } + + CARPART_LOD GetMaxBrakeLodLevel() const { + return this->mMaxBrakeLodLevel; + } + + CARPART_LOD GetMaxSpoilerLodLevel() const { + return this->mMaxSpoilerLodLevel; + } + + CARPART_LOD GetMaxRoofScoopLodLevel() const { + return this->mMaxRoofScoopLodLevel; + } + CarType Type; // offset 0x0, size 0x4 char InstanceIndex; // offset 0x4, size 0x1 char HasDash; // offset 0x5, size 0x1 @@ -137,6 +501,19 @@ struct CarTypeInfo { char Skinnable; // offset 0xC7, size 0x1 int Padding; // offset 0xC8, size 0x4 int DefaultBasePaint; // offset 0xCC, size 0x4 + + char *GetBaseModelName(); + char *GetName() { + return this->CarTypeName; + } + + char *GetCarTypeName() { + return this->CarTypeName; + } + + CarUsageType GetCarUsageType() { + return this->UsageType; + } }; #endif diff --git a/src/Speed/Indep/Src/World/CarLoader.cpp b/src/Speed/Indep/Src/World/CarLoader.cpp index e69de29bb..6c5e22d55 100644 --- a/src/Speed/Indep/Src/World/CarLoader.cpp +++ b/src/Speed/Indep/Src/World/CarLoader.cpp @@ -0,0 +1,2511 @@ +#include "./CarLoader.hpp" +#include "Speed/Indep/Src/Ecstasy/DefragFixer.hpp" +#include "Speed/Indep/bWare/Inc/bPrintf.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#include "Speed/Indep/bWare/Inc/bChunk.hpp" +#include "Speed/Indep/bWare/Inc/bMemory.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" +#include "Speed/Indep/Src/Misc/Profiler.hpp" +#include "Speed/Indep/Src/Misc/ResourceLoader.hpp" +#include "Speed/Indep/Src/Misc/bFile.hpp" +#include "Speed/Indep/Src/World/CarRender.hpp" +#include "Speed/Indep/Src/World/TrackStreamer.hpp" + +CarLoader TheCarLoader; + +struct RideInfoLayout { + CarType Type; + char InstanceIndex; + char HasDash; + char CanBeVertexDamaged; + char SkinType; + CARPART_LOD mMinLodLevel; + CARPART_LOD mMaxLodLevel; + CARPART_LOD mMinFELodLevel; + CARPART_LOD mMaxFELodLevel; +}; +struct CarPartAttributeTable { + short NumAttributes; + short AttributeOffsetTable[1]; + + unsigned int GetByteSize() { + return static_cast(this->NumAttributes * sizeof(short) + sizeof(short)); + } + + void EndianSwap() { + bPlatEndianSwap(&this->NumAttributes); + + for (int i = 0; i < this->NumAttributes; i++) { + bPlatEndianSwap(&this->AttributeOffsetTable[i]); + } + } +}; +struct CarPartModelTable { + char TemplatedNameHashes; + char pad; + unsigned short MiddleStringOffset; + const char *ModelNames[1][5]; + + unsigned int GetModelNameHash(unsigned int base_namehash, int model_num, int lod); + + void EndianSwap() { + bPlatEndianSwap(&this->MiddleStringOffset); + + for (int i = 0; i < 1; i++) { + for (int j = 0; j < 5; j++) { + bPlatEndianSwap(reinterpret_cast(const_cast(&this->ModelNames[i][j]))); + } + } + } +}; + +struct CarPartPack : public bTNode { + unsigned int Version; + const char *StringTable; + unsigned int StringTableSize; + CarPartAttributeTable *AttributeTableTable; + unsigned int NumAttributeTables; + CarPartAttribute *AttributesTable; + unsigned int NumAttributes; + unsigned int *TypeNameTable; + unsigned int NumTypeNames; + CarPartModelTable *ModelTable; + unsigned int NumModelTables; + CarPart *PartsTable; + unsigned int NumParts; + + void EndianSwap() { + bPlatEndianSwap(&this->Version); + bPlatEndianSwap(&this->NumParts); + bPlatEndianSwap(&this->NumAttributes); + bPlatEndianSwap(&this->NumTypeNames); + bPlatEndianSwap(&this->NumModelTables); + bPlatEndianSwap(&this->NumAttributeTables); + } + + void InPlaceInit() { + this->Next = this; + this->Prev = this; + } +}; +struct CarPartIndex { + CarPart *Part; + int NumParts; +}; +struct CarSlotTypeOverride { + unsigned int CarType; + unsigned int SlotId; + unsigned int LookupType[2]; +}; +struct UsedCarTextureInfoMirror { + unsigned int TexturesToLoadPerm[87]; + unsigned int TexturesToLoadTemp[87]; + int NumTexturesToLoadPerm; + int NumTexturesToLoadTemp; + unsigned int MappedSkinHash; + unsigned int MappedSkinBHash; + unsigned int MappedGlobalHash; + unsigned int MappedWheelHash; + unsigned int MappedSpinnerHash; + unsigned int MappedBadging; + unsigned int MappedSpoilerHash; + unsigned int MappedRoofScoopHash; + unsigned int MappedDashSkinHash; + unsigned int MappedLightHash[11]; + unsigned int MappedTireHash; + unsigned int MappedRimHash; + unsigned int MappedRimBlurHash; + unsigned int MappedLicensePlateHash; + unsigned int ReplaceSkinHash; + unsigned int ReplaceSkinBHash; + unsigned int ReplaceGlobalHash; + unsigned int ReplaceWheelHash; + unsigned int ReplaceSpinnerHash; + unsigned int ReplaceSpoilerHash; + unsigned int ReplaceRoofScoopHash; + unsigned int ReplaceDashSkinHash; + unsigned int ReplaceHeadlightHash[3]; + unsigned int ReplaceHeadlightGlassHash[3]; + unsigned int ReplaceBrakelightHash[3]; + unsigned int ReplaceBrakelightGlassHash[3]; + unsigned int ReplaceReverselightHash[3]; + unsigned int ShadowHash; +}; +struct CarPartDatabaseLayout { + bTList CarPartPackList; + int NumPacks; + int NumParts; + int NumBytes; + CarPartIndex PaintPart_Gloss[3]; + CarPartIndex PaintPart_Metallic[3]; + CarPartIndex PaintPart_Pearl[3]; + CarPartIndex PaintPart_Vinyl[3]; + CarPartIndex PaintPart_Rims[3]; + CarPartIndex PaintPart_Caliper[3]; + CarPartIndex VinylPart_All[3]; + CarPartIndex VinylPart_Body[3]; + CarPartIndex VinylPart_Hood[3]; + CarPartIndex VinylPart_Side[3]; + CarPartIndex VinylPart_Manufacturer[3]; +}; +struct _DefragmentParams { + int NumCopyStorage; + int CopyStorageSize[8]; + void *CopyStorageMem[8]; + int LargestAllocationSize; + char LargestAllocationName[64]; + void *pAllocation; + void *pNewAllocation; + char AllocationName[64]; +}; +struct CarMemoryInfoEntryLayout { + const char *Name; + int pad0; + int Size; + int pad1[3]; +}; + +extern CarPartDatabase CarPartDB; +extern CarTypeInfo *CarTypeInfoArray; +signed char *CarSlotAnimHookupTable; +unsigned int *CarSlotAnimHideOpenTable; +unsigned int *CarSlotAnimHideClosedTable; +unsigned int *DefaultSlotTypeNameTable; +CarSlotTypeOverride *SlotTypeOverrideTable; +int NumSlotTypeOverrides; +const char *CarPartStringTable; +unsigned int CarPartStringTableSize; +unsigned int *CarPartTypeNameHashTable; +unsigned int CarPartTypeNameHashTableSize; +CarPart *CarPartPartsTable; +CarPartModelTable *CarPartModelsTable; +CarPartPack *MasterCarPartPack; +extern CarMemoryInfoEntryLayout CarMemoryInfoTable[6]; +extern const char lbl_8040A594[] asm("lbl_8040A594"); + +unsigned int CarPartModelTable::GetModelNameHash(unsigned int base_namehash, int model_num, int lod) { + if (reinterpret_cast(this->ModelNames[model_num][lod]) == -1) { + return 0; + } + + if (!this->TemplatedNameHashes) { + return reinterpret_cast(this->ModelNames[model_num][lod]); + } + + unsigned int namehash = base_namehash; + char lod_name[3] = {'_', static_cast(lod + 'A'), '\0'}; + + if (this->MiddleStringOffset != 0xFFFF) { + namehash = bStringHash(MasterCarPartPack->StringTable + this->MiddleStringOffset * 4, namehash); + } + + namehash = bStringHash(this->ModelNames[model_num][lod], namehash); + return bStringHash(lod_name, namehash); +} + +int ConvertVinylGroupNumberToVinylType(int vinyl_group_number) { + switch (vinyl_group_number) { + case 0: + case 2: + case 4: + case 5: + case 7: + case 9: + case 0xB: + case 0xC: + case 0xE: + return 0; + case 1: + case 3: + case 6: + case 8: + case 0xA: + case 0xD: + return 1; + case 0x11: + return 2; + case 0x10: + case 0x12: + return 3; + case 0xF: + return 4; + default: + return 0; + } +} +int CarInfo_GetMaxCompositingBufferSize(); +void GetUsedCarTextureInfo(UsedCarTextureInfo *used_texture_info, RideInfo *ride_info, int front_end_only); +extern int CarLoaderMemoryPoolNumber; +int CompositeSkin(RideInfo *ride_info); +extern SlotPool *LoadedTexturePackSlotPool; +extern SlotPool *LoadedSolidPackSlotPool; +extern SlotPool *LoadedSkinLayerSlotPool; +extern SlotPool *LoadedRideInfoSlotPool; +extern int UsePrecompositeVinyls; +_DefragmentParams DefragmentParams; +int bMemoryGetAllocations(int pool_num, void **allocations, int max_allocations); +int bGetMallocSize(const void *ptr); +const char *bGetMallocName(void *ptr); +int bGetMallocPool(void *ptr); +void ScratchPadMemCpy(void *dest, const void *src, unsigned int numbytes); +int CarInfo_GetResourceCost(CarType car_type, bool is_player_car, bool two_player); +float GetDebugRealTime(); +extern int CarLoaderServiceLoadingDepth; +extern int QueuedFileDefaultPriority; +void SetDelayedResourceCallback(void (*callback)(int), int param); +void eFixupReplacementTextureTables(); +void RefreshAllRenderInfo(CarType car_type); +void TrackStreamer_FlushHibernatingSections(TrackStreamer *track_streamer) asm("FlushHibernatingSections__13TrackStreamer"); +void TrackStreamer_MakeSpaceInPool(TrackStreamer *track_streamer, int size, bool use_callback) + asm("MakeSpaceInPool__13TrackStreamerib"); +LoadedTexturePack *LoadedTexturePack_Construct(LoadedTexturePack *loaded_texture_pack, const char *filename, int max_header_size) + asm("__17LoadedTexturePackPCci"); +void LoadedTexturePack_Destruct(LoadedTexturePack *loaded_texture_pack, int in_chrg) asm("_._17LoadedTexturePack"); +LoadedSolidPack *LoadedSolidPack_Construct(LoadedSolidPack *loaded_solid_pack, const char *filename) asm("__15LoadedSolidPackPCc"); +void LoadedSolidPack_Destruct(LoadedSolidPack *loaded_solid_pack, int in_chrg) asm("_._15LoadedSolidPack"); +LoadedSkinLayer *LoadedSkinLayer_Construct(LoadedSkinLayer *loaded_skin_layer, unsigned int name_hash) asm("__15LoadedSkinLayerUi"); +LoadedRideInfo *LoadedRideInfo_Construct(LoadedRideInfo *loaded_ride_info, RideInfo *ride_info, int in_front_end, int is_two_player, + int is_player_car) + asm("__14LoadedRideInfoP8RideInfoiii"); +int GatherModelHashes(RideInfo *ride_info, unsigned int *model_hashes, int num_hashes, int max_model_hashes, int first, int last); +int LoadedCar_GetModelHashes(LoadedCar *loaded_car, unsigned int *name_hashes, int max_hashes) + asm("GetModelHashes__9LoadedCarPUii"); +int LoadedSkin_GetTextureHashes(LoadedSkin *loaded_skin, unsigned int *name_hashes, int max_hashes, int load_perm_layers) + asm("GetTextureHashes__10LoadedSkinPUiii"); +int CarLoader_AllocateSkinLayers(CarLoader *car_loader, unsigned int *name_hashes, int num_hashes, + LoadedSkinLayer **loaded_skin_layers, int max_layers, const char *filename) + asm("AllocateSkinLayers__9CarLoaderPUiiPP15LoadedSkinLayeriPCc"); +int CarLoader_LoadSkinLayers(CarLoader *car_loader, unsigned int *name_hashes, int max_hashes, + LoadedSkinLayer **loaded_skin_layers, int max_layers) + asm("LoadSkinLayers__9CarLoaderPUiiPP15LoadedSkinLayeri"); +inline void LoadedTexturePack::operator delete(void *ptr) { + bFree(LoadedTexturePackSlotPool, ptr); +} + +LoadedTexturePack::~LoadedTexturePack() { + bFreeSharedString(this->Filename); +} + +inline void LoadedSolidPack::operator delete(void *ptr) { + bFree(LoadedSolidPackSlotPool, ptr); +} + +LoadedSolidPack::~LoadedSolidPack() { + bFreeSharedString(this->Filename); +} + +inline void LoadedSkinLayer::operator delete(void *ptr) { + bFree(LoadedSkinLayerSlotPool, ptr); +} + +inline void LoadedRideInfo::operator delete(void *ptr) { + bFree(LoadedRideInfoSlotPool, ptr); +} + +inline int LoadedSkin::IsLoaded() { + return this->LoadStatePerm == CARLOADSTATE_LOADED && this->LoadStateTemp == CARLOADSTATE_LOADED && this->DoneComposite != 0; +} + +inline CarTypeInfo *GetCarTypeInfo(CarType car_type) { + return &CarTypeInfoArray[car_type]; +} + +inline int LoadedCar::ShouldWeStream() { + return GetCarTypeInfo(this->Type)->GetCarUsageType() != 2; +} + +void CarLoader_UnallocateSkinLayers(CarLoader *car_loader, LoadedSkinLayer **loaded_skin_layers, int num_layers) + asm("UnallocateSkinLayers__9CarLoaderPP15LoadedSkinLayeri"); +int CarLoader_MakeSpaceInCarMemoryPool(CarLoader *car_loader, int required_size, int max_allocations, bool stream_textures) + asm("MakeSpaceInCarMemoryPool__9CarLoaderiib"); +void CarLoader_UnloadUnallocatedRideInfos(CarLoader *car_loader, int num_ride_infos) + asm("UnloadUnallocatedRideInfos__9CarLoaderi"); +void CarLoader_LoadedSolidPackCallback(CarLoader *car_loader, LoadedSolidPack *loaded_solid_pack) + asm("LoadedSolidPackCallback__9CarLoaderP15LoadedSolidPack"); +void CarLoader_LoadedCarCallback(CarLoader *car_loader, LoadedCar *loaded_car) asm("LoadedCarCallback__9CarLoaderP9LoadedCar"); +void CarLoader_LoadedWheelModelsCallback(CarLoader *car_loader) asm("LoadedWheelModelsCallback__9CarLoader"); +void CarLoader_LoadedWheelTexturesCallback(CarLoader *car_loader) asm("LoadedWheelTexturesCallback__9CarLoader"); +void CarLoader_LoadedAllTexturesFromPackCallback(CarLoader *car_loader) asm("LoadedAllTexturesFromPackCallback__9CarLoader"); +void bCloseMemoryPool(int pool_num); +bool bSetMemoryPoolDebugFill(int pool_num, bool on_off); +void bSetMemoryPoolTopDirection(int pool_num, bool top_means_larger_address); +void _eLoadStreamingSolid(unsigned int *name_hash_table, int num_hashes, void (*callback)(unsigned int), unsigned int param0, + int memory_pool_num) + asm("eLoadStreamingSolid__FPUiiPFPv_vPvi"); +inline void eLoadStreamingSolid(unsigned int *name_hash_table, int num_hashes, void (*callback)(unsigned int), unsigned int param0, + int memory_pool_num) { + _eLoadStreamingSolid(name_hash_table, num_hashes, callback, param0, memory_pool_num); +} +int eLoadStreamingSolidPack(const char *filename, void (*callback_function)(int), int callback_param, int memory_pool_num); +void eLoadStreamingTexture(unsigned int *name_hash_table, int num_hashes, void (*callback)(unsigned int), unsigned int param0, + int memory_pool_num); +int eLoadStreamingTexturePack(const char *filename, void (*callback_func)(unsigned int), unsigned int callback_param, int memory_pool_num); +void eUnloadStreamingTexture(unsigned int *name_hash_table, int num_hashes); +void eUnloadStreamingSolid(unsigned int *name_hash_table, int num_hashes); +int eUnloadStreamingSolidPack(const char *filename); +void eUnloadStreamingTexturePack(const char *filename); + +struct QueuedFilePrioritySetter { + int SavedPriority; + + QueuedFilePrioritySetter() + : SavedPriority(QueuedFileDefaultPriority) + { + CarLoaderServiceLoadingDepth++; + QueuedFileDefaultPriority = 4; + } + + ~QueuedFilePrioritySetter() { + CarLoaderServiceLoadingDepth--; + QueuedFileDefaultPriority = SavedPriority; + } +}; + +int GatherModelHashes(RideInfo *ride_info, unsigned int *model_hashes, int num_hashes, int max_model_hashes, int first, int last) { + ProfileNode profile_node("GatherModelHashes", 0); + CARPART_LOD minLodLevel = ride_info->GetMinLodLevel(); + CARPART_LOD maxLodLevel = ride_info->GetMaxLodLevel(); + + for (int slotIx = first; slotIx < last; slotIx++) { + for (CarPart *part = CarPartDB.NewGetFirstCarPart(ride_info->Type, slotIx, 0, -1); part != 0; + part = CarPartDB.NewGetNextCarPart(part, ride_info->Type, slotIx, 0, -1)) { + for (int modelIx = 0; modelIx < 1; modelIx++) { + for (int lod = minLodLevel; lod <= maxLodLevel; lod++) { + unsigned int model_name_hash = 0; + + if (part != 0) { + model_name_hash = part->GetModelNameHash(modelIx, lod); + } + + if (model_name_hash != 0) { + if (num_hashes < max_model_hashes) { + model_hashes[num_hashes] = model_name_hash; + } + + num_hashes++; + } + } + } + } + } + + return num_hashes; +} + +void *MoveDefragmentAllocation(void *allocation) { + ProfileNode profile_node("MoveDefragmentAllocation", 0); + int allocation_size = bGetMallocSize(allocation); + + if (allocation_size > DefragmentParams.LargestAllocationSize) { + return allocation; + } + + bool use_copy_storage = + allocation_size + 0x480 > reinterpret_cast(allocation) - reinterpret_cast(DefragmentParams.pNewAllocation); + void *new_allocation = allocation; + + if (use_copy_storage) { + int amount_copied = 0; + int n = 0; + if (allocation_size > 0) { + do { + int amount_to_copy = allocation_size - amount_copied; + + if (DefragmentParams.CopyStorageSize[n] < amount_to_copy) { + amount_to_copy = DefragmentParams.CopyStorageSize[n]; + } + + ScratchPadMemCpy(DefragmentParams.CopyStorageMem[n], reinterpret_cast(allocation) + amount_copied, amount_to_copy); + amount_copied += amount_to_copy; + n++; + } while (amount_copied < allocation_size); + } + + bFree(allocation); + new_allocation = 0; + DefragmentParams.pAllocation = 0; + } + + allocation = bMalloc(allocation_size, (CarLoaderMemoryPoolNumber & 0xF) | 0x2000); + + if (allocation != DefragmentParams.pNewAllocation) { + bMemoryPrintAllocationsByAddress(CarLoaderMemoryPoolNumber, 0, 0x7FFFFFFF); + bBreak(); + } + + if (use_copy_storage) { + int amount_copied = 0; + int n = 0; + if (allocation_size > 0) { + do { + int amount_to_copy = allocation_size - amount_copied; + + if (DefragmentParams.CopyStorageSize[n] < amount_to_copy) { + amount_to_copy = DefragmentParams.CopyStorageSize[n]; + } + + ScratchPadMemCpy(reinterpret_cast(allocation) + amount_copied, DefragmentParams.CopyStorageMem[n], amount_to_copy); + amount_copied += amount_to_copy; + n++; + } while (amount_copied < allocation_size); + } + } else { + ScratchPadMemCpy(allocation, new_allocation, allocation_size); + bFree(new_allocation); + DefragmentParams.pAllocation = 0; + } + + DCStoreRange(allocation, allocation_size); + + return allocation; +} + +CarLoader::CarLoader() { + this->StartLoadingTime = 0.0f; + this->LoadingInProgress = 0; + this->LoadingMode = MODE_FRONT_END; + this->InFrontEndFlag = 0; + this->TwoPlayerFlag = 0; + this->NumLoadedRideInfos = 0; + this->NumAllocatedRideInfos = 0; + this->MayNeedDefragmentation = 0; + this->pCallback = 0; + this->MemoryPoolMem = 0; + this->MemoryPoolSize = 0; + this->NumSpongeAllocations = 0; + this->NumLoadingSkinLayers = 0; +} + +LoadedTexturePack::LoadedTexturePack(const char *filename, int max_header_size) + : Filename(bAllocateSharedString(filename)), // + NumInstances(0), // + LoadState(0), // + Pad0(0), // + MaxHeaderSize(max_header_size), // + pStreamingPack(0) { + if (bFileSize(this->Filename) == 0) { + this->LoadState = 2; + } +} + +LoadedSolidPack::LoadedSolidPack(const char *filename) + : Filename(bAllocateSharedString(filename)), // + NumInstances(0), // + LoadState(0), // + pStreamingPack(0), // + pResourceFile(0) {} + +int CarInfo_GetResourceCost(CarType car_type, bool is_player_car, bool two_player) { + CarTypeInfo *car_type_info = &CarTypeInfoArray[car_type]; + int car_memory_info = car_type_info->CarMemTypeHash; + int i; + CarMemoryInfoEntryLayout *entry; + + (void)two_player; + if (is_player_car != 0) { + car_memory_info = bStringHash(lbl_8040A594); + } + + i = 0; + do { + entry = &CarMemoryInfoTable[i]; + if (bStringHash(entry->Name) != car_memory_info) { + i++; + } else { + return entry->Size << 10; + } + } while (i < 6); + + return 0; +} + +int LoadedSkin::GetTextureHashes(unsigned int *texture_hashes, int max_texture_hashes, int perm) { + UsedCarTextureInfoMirror used_texture_info; + + bMemSet(texture_hashes, 0, max_texture_hashes << 2); + GetUsedCarTextureInfo(reinterpret_cast(&used_texture_info), this->pRideInfo, this->InFrontEnd); + + int num_hashes = used_texture_info.NumTexturesToLoadTemp; + unsigned int *hashes = used_texture_info.TexturesToLoadTemp; + + if (perm != 0) { + hashes = used_texture_info.TexturesToLoadPerm; + num_hashes = used_texture_info.NumTexturesToLoadPerm; + } + + bMemCpy(texture_hashes, hashes, num_hashes << 2); + return num_hashes; +} + +int LoadedCar::GetModelHashes(unsigned int *model_hashes, int max_model_hashes) { + ProfileNode profile_node("GetModelHashes", 0); + RideInfo *ride_info; + + bMemSet(model_hashes, 0, max_model_hashes << 2); + ride_info = this->pRideInfo; + + CARPART_LOD minLodLevel = ride_info->GetMinLodLevel(); + CARPART_LOD maxLodLevel = ride_info->GetMaxLodLevel(); + + if (this->InFrontEnd != 0) { + minLodLevel = ride_info->GetMinFELodLevel(); + maxLodLevel = ride_info->GetMaxFELodLevel(); + } + + int num_hashes = 0; + + for (int slot_id = 0; slot_id < 0x4c; slot_id++) { + CarPart *car_part = ride_info->GetPart(slot_id); + CARPART_LOD currentSlotMinLodLevel = minLodLevel; + CARPART_LOD currentSlotMaxLodLevel = maxLodLevel; + + ride_info->GetSpecialLODRangeForCarSlot(slot_id, ¤tSlotMinLodLevel, ¤tSlotMaxLodLevel, this->InFrontEnd != 0); + + for (int model = 0; model < 1; model++) { + for (int lod = currentSlotMinLodLevel; lod <= currentSlotMaxLodLevel; lod++) { + if (car_part != 0) { + unsigned int model_name_hash = car_part->GetModelNameHash(model, lod); + + if (model_name_hash != 0) { + if (num_hashes < max_model_hashes) { + model_hashes[num_hashes] = model_name_hash; + } + num_hashes++; + } + } + } + } + } + + int new_num_hashes = GatherModelHashes(ride_info, model_hashes, num_hashes, max_model_hashes, 0x2e, 0x33); + num_hashes = new_num_hashes; + new_num_hashes = GatherModelHashes(ride_info, model_hashes, num_hashes, max_model_hashes, 1, 0x17); + num_hashes = new_num_hashes; + + if (num_hashes > max_model_hashes) { + num_hashes = max_model_hashes; + } + + unsigned char bitfield[512]; + + bMemSet(bitfield, 0, 0x200); + profile_node.Begin("GetModelHashes", 0); + + int num_hits = 0; + + int n = 0; + + while (n < num_hashes) { + unsigned int hash = model_hashes[n]; + int bit = (hash >> 3) & 0x1ff; + bool duplicate = false; + + if (((bitfield[bit] >> (hash & 7)) & 1U) == 0) { + bitfield[bit] |= 1 << (hash & 7); + } else { + int i = 0; + + if (num_hits > 0) { + while (i < num_hits && model_hashes[i] != hash) { + i++; + } + } + + if (i != num_hits) { + duplicate = true; + } + } + + if (!duplicate) { + model_hashes[num_hits] = hash; + num_hits++; + } + + n++; + } + + return num_hits; +} + +LoadedSkinLayer::LoadedSkinLayer(unsigned int name_hash) { + this->NameHash = name_hash; + this->NumInstances = 0; + this->LoadState = 0; + this->pad0 = 0; +} + +int CarLoader::Load(RideInfo *ride_info) { + int high_priority = 0; + int is_player_car = ride_info->GetCarRenderUsage() == CarRenderUsage_Player; + char filename[128]; + + bSPrintf(filename, "CARS\\%s\\TEXTURES.BIN", GetCarTypeName(ride_info->Type)); + + if (!bFileExists(filename)) { + bBreak(); + } + + LoadedRideInfo *loaded_ride_info = this->AllocateRideInfo(ride_info, is_player_car); + + loaded_ride_info->HighPriority = high_priority; + loaded_ride_info->IsPlayerCar = is_player_car; + this->LoadedRideInfoList.Remove(loaded_ride_info); + this->LoadedRideInfoList.AddTail(loaded_ride_info); + return loaded_ride_info->ID; +} + +LoadedRideInfo *CarLoader::AllocateRideInfo(RideInfo *ride_info, int is_player_car) { + LoadedRideInfo *loaded_ride_info = this->FindLoadedRideInfo(ride_info); + + if (loaded_ride_info == 0) { + while (bIsSlotPoolFull(LoadedRideInfoSlotPool)) { + while (this->LoadingInProgress != 0) { + ServiceResourceLoading(); + } + + this->RemoveSomethingFromCarMemoryPool(true); + } + + loaded_ride_info = LoadedRideInfo_Construct(static_cast(bOMalloc(LoadedRideInfoSlotPool)), ride_info, + this->InFrontEndFlag, this->TwoPlayerFlag, is_player_car); + this->NumLoadedRideInfos++; + loaded_ride_info->pLoadedCar->pLoadedSolidPack = this->AllocateSolidPack(loaded_ride_info->pCarTypeInfo->GeometryFilename); + this->AllocateSkinLayers(loaded_ride_info->pLoadedWheel->SkinNameHashesPerm, 4, + loaded_ride_info->pLoadedWheel->LoadedSkinLayersPerm, 4, 0); + + char texture_filename[72]; + + bSPrintf(texture_filename, "CARS\\%s\\TEXTURES.BIN", loaded_ride_info->pCarTypeInfo->CarTypeName); + loaded_ride_info->pLoadedSkin->pLoadedTexturesPack = this->AllocateTexturePack(texture_filename, 0x8000); + + if (ride_info->SkinType != 0) { + char vinyl_filename[72]; + + if (UsePrecompositeVinyls == 0 && ride_info->SkinType != 2) { + bSPrintf(vinyl_filename, "CARS\\%s\\VINYLS.BIN", loaded_ride_info->pCarTypeInfo->CarTypeName); + } else { + bSPrintf(vinyl_filename, "CARS\\%s\\PREVINYL.BIN", loaded_ride_info->pCarTypeInfo->CarTypeName); + } + + if (bFileExists(vinyl_filename)) { + loaded_ride_info->pLoadedSkin->pLoadedVinylsPack = this->AllocateTexturePack(vinyl_filename, 0x19000); + } + } + + unsigned int texture_hashes[129]; + int num_perm_hashes = loaded_ride_info->pLoadedSkin->GetTextureHashes(texture_hashes, 0x80, true); + + loaded_ride_info->pLoadedSkin->NumLoadedSkinLayersPerm = num_perm_hashes; + this->AllocateSkinLayers(texture_hashes, num_perm_hashes, loaded_ride_info->pLoadedSkin->LoadedSkinLayersPerm, + num_perm_hashes, 0); + + int num_temp_hashes = loaded_ride_info->pLoadedSkin->GetTextureHashes(texture_hashes, 0x80, false); + + loaded_ride_info->pLoadedSkin->NumLoadedSkinLayersTemp = num_temp_hashes; + this->AllocateSkinLayers(texture_hashes, num_temp_hashes, loaded_ride_info->pLoadedSkin->LoadedSkinLayersTemp, + num_temp_hashes, 0); + } + + if (loaded_ride_info->NumInstances == 0) { + this->NumAllocatedRideInfos++; + } + + loaded_ride_info->NumInstances++; + return loaded_ride_info; +} + +void CarLoader::UnallocateSolidPack(LoadedSolidPack *loaded_solid_pack) { + loaded_solid_pack->NumInstances--; +} + +int CarLoader::UnloadSolidPack(LoadedSolidPack *loaded_solid_pack) { + if (loaded_solid_pack->NumInstances == 0) { + if (loaded_solid_pack->LoadState == CARLOADSTATE_LOADED) { + if (loaded_solid_pack->pResourceFile != 0) { + UnloadResourceFile(loaded_solid_pack->pResourceFile); + loaded_solid_pack->pResourceFile = 0; + } else { + eUnloadStreamingSolidPack(loaded_solid_pack->Filename); + } + } + + delete this->LoadedSolidPackList.Remove(loaded_solid_pack); + return 1; + } + + return 0; +} + +int CarLoader::UnloadCar(LoadedCar *loaded_car) { + if (loaded_car->LoadState == CARLOADSTATE_LOADED && loaded_car->ShouldWeStream()) { + unsigned int model_hashes[800]; + int num_model_hashes = loaded_car->GetModelHashes(model_hashes, 800); + + eUnloadStreamingSolid(model_hashes, num_model_hashes); + } + + this->UnallocateSolidPack(loaded_car->pLoadedSolidPack); + this->UnloadSolidPack(loaded_car->pLoadedSolidPack); + loaded_car->pLoadedSolidPack = 0; + return 1; +} + +int CarLoader::UnloadWheel(LoadedWheel *loaded_wheel) { + unsigned int name_hashes[128]; + + this->UnallocateSkinLayers(loaded_wheel->LoadedSkinLayersPerm, 4); + + int num_hashes = this->UnloadSkinLayers(name_hashes, 0x80, loaded_wheel->LoadedSkinLayersPerm, 4); + + if (num_hashes != 0) { + eUnloadStreamingTexture(name_hashes, num_hashes); + } + + if (loaded_wheel->LoadState == CARLOADSTATE_LOADED) { + eUnloadStreamingSolid(reinterpret_cast(loaded_wheel->ModelNameHashes), 5); + } + + return 1; +} + +int CarLoader::UnloadSkinPerms(LoadedSkin *loaded_skin) { + unsigned int name_hash_table[87]; + + this->UnallocateSkinLayers(loaded_skin->LoadedSkinLayersPerm, loaded_skin->NumLoadedSkinLayersPerm); + + int num_name_hashes = + this->UnloadSkinLayers(name_hash_table, 0x57, loaded_skin->LoadedSkinLayersPerm, loaded_skin->NumLoadedSkinLayersPerm); + + if (num_name_hashes == 0) { + return 0; + } + + eUnloadStreamingTexture(name_hash_table, num_name_hashes); + loaded_skin->NumLoadedSkinLayersPerm = 0; + return 1; +} + +int CarLoader::UnloadSkin(LoadedSkin *loaded_skin) { + this->UnloadSkinTemporaries(loaded_skin, 1); + this->UnloadSkinPerms(loaded_skin); + this->UnallocateTexturePack(loaded_skin->pLoadedTexturesPack); + this->UnloadTexturePack(loaded_skin->pLoadedTexturesPack); + loaded_skin->pLoadedTexturesPack = 0; + return 1; +} + +int CarLoader::UnallocateRideInfo(LoadedRideInfo *loaded_ride_info) { + loaded_ride_info->NumInstances--; + + if (loaded_ride_info->NumInstances == 0) { + this->NumAllocatedRideInfos--; + return 1; + } + + return 0; +} + +int CarLoader::UnloadRideInfo(LoadedRideInfo *loaded_ride_info, int leave_if_in_mempool) { + ProfileNode profile_node("CarLoader::UnloadRideInfo", 0); + + if (loaded_ride_info->NumInstances > 0) { + return 0; + } + + if (loaded_ride_info->pLoadedSkin != 0 && loaded_ride_info->pLoadedSkin->IsLoaded()) { + this->UnloadSkinTemporaries(loaded_ride_info->pLoadedSkin, 0); + } + + int in_mempool = this->IsLoaded(loaded_ride_info); + + if (leave_if_in_mempool != 0 && in_mempool != 0) { + return 0; + } + + this->UnloadSkin(loaded_ride_info->pLoadedSkin); + this->UnloadWheel(loaded_ride_info->pLoadedWheel); + this->UnloadCar(loaded_ride_info->pLoadedCar); + delete this->LoadedRideInfoList.Remove(loaded_ride_info); + this->NumLoadedRideInfos--; + this->MayNeedDefragmentation++; + return 1; +} + +void CarLoader::Unload(int handle) { + LoadedRideInfo *loaded_ride_info = this->FindLoadedRideInfo(handle); + + if (loaded_ride_info != 0 && loaded_ride_info->NumInstances != 0) { + if (loaded_ride_info->HighPriority != 0) { + loaded_ride_info->Remove(); + this->LoadedRideInfoList.AddTail(loaded_ride_info); + } + + this->UnallocateRideInfo(loaded_ride_info); + + if (loaded_ride_info->NumInstances == 0 && loaded_ride_info->LoadState == CARLOADSTATE_QUEUED) { + this->UnloadRideInfo(loaded_ride_info, 0); + } + } +} + +int CarLoader::IsLoaded(int handle) { + LoadedRideInfo *loaded_ride_info = this->FindLoadedRideInfo(handle); + + if (loaded_ride_info == 0 || loaded_ride_info->NumInstances == 0) { + return 0; + } + + return this->IsLoaded(loaded_ride_info); +} + +int CarLoader::IsLoaded(LoadedRideInfo *loaded_ride_info) { + if (loaded_ride_info->pLoadedCar != 0 && loaded_ride_info->pLoadedCar->LoadState == CARLOADSTATE_LOADED && + loaded_ride_info->pLoadedWheel != 0 && loaded_ride_info->pLoadedWheel->LoadState == CARLOADSTATE_LOADED) { + LoadedSkin *loaded_skin = loaded_ride_info->pLoadedSkin; + + if (loaded_skin != 0 && loaded_skin->LoadStatePerm == CARLOADSTATE_LOADED && + loaded_skin->LoadStateTemp == CARLOADSTATE_LOADED && loaded_skin->DoneComposite != 0) { + return 1; + } + } + + return 0; +} + +void CarLoader::UnloadEverything() { + LoadedRideInfo *high_priority_ride_info = 0; + + for (LoadedRideInfo *loaded_ride_info = this->LoadedRideInfoList.GetHead(); + loaded_ride_info != this->LoadedRideInfoList.EndOfList(); loaded_ride_info = loaded_ride_info->GetNext()) { + if (loaded_ride_info->NumInstances > 0) { + this->UnallocateRideInfo(loaded_ride_info); + } + + if (loaded_ride_info->HighPriority != 0) { + high_priority_ride_info = loaded_ride_info; + } + } + + if (high_priority_ride_info != 0) { + high_priority_ride_info->Remove(); + this->LoadedRideInfoList.AddTail(high_priority_ride_info); + } + + while (this->LoadingInProgress != 0) { + ServiceResourceLoading(); + } + + this->UnloadOverflowedResources(); +} + +void CarLoader::UnloadOverflowedResources() { + LoadedRideInfo *loaded_ride_info = this->LoadedRideInfoList.GetHead(); + + while (loaded_ride_info != this->LoadedRideInfoList.EndOfList()) { + LoadedRideInfo *next = loaded_ride_info->GetNext(); + + this->UnloadRideInfo(loaded_ride_info, 1); + loaded_ride_info = next; + } +} + +void CarLoader::UnloadUnallocatedRideInfos(int max_left_unloaded) { + { + bool force_unload = false; + while (NumLoadedRideInfos - NumAllocatedRideInfos >= max_left_unloaded) { + if (!RemoveSomethingFromCarMemoryPool(force_unload)) { + return; + } + } + } +} + +void CarLoader::UnloadAllSkinTemporaries() { + for (LoadedRideInfo *loaded_ride_info = this->LoadedRideInfoList.GetHead(); + loaded_ride_info != this->LoadedRideInfoList.EndOfList(); loaded_ride_info = loaded_ride_info->GetNext()) { + LoadedSkin *loaded_skin = loaded_ride_info->pLoadedSkin; + + if (loaded_skin->IsLoaded()) { + this->UnloadSkinTemporaries(loaded_skin, 0); + } else if (loaded_ride_info->NumInstances == 0 && loaded_skin->LoadStateTemp == CARLOADSTATE_LOADED) { + int force_unload = 1; + this->UnloadSkinTemporaries(loaded_skin, force_unload); + } + } +} + +bool CarLoader::MakeSpaceInPool(int size) { + while (this->LoadingInProgress != 0) { + ServiceResourceLoading(); + } + + return this->MakeSpaceInCarMemoryPool(size, 0, false) != 0; +} + +int CarLoader::MakeSpaceInCarMemoryPool(int largest_malloc_needed, int amount_free_needed, bool allocating_stream_header_chunks) { + if (amount_free_needed == 0) { + amount_free_needed = largest_malloc_needed; + } + + int free_memory = bCountFreeMemory(CarLoaderMemoryPoolNumber); + int largest_malloc = bLargestMalloc((CarLoaderMemoryPoolNumber & 0xF) | 0x2000); + + while ((free_memory < amount_free_needed || largest_malloc < largest_malloc_needed) && + this->RemoveSomethingFromCarMemoryPool(true) != 0) { + free_memory = bCountFreeMemory(CarLoaderMemoryPoolNumber); + largest_malloc = bLargestMalloc((CarLoaderMemoryPoolNumber & 0xF) | 0x2000); + } + + if (free_memory < amount_free_needed || largest_malloc < largest_malloc_needed) { + return 0; + } + + if (allocating_stream_header_chunks) { + unsigned int lowest_header_chunks = 0; + + for (int i = 0; i < 2; i++) { + eStreamPackLoader *loader = &StreamingTexturePackLoader; + + if (i == 1) { + loader = &StreamingSolidPackLoader; + } + + for (eStreamingPack *streaming_pack = loader->LoadedStreamingPackList.GetHead(); + streaming_pack != loader->LoadedStreamingPackList.EndOfList(); streaming_pack = streaming_pack->GetNext()) { + if (streaming_pack->HeaderChunks != 0 && bGetMallocPool(streaming_pack->HeaderChunks) == CarLoaderMemoryPoolNumber && + (lowest_header_chunks == 0 || + reinterpret_cast(streaming_pack->HeaderChunks) < lowest_header_chunks)) { + lowest_header_chunks = reinterpret_cast(streaming_pack->HeaderChunks); + } + } + } + + void *allocated_memory = bMalloc(largest_malloc_needed, (CarLoaderMemoryPoolNumber & 0xF) | 0x2040); + + bFree(allocated_memory); + + if (lowest_header_chunks != 0 && allocated_memory != 0) { + int distance = reinterpret_cast(allocated_memory) + largest_malloc_needed - lowest_header_chunks; + + if (distance < 0) { + distance = -distance; + } + + if (distance > 0x100) { + this->DefragmentPool(); + } + } + } + + return 1; +} + +int CarLoader::RemoveSomethingFromCarMemoryPool(bool force_unload) { + LoadedRideInfo *loaded_ride_info; + + for (loaded_ride_info = this->LoadedRideInfoList.GetHead(); loaded_ride_info != this->LoadedRideInfoList.EndOfList(); + loaded_ride_info = loaded_ride_info->GetNext()) { + int leave_if_in_mempool = this->UnloadRideInfo(loaded_ride_info, 0); + + if (leave_if_in_mempool != 0) { + return 1; + } + } + + if (force_unload == 0) { + return 0; + } + + if (this->DefragmentPool() != 0) { + return 1; + } + + if (this->NumSpongeAllocations != 0) { + this->NumSpongeAllocations--; + bFree(this->SpongeAllocations[this->NumSpongeAllocations]); + this->MayNeedDefragmentation++; + return 1; + } + + { + int mem_remaining; + } + + { + int pass = 0; + + do { + for (loaded_ride_info = this->LoadedRideInfoList.GetHead(); + loaded_ride_info != this->LoadedRideInfoList.EndOfList(); + loaded_ride_info = loaded_ride_info->GetNext()) { + if (loaded_ride_info->HighPriority == 0 || pass == 1) { + LoadedSkin *loaded_skin = loaded_ride_info->pLoadedSkin; + + { + if (loaded_skin->LoadStatePerm == CARLOADSTATE_LOADED) { + if (this->UnloadSkinPerms(loaded_skin) != 0) { + int success_scope; + + if (this->LoadingMode == MODE_FRONT_END) { + bBreak(); + } + + eFixupReplacementTextureTables(); + RefreshAllRenderInfo(loaded_skin->pRideInfo->Type); + return 1; + } + } + } + } + } + + pass++; + } while (pass < 2); + } + + this->PrintMemoryUsage(false); + bBreak(); + return 0; +} + +void CarLoader::PrintMemoryUsage(bool on_screen) { + static float lastTime; + + if (2.5f <= GetDebugRealTime() - lastTime) { + void *allocations[1152]; + char allocation_uses[1024]; + void *memory_entries[256]; + int total_unique_loaded_size = 0; + int num_allocations; + + lastTime = GetDebugRealTime(); + num_allocations = bMemoryGetAllocations(CarLoaderMemoryPoolNumber, allocations, 0x480); + bMemSet(allocation_uses, 0, 0x400); + + for (LoadedRideInfo *loaded_ride_info = this->LoadedRideInfoList.GetHead(); + loaded_ride_info != this->LoadedRideInfoList.EndOfList(); loaded_ride_info = loaded_ride_info->GetNext()) { + if (!on_screen) { + this->IsLoaded(loaded_ride_info); + } + + int num_memory_entries = this->GetMemoryEntries(loaded_ride_info->pLoadedCar, memory_entries, 0); + int total_memory_size = 0; + int unique_memory_size = 0; + + num_memory_entries = this->GetMemoryEntries(loaded_ride_info->pLoadedSkin, memory_entries, num_memory_entries); + num_memory_entries = this->GetMemoryEntries(loaded_ride_info->pLoadedWheel, memory_entries, num_memory_entries); + + for (int i = 0; i < num_memory_entries; i++) { + void *memory_entry = memory_entries[i]; + int allocation_size = bGetMallocSize(memory_entry); + + for (int j = 0; j < num_allocations; j++) { + if (memory_entry == allocations[j]) { + total_memory_size += allocation_size; + + if (allocation_uses[j] == 0) { + unique_memory_size += allocation_size; + } + + allocation_uses[j]++; + break; + } + } + } + + if (loaded_ride_info->NumInstances > 0) { + total_unique_loaded_size += unique_memory_size; + } + + int resource_cost = CarInfo_GetResourceCost(loaded_ride_info->TheRideInfo.Type, loaded_ride_info->IsPlayerCar != 0, + this->TwoPlayerFlag != 0); + + if (on_screen && loaded_ride_info->NumInstances > 0) { + bReleasePrintf("%s: %dK %dK %dK\n", loaded_ride_info->Name, (total_memory_size + 0x3FF) >> 10, + (unique_memory_size + 0x3FF) >> 10, (resource_cost + 0x3FF) >> 10); + } + } + + if (on_screen) { + bReleasePrintf("Loaded cars: %dK %dK\n", (total_unique_loaded_size + 0x3FF) >> 10, + (this->MemoryPoolSize + 0x3FF) >> 10); + } + + if (num_allocations > 0) { + int num_sponge_allocations = this->NumSpongeAllocations; + + for (int i = 0; i < num_allocations; i++) { + void *allocation = allocations[i]; + + for (int j = 0; j < num_sponge_allocations; j++) { + if (this->SpongeAllocations[j] == allocation) { + allocation_uses[i] = 1; + } + } + } + } + } +} + +bool CarLoader::DefragmentAllocation(void *allocation) { + static int last_result_was_textures; + + for (int i = 0; i < this->NumSpongeAllocations; i++) { + if (this->SpongeAllocations[i] == allocation) { + this->SpongeAllocations[i] = MoveDefragmentAllocation(allocation); + return true; + } + } + + for (LoadedRideInfo *loaded_ride_info = this->LoadedRideInfoList.GetHead(); + loaded_ride_info != this->LoadedRideInfoList.EndOfList(); loaded_ride_info = loaded_ride_info->GetNext()) { + LoadedSolidPack *loaded_solid_pack = loaded_ride_info->pLoadedCar->pLoadedSolidPack; + + if (loaded_solid_pack->pResourceFile != 0 && loaded_solid_pack->pResourceFile->GetMemory() == allocation) { + loaded_solid_pack->pResourceFile->ManualUnload(); + loaded_solid_pack->pResourceFile->ManualReload(reinterpret_cast(MoveDefragmentAllocation(allocation))); + return true; + } + } + + if (last_result_was_textures != 0 && StreamingTexturePackLoader.DefragmentAllocation(allocation)) { + return true; + } + + if (StreamingSolidPackLoader.DefragmentAllocation(allocation)) { + last_result_was_textures = 0; + return true; + } + + if (StreamingTexturePackLoader.DefragmentAllocation(allocation)) { + last_result_was_textures = 1; + return true; + } + + return false; +} + +bool CarLoader::AllocateDefragmentStorage() { + const char *memory_pool_names[5] = { + "Main Pool", + "Track Streaming", + "Audio Memory Pool", + "Speech Cache Memory Pool", + "FEngMemoryPool", + }; + int total_size = 0; + int num_copy_storage = 0; + + if (DefragmentParams.LargestAllocationSize > 0) { + while (true) { + int largest_malloc = 0; + int memory_pool_num = -1; + int required_size = DefragmentParams.LargestAllocationSize - total_size; + + for (int i = 0; i < 5; i++) { + int pool_num = bGetMemoryPoolNum(memory_pool_names[i]); + + if (pool_num > -1) { + int pool_largest_malloc = bLargestMalloc(pool_num); + + if (largest_malloc < pool_largest_malloc) { + memory_pool_num = pool_num; + largest_malloc = pool_largest_malloc; + } + } + } + + if (largest_malloc == 0) { + break; + } + + if (required_size < largest_malloc) { + largest_malloc = required_size; + } + + DefragmentParams.CopyStorageSize[num_copy_storage] = largest_malloc; + DefragmentParams.CopyStorageMem[num_copy_storage] = bMalloc(largest_malloc, memory_pool_num & 0xF); + total_size += largest_malloc; + num_copy_storage++; + + if (DefragmentParams.LargestAllocationSize <= total_size || num_copy_storage > 7) { + break; + } + } + } + + DefragmentParams.NumCopyStorage = num_copy_storage; + return total_size == DefragmentParams.LargestAllocationSize; +} + +void CarLoader::FreeDefragmentStorage() { + for (int i = 0; i < DefragmentParams.NumCopyStorage; i++) { + bFree(DefragmentParams.CopyStorageMem[i]); + } + + DefragmentParams.NumCopyStorage = 0; +} + +int CarLoader::DefragmentPool() { + static int loop_number; + void *allocations[1152]; + void *probe_allocations[33]; + + if (this->MayNeedDefragmentation == 0) { + return 0; + } + + bGetTicker(); + + int num_allocations = bMemoryGetAllocations(CarLoaderMemoryPoolNumber, allocations, 0x480); + + bMemSet(&DefragmentParams, 0, sizeof(DefragmentParams)); + + if (num_allocations > 0) { + for (int i = 0; i < num_allocations; i++) { + void *allocation = allocations[i]; + int allocation_size = bGetMallocSize(allocation); + + if (DefragmentParams.LargestAllocationSize < allocation_size) { + DefragmentParams.LargestAllocationSize = allocation_size; + bSafeStrCpy(DefragmentParams.LargestAllocationName, bGetMallocName(allocation), 0x40); + } + } + } + + if (!this->AllocateDefragmentStorage()) { + this->FreeDefragmentStorage(); + return 0; + } + + eWaitUntilRenderingDone(); + gDefragFixer.NumRanges = 0; + gDefragFixer.MemLow = 0; + gDefragFixer.MemHigh = 0; + + int num_probe_allocations = 0; + int upper_allocation = reinterpret_cast(bMalloc(0x80, (CarLoaderMemoryPoolNumber & 0xF) | 0x2000)); + + bFree(reinterpret_cast(upper_allocation)); + + for (int i = 0; i < num_allocations; i++) { + void *allocation = allocations[i]; + int movement_offset = 0; + int allocation_size = bGetMallocSize(allocation); + + if (upper_allocation < reinterpret_cast(allocation)) { + DefragmentParams.pAllocation = allocation; + bStrNCpy(DefragmentParams.AllocationName, bGetMallocName(allocation), 0x3F); + + while (true) { + void *probe_allocation = bMalloc(1, (CarLoaderMemoryPoolNumber & 0xF) | 0x2000); + + if (reinterpret_cast(probe_allocation) >= upper_allocation - 0x80) { + bFree(probe_allocation); + movement_offset = reinterpret_cast(probe_allocation) - reinterpret_cast(allocation); + ChunkMovementOffset = movement_offset; + DefragmentParams.pNewAllocation = probe_allocation; + + if (!this->DefragmentAllocation(allocation)) { + movement_offset = 0; + } + + ChunkMovementOffset = 0; + break; + } + + probe_allocations[num_probe_allocations] = probe_allocation; + num_probe_allocations++; + } + } + + gDefragFixer.Add(allocation, allocation_size, movement_offset); + loop_number++; + } + + for (int i = 0; i < num_probe_allocations; i++) { + bFree(probe_allocations[i]); + } + + this->FreeDefragmentStorage(); + bMemSet(&DefragmentParams, 0, sizeof(DefragmentParams)); + eFixupReplacementTextureTables(); + RefreshAllRenderInfo(static_cast(-1)); + gDefragFixer.MemLow = 0; + gDefragFixer.NumRanges = 0; + gDefragFixer.MemHigh = 0; + this->MayNeedDefragmentation = 0; + return 1; +} + +LoadedWheel::LoadedWheel(RideInfo *ride_info, bool in_fe) { + this->LoadState = CARLOADSTATE_QUEUED; + this->LoadStateSkinPerm = CARLOADSTATE_QUEUED; + this->LoadStateSkinTemp = CARLOADSTATE_QUEUED; + this->PartNameHash = 0; + this->TextureBaseNameHash = 0; + this->pCarPart = 0; + + if (in_fe) { + this->mMinLodLevel = ride_info->GetMinFELodLevel(); + this->mMaxLodLevel = ride_info->GetMaxFELodLevel(); + } else { + this->mMinLodLevel = ride_info->GetMinLodLevel(); + this->mMaxLodLevel = ride_info->GetMaxLodLevel(); + } + + bMemSet(this->ModelNameHashes, 0, sizeof(this->ModelNameHashes)); + bMemSet(this->SkinNameHashesPerm, 0, sizeof(this->SkinNameHashesPerm)); + bMemSet(this->SkinNameHashesTemp, 0, sizeof(this->SkinNameHashesTemp)); + bMemSet(this->LoadedSkinLayersPerm, 0, sizeof(this->LoadedSkinLayersPerm)); + bMemSet(this->LoadedSkinLayersTemp, 0, sizeof(this->LoadedSkinLayersTemp)); + + CarPart *car_part = ride_info->GetPart(0x42); + if (car_part != 0) { + this->pCarPart = car_part; + this->PartNameHash = car_part->GetPartNameHash(); + this->TextureBaseNameHash = car_part->GetTextureNameHash(); + + if (car_part->GetCarTypeNameHash() == bStringHash("WHEELS")) { + for (int model = 0; model < 1; model++) { + for (int lod = this->mMinLodLevel; lod <= this->mMaxLodLevel; lod++) { + this->ModelNameHashes[model][lod] = car_part->GetModelNameHash(model, lod); + } + } + + if (this->TextureBaseNameHash != 0) { + this->SkinNameHashesPerm[0] = bStringHash("_WHEEL", this->TextureBaseNameHash); + } + return; + } + } + + this->LoadState = CARLOADSTATE_LOADED; + this->LoadStateSkinPerm = CARLOADSTATE_LOADED; + this->LoadStateSkinTemp = CARLOADSTATE_LOADED; +} + +LoadedSkin::LoadedSkin(RideInfo *ride_info, int in_front_end, int is_player_skin) + : pRideInfo(ride_info), // + LoadStatePerm(0), // + LoadStateTemp(0), // + DoneComposite(0), // + IsPlayerSkin(is_player_skin), // + InFrontEnd(in_front_end), // + pLoadedTexturesPack(0), // + pLoadedVinylsPack(0), // + NumLoadedSkinLayersPerm(0) { + bMemSet(this->LoadedSkinLayersPerm, 0, sizeof(this->LoadedSkinLayersPerm)); + this->NumLoadedSkinLayersTemp = 0; + bMemSet(this->LoadedSkinLayersTemp, 0, sizeof(this->LoadedSkinLayersTemp)); +} + +LoadedCar::LoadedCar(RideInfo *ride_info, int in_front_end, int is_two_player) { + this->pRideInfo = ride_info; + this->LoadState = CARLOADSTATE_QUEUED; + this->Type = ride_info->Type; + this->InFrontEnd = in_front_end; + this->IsTwoPlayer = is_two_player; + this->pLoadedSolidPack = 0; +} + +int LoadedRideInfo::sNextID; + +LoadedRideInfo::LoadedRideInfo(RideInfo *ride_info, int in_front_end, int is_two_player, int is_player_car) + : TheRideInfo(*ride_info), // + TheLoadedCar(&TheRideInfo, in_front_end, is_two_player), // + TheLoadedWheel(&TheRideInfo, in_front_end != 0), // + TheLoadedSkin(&TheRideInfo, in_front_end, is_player_car) { + this->NumInstances = 0; + this->LoadState = CARLOADSTATE_QUEUED; + this->PrintedLoading = 0; + this->HighPriority = 0; + this->pCarTypeInfo = &CarTypeInfoArray[ride_info->Type]; + this->ID = sNextID++; + this->pLoadedCar = &this->TheLoadedCar; + this->pLoadedWheel = &this->TheLoadedWheel; + this->pLoadedSkin = &this->TheLoadedSkin; + bSPrintf(this->Name, "%s(%d)", this->pCarTypeInfo->GetName(), this->ID); +} + +void InitCarLoader() { + LoadedTexturePackSlotPool = bNewSlotPool(0x18, 0x1e, "CarLoadedTexturePackSlotPool", 0); + LoadedSolidPackSlotPool = bNewSlotPool(0x18, 0x1e, "CarLoadedSolidPackSlotPool", 0); + LoadedSkinLayerSlotPool = bNewSlotPool(0x10, 0x2ee, "CarLoadedSkinLayerSlotPool", 0); + LoadedRideInfoSlotPool = bNewSlotPool(0x6d4, 0x14, "CarLoadedRideInfoSlotPool", 0); +} + +static inline int ClampUpgradeLevel(int level) { + if (level < 0) { + return 0; + } + + if (level > 2) { + return 2; + } + + return level; +} + +int LoaderCarInfo(bChunk *chunk) { + unsigned int chunk_id = chunk->GetID(); + volatile CarType allowed_types[1]; + + if (chunk_id == 0x34600) { + CarTypeInfoArray = reinterpret_cast(chunk->GetAlignedData(16)); + + for (int j = 0; j < 0x54; j++) { + CarTypeInfo *pCarInfo = &CarTypeInfoArray[j]; + + bEndianSwap32(&pCarInfo->CarTypeNameHash); + bEndianSwap32(&pCarInfo->HeadlightFOV); + bPlatEndianSwap(&pCarInfo->HeadlightPosition); + bPlatEndianSwap(&pCarInfo->DriverRenderingOffset); + bPlatEndianSwap(&pCarInfo->InCarSteeringWheelRenderingOffset); + bEndianSwap32(&pCarInfo->DefaultBasePaint); + bEndianSwap32(&pCarInfo->Type); + bEndianSwap32(&pCarInfo->UsageType); + bEndianSwap32(&pCarInfo->CarMemTypeHash); + + for (int i = 0; i < 5; i++) { + bEndianSwap32(&pCarInfo->MinTimeBetweenUses[i]); + } + } + } else if (chunk_id == 0x34608) { + CarSlotAnimHookupTable = reinterpret_cast(chunk->GetData()); + } else if (chunk_id == 0x34609) { + CarSlotAnimHideOpenTable = reinterpret_cast(chunk->GetData()); + CarSlotAnimHideClosedTable = reinterpret_cast(chunk->GetData()) + 0x20; + return 1; + } else if (chunk_id == 0x34607) { + DefaultSlotTypeNameTable = reinterpret_cast(chunk->GetData()); + SlotTypeOverrideTable = reinterpret_cast(reinterpret_cast(chunk->GetData()) + 0x116); + NumSlotTypeOverrides = static_cast(chunk->GetSize() - 0x458) >> 4; + + for (int i = 0; i < 0x116; i++) { + bEndianSwap32(&DefaultSlotTypeNameTable[i]); + } + + for (int i = 0; i < NumSlotTypeOverrides; i++) { + bEndianSwap32(&SlotTypeOverrideTable[i].CarType); + bEndianSwap32(&SlotTypeOverrideTable[i].SlotId); + + for (int j = 0; j < 2; j++) { + bEndianSwap32(&SlotTypeOverrideTable[i].LookupType[j]); + } + } + } else if (chunk_id == 0x80034602) { + bChunk *car_pack_chunk = chunk->GetFirstChunk(); + bChunk *car_string_table_chunk = car_pack_chunk->GetNext(); + bChunk *car_attributetable_table_chunk = car_string_table_chunk->GetNext(); + bChunk *car_attributes_table_chunk = car_attributetable_table_chunk->GetNext(); + bChunk *car_model_table_chunk = car_attributes_table_chunk->GetNext(); + bChunk *car_typename_table_chunk = car_model_table_chunk->GetNext(); + bChunk *car_parts_table_chunk = car_typename_table_chunk->GetNext(); + CarPartPack *car_part_pack = reinterpret_cast(car_pack_chunk->GetData()); + unsigned char *track; + unsigned char *end_track; + + car_part_pack->EndianSwap(); + car_part_pack->AttributesTable = reinterpret_cast(car_attributes_table_chunk->GetData()); + car_part_pack->InPlaceInit(); + car_part_pack->AttributeTableTable = reinterpret_cast(car_attributetable_table_chunk->GetData()); + car_part_pack->PartsTable = reinterpret_cast(car_parts_table_chunk->GetData()); + car_part_pack->TypeNameTable = reinterpret_cast(car_typename_table_chunk->GetData()); + car_part_pack->ModelTable = reinterpret_cast(car_model_table_chunk->GetData()); + car_part_pack->StringTable = reinterpret_cast(car_string_table_chunk->GetData()); + car_part_pack->StringTableSize = car_string_table_chunk->GetSize(); + CarPartStringTable = reinterpret_cast(car_string_table_chunk->GetData()); + CarPartTypeNameHashTable = reinterpret_cast(car_typename_table_chunk->GetData()); + CarPartStringTableSize = car_string_table_chunk->GetSize(); + CarPartTypeNameHashTableSize = car_part_pack->NumTypeNames; + CarPartPartsTable = reinterpret_cast(car_parts_table_chunk->GetData()); + CarPartModelsTable = reinterpret_cast(car_model_table_chunk->GetData()); + MasterCarPartPack = car_part_pack; + track = reinterpret_cast(car_part_pack->AttributeTableTable); + end_track = track + car_attributetable_table_chunk->GetSize(); + + while (track < end_track) { + reinterpret_cast(track)->EndianSwap(); + track += reinterpret_cast(track)->GetByteSize(); + } + + for (unsigned int attribute_index = 0; attribute_index < car_part_pack->NumAttributes; attribute_index++) { + CarPartAttribute *car_part_attribute = &car_part_pack->AttributesTable[attribute_index]; + + bEndianSwap32(&car_part_attribute->Params.iParam); + bEndianSwap32(&car_part_attribute->NameHash); + } + + for (unsigned int typename_hash_index = 0; typename_hash_index < car_part_pack->NumTypeNames; typename_hash_index++) { + bEndianSwap32(&CarPartTypeNameHashTable[typename_hash_index]); + } + + for (unsigned int model_table_index = 0; model_table_index < car_part_pack->NumModelTables; model_table_index++) { + CarPartModelTable *model_table = + reinterpret_cast(reinterpret_cast(car_part_pack->ModelTable) + model_table_index * 0x18); + + model_table->EndianSwap(); + + if (model_table->TemplatedNameHashes != 0) { + for (int model_number = 0; model_number < 1; model_number++) { + for (int model_lod = 0; model_lod < 5; model_lod++) { + if (reinterpret_cast(model_table->ModelNames[model_number][model_lod]) != -1) { + model_table->ModelNames[model_number][model_lod] = reinterpret_cast( + const_cast(CarPartStringTable) + reinterpret_cast(model_table->ModelNames[model_number][model_lod]) * 4); + } + } + } + } + } + + for (unsigned int i = 0; i < car_part_pack->NumParts; i++) { + CarPart *car_part = reinterpret_cast(reinterpret_cast(car_part_pack->PartsTable) + i * 0xE); + CarPartIndex *index0 = 0; + CarPartIndex *index1 = 0; + + car_part->EndianSwap(); + + int part_id = car_part->GetPartID(); + unsigned int brand_name = car_part->GetBrandNameHash(); + unsigned char packed_group = *(reinterpret_cast(car_part) + 5); + int upgrade_level = (packed_group >> 5) - 1; + int group_number = packed_group & 0x1F; + + if (upgrade_level < 0) { + upgrade_level = 0; + } + if (upgrade_level > 2) { + upgrade_level = 2; + } + + if (part_id == 'L') { + switch (brand_name) { + case 0x02DAAB07: + index0 = &reinterpret_cast(&CarPartDB)->PaintPart_Gloss[upgrade_level]; + break; + case 0x03437A52: + index0 = &reinterpret_cast(&CarPartDB)->PaintPart_Metallic[upgrade_level]; + break; + case 0x03797533: + index0 = &reinterpret_cast(&CarPartDB)->PaintPart_Pearl[upgrade_level]; + break; + case 0x03E871F1: + index0 = &reinterpret_cast(&CarPartDB)->PaintPart_Vinyl[upgrade_level]; + break; + case 0x0000DA27: + index0 = &reinterpret_cast(&CarPartDB)->PaintPart_Rims[upgrade_level]; + break; + case 0xD6640DFF: + index0 = &reinterpret_cast(&CarPartDB)->PaintPart_Caliper[upgrade_level]; + break; + } + } else if (part_id == 'O') { + int vinyl_type = ConvertVinylGroupNumberToVinylType(group_number); + + switch (vinyl_type) { + case 0: + index0 = &reinterpret_cast(&CarPartDB)->VinylPart_Side[upgrade_level]; + break; + case 1: + index0 = &reinterpret_cast(&CarPartDB)->VinylPart_Hood[upgrade_level]; + break; + case 2: + index0 = &reinterpret_cast(&CarPartDB)->VinylPart_Body[upgrade_level]; + break; + case 3: + index0 = &reinterpret_cast(&CarPartDB)->VinylPart_Manufacturer[upgrade_level]; + break; + } + + index1 = &reinterpret_cast(&CarPartDB)->VinylPart_All[upgrade_level]; + } + + if (index0 != 0) { + if (index0->Part == 0) { + index0->Part = car_part; + } + index0->NumParts++; + } + + if (index1 != 0) { + if (index1->Part == 0) { + index1->Part = car_part; + } + index1->NumParts++; + } + } + + CarPartDatabaseLayout *database = reinterpret_cast(&CarPartDB); + MasterCarPartPack = car_part_pack; + database->CarPartPackList.AddTail(car_part_pack); + database->NumPacks += 1; + database->NumParts += car_part_pack->NumParts; + database->NumBytes += chunk->GetSize(); + } else { + return 0; + } + + return 1; +} + +int UnloaderCarInfo(bChunk *chunk) { + unsigned int chunk_id = chunk->GetID(); + + if (chunk_id == 0x34600) { + CarTypeInfoArray = 0; + return 1; + } + + if (chunk_id == 0x80034602) { + bChunk *car_pack_chunk = chunk->GetFirstChunk(); + CarPartPack *car_part_pack = reinterpret_cast(car_pack_chunk->GetData()); + CarPartDatabaseLayout *database = reinterpret_cast(&CarPartDB); + + car_part_pack->Remove(); + database->NumPacks -= 1; + database->NumParts -= car_part_pack->NumParts; + database->NumBytes -= chunk->GetSize(); + return 1; + } + + return 0; +} + +void CarLoader::SetLoadingMode(eLoadingMode mode, int two_player_flag) { + this->InFrontEndFlag = mode == MODE_FRONT_END; + this->LoadingMode = mode; + this->TwoPlayerFlag = two_player_flag; +} + +LoadedSolidPack *CarLoader::AllocateSolidPack(const char *filename) { + LoadedSolidPack *loaded_solid_pack = this->FindLoadedSolidPack(filename); + + if (loaded_solid_pack == 0) { + loaded_solid_pack = LoadedSolidPack_Construct(static_cast(bOMalloc(LoadedSolidPackSlotPool)), filename); + this->LoadedSolidPackList.AddTail(loaded_solid_pack); + } + + loaded_solid_pack->NumInstances++; + return loaded_solid_pack; +} + +LoadedTexturePack *CarLoader::AllocateTexturePack(const char *filename, int max_header_size) { + LoadedTexturePack *loaded_texture_pack = this->FindLoadedTexturePack(filename); + + if (loaded_texture_pack == 0) { + loaded_texture_pack = LoadedTexturePack_Construct(static_cast(bOMalloc(LoadedTexturePackSlotPool)), + filename, max_header_size); + this->LoadedTexturePackList.AddTail(loaded_texture_pack); + } + + loaded_texture_pack->NumInstances++; + return loaded_texture_pack; +} + +int CarLoader::AllocateSkinLayers(unsigned int *name_hash_table, int num_name_hashes, LoadedSkinLayer **loaded_skin_layer_table, + int max_loaded_skin_layers, const char *filename) { + eStreamingPack *streaming_pack = 0; + + if (filename != 0) { + streaming_pack = StreamingTexturePackLoader.GetLoadedStreamingPack(filename); + + if (streaming_pack == 0) { + return 0; + } + } + + int num_skin_layers = 0; + + for (int n = 0; n < num_name_hashes; n++) { + unsigned int name_hash = name_hash_table[n]; + + if (name_hash != 0 && + (streaming_pack == 0 || StreamingTexturePackLoader.GetLoadedStreamingPack(name_hash) == streaming_pack)) { + LoadedSkinLayer *loaded_skin_layer = this->FindLoadedSkinLayer(name_hash); + + if (loaded_skin_layer == 0) { + loaded_skin_layer = LoadedSkinLayer_Construct(static_cast(bOMalloc(LoadedSkinLayerSlotPool)), name_hash); + this->LoadedSkinLayerList.AddTail(loaded_skin_layer); + } + + loaded_skin_layer->NumInstances++; + loaded_skin_layer_table[num_skin_layers] = loaded_skin_layer; + num_skin_layers++; + } + } + + for (int i = num_skin_layers; i < max_loaded_skin_layers; i++) { + loaded_skin_layer_table[i] = 0; + } + + return num_skin_layers; +} + +void CarLoader::UnallocateSkinLayers(LoadedSkinLayer **loaded_skin_layer_table, int num_loaded_skin_layers) { + for (int n = 0; n < num_loaded_skin_layers; n++) { + LoadedSkinLayer *loaded_skin_layer = loaded_skin_layer_table[n]; + + if (loaded_skin_layer != 0) { + loaded_skin_layer->NumInstances--; + } + } +} + +int CarLoader::LoadSkinLayers(unsigned int *name_hash_table, int max_name_hashes, LoadedSkinLayer **loaded_skin_layer_table, + int num_loaded_skin_layers) { + int num_name_hashes = 0; + + for (int n = 0; n < num_loaded_skin_layers; n++) { + LoadedSkinLayer *loaded_skin_layer = loaded_skin_layer_table[n]; + + if (loaded_skin_layer != 0 && loaded_skin_layer->LoadState == CARLOADSTATE_QUEUED) { + loaded_skin_layer->LoadState = CARLOADSTATE_LOADING; + name_hash_table[num_name_hashes] = loaded_skin_layer->NameHash; + num_name_hashes++; + } + } + + return num_name_hashes; +} + +void CarLoader::UnallocateTexturePack(LoadedTexturePack *loaded_texture_pack) { + loaded_texture_pack->NumInstances--; +} + +int CarLoader::GetMemoryEntries(LoadedSolidPack *loaded_solid_pack, void **memory_entries, int num_memory_entries) { + if (loaded_solid_pack->pStreamingPack != 0) { + num_memory_entries = loaded_solid_pack->pStreamingPack->GetHeaderMemoryEntries(memory_entries, num_memory_entries); + } + + if (loaded_solid_pack->pResourceFile != 0) { + memory_entries[num_memory_entries] = loaded_solid_pack->pResourceFile->GetMemory(); + num_memory_entries++; + } + + return num_memory_entries; +} + +int CarLoader::GetMemoryEntries(LoadedTexturePack *loaded_texture_pack, void **memory_entries, int num_memory_entries) { + int result = num_memory_entries; + + if (loaded_texture_pack->pStreamingPack != 0) { + result = loaded_texture_pack->pStreamingPack->GetHeaderMemoryEntries(memory_entries, result); + } + + return result; +} + +int CarLoader::GetMemoryEntries(LoadedWheel *loaded_wheel, void **memory_entries, int num_memory_entries) { + for (int i = 0; i < 4; i++) { + num_memory_entries = this->GetMemoryEntries(loaded_wheel->LoadedSkinLayersPerm[i], memory_entries, num_memory_entries); + } + + for (int i = 0; i < 4; i++) { + num_memory_entries = this->GetMemoryEntries(loaded_wheel->LoadedSkinLayersTemp[i], memory_entries, num_memory_entries); + } + + return StreamingSolidPackLoader.GetMemoryEntries(reinterpret_cast(loaded_wheel->ModelNameHashes), 5, memory_entries, + num_memory_entries); +} + +int CarLoader::GetMemoryEntries(LoadedSkin *loaded_skin, void **memory_entries, int num_memory_entries) { + num_memory_entries = this->GetMemoryEntries(loaded_skin->pLoadedTexturesPack, memory_entries, num_memory_entries); + + for (int i = 0; i < loaded_skin->NumLoadedSkinLayersPerm; i++) { + num_memory_entries = this->GetMemoryEntries(loaded_skin->LoadedSkinLayersPerm[i], memory_entries, num_memory_entries); + } + + for (int i = 0; i < loaded_skin->NumLoadedSkinLayersTemp; i++) { + num_memory_entries = this->GetMemoryEntries(loaded_skin->LoadedSkinLayersTemp[i], memory_entries, num_memory_entries); + } + + return num_memory_entries; +} + +int CarLoader::GetMemoryEntries(LoadedSkinLayer *loaded_skin_layer, void **memory_entries, int num_memory_entries) { + if (loaded_skin_layer != 0 && loaded_skin_layer->LoadState == CARLOADSTATE_LOADED) { + eStreamingEntry *streaming_entry = StreamingTexturePackLoader.GetStreamingEntry(loaded_skin_layer->NameHash); + + if (streaming_entry != 0 && streaming_entry->ChunkData != 0) { + memory_entries[num_memory_entries] = streaming_entry->ChunkData; + num_memory_entries++; + } + } + + return num_memory_entries; +} + +int CarLoader::GetMemoryEntries(LoadedCar *loaded_car, void **memory_entries, int num_memory_entries) { + unsigned int name_hashes[800]; + int num_used_entries = this->GetMemoryEntries(loaded_car->pLoadedSolidPack, memory_entries, num_memory_entries); + int num_hashes = loaded_car->GetModelHashes(name_hashes, 800); + + return StreamingSolidPackLoader.GetMemoryEntries(name_hashes, num_hashes, memory_entries, num_used_entries); +} + +int CarLoader::LoadTexturePack(LoadedTexturePack *loaded_texture_pack, int use_memory_pool) { + int memory_pool_num = 0; + + if (use_memory_pool != 0) { + memory_pool_num = CarLoaderMemoryPoolNumber; + CarLoader_MakeSpaceInCarMemoryPool(this, loaded_texture_pack->MaxHeaderSize, 0, true); + } + + this->LoadingInProgress = 1; + loaded_texture_pack->LoadState = CARLOADSTATE_LOADING; + eLoadStreamingTexturePack(loaded_texture_pack->Filename, LoadedTexturePackCallbackBridge, + reinterpret_cast(loaded_texture_pack), memory_pool_num); + loaded_texture_pack->pStreamingPack = StreamingTexturePackLoader.GetLoadedStreamingPack(loaded_texture_pack->Filename); + return 1; +} + +void CarLoader::LoadedTexturePackCallback(LoadedTexturePack *loaded_texture_pack) { + loaded_texture_pack->LoadState = CARLOADSTATE_LOADED; + this->LoadingDoneCallback(); +} + +int CarLoader::UnloadTexturePack(LoadedTexturePack *loaded_texture_pack) { + if (loaded_texture_pack->NumInstances == 0) { + if (loaded_texture_pack->LoadState == CARLOADSTATE_LOADED && loaded_texture_pack->pStreamingPack != 0) { + eUnloadStreamingTexturePack(loaded_texture_pack->Filename); + } + + delete this->LoadedTexturePackList.Remove(loaded_texture_pack); + return 1; + } + + return 0; +} + +int CarLoader::LoadSkin(LoadedSkin *loaded_skin, int load_perm_layers) { + unsigned int name_hashes[87]; + int loaded_hashes; + + if (load_perm_layers == 0) { + loaded_hashes = this->LoadSkinLayers(name_hashes, 0x57, loaded_skin->LoadedSkinLayersTemp, loaded_skin->NumLoadedSkinLayersTemp); + loaded_skin->LoadStateTemp = loaded_hashes != 0 ? CARLOADSTATE_LOADING : CARLOADSTATE_LOADED; + } else { + loaded_hashes = this->LoadSkinLayers(name_hashes, 0x57, loaded_skin->LoadedSkinLayersPerm, loaded_skin->NumLoadedSkinLayersPerm); + loaded_skin->LoadStatePerm = loaded_hashes != 0 ? CARLOADSTATE_LOADING : CARLOADSTATE_LOADED; + } + + int memory_pool_num = CarLoaderMemoryPoolNumber; + + if (loaded_hashes > 0) { + if (load_perm_layers == 0 && this->LoadingMode != MODE_IN_GAME) { + memory_pool_num = this->LoadingMode == MODE_LOADING_GAME ? 7 : 0; + } else { + do { + if (StreamingTexturePackLoader.TestLoadStreamingEntry(name_hashes, loaded_hashes, CarLoaderMemoryPoolNumber, true) == 0) { + break; + } + } while (this->RemoveSomethingFromCarMemoryPool(true) != 0); + } + + this->LoadingInProgress = 1; + eLoadStreamingTexture(name_hashes, loaded_hashes, LoadedSkinCallbackBridge, reinterpret_cast(loaded_skin), + memory_pool_num); + return 1; + } + + return 0; +} + +void CarLoader::LoadedSkinCallback(LoadedSkin *loaded_skin) { + if (loaded_skin->LoadStatePerm == CARLOADSTATE_LOADING) { + loaded_skin->LoadStatePerm = CARLOADSTATE_LOADED; + this->LoadedSkinLayers(loaded_skin->LoadedSkinLayersPerm, loaded_skin->NumLoadedSkinLayersPerm); + } + + if (loaded_skin->LoadStateTemp == CARLOADSTATE_LOADING) { + loaded_skin->LoadStateTemp = CARLOADSTATE_LOADED; + this->LoadedSkinLayers(loaded_skin->LoadedSkinLayersTemp, loaded_skin->NumLoadedSkinLayersTemp); + } + + this->LoadingDoneCallback(); +} + +int CarLoader::UnloadSkinTemporaries(LoadedSkin *loaded_skin, int force_unload) { + if ((loaded_skin->LoadStateTemp == CARLOADSTATE_LOADED && loaded_skin->DoneComposite != 0) || force_unload != 0) { + int unloaded_something = 0; + unsigned int name_hash_table[87]; + + this->UnallocateSkinLayers(loaded_skin->LoadedSkinLayersTemp, loaded_skin->NumLoadedSkinLayersTemp); + int num_name_hashes = this->UnloadSkinLayers(name_hash_table, 0x57, loaded_skin->LoadedSkinLayersTemp, + loaded_skin->NumLoadedSkinLayersTemp); + loaded_skin->NumLoadedSkinLayersTemp = 0; + + if (num_name_hashes != 0) { + unloaded_something = 1; + eUnloadStreamingTexture(name_hash_table, num_name_hashes); + } + + if (loaded_skin->pLoadedVinylsPack != 0) { + unloaded_something += 1; + this->UnallocateTexturePack(loaded_skin->pLoadedVinylsPack); + this->UnloadTexturePack(loaded_skin->pLoadedVinylsPack); + loaded_skin->pLoadedVinylsPack = 0; + } + + return unloaded_something; + } + + return 0; +} + +LoadedSolidPack *CarLoader::FindLoadedSolidPack(const char *filename) { + for (LoadedSolidPack *loaded_solid_pack = this->LoadedSolidPackList.GetHead(); + loaded_solid_pack != this->LoadedSolidPackList.EndOfList(); loaded_solid_pack = loaded_solid_pack->GetNext()) { + if (bStrCmp(loaded_solid_pack->Filename, filename) == 0) { + return loaded_solid_pack; + } + } + + return 0; +} + +LoadedTexturePack *CarLoader::FindLoadedTexturePack(const char *filename) { + for (LoadedTexturePack *loaded_texture_pack = this->LoadedTexturePackList.GetHead(); + loaded_texture_pack != this->LoadedTexturePackList.EndOfList(); loaded_texture_pack = loaded_texture_pack->GetNext()) { + if (bStrCmp(loaded_texture_pack->Filename, filename) == 0) { + return loaded_texture_pack; + } + } + + return 0; +} + +LoadedSkinLayer *CarLoader::FindLoadedSkinLayer(unsigned int name_hash) { + for (LoadedSkinLayer *loaded_skin_layer = this->LoadedSkinLayerList.GetHead(); + loaded_skin_layer != this->LoadedSkinLayerList.EndOfList(); loaded_skin_layer = loaded_skin_layer->GetNext()) { + if (loaded_skin_layer->NameHash == name_hash) { + return loaded_skin_layer; + } + } + + return 0; +} + +LoadedRideInfo *CarLoader::FindLoadedRideInfo(int handle) { + for (LoadedRideInfo *loaded_ride_info = this->LoadedRideInfoList.GetHead(); + loaded_ride_info != this->LoadedRideInfoList.EndOfList(); loaded_ride_info = loaded_ride_info->GetNext()) { + if (loaded_ride_info->ID == handle) { + return loaded_ride_info; + } + } + + return 0; +} + +LoadedRideInfo *CarLoader::FindLoadedRideInfo(RideInfo *ride_info) { + return 0; +} + +void CarLoader::LoadingDoneCallback() { + this->LoadingInProgress = 0; + this->ServiceLoading(); +} + +void CarLoader::BeginLoading(void (*callback)(unsigned int), unsigned int param) { + if (this->LoadingInProgress != 0) { + if (this->LoadingInProgress == 2) { + this->LoadingInProgress = 1; + } + return; + } + + this->StartLoadingTime = GetDebugRealTime(); + + if (callback != 0) { + this->pCallback = callback; + this->Param = param; + } + + if (this->LoadingInProgress == 0) { + this->ServiceLoading(); + } +} + +void CarLoader::ServiceLoading() { + int num_unallocated_ride_infos = this->NumLoadedRideInfos - this->NumAllocatedRideInfos; + + if (num_unallocated_ride_infos > 0) { + int free_slots = bCountFreeSlots(LoadedRideInfoSlotPool); + + if (free_slots < 10) { + int slots_to_leave = 10 - free_slots; + + if (slots_to_leave > num_unallocated_ride_infos) { + slots_to_leave = num_unallocated_ride_infos; + } + + this->UnloadUnallocatedRideInfos(num_unallocated_ride_infos - slots_to_leave); + } + } + + QueuedFilePrioritySetter queued_file_priority_setter; + + if (this->LoadAllWheelModels() != 0) { + return; + } + + if (this->LoadAllWheelTextures() != 0) { + return; + } + + if (this->LoadAllTexturesFromPack("CARS\\TEXTURES.BIN", 1) != 0) { + return; + } + + for (LoadedRideInfo *loaded_ride_info = this->LoadedRideInfoList.GetHead(); + loaded_ride_info != this->LoadedRideInfoList.EndOfList(); loaded_ride_info = loaded_ride_info->GetNext()) { + if (loaded_ride_info->NumInstances > 0 && loaded_ride_info->LoadState != CARLOADSTATE_LOADED) { + loaded_ride_info->LoadState = CARLOADSTATE_LOADING; + + if (loaded_ride_info->PrintedLoading == 0) { + loaded_ride_info->PrintedLoading = 1; + } + + LoadedCar *loaded_car = loaded_ride_info->pLoadedCar; + + if (loaded_car->pLoadedSolidPack->LoadState == CARLOADSTATE_QUEUED) { + CarTypeInfo *car_type_info = &CarTypeInfoArray[loaded_car->Type]; + + this->LoadSolidPack(loaded_car->pLoadedSolidPack, car_type_info->UsageType != 2); + return; + } + + if (loaded_car->LoadState == CARLOADSTATE_QUEUED) { + if (this->LoadCar(loaded_car) != 0) { + return; + } + } + + LoadedSkin *loaded_skin = loaded_ride_info->pLoadedSkin; + + if (loaded_skin->LoadStatePerm == CARLOADSTATE_QUEUED) { + if (loaded_skin->pLoadedTexturesPack->LoadState == CARLOADSTATE_QUEUED) { + if (this->LoadTexturePack(loaded_skin->pLoadedTexturesPack, 1) != 0) { + return; + } + } + + if (this->LoadSkin(loaded_skin, 1) != 0) { + return; + } + } + + if (loaded_skin->LoadStateTemp == CARLOADSTATE_QUEUED) { + if (this->LoadingMode == MODE_FRONT_END) { + this->UnloadAllSkinTemporaries(); + } + + LoadedTexturePack *loaded_vinyls_pack = loaded_skin->pLoadedVinylsPack; + + if (loaded_vinyls_pack != 0) { + if (loaded_vinyls_pack->LoadState == CARLOADSTATE_QUEUED) { + this->LoadTexturePack(loaded_vinyls_pack, this->LoadingMode == MODE_IN_GAME); + return; + } + } + + if (this->LoadSkin(loaded_skin, 0) != 0) { + return; + } + } + + if (loaded_skin->DoneComposite == 0) { + this->CompositeSkin(loaded_skin); + } + + if (this->LoadingMode != MODE_FRONT_END) { + this->UnloadSkinTemporaries(loaded_skin, 0); + } + + loaded_ride_info->LoadState = CARLOADSTATE_LOADED; + } + } + + if (this->pCallback != 0) { + this->LoadingInProgress = 2; + SetDelayedResourceCallback(CallUserCallback, reinterpret_cast(this)); + } +} + +void CarLoader::CallUserCallback(int param) { + CarLoader *car_loader = reinterpret_cast(param); + + if (car_loader->LoadingInProgress == 1) { + car_loader->LoadingDoneCallback(); + } else { + void (*callback)(unsigned int) = car_loader->pCallback; + + car_loader->LoadingInProgress = 0; + car_loader->pCallback = 0; + callback(car_loader->Param); + } +} + +void CarLoader::LoadedSolidPackCallbackBridge(unsigned int param) { + TheCarLoader.LoadedSolidPackCallback(reinterpret_cast(param)); +} + +void CarLoader::LoadedSolidPackCallbackBridge(int param) { + TheCarLoader.LoadedSolidPackCallback(reinterpret_cast(param)); +} + +void CarLoader::LoadedTexturePackCallbackBridge(unsigned int param) { + TheCarLoader.LoadedTexturePackCallback(reinterpret_cast(param)); +} + +void CarLoader::LoadedCarCallbackBridge(unsigned int param) { + TheCarLoader.LoadedCarCallback(reinterpret_cast(param)); +} + +void CarLoader::LoadedWheelModelsCallbackBridge(unsigned int) { + TheCarLoader.LoadedWheelModelsCallback(); +} + +void CarLoader::LoadedWheelTexturesCallbackBridge(unsigned int) { + TheCarLoader.LoadedWheelTexturesCallback(); +} + +void CarLoader::LoadedAllTexturesFromPackCallbackBridge(unsigned int) { + TheCarLoader.LoadedAllTexturesFromPackCallback(); +} + +void CarLoader::LoadedSkinCallbackBridge(unsigned int param) { + TheCarLoader.LoadedSkinCallback(reinterpret_cast(param)); +} + +void CarLoader::LoadedSolidPackCallback(LoadedSolidPack *loaded_solid_pack) { + loaded_solid_pack->LoadState = CARLOADSTATE_LOADED; + this->LoadingDoneCallback(); +} + +void CarLoader::LoadedCarCallback(LoadedCar *loaded_car) { + loaded_car->LoadState = CARLOADSTATE_LOADED; + this->LoadingDoneCallback(); +} + +void CarLoader::LoadedWheelModelsCallback() { + for (LoadedRideInfo *loaded_ride_info = this->LoadedRideInfoList.GetHead(); + loaded_ride_info != this->LoadedRideInfoList.EndOfList(); loaded_ride_info = loaded_ride_info->GetNext()) { + if (loaded_ride_info->pLoadedWheel->LoadState == CARLOADSTATE_LOADING) { + loaded_ride_info->pLoadedWheel->LoadState = CARLOADSTATE_LOADED; + } + } + + this->LoadingDoneCallback(); +} + +void CarLoader::LoadedWheelTexturesCallback() { + for (LoadedRideInfo *loaded_ride_info = this->LoadedRideInfoList.GetHead(); + loaded_ride_info != this->LoadedRideInfoList.EndOfList(); loaded_ride_info = loaded_ride_info->GetNext()) { + LoadedWheel *loaded_wheel = loaded_ride_info->pLoadedWheel; + + if (loaded_wheel->LoadStateSkinPerm == CARLOADSTATE_LOADING) { + loaded_wheel->LoadStateSkinPerm = CARLOADSTATE_LOADED; + this->LoadedSkinLayers(loaded_wheel->LoadedSkinLayersPerm, 4); + } + + if (loaded_wheel->LoadStateSkinTemp == CARLOADSTATE_LOADING) { + loaded_wheel->LoadStateSkinTemp = CARLOADSTATE_LOADED; + this->LoadedSkinLayers(loaded_wheel->LoadedSkinLayersTemp, 4); + } + } + + this->LoadingDoneCallback(); +} + +void CarLoader::LoadedAllTexturesFromPackCallback() { + unsigned int name_hashes[512]; + + this->LoadedSkinLayers(this->LoadingSkinLayers, this->NumLoadingSkinLayers); + CarLoader_UnallocateSkinLayers(this, this->LoadingSkinLayers, this->NumLoadingSkinLayers); + + int unloaded_hashes = this->UnloadSkinLayers(name_hashes, 0x200, this->LoadingSkinLayers, this->NumLoadingSkinLayers); + + if (unloaded_hashes != 0) { + eUnloadStreamingTexture(name_hashes, unloaded_hashes); + } + + this->NumLoadingSkinLayers = 0; + this->LoadingDoneCallback(); +} + +void CarLoader::LoadedSkinLayers(LoadedSkinLayer **loaded_skin_layer_table, int num_loaded_skin_layers) { + for (int i = 0; i < num_loaded_skin_layers; i++) { + LoadedSkinLayer *loaded_skin_layer = loaded_skin_layer_table[i]; + + if (loaded_skin_layer != 0 && loaded_skin_layer->LoadState == CARLOADSTATE_LOADING) { + loaded_skin_layer->LoadState = CARLOADSTATE_LOADED; + } + } +} + +int CarLoader::UnloadSkinLayers(unsigned int *name_hash_table, int max_name_hashes, LoadedSkinLayer **loaded_skin_layer_table, + int num_loaded_skin_layers) { + int num_name_hashes = 0; + + for (int n = 0; n < num_loaded_skin_layers; n++) { + LoadedSkinLayer *loaded_skin_layer = loaded_skin_layer_table[n]; + + if (loaded_skin_layer != 0 && loaded_skin_layer->NumInstances == 0) { + if (loaded_skin_layer->LoadState == CARLOADSTATE_LOADED) { + name_hash_table[num_name_hashes] = loaded_skin_layer->NameHash; + num_name_hashes++; + } + + delete this->LoadedSkinLayerList.Remove(loaded_skin_layer); + } + } + + return num_name_hashes; +} + +void CarLoader::SetMemoryPoolSize(int size) { + if (this->MemoryPoolSize != size) { + if (this->MemoryPoolSize != 0) { + for (int i = 0; i < this->NumSpongeAllocations; i++) { + bFree(this->SpongeAllocations[i]); + } + + this->NumSpongeAllocations = 0; + CarLoader_UnloadUnallocatedRideInfos(this, 0); + + if (this->LoadedRideInfoList.GetHead() != this->LoadedRideInfoList.EndOfList()) { + return; + } + + bCloseMemoryPool(CarLoaderMemoryPoolNumber); + bFree(this->MemoryPoolMem); + this->MemoryPoolMem = 0; + this->MemoryPoolSize = 0; + } + + if (size != 0) { + TheTrackStreamer.FlushHibernatingSections(); + TheTrackStreamer.MakeSpaceInPool(size, true); + + this->MemoryPoolMem = bMalloc(size, 7); + this->MemoryPoolSize = size; + CarLoaderMemoryPoolNumber = bGetFreeMemoryPoolNum(); + + bInitMemoryPool(CarLoaderMemoryPoolNumber, this->MemoryPoolMem, this->MemoryPoolSize, "Cars"); + bSetMemoryPoolDebugFill(CarLoaderMemoryPoolNumber, false); + bSetMemoryPoolTopDirection(CarLoaderMemoryPoolNumber, true); + this->NumSpongeAllocations = 0; + } + } +} + +int CarLoader::LoadCar(LoadedCar *loaded_car) { + if (!loaded_car->ShouldWeStream()) { + loaded_car->LoadState = CARLOADSTATE_LOADED; + return 0; + } + + { + ProfileNode profile_node("LoadCar", 0); + unsigned int model_hashes[800]; + int num_model_hashes = LoadedCar_GetModelHashes(loaded_car, model_hashes, 800); + + do { + if (StreamingSolidPackLoader.TestLoadStreamingEntry(model_hashes, num_model_hashes, CarLoaderMemoryPoolNumber, true) == 0) { + break; + } + } while (this->RemoveSomethingFromCarMemoryPool(true) != 0); + + loaded_car->LoadState = CARLOADSTATE_LOADING; + this->LoadingInProgress = 1; + StreamingSolidPackLoader.EnableStreamingPack(nullptr); + profile_node.End(); + eLoadStreamingSolid(model_hashes, num_model_hashes, LoadedCarCallbackBridge, reinterpret_cast(loaded_car), + CarLoaderMemoryPoolNumber); + } + + return 1; +} + +int CarLoader::LoadAllWheelModels() { + unsigned int name_hashes[128]; + int num_hashes = 0; + + for (LoadedRideInfo *loaded_ride_info = this->LoadedRideInfoList.GetHead(); + loaded_ride_info != this->LoadedRideInfoList.EndOfList(); loaded_ride_info = loaded_ride_info->GetNext()) { + if (loaded_ride_info->NumInstances != 0 && loaded_ride_info->pLoadedWheel != 0 && + loaded_ride_info->pLoadedWheel->LoadState == CARLOADSTATE_QUEUED) { + LoadedWheel *loaded_wheel = loaded_ride_info->pLoadedWheel; + + loaded_wheel->LoadState = CARLOADSTATE_LOADING; + loaded_ride_info->LoadState = CARLOADSTATE_LOADING; + + for (int model = 0; model < 1; model++) { + for (int lod = loaded_wheel->mMinLodLevel; lod <= loaded_wheel->mMaxLodLevel; lod++) { + unsigned int model_name_hash = loaded_wheel->ModelNameHashes[model][lod]; + + if (model_name_hash != 0) { + name_hashes[num_hashes] = model_name_hash; + num_hashes++; + } + } + } + } + } + + if (num_hashes < 1) { + return 0; + } + + do { + if (StreamingSolidPackLoader.TestLoadStreamingEntry(name_hashes, num_hashes, CarLoaderMemoryPoolNumber, true) == 0) { + break; + } + } while (this->RemoveSomethingFromCarMemoryPool(true) != 0); + + this->LoadingInProgress = 1; + eLoadStreamingSolid(name_hashes, num_hashes, LoadedWheelModelsCallbackBridge, 0, CarLoaderMemoryPoolNumber); + return 1; +} + +int CarLoader::LoadAllWheelTextures() { + unsigned int name_hashes[128]; + int num_hashes = 0; + + for (LoadedRideInfo *loaded_ride_info = this->LoadedRideInfoList.GetHead(); + loaded_ride_info != this->LoadedRideInfoList.EndOfList(); loaded_ride_info = loaded_ride_info->GetNext()) { + if (loaded_ride_info->NumInstances != 0) { + LoadedWheel *loaded_wheel = loaded_ride_info->pLoadedWheel; + + if (loaded_wheel->LoadStateSkinPerm == CARLOADSTATE_QUEUED) { + int loaded_hashes = CarLoader_LoadSkinLayers(this, &name_hashes[num_hashes], 0x80 - num_hashes, + loaded_wheel->LoadedSkinLayersPerm, 4); + + if (loaded_hashes == 0) { + loaded_wheel->LoadStateSkinPerm = CARLOADSTATE_LOADED; + } else { + loaded_wheel->LoadStateSkinPerm = CARLOADSTATE_LOADING; + } + loaded_ride_info->LoadState = CARLOADSTATE_LOADING; + num_hashes += loaded_hashes; + } + + if (loaded_wheel->LoadStateSkinTemp == CARLOADSTATE_QUEUED) { + loaded_wheel->LoadStateSkinTemp = CARLOADSTATE_LOADED; + } + } + } + + if (num_hashes < 1) { + return 0; + } + + do { + if (StreamingTexturePackLoader.TestLoadStreamingEntry(name_hashes, num_hashes, CarLoaderMemoryPoolNumber, true) == 0) { + break; + } + } while (this->RemoveSomethingFromCarMemoryPool(true) != 0); + + this->LoadingInProgress = 1; + eLoadStreamingTexture(name_hashes, num_hashes, LoadedWheelTexturesCallbackBridge, 0, CarLoaderMemoryPoolNumber); + return 1; +} + +void CarLoader::LoadSolidPack(LoadedSolidPack *loaded_solid_pack, int stream_solids) { + if (stream_solids != 0) { + CarLoader_MakeSpaceInCarMemoryPool(this, 0x8000, 0, true); + loaded_solid_pack->LoadState = CARLOADSTATE_LOADING; + this->LoadingInProgress = 1; + eLoadStreamingSolidPack(loaded_solid_pack->Filename, + reinterpret_cast(static_cast(LoadedSolidPackCallbackBridge)), + loaded_solid_pack, CarLoaderMemoryPoolNumber); + loaded_solid_pack->pStreamingPack = StreamingSolidPackLoader.GetLoadedStreamingPack(loaded_solid_pack->Filename); + + if (loaded_solid_pack->pStreamingPack == 0) { + LoadedSolidPackCallbackBridge(reinterpret_cast(loaded_solid_pack)); + } + } else { + loaded_solid_pack->pResourceFile = CreateResourceFile(loaded_solid_pack->Filename, RESOURCE_FILE_CAR, 0, 0, 0); + + int file_size = bFileSize(loaded_solid_pack->Filename); + int pool = 0; + + if (CarLoader_MakeSpaceInCarMemoryPool(this, file_size, 0, false) != 0) { + pool = CarLoaderMemoryPoolNumber; + } + + loaded_solid_pack->pResourceFile->SetAllocationParams((pool & 0xF) | 0x2000, loaded_solid_pack->Filename); + loaded_solid_pack->LoadState = CARLOADSTATE_LOADING; + this->LoadingInProgress = 1; + loaded_solid_pack->pResourceFile->BeginLoading( + reinterpret_cast(static_cast(LoadedSolidPackCallbackBridge)), loaded_solid_pack); + } +} + +int CarLoader::LoadAllTexturesFromPack(const char *filename, int load_perm_layers) { + unsigned int allocated_name_hashes[128]; + unsigned int name_hashes[512]; + + for (LoadedRideInfo *loaded_ride_info = this->LoadedRideInfoList.GetHead(); + loaded_ride_info != this->LoadedRideInfoList.EndOfList(); loaded_ride_info = loaded_ride_info->GetNext()) { + if (loaded_ride_info->NumInstances > 0 && loaded_ride_info->LoadState == CARLOADSTATE_QUEUED) { + int num_hashes = LoadedSkin_GetTextureHashes(loaded_ride_info->pLoadedSkin, allocated_name_hashes, 0x80, load_perm_layers); + int allocated_skin_layers = CarLoader_AllocateSkinLayers(this, allocated_name_hashes, num_hashes, + &this->LoadingSkinLayers[this->NumLoadingSkinLayers], + 0x200 - this->NumLoadingSkinLayers, filename); + + this->NumLoadingSkinLayers += allocated_skin_layers; + } + } + + int loaded_hashes = CarLoader_LoadSkinLayers(this, name_hashes, 0x200, this->LoadingSkinLayers, this->NumLoadingSkinLayers); + int memory_pool_num = CarLoaderMemoryPoolNumber; + + if (loaded_hashes == 0) { + CarLoader_UnallocateSkinLayers(this, this->LoadingSkinLayers, this->NumLoadingSkinLayers); + this->NumLoadingSkinLayers = 0; + memory_pool_num = 0; + } else { + if (load_perm_layers != 0 || this->LoadingMode == MODE_IN_GAME) { + do { + memory_pool_num = CarLoaderMemoryPoolNumber; + + if (StreamingTexturePackLoader.TestLoadStreamingEntry(name_hashes, loaded_hashes, CarLoaderMemoryPoolNumber, true) == 0) { + break; + } + } while (this->RemoveSomethingFromCarMemoryPool(true) != 0); + } else { + memory_pool_num = 0; + } + + this->LoadingInProgress = 1; + eLoadStreamingTexture(name_hashes, loaded_hashes, LoadedAllTexturesFromPackCallbackBridge, 0, memory_pool_num); + return 1; + } + + return memory_pool_num; +} + +void CarLoader::CompositeSkin(LoadedSkin *loaded_skin) { + if (loaded_skin->pRideInfo->IsUsingCompositeSkin() != 0) { + if (this->LoadingMode == MODE_IN_GAME) { + int required_size = CarInfo_GetMaxCompositingBufferSize(); + + if (required_size > bCountFreeMemory(CarLoaderMemoryPoolNumber)) { + do { + if (!this->RemoveSomethingFromCarMemoryPool(false)) { + break; + } + } while (required_size > bCountFreeMemory(CarLoaderMemoryPoolNumber)); + + this->DefragmentPool(); + } + } + + ::CompositeSkin(loaded_skin->pRideInfo); + } + + loaded_skin->DoneComposite = 1; +} diff --git a/src/Speed/Indep/Src/World/CarLoader.hpp b/src/Speed/Indep/Src/World/CarLoader.hpp index 44846da1e..73b30f332 100644 --- a/src/Speed/Indep/Src/World/CarLoader.hpp +++ b/src/Speed/Indep/Src/World/CarLoader.hpp @@ -18,6 +18,10 @@ enum CarLoadState { // total size: 0x18 class LoadedSolidPack : public bTNode { public: + LoadedSolidPack(const char *filename); + ~LoadedSolidPack(); + static void operator delete(void *ptr); + const char *Filename; // offset 0x8, size 0x4 eStreamingPack *pStreamingPack; // offset 0xC, size 0x4 ResourceFile *pResourceFile; // offset 0x10, size 0x4 @@ -28,6 +32,10 @@ class LoadedSolidPack : public bTNode { // total size: 0x18 struct LoadedTexturePack : public bTNode { public: + LoadedTexturePack(const char *filename, int max_header_size); + ~LoadedTexturePack(); + static void operator delete(void *ptr); + const char *Filename; // offset 0x8, size 0x4 short NumInstances; // offset 0xC, size 0x2 char LoadState; // offset 0xE, size 0x1 @@ -39,6 +47,9 @@ struct LoadedTexturePack : public bTNode { // total size: 0x10 class LoadedSkinLayer : public bTNode { public: + LoadedSkinLayer(unsigned int name_hash); + static void operator delete(void *ptr); + unsigned int NameHash; // offset 0x8, size 0x4 short NumInstances; // offset 0xC, size 0x2 char LoadState; // offset 0xE, size 0x1 @@ -48,13 +59,15 @@ class LoadedSkinLayer : public bTNode { // total size: 0x7C class LoadedWheel : public bTNode { public: + LoadedWheel(RideInfo *ride_info, bool in_fe); + CarLoadState LoadState; // offset 0x8, size 0x4 CarLoadState LoadStateSkinPerm; // offset 0xC, size 0x4 CarLoadState LoadStateSkinTemp; // offset 0x10, size 0x4 unsigned int PartNameHash; // offset 0x14, size 0x4 unsigned int TextureBaseNameHash; // offset 0x18, size 0x4 CarPart *pCarPart; // offset 0x1C, size 0x4 - unsigned int ModelNameHashes[5][1]; // offset 0x20, size 0x14 + unsigned int ModelNameHashes[1][5]; // offset 0x20, size 0x14 unsigned int SkinNameHashesPerm[4]; // offset 0x34, size 0x10 unsigned int SkinNameHashesTemp[4]; // offset 0x44, size 0x10 LoadedSkinLayer *LoadedSkinLayersPerm[4]; // offset 0x54, size 0x10 @@ -66,6 +79,10 @@ class LoadedWheel : public bTNode { // total size: 0x2E0 class LoadedSkin : public bTNode { public: + LoadedSkin(RideInfo *ride_info, int in_front_end, int is_player_skin); + int GetTextureHashes(unsigned int *texture_hashes, int max_texture_hashes, int perm); + int IsLoaded(); + RideInfo *pRideInfo; // offset 0x8, size 0x4 char LoadStatePerm; // offset 0xC, size 0x1 char LoadStateTemp; // offset 0xD, size 0x1 @@ -83,6 +100,10 @@ class LoadedSkin : public bTNode { // total size: 0x20 class LoadedCar : public bTNode { public: + LoadedCar(RideInfo *ride_info, int in_front_end, int is_two_player); + int GetModelHashes(unsigned int *model_hashes, int max_model_hashes); + int ShouldWeStream(); + CarType Type; // offset 0x8, size 0x4 struct RideInfo *pRideInfo; // offset 0xC, size 0x4 int InFrontEnd; // offset 0x10, size 0x4 @@ -94,6 +115,9 @@ class LoadedCar : public bTNode { // total size: 0x6D4 class LoadedRideInfo : public bTNode { public: + LoadedRideInfo(RideInfo *ride_info, int in_front_end, int is_two_player, int is_player_car); + static void operator delete(void *ptr); + int NumInstances; // offset 0x8, size 0x4 CarLoadState LoadState; // offset 0xC, size 0x4 int HighPriority; // offset 0x10, size 0x4 @@ -109,11 +133,15 @@ class LoadedRideInfo : public bTNode { LoadedWheel TheLoadedWheel; // offset 0x374, size 0x7C LoadedSkin TheLoadedSkin; // offset 0x3F0, size 0x2E0 int ID; // offset 0x6D0, size 0x4 + + static int sNextID; }; // total size: 0x8B0 class CarLoader { public: + CarLoader(); + enum eLoadingMode { MODE_FRONT_END = 0, MODE_LOADING_GAME = 1, diff --git a/src/Speed/Indep/Src/World/CarPartNames.cpp b/src/Speed/Indep/Src/World/CarPartNames.cpp index e69de29bb..2c1bd9e72 100644 --- a/src/Speed/Indep/Src/World/CarPartNames.cpp +++ b/src/Speed/Indep/Src/World/CarPartNames.cpp @@ -0,0 +1,187 @@ +#include "Speed/Indep/Libs/Support/Utility/UCrc.h" +#include "Speed/Indep/Src/World/CarInfo.hpp" +#include "string.h" + +struct CarPartIDName { + int PartID; + const char *Name; + unsigned int NameHash; +}; + +struct CarSlotIDName { + int SlotID; + const char *Name; +}; + +CarPartIDName CarPartIDNames[CARPARTID_NUM]; +CarPartIDName CarPartIDOldNames[1]; +extern CarSlotIDName CarSlotIDNames[] asm("CarSlotIDNames"); + +#define INIT_CAR_PART_NAME(id, name_literal) \ + memset(&CarPartIDNames[id], 0, sizeof(CarPartIDNames[id])); \ + CarPartIDNames[id].PartID = id; \ + CarPartIDNames[id].Name = name_literal; \ + CarPartIDNames[id].NameHash = stringhash32(name_literal) + +struct CarPartIDNamesStartup { + CarPartIDNamesStartup() { + memset(CarPartIDNames, 0, sizeof(CarPartIDNames)); + + memset(&CarPartIDNames[0], 0, sizeof(CarPartIDNames[0])); + CarPartIDNames[0].Name = "BASE"; + CarPartIDNames[0].NameHash = stringhash32("BASE"); + + INIT_CAR_PART_NAME(1, "DAMAGE_FRONT_WINDOW"); + INIT_CAR_PART_NAME(2, "DAMAGE_BODY"); + INIT_CAR_PART_NAME(3, "DAMAGE_COP_LIGHTS"); + INIT_CAR_PART_NAME(4, "DAMAGE_COP_SPOILER"); + INIT_CAR_PART_NAME(5, "DAMAGE_FRONT_WHEEL"); + INIT_CAR_PART_NAME(6, "DAMAGE_LEFT_BRAKELIGHT"); + INIT_CAR_PART_NAME(7, "DAMAGE_RIGHT_BRAKELIGHT"); + INIT_CAR_PART_NAME(8, "DAMAGE_LEFT_HEADLIGHT"); + INIT_CAR_PART_NAME(9, "DAMAGE_RIGHT_HEADLIGHT"); + INIT_CAR_PART_NAME(10, "DAMAGE_HOOD"); + INIT_CAR_PART_NAME(11, "DAMAGE_BUSHGUARD"); + INIT_CAR_PART_NAME(12, "DAMAGE_FRONT_BUMPER"); + INIT_CAR_PART_NAME(13, "DAMAGE_RIGHT_DOOR"); + INIT_CAR_PART_NAME(14, "DAMAGE_RIGHT_REAR_DOOR"); + INIT_CAR_PART_NAME(15, "DAMAGE_TRUNK"); + INIT_CAR_PART_NAME(16, "DAMAGE_REAR_BUMPER"); + INIT_CAR_PART_NAME(17, "DAMAGE_REAR_LEFT_WINDOW"); + INIT_CAR_PART_NAME(18, "DAMAGE_FRONT_LEFT_WINDOW"); + INIT_CAR_PART_NAME(19, "DAMAGE_FRONT_RIGHT_WINDOW"); + INIT_CAR_PART_NAME(20, "DAMAGE_REAR_RIGHT_WINDOW"); + INIT_CAR_PART_NAME(21, "DAMAGE_LEFT_DOOR"); + INIT_CAR_PART_NAME(22, "DAMAGE_LEFT_REAR_DOOR"); + INIT_CAR_PART_NAME(23, "BODY"); + INIT_CAR_PART_NAME(24, "FRONT_BRAKE"); + INIT_CAR_PART_NAME(25, "FRONT_LEFT_WINDOW"); + INIT_CAR_PART_NAME(26, "FRONT_RIGHT_WINDOW"); + INIT_CAR_PART_NAME(27, "FRONT_WINDOW"); + INIT_CAR_PART_NAME(28, "INTERIOR"); + INIT_CAR_PART_NAME(29, "LEFT_BRAKELIGHT"); + INIT_CAR_PART_NAME(30, "LEFT_BRAKELIGHT_GLASS"); + INIT_CAR_PART_NAME(31, "LEFT_HEADLIGHT"); + INIT_CAR_PART_NAME(32, "LEFT_HEADLIGHT_GLASS"); + INIT_CAR_PART_NAME(33, "LEFT_SIDE_MIRROR"); + INIT_CAR_PART_NAME(34, "REAR_BRAKE"); + INIT_CAR_PART_NAME(35, "REAR_LEFT_WINDOW"); + INIT_CAR_PART_NAME(36, "REAR_RIGHT_WINDOW"); + INIT_CAR_PART_NAME(37, "REAR_WINDOW"); + INIT_CAR_PART_NAME(38, "RIGHT_BRAKELIGHT"); + INIT_CAR_PART_NAME(39, "RIGHT_BRAKELIGHT_GLASS"); + INIT_CAR_PART_NAME(40, "RIGHT_HEADLIGHT"); + INIT_CAR_PART_NAME(41, "RIGHT_HEADLIGHT_GLASS"); + INIT_CAR_PART_NAME(42, "RIGHT_SIDE_MIRROR"); + INIT_CAR_PART_NAME(43, "DRIVER"); + INIT_CAR_PART_NAME(44, "SPOILER"); + INIT_CAR_PART_NAME(45, "UNIVERSAL_SPOILER_BASE"); + INIT_CAR_PART_NAME(46, "DAMAGE0_FRONT"); + INIT_CAR_PART_NAME(47, "DAMAGE0_FRONTLEFT"); + INIT_CAR_PART_NAME(48, "DAMAGE0_FRONTRIGHT"); + INIT_CAR_PART_NAME(49, "DAMAGE0_REAR"); + INIT_CAR_PART_NAME(50, "DAMAGE0_REARLEFT"); + INIT_CAR_PART_NAME(51, "DAMAGE0_REARRIGHT"); + INIT_CAR_PART_NAME(52, "ATTACHMENT0"); + INIT_CAR_PART_NAME(53, "ATTACHMENT1"); + INIT_CAR_PART_NAME(54, "ATTACHMENT2"); + INIT_CAR_PART_NAME(55, "ATTACHMENT3"); + INIT_CAR_PART_NAME(56, "ATTACHMENT4"); + INIT_CAR_PART_NAME(57, "ATTACHMENT5"); + INIT_CAR_PART_NAME(58, "ATTACHMENT6"); + INIT_CAR_PART_NAME(59, "ATTACHMENT7"); + INIT_CAR_PART_NAME(60, "ATTACHMENT8"); + INIT_CAR_PART_NAME(61, "ATTACHMENT9"); + INIT_CAR_PART_NAME(62, "ROOF"); + INIT_CAR_PART_NAME(63, "HOOD"); + INIT_CAR_PART_NAME(64, "HEADLIGHT"); + INIT_CAR_PART_NAME(65, "BRAKELIGHT"); + INIT_CAR_PART_NAME(66, "BRAKE"); + INIT_CAR_PART_NAME(67, "WHEEL"); + INIT_CAR_PART_NAME(68, "SPINNER"); + INIT_CAR_PART_NAME(69, "LICENSE_PLATE"); + INIT_CAR_PART_NAME(70, "DECAL_FRONT_WINDOW"); + INIT_CAR_PART_NAME(71, "DECAL_REAR_WINDOW"); + INIT_CAR_PART_NAME(72, "DECAL_LEFT_DOOR"); + INIT_CAR_PART_NAME(73, "DECAL_RIGHT_DOOR"); + INIT_CAR_PART_NAME(74, "DECAL_LEFT_QUARTER"); + INIT_CAR_PART_NAME(75, "DECAL_RIGHT_QUARTER"); + INIT_CAR_PART_NAME(76, "PAINT"); + INIT_CAR_PART_NAME(77, "VINYL_PAINT"); + INIT_CAR_PART_NAME(78, "RIM_PAINT"); + INIT_CAR_PART_NAME(79, "VINYL"); + INIT_CAR_PART_NAME(80, "DECAL_TEXTURE"); + INIT_CAR_PART_NAME(81, "WINDOW_TINT"); + INIT_CAR_PART_NAME(82, "CUSTOM_HUD"); + INIT_CAR_PART_NAME(83, "CUSTOM_HUD_PAINT"); + INIT_CAR_PART_NAME(84, "CV"); + INIT_CAR_PART_NAME(85, "WHEEL_MANUFACTURER"); + INIT_CAR_PART_NAME(86, "MISC"); + + memset(CarPartIDOldNames, 0, sizeof(CarPartIDOldNames)); + CarPartIDOldNames[0].PartID = CARPARTID_BRAKE; + CarPartIDOldNames[0].Name = "BRAKE"; + CarPartIDOldNames[0].NameHash = stringhash32("BRAKE"); + } +}; + +CarPartIDNamesStartup _CarPartIDNamesStartup; + +#undef INIT_CAR_PART_NAME + +int GetNumCarPartIDNames(); +int GetNumCarSlotIDNames(); + +const char *GetCarPartNameFromID(int car_part_id) { + int num_car_part_names = GetNumCarPartIDNames(); + + if (car_part_id >= 0 && car_part_id < num_car_part_names) { + if (CarPartIDNames[car_part_id].PartID == car_part_id) { + return CarPartIDNames[car_part_id].Name; + } + + for (int i = 0; i < num_car_part_names; i++) { + if (CarPartIDNames[i].PartID == car_part_id) { + return CarPartIDNames[i].Name; + } + } + } + + return 0; +} + +const char *GetCarSlotNameFromID(int car_slot_id) { + int num_car_slot_names = GetNumCarSlotIDNames(); + + if (car_slot_id >= 0 && car_slot_id < num_car_slot_names) { + if (CarSlotIDNames[car_slot_id].SlotID == car_slot_id) { + return CarSlotIDNames[car_slot_id].Name; + } + + for (int i = 0; i < num_car_slot_names; i++) { + if (CarSlotIDNames[i].SlotID == car_slot_id) { + return CarSlotIDNames[i].Name; + } + } + } + + return 0; +} + +int GetCarPartIDFromCrc(UCrc32 crc) { + int num_car_part_names = GetNumCarPartIDNames(); + + for (int i = 0; i < num_car_part_names; i++) { + if (crc.GetValue() == CarPartIDNames[i].NameHash) { + return CarPartIDNames[i].PartID; + } + } + + for (unsigned int j = 0; j < 1; j++) { + if (crc.GetValue() == CarPartIDOldNames[j].NameHash) { + return CarPartIDOldNames[j].PartID; + } + } + + return -1; +} diff --git a/src/Speed/Indep/Src/World/CarRender.cpp b/src/Speed/Indep/Src/World/CarRender.cpp index 19bf55df4..8e7cdf871 100644 --- a/src/Speed/Indep/Src/World/CarRender.cpp +++ b/src/Speed/Indep/Src/World/CarRender.cpp @@ -2,15 +2,26 @@ #include "Interfaces/IVehicleDamageBehaviour.h" #include "Speed/Indep/Libs/Support/Utility/UTypes.h" #include "Speed/Indep/Src/Ecstasy/DefragFixer.hpp" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" #include "Speed/Indep/Src/Ecstasy/EcstasyData.hpp" #include "Speed/Indep/Src/Ecstasy/EcstasyE.hpp" #include "Speed/Indep/Src/Ecstasy/Texture.hpp" #include "Speed/Indep/Src/Ecstasy/eMath.hpp" +#include "Speed/Indep/Src/Camera/CameraMover.hpp" #include "Speed/Indep/Src/Generated/AttribSys/Classes/ecar.h" +#include "Speed/Indep/Src/Interfaces/SimEntities/IPlayer.h" +#include "Speed/Indep/Src/Interfaces/SimActivities/INIS.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRigidBody.h" #include "Speed/Indep/Src/Main/AttribSupport.h" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/Misc/BuildRegion.hpp" #include "Speed/Indep/Src/Misc/GameFlow.hpp" #include "Speed/Indep/Src/Misc/Profiler.hpp" +#include "Speed/Indep/Src/Sim/Simulation.h" #include "Speed/Indep/Src/World/CarInfo.hpp" +#include "Speed/Indep/Src/World/Sun.hpp" +#include "Speed/Indep/Src/World/VehicleFragmentConn.h" +#include "Speed/Indep/Src/World/VehicleRenderConn.h" #include "Speed/Indep/Src/World/World.hpp" #include "Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h" #include "Speed/Indep/Tools/AttribSys/Runtime/Common/AttribPrivate.h" @@ -18,6 +29,11 @@ #include "Speed/Indep/bWare/Inc/bMath.hpp" #include "Speed/Indep/bWare/Inc/bMemory.hpp" #include "Speed/Indep/bWare/Inc/bSlotPool.hpp" +#include "Speed/Indep/bWare/Inc/bVector.hpp" + +namespace BuildRegion { +bool IsEurope(); +} float culldiv = 12000.0f; static const CarFXPosInfo FXMarkerNameHashMappings[28] = { @@ -54,6 +70,349 @@ static const CarFXPosInfo FXMarkerNameHashMappings[28] = { SlotPool *CarEmitterPositionSlotPool = nullptr; const int MAX_CAR_PART_MODELS = 250; SlotPool *CarPartModelPool = nullptr; +extern void *gINISInstance asm("_Q33UTL11Collectionst9Singleton1Z4INIS_mInstance"); + +struct eEnvMap { + void UpdateCameras(bVector3 *viewer_world_position, bVector3 *envmap_world_position); +}; + +eSolid *eFindSolid(unsigned int name_hash); +void GetUsedCarTextureInfo(UsedCarTextureInfo *used_texture_info, RideInfo *ride_info, int front_end_only); +extern float copm; +extern float copt; +extern int copModulo; +extern float cpr; +extern float cpb; +extern float cpw; +extern float copoffsetr; +extern float copoffsetb; +extern float copoffsetw; +extern float enX; +extern float enY; +extern float enZ; +extern float hOffX; +extern float hOffY; +extern float hRad1x; +extern float hRad2x; +extern float hRad1y; +extern float hRad2y; +extern float hRad0x; +extern float hRad3x; +extern float hRad0y; +extern float hRad3y; +extern unsigned int TireFaceIt; +float TireFace(bMatrix4 *matrix, eView *view); +extern unsigned int CarReplacementDecalHash[CarRenderInfo::REPLACETEX_DECAL_NUM]; +extern unsigned int hcL; +extern unsigned int FrameMallocFailed; +extern unsigned int FrameMallocFailAmount; +extern int DrawCars; +extern int DrawCarShadow; +extern float WorldTimeElapsed; +extern int ForceCarLOD; +extern int ForceTireLOD; +extern int ForceReverselightsOn; +extern int TweakKitWheelOffsetFront; +extern int TweakKitWheelOffsetRear; +extern int ForceBrakelightsOn; +extern int ForceHeadlightsOn; +extern int iRam8047ff04; +extern float lbl_8040AA60; +extern float lbl_8040AA68; +extern float lbl_8040AA6C; +extern bVector3 EnvMapEyeOffset; +extern bVector3 EnvMapCamOffset; +extern float WheelStandardWidth; +extern float WheelStandardRadius; +extern float lbl_8040AD70; +extern float lbl_8040AD74; +extern float lbl_8040AD78; +extern float lbl_8040AD7C; +extern float lbl_8040AD80; +extern float lbl_8040AD84; +extern float lbl_8040AD88; +extern float lbl_8040AD8C; +extern float lbl_8040AD90; +extern float lbl_8040AD94; +extern float lbl_8040AD98; +extern float lbl_8040AD9C; +extern float lbl_8040ADA0; +extern float lbl_8040ADA4; +extern float lbl_8040ADA8; +extern float lbl_8040ADAC; +extern float lbl_8040ADB0; +extern float lbl_8040ADB4; +extern float lbl_8040ADB8; +extern float lbl_8040ADBC; +extern float lbl_8040ADC0; +extern float lbl_8040ADC4; +extern float lbl_8040ADC8; +extern float lbl_8040ADCC; +extern float lbl_8040ADD0; +extern float lbl_8040ADD4; +extern float lbl_8040ADD8; +extern float lbl_8040ADDC; +extern float lbl_8040ADE0; +extern float lbl_8040ADE4; +extern float lbl_8040ADE8; +extern float lbl_8040ADEC; +extern float lbl_8040ADF0; +extern float lbl_8040ADF4; +extern float lbl_8040ADF8; +extern float lbl_8040ADFC; +extern float lbl_8040AE00; +extern float lbl_8040AE04; +extern float copWhitemul; +extern int gTWEAKER_NISLightEnabled; +extern float gTWEAKER_NISLightIntensity; +extern float gTWEAKER_NISLightPosX; +extern float gTWEAKER_NISLightPosY; +extern float gTWEAKER_NISLightPosZ; +extern unsigned int Lightslot; +extern int PrintLightQuery; +extern int PrintQueryLightMat; +extern int EnableEnvMap; +extern eShaperLightRig ShaperLightsBackRoom; +extern eShaperLightRig ShaperLightsCarLot; +extern eShaperLightRig ShaperLightsCarsInGame; +extern eShaperLightRig ShaperLightsCShop; +extern eShaperLightRig ShaperLightsCharacters; +extern eShaperLightRig ShaperLightsCharactersBackup; +extern eShaperLightRig ShaperLightsQRace; +extern eShaperLightRig ShaperLightsSafehouse; +extern bMatrix4 hack_man_matrix; +extern bVector3 hull_Origin asm("hull_Origin"); +extern bVector3 hull_Normal asm("hull_Normal"); +extern bVector3 hullVertArray1[16] asm("hullVertArray1"); +extern bVector3 hullVertArray2[16] asm("hullVertArray2"); +extern bVector3 hullVertArray3[48] asm("hullVertArray3"); +extern bVector4 PointCloud[16] asm("PointCloud"); +extern bVector3 *P[17] asm("P"); +int ch2d(float **P, int n); +extern float FancyCarShadowEdgeMult; +extern float car_elevation; +extern float car_elevation_scale; +extern int dshad; +extern bVector3 cs_lightV asm("cs_lightV"); +extern float cs_OneOverZ asm("cs_OneOverZ"); +extern int counter_31665 asm("counter.31665"); +extern int counter_31669 asm("counter.31669"); +extern float heliScale; +extern bVector4 feposoff; +extern CarTypeInfo *CarTypeInfoArray; +extern void RestoreShaperRig(eShaperLightRig *ShaperRigP, unsigned int slot, eShaperLightRig *ShaperRigBP); +extern void AddQuickDynamicLight(eShaperLightRig *ShaperRigP, unsigned int slot, float r, float g, float b, float intensity, bVector3 *position); +extern int bBoundingBoxIsInside(const bVector3 *bbox_min, const bVector3 *bbox_max, const bVector3 *point, float extra_width); + +void sh_Setup(bVector3 *car_pos); + +void sh_Setup(bVector3 *car_pos) { + bVector3 light_pos; + bVector3 light_delta; + float light_length; + unsigned short shadow_angle; + + if (SunInfo == 0) { + light_pos.x = lbl_8040AD80; + light_pos.y = lbl_8040AD84; + light_pos.z = lbl_8040AD88; + } else { + light_pos.x = SunInfo->CarShadowPositionX; + light_pos.y = SunInfo->CarShadowPositionY; + light_pos.z = SunInfo->CarShadowPositionZ; + } + + light_delta.x = car_pos->x - light_pos.x; + light_delta.y = car_pos->y - light_pos.y; + light_delta.z = car_pos->z - light_pos.z; + + cs_lightV.z = light_delta.z; + cs_lightV.x = light_delta.x; + cs_lightV.y = light_delta.y; + + light_length = lbl_8040AD84; + float xy_length_sq = light_delta.x * light_delta.x + light_delta.y * light_delta.y; + if (lbl_8040AD8C < xy_length_sq) { + light_length = bSqrt(xy_length_sq); + } + + shadow_angle = bATan(light_length, -cs_lightV.z); + if (shadow_angle < 4000) { + float sin_angle; + float cos_angle; + float abs_y; + + bSinCos(&sin_angle, &cos_angle, 4000); + xy_length_sq = cs_lightV.z * cs_lightV.z + cs_lightV.x * cs_lightV.x + cs_lightV.y * cs_lightV.y; + light_length = lbl_8040AD84; + if (lbl_8040AD8C < xy_length_sq) { + light_length = bSqrt(xy_length_sq); + } + + abs_y = bAbs(cs_lightV.y); + cs_lightV.z = -light_length * sin_angle; + cs_lightV.y = (cs_lightV.y / (bAbs(cs_lightV.x) + abs_y)) * light_length * cos_angle; + cs_lightV.x = (cs_lightV.x / (bAbs(cs_lightV.x) + abs_y)) * light_length * cos_angle; + } + + cs_OneOverZ = lbl_8040AD84; + if (cs_lightV.z != lbl_8040AD84) { + cs_OneOverZ = lbl_8040AD94 / cs_lightV.z; + } +} + +static float const CarBodyLodSwapSize[] = {120.0f, 25.0f, 20.0f, 10.0f, 0.0f}; +static float const TrafficCarBodyLodSwapSize[] = {20.0f, 10.0f, 4.0f, 0.0f, 0.0f}; + +namespace { + +void Render(eViewPlatInterface *view, eModel *model, bMatrix4 *local_to_world, eLightContext *light_context, unsigned int flags, + unsigned int exc_flag); +void Render(eViewPlatInterface *view, ePoly *poly, TextureInfo *texture_info, bMatrix4 *matrix, int accurate, float z_bias); +int eSmoothNormals(eModel **model_table, int num_models); +eEnvMap *eGetEnvMap(); +bool eBeginStrip(TextureInfo *a, int b, bMatrix4 *c); +bool eEndStrip(eView *view); +void eAddVertex(const bVector3 &v); +void eAddColour(unsigned int colour); +void eAddUV(float u, float v); +bool exBeginStrip(TextureInfo *tex, int a, bMatrix4 *mat); +void exAddVertex(const bVector3 &v); +void exAddColour(unsigned int colour); +void exAddUV(float u, float v); +bool exEndStrip(eView *view); +int CarPart_GetAppliedAttributeIParam(CarPart *part, unsigned int namehash, int default_value) asm("GetAppliedAttributeIParam__7CarPartUii"); +int CarPart_HasAppliedAttribute(CarPart *part, unsigned int namehash) asm("HasAppliedAttribute__7CarPartUi"); +unsigned int CarPart_GetAppliedAttributeUParam(CarPart *part, unsigned int namehash, unsigned int default_value) + asm("GetAppliedAttributeUParam__7CarPartUiUi"); +unsigned int CarPart_GetModelNameHash(CarPart *part, int model_num, int lod) asm("GetModelNameHash__7CarPartii"); + +template struct bSNodeLayout { + T *Next; +}; + +template struct bSListLayout { + T *Head; + T *Tail; +}; + +struct CarRenderUsedCarTextureInfoLayout { + unsigned int TexturesToLoadPerm[87]; + unsigned int TexturesToLoadTemp[87]; + int NumTexturesToLoadPerm; + int NumTexturesToLoadTemp; + unsigned int MappedSkinHash; + unsigned int MappedSkinBHash; + unsigned int MappedGlobalHash; + unsigned int MappedWheelHash; + unsigned int MappedSpinnerHash; + unsigned int MappedBadging; + unsigned int MappedSpoilerHash; + unsigned int MappedRoofScoopHash; + unsigned int MappedDashSkinHash; + unsigned int MappedLightHash[11]; + unsigned int MappedTireHash; + unsigned int MappedRimHash; + unsigned int MappedRimBlurHash; + unsigned int MappedLicensePlateHash; + unsigned int ReplaceSkinHash; + unsigned int ReplaceSkinBHash; + unsigned int ReplaceGlobalHash; + unsigned int ReplaceWheelHash; + unsigned int ReplaceSpinnerHash; + unsigned int ReplaceSpoilerHash; + unsigned int ReplaceRoofScoopHash; + unsigned int ReplaceDashSkinHash; + unsigned int ReplaceHeadlightHash[3]; + unsigned int ReplaceHeadlightGlassHash[3]; + unsigned int ReplaceBrakelightHash[3]; + unsigned int ReplaceBrakelightGlassHash[3]; + unsigned int ReplaceReverselightHash[3]; + unsigned int ShadowHash; +}; + +struct CarRenderRideInfoLayout { + CarType Type; + char InstanceIndex; + char HasDash; + char CanBeVertexDamaged; + char SkinType; + CARPART_LOD mMinLodLevel; + CARPART_LOD mMaxLodLevel; + CARPART_LOD mMinFELodLevel; + CARPART_LOD mMaxFELodLevel; + CARPART_LOD mMaxLicenseLodLevel; + CARPART_LOD mMinTrafficDiffuseLodLevel; + CARPART_LOD mMinShadowLodLevel; + CARPART_LOD mMaxShadowLodLevel; + CARPART_LOD mMaxTireLodLevel; + CARPART_LOD mMaxBrakeLodLevel; + CARPART_LOD mMaxSpoilerLodLevel; + CARPART_LOD mMaxRoofScoopLodLevel; + CARPART_LOD mMinReflectionLodLevel; +}; + +struct FrontEndRenderingCarLayout { + bNode Node; + RideInfo mRideInfo; +}; + +struct CameraPositionAccess { + char pad[0x50]; + float x; + float y; + float z; +}; + +template void InitSList(bSList &list) { + bSListLayout &layout = reinterpret_cast &>(list); + T *end = list.EndOfList(); + + layout.Head = end; + layout.Tail = end; +} + +} // namespace + +inline void *CarEmitterPosition::operator new(unsigned int size) { + return bOMalloc(CarEmitterPositionSlotPool); +} + +inline void CarEmitterPosition::operator delete(void *ptr) { + bFree(CarEmitterPositionSlotPool, ptr); +} + +inline CarEmitterPosition::CarEmitterPosition(ePositionMarker *position_marker) { + PositionMarker = position_marker; + X = position_marker->Matrix.v3.x; + Y = position_marker->Matrix.v3.y; + Z = position_marker->Matrix.v3.z; +} + +inline CarEmitterPosition::CarEmitterPosition(float x, float y, float z) { + PositionMarker = nullptr; + X = x; + Y = y; + Z = z; +} + +template <> inline CarEmitterPosition *bSList::EndOfList() { + return reinterpret_cast(this); +} + +template <> inline bSList::bSList() { + Head = EndOfList(); + Tail = EndOfList(); +} + +template <> inline CarEmitterPosition *bSList::AddTail(CarEmitterPosition *node) { + CarEmitterPosition *prev_tail = Tail; + + Tail = node; + prev_tail->Next = node; + node->Next = EndOfList(); + return node; +} bool GetNumCarEffectMarkerHashes(CarEffectPosition fx_pos, int &count_out) { bool position_marker_based_fxpos = false; @@ -70,8 +429,94 @@ const unsigned int *GetCarEffectMarkerHashes(CarEffectPosition fx_pos) { return reinterpret_cast(&FXMarkerNameHashMappings[fx_pos].marker_count); } -CarPartCullingPlaneInfo CarPartCullingPlaneInfoTable[11]; +CarPartCullingPlaneInfo CarPartCullingPlaneInfoTable[11] = { + CarPartCullingPlaneInfo(CULLABLE_CAR_PART_TIRE_FL, "CULLABLE_CAR_PART_TIRE_FL", CULLING_POLARITY_ANY_VISIBLE, 3, + bVector3(-0.01f, -1.0f, 0.0f), bVector3(0.0f, 0.0f, 1.0f), bVector3(-0.70710677f, 0.0f, 0.70710677f), + 0.1f, 0.86602539f, 0.5f), + CarPartCullingPlaneInfo(CULLABLE_CAR_PART_TIRE_FR, "CULLABLE_CAR_PART_TIRE_FR", CULLING_POLARITY_ANY_VISIBLE, 3, + bVector3(-0.01f, 1.0f, 0.0f), bVector3(0.0f, 0.0f, 1.0f), bVector3(-0.70710677f, 0.0f, 0.70710677f), + 0.1f, 0.86602539f, 0.5f), + CarPartCullingPlaneInfo(CULLABLE_CAR_PART_TIRE_RR, "CULLABLE_CAR_PART_TIRE_RR", CULLING_POLARITY_ANY_VISIBLE, 3, + bVector3(0.01f, 1.0f, 0.0f), bVector3(0.0f, 0.0f, 1.0f), bVector3(0.70710677f, 0.0f, 0.70710677f), + 0.1f, 0.86602539f, 0.5f), + CarPartCullingPlaneInfo(CULLABLE_CAR_PART_TIRE_RL, "CULLABLE_CAR_PART_TIRE_RL", CULLING_POLARITY_ANY_VISIBLE, 3, + bVector3(0.01f, -1.0f, 0.0f), bVector3(0.0f, 0.0f, 1.0f), bVector3(0.70710677f, 0.0f, 0.70710677f), + 0.1f, 0.86602539f, 0.5f), + CarPartCullingPlaneInfo(CULLABLE_CAR_PART_BRAKE_FL, "CULLABLE_CAR_PART_BRAKE_FL", CULLING_POLARITY_ALL_MUST_BE_VISIBLE, 2, + bVector3(0.36f, -1.0f, 0.0f), bVector3(-0.77f, -1.0f, 0.0f), bVector3(-0.77f, -1.0f, 0.0f), 0.0f, + -0.77f, 0.0f), + CarPartCullingPlaneInfo(CULLABLE_CAR_PART_BRAKE_FR, "CULLABLE_CAR_PART_BRAKE_FR", CULLING_POLARITY_ALL_MUST_BE_VISIBLE, 2, + bVector3(0.36f, 1.0f, 0.0f), bVector3(-0.77f, 1.0f, 0.0f), bVector3(-0.77f, -1.0f, 0.0f), 0.0f, + -0.77f, 0.0f), + CarPartCullingPlaneInfo(CULLABLE_CAR_PART_BRAKE_RR, "CULLABLE_CAR_PART_BRAKE_RR", CULLING_POLARITY_ALL_MUST_BE_VISIBLE, 2, + bVector3(0.36f, 1.0f, 0.0f), bVector3(-0.77f, 1.0f, 0.0f), bVector3(-0.77f, -1.0f, 0.0f), 0.0f, + -0.77f, 0.0f), + CarPartCullingPlaneInfo(CULLABLE_CAR_PART_BRAKE_RL, "CULLABLE_CAR_PART_BRAKE_RL", CULLING_POLARITY_ALL_MUST_BE_VISIBLE, 2, + bVector3(0.36f, -1.0f, 0.0f), bVector3(-0.77f, -1.0f, 0.0f), bVector3(-0.77f, -1.0f, 0.0f), 0.0f, + -0.77f, 0.0f), + CarPartCullingPlaneInfo(CULLABLE_CAR_PART_SIDE_FRONT, "CULLABLE_CAR_PART_SIDE_FRONT", CULLING_POLARITY_ANY_VISIBLE, 1, + bVector3(-1.0f, 0.0f, 0.0f), bVector3(-1.0f, 0.0f, 0.0f), bVector3(-1.0f, 0.0f, 0.0f), 0.0f, 0.0f, + 0.0f), + CarPartCullingPlaneInfo(CULLABLE_CAR_PART_SIDE_REAR, "CULLABLE_CAR_PART_SIDE_REAR", CULLING_POLARITY_ANY_VISIBLE, 1, + bVector3(1.0f, 0.0f, 0.0f), bVector3(-1.0f, 0.0f, 0.0f), bVector3(-1.0f, 0.0f, 0.0f), 0.0f, 0.0f, + 0.0f), + CarPartCullingPlaneInfo(CULLABLE_CAR_PART_UNDERNEATH, "CULLABLE_CAR_PART_UNDERNEATH", CULLING_POLARITY_ALL_MUST_BE_VISIBLE, 2, + bVector3(-0.07f, 0.0f, 1.0f), bVector3(0.07f, 0.0f, 1.0f), bVector3(0.07f, 0.0f, 1.0f), -0.77f, + -0.77f, 0.0f), +}; const CarPartCullingPlaneInfo *pCurrentPartCullingPlaneInfo = nullptr; +unsigned int CarReplacementDecalHash[CarRenderInfo::REPLACETEX_DECAL_NUM]; +unsigned int gTrunkAudioMarkerHash[12]; + +struct CarRenderHashStartup { + CarRenderHashStartup() { + CarReplacementDecalHash[0] = bStringHash("BOTTOM_DECAL"); + + unsigned int *decal_hash = &CarReplacementDecalHash[1]; + *decal_hash = bStringHash("FRONT_BUMPER_DECAL"); + *++decal_hash = bStringHash("FRONT_DECAL"); + *++decal_hash = bStringHash("GTWING_DECAL"); + *++decal_hash = bStringHash("HOOD_DECAL"); + *++decal_hash = bStringHash("LEFT_BRAKELIGHT_DECAL"); + *++decal_hash = bStringHash("LEFT_DOOR_DECAL"); + *++decal_hash = bStringHash("LEFT_FENDER_DECAL"); + *++decal_hash = bStringHash("LEFT_QUARTER_DECAL"); + *++decal_hash = bStringHash("LEFT_SIDE_MIRROR_DECAL"); + *++decal_hash = bStringHash("LEFT_SKIRT_DECAL"); + *++decal_hash = bStringHash("REAR_BUMPER_DECAL"); + *++decal_hash = bStringHash("REAR_DECAL"); + *++decal_hash = bStringHash("RIGHT_BRAKELIGHT_DECAL"); + *++decal_hash = bStringHash("RIGHT_DOOR_DECAL"); + *++decal_hash = bStringHash("RIGHT_FENDER_DECAL"); + *++decal_hash = bStringHash("RIGHT_QUARTER_DECAL"); + *++decal_hash = bStringHash("RIGHT_SIDE_MIRROR_DECAL"); + *++decal_hash = bStringHash("RIGHT_SKIRT_DECAL"); + *++decal_hash = bStringHash("TOP_DECAL"); + *++decal_hash = bStringHash("FRONT_WINDOW_DECAL"); + *++decal_hash = bStringHash("REAR_WINDOW_DECAL"); + *++decal_hash = bStringHash("LEFT_FRONT_WINDOW_DECAL"); + *++decal_hash = bStringHash("LEFT_REAR_WINDOW_DECAL"); + *++decal_hash = bStringHash("RIGHT_FRONT_WINDOW_DECAL"); + *++decal_hash = bStringHash("RIGHT_REAR_WINDOW_DECAL"); + + gTrunkAudioMarkerHash[0] = bStringHash("MARKER_AUDIO_COMP_0"); + + unsigned int *trunk_audio_hash = &gTrunkAudioMarkerHash[1]; + *trunk_audio_hash = bStringHash("MARKER_AUDIO_COMP_1"); + *++trunk_audio_hash = bStringHash("MARKER_AUDIO_COMP_2"); + *++trunk_audio_hash = bStringHash("MARKER_AUDIO_COMP_3"); + *++trunk_audio_hash = bStringHash("MARKER_AUDIO_COMP_4"); + *++trunk_audio_hash = bStringHash("MARKER_AUDIO_COMP_5"); + *++trunk_audio_hash = bStringHash("MARKER_AUDIO_COMP_6"); + *++trunk_audio_hash = bStringHash("MARKER_AUDIO_COMP_7"); + *++trunk_audio_hash = bStringHash("MARKER_AUDIO_COMP_8"); + *++trunk_audio_hash = bStringHash("MARKER_AUDIO_COMP_9"); + *++trunk_audio_hash = bStringHash("MARKER_AUDIO_COMP_10"); + *++trunk_audio_hash = bStringHash("MARKER_AUDIO_COMP_11"); + } +} CarRenderHashStartupInitializer; + +extern "C" void __5ePoly(ePoly *); void CarPartCuller::InitPart(eCullableCarParts type, const bVector3 *position) { CarPartCullingPlaneInfo *plane_info = &CarPartCullingPlaneInfoTable[type]; @@ -354,12 +799,421 @@ bMatrix4 NISCopCarDoorClosedMarkers[4]; // UNSOLVED, to preserve my sanity CarRenderInfo::CarRenderInfo(RideInfo *ride_info) - : mDamageBehaviour(nullptr), mWorldPos(0.025f), mAttributes(Attrib::FindCollection(this->GetAttributes().ClassKey(), 0xeec2271a), 0, nullptr) { - // ... + : mDamageBehaviour(nullptr), // + mWCollider(nullptr), // + mWorldPos(0.025f), // + mEmitterPositionsInitialized(false), // + mOnLights(0), // + mBrokenLights(0), // + mRadius(lbl_8040AA60), // + mAttributes(0xeec2271a, 0, nullptr), // + mFlashing(false), // + mFlashInterval(0.0f) +{ + ProfileNode profile_node; + bVector3 tire_positions[4]; + float wheel_radius[4]; + + bMemSet(&this->TheCarPartCuller, 0, sizeof(this->TheCarPartCuller)); + CarTypeInfo *info = &CarTypeInfoArray[ride_info->Type]; + char *car_base_name = info->BaseModelName; + this->mAttributes.ChangeWithDefault(Attrib::StringToLowerCaseKey(car_base_name)); + *reinterpret_cast(&this->mMirrorLeftWheels) = + static_cast(this->mAttributes.WheelSpokeCount()) >> 7; + bMemSet(&this->mDamageInfoCache, 0, 0x14); + + this->AnimTime = 0.0f; + for (unsigned int wheel = 0; wheel < 4; wheel++) { + UMath::Vector4 tire_offset; + + this->GetAttributes().TireOffsets(tire_offset, wheel); + tire_positions[wheel] = bVector3(tire_offset.x, tire_offset.y, tire_offset.z); + wheel_radius[wheel] = tire_offset.w; + } + + this->WheelWidths[0] = WheelStandardWidth; + this->WheelWidths[1] = WheelStandardWidth; + this->WheelRadius[0] = WheelStandardRadius; + this->WheelRadius[1] = WheelStandardRadius; + + for (int i = 0; i < 4; i++) { + this->WheelWidthScales[i] = 1.0f; + this->WheelRadiusScales[i] = 1.0f; + } + + this->mVelocity.x = 0.0f; + this->mVelocity.y = 0.0f; + this->mVelocity.z = 0.0f; + this->mAngularVelocity.x = 0.0f; + this->mAngularVelocity.y = 0.0f; + this->mAngularVelocity.z = 0.0f; + this->mAcceleration.x = 0.0f; + this->mAcceleration.y = 0.0f; + this->mAcceleration.z = 0.0f; + + if (TheGameFlowManager.IsInFrontend()) { + this->mMinLodLevel = reinterpret_cast(ride_info)->mMinFELodLevel; + this->mMaxLodLevel = reinterpret_cast(ride_info)->mMaxFELodLevel; + } else { + this->mMinLodLevel = reinterpret_cast(ride_info)->mMinLodLevel; + this->mMaxLodLevel = reinterpret_cast(ride_info)->mMaxLodLevel; + } + + CARPART_LOD min_reflection_lod = reinterpret_cast(ride_info)->mMinReflectionLodLevel; + + this->pRideInfo = ride_info; + this->mMinReflectionLodLevel = min_reflection_lod; + this->LastCarPartChanged = -1; + this->CarTimebaseStart = bRandom(1.0f); + this->mDeltaTime = 0.0f; + CarTypeInfo *car_type_info = &CarTypeInfoArray[this->pRideInfo->Type]; + int is_traffic_car = car_type_info->GetCarUsageType() == CAR_USAGE_TYPE_TRAFFIC; + + bMemSet(this->mCarPartModels, 0, sizeof(this->mCarPartModels)); + this->pCarTypeInfo = car_type_info; + this->CarbonHood = 0; + GetUsedCarTextureInfo(&this->mUsedTextureInfos, this->pRideInfo, 0); + { + UsedCarTextureInfo *used_texture_info = &this->mUsedTextureInfos; + unsigned int mapped_skin_hash = used_texture_info->MappedSkinHash; + unsigned int mapped_skin_b_hash = used_texture_info->MappedSkinBHash; + unsigned int mapped_global_hash = used_texture_info->MappedGlobalHash; + unsigned int mapped_badging_hash = used_texture_info->MappedBadging; + unsigned int mapped_wheel_hash = used_texture_info->MappedWheelHash; + unsigned int mapped_spinner_hash = used_texture_info->MappedSpinnerHash; + unsigned int mapped_spoiler_hash = used_texture_info->MappedSpoilerHash; + unsigned int mapped_roof_scoop_hash = used_texture_info->MappedRoofScoopHash; + unsigned int mapped_dash_skin_hash = used_texture_info->MappedDashSkinHash; + + this->MasterReplacementTextureTable[REPLACETEX_CARSKIN].SetOldNameHash(mapped_skin_hash); + this->MasterReplacementTextureTable[REPLACETEX_CARSKINB].SetOldNameHash(mapped_skin_b_hash); + this->MasterReplacementTextureTable[REPLACETEX_GLOBALSKIN].SetOldNameHash(mapped_global_hash); + this->MasterReplacementTextureTable[REPLACETEX_CARBONSKIN].SetOldNameHash(0xA7366AE6); + this->MasterReplacementTextureTable[REPLACETEX_GLOBALCARBONSKIN].SetOldNameHash(0x3C84D757); + this->MasterReplacementTextureTable[REPLACETEX_BADGING].SetOldNameHash(mapped_badging_hash); + this->MasterReplacementTextureTable[REPLACETEX_WHEEL].SetOldNameHash(mapped_wheel_hash); + this->MasterReplacementTextureTable[REPLACETEX_SPINNER].SetOldNameHash(mapped_spinner_hash); + this->MasterReplacementTextureTable[REPLACETEX_SPOILER].SetOldNameHash(mapped_spoiler_hash); + this->MasterReplacementTextureTable[REPLACETEX_ROOF_SCOOP].SetOldNameHash(mapped_roof_scoop_hash); + this->MasterReplacementTextureTable[REPLACETEX_DASHSKIN].SetOldNameHash(mapped_dash_skin_hash); + this->MasterReplacementTextureTable[REPLACETEX_DRIVER].SetOldNameHash(0x5799E60B); + unsigned int mapped_tire_hash = used_texture_info->MappedTireHash; + this->MasterReplacementTextureTable[REPLACETEX_TIRE].SetOldNameHash(mapped_tire_hash); + if (is_traffic_car != 0) { + this->MasterReplacementTextureTable[REPLACETEX_TIRE].SetNewNameHash(bStringHash("_N")); + } + this->MasterReplacementTextureTable[REPLACETEX_WINDOW_FRONT].SetOldNameHash(bStringHash("WINDOW_FRONT")); + this->MasterReplacementTextureTable[REPLACETEX_WINDOW_REAR].SetOldNameHash(bStringHash("WINDOW_REAR")); + this->MasterReplacementTextureTable[REPLACETEX_WINDOW_LEFT_FRONT].SetOldNameHash(bStringHash("WINDOW_LEFT_FRONT")); + this->MasterReplacementTextureTable[REPLACETEX_WINDOW_LEFT_REAR].SetOldNameHash(bStringHash("WINDOW_LEFT_REAR")); + this->MasterReplacementTextureTable[REPLACETEX_WINDOW_RIGHT_FRONT].SetOldNameHash(bStringHash("WINDOW_RIGHT_FRONT")); + this->MasterReplacementTextureTable[REPLACETEX_WINDOW_RIGHT_REAR].SetOldNameHash(bStringHash("WINDOW_RIGHT_REAR")); + this->MasterReplacementTextureTable[REPLACETEX_WINDOW_REAR_DEFOST].SetOldNameHash(bStringHash("REAR_DEFROSTER")); + this->MasterReplacementTextureTable[REPLACETEX_WINDOW2_FRONT].SetOldNameHash(bStringHash("WINDOW_FRONT")); + this->MasterReplacementTextureTable[REPLACETEX_WINDOW2_REAR].SetOldNameHash(bStringHash("WINDOW_REAR")); + this->MasterReplacementTextureTable[REPLACETEX_WINDOW2_LEFT_FRONT].SetOldNameHash(bStringHash("WINDOW_LEFT_FRONT")); + this->MasterReplacementTextureTable[REPLACETEX_WINDOW2_LEFT_REAR].SetOldNameHash(bStringHash("WINDOW_LEFT_REAR")); + this->MasterReplacementTextureTable[REPLACETEX_WINDOW2_RIGHT_FRONT].SetOldNameHash(bStringHash("WINDOW_RIGHT_FRONT")); + this->MasterReplacementTextureTable[REPLACETEX_WINDOW2_RIGHT_REAR].SetOldNameHash(bStringHash("WINDOW_RIGHT_REAR")); + this->MasterReplacementTextureTable[REPLACETEX_WINDOW2_REAR_DEFOST].SetOldNameHash(bStringHash("REAR_DEFROSTER")); + this->MasterReplacementTextureTable[REPLACETEX_HEADLIGHT_LEFT].SetOldNameHash(0xA7E6EA53); + this->MasterReplacementTextureTable[REPLACETEX_HEADLIGHT_RIGHT].SetOldNameHash(0xA532FC46); + this->MasterReplacementTextureTable[REPLACETEX_OLD_HEADLIGHT_LEFT].SetOldNameHash(this->mUsedTextureInfos.MappedLightHash[0]); + this->MasterReplacementTextureTable[REPLACETEX_OLD_HEADLIGHT_RIGHT].SetOldNameHash(this->mUsedTextureInfos.MappedLightHash[1]); + this->MasterReplacementTextureTable[REPLACETEX_OLD_BRAKELIGHT_LEFT].SetOldNameHash(this->mUsedTextureInfos.MappedLightHash[2]); + this->MasterReplacementTextureTable[REPLACETEX_OLD_BRAKELIGHT_RIGHT].SetOldNameHash(this->mUsedTextureInfos.MappedLightHash[3]); + this->MasterReplacementTextureTable[REPLACETEX_OLD_BRAKELIGHT_CENTRE].SetOldNameHash(this->mUsedTextureInfos.MappedLightHash[4]); + this->MasterReplacementTextureTable[REPLACETEX_OLD_HEADLIGHT_GLASS_LEFT].SetOldNameHash(this->mUsedTextureInfos.MappedLightHash[5]); + this->MasterReplacementTextureTable[REPLACETEX_OLD_HEADLIGHT_GLASS_RIGHT].SetOldNameHash(this->mUsedTextureInfos.MappedLightHash[6]); + this->MasterReplacementTextureTable[REPLACETEX_OLD_BRAKELIGHT_GLASS_LEFT].SetOldNameHash(this->mUsedTextureInfos.MappedLightHash[7]); + this->MasterReplacementTextureTable[REPLACETEX_OLD_BRAKELIGHT_GLASS_RIGHT].SetOldNameHash(this->mUsedTextureInfos.MappedLightHash[8]); + this->MasterReplacementTextureTable[REPLACETEX_OLD_BRAKELIGHT_GLASS_CENTRE].SetOldNameHash(this->mUsedTextureInfos.MappedLightHash[9]); + this->BrakeLeftReplacementTextureTable[0].SetOldNameHash(0x17F9F794); + this->BrakeLeftReplacementTextureTable[0].SetNewNameHash(0x85E9C79E); + this->BrakeLeftReplacementTextureTable[1].SetOldNameHash(this->mUsedTextureInfos.MappedGlobalHash); + this->BrakeRightReplacementTextureTable[0].SetOldNameHash(0x17F9F794); + this->BrakeRightReplacementTextureTable[0].SetNewNameHash(0x17F9F794); + this->BrakeRightReplacementTextureTable[1].SetOldNameHash(this->mUsedTextureInfos.MappedGlobalHash); + } + this->SwitchSkin(this->pRideInfo); + { + UsedCarTextureInfo *used_texture_info = &this->mUsedTextureInfos; + unsigned int badging_hash = used_texture_info->MappedBadging; + const char *europe_suffix = BuildRegion::IsEurope() ? "_EU" : nullptr; + + if (europe_suffix != nullptr) { + unsigned int europe_badging_hash = bStringHash(europe_suffix, badging_hash); + + if (GetTextureInfo(europe_badging_hash, 0, 0) != nullptr) { + badging_hash = europe_badging_hash; + } + } + TextureInfo *badging_texture = GetTextureInfo(badging_hash, 0, 0); + + if (badging_texture != nullptr) { + badging_texture->ApplyAlphaSorting = 0; + } + + this->MasterReplacementTextureTable[REPLACETEX_BADGING].SetNewNameHash(badging_hash); + } + this->UpdateCarReplacementTextures(); + this->UpdateDecalTextures(ride_info); + this->MasterReplacementTextureTable[REPLACETEX_DRIVER].SetNewNameHash(0xA244D489); + this->UpdateCarParts(); + this->ShadowTexture = GetTextureInfo(bStringHash("CARSHADOW"), 1, 0); + this->ShadowRampTexture = GetTextureInfo(0xBADB4475, 0, 0); + + if (this->ShadowTexture != nullptr) { + this->ShadowTexture->ApplyAlphaSorting = 0; + this->ShadowTexture->RenderingOrder = 2; + } + + if (this->ShadowRampTexture != nullptr) { + this->ShadowRampTexture->ApplyAlphaSorting = 0; + this->ShadowRampTexture->RenderingOrder = 3; + } + + { + eModel *base_model = this->mCarPartModels[CARSLOTID_BASE][0][this->mMinLodLevel].GetModel(); + + if (base_model != nullptr) { + bVector4 *pivot_position = base_model->GetPivotPosition(); + float pivot_x; + + if (pivot_position != nullptr) { + pivot_x = pivot_position->x; + } else { + pivot_x = 0.0f; + } + this->PivotPosition.x = pivot_x; + + float pivot_y; + + if (pivot_position != nullptr) { + pivot_y = pivot_position->y; + } else { + pivot_y = 0.0f; + } + this->PivotPosition.y = pivot_y; + + float pivot_z; + + if (pivot_position != nullptr) { + pivot_z = pivot_position->z; + } else { + pivot_z = 0.0f; + } + this->PivotPosition.z = pivot_z; + } + } + + this->CreateCarLightFlares(); + + { + this->TheCarPartCuller.InitPart(CULLABLE_CAR_PART_TIRE_FL, &tire_positions[0]); + this->TheCarPartCuller.InitPart(CULLABLE_CAR_PART_TIRE_FR, &tire_positions[1]); + this->TheCarPartCuller.InitPart(CULLABLE_CAR_PART_TIRE_RR, &tire_positions[2]); + this->TheCarPartCuller.InitPart(CULLABLE_CAR_PART_TIRE_RL, &tire_positions[3]); + + bVector3 v_left = tire_positions[0] + tire_positions[3]; + v_left /= 2.0f; + bVector3 v_right = tire_positions[1] + tire_positions[2]; + v_right /= 2.0f; + + this->TheCarPartCuller.InitPart(CULLABLE_CAR_PART_BRAKE_FL, &tire_positions[0]); + this->TheCarPartCuller.InitPart(CULLABLE_CAR_PART_BRAKE_FR, &tire_positions[1]); + this->TheCarPartCuller.InitPart(CULLABLE_CAR_PART_BRAKE_RR, &tire_positions[2]); + this->TheCarPartCuller.InitPart(CULLABLE_CAR_PART_BRAKE_RL, &tire_positions[3]); + this->TheCarPartCuller.InitPart(CULLABLE_CAR_PART_SIDE_FRONT, &tire_positions[0]); + this->TheCarPartCuller.InitPart(CULLABLE_CAR_PART_SIDE_REAR, &tire_positions[2]); + + bVector3 v_underneath = v_left + v_right; + v_underneath /= 2.0f; + this->TheCarPartCuller.InitPart(CULLABLE_CAR_PART_UNDERNEATH, &v_underneath); + + bVector3 v_front_diff = tire_positions[0] - tire_positions[1]; + bVector3 v_side_diff = tire_positions[1] - tire_positions[2]; + bVector3 v_normal; + bCross(&v_normal, &v_front_diff, &v_side_diff); + bNormalize(&v_normal, &v_normal); + + float tire_radius = wheel_radius[0]; + if (tire_radius < wheel_radius[1]) { + tire_radius = wheel_radius[1]; + } + if (tire_radius < wheel_radius[2]) { + tire_radius = wheel_radius[2]; + } + if (tire_radius < wheel_radius[3]) { + tire_radius = wheel_radius[3]; + } + + bScale(&v_normal, &v_normal, tire_radius); + bAdd(&v_underneath, &v_underneath, &v_normal); + } + + { + for (int lod = this->mMinLodLevel; lod < this->mMaxLodLevel + 1; lod++) { + eModel *smooth_normal_models[0x4C]; + + bMemSet(smooth_normal_models, 0, sizeof(smooth_normal_models)); + for (int slot = 0; slot < 0x4C; slot++) { + eModel *model = this->mCarPartModels[slot][0][lod].GetModel(); + eModel *smooth_model = nullptr; + + if (model != nullptr) { + eModel *previous_model = nullptr; + + if (slot == CARSLOTID_RIGHT_SIDE_MIRROR) { + goto use_smooth_normal_model; + } else { + if (slot > CARSLOTID_RIGHT_SIDE_MIRROR) { + if (slot != CARSLOTID_SPOILER && + (slot < CARSLOTID_SPOILER || slot > CARSLOTID_BRAKELIGHT || slot < CARSLOTID_ROOF)) { + goto skip_smooth_normal_model; + } + goto use_smooth_normal_model; + } + if (slot == CARSLOTID_BODY) { + goto use_smooth_normal_model; + } + if (slot < CARSLOTID_BODY + 1) { + if (slot == CARSLOTID_BASE) { + goto use_smooth_normal_model; + } + } else if (slot == CARSLOTID_LEFT_SIDE_MIRROR) { + goto use_smooth_normal_model; + } + goto skip_smooth_normal_model; + } + +use_smooth_normal_model: + if (lod > this->mMinLodLevel) { + previous_model = this->mCarPartModels[slot][0][lod - 1].GetModel(); + } + + if (lod <= this->mMinLodLevel || previous_model == nullptr || + previous_model->GetNameHash() != model->GetNameHash()) { + smooth_model = model; + } + +skip_smooth_normal_model: {} + } + + smooth_normal_models[slot] = smooth_model; + } + + eSmoothNormals(smooth_normal_models, 0x4C); + } + } + + { + unsigned int light_material_hash = 0; + CarPart *base_paint_part = ride_info->GetPart(CARSLOTID_BASE_PAINT); + + if (base_paint_part != nullptr) { + light_material_hash = CarPart_GetAppliedAttributeUParam(base_paint_part, 0x6BA02C05, 0); + } + + this->LightMaterial_CarSkin = elGetLightMaterial(light_material_hash); + this->LightMaterial_Carbon = elGetLightMaterial(bStringHash("CARBONFIBRE")); + + { + CarPart *window_tint_part = ride_info->GetPart(CARSLOTID_WINDOW_TINT); + unsigned int window_tint_material_hash = 0x471A1DCA; + + if (window_tint_part != nullptr) { + window_tint_material_hash = CarPart_GetAppliedAttributeUParam(window_tint_part, 0x6BA02C05, 0); + } + + this->LightMaterial_WindowTint = elGetLightMaterial(window_tint_material_hash); + } + + { + CarPart *paint_rim_part = ride_info->GetPart(CARSLOTID_PAINT_RIM); + CarPart *front_wheel_part = ride_info->GetPart(CARSLOTID_FRONT_WHEEL); + CarPart *spoiler_part = ride_info->GetPart(CARSLOTID_SPOILER); + CarPart *roof_part = ride_info->GetPart(CARSLOTID_ROOF); + unsigned int wheel_rim_material_hash = 0; + unsigned int caliper_material_hash = 0; + unsigned int spoiler_material_hash = 0; + unsigned int roof_material_hash = 0; + eLightMaterial *wheel_rim_material; + eLightMaterial *caliper_material; + eLightMaterial *spoiler_material; + eLightMaterial *roof_material; + + this->LightMaterial_Spinner = nullptr; + + if (paint_rim_part == nullptr || (reinterpret_cast(paint_rim_part)[5] >> 5) == 0) { + paint_rim_part = nullptr; + } + + if (front_wheel_part == nullptr || (reinterpret_cast(front_wheel_part)[5] >> 5) == 0) { + front_wheel_part = nullptr; + } + + if (spoiler_part == nullptr || (reinterpret_cast(spoiler_part)[5] >> 5) == 0) { + spoiler_part = nullptr; + } + + if (roof_part == nullptr || (reinterpret_cast(roof_part)[5] >> 5) == 0) { + roof_part = nullptr; + } + + if (paint_rim_part != nullptr) { + wheel_rim_material_hash = CarPart_GetAppliedAttributeUParam(paint_rim_part, 0x6BA02C05, 0); + } + + if (front_wheel_part != nullptr) { + caliper_material_hash = CarPart_GetAppliedAttributeUParam(front_wheel_part, 0x6BA02C05, 0); + } + + if (spoiler_part != nullptr) { + spoiler_material_hash = CarPart_GetAppliedAttributeUParam(spoiler_part, 0x6BA02C05, 0); + } + + if (roof_part != nullptr) { + roof_material_hash = CarPart_GetAppliedAttributeUParam(roof_part, 0x6BA02C05, 0); + } + + if (wheel_rim_material_hash != 0) { + wheel_rim_material = elGetLightMaterial(wheel_rim_material_hash); + } else { + wheel_rim_material = nullptr; + } + + if (caliper_material_hash != 0) { + caliper_material = elGetLightMaterial(caliper_material_hash); + } else { + caliper_material = nullptr; + } + + if (spoiler_material_hash != 0) { + spoiler_material = elGetLightMaterial(spoiler_material_hash); + } else { + spoiler_material = nullptr; + } + + if (roof_material_hash != 0) { + roof_material = elGetLightMaterial(roof_material_hash); + } else { + roof_material = nullptr; + } + + this->LightMaterial_Caliper = caliper_material; + this->LightMaterial_WheelRim = wheel_rim_material; + this->LightMaterial_Spoiler = spoiler_material; + this->LightMaterial_Roof = roof_material; + } + } + + this->UpdateWheelYRenderOffset(); + { //? - eModel *front_wheel_model = this->mCarPartModels[this->mMinLodLevel][0][1].GetModel(); - eModel *rear_wheel_model = this->mCarPartModels[this->mMinLodLevel][0][2].GetModel(); + eModel *front_wheel_model = this->mCarPartModels[CARSLOTID_FRONT_WHEEL][0][this->mMinLodLevel].GetModel(); + eModel *rear_wheel_model = this->mCarPartModels[CARSLOTID_REAR_WHEEL][0][this->mMinLodLevel].GetModel(); ePositionMarker *front_position_marker; if (front_wheel_model != nullptr) { @@ -397,8 +1251,8 @@ CarRenderInfo::CarRenderInfo(RideInfo *ride_info) eIdentity(&NISCopCarDoorOpenMarkers[i]); eIdentity(&NISCopCarDoorClosedMarkers[i]); } - - eModel *base_model = this->mCarPartModels[this->mMinLodLevel][0][1].GetModel(); + + eModel *base_model = this->mCarPartModels[CARSLOTID_BODY][0][this->mMinLodLevel].GetModel(); if (base_model != nullptr) { unsigned int open_string_hashes[4] = {0xF91BCA96, 0x8DE14C29, 0x60989ECA, 0xD0F2CD17}; unsigned int closed_string_hashes[4] = {0x58A2A425, 0x8FE91DD8, 0x05C907D9, 0x7B3CD206}; @@ -410,8 +1264,8 @@ CarRenderInfo::CarRenderInfo(RideInfo *ride_info) if (open_marker == nullptr || closed_marker == nullptr) continue; - open_marker->Matrix = NISCopCarDoorOpenMarkers[i]; - closed_marker->Matrix = NISCopCarDoorClosedMarkers[i]; + NISCopCarDoorOpenMarkers[i] = open_marker->Matrix; + NISCopCarDoorClosedMarkers[i] = closed_marker->Matrix; } } } @@ -435,7 +1289,7 @@ CarRenderInfo::CarRenderInfo(RideInfo *ride_info) // UNSOLVED CarRenderInfo::~CarRenderInfo() { for (int model_index = 0; model_index < 0x4C; model_index++) { - for (int model_number = 0; model_index < 1; model_index++) { + for (int model_number = 0; model_number < 1; model_number++) { for (int model_lod = this->mMinLodLevel; model_lod <= this->mMaxLodLevel; model_lod++) { eModel *model = this->mCarPartModels[model_index][model_number][model_lod].GetModel(); if (model == nullptr) @@ -450,6 +1304,11 @@ CarRenderInfo::~CarRenderInfo() { } } } + + if (mDamageBehaviour != nullptr) { + delete mDamageBehaviour; + mDamageBehaviour = nullptr; + } } void CarRenderInfo::Init() { @@ -582,3 +1441,3083 @@ unsigned int CarRenderInfo::HideCarPart(int slotId, bool hide) { return model_namehash; } + +struct CarPartMetaLayout { + unsigned short PartNameHashBot; + unsigned short PartNameHashTop; + char PartID; + unsigned char GroupNumber_UpgradeLevel; + char BaseModelNameHashSelector; + unsigned char CarTypeNameHashIndex; + unsigned short NameOffset; + unsigned short AttributeTableOffset; + unsigned short ModelNameHashTableOffset; +}; + +struct CameraAnchorLayout { + bVector3 mVelocity; + float mVelMag; + float mTopSpeed; + bVector3 mGeomPos; +}; + +struct CarPartModelLayout { + unsigned int mModel; +}; + +static inline eModel *GetPackedCarPartModel(CarPartModel *car_part_model) { + return reinterpret_cast(reinterpret_cast(car_part_model)->mModel & ~0x3); +} + +static inline void SetPackedCarPartModel(CarPartModel *car_part_model, eModel *model) { + unsigned int &packed_model = reinterpret_cast(car_part_model)->mModel; + + packed_model = reinterpret_cast(model) | (packed_model & 1); +} + +static inline void ClearPackedCarPartModel(CarPartModel *car_part_model) { + reinterpret_cast(car_part_model)->mModel &= 1; +} + +static inline bool DotPassesTest(const bVector3 *point) { + bVector3 vec = *point - hull_Origin; + float dot = bDot(&vec, &hull_Normal); + + if (dot < lbl_8040ADC0) { + dot = -dot; + } + + return dot < lbl_8040ADEC; +} + +static inline void *CarRenderFrameMalloc(unsigned int size) { + unsigned char *address = CurrentBufferPos; + + if (CurrentBufferEnd <= CurrentBufferPos + size) { + FrameMallocFailed = 1; + FrameMallocFailAmount += size; + address = 0; + } else { + CurrentBufferPos += size; + } + + return address; +} + +inline bool CarRenderInfo::IsLightBroken(VehicleFX::ID id) const { + return (this->mBrokenLights & id) != 0; +} + +inline bool CarRenderInfo::IsLightOn(VehicleFX::ID id) const { + return (this->mOnLights & id) != 0; +} + +void elResetLightContext(eDynamicLightContext *light_context); +int elSetupLights(eDynamicLightContext *light_context, eShaperLightRig *shaper_lights, bVector3 *local_pos, bMatrix4 *local_world, bMatrix4 *world_view, + eView *view); +int elCloneLightContext(eDynamicLightContext *light_context, bMatrix4 *local_world, bMatrix4 *world_view, bVector4 *camera_world_position, eView *view, + eDynamicLightContext *old_context); + +void CarRenderInfo::UpdateCarParts() { + ProfileNode profile_node("UpdateCarParts", 0); + + bInitializeBoundingBox(&this->AABBMin, &this->AABBMax); + + for (int slot_id = 0; slot_id < 0x4C; slot_id++) { + for (int model_number = 0; model_number < 1; model_number++) { + for (int lod = this->mMinLodLevel; lod <= this->mMaxLodLevel; lod++) { + eModel *model = this->mCarPartModels[slot_id][model_number][lod].GetModel(); + + if (model != 0 && model->GetNameHash() != 0) { + model->UnInit(); + CarPartModelPool->Free(model); + this->mCarPartModels[slot_id][model_number][lod].SetModel(nullptr); + } + } + } + } + + RideInfo *ride_info = this->pRideInfo; + for (int slot_id = 0; slot_id < 0x4C; slot_id++) { + CarPart *car_part = ride_info->GetPart(slot_id); + + if (car_part == 0) { + continue; + } + + for (int model_number = 0; model_number < 1; model_number++) { + for (int lod = this->mMinLodLevel; lod <= this->mMaxLodLevel; lod++) { + CARPART_LOD special_minimum; + CARPART_LOD special_maximum; + CARPART_LOD model_lod = static_cast(lod); + + if (ride_info->GetSpecialLODRangeForCarSlot( + slot_id, + &special_minimum, + &special_maximum, + IsGameFlowInFrontEnd())) { + model_lod = static_cast(bClamp(model_lod, special_minimum, special_maximum)); + } + + unsigned int model_name_hash = CarPart_GetModelNameHash(car_part, model_number, model_lod); + + if (model_name_hash == 0) { + continue; + } + + eModel *model = static_cast(CarPartModelPool->Malloc()); + + this->mCarPartModels[slot_id][model_number][lod].SetModel(model); + model->Init(model_name_hash); + + if (!model->HasSolid()) { + model->UnInit(); + CarPartModelPool->Free(model); + this->mCarPartModels[slot_id][model_number][lod].SetModel(nullptr); + model = 0; + } + + if (model != 0) { + if (slot_id < CARSLOTID_DECAL_FRONT_WINDOW || slot_id > CARSLOTID_DECAL_RIGHT_QUARTER) { + if (slot_id == CARSLOTID_HOOD) { + int carbon_hood = CarPart_GetAppliedAttributeIParam(ride_info->GetPart(CARSLOTID_HOOD), 0x721AFF7C, 0); + + if (carbon_hood != 0) { + model->AttachReplacementTextureTable(this->CarbonReplacementTextureTable, REPLACETEX_NUM, 0); + this->CarbonHood = 1; + } else { + model->AttachReplacementTextureTable(this->MasterReplacementTextureTable, REPLACETEX_NUM, 0); + this->CarbonHood = carbon_hood; + } + } else { + model->AttachReplacementTextureTable(this->MasterReplacementTextureTable, REPLACETEX_NUM, 0); + } + } else { + model->AttachReplacementTextureTable(&this->DecalReplacementTextureTable[(slot_id - CARSLOTID_DECAL_FRONT_WINDOW) * 8], 8, 0); + } + } + } + + eModel *model; + + if (slot_id <= CARSLOTID_DAMAGE_REAR_BUMPER) { + if (slot_id >= CARSLOTID_DAMAGE_TRUNK) { + goto expand_bbox; + } + + if (slot_id > CARSLOTID_DAMAGE_COP_LIGHTS) { + if (slot_id <= CARSLOTID_DAMAGE_FRONT_BUMPER) { + if (slot_id >= CARSLOTID_DAMAGE_HOOD) { + goto expand_bbox; + } + } + } else { + if (slot_id >= CARSLOTID_DAMAGE_BODY) { + goto expand_bbox; + } + if (slot_id == CARSLOTID_BASE) { + goto expand_bbox; + } + } + } else if (slot_id == CARSLOTID_RIGHT_SIDE_MIRROR) { + goto expand_bbox; + } else if (slot_id < CARSLOTID_RIGHT_SIDE_MIRROR) { + if (slot_id == CARSLOTID_BODY) { + goto expand_bbox; + } + if (slot_id == CARSLOTID_LEFT_SIDE_MIRROR) { + goto expand_bbox; + } + } else { + if (slot_id == CARSLOTID_SPOILER) { + goto expand_bbox; + } + if (slot_id >= CARSLOTID_ROOF) { + if (slot_id <= CARSLOTID_HOOD) { + goto expand_bbox; + } + } + } + + goto skip_expand_bbox; + + expand_bbox: + model = this->mCarPartModels[slot_id][model_number][this->mMinLodLevel].GetModel(); + + if (model != 0) { + bVector3 bbox_min; + bVector3 bbox_max; + + model->GetBoundingBox(&bbox_min, &bbox_max); + bExpandBoundingBox(&this->AABBMin, &this->AABBMax, &bbox_min, &bbox_max); + } + + skip_expand_bbox: + ; + } + } + + for (int model_number = 0; model_number < 1; model_number++) { + for (int lod = this->mMinLodLevel; lod <= this->mMaxLodLevel; lod++) { + CarPartModel *front_wheel_part_model = &this->mCarPartModels[CARSLOTID_FRONT_WHEEL][model_number][lod]; + eModel *front_wheel_model = front_wheel_part_model->GetModel(); + CarPartModel *rear_wheel_part_model = &this->mCarPartModels[CARSLOTID_REAR_WHEEL][model_number][lod]; + eModel *rear_wheel_model = rear_wheel_part_model->GetModel(); + + if (front_wheel_model != 0 && rear_wheel_model == 0) { + rear_wheel_model = static_cast(CarPartModelPool->Malloc()); + rear_wheel_part_model->SetModel(rear_wheel_model); + rear_wheel_model->Init(front_wheel_model->GetNameHash()); + + if (!rear_wheel_model->HasSolid()) { + rear_wheel_model->UnInit(); + CarPartModelPool->Free(rear_wheel_model); + rear_wheel_part_model->SetModel(nullptr); + } else { + rear_wheel_model->AttachReplacementTextureTable(this->MasterReplacementTextureTable, REPLACETEX_NUM, 0); + } + } + + CarPartModel *front_brake_part_model = &this->mCarPartModels[CARSLOTID_FRONT_BRAKE][model_number][lod]; + CarPartModel *rear_brake_part_model = &this->mCarPartModels[CARSLOTID_REAR_BRAKE][model_number][lod]; + eModel *front_brake_model = front_brake_part_model->GetModel(); + eModel *rear_brake_model = rear_brake_part_model->GetModel(); + + if (front_brake_model != 0 && rear_brake_model == 0) { + rear_brake_model = static_cast(CarPartModelPool->Malloc()); + rear_brake_part_model->SetModel(rear_brake_model); + rear_brake_model->Init(front_brake_model->GetNameHash()); + + if (!rear_brake_model->HasSolid()) { + rear_brake_model->UnInit(); + CarPartModelPool->Free(rear_brake_model); + rear_brake_part_model->SetModel(nullptr); + } else { + rear_brake_model->AttachReplacementTextureTable(this->MasterReplacementTextureTable, REPLACETEX_NUM, 0); + } + } + } + } + + eModel *front_wheel_model = this->mCarPartModels[CARSLOTID_FRONT_WHEEL][0][this->mMinLodLevel].GetModel(); + eModel *rear_wheel_model = this->mCarPartModels[CARSLOTID_REAR_WHEEL][0][this->mMinLodLevel].GetModel(); + bVector3 bbox_min; + bVector3 bbox_max; + + if (front_wheel_model != 0) { + float wheel_width; + float wheel_radius; + + front_wheel_model->GetBoundingBox(&bbox_min, &bbox_max); + wheel_width = UMath::Abs(bbox_max.y - bbox_min.y); + this->WheelWidths[0] = wheel_width; + + wheel_radius = UMath::Abs(bbox_max.x - bbox_min.x); + this->WheelRadius[0] = wheel_radius * 0.5f; + } + + if (rear_wheel_model != 0) { + float wheel_width; + float wheel_radius; + + rear_wheel_model->GetBoundingBox(&bbox_min, &bbox_max); + wheel_width = UMath::Abs(bbox_max.y - bbox_min.y); + this->WheelWidths[1] = wheel_width; + + wheel_radius = UMath::Abs(bbox_max.x - bbox_min.x); + this->WheelRadius[1] = wheel_radius * 0.5f; + } + + this->ModelOffset = (this->AABBMax + this->AABBMin) * 0.5f; + + CarPart *base_part = ride_info->GetPart(CARSLOTID_BASE); + if (base_part != 0) { + eSolid *solid = eFindSolid(CarPart_GetModelNameHash(base_part, 0, this->mMinLodLevel)); + + if (solid != 0) { + this->SpoilerPositionMarker = solid->GetPostionMarker(0xC93B73FD); + this->SpoilerPositionMarker2 = solid->GetPostionMarker(0xF0A9F3CF); + this->RoofScoopPositionMarker = solid->GetPostionMarker(0x90C81258); + } else { + this->SpoilerPositionMarker = 0; + this->SpoilerPositionMarker2 = 0; + this->RoofScoopPositionMarker = 0; + } + } else { + this->SpoilerPositionMarker = 0; + this->SpoilerPositionMarker2 = 0; + this->RoofScoopPositionMarker = 0; + } + + CarPart *spoiler_part = ride_info->GetPart(CARSLOTID_SPOILER); + bool mirror_left_wheels = true; + + if (spoiler_part != 0) { + mirror_left_wheels = (reinterpret_cast(spoiler_part)->GroupNumber_UpgradeLevel >> 5) == 0; + } + + for (int lod = this->mMinLodLevel; lod <= this->mMaxLodLevel; lod++) { + this->mCarPartModels[CARSLOTID_UNIVERSAL_SPOILER_BASE][0][lod].Hide(mirror_left_wheels); + } + this->mMirrorLeftWheels = mirror_left_wheels; + + this->SetCarDamageState(false, 0x2E, 0x33); +} + +void CarRenderInfo::UpdateWheelYRenderOffset() { + CarPart *front_wheel = nullptr; + CarPart *rear_wheel = nullptr; + int front_upgrade_level = 0; + int rear_upgrade_level = 0; + UMath::Vector4 tire_offset; + + if (this->pCarTypeInfo == nullptr) { + bMemSet(this->WheelYRenderOffset, 0, sizeof(this->WheelYRenderOffset)); + return; + } + + if (this->pRideInfo != nullptr) { + front_wheel = this->pRideInfo->GetPart(CARSLOTID_FRONT_WHEEL); + } + if (this->pRideInfo != nullptr) { + rear_wheel = this->pRideInfo->GetPart(CARSLOTID_REAR_WHEEL); + } + + if (front_wheel != nullptr) { + front_upgrade_level = 0; + } + if (rear_wheel != nullptr) { + rear_upgrade_level = 0; + } + + for (int wheel = 0; wheel < 4; wheel++) { + int wheel_end = (wheel > 1); + int kit_number = wheel_end ? rear_upgrade_level : front_upgrade_level; + CarPart *body_part = nullptr; + int kit_wheel_offset; + float kit_wheel_offset_float; + float model_width; + float model_radius; + float desired_width; + float desired_radius; + + const UMath::Vector4 &tire_ref = this->GetAttributes().TireOffsets(wheel); + this->WheelYRenderOffset[wheel] = -tire_ref.y; + + body_part = this->pRideInfo->GetPart(CARSLOTID_BODY); + + if (body_part != nullptr) { + kit_number = CarPart_GetAppliedAttributeIParam(body_part, 0x796C0CB0, 0); + } + + if (wheel_end == 0) { + if (TweakKitWheelOffsetFront == 0) { + kit_wheel_offset = this->GetAttributes().KitWheelOffsetFront(kit_number); + } else { + kit_wheel_offset = TweakKitWheelOffsetFront; + } + } else if (TweakKitWheelOffsetRear == 0) { + kit_wheel_offset = this->GetAttributes().KitWheelOffsetRear(kit_number); + } else { + kit_wheel_offset = TweakKitWheelOffsetRear; + } + + kit_wheel_offset_float = static_cast(kit_wheel_offset) * 0.001f; + if (this->WheelYRenderOffset[wheel] <= 0.0f) { + this->WheelYRenderOffset[wheel] -= kit_wheel_offset_float; + } else { + this->WheelYRenderOffset[wheel] += kit_wheel_offset_float; + } + + model_width = this->WheelWidths[wheel_end]; + model_radius = this->WheelRadius[wheel_end]; + desired_width = this->GetAttributes().TireSkidWidth(wheel); + + if (wheel <= 1) { + desired_width *= this->GetAttributes().TireSkidWidthKitScale(kit_number).x; + } else { + desired_width *= this->GetAttributes().TireSkidWidthKitScale(kit_number).y; + } + + tire_offset = tire_ref; + desired_radius = tire_offset.w; + + if (model_width > 0.0f && desired_width > 0.0f) { + this->WheelWidthScales[wheel] = desired_width / model_width; + } else { + this->WheelWidthScales[wheel] = 1.0f; + } + + if (model_radius > 0.0f && desired_radius > 0.0f) { + this->WheelRadiusScales[wheel] = desired_radius / model_radius; + } else { + this->WheelRadiusScales[wheel] = 1.0f; + } + } +} + +void CarRenderInfo::UpdateDecalTextures(RideInfo *ride_info) { + unsigned int alpha_hash = bStringHash("DEFAULTALPHA"); + + for (int i = REPLACETEX_DECAL_START; i <= REPLACETEX_DECAL_END; i++) { + this->MasterReplacementTextureTable[i].SetOldNameHash(CarReplacementDecalHash[i - REPLACETEX_DECAL_START]); + this->MasterReplacementTextureTable[i].SetNewNameHash(alpha_hash); + } + + unsigned int decal_hashes[8] = { + bStringHash("DUMMY_DECAL1"), + bStringHash("DUMMY_DECAL2"), + bStringHash("DUMMY_DECAL3"), + bStringHash("DUMMY_DECAL4"), + bStringHash("DUMMY_DECAL5"), + bStringHash("DUMMY_DECAL6"), + bStringHash("DUMMY_NUMBER_LEFT"), + bStringHash("DUMMY_NUMBER_RIGHT"), + }; + + for (int i = 0; i < 48; i++) { + this->DecalReplacementTextureTable[i].SetOldNameHash(decal_hashes[i % 8]); + this->DecalReplacementTextureTable[i].SetNewNameHash(alpha_hash); + } + + int hood_decals = 1; + if (ride_info->GetPart(CARSLOTID_HOOD) == 0) { + hood_decals = 0; + } + unsigned int size_hash = bStringHash("SIZE"); + unsigned int shape_hash = bStringHash("SHAPE"); + unsigned int size_hashes[3] = { + bStringHash("SMALL"), + bStringHash("MEDIUM"), + bStringHash("LARGE"), + }; + unsigned int shape_hashes[3] = { + bStringHash("SQUARE"), + bStringHash("RECT"), + bStringHash("WIDE"), + }; + + for (int i = CARSLOTID_DECAL_FRONT_WINDOW; i < CARSLOTID_BASE_PAINT; i++) { + CarPart *decal_model_part = ride_info->GetPart(i); + int decal_index = i - CARSLOTID_DECAL_FRONT_WINDOW; + + if (decal_model_part != 0 && hood_decals != 0 && CarPart_HasAppliedAttribute(decal_model_part, size_hash) != 0 && + CarPart_HasAppliedAttribute(decal_model_part, shape_hash) != 0) { + unsigned int decal_size = CarPart_GetAppliedAttributeUParam(decal_model_part, size_hash, 0); + unsigned int decal_shape = CarPart_GetAppliedAttributeUParam(decal_model_part, shape_hash, 0); + eReplacementTextureTable *replace_table = &this->DecalReplacementTextureTable[decal_index * 8]; + int first_tex_part = CARSLOTID_DECAL_FRONT_WINDOW_TEX0 + decal_index * 8; + + (void)decal_size; + (void)size_hashes; + + for (int j = 0; j < 8; j++) { + CarPart *decal_texture_part = ride_info->GetPart(j + first_tex_part); + + if (decal_texture_part != 0) { + char buf[128]; + unsigned int base_hash = CarPart_GetAppliedAttributeUParam(decal_texture_part, bStringHash("NAME"), 0); + unsigned int decal_texture_hash; + + if (decal_shape == shape_hashes[0]) { + bStrCpy(buf, "_SQUARE"); + } else if (decal_shape == shape_hashes[1]) { + bStrCpy(buf, "_RECT"); + } else if (decal_shape == shape_hashes[2]) { + bStrCpy(buf, "_WIDE"); + } + + decal_texture_hash = bStringHash(buf, base_hash); + replace_table[j].SetNewNameHash(decal_texture_hash); + } + } + } + } +} + +bool CarRenderInfo::Render(eView *view, const bVector3 *world_position, const bMatrix4 *body_matrix, bMatrix4 *tire_matrices, + bMatrix4 *brake_matrices, bMatrix4 *, unsigned int extra_render_flags, int force_light_state, + int reflexion, float shadow_scale, enum CARPART_LOD tireLOD, enum CARPART_LOD carLOD) { + ProfileNode profile_node; + bVector3 position = *world_position; + int car_pixel_size; + + if (this->pCarTypeInfo != 0 && this->pCarTypeInfo->UsageType == CAR_USAGE_TYPE_COP) { + view->NumCopsTotal++; + } + car_pixel_size = view->GetPixelSize(&position, this->mRadius); + if (eGetCurrentViewMode() == EVIEWMODE_TWOH && iRam8047ff04 != 3) { + car_pixel_size = static_cast(static_cast(car_pixel_size) * 0.5f); + } + if (car_pixel_size < view->PixelMinSize) { + if (static_cast(view->GetID() - 13) > 1) { + return true; + } + } + if (DrawCars == 0) { + return true; + } + + Camera *camera = view->GetCamera(); + int is_traffic_car; + if (this->pCarTypeInfo != 0) { + is_traffic_car = this->pCarTypeInfo->UsageType == CAR_USAGE_TYPE_RACING; + } else { + is_traffic_car = 0; + } + + eDynamicLightContext *light_context; + if (CurrentBufferEnd <= CurrentBufferPos + 0x130) { + FrameMallocFailed = 1; + FrameMallocFailAmount += 0x130; + light_context = 0; + } else { + light_context = reinterpret_cast(CurrentBufferPos); + CurrentBufferPos += 0x130; + } + + bMatrix4 *local_world; + { + unsigned char *address = CurrentBufferPos; + if (address + 0x40 >= CurrentBufferEnd) { + FrameMallocFailed = 1; + FrameMallocFailAmount += 0x40; + local_world = 0; + } else { + CurrentBufferPos = address + 0x40; + local_world = reinterpret_cast(address); + } + } + + bMatrix4 *cpy_local_world; + { + unsigned char *address = CurrentBufferPos; + if (address + 0x40 >= CurrentBufferEnd) { + FrameMallocFailed = 1; + FrameMallocFailAmount += 0x40; + cpy_local_world = 0; + } else { + CurrentBufferPos = address + 0x40; + cpy_local_world = reinterpret_cast(address); + } + } + + bMatrix4 *biased_identity; + { + unsigned char *address = CurrentBufferPos; + if (address + 0x40 >= CurrentBufferEnd) { + FrameMallocFailed = 1; + FrameMallocFailAmount += 0x40; + biased_identity = 0; + } else { + CurrentBufferPos = address + 0x40; + biased_identity = reinterpret_cast(address); + } + } + + bMatrix4 *biased_local_world; + { + unsigned char *address = CurrentBufferPos; + if (address + 0x40 >= CurrentBufferEnd) { + FrameMallocFailed = 1; + FrameMallocFailAmount += 0x40; + biased_local_world = 0; + } else { + CurrentBufferPos = address + 0x40; + biased_local_world = reinterpret_cast(address); + } + } + + if (light_context == 0 || local_world == 0 || biased_identity == 0 || biased_local_world == 0) { + return true; + } + + PSMTX44Copy(*reinterpret_cast(body_matrix), *reinterpret_cast(local_world)); + eMulMatrix(local_world, &CarScaleMatrix, local_world); + local_world->v3.x = position.x; + local_world->v3.y = position.y; + local_world->v3.z = position.z; + local_world->v3.w = 1.0f; + + if (view->GetVisibleState(&this->AABBMin, &this->AABBMax, local_world) == EVISIBLESTATE_NOT) { + return true; + } + + bVector4 camera_world_position; + if (camera != 0) { + camera_world_position.x = camera->GetPosition()->x; + camera_world_position.y = camera->GetPosition()->y; + camera_world_position.z = camera->GetPosition()->z; + } else { + camera_world_position.x = position.x; + camera_world_position.y = position.y; + camera_world_position.z = position.z; + } + camera_world_position.w = 1.0f; + + Player *player1 = Player::GetPlayerByIndex(0); + int in_front_end = IsGameFlowInFrontEnd(); + int print_query_light_mat = PrintQueryLightMat; + if (print_query_light_mat != 0) { + PrintLightQuery = 1; + } + + PSMTX44Copy(*reinterpret_cast(body_matrix), *reinterpret_cast(cpy_local_world)); + cpy_local_world->v3.x = position.x; + cpy_local_world->v3.y = position.y; + cpy_local_world->v3.z = position.z; + cpy_local_world->v3.w = 1.0f; + + eDynamicLightContext base_light_context; + elResetLightContext(&base_light_context); + + FEManager *fe_manager = FEManager::Get(); + eShaperLightRig *shaper_lights; + switch (fe_manager->GetGarageType()) { + case GARAGETYPE_CAREER_SAFEHOUSE: + shaper_lights = &ShaperLightsSafehouse; + break; + case GARAGETYPE_CUSTOMIZATION_SHOP: + shaper_lights = &ShaperLightsCShop; + break; + case GARAGETYPE_CUSTOMIZATION_SHOP_BACKROOM: + shaper_lights = &ShaperLightsBackRoom; + break; + case GARAGETYPE_CAR_LOT: + shaper_lights = &ShaperLightsCarLot; + break; + default: + shaper_lights = &ShaperLightsQRace; + break; + } + if (iRam8047ff04 == 6) { + shaper_lights = &ShaperLightsCarsInGame; + } + + elSetupLights(&base_light_context, shaper_lights, &position, 0, &hack_man_matrix, view); + elCloneLightContext(light_context, cpy_local_world, &hack_man_matrix, &camera_world_position, view, &base_light_context); + this->CarFrame = eFrameCounter; + if (print_query_light_mat != 0) { + PrintLightQuery = 0; + } + + unsigned int disable_env_flag = 0; + unsigned int disable_env_flag_tires = 0; + this->UpdateLightStateTextures(); + if (EnableEnvMap == 0 || static_cast(car_pixel_size) < lbl_8040AD70) { + disable_env_flag = 1; + disable_env_flag_tires = 1; + } + + int bodyLodIx = 0; + int tireLodIx = 0; + int mMinLod = static_cast(this->mMinLodLevel); + int mMaxLod = static_cast(this->mMaxLodLevel); + float const *body_lod_swap_size; + + if (!is_traffic_car) { + if (mMinLod == 2) { + goto use_traffic_lod; + } + body_lod_swap_size = CarBodyLodSwapSize; + while (static_cast(car_pixel_size) < body_lod_swap_size[bodyLodIx]) { + bodyLodIx++; + tireLodIx++; + } + } else { + extra_render_flags |= 0x80000; + use_traffic_lod: + body_lod_swap_size = TrafficCarBodyLodSwapSize; + while (static_cast(car_pixel_size) < body_lod_swap_size[bodyLodIx]) { + bodyLodIx++; + tireLodIx++; + } + } + + int car_body_lod = mMaxLod; + if (bodyLodIx + mMinLod < mMaxLod) { + car_body_lod = bodyLodIx + mMinLod; + } + int car_tire_lod = mMaxLod; + if (tireLodIx + mMinLod < mMaxLod) { + car_tire_lod = tireLodIx + mMinLod; + } + + if (ForceCarLOD != -1) { + car_body_lod = mMinLod; + if (mMinLod < ForceCarLOD) { + car_body_lod = ForceCarLOD; + } + if (mMaxLod < car_body_lod) { + car_body_lod = mMaxLod; + } + } + if (ForceTireLOD != -1) { + car_tire_lod = mMinLod; + if (mMinLod < ForceTireLOD) { + car_tire_lod = ForceTireLOD; + } + if (mMaxLod < car_tire_lod) { + car_tire_lod = mMaxLod; + } + } + + if (car_body_lod == -1 && car_tire_lod == -1) { + return true; + } + + if (this->pRideInfo != 0 && this->pCarTypeInfo != 0 && this->pCarTypeInfo->UsageType != CAR_USAGE_TYPE_RACING) { + extra_render_flags |= 0x800; + if (INIS::Get() != 0) { + extra_render_flags |= 0x80000; + } + } + + bMatrix4 world_local; + bInvertMatrix(&world_local, local_world); + bVector3 camera_eye_in_car_space; + bMulMatrix(&camera_eye_in_car_space, &world_local, camera->GetPosition()); + float fDistanceToCamera = bDistBetween(camera->GetPosition(), reinterpret_cast(&local_world->v3)); + + if (car_body_lod == 0 && fDistanceToCamera < lbl_8040AD74 && view->GetID() == 1 && INIS::Get() == 0) { + camera_eye_in_car_space.y += lbl_8040AD78; + if (bBoundingBoxIsInside(&this->AABBMin, &this->AABBMax, &camera_eye_in_car_space, lbl_8040AD7C) != 0) { + return true; + } + camera_eye_in_car_space.y -= lbl_8040AD78; + } + + PSMTX44Copy(*reinterpret_cast(local_world), *reinterpret_cast(biased_identity)); + *biased_local_world = eMathIdentityMatrix; + biased_local_world->v3.x = position.x; + biased_local_world->v3.y = position.y; + biased_local_world->v3.z = position.z; + biased_local_world->v3.w = 1.0f; + + bool nisPlaying = false; + INIS *nis = INIS::Get(); + if (nis != 0) { + nisPlaying = nis->IsPlaying(); + } + + if (reflexion == 0) { + float bias_amount = lbl_8040AD80; + biased_identity->v3.y += bias_amount; + float dist_bias = static_cast((fDistanceToCamera - lbl_8040AD84) / fDistanceToCamera); + if (in_front_end) { + view->BiasMatrixForZSorting(biased_identity, dist_bias); + } + view->BiasMatrixForZSorting(biased_local_world, dist_bias); + + bMatrix4 neg_pos_matrix; + eIdentity(&neg_pos_matrix); + neg_pos_matrix.v3.x = -position.x; + neg_pos_matrix.v3.y = -position.y; + neg_pos_matrix.v3.z = -position.z; + eMulMatrix(biased_local_world, &neg_pos_matrix, biased_local_world); + } + + unsigned short tangR = this->mSteeringR; + unsigned short tangL = this->mSteeringL; + unsigned short steerAngle = tangR; + if (tangR > 0x8000) { + steerAngle = static_cast(-tangR); + } + unsigned short absL = tangL; + if (tangL > 0x8000) { + absL = static_cast(-tangL); + } + if (absL < steerAngle) { + tangL = tangR; + } + this->TheCarPartCuller.CullParts(&camera_eye_in_car_space, tangL); + + unsigned int body_render_flags; + if (DrawCarShadow != 0 && reflexion == 0) { + this->DrawAmbientShadow(view, &position, shadow_scale, local_world, &world_local, biased_local_world); + if (iRam8047ff04 != 3 && this->pCarTypeInfo->UsageType != CAR_USAGE_TYPE_COP && is_traffic_car) { + this->DrawKeithProjShadow(view, &position, local_world, &world_local, biased_local_world, car_body_lod); + } + } + + body_render_flags = 0; + if (iRam8047ff04 == 6 && view->GetID() != 3) { + body_render_flags = 0x8000; + } + + eLightMaterial *light_material_carskin = this->LightMaterial_CarSkin; + eLightMaterial *light_material_spoiler = this->LightMaterial_Spoiler; + if (light_material_spoiler == 0) { + light_material_spoiler = light_material_carskin; + } + eLightMaterial *light_material_roof = this->LightMaterial_Roof; + if (light_material_roof == 0) { + light_material_roof = light_material_carskin; + } + eLightMaterial *light_material_tint = this->LightMaterial_WindowTint; + + bMatrix4 trans_pivot[4]; + bMatrix4 trans_axle[4]; + bMatrix4 brake_trans_pivot[4]; + float wheel_pivot_trans_amount; + float front_wheel_brake_marker; + float rear_wheel_brake_marker; + unsigned short wheel_camber_angle_front; + float wheel_camber_push_down_front; + unsigned short wheel_camber_angle_rear; + float wheel_camber_push_down_rear; + unsigned short wheel_wobble_angle; + int tire_visible0; + int tire_visible1; + int tire_visible2; + int tire_visible3; + eDynamicLightContext *tire_light_context; + int tires_enabled; + bMatrix4 *tirelight_world_view; + + for (int slot_id = 0; slot_id < 0x4C; slot_id++) { + bMatrix4 *slot_local_world; + { + unsigned char *addr = CurrentBufferPos; + unsigned int sz = sizeof(bMatrix4); + if (CurrentBufferEnd <= addr + sz) { + FrameMallocFailed = 1; + FrameMallocFailAmount += sz; + slot_local_world = 0; + } else { + CurrentBufferPos = addr + sz; + slot_local_world = reinterpret_cast(addr); + } + } + if (slot_local_world == 0) { + continue; + } + + PSMTX44Copy(*reinterpret_cast(biased_identity), *reinterpret_cast(slot_local_world)); + + if (slot_id == CARSLOTID_FRONT_WHEEL || slot_id == CARSLOTID_REAR_WHEEL || slot_id == CARSLOTID_FRONT_BRAKE || + slot_id == CARSLOTID_REAR_BRAKE || slot_id == CARSLOTID_SPOILER || slot_id == CARSLOTID_ROOF) { + continue; + } + + CarPartModel *car_part_model = &this->mCarPartModels[slot_id][0][car_body_lod]; + eModel *model = car_part_model->GetModel(); + + if (model != 0) { + eLightMaterial *paint_material = light_material_carskin; + + if (slot_id == CARSLOTID_SPOILER || slot_id == CARSLOTID_UNIVERSAL_SPOILER_BASE) { + paint_material = light_material_spoiler; + } else if (slot_id == CARSLOTID_ROOF) { + paint_material = light_material_roof; + } else if (slot_id == CARSLOTID_HOOD && this->CarbonHood != 0 && this->LightMaterial_Carbon != 0) { + paint_material = this->LightMaterial_Carbon; + } + + if (paint_material != 0) { + model->ReplaceLightMaterial(0xD6D6080A, paint_material); + } + if (light_material_tint != 0) { + model->ReplaceLightMaterial(0x471A1DCA, light_material_tint); + } + } + + this->RenderPart(view, car_part_model, slot_local_world, light_context, extra_render_flags | disable_env_flag | body_render_flags); + } + + // Spoiler section + if (car_body_lod <= pRideInfo->GetMaxSpoilerLodLevel()) { + bMatrix4 *spoiler_local_world; + { + unsigned char *address = CurrentBufferPos; + if (address + 0x40 >= CurrentBufferEnd) { + FrameMallocFailed = 1; + FrameMallocFailAmount += 0x40; + spoiler_local_world = nullptr; + } else { + CurrentBufferPos = address + 0x40; + spoiler_local_world = reinterpret_cast(address); + } + } + + if (spoiler_local_world) { + CarPart *part_spoiler = pRideInfo->GetPart(CARSLOTID_SPOILER); + if (part_spoiler) { + unsigned int hash = bStringHash("USEMARKER2"); + int use_marker = CarPart_GetAppliedAttributeIParam(part_spoiler, hash, 0); + if (use_marker != 0 && this->SpoilerPositionMarker2 != nullptr) { + this->SpoilerPositionMarker = this->SpoilerPositionMarker2; + } + } + + if (this->SpoilerPositionMarker != nullptr && part_spoiler != nullptr && + (reinterpret_cast(part_spoiler)[5] >> 5) != 0) { + for (int i = 0; i < 1; i++) { + eModel *spoiler_model = this->mCarPartModels[CARSLOTID_SPOILER][i][car_body_lod].GetModel(); + if (spoiler_model) { + eMulMatrix(&spoiler_local_world[i], + reinterpret_cast(reinterpret_cast(this->SpoilerPositionMarker) + 0x10), + local_world); + spoiler_model->ReplaceLightMaterial(0xD6D6080A, light_material_spoiler); + ::Render(view, spoiler_model, &spoiler_local_world[i], light_context, extra_render_flags | disable_env_flag, 0); + } + } + } else { + for (int i = 0; i < 1; i++) { + eModel *spoiler_model = this->mCarPartModels[CARSLOTID_SPOILER][i][car_body_lod].GetModel(); + if (spoiler_model) { + spoiler_model->ReplaceLightMaterial(0xD6D6080A, light_material_spoiler); + ::Render(view, spoiler_model, local_world, light_context, extra_render_flags | disable_env_flag, 0); + } + } + } + } + } + + // Roof scoop section + if (car_body_lod <= pRideInfo->GetMaxRoofScoopLodLevel()) { + bMatrix4 *roof_local_world; + { + unsigned char *address = CurrentBufferPos; + if (address + 0x40 >= CurrentBufferEnd) { + FrameMallocFailed = 1; + FrameMallocFailAmount += 0x40; + roof_local_world = nullptr; + } else { + CurrentBufferPos = address + 0x40; + roof_local_world = reinterpret_cast(address); + } + } + + if (roof_local_world) { + if (this->RoofScoopPositionMarker != nullptr) { + eMulMatrix(roof_local_world, + reinterpret_cast(reinterpret_cast(this->RoofScoopPositionMarker) + 0x10), + local_world); + for (int i = 0; i < 1; i++) { + eModel *roof_scoop_model = this->mCarPartModels[CARSLOTID_ROOF][i][car_body_lod].GetModel(); + if (roof_scoop_model) { + roof_scoop_model->ReplaceLightMaterial(0xD6D6080A, light_material_roof); + ::Render(view, roof_scoop_model, roof_local_world, light_context, + (extra_render_flags | disable_env_flag) | body_render_flags, 0); + } + } + } else { + for (int i = 0; i < 1; i++) { + eModel *roof_scoop_model = this->mCarPartModels[CARSLOTID_ROOF][i][car_body_lod].GetModel(); + if (roof_scoop_model) { + roof_scoop_model->ReplaceLightMaterial(0xD6D6080A, light_material_roof); + ::Render(view, roof_scoop_model, local_world, light_context, + (extra_render_flags | disable_env_flag) | body_render_flags, 0); + } + } + } + } + } + + // Camber computation + { + float camber_amount_front = this->GetAttributes().CamberFront(); + float camber_amount_rear = this->GetAttributes().CamberRear(); + wheel_camber_angle_front = bDegToAng(camber_amount_front); + wheel_camber_angle_rear = bDegToAng(camber_amount_rear); + wheel_camber_push_down_rear = camber_amount_rear * 0.01f; + wheel_camber_push_down_front = camber_amount_front * 0.01f; + } + + wheel_wobble_angle = bDegToAng(0.0f); + + // Wheel scale loop - initialize transformation matrices + { + for (int wheel = 0; wheel < 4; wheel++) { + int wheel_end = wheel < 2 ? 0 : 1; + float wheel_width_scale = this->WheelWidthScales[wheel]; + float wheel_width = this->WheelWidths[wheel_end]; + float wheel_radius_scale = this->WheelRadiusScales[wheel]; + float yrender_offset = this->WheelYRenderOffset[wheel]; + float pivot_y = this->PivotPosition.y; + float wheel_brake_marker_y = this->WheelBrakeMarkerY[wheel_end]; + + eIdentity(&trans_pivot[wheel]); + eIdentity(&trans_axle[wheel]); + eIdentity(&brake_trans_pivot[wheel]); + + trans_pivot[wheel].v0.x = wheel_width_scale; + trans_pivot[wheel].v1.y = wheel_radius_scale; + trans_pivot[wheel].v2.z = wheel_radius_scale; + trans_pivot[wheel].v3.y = yrender_offset - pivot_y; + + trans_axle[wheel].v0.x = wheel_width_scale; + trans_axle[wheel].v1.y = wheel_radius_scale; + trans_axle[wheel].v2.z = wheel_radius_scale; + trans_axle[wheel].v3.y = yrender_offset - pivot_y; + trans_axle[wheel].v3.x = wheel_width * (wheel_width_scale - 1.0f) * 0.5f; + + brake_trans_pivot[wheel].v3.y = yrender_offset - pivot_y; + { + float marker_y = wheel_brake_marker_y; + brake_trans_pivot[wheel].v3.y += marker_y; + } + } + } + + // Tire section + if (tire_matrices != 0) { + CARPART_LOD max_tire_lod = this->pRideInfo->GetMaxTireLodLevel(); + + bMatrix4 *tire_local_world; + float extra_rear_tire_offset; + bMatrix4 *extra_tire_local_world; + + // Allocate tire_local_world from frame buffer + { + unsigned char *address = CurrentBufferPos; + unsigned int size = sizeof(bMatrix4); + if (address + size >= CurrentBufferEnd) { + FrameMallocFailed = 1; + FrameMallocFailAmount += size; + tire_local_world = 0; + } else { + CurrentBufferPos = address + size; + tire_local_world = reinterpret_cast(address); + } + } + + extra_rear_tire_offset = this->GetAttributes().ExtraRearTireOffset(0); + + // Allocate extra_tire_local_world from frame buffer + { + unsigned char *address = CurrentBufferPos; + unsigned int size = sizeof(bMatrix4); + if (address + size >= CurrentBufferEnd) { + FrameMallocFailed = 1; + FrameMallocFailAmount += size; + extra_tire_local_world = 0; + } else { + CurrentBufferPos = address + size; + extra_tire_local_world = reinterpret_cast(address); + } + } + + // Allocate tire_light_context from frame buffer + { + unsigned char *address = CurrentBufferPos; + unsigned int size = sizeof(eDynamicLightContext); + if (address + size >= CurrentBufferEnd) { + FrameMallocFailed = 1; + FrameMallocFailAmount += size; + tire_light_context = 0; + } else { + CurrentBufferPos = address + size; + tire_light_context = reinterpret_cast(address); + } + } + + { + int tire_lod = car_tire_lod; + CarPartModel *front_tire_models[1]; + CarPartModel *rear_tire_models[1]; + eLightMaterial *light_material_rim[2]; + bMatrix4 *left_tire_flip; + unsigned int extra_mirror_flag; + bMatrix4 wobbleMat; + + tire_visible0 = this->TheCarPartCuller.IsPartVisible(CULLABLE_CAR_PART_TIRE_FL); + tire_visible1 = this->TheCarPartCuller.IsPartVisible(CULLABLE_CAR_PART_TIRE_FR); + tire_visible2 = this->TheCarPartCuller.IsPartVisible(CULLABLE_CAR_PART_TIRE_RR); + tire_visible3 = this->TheCarPartCuller.IsPartVisible(CULLABLE_CAR_PART_TIRE_RL); + + if (max_tire_lod > car_tire_lod) { + tire_lod = max_tire_lod; + } + + // Check tire models across LOD levels + { + for (int i = tire_lod; i >= 0; i--) { + front_tire_models[0] = &this->mCarPartModels[CARSLOTID_FRONT_WHEEL][0][i]; + if (front_tire_models[0]->GetModel() != 0) { + break; + } + if (i == 0) { + front_tire_models[0]->GetModel(); + } + } + for (int i = tire_lod; i >= 0; i--) { + rear_tire_models[0] = &this->mCarPartModels[CARSLOTID_REAR_WHEEL][0][i]; + if (rear_tire_models[0]->GetModel() != 0) { + break; + } + if (i == 0) { + rear_tire_models[0]->GetModel(); + } + } + } + + light_material_rim[0] = this->LightMaterial_WheelRim; + light_material_rim[1] = this->LightMaterial_Spinner; + + if (this->mMirrorLeftWheels) { + left_tire_flip = &LeftTireMirrorMatrix; + } else { + left_tire_flip = &LeftTireRotateZMatrix; + } + + extra_mirror_flag = extra_render_flags | disable_env_flag_tires; + + wobbleMat = bMatrix4(); + bIdentity(&wobbleMat); + + // Tire 0 (front-left) + { + bMatrix4 *starting_tire_matrix = &tire_matrices[0]; + bMatrix4 tire_matrix_for_camber; + bMatrix4 tire0; + + { + bVector3 wheel_offset; + bCopy(&tire_matrix_for_camber, starting_tire_matrix); + wheel_offset = bVector3(); + bCopy(&wheel_offset, reinterpret_cast(&tire_matrix_for_camber.v3)); + bFill(&tire_matrix_for_camber.v3, 0.0f, 0.0f, 0.0f, 1.0f); + bCopy(reinterpret_cast(&tire0.v3), &wheel_offset); + } + + eRotateX(&tire_matrix_for_camber, &tire_matrix_for_camber, wheel_camber_angle_front); + eMulMatrix(&tire0, &tire_matrix_for_camber, &trans_pivot[0]); + tire0.v3.y += wheel_camber_push_down_front; + eMulMatrix(&tire0, left_tire_flip, &tire0); + eMulMatrix(&tire0, &wobbleMat, &tire0); + PSMTX44Copy(*reinterpret_cast(&tire0), *reinterpret_cast(tire_local_world)); + eMulMatrix(tire_local_world, tire_local_world, local_world); + + if (tire_visible0) { + eModel *tire_model = front_tire_models[0]->GetModel(); + if (tire_model != 0) { + eReplacementTextureTable *rep_table = this->BrakeLeftReplacementTextureTable; + tire_model->AttachReplacementTextureTable(rep_table, 2, 0); + if (light_material_rim[0] != 0) { + tire_model->ReplaceLightMaterial(0xD6640DFF, light_material_rim[0]); + } + if (light_material_rim[1] != 0) { + tire_model->ReplaceLightMaterial(0xA3186E2B, light_material_rim[1]); + } + float face = TireFace(tire_local_world, view); + ::Render(view, tire_model, tire_local_world, light_context, extra_mirror_flag, 0); + } + } + } + + // Tire 1 (front-right) + { + bMatrix4 *starting_tire_matrix = &tire_matrices[1]; + bMatrix4 tire_matrix_for_camber; + bMatrix4 tire1; + + { + bVector3 wheel_offset; + bCopy(&tire_matrix_for_camber, starting_tire_matrix); + wheel_offset = bVector3(); + bCopy(&wheel_offset, reinterpret_cast(&tire_matrix_for_camber.v3)); + bFill(&tire_matrix_for_camber.v3, 0.0f, 0.0f, 0.0f, 1.0f); + bCopy(reinterpret_cast(&tire1.v3), &wheel_offset); + } + + eRotateX(&tire_matrix_for_camber, &tire_matrix_for_camber, wheel_camber_angle_front); + eMulMatrix(&tire1, &tire_matrix_for_camber, &trans_pivot[1]); + tire1.v3.y += wheel_camber_push_down_front; + tire1 = tire1; + eMulMatrix(&tire1, &wobbleMat, &tire1); + PSMTX44Copy(*reinterpret_cast(&tire1), *reinterpret_cast(tire_local_world)); + eMulMatrix(tire_local_world, tire_local_world, local_world); + + if (tire_visible1) { + eModel *tire_model = front_tire_models[0]->GetModel(); + if (tire_model != 0) { + eReplacementTextureTable *rep_table = this->BrakeRightReplacementTextureTable; + tire_model->AttachReplacementTextureTable(rep_table, 2, 0); + if (light_material_rim[0] != 0) { + tire_model->ReplaceLightMaterial(0xD6640DFF, light_material_rim[0]); + } + if (light_material_rim[1] != 0) { + tire_model->ReplaceLightMaterial(0xA3186E2B, light_material_rim[1]); + } + float face = TireFace(tire_local_world, view); + ::Render(view, tire_model, tire_local_world, light_context, extra_mirror_flag, 0); + } + } + } + + // Tire 2 (rear-right) + { + bMatrix4 *starting_tire_matrix = &tire_matrices[2]; + bMatrix4 tire_matrix_for_camber; + bMatrix4 tire2; + + { + bVector3 wheel_offset; + bCopy(&tire_matrix_for_camber, starting_tire_matrix); + wheel_offset = bVector3(); + bCopy(&wheel_offset, reinterpret_cast(&tire_matrix_for_camber.v3)); + bFill(&tire_matrix_for_camber.v3, 0.0f, 0.0f, 0.0f, 1.0f); + bCopy(reinterpret_cast(&tire2.v3), &wheel_offset); + } + + eRotateX(&tire_matrix_for_camber, &tire_matrix_for_camber, wheel_camber_angle_rear); + eMulMatrix(&tire2, &tire_matrix_for_camber, &trans_pivot[2]); + tire2.v3.y += wheel_camber_push_down_rear; + tire2 = tire2; + eMulMatrix(&tire2, &wobbleMat, &tire2); + PSMTX44Copy(*reinterpret_cast(&tire2), *reinterpret_cast(tire_local_world)); + eMulMatrix(tire_local_world, tire_local_world, local_world); + + if (tire_visible2) { + if (IsGameFlowInGame()) { + eModel *tire_model = rear_tire_models[0]->GetModel(); + if (tire_model != 0) { + eReplacementTextureTable *rep_table = this->BrakeRightReplacementTextureTable; + tire_model->AttachReplacementTextureTable(rep_table, 2, 0); + if (light_material_rim[0] != 0) { + tire_model->ReplaceLightMaterial(0xD6640DFF, light_material_rim[0]); + } + if (light_material_rim[1] != 0) { + tire_model->ReplaceLightMaterial(0xA3186E2B, light_material_rim[1]); + } + float face = TireFace(tire_local_world, view); + ::Render(view, tire_model, tire_local_world, light_context, extra_mirror_flag, 0); + } + } else { + CarPartModel *tmpM = rear_tire_models[0]; + this->RenderPart(view, tmpM, tire_local_world, light_context, extra_mirror_flag); + } + } + } + + // Extra rear tire offset intermediate + { + for (int i = 3; i >= 0; i--) { + } + } + + // Tire 3 (rear-left) + { + bMatrix4 *starting_tire_matrix = &tire_matrices[3]; + bMatrix4 tire_matrix_for_camber; + bMatrix4 tire3; + + { + bVector3 wheel_offset; + bCopy(&tire_matrix_for_camber, starting_tire_matrix); + wheel_offset = bVector3(); + bCopy(&wheel_offset, reinterpret_cast(&tire_matrix_for_camber.v3)); + bFill(&tire_matrix_for_camber.v3, 0.0f, 0.0f, 0.0f, 1.0f); + bCopy(reinterpret_cast(&tire3.v3), &wheel_offset); + } + + eRotateX(&tire_matrix_for_camber, &tire_matrix_for_camber, wheel_camber_angle_rear); + eMulMatrix(&tire3, &tire_matrix_for_camber, &trans_pivot[3]); + tire3.v3.y += wheel_camber_push_down_rear; + eMulMatrix(&tire3, left_tire_flip, &tire3); + eMulMatrix(&tire3, &wobbleMat, &tire3); + PSMTX44Copy(*reinterpret_cast(&tire3), *reinterpret_cast(tire_local_world)); + eMulMatrix(tire_local_world, tire_local_world, local_world); + + if (tire_visible3) { + if (IsGameFlowInGame()) { + eModel *tire_model = rear_tire_models[0]->GetModel(); + if (tire_model != 0) { + eReplacementTextureTable *rep_table = this->BrakeLeftReplacementTextureTable; + tire_model->AttachReplacementTextureTable(rep_table, 2, 0); + if (light_material_rim[0] != 0) { + tire_model->ReplaceLightMaterial(0xD6640DFF, light_material_rim[0]); + } + if (light_material_rim[1] != 0) { + tire_model->ReplaceLightMaterial(0xA3186E2B, light_material_rim[1]); + } + float face = TireFace(tire_local_world, view); + ::Render(view, tire_model, tire_local_world, light_context, extra_mirror_flag, 0); + } + } else { + CarPartModel *tmpM = rear_tire_models[0]; + this->RenderPart(view, tmpM, tire_local_world, light_context, extra_mirror_flag); + } + } + } + } + } + + // Brake section + if (brake_matrices != 0) { + CARPART_LOD max_brake_lod = this->pRideInfo->GetMaxBrakeLodLevel(); + + eModel *front_brake_check = this->mCarPartModels[CARSLOTID_FRONT_BRAKE][0][car_body_lod].GetModel(); + + int brakes_visible_front_left = this->TheCarPartCuller.IsPartVisible(CULLABLE_CAR_PART_BRAKE_FL); + int brakes_visible_front_right = this->TheCarPartCuller.IsPartVisible(CULLABLE_CAR_PART_BRAKE_FR); + int brakes_visible_rear_right = this->TheCarPartCuller.IsPartVisible(CULLABLE_CAR_PART_BRAKE_RR); + int brakes_visible_rear_left = this->TheCarPartCuller.IsPartVisible(CULLABLE_CAR_PART_BRAKE_RL); + + if (car_body_lod <= max_brake_lod && front_brake_check != 0) { + bMatrix4 *brake_local_world; + eDynamicLightContext *brake_light_context; + + { + unsigned char *address = CurrentBufferPos; + unsigned int size = sizeof(bMatrix4) * 4; + if (address + size >= CurrentBufferEnd) { + FrameMallocFailed = 1; + FrameMallocFailAmount += size; + brake_local_world = 0; + } else { + CurrentBufferPos = address + size; + brake_local_world = reinterpret_cast(address); + } + } + + { + unsigned char *address = CurrentBufferPos; + unsigned int size = sizeof(eDynamicLightContext) * 4; + if (address + size >= CurrentBufferEnd) { + FrameMallocFailed = 1; + FrameMallocFailAmount += size; + brake_light_context = 0; + } else { + CurrentBufferPos = address + size; + brake_light_context = reinterpret_cast(address); + } + } + + if (brake_local_world != 0 && brake_light_context != 0) { + int brakes_lod = car_body_lod; + eModel *front_brake_models[1]; + eModel *rear_brake_models[1]; + eLightMaterial *light_material_caliper = this->LightMaterial_Caliper; + bMatrix4 mirror; + + { + for (int i = brakes_lod; i >= 0; i--) { + front_brake_models[0] = this->mCarPartModels[CARSLOTID_FRONT_BRAKE][0][i].GetModel(); + rear_brake_models[0] = this->mCarPartModels[CARSLOTID_REAR_BRAKE][0][i].GetModel(); + if (front_brake_models[0] != 0 || rear_brake_models[0] != 0) { + break; + } + } + } + + if (light_material_caliper != 0) { + if (front_brake_models[0] != 0) { + front_brake_models[0]->ReplaceLightMaterial(0xD6640DFF, light_material_caliper); + front_brake_models[0]->ReplaceLightMaterial(0xA3186E2B, light_material_caliper); + } + if (rear_brake_models[0] != 0) { + rear_brake_models[0]->ReplaceLightMaterial(0xD6640DFF, light_material_caliper); + rear_brake_models[0]->ReplaceLightMaterial(0xA3186E2B, light_material_caliper); + } + } + + mirror = bMatrix4(); + eIdentity(&mirror); + mirror.v0.x = -1.0f; + + if (brakes_visible_front_left) { + bMatrix4 *starting_brake_matrix = &brake_matrices[0]; + bMatrix4 brake_matrix_for_camber; + bMatrix4 bm0; + + { + bVector3 wheel_offset; + bCopy(&brake_matrix_for_camber, starting_brake_matrix); + wheel_offset = bVector3(); + bCopy(&wheel_offset, reinterpret_cast(&brake_matrix_for_camber.v3)); + bFill(&brake_matrix_for_camber.v3, 0.0f, 0.0f, 0.0f, 1.0f); + bCopy(reinterpret_cast(&bm0.v3), &wheel_offset); + } + + eRotateX(&brake_matrix_for_camber, &brake_matrix_for_camber, wheel_camber_angle_front); + eMulMatrix(&bm0, &brake_trans_pivot[0], &mirror); + eMulMatrix(&bm0, &bm0, &brake_matrix_for_camber); + eMulMatrix(&bm0, &bm0, &trans_axle[0]); + eMulMatrix(&brake_local_world[0], &bm0, local_world); + + if (IsGameFlowInGame()) { + for (int i = 0; i < 1; i++) { + if (front_brake_models[i] != 0) { + front_brake_models[i]->AttachReplacementTextureTable(this->BrakeLeftReplacementTextureTable, 2, 0); + ::Render(view, front_brake_models[i], &brake_local_world[0], light_context, + extra_render_flags | disable_env_flag_tires | 0x40000, 0); + } + } + } else { + for (int i = 0; i < 1; i++) { + if (front_brake_models[i] != 0) { + front_brake_models[i]->AttachReplacementTextureTable(this->BrakeLeftReplacementTextureTable, 2, 0); + elCloneLightContext(&brake_light_context[0], &brake_local_world[0], &hack_man_matrix, &camera_world_position, view, + &base_light_context); + ::Render(view, front_brake_models[i], &brake_local_world[0], &brake_light_context[0], + extra_render_flags | disable_env_flag_tires | 0x40000, 0); + } + } + } + } + + if (brakes_visible_front_right) { + bMatrix4 *starting_brake_matrix = &brake_matrices[1]; + bMatrix4 brake_matrix_for_camber; + bMatrix4 bm1; + + { + bVector3 wheel_offset; + bCopy(&brake_matrix_for_camber, starting_brake_matrix); + wheel_offset = bVector3(); + bCopy(&wheel_offset, reinterpret_cast(&brake_matrix_for_camber.v3)); + bFill(&brake_matrix_for_camber.v3, 0.0f, 0.0f, 0.0f, 1.0f); + bCopy(reinterpret_cast(&bm1.v3), &wheel_offset); + } + + eRotateX(&brake_matrix_for_camber, &brake_matrix_for_camber, -wheel_camber_angle_front); + eMulMatrix(&bm1, &brake_trans_pivot[1], &brake_matrix_for_camber); + eMulMatrix(&bm1, &bm1, &trans_axle[1]); + eMulMatrix(&brake_local_world[1], &bm1, local_world); + + if (IsGameFlowInGame()) { + for (int i = 0; i < 1; i++) { + if (front_brake_models[i] != 0) { + front_brake_models[i]->AttachReplacementTextureTable(this->BrakeRightReplacementTextureTable, 2, 0); + ::Render(view, front_brake_models[i], &brake_local_world[1], light_context, extra_render_flags | disable_env_flag_tires, + 0); + } + } + } else { + for (int i = 0; i < 1; i++) { + if (front_brake_models[i] != 0) { + front_brake_models[i]->AttachReplacementTextureTable(this->BrakeRightReplacementTextureTable, 2, 0); + elCloneLightContext(&brake_light_context[1], &brake_local_world[1], &hack_man_matrix, &camera_world_position, view, + &base_light_context); + ::Render(view, front_brake_models[i], &brake_local_world[1], &brake_light_context[1], + extra_render_flags | disable_env_flag_tires, 0); + } + } + } + } + + if (brakes_visible_rear_right) { + bMatrix4 *starting_brake_matrix = &brake_matrices[2]; + bMatrix4 brake_matrix_for_camber; + bMatrix4 bm2; + + { + bVector3 wheel_offset; + bCopy(&brake_matrix_for_camber, starting_brake_matrix); + wheel_offset = bVector3(); + bCopy(&wheel_offset, reinterpret_cast(&brake_matrix_for_camber.v3)); + bFill(&brake_matrix_for_camber.v3, 0.0f, 0.0f, 0.0f, 1.0f); + bCopy(reinterpret_cast(&bm2.v3), &wheel_offset); + } + + eRotateX(&brake_matrix_for_camber, &brake_matrix_for_camber, -wheel_camber_angle_rear); + eMulMatrix(&bm2, &brake_trans_pivot[2], &brake_matrix_for_camber); + eMulMatrix(&bm2, &bm2, &trans_axle[2]); + eMulMatrix(&brake_local_world[2], &bm2, local_world); + + if (IsGameFlowInGame()) { + for (int i = 0; i < 1; i++) { + if (rear_brake_models[i] != 0) { + rear_brake_models[i]->AttachReplacementTextureTable(this->BrakeRightReplacementTextureTable, 2, 0); + ::Render(view, rear_brake_models[i], &brake_local_world[2], light_context, extra_render_flags | disable_env_flag_tires, + 0); + } + } + } else { + for (int i = 0; i < 1; i++) { + if (rear_brake_models[i] != 0) { + rear_brake_models[i]->AttachReplacementTextureTable(this->BrakeRightReplacementTextureTable, 2, 0); + elCloneLightContext(&brake_light_context[2], &brake_local_world[2], &hack_man_matrix, &camera_world_position, view, + &base_light_context); + ::Render(view, rear_brake_models[i], &brake_local_world[2], &brake_light_context[2], + extra_render_flags | disable_env_flag_tires, 0); + } + } + } + } + + if (brakes_visible_rear_left) { + bMatrix4 *starting_brake_matrix = &brake_matrices[3]; + bMatrix4 brake_matrix_for_camber; + bMatrix4 bm3; + + { + bVector3 wheel_offset; + bCopy(&brake_matrix_for_camber, starting_brake_matrix); + wheel_offset = bVector3(); + bCopy(&wheel_offset, reinterpret_cast(&brake_matrix_for_camber.v3)); + bFill(&brake_matrix_for_camber.v3, 0.0f, 0.0f, 0.0f, 1.0f); + bCopy(reinterpret_cast(&bm3.v3), &wheel_offset); + } + + eRotateX(&brake_matrix_for_camber, &brake_matrix_for_camber, wheel_camber_angle_rear); + eMulMatrix(&bm3, &brake_trans_pivot[3], &mirror); + eMulMatrix(&bm3, &bm3, &brake_matrix_for_camber); + eMulMatrix(&bm3, &bm3, &trans_axle[3]); + eMulMatrix(&brake_local_world[3], &bm3, local_world); + + if (IsGameFlowInGame()) { + for (int i = 0; i < 1; i++) { + if (rear_brake_models[i] != 0) { + rear_brake_models[i]->AttachReplacementTextureTable(this->BrakeLeftReplacementTextureTable, 2, 0); + ::Render(view, rear_brake_models[i], &brake_local_world[3], light_context, + extra_render_flags | disable_env_flag_tires | 0x40000, 0); + } + } + } else { + for (int i = 0; i < 1; i++) { + if (rear_brake_models[i] != 0) { + rear_brake_models[i]->AttachReplacementTextureTable(this->BrakeLeftReplacementTextureTable, 2, 0); + elCloneLightContext(&brake_light_context[3], &brake_local_world[3], &hack_man_matrix, &camera_world_position, view, + &base_light_context); + ::Render(view, rear_brake_models[i], &brake_local_world[3], &brake_light_context[3], + extra_render_flags | disable_env_flag_tires | 0x40000, 0); + } + } + } + } + } + } + } + + if (tire_matrices != 0 && !this->mEmitterPositionsInitialized) { + bVector4 tire_positions[4]; + + for (int wheel = 0; wheel < 4; wheel++) { + tire_positions[wheel] = tire_matrices[wheel].v3; + } + + this->InitEmitterPositions(tire_positions); + } + + this->RenderFlaresOnCar(view, &position, body_matrix, force_light_state, reflexion, static_cast(extra_render_flags)); + + return true; +} + +void CarRenderInfo::RenderPart(eView *view, CarPartModel *carPart, bMatrix4 *local_to_world, eDynamicLightContext *light_context, + unsigned int flags) { + if (carPart != nullptr) { + if (carPart->IsLodMissing()) { + view->Render(&StandardDebugModel, local_to_world, light_context, flags, nullptr); + } else { + view->Render(carPart->GetModel(), local_to_world, light_context, flags, nullptr); + } + } +} + +int CarRenderInfo::GetEmitterPositions(bSList &markers_out, const unsigned int *position_name_hashes, + int num_pos_name_hashes) { + int count; + + if (this->pCarTypeInfo == nullptr) { + return 0; + } + + count = 0; + + for (int slot_model_index = 0; slot_model_index < 0x4C; slot_model_index++) { + eModel *model = this->mCarPartModels[slot_model_index][0][this->mMinLodLevel].GetModel(); + ePositionMarker *position_marker = nullptr; + + if (model == nullptr) { + continue; + } + + while ((position_marker = model->GetPostionMarker(position_marker)) != nullptr) { + unsigned int position_marker_namehash; + + for (int i = 0; i < num_pos_name_hashes; i++) { + if (position_marker->NameHash == position_name_hashes[i]) { + CarEmitterPosition *empos; + + empos = new CarEmitterPosition(position_marker); + markers_out.AddTail(empos); + count++; + } + } + } + } + + return count; +} + +void CarRenderInfo::InitEmitterPositions(bVector4 *tire_positions) { + if (this->pCarTypeInfo != nullptr && !this->mEmitterPositionsInitialized) { + int fxpos; + + for (fxpos = 0; fxpos < NUM_CARFXPOS; fxpos++) { + int pos_count = 0; + bool is_based_off_position_marker; + + is_based_off_position_marker = GetNumCarEffectMarkerHashes(static_cast(fxpos), pos_count); + + if (is_based_off_position_marker) { + if (pos_count > 0) { + this->GetEmitterPositions(this->EmitterPositionList[fxpos], + GetCarEffectMarkerHashes(static_cast(fxpos)), + pos_count); + } + continue; + } + + { + CarEffectPosition efxpos = static_cast(fxpos); + bSList &pos_list = this->EmitterPositionList[fxpos]; + CarEmitterPosition *empos = nullptr; + switch (efxpos) { + case CARFXPOS_NONE: + empos = new CarEmitterPosition(0.0f, 0.0f, 0.0f); + pos_list.AddTail(empos); + break; + case CARFXPOS_FRONT_TIRES: + { + bVector4 *fl = tire_positions; + bVector4 *fr = tire_positions + 1; + bVector4 avg = *fl + *fr; + + avg *= 0.5f; + empos = new CarEmitterPosition(avg.x, avg.y, avg.z); + pos_list.AddTail(empos); + } + break; + case CARFXPOS_REAR_TIRES: + { + bVector4 *rr = tire_positions + 2; + bVector4 *rl = tire_positions + 3; + bVector4 avg = *rr + *rl; + + avg *= 0.5f; + empos = new CarEmitterPosition(avg.x, avg.y, avg.z); + pos_list.AddTail(empos); + } + break; + case CARFXPOS_LEFT_TIRES: + { + bVector4 *fl = tire_positions; + bVector4 *rl = tire_positions + 3; + bVector4 avg = *fl + *rl; + + avg *= 0.5f; + empos = new CarEmitterPosition(avg.x, avg.y, avg.z); + pos_list.AddTail(empos); + } + break; + case CARFXPOS_RIGHT_TIRES: + { + bVector4 *fr = tire_positions + 1; + bVector4 *rr = tire_positions + 2; + bVector4 avg = *fr + *rr; + + avg *= 0.5f; + empos = new CarEmitterPosition(avg.x, avg.y, avg.z); + pos_list.AddTail(empos); + } + break; + case CARFXPOS_TIRE_FL: + empos = new CarEmitterPosition(tire_positions->x, tire_positions->y, tire_positions->z); + pos_list.AddTail(empos); + break; + case CARFXPOS_TIRE_FR: + empos = new CarEmitterPosition(tire_positions[1].x, tire_positions[1].y, tire_positions[1].z); + pos_list.AddTail(empos); + break; + case CARFXPOS_TIRE_RR: + empos = new CarEmitterPosition(tire_positions[2].x, tire_positions[2].y, tire_positions[2].z); + pos_list.AddTail(empos); + break; + case CARFXPOS_TIRE_RL: + empos = new CarEmitterPosition(tire_positions[3].x, tire_positions[3].y, tire_positions[3].z); + pos_list.AddTail(empos); + break; + case CARFXPOS_ENGINE: + { + bVector4 *fl = tire_positions; + bVector4 *fr = tire_positions + 1; + bVector4 avg = *fl + *fr; + + avg *= 0.5f; + bVector4 diff; + bSub(&diff, fl, fr); + empos = new CarEmitterPosition(avg.x, avg.y, avg.z + diff.y * 0.2f); + pos_list.AddTail(empos); + } + break; + } + } + } + + this->mEmitterPositionsInitialized = true; + } +} + +int cmpl(const void *a, const void *b) { + const float *pa = *reinterpret_cast(a); + const float *pb = *reinterpret_cast(b); + float delta = pa[0] - pb[0]; + + if (0.0f < delta) { + return 1; + } + if (delta < 0.0f) { + return -1; + } + + delta = pb[1] - pa[1]; + if (0.0f < delta) { + return 1; + } + if (delta < 0.0f) { + return -1; + } + + return 0; +} + +int cmph(const void *a, const void *b) { + return cmpl(b, a); +} + +void bRotateVector(bVector3 *dest, const bMatrix4 *m, bVector3 *v) { + dest->x = m->v0.x * v->x + m->v1.x * v->y + m->v2.x * v->z; + dest->y = m->v0.y * v->x + m->v1.y * v->y + m->v2.y * v->z; + dest->z = m->v0.z * v->x + m->v1.z * v->y + m->v2.z * v->z; +} + +float coplightflicker(float time, int offset) { + int counter = counter_31665 + 1; + counter_31665 = counter % copModulo; + + return bCos((time + copt * static_cast(offset)) * copm); +} + +float coplightflicker2(float time, int whichColor, int flarecount) { + int counter; + float offset; + float a; + float t; + + counter = counter_31669 + 1; + counter_31669 = counter % copModulo; + + switch (whichColor) { + case 0: + offset = copoffsetr; + break; + case 1: + offset = copoffsetb; + break; + case 2: + offset = copoffsetw; + break; + } + + t = bCos(time * 24.0f); + t *= t; + + if (whichColor == 2) { + a = t * coplightflicker(time, flarecount); + return a; + } + + float c = bCos(time * 8.0f + offset); + if (c > 0.2f) { + a = t; + return a; + } + + a = 0.0f; + return a; +} + +float TireFace(bMatrix4 *matrix, eView *view) { + float face; + + if (TireFaceIt != 0) { + bVector3 cDir(*view->GetCamera()->GetDirection()); + bMatrix4 matrix2(*matrix); + bVector3 N(enX, enY, enZ); + + eMulVector(&N, &matrix2, &N); + face = bDot(&N, &cDir); + } else { + face = 1.0f; + } + + return face; +} + +void CarRenderInfo::UpdateCarReplacementTextures() { + bMemCpy(this->CarbonReplacementTextureTable, this->MasterReplacementTextureTable, sizeof(this->CarbonReplacementTextureTable)); + + this->CarbonReplacementTextureTable[REPLACETEX_CARSKIN].SetNewNameHash(bStringHash("CARBONFIBRE")); + this->CarbonReplacementTextureTable[REPLACETEX_CARSKINB].SetNewNameHash( + *reinterpret_cast(reinterpret_cast(this) + 0x15B0)); + this->CarbonReplacementTextureTable[REPLACETEX_GLOBALSKIN].SetNewNameHash( + *reinterpret_cast(reinterpret_cast(this) + 0x15B0)); + this->CarbonReplacementTextureTable[REPLACETEX_CARBONSKIN].SetNewNameHash(bStringHash("CARBONFIBRE")); + this->CarbonReplacementTextureTable[REPLACETEX_GLOBALCARBONSKIN].SetNewNameHash(bStringHash("CARBONFIBRE")); +} + +void CarRenderInfo::SwitchSkin(RideInfo *ride_info) { + this->pRideInfo = ride_info; + GetUsedCarTextureInfo(&this->mUsedTextureInfos, ride_info, 0); + + this->MasterReplacementTextureTable[REPLACETEX_CARSKIN].SetNewNameHash(this->mUsedTextureInfos.ReplaceSkinHash); + this->MasterReplacementTextureTable[REPLACETEX_CARSKINB].SetNewNameHash(this->mUsedTextureInfos.ReplaceSkinBHash); + this->MasterReplacementTextureTable[REPLACETEX_WHEEL].SetNewNameHash(this->mUsedTextureInfos.ReplaceWheelHash); + this->MasterReplacementTextureTable[REPLACETEX_SPINNER].SetNewNameHash(this->mUsedTextureInfos.ReplaceSpinnerHash); + this->MasterReplacementTextureTable[REPLACETEX_SPOILER].SetNewNameHash(this->mUsedTextureInfos.ReplaceSpoilerHash); + this->MasterReplacementTextureTable[REPLACETEX_ROOF_SCOOP].SetNewNameHash(this->mUsedTextureInfos.ReplaceRoofScoopHash); + this->MasterReplacementTextureTable[REPLACETEX_GLOBALSKIN].SetNewNameHash(this->mUsedTextureInfos.ReplaceGlobalHash); + this->MasterReplacementTextureTable[REPLACETEX_CARBONSKIN].SetNewNameHash(this->mUsedTextureInfos.ReplaceGlobalHash); + this->MasterReplacementTextureTable[REPLACETEX_GLOBALCARBONSKIN].SetNewNameHash(this->mUsedTextureInfos.ReplaceGlobalHash); + + bMemCpy(this->CarbonReplacementTextureTable, this->MasterReplacementTextureTable, sizeof(this->CarbonReplacementTextureTable)); + + this->CarbonReplacementTextureTable[REPLACETEX_CARSKIN].SetNewNameHash(bStringHash("CARBONFIBRE")); + this->CarbonReplacementTextureTable[REPLACETEX_CARSKINB].SetNewNameHash(this->mUsedTextureInfos.ReplaceGlobalHash); + this->CarbonReplacementTextureTable[REPLACETEX_GLOBALSKIN].SetNewNameHash(this->mUsedTextureInfos.ReplaceGlobalHash); + this->CarbonReplacementTextureTable[REPLACETEX_CARBONSKIN].SetNewNameHash(bStringHash("CARBONFIBRE")); + this->CarbonReplacementTextureTable[REPLACETEX_GLOBALCARBONSKIN].SetNewNameHash(bStringHash("CARBONFIBRE")); + + this->BrakeLeftReplacementTextureTable[1].SetNewNameHash(this->mUsedTextureInfos.ReplaceGlobalHash); + this->BrakeRightReplacementTextureTable[1].SetNewNameHash(this->mUsedTextureInfos.ReplaceGlobalHash); +} + +void CarRenderInfo::CreateCarLightFlares() { + if (this->pCarTypeInfo != 0) { + int front_marker_slot = -1; + int rear_marker_slot = -1; + + for (int slot_model_index = 0x4B; slot_model_index >= 0; slot_model_index--) { + eModel *model = this->mCarPartModels[slot_model_index][0][this->mMinLodLevel].GetModel(); + ePositionMarker *position_marker = 0; + + if (model == 0) { + continue; + } + + while ((position_marker = model->GetPostionMarker(position_marker)) != 0) { + unsigned int name_hash = position_marker->NameHash; + int flare_type; + + switch (name_hash) { + case 0xD09091C6: + case 0x9DB90133: + case 0x7A5BCF69: + flare_type = 0; + break; + case 0x31A66786: + case 0xA2A2FC7C: + case 0xBF700A79: + flare_type = 1; + break; + case 0x1E4150B4: + flare_type = 5; + break; + case 0xE662C161: + flare_type = 6; + break; + case 0xB4348DBA: + flare_type = 7; + break; + case 0x41489594: + flare_type = 10; + break; + case 0x6A52A241: + flare_type = 11; + break; + case 0x28CD78F5: + flare_type = 12; + break; + case 0x7A5B2F25: + if (front_marker_slot != slot_model_index && front_marker_slot > 0) { + continue; + } + front_marker_slot = slot_model_index; + flare_type = 3; + break; + case 0x7ADF7EF8: + if (rear_marker_slot != slot_model_index && rear_marker_slot > 0) { + continue; + } + rear_marker_slot = slot_model_index; + flare_type = 3; + break; + default: + flare_type = -1; + break; + } + + if (flare_type != -1) { + eLightFlare *light_flare = static_cast(gFastMem.Alloc(sizeof(eLightFlare), 0)); + + light_flare->NameHash = name_hash; + light_flare->Type = static_cast(flare_type); + if ((flare_type - 5U < 3) || flare_type == 10 || flare_type == 11 || flare_type == 12) { + light_flare->Flags = 2; + } else { + light_flare->Flags = 4; + } + light_flare->PositionX = position_marker->Matrix.v3.x; + light_flare->PositionY = position_marker->Matrix.v3.y; + light_flare->PositionZ = position_marker->Matrix.v3.z; + light_flare->ReflectPosZ = 0.0f; + light_flare->DirectionX = position_marker->Matrix.v2.x; + light_flare->DirectionY = position_marker->Matrix.v2.y; + light_flare->DirectionZ = position_marker->Matrix.v2.z; + this->LightFlareList.AddTail(light_flare); + } + } + } + } +} + +void CarRenderInfo::RenderTextureHeadlights(eView *view, bMatrix4 *l_w, unsigned int) { + bMatrix4 *matrix; + unsigned char *address = CurrentBufferPos; + + if (address + 0x40 >= CurrentBufferEnd) { + FrameMallocFailed = 1; + FrameMallocFailAmount += 0x40; + matrix = 0; + } else { + matrix = reinterpret_cast(address); + CurrentBufferPos = address + 0x40; + } + + PSMTX44Copy(*reinterpret_cast(l_w), *reinterpret_cast(matrix)); + + if (matrix != 0) { + bVector3 Up(0.0f, 0.0f, 1.0f); + bVector3 Basis(matrix->v0.z, matrix->v1.z, matrix->v2.z); + float hOffZ0 = 0.0f; + float hOffZ1 = 0.0f; + float hOffZ2 = 0.0f; + float hOffZ3 = 0.0f; + float hOffMID12 = 1.0f; + float hOffMID03 = 0.0f; + float hOffMID13 = 1.0f; + + if (bDot(&Up, &Basis) < 0.707f) { + return; + } + + ePoly poly; + TextureInfo *texture_info = GetTextureInfo(bStringHash("2PLAYERHEADLIGHT1"), 1, 0); + + poly.Vertices[0].x = hOffX - hRad0x; + poly.Vertices[0].y = hOffY - hRad0y; + poly.Vertices[1].x = hRad1x + hOffX; + poly.Vertices[1].y = hOffY - hRad1y; + poly.Vertices[3].x = hOffX - hRad3x; + poly.Vertices[3].y = hRad3y + hOffY; + poly.Vertices[2].x = hRad2x + hOffX; + poly.Vertices[2].y = hRad2y + hOffY; + + poly.Vertices[0].z = hOffZ0; + poly.Vertices[1].z = hOffZ1; + poly.Vertices[2].z = hOffZ2; + poly.Vertices[3].z = hOffZ3; + + poly.UVs[0][0] = hOffMID03; + poly.UVs[1][0] = hOffMID03; + poly.UVs[0][1] = hOffMID12; + poly.UVs[1][1] = hOffMID03; + poly.UVs[0][2] = hOffMID12; + poly.UVs[1][2] = hOffMID13; + poly.UVs[0][3] = hOffMID03; + poly.UVs[1][3] = hOffMID13; + + reinterpret_cast(poly.Colours)[0] = hcL; + reinterpret_cast(poly.Colours)[1] = hcL; + reinterpret_cast(poly.Colours)[2] = hcL; + reinterpret_cast(poly.Colours)[3] = hcL; + + ::Render(view, &poly, texture_info, matrix, 0, 0.0f); + } +} + +void CarRenderInfo::RenderFlaresOnCar(eView *view, const bVector3 *position, const bMatrix4 *body_matrix, int force_light_state, int reflexion, + int renderFlareFlags) { + ProfileNode profile_node("RenderFlaresOnCar", 0); + float Ftime = Sim::GetTime() + this->CarTimebaseStart; + bMatrix4 *local_world = reinterpret_cast(CurrentBufferPos); + + if (CurrentBufferEnd <= CurrentBufferPos + sizeof(bMatrix4)) { + FrameMallocFailed = 1; + FrameMallocFailAmount += sizeof(bMatrix4); + local_world = 0; + } else { + CurrentBufferPos += sizeof(bMatrix4); + } + + if (local_world == 0) { + return; + } + + PSMTX44Copy(*reinterpret_cast(body_matrix), *reinterpret_cast(local_world)); + local_world->v3.x = position->x; + local_world->v3.y = position->y; + local_world->v3.z = position->z; + local_world->v3.w = 1.0f; + + if (!reflexion) { + this->RenderTextureHeadlights(view, local_world, 0); + } + + if (this->pCarTypeInfo != 0 && this->pCarTypeInfo->GetCarUsageType() == CAR_USAGE_TYPE_COP) { + if (this->IsLightOn(VehicleFX::LIGHT_COPRED)) { + view->NumCopsCherry++; + } + } + + int car_pixel_size = view->GetPixelSize(position, this->mRadius); + if (eGetCurrentViewMode() == EVIEWMODE_TWOH) { + car_pixel_size = static_cast(static_cast(car_pixel_size) * 0.5f); + } + + if (car_pixel_size < view->PixelMinSize) { + return; + } + + unsigned int visibility_state = view->GetVisibleState(&this->AABBMin, &this->AABBMax, local_world); + if (visibility_state == EVISIBLESTATE_NOT) { + return; + } + + CarTypeInfo *car_type_info = &CarTypeInfoArray[this->pRideInfo->Type]; + int is_traffic_car = car_type_info->GetCarUsageType() == CAR_USAGE_TYPE_TRAFFIC; + float base_headlight_intensity; + float base_brakelight_intensity; + + if (is_traffic_car != 0) { + base_brakelight_intensity = 0.0f; + base_headlight_intensity = 1.0f; + } else { + base_headlight_intensity = 1.0f; + base_brakelight_intensity = 0.0f; + } + + float headlight_left_intensity; + if (UTL::Collections::Singleton::Get() == 0) { + headlight_left_intensity = base_headlight_intensity; + } else { + headlight_left_intensity = 0.5f; + } + + float headlight_right_intensity; + if (UTL::Collections::Singleton::Get() == 0) { + headlight_right_intensity = base_headlight_intensity; + } else { + headlight_right_intensity = 0.5f; + } + + float brakelight_left_intensity = base_brakelight_intensity; + float brakelight_centre_intensity = 0.0f; + float brakelight_right_intensity = brakelight_left_intensity; + float reverselight_left_intensity = 0.0f; + float reverselight_right_intensity = 0.0f; + float coplight_intensityR = 0.0f; + float coplight_intensityB = 0.0f; + float coplight_intensityW = 0.0f; + unsigned int flashHeadlights = 0; + + if (ForceHeadlightsOn != 0) { + force_light_state |= 1; + } + if (ForceBrakelightsOn != 0) { + force_light_state |= 2; + } + if (ForceReverselightsOn != 0) { + force_light_state |= 4; + } + + if (force_light_state & 1) { + headlight_left_intensity += 1.0f; + headlight_right_intensity += 1.0f; + } else if (force_light_state & 8) { + headlight_left_intensity = 0.0f; + headlight_right_intensity = 0.0f; + } + if (force_light_state & 2) { + brakelight_left_intensity += 1.0f; + brakelight_right_intensity += 1.0f; + brakelight_centre_intensity += 1.0f; + } + if (this->IsLightOn(VehicleFX::LIGHT_LHEAD)) { + headlight_left_intensity = 1.0f; + } + if (this->IsLightBroken(VehicleFX::LIGHT_LHEAD)) { + headlight_left_intensity = 0.0f; + } + if (this->IsLightOn(VehicleFX::LIGHT_RHEAD)) { + headlight_right_intensity = 1.0f; + } + if (this->IsLightBroken(VehicleFX::LIGHT_RHEAD)) { + headlight_right_intensity = 0.0f; + } + if (this->IsLightOn(VehicleFX::LIGHT_LBRAKE)) { + brakelight_left_intensity += 1.0f; + } + if (this->IsLightBroken(VehicleFX::LIGHT_LBRAKE)) { + brakelight_left_intensity = 0.0f; + } + if (this->IsLightOn(VehicleFX::LIGHT_RBRAKE)) { + brakelight_right_intensity += 1.0f; + } + if (this->IsLightBroken(VehicleFX::LIGHT_RBRAKE)) { + brakelight_right_intensity = 0.0f; + } + if (this->IsLightOn(VehicleFX::LIGHT_CBRAKE)) { + brakelight_centre_intensity += 1.0f; + } + if (this->IsLightBroken(VehicleFX::LIGHT_CBRAKE)) { + brakelight_centre_intensity = 0.0f; + } + if (force_light_state & 4) { + reverselight_left_intensity += 1.0f; + reverselight_right_intensity += 1.0f; + } + if (this->IsLightOn(VehicleFX::LIGHT_LREVERSE)) { + reverselight_left_intensity += 1.0f; + } + if (this->IsLightBroken(VehicleFX::LIGHT_LREVERSE)) { + reverselight_left_intensity = 0.0f; + } + if (this->IsLightOn(VehicleFX::LIGHT_RREVERSE)) { + reverselight_right_intensity += 1.0f; + } + if (this->IsLightBroken(VehicleFX::LIGHT_RREVERSE)) { + reverselight_right_intensity = 0.0f; + } + if (this->IsLightOn(VehicleFX::LIGHT_COPRED)) { + coplight_intensityR = cpr; + } + if (this->IsLightBroken(VehicleFX::LIGHT_COPRED)) { + coplight_intensityR = 0.0f; + } + if (this->IsLightOn(VehicleFX::LIGHT_COPWHITE)) { + coplight_intensityW = cpw; + flashHeadlights = 1; + } + if (this->IsLightBroken(VehicleFX::LIGHT_COPWHITE)) { + coplight_intensityW = 0.0f; + } + if (this->IsLightOn(VehicleFX::LIGHT_COPBLUE)) { + coplight_intensityB = cpb; + } + if (this->IsLightBroken(VehicleFX::LIGHT_COPBLUE)) { + coplight_intensityB = 0.0f; + } + + CarPart *preview_part = this->pRideInfo->GetPreviewPart(); + CAR_PART_ID preview_part_id = CARPARTID_NUM; + + if (preview_part != 0) { + preview_part_id = static_cast(*reinterpret_cast(reinterpret_cast(preview_part) + 4)); + } + float constFlicker = coplightflicker(Ftime, 0); + int FlareCount = 0; + + for (eLightFlare *light_flare = this->LightFlareList.GetHead(); light_flare != this->LightFlareList.EndOfList(); + light_flare = light_flare->GetNext()) { + unsigned int name_hash = light_flare->NameHash; + int is_brakelight = preview_part_id == CARPARTID_BRAKELIGHT; + int is_headlight = preview_part_id == CARPARTID_HEADLIGHT; + float intensity = 0.0f; + float sizescale = 1.0f; + + if (is_traffic_car != 0 && light_flare->Type == 1) { + light_flare->Type = 2; + } + if ((renderFlareFlags & 2) != 0 && light_flare->Type != 1) { + continue; + } + if ((renderFlareFlags & 1) != 0) { + if (light_flare->Type < 5 || light_flare->Type > 12) { + continue; + } + sizescale = 0.75f; + } + + switch (name_hash) { + case 0x9DB90133: + intensity = headlight_left_intensity; + if (flashHeadlights != 0) { + intensity *= constFlicker; + } + break; + case 0xD09091C6: + intensity = headlight_right_intensity; + if (flashHeadlights != 0) { + intensity *= 1.0f - constFlicker; + } + break; + case 0x31A66786: + intensity = brakelight_left_intensity; + break; + case 0xBF700A79: + intensity = brakelight_right_intensity; + break; + case 0xA2A2FC7C: + intensity = brakelight_centre_intensity; + break; + case 0x7A5B2F25: + intensity = reverselight_left_intensity; + break; + case 0x7ADF7EF8: + intensity = reverselight_right_intensity; + break; + case 0x1E4150B4: + case 0x41489594: + intensity = coplight_intensityR * coplightflicker2(Ftime, 0, FlareCount); + break; + case 0x6A52A241: + case 0xE662C161: + intensity = coplight_intensityB * coplightflicker2(Ftime, 1, FlareCount); + break; + case 0xB4348DBA: + intensity = bSin(coplight_intensityW * coplightflicker2(Ftime, 2, FlareCount) * copWhitemul); + break; + case 0x28CD78F5: + if (is_brakelight || (is_headlight && renderFlareFlags != 0)) { + intensity = 1.0f; + } + break; + default: + intensity = 0.0f; + break; + } + + if (intensity > 1.0f) { + intensity = 1.0f; + } + + if (intensity > 0.0f) { + if (!reflexion) { + eRenderLightFlare( + view, light_flare, local_world, intensity, REF_NONE, (renderFlareFlags & 1) != 0 ? FLARE_ENV : FLARE_NORM, 0.0f, 0, + sizescale + ); + } else { + eRenderLightFlare(view, light_flare, local_world, intensity, REF_TOPO, FLARE_REFLECT, 0.0f, 0, 1.0f); + } + } + + FlareCount++; + } + + if (view->GetID() == EVIEW_FIRST_PLAYER && !reflexion) { + bVector3 NisLightPosition(position->x + gTWEAKER_NISLightPosX, position->y + gTWEAKER_NISLightPosY, position->z + gTWEAKER_NISLightPosZ); + bVector3 *lightPosition = const_cast(position); + float extraIntensity = 1.0f; + + if (gTWEAKER_NISLightEnabled != 0) { + lightPosition = &NisLightPosition; + extraIntensity = gTWEAKER_NISLightIntensity; + } + + if (coplight_intensityR > 0.0f) { + AddQuickDynamicLight(&ShaperLightsCharacters, Lightslot, 1.0f, 0.0f, 0.0f, coplight_intensityR * extraIntensity, lightPosition); + } else if (coplight_intensityB > 0.0f) { + AddQuickDynamicLight(&ShaperLightsCharacters, Lightslot, 0.0f, 0.0f, 1.0f, coplight_intensityB * extraIntensity, lightPosition); + } else { + RestoreShaperRig(&ShaperLightsCharacters, Lightslot, &ShaperLightsCharactersBackup); + } + } +} + +void CarRenderInfo::UpdateLightStateTextures() { + bool lights_always_on; + + { + int left_light_state = 1; + int right_light_state = left_light_state; + int left_light_state_hash = this->mUsedTextureInfos.ReplaceHeadlightHash[left_light_state]; + int right_light_state_hash = this->mUsedTextureInfos.ReplaceHeadlightHash[right_light_state]; + int left_light_glass_state_hash = this->mUsedTextureInfos.ReplaceHeadlightGlassHash[left_light_state]; + int right_light_glass_state_hash = this->mUsedTextureInfos.ReplaceHeadlightGlassHash[right_light_state]; + + this->MasterReplacementTextureTable[REPLACETEX_HEADLIGHT_LEFT].SetNewNameHash(left_light_state_hash); + this->MasterReplacementTextureTable[REPLACETEX_HEADLIGHT_RIGHT].SetNewNameHash(right_light_state_hash); + this->MasterReplacementTextureTable[REPLACETEX_HEADLIGHT_GLASS_LEFT].SetNewNameHash(left_light_glass_state_hash); + this->MasterReplacementTextureTable[REPLACETEX_HEADLIGHT_GLASS_RIGHT].SetNewNameHash(right_light_glass_state_hash); + this->MasterReplacementTextureTable[REPLACETEX_OLD_HEADLIGHT_LEFT].SetNewNameHash(left_light_state_hash); + this->MasterReplacementTextureTable[REPLACETEX_OLD_HEADLIGHT_RIGHT].SetNewNameHash(right_light_state_hash); + this->MasterReplacementTextureTable[REPLACETEX_OLD_HEADLIGHT_GLASS_LEFT].SetNewNameHash(left_light_glass_state_hash); + this->MasterReplacementTextureTable[REPLACETEX_OLD_HEADLIGHT_GLASS_RIGHT].SetNewNameHash(right_light_glass_state_hash); + this->CarbonReplacementTextureTable[REPLACETEX_HEADLIGHT_LEFT].SetNewNameHash(left_light_state_hash); + this->CarbonReplacementTextureTable[REPLACETEX_HEADLIGHT_RIGHT].SetNewNameHash(right_light_state_hash); + this->CarbonReplacementTextureTable[REPLACETEX_HEADLIGHT_GLASS_LEFT].SetNewNameHash(left_light_glass_state_hash); + this->CarbonReplacementTextureTable[REPLACETEX_HEADLIGHT_GLASS_RIGHT].SetNewNameHash(right_light_glass_state_hash); + this->CarbonReplacementTextureTable[REPLACETEX_OLD_HEADLIGHT_LEFT].SetNewNameHash(left_light_state_hash); + this->CarbonReplacementTextureTable[REPLACETEX_OLD_HEADLIGHT_RIGHT].SetNewNameHash(right_light_state_hash); + this->CarbonReplacementTextureTable[REPLACETEX_OLD_HEADLIGHT_GLASS_LEFT].SetNewNameHash(left_light_glass_state_hash); + this->CarbonReplacementTextureTable[REPLACETEX_OLD_HEADLIGHT_GLASS_RIGHT].SetNewNameHash(right_light_glass_state_hash); + + unsigned int new_headlight_hash = this->MasterReplacementTextureTable[REPLACETEX_WINDOW_FRONT].GetNewNameHash(); + this->MasterReplacementTextureTable[REPLACETEX_HEADLIGHT_GLASS_LEFT].SetNewNameHash(new_headlight_hash); + this->MasterReplacementTextureTable[REPLACETEX_HEADLIGHT_GLASS_RIGHT].SetNewNameHash(new_headlight_hash); + this->MasterReplacementTextureTable[REPLACETEX_OLD_HEADLIGHT_GLASS_LEFT].SetNewNameHash(new_headlight_hash); + this->MasterReplacementTextureTable[REPLACETEX_OLD_HEADLIGHT_GLASS_RIGHT].SetNewNameHash(new_headlight_hash); + } + + { + int left_light_state = 0; + int right_light_state = 0; + int centre_light_state = 0; + + if (!this->IsLightBroken(VehicleFX::LIGHT_LBRAKE) && this->IsLightOn(VehicleFX::LIGHT_LBRAKE)) { + left_light_state = 1; + } + if (!this->IsLightBroken(VehicleFX::LIGHT_RBRAKE) && this->IsLightOn(VehicleFX::LIGHT_RBRAKE)) { + right_light_state = 1; + } + if (!this->IsLightBroken(VehicleFX::LIGHT_CBRAKE) && this->IsLightOn(VehicleFX::LIGHT_CBRAKE)) { + centre_light_state = 1; + } + if (ForceBrakelightsOn != 0) { + left_light_state = 1; + right_light_state = 1; + } + + int left_light_state_hash = this->mUsedTextureInfos.ReplaceBrakelightHash[left_light_state]; + int right_light_state_hash = this->mUsedTextureInfos.ReplaceBrakelightHash[right_light_state]; + int centre_light_state_hash = this->mUsedTextureInfos.ReplaceBrakelightHash[centre_light_state]; + int left_light_glass_state_hash = this->mUsedTextureInfos.ReplaceBrakelightGlassHash[left_light_state]; + int right_light_glass_state_hash = this->mUsedTextureInfos.ReplaceBrakelightGlassHash[right_light_state]; + int centre_light_glass_state_hash = this->mUsedTextureInfos.ReplaceBrakelightGlassHash[centre_light_state]; + + this->MasterReplacementTextureTable[REPLACETEX_BRAKELIGHT_LEFT].SetNewNameHash(left_light_state_hash); + this->MasterReplacementTextureTable[REPLACETEX_BRAKELIGHT_RIGHT].SetNewNameHash(right_light_state_hash); + this->MasterReplacementTextureTable[REPLACETEX_BRAKELIGHT_CENTRE].SetNewNameHash(centre_light_state_hash); + this->MasterReplacementTextureTable[REPLACETEX_BRAKELIGHT_GLASS_LEFT].SetNewNameHash(left_light_glass_state_hash); + this->MasterReplacementTextureTable[REPLACETEX_BRAKELIGHT_GLASS_RIGHT].SetNewNameHash(right_light_glass_state_hash); + this->MasterReplacementTextureTable[REPLACETEX_BRAKELIGHT_GLASS_CENTRE].SetNewNameHash(centre_light_glass_state_hash); + this->MasterReplacementTextureTable[REPLACETEX_OLD_BRAKELIGHT_LEFT].SetNewNameHash(left_light_state_hash); + this->MasterReplacementTextureTable[REPLACETEX_OLD_BRAKELIGHT_RIGHT].SetNewNameHash(right_light_state_hash); + this->MasterReplacementTextureTable[REPLACETEX_OLD_BRAKELIGHT_CENTRE].SetNewNameHash(centre_light_state_hash); + this->MasterReplacementTextureTable[REPLACETEX_OLD_BRAKELIGHT_GLASS_LEFT].SetNewNameHash(left_light_glass_state_hash); + this->MasterReplacementTextureTable[REPLACETEX_OLD_BRAKELIGHT_GLASS_RIGHT].SetNewNameHash(right_light_glass_state_hash); + this->MasterReplacementTextureTable[REPLACETEX_OLD_BRAKELIGHT_GLASS_CENTRE].SetNewNameHash(centre_light_glass_state_hash); + } +} + +void UpdateEnvironmentMapCameras() { + bVector3 *car_world_position = nullptr; + eView *view = eGetView(1, false); + + if (view->GetCameraMover() != nullptr) { + CameraAnchor *anchor = view->GetCameraMover()->GetAnchor(); + + if (anchor != nullptr) { + car_world_position = &reinterpret_cast(anchor)->mGeomPos; + } else { + static bVector3 sCarWorldPosition_31751; + static int tmp_45_31752; + + if (tmp_45_31752 == 0) { + tmp_45_31752 = 1; + } + + IPlayer *first_player = IPlayer::First(PLAYER_LOCAL); + if (first_player != nullptr) { + ISimable *simable = first_player->GetSimable(); + IRigidBody *player_rigid_body = simable != nullptr ? simable->GetRigidBody() : nullptr; + + if (player_rigid_body != nullptr) { + eSwizzleWorldVector(player_rigid_body->GetPosition(), sCarWorldPosition_31751); + bSub(&sCarWorldPosition_31751, &sCarWorldPosition_31751, &EnvMapEyeOffset); + car_world_position = &sCarWorldPosition_31751; + } + } + } + } + + if (car_world_position == nullptr) { + FrontEndRenderingCar *fecar = nullptr; + + if (FrontEndRenderingCarList.GetHead() != FrontEndRenderingCarList.EndOfList()) { + fecar = FrontEndRenderingCarList.GetHead(); + } + if (fecar == nullptr) { + return; + } + + car_world_position = &fecar->Position; + } + + bVector3 camera_eye_position(*view->GetCamera()->GetPosition()); + bVector3 envmap_position; + bAdd(&camera_eye_position, &camera_eye_position, &EnvMapCamOffset); + bAdd(&envmap_position, car_world_position, &EnvMapEyeOffset); + eGetEnvMap()->UpdateCameras(&camera_eye_position, &envmap_position); +} + +void RefreshAllFrontEndCarRenderInfos(CarType type) { + for (FrontEndRenderingCar *front_end_car = FrontEndRenderingCarList.GetHead(); front_end_car != FrontEndRenderingCarList.EndOfList(); + front_end_car = front_end_car->GetNext()) { + RideInfo *ride_info = &reinterpret_cast(front_end_car)->mRideInfo; + + if ((type == static_cast(-1) || ride_info->Type == type) && front_end_car->RenderInfo != 0) { + front_end_car->RenderInfo->Refresh(); + } + } +} + +void RefreshAllRenderInfo(CarType type) { + UTL::Collections::Listable::List::const_iterator it = VehicleRenderConn::GetList().begin(); + + while (it != VehicleRenderConn::GetList().end()) { + VehicleRenderConn *conn = *it; + + if ((type == static_cast(-1) || conn->GetCarType() == type) && conn->mState > 1) { + conn->RefreshRenderInfo(); + } + ++it; + } + + RefreshAllFrontEndCarRenderInfos(type); +} + +void RenderFEFlares(eView *, int) {} + +void RenderFrontEndCars(eView *view, int reflection) { + if (DrawCars != 0) { + if (reflection) { + FEManager *fe_manager = FEManager::Get(); + if (fe_manager->GetGarageType() == GARAGETYPE_CAR_LOT) { + return; + } + } + + eGetCurrentViewMode(); + + for (FrontEndRenderingCar *front_end_car = FrontEndRenderingCarList.GetHead(); front_end_car != FrontEndRenderingCarList.EndOfList(); + front_end_car = front_end_car->GetNext()) { + CarRenderInfo *render_info = front_end_car->RenderInfo; + + if (render_info != 0 && front_end_car->Visible != 0) { + CARPART_LOD lod = render_info->mMinLodLevel; + bMatrix4 body_matrix(front_end_car->BodyMatrix); + bVector3 position(front_end_car->Position.x, front_end_car->Position.y, front_end_car->Position.z); + + if (reflection) { + float offset_scale = + *reinterpret_cast(*reinterpret_cast(reinterpret_cast(render_info) + 0x1764) + 0xF4); + + body_matrix.v2.x = -body_matrix.v2.x; + body_matrix.v2.y = -body_matrix.v2.y; + body_matrix.v2.z = -body_matrix.v2.z; + position.x += feposoff.x + body_matrix.v2.x * offset_scale; + position.y += feposoff.y + body_matrix.v2.y * offset_scale; + position.z += feposoff.z + body_matrix.v2.z * offset_scale; + } + + render_info->Render(view, &position, &body_matrix, front_end_car->TireMatrices, front_end_car->BrakeMatrices, + front_end_car->TireMatrices, reflection, 0, reflection, 1.0f, lod, lod); + } + } + } +} + +void RenderVehicleFlares(eView *view, int reflection, int renderFlareFlags) { + VehicleRenderConn::RenderFlares(view, reflection, renderFlareFlags); +} + +void DrawTestCars(eView *view, int reflection) { + VehicleRenderConn::RenderAll(view, reflection); +} + +int smooth_shadow_corners(int nVerts) { + bVector3 v[2]; + bVector3 vTemp; + int i; + int iNew; + int nCurr; + int nPrev; + int nNext; + + iNew = 0; + nPrev = nVerts - 1; + v[0] = hullVertArray2[0] - hullVertArray2[nPrev]; + v[0] *= lbl_8040AD7C; + + for (i = 0; i < nVerts; i++) { + nCurr = i & 1; + nPrev = nCurr ^ 1; + nNext = i + 1; + + if (nNext == nVerts) { + nNext = 0; + } + + v[nCurr] = hullVertArray2[nNext] - hullVertArray2[i]; + v[nCurr] *= lbl_8040AD7C; + + hullVertArray3[iNew] = hullVertArray2[i] - v[nPrev]; + hullVertArray3[iNew + 1] = hullVertArray2[i] + v[nCurr]; + vTemp = v[nCurr] - v[nPrev]; + bScaleAdd(&hullVertArray3[iNew + 2], &hullVertArray2[i], &vTemp, lbl_8040AD7C); + iNew += 3; + } + + return iNew; +} + +void CarRenderInfo::DrawAmbientShadow(eView *view, const bVector3 *position, float shadow_scale, bMatrix4 *localWorld, bMatrix4 *worldLocal, + bMatrix4 *biasedIdentity) { + const int N = 16; + int in_front_end; + bVector3 usPoint; + float scaleW; + float scaleL; + bVector3 min; + bVector3 max; + float scale; + bVector3 SunCarVector; + bVector3 light_pos; + SunChunkInfo *sun_info; + float SunScale; + bVector3 sunpos_in_car_space; + float sunAdjX; + float sunAdjY; + float sunDX; + float sunDY; + float sunStartX; + float sunStartY; + int bad_points[4]; + float py; + float px; + float dy; + float dx; + float ps; + float pt; + float ds; + float dt; + float shadow_alpha_scale; + unsigned int shadow_colour; + float shadow_alpha_min; + float shadow_alpha_max; + float shadow_alpha; + int shadow_alphai; + int shadow_alphai_raw; + TextureInfo *texture_info; + unsigned int colour; + + hull_Origin = *position; + if (TheGameFlowManager.GetState() == GAMEFLOW_STATE_RACING) { + eUnSwizzleWorldVector(*position, usPoint); + this->mWorldPos.FindClosestFace(this->mWCollider, reinterpret_cast(usPoint), false); + if (this->mWorldPos.OnValidFace()) { + this->mCar_elevation = this->mWorldPos.HeightAtPoint(reinterpret_cast(usPoint)); + car_elevation = position->z - this->mCar_elevation; + } + } + + car_elevation_scale = lbl_8040ADC0; + if (car_elevation > lbl_8040ADC0 && car_elevation < lbl_8040ADC4) { + car_elevation_scale = car_elevation * lbl_8040ADC8; + } else if (car_elevation > lbl_8040ADC4) { + car_elevation_scale = lbl_8040ADCC; + } + + min = this->AABBMin; + max = this->AABBMax; + in_front_end = IsGameFlowInFrontEnd(); + + scale = lbl_8040ADD0; + if (this->pRideInfo->Type == static_cast(4)) { + scale *= heliScale; + } + + min.x *= scale; + min.y *= scale; + min.z *= scale; + max.x *= scale; + max.y *= scale; + max.z *= scale; + sun_info = SunInfo; + if (sun_info == 0) { + light_pos.x = lbl_8040ADD4; + light_pos.y = lbl_8040ADC0; + light_pos.z = lbl_8040ADD8; + } else { + light_pos.x = sun_info->CarShadowPositionX; + light_pos.y = sun_info->CarShadowPositionY; + light_pos.z = sun_info->CarShadowPositionZ; + } + + SunCarVector = light_pos - *position; + bNormalize(&SunCarVector, &SunCarVector); + SunScale = (lbl_8040ADCC - SunCarVector.z) * lbl_8040ADDC; + bMulMatrix(&sunpos_in_car_space, worldLocal, &light_pos); + bNormalize(&sunpos_in_car_space, &sunpos_in_car_space); + + sunAdjY = -sunpos_in_car_space.y * SunScale * lbl_8040ADE0; + sunAdjX = -sunpos_in_car_space.x * SunScale * lbl_8040ADE0; + sunDX = bAbs(sunAdjX); + sunDY = bAbs(sunAdjY); + sunStartX = sunAdjX; + if (sunAdjX > lbl_8040ADC0) { + sunStartX = lbl_8040ADC0; + } + sunStartY = sunAdjY; + if (sunAdjY > lbl_8040ADC0) { + sunStartY = lbl_8040ADC0; + } + + bVector3 p[16]; + bVector3 *pp; + bVector2 uv[16]; + bVector2 *puv; + pp = p; + puv = uv; + py = min.y + sunStartY; + scaleW = (max.y - min.y) * lbl_8040ADE0; + scaleL = (max.x - min.x) * lbl_8040ADE0; + dy = scaleW; + dx = scaleL; + ds = lbl_8040ADE0; + dt = lbl_8040ADE0; + pt = lbl_8040ADC0; + float pz = lbl_8040ADC0; + + for (int y = 0; y < 4; y++) { + px = min.x + sunStartX; + ps = lbl_8040ADC0; + for (int x = 0; x < 4; x++) { + pp->x = px; + puv->x = ps; + pp->y = py; + pp->z = pz; + puv->y = pt; + eMulVector(pp, localWorld, pp); + px += sunDX; + ps += ds; + pp++; + puv++; + px += dx; + } + bad_points[y] = 0; + py += sunDY; + py += dy; + pt += dt; + } + + if (in_front_end != 0) { + bVector3 *pz = p; + + for (int x = 0; x < N; x++) { + pz->z = lbl_8040ADC0; + pz++; + } + } else if (this->mWCollider != 0) { + bVector3 usCenter; + bVector3 sCenter; + bVector3 ref; + bool quitIfSameFace = true; + + sCenter = *position; + eUnSwizzleWorldVector(sCenter, usCenter); + this->mWorldPos.SetTolerance(lbl_8040ADE4); + this->mWorldPos.FindClosestFace(this->mWCollider, reinterpret_cast(usCenter), false); + if (this->mWorldPos.OnValidFace()) { + UMath::Vector4 worldNormal; + + memset(&worldNormal, 0, sizeof(worldNormal)); + worldNormal.y = lbl_8040ADCC; + this->mWorldPos.UNormal(&worldNormal); + UMath::Unitxyz(worldNormal, worldNormal); + eSwizzleWorldVector(reinterpret_cast(UMath::Vector4To3(worldNormal)), hull_Normal); + } else { + hull_Normal.x = lbl_8040ADC0; + hull_Normal.y = lbl_8040ADC0; + hull_Normal.z = lbl_8040ADCC; + } + + ref.x = lbl_8040ADC0; + ref.y = lbl_8040ADC0; + ref.z = lbl_8040ADC0; + eMulVector(&ref, localWorld, &ref); + this->mWorldPos.SetTolerance(lbl_8040ADE4); + bVector3 *point = p; + for (int x = 0; x < N; x++, point++) { + bVector3 sPoint; + bVector3 usPoint; + bool validFace; + + sPoint = *point; + eUnSwizzleWorldVector(sPoint, usPoint); + this->mWorldPos.FindClosestFace(this->mWCollider, reinterpret_cast(usPoint), quitIfSameFace); + validFace = this->mWorldPos.OnValidFace(); + point->z = this->mWorldPos.HeightAtPoint(reinterpret_cast(usPoint)); + if (validFace) { + bVector3 vec = *point - hull_Origin; + float dot = bDot(&vec, &hull_Normal); + + if (dot < lbl_8040ADC0) { + dot = -dot; + } + + if (dot < lbl_8040ADEC) { + quitIfSameFace = true; + continue; + } + } + + quitIfSameFace = false; + + if (this->pRideInfo->Type == static_cast(4)) { + point->z = lbl_8040ADCC; + bad_points[x / 4]++; + } else { + point->z = this->mCar_elevation; + ref.z = this->mCar_elevation; + } + } + } + + shadow_alpha_scale = bAbs(localWorld->v2.z) * (lbl_8040ADCC - car_elevation_scale); + if (in_front_end != 0) { + shadow_alpha_min = lbl_8040ADF4; + shadow_alpha_max = lbl_8040ADF4; + } else { + shadow_alpha_min = lbl_8040ADC0; + shadow_alpha_max = lbl_8040ADF8; + } + + shadow_alpha = (shadow_alpha_max - shadow_alpha_min) * shadow_alpha_scale + shadow_alpha_min; + shadow_alphai_raw = static_cast(shadow_alpha); + shadow_alphai = bClamp(shadow_alphai_raw, 0, 0xFE); + + shadow_colour = static_cast(shadow_alphai << 24) | 0x00808080; + texture_info = this->ShadowTexture; + if (texture_info == 0 || (shadow_colour & 0xFF000000) == 0) { + return; + } + + colour = shadow_colour; + { + bVector3 *p0 = p; + bVector3 *p1 = p + 4; + bVector2 *u0 = uv; + bVector2 *u1 = uv + 4; + + for (int y = 0; y < 3; y++) { + if (bad_points[y] + bad_points[y + 1] <= 3) { + if (exBeginStrip(texture_info, 8, biasedIdentity)) { + for (int x = 0; x < 4; x++) { + exAddVertex(*p0); + p0++; + exAddColour(colour); + exAddUV(u0->x, u0->y); + u0++; + exAddVertex(*p1); + p1++; + exAddColour(colour); + exAddUV(u1->x, u1->y); + u1++; + } + exEndStrip(view); + } else { + p0 += 4; + p1 += 4; + u0 += 4; + u1 += 4; + } + } else { + p0 += 4; + p1 += 4; + u0 += 4; + u1 += 4; + } + } + } +} + +void CarRenderInfo::convex_hull(bVector3 *p, const WCollider *wcoll, int &n, float Z, float zBias, int fast) { + int i; + int dec; + bool bPointValid; + bVector3 usPoint; + + for (i = 0; i < n; i++) { + P[i] = &p[i]; + } + + n = ch2d(reinterpret_cast(P), n); + if (wcoll != nullptr) { + this->mWorldPos.SetTolerance(lbl_8040AD70); + if (fast != 0) { + bVector3 *vec; + float fastZ = Z; + + vec = hullVertArray2; + bFill(vec, P[0]->x, P[0]->y, Z); + + eUnSwizzleWorldVector(*vec, usPoint); + this->mWorldPos.FindClosestFace(wcoll, reinterpret_cast(usPoint), true); + if (!this->mWorldPos.OnValidFace()) { + fastZ = this->mWorldPos.HeightAtPoint(reinterpret_cast(usPoint)); + } + + dec = 0; + for (i = 0; i < n; i++) { + bFill(vec, P[i]->x, P[i]->y, fastZ); + + bVector3 dotVec = *vec - hull_Origin; + float dot = bDot(&dotVec, &hull_Normal); + if (dot < lbl_8040AD74) { + dot = -dot; + } + + if (lbl_8040AD78 <= dot) { + dec++; + } else { + vec++; + } + } + } else { + bool quitIfSameFace = true; + bVector3 *vec; + + vec = hullVertArray2; + dec = 0; + + for (i = 0; i < n; i++) { + bFill(vec, P[i]->x, P[i]->y, Z); + + eUnSwizzleWorldVector(*vec, usPoint); + this->mWorldPos.FindClosestFace(wcoll, reinterpret_cast(usPoint), quitIfSameFace); + if (!this->mWorldPos.OnValidFace()) { + vec->z = this->mWorldPos.HeightAtPoint(reinterpret_cast(usPoint)); + } + + bVector3 dotVec = *vec - hull_Origin; + float dot = bDot(&dotVec, &hull_Normal); + if (dot < lbl_8040AD74) { + dot = -dot; + } + + if (lbl_8040AD78 <= dot) { + dec++; + } else { + vec++; + } + quitIfSameFace = false; + } + } + + n -= dec; + } +} + +static inline bool ccw(float **P, int i, int j, int k) { + float d = P[k][1] - P[j][1]; + float c = P[k][0] - P[j][0]; + float b = P[i][1] - P[j][1]; + float a = P[i][0] - P[j][0]; + return a * d - b * c > 0.0f; +} + +int make_chain(float **V, int n, int (*cmp)(const void *, const void *)) { + int s; + qsort(V, n, 4, cmp); + s = 1; + for (int i = 2; i < n; i++) { + int j; + for (j = s; j >= 1 && !ccw(V, j - 1, j, i); j--) { + } + s = j + 1; + float *t = V[s]; + V[s] = V[i]; + V[i] = t; + } + return s; +} + +int ch2d(float **P, int n) { + int u = make_chain(P, n, cmpl); + P[n] = P[0]; + return u + make_chain(P + u, n - u + 1, cmph); +} + +void CarRenderInfo::DrawKeithProjShadow(eView *view, const bVector3 *position, bMatrix4 *localWorld, bMatrix4 *worldLocal, bMatrix4 *biasedIdentity, + int body_lod) { + if (body_lod < 3) { + int n = 16; + bVector3 *shadowVertices = hullVertArray1; + float shadowZ; + bVector3 lightV; + bVector3 scale; + + sh_Setup(const_cast(position)); + shadowZ = position->z; + if (TheGameFlowManager.GetState() == GAMEFLOW_STATE_RACING) { + bVector3 worldPosition; + + eUnSwizzleWorldVector(*position, worldPosition); + this->mWorldPos.FindClosestFace(this->mWCollider, reinterpret_cast(worldPosition), false); + if (this->mWorldPos.OnValidFace()) { + shadowZ = this->mWorldPos.HeightAtPoint(reinterpret_cast(worldPosition)); + } + } + + lightV = cs_lightV; + scale.x = lbl_8040AD98; + scale.y = lbl_8040AD9C; + scale.z = lbl_8040ADA0; + float one_over_z = cs_OneOverZ; + bVector3 *shadow_vertex = shadowVertices; + for (int i = 0; i < n; i++) { + bVector3 localPoint; + bVector3 worldPoint; + float scaleToGround; + + localPoint.x = PointCloud[i].x * scale.x; + localPoint.y = PointCloud[i].y * scale.y; + localPoint.z = PointCloud[i].z * scale.z; + eMulVector(&worldPoint, localWorld, &localPoint); + scaleToGround = (shadowZ - worldPoint.z) * one_over_z; + shadow_vertex->x = scaleToGround * lightV.x + worldPoint.x; + shadow_vertex->y = scaleToGround * lightV.y + worldPoint.y; + shadow_vertex->z = scaleToGround * lightV.z + worldPoint.z; + shadow_vertex++; + } + + int not_min_lod = body_lod != this->mMinLodLevel; + + this->convex_hull(shadowVertices, this->mWCollider, n, shadowZ, lbl_8040ADA4, not_min_lod); + if (!not_min_lod) { + n = smooth_shadow_corners(n); + shadowVertices = hullVertArray3; + } else { + shadowVertices = hullVertArray2; + } + + if (n > 2) { + bVector3 shadowCenter = shadowVertices[0] + shadowVertices[n / 2]; + int alpha = static_cast((lbl_8040ADA0 - car_elevation_scale) * lbl_8040ADB0); + unsigned int colour; + + shadowCenter *= lbl_8040ADA8; + FancyCarShadowEdgeMult = car_elevation_scale * lbl_8040ADB4 + lbl_8040ADB8; + alpha = bClamp(alpha, 0, 0xFE); + colour = static_cast(alpha << 24) | 0x00808080; + + if (dshad != 0) { + int start = 0; + int stop = (n & ~1) - 1; + + while (start < stop) { + int next = start + 2; + + if (eBeginStrip(this->ShadowRampTexture, 4, biasedIdentity)) { + eAddVertex(shadowVertices[start]); + eAddVertex(shadowCenter); + eAddVertex(shadowVertices[start + 1]); + eAddVertex(shadowVertices[next - (next / n) * n]); + eAddColour(colour); + eAddColour(colour); + eAddColour(colour); + eAddColour(colour); + eAddUV(lbl_8040ADBC, lbl_8040ADAC); + eAddUV(lbl_8040ADAC, lbl_8040ADAC); + eAddUV(lbl_8040ADBC, lbl_8040ADAC); + eAddUV(lbl_8040ADBC, lbl_8040ADAC); + eEndStrip(view); + } + + start = next; + } + + if ((n & 1) != 0 && eBeginStrip(this->ShadowRampTexture, 3, biasedIdentity)) { + eAddVertex(shadowVertices[n - 1]); + eAddVertex(shadowCenter); + eAddVertex(shadowVertices[0]); + eAddColour(colour); + eAddColour(colour); + eAddColour(colour); + eAddUV(lbl_8040ADBC, lbl_8040ADAC); + eAddUV(lbl_8040ADAC, lbl_8040ADAC); + eAddUV(lbl_8040ADBC, lbl_8040ADAC); + eEndStrip(view); + } + + { + int nStart = n / 3; + int startIndex = 0; + int section = 0; + + do { + int nSubVerts = nStart; + int nextStart; + int nextSection = section + 1; + + if (nextSection > 2) { + nSubVerts = n - startIndex; + } + nextStart = startIndex + nSubVerts; + + if (exBeginStrip(this->ShadowRampTexture, (nSubVerts + 1) * 2, biasedIdentity)) { + int endIndex = startIndex + nSubVerts; + int loopIndex = startIndex; + + for (; loopIndex < endIndex; loopIndex++) { + bVector3 *edge = &shadowVertices[loopIndex]; + bVector3 sourceVertex(*edge); + bVector3 inner(sourceVertex); + bVector3 edgeVertex(inner); + + inner.x = FancyCarShadowEdgeMult * (edgeVertex.x - shadowCenter.x) + shadowCenter.x; + inner.y = FancyCarShadowEdgeMult * (edgeVertex.y - shadowCenter.y) + shadowCenter.y; + exAddVertex(edgeVertex); + exAddVertex(inner); + exAddColour(colour); + exAddColour(colour); + exAddUV(lbl_8040ADBC, lbl_8040ADAC); + exAddUV(lbl_8040ADA0, lbl_8040ADAC); + } + + if (loopIndex < n) { + } else { + loopIndex = 0; + } + + { + bVector3 *edge = &shadowVertices[loopIndex]; + bVector3 sourceVertex(*edge); + bVector3 inner(sourceVertex); + bVector3 edgeVertex(inner); + + inner.x = FancyCarShadowEdgeMult * (edgeVertex.x - shadowCenter.x) + shadowCenter.x; + inner.y = FancyCarShadowEdgeMult * (edgeVertex.y - shadowCenter.y) + shadowCenter.y; + exAddVertex(edgeVertex); + exAddVertex(inner); + exAddColour(colour); + exAddColour(colour); + exAddUV(lbl_8040ADBC, lbl_8040ADAC); + exAddUV(lbl_8040ADA0, lbl_8040ADAC); + exEndStrip(view); + } + } + + startIndex = nextStart; + section = nextSection; + } while (section < 3); + } + } + } + } +} + +void CarRender_Service(float dT) { + VehicleRenderConn::FetchData(dT); + VehicleFragmentConn::FetchData(dT); +} diff --git a/src/Speed/Indep/Src/World/CarRender.hpp b/src/Speed/Indep/Src/World/CarRender.hpp index 96cc9736d..d90285771 100644 --- a/src/Speed/Indep/Src/World/CarRender.hpp +++ b/src/Speed/Indep/Src/World/CarRender.hpp @@ -16,6 +16,7 @@ #include "Speed/Indep/Src/Sim/Collision.h" #include "Speed/Indep/bWare/Inc/bList.hpp" #include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "Speed/Indep/bWare/Inc/bSlotPool.hpp" /////// NOT IN THIS FILE /////// class ePointSprite3D { @@ -154,16 +155,18 @@ typedef struct tagCarEffectParam { unsigned int NameHash; } CarEffectParam; +extern SlotPool *CarEmitterPositionSlotPool; + class CarEmitterPosition : public bSNode { public: // Functions - static void *operator new(size_t size) {} + static void *operator new(unsigned int size); - static void operator delete(void *ptr) {} + static void operator delete(void *ptr); - CarEmitterPosition(ePositionMarker *position_marker) {} + CarEmitterPosition(ePositionMarker *position_marker); - CarEmitterPosition(float x, float y, float z) {} + CarEmitterPosition(float x, float y, float z); // Members float X; // offset 0x4, size 0x4 @@ -172,7 +175,7 @@ class CarEmitterPosition : public bSNode { ePositionMarker *PositionMarker; // offset 0x10, size 0x4 }; -class UsedCarTextureInfo { +struct UsedCarTextureInfo { // Members unsigned int TexturesToLoadPerm[87]; // offset 0x0, size 0x15C unsigned int TexturesToLoadTemp[87]; // offset 0x15C, size 0x15C @@ -219,9 +222,13 @@ class CarPartModel { ~CarPartModel() {} - void Clear() {} + void Clear() { + mModel = 0; + } - int IsHidden() {} + int IsHidden() { + return this->mModel & 1; + } void Hide(int bHide) { mModel = (mModel & ~3) | (bHide ? 1 : 0); @@ -232,10 +239,12 @@ class CarPartModel { } void SetModel(struct eModel *model) { - this->mModel = reinterpret_cast(model); + this->mModel = reinterpret_cast(model) | this->IsHidden(); } - bool IsLodMissing() const {} + bool IsLodMissing() const { + return (mModel & ~3u) == 0; + } private: unsigned int mModel; // offset 0x0, size 0x4 @@ -329,15 +338,21 @@ class CarRenderInfo { float GetDeltaTime() const {} - void SetRadius(float r) {} + void SetRadius(float r) { + this->mRadius = r; + } float GetRadius() const {} - void SetCollider(const WCollider *collider) {} + void SetCollider(const WCollider *collider) { + this->mWCollider = collider; + } void SetAnimationTime(float animationTime) {} - void SetWheelWobble(unsigned int wheelInd, bool enable) {} + void SetWheelWobble(unsigned int wheelInd, bool enable) { + this->mWheelWobbleEnabled[wheelInd] = enable; + } bool GetWheelWobble(unsigned int wheelInd) {} @@ -369,9 +384,9 @@ class CarRenderInfo { void SetLights(unsigned int vehiclefx_ids) {} - bool IsLightBroken(enum VehicleFX::ID id) const {} + bool IsLightBroken(enum VehicleFX::ID id) const; - bool IsLightOn(enum VehicleFX::ID id) const {} + bool IsLightOn(enum VehicleFX::ID id) const; CarRenderInfo(RideInfo *ride_info); diff --git a/src/Speed/Indep/Src/World/CarRenderConn.cpp b/src/Speed/Indep/Src/World/CarRenderConn.cpp index e69de29bb..751f1d8b6 100644 --- a/src/Speed/Indep/Src/World/CarRenderConn.cpp +++ b/src/Speed/Indep/Src/World/CarRenderConn.cpp @@ -0,0 +1,1506 @@ +#include "Speed/Indep/Src/World/CarRenderConn.h" +#include "Speed/Indep/Src/Interfaces/SimActivities/INIS.h" +#include "Speed/Indep/Src/Physics/PhysicsInfo.hpp" +#include "Speed/Indep/Src/Sim/SimSurface.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/simsurface.h" +#include "Speed/Indep/Src/Ecstasy/EcstasyData.hpp" +#include "Speed/Indep/Src/Ecstasy/EmitterSystem.h" +#include "Speed/Indep/Libs/Support/Utility/UMath.h" + +struct Car; +struct CarPartDatabase; +extern void *gINISInstance asm("_Q33UTL11Collectionst9Singleton1Z4INIS_mInstance"); +extern unsigned int numCopsActiveView; +extern CarPartDatabase CarPartDB; +extern CarType GetCarType__15CarPartDatabaseUi(CarPartDatabase *database, unsigned int model_hash) + asm("GetCarType__15CarPartDatabaseUi"); +extern int GetAppliedAttributeIParam__7CarPartUii(const CarPart *part, unsigned int key, int default_value) + asm("GetAppliedAttributeIParam__7CarPartUii"); +extern int GetCarPartIDFromCrc(UCrc32 part_name); +extern void bRotateVector(bVector3 *dest, const bMatrix4 *m, bVector3 *v); +extern void MakeNoSkid__9SkidMaker(void *skid_maker) asm("MakeNoSkid__9SkidMaker"); +extern void MakeSkid__9SkidMakerP3CarP8bVector3T2if(void *skid_maker, Car *car, bVector3 *skid_centre, bVector3 *skid_direction, + int terrain, float intensity) + asm("MakeSkid__9SkidMakerP3CarP8bVector3T2if"); +void DeleteAllSkids(); +extern void TireState_ctor(TireState *state) asm("__9TireState"); +extern void TireState_dtor(TireState *state, int in_chrg) asm("_._9TireState"); +extern int PhysicsUpgrades_GetLevel(const Attrib::Gen::pvehicle &pvehicle, int type) + asm("GetLevel__Q27Physics8UpgradesRCQ36Attrib3Gen8pvehicleQ37Physics8Upgrades4Type"); +extern int PhysicsUpgrades_GetMaxLevel(const Attrib::Gen::pvehicle &pvehicle, int type) + asm("GetMaxLevel__Q27Physics8UpgradesRCQ36Attrib3Gen8pvehicleQ37Physics8Upgrades4Type"); +extern void VU0_Matrix4ToEuler(const UMath::Matrix4 &m, UMath::Vector3 &e) + asm("VU0_Matrix4ToEuler__FRCQ25UMath7Matrix4RQ25UMath7Vector3"); +extern float RealTimeElapsed; +extern float renderModifier; +extern int Tweak_DisableRoadNoise; +extern int NumTimesRenderTestPlayerCar; +extern CameraAnchor *RVManchor; +extern void AddXenonEffect(EmitterGroup *piggyback_fx, const Attrib::Collection *spec, const bMatrix4 *mat, const bVector4 *vel) + asm("AddXenonEffect__FP12EmitterGroupPCQ26Attrib10CollectionPCQ25UMath7Matrix4PCQ25UMath7Vector4"); +void NotifyTireStateEffectOfEmitterDelete(void *tire_state_effect, EmitterGroup *grp); + +struct TireSkidMaker { + TireSkidMaker(unsigned int value) + : mValue(value) {} + + ~TireSkidMaker() { + MakeNoSkid__9SkidMaker(this); + } + + unsigned int mValue; +} __attribute__((packed)); + +struct TireState : public bTNode { + struct Effect { + Effect() + : mNeedsLazyInit(false), // + mEmitterKey(0), // + mGroup(0), // + mMinVel(0.0f), // + mMaxVel(0.0f), // + mZeroParticleFrameCount(0) {} + + void ResetGroup() { + mGroup = 0; + mEmitterKey = 0; + mMinVel = 0.0f; + mNeedsLazyInit = true; + mZeroParticleFrameCount = 0; + mMaxVel = 0.0f; + } + + void FreeUpFX(); + void LazyInit(); + void Set(const TireEffectRecord &record); + void Update(float speed, const bVector3 *car_velocity, const bMatrix4 *car_matrix, float dT, const bVector4 &pos); + + bool mNeedsLazyInit; + unsigned int mEmitterKey; + EmitterGroup *mGroup; + float mMinVel; + float mMaxVel; + int mZeroParticleFrameCount; + }; + + TireState(); + ~TireState(); + void KillSkids(); + void DoSkids(float intensity, const bVector3 *deltaPos, const bMatrix4 *tirematrix, const bMatrix4 *bodymatrix, float skidWidth); + void DoFX(float slip, float skid, float speed, const bVector3 *car_velocity, const bMatrix4 *car_matrix, float dT); + void SetSurface(const SimSurface &surface); + void UpdateWorld(const WCollider *wc, bool rain, bool flat); + + static void *operator new(unsigned int size) { + return gFastMem.Alloc(size, nullptr); + } + + static void operator delete(void *mem, unsigned int size) { + if (mem) { + gFastMem.Free(mem, size, nullptr); + } + } + + bVector4 mPrevTirePos; + WWorldPos mWPos; + TireSkidMaker mSkidMaker; + bVector4 mTirePos; + bVector4 mGroundPos; + float mRoll; + bool mRaining; + bool mFlat; + SimSurface mSurface; + Effect mSlipFX; + Effect mSkidFX; + Effect mDriveFX; +}; + +bTList gTireStateList; + +UTL::COM::Factory::Prototype _CarRenderConn("CarRenderConn", CarRenderConn::Construct); + +static RoadNoiseRecord Tweak_BlowOutNoise asm("Tweak_BlowOutNoise"); + +struct TweakBlowOutNoiseInit { + TweakBlowOutNoiseInit() { + Tweak_BlowOutNoise.Frequency = 4.0f; + Tweak_BlowOutNoise.Amplitude = 1.0f; + Tweak_BlowOutNoise.MinSpeed = 0.0f; + Tweak_BlowOutNoise.MaxSpeed = 10.0f; + } +} TweakBlowOutNoiseInitializer; + +static void StopEffect(VehicleRenderConn::Effect *effect) { + effect->Stop(); +} + +static inline void EmitterGroupSetOldSurfaceEffectFlag(EmitterGroup *group) { + *reinterpret_cast(reinterpret_cast(group) + 0x18) |= 0x80000; +} + +static inline TireState *CreateTireState() { + TireState *state = reinterpret_cast(gFastMem.Alloc(0xe0, 0)); + + TireState_ctor(state); + return state; +} + +struct RenderPktCarOpen { + void *vtable; + unsigned int mModelHash; +}; + +struct TireStateRoadNoiseMirror { + unsigned char _pad0[0x84]; + Attrib::Gen::simsurface mSurface; +}; + +static inline const Attrib::Collection *TireState_GetSurfaceCollection(const TireState *state) { + return *reinterpret_cast(reinterpret_cast(state) + 0x88); +} + +struct CameraAnchorPovMirror { + unsigned char _pad0[0xD8]; + short mPOVType; +}; + +struct LocalReferenceMirror { + unsigned int mWorldID; + const bMatrix4 *mMatrix; + const bVector3 *mVelocity; + const bVector3 *mAcceleration; +}; + +static inline float &CarRenderInfoF32(CarRenderInfo *info, unsigned int offset) { + return *reinterpret_cast(reinterpret_cast(info) + offset); +} + +static inline unsigned int &CarRenderInfoU32(CarRenderInfo *info, unsigned int offset) { + return *reinterpret_cast(reinterpret_cast(info) + offset); +} + +static inline int &CarRenderInfoI32(CarRenderInfo *info, unsigned int offset) { + return *reinterpret_cast(reinterpret_cast(info) + offset); +} + +static inline short &CarRenderInfoS16(CarRenderInfo *info, unsigned int offset) { + return *reinterpret_cast(reinterpret_cast(info) + offset); +} + +static inline bool eIsGameViewID(int id) { + return id - 1U < 3; +} + +void NotifyTireStateEffectOfEmitterDelete(void *tire_state_effect, EmitterGroup *grp) { + TireState::Effect *effect = static_cast(tire_state_effect); + effect->ResetGroup(); +} + +void TireState::DoSkids(float intensity, const bVector3 *deltaPos, const bMatrix4 *tirematrix, const bMatrix4 *bodymatrix, float SkidWidth) { + WWorldPos *world_pos = reinterpret_cast(reinterpret_cast(this) + 0x18); + void *skid_maker = reinterpret_cast(this) + 0x54; + + if (!world_pos->OnValidFace() || intensity <= 0.3f || SkidWidth <= 0.0f) { + MakeNoSkid__9SkidMaker(skid_maker); + } else { + bMatrix4 world_matrix; + bVector3 skid_direction(-deltaPos->y, deltaPos->x, 0.0f); + bVector3 tire_direction; + bVector3 up(0.0f, 1.0f, 0.0f); + + bMulMatrix(&world_matrix, bodymatrix, tirematrix); + bNormalize(&skid_direction, &skid_direction); + bRotateVector(&tire_direction, &world_matrix, &up); + + float skid_dot = bAbs(bDot(&tire_direction, &skid_direction)); + if (skid_dot < 0.5f) { + skid_dot = 0.5f; + } + + float half_skid_width = SkidWidth * skid_dot * 0.5f; + bNormalize(&skid_direction, &skid_direction, half_skid_width); + + bVector3 tire_position(this->mTirePos.x, this->mTirePos.y, this->mTirePos.z); + bVector3 right_point = tire_position + skid_direction; + bVector3 left_point = tire_position - skid_direction; + UMath::Vector3 right_point_l; + UMath::Vector3 left_point_l; + + bConvertToBond(right_point_l, right_point); + bConvertToBond(left_point_l, left_point); + + float right_elevation = world_pos->HeightAtPoint(right_point_l); + float left_elevation = world_pos->HeightAtPoint(left_point_l); + bVector3 skid_centre(tire_position.x, tire_position.y, (right_elevation + left_elevation) * 0.5f); + + skid_direction.z = right_elevation * 0.5f - left_elevation * 0.5f; + bNormalize(&skid_direction, &skid_direction, half_skid_width); + + float skid_intensity_scale = skid_dot * intensity; + if (skid_intensity_scale < 0.3f) { + skid_intensity_scale = 0.3f; + } + + MakeSkid__9SkidMakerP3CarP8bVector3T2if(skid_maker, 0, &skid_centre, &skid_direction, 1, skid_intensity_scale); + } +} + +void TireState::Effect::FreeUpFX() { + if (this->mGroup != 0) { + *reinterpret_cast(reinterpret_cast(this->mGroup) + 0x18) |= 0x80000; + this->mGroup->UnSubscribe(); + } + + EmitterGroup *group = 0; + this->mGroup = group; + this->mNeedsLazyInit = true; + this->mZeroParticleFrameCount = 0; +} + +void TireState::Effect::LazyInit() { + if (this->mGroup != 0) { + *reinterpret_cast(reinterpret_cast(this->mGroup) + 0x18) |= 0x80000; + this->mGroup->UnSubscribe(); + } + + this->mGroup = 0; + if (this->mEmitterKey == 0 || this->mEmitterKey == 0xeec2271a) { + return; + } + + Attrib::Gen::emittergroup emitter_group_spec(this->mEmitterKey, 0, 0); + if (!emitter_group_spec.IsValid()) { + return; + } + + this->mGroup = gEmitterSystem.CreateEmitterGroup(emitter_group_spec.GetConstCollection(), 0x40000000); + if (this->mGroup != 0) { + this->mGroup->Enable(); + this->mGroup->SubscribeToDeletion(this, NotifyTireStateEffectOfEmitterDelete); + } + + this->mNeedsLazyInit = false; + this->mZeroParticleFrameCount = 0; +} + +void TireState::Effect::Set(const TireEffectRecord &record) { + unsigned int emitter_key = record.EmitterClass; + + this->mMinVel = record.MinSpeed; + this->mMaxVel = record.MaxSpeed; + if (this->mEmitterKey != emitter_key) { + this->mEmitterKey = emitter_key; + this->mNeedsLazyInit = true; + this->mZeroParticleFrameCount = 0; + } +} + +TireState::TireState() + : mWPos(0.025f), // + mSkidMaker(0) { + this->mPrevTirePos = bVector4(0.0f, 0.0f, 0.0f, 0.0f); + this->mTirePos = bVector4(0.0f, 0.0f, 0.0f, 0.0f); + this->mGroundPos = bVector4(0.0f, 0.0f, 0.0f, 0.0f); + this->mRoll = 0.0f; + this->mRaining = false; + this->mFlat = false; + this->SetSurface(SimSurface::kNull); + gTireStateList.AddTail(reinterpret_cast(this)); +} + +TireState::~TireState() { + this->KillSkids(); + this->Remove(); + + if (this->mDriveFX.mGroup != 0) { + this->mDriveFX.mGroup->UnSubscribe(); + } + + if (this->mSkidFX.mGroup != 0) { + this->mSkidFX.mGroup->UnSubscribe(); + } + + if (this->mSlipFX.mGroup != 0) { + this->mSlipFX.mGroup->UnSubscribe(); + } +} + +void TireState::KillSkids() { + MakeNoSkid__9SkidMaker(&this->mSkidMaker); +} + +void KillSkidsOnRaceRestart() { + for (TireState *state = gTireStateList.GetHead(); state != gTireStateList.EndOfList(); state = state->GetNext()) { + state->KillSkids(); + } + + DeleteAllSkids(); +} + +void TireState::Effect::Update(float speed, const bVector3 *car_velocity, const bMatrix4 *car_matrix, float dT, const bVector4 &pos) { + float intensity = 0.0f; + float speed_range = this->mMaxVel - this->mMinVel; + + if (1e-6f < speed_range) { + intensity = UMath::Ramp((speed - this->mMinVel) / speed_range, 0.0f, 1.0f); + } + + if (0.0f < intensity) { + if (this->mNeedsLazyInit) { + this->LazyInit(); + } + + if (this->mGroup != 0) { + bMatrix4 emitter_world = *car_matrix; + + emitter_world.v3 = pos; + emitter_world.v3.w = 1.0f; + this->mGroup->SetLocalWorld(&emitter_world); + this->mGroup->SetInheritVelocity(car_velocity); + this->mGroup->SetIntensity(intensity); + this->mGroup->Update(dT); + + if (this->mGroup->GetNumParticles() == 0) { + this->mZeroParticleFrameCount++; + } + + if (this->mZeroParticleFrameCount < 11) { + return; + } + } else { + return; + } + } else if (this->mGroup == 0) { + return; + } + + EmitterGroupSetOldSurfaceEffectFlag(this->mGroup); + this->mGroup->UnSubscribe(); + this->mZeroParticleFrameCount = 0; + this->mNeedsLazyInit = true; + this->mGroup = 0; +} + +void TireState::SetSurface(const SimSurface &surface) { + unsigned int slip_index; + unsigned int slide_index; + unsigned int drive_index; + + unsigned int slip_count = surface.Num_TireSlipEffects(); + unsigned int slide_count = surface.Num_TireSlideEffects(); + unsigned int drive_count = surface.Num_TireDriveEffects(); + + if (!this->mFlat || slip_count < 3) { + slip_index = 0; + if (this->mRaining) { + slip_index = slip_count > 1; + } + } else { + slip_index = 2; + } + + if (!this->mFlat || slide_count < 3) { + slide_index = 0; + if (this->mRaining) { + slide_index = slide_count > 1; + } + } else { + slide_index = 2; + } + + if (!this->mFlat || drive_count < 3) { + drive_index = 0; + if (this->mRaining) { + drive_index = drive_count > 1; + } + } else { + drive_index = 2; + } + + this->mSurface = surface; + this->mSlipFX.Set(surface.TireSlipEffects(slip_index)); + this->mDriveFX.Set(surface.TireDriveEffects(drive_index)); + this->mSkidFX.Set(surface.TireSlideEffects(slide_index)); +} + +void TireState::UpdateWorld(const WCollider *wc, bool rain, bool flat) { + UMath::Vector3 tire_pos; + UMath::Vector3 ground_pos; + + tire_pos.x = -this->mTirePos.y; + tire_pos.y = this->mTirePos.z; + tire_pos.z = this->mTirePos.x; + + this->mWPos.FindClosestFace(wc, tire_pos, true); + if (TireState_GetSurfaceCollection(this) != this->mWPos.GetSurface() || this->mRaining != rain || this->mFlat != flat) { + this->mRaining = rain; + this->mFlat = flat; + + SimSurface surface(this->mWPos.GetSurface()); + this->SetSurface(surface); + } + + ground_pos = tire_pos; + ground_pos.y = this->mWPos.HeightAtPoint(ground_pos); + this->mGroundPos.y = -ground_pos.x; + this->mGroundPos.x = ground_pos.z; + this->mGroundPos.z = ground_pos.y; +} + +void TireState::DoFX(float slip, float skid, float speed, const bVector3 *car_velocity, const bMatrix4 *car_matrix, float dT) { + if (1.1920929e-07f < slip || slip < -1.1920929e-07f || 1.1920929e-07f < skid || skid < -1.1920929e-07f || 1.1920929e-07f < speed || + speed < -1.1920929e-07f) { + goto update_fx; + } + + this->mDriveFX.FreeUpFX(); + this->mSlipFX.FreeUpFX(); + this->mSkidFX.FreeUpFX(); + return; + +update_fx: + if (this->mWPos.OnValidFace()) { + { + SimSurface surface(this->mWPos.GetSurface()); + this->SetSurface(surface); + } + + this->mSlipFX.Update(slip, car_velocity, car_matrix, dT, this->mGroundPos); + this->mSkidFX.Update(skid, car_velocity, car_matrix, dT, this->mGroundPos); + + if (numCopsActiveView < 2) { + bool inis_active = gINISInstance != 0; + if (!inis_active) { + this->mDriveFX.Update(speed, car_velocity, car_matrix, dT, this->mGroundPos); + } + } + } +} + +Sim::Connection *CarRenderConn::Construct(const Sim::ConnectionData &data) { + RenderConn::Pkt_Car_Open *open = reinterpret_cast(data.pkt); + int car_type = GetCarType__15CarPartDatabaseUi(&CarPartDB, open->mModelHash); + + if (car_type == -1 || car_type > 0x53) { + return 0; + } + + return new CarRenderConn(data, static_cast(car_type), open); +} + +CarRenderConn::CarRenderConn(const Sim::ConnectionData &data, CarType ct, RenderConn::Pkt_Car_Open *oc) + : VehicleRenderConn(data, ct), // + IAttributeable(), // + mAttributes(static_cast(0), 0, 0), // + mPhysics(static_cast(0), 0, 0), // + mTirePhysics(static_cast(0), 0, 0), // + mExtraBodyAngle(UMath::Vector2::kZero), // + mFlatTireAngle(UMath::Vector3::kZero), // + mWheelHop(UMath::Vector3::kZero), // + mRoadNoise(UMath::Vector3::kZero), // + mEnginePower(0.0f), // + mAnimTime(0.0f), // + mShiftPitchAngle(0.0f), // + mEngineTorqueAngle(0.0f), // + mEngineVibrationAngle(0.0f), // + mEnginePitchAngle(0.0f), // + mPerfectLaunchTimer(0.0f), // + mMaxWheelRenderDeltaAngle(0.0f), // + mLastRenderFrame(0), // + mLastVisibleFrame(0), // + mDistanceToView(0.0f), // + mFlashTime(0.0f), // + mShifting(0.0f), // + mDoContrailEffect(false), // + mUsage(oc->mUsage), // + mFlags(0) { + unsigned int i; + + PSMTX44Identity(*reinterpret_cast(&this->mRenderMatrix)); + { + Attrib::Gen::pvehicle physics(oc->mPhysicsKey, 0, nullptr); + this->mPhysics = physics; + } + { + Attrib::Gen::tires tire_physics(this->mPhysics.tires(0), 0, nullptr); + this->mTirePhysics = tire_physics; + } + this->mSteering[0] = 0.0f; + this->mSteering[1] = 0.0f; + + this->mPartState.Clear(); + + for (i = 0; i < 4; i++) { + TireState *state = new TireState; + this->mTireState[i] = state; + this->VehicleRenderConn::mAttributes.TireOffsets(reinterpret_cast(this->mTirePositions[i]), i); + { + float tire_radius = UMath::Max(this->mTirePositions[i].w, 0.1f); + this->mTireRadius[i] = tire_radius; + } + + this->mTirePositions[i].w = 0.0f; + this->mTireState[i]->mPrevTirePos = bVector4(0.0f, 0.0f, 0.0f, 0.0f); + { + int is_front = i < 2; + this->mPhysicsRadius[i] = Physics::Info::WheelDiameter(this->mTirePhysics, is_front) * 0.5f; + } + } + + this->mMaxWheelRenderDeltaAngle = 0.017453f; + this->Load(oc->mWorldID, oc->mUsage, !oc->mSpoolLoad, oc->mCustomizations); + this->SetFlag(CF_ISPLAYER, oc->mUsage == 0); + + if (this->mUsage == 0 || this->mUsage == 2) { + int blowoff_level = PhysicsUpgrades_GetLevel(this->mPhysics, 4); + int blowoff_max_level = PhysicsUpgrades_GetMaxLevel(this->mPhysics, 4); + + if (blowoff_level != 0 || blowoff_level == blowoff_max_level) { + this->SetFlag(CF_BLOWOFF, true); + } + } +} + +void CarRenderConn::OnAttributeChange(const Attrib::Collection *collection, unsigned int attribkey) {} + +CarRenderConn::~CarRenderConn() { + while (!this->mPipeEffects.IsEmpty()) { + VehicleRenderConn::Effect *effect = this->mPipeEffects.GetHead(); + + this->mPipeEffects.RemoveHead(); + delete effect; + } + + while (!this->mEngineEffects.IsEmpty()) { + VehicleRenderConn::Effect *effect = this->mEngineEffects.GetHead(); + + this->mEngineEffects.RemoveHead(); + delete effect; + } + + for (int i = 0; i < 4; i++) { + if (this->mTireState[i] != 0) { + TireState_dtor(this->mTireState[i], 3); + this->mTireState[i] = 0; + } + } +} + +bool CarRenderConn::TestVisibility(float distance) { + if (INIS::Get() != nullptr) { + return true; + } + + if (this->IsViewAnchor()) { + return true; + } + + bool visible = false; + + if (this->mLastVisibleFrame >= this->mLastRenderFrame && this->mLastVisibleFrame != 0) { + visible = true; + } + + if (!visible) { + return false; + } + + return this->mDistanceToView <= distance; +} + +void RenderConn::Pkt_Car_Service::HidePart(const UCrc32 &partname) { + unsigned int part_id = static_cast(GetCarPartIDFromCrc(partname)); + + if (part_id - 1 < 0x4B) { + this->mPartState.Set(part_id); + } +} + +void CarRenderConn::OnEvent(EventID id) { + switch (id) { + case E_MISS_SHIFT: + this->SetFlag(CF_MISSSHIFT, true); + break; + + case E_DOWNSHIFT: + this->mShifting = -1.0f; + break; + + case E_UPSHIFT: + this->mShifting = 1.0f; + break; + + default: + return; + } +} + +void CarRenderConn::UpdateSteering(float dT, const RenderConn::Pkt_Car_Service &data) { + if (!this->TestVisibility(renderModifier * 100.0f)) { + this->mSteering[1] = 0.0f; + this->mSteering[0] = 0.0f; + return; + } + + const float *max_steering = reinterpret_cast(this->VehicleRenderConn::mAttributes.GetAttributePointer(0xa9633fde, 0)); + if (max_steering == 0) { + max_steering = reinterpret_cast(Attrib::DefaultDataArea(sizeof(float))); + } + float steering_limit = *max_steering * 0.017453f; + + const float *steering_speed = + reinterpret_cast(this->VehicleRenderConn::mAttributes.GetAttributePointer(0x79356463, 0)); + if (steering_speed == 0) { + steering_speed = reinterpret_cast(Attrib::DefaultDataArea(sizeof(float))); + } + + float steering_delta = *steering_speed * 0.017453f * dT; + + for (int i = 0; i < 2; i++) { + float target = data.mSteering[i]; + float current = this->mSteering[i]; + + if (current < target - steering_delta) { + current += steering_delta; + } else if (target + steering_delta < current) { + current -= steering_delta; + } else { + current = target; + } + + if (steering_limit < current) { + current = steering_limit; + } + if (current < -steering_limit) { + current = -steering_limit; + } + + this->mSteering[i] = current; + } +} + +void CarRenderConn::UpdateParts(float dT, const RenderConn::Pkt_Car_Service &data) { + if (this->mPartState != data.mPartState) { + unsigned int i = 0; + + while (i < 0x4c) { + bool hide = data.mPartState.Test(i); + + if (hide != this->mPartState.Test(i)) { + if (hide) { + this->HidePart(static_cast(i)); + } else { + this->ShowPart(static_cast(i)); + } + } + + i++; + } + } + + this->mPartState = data.mPartState; +} + +void CarRenderConn::AddRoadNoise(float speed, unsigned int tires, const RoadNoiseRecord &noise) { + if (noise.Frequency * noise.Amplitude * noise.MaxSpeed > 0.0f) { + float intensity = UMath::Ramp(speed, noise.MinSpeed, noise.MaxSpeed); + float frequency = noise.Frequency * intensity; + float amplitude = this->GetAttributes().RoadNoise(0) * DEG2RAD(noise.Amplitude) * intensity; + + unsigned int do_front = tires & 3; + unsigned int do_rear = tires & 0xC; + unsigned int do_roll = do_front | do_rear; + unsigned int do_right = tires & 9; + unsigned int do_left = tires & 6; + + float noise_pitch = 0.0f; + if (do_roll) { + noise_pitch = amplitude * UMath::Sinr(this->mAnimTime * frequency) * 0.5f; + if (!do_front) { + noise_pitch = UMath::Abs(noise_pitch); + } + if (!do_rear) { + noise_pitch = -UMath::Abs(noise_pitch); + } + } + + float noise_roll = 0.0f; + if (do_roll) { + noise_roll = amplitude * UMath::Sinr((this->mAnimTime + 0.33f) * frequency); + if (!do_right) { + noise_roll = UMath::Abs(noise_roll); + } + if (!do_left) { + noise_roll = -UMath::Abs(noise_roll); + } + } + + this->mRoadNoise.y += noise_pitch; + this->mRoadNoise.x += noise_roll; + } +} + +void CarRenderConn::UpdateRoadNoise(float dT, float carspeed, const RenderConn::Pkt_Car_Service &data) { + UMath::Clear(this->mRoadNoise); + + if (this->TestVisibility(renderModifier * 30.0f) && Tweak_DisableRoadNoise == 0) { + RoadNoiseRecord roughness; + + for (unsigned int i = 0; i < 4; i++) { + if (((data.mGroundState >> i) & 1U) != 0) { + const RoadNoiseRecord &tirenoise = + reinterpret_cast(this->mTireState[i])->mSurface.RenderNoise(); + + if (tirenoise.Amplitude * tirenoise.Frequency > roughness.Amplitude * roughness.Frequency) { + roughness = tirenoise; + } + } + } + + this->AddRoadNoise(carspeed, static_cast(data.mGroundState), roughness); + this->AddRoadNoise(carspeed, static_cast(data.mGroundState & data.mBlowOuts), Tweak_BlowOutNoise); + + float siny = UMath::Sinr(this->mRoadNoise.y); + float sinx = UMath::Sinr(this->mRoadNoise.x); + + this->mRoadNoise.z = UMath::Max(this->mTirePositions[0].x * siny, 0.0f); + this->mRoadNoise.z = UMath::Max(this->mTirePositions[3].x * siny, this->mRoadNoise.z); + this->mRoadNoise.z = UMath::Max(this->mTirePositions[0].y * sinx, this->mRoadNoise.z); + this->mRoadNoise.z = UMath::Max(this->mTirePositions[1].y * sinx, this->mRoadNoise.z); + } +} + +void CarRenderConn::UpdateEngineAnimation(float dT, const RenderConn::Pkt_Car_Service &data) { + if (!this->TestVisibility(renderModifier * 30.0f)) { + this->mEnginePower = 0.0f; + this->mEngineVibrationAngle = 0.0f; + this->mEngineTorqueAngle = 0.0f; + this->mShiftPitchAngle = 0.0f; + this->mShifting = 0.0f; + this->mEnginePitchAngle = 0.0f; + return; + } + + float delta; + float rev_accel; + + if (this->mShifting != 0.0f) { + const float car_speed = bLength(this->GetVelocity()); + const float shift_speed = DEG2RAD(this->GetAttributes().ShiftSpeed(0)); + const float max_pitch = DEG2RAD(this->GetAttributes().ShiftAngle(0)); + const int gear = data.mGear - 2; + + if (0.0f < shift_speed && 0.0f < max_pitch && gear >= 0 && 10.0f < car_speed) { + float fwd_accel = bDot(this->GetAcceleration(), reinterpret_cast(&this->mRenderMatrix.v0)); + float gear_ratio = UMath::Ramp(UMath::Abs(fwd_accel * 0.10204081f), 0.1f, 0.5f); + + rev_accel = UMath::Pow(0.95f, static_cast(gear)); + delta = UMath::Sina(UMath::Abs(this->mShifting) * 0.5f) * max_pitch * rev_accel * gear_ratio; + + this->mShiftPitchAngle = delta; + if (this->mShifting < 0.0f) { + this->mShifting = UMath::Min(this->mShifting - (dT * shift_speed) / max_pitch, 0.0f); + this->mShiftPitchAngle *= 0.25f; + } else if (0.0f < this->mShifting) { + this->mShifting = UMath::Max(this->mShifting + (dT * shift_speed) / max_pitch, 0.0f); + } + } else { + this->mShifting = 0.0f; + this->mShiftPitchAngle = 0.0f; + } + } else { + this->mShiftPitchAngle = 0.0f; + } + + delta = data.mEnginePower - this->mEnginePower; + if (UMath::Abs(delta) < 0.005f) { + delta = 0.0f; + } + + this->mEnginePower = UMath::Clamp(this->mEnginePower + delta, 0.0f, 1.0f); + this->mEnginePitchAngle = data.mAnimatedCarPitch; + + if (data.mAnimatedCarRoll != 0.0f) { + this->mEngineTorqueAngle = data.mAnimatedCarRoll; + } else { + float acceleration = (delta / dT) * this->GetAttributes().EngineRev(0); + float max_rev = data.mEnginePower * data.mEngineSpeed * DEG2RAD(this->GetAttributes().EngineRevAngle(0)); + float rev_speed = DEG2RAD(this->GetAttributes().EngineRevSpeed(0)) * dT; + float desired_angle = UMath::Ramp(acceleration, 0.0f, 0.2f) * max_rev; + + if (this->mEngineTorqueAngle < desired_angle) { + this->mEngineTorqueAngle = UMath::Min(this->mEngineTorqueAngle + rev_speed, desired_angle); + } else { + this->mEngineTorqueAngle = UMath::Max(this->mEngineTorqueAngle - rev_speed, desired_angle); + } + + this->mEngineTorqueAngle = UMath::Clamp(this->mEngineTorqueAngle, 0.0f, max_rev); + } + + if (data.mAnimatedCarShake != 0.0f) { + this->mEngineVibrationAngle = data.mAnimatedCarShake; + } else { + float max_vibration = DEG2RAD(this->GetAttributes().EngineVibrationMax(0)); + float min_vibration = DEG2RAD(this->GetAttributes().EngineVibrationMin(0)); + float vibration_freq = this->GetAttributes().EngineVibrationFreq(0); + + this->mEngineVibrationAngle = + data.mEngineSpeed * bSin(this->mAnimTime * vibration_freq * 6.2831855f) * (min_vibration + max_vibration * data.mEngineSpeed); + } +} + +void CarRenderConn::UpdateBodyAnimation(float dT, const RenderConn::Pkt_Car_Service &data) { + if (!this->TestVisibility(renderModifier * 80.0f)) { + this->mExtraBodyAngle = UMath::Vector2::kZero; + return; + } + + const bVector3 *accel = this->GetAcceleration(); + float left_accel = bDot(accel, reinterpret_cast(&this->mRenderMatrix.v1)) * 0.10204081f; + float fwd_accel = bDot(accel, reinterpret_cast(&this->mRenderMatrix.v0)) * 0.10204081f; + const CarBodyMotion &pitch_control = + fwd_accel < 0.0f ? this->GetAttributes().BodyDive() : this->GetAttributes().BodySquat(); + const CarBodyMotion &roll_control = this->GetAttributes().BodyRoll(); + float max_pitch = data.mExtraBodyPitch * DEG2RAD(pitch_control.DegPerG); + float rate_pitch = DEG2RAD(pitch_control.DegPerSec) * dT; + float max_roll = data.mExtraBodyRoll * DEG2RAD(roll_control.DegPerG); + float rate_roll = DEG2RAD(roll_control.DegPerSec) * dT; + + if (UMath::Abs(left_accel) < 0.2f) { + left_accel = 0.0f; + } + + if (UMath::Abs(fwd_accel) < 0.2f) { + fwd_accel = 0.0f; + } + + float dest_angle_x = (max_roll + max_roll) * (UMath::Ramp(left_accel, -roll_control.MaxGs, roll_control.MaxGs) - 0.5f); + float dest_angle_y = + (max_pitch + max_pitch) * (UMath::Ramp(-fwd_accel * 0.10204081f, -pitch_control.MaxGs, pitch_control.MaxGs) - 0.5f); + float speed = bLength(this->GetVelocity()); + + if (speed < 1.0f) { + dest_angle_x = 0.0f; + dest_angle_y = 0.0f; + } + + if (dest_angle_x > this->mExtraBodyAngle.x) { + this->mExtraBodyAngle.x += rate_roll; + this->mExtraBodyAngle.x = UMath::Min(this->mExtraBodyAngle.x, dest_angle_x); + } else if (dest_angle_x < this->mExtraBodyAngle.x) { + this->mExtraBodyAngle.x -= rate_roll; + this->mExtraBodyAngle.x = UMath::Max(this->mExtraBodyAngle.x, dest_angle_x); + } + + if (dest_angle_y > this->mExtraBodyAngle.y) { + this->mExtraBodyAngle.y += rate_pitch; + this->mExtraBodyAngle.y = UMath::Min(this->mExtraBodyAngle.y, dest_angle_y); + } else if (dest_angle_y < this->mExtraBodyAngle.y) { + this->mExtraBodyAngle.y -= rate_pitch; + this->mExtraBodyAngle.y = UMath::Max(this->mExtraBodyAngle.y, dest_angle_y); + } +} + +void CarRenderConn::BuildRenderMatrix(float dT) { + bVector4 offset(this->GetModelOffset()); + CarRenderInfo *carRenderInfo = this->GetRenderInfo(); + + if (0.0f < dT) { + UMath::Vector3 e0; + UMath::Vector3 e1; + UMath::Vector3 v; + UMath::Vector3 v0; + UMath::Vector3 v1; + + VU0_Matrix4ToEuler(reinterpret_cast(this->mRenderMatrix), e0); + VU0_Matrix4ToEuler(reinterpret_cast(*this->GetBodyMatrix()), e1); + UMath::Sub(e1, e0, v); + UMath::Scale(v, (1.0f / dT) * 6.2831855f); + CarRenderInfoF32(carRenderInfo, 0x14) = v.x; + CarRenderInfoF32(carRenderInfo, 0x18) = v.y; + CarRenderInfoF32(carRenderInfo, 0x1C) = v.z; + + v0.x = CarRenderInfoF32(carRenderInfo, 0x4); + v0.y = CarRenderInfoF32(carRenderInfo, 0x8); + v0.z = CarRenderInfoF32(carRenderInfo, 0xC); + v1 = reinterpret_cast(*this->GetVelocity()); + UMath::Sub(v1, v0, v); + UMath::Scale(v, 1.0f / dT); + CarRenderInfoF32(carRenderInfo, 0x24) = v.x; + CarRenderInfoF32(carRenderInfo, 0x28) = v.y; + CarRenderInfoF32(carRenderInfo, 0x2C) = v.z; + CarRenderInfoF32(carRenderInfo, 0x4) = v1.x; + CarRenderInfoF32(carRenderInfo, 0x8) = v1.y; + CarRenderInfoF32(carRenderInfo, 0xC) = v1.z; + } + + this->mRenderMatrix = *this->GetBodyMatrix(); + + bVector4 rotOffset; + eMulVector(&rotOffset, this->GetBodyMatrix(), &offset); + this->mRenderMatrix.v3.x -= rotOffset.x; + this->mRenderMatrix.v3.y -= rotOffset.y; + this->mRenderMatrix.v3.z -= rotOffset.z; +} + +void CarRenderConn::UpdateTires(float dT, float carspeed, const RenderConn::Pkt_Car_Service &data) { + float wheel_hop_roll = 0.0f; + float wheel_hop_pitch = 0.0f; + float tire_hop = 0.0f; + bool flatten_tires = false; + bool hop_wheels = false; + bool is_view_anchor; + bool can_do_fx; + CarRenderInfo *car_render_info; + + this->mFlatTireAngle = UMath::Vector3::kZero; + + if (this->TestVisibility(renderModifier * 30.0f)) { + flatten_tires = true; + float hop_scale = this->GetAttributes().WheelHopScale(0); + if (0.0f < data.mExtraBodyPitch && 0.0f < hop_scale) { + hop_scale *= hop_scale; + + wheel_hop_roll = hop_scale * data.mExtraBodyPitch * DEG2RAD(0.15f) * UMath::Abs(bSin(this->mAnimTime + 37.699112f)); + wheel_hop_pitch = hop_scale * data.mExtraBodyPitch * DEG2RAD(0.2f) * UMath::Abs(bSin(this->mAnimTime * 150.79645f)); + tire_hop = hop_scale * data.mExtraBodyPitch * DEG2RAD(0.3f) * UMath::Abs(bSin((this->mAnimTime + 0.5f) * 150.79645f)); + hop_wheels = true; + } + } + + this->mWheelHop = UMath::Vector3::kZero; + is_view_anchor = this->IsViewAnchor(); + can_do_fx = this->TestVisibility(renderModifier * 80.0f); + car_render_info = this->GetRenderInfo(); + (void)is_view_anchor; + + for (unsigned int i = 0; i < 4; i++) { + const unsigned int axle = i >> 1; + const bool onground = ((data.mGroundState >> i) & 1U) != 0; + const bool is_flat = ((data.mBlowOuts >> i) & 1U) != 0; + TireState *state = this->mTireState[i]; + float compression = data.mCompressions[i] + (this->mTireRadius[i] - this->mPhysicsRadius[i]); + float dW = UMath::Clamp((data.mWheelSpeed[i] / this->mTireRadius[i]) * dT, -this->mMaxWheelRenderDeltaAngle, + this->mMaxWheelRenderDeltaAngle); + + eIdentity(&this->mTireMatrices[i]); + eIdentity(&this->mBrakeMatrices[i]); + + state->mRoll += dW; + if (6.2831855f <= state->mRoll) { + state->mRoll -= 6.2831855f; + } else if (state->mRoll <= -6.2831855f) { + state->mRoll += 6.2831855f; + } + + eRotateY(&this->mTireMatrices[i], &this->mTireMatrices[i], bRadToAng(state->mRoll)); + if (i < 2) { + eRotateZ(&this->mTireMatrices[i], &this->mTireMatrices[i], bRadToAng(this->mSteering[i])); + eRotateZ(&this->mBrakeMatrices[i], &this->mBrakeMatrices[i], bRadToAng(this->mSteering[i])); + } + + if (flatten_tires && is_flat) { + compression += -0.12f; + float x_angle = UMath::Atan2r(0.12f, UMath::Abs(this->mTirePositions[i].y)) * -3.1415927f; + float y_angle = UMath::Atan2r(0.12f, UMath::Abs(this->mTirePositions[i].x)) * 3.1415927f; + + if (this->mTirePositions[i].y < 0.0f) { + x_angle = -x_angle; + } + if (this->mTirePositions[i].x < 0.0f) { + y_angle = -y_angle; + } + + this->mFlatTireAngle.x += x_angle; + this->mFlatTireAngle.y += y_angle; + this->mFlatTireAngle.z += -0.03f; + } + + if (i > 1 && hop_wheels && onground) { + float hop_speed_scale; + + if (0.0f < data.mTireSlip[i]) { + hop_speed_scale = (data.mTireSlip[i] - 1.0f) / 4.0f; + } else { + hop_speed_scale = (-data.mTireSlip[i] - 5.0f) / 15.0f; + } + + hop_speed_scale = UMath::Ramp(hop_speed_scale, 0.0f, 1.0f); + this->mWheelHop.y = wheel_hop_pitch * hop_speed_scale; + this->mWheelHop.z = + UMath::Max(this->mWheelHop.z, this->mTirePositions[0].x * UMath::Sinr(wheel_hop_pitch * hop_speed_scale)); + this->mWheelHop.x = wheel_hop_roll * hop_speed_scale; + compression += bSin(tire_hop * hop_speed_scale) * (this->mTirePositions[0].x - this->mTirePositions[i].x); + } + + this->mBrakeMatrices[i].v3.x = this->mTirePositions[i].x; + this->mBrakeMatrices[i].v3.z += this->mTirePositions[i].z + compression; + this->mBrakeMatrices[i].v3.y = this->mTirePositions[i].y; + + this->mTireMatrices[i].v3.x = this->mTirePositions[i].x; + this->mTireMatrices[i].v3.z += this->mTirePositions[i].z + compression; + this->mTireMatrices[i].v3.y = this->mTirePositions[i].y; + + if (can_do_fx) { + car_render_info->SetWheelWobble(i, (data.mBlowOuts >> i) & 1U); + } + + eMulVector(&state->mTirePos, &this->mRenderMatrix, &this->mTireMatrices[i].v3); + state->UpdateWorld(this->GetWCollider(), this->GetFlag(CF_ISRAINING), is_flat); + + if (onground) { + if (can_do_fx) { + float skid = UMath::Max(UMath::Abs(data.mTireSkid[i] * 0.05f) - 0.1f, 0.0f); + float slip = UMath::Max(UMath::Abs(data.mTireSlip[i] * 0.2f) - 0.1f, 0.0f); + float intensity = UMath::Sqrt(skid * skid + slip * slip); + + if (0.0f < intensity) { + bVector4 delta_pos; + float tire_skid_width; + + bSub(&delta_pos, &state->mTirePos, &state->mPrevTirePos); + tire_skid_width = this->GetAttributes().TireSkidWidth(i); + + state->DoSkids(intensity, reinterpret_cast(&delta_pos), &this->mTireMatrices[i], &this->mRenderMatrix, + tire_skid_width); + } else { + state->KillSkids(); + } + + state->DoFX(data.mTireSlip[i] * this->GetAttributes().SlipFX(axle), + data.mTireSkid[i] * this->GetAttributes().SkidFX(axle), carspeed, + this->GetVelocity(), &this->mRenderMatrix, dT); + } else { + state->KillSkids(); + } + } else { + state->KillSkids(); + } + + state->mPrevTirePos = state->mTirePos; + } +} + +void CarRenderConn::UpdateRenderMatrix(float dT) { + if (!this->TestVisibility(renderModifier * 80.0f)) { + return; + } + + bMatrix4 tire_matrices[4]; + bMatrix4 brake_matrices[4]; + for (int i = 0; i < 4; i++) { + eMulMatrix(&tire_matrices[i], &this->mTireMatrices[i], &this->mRenderMatrix); + eMulMatrix(&brake_matrices[i], &this->mBrakeMatrices[i], &this->mRenderMatrix); + } + + float rotate_x = + this->mExtraBodyAngle.x + this->mWheelHop.x + this->mFlatTireAngle.x + this->mRoadNoise.y + this->mEngineTorqueAngle + this->mEngineVibrationAngle; + float rotate_y = this->GetAttributes().ExtraPitch(0) * 0.017453f + this->mExtraBodyAngle.y + this->mWheelHop.y + this->mFlatTireAngle.y + + this->mRoadNoise.x + this->mEnginePitchAngle + this->mShiftPitchAngle; + float ride_height = this->GetAttributes().RideHeight() * 0.0254f + this->mWheelHop.z + this->mRoadNoise.z + this->mFlatTireAngle.z; + + bMatrix4 identity; + bMatrix4 x_rotation; + bMatrix4 y_rotation; + bMatrix4 rotation; + bMatrix4 old_render_matrix; + bMatrix4 inverse_matrix; + PSMTX44Identity(*reinterpret_cast(&identity)); + eRotateX(&x_rotation, &identity, static_cast(rotate_x * 10430.378f)); + eRotateY(&y_rotation, &identity, static_cast(rotate_y * 10430.378f)); + eMulMatrix(&rotation, &x_rotation, &y_rotation); + + PSMTX44Copy(*reinterpret_cast(&this->mRenderMatrix), *reinterpret_cast(&old_render_matrix)); + eMulMatrix(&this->mRenderMatrix, &rotation, &old_render_matrix); + + this->mRenderMatrix.v3.x += this->mRenderMatrix.v2.x * ride_height; + this->mRenderMatrix.v3.y += this->mRenderMatrix.v2.y * ride_height; + this->mRenderMatrix.v3.z += this->mRenderMatrix.v2.z * ride_height; + + bInvertMatrix(&inverse_matrix, &this->mRenderMatrix); + for (int i = 0; i < 4; i++) { + eMulMatrix(&this->mTireMatrices[i], &tire_matrices[i], &inverse_matrix); + eMulMatrix(&this->mBrakeMatrices[i], &brake_matrices[i], &inverse_matrix); + } + + float wheel_well = this->GetAttributes().WheelWell(0) * 0.0254f; + if (0.0f < wheel_well) { + float max_wheel = this->mTireMatrices[0].v3.y + this->mTireRadius[0]; + for (int i = 1; i < 4; i++) { + float wheel_height = this->mTireMatrices[i].v3.y + this->mTireRadius[i]; + if (max_wheel < wheel_height) { + max_wheel = wheel_height; + } + } + + if (wheel_well < max_wheel) { + float delta = max_wheel - wheel_well; + this->mRenderMatrix.v3.x += this->mRenderMatrix.v2.x * delta; + this->mRenderMatrix.v3.y += this->mRenderMatrix.v2.y * delta; + this->mRenderMatrix.v3.z += this->mRenderMatrix.v2.z * delta; + for (int i = 0; i < 4; i++) { + this->mTireMatrices[i].v3.y -= delta; + this->mBrakeMatrices[i].v3.y -= delta; + } + } + } + + (void)dT; +} + +void CarRenderConn::Update(const RenderConn::Pkt_Car_Service &data, float dT) { + if (this->CanUpdate()) { + CarRenderInfo *render_info = this->mRenderInfo; + + if (render_info == 0) { + return; + } + + render_info->SetDamageInfo(*reinterpret_cast(&data.mDamageInfo)); + CarRenderInfoF32(render_info, 0x1754) = dT; + CarRenderInfoU32(render_info, 0x1608) = data.mLights; + CarRenderInfoU32(render_info, 0x160C) = data.mBrokenLights; + int flashing = data.mFlashing; + + CarRenderInfoI32(render_info, 0x1770) = flashing; + if (flashing != 0) { + CarRenderInfoF32(render_info, 0x1774) += RealTimeElapsed; + if (0.05f < CarRenderInfoF32(render_info, 0x1774)) { + CarRenderInfoF32(render_info, 0x1774) -= 0.12f; + } + } + + CarRenderInfoU32(render_info, 0x1170) = data.mNos; + CarRenderInfoS16(render_info, 0x1174) = static_cast(this->mSteering[0] * 10430.378f); + CarRenderInfoS16(render_info, 0x1176) = static_cast(this->mSteering[1] * 10430.378f); + float carspeed = bLength(this->GetVelocity()); + + this->mAnimTime += dT; + if (10.0f <= this->mAnimTime) { + this->mAnimTime -= 10.0f; + } + + CarRenderInfoF32(render_info, 0x0) = this->mAnimTime; + this->UpdateParts(dT, data); + this->BuildRenderMatrix(dT); + this->SetFlag(CF_ISRAINING, this->CheckForRain()); + this->UpdateSteering(dT, data); + this->UpdateTires(dT, carspeed, data); + this->UpdateRoadNoise(dT, carspeed, data); + this->UpdateBodyAnimation(dT, data); + this->UpdateEngineAnimation(dT, data); + if (this->GetFlag(CF_ISPLAYER)) { + this->UpdateContrails(data, dT); + } + this->UpdateRenderMatrix(dT); + this->UpdateEffects(data, dT); + this->VehicleRenderConn::Update(dT); + } +} + +void CarRenderConn::UpdateContrails(const RenderConn::Pkt_Car_Service &data, float dT) { + float carspeed = bLength(this->GetVelocity()); + + if (data.mNos || 44.0f <= carspeed) { + if (!INIS::Exists()) { + this->mDoContrailEffect = true; + return; + } + } + + this->mDoContrailEffect = false; +} + +void CarRenderConn::UpdateEffects(const RenderConn::Pkt_Car_Service &data, float dT) { + if (!this->TestVisibility(renderModifier * 80.0f)) { + void (*stop_effect)(VehicleRenderConn::Effect *) = StopEffect; + VehicleRenderConn::Effect *pipe_effect = this->mPipeEffects.GetHead(); + VehicleRenderConn::Effect *pipe_effect_end = this->mPipeEffects.EndOfList(); + + for (;;) { + if (pipe_effect == pipe_effect_end) { + break; + } + stop_effect(pipe_effect); + pipe_effect = pipe_effect->GetNext(); + } + + VehicleRenderConn::Effect *engine_effect = this->mEngineEffects.GetHead(); + VehicleRenderConn::Effect *engine_effect_end = this->mEngineEffects.EndOfList(); + for (;;) { + if (engine_effect == engine_effect_end) { + break; + } + stop_effect(engine_effect); + engine_effect = engine_effect->GetNext(); + } + return; + } + + unsigned int damage_key = this->GetAttributes().DamageEffect(0).GetCollectionKey(); + unsigned int death_key = this->GetAttributes().DeathEffect(0).GetCollectionKey(); + unsigned int engine_key = this->GetAttributes().EngineBlownEffect(0).GetCollectionKey(); + unsigned int missshift_key = this->GetAttributes().MissShiftEffect(0).GetCollectionKey(); + unsigned int nos_key = this->GetAttributes().NOSEffect(0).GetCollectionKey(); + + for (VehicleRenderConn::Effect *pipe_effect = this->mPipeEffects.GetHead(); pipe_effect != this->mPipeEffects.EndOfList(); + pipe_effect = pipe_effect->GetNext()) { + if (data.mNos) { + pipe_effect->Update(&this->mRenderMatrix, nos_key, dT, 1.0f, this->GetVelocity()); + } else if (this->GetFlag(CF_MISSSHIFT)) { + pipe_effect->Fire(&this->mRenderMatrix, missshift_key, 1.0f, this->GetVelocity()); + } else if (this->GetFlag(CF_BLOWOFF) && this->mShifting != 0.0f) { + pipe_effect->Update(&this->mRenderMatrix, nos_key, dT, 1.0f, this->GetVelocity()); + } else { + pipe_effect->Stop(); + } + } + + for (VehicleRenderConn::Effect *engine_effect = this->mEngineEffects.GetHead(); engine_effect != this->mEngineEffects.EndOfList(); + engine_effect = engine_effect->GetNext()) { + if (death_key != 0 && data.mHealth <= 0.0f) { + engine_effect->Update(&this->mRenderMatrix, death_key, dT, 1.0f, this->GetVelocity()); + } else if (damage_key != 0 && data.mHealth <= 1.0f) { + engine_effect->Update(&this->mRenderMatrix, damage_key, dT, 1.0f, this->GetVelocity()); + } else if (data.mEngineBlown) { + engine_effect->Update(&this->mRenderMatrix, engine_key, dT, 1.0f, this->GetVelocity()); + } else { + engine_effect->Stop(); + } + } + + this->SetFlag(CF_MISSSHIFT, false); +} + +void CarRenderConn::Hide(bool b) { + if (this->GetFlag(CF_HIDDEN) != b) { + this->SetFlag(CF_HIDDEN, b); + if (b) { + this->mAnimTime = 0.0f; + for (int i = 0; i < 4; i++) { + this->mTireState[i]->KillSkids(); + } + + this->mHide = true; + + for (VehicleRenderConn::Effect *effect = this->mEngineEffects.GetHead(); effect != this->mEngineEffects.EndOfList(); + effect = effect->GetNext()) { + StopEffect(effect); + } + + for (VehicleRenderConn::Effect *effect = this->mPipeEffects.GetHead(); effect != this->mPipeEffects.EndOfList(); + effect = effect->GetNext()) { + StopEffect(effect); + } + } + } +} + +void CarRenderConn::OnFetch(float dT) { + bool in_view = false; + + if ((this->mLastVisibleFrame >= this->mLastRenderFrame && this->mLastVisibleFrame != 0) || this->IsViewAnchor()) { + in_view = true; + } + + RenderConn::Pkt_Car_Service pkt(in_view, this->mDistanceToView); + if (this->Service(&pkt)) { + this->Hide(false); + this->Update(pkt, dT); + } else { + this->Hide(true); + } +} + +void CarRenderConn::OnLoaded(CarRenderInfo *carrender_info) { + this->VehicleRenderConn::OnLoaded(carrender_info); + if (carrender_info == 0) { + return; + } + + if (carrender_info->pRideInfo) { + CarPart *part_rim = carrender_info->pRideInfo->GetPart(0x42); + if (part_rim) { + unsigned int numSpokes = static_cast( + bAbs(GetAppliedAttributeIParam__7CarPartUii(part_rim, 0x1b0ea1a9, 0))); + + if (numSpokes == 0) { + numSpokes = bAbs(this->VehicleRenderConn::mAttributes.WheelSpokeCount()); + } + + if (numSpokes != 0) { + float spoke_count = static_cast(numSpokes); + spoke_count += spoke_count; + this->mMaxWheelRenderDeltaAngle = DEG2RAD(360.0f / spoke_count - 1.0f); + } + } + } + + carrender_info->InitEmitterPositions(this->mTirePositions); + + if (this->mPipeEffects.IsEmpty()) { + for (CarEmitterPosition *emitter_position = carrender_info->EmitterPositionList[10].GetHead(); + emitter_position != carrender_info->EmitterPositionList[10].EndOfList(); emitter_position = emitter_position->GetNext()) { + ePositionMarker *position_marker = emitter_position->PositionMarker; + if (position_marker) { + this->mPipeEffects.AddTail(new VehicleRenderConn::Effect(&position_marker->Matrix)); + } else { + bMatrix4 tempmat; + bIdentity(&tempmat); + tempmat.v3.x = emitter_position->X; + tempmat.v3.y = emitter_position->Y; + tempmat.v3.z = emitter_position->Z; + this->mPipeEffects.AddTail(new VehicleRenderConn::Effect(&tempmat)); + } + } + } + + if (this->mEngineEffects.IsEmpty()) { + for (CarEmitterPosition *emitter_position = carrender_info->EmitterPositionList[9].GetHead(); + emitter_position != carrender_info->EmitterPositionList[9].EndOfList(); emitter_position = emitter_position->GetNext()) { + ePositionMarker *position_marker = emitter_position->PositionMarker; + if (position_marker) { + this->mEngineEffects.AddTail(new VehicleRenderConn::Effect(&position_marker->Matrix)); + } else { + bMatrix4 tempmat; + bIdentity(&tempmat); + tempmat.v3.x = emitter_position->X; + tempmat.v3.y = emitter_position->Y; + tempmat.v3.z = emitter_position->Z; + this->mEngineEffects.AddTail(new VehicleRenderConn::Effect(&tempmat)); + } + } + } +} + +void CarRenderConn::GetRenderMatrix(bMatrix4 *matrix) { + PSMTX44Copy(*reinterpret_cast(&this->mRenderMatrix), *reinterpret_cast(matrix)); +} + +void CarRenderConn::OnRender(eView *view, int reflection) { + if (!this->CanRender()) { + return; + } + + if (this->mLastRenderFrame != eGetFrameCounter()) { + this->mDistanceToView = 1000000.0f; + } + this->mLastRenderFrame = eGetFrameCounter(); + + CameraMover *camera_mover = view->GetCameraMover(); + + if (camera_mover != 0 && !camera_mover->RenderCarPOV()) { + CameraAnchor *anchor = camera_mover->GetAnchor(); + + if (anchor != 0 && anchor->GetWorldID() == this->GetWorldID()) { + return; + } + } + + if (view->GetID() > 0xF && view->GetID() < 0x16) { + CameraMover *rear_view_mover = eGetView(1, false)->GetCameraMover(); + + if (rear_view_mover != 0) { + CameraAnchor *anchor = rear_view_mover->GetAnchor(); + + if (anchor != 0 && anchor->GetWorldID() == this->GetWorldID()) { + return; + } + } + } + + if (this->mDoContrailEffect && camera_mover != 0 && camera_mover->OutsidePOV() && + (view->GetID() == 1 || view->GetID() == 2)) { + const Attrib::Collection *xenon_effect = Attrib::FindCollection(0x6F5943F1, 0x16AFDE7B); + AddXenonEffect(0, xenon_effect, this->GetBodyMatrix(), reinterpret_cast(this->GetVelocity())); + } + + if (view->GetID() == 3 && camera_mover != 0) { + RVManchor = camera_mover->GetAnchor(); + + if (RVManchor != 0 && RVManchor->GetWorldID() == this->GetWorldID()) { + return; + } + } + + if (camera_mover != 0 && eIsGameViewID(view->GetID())) { + float distance = camera_mover->GetDistanceTo(reinterpret_cast(&this->mRenderMatrix.v3)); + this->mDistanceToView = UMath::Min(this->mDistanceToView, distance); + } + + CarRenderInfo *render_info = this->mRenderInfo; + if (render_info == 0) { + return; + } + + eGetCurrentViewMode(); + + bMatrix4 body_matrix = this->mRenderMatrix; + + if (reflection == 0 && this->IsViewAnchor(view)) { + CameraMover *anchor_mover = view->GetCameraMover(); + CameraAnchor *anchor = anchor_mover->GetAnchor(); + + if (reinterpret_cast(anchor)->mPOVType == 1) { + bVector4 translated_offset; + + PSMTX44Copy(*reinterpret_cast(this->GetBodyMatrix()), *reinterpret_cast(&body_matrix)); + bVector4 offset = this->mModelOffset; + eMulVector(&translated_offset, &body_matrix, &offset); + body_matrix.v3.x -= translated_offset.x; + body_matrix.v3.y -= translated_offset.y; + body_matrix.v3.z -= translated_offset.z; + } + } + + unsigned int extra_render_flags = 0; + if (reflection != 0) { + extra_render_flags = 0x401; + body_matrix.v2.x = -body_matrix.v2.x; + body_matrix.v2.y = -body_matrix.v2.y; + body_matrix.v2.z = -body_matrix.v2.z; + } + + bVector3 world_position; + world_position.x = body_matrix.v3.x; + world_position.y = body_matrix.v3.y; + world_position.z = body_matrix.v3.z; + body_matrix.v3.x = 0.0f; + body_matrix.v3.y = 0.0f; + body_matrix.v3.z = 0.0f; + + bool is_player = true; + if (!(this->mFlags & CF_ISPLAYER)) { + is_player = false; + } + + if (is_player) { + if (NumTimesRenderTestPlayerCar != 0) { + for (int i = 0; i < NumTimesRenderTestPlayerCar; i++) { + CARPART_LOD min_lod = render_info->mMinLodLevel; + + if (render_info->Render(view, &world_position, &body_matrix, this->mTireMatrices, this->mBrakeMatrices, this->mTireMatrices, + extra_render_flags, 0, reflection, static_cast(static_cast(min_lod)), min_lod, + static_cast(0)) && + view->GetID() < 4) { + this->mLastVisibleFrame = eGetFrameCounter(); + } + } + + goto render_done; + } + } + + { + CARPART_LOD min_lod = render_info->mMinLodLevel; + + if (render_info->Render(view, &world_position, &body_matrix, this->mTireMatrices, this->mBrakeMatrices, this->mTireMatrices, + extra_render_flags, 0, reflection, 1.0f, min_lod, min_lod) && + view->GetID() < 4) { + this->mLastVisibleFrame = eFrameCounter; + } + } + +render_done: + ; +} diff --git a/src/Speed/Indep/Src/World/CarRenderConn.h b/src/Speed/Indep/Src/World/CarRenderConn.h index 97247c65d..1858bcf3a 100644 --- a/src/Speed/Indep/Src/World/CarRenderConn.h +++ b/src/Speed/Indep/Src/World/CarRenderConn.h @@ -5,6 +5,201 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/UBitArray.h" +#include "Speed/Indep/Libs/Support/Utility/UTypes.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/ecar.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/tires.h" +#include "Speed/Indep/Src/Interfaces/IAttributeable.h" +#include "Speed/Indep/Src/World/VehicleRenderConn.h" +#include "Speed/Indep/bWare/Inc/bWare.hpp" +struct RoadNoiseRecord; +struct TireState; + +typedef BitArray PartState; + +namespace RenderConn { +class Pkt_Car_Open : public Sim::Packet { + public: + unsigned int mModelHash; // offset 0x4, size 0x4 + unsigned int mWorldID; // offset 0x8, size 0x4 + CarRenderUsage mUsage; // offset 0xC, size 0x4 + const FECustomizationRecord *mCustomizations; // offset 0x10, size 0x4 + unsigned int mPhysicsKey; // offset 0x14, size 0x4 + bool mSpoolLoad; // offset 0x18, size 0x1 + unsigned char _pad19[3]; // offset 0x19, size 0x3 +}; + +class Pkt_Car_Service : public Sim::Packet { + public: + Pkt_Car_Service(bool inview, float distancetoview) { + unsigned int i; + + this->mDamageInfo = 0; + + *reinterpret_cast(&this->mFlashing) = 0; + bMemSet(this->mCompressions, 0, 0x10); + bMemSet(this->mSteering, 0, 8); + bMemSet(this->mWheelSpeed, 0, 0x10); + bMemSet(this->mTireSkid, 0, 0x10); + bMemSet(this->mTireSlip, 0, 0x10); + this->mAnimatedCarShake = 0.0f; + *reinterpret_cast(&this->mInView) = inview; + this->mDistanceToView = distancetoview; + this->mShift = 1; + this->mHealth = 1.0f; + this->mBlowOuts = 0; + this->mGroundState = 0; + this->mLights = 0; + this->mBrokenLights = 0; + *reinterpret_cast(&this->mNos) = 0; + *reinterpret_cast(&this->mEngineBlown) = 0; + this->mGear = 1; + this->mEnginePower = 0.0f; + this->mExtraBodyRoll = 0.0f; + this->mExtraBodyPitch = 0.0f; + this->mEngineSpeed = 0.0f; + this->mAnimatedCarPitch = 0.0f; + this->mAnimatedCarRoll = 0.0f; + } + + UCrc32 ConnectionClass() override { + static UCrc32 hash("CarRenderConn"); + return hash; + } + + unsigned int Size() override { + return 0xA8; + } + + static unsigned int SType() { + static UCrc32 hash("Pkt_Car_Service"); + return hash.GetValue(); + } + + unsigned int Type() override { + return SType(); + } + + void HidePart(const UCrc32 &partname); + + float mCompressions[4]; // offset 0x4, size 0x10 + float mWheelSpeed[4]; // offset 0x14, size 0x10 + float mTireSkid[4]; // offset 0x24, size 0x10 + float mTireSlip[4]; // offset 0x34, size 0x10 + float mSteering[2]; // offset 0x44, size 0x8 + int mGroundState; // offset 0x4C, size 0x4 + unsigned int mDamageInfo; // offset 0x50, size 0x4 + PartState mPartState; // offset 0x54, size 0xC + unsigned int mLights; // offset 0x60, size 0x4 + unsigned int mBrokenLights; // offset 0x64, size 0x4 + bool mInView; // offset 0x68, size 0x4 + float mDistanceToView; // offset 0x6C, size 0x4 + bool mFlashing; // offset 0x70, size 0x4 + bool mNos; // offset 0x74, size 0x4 + bool mEngineBlown; // offset 0x78, size 0x4 + int mShift; // offset 0x7C, size 0x4 + int mGear; // offset 0x80, size 0x4 + float mEnginePower; // offset 0x84, size 0x4 + float mEngineSpeed; // offset 0x88, size 0x4 + float mExtraBodyRoll; // offset 0x8C, size 0x4 + float mExtraBodyPitch; // offset 0x90, size 0x4 + int mBlowOuts; // offset 0x94, size 0x4 + float mHealth; // offset 0x98, size 0x4 + float mAnimatedCarPitch; // offset 0x9C, size 0x4 + float mAnimatedCarRoll; // offset 0xA0, size 0x4 + float mAnimatedCarShake; // offset 0xA4, size 0x4 +}; +} + +class CarRenderConn : public VehicleRenderConn, public IAttributeable { + public: + enum eFlag { + CF_HIDDEN = 1, + CF_MISSSHIFT = 2, + CF_ISPLAYER = 4, + CF_ISRAINING = 8, + CF_BLOWOFF = 16, + }; + + typedef bTList PipeEffects; + typedef bTList EngineEffects; + + static Sim::Connection *Construct(const Sim::ConnectionData &data); + + CarRenderConn(const Sim::ConnectionData &data, CarType ct, RenderConn::Pkt_Car_Open *oc); + ~CarRenderConn() override; + + void OnAttributeChange(const Attrib::Collection *collection, unsigned int attribkey) override; + bool TestVisibility(float distance); + void OnEvent(EventID id) override; + void OnFetch(float dT) override; + void OnLoaded(CarRenderInfo *carrender_info) override; + void GetRenderMatrix(bMatrix4 *matrix) override; + void OnRender(eView *view, int reflection) override; + + private: + void UpdateSteering(float dT, const RenderConn::Pkt_Car_Service &data); + void UpdateParts(float dT, const RenderConn::Pkt_Car_Service &data); + void AddRoadNoise(float speed, unsigned int tires, const RoadNoiseRecord &noise); + void UpdateRoadNoise(float dT, float carspeed, const RenderConn::Pkt_Car_Service &data); + void UpdateEngineAnimation(float dT, const RenderConn::Pkt_Car_Service &data); + void UpdateBodyAnimation(float dT, const RenderConn::Pkt_Car_Service &data); + void UpdateContrails(const RenderConn::Pkt_Car_Service &data, float dT); + void UpdateTires(float dT, float carspeed, const RenderConn::Pkt_Car_Service &data); + void UpdateEffects(const RenderConn::Pkt_Car_Service &data, float dT); + void Update(const RenderConn::Pkt_Car_Service &data, float dT); + void BuildRenderMatrix(float dT); + void UpdateRenderMatrix(float dT); + void Hide(bool b); + + bool GetFlag(eFlag flag) const { + return (this->mFlags & flag) != 0; + } + + void SetFlag(eFlag flag, bool on) { + if (on) { + this->mFlags |= flag; + } else { + this->mFlags &= ~flag; + } + } + + TireState *mTireState[4]; // offset 0x68, size 0x10 + bVector4 mTirePositions[4]; // offset 0x78, size 0x40 + float mTireRadius[4]; // offset 0xB8, size 0x10 + float mPhysicsRadius[4]; // offset 0xC8, size 0x10 + Attrib::Gen::ecar mAttributes; // offset 0xD8, size 0x14 + Attrib::Gen::pvehicle mPhysics; // offset 0xEC, size 0x14 + Attrib::Gen::tires mTirePhysics; // offset 0x100, size 0x14 + bMatrix4 mTireMatrices[4]; // offset 0x114, size 0x100 + bMatrix4 mBrakeMatrices[4]; // offset 0x214, size 0x100 + bMatrix4 mRenderMatrix; // offset 0x314, size 0x40 + UMath::Vector2 mExtraBodyAngle; // offset 0x354, size 0x8 + UMath::Vector3 mFlatTireAngle; // offset 0x35C, size 0xC + UMath::Vector3 mWheelHop; // offset 0x368, size 0xC + UMath::Vector3 mRoadNoise; // offset 0x374, size 0xC + float mEnginePower; // offset 0x380, size 0x4 + float mAnimTime; // offset 0x384, size 0x4 + float mShiftPitchAngle; // offset 0x388, size 0x4 + float mEngineTorqueAngle; // offset 0x38C, size 0x4 + float mEngineVibrationAngle; // offset 0x390, size 0x4 + float mEnginePitchAngle; // offset 0x394, size 0x4 + float mPerfectLaunchTimer; // offset 0x398, size 0x4 + float mMaxWheelRenderDeltaAngle; // offset 0x39C, size 0x4 + PartState mPartState; // offset 0x3A0, size 0xC + unsigned int mLastRenderFrame; // offset 0x3AC, size 0x4 + unsigned int mLastVisibleFrame; // offset 0x3B0, size 0x4 + float mDistanceToView; // offset 0x3B4, size 0x4 + float mFlashTime; // offset 0x3B8, size 0x4 + float mShifting; // offset 0x3BC, size 0x4 + float mSteering[2]; // offset 0x3C0, size 0x8 + PipeEffects mPipeEffects; // offset 0x3C8, size 0x8 + EngineEffects mEngineEffects; // offset 0x3D0, size 0x8 + bool mDoContrailEffect; // offset 0x3D8, size 0x1 + CarRenderUsage mUsage; // offset 0x3DC, size 0x4 + unsigned int mFlags; // offset 0x3E0, size 0x4 +}; #endif diff --git a/src/Speed/Indep/Src/World/CarSkin.cpp b/src/Speed/Indep/Src/World/CarSkin.cpp index e69de29bb..33e439d25 100644 --- a/src/Speed/Indep/Src/World/CarSkin.cpp +++ b/src/Speed/Indep/Src/World/CarSkin.cpp @@ -0,0 +1,1196 @@ +#include "./Car.hpp" +#include "Speed/Indep/Src/Ecstasy/Texture.hpp" +#include "Speed/Indep/bWare/Inc/bMemory.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +void *TextureInfo_LockImage(TextureInfoPlatInterface *texture_info, TextureLockType lock) + asm("LockImage__24TextureInfoPlatInterface15TextureLockType"); +void TextureInfo_UnlockImage(TextureInfoPlatInterface *texture_info, void *image_lock) asm("UnlockImage__24TextureInfoPlatInterfacePv"); +void *TextureInfo_LockPalette(TextureInfoPlatInterface *texture_info, TextureLockType lock) + asm("LockPalette__24TextureInfoPlatInterface15TextureLockType"); +void TextureInfo_UnlockPalette(TextureInfoPlatInterface *texture_info, void *palette_lock) asm("UnlockPalette__24TextureInfoPlatInterfacePv"); +void eUnSwizzle8bitPalette(unsigned int *palette); +void eSwizzle8bitPalette(unsigned int *palette); +unsigned int ScaleColours(unsigned int a, unsigned int b); +unsigned int GetBlendColour(unsigned int *colours, float *weights, int num_colours, bool max_alpha_blend); +unsigned int RemapColour(unsigned int colour, unsigned int *remap_colours); +char *bStrCat(char *dest, const char *source1, const char *source2); +void initnet(unsigned char *thepic, int len, int num_colours, int sample); +void learn(); +void unbiasnet(); +void nqGetPaletteEntry(int i, unsigned char &r, unsigned char &g, unsigned char &b, unsigned char &a); +void inxbuild(); +int inxsearch(int b, int g, int r, int aa); +int UsedCarTextureAddToTable(unsigned int *table, int num_used, int max_textures, unsigned int texture_hash); +unsigned int GetWheelTextureHash(RideInfo *ride_info); +unsigned int GetWheelTextureMaskHash(RideInfo *ride_info); +unsigned int GetVinylLayerHash(RideInfo *ride_info, int layer); +unsigned int GetVinylLayerMaskHash(RideInfo *ride_info, int layer); +unsigned int GetHoodSpoilerHash(RideInfo *ride_info); +unsigned int GetHoodSpoilerMaskHash(RideInfo *ride_info); +unsigned int GetSpinnerTextureHash(RideInfo *ride_info); +unsigned int GetSpinnerTextureMaskHash(RideInfo *ride_info); +int DumpPreComp(VinylLayerInfo *layer_info, TextureInfo *dest_texture); +extern int UsePrecompositeVinyls; +extern int swatch_offset_init; +extern int swatch_offset_count[4]; +int swatch_offset_cache[64]; +int CompositeSkin(SkinCompositeParams *composite_params); +int CompositeSkin32(SkinCompositeParams *composite_params); +int IsInSkinCompositeCache(SkinCompositeParams *skin_composite_params); +int CompositeWheel(RideInfo *ride_info, unsigned int dest_namehash, unsigned int src_namehash, unsigned int mask_namehash, CAR_SLOT_ID paint_slot); +int CompositeRim(RideInfo *ride_info); + +SkinCompositeParams SkinCompositeParameterCache[4]; + +struct CompColour { + unsigned char a; + unsigned char b; + unsigned char g; + unsigned char r; + + CompColour() {} +}; + +inline char *CarTypeInfo::GetBaseModelName() { + return BaseModelName; +} + +SkinCompositeParams *GetSkinCompositeParams(unsigned int dest_name_hash) { + SkinCompositeParams *cache_params = nullptr; + + switch (dest_name_hash) { + case 0x530B82B0: + cache_params = &SkinCompositeParameterCache[0]; + break; + case 0x530B82B1: + cache_params = &SkinCompositeParameterCache[1]; + break; + case 0x530B82B2: + cache_params = &SkinCompositeParameterCache[2]; + break; + case 0x530B82B3: + cache_params = &SkinCompositeParameterCache[3]; + break; + } + + return cache_params; +} + +bool CompareCompositeParams(SkinCompositeParams *a, SkinCompositeParams *b) { + if (a->DestTexture != b->DestTexture || a->BaseColour != b->BaseColour) { + return false; + } + + for (int i = 0; i < 4; i++) { + if (a->SwatchColours[i] != b->SwatchColours[i]) { + return false; + } + } + + for (int i = 0; i < 1; i++) { + VinylLayerInfo *info_a = &a->VinylLayerInfos[i]; + VinylLayerInfo *info_b = &b->VinylLayerInfos[i]; + + if (info_a->m_LayerHash != info_b->m_LayerHash || info_a->m_LayerTexture != info_b->m_LayerTexture || + info_a->m_LayerMaskTexture != info_b->m_LayerMaskTexture || info_a->m_NumColours != info_b->m_NumColours || + info_a->m_RemapPalette != info_b->m_RemapPalette || info_a->m_RemapColours[0] != info_b->m_RemapColours[0] || + info_a->m_RemapColours[1] != info_b->m_RemapColours[1] || + info_a->m_RemapColours[2] != info_b->m_RemapColours[2] || + info_a->m_RemapColours[3] != info_b->m_RemapColours[3]) { + return false; + } + } + + return true; +} + +void UpdateSkinCompositeCache(SkinCompositeParams *skin_composite_params) { + SkinCompositeParams *cache_params = GetSkinCompositeParams(skin_composite_params->DestTexture->NameHash); + + if (cache_params != 0) { + bMemCpy(cache_params, skin_composite_params, sizeof(*skin_composite_params)); + } +} + +void FlushFromSkinCompositeCache(unsigned int texture_name_hash) { + SkinCompositeParams *cache_params = GetSkinCompositeParams(texture_name_hash); + + if (cache_params != 0) { + bMemSet(cache_params, 0, sizeof(*cache_params)); + } +} + +int IsInSkinCompositeCache(SkinCompositeParams *skin_composite_params) { + SkinCompositeParams *cache_params = GetSkinCompositeParams(skin_composite_params->DestTexture->NameHash); + bool match; + + if (cache_params) { + match = CompareCompositeParams(cache_params, skin_composite_params); + return match; + } + + return 0; +} + +int CompositeSkin32(SkinCompositeParams *composite_params) { + unsigned int base_colour; + unsigned int *swatch_colours; + VinylLayerInfo *layer_infos; + TextureInfo *dest_texture; + int num_layers; + int debug_print; + + base_colour = composite_params->BaseColour; + swatch_colours = composite_params->SwatchColours; + layer_infos = composite_params->VinylLayerInfos; + dest_texture = composite_params->DestTexture; + num_layers = composite_params->NumLayers; + (void)debug_print; + + if (dest_texture == 0) { + return 0; + } + + if (dest_texture->ImageCompressionType != TEXCOMP_32BIT) { + return 0; + } + + unsigned int *dest_image_data = static_cast(TextureInfo_LockImage(dest_texture, TEXLOCK_WRITE)); + int dest_width = dest_texture->Width; + int dest_height = dest_texture->Height; + unsigned int base; + unsigned int *dest_pixel; + unsigned int *end_pixel; + int num_pixels; + + if (swatch_offset_init == 0) { + unsigned int swatch_lookup_colours[4] = { + 0xBF0000FF, + 0xBF00FF00, + 0xBFFF0000, + 0xBFFF00FF, + }; + unsigned int *dest = dest_image_data; + unsigned int *dest_end; + + bMemSet(swatch_offset_cache, 0, sizeof(swatch_offset_cache)); + + dest_end = dest_image_data + dest_width * dest_height; + while (dest < dest_end) { + int pixel_offset = dest - dest_image_data; + int i = 0; + + do { + if (*dest == swatch_lookup_colours[i]) { + int *swatch_offsets = swatch_offset_cache + i * 16; + int count = swatch_offset_count[i]; + + swatch_offset_count[i] = count + 1; + swatch_offsets[count] = pixel_offset; + break; + } + + i++; + } while (i < 4); + + dest++; + } + + swatch_offset_init = 1; + } + + num_pixels = dest_width * dest_height; + base = base_colour; + reinterpret_cast(&base)[2] = static_cast(base_colour >> 24); + reinterpret_cast(&base)[0] = static_cast(base_colour >> 8); + + for (dest_pixel = dest_image_data, end_pixel = dest_image_data + num_pixels; dest_pixel < end_pixel; dest_pixel++) { + *dest_pixel = base; + } + + for (int i = 0; i < num_layers; i++) { + VinylLayerInfo *info = &layer_infos[i]; + + if (info->m_LayerMaskData != 0) { + unsigned int *image_src = reinterpret_cast(info->m_LayerImageData); + unsigned int *dest = dest_image_data; + unsigned int *mask_src = reinterpret_cast(info->m_LayerMaskData); + unsigned int *image_end = image_src + num_pixels; + + for (; image_src < image_end; image_src++, mask_src++, dest++) { + unsigned int src_pixel = *image_src; + unsigned int dest_pixel = *dest; + unsigned int blend_value = reinterpret_cast(mask_src)[2]; + + if (info->m_RemapPalette != 0 && blend_value != 0) { + CompColour src_colour; + + *reinterpret_cast(&src_colour) = src_pixel; + src_colour.g = static_cast(src_pixel >> 24); + src_colour.a = static_cast(src_pixel >> 8); + src_pixel = RemapColour(*reinterpret_cast(&src_colour), info->m_RemapColours); + } + + if (blend_value < 0x80) { + if (blend_value != 0) { + unsigned int colours[2]; + float weights[2]; + + colours[0] = src_pixel; + colours[1] = dest_pixel; + weights[0] = static_cast(blend_value) / 255.0f; + weights[1] = 1.0f - weights[0]; + + if (1.0f < weights[0]) { + weights[0] = 1.0f; + } + + if (weights[1] < 0.0f) { + weights[1] = 0.0f; + } + + src_pixel = GetBlendColour(colours, weights, 2, false); + *dest = src_pixel; + } + } else { + *dest = src_pixel; + } + } + } + } + + for (int i = 0; i < 4; i++) { + int *swatch_offsets = swatch_offset_cache + i * 16; + unsigned int swatch_colour = swatch_colours[i]; + + for (int j = 0; j < swatch_offset_count[i]; j++) { + dest_image_data[swatch_offsets[j]] = swatch_colour; + } + } + + TextureInfo_UnlockImage(dest_texture, dest_image_data); + return 1; +} + +unsigned int ScaleColours(unsigned int a, unsigned int b) { + unsigned char *colour_a = reinterpret_cast(&a); + unsigned char *colour_b = reinterpret_cast(&b); + unsigned int final_colour = 0; + float channel_scale; + + channel_scale = static_cast(static_cast(colour_a[2])); + channel_scale *= 0.003921569f; + channel_scale *= static_cast(static_cast(colour_b[2])); + reinterpret_cast(&final_colour)[2] = static_cast(channel_scale); + channel_scale = static_cast(static_cast(colour_a[1])); + channel_scale *= 0.003921569f; + channel_scale *= static_cast(static_cast(colour_b[1])); + reinterpret_cast(&final_colour)[1] = static_cast(channel_scale); + channel_scale = static_cast(static_cast(colour_a[0])); + channel_scale *= 0.003921569f; + channel_scale *= static_cast(static_cast(colour_b[0])); + reinterpret_cast(&final_colour)[0] = static_cast(channel_scale); + channel_scale = static_cast(static_cast(colour_a[3])); + channel_scale *= 0.003921569f; + channel_scale *= static_cast(static_cast(colour_b[3])); + reinterpret_cast(&final_colour)[3] = static_cast(channel_scale); + return final_colour; +} + +unsigned int GetBlendColour(unsigned int *colours, float *weights, int num_colours, bool max_alpha_blend) { + unsigned char *comp_colours; + unsigned int final_colour = 0; + int r = 0; + int g = 0; + int b = 0; + int a = 0; + + for (int i = 0; i < num_colours; i++) { + float weight = weights[i]; + + if (weight > 0.003921569f) { + comp_colours = reinterpret_cast(&colours[i]); + g += static_cast(weight * static_cast(comp_colours[2])); + b += static_cast(weight * static_cast(comp_colours[1])); + r += static_cast(weight * static_cast(comp_colours[0])); + + if (max_alpha_blend) { + int tempa = static_cast(weight * static_cast(comp_colours[3])) & 0xFF; + + if (tempa > a) { + a = tempa; + } + } else { + a += static_cast(weight * static_cast(comp_colours[3])); + } + } + } + + if (b > 0xFF) { + b = 0xFF; + } + + reinterpret_cast(&final_colour)[2] = static_cast(b); + + if (g > 0xFF) { + g = 0xFF; + } + + reinterpret_cast(&final_colour)[1] = static_cast(g); + + if (r > 0xFF) { + r = 0xFF; + } + + reinterpret_cast(&final_colour)[0] = static_cast(r); + + if (a > 0xFF) { + a = 0xFF; + } + + reinterpret_cast(&final_colour)[3] = static_cast(a); + return final_colour; +} + +unsigned int RemapColour(unsigned int colour, unsigned int *colour_map) { + CompColour col = *reinterpret_cast(&colour); + float weights[4]; + unsigned int result; + + weights[0] = static_cast(static_cast(col.r)) * 0.003921569f; + weights[1] = static_cast(static_cast(col.g)) * 0.003921569f; + weights[2] = static_cast(static_cast(col.b)) * 0.003921569f; + weights[3] = 0.0f; + result = GetBlendColour(colour_map, weights, 3, true); + return result; +} + + +int CompositeSkin(SkinCompositeParams *composite_params) { + struct SemiTransPixel { + short x; + short y; + }; + + TextureInfo *dest_texture; + unsigned int base_colour; + unsigned int *swatch_colours; + VinylLayerInfo *layer_infos; + int num_layers; + int debug_print; + + base_colour = composite_params->BaseColour; + swatch_colours = composite_params->SwatchColours; + layer_infos = composite_params->VinylLayerInfos; + dest_texture = composite_params->DestTexture; + num_layers = composite_params->NumLayers; + (void)debug_print; + + if (dest_texture == 0) { + return 0; + } + if (dest_texture->ImageCompressionType != TEXCOMP_8BIT) { + return 0; + } + + { + unsigned char *dest_image_data = static_cast(TextureInfo_LockImage(dest_texture, TEXLOCK_WRITE)); + unsigned char *dest = dest_image_data; + unsigned int *dest_palette_data = static_cast(TextureInfo_LockPalette(dest_texture, TEXLOCK_WRITE)); + eUnSwizzle8bitPalette(dest_palette_data); + int dest_width = dest_texture->Width; + int dest_height = dest_texture->Height; + int max_semi_trans_pixels = 0xC000; + SemiTransPixel *semi_trans_pixels; + unsigned int *semi_trans_colours; + int semi_trans_pixels_buffer_size = 0x30000; + int total_malloc_required = semi_trans_pixels_buffer_size; + int cur_semi_trans_pixel; + int num_pixels; + unsigned char *dest_end; + unsigned char *image_src[1]; + unsigned char *mask_src[1]; + int current_palette_base; + + cur_semi_trans_pixel = 0; + semi_trans_pixels = static_cast(bMalloc(total_malloc_required, 0, 0, (GetVirtualMemoryPoolNumber() & 0xF) | 0x40)); + semi_trans_colours = static_cast(bMalloc(total_malloc_required, 0, 0, (GetVirtualMemoryPoolNumber() & 0xF) | 0x40)); + num_pixels = dest_width * dest_height; + dest_end = dest_image_data + num_pixels; + + for (int i = 0; i < num_layers; i++) { + VinylLayerInfo *info = &layer_infos[i]; + + if (info->m_LayerHash != 0) { + eUnSwizzle8bitPalette(info->m_LayerImagePaletteData); + eUnSwizzle8bitPalette(info->m_LayerMaskPaletteData); + image_src[i] = info->m_LayerImageData; + mask_src[i] = info->m_LayerMaskData; + } + } + + if (swatch_offset_init == 0) { + unsigned int swatch_lookup_colours[4] = { + 0xA00000F0, + 0xA000F000, + 0xA0F00000, + 0xA0F000F0, + }; + int swatch_indices[4] = { + -1, + -1, + -1, + -1, + }; + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 0x100; j++) { + if (dest_palette_data[j] == swatch_lookup_colours[i]) { + swatch_indices[i] = j; + break; + } + } + } + + bMemSet(swatch_offset_cache, 0, sizeof(swatch_offset_cache)); + + for (dest = dest_image_data; dest < dest_end; dest++) { + int i = 0; + + do { + if (static_cast(*dest) == static_cast(swatch_indices[i])) { + int *swatch_offsets = swatch_offset_cache + i * 16; + + *dest = static_cast(i + 1); + int count = swatch_offset_count[i]; + + swatch_offset_count[i] = count + 1; + swatch_offsets[count] = dest - dest_image_data; + break; + } + + i++; + } while (i < 4); + + if (i == 4) { + *dest = 0; + } + } + + swatch_offset_init = 1; + } else { + bMemSet(dest_image_data, 0, dest_width * dest_height); + } + + dest_palette_data[0] = base_colour; + current_palette_base = 1; + for (int i = 0; i < 4; i++) { + dest_palette_data[current_palette_base] = swatch_colours[i]; + current_palette_base++; + } + + for (dest = dest_image_data; dest < dest_end; dest++) { + unsigned int dest_pixel = dest_palette_data[*dest]; + + for (int i = 0; i < num_layers; i++) { + VinylLayerInfo *info = &layer_infos[i]; + + if (info->m_LayerHash != 0) { + unsigned int src_pixel = info->m_LayerImagePaletteData[*image_src[i]]; + unsigned int src_mask = info->m_LayerMaskPaletteData[*mask_src[i]]; + + if (info->m_RemapPalette != 0 && (src_mask & 0xFF) != 0) { + src_pixel = RemapColour(src_pixel, info->m_RemapColours); + } + + if ((src_mask & 0xFF) > 0x7F) { + *dest = static_cast(*image_src[i] + current_palette_base); + dest_pixel = src_pixel; + } else if ((src_mask & 0xFF) != 0) { + float weights[2]; + unsigned int colours[2]; + unsigned int blend_colour; + int x; + int y; + + weights[0] = static_cast(src_mask & 0xFF) / 255.0f; + if (weights[0] > 1.0f) { + weights[0] = 1.0f; + } + + weights[1] = 1.0f - weights[0]; + colours[0] = src_pixel; + colours[1] = dest_pixel; + blend_colour = GetBlendColour(colours, weights, 2, false); + semi_trans_colours[cur_semi_trans_pixel] = blend_colour; + x = dest - dest_image_data; + y = x / dest_texture->Width; + semi_trans_pixels[cur_semi_trans_pixel].x = x - y * dest_texture->Width; + semi_trans_pixels[cur_semi_trans_pixel].y = y; + + cur_semi_trans_pixel++; + if (cur_semi_trans_pixel >= max_semi_trans_pixels) { + cur_semi_trans_pixel--; + } + + *dest = 0xFF; + } + + image_src[i]++; + mask_src[i]++; + } + } + } + + for (int i = 0; i < num_layers; i++) { + VinylLayerInfo *info = &layer_infos[i]; + + if (info->m_RemapPalette == 0) { + for (int j = 0; j < info->m_NumColours; j++) { + dest_palette_data[current_palette_base + j] = info->m_LayerImagePaletteData[j]; + } + } else { + for (int j = 0; j < info->m_NumColours; j++) { + dest_palette_data[current_palette_base + j] = + RemapColour(info->m_LayerImagePaletteData[j], info->m_RemapColours); + } + } + + eSwizzle8bitPalette(info->m_LayerImagePaletteData); + eSwizzle8bitPalette(info->m_LayerMaskPaletteData); + current_palette_base += info->m_NumColours; + } + + if (cur_semi_trans_pixel != 0) { + int max_remap_colours = 0xFF - current_palette_base; + int num_pixels = cur_semi_trans_pixel; + unsigned char *input; + + if (bLargestMalloc(0) < (num_pixels << 2)) { + num_pixels = 0; + } + + input = static_cast(bMalloc(num_pixels << 2, 0, 0, 0x40)); + + for (int i = 0, p = 0; i < num_pixels; i++, p++) { + unsigned int pixel_colour = semi_trans_colours[i]; + + input[p * 4] = static_cast(pixel_colour); + input[p * 4 + 1] = static_cast(pixel_colour >> 8); + input[p * 4 + 2] = static_cast(pixel_colour >> 16); + input[p * 4 + 3] = static_cast(pixel_colour >> 24); + } + + if (num_pixels < max_remap_colours) { + for (int i = 0; i < num_pixels; i++) { + SemiTransPixel *pixel = &semi_trans_pixels[i]; + unsigned char *dest = &dest_image_data[pixel->x + pixel->y * dest_texture->Width]; + + *dest = static_cast(current_palette_base + i); + dest_palette_data[current_palette_base + i] = semi_trans_colours[i]; + } + } else { + initnet(input, num_pixels << 2, max_remap_colours, 0x14); + learn(); + unbiasnet(); + + for (int i = 0; i < max_remap_colours; i++) { + unsigned char r; + unsigned char g; + unsigned char b; + unsigned char a; + + nqGetPaletteEntry(i, r, g, b, a); + dest_palette_data[current_palette_base + i] = (((static_cast(g) << 8) | b) | + (static_cast(r) << 16)) | + (static_cast(a) << 24); + } + + inxbuild(); + + for (int i = 0; i < num_pixels; i++) { + SemiTransPixel *pixel = &semi_trans_pixels[i]; + unsigned char *dest = &dest_image_data[pixel->x + pixel->y * dest_texture->Width]; + + if (*dest == 0xFF) { + unsigned int colour = semi_trans_colours[i]; + int index = inxsearch(colour & 0xFF, (colour >> 8) & 0xFF, (colour >> 16) & 0xFF, colour >> 24); + + if (index < max_remap_colours) { + *dest = static_cast(index + current_palette_base); + } else { + *dest = 0; + } + } + } + } + + bFree(input); + } + + bFree(semi_trans_pixels); + bFree(semi_trans_colours); + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < swatch_offset_count[i]; j++) { + dest_image_data[swatch_offset_cache[j + i * 16]] = static_cast(i + 1); + } + } + + eSwizzle8bitPalette(dest_palette_data); + TextureInfo_UnlockImage(dest_texture, dest_image_data); + TextureInfo_UnlockPalette(dest_texture, dest_palette_data); + return 1; + } +} + +int CompositeSkin(RideInfo *ride_info) { + unsigned int dest_namehash; + TextureInfo *dest_texture; + int do_32bit_composite; + const int first_vinyl_layer = 0; + CarPart *base_paint_part; + unsigned int base_paint_colour; + int red; + int green; + int blue; + int gloss; + unsigned int swatch_colours[4]; + VinylLayerInfo vinyl_layer_infos[1]; + int total_layer_colours; + const int max_layer_colours = 1; + int cur_layer; + SkinCompositeParams composite_params; + int success; + + if (ride_info->IsUsingCompositeSkin() == 0) { + return 1; + } + + dest_namehash = ride_info->GetCompositeSkinNameHash(); + dest_texture = GetTextureInfo(dest_namehash, false, false); + if (dest_texture == 0) { + return 1; + } + + do_32bit_composite = dest_texture->ImageCompressionType == TEXCOMP_32BIT; + base_paint_part = ride_info->GetPart(CARSLOTID_BASE_PAINT); + if (base_paint_part == 0) { + return 1; + } + + red = base_paint_part->GetAppliedAttributeIParam(bStringHash("RED"), 0); + green = base_paint_part->GetAppliedAttributeIParam(bStringHash("GREEN"), 0); + blue = base_paint_part->GetAppliedAttributeIParam(bStringHash("BLUE"), 0); + gloss = base_paint_part->GetAppliedAttributeIParam(bStringHash("GLOSS"), 0); + + base_paint_colour = (gloss << 24) | (blue << 16) | (green << 8) | red; + + for (int i = 0; i < 4; i++) { + swatch_colours[i] = base_paint_colour; + } + + total_layer_colours = 0; + bMemSet(vinyl_layer_infos, 0, sizeof(vinyl_layer_infos)); + cur_layer = first_vinyl_layer; + + do { + VinylLayerInfo *info = &vinyl_layer_infos[cur_layer]; + unsigned int mask_hash; + + if (cur_layer == first_vinyl_layer) { + int car_part_id = CARSLOTID_VINYL_LAYER0 + cur_layer; + CarPart *car_part = ride_info->GetPart(car_part_id); + + if (car_part != 0) { + info->m_LayerHash = GetVinylLayerHash(ride_info, cur_layer); + info->m_NumColours = car_part->GetAppliedAttributeIParam(bStringHash("NUMCOLOURS"), 0); + if (info->m_NumColours == 0) { + return 0; + } + } + } + + if (info->m_LayerHash != 0) { + info->m_LayerTexture = GetTextureInfo(info->m_LayerHash, false, false); + if (info->m_LayerTexture == 0) { + info->m_LayerHash = 0; + } else { + info->m_LayerImageData = static_cast(TextureInfo_LockImage(info->m_LayerTexture, TEXLOCK_READ)); + if (do_32bit_composite == 0) { + info->m_LayerImagePaletteData = + static_cast(TextureInfo_LockPalette(info->m_LayerTexture, TEXLOCK_READ)); + } + + if (info->m_LayerImageData != 0) { + if (UsePrecompositeVinyls == 0 && ride_info->SkinType != 2) { + mask_hash = bStringHash("_MASK", info->m_LayerHash); + info->m_LayerMaskTexture = GetTextureInfo(mask_hash, false, false); + if (info->m_LayerMaskTexture == 0) { + info->m_LayerHash = 0; + } else { + info->m_LayerMaskData = + static_cast(TextureInfo_LockImage(info->m_LayerMaskTexture, TEXLOCK_READ)); + if (do_32bit_composite == 0) { + info->m_LayerMaskPaletteData = + static_cast(TextureInfo_LockPalette(info->m_LayerMaskTexture, TEXLOCK_READ)); + } + + if (info->m_LayerMaskData != 0) { + int next_total_layer_colours = total_layer_colours + 1; + + if (cur_layer == first_vinyl_layer) { + CarPart *car_part = ride_info->GetPart(CARSLOTID_VINYL_LAYER0 + cur_layer); + + if (car_part != 0 && car_part->HasAppliedAttribute(bStringHash("REMAP")) != 0) { + info->m_RemapPalette = car_part->GetAppliedAttributeIParam(bStringHash("REMAP"), 0); + if (info->m_RemapPalette != 0) { + for (int j = 0; j < 4; j++) { + CarPart *colour_part = ride_info->GetPart(CARSLOTID_VINYL_COLOUR0_0 + j); + + if (colour_part != 0) { + unsigned int remap_colour = + colour_part->GetAppliedAttributeIParam(bStringHash("RED"), 0); + int remap_green = + colour_part->GetAppliedAttributeIParam(bStringHash("GREEN"), 0); + int remap_blue = + colour_part->GetAppliedAttributeIParam(bStringHash("BLUE"), 0); + int remap_gloss = + colour_part->GetAppliedAttributeIParam(bStringHash("GLOSS"), 0); + + info->m_RemapColours[j] = (remap_gloss << 24) | (remap_blue << 16) | + (remap_green << 8) | remap_colour; + } else { + info->m_RemapColours[j] = 0xFFu << (j << 3); + } + } + } + } + } + + total_layer_colours = next_total_layer_colours; + } else { + info->m_LayerHash = 0; + } + } + } else { + DumpPreComp(info, dest_texture); + return 1; + } + } else { + info->m_LayerHash = 0; + } + } + } + + cur_layer++; + + if (cur_layer >= max_layer_colours) { + success = 1; + eWaitUntilRenderingDone(); + CompositeRim(ride_info); + + composite_params.BaseColour = base_paint_colour; + composite_params.NumLayers = total_layer_colours; + composite_params.DestTexture = dest_texture; + bMemCpy(composite_params.SwatchColours, swatch_colours, sizeof(swatch_colours)); + bMemCpy(composite_params.VinylLayerInfos, vinyl_layer_infos, sizeof(vinyl_layer_infos)); + + if (IsInSkinCompositeCache(&composite_params) == 0) { + UpdateSkinCompositeCache(&composite_params); + if (dest_texture->ImageCompressionType == TEXCOMP_8BIT) { + success = CompositeSkin(&composite_params); + } else { + success = CompositeSkin32(&composite_params); + } + } + + { + int i = 0; + + do { + VinylLayerInfo *info = &vinyl_layer_infos[i]; + + if (info->m_LayerImageData != 0) { + TextureInfo_UnlockImage(info->m_LayerTexture, info->m_LayerImageData); + } + + if (info->m_LayerImagePaletteData != 0) { + TextureInfo_UnlockPalette(info->m_LayerTexture, info->m_LayerImagePaletteData); + } + + if (info->m_LayerMaskData != 0) { + TextureInfo_UnlockImage(info->m_LayerMaskTexture, info->m_LayerMaskData); + } + + if (info->m_LayerMaskPaletteData != 0) { + TextureInfo_UnlockPalette(info->m_LayerMaskTexture, info->m_LayerMaskPaletteData); + } + + i++; + } while (i < max_layer_colours); + } + + return success; + } + } while (true); + + return 1; +} + +int DumpPreComp(VinylLayerInfo *info, TextureInfo *dest_texture) { + void *dest_image_data = TextureInfo_LockImage(dest_texture, TEXLOCK_WRITE); + int pixel_size = 1; + + if (dest_texture->ImageCompressionType == TEXCOMP_32BIT) { + pixel_size = 4; + } + + bMemCpy(dest_image_data, info->m_LayerImageData, dest_texture->Width * dest_texture->Height * pixel_size); + + if (dest_texture->ImageCompressionType == TEXCOMP_8BIT) { + unsigned int *dest_palette_data = static_cast(TextureInfo_LockPalette(dest_texture, TEXLOCK_WRITE)); + + eUnSwizzle8bitPalette(dest_palette_data); + eUnSwizzle8bitPalette(info->m_LayerImagePaletteData); + bMemCpy(dest_palette_data, info->m_LayerImagePaletteData, info->m_NumColours << 2); + eSwizzle8bitPalette(info->m_LayerImagePaletteData); + eSwizzle8bitPalette(dest_palette_data); + TextureInfo_UnlockPalette(dest_texture, dest_palette_data); + } + + TextureInfo_UnlockImage(dest_texture, dest_image_data); + return 1; +} + +unsigned int GetWheelTextureHash(RideInfo *ride_info) { + CarPart *wheel = ride_info->GetPart(CARSLOTID_FRONT_WHEEL); + + if (wheel == 0) { + return 0; + } + + return bStringHash("_WHEEL", wheel->GetAppliedAttributeUParam(0x10C98090, 0)); +} + +unsigned int GetWheelTextureMaskHash(RideInfo *ride_info) { + CarPart *wheel = ride_info->GetPart(CARSLOTID_FRONT_WHEEL); + + if (wheel == 0) { + return 0; + } + + return bStringHash("_WHEEL_INNER_MASK", wheel->GetAppliedAttributeUParam(0x10C98090, 0)); +} + +unsigned int GetHoodSpoilerHash(RideInfo *ride_info) { + return 0; +} + +unsigned int GetHoodSpoilerMaskHash(RideInfo *ride_info) { + return 0; +} + +unsigned int GetSpinnerTextureHash(RideInfo *ride_info) { + CarPart *rim_part = ride_info->GetPart(CARSLOTID_FRONT_WHEEL); + + if (rim_part == 0) { + return 0; + } + + return rim_part->GetAppliedAttributeUParam(bStringHash("SPINNER_TEXTURE"), 0); +} + +unsigned int GetSpinnerTextureMaskHash(RideInfo *ride_info) { + CarPart *rim_part = ride_info->GetPart(CARSLOTID_FRONT_WHEEL); + unsigned int spinner_hash; + + if (rim_part == 0) { + return 0; + } + + spinner_hash = rim_part->GetAppliedAttributeUParam(bStringHash("SPINNER_TEXTURE"), 0); + if (spinner_hash == 0) { + return 0; + } + + return bStringHash("_MASK", spinner_hash); +} + +unsigned int GetVinylLayerHash(CarPart *car_part, CarType car_type, int skin_type) { + CarTypeInfo *car_type_info = GetCarTypeInfo(car_type); + const char *texture_name = car_part->GetAppliedAttributeString(bStringHash("TEXTURE"), 0); + + if (texture_name == nullptr) { + return 0; + } + + char layer_name[64]; + + bStrCpy(layer_name, car_type_info->GetBaseModelName()); + if (UsePrecompositeVinyls || skin_type == 2) { + bStrCat(layer_name, layer_name, "_PRECOM_"); + } else { + bStrCat(layer_name, layer_name, "_"); + } + bStrCat(layer_name, layer_name, texture_name); + return bStringHash(layer_name); +} + +unsigned int GetVinylLayerHash(RideInfo *ride_info, int layer) { + CarPart *vinyl = ride_info->GetPart(CARSLOTID_VINYL_LAYER0 + layer); + + if (vinyl == 0) { + return 0; + } + + return GetVinylLayerHash(vinyl, ride_info->Type, ride_info->SkinType); +} + +unsigned int GetVinylLayerMaskHash(RideInfo *ride_info, int layer) { + CarPart *car_part = ride_info->GetPart(CARSLOTID_VINYL_LAYER0 + layer); + CarType car_type; + CarTypeInfo *car_type_info; + const char *texture_name; + char layer_name[64]; + + if (car_part != 0) { + car_type = ride_info->Type; + car_type_info = GetCarTypeInfo(car_type); + texture_name = car_part->GetAppliedAttributeString(bStringHash("TEXTURE"), 0); + + if (texture_name != 0) { + bStrCpy(layer_name, car_type_info->GetBaseModelName()); + bStrCat(layer_name, layer_name, "_"); + bStrCat(layer_name, layer_name, texture_name); + bStrCat(layer_name, layer_name, "_MASK"); + return bStringHash(layer_name); + } + } + + return 0; +} + + +int CompositeWheel32(TextureInfo *dest_texture, TextureInfo *src_texture, TextureInfo *src_mask, unsigned int remap_colour) { + unsigned int *dest_image_data = static_cast(TextureInfo_LockImage(dest_texture, TEXLOCK_WRITE)); + unsigned int *src_image_data = static_cast(TextureInfo_LockImage(src_texture, TEXLOCK_READ)); + unsigned int *src_mask_data = static_cast(TextureInfo_LockImage(src_mask, TEXLOCK_READ)); + int num_pixels = dest_texture->Width * dest_texture->Height; + unsigned int *dest_pixel = dest_image_data; + unsigned int *end_pixel = dest_image_data + num_pixels; + unsigned int *src_pixel = src_image_data; + unsigned int *src_mask_pixel = src_mask_data; + + while (dest_pixel < end_pixel) { + unsigned int colour_pixel = *src_pixel; + unsigned int blend_colours[2]; + float blend = static_cast(reinterpret_cast(src_mask_pixel)[2]) / 255.0f; + float weights[2]; + + blend_colours[0] = colour_pixel; + blend_colours[1] = ScaleColours(colour_pixel, remap_colour); + weights[0] = 1.0f - blend; + weights[1] = blend; + + colour_pixel = GetBlendColour(blend_colours, weights, 2, false); + *dest_pixel = colour_pixel & 0xFFFFFF; + *dest_pixel = (colour_pixel & 0xFFFFFF) | (blend_colours[0] & 0xFF000000); + + dest_pixel++; + src_pixel++; + src_mask_pixel++; + } + + TextureInfo_UnlockImage(src_mask, src_mask_data); + TextureInfo_UnlockImage(src_texture, src_image_data); + TextureInfo_UnlockImage(dest_texture, dest_image_data); + + return 1; +} + +int CompositeWheel8(TextureInfo *dest_texture, TextureInfo *src_texture, TextureInfo *src_mask, unsigned int remap_colour) { + unsigned char *dest_image_data = static_cast(TextureInfo_LockImage(dest_texture, TEXLOCK_WRITE)); + unsigned int *dest_palette_data = static_cast(TextureInfo_LockPalette(dest_texture, TEXLOCK_WRITE)); + unsigned char *src_image_data = static_cast(TextureInfo_LockImage(src_texture, TEXLOCK_READ)); + unsigned int *src_palette_data = static_cast(TextureInfo_LockPalette(src_texture, TEXLOCK_READ)); + unsigned char *src_mask_data = static_cast(TextureInfo_LockImage(src_mask, TEXLOCK_READ)); + unsigned int *src_mask_palette_data = static_cast(TextureInfo_LockPalette(src_mask, TEXLOCK_READ)); + unsigned int row = 0; + + eUnSwizzle8bitPalette(dest_palette_data); + eUnSwizzle8bitPalette(src_palette_data); + eUnSwizzle8bitPalette(src_mask_palette_data); + + do { + float blend = static_cast(row) / 15.0f; + int i = 0; + + do { + unsigned int blend_colours[2]; + float weights[2]; + unsigned int colour = src_palette_data[row * 16 + i]; + + blend_colours[0] = colour; + blend_colours[1] = ScaleColours(colour, remap_colour); + weights[0] = 1.0f - blend; + weights[1] = blend; + + colour = GetBlendColour(blend_colours, weights, 2, false); + dest_palette_data[(row * 16) + i] = colour & 0xFFFFFF; + dest_palette_data[(row * 16) + i] = (colour & 0xFFFFFF) | (blend_colours[0] & 0xFF000000); + i++; + } while (i < 16); + + row++; + } while (row < 16); + + eUnSwizzle8bitPalette(dest_palette_data); + eUnSwizzle8bitPalette(src_palette_data); + TextureInfo_UnlockPalette(dest_texture, dest_palette_data); + TextureInfo_UnlockPalette(src_texture, src_palette_data); + + { + int num_pixels = dest_texture->Width * dest_texture->Height; + unsigned char *dest_pixel = dest_image_data; + unsigned char *end_pixel = dest_image_data + num_pixels; + unsigned char *src_pixel = src_image_data; + unsigned char *src_mask_pixel = src_mask_data; + + for (; dest_pixel < end_pixel; dest_pixel++) { + unsigned int mask = src_mask_palette_data[*src_mask_pixel]; + + *dest_pixel = *src_pixel + (static_cast(mask) & 0xF0); + src_pixel++; + src_mask_pixel++; + } + } + + eSwizzle8bitPalette(src_mask_palette_data); + TextureInfo_UnlockPalette(src_mask, src_mask_palette_data); + TextureInfo_UnlockImage(src_mask, src_mask_data); + TextureInfo_UnlockImage(src_texture, src_image_data); + TextureInfo_UnlockImage(dest_texture, dest_image_data); + + return 1; +} + +int CompositeWheel(RideInfo *ride_info, unsigned int dest_namehash, unsigned int src_namehash, unsigned int mask_namehash, CAR_SLOT_ID paint_slot) { + TextureInfo *dest_texture = GetTextureInfo(dest_namehash, false, false); + + if (dest_texture == 0) { + return 1; + } + + int do_32bit_composite = dest_texture->ImageCompressionType == TEXCOMP_32BIT; + TextureInfo *src_texture = GetTextureInfo(src_namehash, false, false); + TextureInfo *src_mask = GetTextureInfo(mask_namehash, false, false); + + if (src_texture != 0 && src_mask != 0) { + CarPart *car_part = ride_info->GetPart(paint_slot); + unsigned int wheel_colour = 0xFFFFFFFF; + + if (car_part != 0) { + int red = car_part->GetAppliedAttributeIParam(bStringHash("RED"), 0); + int green = car_part->GetAppliedAttributeIParam(bStringHash("GREEN"), 0); + int blue = car_part->GetAppliedAttributeIParam(bStringHash("BLUE"), 0); + int gloss = car_part->GetAppliedAttributeIParam(bStringHash("GLOSS"), 0); + + wheel_colour = (gloss << 24) | (blue << 16) | (green << 8) | red; + } + + if (do_32bit_composite != 0) { + if (src_texture->ImageCompressionType != TEXCOMP_32BIT) { + return 0; + } + + if (src_mask->ImageCompressionType != TEXCOMP_32BIT) { + return 0; + } + } else { + if (src_texture->ImageCompressionType == TEXCOMP_32BIT) { + return 0; + } + + if (src_mask->ImageCompressionType == TEXCOMP_32BIT) { + return 0; + } + } + + if (dest_texture->Width == src_texture->Width && dest_texture->Width == src_mask->Width && + dest_texture->Height == src_texture->Height && dest_texture->Height == src_mask->Height) { + if (!do_32bit_composite) { + return CompositeWheel8(dest_texture, src_texture, src_mask, wheel_colour); + } + + return CompositeWheel32(dest_texture, src_texture, src_mask, wheel_colour); + } + } + + return 0; +} + +int CompositeRim(RideInfo *ride_info) { + unsigned int dest_namehash = ride_info->GetCompositeWheelNameHash(); + unsigned int src_namehash = GetWheelTextureHash(ride_info); + unsigned int mask_namehash = GetWheelTextureMaskHash(ride_info); + + return CompositeWheel(ride_info, dest_namehash, src_namehash, mask_namehash, CARSLOTID_PAINT_RIM); +} + +int GetTempCarSkinTextures(unsigned int *textures_to_load, int num_textures, int max_textures, RideInfo *ride) { + for (int car_part_id = CARSLOTID_VINYL_LAYER0; car_part_id < CARSLOTID_PAINT_RIM; car_part_id++) { + CarPart *part = ride->GetPart(static_cast(car_part_id)); + + if (part != 0) { + const char *name = part->GetName(); + unsigned int vinyl_hash = GetVinylLayerHash(ride, car_part_id - CARSLOTID_VINYL_LAYER0); + unsigned int mask_hash = GetVinylLayerMaskHash(ride, car_part_id - CARSLOTID_VINYL_LAYER0); + int added_vinyls = UsedCarTextureAddToTable(textures_to_load, num_textures, max_textures, vinyl_hash); + int added_masks = UsedCarTextureAddToTable(textures_to_load, num_textures + added_vinyls, max_textures, mask_hash); + + num_textures += added_vinyls + added_masks; + (void)name; + } + } + + { + unsigned int hood_spoiler_hash = GetHoodSpoilerHash(ride); + unsigned int hood_spoiler_mask_hash = GetHoodSpoilerMaskHash(ride); + + if (hood_spoiler_hash != 0 && hood_spoiler_mask_hash != 0) { + int added_spoilers = UsedCarTextureAddToTable(textures_to_load, num_textures, max_textures, hood_spoiler_hash); + int added_masks = UsedCarTextureAddToTable(textures_to_load, num_textures + added_spoilers, max_textures, hood_spoiler_mask_hash); + + num_textures += added_spoilers + added_masks; + } + } + + { + unsigned int wheel_hash = GetWheelTextureHash(ride); + unsigned int wheel_mask_hash = GetWheelTextureMaskHash(ride); + + if (wheel_hash != 0 && wheel_mask_hash != 0) { + int added_wheels = UsedCarTextureAddToTable(textures_to_load, num_textures, max_textures, wheel_hash); + int added_masks = UsedCarTextureAddToTable(textures_to_load, num_textures + added_wheels, max_textures, wheel_mask_hash); + + num_textures += added_wheels + added_masks; + } + } + + { + unsigned int spinner_hash = GetSpinnerTextureHash(ride); + unsigned int spinner_mask_hash = GetSpinnerTextureMaskHash(ride); + + if (spinner_hash != 0 && spinner_mask_hash != 0) { + int added_spinners = UsedCarTextureAddToTable(textures_to_load, num_textures, max_textures, spinner_hash); + int added_masks = UsedCarTextureAddToTable(textures_to_load, num_textures + added_spinners, max_textures, spinner_mask_hash); + + num_textures += added_spinners + added_masks; + } + } + + return num_textures; +} 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..47e99c4f3 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; +struct 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/Common/WCollider.cpp b/src/Speed/Indep/Src/World/Common/WCollider.cpp index e69de29bb..32142e71b 100644 --- a/src/Speed/Indep/Src/World/Common/WCollider.cpp +++ b/src/Speed/Indep/Src/World/Common/WCollider.cpp @@ -0,0 +1,334 @@ +#include "Speed/Indep/Src/World/WCollider.h" + +#include "Speed/Indep/Libs/Support/Utility/UMath.h" +#include "Speed/Indep/Src/World/WCollisionMgr.h" +#include "Speed/Indep/Src/World/WWorld.h" +#include "Speed/Indep/Src/World/WWorldMath.h" +#include "Speed/Indep/bWare/Inc/bMath.hpp" + +void VU0_v4crossprodxyz(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &r); + +bool Tweak_colliderDraws; + +UTL::Std::map WCollider::fWuidMap; +UTL::Collections::Listable::List UTL::Collections::Listable::_mTable; + +WCollider::WCollider(eColliderShape colliderShape, unsigned int typeMask, unsigned int exclusionMask) + : fRequestedPosition(UMath::Vector3::kZero), // + fRequestedRadius(0.0f), // + fLastRequestedPosition(UMath::Vector3::kZero), // + fLastRequestedRadius(0.0f), // + fPosition(UMath::Vector3::kZero), // + fRadius(0.0f), // + fLastRefreshedPosition(UMath::Vector3::kZero), // + fRegionInitialized(false), // + fColliderShape(colliderShape), // + fTypeMask(typeMask), // + fRefCount(0), // + fWorldID(0), // + fExclusionFlags(exclusionMask) { + ReserveLists(typeMask); +} + +WCollider::~WCollider() { + Clear(); +} + +WCollider *WCollider::Get(unsigned int wuid) { + if (wuid == 0) { + return nullptr; + } + + UTL::Std::map::iterator iter = fWuidMap.find(wuid); + if (iter != fWuidMap.end()) { + return iter->second; + } + + return nullptr; +} + +WCollider *WCollider::Create(unsigned int wuid, eColliderShape shape, unsigned int typeCheckMask, unsigned int exclusionMask) { + WCollider *col = Get(wuid); + if (col == nullptr) { + col = new WCollider(shape, typeCheckMask, exclusionMask); + col->fWorldID = wuid; + col->AddRef(); + if (wuid != 0) { + fWuidMap[wuid] = col; + } + return col; + } else { + col->AddRef(); + return col; + } +} + +void WCollider::Destroy(WCollider *col) { + col->RemoveRef(); + if (col->fRefCount == 0) { + UTL::Std::map::iterator iter = fWuidMap.find(col->fWorldID); + if (iter != fWuidMap.end()) { + fWuidMap.erase(iter); + } + delete col; + } +} + +static void CalcNewRegionSizeFromRequested(bool useLastData, const UMath::Vector3 &reqPos, float reqRad, const UMath::Vector3 &oldPos, + float oldRad, const UMath::Vector3 &lastPos, UMath::Vector3 &pos, float &rad) { + if (!useLastData) { + pos = reqPos; + rad = reqRad * 1.1f; + return; + } + + float vel = UMath::Distance(reqPos, lastPos); + if (vel > 5.0f) { + pos = reqPos; + rad = reqRad * 1.1f; + return; + } + + UMath::Vector3 moveVec; + UMath::Sub(reqPos, oldPos, moveVec); + float lifeFactor = 2.5f; + UMath::Unit(moveVec, moveVec); + UMath::Scale(moveVec, vel * lifeFactor, moveVec); + UMath::Add(reqPos, moveVec, pos); + + float moveDist = UMath::Length(moveVec); + rad = reqRad + moveDist + 0.1f; + if (rad > 25.0f) { + rad = 25.0f; + } +} + +void WCollider::InvalidateIntersectingColliders(const UMath::Vector4 &posRad) { + for (List::const_iterator iter = GetList().begin(); iter != GetList().end(); ++iter) { + WCollider &collider = **iter; + + if (UMath::Distance(collider.fPosition, UMath::Vector4To3(posRad)) < posRad.w + collider.fRadius) { + collider.Clear(); + } + } +} + +void WCollider::Refresh(const UMath::Vector3 &pt, float radius, bool predictiveSizing) { + if (!WWorld::Get().IsValid()) { + EmptyLists(0x1C); + return; + } + + unsigned int updateMask = GetUpdateMask(pt, radius); + if (updateMask != 0) { + EmptyLists(updateMask); + ReserveLists(updateMask); + + if (updateMask == fTypeMask) { + fRequestedPosition = pt; + fRequestedRadius = radius; + + if (predictiveSizing) { + CalcNewRegionSizeFromRequested(fRegionInitialized, fRequestedPosition, radius, fLastRequestedPosition, fLastRequestedRadius, fLastRefreshedPosition, + fPosition, fRadius); + } else { + fPosition = fRequestedPosition; + fRadius = fRequestedRadius * 1.1f; + } + } + + PrepareRegion(updateMask); + } + + if (predictiveSizing) { + fLastRefreshedPosition = pt; + fLastRequestedPosition = pt; + fLastRequestedRadius = radius; + } +} + +void WCollider::PrepareRegion(unsigned int updateMask) { + if (updateMask & 0xC) { + WCollisionMgr(fExclusionFlags, 3).GetInstanceList(fInstanceCacheList, fPosition, fRadius, fColliderShape == kColliderShape_Cylinder); + if (updateMask & 0x8) { + WCollisionMgr(fExclusionFlags, 3).GetTriList(fInstanceCacheList, fPosition, fRadius, fTriList); + } + } + + if (updateMask & 0x4) { + fBarrierList.reserve(0x15); + WCollisionMgr(fExclusionFlags, 3).GetBarrierList(fBarrierList, fInstanceCacheList, fPosition, fRadius); + } + + if (updateMask & 0x10) { + WCollisionMgr(fExclusionFlags, 3).GetObjectList(fObbList, fPosition, fRadius); + } + + fRegionInitialized = true; +} + +bool WCollider::IsEmpty() const { + return fInstanceCacheList.empty() && fBarrierList.empty(); +} + +void WCollider::Clear() { + if (fRegionInitialized) { + ClearLists(0x1C); + fRegionInitialized = false; + } +} + +void WCollider::ClearLists(unsigned int typeMask) { + if (typeMask & 0x8) { + fInstanceCacheList.clear(); + fTriList.clear_all(); + } + + if (typeMask & 0x4) { + fBarrierList.clear(); + } + + if (typeMask & 0x10) { + fObbList.clear(); + } +} + +void WCollider::EmptyLists(unsigned int typeMask) { + if (typeMask & 0x8) { + fInstanceCacheList.resize(0); + fTriList.clear_all(); + } + + if (typeMask & 0x4) { + fBarrierList.resize(0); + } + + if (typeMask & 0x10) { + fObbList.resize(0); + } +} + +void WCollider::ReserveLists(unsigned int typeMask) { + if (typeMask & 0x8) { + fInstanceCacheList.reserve(0x64); + fTriList.reserve(0x8); + } + + if (typeMask & 0x4) { + fBarrierList.reserve(0x19); + } + + if (typeMask & 0x10) { + fObbList.reserve(0x19); + } +} + +unsigned int WCollider::Validate() const { + if (!*reinterpret_cast(&fRegionInitialized)) return 0; + return 1; +} + +unsigned int WCollider::GetUpdateMask(const UMath::Vector3 &pt, float radius) { + unsigned int updateMask = fTypeMask & 0x10; + if (!Validate() || !InRegion(pt, radius)) { + updateMask |= fTypeMask & 0xC; + } + return updateMask; +} + +bool WCollider::InRegion(const UMath::Vector3 &pt, float radius) const { + float radDiff = fRadius - radius; + if (radDiff < 0.0f) { + return false; + } + return UMath::DistanceSquare(pt, fPosition) < radDiff * radDiff; +} + +void WCollider::InvalidateAllCachedData() { + const List &list = GetList(); + List::const_iterator iter = list.begin(); + List::const_iterator end = list.end(); + const unsigned int regionInitialized = false; + while (iter != end) { + (*iter)->Clear(); + (*iter)->fRegionInitialized = regionInitialized; + ++iter; + } +} + +void WCollisionObject::MakeMatrix(UMath::Matrix4 &m, bool addXLate) const { + m = fMat; + + if (addXLate) { + m.v3.x = fPosRadius.x; + m.v3.y = fPosRadius.y; + m.v3.z = fPosRadius.z; + m.v3.w = 1.0f; + return; + } + + m.v3.x = 0.0f; + m.v3.y = 0.0f; + m.v3.z = 0.0f; + m.v3.w = 1.0f; +} + +float WCollisionInstance::CalcSphericalRadius() const { + float maxExtent = (fInvMatRow2Length.w < fInvPosRadius.w) ? fInvPosRadius.w : fInvMatRow2Length.w; + maxExtent = (fHeight < maxExtent) ? maxExtent : fHeight; + return (fInvMatRow0Width.w < maxExtent) ? maxExtent : fInvMatRow0Width.w; +} + +void WCollisionInstance::CalcPosition(UMath::Vector3 &pos) const { + pos.x = (-fInvPosRadius.x * fInvMatRow0Width.x - fInvPosRadius.y * fInvMatRow0Width.y) - fInvPosRadius.z * fInvMatRow0Width.z; + pos.z = (-fInvPosRadius.x * fInvMatRow2Length.x - fInvPosRadius.y * fInvMatRow2Length.y) - fInvPosRadius.z * fInvMatRow2Length.z; + bool needsCross = NeedsCrossProduct(); + + if (needsCross) { + UMath::Vector4 upVec; + VU0_v4crossprodxyz(reinterpret_cast(fInvMatRow2Length), reinterpret_cast(fInvMatRow0Width), + upVec); + pos.y = (-fInvPosRadius.x * upVec.x - fInvPosRadius.y * upVec.y) - fInvPosRadius.z * upVec.z; + } else { + pos.y = -fInvPosRadius.y; + } +} + +void WCollisionInstance::MakeMatrix(UMath::Matrix4 &m, bool addXLate) const { + m.v0.x = fInvMatRow0Width.x; + m.v0.y = fInvMatRow0Width.y; + m.v0.z = fInvMatRow0Width.z; + m.v0.w = 0.0f; + bool needsCross = NeedsCrossProduct(); + + if (needsCross) { + VU0_v4crossprodxyz(reinterpret_cast(fInvMatRow2Length), reinterpret_cast(fInvMatRow0Width), + m.v1); + m.v1.w = 0.0f; + } else { + m.v1.x = 0.0f; + m.v1.y = 1.0f; + m.v1.z = 0.0f; + m.v1.w = 0.0f; + } + + m.v2.x = fInvMatRow2Length.x; + m.v2.y = fInvMatRow2Length.y; + m.v2.z = fInvMatRow2Length.z; + m.v2.w = 0.0f; + + if (addXLate) { + m.v3.x = fInvPosRadius.x; + m.v3.y = fInvPosRadius.y; + m.v3.z = fInvPosRadius.z; + m.v3.w = 1.0f; + } else { + m.v3.x = 0.0f; + m.v3.y = 0.0f; + m.v3.z = 0.0f; + m.v3.w = 1.0f; + } +} + +template class UTL::Vector; +template class UTL::FixedVector; diff --git a/src/Speed/Indep/Src/World/Common/WCollision.cpp b/src/Speed/Indep/Src/World/Common/WCollision.cpp index e69de29bb..7f78f3886 100644 --- a/src/Speed/Indep/Src/World/Common/WCollision.cpp +++ b/src/Speed/Indep/Src/World/Common/WCollision.cpp @@ -0,0 +1,83 @@ +#include "Speed/Indep/Src/World/WCollision.h" +#include "Speed/Indep/Src/World/WCollisionTri.h" +#include "Speed/Indep/Src/World/WWorldMath.h" + +void VU0_v4crossprodxyz(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &r); + +void WCollisionObject::MakeMatrix(UMath::Matrix4 &m, bool addXLate) const { + const unsigned int *src = reinterpret_cast(&fMat); + unsigned int *dst = reinterpret_cast(&m); + for (unsigned int i = 0; i < 0x10; ++i) { + dst[i] = src[i]; + } + + if (addXLate) { + m.v3.x = fPosRadius.x; + m.v3.y = fPosRadius.y; + m.v3.z = fPosRadius.z; + m.v3.w = 1.0f; + return; + } + + m.v3.x = 0.0f; + m.v3.y = 0.0f; + m.v3.z = 0.0f; + m.v3.w = 1.0f; +} + +float WCollisionInstance::CalcSphericalRadius() const { + float maxExtent = WWorldMath::wmax(fInvMatRow2Length.w, fInvPosRadius.w); + maxExtent = WWorldMath::wmax(fHeight, maxExtent); + return WWorldMath::wmax(fInvMatRow0Width.w, maxExtent); +} + +void WCollisionInstance::CalcPosition(UMath::Vector3 &pos) const { + bool needsCross = NeedsCrossProduct(); + pos.x = (-fInvPosRadius.x * fInvMatRow0Width.x - fInvPosRadius.y * fInvMatRow0Width.y) - fInvPosRadius.z * fInvMatRow0Width.z; + pos.z = (-fInvPosRadius.x * fInvMatRow2Length.x - fInvPosRadius.y * fInvMatRow2Length.y) - fInvPosRadius.z * fInvMatRow2Length.z; + + if (needsCross) { + UMath::Vector4 upVec; + VU0_v4crossprodxyz(reinterpret_cast(fInvMatRow2Length), reinterpret_cast(fInvMatRow0Width), + upVec); + pos.y = (-fInvPosRadius.x * upVec.x - fInvPosRadius.y * upVec.y) - fInvPosRadius.z * upVec.z; + } else { + pos.y = -fInvPosRadius.y; + } +} + +void WCollisionInstance::MakeMatrix(UMath::Matrix4 &m, bool addXLate) const { + bool needsCross = NeedsCrossProduct(); + m.v0.x = fInvMatRow0Width.x; + m.v0.y = fInvMatRow0Width.y; + m.v0.z = fInvMatRow0Width.z; + m.v0.w = 0.0f; + + if (needsCross) { + VU0_v4crossprodxyz(reinterpret_cast(fInvMatRow2Length), reinterpret_cast(fInvMatRow0Width), + m.v1); + m.v1.w = 0.0f; + } else { + m.v1.x = 0.0f; + m.v1.y = 1.0f; + m.v1.z = 0.0f; + m.v1.w = 0.0f; + } + + m.v2.x = fInvMatRow2Length.x; + m.v2.y = fInvMatRow2Length.y; + m.v2.z = fInvMatRow2Length.z; + m.v2.w = 0.0f; + + if (addXLate) { + m.v3.x = fInvPosRadius.x; + m.v3.y = fInvPosRadius.y; + m.v3.z = fInvPosRadius.z; + m.v3.w = 1.0f; + } else { + m.v3.x = 0.0f; + m.v3.y = 0.0f; + m.v3.z = 0.0f; + m.v3.w = 1.0f; + } +} diff --git a/src/Speed/Indep/Src/World/Common/WCollisionAssets.cpp b/src/Speed/Indep/Src/World/Common/WCollisionAssets.cpp index e69de29bb..a5149c02b 100644 --- a/src/Speed/Indep/Src/World/Common/WCollisionAssets.cpp +++ b/src/Speed/Indep/Src/World/Common/WCollisionAssets.cpp @@ -0,0 +1,325 @@ +#include "Speed/Indep/Src/World/WCollisionAssets.h" + +#include "Speed/Indep/Src/World/Common/WGrid.h" +#include "Speed/Indep/Src/World/WCollider.h" +#include "Speed/Indep/Src/World/WCollisionPack.h" +#include "Speed/Indep/Src/World/WGridManagedDynamicElem.h" +#include "Speed/Indep/Src/World/WTrigger.h" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +struct TrackOBB { + unsigned int TypeNameHash; + unsigned int Pad[3]; + bMatrix4 Matrix; + bVector3 Dims; +}; + +int GetNumTrackOBBs(); +TrackOBB *GetTrackOBB(int index); + +struct ManagedCollisionInstance { + ManagedCollisionInstance(WCollisionInstance *cInst, unsigned int trigInd) + : mCInst(cInst), // + mTriggerInd(trigInd) {} + + WCollisionInstance *mCInst; + unsigned int mTriggerInd; +}; + +WCollisionAssets::WCollisionAssets() + : fStaticCollisionInstances(nullptr), // + fStaticCollisionInstancesCount(0), // + fManagedCollisionInstances(new (__FILE__, __LINE__) CollisionInstanceMap), // + fManagedCollisionInstancesInd(0x8000), // + fStaticCollisionObjects(nullptr), // + fStaticCollisionObjectsCount(0), // + fManagedCollisionObjects(new (__FILE__, __LINE__) CollisionObjectMap), // + fManagedCollisionObjectsInd(0x8000), // + fNumPackLoadCallbacks(0) { + unsigned int onCallback; + + for (onCallback = 0; onCallback <= 3; ++onCallback) { + fPackLoadCallback[onCallback] = nullptr; + } + + fStaticTriggers = nullptr; + fStaticTriggersCount = 0; + fStaticCollisionObjects = nullptr; + + mCollisionPackList = new (__FILE__, __LINE__) WCollisionPack *[0xA8C]; + int ix; + for (ix = 0; ix <= 0xA8B; ++ix) { + mCollisionPackList[ix] = nullptr; + } +} + +WCollisionAssets::~WCollisionAssets() { + int ix; + + for (ix = 0; ix <= 0xA8B; ++ix) { + if (mCollisionPackList[ix] != nullptr) { + delete mCollisionPackList[ix]; + mCollisionPackList[ix] = nullptr; + } + } + + if (mCollisionPackList != nullptr) { + delete[] mCollisionPackList; + mCollisionPackList = nullptr; + } + + delete fManagedCollisionInstances; + fManagedCollisionInstances = nullptr; + + for (CollisionObjectMap::iterator iter = fManagedCollisionObjects->begin(); iter != fManagedCollisionObjects->end(); ++iter) { + delete iter->second; + } + delete fManagedCollisionObjects; + fManagedCollisionObjects = nullptr; +} + +void WCollisionAssets::Init(const UGroup *mapGroup, const UData *triggerUData) { + if (mapGroup == nullptr) { + sWCollisionAssets = new WCollisionAssets(); + WTriggerManager::Init(); + WGrid::Init(nullptr); + WGridManagedDynamicElem::Init(); + } else { + sWCollisionAssets = new WCollisionAssets(); + if (triggerUData == nullptr) { + sWCollisionAssets->fStaticTriggers = nullptr; + sOriginalTriggerData = nullptr; + sSavedTriggerData = nullptr; + } + WTriggerManager::Init(); + WGrid::Init(mapGroup); + unsigned int num = GetNumTrackOBBs(); + for (num = num - 1; num != static_cast(-1); --num) { + TrackOBB *tobb = GetTrackOBB(num); + UMath::Matrix4 mat = *reinterpret_cast(&tobb->Matrix); + bConvertToBond(reinterpret_cast(mat), tobb->Matrix); + UMath::Vector3 dim; + bConvertToBond(dim, tobb->Dims); + dim.x = bAbs(dim.x); + dim.y = bAbs(dim.y); + dim.z = bAbs(dim.z); + WCollisionObject *obj = Get().CreateObject(dim, mat, false); + } + } +} + +void WCollisionAssets::Shutdown() { + if (sWCollisionAssets != nullptr) { + delete sWCollisionAssets; + } + + sWCollisionAssets = nullptr; + if (WTriggerManager::fgTriggerManager != nullptr) { + delete WTriggerManager::fgTriggerManager; + } + WTriggerManager::fgTriggerManager = nullptr; + WGridManagedDynamicElem::fgManagedDynamicElemList.clear(); + WGrid::Shutdown(); +} + +void WCollisionAssets::SetExclusionFlags(WCollisionPack *collisionPack) { + if (collisionPack != nullptr) { + unsigned int i = 0; + + while (true) { + WCollisionInstance *cInst = const_cast(collisionPack->Instance(static_cast(i))); + if (cInst == nullptr) { + break; + } + + if (i >= collisionPack->mInstanceNum) { + break; + } + + unsigned int exclusionFlags = 0; + if (cInst->fGroupNumber != 0) { + exclusionFlags = 0xC0; + } + + if (cInst->fCollisionArticle != nullptr) { + if (cInst->fCollisionArticle->fNumStrips == 0) { + cInst->fFlags |= exclusionFlags; + } + } + + unsigned int next = i + 1; + if (exclusionFlags != 0) { + const WCollisionArticle *cArt = cInst->fCollisionArticle; + if (cArt != nullptr) { + const WCollisionBarrier *barrier = cArt->GetBarrier(0u); + int j = 0; + if (j < cArt->fNumEdges) { + do { + const_cast(barrier->GetWSurface()).FlagsRef() |= exclusionFlags; + ++j; + barrier = barrier->Next(); + } while (j < cInst->fCollisionArticle->fNumEdges); + } + } + } + + i = next; + } + } +} + +void WCollisionAssets::SetExclusionFlags() { + unsigned int i = 0; + + do { + SetExclusionFlags(mCollisionPackList[i]); + ++i; + } while (i <= 0x3FF); + + WCollider::InvalidateAllCachedData(); +} + +void WCollisionAssets::AddPackLoadCallback(void (*callback)(int, bool)) { + unsigned int numPackLoadCallbacks = fNumPackLoadCallbacks; + if (numPackLoadCallbacks > 3) { + return; + } + + fPackLoadCallback[numPackLoadCallbacks] = callback; + fNumPackLoadCallbacks = numPackLoadCallbacks + 1; +} + +void WCollisionAssets::RemovePackLoadCallback(void (*callback)(int, bool)) { + unsigned int i = 0; + + while (i < fNumPackLoadCallbacks) { + if (fPackLoadCallback[i] == callback) { + if (i < fNumPackLoadCallbacks - 1) { + fPackLoadCallback[i] = fPackLoadCallback[fNumPackLoadCallbacks - 1]; + } else { + fPackLoadCallback[i] = nullptr; + } + --fNumPackLoadCallbacks; + } else { + ++i; + } + } +} + +const WCollisionInstance *WCollisionAssets::Instance(unsigned int ind) const { + unsigned short sectionId = static_cast(ind >> 0x10); + if (sectionId > 0xA8B) { + return nullptr; + } + + WCollisionPack *collisionPack = mCollisionPackList[sectionId]; + if (collisionPack != nullptr) { + return collisionPack->Instance(static_cast(ind)); + } + + return nullptr; +} + +const WCollisionObject *WCollisionAssets::Object(unsigned int ind) const { + unsigned short sectionId = static_cast(ind >> 0x10); + if (sectionId > 0xA8B) { + return nullptr; + } + + if (ind > 0x7FFF) { + return (*fManagedCollisionObjects)[ind]; + } + + WCollisionPack *collisionPack = mCollisionPackList[sectionId]; + if (collisionPack != nullptr) { + return collisionPack->Object(static_cast(ind)); + } + + return nullptr; +} + +WTrigger &WCollisionAssets::Trigger(unsigned int tag) const { + return *reinterpret_cast(tag); +} + +void WCollisionAssets::AddTrigger(WTrigger *trig) { + trig->UpdatePos(UMath::Vector4To3(trig->fPosRadius), reinterpret_cast(trig)); +} + +void WCollisionAssets::RemoveTrigger(WTrigger *trigger) { + UMath::Vector4 oldPosRad = trigger->fPosRadius; + + WGridManagedDynamicElem::AddElem(&oldPosRad, nullptr, WGrid_kTrigger, reinterpret_cast(trigger)); + if (trigger != nullptr && ((static_cast(reinterpret_cast(trigger)[0x12]) << 8) & 0x100) == 0) { + delete trigger; + } +} + +void WCollisionAssets::LoadCollisionPack(bChunk *chunk) { + bChunkCarpHeader *chunk_header = reinterpret_cast(chunk->GetAlignedData(16)); + int sectionId = chunk_header->GetSectionNumber(); + if (!chunk_header->IsResolved()) { + bPlatEndianSwap(§ionId); + } + if (mCollisionPackList[sectionId] == nullptr) { + mCollisionPackList[sectionId] = new WCollisionPack(chunk); + for (unsigned int onCallback = 0; onCallback < fNumPackLoadCallbacks; ++onCallback) { + if (fPackLoadCallback[onCallback] != nullptr) { + fPackLoadCallback[onCallback](sectionId, true); + } + } + } + SetExclusionFlags(mCollisionPackList[sectionId]); +} + +void WCollisionAssets::UnLoadCollisionPack(bChunk *chunk) { + bChunkCarpHeader *chunk_header = reinterpret_cast(chunk->GetAlignedData(16)); + int sectionId = chunk_header->GetSectionNumber(); + if (mCollisionPackList[sectionId] != nullptr) { + for (unsigned int onCallback = 0; onCallback < fNumPackLoadCallbacks; ++onCallback) { + if (fPackLoadCallback[onCallback] != nullptr) { + fPackLoadCallback[onCallback](sectionId, false); + } + } + delete mCollisionPackList[sectionId]; + mCollisionPackList[sectionId] = nullptr; + } +} + +unsigned int WCollisionAssets::AddObject(WCollisionObject *obj) { + unsigned int objectInd = fManagedCollisionObjectsInd; + fManagedCollisionObjectsInd = objectInd + 1; + (*fManagedCollisionObjects)[objectInd] = obj; + return objectInd; +} + +WCollisionObject *WCollisionAssets::CreateObject(const UMath::Vector3 &dim, const UMath::Matrix4 &mat, bool dynamicFlag) { + WCollisionObject *obj = new (__FILE__, __LINE__) WCollisionObject; + obj->fFlags = 0; + obj->fMat = mat; + if (dynamicFlag) { + obj->fFlags |= 1; + } + obj->fRenderInstanceInd = 0; + obj->fFlags |= 0x40; + obj->fPosRadius.x = mat.v3.x; + obj->fPosRadius.y = mat.v3.y - dim.y; + obj->fPosRadius.z = mat.v3.z; + obj->fType = 0; + obj->fDimensions = UMath::Vector4Make(dim, 1.0f); + obj->fDimensions.w = 1.0f; + obj->fPosRadius.w = UMath::Sqrt(obj->fDimensions.x * obj->fDimensions.x + obj->fDimensions.z * obj->fDimensions.z); + obj->fSurface.fFlags = 0; + obj->fSurface.fSurface = 0; + + unsigned int objectInd = AddObject(obj); + WGridManagedDynamicElem::AddElem(nullptr, &obj->fPosRadius, WGrid_kObject, objectInd); + + if (dynamicFlag) { + WGridNodeElem elem(objectInd, WGrid_kObject); + WGridManagedDynamicElem dynElem(&obj->fPosRadius, &mat.v3, elem); + WGridManagedDynamicElem::DynamicElemList().push_back(dynElem); + } + + return obj; +} diff --git a/src/Speed/Indep/Src/World/Common/WCollisionMgr.cpp b/src/Speed/Indep/Src/World/Common/WCollisionMgr.cpp index e69de29bb..892954680 100644 --- a/src/Speed/Indep/Src/World/Common/WCollisionMgr.cpp +++ b/src/Speed/Indep/Src/World/Common/WCollisionMgr.cpp @@ -0,0 +1,1245 @@ +#include "Speed/Indep/Src/World/WCollisionMgr.h" +#include "Speed/Indep/Src/World/WCollisionAssets.h" +#include "Speed/Indep/Src/World/WWorldMath.h" +#include "Speed/Indep/Src/World/WWorldPos.h" +#include "Speed/Indep/Src/World/Common/WGrid.h" +#include "Speed/Indep/Libs/Support/Utility/UVector.h" + +#include +#include + +void OrthoInverse(UMath::Matrix4 &m); + +struct UTransform { + UTransform() {} + UTransform(const UMath::Matrix4 &m) : fTransform(m) {} + ~UTransform() {} + UMath::Matrix4 fTransform; +}; + +inline void WCollisionStrip::MakeFace(unsigned int ind, const UMath::Vector3 &cp, WCollisionTri &retFace) const { + const WCollisionPackedVert *v = Verts() + ind; + retFace.fPt0.x = static_cast(v->x) * (1.0f / 128.0f) + cp.x; + retFace.fPt0.y = static_cast(v->y) * (1.0f / 128.0f) + cp.y; + retFace.fPt0.z = static_cast(v->z) * (1.0f / 128.0f) + cp.z; + retFace.fSurfaceRef = nullptr; + v++; + retFace.fPt1.x = static_cast(v->x) * (1.0f / 128.0f) + cp.x; + retFace.fPt1.y = static_cast(v->y) * (1.0f / 128.0f) + cp.y; + retFace.fPt1.z = static_cast(v->z) * (1.0f / 128.0f) + cp.z; + retFace.fFlags = 0; + v++; + retFace.fPt2.x = static_cast(v->x) * (1.0f / 128.0f) + cp.x; + retFace.fPt2.y = static_cast(v->y) * (1.0f / 128.0f) + cp.y; + retFace.fPt2.z = static_cast(v->z) * (1.0f / 128.0f) + cp.z; + retFace.fSurface = WSurface(v->surface); +} + +inline void WCollisionStrip::MakeNextFace(unsigned int ind, const UMath::Vector3 &cp, WCollisionTri &retFace) const { + const WCollisionPackedVert *v = Verts() + ind + 2; + retFace.fPt0 = retFace.fPt1; + retFace.fPt1 = retFace.fPt2; + retFace.fPt2.x = static_cast(v->x) * (1.0f / 128.0f) + cp.x; + retFace.fPt2.y = static_cast(v->y) * (1.0f / 128.0f) + cp.y; + retFace.fPt2.z = static_cast(v->z) * (1.0f / 128.0f) + cp.z; + retFace.fSurface = WSurface(v->surface); +} + +inline void NearPtLinePerSegXZ(const UMath::Vector3 &p0, const UMath::Vector3 &p1, float &invDen, UMath::Vector3 &diffVec) { + UMath::Sub(p1, p0, diffVec); + diffVec.y = 0.0f; + float ud = diffVec.x * diffVec.x + diffVec.z * diffVec.z; + invDen = 0.0f; + if (0.0f < ud) { + invDen = 1.0f / ud; + } +} + +inline void NearPtLine(const UMath::Vector3 &pt, const UMath::Vector3 &p0, float den, const UMath::Vector3 &diffVec, UMath::Vector3 &nearPt) { + float u = ((pt.x - p0.x) * diffVec.x + (pt.y - p0.y) * diffVec.y + (pt.z - p0.z) * diffVec.z) * den; + u = UMath::Min(u, 1.0f); + u = UMath::Max(u, 0.0f); + nearPt.x = u * diffVec.x + p0.x; + nearPt.y = u * diffVec.y + p0.y; + nearPt.z = u * diffVec.z + p0.z; +} + +inline void NearPtLineXZ(const UMath::Vector3 &pt, const UMath::Vector3 &p0, float den, const UMath::Vector3 &diffVec, UMath::Vector3 &nearPt) { + float u = ((pt.x - p0.x) * diffVec.x + (pt.z - p0.z) * diffVec.z) * den; + u = UMath::Min(u, 1.0f); + u = UMath::Max(0.0f, u); + nearPt.x = u * diffVec.x + p0.x; + nearPt.z = u * diffVec.z + p0.z; +} + +void WCollisionMgr::GetInstanceListGuts(const NodeIndexList &nodeInds, WCollisionInstanceCacheList &instList, const UMath::Vector3 &inPt, float radius, + bool cylinderTest) { + UMath::Vector3 pt; + const WGrid &grid = WGrid::Get(); + pt = inPt; + ++fIterCount; + + for (const unsigned int *iter = nodeInds.begin(); iter != nodeInds.end(); ++iter) { + WGridNode *node = grid.fNodes[*iter]; + if (node != nullptr) { + WGridNode::iterator eIter(node, WGrid_kInstance); + const unsigned int *instIndPtr; + while ((instIndPtr = eIter.GetIndPtr()) != nullptr) { + unsigned int instInd = *instIndPtr; + const WCollisionInstance *cInst = WCollisionAssets::Get().Instance(instInd); + + if (cInst != nullptr && + (cInst->fGroupNumber == 0 || IsSceneryGroupEnabled(cInst->fGroupNumber)) && + cInst->fCollisionArticle != nullptr && + InstancePassesExclusion(*cInst) && + cInst->fIterStamp != fIterCount) { + const_cast(cInst)->fIterStamp = fIterCount; + + UMath::Vector3 instPos; + cInst->CalcPosition(instPos); + + float instRadius; + if (cInst->NeedsCrossProduct()) { + instRadius = cInst->CalcSphericalRadius(); + } else { + instRadius = cInst->fInvPosRadius.w; + } + + float radSum = radius + instRadius; + if (UMath::DistanceSquarexz(instPos, pt) < radSum * radSum) { + instList.push_back(cInst); + } + } + } + } + } +} + +void WCollisionMgr::GetInstanceList(WCollisionInstanceCacheList &instList, const UMath::Vector3 &pt, float radius, bool cylinderTest) { + UTL::FastVector nodeInds; + nodeInds.reserve(0x40); + WGrid::Get().FindNodes(pt, radius, nodeInds); + GetInstanceListGuts(nodeInds, instList, pt, radius, cylinderTest); +} + +void WCollisionMgr::GetInstanceListGuts(const NodeIndexList &nodeInds, WCollisionInstanceCacheList &instList, const UMath::Vector4 *seg) { + const WGrid &grid = WGrid::Get(); + float invDen; + UMath::Vector3 npVec; + + ++fIterCount; + NearPtLinePerSegXZ(UMath::Vector4To3(seg[0]), UMath::Vector4To3(seg[1]), invDen, npVec); + float minSegY = WWorldMath::wmin(seg[1].y, seg[0].y); + float maxSegY = WWorldMath::wmax(seg[1].y, seg[0].y); + + for (const unsigned int *iter = nodeInds.begin(); iter != nodeInds.end(); ++iter) { + WGridNode *node = grid.fNodes[*iter]; + if (node != nullptr) { + WGridNode::iterator eIter(node, WGrid_kInstance); + const unsigned int *instIndPtr; + while ((instIndPtr = eIter.GetIndPtr()) != nullptr) { + unsigned int instInd = *instIndPtr; + const WCollisionInstance *cInst = WCollisionAssets::Get().Instance(instInd); + + if (cInst != nullptr && + (cInst->fGroupNumber == 0 || IsSceneryGroupEnabled(cInst->fGroupNumber)) && + cInst->fCollisionArticle != nullptr && + InstancePassesExclusion(*cInst) && + cInst->fIterStamp != fIterCount) { + float instRad = cInst->fInvPosRadius.w; + const_cast(cInst)->fIterStamp = fIterCount; + + UMath::Matrix4 invMat; + cInst->MakeMatrix(invMat, true); + OrthoInverse(invMat); + + const UMath::Vector3 &instPos = UMath::Vector4To3(invMat.v3); + + UMath::Vector3 nearPt; + NearPtLineXZ(instPos, UMath::Vector4To3(seg[0]), invDen, npVec, nearPt); + + UMath::Vector3 diffVec; + UMath::Sub(instPos, nearPt, diffVec); + + float dSq = diffVec.x * diffVec.x + diffVec.z * diffVec.z; + float instRadSq = instRad * instRad; + + if (dSq < instRadSq) { + if (cInst->NeedsCrossProduct()) { + instList.push_back(cInst); + } else { + float instTopY = instPos.y + cInst->fHeight; + float instBotY = instPos.y - cInst->fHeight; + if (instTopY > minSegY && instBotY < maxSegY) { + instList.push_back(cInst); + } + } + } + } + } + } + } +} + +void WCollisionMgr::GetInstanceList(WCollisionInstanceCacheList &instList, const UMath::Vector4 *seg) { + UTL::FastVector nodeInds; + nodeInds.reserve(0x40); + WGrid::Get().FindNodes(seg, nodeInds); + GetInstanceListGuts(nodeInds, instList, seg); +} + +static void CalcCollisionFaceNormal(UMath::Vector3 *norm, UMath::Vector4 *facePts) { + UMath::Vector3 vecZ; + UMath::Vector3 vecX; + UMath::Vector3 normal; + vecX.x = facePts[1].x - facePts[0].x; + vecX.y = facePts[1].y - facePts[0].y; + vecZ.z = facePts[0].z - facePts[2].z; + vecX.z = facePts[1].z - facePts[0].z; + vecZ.x = facePts[0].x - facePts[2].x; + vecZ.y = facePts[0].y - facePts[2].y; + UMath::Cross(vecX, vecZ, normal); + if (normal.x == 0.0f && normal.y == 0.0f && normal.z == 0.0f) { + norm->x = 0.0f; + norm->z = 0.0f; + norm->y = 1.0f; + } else { + v3unit(&normal, norm); + } + if (norm->y < 0.0f) { + norm->y = -norm->y; + norm->x = -norm->x; + norm->z = -norm->z; + } + if (norm->y >= 1.0f) { + norm->y = 1.0f; + } +} + +bool WCollisionMgr::GetWorldHeightAtPointRigorous(const UMath::Vector3 &pt, float &height, UMath::Vector3 *normal) { + if (!GetWorldHeightAtPoint(pt, height, normal)) { + UMath::Vector4 seg[2]; + seg[1] = UMath::Vector4Make(pt, 1.0f); + seg[0] = seg[1]; + seg[0].y -= 2.0f; + seg[1].y += 1000.0f; + + WorldCollisionInfo cInfo; + WCollisionMgr(0, 3).CheckHitWorld(seg, cInfo, 1); + + if (cInfo.HitSomething()) { + if (cInfo.fType == 1) { + height = cInfo.fCollidePt.y; + if (normal != nullptr) { + *normal = UMath::Vector4To3(cInfo.fNormal); + } + } else { + return false; + } + } else { + return false; + } + } + return true; +} + +int WCollisionMgr::CheckHitWorld(const UMath::Vector4 *inputSeg, WorldCollisionInfo &cInfo, unsigned int primMask) { + UMath::Vector4 seg[2]; + UMath::Vector4 segPosRad; + int hitWorld; + + cInfo.fType = 0; + + UMath::Copy(inputSeg[0], seg[0]); + UMath::Copy(inputSeg[1], seg[1]); + + float len = UMath::Distancexyz(seg[0], seg[1]); + if (len == 0.0f) { + return 0; + } + + if (len < 0.5f) { + UMath::Vector4 diffVec; + UMath::Subxyz(seg[1], seg[0], diffVec); + UMath::Scalexyz(diffVec, 0.5f / len, diffVec); + UMath::Addxyz(seg[0], diffVec, seg[1]); + } + + hitWorld = 0; + + UMath::Add(UMath::Vector4To3(seg[0]), UMath::Vector4To3(seg[1]), UMath::Vector4To3(segPosRad)); + UMath::Scale(UMath::Vector4To3(segPosRad), 0.5f, UMath::Vector4To3(segPosRad)); + segPosRad.w = len * 0.5f; + + WCollisionInstanceCacheList instList; + instList.reserve(0x40); + + fPrimitiveMask = primMask; + GetInstanceList(instList, seg); + fPrimitiveMask = 3; + + if ((primMask & 2) != 0) { + if (GetBarrierNormal(instList, seg, cInfo)) { + hitWorld = 2; + } + } + + if (hitWorld != 0) { + UMath::Copy(cInfo.fCollidePt, seg[1]); + } + + if ((primMask & 1) != 0) { + static WWorldPos wPos(2.0f); + + wPos.FindClosestFace(instList, UMath::Vector4To3(seg[0]), UMath::Vector4To3(seg[1])); + + if (wPos.OnValidFace()) { + WorldCollisionInfo cInfoFace; + float t; + + cInfoFace.fType = 1; + + wPos.UNormal(&UMath::Vector4To3(cInfoFace.fNormal)); + cInfoFace.fNormal.w = 1.0f; + + if (WWorldMath::IntersectSegPlane( + UMath::Vector4To3(seg[0]), + UMath::Vector4To3(seg[1]), + UMath::Vector4To3(wPos.FacePoint(0)), + UMath::Vector4To3(cInfoFace.fNormal), + UMath::Vector4To3(cInfoFace.fCollidePt), + t)) { + cInfoFace.fAnimated = 0; + cInfoFace.fCInst = nullptr; + + UMath::Vector4 hitVec; + UMath::Subxyz(seg[0], cInfoFace.fCollidePt, hitVec); + float dot = UMath::Dotxyz(cInfoFace.fNormal, hitVec); + + if (dot < 0.0f) { + cInfoFace.fNormal.x = -cInfoFace.fNormal.x; + cInfoFace.fNormal.y = -cInfoFace.fNormal.y; + cInfoFace.fNormal.z = -cInfoFace.fNormal.z; + } + + if (hitWorld == 0) { + cInfo = cInfoFace; + } else { + float dsq1 = UMath::DistanceSquare(UMath::Vector4To3(seg[0]), UMath::Vector4To3(cInfo.fCollidePt)); + float dsq2 = UMath::DistanceSquare(UMath::Vector4To3(seg[0]), UMath::Vector4To3(cInfoFace.fCollidePt)); + if (dsq1 <= dsq2) { + goto done; + } + cInfo = cInfoFace; + } + hitWorld = 1; + } + } + } + +done: + if (hitWorld != 0) { + UMath::Copy(cInfo.fCollidePt, seg[1]); + } + + return cInfo.HitSomething() ? 1 : 0; +} + +bool WCollisionMgr::GetWorldHeightAtPoint(const UMath::Vector3 &pt, float &height, UMath::Vector3 *normal) { + WWorldPos temp(2.0f); + temp.FindClosestFace(pt, true); + if (temp.OnValidFace()) { + UMath::Vector3 norm; + temp.UNormal(&norm); + height = WWorldMath::GetPlaneY(norm, UMath::Vector4To3(temp.FacePoint(0)), pt); + if (normal != nullptr) { + *normal = norm; + } + return true; + } + return false; +} + +void WCollisionMgr::ClosestCollisionInfo(const UMath::Vector4 *seg, const WorldCollisionInfo &c1, const WorldCollisionInfo &c2, WorldCollisionInfo &result) { + if (c1.HitSomething() || c2.HitSomething()) { + float distSqC1 = FLT_MAX; + float distSqC2 = FLT_MAX; + if (c1.HitSomething()) { + distSqC1 = UMath::DistanceSquare(UMath::Vector4To3(*seg), UMath::Vector4To3(c1.fCollidePt)); + } + if (c2.HitSomething()) { + distSqC2 = UMath::DistanceSquare(UMath::Vector4To3(*seg), UMath::Vector4To3(c2.fCollidePt)); + } + if (distSqC1 < distSqC2) { + result = c1; + } else { + result = c2; + } + } +} + +void WCollisionMgr::GetObjectListGuts(const NodeIndexList &nodeInds, WCollisionObjectList &obbObjectList, const UMath::Vector3 &pt, float radius) { + const WGrid &grid = WGrid::Get(); + + for (const unsigned int *iter = nodeInds.begin(); iter != nodeInds.end(); ++iter) { + WGridNode *node = grid.fNodes[*iter]; + if (node != nullptr) { + WGridNode::iterator eIter(node, WGrid_kObject); + const unsigned int *objIndPtr; + while ((objIndPtr = eIter.GetIndPtr()) != nullptr) { + const WCollisionObject *cObj = WCollisionAssets::Get().Object(*objIndPtr); + if (cObj != nullptr) { + UMath::Vector3 cp = UMath::Vector4To3(cObj->fPosRadius); + float distSq = UMath::DistanceSquarexz(cp, pt); + float totalRadius = radius + cObj->fPosRadius.w; + if (distSq < totalRadius * totalRadius && cObj->fType == 0) { + obbObjectList.push_back(cObj); + } + } + } + } + } +} + +void WCollisionMgr::GetObjectList(WCollisionObjectList &obbObjectList, const UMath::Vector3 &pt, float radius) { + UTL::FastVector nodeInds; + nodeInds.reserve(0x40); + WGrid::Get().FindNodes(pt, radius, nodeInds); + obbObjectList.reserve(0x10); + GetObjectListGuts(nodeInds, obbObjectList, pt, radius); +} + +void WCollisionMgr::BuildGeomFromWorldObb(const WCollisionObject &object, float dt, Dynamics::Collision::Geometry &geom, UMath::Vector3 &vel, + WSurface &surface) { + surface = object.GetWSurface(); + + UMath::Vector3 pos = UMath::Vector3Make(object.fPosRadius.x, object.fPosRadius.y, object.fPosRadius.z); + UMath::Vector3 objPos = pos; + UMath::Matrix4 objMat; + + object.MakeMatrix(objMat, false); + + vel.x = 0.0f; + vel.z = 0.0f; + vel.y = 0.0f; + UMath::ScaleAdd(UMath::Vector4To3(objMat.v1), object.fDimensions.y, objPos, pos); + + UMath::Vector3 dP = UMath::Vector3Make(object.fDimensions.x, object.fDimensions.y, object.fDimensions.z); + UMath::Vector3 dim = dP; + UMath::Scale(vel, dt, dP); + + geom.Set(objMat, pos, dim, Dynamics::Collision::Geometry::BOX, dP); +} + +bool WCollisionMgr::Collide(Dynamics::Collision::Geometry *geom, const WCollisionBarrierList *barrierList, ICollisionHandler *results, + void *userdata, bool force_single_sided) { + bool hit = false; + if (!barrierList || barrierList->empty()) { + return hit; + } + + const WCollisionBarrierList &barriers = *barrierList; + UMath::Matrix4 mat = UMath::Matrix4::kIdentity; + + for (const WCollisionBarrierListEntry *iter = barriers.begin(); iter != barriers.end(); ++iter) { + if (!SurfacePassesExclusion(iter->fB.GetWSurface())) { + continue; + } + + UMath::Vector4 bcp; + iter->fB.GetCenter(bcp); + + UMath::Vector3 &vR = UMath::Vector4To3(mat[0]); + UMath::Vector3 &vU = UMath::Vector4To3(mat[1]); + UMath::Vector3 &vF = UMath::Vector4To3(mat[2]); + + iter->fB.GetNormal(vF); + + float w = iter->fB.GetWidth(); + float h = iter->fB.GetHeight(); + + UMath::Vector3 bdim; + memset(&bdim, 0, sizeof(bdim)); + bdim.x = w * 0.5f; + bdim.y = h * 0.5f; + + vU = UMath::Vector3Make(0.0f, 1.0f, 0.0f); + + UMath::Cross(vU, vF, vR); + + Dynamics::Collision::Geometry bgeom(mat, UVector3(bcp), bdim, Dynamics::Collision::Geometry::BOX, UMath::Vector3::kZero); + + if (!Dynamics::Collision::Geometry::FindIntersection(geom, &bgeom, geom)) { + continue; + } + + if (force_single_sided || !iter->fB.GetWSurface().HasFlag(0x10)) { + if (UMath::Dot(vF, geom->GetCollisionNormal()) <= 0.0f) { + continue; + } + } + + hit = true; + + if (!results) { + return true; + } + + float penetration = -geom->GetOverlap(); + + WorldCollisionInfo cInfo; + cInfo.fNormal = UMath::Vector4Make(geom->GetCollisionNormal(), penetration); + cInfo.fBle = *iter; + cInfo.fType = 2; + cInfo.fCollidePt = UMath::Vector4Make(geom->GetCollisionPoint(), penetration); + + if (cInfo.fCInst && cInfo.fCInst->IsDynamic()) { + cInfo.fAnimated = true; + } + + UMath::Vector3 dP; + UMath::Scale(geom->GetCollisionNormal(), -geom->GetOverlap(), dP); + + UMath::Vector3 cPoint; + cPoint = geom->GetCollisionPoint(); + + if (geom->PenetratesOther()) { + UMath::Add(cPoint, dP, cPoint); + } + + if (results->OnWCollide(cInfo, cPoint, userdata)) { + geom->Move(dP); + } + } + + return hit; +} + +bool WCollisionMgr::Collide(Dynamics::Collision::Geometry *geom, const WCollisionInstanceCacheList *instanceList, ICollisionHandler *results, + void *userdata) { + bool hit = false; + + if (instanceList != nullptr && !instanceList->empty()) { + unsigned int i; + unsigned int j; + static const UMath::Vector4 offsets[2][4] = { + {{-1.0f, -1.0f, 1.0f, 1.0f}, {1.0f, 1.0f, -1.0f, 1.0f}, {-1.0f, 1.0f, 1.0f, 1.0f}, {1.0f, -1.0f, -1.0f, 1.0f}}, + {{1.0f, 1.0f, 1.0f, 1.0f}, {-1.0f, -1.0f, -1.0f, 1.0f}, {1.0f, -1.0f, 1.0f, 1.0f}, {-1.0f, 1.0f, -1.0f, 1.0f}}}; + unsigned int num2check; + UMath::Vector4 arms[2][4]; + UMath::Vector4 dim = UMath::Vector4Make(geom->GetDimension(), 1.0f); + UMath::Vector4 cp = UMath::Vector4Make(geom->GetPosition(), 1.0f); + UMath::Vector4 seg[2]; + UMath::Vector4 delta; + + for (i = 0; i < 4; i++) { + for (j = 0; j < 2; j++) { + seg[0].w = 1.0f; + UMath::Scalexyz(offsets[0][i * 2 + j], dim, seg[0]); + UMath::Rotate(seg[0], geom->GetOrientation(), seg[1]); + seg[1].w = 1.0f; + UMath::Add(seg[1], cp, arms[0][i * 2 + j]); + arms[0][i * 2 + j].w = 1.0f; + } + } + + delta = UMath::Vector4::kIdentity; + + for (i = 0; i < 4; i++) { + UMath::Addxyz(arms[0][i * 2], delta, seg[0]); + UMath::Addxyz(arms[0][i * 2 + 1], delta, seg[1]); + + WorldCollisionInfo cInfo; + + if (GetWorldNormal(instanceList, nullptr, seg, cInfo)) { + if (results == nullptr) { + return true; + } + + UMath::Vector4 penVec; + UMath::Vector4 cPoint; + float penetration; + + float dist0 = UMath::DistanceSquarexyz(seg[0], cInfo.fCollidePt); + float dist1 = UMath::DistanceSquarexyz(seg[1], cInfo.fCollidePt); + + if (dist0 < dist1) { + cPoint = seg[0]; + } else { + cPoint = seg[1]; + } + + UMath::Subxyz(cPoint, cInfo.fCollidePt, penVec); + penVec.w = 0.0f; + cInfo.fNormal.w = 1.0f; + penetration = -UMath::Dotxyz(penVec, cInfo.fNormal); + if (penetration < 0.0f) { + UMath::Negatexyz(cInfo.fNormal); + penetration = -penetration; + } + + cInfo.fNormal.w = penetration; + cPoint.w = penetration; + + if (results->OnWCollide(cInfo, UMath::Vector4To3(cPoint), userdata)) { + UMath::ScaleAddxyz(cInfo.fNormal, cInfo.fNormal.w, delta, delta); + } + + hit = true; + } + } + } + + return hit; +} + +bool WCollisionMgr::GetClosestIntersectingBarrier(const WCollisionBarrierList &barrierList, const UMath::Vector4 *testSegment, WorldCollisionInfo &cInfo) { + cInfo.fType = 0; + float closestDistSq = FLT_MAX; + const WCollisionBarrierListEntry *ret = nullptr; + + for (const WCollisionBarrierListEntry *bIter = barrierList.begin(); bIter != barrierList.end(); ++bIter) { + const WCollisionBarrier *barrier = &bIter->fB; + if (!SurfacePassesExclusion(barrier->GetWSurface())) { + continue; + } + UMath::Vector4 intersectionPt; + if (WWorldMath::SegmentIntersect(testSegment, barrier->GetPts(), &intersectionPt)) { + float y = barrier->YTop(); + float yBot = barrier->YBot(); + if (y > yBot) { + y = yBot; + } + if (intersectionPt.y > y) { + y = barrier->YTop(); + if (y < yBot) { + y = yBot; + } + if (intersectionPt.y < y) { + float distSq = UMath::DistanceSquare(UMath::Vector4To3(intersectionPt), UMath::Vector4To3(*testSegment)); + if (distSq < closestDistSq) { + cInfo.fCollidePt = intersectionPt; + ret = bIter; + closestDistSq = distSq; + } + } + } + } + } + + if (ret != nullptr) { + cInfo.fBle = *ret; + cInfo.fType = 2; + } + return cInfo.HitSomething(); +} + +bool WCollisionMgr::GetWorldNormal(const WCollisionInstanceCacheList *instList, const WCollisionBarrierList *barrierList, const UMath::Vector4 *seg, + WorldCollisionInfo &cInfo) { + WorldCollisionInfo cInfoFaces; + WorldCollisionInfo cInfoBarrier; + + if (barrierList != nullptr) { + GetBarrierNormal(*barrierList, seg, cInfoBarrier); + } + + if (instList != nullptr) { + static WWorldPos wPos(2.0f); + + wPos.FindClosestFace(*instList, UMath::Vector4To3(seg[0]), UMath::Vector4To3(seg[1])); + + if (wPos.OnValidFace()) { + float t; + + wPos.UNormal(&UMath::Vector4To3(cInfoFaces.fNormal)); + cInfoFaces.fNormal.w = 1.0f; + + if (WWorldMath::IntersectSegPlane( + UMath::Vector4To3(seg[0]), + UMath::Vector4To3(seg[1]), + UMath::Vector4To3(wPos.FacePoint(0)), + UMath::Vector4To3(cInfoFaces.fNormal), + UMath::Vector4To3(cInfoFaces.fCollidePt), + t)) { + cInfoFaces.fType = 1; + cInfoFaces.fAnimated = 0; + cInfoFaces.fCInst = nullptr; + + UMath::Vector4 hitVec; + UMath::Subxyz(seg[0], cInfoFaces.fCollidePt, hitVec); + float dot = UMath::Dotxyz(cInfoFaces.fNormal, hitVec); + + if (dot < 0.0f) { + UMath::Negatexyz(cInfoFaces.fNormal); + } + } + } + } + + cInfo.fType = 0; + ClosestCollisionInfo(seg, cInfoFaces, cInfoBarrier, cInfo); + return cInfo.HitSomething(); +} + +bool WCollisionMgr::GetBarrierNormal(const WCollisionBarrierList &barrierList, const UMath::Vector4 *testSegment, WorldCollisionInfo &cInfo) { + cInfo.fType = 0; + if (GetClosestIntersectingBarrier(barrierList, testSegment, cInfo)) { + cInfo.fBle.fB.GetNormal(UMath::Vector4To3(cInfo.fNormal)); + cInfo.fNormal.w = 0.0f; + cInfo.fAnimated = 0; + cInfo.fCInst = nullptr; + UMath::Vector3 testVec; + UMath::Sub(UMath::Vector4To3(*testSegment), UMath::Vector4To3(cInfo.fCollidePt), testVec); + if (cInfo.fNormal.x * testVec.x + cInfo.fNormal.z * testVec.z < 0.0f) { + cInfo.fNormal.x = -cInfo.fNormal.x; + cInfo.fNormal.z = -cInfo.fNormal.z; + } + cInfo.fType = 2; + } + return cInfo.HitSomething(); +} + +bool WCollisionMgr::GetBarrierNormal(const WCollisionInstanceCacheList &instList, const UMath::Vector4 *testSegment, WorldCollisionInfo &cInfo) { + const WCollisionBarrier *closestBarrier = nullptr; + const WCollisionInstance *closestBarrierInst = nullptr; + UMath::Vector4 closestIntersectionPt; + float closestDistSq = FLT_MAX; + + for (const WCollisionInstance *const *iIter = instList.begin(); iIter != instList.end(); ++iIter) { + const WCollisionInstance &cInst = **iIter; + const WCollisionArticle *cArt = cInst.fCollisionArticle; + if (cArt != nullptr && cArt->fNumEdges != 0) { + UMath::Matrix4 invMat; + UMath::Vector4 tseg[2]; + cInst.MakeMatrix(invMat, true); + UMath::RotateTranslate(UMath::Vector4To3(testSegment[0]), invMat, UMath::Vector4To3(tseg[0])); + UMath::RotateTranslate(UMath::Vector4To3(testSegment[1]), invMat, UMath::Vector4To3(tseg[1])); + + const WCollisionBarrier *barrier = cArt->GetBarrier(0); + for (int i = 0; i < cArt->fNumEdges; ++i) { + if (!SurfacePassesExclusion(barrier->GetWSurface())) { + barrier = barrier->Next(); + continue; + } + UMath::Vector4 intersectionPt; + if (WWorldMath::SegmentIntersect(tseg, barrier->GetPts(), &intersectionPt)) { + float yTop = barrier->YTop(); + float yBot = barrier->YBot(); + float yMin = yTop; + if (yTop > yBot) { + yMin = yBot; + } + if (intersectionPt.y > yMin) { + if (yTop < yBot) { + yTop = yBot; + } + if (intersectionPt.y < yTop) { + float distSq = UMath::DistanceSquare( + UMath::Vector4To3(intersectionPt), + UMath::Vector4To3(tseg[0])); + if (distSq < closestDistSq) { + UMath::Copy(intersectionPt, closestIntersectionPt); + closestBarrier = barrier; + closestBarrierInst = &cInst; + closestDistSq = distSq; + } + } + } + } + barrier = barrier->Next(); + } + } + } + + cInfo.fType = 0; + if (closestBarrier != nullptr) { + cInfo.fCInst = closestBarrierInst; + cInfo.fType = 2; + cInfo.fAnimated = closestBarrierInst->IsDynamic(); + + UMath::Matrix4 invMat; + closestBarrierInst->MakeMatrix(invMat, true); + OrthoInverse(invMat); + + { + WCollisionBarrier b; + UMath::RotateTranslate(UMath::Vector4To3(*closestBarrier->GetPt(0)), invMat, UMath::Vector4To3(b.fPts[0])); + UMath::RotateTranslate(UMath::Vector4To3(*closestBarrier->GetPt(1)), invMat, UMath::Vector4To3(b.fPts[1])); + b.fPts[0].w = closestBarrier->fPts[0].w; + b.fPts[1].w = closestBarrier->fPts[1].w; + + const Attrib::Collection *surfaceHash = closestBarrierInst->fCollisionArticle->GetSurface(b.GetWSurface().Surface()); + + WCollisionBarrierListEntry ble(b, surfaceHash, closestDistSq); + cInfo.fBle = ble; + } + + UMath::RotateTranslate(UMath::Vector4To3(closestIntersectionPt), invMat, UMath::Vector4To3(cInfo.fCollidePt)); + cInfo.fCollidePt.w = 0.0f; + + cInfo.fBle.fB.GetNormal(UMath::Vector4To3(cInfo.fNormal)); + cInfo.fNormal.y = 0.0f; + cInfo.fNormal.w = 0.0f; + + { + UMath::Vector3 testVec; + UMath::Sub(UMath::Vector4To3(*testSegment), UMath::Vector4To3(cInfo.fCollidePt), testVec); + if (cInfo.fNormal.x * testVec.x + cInfo.fNormal.z * testVec.z < 0.0f) { + cInfo.fNormal.x = -cInfo.fNormal.x; + cInfo.fNormal.z = -cInfo.fNormal.z; + } + } + } + return cInfo.HitSomething(); +} + +void WCollisionMgr::GetBarrierList(WCollisionBarrierList &barrierList, const WCollisionInstanceCacheList &instList, const UMath::Vector3 &pos, float radius) { + float radiusSq = radius * radius; + barrierList.reserve(0x15); + + for (const WCollisionInstance *const *iIter = instList.begin(); iIter != instList.end(); ++iIter) { + const WCollisionInstance &cInst = **iIter; + + if (!InstancePassesExclusion(cInst)) continue; + + const WCollisionArticle *cArt = cInst.fCollisionArticle; + if (cArt == nullptr || cArt->fNumEdges == 0) continue; + + UMath::Vector3 tpt; + UMath::Matrix4 invMat; + UTransform t; + bool tValid = false; + + cInst.MakeMatrix(invMat, true); + UMath::RotateTranslate(pos, invMat, tpt); + + const WCollisionBarrier *barrier = cArt->GetBarrier(0); + for (int i = 0; i < cArt->fNumEdges; ++i) { + if (SurfacePassesExclusion(barrier->GetWSurface())) { + float distsqr = barrier->DistSq(tpt); + if (distsqr < radiusSq) { + float yBot = barrier->YBot(); + float yTop = barrier->YTop(); + float yMin = yBot; + if (yTop < yBot) { + yMin = yTop; + } + if (yMin < tpt.y + radius) { + float yMax = yBot; + if (yBot < yTop) { + yMax = yTop; + } + if (tpt.y - radius <= yMax) { + if (!tValid) { + t.fTransform = invMat; + t.fTransform[0][3] = 0.0f; + t.fTransform[1][3] = 0.0f; + t.fTransform[2][3] = 0.0f; + t.fTransform[3][3] = 1.0f; + OrthoInverse(t.fTransform); + tValid = true; + } + + WCollisionBarrier wBarrier = *barrier; + UMath::RotateTranslate(UMath::Vector4To3(wBarrier.fPts[0]), t.fTransform, UMath::Vector4To3(wBarrier.fPts[0])); + UMath::RotateTranslate(UMath::Vector4To3(wBarrier.fPts[1]), t.fTransform, UMath::Vector4To3(wBarrier.fPts[1])); + wBarrier.fPts[0].w = barrier->fPts[0].w; + wBarrier.fPts[1].w = barrier->fPts[1].w; + + const Attrib::Collection *collection = cArt->GetSurface(wBarrier.GetWSurface().Surface()); + WCollisionBarrierListEntry ble(wBarrier, collection, distsqr); + + WCollisionBarrierListEntry *it = std::upper_bound(barrierList.begin(), barrierList.end(), ble); + barrierList.insert(it, ble); + } + } + } + } + barrier = barrier->Next(); + } + } +} + +bool WCollisionMgr::FindFaceInTriStrip(const UMath::Vector3 &pt, const WCollisionStripSphere *sp, const WCollisionStrip *strip, + WCollisionTri &retFace) { + if (!StripPassesExclusion(*strip)) { + return false; + } + + int numTris = strip->NumTris(); + strip->MakeFace(0, sp->fPos, retFace); + + if (strip->Flags() & 2) { + for (int i = 0; i < numTris; ) { + const WSurface &surf = retFace.fSurface; + if (!surf.HasFlag(8)) { + if (WWorldMath::InTri(pt, reinterpret_cast(&retFace))) { + if (SurfacePassesExclusion(surf)) { + if (retFace.MinY() < pt.y + 1.0f) { + return true; + } + } + } + } + ++i; + if (i >= numTris) break; + strip->MakeNextFace(i, sp->fPos, retFace); + } + } else { + bool rightFlag = (strip->Flags() & 1) != 0; + strip->MakeFace(0, sp->fPos, retFace); + for (int i = 0; i < numTris; ) { + if (rightFlag) { + const WSurface &surf = retFace.fSurface; + if (!surf.HasFlag(8)) { + if (WWorldMath::InTriR(pt, reinterpret_cast(&retFace))) { + if (SurfacePassesExclusion(surf)) { + return true; + } + } + } + } else { + const WSurface &surf = retFace.fSurface; + if (!surf.HasFlag(8)) { + if (WWorldMath::InTriL(pt, reinterpret_cast(&retFace))) { + if (SurfacePassesExclusion(surf)) { + return true; + } + } + } + } + ++i; + rightFlag = !rightFlag; + if (i >= numTris) break; + strip->MakeNextFace(i, sp->fPos, retFace); + } + } + + return false; +} + +bool WCollisionMgr::FindFaceInTriStrip(const UMath::Matrix4 &vectorMat, const UMath::Vector3 &pt, + const WCollisionStripSphere *sp, const WCollisionStrip *strip, + float &faceY, WCollisionTri &retFace) { + if (!StripPassesExclusion(*strip)) { + return false; + } + + int numTris = strip->NumTris(); + bool foundFace = false; + WCollisionTri face; + strip->MakeFace(0, sp->fPos, face); + + int retFaceInd = 0; + float bestFaceY = 1e38f; + + UMath::RotateTranslate(face.fPt0, vectorMat, face.fPt0); + UMath::RotateTranslate(face.fPt1, vectorMat, face.fPt1); + + for (int i = 0; i < numTris; ) { + UMath::RotateTranslate(face.fPt2, vectorMat, face.fPt2); + if (WWorldMath::InTri(pt, reinterpret_cast(&face))) { + UMath::Vector3 norm; + CalcCollisionFaceNormal(&norm, reinterpret_cast(&face)); + float y = pt.y - WWorldMath::GetPlaneY(norm, face.fPt2, pt); + faceY = y; + if (y < bestFaceY && -1.0f < y && SurfacePassesExclusion(face.fSurface)) { + bestFaceY = y; + retFaceInd = i; + foundFace = true; + } + } + ++i; + if (i >= numTris) break; + strip->MakeNextFace(i, sp->fPos, face); + } + + if (foundFace) { + strip->MakeFace(retFaceInd, sp->fPos, retFace); + } + faceY = bestFaceY; + return foundFace; +} + +extern "C" void v3sub(int num, const UMath::Vector3 *src, const UMath::Vector3 *vtosub, UMath::Vector3 *results); + +struct AABB { + bVector2 mMin; + bVector2 mMax; + + AABB(const UMath::Vector3 &pt, float radius) { + mMin.x = pt.x - radius; + mMin.y = pt.z - radius; + mMax.x = pt.x + radius; + mMax.y = pt.z + radius; + } + + AABB(const UMath::Vector3 &pt1, const UMath::Vector3 &pt2, const UMath::Vector3 &pt3) { + mMin.x = bMin(pt3.x, bMin(pt1.x, pt2.x)); + mMin.y = bMin(pt3.z, bMin(pt1.z, pt2.z)); + mMax.x = bMax(pt3.x, bMax(pt1.x, pt2.x)); + mMax.y = bMax(pt3.z, bMax(pt1.z, pt2.z)); + } + + bool Overlap(const AABB &test) { + if (!(test.mMin.x > mMax.x)) { + if (!(test.mMin.y > mMax.y)) { + if (test.mMax.x >= mMin.x) { + return test.mMax.y >= mMin.y; + } + } + } + return false; + } +}; + +inline float PTDir(const UMath::Vector3 &vert, const UMath::Vector3 &p0, const UMath::Vector3 &p1) { + float x0 = vert.x - p0.x; + float z0 = vert.z - p0.z; + float x1 = p1.x - p0.x; + float z1 = p1.z - p0.z; + return x1 * z0 - x0 * z1; +} + +inline float PtDir(const UMath::Vector3 &p1, const UMath::Vector3 &p2, const UMath::Vector3 &p3) { + return (p2.x - p3.x) * (p1.z - p3.z) - (p1.x - p3.x) * (p2.z - p3.z); +} + +inline float XZDistSq(const UMath::Vector3 &p0, const UMath::Vector3 &p1) { + return (p0.x - p1.x) * (p0.x - p1.x) + (p0.z - p1.z) * (p0.z - p1.z); +} + +void WCollisionMgr::GetTriList(const WCollisionInstanceCacheList &instList, const UMath::Vector3 &pt, float radius, WCollisionTriList &triList) { + float radiusSq = radius * radius; + + for (const WCollisionInstance *const *iIter = instList.begin(); iIter != instList.end(); ++iIter) { + const WCollisionInstance &cInst = **iIter; + const WCollisionArticle *cArt = cInst.fCollisionArticle; + if (cArt != nullptr) { + UMath::Vector3 ipt; + UMath::Vector3 tpt; + UMath::Matrix4 invMat; + + ipt = pt; + cInst.MakeMatrix(invMat, true); + UMath::RotateTranslate(ipt, invMat, tpt); + + AABB regionAABB(pt, radius); + + const WCollisionStripSphere *sp = cArt->GetStripSphere(0); + for (int i = 0; i < cArt->fNumStrips; ++i, ++sp) { + UMath::Vector3 diffVec; + v3sub(1, &sp->fPos, &tpt, &diffVec); + + float spRadius = static_cast(static_cast(sp->fRadius)) * (1.0f / 16.0f) + radius; + float dSq = diffVec.x * diffVec.x + diffVec.z * diffVec.z; + if (dSq < spRadius * spRadius) { + const WCollisionStrip *strip = reinterpret_cast( + reinterpret_cast(cArt) + sp->Offset()); + int numTris = strip->NumTris(); + WCollisionTri face; + UMath::Vector3 off; + + UMath::Sub(sp->fPos, UMath::Vector4To3(invMat.v3), off); + + strip->MakeFace(0, off, face); + + for (int i = 0; i < numTris; ++i) { + AABB faceAABB(face.fPt1, face.fPt2, face.fPt0); + if (faceAABB.Overlap(regionAABB)) { + UMath::Vector3 nearPt; + float dir = PTDir(face.fPt1, face.fPt0, face.fPt2); + float side; + + side = PtDir(pt, face.fPt0, face.fPt1); + if (!(side * dir > 0.0f)) { + WWorldMath::NearestPointLine2D3(pt, face.fPt0, face.fPt1, nearPt); + if (!(side * dir > 0.0f)) { + if (XZDistSq(pt, nearPt) >= radiusSq) { + goto next_tri; + } + } + } + + side = PtDir(pt, face.fPt1, face.fPt2); + if (!(side * dir > 0.0f)) { + WWorldMath::NearestPointLine2D3(pt, face.fPt1, face.fPt2, nearPt); + if (!(side * dir > 0.0f)) { + if (XZDistSq(pt, nearPt) >= radiusSq) { + goto next_tri; + } + } + } + + side = PtDir(pt, face.fPt2, face.fPt0); + if (!(side * dir > 0.0f)) { + WWorldMath::NearestPointLine2D3(pt, face.fPt2, face.fPt0, nearPt); + if (!(side * dir > 0.0f)) { + if (XZDistSq(pt, nearPt) >= radiusSq) { + goto next_tri; + } + } + } + + face.fSurfaceRef = reinterpret_cast(cArt->GetSurface(face.fSurface.Surface())); + triList.add_tri(face); + } + next_tri: + if (i + 1 < numTris) { + strip->MakeNextFace(i + 1, off, face); + } + } + } + } + } + } +} + +inline void MakeWorldSpaceFace(WCollisionTri &worldFace, const WCollisionTri &localFace, const UMath::Matrix4 &invMat) { + UTransform t(invMat); + OrthoInverse(t.fTransform); + worldFace.fSurface = localFace.fSurface; + worldFace.fFlags = localFace.fFlags; + UMath::RotateTranslate(localFace.fPt0, t.fTransform, worldFace.fPt0); + UMath::RotateTranslate(localFace.fPt1, t.fTransform, worldFace.fPt1); + UMath::RotateTranslate(localFace.fPt2, t.fTransform, worldFace.fPt2); +} + +bool WCollisionMgr::FindFaceInCInst(const UMath::Vector3 &pt, const WCollisionInstance &cInst, WCollisionTri &retFace, float &retDist) { + UMath::Vector3 tpt; + UMath::Matrix4 invMat; + + cInst.MakeMatrix(invMat, true); + UMath::RotateTranslate(pt, invMat, tpt); + + const WCollisionArticle *cArt = cInst.fCollisionArticle; + + if (cArt == nullptr || cArt->fNumStrips == 0 || + tpt.x > cInst.fInvMatRow0Width.w || tpt.x < -cInst.fInvMatRow0Width.w || + tpt.z > cInst.fInvMatRow2Length.w || tpt.z < -cInst.fInvMatRow2Length.w) { + return false; + } + + WCollisionTri retVal; + bool foundFace; + float leastYDist = 1e38f; + foundFace = false; + const WCollisionStripSphere *sp = cArt->GetStripSphere(0); + + for (int i = 0; i < cArt->fNumStrips; ++sp, ++i) { + UMath::Vector3 diffVec; + UMath::Sub(sp->fPos, tpt, diffVec); + float radius = static_cast(static_cast(sp->fRadius)) * (1.0f / 16.0f); + float dSq = diffVec.x * diffVec.x + diffVec.z * diffVec.z; + float radsSq = radius * radius; + if (dSq < radsSq) { + const WCollisionStrip *strip = reinterpret_cast( + reinterpret_cast(cArt) + sp->Offset()); + WCollisionTri face; + if (FindFaceInTriStrip(tpt, sp, strip, face)) { + UMath::Vector3 norm; + face.GetNormal(&norm); + if (norm.y < 0.0f) { + norm.y = -norm.y; + norm.x = -norm.x; + norm.z = -norm.z; + } + if (norm.y >= 0.9999f) { + norm.y = 0.9999f; + } + float dist = tpt.y - WWorldMath::GetPlaneY(norm, face.fPt0, tpt); + + bool minYBelowTestPoint = (face.MinY() - 0.5f) < tpt.y; + + if (cInst.IsYVecNotUp()) { + minYBelowTestPoint = (face.MinY() - 0.5f) > tpt.y; + } + + if (minYBelowTestPoint && 0.0f < dist && dist < leastYDist) { + foundFace = true; + leastYDist = dist; + retVal = face; + retDist = dist; + } + } + } + } + + if (foundFace) { + MakeWorldSpaceFace(retFace, retVal, invMat); + return true; + } + + return false; +} + +bool WCollisionMgr::FindFaceInCInst(const UMath::Matrix4 &vectorMat, const UMath::Vector3 &endPt, + const WCollisionInstance &cInst, WCollisionTri &retFace, float &retDist) { + UMath::Matrix4 invMat; + cInst.MakeMatrix(invMat, true); + + UTransform mat(invMat); + OrthoInverse(mat.fTransform); + UTransform vecMatInv(vectorMat); + OrthoInverse(vecMatInv.fTransform); + + UMath::Matrix4 combinedMat; + UMath::Mult(mat.fTransform, vecMatInv.fTransform, combinedMat); + + const WCollisionArticle *cArt = cInst.fCollisionArticle; + if (cArt == nullptr) { + return false; + } + + WCollisionTri retVal; + float leastYDist = 1e38f; + + UMath::Vector3 tp0; + UMath::Vector3 tp1; + + const UMath::Vector3 &startPt = UMath::Vector4To3(vectorMat.v3); + bool foundFace = false; + UMath::RotateTranslate(startPt, invMat, tp0); + + const WCollisionStripSphere *sp = cArt->GetStripSphere(0); + UMath::RotateTranslate(endPt, invMat, tp1); + + int i = 0; + + UMath::Vector3 npVec; + UMath::Sub(tp1, tp0, npVec); + float invDen = 1.0f / (npVec.x * npVec.x + npVec.y * npVec.y + npVec.z * npVec.z); + + for (; i < cArt->fNumStrips; ++i, ++sp) { + float radius = static_cast(static_cast(sp->fRadius)) * (1.0f / 16.0f); + float radsSq = radius * radius; + + UMath::Vector3 diffVec; + UMath::Vector3 nearPt; + NearPtLine(sp->fPos, tp0, invDen, npVec, nearPt); + UMath::Sub(sp->fPos, nearPt, diffVec); + float dSq = diffVec.x * diffVec.x + diffVec.y * diffVec.y + diffVec.z * diffVec.z; + + if (dSq < radsSq) { + const WCollisionStrip *strip = reinterpret_cast( + reinterpret_cast(cArt) + sp->Offset()); + WCollisionTri face; + float faceY; + if (FindFaceInTriStrip(combinedMat, UMath::Vector3::kZero, sp, strip, faceY, face)) { + if (0.0f < faceY && faceY < leastYDist) { + leastYDist = faceY; + retVal = face; + retDist = faceY; + foundFace = true; + } + } + } + } + + if (foundFace) { + MakeWorldSpaceFace(retFace, retVal, invMat); + return true; + } + + return false; +} diff --git a/src/Speed/Indep/Src/World/Common/WCollisionPack.cpp b/src/Speed/Indep/Src/World/Common/WCollisionPack.cpp index e69de29bb..6b1b85127 100644 --- a/src/Speed/Indep/Src/World/Common/WCollisionPack.cpp +++ b/src/Speed/Indep/Src/World/Common/WCollisionPack.cpp @@ -0,0 +1,139 @@ +#include "Speed/Indep/Src/World/WCollisionPack.h" + +#include "Speed/Indep/Src/Sim/SimSurface.h" + +static const unsigned int kWCollisionArticleGroupTag = 0x41727469; +static const unsigned int kWCollisionInstanceTag = 0x63690000; +static const unsigned int kWCollisionObjectTag = 0x636F0000; +static const unsigned int kDefaultCollisionArticleTag = 0x63612020; + +WCollisionPack::WCollisionPack(bChunk *chunk) + : mSectionNumber(0), // + mInstanceNum(0), // + mInstanceList(nullptr), // + mObjectNum(0), // + mObjectList(nullptr), // + mCarpChunkHeader(reinterpret_cast(chunk->GetAlignedData(0x10))) { + Init(chunk); +} + +WCollisionPack::~WCollisionPack() { + DeInit(); +} + +void WCollisionPack::Init(bChunk *chunk) { + const void *carpSource; + int carpSize[1]; + unsigned int deltaRelocationOffset; + const UGroup *carpGroup; + + if (mCarpChunkHeader->GetFlags() & 1) { + } else { + mCarpChunkHeader->PlatformEndianSwap(); + } + + carpSource = mCarpChunkHeader + 1; + mSectionNumber = mCarpChunkHeader->GetSectionNumber(); + deltaRelocationOffset = 0; + carpSize[0] = mCarpChunkHeader->GetCarpSize(); + + if (mCarpChunkHeader->GetFlags() & 1) { + deltaRelocationOffset = + reinterpret_cast(mCarpChunkHeader) - reinterpret_cast(mCarpChunkHeader->GetLastAddress()); + } + + if (mCarpChunkHeader != mCarpChunkHeader->GetLastAddress()) { + carpGroup = UGroup::Deserialize(1, reinterpret_cast(carpSize), &carpSource, deltaRelocationOffset); + } else { + carpGroup = reinterpret_cast(carpSource); + } + + Resolve(carpGroup->GroupLocateTag(kWCollisionArticleGroupTag), 0); + mCarpChunkHeader->SetLastAddress(mCarpChunkHeader); + mCarpChunkHeader->SetResolved(); + + (void)chunk; +} + +void WCollisionPack::DeInit() { + mSectionNumber = 0; + mInstanceList = nullptr; + mObjectList = nullptr; + mInstanceNum = 0; + mObjectNum = 0; +} + +void WCollisionArticle::Resolve() { + if (!fResolvedFlag) { + for (int i = 0; i < fNumSurfaces; ++i) { + unsigned int *surfaceData = reinterpret_cast(reinterpret_cast(this) + (fStripsSize + 0x10) + fEdgesSize); + UCrc32 crc(surfaceData[i]); + surfaceData[i] = reinterpret_cast(SimSurface::Lookup(crc)); + } + fResolvedFlag = true; + } +} + +void WCollisionPack::Resolve(const UGroup *cGroup, unsigned int deltaAddress) { + unsigned int i = 0; + const UData *instanceUData = cGroup->DataLocateTag(kWCollisionInstanceTag); + const UData *objectUData = cGroup->DataLocateTag(kWCollisionObjectTag); + + if (instanceUData != reinterpret_cast( + reinterpret_cast(cGroup->GetArray()) + cGroup->fGroupCount * sizeof(UGroup) + + cGroup->fDataCount * sizeof(UData))) { + mInstanceList = reinterpret_cast(instanceUData->GetDataConst()); + mInstanceNum = instanceUData->DataCount(); + } else { + mInstanceList = nullptr; + mInstanceNum = i; + } + + if (objectUData != reinterpret_cast( + reinterpret_cast(cGroup->GetArray()) + cGroup->fGroupCount * sizeof(UGroup) + + cGroup->fDataCount * sizeof(UData))) { + mObjectList = reinterpret_cast(objectUData->GetDataConst()); + mObjectNum = objectUData->DataCount(); + } else { + mObjectList = nullptr; + mObjectNum = 0; + } + + i = 0; + for (; i < mInstanceNum; ++i) { + WCollisionInstance *cInst = const_cast(&mInstanceList[i]); + if (deltaAddress == 0) { + unsigned int articleTag = kDefaultCollisionArticleTag; + int renderInstanceInd = cInst->fRenderInstanceInd; + if (renderInstanceInd != -1) { + articleTag = 0x63610000 | renderInstanceInd; + } + + const UData *articleUData = cGroup->DataLocateTag(articleTag); + WCollisionArticle *cArt; + if (articleUData != reinterpret_cast( + reinterpret_cast(cGroup->GetArray()) + cGroup->fGroupCount * sizeof(UGroup) + + cGroup->fDataCount * sizeof(UData))) { + cArt = reinterpret_cast(const_cast(articleUData->GetDataConst())); + } else { + cArt = nullptr; + } + + cInst->fCollisionArticle = cArt; + if (cArt) { + cArt->Resolve(); + } + } else if (cInst->fCollisionArticle != nullptr) { + cInst->fCollisionArticle = + reinterpret_cast(reinterpret_cast(cInst->fCollisionArticle) + deltaAddress); + } + } +} + +const WCollisionInstance *WCollisionPack::Instance(unsigned short index) const { + return &mInstanceList[index]; +} + +const WCollisionObject *WCollisionPack::Object(unsigned short index) const { + return &mObjectList[index]; +} diff --git a/src/Speed/Indep/Src/World/Common/WCollisionTri.cpp b/src/Speed/Indep/Src/World/Common/WCollisionTri.cpp index e69de29bb..21ddd21cf 100644 --- a/src/Speed/Indep/Src/World/Common/WCollisionTri.cpp +++ b/src/Speed/Indep/Src/World/Common/WCollisionTri.cpp @@ -0,0 +1 @@ +#include "Speed/Indep/Src/World/WCollisionTri.h" \ No newline at end of file diff --git a/src/Speed/Indep/Src/World/Common/WGrid.cpp b/src/Speed/Indep/Src/World/Common/WGrid.cpp index e69de29bb..b59f73abf 100644 --- a/src/Speed/Indep/Src/World/Common/WGrid.cpp +++ b/src/Speed/Indep/Src/World/Common/WGrid.cpp @@ -0,0 +1,384 @@ +#include "Speed/Indep/Src/World/Common/WGrid.h" +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#include + +inline int FLOAT2INT(float f) { + return static_cast< int >(f); +} + +WGrid::WGrid(const UMath::Vector4 &min, unsigned int rows, unsigned int cols, float edgeSize) { + fMin = min; + fEdgeSize = edgeSize; + fInvEdgeSize = 1.0f / edgeSize; + fNumCols = cols; + fNumRows = rows; + fNodes = static_cast(bMalloc(cols * 4 * rows, 0)); + for (int i = 0; i < static_cast(rows * cols); i++) { + fNodes[i] = 0; + } +} + +WGrid::~WGrid() { + bFree(fNodes); +} + +void WGrid::Init(const UGroup *mapGroup) { + fgMapGroup = mapGroup; + if (mapGroup != nullptr) { + const UGroup *cDatGroup = mapGroup->GroupLocate('CD', 'at'); + const UData *carpGridData = cDatGroup->DataLocate('CG', 'rd'); + const WGrid *carpGrid = static_cast(carpGridData->GetDataConst()); + + if (carpGrid->fNumRows == 0 || carpGrid->fNumCols == 0) { + fgGrid = new WGrid(UMath::Vector4Make(-10000.0f, -10000.0f, -10000.0f, 1.0f), 2, 2, 10000.0f); + } else { + fgGrid = new WGrid(carpGrid->fMin, carpGrid->fNumRows, carpGrid->fNumCols, carpGrid->fEdgeSize); + } + + unsigned int numNodes = cDatGroup->DataCountType(UDataGroupTag('CG', 'cn')); + const UData *nodeDat = cDatGroup->DataLocateFirst(UDataGroupTag('CG', 'cn'), 0xFFFFFFFF, 0xFFFFFFFF); + if (nodeDat != cDatGroup->DataEnd()) { + const WGridNode *n = static_cast(nodeDat->GetDataConst()); + for (unsigned int i = 0; i < nodeDat->DataCount(); i++) { + fgGrid->fNodes[n->GetNodeInd()] = const_cast(n); + n = reinterpret_cast(reinterpret_cast(n) + n->TotalSize()); + } + } + } else { + fgGrid = new WGrid(UMath::Vector4Make(-10000.0f, -10000.0f, -10000.0f, 1.0f), 2, 2, 10000.0f); + } +} + +void WGrid::Shutdown() { + if (fgGrid != nullptr) { + for (int i = 0; i < static_cast(fgGrid->fNumRows * fgGrid->fNumCols); i++) { + WGridNode *node = fgGrid->fNodes[i]; + if (node != nullptr) { + node->ShutDown(); + fgGrid->fNodes[i] = nullptr; + } + } + delete fgGrid; + fgGrid = nullptr; + } + fgMapGroup = nullptr; +} + +void WGrid::FindNodes(const UMath::Vector3 &pt, float radius, UTL::Vector &nodeIndList) const { + UMath::Vector4 pts[2]; + pts[0].x = pt.x - radius; + pts[0].z = pt.z - radius; + pts[1].y = 0.0f; + pts[1].x = pt.x + radius; + pts[1].z = pt.z + radius; + pts[0].y = 0.0f; + FindNodesBox(pts, nodeIndList); +} + +void WGrid::FindNodesBox(const UMath::Vector4 *pts, UTL::Vector &nodeIndList) const { + unsigned int colMin; + unsigned int rowMin; + unsigned int colMax; + unsigned int rowMax; + + GetRowCol(UMath::Vector4To3(pts[0]), rowMin, colMin); + GetRowCol(UMath::Vector4To3(pts[1]), rowMax, colMax); + + if (rowMin > rowMax) { + unsigned int temp = rowMin; + rowMin = rowMax; + rowMax = temp; + } + if (colMin > colMax) { + unsigned int temp = colMin; + colMin = colMax; + colMax = temp; + } + + if (rowMax - rowMin > 20) { + rowMax = rowMin; + } + if (colMax - colMin > 20) { + colMax = colMin; + } + + RangeCheckROWCOL(rowMin, colMin); + RangeCheckROWCOL(rowMax, colMax); + + for (unsigned int col = colMin; col <= colMax; col++) { + for (unsigned int row = rowMin; row <= rowMax; row++) { + nodeIndList.push_back(GetNodeInd(row, col)); + } + } +} + +void WGrid::FindNodes(const UMath::Vector4 *seg, UTL::Vector &nodeIndList) const { + static int iMaxNumNodes = 100; + int iNumNodes; + float fDirX = seg[1].x - seg[0].x; + float fDirY = seg[1].z - seg[0].z; + + UMath::Vector2 points[2]; + points[0].x = seg[0].x; + points[0].y = seg[0].z; + points[1].x = seg[1].x; + points[1].y = seg[1].z; + + int iStartPosX = static_cast< int >(FLOAT2INT(points[0].x - fMin.x) * fInvEdgeSize); + int iStartPosY = static_cast< int >(FLOAT2INT(points[0].y - fMin.z) * fInvEdgeSize); + + bool bStartPosOut = false; + if (iStartPosX < 0 || iStartPosX >= static_cast< int >(fNumCols) || + iStartPosY < 0 || iStartPosY >= static_cast< int >(fNumRows)) { + bStartPosOut = true; + } + + int iEndPosX = static_cast< int >(FLOAT2INT(seg[1].x - fMin.x) * fInvEdgeSize); + int iEndPosY = static_cast< int >(FLOAT2INT(seg[1].z - fMin.z) * fInvEdgeSize); + + bool bEndPosOut = false; + if (iEndPosX < 0 || iEndPosX >= static_cast< int >(fNumCols) || + iEndPosY < 0 || iEndPosY >= static_cast< int >(fNumRows)) { + bEndPosOut = true; + } + + if (UMath::Abs(fDirX) < 0.05f) { + fDirX = 0.05f; + } + if (UMath::Abs(fDirY) < 0.05f) { + fDirY = 0.05f; + } + + if (!bStartPosOut) { + if (bEndPosOut) { + float fBarrierPosX; + float fBarrierPosY; + float fBarrierDistX; + float fBarrierDistY; + + if (fDirX <= 0.0f) { + fBarrierPosX = fMin.x + 0.1f; + } else { + fBarrierPosX = static_cast< float >(fNumCols) * fEdgeSize + fMin.x - 0.1f; + } + if (fDirY <= 0.0f) { + fBarrierPosY = fMin.z + 0.1f; + } else { + fBarrierPosY = static_cast< float >(fNumRows) * fEdgeSize + fMin.z - 0.1f; + } + + fBarrierDistX = (fBarrierPosX - points[0].x) / fDirX; + fBarrierDistY = (fBarrierPosY - points[0].y) / fDirY; + + if (fBarrierDistY <= fBarrierDistX) { + points[1].x = fBarrierDistY * fDirX + points[0].x; + } else { + points[1].y = fBarrierDistX * fDirY + points[0].y; + } + + iEndPosX = static_cast< int >(FLOAT2INT(points[1].x - fMin.x) * fInvEdgeSize); + iEndPosY = static_cast< int >(FLOAT2INT(points[1].y - fMin.z) * fInvEdgeSize); + } + } else { + if (bEndPosOut) { + return; + } + + float fBarrierPosX; + float fBarrierPosY; + float fRevDirX = -fDirX; + float fRevDirY = -fDirY; + float fBarrierDistX; + float fBarrierDistY; + + if (fRevDirX <= 0.0f) { + fBarrierPosX = fMin.x + 0.1f; + } else { + fBarrierPosX = static_cast< float >(fNumCols) * fEdgeSize + fMin.x - 0.1f; + } + if (fRevDirY <= 0.0f) { + fBarrierPosY = fMin.z + 0.1f; + } else { + fBarrierPosY = static_cast< float >(fNumRows) * fEdgeSize + fMin.z - 0.1f; + } + + fBarrierDistX = (fBarrierPosX - points[1].x) / fRevDirX; + fBarrierDistY = (fBarrierPosY - points[1].y) / fRevDirY; + + if (fBarrierDistY <= fBarrierDistX) { + points[0].x = fBarrierDistY * fRevDirX + points[1].x; + } else { + points[0].y = fBarrierDistX * fRevDirY + points[1].y; + } + + iStartPosX = static_cast< int >(FLOAT2INT(points[0].x - fMin.x) * fInvEdgeSize); + iStartPosY = static_cast< int >(FLOAT2INT(points[0].y - fMin.z) * fInvEdgeSize); + } + + nodeIndList.push_back(GetNodeInd(iStartPosY, iStartPosX)); + iNumNodes = 1; + + float fLength = UMath::Sqrt(fDirX * fDirX + fDirY * fDirY); + + if (fLength <= fEdgeSize) { + if (iStartPosX == iEndPosX) { + if (iStartPosY == iEndPosY) { + return; + } + } else if (iStartPosY != iEndPosY) { + goto dda_traverse; + } + + nodeIndList.push_back(GetNodeInd(iEndPosY, iEndPosX)); + return; + } + +dda_traverse: + { + float fVx = fDirX / fLength; + float fVy = fDirY / fLength; + + float fInvVx; + float fInvVy; + float fRx; + float fRy; + float fCurX = points[0].x; + float fCurY = points[0].y; + int iCurPosX = iStartPosX; + int iCurPosY = iStartPosY; + bool bEast = fVx < 0.0f; + bool bNorth = fVy < 0.0f; + float fWallX; + float fWallY; + + if (UMath::Abs(fVx) >= 0.0001f) { + fInvVx = 1.0f / fVx; + } else { + fInvVx = 10000.0f; + } + + if (UMath::Abs(fVy) >= 0.0001f) { + fInvVy = 1.0f / fVy; + } else { + fInvVy = 10000.0f; + } + + if (bEast) { + fWallX = floorf(fCurX * fInvEdgeSize) * fEdgeSize; + } else { + fWallX = ceilf(fCurX * fInvEdgeSize) * fEdgeSize; + } + + if (bNorth) { + fWallY = floorf(fCurY * fInvEdgeSize) * fEdgeSize; + } else { + fWallY = ceilf(fCurY * fInvEdgeSize) * fEdgeSize; + } + + if (iCurPosX == iEndPosX) { + goto walk_y; + } + if (iCurPosY == iEndPosY) { + goto walk_x; + } + + dda_step: + fRx = (fWallX - fCurX) * fInvVx; + fRy = (fWallY - fCurY) * fInvVy; + + if (fRy <= fRx) { + fCurX = fRy * fVx + fCurX; + fCurY = fWallY; + if (bNorth) { + iCurPosY--; + fWallY -= fEdgeSize; + } else { + iCurPosY++; + fWallY += fEdgeSize; + } + } else { + fCurY = fRx * fVy + fCurY; + fCurX = fWallX; + if (bEast) { + iCurPosX--; + fWallX -= fEdgeSize; + } else { + iCurPosX++; + fWallX += fEdgeSize; + } + } + + nodeIndList.push_back(GetNodeInd(iCurPosY, iCurPosX)); + iNumNodes++; + + if (iNumNodes > iMaxNumNodes) { + nodeIndList.clear(); + WGrid::Get().FindNodes(UMath::Vector4To3(seg[0]), 1.0f, nodeIndList); + return; + } + + if (iCurPosX != iEndPosX) { + if (iCurPosY != iEndPosY) { + goto dda_step; + } + goto walk_x; + } + + walk_y: + if (iCurPosY == iEndPosY) { + return; + } + + if (bNorth) { + do { + iCurPosY--; + if (iCurPosY < iEndPosY) { + return; + } + nodeIndList.push_back(GetNodeInd(iCurPosY, iCurPosX)); + iNumNodes++; + } while (iNumNodes <= iMaxNumNodes); + } else { + do { + iCurPosY++; + if (iEndPosY < iCurPosY) { + return; + } + nodeIndList.push_back(GetNodeInd(iCurPosY, iCurPosX)); + iNumNodes++; + } while (iNumNodes <= iMaxNumNodes); + } + + nodeIndList.clear(); + WGrid::Get().FindNodes(UMath::Vector4To3(seg[0]), 1.0f, nodeIndList); + return; + + walk_x: + if (iCurPosX == iEndPosX) { + goto walk_y; + } + + if (bEast) { + do { + iCurPosX--; + if (iCurPosX < iEndPosX) { + return; + } + nodeIndList.push_back(GetNodeInd(iCurPosY, iCurPosX)); + iNumNodes++; + } while (iNumNodes <= iMaxNumNodes); + } else { + do { + iCurPosX++; + if (iEndPosX < iCurPosX) { + return; + } + nodeIndList.push_back(GetNodeInd(iCurPosY, iCurPosX)); + iNumNodes++; + } while (iNumNodes <= iMaxNumNodes); + } + + nodeIndList.clear(); + WGrid::Get().FindNodes(UMath::Vector4To3(seg[0]), 1.0f, nodeIndList); + } +} \ No newline at end of file diff --git a/src/Speed/Indep/Src/World/Common/WGrid.h b/src/Speed/Indep/Src/World/Common/WGrid.h index 62782f652..7c92179c8 100644 --- a/src/Speed/Indep/Src/World/Common/WGrid.h +++ b/src/Speed/Indep/Src/World/Common/WGrid.h @@ -5,6 +5,85 @@ #pragma once #endif +#include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "Speed/Indep/Src/World/Common/WGridNode.h" +struct UGroup; + +struct WGrid { + static bool Initialized() { return fgGrid != 0; } + static WGrid &Get() { return *fgGrid; } + + WGrid(const UMath::Vector4 &min, unsigned int rows, unsigned int cols, float edgeSize); + ~WGrid(); + + static void Init(const UGroup *mapGroup); + static void Shutdown(); + static void Restart(); + + void FindNodes(const UMath::Vector3 &pt, float radius, UTL::Vector &nodeIndList) const; + void FindNodesBox(const UMath::Vector4 *pts, UTL::Vector &nodeIndList) const; + void FindNodes(const UMath::Vector4 *seg, UTL::Vector &nodeIndList) const; + void FindNodes(const UMath::Vector4 &min, const UMath::Vector4 &max, UTL::Vector &nodeIndList) const; + void FindNodes(const UMath::Vector3 &pt, float radiusInner, float radiusOuter, UTL::Vector &nodeIndList) const; + bool RangeCheck(const UMath::Vector3 *pts) const; + + inline bool IsValidNode(unsigned short nodeInd) { + return nodeInd < fNumRows * fNumCols; + } + + inline void RangeCheckROWCOL(unsigned int &row, unsigned int &col) const { + if (col >= fNumCols) { + if (col > 0x7FFFFFFEU) { + col = 0; + } else { + col = fNumCols - 1; + } + } + if (row >= fNumRows) { + if (row > 0x7FFFFFFEU) { + row = 0; + } else { + row = fNumRows - 1; + } + } + } + + inline unsigned int GetNodeInd(unsigned int row, unsigned int col) const { + return row * fNumCols + col; + } + + inline void GetRowCol(const UMath::Vector3 &pt, unsigned int &row, unsigned int &col) const { + col = static_cast< int >((pt.x - fMin.x) * fInvEdgeSize); + row = static_cast< int >((pt.z - fMin.z) * fInvEdgeSize); + RangeCheckROWCOL(row, col); + } + + inline void GetRowCol(unsigned int ind, unsigned int &row, unsigned int &col) const { + row = ind / fNumCols; + col = ind - row * fNumCols; + RangeCheckROWCOL(row, col); + } + + inline WGridNode *GetNode(unsigned int row, unsigned int col) const { + return fNodes[GetNodeInd(row, col)]; + } + + inline WGridNode *GetNode(unsigned int ind) const { + unsigned int row, col; + GetRowCol(ind, row, col); + return GetNode(row, col); + } + + static WGrid *fgGrid; + static const UGroup *fgMapGroup; + + UMath::Vector4 fMin; // offset 0x0, size 0x10 + float fEdgeSize; // offset 0x10, size 0x4 + float fInvEdgeSize; // offset 0x14, size 0x4 + unsigned int fNumRows; // offset 0x18, size 0x4 + unsigned int fNumCols; // offset 0x1C, size 0x4 + WGridNode **fNodes; // offset 0x20, size 0x4 +}; #endif diff --git a/src/Speed/Indep/Src/World/Common/WGridManagedDynamicElem.cpp b/src/Speed/Indep/Src/World/Common/WGridManagedDynamicElem.cpp index e69de29bb..5336f31ff 100644 --- a/src/Speed/Indep/Src/World/Common/WGridManagedDynamicElem.cpp +++ b/src/Speed/Indep/Src/World/Common/WGridManagedDynamicElem.cpp @@ -0,0 +1,155 @@ +#include "Speed/Indep/Src/World/WGridManagedDynamicElem.h" + +#include "Speed/Indep/Libs/Support/Utility/UTLVector.h" + +#include "Speed/Indep/Src/Physics/Dynamics/Collision.h" +#include "Speed/Indep/Src/World/Common/WGrid.h" +#include "Speed/Indep/Src/World/WCollider.h" +#include "Speed/Indep/Src/World/WTrigger.h" + +void OrthoInverse(UMath::Matrix4 &m); +void v3add(int num, const UMath::Vector3 *src, const UMath::Vector3 *vtosub, UMath::Vector3 *results); +void v3add(int num, const UMath::Vector3 *src, const UMath::Vector3 *vtosub, UMath::Vector3 *results); + +std::list > WGridManagedDynamicElem::fgManagedDynamicElemList; + +WGridManagedDynamicElem::WGridManagedDynamicElem(UMath::Vector4 *dstPosRad, const UMath::Vector4 *srcPosRad, const WGridNodeElem &elem) + : fType(1), // + fLastPosRad(UMath::Vector4::kIdentity), // + fElem(elem), // + fDstPosRad(dstPosRad), // + fDstCInst(nullptr), // + fDstTrigger(nullptr) +{ + fSrcPosRad = srcPosRad; + fPosRad = srcPosRad; +} + +void WGridManagedDynamicElem::Update() { + switch (fType) { + case 1: { + UMath::Vector4To3(*fDstPosRad) = UMath::Vector4To3(*fSrcPosRad); + + if (UMath::Abs(fPosRad->x - fLastPosRad.x) <= 0.01f) { + if (UMath::Abs(fPosRad->z - fLastPosRad.z) <= 0.01f) { + return; + } + } + { + UMath::Vector4 tempPosRad = UMath::Vector4Make(UMath::Vector4To3(*fSrcPosRad), fDstPosRad->w); + + AddElem(&fLastPosRad, &tempPosRad, static_cast(fElem.fType), fElem.fInd); + WCollider::InvalidateIntersectingColliders(tempPosRad); + fLastPosRad = tempPosRad; + } + break; + } + case 2: { + { + UMath::Matrix4 m; + m[0] = fPosRad[0]; + m[2] = fPosRad[2]; + m[3] = fPosRad[3]; + OrthoInverse(m); + + UMath::Vector4To3(reinterpret_cast(fDstCInst->fInvMatRow0Width)) = UMath::Vector4To3(m[0]); + UMath::Vector4To3(reinterpret_cast(fDstCInst->fInvMatRow2Length)) = UMath::Vector4To3(m[1]); + UMath::Vector4To3(reinterpret_cast(fDstCInst->fInvPosRadius)) = UMath::Vector4To3(m[2]); + + if (UMath::Abs(fPosRad->x - fLastPosRad.x) <= 0.01f) { + if (UMath::Abs(fPosRad->z - fLastPosRad.z) <= 0.01f) { + return; + } + } + { + UMath::Vector4 tempPosRad = UMath::Vector4Make(UMath::Vector4To3(*fPosRad), reinterpret_cast(fDstCInst->fInvPosRadius).w); + + AddElem(&fLastPosRad, &tempPosRad, static_cast(fElem.fType), fElem.fInd); + WCollider::InvalidateIntersectingColliders(tempPosRad); + fLastPosRad = tempPosRad; + } + } + break; + } + case 3: { + { + UMath::Matrix4 m; + m[0] = fPosRad[0]; + m[1] = fPosRad[1]; + m[2] = fPosRad[2]; + m[3] = fPosRad[3]; + + UMath::Vector4To3(fDstTrigger->fMatRow0Width) = UMath::Vector4To3(m[0]); + UMath::Vector4To3(fDstTrigger->fMatRow2Length) = UMath::Vector4To3(m[2]); + v3add(1, reinterpret_cast(&m[3]), + reinterpret_cast(&fOffsetVec), + reinterpret_cast(&fDstTrigger->fPosRadius)); + + if (UMath::Abs(fPosRad->x - fLastPosRad.x) <= 0.01f) { + if (UMath::Abs(fPosRad->z - fLastPosRad.z) <= 0.01f) { + return; + } + } + { + UMath::Vector4 tempPosRad = UMath::Vector4Make(UMath::Vector4To3(*fPosRad), fDstTrigger->fPosRadius.w); + UMath::Vector4 offsetPosRad = tempPosRad; + UMath::Vector4 offsetLastPosRad = fLastPosRad; + + v3add(1, reinterpret_cast(&tempPosRad), + reinterpret_cast(&fOffsetVec), + reinterpret_cast(&offsetPosRad)); + v3add(1, reinterpret_cast(&fLastPosRad), + reinterpret_cast(&fOffsetVec), + reinterpret_cast(&offsetLastPosRad)); + + AddElem(&offsetLastPosRad, &offsetPosRad, static_cast(fElem.fType), fElem.fInd); + fLastPosRad = tempPosRad; + } + } + break; + } + } +} + +void WGridManagedDynamicElem::AddElem(const UMath::Vector4 *oldPosRad, const UMath::Vector4 *newPosRad, + WGridNode_ElemType type, unsigned int dataInd) { + if (oldPosRad != nullptr) { + UTL::FastVector nodeInds; + WGrid::Get().FindNodes(UMath::Vector4To3(*oldPosRad), oldPosRad->w, nodeInds); + for (int i = 0; i < static_cast(nodeInds.size()); i++) { + WGridNode *n = WGrid::Get().GetNode(nodeInds[i]); + if (n != nullptr) { + n->RemoveDynamic(dataInd, type); + } + } + } + + if (newPosRad != nullptr) { + UTL::FastVector nodeInds; + WGrid::Get().FindNodes(UMath::Vector4To3(*newPosRad), newPosRad->w, nodeInds); + for (int i = 0; i < static_cast(nodeInds.size()); i++) { + WGridNode *n = WGrid::Get().GetNode(nodeInds[i]); + if (n != nullptr) { + int count = n->GetElemTypeCount(type); + bool inList = false; + const unsigned int *typeInds = n->GetElemTypePtr(type); + for (int cnt = 0; cnt < count; cnt++) { + if (typeInds[cnt] == dataInd) { + inList = true; + break; + } + } + if (!inList) { + n->AddDynamic(dataInd, type); + } + } + } + } +} + + +void WGridManagedDynamicElem::UpdateElems() { + for (std::list >::iterator eIter = fgManagedDynamicElemList.begin(); eIter != fgManagedDynamicElemList.end(); ++eIter) { + (*eIter).Update(); + } +} diff --git a/src/Speed/Indep/Src/World/Common/WGridNode.cpp b/src/Speed/Indep/Src/World/Common/WGridNode.cpp index e69de29bb..2fc3b6422 100644 --- a/src/Speed/Indep/Src/World/Common/WGridNode.cpp +++ b/src/Speed/Indep/Src/World/Common/WGridNode.cpp @@ -0,0 +1,9 @@ +#include "Speed/Indep/Src/World/Common/WGridNode.h" + +unsigned int WGridNode::TotalSize() const { + unsigned int size = sizeof(WGridNode); + for (int i = 0; i < 4; i++) { + size += fElemCounts[i] * 4; + } + return size; +} diff --git a/src/Speed/Indep/Src/World/Common/WGridNode.h b/src/Speed/Indep/Src/World/Common/WGridNode.h index faf4698f8..1cf62a9bd 100644 --- a/src/Speed/Indep/Src/World/Common/WGridNode.h +++ b/src/Speed/Indep/Src/World/Common/WGridNode.h @@ -5,6 +5,129 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/FastMem.h" +#include "Speed/Indep/Src/World/WGridManagedDynamicElem.h" +struct WGridNodeElemList : public std::list > { + static void *operator new(unsigned int size) { return gFastMem.Alloc(size, nullptr); } + static void operator delete(void *mem, unsigned int size) { gFastMem.Free(mem, size, nullptr); } +}; + +struct WGridNode { + // total size: 0x1C + struct iterator { + WGridNode_ElemType fType; // offset 0x0, size 0x4 + const WGridNode *fNode; // offset 0x4, size 0x4 + int fNumEntriesRemaining; // offset 0x8, size 0x4 + const unsigned int *fElemInd; // offset 0xC, size 0x4 + bool fValid; // offset 0x10, size 0x1 + bool fDynamic; // offset 0x14, size 0x1 + WGridNodeElemList::iterator fIter; // offset 0x18, size 0x4 + + inline iterator(const WGridNode *node, WGridNode_ElemType type); + inline void Invalidate(); + inline const unsigned int *GetIndPtr(); + }; + + unsigned int TotalSize() const; + + void ShutDown() { + if (fDynElems != nullptr) { + delete fDynElems; + } + fDynElems = nullptr; + } + + unsigned int GetNodeInd() const { return fNodeInd; } + + inline const unsigned int GetElemTypeCount(WGridNode_ElemType type) const { + return fElemCounts[type]; + } + + inline const unsigned int *GetElemTypePtr(WGridNode_ElemType type) const { + unsigned int offset = fElemOffsets[type] + sizeof(WGridNode); + return reinterpret_cast(reinterpret_cast(this) + offset); + } + + inline unsigned int GetElemType(unsigned int index, WGridNode_ElemType type) const { + return GetElemTypePtr(type)[index]; + } + + inline void AddDynamic(unsigned int ind, WGridNode_ElemType type) { + if (fDynElems == nullptr) { + fDynElems = new WGridNodeElemList(); + } + WGridNodeElem elem(ind, type); + fDynElems->push_back(elem); + } + + inline void RemoveDynamic(unsigned int ind, WGridNode_ElemType type) { + if (fDynElems != nullptr) { + for (WGridNodeElemList::iterator eIter = fDynElems->begin(); eIter != fDynElems->end(); ++eIter) { + if ((*eIter).fInd == ind && (*eIter).fType == type) { + fDynElems->erase(eIter); + return; + } + } + } + } + + WGridNodeElemList* fDynElems; // offset 0x0, size 0x4 + unsigned short fNodeInd; // offset 0x4, size 0x2 + unsigned short fPad; // offset 0x6, size 0x2 + unsigned char fElemCounts[4]; // offset 0x8, size 0x4 + unsigned short fElemOffsets[4]; // offset 0xC, size 0x8 +}; + +inline WGridNode::iterator::iterator(const WGridNode *node, WGridNode_ElemType type) + : fType(type), // + fNode(node), // + fNumEntriesRemaining(0), // + fElemInd(nullptr), // + fValid(false), // + fDynamic(false) { + fNumEntriesRemaining = node->GetElemTypeCount(type); + if (fNumEntriesRemaining > 0) { + fElemInd = node->GetElemTypePtr(type); + fValid = true; + } + if (node->fDynElems != nullptr) { + fIter = node->fDynElems->begin(); + fValid = true; + } +} + +inline void WGridNode::iterator::Invalidate() { + fElemInd = nullptr; + fValid = false; +} + +inline const unsigned int *WGridNode::iterator::GetIndPtr() { + if (!fValid) { + return nullptr; + } + const unsigned int *retInd = nullptr; + if (!fDynamic && fNumEntriesRemaining > 0) { + fNumEntriesRemaining--; + fValid = true; + retInd = fElemInd; + fElemInd++; + } else if (fNode->fDynElems != nullptr) { + fDynamic = true; + while (fIter != fNode->fDynElems->end() && (*fIter).fType != fType) { + ++fIter; + } + if (fIter != fNode->fDynElems->end()) { + retInd = &(*fIter).fInd; + ++fIter; + fElemInd = retInd; + } else { + Invalidate(); + } + } else { + Invalidate(); + } + return retInd; +} #endif diff --git a/src/Speed/Indep/Src/World/Common/WPathFinder.cpp b/src/Speed/Indep/Src/World/Common/WPathFinder.cpp index e69de29bb..320e03f27 100644 --- a/src/Speed/Indep/Src/World/Common/WPathFinder.cpp +++ b/src/Speed/Indep/Src/World/Common/WPathFinder.cpp @@ -0,0 +1,479 @@ +#include "Speed/Indep/Src/World/WPathFinder.h" + +#include "Speed/Indep/Libs/Support/Utility/UStandard.h" +#include "Speed/Indep/Libs/Support/Utility/UVector.h" +#include "Speed/Indep/Src/Misc/Joylog.hpp" +#include "Speed/Indep/Src/Sim/Simulation.h" + +extern int bPathFinderPrints; + +UTL::COM::Factory::Prototype _PathFinder("PathFinder", PathFinder::Construct); + +PathFinder* PathFinder::pInstance; + +PathFinder::PathFinder() + : Activity(0) { + AStarNodeSlotPool = bNewSlotPool(0x14, 0xC00, "AStarNodeSlotPool", 0); + AStarSearchSlotPool = bNewSlotPool(0x54, 0x10, "AStarSearchSlotPool", 0); + mSimTask = AddTask(UCrc32("AIVehicle"), 1.0f, 0.0f, Sim::TASK_FRAME_VARIABLE); + Sim::ProfileTask(mSimTask, "PathFinder"); +} + +PathFinder::~PathFinder() { + lSearches.DeleteAllElements(); + bDeleteSlotPool(AStarSearchSlotPool); + bDeleteSlotPool(AStarNodeSlotPool); + AStarSearchSlotPool = nullptr; + AStarNodeSlotPool = nullptr; + RemoveTask(mSimTask); + pInstance = nullptr; +} + +Sim::IActivity* PathFinder::Construct(Sim::Param params) { + PathFinder *manager = pInstance; + if (manager == nullptr) { + manager = new PathFinder(); + pInstance = manager; + } + return manager; +} + +void PathFinder::ServiceAll() { + while (!lSearches.IsEmpty()) { + Service(1000.0f); + } +} + +bool PathFinder::OnTask(HSIMTASK__* htask, float elapsed_seconds) { + if (htask != mSimTask) { + return false; + } + Service(1.0f); + return true; +} + +AStarSearch* PathFinder::Pending(WRoadNav* road_nav) { + for (AStarSearch* s = lSearches.GetHead(); s != lSearches.EndOfList(); s = s->GetNext()) { + if (road_nav == s->pRoadNav) { + return s; + } + } + return nullptr; +} + +void PathFinder::Service(float time_limit_ms) { + float elapsed_ms = 0.0f; + while (!lSearches.IsEmpty() && elapsed_ms < time_limit_ms) { + AStarSearch *search = lSearches.GetHead(); + elapsed_ms += search->Service(time_limit_ms - elapsed_ms); + if (search->IsFinished()) { + search->Remove(); + delete search; + } + } +} + +void PathFinder::Cancel(WRoadNav *road_nav) { + AStarSearch *search = lSearches.GetHead(); + while (search != lSearches.EndOfList()) { + AStarSearch *next_search = search->GetNext(); + if (road_nav == search->GetRoadNav()) { + search->Remove(); + delete search; + } + search = next_search; + } +} + +AStarSearch *PathFinder::Submit(WRoadNav *road_nav, const UMath::Vector3 *goal_position, const UMath::Vector3 *goal_direction, const char *shortcut_allowed) { + Cancel(road_nav); + AStarSearch *ret = nullptr; + if (!bIsSlotPoolFull(AStarSearchSlotPool) && bCountFreeSlots(AStarNodeSlotPool) > 1) { + ret = new AStarSearch(road_nav, goal_position, goal_direction, shortcut_allowed); + lSearches.AddTail(ret); + } + return ret; +} + +bool AStarSearch::IsGoal(AStarNode *node) { + bool result = false; + if (nGoalSegment == node->GetSegmentIndex() && (pGoalNode == nullptr || pGoalNode == node->GetRoadNode())) { + result = true; + } + return result; +} + +AStarNode *AStarSearch::FindOpenNode(const WRoadNode *road_node, int segment_number) { + for (AStarNode *node = lOpen.GetHead(); node != lOpen.EndOfList(); node = node->GetNext()) { + if (road_node == node->GetRoadNode() && segment_number == node->GetSegmentIndex()) { + return node; + } + } + return nullptr; +} + +AStarNode *AStarSearch::FindClosedNode(const WRoadNode *road_node, int segment_number) { + for (AStarNode *node = lClosed.GetHead(); node != lClosed.EndOfList(); node = node->GetNext()) { + if (road_node == node->GetRoadNode() && segment_number == node->GetSegmentIndex()) { + return node; + } + } + return nullptr; +} + +AStarSearch::AStarSearch(WRoadNav *road_nav, const UMath::Vector3 *goal_position, + const UMath::Vector3 *goal_direction, const char *shortcut_allowed) + : nState(static_cast(0)), // + pSolution(nullptr), // + nServices(0), // + nSteps(0), // + fSearchTime(0.0f), // + pRoadNav(road_nav) { + vGoalPosition = *goal_position; + + WRoadNav goal_nav; + goal_nav.SetNavType(WRoadNav::kTypeDirection); + goal_nav.SetPathType(road_nav->GetPathType()); + goal_nav.SetRaceFilter(road_nav->GetRaceFilter()); + goal_nav.SetTrafficFilter(road_nav->GetTrafficFilter()); + goal_nav.SetCopFilter(road_nav->GetCopFilter()); + goal_nav.SetDecisionFilter(road_nav->GetDecisionFilter()); + + nShortcutCached = 0; + nShortcutAllowed = 0; + pShortcutAllowed = shortcut_allowed; + + unsigned char shortcut_number = road_nav->GetShortcutNumber(); + if (shortcut_number != 0xff) { + unsigned int mask = 1 << shortcut_number; + nShortcutCached |= mask; + nShortcutAllowed |= mask; + if (road_nav->GetPathType() == WRoadNav::kPathRaceRoute + && shortcut_allowed != nullptr + && shortcut_allowed[shortcut_number] == '\0') { + nState = static_cast(3); + return; + } + } + + UMath::Vector3 fake = UMath::Vector3::kZero; + if (goal_direction != nullptr) { + goal_nav.InitAtPoint(*goal_position, *goal_direction, false, 0.0f); + } else { + goal_nav.InitAtPoint(*goal_position, fake, false, 0.0f); + } + + WRoadNetwork &road_network = WRoadNetwork::Get(); + bool race_route = (road_nav->GetPathType() == WRoadNav::kPathRaceRoute); + + if (!goal_nav.IsValid()) { + bVector2 goal_position_2d(goal_position->z, -goal_position->x); + bVector2 goal_direction_2d(0.0f, 0.0f); + if (goal_direction != nullptr) { + goal_direction_2d.x = goal_direction->z; + goal_direction_2d.y = -goal_direction->x; + } + nState = static_cast(3); + return; + } + + nGoalSegment = static_cast(goal_nav.GetSegmentInd()); + road_nav->SetPathGoal(static_cast(nGoalSegment), goal_nav.GetSegmentTime()); + + if (road_nav->IsSegmentInCookieTrail(nGoalSegment, false)) { + nState = static_cast(1); + return; + } + + if (goal_direction == nullptr) { + pGoalNode = nullptr; + } else { + const WRoadSegment *goal_segment = road_network.GetSegment(nGoalSegment); + pGoalNode = road_network.GetNode(goal_segment->fNodeIndex[static_cast(goal_nav.GetNodeInd())]); + } + + int current_segment_index = static_cast(road_nav->GetSegmentInd()); + const WRoadSegment *current_segment = road_network.GetSegment(current_segment_index); + const WRoadNode *current_road_node = road_network.GetNode( + current_segment->fNodeIndex[static_cast(road_nav->GetNodeInd())]); + + if (bPathFinderPrints) { + bVector2 goal_position_2d(goal_position->z, -goal_position->x); + bVector2 current_position_2d(road_nav->GetPosition().z, -road_nav->GetPosition().x); + bVector2 goal_direction_2d(0.0f, 0.0f); + if (goal_direction != nullptr) { + goal_direction_2d = bVector2(goal_direction->z, -goal_direction->x); + } + } + + float estimated_cost = UMath::Distance(current_road_node->fPosition, vGoalPosition); + AStarNode *start_node = static_cast(bMalloc(AStarNodeSlotPool)); + start_node->nParentSlot = -1; + start_node->nSegmentIndex = static_cast(current_segment_index); + start_node->nRoadNode = current_road_node->fIndex; + start_node->fActualCost = 0; + start_node->fEstimatedCost = static_cast(static_cast(estimated_cost / ASTAR_METRIC_SCALE + 0.5f)); + lOpen.AddTail(start_node); + + if (!race_route) { + const WRoadNode *opp_road_node = road_network.GetSegmentOppNode(current_segment_index, current_road_node); + float opp_estimated_cost = UMath::Distance(opp_road_node->fPosition, vGoalPosition); + AStarNode *other_way_node = static_cast(bMalloc(AStarNodeSlotPool)); + other_way_node->nParentSlot = -1; + other_way_node->nSegmentIndex = static_cast(current_segment_index); + other_way_node->nRoadNode = opp_road_node->fIndex; + other_way_node->fActualCost = 0; + other_way_node->fEstimatedCost = static_cast(static_cast(opp_estimated_cost / ASTAR_METRIC_SCALE + 0.5f)); + lOpen.AddSorted(AStarCheckFlip, other_way_node); + } +} + +AStarSearch::~AStarSearch() { + delete pSolution; +} + +bool AStarSearch::Admissible(const WRoadSegment *segment, bool forward, WRoadNav::EPathType path_type) { + WRoadNetwork &rn = WRoadNetwork::Get(); + + switch (path_type) { + case WRoadNav::kPathGPS: + if (segment->IsOneWay() && !forward) + return false; + if (segment->CrossesBarrier() || segment->CrossesDriveThroughBarrier()) + return false; + return true; + case WRoadNav::kPathCop: + if (segment->IsOneWay() && !forward) + return false; + return true; + case WRoadNav::kPathRacer: { + if (segment->IsShortcut()) { + int shortcut_number = rn.GetSegmentShortcutNumber(segment); + bool shortcut_allowed = pRoadNav->MakeShortcutDecision(shortcut_number, &nShortcutCached, &nShortcutAllowed); + if (!shortcut_allowed) + return false; + } + if (segment->IsOneWay() && !forward) + return false; + if (segment->CrossesBarrier() || segment->CrossesDriveThroughBarrier()) + return false; + return true; + } + case WRoadNav::kPathPlayer: { + if (segment->IsShortcut()) { + int shortcut_number = rn.GetSegmentShortcutNumber(segment); + if (shortcut_number != pRoadNav->GetShortcutNumber()) + return false; + } + if (segment->IsOneWay() && !forward) + return false; + if (segment->CrossesBarrier() || segment->CrossesDriveThroughBarrier()) + return false; + return true; + } + case WRoadNav::kPathRaceRoute: { + if (segment->IsShortcut()) { + int shortcut_number = rn.GetSegmentShortcutNumber(segment); + if (pShortcutAllowed[shortcut_number] == 0) + return false; + } + if (segment->CrossesBarrier() || segment->CrossesDriveThroughBarrier()) + return false; + if (segment->IsOneWay() && !forward) + return false; + return true; + } + default: + return true; + } +} + +float AStarSearch::Service(float time_limit_ms) { + unsigned int start_ticker = bGetTicker(); + WRoadNav::EPathType path_type = pRoadNav->GetPathType(); + bool race_route = (path_type == WRoadNav::kPathRaceRoute); + int prev_timeout_loops = 0x7FFFFFFF; + nServices++; + if (Joylog::IsReplaying()) { + prev_timeout_loops = static_cast(Joylog::GetData(32, JOYLOG_CHANNEL_PATHFINDER_TIMEOUT)); + } + int num_loops = 0; + while (IsActive()) { + if (lOpen.IsEmpty()) { + if (IsActive() && lOpen.IsEmpty()) { + pSolution = nullptr; + nState = static_cast(3); + } + break; + } + if (Joylog::IsReplaying()) { + if (num_loops == prev_timeout_loops) { + break; + } + } else { + float elapsed_ms = bGetTickerDifference(start_ticker); + if (elapsed_ms > time_limit_ms) { + break; + } + } + AStarNode *current_node = lOpen.RemoveTail(); + num_loops++; + nSteps++; + if (IsGoal(current_node)) { + pSolution = current_node; + nState = static_cast(1); + break; + } + WRoadNetwork &road_network = WRoadNetwork::Get(); + const WRoadNode *current_road_node = current_node->GetRoadNode(); + int num_segments = static_cast(current_road_node->fNumSegments); + if (bCountFreeSlots(AStarNodeSlotPool) < static_cast(num_segments - 1)) { + if (race_route) { + nState = static_cast(2); + } else { + nState = static_cast(3); + } + if (race_route) { + pSolution = nullptr; + } else { + pSolution = current_node; + } + break; + } + for (int i = 0; i < num_segments; i++) { + int segment_index = static_cast(current_road_node->fSegmentIndex[i]); + int current_segment_index = current_node->GetSegmentIndex(); + if (segment_index == current_segment_index) { + continue; + } + const WRoadSegment *segment = road_network.GetSegment(segment_index); + const WRoadSegment *current_segment = road_network.GetSegment(current_segment_index); + if (current_segment->IsDecision() && segment->IsDecision() && path_type != WRoadNav::kPathCop) { + continue; + } + bool forward = (current_road_node->fIndex == segment->fNodeIndex[0]); + if (!Admissible(segment, forward, path_type)) { + continue; + } + float actual_cost = current_node->GetActualCost() + segment->GetLength(); + const WRoadNode *next_road_node = road_network.GetSegmentOppNode(segment_index, current_road_node); + AStarNode *already_open = FindOpenNode(next_road_node, segment_index); + if (already_open != nullptr && actual_cost >= already_open->GetActualCost()) { + continue; + } + AStarNode *already_closed = FindClosedNode(next_road_node, segment_index); + if (already_closed != nullptr && actual_cost >= already_closed->GetActualCost()) { + continue; + } + if (already_open != nullptr) { + already_open->Remove(); + delete already_open; + } + if (already_closed != nullptr) { + already_closed->Remove(); + delete already_closed; + } + float estimated_cost = UMath::Distance(next_road_node->fPosition, vGoalPosition); + AStarNode *new_node = new AStarNode(current_node, next_road_node, segment_index, actual_cost, estimated_cost); + lOpen.AddSorted(AStarCheckFlip, new_node); + } + lClosed.AddHead(current_node); + } + if (nState > 0) { + AStarNode *node = pSolution; + int num_segments = 0; + while (node != nullptr) { + num_segments++; + node = node->GetParent(); + } + node = pSolution; + int max_segments = pRoadNav->GetMaxPathSegments(); + while (node != nullptr && num_segments > max_segments) { + num_segments--; + node = node->GetParent(); + } + bool race_route_bogus = race_route; + bool segment_found = num_segments > 0; + if (segment_found) { + pRoadNav->SetNavType(WRoadNav::kTypePath); + race_route_bogus = false; + if (static_cast(pRoadNav->GetSegmentInd()) == nGoalSegment) { + UMath::Vector3 dir; + UMath::Sub(vGoalPosition, pRoadNav->GetPosition(), dir); + float dot = UMath::Dot(dir, pRoadNav->GetForwardVector()); + if (dot < 0.0f) { + pRoadNav->Reverse(); + race_route_bogus = race_route; + } + } + } + pRoadNav->SetNumPathSegments(num_segments); + unsigned short *segments = pRoadNav->GetPathSegments(); + if (node != nullptr) { + while (segment_found) { + int segment_index = node->GetSegmentIndex(); + num_segments--; + segments[num_segments] = static_cast(segment_index); + if (segment_index == static_cast(pRoadNav->GetSegmentInd())) { + WRoadNetwork &roadNetwork = WRoadNetwork::Get(); + const WRoadSegment *segment = roadNetwork.GetSegment(segment_index); + const WRoadNode *currentnode = roadNetwork.GetNode(segment->fNodeIndex[static_cast(pRoadNav->GetNodeInd())]); + if (currentnode != node->GetRoadNode()) { + pRoadNav->Reverse(); + race_route_bogus = race_route; + } + } + node = node->GetParent(); + if (node == nullptr) { + break; + } + segment_found = num_segments > 0; + } + } + unsigned int path_segment_count = static_cast(pRoadNav->GetNumPathSegments()); + if (pRoadNav->GetPathType() == WRoadNav::kPathRaceRoute && nState != static_cast(1)) { + pRoadNav->SetNumPathSegments(0); + } + if (race_route) { + typedef UTL::Std::set SEGMENT_SET; + SEGMENT_SET segment_set; + for (int i = 0; i < static_cast(path_segment_count); i++) { + segment_set.insert(pRoadNav->GetPathSegment(i)); + } + if (segment_set.size() < path_segment_count) { + race_route_bogus = true; + } + } + if (race_route_bogus) { + bVector2 goal_position_2d(vGoalPosition.z, -vGoalPosition.x); + bVector2 current_position_2d(pRoadNav->GetPosition().z, -pRoadNav->GetPosition().x); + pRoadNav->SetNavType(WRoadNav::kTypeDirection); + pRoadNav->SetNumPathSegments(0); + nState = static_cast(3); + pSolution = nullptr; + } + if (bPathFinderPrints) { + float search_time = bGetTickerDifference(start_ticker); + } + } + float elapsed_ms = bGetTickerDifference(start_ticker); + if (Joylog::IsCapturing()) { + Joylog::AddData(num_loops, 32, JOYLOG_CHANNEL_PATHFINDER_TIMEOUT); + } + if (Joylog::IsCapturing()) { + Joylog::AddData(static_cast(elapsed_ms), 32, JOYLOG_CHANNEL_PATHFINDER_TIMEOUT); + } else if (Joylog::IsReplaying()) { + elapsed_ms = static_cast(static_cast(Joylog::GetData(32, JOYLOG_CHANNEL_PATHFINDER_TIMEOUT))); + } + fSearchTime += elapsed_ms; + return elapsed_ms; +} + + +int AStarSearch::AStarCheckFlip(AStarNode *before, AStarNode *after) { + return before->GetTotalCost() <= after->GetTotalCost(); +} + +void AStarNode::operator delete(void *ptr) { + bFree(AStarNodeSlotPool, ptr); +} diff --git a/src/Speed/Indep/Src/World/Common/WRoadNetwork.cpp b/src/Speed/Indep/Src/World/Common/WRoadNetwork.cpp index e69de29bb..c8a310b67 100644 --- a/src/Speed/Indep/Src/World/Common/WRoadNetwork.cpp +++ b/src/Speed/Indep/Src/World/Common/WRoadNetwork.cpp @@ -0,0 +1,3652 @@ +#include "Speed/Indep/Src/World/WRoadNetwork.h" + +#include "Speed/Indep/Libs/Support/Utility/FastMem.h" +#include "Speed/Indep/Libs/Support/Utility/UVector.h" +#include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "Speed/Indep/Src/Gameplay/GMarker.h" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Interfaces/IBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/IArticulatedVehicle.h" +#include "Speed/Indep/Src/Interfaces/Simables/IVehicle.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRigidBody.h" +#include "Speed/Indep/Src/Physics/Common/VehicleSystem.h" +#include "Speed/Indep/Src/AI/AITarget.h" +#include "Speed/Indep/Src/AI/AIVehicle.h" +#include "Speed/Indep/Src/World/Common/WGrid.h" +#include "Speed/Indep/Src/World/WPathFinder.h" +#include "Speed/Indep/Src/World/WWorld.h" +#include "Speed/Indep/Src/World/TrackPath.hpp" +#include "Speed/Indep/Src/Sim/SimSubSystem.h" + +extern BOOL bBoundingBoxIsInside(const bVector2 *bbox_min, const bVector2 *bbox_max, const bVector2 *point, float extra_width); +extern void bInitializeBoundingBox(bVector2 *min, bVector2 *max); +extern void bExpandBoundingBox(bVector2 *min, bVector2 *max, const bVector2 *point); +extern bool bAiRandomTurns; + +static const int drivable_lanes[8] = { + static_cast(0xFFFFDF7F), + 0x00000002, + static_cast(0xFFFFDF7F), + static_cast(0xFFFFDF7B), + static_cast(0xFFFFDF7F), + 0x00000402, + static_cast(0xFFFFDF7F), + static_cast(0xFFFFFFFF), +}; + +static const int selectable_lanes[8] = { + 0x00000402, + 0x00000002, + static_cast(0xFFFFDF5B), + 0x00000472, + static_cast(0xFFFFDF7F), + 0x00000402, + 0x00000402, + static_cast(0xFFFFFFFF), +}; + +Sim::SubSystem _Physics_System_WRoadNetwork("WRoadNetwork", WRoadNetwork::Init, WRoadNetwork::Shutdown); + +void WRoadNetwork::Init() { + if (fgRoadNetwork == nullptr) { + fgRoadNetwork = new WRoadNetwork(); + fValid = false; + fValidRaceFilter = false; + fValidTrafficRoads = true; + fNumNodes = 0; + fNumSegments = 0; + fNumIntersections = 0; + fNumRoads = 0; + nRoadMemoryUsage = 0; + nNodeMemoryUsage = 0; + nProfileMemoryUsage = 0; + nSegmentMemoryUsage = 0; + nIntersectionMemoryUsage = 0; + nTotalMemoryUsage = 0; + + if (WWorld::Get().GetMapGroup()) { + const UGroup *networkGroup = WWorld::Get().GetMapGroup()->GroupLocate('RN', 'gp'); + if (networkGroup != WWorld::Get().GetMapGroup()->GroupEnd()) { + const UData *headerData = networkGroup->DataLocate('RN', 'hd'); + const WRoadNetworkInfo *roadInfo = + static_cast< const WRoadNetworkInfo * >(headerData->GetDataConst()); + + fNumProfiles = roadInfo->fNumProfiles; + fNumNodes = roadInfo->fNumNodes; + fNumSegments = roadInfo->fNumSegments; + fNumIntersections = roadInfo->fNumIntersections; + fNumRoads = roadInfo->fNumRoads; + nRoadMemoryUsage = fNumRoads * sizeof(WRoad); + nNodeMemoryUsage = fNumNodes * sizeof(WRoadNode); + nProfileMemoryUsage = fNumProfiles * sizeof(WRoadProfile); + nSegmentMemoryUsage = fNumSegments * sizeof(WRoadSegment); + nIntersectionMemoryUsage = fNumIntersections * sizeof(WRoadIntersection); + nTotalMemoryUsage = nRoadMemoryUsage + nNodeMemoryUsage + + nProfileMemoryUsage + nSegmentMemoryUsage + + nIntersectionMemoryUsage; + + if (fNumNodes == 0) { + fValid = false; + return; + } + + const UData *data; + void *nonConstData; + + data = networkGroup->DataLocate('RN', 'pf'); + nonConstData = const_cast< void * >(data->GetDataConst()); + if (data != networkGroup->DataEnd()) { + fProfiles = static_cast< WRoadProfile * >(nonConstData); + } + + data = networkGroup->DataLocate('RN', 'nd'); + nonConstData = const_cast< void * >(data->GetDataConst()); + if (data != networkGroup->DataEnd()) { + fNodes = static_cast< WRoadNode * >(nonConstData); + } + + data = networkGroup->DataLocate('RN', 'sg'); + nonConstData = const_cast< void * >(data->GetDataConst()); + if (data != networkGroup->DataEnd()) { + fSegments = static_cast< WRoadSegment * >(nonConstData); + } + + data = networkGroup->DataLocate('RN', 'rd'); + nonConstData = const_cast< void * >(data->GetDataConst()); + if (data != networkGroup->DataEnd()) { + fRoads = static_cast< WRoad * >(nonConstData); + } + + fValid = true; + } + } + fgRoadNetwork->ResolveBarriers(); + fgRoadNetwork->ResolveShortcuts(); + fgRoadNetwork->ResetRaceSegments(); + } +} + +void WRoadNetwork::Shutdown() { + if (fgRoadNetwork) { + gFastMem.Free(fgRoadNetwork, sizeof(WRoadNetwork), nullptr); + fgRoadNetwork = nullptr; + } +} + +bool WRoadNetwork::SegmentCrossesBarrier(WRoadSegment *segment, TrackPathBarrier *barrier) { + USpline spline; + bVector2 points[2]; + BuildSegmentSpline(*segment, spline); + int num_pieces = bMax(static_cast< int >(bCeil(segment->GetLength() * 0.1f)), 4); + float inc = 1.0f / static_cast< float >(num_pieces); + for (int i = 0; i <= num_pieces; i++) { + int which_point = i & 1; + UMath::Vector4 v4; + spline.EvaluateSpline(static_cast< float >(i) * inc, v4); + bVector2 temp(v4.z, -v4.x); + points[which_point] = temp; + if (i > 0) { + if (barrier->Intersects(&points[which_point], &points[which_point ^ 1])) { + return true; + } + } + } + return false; +} + +void WRoadNetwork::ResetRaceSegments() { + fValidRaceFilter = false; + for (int i = 0; i < static_cast(fNumSegments); i++) { + GetSegmentNonConst(i)->SetInRace(false); + GetSegmentNonConst(i)->SetRaceRouteForward(false); + } +} + +void WRoadNetwork::ResetBarriers() { + for (unsigned int segment_number = 0; segment_number < fNumSegments; segment_number++) { + WRoadSegment *segment = GetSegmentNonConst(segment_number); + segment->SetCrossesDriveThroughBarrier(false); + segment->SetCrossesBarrier(false); + } +} + + +void WRoadNetwork::ResolveBarriers() { + int num_exemptions = 0; + short exempted_roads[4]; + + ResetBarriers(); + + for (int i = 0; i < 4; i++) { + exempted_roads[i] = -1; + } + + if (GRaceStatus::Exists()) { + WRoadNav nav; + const float dir_weight = 1.0f; + const bool force_centre_lane = true; + nav.SetDecisionFilter(true); + nav.SetNavType(WRoadNav::kTypeDirection); + GRaceParameters *race_parameters = GRaceStatus::Get().GetRaceParameters(); + + if (race_parameters != nullptr) { + num_exemptions = race_parameters->GetNumBarrierExemptions(); + if (num_exemptions > 0) { + for (int i = 0; i < num_exemptions; i++) { + GMarker *exemption = race_parameters->GetBarrierExemption(i); + nav.InitAtPoint(exemption->GetPosition(), exemption->GetDirection(), + force_centre_lane, dir_weight); + if (nav.IsValid()) { + exempted_roads[i] = nav.GetRoadInd(); + } + } + } + } else { + UMath::Vector3 hack_direction = UMath::Vector3Make(-0.7f, 0.0f, 0.7f); + UMath::Vector3 hack_position = UMath::Vector3Make(-2511.0f, 147.8f, 1783.0f); + nav.InitAtPoint(hack_position, hack_direction, force_centre_lane, dir_weight); + if (nav.IsValid()) { + num_exemptions = 1; + exempted_roads[0] = nav.GetRoadInd(); + } + } + } + + WGrid &grid = WGrid::Get(); + WRoadNetwork &roadNetwork = WRoadNetwork::Get(); + int num_barriers = TheTrackPathManager.GetNumBarriers(); + + for (int barrier_number = 0; barrier_number < num_barriers; barrier_number++) { + TrackPathBarrier *barrier = TheTrackPathManager.GetBarrier(barrier_number); + if (barrier->IsEnabled()) { + typedef UTL::Std::set SEGMENT_SET; + UMath::Vector4 barrier_points[2]; + barrier_points[0] = UMath::Vector4Make(-barrier->Points[0].y, 0.0f, + barrier->Points[0].x, 1.0f); + barrier_points[1] = UMath::Vector4Make(-barrier->Points[1].y, 0.0f, + barrier->Points[1].x, 1.0f); + + UTL::FastVector node_list; + grid.FindNodes(barrier_points, node_list); + + SEGMENT_SET segment_set; + + for (unsigned int *iter = node_list.begin(); iter != node_list.end(); ++iter) { + WGridNode *grid_node = grid.fNodes[*iter]; + if (grid_node != nullptr) { + unsigned int numSegments = grid_node->GetElemTypeCount(WGrid_kRoadSegment); + for (unsigned int i = 0; i < numSegments; i++) { + segment_set.insert( + static_cast(grid_node->GetElemType(i, WGrid_kRoadSegment))); + } + } + } + + for (SEGMENT_SET::const_iterator it = segment_set.begin(); + it != segment_set.end(); ++it) { + short segment_number = *it; + WRoadSegment *segment = roadNetwork.GetSegmentNonConst(segment_number); + if (SegmentCrossesBarrier(segment, barrier)) { + bool exempt = false; + short road_number = segment->fRoadID; + if (num_exemptions > 0 && road_number != -1) { + for (int j = 0; j < num_exemptions; j++) { + exempt = exempt | (road_number == exempted_roads[j]); + } + } + if (!exempt) { + if (barrier->IsPlayerBarrier()) { + segment->SetCrossesDriveThroughBarrier(true); + } else { + segment->SetCrossesBarrier(true); + } + } + } + } + } + } +} + +void WRoadNetwork::GetSegmentNodes(const WRoadSegment &segment, const WRoadNode **node) { + WRoadNetwork &roadNetwork = Get(); + node[0] = roadNetwork.GetNode(segment.fNodeIndex[0]); + node[1] = roadNetwork.GetNode(segment.fNodeIndex[1]); +} + +const WRoadProfile *WRoadNetwork::GetSegmentProfile(const WRoadSegment &segment, int node_index) { + WRoadNetwork &roadNetwork = Get(); + const WRoadNode *node = roadNetwork.GetNode(segment.fNodeIndex[node_index]); + if (!node || node->fProfileIndex < 0) { + return &fInvalidProfile; + } + return roadNetwork.GetProfile(node->fProfileIndex); +} + +void WRoadNetwork::GetSegmentForwardVector(int segInd, UMath::Vector3 &forwardVector) { + WRoadNetwork &roadNetwork = Get(); + const WRoadSegment *segment = roadNetwork.GetSegment(segInd); + roadNetwork.GetSegmentForwardVector(*segment, forwardVector); +} + +const WRoadNode *WRoadNetwork::GetSegmentOppNode(int segInd, const WRoadNode *node) { + WRoadNetwork &roadNetwork = Get(); + const WRoadSegment *segment = roadNetwork.GetSegment(segInd); + return roadNetwork.GetSegmentOppNode(*segment, node); +} + +const WRoadNode *WRoadNetwork::GetSegmentOppNode(const WRoadSegment &segment, const WRoadNode *node) { + WRoadNetwork &roadNetwork = Get(); + const WRoadNode *nodePtr[2]; + roadNetwork.GetSegmentNodes(segment, nodePtr); + if (node == nodePtr[0]) { + return nodePtr[1]; + } + return nodePtr[0]; +} + +void WRoadNav::SetCookieTrail(CookieTrail *p_cookies) { + pCookieTrail = p_cookies; + bCookieTrail = (p_cookies != nullptr); +} + +void WRoadNav::SetCookieTrail(bool b) { + if (b && pCookieTrail == nullptr) { + pCookieTrail = new CookieTrail(); + } + bCookieTrail = b; +} + +void WRoadNav::ClearCookieTrail() { + if (pCookieTrail) { + pCookieTrail->Clear(); + } + nCookieIndex = 0; +} + +void WRoadNav::ResetCookieTrail() { + ClearCookieTrail(); + UpdateCookieTrail(3.0f); +} + +void WRoadNav::MaybeAllocatePathSegments() { + if (pPathSegments == nullptr) { + pPathSegments = new unsigned short[0x3FC / sizeof(unsigned short)]; + } +} + +void WRoadNav::SetPathType(EPathType type) { + fPathType = type; +} + +void WRoadNav::SetVehicle(AIVehicle *ai_vehicle) { + pAIVehicle = ai_vehicle; + DetermineVehicleHalfWidth(); +} + +void WRoadNav::DetermineVehicleHalfWidth() { + fVehicleHalfWidth = 1.0f; + if (pAIVehicle != nullptr) { + IBody *body; + if (pAIVehicle->GetOwner()->QueryInterface(&body)) { + UMath::Vector3 dimension; + body->GetDimension(dimension); + fVehicleHalfWidth = dimension.x; + } + + if (GRaceStatus::IsDragRace()) { + IVehicle *ivehicle; + if (pAIVehicle->GetOwner()->QueryInterface(&ivehicle)) { + if (VehicleClass::TRACTOR == ivehicle->GetVehicleClass()) { + fVehicleHalfWidth += 2.0f; + } + } + } + } +} + +bool WRoadNav::IsDrivable(int lane_type) const { + return (drivable_lanes[fLaneType] >> lane_type) & 1; +} + +bool WRoadNav::IsSelectable(int lane_type) const { + return (selectable_lanes[fLaneType] >> lane_type) & 1; +} + +void WRoadNav::SnapToSelectableLane() { + float offset = SnapToSelectableLane(fLaneOffset); + ChangeLanes(offset, 0.0f); +} + +float WRoadNav::SnapToSelectableLane(float input_offset) { + return SnapToSelectableLane(input_offset, fSegmentInd, fNodeInd); +} + +float WRoadNav::SnapToSelectableLane(float input_offset, int segment_no, char node_index) { + WRoadNetwork &roadNetwork = WRoadNetwork::Get(); + bool cop_lane = fLaneType == kLaneCop || fLaneType == kLaneCopReckless; + bool drag_lane = fLaneType == kLaneDrag; + bool grid_lane = fLaneType == kLaneStartingGrid; + bool racing_lane = fLaneType == kLaneRacing; + const WRoadSegment *segment = roadNetwork.GetSegment(segment_no); + const WRoadProfile *profile = roadNetwork.GetSegmentProfile(*segment, node_index); + bool inverted = segment->IsProfileInverted(node_index); + bool forward = node_index > 0; + + int best_lane = -1; + bool found_lane = false; + bool best_backward = false; + float next_offset = 0.0f; + float offset_difference = 1000.0f; + + int num_forward_lanes = profile->GetNumLanes(forward, inverted); + + for (int n = 0; n < num_forward_lanes; n++) { + int lane = profile->GetNthLane(n, forward, inverted); + unsigned char lane_type = profile->GetLaneType(lane, inverted); + if (IsSelectable(lane_type)) { + float offset = profile->GetRawLaneOffset(lane); + float difference = bClamp(offset - input_offset, -offset_difference, offset_difference); + if (offset - input_offset == difference) { + offset_difference = bAbs(offset - input_offset); + found_lane = true; + best_lane = lane; + next_offset = offset; + } + } + } + + int num_backward_lanes = profile->GetNumLanes(!forward, inverted); + + if ((cop_lane || drag_lane || grid_lane || racing_lane || !found_lane) && num_backward_lanes > 0) { + for (int n = 0; n < num_backward_lanes; n++) { + int lane = profile->GetNthLane(n, !forward, inverted); + unsigned char lane_type = profile->GetLaneType(lane, inverted); + if (IsSelectable(lane_type)) { + float offset = -profile->GetRawLaneOffset(lane); + float difference = bClamp(offset - input_offset, -offset_difference, offset_difference); + if (offset - input_offset == difference) { + best_backward = true; + offset_difference = bAbs(offset - input_offset); + next_offset = offset; + best_lane = lane; + } + } + } + } + + float output_offset = next_offset; + + if ((cop_lane || racing_lane) && best_lane > -1) { + float offset = profile->GetRawLaneOffset(best_lane); + float width = profile->GetRawLaneWidth(best_lane); + float difference = bClamp(input_offset - next_offset, -width * 0.5f, width * 0.5f); + + bool inverted_xor_backward = (profile->GetLaneNumber(best_lane, inverted) < profile->GetMiddleZone(inverted)) != best_backward; + + int left_lane; + int right_lane; + if (inverted_xor_backward) { + left_lane = profile->fNumZones - 1; + right_lane = 0; + } else { + left_lane = 0; + right_lane = profile->fNumZones - 1; + } + + float right = profile->GetRelativeLaneOffset(right_lane, inverted); + float right_width = profile->GetLaneWidth(right_lane, inverted); + + float left = profile->GetRelativeLaneOffset(left_lane, inverted); + float left_width = profile->GetLaneWidth(left_lane, inverted); + + output_offset = next_offset + difference; + float safety_margin = right + right_width - 0.5f; + float left_limit = left - left_width + 0.5f; + output_offset = bClamp(output_offset, left_limit, safety_margin); + } + + return output_offset; +} + +int WRoadNav::ClosestCookieAhead(const UMath::Vector3 &position, NavCookie *interpolated_cookie) { + return ClosestCookieAhead(position, nullptr, pCookieTrail->Count(), interpolated_cookie); +} + +int WRoadNav::ClosestCookieAhead(const UMath::Vector3 &position, NavCookie *cookies, int num_cookies, + NavCookie *interpolated_cookie) { + int ret = -1; + float closest = -1.0f; + if (num_cookies > 0) { + float previous_dot = 0.0f; + bVector2 car_position(position.x, position.z); + for (int n = 0; n < num_cookies; n++) { + const NavCookie &cookie = cookies ? cookies[n] : pCookieTrail->NthOldest(n); + bVector2 cookie_to_car = car_position - bVector2(cookie.Centre.x, cookie.Centre.z); + float distance_squared = bDot(&cookie_to_car, &cookie_to_car); + float current_dot = bDot(reinterpret_cast(cookie.Forward), cookie_to_car); + if (n == 0) { + if (current_dot < 0.0f) { + ret = 0; + closest = distance_squared; + } + } else { + bool between = current_dot * previous_dot < 0.0f; + if (between && (ret < 0 || distance_squared < closest)) { + ret = n; + closest = distance_squared; + } + } + previous_dot = current_dot; + } + } + return ret; +} + +void WRoadNav::SetStartEndControls(const WRoadSegment &segment) { + SetControlPos(segment, true); + SetControlPos(segment, false); +} + +bool WRoadNav::FindingPath() { + PathFinder *path_finder = PathFinder::Get(); + return path_finder != nullptr && path_finder->Pending(this) != nullptr; +} + +float WRoadNav::GetPathDistanceRemaining() { + WRoadNetwork &rn = WRoadNetwork::Get(); + float distance = 0.0f; + if (GetNavType() == kTypePath && pPathSegments != nullptr) { + bool accumulate = false; + for (int i = 0; i < nPathSegments; i++) { + unsigned short segment_number = pPathSegments[i]; + float min_param = 0.0f; + float max_param = 1.0f; + if (segment_number == GetSegmentInd()) { + min_param = GetSegmentTime(); + accumulate = true; + } + bool break_out = false; + if (segment_number == nPathGoalSegment) { + max_param = fPathGoalParam; + break_out = true; + } + if (accumulate) { + float coef = bMax(0.0f, max_param - min_param); + const WRoadSegment *segment = rn.GetSegment(segment_number); + float segment_length = coef * segment->GetLength(); + if (segment->IsShortcut()) { + const WRoad *road = rn.GetRoad(segment->fRoadID); + segment_length *= road->GetScale(); + } + distance += segment_length; + } + if (break_out) { + break; + } + } + } + return distance; +} + +bool WRoadNav::CanTrafficSpawn() { + if (!IsValid()) { + return false; + } + + WRoadNetwork &road_network = WRoadNetwork::Get(); + const WRoadSegment *segment = road_network.GetSegment(GetSegmentInd()); + int which_node = GetNodeInd(); + + if (segment->IsDecision()) { + return false; + } + if (!segment->IsTrafficAllowed()) { + return false; + } + if (segment->IsOneWay() && which_node == 0) { + return false; + } + + int player_or_racer = (which_node == 1); + bool inverted = segment->IsProfileInverted(which_node); + + const WRoadProfile *profile = road_network.GetSegmentProfile(*segment, which_node); + + int num_traffic_lanes = profile->GetNumTrafficLanes(player_or_racer, inverted); + if (!num_traffic_lanes) { + return false; + } + + int random_lane = bRandom(num_traffic_lanes); + int lane = profile->GetNthTrafficLane(random_lane, player_or_racer, inverted); + float offset = profile->GetLaneOffset(lane, false); + ChangeLanes(offset, 0.0f); + + SetLaneInd(static_cast< char >(lane)); + + return IsValid(); +} + +float WRoadNav::CookieTrailCurvature(const UMath::Vector3 &car_position, const UMath::Vector3 &car_velocity) { + if (pCookieTrail == nullptr) { + return 0.0f; + } + + float road_curvature = 0.0f; + float apex = 0.0f; + + if (pCookieTrail->Count() > 2) { + if (IsOccluded() && !IsOccludedFromBehind()) { + UMath::Vector3 current_to_apex; + const UMath::Vector3 &nav_position = fPosition; + const UMath::Vector3 &apex_position = fApexPosition; + const UMath::Vector3 &occluded_position = fOccludedPosition; + + UMath::Sub(occluded_position, car_position, current_to_apex); + current_to_apex.y = 0.0f; + float dist_to_apex = UMath::Normalize(current_to_apex); + + if (dist_to_apex > 1.0f) { + int apex_cookie_index = ClosestCookieAhead(fApexPosition, nullptr); + + if (apex_cookie_index >= 0) { + const NavCookie &apex_cookie = pCookieTrail->NthOldest(apex_cookie_index); + float apex_width = bAbs(apex_cookie.LeftOffset - apex_cookie.RightOffset); + apex_width = UMath::Max(apex_width, dist_to_apex); + + UMath::Vector3 apex_to_nav; + UMath::Sub(nav_position, apex_position, apex_to_nav); + apex_to_nav.y = 0.0f; + UMath::Normalize(apex_to_nav); + + float sina = UMath::Clamp(current_to_apex.x * apex_to_nav.z - current_to_apex.z * apex_to_nav.x, -1.0f, 1.0f); + float angle = UMath::Abs(UMath::ASinr(sina)); + + if (current_to_apex.x * apex_to_nav.x + current_to_apex.z * apex_to_nav.z < 0.0f) { + angle = static_cast< float >(M_PI) - angle; + } + + float div = UMath::Max(1.0f, apex_width); + apex = angle * UMath::Sinr(UMath::Min(angle, static_cast< float >(M_PI_2))); + + if (nAvoidableOcclusion != 0) { + float my_trailingspeed = UMath::Dot(car_velocity, current_to_apex); + float ratio = 0.0f; + if (my_trailingspeed > 0.5f) { + float closing_speed = (my_trailingspeed - fOccludingTrailSpeed) / my_trailingspeed; + ratio = UMath::Ramp(closing_speed, 0.0f, 1.0f); + } + apex *= ratio * ratio; + } + + apex = UMath::Clamp(apex, 0.0f, static_cast< float >(M_PI)); + apex = apex / div; + } + } + } + } + + float distance = 0.0f; + float total_curvature = 0.0f; + float previous_curvature = 0.0f; + int num_cookies = pCookieTrail->Count(); + + for (int i = nCookieIndex; i < num_cookies; i++) { + const NavCookie &cookie = pCookieTrail->NthOldest(i); + float current_curvature = UMath::Clamp(cookie.Curvature, -0.01f, 0.01f); + if (nCookieIndex < i) { + float length = cookie.Length; + float avg_curvature = (current_curvature + previous_curvature) * 0.5f; + total_curvature += length * avg_curvature; + distance += length; + } + previous_curvature = current_curvature; + } + + if (distance > 0.0f) { + road_curvature = bAbs(total_curvature / distance); + } + + return UMath::Max(apex, road_curvature); +} + +bool WRoadNav::IsSegmentInPath(int segment_number) { + if (GetNavType() == kTypePath) { + int num_segments = GetNumPathSegments(); + for (int i = 0; i < num_segments; i++) { + if (segment_number == GetPathSegment(i)) { + return true; + } + } + } + return false; +} + +unsigned char WRoadNetwork::GetSegmentShortcutNumber(const WRoadSegment *segment) { + if (segment->IsShortcut() && segment->fRoadID != -1) { + return GetRoad(segment->fRoadID)->nShortcut; + } + return 0xFF; +} + +void WRoadNav::CancelPathFinding() { + PathFinder *path_finder = PathFinder::Get(); + if (path_finder != nullptr) { + path_finder->Cancel(this); + } + if (GetNavType() == kTypePath) { + SetNavType(kTypeDirection); + } +} + +void WRoadNetwork::GetSegmentEndPoints(const WRoadSegment &segment, UMath::Vector3 &start, UMath::Vector3 &end) { + WRoadNetwork &roadNetwork = Get(); + const WRoadNode *nodePtr[2]; + roadNetwork.GetSegmentNodes(segment, nodePtr); + start = nodePtr[0]->fPosition; + end = nodePtr[1]->fPosition; +} + +void WRoadNetwork::GetSegmentForwardVector(const WRoadSegment &segment, UMath::Vector3 &forwardVector) { + const WRoadNode *nodes[2]; + GetSegmentNodes(segment, nodes); + UMath::Vector3 v = UVector3(nodes[1]->fPosition) - nodes[0]->fPosition; + UMath::Unit(v, forwardVector); +} + +void WRoadNetwork::GetPointOnSegment(const WRoadSegment &segment, float d, UMath::Vector3 &point) { + WRoadNetwork &roadNetwork = Get(); + if (d > 1.0f) { + d = 1.0f; + } + if (d < 0.0f) { + d = 0.0f; + } + UMath::Vector3 start; + UMath::Vector3 end; + roadNetwork.GetSegmentEndPoints(segment, start, end); + GetPointOnSegment(start, end, segment, d, point); +} + +void WRoadNetwork::GetPointOnSegment(const UMath::Vector3 &start, const UMath::Vector3 &end, const WRoadSegment &segment, float d, UMath::Vector3 &point) { + if (segment.IsCurved()) { + GetSegmentCurveStep(start, end, segment, d, point); + return; + } + point.x = start.x + (end.x - start.x) * d; + point.y = start.y + (end.y - start.y) * d; + point.z = start.z + (end.z - start.z) * d; +} + +void WRoadNetwork::GetSegmentCurveStep(const UMath::Vector3 &start, const UMath::Vector3 &end, const WRoadSegment &segment, float u, UMath::Vector3 &point) { + WRoadNetwork &roadNetwork = Get(); + static USpline roadSpline; + UMath::Vector4 tempPos; + UMath::Vector3 end_control; + UMath::Vector3 start_control; + segment.GetEndControl(end_control); + segment.GetStartControl(start_control); + roadSpline.BuildSplineEx(start, UVector3(start) + UVector3(start_control), end, UVector3(end) + UVector3(end_control)); + roadSpline.EvaluateSpline(u, tempPos); + point = UMath::Vector4To3(tempPos); +} + +void WRoadNetwork::BuildSegmentSpline(const WRoadSegment &segment, USpline &spline) { + const WRoadNode *nodePtr[2]; + UMath::Vector3 end_control; + segment.GetEndControl(end_control); + UMath::Vector3 start_control; + segment.GetStartControl(start_control); + GetSegmentNodes(segment, nodePtr); + const UMath::Vector3 &start = nodePtr[0]->fPosition; + const UMath::Vector3 &end = nodePtr[1]->fPosition; + spline.BuildSplineEx(start, UVector3(start) + UVector3(start_control), end, UVector3(end) + UVector3(end_control)); +} + +bool WRoadNetwork::GetSegmentProfiles(const WRoadSegment &segment, const WRoadProfile **profile) { + WRoadNetwork &roadNetwork = Get(); + const WRoadNode *node[2]; + node[0] = roadNetwork.GetNode(segment.fNodeIndex[0]); + node[1] = roadNetwork.GetNode(segment.fNodeIndex[1]); + if (node[0] == nullptr || node[1] == nullptr || node[0]->fProfileIndex < 0 || node[1]->fProfileIndex < 0) { + profile[0] = &fInvalidProfile; + profile[1] = &fInvalidProfile; + return false; + } + profile[0] = roadNetwork.GetProfile(node[0]->fProfileIndex); + profile[1] = roadNetwork.GetProfile(node[1]->fProfileIndex); + return true; +} + +int WRoadNetwork::GetSegmentNumTrafficLanes(const WRoadSegment &segment) { + WRoadNetwork &roadNetwork = Get(); + const WRoadProfile *profilePtr[2]; + int numTrafficLanes[2] = {0}; + roadNetwork.GetSegmentProfiles(segment, profilePtr); + for (int i = 0; i < profilePtr[0]->fNumZones; i++) { + if (profilePtr[0]->GetLaneType(i, false) == 1) { + numTrafficLanes[0]++; + } + } + for (int i = 0; i < profilePtr[1]->fNumZones; i++) { + if (profilePtr[1]->GetLaneType(i, false) == 1) { + numTrafficLanes[1]++; + } + } + return UMath::Max(numTrafficLanes[1], numTrafficLanes[0]); +} + +int WRoadNetwork::GetSegmentTrafficLaneInd(const WRoadSegment &segment, int lane_count) { + const WRoadProfile *profile[2]; + Get().GetSegmentProfiles(segment, profile); + for (int i = 0; i < profile[0]->fNumZones; i++) { + if (profile[0]->GetLaneType(i, false) == 1) { + if (lane_count <= 0) { + return i; + } + lane_count--; + } + } + return 0; +} + +void WRoadNetwork::FlagSegmentRaceDirection(int FirstSegIndex, int SecondSegIndex) { + WRoadSegment *FirstSeg = GetSegmentNonConst(FirstSegIndex); + WRoadSegment *SecondSeg = GetSegmentNonConst(SecondSegIndex); + if (FirstSeg->fNodeIndex[0] == SecondSeg->fNodeIndex[0] || FirstSeg->fNodeIndex[0] == SecondSeg->fNodeIndex[1]) { + FirstSeg->SetRaceRouteForward(false); + } else { + FirstSeg->SetRaceRouteForward(true); + } + if (SecondSeg->fNodeIndex[0] == FirstSeg->fNodeIndex[0] || SecondSeg->fNodeIndex[0] == FirstSeg->fNodeIndex[1]) { + SecondSeg->SetRaceRouteForward(true); + } else { + SecondSeg->SetRaceRouteForward(false); + } +} + +void WRoadNetwork::AddRaceSegments(WRoadNav *road_nav) { + if (road_nav->GetNavType() != WRoadNav::kTypePath) { + return; + } + int num_segments = road_nav->GetNumPathSegments(); + for (int i = 0; i < num_segments; i++) { + GetSegmentNonConst(road_nav->GetPathSegment(i))->SetInRace(true); + if (i < num_segments - 1) { + FlagSegmentRaceDirection(road_nav->GetPathSegment(i), road_nav->GetPathSegment(i + 1)); + } + } +} + +void WRoadNetwork::ResetShortcuts() { + for (unsigned int segment_number = 0; segment_number < fNumSegments; segment_number++) { + WRoadSegment *segment = GetSegmentNonConst(segment_number); + segment->SetShortcut(false); + } + for (unsigned int road_number = 0; road_number < fNumRoads; road_number++) { + WRoad *road = GetRoadNonConst(road_number); + road->nShortcut = 0xFF; + } +} + +void WRoadNetwork::ResolveShortcuts() { + ResetShortcuts(); + if (GRaceStatus::Exists()) { + GRaceParameters *race_parameters = GRaceStatus::Get().GetRaceParameters(); + if (race_parameters != nullptr) { + WRoadNav nav; + nav.SetDecisionFilter(true); + nav.SetNavType(WRoadNav::kTypeDirection); + int num_shortcuts = race_parameters->GetNumShortcuts(); + for (int i = 0; i < num_shortcuts; i++) { + GMarker *shortcut = race_parameters->GetShortcut(i); + if (shortcut != nullptr) { + nav.InitAtPoint(shortcut->GetPosition(), shortcut->GetDirection(), true, 0.7f); + if (nav.IsValid()) { + WRoad *road = Get().GetRoadNonConst(nav.GetRoadInd()); + road->nShortcut = static_cast(i); + } + } + } + for (unsigned int segment_number = 0; segment_number < fNumSegments; segment_number++) { + WRoadSegment *segment = GetSegmentNonConst(segment_number); + if (segment->fRoadID != -1) { + if (Get().GetRoad(segment->fRoadID)->nShortcut != 0xFF) { + segment->SetShortcut(true); + } + } + } + } + } +} + +bool WRoadNetwork::GetSegmentTrafficLaneRightSide(const WRoadSegment &segment, int laneInd) { + WRoadNetwork &roadNetwork = Get(); + const WRoadProfile *profilePtr[2]; + roadNetwork.GetSegmentProfiles(segment, profilePtr); + return laneInd >= profilePtr[0]->fMiddleZone; +} + +int WRoadNetwork::GetRightMostTrafficEntrance(int node_number, int onto_segment) { + int ret = -1; + const WRoadNode *node = GetNode(node_number); + int num_segments = node->fNumSegments; + + if (num_segments < 3) { + return ret; + } + + const WRoadSegment *segment = GetSegment(onto_segment); + int which_node = node_number != segment->fNodeIndex[0]; + bool inverted = segment->IsProfileInverted(which_node); + bool forward = static_cast< bool >(which_node) == inverted; + const WRoadProfile *profile = GetProfile(node->fProfileIndex); + + if (profile->GetNumTrafficLanes(forward, inverted) > 0) { + UMath::Vector2 onto_forward; + float best_cross; + bool first = true; + UMath::Vector2 best_vector; + + segment->GetForwardVec(which_node, onto_forward); + + if (which_node) { + UMath::Scale(onto_forward, -1.0f, onto_forward); + } + + for (int i = 0; i < num_segments; i++) { + int segment_number = node->fSegmentIndex[i]; + if (segment_number != onto_segment) { + const WRoadSegment *from_segment = GetSegment(segment_number); + const WRoadNode *from_node = GetSegmentOppNode(segment_number, node); + int from_which_node = node_number != from_segment->fNodeIndex[0]; + bool from_inverted = from_segment->IsProfileInverted(from_which_node); + bool from_forward = from_inverted != static_cast< bool >(from_which_node); + const WRoadProfile *from_profile = GetProfile(from_node->fProfileIndex); + + if (from_profile->GetNumTrafficLanes(from_forward, from_inverted) != 0) { + UMath::Vector3 vector_3d; + UMath::Sub(from_node->fPosition, node->fPosition, vector_3d); + UMath::Vector2 vector = UMath::Vector2Make(vector_3d.x, vector_3d.z); + float cross = UMath::Cross(vector, onto_forward); + + if (first) { + first = false; + best_cross = cross; + best_vector = vector; + ret = segment_number; + } else { + float old_cross = UMath::Cross(best_vector, vector); + bool right_of_onto = cross >= 0.0f; + bool right_of_best = old_cross >= 0.0f; + bool is_best; + if (best_cross < 0.0f) { + is_best = right_of_onto || right_of_best; + } else { + is_best = right_of_onto && right_of_best; + } + if (is_best) { + best_cross = cross; + best_vector = vector; + ret = segment_number; + } + } + } + } + } + } + + return ret; +} + +int WRoadProfile::GetNumTrafficLanes(bool forward) const { + int num_traffic_lanes = 0; + int num_lanes = GetNumLanes(forward); + for (int i = 0; i < num_lanes; i++) { + if (GetLaneType(i, !forward) == 1) { + num_traffic_lanes++; + } + } + return num_traffic_lanes; +} + +int WRoadProfile::GetNthTrafficLane(int n, bool forward) const { + int num_traffic_lanes = 0; + int num_lanes = GetNumLanes(forward); + int fallback = GetMiddleZone(forward); + for (int i = 0; i < num_lanes; i++) { + int real_lane = GetNthLane(i, forward); + if (GetLaneType(real_lane, false) == 1) { + if (num_traffic_lanes == n) { + return real_lane; + } + num_traffic_lanes++; + fallback = real_lane; + } + } + return fallback; +} + +int WRoadProfile::GetNthTrafficLaneFromCurb(int n, bool forward) const { + int num_traffic_lanes = 0; + int num_lanes = GetNumLanes(forward); + int fallback = GetMiddleZone(!forward); + for (int i = num_lanes - 1; i >= 0; i--) { + int real_lane = GetNthLane(i, !forward); + if (GetLaneType(real_lane, false) == 1) { + if (num_traffic_lanes == n) { + return real_lane; + } + num_traffic_lanes++; + fallback = real_lane; + } + } + return fallback; +} + +unsigned char WRoadNav::FirstShortcutInPath() { + if (GetNavType() == kTypePath) { + int num_segments = GetNumPathSegments(); + for (int i = 0; i < num_segments; i++) { + const WRoadSegment *segment = WRoadNetwork::Get().GetSegment(GetPathSegment(i)); + if (segment->IsShortcut()) { + const WRoad *road = WRoadNetwork::Get().GetRoad(segment->fRoadID); + return road->nShortcut; + } + } + } + return 0xff; +} + +const WRoadSegment *GetAttachedDirectionalSegment(const WRoadNode *node, short segment_index) { + WRoadNetwork &roadNetwork = WRoadNetwork::Get(); + for (int i = 0; i < node->fNumSegments; i++) { + const WRoadSegment *newRoadSegment = roadNetwork.GetSegment(node->fSegmentIndex[i]); + if (segment_index != newRoadSegment->fIndex && (newRoadSegment->fFlags ^ 1) & 1) { + return newRoadSegment; + } + } + return nullptr; +} +WRoadNav::WRoadNav() { + fOccludingTrailSpeed = 0.0f; + pAIVehicle = nullptr; + bRaceFilter = false; + bTrafficFilter = false; + bCopFilter = false; + bDecisionFilter = false; + pCookieTrail = nullptr; + bCookieTrail = false; + pPathSegments = nullptr; + nRoadOcclusion = 0; + nAvoidableOcclusion = 0; + bOccludedFromBehind = false; + Reset(); +} + +WRoadNav::~WRoadNav() { + PathFinder *path_finder = PathFinder::Get(); + if (path_finder) { + path_finder->Cancel(this); + } + if (pCookieTrail) { + delete pCookieTrail; + } + if (pPathSegments) { + delete pPathSegments; + } +} + +bool WRoadNav::IsWrongWay() const { + if (!GetRaceFilter()) { + return false; + } + if (!IsValid()) { + return false; + } + bool result = false; + bool is_node_one = (fNodeInd == 1); + const WRoadSegment *segment = GetSegment(); + if (segment->IsInRace()) { + bool seg_foward = true; + if (!segment->RaceRouteForward()) { + seg_foward = false; + } + if (is_node_one) { + if (!seg_foward) { + result = true; + } + } else if (seg_foward) { + result = true; + } + } + return result; +} + +unsigned int WRoadNav::GetRoadSpeechId() { + unsigned short segment_index = GetSegmentInd(); + WRoadNetwork &road_network = WRoadNetwork::Get(); + unsigned short num_segments = road_network.GetNumSegments(); + if (segment_index != bClamp(segment_index, 0, num_segments - 1)) { + return 0; + } + const WRoadSegment *segment = road_network.GetSegment(segment_index); + short road_index = segment->fRoadID; + short num_roads = road_network.GetNumRoads(); + if (road_index != bClamp(road_index, 0, num_roads - 1)) { + return 0; + } + const WRoad *road = road_network.GetRoad(road_index); + return road->nSpeechId; +} + +unsigned char WRoadNav::GetShortcutNumber() { + if (!IsValid()) { + return 0xFF; + } + WRoadNetwork &rn = WRoadNetwork::Get(); + int segment_number = GetSegmentInd(); + const WRoadSegment *segment = rn.GetSegment(segment_number); + if (segment->IsDecision()) { + const WRoadNode *nodes[2]; + int which_node = GetNodeInd(); + rn.GetSegmentNodes(*segment, nodes); + segment = GetAttachedDirectionalSegment(nodes[which_node], segment_number); + if (segment == nullptr || !segment->IsShortcut()) { + segment = GetAttachedDirectionalSegment(nodes[which_node ^ 1], segment_number); + } + } + if (segment != nullptr && segment->IsShortcut()) { + int road_number = segment->fRoadID; + if (road_number != -1) { + const WRoad *road = rn.GetRoad(road_number); + return road->nShortcut; + } + } + return 0xFF; +} + +bool WRoadNav::IsOnLegalRoad() { + if (IsValid()) { + int segment_number = GetSegmentInd(); + WRoadNetwork &rn = WRoadNetwork::Get(); + const WRoadSegment *segment = rn.GetSegment(segment_number); + if (segment->IsDecision()) { + const WRoadNode *node = rn.GetNode(segment->fNodeIndex[GetNodeInd()]); + segment = GetAttachedDirectionalSegment(node, segment_number); + } + return segment != nullptr && segment->IsTrafficAllowed(); + } + return false; +} + +bool WRoadNav::FindPath(const UMath::Vector3 *goal_position, const UMath::Vector3 *goal_direction, char *shortcut_allowed) { + PathFinder *path_finder = PathFinder::Get(); + if (path_finder != nullptr) { + MaybeAllocatePathSegments(); + AStarSearch *search = path_finder->Pending(this); + if (search == nullptr) { + search = path_finder->Submit(this, goal_position, goal_direction, shortcut_allowed); + } + return search != nullptr; + } + return false; +} + +bool WRoadNav::FindPathNow(const UMath::Vector3 *goal_position, const UMath::Vector3 *goal_direction, char *shortcut_allowed) { + bool ret = FindPath(goal_position, goal_direction, shortcut_allowed); + if (ret) { + PathFinder::Get()->ServiceAll(); + } + return ret && nPathSegments > 0; +} + +void WRoadNav::SetStartEndPos(const WRoadSegment &segment, float startOffset, float endOffset) { + SetBoundPos(segment, endOffset, false); + SetBoundPos(segment, startOffset, true); +} + +void WRoadNav::InitAtPoint(const UMath::Vector3 &pos, const UMath::Vector3 &dir, bool forceCenterLane, float dirWeight) { + float time; + int segment = FindClosestSegmentInd(pos, dir, dirWeight, fPosition, time); + if (segment == -1) { + fSegmentInd = 0; + fValid = false; + } else { + InitAtSegment(static_cast(segment), time, pos, dir, forceCenterLane); + } +} + +void WRoadNav::InitAtPath(const UMath::Vector3 &position, bool forceCenterLane) { + UMath::Vector3 found_position; + UMath::Vector3 found_direction; + unsigned short found_segment; + float found_interval = -1.0f; + int result = FindClosestOnPath(position, &found_position, &found_direction, &found_segment, &found_interval); + if (result == 0) { + fSegmentInd = 0; + fValid = false; + } else { + InitAtSegment(static_cast(found_segment), found_interval, found_position, found_direction, forceCenterLane); + } +} + +bool WRoadNav::UpdateLaneChange(float distance) { + if (fLaneChangeDist <= 0.0f) { + return false; + } + float laneChangeLerp = (fLaneChangeInc + distance) / fLaneChangeDist; + fLaneChangeInc = fLaneChangeInc + distance; + if (laneChangeLerp < 1.0f) { + fLaneOffset = (fToLaneOffset - fFromLaneOffset) * laneChangeLerp + fFromLaneOffset; + } else { + fLaneOffset = fToLaneOffset; + fLaneChangeDist = 0.0f; + fFromLaneOffset = fToLaneOffset; + fLaneChangeInc = 0.0f; + } + return true; +} + +void WRoadNav::RebuildSplines(const WRoadSegment *segment) { + if (segment->IsCurved()) { + fRoadSpline.BuildSplineEx(fStartPos, fStartControl, fEndPos, fEndControl); + if (bCookieTrail) { + fLeftSpline.BuildSplineEx(fLeftStartPos, fLeftStartControl, fLeftEndPos, fLeftEndControl); + fRightSpline.BuildSplineEx(fRightStartPos, fRightStartControl, fRightEndPos, fRightEndControl); + } + } +} + +void WRoadNav::InitFromOtherNav(WRoadNav *other_nav, bool flip_direction) { + if (other_nav != this) { + fSegmentInd = other_nav->GetSegmentInd(); + fNodeInd = other_nav->GetNodeInd(); + fSegTime = other_nav->GetSegmentTime(); + SetLaneInd(other_nav->GetLaneInd()); + SetLaneOffset(other_nav->GetLaneOffset()); + fValid = other_nav->fValid; + if (flip_direction) { + fNodeInd = fNodeInd ^ 1; + fSegTime = 1.0f - fSegTime; + } + WRoadNetwork &road_network = WRoadNetwork::Get(); + const WRoadSegment *segment = road_network.GetSegment(fSegmentInd); + SetStartEndPos(*segment, GetLaneOffset()); + SetStartEndControls(*segment); + RebuildSplines(segment); + EvaluateSplines(segment); + ResetCookieTrail(); + } +} + +void WRoadNav::Reverse() { + if (GetRaceFilter() && !IsWrongWay() && (fPathType == kPathRacer || fPathType == kPathPlayer)) { + return; + } + fNodeInd = fNodeInd ^ 1; + fSegTime = 1.0f - fSegTime; + const WRoadSegment *segment = WRoadNetwork::Get().GetSegment(fSegmentInd); + SetStartEndPos(*segment, fLaneOffset); + SetStartEndControls(*segment); + RebuildSplines(segment); + EvaluateSplines(segment); + ResetCookieTrail(); +} + +void WRoadNav::PullOver() { + ClearCookieTrail(); + + int which_node = GetNodeInd(); + WRoadNetwork &rn = WRoadNetwork::Get(); + int segment_number = GetSegmentInd(); + const WRoadSegment *segment = rn.GetSegment(segment_number); + const WRoadProfile *profile = rn.GetSegmentProfile(*segment, which_node); + int num_lanes = profile->fNumZones; + bool inverted = segment->IsProfileInverted(which_node) ^ (which_node == 0); + + int lane = profile->GetLaneNumber(GetLaneInd(), inverted); + + bool is_barrier = false; + bool last_lane; + while (lane < num_lanes - 1) { + int next_lane_type = profile->GetLaneType(lane + 1, inverted); + if (next_lane_type == kLaneAny) { + is_barrier = true; + } + if (next_lane_type != kLaneTraffic) break; + lane++; + } + + float extra = fVehicleHalfWidth; + if (lane == num_lanes - 1 || is_barrier) { + extra = -extra; + } + + float offset = profile->GetLaneOffset(lane, inverted) + profile->GetLaneWidth(lane, inverted) * 0.5f + extra; + + const UMath::Vector3 &nav_forward = GetForwardVector(); + UMath::Vector3 nav_right = UMath::Vector3Make(nav_forward.z, 0.0f, -nav_forward.x); + UMath::Normalize(nav_right); + + float offset_change = offset - GetLaneOffset(); + UMath::ScaleAdd(nav_right, offset_change, GetPosition(), GetPosition()); +} + +bool WRoadNav::IsPointInCookieTrail(const UMath::Vector3 &position_3d, float margin) { + if (pCookieTrail != nullptr) { + bVector2 position(position_3d.x, position_3d.z); + if (bBoundingBoxIsInside(&vCookieTrailBoxMin, &vCookieTrailBoxMax, &position, margin)) { + int closest_cookie = ClosestCookieAhead(position_3d, nullptr); + if (closest_cookie >= nCookieIndex) { + const NavCookie &cookie = pCookieTrail->NthOldest(closest_cookie); + float y = bClamp(position_3d.y, cookie.Centre.y - 5.0f, cookie.Centre.y + 5.0f); + if (position_3d.y == y) { + float min_offset = cookie.LeftOffset - margin; + float max_offset = cookie.RightOffset + margin; + bVector2 centre_2d(cookie.Centre.x, cookie.Centre.z); + bVector2 cookie_to_position = position - centre_2d; + float offset = bCross(&cookie_to_position, reinterpret_cast(&cookie.Forward)); + float clamped_offset = bClamp(offset, min_offset, max_offset); + return offset == clamped_offset; + } + } + } + } + return false; +} + +bool WRoadNav::IsSegmentInCookieTrail(int segment_number, bool use_whole_path) { + if (pCookieTrail != nullptr) { + int num_cookies = pCookieTrail->Count(); + int i = 0; + if (!use_whole_path) { + i = nCookieIndex; + } + for (; i < num_cookies; i++) { + const NavCookie &cookie = pCookieTrail->NthOldest(i); + if (segment_number == cookie.SegmentNumber) { + return true; + } + } + } + return false; +} + +bool WRoadNav::CookieCutter(NavCookie &cookie, const UMath::Vector3 ¢re, float projection, bool pass_left, + unsigned int cut_flags) { + bVector2 cookie_to_centre(centre.x - cookie.Centre.x, centre.z - cookie.Centre.z); + float l = bCross(&cookie_to_centre, reinterpret_cast(&cookie.Forward)); + float left_offset = cookie.LeftOffset; + float right_offset = cookie.RightOffset; + + if (l != bClamp(l, left_offset - projection, right_offset + projection)) { + return false; + } + + cookie.Flags |= 1 | cut_flags; + + bVector2 cookie_centre(cookie.Centre.x, cookie.Centre.z); + bVector2 cookie_right(cookie.Forward.y, -cookie.Forward.x); + + float minimum_width; + if (GetNavType() == kTypeTraffic) { + minimum_width = 0.1f; + } else { + minimum_width = 1.0f; + } + + if (pass_left) { + right_offset = bMax(left_offset + minimum_width, l - projection); + bScaleAdd(reinterpret_cast(&cookie.Right), &cookie_centre, &cookie_right, right_offset); + cookie.RightOffset = right_offset; + } else { + left_offset = bMin(l + projection, right_offset - minimum_width); + bScaleAdd(reinterpret_cast(&cookie.Left), &cookie_centre, &cookie_right, left_offset); + cookie.LeftOffset = left_offset; + } + + return true; +} + +void WRoadNav::ClampCookieCentres(NavCookie *cookies, int num_cookies) { + for (int i = 0; i < num_cookies; i++) { + NavCookie &cookie = cookies[i]; + if (cookie.Flags & 1) { + float size = (cookie.RightOffset - cookie.LeftOffset) * 0.5f; + float cx = (cookie.Left.x + cookie.Right.x) * 0.5f; + float cz = (cookie.Left.y + cookie.Right.y) * 0.5f; + cookie.RightOffset = size; + cookie.Centre.x = cx; + cookie.Centre.z = cz; + cookie.LeftOffset = -size; + } + } +} + +static float TimeToClosestApproach(const UMath::Vector3 &p0, const UMath::Vector3 &v0, const UMath::Vector3 &p1, const UMath::Vector3 &v1, float *closing_speed) { + UMath::Vector3 p; + UMath::Vector3 v; + UMath::Vector3 dir; + p = UVector3(p1) - p0; + v = UVector3(v1) - v0; + UMath::Unit(p, dir); + float a = UMath::Dot(dir, v); + *closing_speed = -a; + float b = UMath::Dot(v, v); + a = UMath::Dot(p, v); + if (b < 0.001f) { + return 1000.0f; + } + return -a / b; +} + +int WRoadNav::FetchAvoidables(IBody **avoidables, const int listsize) const { + IVehicleAI *my_ai = pAIVehicle; + if (!my_ai) { + return 0; + } + + IPursuit *my_pursuit = my_ai->GetPursuit(); + + IPursuitAI *my_pursuitai; + my_ai->QueryInterface(&my_pursuitai); + bool is_formation_cop = false; + if (my_pursuitai && my_pursuitai->GetInFormation()) { + is_formation_cop = true; + } + + ISimable *my_pursuit_target = nullptr; + if (my_pursuit && is_formation_cop) { + AITarget *target = my_pursuit->GetTarget(); + if (target) { + my_pursuit_target = target->GetSimable(); + } + } + + int num_avoidables = 0; + + IArticulatedVehicle *my_hitch; + my_ai->QueryInterface(&my_hitch); + + const AvoidableList &avoidable_list = my_ai->GetAvoidableList(); + + for (AvoidableList::const_iterator iter = avoidable_list.begin(); + iter != avoidable_list.end() && num_avoidables < listsize; + iter++) { + AIAvoidable *av = *iter; + IBody *avoidable_body; + if (!av->QueryInterface(&avoidable_body)) { + continue; + } + + if (my_hitch) { + if (ComparePtr(avoidable_body, my_hitch->GetTrailer()) && my_hitch->IsHitched()) { + continue; + } + } + + if (is_formation_cop && my_pursuit) { + IVehicleAI *his_ai; + if (avoidable_body->QueryInterface(&his_ai)) { + IPursuit *his_pursuit = his_ai->GetPursuit(); + if (my_pursuit == his_pursuit) { + IPursuitAI *his_pursuitai; + if (ComparePtr(my_pursuit_target, his_ai)) { + continue; + } + his_ai->QueryInterface(&his_pursuitai); + if (his_pursuitai && his_pursuitai->GetInFormation()) { + continue; + } + } + } + } + + avoidables[num_avoidables] = avoidable_body; + num_avoidables++; + } + + return num_avoidables; +} + +void WRoadNav::HolePunchAvoidables(NavCookie *cookies, int num_cookies, float current_offset, float delta_offset) { + if (num_cookies == 0) return; + + IVehicleAI *my_ai = pAIVehicle; + if (!my_ai) return; + + IBody *avoidables[32]; + int num_avoidables = FetchAvoidables(avoidables, 32); + if (num_avoidables == 0) return; + + IRigidBody *my_body; + my_ai->QueryInterface(&my_body); + + const UMath::Vector3 &my_position = my_body->GetPosition(); + + int my_cookie_index = ClosestCookieAhead(my_position, cookies, num_cookies, nullptr); + if (my_cookie_index < 0) return; + + const NavCookie &my_cookie = cookies[my_cookie_index]; + + bool is_racer = false; + int closest_avoidable = num_cookies; + + const UMath::Vector3 &my_velocity = my_body->GetLinearVelocity(); + + UMath::Vector3 my_right; + my_body->GetRightVector(my_right); + UMath::Vector3 my_forward; + my_body->GetForwardVector(my_forward); + UMath::Vector3 my_dimension; + my_body->GetDimension(my_dimension); + + float my_speed = my_body->GetSpeed(); + + bVector2 nav_forward(fForwardVector.x, fForwardVector.z); + bNormalize(&nav_forward, &nav_forward); + + if (GetPathType() == kPathRacer || GetPathType() == kPathPlayer) { + is_racer = true; + } + + bool is_traffic = GetNavType() == kTypeTraffic; + bool is_drag = GRaceStatus::IsDragRace(); + + for (int i = 0; i < num_avoidables; i++) { + IBody *avoidable_body = avoidables[i]; + + UMath::Matrix4 tranform; + avoidable_body->GetTransform(tranform); + + const UMath::Vector3 &avoidable_forward = UMath::Vector4To3(tranform.v2); + const UMath::Vector3 &avoidable_right = UMath::Vector4To3(tranform.v0); + + UMath::Vector3 avoidable_position; + avoidable_position.y = tranform.v3.y; + avoidable_position.z = tranform.v3.z; + avoidable_position.x = tranform.v3.x; + + float elevation = avoidable_position.y - my_cookie.Centre.y; + if (bClamp(elevation, -5.0f, 5.0f) != elevation) continue; + + UMath::Vector3 avoidable_dimension; + avoidable_body->GetDimension(avoidable_dimension); + + IVehicle *his_vehicle; + DriverClass his_class; + if (!avoidable_body->QueryInterface(&his_vehicle)) { + his_class = DRIVER_NONE; + } else { + his_class = his_vehicle->GetDriverClass(); + } + + bool he_is_traffic = false; + if (his_vehicle && (his_class == DRIVER_TRAFFIC || his_class == DRIVER_NONE)) { + he_is_traffic = true; + } + + if (is_racer && he_is_traffic) { + float facing = avoidable_right.x * my_cookie.Forward.x + avoidable_right.z * my_cookie.Forward.y; + if (facing < 0.0f) { + facing = -facing; + } + if (facing > 0.707f && his_vehicle && his_vehicle->GetVehicleClass() == VehicleClass::TRAILER) { + UMath::ScaleAdd(avoidable_forward, -6.0f, avoidable_position, avoidable_position); + avoidable_dimension.x = 1.8f; + avoidable_dimension.z = 1.8f; + } + } + + UMath::Vector3 avoidable_to_me; + UMath::Sub(avoidable_position, my_position, avoidable_to_me); + + float dot_fwd = avoidable_forward.x * my_cookie.Forward.x + avoidable_forward.z * my_cookie.Forward.y; + if (dot_fwd < 0.0f) dot_fwd = -dot_fwd; + float dot_right = avoidable_right.x * my_cookie.Forward.x + avoidable_right.z * my_cookie.Forward.y; + if (dot_right < 0.0f) dot_right = -dot_right; + float his_extent = dot_right * avoidable_dimension.x + dot_fwd * avoidable_dimension.z + avoidable_dimension.x + 0.5f; + + float dist_ahead = avoidable_to_me.x * my_cookie.Forward.x + avoidable_to_me.z * my_cookie.Forward.y; + + float my_extent = my_dimension.x + 0.5f + my_dimension.z; + + float dot_my_fwd = UMath::Abs(UMath::Dot(my_right, avoidable_forward)); + float dot_my_right = UMath::Abs(UMath::Dot(my_right, avoidable_right)); + float extent_side = dot_my_right * avoidable_dimension.x + dot_my_fwd * avoidable_dimension.z + my_dimension.x; + + float dist_side = UMath::Abs(UMath::Dot(avoidable_to_me, my_right)); + + unsigned int cut_flags = 0; + + if (is_traffic || my_speed < 20.0f) { + if (dist_ahead + his_extent < my_extent) { + cut_flags = 2; + } + } else { + if (dist_ahead - his_extent <= my_extent) { + cut_flags = 2; + } + } + + float combined_extent = my_extent + his_extent; + + if (dist_ahead < -combined_extent) continue; + if (dist_ahead + his_extent < 0.0f && dist_side < extent_side) continue; + + UMath::Vector3 his_velocity; + avoidable_body->GetLinearVelocity(his_velocity); + + float gap_ahead = UMath::Max(0.0f, dist_ahead - combined_extent); + + UMath::Vector3 my_nose; + my_nose.x = my_position.x; + my_nose.y = my_position.y; + my_nose.z = my_position.z; + + float my_nose_ahead = UMath::Min(my_extent, gap_ahead); + my_nose.x += my_cookie.Forward.x * my_nose_ahead; + my_nose.z += my_cookie.Forward.y * my_nose_ahead; + + float his_nose_ahead = UMath::Min(his_extent, gap_ahead); + + UMath::Vector3 his_nose; + his_nose.y = avoidable_position.y; + his_nose.x = avoidable_position.x - my_cookie.Forward.x * his_nose_ahead; + his_nose.z = avoidable_position.z - my_cookie.Forward.y * his_nose_ahead; + + float closing_speed = 0.0f; + float approach_time = TimeToClosestApproach(my_nose, my_velocity, his_nose, his_velocity, &closing_speed); + + bool blocked_traffic = false; + if (cut_flags == 0) { + if (closing_speed <= 0.0f) { + float trailing_speed = combined_extent; + if (is_traffic) { + trailing_speed = combined_extent + my_speed * 0.5f + my_extent + my_extent; + } + if (dist_ahead > trailing_speed) continue; + if (is_traffic && dist_side > extent_side + 1.0f) continue; + } + blocked_traffic = is_traffic; + } + + if (blocked_traffic || approach_time < 3.0f) { + UMath::Vector3 point_of_impact; + point_of_impact.x = avoidable_position.x; + point_of_impact.y = avoidable_position.y; + point_of_impact.z = avoidable_position.z; + + float closing_along = his_velocity.x * my_cookie.Forward.x + his_velocity.z * my_cookie.Forward.y; + float time_offset = approach_time * closing_along - combined_extent; + point_of_impact.z += my_cookie.Forward.y * time_offset; + point_of_impact.x += my_cookie.Forward.x * time_offset; + + int closest_cookie = ClosestCookieAhead(point_of_impact, cookies, num_cookies, nullptr); + if (closest_cookie > -1) { + const NavCookie &cookie = cookies[closest_cookie]; + + UMath::Vector3 right; + UMath::Scale(avoidable_right, avoidable_dimension.x, right); + UMath::Vector3 forward; + UMath::Scale(avoidable_forward, avoidable_dimension.z, forward); + + bVector2 left_diagonal(forward.x - right.x, forward.z - right.z); + bVector2 right_diagonal(forward.x + right.x, forward.z + right.z); + bVector2 avoidable_velocity(his_velocity.x, his_velocity.z); + + float avoidable_delta_offset = bCross(&avoidable_velocity, reinterpret_cast(&cookie.Forward)); + + if (closest_cookie < closest_avoidable && dist_ahead > combined_extent) { + fOccludingTrailSpeed = closing_along; + closest_avoidable = closest_cookie; + } + + UMath::Vector3 cut_to_position; + cut_to_position.x = point_of_impact.x; + cut_to_position.y = point_of_impact.y; + cut_to_position.z = point_of_impact.z; + + float lateral_projection = bClamp(approach_time, 0.0f, 1.0f); + float offset_change = avoidable_delta_offset * lateral_projection; + + cut_to_position.x += offset_change * cookie.Forward.y * 0.8f; + cut_to_position.z -= offset_change * cookie.Forward.x * 0.8f; + + bVector2 cookie_to_avoidable(cut_to_position.x - cookie.Centre.x, cut_to_position.z - cookie.Centre.z); + + float extra_width = offset_change * 0.2f; + + bVector2 cookie_to_me(my_position.x - cookie.Centre.x, my_position.z - cookie.Centre.z); + + float my_d = bDot(&cookie_to_me, reinterpret_cast(&cookie.Forward)); + float avoidable_d = bDot(&cookie_to_avoidable, reinterpret_cast(&cookie.Forward)); + + float close_factor = UMath::Ramp(avoidable_d - my_d, -6.0f, 12.0f); + + float right_projection = bCross(&right_diagonal, reinterpret_cast(&cookie.Forward)); + float left_projection = bCross(&left_diagonal, reinterpret_cast(&cookie.Forward)); + float avoidable_half_width = bAbs(right_projection); + float tmp = bAbs(left_projection); + avoidable_half_width = bMax(avoidable_half_width, tmp); + + float adjusted_width = extra_width * close_factor + avoidable_half_width; + + float nav_cross = bCross(reinterpret_cast(&nav_forward), reinterpret_cast(&cookie.Forward)); + float new_current_offset = lateral_projection * close_factor * delta_offset * 0.2f + current_offset + nav_cross + nav_cross; + + float hole_punch_safety_margin = close_factor; + if (is_drag) { + hole_punch_safety_margin = close_factor * 0.8f; + } + + float avoidable_offset = bCross(&cookie_to_avoidable, reinterpret_cast(&cookie.Forward)); + + float gap_left = avoidable_offset - adjusted_width - cookie.LeftOffset; + float gap_right = cookie.RightOffset - avoidable_offset - adjusted_width; + float gap_required = hole_punch_safety_margin + fVehicleHalfWidth; + + bool fit_left = gap_left > gap_required; + bool fit_right = gap_right > gap_required; + + bool pass_left = new_current_offset < avoidable_offset; + if (fit_right && !fit_left) { + pass_left = false; + } else if (!fit_right && fit_left) { + pass_left = true; + } + + float total_width = adjusted_width + fVehicleHalfWidth + hole_punch_safety_margin; + + int i = closest_cookie; + if (closest_cookie < num_cookies) { + while (true) { + NavCookie &this_cookie = cookies[i]; + int result = CookieCutter(this_cookie, cut_to_position, total_width, pass_left, cut_flags); + if (result == 0 && i == closest_cookie) break; + + UMath::Vector2 delta; + delta.x = point_of_impact.x - this_cookie.Centre.x; + delta.y = point_of_impact.y - this_cookie.Centre.y; + float dist_to_tail = bDot(reinterpret_cast(&delta), reinterpret_cast(&this_cookie.Forward)) + combined_extent + combined_extent; + if (dist_to_tail < 0.0f) break; + i++; + if (i >= num_cookies) break; + } + } + } + } + } + + ClampCookieCentres(cookies, num_cookies); +} + +void WRoadNetwork::GetPointAndVecOnSegment(const WRoadSegment &segment, float d, UMath::Vector3 &point, UMath::Vector3 &vec) { + WRoadNetwork &roadNetwork = Get(); + GetPointOnSegment(segment, d, point); + if (segment.IsCurved()) { + static USpline roadSpline; + roadNetwork.BuildSegmentSpline(segment, roadSpline); + UMath::Vector4 tangent; + roadSpline.EvaluateTangent(d, tangent); + vec = UMath::Vector4To3(tangent); + } else { + roadNetwork.GetSegmentForwardVector(segment, vec); + } +} + +float WRoadNetwork::GetLinePointIntersect(const UMath::Vector3 &start, const UMath::Vector3 &end, const UMath::Vector3 &pt, UMath::Vector3 &intersect, bool checkBound) { + UMath::Vector3 segVec; + UMath::Vector3 posVec; + + UMath::Sub(end, start, segVec); + UMath::Unit(segVec, segVec); + UMath::Sub(pt, start, posVec); + float length = UMath::Distance(pt, start); + UMath::Unit(posVec, posVec); + float dist = length * UMath::Dot(segVec, posVec); + float segDist = UMath::Distance(start, end); + + if (checkBound) { + if (dist >= segDist) { + intersect = end; + return 1.0f; + } + if (dist <= 0.0f) { + intersect = start; + return 0.0f; + } + } + + UMath::Unit(segVec, segVec); + UMath::Scale(segVec, dist, segVec); + UMath::Add(start, segVec, intersect); + return dist / segDist; +} + +float WRoadNetwork::GetSegmentPointIntersect(const WRoadSegment &segment, const UMath::Vector3 &pt, UMath::Vector3 &intersect, bool checkBound) { + UMath::Vector3 pos; + UMath::Vector3 pos2; + const WRoadNode *node0 = GetNode(segment.fNodeIndex[0]); + const WRoadNode *node1 = GetNode(segment.fNodeIndex[1]); + pos = node0->fPosition; + pos2 = node1->fPosition; + return GetLinePointIntersect(pos, pos2, pt, intersect, checkBound); +} + +bool WRoadNav::OnPath() const { + if (fNavType != kTypePath || !IsValid() || pPathSegments == nullptr || nPathSegments <= 0) { + return false; + } + int i; + WRoadNetwork &roadNetwork = WRoadNetwork::Get(); + const WRoadSegment *segment = roadNetwork.GetSegment(fSegmentInd); + const WRoadNode *node = roadNetwork.GetNode(segment->fNodeIndex[static_cast(fNodeInd)]); + bool found; + for (i = 0; i < nPathSegments; i++) { + if (fSegmentInd == pPathSegments[i]) { + break; + } + } + if (++i < nPathSegments) { + int new_segment_index = pPathSegments[i]; + const WRoadSegment *new_segment = roadNetwork.GetSegment(new_segment_index); + const WRoadNode *new_nodes[2]; + roadNetwork.GetSegmentNodes(*new_segment, new_nodes); + bool match_first = node == new_nodes[0]; + bool match_second = node == new_nodes[1]; + if (!match_first && !match_second) { + return false; + } + return true; + } + return false; +} + +short WRoadNav::GetNextOffset(const UMath::Vector3 &to, float &nextLaneOffset, char &nodeInd, bool &useOldStartPos) { + useOldStartPos = true; + short newSegInd = GetSegmentInd(); + WRoadNetwork &roadNetwork = WRoadNetwork::Get(); + const WRoadSegment *segment = roadNetwork.GetSegment(newSegInd); + const WRoadNode *node = roadNetwork.GetNode(segment->fNodeIndex[static_cast< int >(GetNodeInd())]); + bool end_of_path = false; + + if (fNavType == kTypePath) { + bool found = false; + if (pPathSegments != nullptr) { + int i; + for (i = 0; i < nPathSegments; i++) { + if (fSegmentInd == pPathSegments[i]) { + break; + } + } + if (++i < nPathSegments) { + int new_segment_index = pPathSegments[i]; + const WRoadNode *new_nodes[2]; + const WRoadSegment *new_segment = roadNetwork.GetSegment(new_segment_index); + roadNetwork.GetSegmentNodes(*new_segment, new_nodes); + bool match_first = (node == new_nodes[0]); + bool match_second = (node == new_nodes[1]); + if (match_first || match_second) { + nodeInd = match_first; + newSegInd = static_cast< short >(new_segment_index); + } else { + nodeInd = nodeInd ^ 1; + useOldStartPos = false; + } + found = true; + } else { + end_of_path = true; + } + } + if (!found) { + if (fPathType == kPathGPS) { + newSegInd = GetSegmentInd(); + return newSegInd; + } + SetNavType(kTypeDirection); + } + } + + if (fNavType == kTypeDirection) { + UMath::Vector3 toVec = to; + UMath::Unit(toVec, toVec); + const WRoadSegment *checkSegment = GetAttachedDirectionalSegment(node, GetSegmentInd()); + + if (node->fNumSegments > 1) { + if (checkSegment != nullptr) { + newSegInd = checkSegment->fIndex; + nodeInd = (node == roadNetwork.GetNode(checkSegment->fNodeIndex[0])); + } else if (!segment->IsDecision()) { + unsigned int shortcut_cached = 0; + unsigned int shortcut_allowed = 0; + unsigned char shortcut_number = GetShortcutNumber(); + float closest_to_target = 2.0f; + + if (shortcut_number != 0xFF) { + int mask = 1 << shortcut_number; + shortcut_cached |= mask; + shortcut_allowed |= mask; + } + + float target_dot; + if (bAiRandomTurns) { + target_dot = bRandom(1.0f); + } else { + target_dot = 1.0f; + } + + for (int i = 0; i < static_cast< int >(node->fNumSegments); i++) { + if (node->fSegmentIndex[i] == GetSegmentInd()) continue; + + bool respect_full_barriers = RespectFullBarriers(); + bool respect_drive_through_barriers = RespectDriveThroughBarriers(); + + const WRoadSegment *newRoadSegment = roadNetwork.GetSegment(node->fSegmentIndex[i]); + float worst_gap_to_target = 0.0f; + const int kMaxWalkSegments = 19; + const float kMaxWalkDistance = 100.0f; + float distance = kMaxWalkDistance; + const WRoadNode *walkRoadNode = node; + const WRoadSegment *walkRoadSegment = newRoadSegment; + + for (int w = 0; ; ) { + if (respect_full_barriers && walkRoadSegment->CrossesBarrier(respect_drive_through_barriers)) { + walkRoadSegment = nullptr; + break; + } + + bool walk_segment_forward = (walkRoadNode == roadNetwork.GetNode(walkRoadSegment->fNodeIndex[0])); + + if (bRaceFilter) { + if (!walkRoadSegment->IsInRace()) { + walkRoadSegment = nullptr; + break; + } + bool race_route_forward = walkRoadSegment->RaceRouteForward(); + if (walk_segment_forward) { + if (!race_route_forward) { + walkRoadSegment = nullptr; + break; + } + } else if (race_route_forward) { + walkRoadSegment = nullptr; + break; + } + } + + if (bTrafficFilter && !walkRoadSegment->IsTrafficAllowed()) { + walkRoadSegment = nullptr; + break; + } + + if (bCopFilter && !walkRoadSegment->ShouldCopsConsider()) { + walkRoadSegment = nullptr; + break; + } + + if (walkRoadSegment->IsShortcut()) { + const WRoad *road = roadNetwork.GetRoad(walkRoadSegment->fRoadID); + if (!MakeShortcutDecision(road->nShortcut, &shortcut_cached, &shortcut_allowed)) { + walkRoadSegment = nullptr; + break; + } + } + + UMath::Vector3 vec; + roadNetwork.GetSegmentForwardVector(*walkRoadSegment, vec); + if (!walk_segment_forward) { + UMath::Negate(vec); + } + UMath::Unit(vec, vec); + float dot = UMath::Dot(vec, toVec); + float gap_to_target = bAbs(dot - target_dot); + if (gap_to_target > worst_gap_to_target) { + worst_gap_to_target = gap_to_target; + } + if (worst_gap_to_target >= closest_to_target) { + walkRoadSegment = nullptr; + break; + } + + distance -= walkRoadSegment->GetLength(); + if (w > 0 && distance <= 0.0f) break; + + const WRoadNode *oppNode = roadNetwork.GetSegmentOppNode(*walkRoadSegment, walkRoadNode); + const WRoadSegment *nextSeg = GetAttachedDirectionalSegment(oppNode, walkRoadSegment->fIndex); + if (nextSeg == nullptr) { + if (w == 0) { + walkRoadSegment = nullptr; + } + break; + } + if (oppNode != roadNetwork.GetNode(nextSeg->fNodeIndex[0]) && nextSeg->IsOneWay()) { + walkRoadSegment = nullptr; + break; + } + if (end_of_path) break; + w++; + walkRoadSegment = nextSeg; + walkRoadNode = oppNode; + if (w > kMaxWalkSegments) break; + } + + if (walkRoadSegment != nullptr && closest_to_target > worst_gap_to_target) { + newSegInd = node->fSegmentIndex[i]; + char towards = (node == roadNetwork.GetNode(roadNetwork.GetSegment(newSegInd)->fNodeIndex[0])); + nodeInd = towards; + closest_to_target = worst_gap_to_target; + } + } + + if (newSegInd == GetSegmentInd()) { + nodeInd = nodeInd ^ 1; + useOldStartPos = false; + } + } else { + nodeInd = nodeInd ^ 1; + useOldStartPos = false; + } + } else { + nodeInd = nodeInd ^ 1; + useOldStartPos = false; + } + } + + nextLaneOffset = SnapToSelectableLane(fLaneOffset, newSegInd, nodeInd); + return newSegInd; +} + +void WRoadNav::ChangeLanes(float new_lane_offset, float dist) { + if (dist > 0.0f) { + float old_lane_offset = fToLaneOffset; + fLaneChangeInc = 0.0f; + fLaneOffset = old_lane_offset; + fToLaneOffset = new_lane_offset; + fLaneChangeDist = dist; + fFromLaneOffset = old_lane_offset; + return; + } + float old_lane_offset = fLaneOffset; + if (old_lane_offset != new_lane_offset) { + SetLaneOffset(new_lane_offset); + fLaneChangeDist = 0.0f; + const WRoadSegment *segment = WRoadNetwork::Get().GetSegment(fSegmentInd); + if (segment->IsDecision() && fLaneType != kLaneStartingGrid) { + SetBoundPos(*segment, new_lane_offset, false); + SetControlPos(*segment, false); + } else { + SetStartEndPos(*segment, new_lane_offset, new_lane_offset); + SetStartEndControls(*segment); + } + RebuildSplines(segment); + EvaluateSplines(segment); + } +} + +bool WRoadNav::IncLane(int direction) { + if (!IsValid()) { + return false; + } + + WRoadNetwork &roadNetwork = WRoadNetwork::Get(); + const WRoadSegment *segment = roadNetwork.GetSegment(GetSegmentInd()); + const WRoadProfile *profile = roadNetwork.GetSegmentProfile(*segment, GetNodeInd()); + + bool backward = (GetNodeInd() == 0); + bool inverted = segment->IsProfileInverted(GetNodeInd()); + bool inverted_xor_backward = inverted ^ backward; + + int current_lane = 0; + { + for (int n = 0; n < profile->fNumZones - 1; n++) { + float width = profile->GetLaneWidth(n, inverted_xor_backward); + float offset = profile->GetRelativeLaneOffset(n, inverted_xor_backward); + if (fLaneOffset > width * 0.5f + offset) { + current_lane++; + } + } + } + + do { + if (direction > 0) { + current_lane++; + } else { + current_lane--; + } + if (current_lane < 0 || current_lane >= profile->fNumZones) { + return false; + } + } while (!IsSelectable(profile->GetLaneType(current_lane, inverted_xor_backward))); + + float new_offset = profile->GetRelativeLaneOffset(current_lane, inverted_xor_backward); + float new_width = profile->GetLaneWidth(current_lane, inverted_xor_backward); + + if (UMath::Abs(new_offset - fLaneOffset) >= new_width * 0.5f) { + ChangeLanes(new_offset, 0.0f); + return true; + } + + return false; +} + +void WRoadNav::EvaluateSplines(const WRoadSegment *segment) { + if (segment->IsCurved()) { + UMath::Vector4 tempPos; + fRoadSpline.EvaluateSpline(fSegTime, tempPos); + fPosition = UMath::Vector4To3(tempPos); + fRoadSpline.EvaluateTangent(fSegTime, tempPos); + fForwardVector = UMath::Vector4To3(tempPos); + fCurvature = fRoadSpline.EvaluateCurvatureXZ(fSegTime); + if (bCookieTrail) { + UMath::Vector4 left_position; + UMath::Vector4 right_position; + fLeftSpline.EvaluateSpline(fSegTime, left_position); + fRightSpline.EvaluateSpline(fSegTime, right_position); + fLeftPosition = UMath::Vector4To3(left_position); + fRightPosition = UMath::Vector4To3(right_position); + } + } else { + fCurvature = 0.0f; + WRoadNetwork &roadNetwork = WRoadNetwork::Get(); + UMath::Sub(fEndPos, fStartPos, fForwardVector); + roadNetwork.GetPointOnSegment(fStartPos, fEndPos, *segment, fSegTime, fPosition); + if (bCookieTrail) { + roadNetwork.GetPointOnSegment(fLeftStartPos, fLeftEndPos, *segment, fSegTime, fLeftPosition); + roadNetwork.GetPointOnSegment(fRightStartPos, fRightEndPos, *segment, fSegTime, fRightPosition); + } + } +} + +void WRoadNav::UpdateCookieTrail(float cookie_gap) { + if (!IsValid()) return; + if (!bCookieTrail || pCookieTrail == nullptr) return; + + int num_cookies = pCookieTrail->Count(); + float cookie_length = 0.0f; + bool add_new_cookie = (num_cookies == 0); + + if (!add_new_cookie) { + const NavCookie &newest_cookie = pCookieTrail->Newest(); + UMath::Vector3 current_cookie_ray = UVector3(GetPosition()) - newest_cookie.Centre; + float current_ray_length = UMath::Length(current_cookie_ray); + + if (current_ray_length >= cookie_gap) { + add_new_cookie = true; + cookie_length = current_ray_length; + bVector2 cookie_ray_2d(current_cookie_ray.x, current_cookie_ray.z); + if (bDot(&cookie_ray_2d, reinterpret_cast(&newest_cookie.Forward)) < -0.99f) { + ClearCookieTrail(); + } + } + if (!add_new_cookie) return; + } + + NavCookie cookie; + cookie.SetSegmentParameter(GetSegmentTime()); + cookie.Centre = GetPosition(); + cookie.Flags = 0; + cookie.Length = cookie_length; + cookie.Curvature = GetCurvature(); + cookie.SegmentNumber = GetSegmentInd() & 0x7FFF; + cookie.SegmentNodeInd = GetNodeInd() & 1; + + bVector2 centre(GetPosition().x, GetPosition().z); + + cookie.Left = UMath::Vector2Make(GetLeftPosition().x, GetLeftPosition().z); + cookie.Right = UMath::Vector2Make(GetRightPosition().x, GetRightPosition().z); + + bVector2 centre_to_left = *reinterpret_cast(&cookie.Left) - centre; + bVector2 centre_to_right = *reinterpret_cast(&cookie.Right) - centre; + bVector2 right = centre_to_right - centre_to_left; + bVector2 forward(-right.y, right.x); + + bNormalize(reinterpret_cast(&cookie.Forward), &forward); + + cookie.LeftOffset = bCross(¢re_to_left, reinterpret_cast(&cookie.Forward)); + cookie.RightOffset = bCross(¢re_to_right, reinterpret_cast(&cookie.Forward)); + + if (num_cookies == 0) { + mCurrentCookie = cookie; + } + + if (pCookieTrail->Count() == pCookieTrail->Capacity()) { + nCookieIndex = bMax(nCookieIndex - 1, 0); + } + + pCookieTrail->AddNew(cookie); +} + +void WRoadNav::IncNavPosition(float dist, const UMath::Vector3 &to, float max_lookahead) { + if (!fValid) return; + + float cookie_gap = 3.0f; + if (max_lookahead > 0.0f) { + cookie_gap = UMath::Clamp(max_lookahead * (1.0f / 26.0f), 1.0f, cookie_gap); + } + + if (bCookieTrail && pCookieTrail != nullptr) { + while (dist > 0.0f) { + float incdist = bMin(cookie_gap * 1.1f, dist); + PrivateIncNavPosition(incdist, to); + dist -= incdist; + UpdateCookieTrail(cookie_gap); + } + } else { + PrivateIncNavPosition(dist, to); + } +} + +void WRoadNav::PrivateIncNavPosition(float dist, const UMath::Vector3 &to) { + WRoadNetwork &roadNetwork = WRoadNetwork::Get(); + const WRoadSegment *segment; + float segmentLength; + float distFraction; + float toLength; + + for (;;) { + segment = roadNetwork.GetSegment(GetSegmentInd()); + segmentLength = UMath::Max(0.01f, UMath::Distance(fStartPos, fEndPos)); + distFraction = dist / segmentLength; + toLength = UMath::Length(to); + + if (fSegTime + distFraction <= 1.0f) { + fSegTime = fSegTime + distFraction; + fSegTime = UMath::Min(UMath::Max(fSegTime, 0.0f), 1.0f); + + if (UpdateLaneChange(dist)) { + SetStartEndPos(*segment, fLaneOffset); + SetStartEndControls(*segment); + RebuildSplines(segment); + } + + EvaluateSplines(segment); + return; + } + + { + short newSegInd = GetSegmentInd(); + + UpdateLaneChange((1.0f - fSegTime) * segmentLength); + + char old_node_ind = fNodeInd; + float nextLaneOffset = fLaneOffset; + bool useOldStartPos = false; + const WRoadSegment *newSegment; + + if (fNavType == kTypeDirection && toLength == 0.0f) { + UMath::Vector3 endTo; + segment->GetForwardVec(old_node_ind, endTo); + if (fNodeInd == 0) { + UMath::Negate(endTo); + } + newSegInd = GetNextOffset(endTo, nextLaneOffset, fNodeInd, useOldStartPos); + } else if (fNavType == kTypeDirection || fNavType == kTypePath) { + newSegInd = GetNextOffset(to, nextLaneOffset, fNodeInd, useOldStartPos); + } else if (fNavType == kTypeTraffic) { + newSegInd = GetNextTraffic(to, nextLaneOffset, fNodeInd, useOldStartPos); + } + + if (GetSegmentInd() == newSegInd && fNodeInd == old_node_ind) { + fDeadEnd = 1; + if (fNavType == kTypeTraffic) return; + } + + fSegmentInd = static_cast< short >(newSegInd); + newSegment = roadNetwork.GetSegment(GetSegmentInd()); + + if (useOldStartPos) { + fStartPos = fEndPos; + if (bCookieTrail) { + fLeftStartPos = fLeftEndPos; + fRightStartPos = fRightEndPos; + } + SetBoundPos(*newSegment, nextLaneOffset, false); + fLaneOffset = nextLaneOffset; + fToLaneOffset = nextLaneOffset; + fFromLaneOffset = nextLaneOffset; + } else { + SetStartEndPos(*newSegment, fLaneOffset); + } + + SetStartEndControls(*newSegment); + RebuildSplines(newSegment); + fPosition = fStartPos; + dist = (distFraction - (1.0f - fSegTime)) * segmentLength; + fSegTime = 0.0f; + } + } +} + +void WRoadNav::Reset() { + fValid = false; + ClearCookieTrail(); + DetermineVehicleHalfWidth(); + fPathType = kPathNone; + nPathGoalSegment = 0xFFFF; + fNavType = kTypeNone; + fLaneType = kLaneRacing; + nPathSegments = 0; + bCrossedPathGoal = false; + fPosition = UMath::Vector3Make(0.0f, 0.0f, 0.0f); + fForwardVector = UMath::Vector3Make(0.0f, 0.0f, 0.0f); + fNodeInd = 0; + fSegmentInd = 0; + fSegTime = 0.0f; + fCurvature = 0.0f; + fStartPos = UMath::Vector3Make(0.0f, 0.0f, 0.0f); + fEndPos = UMath::Vector3Make(0.0f, 0.0f, 0.0f); + fStartControl = UMath::Vector3Make(0.0f, 0.0f, 0.0f); + fEndControl = UMath::Vector3Make(0.0f, 0.0f, 0.0f); + fDeadEnd = 0; + fLaneInd = 0; + fFromLaneInd = 0; + fToLaneInd = 0; + fLaneOffset = 0.0f; + fFromLaneOffset = 0.0f; + fToLaneOffset = 0.0f; + fLaneChangeDist = 0.0f; + fLaneChangeInc = 0.0f; + mOutOfBounds = 0.0f; +} + +void WRoadNav::UpdateOccludedPosition(bool occlude_avoidables) { + if (!bCookieTrail) return; + + nRoadOcclusion = 0; + nAvoidableOcclusion = 0; + fOccludingTrailSpeed = 0.0f; + bOccludedFromBehind = false; + + ISimable *simable = pAIVehicle ? pAIVehicle->GetSimable() : nullptr; + IRigidBody *car = simable ? simable->GetRigidBody() : nullptr; + if (!car) return; + + int num_cookies = pCookieTrail->Count(); + if (num_cookies <= 0) return; + + UMath::Vector3 car_forward_3d; + car->GetForwardVector(car_forward_3d); + const UMath::Vector3 &car_velocity_3d = car->GetLinearVelocity(); + UMath::Vector3 car_position_3d; + UMath::ScaleAdd(car_forward_3d, 0.0f, car->GetPosition(), car_position_3d); + + bVector2 car_position(car_position_3d.x, car_position_3d.z); + + bool traffic = (fNavType == kTypeTraffic); + float look_min = 0.0f; + float look_max; + float out_scale; + float out_bounds; + if (traffic) { + look_max = 80.0f; + out_bounds = 6.0f; + } else { + look_max = 200.0f; + out_bounds = 10.0f; + } + out_scale = 4.0f; + + int n = nCookieIndex; + float current_dot = 0.0f; + float look_ahead = look_min; + if (n < num_cookies) { + for (; n < num_cookies; n++) { + const NavCookie &cookie = pCookieTrail->NthOldest(n); + bVector2 cookie_to_car(car_position.x - cookie.Centre.x, car_position.y - cookie.Centre.z); + float dot = bDot(reinterpret_cast< const bVector2 * >(&cookie.Forward), &cookie_to_car); + if (dot >= current_dot) { + nCookieIndex = n; + float offset = bCross(&cookie_to_car, reinterpret_cast< const bVector2 * >(&cookie.Forward)); + float out_of_bounds = bMax(cookie.LeftOffset - offset, offset - cookie.RightOffset); + look_ahead = out_scale * bMax(0.0f, -(out_bounds + out_of_bounds)) + look_ahead; + current_dot = dot; + if (look_max - look_ahead < 0.0f) { + look_ahead = look_max; + } + } + if (-look_ahead > dot) break; + } + } + + const NavCookie ¤t_cookie = pCookieTrail->NthOldest(nCookieIndex); + int next_cookie_index = nCookieIndex + 1; + + if (next_cookie_index < num_cookies) { + const NavCookie &next_cookie = pCookieTrail->NthOldest(next_cookie_index); + bVector2 cookie_to_car(car_position.x - next_cookie.Centre.x, car_position.y - next_cookie.Centre.z); + float sum = current_dot + bAbs(bDot(reinterpret_cast< const bVector2 * >(¤t_cookie.Forward), &cookie_to_car)); + float current_blend; + float next_blend; + if (sum > 1e-4f) { + current_blend = 1.0f - current_dot / sum; + } else { + current_blend = 1.0f; + } + next_blend = 1.0f - current_blend; + + UMath::Lerp(current_cookie.Left, next_cookie.Left, current_blend, mCurrentCookie.Left); + UMath::Lerp(current_cookie.Right, next_cookie.Right, current_blend, mCurrentCookie.Right); + UMath::Lerp(current_cookie.Forward, next_cookie.Forward, current_blend, mCurrentCookie.Forward); + + float forward_len = bLength(reinterpret_cast< bVector2 * >(&mCurrentCookie.Forward)); + if (forward_len > 1e-6f) { + float inv_len = 1.0f / forward_len; + mCurrentCookie.Forward.x *= inv_len; + mCurrentCookie.Forward.y *= inv_len; + } + + mCurrentCookie.Centre.x = UMath::Lerp(current_cookie.Centre.x, next_cookie.Centre.x, current_blend); + mCurrentCookie.Centre.y = UMath::Lerp(current_cookie.Centre.y, next_cookie.Centre.y, current_blend); + mCurrentCookie.Centre.z = UMath::Lerp(current_cookie.Centre.z, next_cookie.Centre.z, current_blend); + + mCurrentCookie.LeftOffset = UMath::Lerp(current_cookie.LeftOffset, next_cookie.LeftOffset, current_blend); + mCurrentCookie.RightOffset = UMath::Lerp(current_cookie.RightOffset, next_cookie.RightOffset, current_blend); + + int next_segment_number = next_cookie.SegmentNumber; + int current_segment_number = current_cookie.SegmentNumber; + + if (current_segment_number == next_segment_number) { + mCurrentCookie.SegmentNumber = next_segment_number; + mCurrentCookie.SegmentNodeInd = current_cookie.SegmentNodeInd; + float current_param = current_cookie.GetSegmentParameter(); + float next_param = next_cookie.GetSegmentParameter(); + float t = UMath::Lerp(current_param, next_param, current_blend); + mCurrentCookie.SetSegmentParameter(t); + } else { + WRoadNetwork &rn = WRoadNetwork::Get(); + const WRoadSegment *next_segment = rn.GetSegment(next_segment_number); + const WRoadSegment *current_segment = rn.GetSegment(current_segment_number); + float next_segment_length = next_segment->GetLength(); + float current_segment_length = current_segment->GetLength(); + float next_dist = next_segment_length * next_cookie.GetSegmentParameter(); + float current_dist = current_segment_length * (1.0f - current_cookie.GetSegmentParameter()); + float distance = current_dist * current_blend + next_dist; + if (distance >= 0.0f) { + distance = distance / next_segment_length; + if (distance < 0.0f) { + distance = 0.0f; + } + mCurrentCookie.SegmentNumber = next_segment_number; + } else { + distance = (distance + current_segment_length) / current_segment_length; + mCurrentCookie.SegmentNumber = current_segment_number; + if (distance < 0.0f) { + distance = 0.0f; + } + } + mCurrentCookie.SegmentNodeInd = (distance >= 0.0f) ? next_cookie.SegmentNodeInd : current_cookie.SegmentNodeInd; + if (1.0f - distance < 0.0f) { + distance = 1.0f; + } + mCurrentCookie.SetSegmentParameter(distance); + } + } else { + mCurrentCookie.Forward = current_cookie.Forward; + mCurrentCookie.LeftOffset = current_cookie.LeftOffset; + mCurrentCookie.RightOffset = current_cookie.RightOffset; + mCurrentCookie.SegmentNumber = current_cookie.SegmentNumber; + mCurrentCookie.SegmentNodeInd = current_cookie.SegmentNodeInd; + UMath::ScaleAdd(current_cookie.Left, current_dot, current_cookie.Forward, mCurrentCookie.Left); + UMath::ScaleAdd(current_cookie.Right, current_dot, current_cookie.Forward, mCurrentCookie.Right); + mCurrentCookie.Centre.x = current_cookie.Centre.x + current_dot * current_cookie.Forward.x; + mCurrentCookie.Centre.z = current_cookie.Centre.z + current_dot * current_cookie.Forward.y; + mCurrentCookie.Centre.y = current_cookie.Centre.y; + } + + bVector2 occ_centre(mCurrentCookie.Centre.x, mCurrentCookie.Centre.z); + bVector2 occ_to_car = car_position - occ_centre; + float cross = bCross(&occ_to_car, reinterpret_cast< const bVector2 * >(&mCurrentCookie.Forward)); + float out = bMax(cross - mCurrentCookie.RightOffset, mCurrentCookie.LeftOffset - cross); + mOutOfBounds = fVehicleHalfWidth + out; + + if (n < num_cookies) { + NavCookie cookies[33]; + bMemSet(&cookies[num_cookies], 0, static_cast< unsigned int >(0x40)); + + int i = nCookieIndex; + for (; i < num_cookies; i++) { + cookies[i] = pCookieTrail->NthOldest(i); + } + + bVector2 car_velocity(car_velocity_3d.x, car_velocity_3d.z); + + if (occlude_avoidables) { + float delta_offset = car_velocity.x * mCurrentCookie.Forward.y - mCurrentCookie.Forward.x * car_velocity.y; + HolePunchAvoidables(&cookies[nCookieIndex], num_cookies - nCookieIndex, cross, delta_offset); + } + + UMath::ScaleAdd(car_velocity_3d, 0.0f, car_position_3d, car_position_3d); + bVector2 car_pos_updated(car_position_3d.x, car_position_3d.z); + car_position = car_pos_updated; + + bInitializeBoundingBox(&vCookieTrailBoxMin, &vCookieTrailBoxMax); + int first_index = nCookieIndex; + int last_visible = nCookieIndex; + int occluding_index = nCookieIndex; + int left_limit_index = nCookieIndex; + int right_limit_index = nCookieIndex; + bVector2 *occluder = nullptr; + bool left_occlusion = false; + bVector2 left_limit; + bVector2 right_limit; + + for (int j = nCookieIndex; j <= num_cookies; j++) { + const NavCookie &cookie = (j < num_cookies) ? cookies[j] : *(reinterpret_cast(&fPosition)); + bVector2 left(cookie.Left.x, cookie.Left.y); + bVector2 right(cookie.Right.x, cookie.Right.y); + bVector2 car_to_left = left - car_position; + bVector2 car_to_right = right - car_position; + + if (n < num_cookies) { + bExpandBoundingBox(&vCookieTrailBoxMin, &vCookieTrailBoxMax, &left); + bExpandBoundingBox(&vCookieTrailBoxMin, &vCookieTrailBoxMax, &right); + } + + if (j == first_index) { + left_limit = car_to_left; + right_limit = car_to_right; + } else { + const UMath::Vector3 ¢re_3d = (j < num_cookies) ? cookies[j].Centre : fPosition; + bool use_nav = (j != num_cookies); + bVector2 centre(centre_3d.x, centre_3d.z); + bVector2 car_to_centre = centre - car_position; + + if (j != num_cookies) { + if (left_limit.x * car_to_left.y - car_to_left.x * left_limit.y < 0.0f) { + left_limit = car_to_left; + left_limit_index = j; + } + if (0.0f < right_limit.x * car_to_right.y - car_to_right.x * right_limit.y) { + right_limit = car_to_right; + right_limit_index = j; + } + } + + if (0.0f <= car_to_centre.x * left_limit.y - left_limit.x * car_to_centre.y) { + last_visible = j; + if (0.0f < car_to_centre.x * right_limit.y - right_limit.x * car_to_centre.y) { + left_occlusion = false; + last_visible = last_visible; + occluder = &right_limit; + occluding_index = right_limit_index; + } + } else { + left_occlusion = true; + occluder = &left_limit; + occluding_index = left_limit_index; + } + } + + if (0.0f < left_limit.x * right_limit.y - right_limit.x * left_limit.y) break; + } + + if (last_visible < num_cookies && occluder != nullptr) { + const NavCookie &apex_cookie = cookies[occluding_index]; + UMath::Vector3 c; + c.x = occluder->x; + c.y = cookies[occluding_index].Centre.y - car_position_3d.y; + c.z = occluder->y; + UMath::Vector3 result; + UMath::Add(car_position_3d, c, result); + + fApexPosition.x = result.x; + fApexPosition.z = result.z; + fApexPosition.y = result.y; + + if ((apex_cookie.Flags & 1) == 0) { + nRoadOcclusion = left_occlusion ? -1 : 1; + } else { + nRoadOcclusion = 0; + } + + if ((apex_cookie.Flags & 1) != 0) { + nAvoidableOcclusion = left_occlusion ? -1 : 1; + } else { + nAvoidableOcclusion = 0; + } + + int behind = 0; + if (nAvoidableOcclusion != 0) { + behind = (apex_cookie.Flags & 2) != 0 ? 1 : 0; + } + bOccludedFromBehind = behind; + + if (IsOccluded()) { + float apex_width = bAbs(apex_cookie.RightOffset - apex_cookie.LeftOffset); + bVector2 apex_2d(fApexPosition.x, fApexPosition.z); + bVector2 apex_to_car = car_position - apex_2d; + + bVector2 to_left(cookies[occluding_index].Left.x - apex_2d.x, cookies[occluding_index].Left.y - apex_2d.y); + bVector2 left_dir = bNormalize(to_left); + + bVector2 to_right(cookies[occluding_index].Right.x - apex_2d.x, cookies[occluding_index].Right.y - apex_2d.y); + bVector2 right_dir = bNormalize(to_right); + + float dist_to_apex = bLength(&apex_to_car); + + bVector2 apex_to_nav = right_dir * dist_to_apex; + bVector2 desired_position = apex_2d + apex_to_nav; + + bVector2 bisect = left_dir + right_dir; + bVector2 perp = bNormalize(bisect); + + bVector2 desired_to_apex = desired_position - apex_2d; + float projection = bDot(desired_to_apex, perp); + projection = bMin(projection, apex_width); + + if (nAvoidableOcclusion != 0) { + float my_trailingspeed = bDot(car_velocity, perp); + float closing_speed = my_trailingspeed + my_trailingspeed; + if (closing_speed > 1e-6f) { + float ratio = UMath::Ramp(my_trailingspeed - fOccludingTrailSpeed, 0.0f, closing_speed); + ratio = ratio + ratio; + } else { + projection = 0.0f; + } + projection = projection * projection; + projection = bMin(projection, apex_width); + } + + perp *= projection; + fOccludedPosition.x = perp.x + fApexPosition.x; + fOccludedPosition.z = perp.y + fApexPosition.z; + fOccludedPosition.y = fApexPosition.y; + } else { + fOccludedPosition = fPosition; + } + } + } + + if (nAvoidableOcclusion == 0) { + bOccludedFromBehind = false; + fOccludingTrailSpeed = 0.0f; + } +} + +float WRoadNav::FindClosestOnSpline(const UMath::Vector3 &point, UMath::Vector3 &intersectPoint, float &timeStep, float incStep, int segInd) { + UMath::Vector3 newIntersectPoint; + UMath::Vector3 pointToIntersect; + UMath::Sub(intersectPoint, point, pointToIntersect); + + WRoadNetwork &roadNetwork = WRoadNetwork::Get(); + const WRoadSegment *segment = roadNetwork.GetSegment(segInd); + + float timeStepInc = incStep / UMath::Max(1.0f, segment->GetLength()); + + UMath::Vector3 segmentForwardVec; + roadNetwork.GetSegmentForwardVector(segment->fIndex, segmentForwardVec); + + if (UMath::Dot(segmentForwardVec, pointToIntersect) > 0.0f) { + timeStepInc = -timeStepInc; + } + + float currDistance = UMath::Distance(intersectPoint, point); + + float newTimeStep = timeStep + timeStepInc; + if (newTimeStep > 0.0f && newTimeStep < 1.0f) { + while (true) { + roadNetwork.GetPointOnSegment(*segment, newTimeStep, newIntersectPoint); + + float distToNewIntersect = UMath::Distance(newIntersectPoint, point); + + if (distToNewIntersect > currDistance) break; + + intersectPoint = newIntersectPoint; + timeStep = newTimeStep; + newTimeStep += timeStepInc; + currDistance = distToNewIntersect; + + if (newTimeStep <= 0.0f || newTimeStep >= 1.0f) break; + } + } + + return currDistance; +} + +int WRoadNav::FindClosestSegmentInd(const UMath::Vector3 &point, const UMath::Vector3 &dir, float dirWeight, UMath::Vector3 &closestPoint, float &time) { + typedef UTL::Std::set SEGMENT_SET; + const WGrid &grid = WGrid::Get(); + WRoadNetwork &roadNetwork = WRoadNetwork::Get(); + short segInd = -1; + bool found = false; + float foundScore = 0.0f; + UTL::FastVector node_indices; + UMath::Vector3 ndir; + SEGMENT_SET segment_set; + UMath::Vector3 intersectPoint; + UMath::Vector3 intersectDir; + UMath::Vector3 intersectRight; + UMath::Vector3 offset; + UMath::Vector3 start; + UMath::Vector3 end; + UMath::Vector3 segdir; + float timeStep; + float rightedge[2]; + float leftedge[2]; + + node_indices.reserve(0x40); + grid.FindNodes(point, 500.0f, node_indices); + + ndir = dir; + UMath::Normalize(ndir); + + for (unsigned int *iter = node_indices.begin(); iter != node_indices.end(); ++iter) { + WGridNode *grid_node = grid.fNodes[*iter]; + if (grid_node != nullptr) { + unsigned int numSegments = grid_node->GetElemTypeCount(WGrid_kRoadSegment); + for (unsigned int i = 0; i < numSegments; ++i) { + short seg = static_cast< short >(grid_node->GetElemType(i, WGrid_kRoadSegment)); + segment_set.insert(seg); + } + } + } + + for (SEGMENT_SET::const_iterator it = segment_set.begin(); it != segment_set.end(); ++it) { + short index = *it; + if (index >= static_cast< int >(roadNetwork.GetNumSegments())) continue; + const WRoadSegment *segment = roadNetwork.GetSegment(index); + + if (bDecisionFilter && segment->IsDecision()) continue; + if (bRaceFilter && !segment->IsInRace()) continue; + if (bTrafficFilter && !segment->IsTrafficAllowed()) continue; + if (bCopFilter && !segment->ShouldCopsConsider()) continue; + + timeStep = roadNetwork.GetSegmentPointIntersect(*segment, point, intersectPoint, true); + + if (segment->IsCurved()) { + roadNetwork.GetPointOnSegment(*segment, timeStep, intersectPoint); + FindClosestOnSpline(point, intersectPoint, timeStep, 1.0f, static_cast< int >(segment->fIndex)); + FindClosestOnSpline(point, intersectPoint, timeStep, 0.25f, static_cast< int >(segment->fIndex)); + } + + VU0_v3sub(point, intersectPoint, intersectDir); + float currDistance = VU0_sqrt(VU0_v3lengthsquare(intersectDir)); + float currScore; + + if (dirWeight > 0.0f) { + roadNetwork.GetPointAndVecOnSegment(*segment, timeStep, intersectPoint, intersectDir); + + offset.x = intersectDir.z; + offset.y = 0.0f; + offset.z = -intersectDir.x; + intersectRight = offset; + UMath::Normalize(intersectRight); + + UMath::Sub(point, intersectPoint, offset); + float sideDistance = UMath::Dot(intersectRight, offset); + + for (int i = 0; i < 2; i++) { + bool inverted = segment->IsProfileInverted(i); + const WRoadProfile *profile = roadNetwork.GetSegmentProfile(*segment, i); + + float relOffset = profile->GetRelativeLaneOffset(inverted ? profile->fNumZones - 1 : 0, inverted); + float laneWidth = profile->GetLaneWidth(inverted ? profile->fNumZones - 1 : 0, inverted); + rightedge[i] = relOffset - laneWidth * 0.5f; + + relOffset = profile->GetRelativeLaneOffset(inverted ? 0 : profile->fNumZones - 1, inverted); + laneWidth = profile->GetLaneWidth(inverted ? 0 : profile->fNumZones - 1, inverted); + leftedge[i] = relOffset + laneWidth * 0.5f; + } + + float right = rightedge[0] + (rightedge[1] - rightedge[0]) * timeStep; + float left = leftedge[0] + (leftedge[1] - leftedge[0]) * timeStep; + + float oldSideDistance; + if (sideDistance > right) { + oldSideDistance = sideDistance - right; + } else if (sideDistance < left) { + oldSideDistance = sideDistance - left; + } else { + oldSideDistance = 0.0f; + } + + UMath::ScaleAdd(intersectRight, oldSideDistance - sideDistance, offset, offset); + currDistance = UMath::Length(offset); + + start = roadNetwork.GetNode(segment->fNodeIndex[0])->fPosition; + end = roadNetwork.GetNode(segment->fNodeIndex[1])->fPosition; + UMath::Sub(end, start, segdir); + UMath::Normalize(segdir); + + float dot = UMath::Dot(ndir, segdir); + if (!segment->IsOneWay()) { + dot = UMath::Abs(dot); + } + + currScore = currDistance + dirWeight * (1.0f - dot) * 100.0f; + } else { + currScore = currDistance; + } + + if (!found || currScore < foundScore) { + found = true; + closestPoint = intersectPoint; + time = timeStep; + foundScore = currScore; + segInd = index; + } + } + + return segInd; +} + +void WRoadNav::InitAtSegment(short segInd, char laneInd, float timeStep) { + WRoadNetwork &roadNetwork = WRoadNetwork::Get(); + const WRoadSegment *segment = roadNetwork.GetSegment(segInd); + UMath::Vector3 vec; + + fDeadEnd = 0; + fValid = true; + fSegmentInd = segInd; + + roadNetwork.GetSegmentForwardVector(segInd, vec); + + if (!roadNetwork.GetSegmentTrafficLaneRightSide(*segment, laneInd) && !(segment->fFlags & 0x40)) { + fNodeInd = 0; + fForwardVector = UMath::Vector3Make(-vec.x, -vec.y, -vec.z); + fSegTime = fabsf(1.0f - timeStep); + } else { + fNodeInd = 1; + fForwardVector = UMath::Vector3Make(vec.x, vec.y, vec.z); + fSegTime = timeStep; + } + + fStartPos = roadNetwork.GetNode(segment->fNodeIndex[fNodeInd == 0])->fPosition; + fEndPos = roadNetwork.GetNode(segment->fNodeIndex[fNodeInd])->fPosition; + + SetLaneInd(laneInd); + SetLaneOffset(0.0f); + + { + SetLaneInd(laneInd); + const WRoadNode *nodePtr[2]; + const WRoadProfile *profile; + float startOffset; + float endOffset; + roadNetwork.GetSegmentNodes(*segment, nodePtr); + + profile = roadNetwork.GetProfile(nodePtr[fNodeInd == 0]->fProfileIndex); + startOffset = profile->GetRawLaneOffset(static_cast< int >(laneInd)); + + profile = roadNetwork.GetProfile(nodePtr[fNodeInd]->fProfileIndex); + endOffset = profile->GetRawLaneOffset(static_cast< int >(laneInd)); + + float laneOffset = (endOffset - startOffset) * fSegTime + startOffset; + SetLaneOffset(laneOffset); + + SetStartEndPos(*segment, startOffset, endOffset); + } + + SetStartEndControls(*segment); + RebuildSplines(segment); + EvaluateSplines(segment); + ResetCookieTrail(); +} + +void WRoadNav::InitAtSegment(short segInd, const UMath::Vector3 &pos, const UMath::Vector3 &dir, bool forceCenterLane) { + WRoadNetwork &rn = WRoadNetwork::Get(); + const WRoadSegment *segment = rn.GetSegment(segInd); + float timeStep = rn.GetSegmentPointIntersect(*segment, pos, fPosition, true); + if (segment->IsCurved()) { + rn.GetPointOnSegment(*segment, timeStep, fPosition); + FindClosestOnSpline(pos, fPosition, timeStep, 1.0f, static_cast(segment->fIndex)); + FindClosestOnSpline(pos, fPosition, timeStep, 0.25f, static_cast(segment->fIndex)); + } + InitAtSegment(segInd, timeStep, pos, dir, forceCenterLane); +} + +void WRoadNav::InitLaneOffset(const UMath::Vector3 &vehicle_pos) { + WRoadNetwork &roadNetwork = WRoadNetwork::Get(); + const WRoadSegment *segment = roadNetwork.GetSegment(GetSegmentInd()); + UMath::Vector3 nav_forward; + UMath::Unit(GetForwardVector(), nav_forward); + const UMath::Vector3 &nav_position = GetPosition(); + UMath::Vector3 nav_to_vehicle = UVector3(vehicle_pos) - nav_position; + UMath::Vector2 nav_forward_2d = UMath::Vector2Make(nav_forward.x, nav_forward.z); + UMath::Vector2 nav_to_vehicle_2d = UMath::Vector2Make(nav_to_vehicle.x, nav_to_vehicle.z); + float current_offset = UMath::Cross(nav_forward_2d, nav_to_vehicle_2d); + SetStartEndPos(*segment, current_offset); + SetLaneOffset(current_offset); + + if (fNavType != kTypeTraffic) return; + + { + bool forward = GetNodeInd() == 1; + int start = forward ? 0 : 1; + int end = forward ? 1 : 0; + bool end_inverted = segment->IsProfileInverted(end); + bool start_inverted = segment->IsProfileInverted(start); + const WRoadProfile *end_profile = roadNetwork.GetSegmentProfile(*segment, end); + const WRoadProfile *start_profile = roadNetwork.GetSegmentProfile(*segment, start); + int num_start_traffic_lanes = start_profile->GetNumTrafficLanes(forward, start_inverted); + int num_end_traffic_lanes = end_profile->GetNumTrafficLanes(forward, end_inverted); + int num_traffic_lanes = bMin(num_start_traffic_lanes, num_end_traffic_lanes); + if (num_traffic_lanes > 0) { + bool foundClosest = false; + float closestDist; + float startOffset; + float endOffset; + float current_offset = GetLaneOffset(); + for (int i = 0; i < num_traffic_lanes; i++) { + int end_lane = start_profile->GetNthTrafficLaneFromCurb(i, forward, start_inverted); + int start_lane = end_profile->GetNthTrafficLaneFromCurb(i, forward, end_inverted); + float parameter = GetSegmentTime(); + float end_offset = start_profile->GetLaneOffset(end_lane, start_inverted); + float start_offset = end_profile->GetLaneOffset(start_lane, end_inverted); + float lane_offset = UMath::Lerp(start_offset, end_offset, parameter); + float distance = bAbs(current_offset - lane_offset); + if (!foundClosest || distance < closestDist) { + SetLaneOffset(lane_offset); + SetLaneInd(static_cast< char >(end_lane)); + foundClosest = true; + startOffset = end_offset; + closestDist = distance; + endOffset = start_offset; + } + } + SetStartEndPos(*segment, endOffset, startOffset); + } + } + + SetStartEndControls(*segment); + RebuildSplines(segment); + EvaluateSplines(segment); +} + +void WRoadNav::InitAtSegment(short segInd, float timeStep, const UMath::Vector3 &pos, const UMath::Vector3 &dir, bool forceCenterLane) { + WRoadNetwork &roadNetwork = WRoadNetwork::Get(); + const WRoadSegment *segment = roadNetwork.GetSegment(segInd); + UMath::Vector3 segmentForwardVector; + + fDeadEnd = 0; + fValid = true; + fSegmentInd = segInd; + + roadNetwork.GetSegmentForwardVector(*segment, segmentForwardVector); + float facingDot = UMath::Dot(dir, segmentForwardVector); + bool backward; + if (bRaceFilter) { + backward = !segment->RaceRouteForward(); + } else { + backward = facingDot < 0.0f; + } + + if (backward) { + fNodeInd = 0; + UMath::Scale(segmentForwardVector, -1.0f, fForwardVector); + fSegTime = UMath::Abs(1.0f - timeStep); + fStartPos = roadNetwork.GetNode(segment->fNodeIndex[1])->fPosition; + fEndPos = roadNetwork.GetNode(segment->fNodeIndex[0])->fPosition; + } else { + fNodeInd = 1; + fForwardVector = segmentForwardVector; + fSegTime = timeStep; + fStartPos = roadNetwork.GetNode(segment->fNodeIndex[0])->fPosition; + fEndPos = roadNetwork.GetNode(segment->fNodeIndex[1])->fPosition; + } + + SetLaneOffset(0.0f); + SetStartEndPos(*segment, 0.0f); + + SetStartEndControls(*segment); + RebuildSplines(segment); + EvaluateSplines(segment); + + if (!forceCenterLane) { + InitLaneOffset(pos); + } + ResetCookieTrail(); +} + +int WRoadNav::FindClosestOnPath(const UMath::Vector3 &position, UMath::Vector3 *found_position, UMath::Vector3 *found_direction, unsigned short *found_segment, float *found_interval) const { + if (pPathSegments != nullptr && nPathSegments > 0) { + float min_dist_sq = 0.0f; + int found_segment_index = -1; + float best_interval = -1.0f; + UMath::Vector3 best_position; + WRoadNetwork &roadNetwork = WRoadNetwork::Get(); + for (int i = 0; i < nPathSegments; i++) { + const WRoadSegment *segment = roadNetwork.GetSegment(pPathSegments[i]); + UMath::Vector3 intersect; + float d = roadNetwork.GetSegmentPointIntersect(*segment, position, intersect, true); + float dist_square = UMath::DistanceSquare(intersect, position); + if (min_dist_sq > 0.0f && dist_square >= min_dist_sq) continue; + best_position = intersect; + found_segment_index = i; + best_interval = d; + min_dist_sq = dist_square; + } + if (found_segment_index > -1) { + unsigned short segment = pPathSegments[found_segment_index]; + const WRoadNode *this_nodes[2]; + const WRoadSegment *this_segment = roadNetwork.GetSegment(segment); + int tail; + unsigned short next_segment; + unsigned short prev_segment; + USpline roadSpline; + UMath::Vector4 tangent; + + roadNetwork.GetSegmentNodes(*this_segment, this_nodes); + if (found_segment_index < nPathSegments - 1) { + next_segment = pPathSegments[found_segment_index + 1]; + } else { + next_segment = static_cast< unsigned short >(-1); + } + if (found_segment_index >= 1) { + prev_segment = pPathSegments[found_segment_index - 1]; + } else { + prev_segment = static_cast< unsigned short >(-1); + } + + roadNetwork.BuildSegmentSpline(*this_segment, roadSpline); + roadSpline.EvaluateTangent(best_interval, tangent); + + if (found_direction != nullptr) { + *found_direction = UMath::Vector4To3(tangent); + if (!this_nodes[0]->IsSegment(next_segment)) { + if (this_nodes[1]->IsSegment(prev_segment)) { + UMath::Negate(*found_direction); + } + } + } + + if (found_segment != nullptr) { + *found_segment = segment; + } + if (found_position != nullptr) { + *found_position = best_position; + } + if (found_interval != nullptr) { + *found_interval = best_interval; + } + return 1; + } + } + return 0; +} + +void WRoadNav::SetControlPos(const WRoadSegment &segment, bool startControl) { + if (!(segment.fFlags & 0x100)) return; + + { + bool forward = fNodeInd != 0; + bool which_end; + if (fNodeInd == 0) { + which_end = startControl; + } else { + which_end = !startControl; + } + WRoadNetwork &road_network = WRoadNetwork::Get(); + const UMath::Vector3 &nodePos = road_network.GetNode(segment.fNodeIndex[which_end])->fPosition; + const UMath::Vector3 &otherPos = road_network.GetNode(segment.fNodeIndex[which_end ^ 1])->fPosition; + UMath::Vector3 handle; + UMath::Vector3 controlPos; + + segment.GetControl(which_end, handle); + controlPos = UVector3(nodePos) + UVector3(handle); + + float original_distance = UMath::Distance(nodePos, otherPos); + original_distance = UMath::Max(original_distance, 0.001f); + float new_distance = UMath::Distance(fStartPos, fEndPos); + new_distance = UMath::Max(new_distance, 0.001f); + float scale = new_distance / original_distance; + + const UMath::Vector3 &nodeRef = startControl ? fEndPos : fStartPos; + UMath::Vector3 &controlRef = startControl ? fEndControl : fStartControl; + UMath::ScaleAdd(handle, scale, nodeRef, controlRef); + + if (bCookieTrail) { + float left_scale = UMath::Distance(fLeftStartPos, fLeftEndPos); + left_scale = UMath::Max(left_scale, 0.001f); + float right_scale = UMath::Distance(fRightStartPos, fRightEndPos); + right_scale = UMath::Max(right_scale, 0.001f); + + const UMath::Vector3 &leftNodeRef = startControl ? fLeftEndPos : fLeftStartPos; + UMath::Vector3 &leftControlRef = startControl ? fLeftEndControl : fLeftStartControl; + UMath::ScaleAdd(handle, left_scale / original_distance, leftNodeRef, leftControlRef); + + const UMath::Vector3 &rightNodeRef = startControl ? fRightEndPos : fRightStartPos; + UMath::Vector3 &rightControlRef = startControl ? fRightEndControl : fRightStartControl; + UMath::ScaleAdd(handle, right_scale / original_distance, rightNodeRef, rightControlRef); + } + } +} + +void WRoadNav::SetBoundPos(const WRoadSegment &segment, float offset, bool start) { + bool forward = fNodeInd != 0; + bool which_end; + if (fNodeInd == 0) { + which_end = start; + } else { + which_end = !start; + } + WRoadNetwork &road_network = WRoadNetwork::Get(); + const WRoadNode *node = road_network.GetNode(segment.fNodeIndex[which_end]); + const UMath::Vector3 &nodePos = node->fPosition; + UMath::Vector3 rightVec; + float sign = forward ? 1.0f : -1.0f; + + segment.GetRightVec(which_end, rightVec); + + if (bCookieTrail) { + float vehicle_half_width = fVehicleHalfWidth; + float left_offset = offset + vehicle_half_width * 0.5f; + float right_offset = offset - vehicle_half_width * 0.5f; + int nav_type = GetNavType(); + + if (nav_type != kTypeTraffic) { + left_offset = offset + 2.0f; + right_offset = offset - 2.0f; + + const WRoadProfile *profile = road_network.GetSegmentProfile(segment, which_end); + int num_lanes = profile->fNumZones; + + { + int closest_drivable = -1; + float closest_offset = 0.0f; + bool inverted = !forward != segment.IsProfileInverted(fNodeInd); + int middle_lane = profile->GetMiddleZone(inverted); + + { + int lane_index; + for (lane_index = 0; lane_index < num_lanes; lane_index++) { + int lane_type = profile->GetLaneType(lane_index, inverted); + if (IsDrivable(lane_type)) { + float lane_offset = profile->GetLaneOffset(lane_index, inverted); + float lane_distance = bAbs(offset - lane_offset); + if (closest_drivable < 0 || lane_distance < closest_offset) { + closest_drivable = lane_index; + closest_offset = lane_distance; + } + } + } + } + + { + int left_lane = closest_drivable; + int right_lane = closest_drivable; + float left_lane_offset; + float right_lane_offset; + float safety_margin; + float how_unsafe; + + while (left_lane > 0) { + int prev = left_lane - 1; + int lt = profile->GetLaneType(prev, inverted); + if (!IsDrivable(lt)) break; + left_lane = prev; + } + + while (right_lane < num_lanes - 1) { + int next = right_lane + 1; + int lt = profile->GetLaneType(next, inverted); + if (!IsDrivable(lt)) break; + right_lane = next; + } + + left_lane_offset = profile->GetRelativeLaneOffset(left_lane, inverted); + right_lane_offset = profile->GetRelativeLaneOffset(right_lane, inverted); + + float left_width = profile->GetLaneWidth(left_lane, inverted); + float right_width = profile->GetLaneWidth(right_lane, inverted); + + safety_margin = bMax(vehicle_half_width + vehicle_half_width, 0.0f); + + how_unsafe = (vehicle_half_width + vehicle_half_width) - (right_lane_offset - left_lane_offset); + if (how_unsafe < 0.0f) { + how_unsafe = 0.0f; + } + + left_offset = left_lane_offset - how_unsafe * 0.5f; + right_offset = right_lane_offset + how_unsafe * 0.5f; + } + + offset = bClamp(offset, left_offset, right_offset); + } + } + + UMath::Vector3 &leftRef = start ? fLeftStartPos : fLeftEndPos; + UMath::Vector3 &rightRef = start ? fRightStartPos : fRightEndPos; + + UMath::ScaleAdd(rightVec, sign * left_offset, nodePos, leftRef); + UMath::ScaleAdd(rightVec, sign * right_offset, nodePos, rightRef); + } + + UMath::Vector3 &posRef = start ? fStartPos : fEndPos; + UMath::ScaleAdd(rightVec, sign * offset, nodePos, posRef); +} + +bool WRoadNav::ChangeDragDecision(int left_right) { + WRoadNetwork &rn = WRoadNetwork::Get(); + int segment_number = GetSegmentInd(); + const WRoadSegment *segment = rn.GetSegment(segment_number); + + if (!segment->IsDecision()) return false; + + { + UMath::Vector2 ray; + const WRoadNode *nodes[2]; + rn.GetSegmentNodes(*segment, nodes); + int from_which_node = GetNodeInd() ^ 1; + const WRoadNode *from_node = nodes[from_which_node]; + int new_which_node = 0; + float best = 0.7f; + const WRoadSegment *new_segment = nullptr; + float sign = -1.0f; + int num_segments; + + segment->GetForwardVec(from_which_node, ray); + if (from_which_node == 1) { + UMath::Scale(ray, -1.0f); + } + UMath::Normalize(ray); + + if (left_right < 0) { + sign = 1.0f; + } + + num_segments = from_node->fNumSegments; + for (int i = 0; i < num_segments; i++) { + unsigned short departing_segment_number = from_node->fSegmentIndex[i]; + if (departing_segment_number != segment_number) { + const WRoadSegment *departing_segment = rn.GetSegment(departing_segment_number); + if (departing_segment->IsDecision() && departing_segment->IsInRace()) { + int to_which_node = from_node->fIndex != static_cast< int >(departing_segment->fNodeIndex[0]); + + UMath::Vector2 departing_ray; + departing_segment->GetForwardVec(to_which_node, departing_ray); + if (to_which_node) { + UMath::Scale(departing_ray, -1.0f); + } + UMath::Normalize(departing_ray); + + float cross_val = sign * Cross(ray, departing_ray); + if (cross_val >= 0.0f) { + float dot = UMath::Dot(ray, departing_ray); + if (best < dot) { + new_segment = departing_segment; + best = dot; + new_which_node = to_which_node; + } + } + } + } + } + + if (new_segment != nullptr) { + int new_segment_index = new_segment->fIndex; + bool inverted = new_segment->IsProfileInverted(new_which_node) != static_cast< bool >(new_which_node); + const WRoadProfile *new_profile = rn.GetSegmentProfile(*new_segment, new_which_node); + int num_lanes = new_profile->fNumZones; + int end; + int start; + int new_lane; + float new_lane_offset; + UMath::Vector3 new_spline_point; + + if (left_right >= 0) { + end = num_lanes; + } else { + end = -1; + } + + if (left_right >= 0) { + start = 0; + } else { + start = num_lanes - 1; + } + + new_lane = new_profile->GetMiddleZone(inverted); + + for (int lane = start; lane != end; lane += left_right) { + int actual_lane = lane; + if (!inverted) { + actual_lane = (new_profile->fNumZones - lane) - 1; + } + int lane_type = new_profile->GetLaneType(actual_lane, inverted); + if (IsDrivable(lane_type)) { + new_lane = lane; + break; + } + } + + new_lane_offset = new_profile->GetRelativeLaneOffset(new_lane, inverted); + SetLaneOffset(new_lane_offset); + + fNodeInd = new_which_node; + fSegmentInd = new_segment_index; + SetControlPos(*new_segment, false); + SetBoundPos(*new_segment, new_lane_offset, false); + RebuildSplines(new_segment); + FindClosestOnSpline(fPosition, new_spline_point, fSegTime, 1.0f, new_segment_index); + EvaluateSplines(new_segment); + return true; + } + } + return false; +} + +void WRoadNav::ChangeDragLanes(int left_right) { + WRoadNetwork &rn = WRoadNetwork::Get(); + int segment_number = GetSegmentInd(); + const WRoadSegment *segment = rn.GetSegment(segment_number); + char node_ind = GetNodeInd(); + const WRoadProfile *profile = rn.GetSegmentProfile(*segment, node_ind); + + bool backward; + if (node_ind != 0) { + backward = segment->IsStartInverted(); + } else { + backward = segment->IsEndInverted(); + } + + float current_offset = fLaneOffset; + + if (left_right == 0) { + AIVehicle *vehicle = pAIVehicle; + ISimable *sim = nullptr; + if (vehicle != nullptr) { + sim = vehicle->GetSimable(); + } + IRigidBody *rb = nullptr; + if (sim != nullptr) { + rb = sim->GetRigidBody(); + } + if (rb != nullptr) { + WRoadNav temp; + temp.bRaceFilter = WRoadNetwork::Get().IsRaceFilterValid(); + const UMath::Vector3 &rbPos = rb->GetPosition(); + temp.InitAtPoint(rbPos, fForwardVector, false, 1.0f); + if (temp.fValid) { + current_offset = temp.fLaneOffset; + } + } + } + + bool inverted = (backward != 0) == (node_ind != 0); + inverted = !inverted; + + // Find closest selectable lane + int current_lane; + if (inverted) { + current_lane = profile->GetMiddleZone(inverted); + } else { + current_lane = profile->fNumZones - profile->GetMiddleZone(inverted); + } + + // Find initial selectable lane + while (true) { + int actual_lane = current_lane; + if (inverted) { + actual_lane = (profile->fNumZones - current_lane) - 1; + } + int lane_type = profile->GetLaneType(actual_lane, inverted); + if (IsSelectable(lane_type)) break; + current_lane++; + } + + // Get offset for initial lane + int lane_for_offset = current_lane; + int middle; + if (inverted) { + middle = profile->fNumZones - profile->GetMiddleZone(inverted); + lane_for_offset = (profile->fNumZones - current_lane) - 1; + } else { + middle = profile->GetMiddleZone(inverted); + } + float initial_offset = static_cast< float >(profile->GetLaneOffset(lane_for_offset, inverted)) * 0.012208521f; + if (static_cast< int >(current_lane) < static_cast< int >(middle)) { + initial_offset = -initial_offset; + } + + float offset_difference = bAbs(initial_offset - current_offset); + int num_lanes = profile->fNumZones; + + // Find closest selectable lane to current offset + for (int i = 0; i < num_lanes; i++) { + int actual_i = i; + if (inverted) { + actual_i = (profile->fNumZones - i) - 1; + } + int lane_type = profile->GetLaneType(actual_i, inverted); + if (IsSelectable(lane_type)) { + int middle2; + int lane_idx; + if (inverted) { + middle2 = profile->fNumZones - profile->GetMiddleZone(inverted); + lane_idx = (profile->fNumZones - i) - 1; + } else { + middle2 = profile->GetMiddleZone(inverted); + lane_idx = i; + } + float this_offset = static_cast< float >(profile->GetLaneOffset(lane_idx, inverted)) * 0.012208521f; + if (static_cast< int >(i) < static_cast< int >(middle2)) { + this_offset = -this_offset; + } + float diff = this_offset - current_offset; + float clamped = bClamp(diff, -offset_difference, offset_difference); + if (diff == clamped) { + offset_difference = bAbs(diff); + current_lane = i; + } + } + } + + int target_lane = current_lane; + if (left_right != 0) { + int test_lane = current_lane; + while (true) { + test_lane += left_right; + target_lane = current_lane; + if (test_lane >= num_lanes || test_lane < 0) break; + + int actual_test = test_lane; + if (inverted) { + actual_test = (profile->fNumZones - test_lane) - 1; + } + int lt = profile->GetLaneType(actual_test, inverted); + target_lane = current_lane; + if (!IsDrivable(lt) || IsSelectable(lt)) { + target_lane = test_lane; + break; + } + } + + if (target_lane == current_lane) { + if (ChangeDragDecision(left_right)) return; + } + } + + // Compute final offset + int final_lane; + int final_middle; + if (inverted) { + final_middle = profile->fNumZones - profile->GetMiddleZone(inverted); + final_lane = (profile->fNumZones - target_lane) - 1; + } else { + final_middle = profile->GetMiddleZone(inverted); + final_lane = target_lane; + } + float final_offset = static_cast< float >(profile->GetLaneOffset(final_lane, inverted)) * 0.012208521f; + if (static_cast< int >(target_lane) < static_cast< int >(final_middle)) { + final_offset = -final_offset; + } + ChangeLanes(final_offset, 0.0f); +} + +bool WRoadNav::MakeShortcutDecision(int shortcut_number, unsigned int *cached, unsigned int *allowed) { + if (shortcut_number == 0xff) return true; + + if (GetPathType() == kPathPlayer) { + return shortcut_number == GetShortcutNumber(); + } + if (GetPathType() != kPathRacer) return true; + + int mask = 1 << shortcut_number; + if (cached != nullptr && (mask & *cached) != 0) { + bool shortcut_allowed = (mask & *allowed) != 0; + return shortcut_allowed; + } + GRaceParameters *race_parameters = GRaceStatus::Get().GetRaceParameters(); + if (race_parameters != nullptr) { + AIVehicle *vehicle = GetVehicle(); + float skill; + if (vehicle != nullptr) { + skill = vehicle->GetSkill(); + } else { + skill = 0.0f; + } + GMarker *marker = race_parameters->GetShortcut(shortcut_number); + float min = marker->ShortcutMinChance(0); + float max = marker->ShortcutMaxChance(0); + if (min > 1.0f) { + min = min * 0.01f; + } + if (max > 1.0f) { + max = max * 0.01f; + } + float chance = skill * (max - min) + min; + bool shortcut_allowed = bRandom(1.0f) < chance; + if (shortcut_allowed && allowed != nullptr) { + *allowed = *allowed | mask; + } + if (cached != nullptr) { + *cached = *cached | mask; + } + return shortcut_allowed; + } + return true; +} + +short WRoadNav::GetNextTraffic(const UMath::Vector3 &toVec, float &nextLaneOffset, char &nodeInd, bool &useOldStartPos) { + struct Candidate { + int Lane; + int WhichNode; + int SegmentNumber; + bool LastResort; + }; + + WRoadNetwork &roadNetwork = WRoadNetwork::Get(); + int which_node = static_cast< int >(GetNodeInd()); + short oldSegInd = GetSegmentInd(); + const WRoadSegment *segment = roadNetwork.GetSegment(oldSegInd); + const WRoadProfile *profile = roadNetwork.GetSegmentProfile(*segment, which_node); + bool forward = (which_node == 1); + bool inverted = segment->IsProfileInverted(which_node); + char newLaneInd = GetLaneInd(); + int nth_lane = 0; + int num_traffic_lanes = 0; + + int num_lanes = profile->GetNumLanes(forward, inverted); + for (int i = 0; i < num_lanes; i++) { + int real_lane = profile->GetNthLane(i, forward, inverted); + if (profile->GetLaneType(real_lane, inverted) == 1) { + if (newLaneInd == real_lane) { + nth_lane = num_traffic_lanes; + } + num_traffic_lanes++; + } + } + + const WRoadNode *node = roadNetwork.GetNode(segment->fNodeIndex[which_node]); + short newSegInd = GetSegmentInd(); + int current_lane = static_cast< int >(GetLaneInd()); + const WRoadSegment *checkSegment = GetAttachedDirectionalSegment(node, oldSegInd); + + if (node->fNumSegments < 2) { + newSegInd = segment->fIndex; + } else if (checkSegment != nullptr) { + newSegInd = checkSegment->fIndex; + bool new_forward = (node == roadNetwork.GetNode(checkSegment->fNodeIndex[0])); + nodeInd = new_forward; + bool new_inverted = checkSegment->IsProfileInverted(static_cast< int >(nodeInd)); + const WRoadProfile *new_profile = roadNetwork.GetSegmentProfile(*checkSegment, static_cast< int >(nodeInd)); + current_lane = new_profile->GetNthTrafficLane(nth_lane, new_forward, new_inverted); + } else { + float lenSq = UMath::LengthSquare(toVec); + if (lenSq > 0.01f) { + float bestDot = -1.0f; + float check_segment_number = bestDot; + for (int onSeg = 0; onSeg < static_cast< int >(node->fNumSegments); onSeg++) { + const WRoadSegment *intersectionSegment = roadNetwork.GetSegment(node->fSegmentIndex[onSeg]); + if (intersectionSegment->fIndex == GetSegmentInd()) continue; + if (!intersectionSegment->IsTrafficAllowed()) continue; + + const WRoadNode *oppNode = roadNetwork.GetSegmentOppNode(*intersectionSegment, node); + const WRoadSegment *checkSegment = GetAttachedDirectionalSegment(oppNode, -1); + if (checkSegment == nullptr) continue; + if (!checkSegment->IsTrafficAllowed()) continue; + + bool reverse = (oppNode != roadNetwork.GetNode(checkSegment->fNodeIndex[0])); + UMath::Vector3 vec; + roadNetwork.GetSegmentForwardVector(*checkSegment, vec); + if (reverse) { + vec = UMath::Vector3Make(-vec.x, -vec.y, -vec.z); + } + UMath::Unit(vec, vec); + float dot = UMath::Dot(vec, toVec); + + if (dot >= bestDot) { + newSegInd = node->fSegmentIndex[onSeg]; + const WRoadSegment *newSegment = roadNetwork.GetSegment(newSegInd); + nodeInd = (node == roadNetwork.GetNode(newSegment->fNodeIndex[0])); + check_segment_number = static_cast< float >(checkSegment->fIndex); + bestDot = dot; + } + } + + if (check_segment_number != -1.0f) { + const WRoadSegment *newSegment = roadNetwork.GetSegment(newSegInd); + bool new_forward = (nodeInd == 1); + const WRoadProfile *new_profile = roadNetwork.GetSegmentProfile(*newSegment, static_cast< int >(nodeInd)); + bool new_inverted = newSegment->IsProfileInverted(static_cast< int >(nodeInd)); + + int rightmost = roadNetwork.GetRightMostTrafficEntrance( + newSegment->fNodeIndex[static_cast< int >(nodeInd)], + static_cast< int >(check_segment_number)); + + if (newSegInd == rightmost) { + int nth_from_curb = num_traffic_lanes - nth_lane - 1; + current_lane = new_profile->GetNthTrafficLaneFromCurb(nth_from_curb, new_forward, new_inverted); + } else { + current_lane = new_profile->GetNthTrafficLane(nth_lane, new_forward, new_inverted); + } + } + } else { + int num_candidates = 0; + Candidate candidates[7]; + + for (int i = 0; i < static_cast< int >(node->fNumSegments); i++) { + int new_segment_number = node->fSegmentIndex[i]; + if (new_segment_number == GetSegmentInd()) continue; + + const WRoadSegment *decision_segment = roadNetwork.GetSegment(new_segment_number); + if (!decision_segment->IsTrafficAllowed()) continue; + + const WRoadNode *nodePtr[2]; + roadNetwork.GetSegmentNodes(*decision_segment, nodePtr); + const WRoadNode *oppNode = nodePtr[0]; + if (node == nodePtr[0]) { + oppNode = nodePtr[1]; + } + + const WRoadSegment *checkSegment = GetAttachedDirectionalSegment(oppNode, -1); + if (checkSegment == nullptr) continue; + if (!checkSegment->IsTrafficAllowed()) continue; + if (oppNode != roadNetwork.GetNode(checkSegment->fNodeIndex[0]) && checkSegment->IsOneWay()) continue; + + int new_which_node = (node->fIndex != decision_segment->fNodeIndex[1]); + bool new_forward = new_which_node; + bool new_inverted = decision_segment->IsProfileInverted(new_which_node); + const WRoadProfile *new_profile = roadNetwork.GetSegmentProfile(*decision_segment, new_which_node); + int num_traffic_lanes = new_profile->GetNumTrafficLanes(new_forward, new_inverted); + + if (num_traffic_lanes == 0) continue; + + int rightmost_traffic_entrance = roadNetwork.GetRightMostTrafficEntrance( + oppNode->fIndex, checkSegment->fIndex); + + if (new_segment_number == rightmost_traffic_entrance) { + int nth_from_curb = num_traffic_lanes - nth_lane - 1; + candidates[num_candidates].LastResort = (nth_from_curb > 0); + candidates[num_candidates].Lane = new_profile->GetNthTrafficLaneFromCurb(nth_from_curb, new_forward, new_inverted); + } else { + candidates[num_candidates].LastResort = false; + candidates[num_candidates].Lane = new_profile->GetNthTrafficLane(nth_lane, new_forward, new_inverted); + } + candidates[num_candidates].WhichNode = new_which_node; + candidates[num_candidates].SegmentNumber = new_segment_number; + num_candidates++; + } + + if (num_candidates > 0) { + int selection = 0; + if (num_candidates > 1) { + selection = bRandom(num_candidates); + if (candidates[selection].LastResort) { + selection = (selection + 1) % num_candidates; + } + } + nodeInd = static_cast< char >(candidates[selection].WhichNode); + newSegInd = static_cast< short >(candidates[selection].SegmentNumber); + current_lane = candidates[selection].Lane; + } + } + } + + if (newSegInd != GetSegmentInd()) { + if (current_lane < 0) { + current_lane = 0; + } + SetLaneInd(static_cast< char >(current_lane)); + fToLaneInd = static_cast< char >(current_lane); + + const WRoadNode *oppNode = roadNetwork.GetSegmentOppNode(*roadNetwork.GetSegment(newSegInd), node); + nextLaneOffset = roadNetwork.GetProfile(oppNode->fProfileIndex)->GetLaneOffset(current_lane, false); + } + + useOldStartPos = true; + return newSegInd; +} diff --git a/src/Speed/Indep/Src/World/Common/WTrigger.cpp b/src/Speed/Indep/Src/World/Common/WTrigger.cpp index e69de29bb..3bb32cadc 100644 --- a/src/Speed/Indep/Src/World/Common/WTrigger.cpp +++ b/src/Speed/Indep/Src/World/Common/WTrigger.cpp @@ -0,0 +1,476 @@ +#include "Speed/Indep/Src/World/WTrigger.h" + +#include "Speed/Indep/Libs/Support/Utility/FastMem.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRigidBody.h" +#include "Speed/Indep/Src/Main/Event.h" +#include "Speed/Indep/Src/Main/EventSequencer.h" +#include "Speed/Indep/Src/World/WCollisionAssets.h" +#include "Speed/Indep/Src/World/WGridManagedDynamicElem.h" +#include "Speed/Indep/Src/World/Common/WGrid.h" +#include "Speed/Indep/Src/Physics/Dynamics/Collision.h" +#include "Speed/Indep/bWare/Inc/bChunk.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +extern EventDynamicData gEventDynamicData; + +WTrigger::WTrigger() { + bMemSet(this, 0, sizeof(WTrigger)); +} + +WTrigger::WTrigger(const UMath::Matrix4 &mat, const UMath::Vector3 &dimensions, CARP::EventList *eventList, unsigned int flags) { + memcpy(this, &mat, sizeof(UMath::Matrix4)); + reinterpret_cast(this)[0x10] = (reinterpret_cast(this)[0x10] & 0xF0) | 1; + fHeight = dimensions.y + dimensions.y; + fEvents = eventList; + *reinterpret_cast(reinterpret_cast(this) + 0x10) = + (*reinterpret_cast(reinterpret_cast(this) + 0x10) & 0xFF000000) | (flags & 0x00FFFFFF); + reinterpret_cast(this)[0x10] &= 0x0F; + fPosRadius.x = mat[3][0]; + fPosRadius.y = mat[3][1]; + fPosRadius.z = mat[3][2]; + fPosRadius.w = UMath::Length(dimensions); + fMatRow0Width.w = dimensions.x + dimensions.x; + fMatRow2Length.w = dimensions.z + dimensions.z; +} + +bool WTrigger::HasEvent(unsigned int eventID, const CARP::EventStaticData** foundEvent) const { + if (fEvents != nullptr) { + return EventManager::ListHasEvent(fEvents, eventID, foundEvent); + } + return false; +} + +bool WTrigger::TestDirection(const UMath::Vector3& vec) const { + return UMath::Dot(UMath::Vector4To3(fMatRow2Length), vec) >= 0.0f; +} + +void WTrigger::FireEvents(HSIMABLE__ *hSimable) { + if (fEvents != nullptr) { + bMemSet(&gEventDynamicData, 0, sizeof(EventDynamicData)); + gEventDynamicData.fhSimable = reinterpret_cast(hSimable); + gEventDynamicData.fPosition = fPosRadius; + gEventDynamicData.fPosition.w = 1.0f; + gEventDynamicData.fTrigger = this; + gEventDynamicData.fTriggerStimulus = WTriggerManager::Get().GetCurrentStimulus(); + EventManager::FireEventList(fEvents, false); + } + unsigned int hi = static_cast(reinterpret_cast(this)[0x11]) << 16; + unsigned int mid = static_cast(reinterpret_cast(this)[0x12]) << 8; + unsigned int lo = static_cast(reinterpret_cast(this)[0x13]); + unsigned int flags = lo | (mid | hi); + if (flags & 2) { + *reinterpret_cast(reinterpret_cast(this) + 0x10) = + (*reinterpret_cast(reinterpret_cast(this) + 0x10) & 0xFF000000u) | (flags & 0x00FFFFFEu); + } +} + +int LoaderTrigger(bChunk* chunk) { + return 0; +} + +int UnloaderTrigger(bChunk* chunk) { + return 0; +} + +WTriggerManager *WTriggerManager::fgTriggerManager; + +WTriggerManager::WTriggerManager() + : fSilencableEnabled(true) // + , fIterCount(0) // + , fgFireOnExitList(nullptr) { + fgFireOnExitList = new FireOnExitList(); +} + +WTriggerManager::~WTriggerManager() { + if (fgFireOnExitList != nullptr) { + delete fgFireOnExitList; + } +} + +void WTriggerManager::Init() { + fgTriggerManager = new WTriggerManager(); + Restart(); + for (unsigned int i = 0; i < WCollisionAssets::Get().NumTriggers(); i++) { + WTrigger &trig = WCollisionAssets::Get().Trigger(i); + if ((static_cast(reinterpret_cast(&trig)[0x12]) << 8) & 0x200) { + WTrigger &trig2 = WCollisionAssets::Get().Trigger(i); + unsigned int hi = static_cast(reinterpret_cast(&trig2)[0x11]) << 16; + unsigned int mid = static_cast(reinterpret_cast(&trig2)[0x12]) << 8; + unsigned int lo = static_cast(reinterpret_cast(&trig2)[0x13]); + unsigned int flags = lo | (mid | hi); + *reinterpret_cast(reinterpret_cast(&trig2) + 0x10) = + (*reinterpret_cast(reinterpret_cast(&trig2) + 0x10) & 0xFF000000) | (flags & ~0x400); + } + } +} + +void WTriggerManager::Restart() { + fgTriggerManager->fgFireOnExitList->clear(); + Get().EnableSilencables(); +} + +void WTriggerManager::ClearAllFireOnExit() { + if (fgFireOnExitList != nullptr) { + fgFireOnExitList->clear(); + } +} + +void WTriggerManager::DeleteRefs(const WTrigger *trig) { + std::set::const_iterator iter = fgFireOnExitList->begin(); + while (iter != fgFireOnExitList->end()) { + const FireOnExitRec &rec = *iter; + if (trig == &rec.mTrigger) { + std::set::const_iterator newlocation = iter; + ++newlocation; + fgFireOnExitList->erase(iter); + iter = newlocation; + if (iter == fgFireOnExitList->end()) { + return; + } + } + ++iter; + } +} + +void WTriggerManager::SubmitForFire(WTrigger &trig, HSIMABLE__ *hSimable) { + if ((reinterpret_cast(&trig)[0x12] >> 7) != 0) { + ISimable *iSimable = ISimable::FindInstance(hSimable); + if (iSimable != nullptr) { + FireOnExitRec rec(trig, hSimable); + fgFireOnExitList->insert(rec); + } else { + trig.FireEvents(hSimable); + } + } + if ((static_cast(reinterpret_cast(&trig)[0x11]) << 16 & 0x40000) != 0) { + trig.FireEvents(hSimable); + } + unsigned int b11 = reinterpret_cast(&trig)[0x11] << 16; + if (((reinterpret_cast(&trig)[0x12] << 8 | b11) & 0x48000) == 0) { + trig.FireEvents(hSimable); + } +} + +void WTriggerManager::Update(float dT) { + fProcessingStimulus = 1; + IRigidBody * const *enditer = IRigidBody::GetList().end(); + for (IRigidBody * const *iter = IRigidBody::GetList().begin(); iter != enditer; ++iter) { + IRigidBody *rigidBody = *iter; + if (rigidBody->IsSimple()) { + ProcessSRB(rigidBody, dT); + } else { + ProcessRB(rigidBody, dT); + } + } + fProcessingStimulus = 2; + std::set::const_iterator iter = fgFireOnExitList->begin(); + while (iter != fgFireOnExitList->end()) { + const FireOnExitRec &rec = *iter; + ISimable *iSimable = ISimable::FindInstance(rec.mhSimable); + if (iSimable != nullptr) { + IRigidBody *iRigidBody = iSimable->GetRigidBody(); + if (iRigidBody != nullptr) { + bool result; + if (iRigidBody->IsSimple()) { + result = CheckCollideSRB(iRigidBody, &rec.mTrigger, dT); + } else { + result = CheckCollideRB(iRigidBody, &rec.mTrigger, dT); + } + if (result == true) { + ++iter; + continue; + } + rec.mTrigger.FireEvents(rec.mhSimable); + } + } + std::set::const_iterator newlocation = iter; + ++newlocation; + fgFireOnExitList->erase(iter); + iter = newlocation; + if (iter == fgFireOnExitList->end()) { + return; + } + } +} + +WTrigger::~WTrigger() { + if (!((static_cast(reinterpret_cast(this)[0x12]) << 8) & 0x100) && fEvents != nullptr) { + ::operator delete(fEvents); + } + if (WTriggerManager::Exists()) { + WTriggerManager::Get().DeleteRefs(this); + } +} + +void WTrigger::UpdateBox(const UMath::Matrix4& mat, const UMath::Vector3& dimension) { + UMath::Vector4 oldPosRad = fPosRadius; + unsigned int b11 = reinterpret_cast(this)[0x11] << 16; + unsigned int flags = reinterpret_cast(this)[0x13] + | (reinterpret_cast(this)[0x12] << 8 + | b11); + CARP::EventList* eventList = fEvents; + + memcpy(this, &mat, sizeof(UMath::Matrix4)); + + reinterpret_cast(this)[0x10] = (reinterpret_cast(this)[0x10] & 0xF0) | 1; + + if (mat.v1.y < 0.0f) { + flags |= 0x1000; + } else { + flags &= ~0x1000; + } + + fHeight = dimension.y + dimension.y; + fEvents = eventList; + *reinterpret_cast(reinterpret_cast(this) + 0x10) = + (*reinterpret_cast(reinterpret_cast(this) + 0x10) & 0xFF000000) | (flags & 0x00FFFFFF); + reinterpret_cast(this)[0x10] &= 0x0F; + + fPosRadius.x = mat[3][0]; + fPosRadius.y = mat[3][1]; + fPosRadius.z = mat[3][2]; + fPosRadius.w = UMath::Length(dimension); + + fMatRow0Width.w = dimension.x + dimension.x; + fMatRow2Length.w = dimension.z + dimension.z; + + WGridManagedDynamicElem::AddElem(&oldPosRad, &fPosRadius, WGrid_kTrigger, reinterpret_cast(this)); +} + +bool WTrigger::UpdatePos(const UMath::Vector3 &newPos, unsigned int triggerInd) { + UMath::Vector4 oldPosRad = fPosRadius; + fPosRadius.x = newPos.x; + fPosRadius.y = newPos.y; + fPosRadius.z = newPos.z; + WGridManagedDynamicElem::AddElem(&oldPosRad, &fPosRadius, WGrid_kTrigger, triggerInd); + return true; +} + +void WTriggerManager::ProcessRB(IRigidBody *rBody, float dT) { + fIterCount++; + unsigned int activateFlag = rBody->GetTriggerFlags(); + if (activateFlag == 0) { + return; + } + float radius = rBody->GetRadius(); + UTL::FastVector nodeInds; + nodeInds.reserve(0x40); + const WGrid &grid = WGrid::Get(); + grid.FindNodes(rBody->GetPosition(), radius, nodeInds); + for (unsigned int *iter = nodeInds.begin(); iter != nodeInds.end(); ++iter) { + WGridNode *gridNode = grid.fNodes[*iter]; + if (gridNode != nullptr) { + WGridNode::iterator eIter(gridNode, WGrid_kTrigger); + while (const unsigned int *indPtr = eIter.GetIndPtr()) { + unsigned int ind = *indPtr; + WTrigger &trig = WCollisionAssets::Get().Trigger(ind); + if (trig.fIterStamp != fIterCount) { + trig.fIterStamp = fIterCount; + if (trig.IsEnabled(fSilencableEnabled) && + (((static_cast(reinterpret_cast(&trig)[0x11]) << 16) + | (static_cast(reinterpret_cast(&trig)[0x12]) << 8) + | static_cast(reinterpret_cast(&trig)[0x13])) & activateFlag) != 0 && + CheckCollideRB(rBody, &trig, dT)) { + HSIMABLE__ *hSimable = rBody->GetOwner()->GetInstanceHandle(); + SubmitForFire(trig, hSimable); + } + } + } + } + } +} + +void WTriggerManager::ProcessSRB(IRigidBody *srBody, float dT) { + fIterCount++; + unsigned int activateFlag = srBody->GetTriggerFlags(); + if (activateFlag == 0) { + return; + } + float radius = UMath::Max(srBody->GetRadius(), 1.5f); + UTL::FastVector nodeInds; + nodeInds.reserve(0x40); + const WGrid &grid = WGrid::Get(); + grid.FindNodes(srBody->GetPosition(), radius, nodeInds); + for (unsigned int *iter = nodeInds.begin(); iter != nodeInds.end(); ++iter) { + WGridNode *gridNode = grid.fNodes[*iter]; + if (gridNode != nullptr) { + WGridNode::iterator eIter(gridNode, WGrid_kTrigger); + while (const unsigned int *indPtr = eIter.GetIndPtr()) { + unsigned int ind = *indPtr; + WTrigger &trig = WCollisionAssets::Get().Trigger(ind); + if (trig.fIterStamp != fIterCount) { + trig.fIterStamp = fIterCount; + if (trig.IsEnabled(fSilencableEnabled) && + (((static_cast(reinterpret_cast(&trig)[0x11]) << 16) + | (static_cast(reinterpret_cast(&trig)[0x12]) << 8) + | static_cast(reinterpret_cast(&trig)[0x13])) & activateFlag) != 0) { + if (srBody->GetOwner()->IsOwnedByPlayer() || !(reinterpret_cast(&trig)[0x13] & 0x80)) { + if (CheckCollideSRB(srBody, &trig, dT)) { + HSIMABLE__ *hSimable = srBody->GetOwner()->GetInstanceHandle(); + SubmitForFire(trig, hSimable); + } + } + } + } + } + } + } +} + +bool WTriggerManager::CheckCollideRB(const IRigidBody *rBody, const WTrigger *trig, float dT) const { + const float rbRadius = rBody->GetRadius(); + UMath::Vector3 rPos; + UMath::Vector3 cp; + float radsSq; + UMath::Vector3 dP; + + radsSq = rBody->GetSpeed() * dT + rbRadius + trig->fPosRadius.w; + cp = UMath::Vector4To3(trig->fPosRadius); + radsSq *= radsSq; + UMath::Scale(rBody->GetLinearVelocity(), dT, dP); + UMath::Add(rBody->GetPosition(), dP, rPos); + + if ((reinterpret_cast(trig)[0x10] & 0xF) == 2) { + if (UMath::DistanceSquare(cp, rPos) <= radsSq) { + if ((static_cast(reinterpret_cast(trig)[0x12]) << 8) & 0x800) { + if (!trig->TestDirection(rBody->GetLinearVelocity())) { + return false; + } + } + return true; + } + } else { + if (UMath::DistanceSquarexz(cp, rPos) <= radsSq) { + if ((static_cast(reinterpret_cast(trig)[0x12]) << 8) & 0x800) { + if (!trig->TestDirection(rBody->GetLinearVelocity())) { + return false; + } + } + if ((reinterpret_cast(trig)[0x10] & 0xF) == 3) { + if (rPos.y + rbRadius >= trig->fPosRadius.y - trig->fHeight * 0.5f) { + if (rPos.y - rbRadius < trig->fPosRadius.y + trig->fHeight * 0.5f) { + return true; + } + } + } else if ((reinterpret_cast(trig)[0x10] & 0xF) == 1) { + UMath::Vector3 dim3; + UMath::Matrix4 bodyMat; + rBody->GetDimension(dim3); + rBody->GetMatrix4(bodyMat); + Dynamics::Collision::Geometry carOBB(bodyMat, rPos, dim3, Dynamics::Collision::Geometry::BOX, dP); + UMath::Matrix4 m; + trig->MakeMatrix(m, false, false); + UMath::Vector4 trigPos = trig->fPosRadius; + trigPos.w = 1.0f; + UMath::Vector3 trigDimension; + trigDimension.x = trig->fMatRow0Width.w * 0.5f; + trigDimension.y = trig->fHeight * 0.5f; + trigDimension.z = trig->fMatRow2Length.w * 0.5f; + Dynamics::Collision::Geometry trigOBB(m, UMath::Vector4To3(trigPos), trigDimension, Dynamics::Collision::Geometry::BOX, UMath::Vector3::kZero); + if (Dynamics::Collision::Geometry::FindIntersection(&carOBB, &trigOBB, &carOBB)) { + return true; + } + } + } + } + return false; +} + +bool WTriggerManager::CheckCollideSRB(const IRigidBody *srBody, const WTrigger *trig, float dT) const { + UMath::Vector3 rPos; + float srRadius; + float srRadiusPlusVel; + UMath::Vector3 dVdT; + UMath::Vector3 cp; + float radsSq; + + rPos = srBody->GetPosition(); + srRadius = srBody->GetRadius(); + srRadiusPlusVel = srBody->GetSpeed() * dT + srRadius; + UMath::Scale(srBody->GetLinearVelocity(), dT, dVdT); + UMath::Add(rPos, dVdT, rPos); + cp = UMath::Vector4To3(trig->fPosRadius); + unsigned char shapeNum = reinterpret_cast(trig)[0x10] & 0xF; + srRadiusPlusVel += trig->fPosRadius.w; + radsSq = srRadiusPlusVel * srRadiusPlusVel; + + if (shapeNum == 1) { + if ((static_cast(reinterpret_cast(trig)[0x12]) << 8) & 0x800) { + if (!trig->TestDirection(srBody->GetLinearVelocity())) { + return false; + } + } + UMath::Vector3 dim3; + memset(&dim3, 0, sizeof(dim3)); + dim3.x = srRadius; + dim3.y = srRadius; + dim3.z = srRadius; + UMath::Matrix4 bodyMat; + srBody->GetMatrix4(bodyMat); + Dynamics::Collision::Geometry srbOBB(bodyMat, rPos, dim3, Dynamics::Collision::Geometry::SPHERE, dVdT); + UMath::Matrix4 m; + trig->MakeMatrix(m, false, false); + UMath::Vector4 trigPos = trig->fPosRadius; + trigPos.w = 1.0f; + UMath::Vector3 trigDimension; + trigDimension.x = trig->fMatRow0Width.w * 0.5f; + trigDimension.y = trig->fHeight * 0.5f; + trigDimension.z = trig->fMatRow2Length.w * 0.5f; + Dynamics::Collision::Geometry trigOBB(m, UMath::Vector4To3(trigPos), trigDimension, Dynamics::Collision::Geometry::BOX, UMath::Vector3::kZero); + if (Dynamics::Collision::Geometry::FindIntersection(&trigOBB, &srbOBB, &trigOBB)) { + return true; + } + } else if (shapeNum == 2) { + if (UMath::DistanceSquare(cp, rPos) < radsSq) { + if ((static_cast(reinterpret_cast(trig)[0x12]) << 8) & 0x800) { + if (!trig->TestDirection(srBody->GetLinearVelocity())) { + return false; + } + } + return true; + } + } else if (shapeNum == 3) { + if (UMath::DistanceSquarexz(cp, rPos) < radsSq) { + if ((static_cast(reinterpret_cast(trig)[0x12]) << 8) & 0x800) { + if (!trig->TestDirection(srBody->GetLinearVelocity())) { + return false; + } + } + if (rPos.y + srRadius >= trig->fPosRadius.y - trig->fHeight * 0.5f && + rPos.y - srRadius < trig->fPosRadius.y + trig->fHeight * 0.5f) { + return true; + } + } + } + return false; +} + +inline float DistanceSquared_XZ(const UMath::Vector3 &a, const UMath::Vector3 &b) { + float z = a.z - b.z; + float x = a.x - b.x; + return x * x + z * z; +} + +void WTriggerManager::GetIntersectingTriggers(const UMath::Vector3 &pt, float radius, WTriggerList *triggerList) const { + UTL::FastVector nodeInds; + fIterCount++; + nodeInds.reserve(0x40); + const WGrid &grid = WGrid::Get(); + grid.FindNodes(pt, radius, nodeInds); + for (unsigned int *iter = nodeInds.begin(); iter != nodeInds.end(); ++iter) { + WGridNode *gridNode = grid.fNodes[*iter]; + if (gridNode != nullptr) { + WGridNode::iterator eIter(gridNode, WGrid_kTrigger); + while (const unsigned int *indPtr = eIter.GetIndPtr()) { + unsigned int ind = *indPtr; + WTrigger &trig = WCollisionAssets::Get().Trigger(ind); + if (trig.fIterStamp != fIterCount) { + trig.fIterStamp = fIterCount; + if (DistanceSquared_XZ(pt, UMath::Vector4To3(trig.fPosRadius)) < (radius + trig.fPosRadius.w) * (radius + trig.fPosRadius.w)) { + triggerList->push_back(&trig); + } + } + } + } + } +} diff --git a/src/Speed/Indep/Src/World/Common/WWorld.cpp b/src/Speed/Indep/Src/World/Common/WWorld.cpp index e69de29bb..6cc86d12b 100644 --- a/src/Speed/Indep/Src/World/Common/WWorld.cpp +++ b/src/Speed/Indep/Src/World/Common/WWorld.cpp @@ -0,0 +1,92 @@ +#include "Speed/Indep/Src/World/WWorld.h" + +#include "Speed/Indep/Libs/Support/Miscellaneous/CARP.h" +#include "Speed/Indep/Src/Sim/SimSurface.h" +#include "Speed/Indep/Src/World/WCollision.h" +#include "Speed/Indep/Src/World/WCollisionAssets.h" + +int LoaderCarpWGrid(bChunk *chunk); +int UnloaderCarpWGrid(bChunk *chunk); + +bChunkLoader bChunkLoaderWGrid(0x3B800, LoaderCarpWGrid, UnloaderCarpWGrid); + +WWorld* WWorld::fgWorld; + +WWorld::WWorld() + : fAttributes(0xeec2271a, 0, nullptr) // + , fRootWorldGroup(nullptr) // + , fCarpData(nullptr) // + , fCarpDataSize(0) // +{ +} + +void WWorld::Init() { + if (!fgWorld) { + fgWorld = new WWorld(); + SimSurface::InitSystem(); + WSurface::InitSystem(); + } +} + +int WWorld::Loader(bChunk* chunk) { + if (chunk->GetID() != 0x3B800) { + return 0; + } + fCarpData = chunk->GetAlignedData(16); + fCarpDataSize = chunk->GetAlignedSize(16); + Open(); + return 1; +} + +int WWorld::Unloader(bChunk* chunk) { + if (chunk->GetID() != 0x3B800) { + return 0; + } + fCarpData = nullptr; + fCarpDataSize = 0; + return 1; +} + +bool WWorld::Open() { + const void *sources[3]; + int sizes[3]; + + sources[2] = nullptr; + sources[1] = nullptr; + sizes[2] = 0; + sizes[1] = 0; + sources[0] = fCarpData; + sizes[0] = fCarpDataSize; + const UGroup *persistentGroup = UGroup::Deserialize(1, reinterpret_cast(sizes), sources, 0); + CARP::ResolveTagReferences(persistentGroup, 0); + WCollisionAssets::Init(persistentGroup, nullptr); + fRootWorldGroup = persistentGroup; + + unsigned int artCount = persistentGroup->GroupCountType(0x41727469); + const UGroup *article = fRootWorldGroup->GroupLocateFirst(0x41727469, 0xFFFFFFFF, 0xFFFFFFFF); + + for (unsigned int artInd = 0; artInd < artCount; artInd++) { + article->DataLocateTag(0x53426172); + article->GetArray(); + article++; + } + + return fRootWorldGroup != nullptr; +} + +int LoaderCarpWGrid(bChunk* chunk) { + return WWorld::Get().Loader(chunk); +} + +int UnloaderCarpWGrid(bChunk* chunk) { + return WWorld::Get().Unloader(chunk); +} + +void WWorld::Close() { + WCollisionAssets::Shutdown(); +} + +WSurface WSurface::kNull; + +void WSurface::InitSystem() { +} diff --git a/src/Speed/Indep/Src/World/Common/WWorldMath.cpp b/src/Speed/Indep/Src/World/Common/WWorldMath.cpp index e69de29bb..b01eb38b5 100644 --- a/src/Speed/Indep/Src/World/Common/WWorldMath.cpp +++ b/src/Speed/Indep/Src/World/Common/WWorldMath.cpp @@ -0,0 +1,203 @@ +#include "Speed/Indep/Src/World/WWorldMath.h" + +extern "C" float rsqrt(const float x); + +bool WWorldMath::IntersectCircle(float x1, float y1, float x2, float y2, float cx, float cy, float r, float &u1, float &u2) { + if (InCircle(x1, y1, cx, cy, r) && InCircle(x2, y2, cx, cy, r)) { + u2 = 0.0f; + u1 = 0.0f; + return true; + } + + y1 -= cy; + y2 -= cy; + y2 -= y1; + x1 -= cx; + x2 -= cx; + x2 -= x1; + float a = x2 * x2 + y2 * y2; + + if (a == 0.0f) { + u2 = 0.0f; + u1 = 0.0f; + return true; + } + + float b = x2 * x1 + y2 * y1; + float t = b + b; + float c = (x1 * x1 + y1 * y1 - r * r) * 4.0f; + + if (t * t - a * c >= 0.0f) { + float root = rsqrt(t * t - a * c); + float inv2timesA = 1.0f / (a + a); + u1 = (-t - root) * inv2timesA; + u2 = (-t + root) * inv2timesA; + if (u1 >= 0.0f && u1 <= 1.0f) { + return true; + } + if (u2 >= 0.0f && u2 <= 1.0f) { + return true; + } + } + return false; +} + +bool WWorldMath::MakeSegSpaceMatrix(const UMath::Vector3 &startPt, const UMath::Vector3 &endPt, UMath::Matrix4 &mat) { + if (PtsEqual(startPt, endPt, 0.001f)) { + return false; + } + + UMath::Vector4 &right = mat.v0; + UMath::Vector4 &forward = mat.v1; + UMath::Vector4 &up = mat.v2; + UMath::Vector4 &trans = mat.v3; + + UMath::Sub(startPt, endPt, UMath::Vector4To3(forward)); + forward.w = 0.0f; + + float rLen = InvSqrt(forward.x * forward.x + forward.y * forward.y + forward.z * forward.z); + float fwdY = forward.y * rLen; + forward.x = forward.x * rLen; + forward.z = forward.z * rLen; + forward.y = fwdY; + + if (__builtin_fabsf(fwdY) > 0.9f) { + right.y = 0.0f; + right.z = 0.0f; + right.x = 1.0f; + right.w = 0.0f; + } else { + right.x = 0.0f; + right.z = 0.0f; + right.y = 1.0f; + right.w = 0.0f; + } + + Crossxyz(right, forward, up); + + up.w = 0.0f; + float lensq = up.x * up.x + up.y * up.y + up.z * up.z; + if (lensq != 0.0f) { + rLen = InvSqrt(lensq); + } else { + rLen = 0.0f; + } + up.x = up.x * rLen; + up.y = up.y * rLen; + up.z = up.z * rLen; + + Crossxyz(forward, up, right); + + trans = UMath::Vector4Make(startPt, 1.0f); + + return true; +} + +float WWorldMath::GetPlaneY(const UMath::Vector3 &normal, const UMath::Vector3 &pointOnPlane, const UMath::Vector3 &testPoint) { + if (normal.y == 0.0f) { + return pointOnPlane.y; + } + return pointOnPlane.y - (normal.x * (testPoint.x - pointOnPlane.x) + normal.z * (testPoint.z - pointOnPlane.z)) / normal.y; +} + +static const float kZero = 0.0f; + +void WWorldMath::NearestPointLine2D3(const UMath::Vector3 &pt, const UMath::Vector3 &p0, const UMath::Vector3 &p1, UMath::Vector3 &nearPt) { + const float &x1 = p0.x; + const float &z1 = p0.z; + const float &x2 = p1.x; + const float &z2 = p1.z; + const float &px = pt.x; + const float &pz = pt.z; + float z = z2 - z1; + float x = x2 - x1; + float div = pow2(x) + pow2(z); + float u = (px - x1) * x + (pz - z1) * z; + if (0.0f < div) { + u = u / div; + } else { + u = kZero; + } + float nx = u * (x2 - x1) + x1; + float nz = u * (z2 - z1) + z1; + nearPt.z = nz; + nearPt.y = 0.0f; + nearPt.x = nx; +} + +void WWorldMath::NearestPointLine2D(const UMath::Vector4 &pt, const UMath::Vector4 *line, UMath::Vector4 &nearPt) { + const float &x1 = line[0].x; + const float &z1 = line[0].z; + const float &x2 = line[1].x; + const float &z2 = line[1].z; + const float &px = pt.x; + const float &pz = pt.z; + float z = z2 - z1; + float x = x2 - x1; + float div = pow2(x) + pow2(z); + float u = (px - x1) * x + (pz - z1) * z; + if (0.0f < div) { + u = u / div; + } else { + u = kZero; + } + float nx = u * (x2 - x1) + x1; + float nz = u * (z2 - z1) + z1; + nearPt.z = nz; + nearPt.y = 0.0f; + nearPt.x = nx; +} + +bool WWorldMath::IntersectSegPlane(const UMath::Vector3 &P1, const UMath::Vector3 &P2, const UMath::Vector3 &PtOnPlane, const UMath::Vector3 &Normal, UMath::Vector3 &intersectionPt, float &t) { + UMath::Vector3 PtOnPlanemP1; + UMath::Vector3 P2mP1; + UMath::Sub(PtOnPlane, P1, PtOnPlanemP1); + UMath::Sub(P2, P1, P2mP1); + float d = UMath::Dot(Normal, P2mP1); + if (d == 0.0f) { + return false; + } + float n = UMath::Dot(Normal, PtOnPlanemP1); + t = n / d; + UMath::Sub(P2, P1, intersectionPt); + UMath::ScaleAdd(intersectionPt, t, P1, intersectionPt); + bool result = false; + if (t < 0.0f || (result = true, t > 1.0f)) { + result = false; + } + return result; +} + +bool WWorldMath::SegmentIntersect(const UMath::Vector4 *line1, const UMath::Vector4 *line2, UMath::Vector4 *intersectPt) { + const float l1x = line1[0].x; + const float l1z = line1[0].z; + const float l2x = line2[0].x; + const float z11 = line1[1].z - l1z; + const float x22 = line2[1].x - l2x; + const float l2z = line2[0].z; + const float z22 = line2[1].z - l2z; + const float x11 = line1[1].x - l1x; + const float ua_d = z22 * x11 - x22 * z11; + if (ua_d == 0.0f) { + return false; + } + { + const float z12 = l1z - l2z; + const float x12 = l1x - l2x; + const float ua_n = x22 * z12 - z22 * x12; + if ((ua_n >= 0.0f && ua_n <= ua_d) || (ua_n <= 0.0f && ua_n >= ua_d)) { + const float ub_n = x11 * z12 - z11 * x12; + if ((ub_n >= 0.0f && ub_n <= ua_d) || (ub_n <= 0.0f && ub_n >= ua_d)) { + if (intersectPt != nullptr) { + float t = ua_n / ua_d; + intersectPt->x = t * x11 + l1x; + intersectPt->z = t * z11 + l1z; + intersectPt->w = 1.0f; + intersectPt->y = t * (line1[1].y - line1[0].y) + line1[0].y; + } + return true; + } + } + } + return false; +} diff --git a/src/Speed/Indep/Src/World/Common/WWorldPos.cpp b/src/Speed/Indep/Src/World/Common/WWorldPos.cpp index e69de29bb..9be628232 100644 --- a/src/Speed/Indep/Src/World/Common/WWorldPos.cpp +++ b/src/Speed/Indep/Src/World/Common/WWorldPos.cpp @@ -0,0 +1,207 @@ +#include "Speed/Indep/Src/World/WWorldPos.h" +#include "Speed/Indep/Src/Sim/SimSurface.h" +#include "Speed/Indep/Src/World/WCollisionMgr.h" +#include "Speed/Indep/Src/World/WWorldMath.h" + +bool WWorldPos::FindClosestFace(const WCollider *collider, const UMath::Vector3 &ptRaw, bool quitIfOnSameFace) { + if (collider != nullptr) { + return FindClosestFace(collider->GetTriList(), ptRaw, quitIfOnSameFace); + } + return FindClosestFace(ptRaw, quitIfOnSameFace); +} + +bool WWorldPos::FindClosestFace(const UMath::Vector3 &ptRaw, bool quitIfOnSameFace) { + return FindClosestFaceInternal(static_cast(nullptr), ptRaw, quitIfOnSameFace); +} + +bool WWorldPos::FindClosestFaceInternal(const WCollisionInstanceCacheList *instList, const UMath::Vector3 &ptRaw, bool quitIfOnSameFace) { + fUsageCount++; + UMath::Vector3 pt = ptRaw; + pt.y += fYOffset; + + bool faceChanged = false; + if (fFaceValid) { + if (WWorldMath::InTri(pt, reinterpret_cast(&fFace))) { + faceChanged = true; + } + } + + if (quitIfOnSameFace) { + return !faceChanged; + } + + if (instList != nullptr) { + FindClosestFaceInternal(*instList, pt); + } else { + WCollisionInstanceCacheList localList; + localList.reserve(16); + WCollisionMgr collMgr(0, 3); + collMgr.GetInstanceList(localList, pt, 0.0f, true); + FindClosestFaceInternal(localList, pt); + } + return true; +} + +bool WWorldPos::FindClosestFaceInternal(const WCollisionInstanceCacheList &instList, const UMath::Vector3 &pt) { + fFaceValid = 0; + UMath::Matrix4 mat; + UMath::Vector3 startPt; + UMath::Vector3 endPt; + bool matValid = false; + bool foundFace = false; + float bestDist = 100000.0f; + + for (const WCollisionInstance *const *iIter = instList.begin(); iIter != instList.end(); ++iIter) { + WCollisionTri face; + float dist; + + if (!(*iIter)->NeedsCrossProduct()) { + WCollisionMgr collMgr(0, 3); + if (collMgr.FindFaceInCInst(pt, **iIter, face, dist)) { + foundFace = true; + if (dist < bestDist) { + fFaceValid = 1; + fFace = face; + FindSurface(*(*iIter)->fCollisionArticle); + bestDist = dist; + } + } + } else { + if (!matValid) { + startPt = pt; + endPt = startPt; + matValid = true; + endPt.y -= 100.0f; + startPt.y += fYOffset; + WWorldMath::MakeSegSpaceMatrix(startPt, endPt, mat); + } + WCollisionMgr collMgr2(0, 3); + if (collMgr2.FindFaceInCInst(mat, endPt, **iIter, face, dist)) { + foundFace = true; + dist = dist - fYOffset; + if (dist < bestDist) { + fFaceValid = 1; + fFace = face; + FindSurface(*(*iIter)->fCollisionArticle); + bestDist = dist; + } + } + } + } + + return foundFace; +} + +bool WWorldPos::FindClosestFace(const WCollisionTriList &triList, const UMath::Vector3 &ipt, bool quitIfOnSameFace) { + bool foundFace = false; + bool onSameFace = false; + fUsageCount++; + UMath::Vector3 pt = ipt; + + if (fFaceValid) { + onSameFace = WWorldMath::InTri(pt, reinterpret_cast(&fFace)); + } + + if (onSameFace && quitIfOnSameFace) { + return false; + } + + float bestDist = 100000.0f; + fFaceValid = 0; + pt.y = pt.y + fYOffset; + + for (WCollisionTriBlock *const *bIter = triList.begin(); bIter != triList.end(); ++bIter) { + if (foundFace && !fFace.fSurface.HasFlag(4)) break; + + const WCollisionTriBlock &triBlock = **bIter; + for (const WCollisionTri *iIter = triBlock.begin(); iIter != triBlock.end(); ++iIter) { + const WCollisionTri &tri = *iIter; + + if (WWorldMath::InTri(pt, reinterpret_cast(&tri))) { + UMath::Vector3 norm; + tri.GetNormal(&norm); + + if (norm.y < 0.0f) { + norm.y = -norm.y; + norm.x = -norm.x; + norm.z = -norm.z; + } + if (0.9999f <= norm.y) { + norm.y = 0.9999f; + } + + float y = WWorldMath::GetPlaneY(norm, tri.fPt0, pt); + float dist = pt.y - y; + if (dist < bestDist && -100000.0f < dist) { + fFaceValid = 1; + fFace = tri; + foundFace = true; + fSurface = reinterpret_cast(tri.fSurfaceRef); + bestDist = dist; + if (!fFace.fSurface.HasFlag(4)) break; + } + } + } + } + + return !onSameFace; +} + +bool WWorldPos::FindClosestFace(const WCollisionInstanceCacheList &instList, const UMath::Vector3 &pt, const UMath::Vector3 &endPt) { + fUsageCount++; + fFaceValid = 0; + + UMath::Matrix4 mat; + if (WWorldMath::MakeSegSpaceMatrix(pt, endPt, mat)) { + float bestDist = 100000.0f; + fFaceValid = 0; + + for (const WCollisionInstance *const *iIter = instList.begin(); iIter != instList.end(); ++iIter) { + WCollisionTri face; + float dist; + WCollisionMgr collMgr(0, 3); + if (collMgr.FindFaceInCInst(mat, endPt, **iIter, face, dist)) { + if (dist < bestDist) { + fFaceValid = 1; + fFace = face; + FindSurface(*(*iIter)->fCollisionArticle); + bestDist = dist; + } + } + } + } + + return false; +} + +bool WWorldPos::Update(const UMath::Vector3 &pos, UMath::Vector4 &dest, bool usecache, const WCollider *collider, bool keep_valid) { + bool was_valid = OnValidFace(); + bool recalcNormal = FindClosestFace(collider, pos, usecache); + if (!OnValidFace()) { + fSurface = SimSurface::kNull.GetConstCollection(); + if (keep_valid && was_valid) { + ForceFaceValidity(); + } else { + dest.w = -1.0f; + return false; + } + } + if (recalcNormal) { + UNormal(&dest); + } + dest.w = (FacePoint(0).x - pos.x) * dest.x + (FacePoint(0).y - pos.y) * dest.y + (FacePoint(0).z - pos.z) * dest.z; + return true; +} + +float WWorldPos::HeightAtPoint(const UMath::Vector3 &pt) const { + if (OnValidFace()) { + UMath::Vector3 norm; + UNormal(&norm); + return WWorldMath::GetPlaneY(norm, UMath::Vector4To3(FacePoint(0)), pt); + } + return 0.0f; +} + +void WWorldPos::FindSurface(const WCollisionArticle &cArt) { + fSurface = cArt.GetSurface(fFace.fSurface.Surface()); +} diff --git a/src/Speed/Indep/Src/World/DamageZones.cpp b/src/Speed/Indep/Src/World/DamageZones.cpp index e69de29bb..64391faa6 100644 --- a/src/Speed/Indep/Src/World/DamageZones.cpp +++ b/src/Speed/Indep/Src/World/DamageZones.cpp @@ -0,0 +1,24 @@ +#include "Speed/Indep/Src/World/DamageZones.h" + +#include "Speed/Indep/Libs/Support/Utility/UCrc.h" +#include "Speed/Indep/Tools/AttribSys/Runtime/AttribHash.h" + +namespace DamageZone { + +static Attrib::StringKey DZSystemName[DZ_MAX]; +static UCrc32 DZDamageStimulus[7]; +static UCrc32 DZImpactStimulus[7]; + +Attrib::StringKey GetSystemName(ID id) { + return DZSystemName[id]; +} + +UCrc32 GetDamageStimulus(unsigned int level) { + return DZDamageStimulus[level]; +} + +UCrc32 GetImpactStimulus(unsigned int level) { + return DZImpactStimulus[level]; +} + +} // namespace DamageZone diff --git a/src/Speed/Indep/Src/World/DamageZones.h b/src/Speed/Indep/Src/World/DamageZones.h index af4826240..739eebe85 100644 --- a/src/Speed/Indep/Src/World/DamageZones.h +++ b/src/Speed/Indep/Src/World/DamageZones.h @@ -5,6 +5,12 @@ #pragma once #endif +struct UCrc32; + +namespace Attrib { +class StringKey; +} + namespace DamageZone { enum ID { @@ -21,6 +27,10 @@ enum ID { DZ_MAX = 10, }; +Attrib::StringKey GetSystemName(ID id); +UCrc32 GetDamageStimulus(unsigned int level); +UCrc32 GetImpactStimulus(unsigned int level); + // total size: 0x4 struct Info { Info() { diff --git a/src/Speed/Indep/Src/World/DebugVehicleSelection.cpp b/src/Speed/Indep/Src/World/DebugVehicleSelection.cpp index 4ca50bdcf..0c249f886 100644 --- a/src/Speed/Indep/Src/World/DebugVehicleSelection.cpp +++ b/src/Speed/Indep/Src/World/DebugVehicleSelection.cpp @@ -28,10 +28,9 @@ void DebugVehicleSelection::Init() { } } -DebugVehicleSelection::DebugVehicleSelection() : UTL::COM::Object(1), IVehicleCache((UTL::COM::Object *)this) { - this->mCollisionObject = ""; - this->mCollisionSurface = ""; - this->mSelectionIndex = 0; +DebugVehicleSelection::DebugVehicleSelection() + : UTL::COM::Object(1), IVehicleCache((UTL::COM::Object *)this), mSelectionIndex(0), mSelectionList(), mCollisionObject(""), + mCollisionSurface("") { this->mSelectionList.reserve(32); this->InitSelectionList(); this->mOnOff = 1; @@ -55,16 +54,16 @@ void DebugVehicleSelection::Service() { } void DebugVehicleSelection::InitSelectionList() { - const Attrib::Class *aClass = Attrib::Database::Get().GetClass(0x4A97EC8F); - unsigned int key = aClass->GetFirstCollection(); - this->mSelectionList.reserve(aClass->GetNumCollections()); + const Attrib::Class *aclass = Attrib::Database::Get().GetClass(0x4A97EC8F); + unsigned int key = aclass->GetFirstCollection(); + this->mSelectionList.reserve(aclass->GetNumCollections()); while (key != 0) { Attrib::Gen::pvehicle atr = Attrib::Gen::pvehicle(key, 0, nullptr); if (atr.MODEL().GetHash32() != UCrc32::kNull.GetValue() && atr.GetParent() == 0xA6ABC921) { const char *vehicleName = atr.CollectionName(); this->mSelectionList.push_back(vehicleName); } - key = aClass->GetNextCollection(key); + key = aclass->GetNextCollection(key); } } @@ -79,13 +78,25 @@ bool DebugVehicleSelection::SwitchPlayerVehicle(const char *attribName) { UMath::Matrix4 transform; oldcar->GetTransform(transform); - // Attrib::StringToKey(attribName); - // VehicleParams params = VehicleParams::VehicleParams(); - // ISimable *icar = UTL::Collections::ListableSet::First(PLAYER_LOCAL)->GetSimable(); - - // oldcar->Attach(icar); - oldcar->Kill(); + unsigned int carType = Attrib::StringToKey(attribName); + + VehicleParams params( + static_cast(this), // + static_cast(0), // + carType, // + UMath::Vector4To3(transform.v2), // + UMath::Vector4To3(transform.v3), // + 2, // + nullptr, // + nullptr); + + ISimable *icar = UTL::COM::Factory::CreateInstance(UCrc32("PVehicle"), params); + if (icar) { + icar->Attach(UTL::Collections::ListableSet::First(PLAYER_LOCAL)); + oldcar->Kill(); + return true; + } - return true; + return false; } diff --git a/src/Speed/Indep/Src/World/DebugVehicleSelection.h b/src/Speed/Indep/Src/World/DebugVehicleSelection.h index d0f472320..da6735c65 100644 --- a/src/Speed/Indep/Src/World/DebugVehicleSelection.h +++ b/src/Speed/Indep/Src/World/DebugVehicleSelection.h @@ -21,6 +21,16 @@ struct DebugVehicleSelection : public UTL::COM::Object, public IVehicleCache { void InitSelectionList(); bool SwitchPlayerVehicle(const char *attribname); + eVehicleCacheResult OnQueryVehicleCache(const IVehicle *removethis, const IVehicleCache *whosasking) const override { + return VCR_DONTCARE; + } + + const char *GetCacheName() const override { + return "DebugVehicleSelection"; + } + + void OnRemovedVehicleCache(IVehicle *ivehicle) override {} + static DebugVehicleSelection &Get() { return *mThis; } 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/FacePixelate.cpp b/src/Speed/Indep/Src/World/FacePixelate.cpp index e69de29bb..5590690e2 100644 --- a/src/Speed/Indep/Src/World/FacePixelate.cpp +++ b/src/Speed/Indep/Src/World/FacePixelate.cpp @@ -0,0 +1,36 @@ +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" + +extern int iRam8047ff04; +extern int FacePixelation_mPixelationOn asm("_14FacePixelation.mPixelationOn"); +extern float FacePixelation_mWidth asm("_14FacePixelation.mWidth"); +extern float FacePixelation_mHeight asm("_14FacePixelation.mHeight"); +extern bVector3 FacePixelation_mWorldPos asm("_14FacePixelation.mWorldPos"); +void eViewPlatInterface_GetScreenPosition(eViewPlatInterface *view, bVector3 *screen_position, const bVector3 *world_position) + asm("GetScreenPosition__18eViewPlatInterfaceP8bVector3PC8bVector3"); + +FacePixelation::FacePixelation(eView *view) { + MyView = view; + mScreenX = 0.0f; + mScreenY = 0.0f; +} + +void FacePixelation::SetLocation(bVector3 &worldPos) { + FacePixelation_mWorldPos = worldPos; +} + +void FacePixelation::GetData(float *x, float *y, float *width, float *height) { + *x = mScreenX; + *y = mScreenY; + *width = FacePixelation_mWidth; + *height = FacePixelation_mHeight; +} + +void FacePixelation::Render() { + if (iRam8047ff04 == 6 && FacePixelation_mPixelationOn != 0) { + bVector3 screen_position; + + eViewPlatInterface_GetScreenPosition(MyView, &screen_position, &FacePixelation_mWorldPos); + mScreenX = screen_position.x; + mScreenY = screen_position.y; + } +} diff --git a/src/Speed/Indep/Src/World/HeliRenderConn.cpp b/src/Speed/Indep/Src/World/HeliRenderConn.cpp index e69de29bb..c00335e7c 100644 --- a/src/Speed/Indep/Src/World/HeliRenderConn.cpp +++ b/src/Speed/Indep/Src/World/HeliRenderConn.cpp @@ -0,0 +1,127 @@ +#include "./HeliRenderConn.h" +#include "./CarInfo.hpp" +#include "Speed/Indep/Src/Camera/CameraMover.hpp" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" + +extern CarPartDatabase CarPartDB; +extern CarType GetCarType__15CarPartDatabaseUi(CarPartDatabase *database, unsigned int model_hash) + asm("GetCarType__15CarPartDatabaseUi"); +extern float lbl_8040B0A8; +extern float lbl_8040B0AC; +extern float lbl_8040B0B0; +extern float lbl_8040B0C0; + +UTL::COM::Factory::Prototype _HeliRenderConn("HeliRenderConn", + HeliRenderConn::Construct); + +Sim::Connection *HeliRenderConn::Construct(const Sim::ConnectionData &data) { + RenderConn::Pkt_Heli_Open *open = reinterpret_cast(data.pkt); + int car_type = GetCarType__15CarPartDatabaseUi(&CarPartDB, open->mModelHash); + + if (car_type == -1 || car_type > 0x53) { + return 0; + } + + return new HeliRenderConn(data, static_cast(car_type), open); +} + +HeliRenderConn::HeliRenderConn(const Sim::ConnectionData &data, CarType type, RenderConn::Pkt_Heli_Open *open) + : VehicleRenderConn(data, type) { + mLastVisibleFrame = 0; + mDistanceToView = lbl_8040B0A8; + mShadowScale = lbl_8040B0AC; + mLastRenderFrame = 0; + + for (int i = 0; i <= 3; i++) { + char *matrix = reinterpret_cast(this) + i * sizeof(this->mMatrices[0]); + PSMTX44Identity(*reinterpret_cast(matrix + 0x64)); + } + + this->Load(open->mWorldID, CarRenderUsage_AIHeli, !open->mSpoolLoad, 0); +} + +HeliRenderConn::~HeliRenderConn() {} + +void HeliRenderConn::Update(const RenderConn::Pkt_Heli_Service &data, float dT) { + if (this->CanUpdate() && this->GetRenderInfo() != nullptr) { + this->mShadowScale = data.mShadowScale; + bVector4 offset(this->GetModelOffset()); + bVector4 rotOffset; + + this->mGeomMatrix = *this->GetBodyMatrix(); + eMulVector(&rotOffset, this->GetBodyMatrix(), &offset); + this->mGeomMatrix.v3.x -= rotOffset.x; + this->mGeomMatrix.v3.y -= rotOffset.y; + this->mGeomMatrix.v3.z -= rotOffset.z; + this->VehicleRenderConn::Update(dT); + } +} + +void HeliRenderConn::OnFetch(float dT) { + bool inview = false; + + if (this->mLastVisibleFrame >= this->mLastRenderFrame && this->mLastRenderFrame != 0) { + inview = true; + } + + RenderConn::Pkt_Heli_Service pkt(inview, this->mDistanceToView); + if (this->Service(&pkt)) { + this->Update(pkt, dT); + } else { + *reinterpret_cast(&this->mHide) = 1; + } +} + +void HeliRenderConn::OnRender(eView *view, int reflection) { + const ReferenceMirror *world_ref = reinterpret_cast(&this->mWorldRef); + CameraMover *mover; + CarRenderInfo *car_render_info; + EVIEWMODE view_mode; + + if (this->mLastRenderFrame != eFrameCounter) { + this->mDistanceToView = lbl_8040B0B0; + } + this->mLastRenderFrame = eFrameCounter; + + if (!this->CanRender()) { + return; + } + + mover = view->GetCameraMover(); + if (mover != 0) { + if (!mover->OutsidePOV()) { + CameraAnchor *anchor = mover->GetAnchor(); + + if (anchor != 0 && anchor->GetWorldID() == world_ref->mWorldID) { + return; + } + } + + if (static_cast(view->GetID() - 1) < 3) { + float distance; + + distance = mover->GetDistanceTo(reinterpret_cast(&this->mGeomMatrix.v3)); + if (this->mDistanceToView < distance) { + distance = this->mDistanceToView; + } + this->mDistanceToView = distance; + } + } + + car_render_info = this->mRenderInfo; + if (car_render_info != 0 && ((view_mode = eGetCurrentViewMode()), reflection == 0)) { + bMatrix4 cbm(this->mGeomMatrix); + bVector3 position(cbm.v3.x, cbm.v3.y, cbm.v3.z); + CARPART_LOD lod = car_render_info->GetMinLodLevel(); + + cbm.v3.x = lbl_8040B0C0; + cbm.v3.y = lbl_8040B0C0; + cbm.v3.z = lbl_8040B0C0; + + if (car_render_info->Render(view, &position, &cbm, this->mMatrices, this->mMatrices, this->mMatrices, 0, reflection, reflection, + this->mShadowScale, lod, lod) && + view->GetID() < 4) { + this->mLastVisibleFrame = eFrameCounter; + } + } +} diff --git a/src/Speed/Indep/Src/World/HeliRenderConn.h b/src/Speed/Indep/Src/World/HeliRenderConn.h index 5143537b5..de98a3331 100644 --- a/src/Speed/Indep/Src/World/HeliRenderConn.h +++ b/src/Speed/Indep/Src/World/HeliRenderConn.h @@ -5,6 +5,111 @@ #pragma once #endif +#include "Speed/Indep/Src/World/VehicleRenderConn.h" +namespace RenderConn { + +class Pkt_Heli_Open : public Sim::Packet { + public: + Pkt_Heli_Open(unsigned int model_hash, unsigned int world_id, bool spool_load) + : mModelHash(model_hash), // + mWorldID(world_id), // + mSpoolLoad(spool_load) {} + + UCrc32 ConnectionClass() override { + static UCrc32 hash("HeliRenderConn"); + return hash; + } + + unsigned int Size() override { + return 0x10; + } + + static unsigned int SType() { + static UCrc32 hash("Pkt_Heli_Open"); + return hash.GetValue(); + } + + unsigned int Type() override { + return SType(); + } + + ~Pkt_Heli_Open() override {} + + unsigned int mModelHash; // offset 0x4, size 0x4 + unsigned int mWorldID; // offset 0x8, size 0x4 + bool mSpoolLoad; // offset 0xC, size 0x1 +}; + +class Pkt_Heli_Service : public Sim::Packet { + public: + Pkt_Heli_Service(bool inview, float distancetoview) + : mInView(inview), // + mDistanceToView(distancetoview) {} + + UCrc32 ConnectionClass() override { + static UCrc32 hash("HeliRenderConn"); + return hash; + } + + unsigned int Size() override { + return 0x14; + } + + static unsigned int SType() { + static UCrc32 hash("Pkt_Heli_Service"); + return hash.GetValue(); + } + + unsigned int Type() override { + return SType(); + } + + bool InView() const { + return this->mInView; + } + + float DistanceToView() const { + return this->mDistanceToView; + } + + void SetShadowScale(float scale) { + this->mShadowScale = scale; + } + + void SetDustStormIntensity(float intensity) { + this->mDustStorm = intensity; + } + + ~Pkt_Heli_Service() override {} + + bool mInView; // offset 0x4, size 0x1 + float mDistanceToView; // offset 0x8, size 0x4 + float mShadowScale; // offset 0xC, size 0x4 + float mDustStorm; // offset 0x10, size 0x4 +}; + +} // namespace RenderConn + +class HeliRenderConn : public VehicleRenderConn { + public: + static Sim::Connection *Construct(const Sim::ConnectionData &data); + + HeliRenderConn(const Sim::ConnectionData &data, CarType type, RenderConn::Pkt_Heli_Open *open); + ~HeliRenderConn() override; + + void OnRender(eView *view, int reflection) override; + void OnFetch(float dT) override; + + private: + void Update(const RenderConn::Pkt_Heli_Service &data, float dT); + + bMatrix4 mMatrices[4]; // offset 0x64, size 0x100 + bMatrix4 mGeomMatrix; // offset 0x164, size 0x40 + unsigned int mLastRenderFrame; // offset 0x1A4, size 0x4 + unsigned int mLastVisibleFrame; // offset 0x1A8, size 0x4 + float mDistanceToView; // offset 0x1AC, size 0x4 + float mShadowScale; // offset 0x1B0, size 0x4 +}; #endif diff --git a/src/Speed/Indep/Src/World/HeliSheet.cpp b/src/Speed/Indep/Src/World/HeliSheet.cpp index e69de29bb..24437fd37 100644 --- a/src/Speed/Indep/Src/World/HeliSheet.cpp +++ b/src/Speed/Indep/Src/World/HeliSheet.cpp @@ -0,0 +1,232 @@ +#include "HeliSheet.hpp" +#include "VisibleSection.hpp" +#include "Speed/Indep/bWare/Inc/bChunk.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +extern double lbl_8040CE80; +extern float lbl_8040CE88; +extern float lbl_8040CE8C; +extern float lbl_8040CE90; +extern double lbl_8040CE98; +extern float lbl_8040CEA0; +extern float lbl_8040CEA4; + +bool bIsPointInPoly(const bVector2 *point, const bVector3 *points, int num_points); + +VisibleSectionBoundary *VisibleSectionManager_FindBoundary(VisibleSectionManager *manager, const bVector2 *point) + asm("FindBoundary__21VisibleSectionManagerPC8bVector2"); + +struct HeliPoly { + short VertexX[3]; + short VertexY[3]; + short VertexZ[3]; + + void GetVertices(bVector3 *vertices); + + bVector3 GetVertex(int n) { + return bVector3(static_cast(this->VertexX[n]) * lbl_8040CEA0, static_cast(this->VertexY[n]) * lbl_8040CEA0, + static_cast(this->VertexZ[n]) * lbl_8040CEA4); + } +}; + +struct HeliSection : public bTNode { + int SectionNumber; + int NumPolys; + int EndianSwapped; + HeliPoly *PolyTable; + + int GetNumPolys() { + return this->NumPolys; + } + + HeliPoly *GetPoly(int n) { + return reinterpret_cast(reinterpret_cast(this->PolyTable) + n * sizeof(HeliPoly)); + } + + void EndianSwap(); +}; + +struct HeliSheetManager { + bTList HeliSectionList; + + HeliSheetManager(); + + int Loader(bChunk *chunk); + + int Unloader(bChunk *chunk); + + HeliPoly *FindHeliPoly(const bVector2 &point); +}; + +int LoaderHeliSheet(bChunk *chunk); +int UnloaderHeliSheet(bChunk *chunk); + +HeliSheetManager gHeliSheetManager; +bChunkLoader bChunkLoaderHeliSheet(0x34159, LoaderHeliSheet, UnloaderHeliSheet); + +void HeliPoly::GetVertices(bVector3 *vertices) { + int n = 0; + + do { + vertices[n] = GetVertex(n); + n++; + } while (n < 3); +} + +void HeliSection::EndianSwap() { + bEndianSwap32(&this->SectionNumber); + bEndianSwap32(&this->NumPolys); + + for (int n = 0; n < this->NumPolys; n++) { + HeliPoly *heli_poly = reinterpret_cast(reinterpret_cast(this->PolyTable) + n * sizeof(HeliPoly)); + + for (int i = 0; i < 3; i++) { + bEndianSwap16(&heli_poly->VertexX[i]); + bEndianSwap16(&heli_poly->VertexY[i]); + bEndianSwap16(&heli_poly->VertexZ[i]); + } + } + + this->EndianSwapped = 1; +} + +float HeliSheetCoordinate::GetElevation(const bVector2 &point, bVector3 *NormalOut, bool *ppoint_valid) { + if (*reinterpret_cast(&this->VertexValid) == 0 || !bIsPointInPoly(&point, this->Vertex, 3)) { + HeliPoly *heli_poly = gHeliSheetManager.FindHeliPoly(point); + + if (heli_poly == 0) { + *reinterpret_cast(&this->VertexValid) = 0; + if (ppoint_valid != 0) { + *reinterpret_cast(ppoint_valid) = 0; + } + return this->PreviousElevation; + } + + *reinterpret_cast(&this->VertexValid) = 1; + heli_poly->GetVertices(this->Vertex); + } + + bVector3 edge0 = this->Vertex[1] - this->Vertex[0]; + bVector3 edge1 = this->Vertex[2] - this->Vertex[0]; + bVector3 normal = bNormalize(bCross(edge0, edge1)); + + float elevation = ((this->Vertex[0].z * normal.z + this->Vertex[0].x * normal.x + this->Vertex[0].y * normal.y) - + (normal.x * point.x + normal.y * point.y)) / + normal.z; + + if (ppoint_valid != 0) { + *reinterpret_cast(ppoint_valid) = 1; + } + + this->PreviousElevation = elevation; + + if (NormalOut != 0) { + NormalOut->x = normal.x; + NormalOut->y = normal.y; + NormalOut->z = normal.z; + } + + return elevation; +} + +HeliSheetManager::HeliSheetManager() {} + +int HeliSheetManager::Loader(bChunk *chunk) { + if (chunk->ID != 0x34159) { + return 0; + } + + int *heli_section = reinterpret_cast((reinterpret_cast(chunk) + 0x17U) & 0xfffffff0); + heli_section[5] = reinterpret_cast(heli_section + 6); + if (heli_section[4] == 0) { + reinterpret_cast(heli_section)->EndianSwap(); + } + + unsigned int *tail = reinterpret_cast(this->HeliSectionList.HeadNode.Prev); + *tail = reinterpret_cast(heli_section); + this->HeliSectionList.HeadNode.Prev = reinterpret_cast(heli_section); + heli_section[1] = reinterpret_cast(tail); + heli_section[0] = reinterpret_cast(this); + return 1; +} + +int HeliSheetManager::Unloader(bChunk *chunk) { + if (chunk->ID != 0x34159) { + return 0; + } + + int *heli_section = reinterpret_cast((reinterpret_cast(chunk) + 0x17U) & 0xfffffff0); + int prev = heli_section[0]; + int *next = reinterpret_cast(heli_section[1]); + + *next = prev; + *reinterpret_cast(prev + 4) = next; + return 1; +} + +int LoaderHeliSheet(bChunk *chunk) { + return gHeliSheetManager.Loader(chunk); +} + +int UnloaderHeliSheet(bChunk *chunk) { + return gHeliSheetManager.Unloader(chunk); +} + +HeliPoly *HeliSheetManager::FindHeliPoly(const bVector2 &point) { + int section_number; + HeliSection *heli_section; + int x; + int y; + + VisibleSectionBoundary *boundary = VisibleSectionManager_FindBoundary(&TheVisibleSectionManager, &point); + if (boundary == 0) { + section_number = 0; + } else { + section_number = boundary->SectionNumber; + } + + if (section_number != 0) { + heli_section = 0; + + for (HeliSection *h = this->HeliSectionList.GetHead(); h != this->HeliSectionList.EndOfList(); h = h->GetNext()) { + if (h->SectionNumber == section_number) { + heli_section = h; + break; + } + } + + if (heli_section != 0) { + x = static_cast(point.x * lbl_8040CE90); + y = static_cast(point.y * lbl_8040CE90); + + for (int n = 0; n < heli_section->GetNumPolys(); n++) { + HeliPoly *heli_poly = heli_section->GetPoly(n); + int max_x = bMax(heli_poly->VertexX[0], heli_poly->VertexX[1]); + max_x = bMax(max_x, heli_poly->VertexX[2]); + int min_x = bMin(heli_poly->VertexX[0], heli_poly->VertexX[1]); + min_x = bMin(min_x, heli_poly->VertexX[2]); + + if (x <= max_x && min_x <= x) { + int max_y = bMax(heli_poly->VertexY[0], heli_poly->VertexY[1]); + max_y = bMax(max_y, heli_poly->VertexY[2]); + int min_y = bMin(heli_poly->VertexY[0], heli_poly->VertexY[1]); + min_y = bMin(min_y, heli_poly->VertexY[2]); + + if (y <= max_y && min_y <= y) { + bVector3 vertices[3]; + + for (int i = 0; i < 3; i++) { + vertices[i] = heli_poly->GetVertex(i); + } + + if (bIsPointInPoly(&point, vertices, 3)) { + return heli_poly; + } + } + } + } + } + } + + return 0; +} diff --git a/src/Speed/Indep/Src/World/Interfaces/IVehicleDamageBehaviour.h b/src/Speed/Indep/Src/World/Interfaces/IVehicleDamageBehaviour.h index ebf238931..13f44f79f 100644 --- a/src/Speed/Indep/Src/World/Interfaces/IVehicleDamageBehaviour.h +++ b/src/Speed/Indep/Src/World/Interfaces/IVehicleDamageBehaviour.h @@ -12,16 +12,16 @@ struct IVehiclePartDamageBehaviour { // Functions IVehiclePartDamageBehaviour() {} - virtual ~IVehiclePartDamageBehaviour() {} + virtual ~IVehiclePartDamageBehaviour(); virtual void Init(); virtual void Reset(); - virtual void Pose(struct bMatrix4 * worldMatrix); - virtual void Update(struct bMatrix4 * worldMatrix); + virtual void Pose(struct bMatrix4 * worldMatrix); + virtual void DamageVehicle(const DamageZone::Info & damageInfo); virtual struct bMatrix4 * GetPartMatrix(unsigned int slotId); @@ -83,10 +83,10 @@ class VehiclePartDamageBehaviour : public IVehiclePartDamageBehaviour { virtual void HidePart(unsigned int slotId); // Overrides: IVehiclePartDamageBehaviour - virtual void Pose(struct bMatrix4 * worldMatrix); + virtual void Update(struct bMatrix4 * worldMatrix); // Overrides: IVehiclePartDamageBehaviour - virtual void Update(struct bMatrix4 * worldMatrix); + virtual void Pose(struct bMatrix4 * worldMatrix); void ManageHoodAnimation(); diff --git a/src/Speed/Indep/Src/World/NeuQuant.cpp b/src/Speed/Indep/Src/World/NeuQuant.cpp index e69de29bb..8bd38af72 100644 --- a/src/Speed/Indep/Src/World/NeuQuant.cpp +++ b/src/Speed/Indep/Src/World/NeuQuant.cpp @@ -0,0 +1,481 @@ +int netsize = 0x100; +int alphadec; +static unsigned char *thepicture; +static int lengthcount; +static int samplefac; +typedef int pixel[5]; +static pixel network[256]; +static int netindex[256]; +static int bias[256]; +static int freq[256]; +static int radpower[32]; + +static int contest(int b, int g, int r, int aa); +static void altersingle(int alpha, int i, int b, int g, int r, int aa); +static void alterneigh(int rad, int i, int b, int g, int r, int aa); + +void initnet(unsigned char *thepic, int len, int num_colours, int sample) { + netsize = num_colours; + thepicture = thepic; + lengthcount = len; + samplefac = sample; + + int i = 0; + int *p; + if (i >= num_colours) { + return; + } + + do { + p = network[i]; + freq[i] = 0x10000 / num_colours; + bias[i] = 0; + p[3] = (i << 12) / num_colours; + p[2] = (i << 12) / num_colours; + p[1] = (i << 12) / num_colours; + p[0] = (i << 12) / num_colours; + i++; + } while (i < num_colours); +} + +void unbiasnet() { + for (int i = 0; i < netsize; i++) { + for (int j = 0; j < 4; j++) { + network[i][j] >>= 4; + } + network[i][4] = i; + } +} + +void learn() { + int i; + int j; + int b; + int g; + int r; + int a; + int radius; + int rad; + int alpha; + int step; + int delta; + int samplepixels; + unsigned char *p; + unsigned char *lim; + + p = thepicture; + samplepixels = lengthcount / (samplefac << 2); + lim = thepicture + lengthcount; + alphadec = (samplefac - 1) / 3 + 30; + + if (samplepixels < 101) { + delta = 1; + } else { + delta = samplepixels / 100; + } + + alpha = 1024; + radius = (netsize << 3) & ~0x3F; + rad = radius >> 6; + + if (rad < 2) { + rad = 0; + } + + j = 0; + if (rad > 0) { + do { + radpower[j] = (((rad * rad - j * j) * 0x100) / (rad * rad)) << 10; + j++; + } while (j < rad); + } + + if (lengthcount == lengthcount / 499 * 499) { + if (lengthcount == lengthcount / 0x1EB * 0x1EB) { + step = 0x7DC; + if (lengthcount != lengthcount / 0x1E7 * 0x1E7) { + step = 0x79C; + } + } else { + step = 0x7AC; + } + } else { + step = 0x7CC; + } + + i = 0; + if (samplepixels > 0) { + do { + b = static_cast(*p) << 4; + g = static_cast(p[1]) << 4; + r = static_cast(p[2]) << 4; + a = static_cast(p[3]) << 4; + j = contest(b, g, r, a); + altersingle(alpha, j, b, g, r, a); + + if (rad != 0) { + alterneigh(rad, j, b, g, r, a); + } + + i++; + + for (p += step; lim <= p; p -= lengthcount) { + } + + if (i == i / delta * delta) { + radius = radius - radius / 0x1E; + rad = radius >> 6; + alpha = alpha - alpha / alphadec; + + if (rad < 2) { + rad = 0; + } + + j = 0; + if (rad > 0) { + do { + radpower[j] = alpha * (((rad * rad - j * j) * 0x100) / (rad * rad)); + j++; + } while (j < rad); + } + } + } while (i < samplepixels); + } +} + +void inxbuild() { + int previouscol = 0; + int startpos = 0; + int i = 0; + + if (i < netsize) { + do { + int *pi = network[i]; + int smallpos = i; + int smallval = pi[1]; + int j = i + 1; + + if (j < netsize) { + do { + int value = network[j][1]; + if (value < smallval) { + smallval = value; + smallpos = j; + } + j++; + } while (j < netsize); + } + + if (i != smallpos) { + int *ps = network[smallpos]; + int temp; + temp = ps[0]; ps[0] = pi[0]; pi[0] = temp; + temp = ps[1]; ps[1] = pi[1]; pi[1] = temp; + temp = ps[2]; ps[2] = pi[2]; pi[2] = temp; + temp = ps[3]; ps[3] = pi[3]; pi[3] = temp; + temp = ps[4]; ps[4] = pi[4]; pi[4] = temp; + } + + if (smallval != previouscol) { + netindex[previouscol] = (startpos + i) >> 1; + int j = previouscol + 1; + if (j < smallval) { + do { + netindex[j] = i; + j++; + } while (j < smallval); + } + previouscol = smallval; + startpos = i; + } + i++; + } while (i < netsize); + } + + netindex[previouscol] = (startpos + netsize - 1) >> 1; + int i2 = previouscol + 1; + if (i2 < 0x100) { + do { + netindex[i2] = netsize - 1; + i2++; + } while (i2 < 0x100); + } +} + +int inxsearch(int b, int g, int r, int aa) { + int best = -1; + int i = netindex[g]; + int j = i - 1; + int bestd = 0x400; + + while (true) { + if (i < netsize) { + int *p = network[i]; + int dist = p[1] - g; + int next = netsize; + + if (dist < bestd) { + next = i + 1; + if (dist < 0) { + dist = -dist; + } + + int a = p[0] - b; + if (a < 0) { + a = -a; + } + + if (dist + a < bestd) { + int value = p[2] - r; + if (value < 0) { + value = -value; + } + value = dist + a + value; + if (value < bestd) { + dist = p[3] - aa; + if (dist < 0) { + dist = -dist; + } + value += dist; + if (value < bestd) { + best = p[4]; + bestd = value; + } + } + } + } + + i = next; + } else if (j < 0) { + return best; + } + + if (j > -1) { + int *p = network[j]; + int dist = g - p[1]; + + if (dist < bestd) { + if (dist < 0) { + dist = -dist; + } + + int a = p[0] - b; + if (a < 0) { + a = -a; + } + + j--; + if (dist + a < bestd) { + int value = p[2] - r; + if (value < 0) { + value = -value; + } + value = dist + a + value; + if (value < bestd) { + dist = p[3] - aa; + if (dist < 0) { + dist = -dist; + } + value += dist; + if (value < bestd) { + best = p[4]; + bestd = value; + } + } + } + } else { + j = -1; + } + } + } +} + +static int contest(int b, int g, int r, int aa) { + int bestd = 0x7FFFFFFF; + int bestbiasd = 0x7FFFFFFF; + int bestpos = -1; + int bestbiaspos = -1; + int *bptr = bias; + int *f = freq; + int i = 0; + + if (i < netsize) { + do { + int *n = network[i]; + int dist = n[0] - b; + if (dist < 0) { + dist = -dist; + } + + int value = n[1] - g; + if (value < 0) { + value = -value; + } + dist += value; + + value = n[2] - r; + if (value < 0) { + value = -value; + } + dist += value; + + value = n[3] - aa; + if (value < 0) { + value = -value; + } + dist += value; + + if (dist < bestd) { + bestd = dist; + bestpos = i; + } + + value = dist - (*bptr >> 12); + if (value < bestbiasd) { + bestbiasd = value; + bestbiaspos = i; + } + + value = *f >> 10; + *f -= value; + *bptr += value << 10; + i++; + f++; + bptr++; + } while (i < netsize); + } + + freq[bestpos] += 0x40; + bias[bestpos] -= 0x10000; + return bestbiaspos; +} + +static void altersingle(int alpha, int i, int b, int g, int r, int aa) { + int *p = &network[i][0]; + int current = p[0]; + int delta = alpha * (current - b); + if (delta < 0) { + delta += 0x3FF; + } + p[0] = current - (delta >> 10); + + current = *++p; + delta = alpha * (current - g); + if (delta < 0) { + delta += 0x3FF; + } + p[0] = current - (delta >> 10); + + current = *++p; + delta = alpha * (current - r); + if (delta < 0) { + delta += 0x3FF; + } + p[0] = current - (delta >> 10); + + current = *++p; + alpha *= current - aa; + if (alpha < 0) { + alpha += 0x3FF; + } + p[0] = current - (alpha >> 10); +} + +static void alterneigh(int rad, int i, int b, int g, int r, int aa) { + int lo = i - rad; + if (lo < -1) { + lo = -1; + } + + int hi = i + rad; + if (hi > netsize) { + hi = netsize; + } + + int j = i + 1; + i--; + int *q = radpower; + + while (j < hi || lo < i) { + int a = *++q; + + if (j < hi) { + int *p = &network[j][0]; + int current = p[0]; + int delta = a * (current - b); + if (delta < 0) { + delta += 0x3FFFF; + } + p[0] = current - (delta >> 18); + + current = p[1]; + delta = a * (current - g); + if (delta < 0) { + delta += 0x3FFFF; + } + p[1] = current - (delta >> 18); + + current = p[2]; + delta = a * (current - r); + if (delta < 0) { + delta += 0x3FFFF; + } + p[2] = current - (delta >> 18); + + current = p[3]; + delta = a * (current - aa); + if (delta < 0) { + delta += 0x3FFFF; + } + j++; + p[3] = current - (delta >> 18); + } + + if (lo < i) { + int *p = &network[i][0]; + int current = p[0]; + int delta = a * (current - b); + if (delta < 0) { + delta += 0x3FFFF; + } + p[0] = current - (delta >> 18); + + current = p[1]; + delta = a * (current - g); + if (delta < 0) { + delta += 0x3FFFF; + } + p[1] = current - (delta >> 18); + + current = p[2]; + delta = a * (current - r); + if (delta < 0) { + delta += 0x3FFFF; + } + p[2] = current - (delta >> 18); + + current = p[3]; + a *= current - aa; + if (a < 0) { + a += 0x3FFFF; + } + i--; + p[3] = current - (a >> 18); + } + } +} + +void nqGetPaletteEntry(int i, unsigned char &r, unsigned char &g, unsigned char &b, unsigned char &a) { + if (i > -1 && i < netsize) { + r = static_cast(network[i][2]); + g = static_cast(network[i][1]); + b = static_cast(network[i][0]); + a = static_cast(network[i][3]); + return; + } + + r = 0; + g = 0; + b = 0; + a = 0; +} diff --git a/src/Speed/Indep/Src/World/OnlineManager.hpp b/src/Speed/Indep/Src/World/OnlineManager.hpp index 0e073ee92..0bee01278 100644 --- a/src/Speed/Indep/Src/World/OnlineManager.hpp +++ b/src/Speed/Indep/Src/World/OnlineManager.hpp @@ -21,6 +21,7 @@ enum eOnlineState { class OnlineManager { public: void StartSimFrame(); + void InitQuantizers(); void EndSimFrame() {} diff --git a/src/Speed/Indep/Src/World/ParameterMaps.cpp b/src/Speed/Indep/Src/World/ParameterMaps.cpp index e69de29bb..c05dc5bf2 100644 --- a/src/Speed/Indep/Src/World/ParameterMaps.cpp +++ b/src/Speed/Indep/Src/World/ParameterMaps.cpp @@ -0,0 +1,504 @@ +#include "./ParameterMaps.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +#include + +namespace { + +enum ParameterMapChunkID { + kPMCH_Header = 0x3B602, + kPMCH_FieldTypes = 0x3B603, + kPMCH_FieldOffsets = 0x3B604, + kPMCH_ParameterData = 0x3B605, + kPMCH_QuadData8 = 0x3B607, + kPMCH_QuadData16 = 0x3B608, +}; + +} // namespace + +ParameterMapsManager &GetParameterMapsManager() { + static ParameterMapsManager TheParameterMapsManager; + return TheParameterMapsManager; +} + +bTList &GetAutoParameterAccessors() { + static bTList AutoParameterAccessors; + return AutoParameterAccessors; +} + +ParameterMapLayer::ParameterMapLayer() + : Header(0), // + FieldTypes(0), // + FieldOffsets(0), // + ParameterData(0), // + QuadData8(0), // + QuadData16(0) {} + +ParameterMapLayer::~ParameterMapLayer() { + Unload(); +} + +void ParameterMapLayer::Load(bChunk **chunk) { + bChunk *root = *chunk; + *chunk = root->GetFirstChunk(); + + while (*chunk < root->GetLastChunk()) { + bChunk *current = *chunk; + + switch (current->GetID()) { + case kPMCH_Header: + this->Header = reinterpret_cast(current->GetData()); + bEndianSwap32(&this->Header->NameHash); + bEndianSwap32(&this->Header->QuadLeft); + bEndianSwap32(&this->Header->QuadTop); + bEndianSwap32(&this->Header->QuadRight); + bEndianSwap32(&this->Header->QuadBottom); + bEndianSwap32(&this->Header->NumberOfQuadNodes); + bEndianSwap32(&this->Header->NumberOfParameterSets); + bEndianSwap32(&this->Header->SizeOfParameterSet); + bEndianSwap32(&this->Header->NumberOfFields); + this->FieldTypes = 0; + this->FieldOffsets = 0; + this->ParameterData = 0; + this->QuadData8 = 0; + this->QuadData16 = 0; + break; + + case kPMCH_FieldTypes: + if (this->Header != 0 && current->Size == this->Header->NumberOfFields * 4) { + this->FieldTypes = reinterpret_cast(current->GetData()); + for (int i = 0; i < this->Header->NumberOfFields; i++) { + bEndianSwap32(&this->FieldTypes[i]); + } + } + break; + + case kPMCH_FieldOffsets: + if (this->Header != 0 && current->Size == this->Header->NumberOfFields * 4) { + this->FieldOffsets = reinterpret_cast(current->GetData()); + for (int i = 0; i < this->Header->NumberOfFields; i++) { + bEndianSwap32(&this->FieldOffsets[i]); + } + } + break; + + case kPMCH_ParameterData: + if (this->Header != 0 && this->FieldTypes != 0 && this->FieldOffsets != 0 && + current->Size == this->Header->NumberOfParameterSets * this->Header->SizeOfParameterSet) { + this->ParameterData = current->GetData(); + for (int set_index = 0; set_index < this->Header->NumberOfParameterSets; set_index++) { + for (int field_index = 0; field_index < this->Header->NumberOfFields; field_index++) { + void *field = this->GetFieldPointer(set_index, field_index); + int field_type = this->FieldTypes[field_index]; + if (field_type == 0 || field_type == 1) { + bEndianSwap32(field); + } + } + } + } + break; + + case kPMCH_QuadData8: + if (this->Header != 0 && current->Size == this->Header->NumberOfQuadNodes * 4) { + this->QuadData8 = reinterpret_cast(current->GetData()); + } + break; + + case kPMCH_QuadData16: + if (this->Header != 0 && current->Size == this->Header->NumberOfQuadNodes * 8) { + this->QuadData16 = reinterpret_cast(current->GetData()); + for (int i = 0; i < this->Header->NumberOfQuadNodes; i++) { + bEndianSwap16(&this->QuadData16[i].Children.Child0); + bEndianSwap16(&this->QuadData16[i].Children.Child1); + bEndianSwap16(&this->QuadData16[i].Children.Child2); + bEndianSwap16(&this->QuadData16[i].Children.Child3); + } + } + break; + } + + *chunk = current->GetNext(); + } +} + +void ParameterMapLayer::Unload() { + this->Header = 0; + this->FieldTypes = 0; + this->FieldOffsets = 0; + this->ParameterData = 0; + + while (!this->ParameterAccessors.IsEmpty()) { + this->ParameterAccessors.GetHead()->ClearLayer(); + } +} + +void ParameterMapLayer::AddParameterAccessor(ParameterAccessor *accessor) { + this->ParameterAccessors.AddTail(accessor); +} + +void ParameterMapLayer::RemoveParameterAccessor(ParameterAccessor *accessor) { + accessor->Remove(); +} + +void *ParameterMapLayer::GetParameterData(float x, float y) { + int parameter_set_index = 0; + + if (this->QuadData8 != 0) { + parameter_set_index = this->GetParameterSetIndexFromQuadData8(x, y); + } else if (this->QuadData16 != 0) { + parameter_set_index = this->GetParameterSetIndexFromQuadData16(x, y); + } + + return reinterpret_cast(this->ParameterData) + this->Header->SizeOfParameterSet * parameter_set_index; +} + +float ParameterMapLayer::GetDataFloat(int field_index, void *parameter_data) { + float *data = reinterpret_cast(static_cast(parameter_data) + FieldOffsets[field_index]); + if (!data) { + return 0.0f; + } + return *data; +} + +int ParameterMapLayer::GetDataInt(int field_index, void *parameter_data) { + int *data = reinterpret_cast(static_cast(parameter_data) + FieldOffsets[field_index]); + if (!data) { + return 0; + } + return *data; +} + +int ParameterMapLayer::GetParameterSetIndexFromMapData(float x, float y) { + if (this->QuadData8 != 0) { + return this->GetParameterSetIndexFromQuadData8(x, y); + } + if (this->QuadData16 != 0) { + return this->GetParameterSetIndexFromQuadData16(x, y); + } + return 0; +} + +int ParameterMapLayer::GetParameterSetIndexFromQuadData8(float x, float y) { + float left = this->Header->QuadLeft; + float top = this->Header->QuadTop; + float right = this->Header->QuadRight; + float bottom = this->Header->QuadBottom; + unsigned int node_index = 0; + + if (x < left || right < x || y < top || bottom < y) { + return 0; + } + + while (true) { + ParameterMapQuad8 *quad = &this->QuadData8[node_index]; + node_index = quad->Children.Child0; + if (node_index == 0) { + return quad->Children.Child1; + } + + float middle_x = (left + right) * 0.5f; + float middle_y = (top + bottom) * 0.5f; + if (x < middle_x) { + right = middle_x; + if (middle_y <= y) { + node_index = quad->Children.Child1; + top = middle_y; + } + } else { + left = middle_x; + if (y < middle_y) { + node_index = quad->Children.Child2; + } else { + node_index = quad->Children.Child3; + top = middle_y; + } + } + } +} + +int ParameterMapLayer::GetParameterSetIndexFromQuadData16(float x, float y) { + float left = this->Header->QuadLeft; + float top = this->Header->QuadTop; + float right = this->Header->QuadRight; + float bottom = this->Header->QuadBottom; + unsigned int node_index = 0; + + if (x < left || right < x || y < top || bottom < y) { + return 0; + } + + while (true) { + ParameterMapQuad16 *quad = &this->QuadData16[node_index]; + node_index = quad->Children.Child0; + if (node_index == 0) { + return quad->Children.Child1; + } + + float middle_x = (left + right) * 0.5f; + float middle_y = (top + bottom) * 0.5f; + if (x < middle_x) { + right = middle_x; + if (middle_y <= y) { + node_index = quad->Children.Child1; + top = middle_y; + } + } else { + left = middle_x; + if (y < middle_y) { + node_index = quad->Children.Child2; + } else { + node_index = quad->Children.Child3; + top = middle_y; + } + } + } +} + +void *ParameterMapLayer::GetFieldPointer(int set_index, int field_index) { + if (!Header->NumberOfParameterSets || !Header->SizeOfParameterSet || + !Header->NumberOfFields || !FieldTypes || !FieldOffsets || !ParameterData) { + return nullptr; + } + if (set_index >= Header->NumberOfParameterSets) { + return nullptr; + } + if (field_index >= Header->NumberOfFields) { + return nullptr; + } + int data_offset = Header->SizeOfParameterSet * set_index + FieldOffsets[field_index]; + return static_cast(ParameterData) + data_offset; +} + +ParameterAccessor::ParameterAccessor(const char *layer_name) + : Layer(0), // + AutoAttachLayerNamehash(0), // + DebugName(layer_name), // + CurrentParameterData(0) { + AutoAttachLayerNamehash = bStringHash(layer_name); + if (!GetParameterMapsManager().GetDataForLayer(AutoAttachLayerNamehash, this, 0)) { + GetAutoParameterAccessors().AddTail(this); + } +} + +ParameterAccessor::~ParameterAccessor() { + ClearLayer(); + if (GetAutoParameterAccessors().IsInList(this)) { + GetAutoParameterAccessors().Remove(this); + } +} + +void ParameterAccessor::SetLayer(ParameterMapLayer *layer) { + this->ClearData(); + + if (this->Layer != 0) { + this->Layer->RemoveParameterAccessor(this); + } + + this->Layer = layer; + if (layer) { + this->SetUpForNewLayer(); + this->Layer->AddParameterAccessor(this); + } else if (this->AutoAttachLayerNamehash) { + GetAutoParameterAccessors().AddTail(this); + } +} + +void ParameterAccessor::ClearLayer() { + this->SetLayer(nullptr); +} + +void ParameterAccessor::CaptureData(float x, float y) { + this->CurrentParameterData = this->Layer != 0 ? this->Layer->GetParameterData(x, y) : this->Layer; +} + +void ParameterAccessor::ClearData() { + this->CurrentParameterData = 0; +} + +float ParameterAccessor::GetDataFloat(int field_index) { + return this->Layer != 0 && this->CurrentParameterData != 0 ? this->Layer->GetDataFloat(field_index, this->CurrentParameterData) : 0.0f; +} + +int ParameterAccessor::GetDataInt(int field_index) { + return this->Layer != 0 && this->CurrentParameterData != 0 ? this->Layer->GetDataInt(field_index, this->CurrentParameterData) : 0; +} + +void ParameterAccessor::SetUpForNewLayer() {} + +ParameterMapsManager::ParameterMapsManager() {} + +ParameterMapsManager::~ParameterMapsManager() { + while (!this->ParameterMapLayers.IsEmpty()) { + delete this->ParameterMapLayers.RemoveHead(); + } +} + +void ParameterMapsManager::AddLayer(ParameterMapLayer *new_layer) { + this->ParameterMapLayers.AddTail(new_layer); +} + +void ParameterMapsManager::UnloadAllLayers() { + for (ParameterMapLayer *current_layer = this->ParameterMapLayers.GetHead(); current_layer != this->ParameterMapLayers.EndOfList(); current_layer = current_layer->GetNext()) { + current_layer->Unload(); + } + while (!this->ParameterMapLayers.IsEmpty()) { + delete this->ParameterMapLayers.RemoveHead(); + } +} + +int ParameterMapsManager::GetDataForLayer(unsigned int layer_name_hash, ParameterAccessor *accessor, int warning_if_not_found) { + for (ParameterMapLayer *layer = this->ParameterMapLayers.GetHead(); layer != this->ParameterMapLayers.EndOfList(); layer = layer->GetNext()) { + if (layer->GetNameHash() == layer_name_hash) { + accessor->SetLayer(layer); + return 1; + } + } + + if (warning_if_not_found) { + } + return 0; +} + +int LoaderParameterMaps(bChunk *chunk) { + if (chunk->GetID() == 0x8003B600) { + bChunk *last_chunk = chunk->GetLastChunk(); + chunk = chunk->GetFirstChunk(); + + while (chunk < last_chunk) { + if (chunk->GetID() == 0x8003B601) { + ParameterMapLayer *new_layer = new (__FILE__, __LINE__) ParameterMapLayer; + new_layer->Load(&chunk); + GetParameterMapsManager().AddLayer(new_layer); + } + } + + ParameterAccessor *current_accessor = GetAutoParameterAccessors().GetHead(); + while (current_accessor != GetAutoParameterAccessors().EndOfList()) { + DumpAutoParameterAccessorsList(); + ParameterAccessor *next_accessor = current_accessor->GetNext(); + unsigned int namehash = current_accessor->GetAutoAttachLayerNamehash(); + if (namehash) { + current_accessor->Remove(); + if (!GetParameterMapsManager().GetDataForLayer(namehash, current_accessor, 0)) { + GetAutoParameterAccessors().AddHead(current_accessor); + } + } + current_accessor = next_accessor; + } + + DumpAutoParameterAccessorsList(); + return 1; + } + return 0; +} + +int UnloaderParameterMaps(bChunk *chunk) { + if (chunk->GetID() == 0x8003B600) { + DumpAutoParameterAccessorsList(); + GetParameterMapsManager().UnloadAllLayers(); + DumpAutoParameterAccessorsList(); + return 1; + } + return 0; +} + +void DumpAutoParameterAccessorsList() {} + +ParameterAccessorBlend::ParameterAccessorBlend(const char *layer_name) + : ParameterAccessor(layer_name), // + LastData(0), // + HaveLastData(0) {} + +ParameterAccessorBlend::~ParameterAccessorBlend() { +} + +void ParameterAccessorBlend::CaptureData(float x, float y, float ratio) { + if (this->Layer == 0 || this->LastData == 0) { + this->CurrentParameterData = 0; + return; + } + + void *current_data = this->Layer->GetParameterData(x, y); + if (this->HaveLastData == 0) { + std::memcpy(this->LastData, current_data, this->Layer->GetSizeOfParameterSet()); + this->HaveLastData = 1; + } else { + for (int i = 0; i < this->Layer->GetNumberOfFields(); i++) { + int field_type = this->Layer->GetFieldType(i); + int field_offset = this->Layer->GetFieldOffset(i); + char *last_data = reinterpret_cast(this->LastData) + field_offset; + char *new_data = reinterpret_cast(current_data) + field_offset; + + if (field_type == 0) { + *reinterpret_cast(last_data) = + *reinterpret_cast(new_data) * ratio + *reinterpret_cast(last_data) * (1.0f - ratio); + } else if (field_type == 1) { + *reinterpret_cast(last_data) = static_cast(static_cast(*reinterpret_cast(new_data)) * ratio + + static_cast(*reinterpret_cast(last_data)) * (1.0f - ratio)); + } + } + } + + this->CurrentParameterData = this->LastData; +} + +void ParameterAccessorBlend::ClearData() { + if (this->LastData != 0) { + delete[] reinterpret_cast(this->LastData); + this->LastData = 0; + } + this->HaveLastData = 0; + ParameterAccessor::ClearData(); +} + +void ParameterAccessorBlend::SetUpForNewLayer() { + if (Layer) { + int data_size = Layer->GetSizeOfParameterSet(); + if (data_size > 0) { + LastData = new (__FILE__, __LINE__) char[data_size]; + } + } +} + +void ParameterAccessorBlend::CaptureData(float x, float y) { + (void)x; + (void)y; +} + +ParameterAccessorBlendByDistance::ParameterAccessorBlendByDistance(const char *layer_name) + : ParameterAccessorBlend(layer_name), // + last_x(0.0f), // + last_y(0.0f), // + HaveLastPosition(0) {} + +ParameterAccessorBlendByDistance::~ParameterAccessorBlendByDistance() {} + +void ParameterAccessorBlendByDistance::CaptureData(float x, float y, float full_blend_distance) { + float ratio = 1.0f; + + if (this->HaveLastPosition != 0 && full_blend_distance != 0.0f) { + float dy = y - this->last_y; + float dx = x - this->last_x; + float distance = bSqrt(dx * dx + dy * dy); + if (distance < full_blend_distance) { + ratio = distance / full_blend_distance; + } + } + + ParameterAccessorBlend::CaptureData(x, y, ratio); + this->last_y = y; + this->last_x = x; + this->HaveLastPosition = 1; +} + +void ParameterAccessorBlendByDistance::SetUpForNewLayer() { + this->last_x = 0.0f; + this->last_y = 0.0f; + this->HaveLastPosition = 0; + ParameterAccessorBlend::SetUpForNewLayer(); +} + +void ParameterAccessorBlendByDistance::CaptureData(float x, float y) { + (void)x; + (void)y; +} diff --git a/src/Speed/Indep/Src/World/ParameterMaps.hpp b/src/Speed/Indep/Src/World/ParameterMaps.hpp index 761f31502..65d128e7e 100644 --- a/src/Speed/Indep/Src/World/ParameterMaps.hpp +++ b/src/Speed/Indep/Src/World/ParameterMaps.hpp @@ -5,6 +5,178 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/UCrc.h" +#include "Speed/Indep/bWare/Inc/bChunk.hpp" +#include "Speed/Indep/bWare/Inc/bList.hpp" +struct ParameterMapLayerHeader { + unsigned int NameHash; // offset 0x0, size 0x4 + float QuadLeft; // offset 0x4, size 0x4 + float QuadTop; // offset 0x8, size 0x4 + float QuadRight; // offset 0xC, size 0x4 + float QuadBottom; // offset 0x10, size 0x4 + int NumberOfQuadNodes; // offset 0x14, size 0x4 + int NumberOfParameterSets; // offset 0x18, size 0x4 + int SizeOfParameterSet; // offset 0x1C, size 0x4 + int NumberOfFields; // offset 0x20, size 0x4 +}; + +union ParameterMapQuad8 { + struct { + unsigned char Child0; + unsigned char Child1; + unsigned char Child2; + unsigned char Child3; + } Children; + struct { + unsigned char IsParent; + unsigned char Data; + } Others; +}; + +union ParameterMapQuad16 { + struct { + unsigned short Child0; + unsigned short Child1; + unsigned short Child2; + unsigned short Child3; + } Children; + struct { + unsigned short IsParent; + unsigned short Data; + } Others; +}; + +class ParameterAccessor; + +class ParameterMapLayer : public bTNode { + public: + ParameterMapLayer(); + ~ParameterMapLayer(); + + unsigned int GetNameHash() { + return this->Header != 0 ? this->Header->NameHash : 0; + } + + int GetSizeOfParameterSet() { + return this->Header != 0 ? this->Header->SizeOfParameterSet : 0; + } + + int GetNumberOfFields() { + return this->Header != 0 ? this->Header->NumberOfFields : 0; + } + + int GetFieldType(int field_index) { + return this->FieldTypes[field_index]; + } + + int GetFieldOffset(int field_index) { + return this->FieldOffsets[field_index]; + } + + void Load(bChunk **chunk); + void Unload(); + void AddParameterAccessor(ParameterAccessor *accessor); + void RemoveParameterAccessor(ParameterAccessor *accessor); + void *GetParameterData(float x, float y); + float GetDataFloat(int field_index, void *parameter_data); + int GetDataInt(int field_index, void *parameter_data); + + private: + int GetParameterSetIndexFromMapData(float x, float y); + int GetParameterSetIndexFromQuadData8(float x, float y); + int GetParameterSetIndexFromQuadData16(float x, float y); + void *GetFieldPointer(int set_index, int field_index); + + ParameterMapLayerHeader *Header; // offset 0x8, size 0x4 + int *FieldTypes; // offset 0xC, size 0x4 + int *FieldOffsets; // offset 0x10, size 0x4 + void *ParameterData; // offset 0x14, size 0x4 + ParameterMapQuad8 *QuadData8; // offset 0x18, size 0x4 + ParameterMapQuad16 *QuadData16; // offset 0x1C, size 0x4 + bTList ParameterAccessors; // offset 0x20, size 0x8 + + friend class ParameterAccessor; + friend class ParameterMapsManager; +}; + +class ParameterAccessor : public bTNode { + public: + ParameterAccessor(const char *layer_name = 0); + virtual ~ParameterAccessor(); + + int IsValid() { + return this->CurrentParameterData != 0; + } + + unsigned int GetAutoAttachLayerNamehash() { + return this->AutoAttachLayerNamehash; + } + + const char *GetDebugName() { + return this->DebugName; + } + + void SetLayer(ParameterMapLayer *layer); + void ClearLayer(); + virtual void CaptureData(float x, float y); + virtual void ClearData(); + virtual float GetDataFloat(int field_index); + virtual int GetDataInt(int field_index); + virtual void SetUpForNewLayer(); + + ParameterMapLayer *Layer; // offset 0x8, size 0x4 + unsigned int AutoAttachLayerNamehash; // offset 0xC, size 0x4 + const char *DebugName; // offset 0x10, size 0x4 + void *CurrentParameterData; // offset 0x14, size 0x4 +}; + +class ParameterAccessorBlend : public ParameterAccessor { + public: + ParameterAccessorBlend(const char *layer_name = 0); + ~ParameterAccessorBlend() override; + + virtual void CaptureData(float x, float y, float ratio); + + void ClearData() override; + void SetUpForNewLayer() override; + void CaptureData(float x, float y) override; + + void *LastData; // offset 0x1C, size 0x4 + int HaveLastData; // offset 0x20, size 0x4 +}; + +class ParameterAccessorBlendByDistance : public ParameterAccessorBlend { + public: + ParameterAccessorBlendByDistance(const char *layer_name = 0); + ~ParameterAccessorBlendByDistance() override; + + void CaptureData(float x, float y, float full_blend_distance) override; + void SetUpForNewLayer() override; + void CaptureData(float x, float y) override; + + float last_x; // offset 0x24, size 0x4 + float last_y; // offset 0x28, size 0x4 + int HaveLastPosition; // offset 0x2C, size 0x4 +}; + +class ParameterMapsManager { + public: + ParameterMapsManager(); + ~ParameterMapsManager(); + + void AddLayer(ParameterMapLayer *new_layer); + void UnloadAllLayers(); + int GetDataForLayer(unsigned int layer_name_hash, ParameterAccessor *accessor, int warning_if_not_found); + int GetDataForLayer(const char *layer_name, ParameterAccessor *accessor, int warning_if_not_found); + + bTList ParameterMapLayers; // offset 0x0, size 0x8 +}; + +ParameterMapsManager &GetParameterMapsManager(); +bTList &GetAutoParameterAccessors(); +int LoaderParameterMaps(bChunk *chunk); +int UnloaderParameterMaps(bChunk *chunk); +void DumpAutoParameterAccessorsList(); #endif diff --git a/src/Speed/Indep/Src/World/Player.hpp b/src/Speed/Indep/Src/World/Player.hpp index 4ca6beea5..177806fbd 100644 --- a/src/Speed/Indep/Src/World/Player.hpp +++ b/src/Speed/Indep/Src/World/Player.hpp @@ -9,7 +9,7 @@ class Player { // total size: 0x1 public: Player *GetPlayerByNumber(int number); - Player *GetPlayerByIndex(int number); + static Player *GetPlayerByIndex(int number); int GetNumPlayers(); }; diff --git a/src/Speed/Indep/Src/World/Rain.hpp b/src/Speed/Indep/Src/World/Rain.hpp index cd6926519..c305de790 100644 --- a/src/Speed/Indep/Src/World/Rain.hpp +++ b/src/Speed/Indep/Src/World/Rain.hpp @@ -11,4 +11,8 @@ int AmIinATunnel(eView *view, int CheckOverPass); int AmIinATunnelSlow(eView *view, int CheckOverPass); void SetRainBase(); +inline bVector3 *Rain::GetWind() { + return &this->PrevailingWindSpeed; +} + #endif diff --git a/src/Speed/Indep/Src/World/Scenery.cpp b/src/Speed/Indep/Src/World/Scenery.cpp index e69de29bb..d8174ffec 100644 --- a/src/Speed/Indep/Src/World/Scenery.cpp +++ b/src/Speed/Indep/Src/World/Scenery.cpp @@ -0,0 +1,1395 @@ +#include "Scenery.hpp" + +#include "Speed/Indep/Libs/Support/Utility/UStandard.h" +#include "VisibleSection.hpp" +#include "WorldModel.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 &= -GetBit(n) - 1U; + } + + 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:")) { + if (bStrICmp(script.GetNextArgumentString(), TrackInfo::GetLoadedTrackInfo()->RegionName) == 0) { + script.GetNextArgumentString(); + char *option = script.GetNextArgumentString(); + bool set_booboo = bStrICmp(option, "SET") == 0; + bool clr_booboo = bStrICmp(option, "CLR") == 0; + 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..94200090c 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,103 @@ struct SceneryCullInfo { int PrecullerSectionNumber; // offset 0xB8, size 0x4 }; +struct ModelHeirarchy; + +// 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 +}; + +// TODO +class ScenerySectionHeader : public bTNode { + public: + 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 +210,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 +238,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/SimpleModelAnim.cpp b/src/Speed/Indep/Src/World/SimpleModelAnim.cpp index e69de29bb..1c33da88a 100644 --- a/src/Speed/Indep/Src/World/SimpleModelAnim.cpp +++ b/src/Speed/Indep/Src/World/SimpleModelAnim.cpp @@ -0,0 +1,79 @@ +#include "SimpleModelAnim.hpp" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Src/Ecstasy/eMath.hpp" +#include "Speed/Indep/Src/Misc/Timer.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" + +enum SimpleModelAnimType { + SIMPLEMODELANIM_ROTATEX = 1, + SIMPLEMODELANIM_ROTATEY = 2, + SIMPLEMODELANIM_ROTATEZ = 4 +}; + +struct SimpleModelAnimInfo { + unsigned int mHash; + SimpleModelAnimType mRotationType; + float mRotationSpeed; + float mRotationAngle; + float mLastAnimTime; +}; + +SimpleModelAnimInfo gSimpleSolidHashList[2] = { + {0, SIMPLEMODELANIM_ROTATEY, 50.0f, 0.0f, 0.0f}, + {0, SIMPLEMODELANIM_ROTATEZ, 50.0f, 0.0f, 0.0f}, +}; + +namespace SimpleModelAnim { + +void Init() { + gSimpleSolidHashList[0].mHash = bStringHash("XO_WINDMILL_BLADE_1B_JG_00"); + gSimpleSolidHashList[1].mHash = bStringHash("XB_DONUTSIGNB_1B_BK_00"); +} + +void Reset() { + for (unsigned int ix = 0; ix < 2; ix++) { + SimpleModelAnimInfo &modelAnim = gSimpleSolidHashList[ix]; + modelAnim.mLastAnimTime = WorldTimer.GetSeconds(); + } +} + +void Update() { + for (unsigned int ix = 0; ix < 2; ix++) { + SimpleModelAnimInfo &modelAnim = gSimpleSolidHashList[ix]; + float elapsed = WorldTimer.GetSeconds() - modelAnim.mLastAnimTime; + modelAnim.mLastAnimTime = WorldTimer.GetSeconds(); + modelAnim.mRotationAngle = elapsed * modelAnim.mRotationSpeed + modelAnim.mRotationAngle; + if (modelAnim.mRotationAngle >= 360.0f) { + modelAnim.mRotationAngle -= 360.0f; + } + } +} + +void Animate(eModel *model, eSolid *solid, bMatrix4 *local_world) { + for (unsigned int ix = 0; ix < 2; ix++) { + SimpleModelAnimInfo &modelAnim = gSimpleSolidHashList[ix]; + if (solid->NameHash == modelAnim.mHash) { + bMatrix4 *modelMatrix = model->GetPivotMatrix(); + bMatrix4 localInverse(*local_world); + + bInvertMatrix(&localInverse, &localInverse); + eMulMatrix(modelMatrix, local_world, &localInverse); + + if (modelAnim.mRotationType & SIMPLEMODELANIM_ROTATEX) { + eRotateX(modelMatrix, modelMatrix, bDegToAng(modelAnim.mRotationAngle)); + } + + if (modelAnim.mRotationType & SIMPLEMODELANIM_ROTATEY) { + eRotateY(modelMatrix, modelMatrix, bDegToAng(modelAnim.mRotationAngle)); + } + + if (modelAnim.mRotationType & SIMPLEMODELANIM_ROTATEZ) { + eRotateZ(modelMatrix, modelMatrix, bDegToAng(modelAnim.mRotationAngle)); + } + + eMulMatrix(local_world, modelMatrix, local_world); + } + } +} + +} // namespace SimpleModelAnim 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..afa3d3748 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" + +struct 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/SkyRender.cpp b/src/Speed/Indep/Src/World/SkyRender.cpp index e69de29bb..ec4e4c80d 100644 --- a/src/Speed/Indep/Src/World/SkyRender.cpp +++ b/src/Speed/Indep/Src/World/SkyRender.cpp @@ -0,0 +1,421 @@ +#include "Sun.hpp" +#include "Scenery.hpp" +#include "TimeOfDay.hpp" +#include "World.hpp" +#include "Speed/Indep/Src/Camera/Camera.hpp" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Src/Ecstasy/Texture.hpp" +#include "Speed/Indep/Src/Ecstasy/eMath.hpp" +#include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "Speed/Indep/bWare/Inc/bSlotPool.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" + +extern unsigned int FrameMallocFailed; +extern unsigned int FrameMallocFailAmount; +extern short SpecularOffset; +extern unsigned short matAng_33578; +int DrawSky = 1; +int DrawSkyEnvMap = 1; +int deblayer[5] = {1, 1, 1, 1, 1}; +float skylayer[5][8] = { + {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}, + {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}, + {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}, + {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}, + {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}, +}; +eModel SkySpecularModel; +eModel SkydomeModel; +bVector3 SunPos; +extern float SunPosY; +extern float WorldTimeElapsed; +float MainSkyScale = 1.0f; +extern float lbl_8040B278; +extern float lbl_8040B27C; +extern float lbl_8040B280; +extern float lbl_8040B284; +extern float lbl_8040B288; +extern float lbl_8040B28C; +extern float lbl_8040B290; +extern float lbl_8040B294; +extern float lbl_8040B298; +extern float lbl_8040B29C; +extern float lbl_8040B274; +extern float lbl_8040B0FC; +extern float lbl_8040B108; +extern float lbl_8040B10C; +extern float lbl_8040B2A0; +extern float lbl_8040B2A4; +extern float lbl_8040B2A8; +extern float lbl_8040B2AC; +extern float lbl_8040B2B0; +extern float lbl_8040B2B4; +extern float lbl_8040B2B8; +extern float lbl_8040B2BC; +extern char lbl_8040B110[]; +extern char lbl_8040B128[]; +extern char lbl_8040B13C[]; +extern char lbl_8040B154[]; +extern char lbl_8040B170[]; +extern char lbl_8040B198[]; + +float MinSkySpecular = 0.0f; +float MaxSkySpecular = 1.0f; +float SkyRenderForceOvercast = 0.0f; +unsigned int BaseSkyHash[2]; +unsigned int SkyHash[10]; +TextureInfo *CurrentSkyTextures[10]; +void (*UserSkyLoadCallback)(int) = 0; +int UserSkyLoadCallbackParam; +int bSkyTexturesLoaded = 0; +eReplacementTextureTable SKYtextable[2]; +eReplacementTextureTable SPECtextable; + +static inline bMatrix4 MakeIdentityMatrix() { + bMatrix4 matrix; + PSMTX44Identity(*reinterpret_cast(&matrix)); + return matrix; +} + +static inline bMatrix4 MakeReflectedIdentityMatrix() { + bMatrix4 matrix = MakeIdentityMatrix(); + matrix.v2.z = -1.0f; + return matrix; +} + +bMatrix4 SkydomeLocalWorld = MakeIdentityMatrix(); +bMatrix4 SkydomeLocalReflectedWorld = MakeReflectedIdentityMatrix(); +bMatrix4 SkySpecularLocalWorld = MakeReflectedIdentityMatrix(); + +void GetSunPos(eView *view, float *x, float *y, float *z); +eSolid *eFindSolid(unsigned int name_hash); +SceneryInstance *FindSceneryInstance(unsigned int scenery_name_hash); +void *FindSceneryInfo(unsigned int scenery_name_hash); +void eLoadStreamingTexture(unsigned int *name_hash_table, int num_hashes, void (*callback)(unsigned int), unsigned int param0, int memory_pool_num); +void eUnloadStreamingTexture(unsigned int *name_hash_table, int num_hashes); + +enum SKY_LAYER { + SKY_LAYER_BLUE = 0, + SKY_LAYER_CLOUDS = 1, + SKY_LAYER_OVERCAST = 2, + SKY_LAYER_LOWREZ = 3, + SKY_LAYER_REFLECTION = 4, + SKY_NUM_LAYERS = 5, +}; + +void SkyLoadCallback(unsigned int param); +void ReplaceSkyTextures(SKY_LAYER layer); + +namespace { + +void Render(eViewPlatInterface *view, eModel *model, bMatrix4 *local_to_world, eLightContext *light_context, unsigned int flags, + unsigned int exc_flag) asm("Render__18eViewPlatInterfaceP6eModelP8bMatrix4P13eLightContextUiT2"); + +} // namespace + +int SkyInitModel(eModel *model, bMatrix4 *local_world, unsigned int scenery_name_hash) { + if (model->GetNameHash() != 0) { + return 1; + } + + if (eFindSolid(scenery_name_hash) == 0) { + return 0; + } + + model->Init(scenery_name_hash); + PSMTX44Identity(*reinterpret_cast(local_world)); + + SceneryInstance *scenery_instance = FindSceneryInstance(scenery_name_hash); + if (scenery_instance != 0) { + short rotation0 = scenery_instance->Rotation[0]; + short rotation1 = scenery_instance->Rotation[1]; + short rotation2 = scenery_instance->Rotation[2]; + float rotation_scale = lbl_8040B0FC; + float row_w = lbl_8040B108; + + local_world->v0.x = static_cast(rotation0) * rotation_scale; + local_world->v0.y = static_cast(rotation1) * rotation_scale; + local_world->v0.z = static_cast(rotation2) * rotation_scale; + local_world->v0.w = row_w; + + rotation0 = scenery_instance->Rotation[3]; + rotation1 = scenery_instance->Rotation[4]; + rotation2 = scenery_instance->Rotation[5]; + local_world->v1.x = static_cast(rotation0) * rotation_scale; + local_world->v1.y = static_cast(rotation1) * rotation_scale; + local_world->v1.z = static_cast(rotation2) * rotation_scale; + local_world->v1.w = row_w; + + rotation0 = scenery_instance->Rotation[6]; + rotation1 = scenery_instance->Rotation[7]; + rotation2 = scenery_instance->Rotation[8]; + local_world->v2.x = static_cast(rotation0) * rotation_scale; + local_world->v2.y = static_cast(rotation1) * rotation_scale; + local_world->v2.z = static_cast(rotation2) * rotation_scale; + local_world->v2.w = row_w; + local_world->v3 = *reinterpret_cast(scenery_instance->Position); + float matrix_w = lbl_8040B10C; + local_world->v3.w = matrix_w; + + eModel **scenery_info_models = reinterpret_cast(reinterpret_cast(FindSceneryInfo(scenery_name_hash)) + 0x28); + unsigned int detail_level = 0; + + do { + if (*scenery_info_models != 0) { + (*scenery_info_models)->UnInit(); + } + detail_level++; + scenery_info_models++; + } while (detail_level < 4); + } + + return 1; +} + +static inline bMatrix4 *eFrameMallocMatrix(int num_matrices) { + unsigned char *address = CurrentBufferPos; + unsigned int size = num_matrices * sizeof(bMatrix4); + if (address + size >= CurrentBufferEnd) { + FrameMallocFailed = 1; + FrameMallocFailAmount += size; + address = 0; + } else { + CurrentBufferPos = address + size; + } + return reinterpret_cast(address); +} + +void StuffSpecular(eView *view) { + ProfileNode profile_node("StuffSpecular", 0); + int view_id = view->GetID(); + Camera *view_camera = view->GetCamera(); + bVector3 CamPosWORLD(*view_camera->GetPosition()); + bMatrix4 *SkydomeLocalWorld = eFrameMallocMatrix(1); + + if (SkydomeLocalWorld != nullptr) { + eIdentity(SkydomeLocalWorld); + SkydomeLocalWorld->v3.x = CamPosWORLD.x; + SkydomeLocalWorld->v3.y = CamPosWORLD.y; + SkydomeLocalWorld->v3.z = CamPosWORLD.z; + } + + if (view_id - 6U > 1) { + return; + } + + GetSunPos(view, &SunPos.x, &SunPos.y, &SunPos.z); + SunPos.z = 0.0f; + SkydomeLocalWorld->v0.x *= lbl_8040B2A8; + SkydomeLocalWorld->v1.y *= lbl_8040B2A8; + SkydomeLocalWorld->v2.z = lbl_8040B2A4 * lbl_8040B2A8; + SkydomeLocalWorld->v3.z += lbl_8040B2AC; + + { + bVector3 MySunDir = SunPos - CamPosWORLD; + bNormalize(&MySunDir, &MySunDir); + bVector3 Ahead(0.0f, 1.0f, 0.0f); + float matCos = bDot(&MySunDir, &Ahead); + unsigned short matAng = bACos(matCos); + if (MySunDir.y < 0.0f) { + matAng = -matAng; + } + + bMatrix4 matmat; + bMatrix4 LocalRot; + + eCreateRotationZ(&LocalRot, static_cast(matAng + SpecularOffset)); + eMulMatrix(SkydomeLocalWorld, &LocalRot, SkydomeLocalWorld); + Render(view, &SkySpecularModel, SkydomeLocalWorld, 0, 0, 0); + + bMatrix4 *SkydomeLocalWorld2 = eFrameMallocMatrix(1); + + eIdentity(SkydomeLocalWorld2); + SkydomeLocalWorld2->v3.x = CamPosWORLD.x; + SkydomeLocalWorld2->v3.y = CamPosWORLD.y; + SkydomeLocalWorld2->v0.x *= lbl_8040B2A8; + SkydomeLocalWorld2->v1.y *= lbl_8040B2A8; + SkydomeLocalWorld2->v2.z *= lbl_8040B2A8; + SkydomeLocalWorld2->v3.z = CamPosWORLD.z + lbl_8040B2B4; + eMulMatrix(SkydomeLocalWorld2, &LocalRot, SkydomeLocalWorld2); + Render(view, &SkySpecularModel, SkydomeLocalWorld2, 0, 0, 0); + } +} + +void StuffSkyLayer(eView *view, SKY_LAYER layer) { + if (deblayer[layer] != 0) { + bMatrix4 *cameraMatrix = view->GetCamera()->GetCameraMatrix(); + bMatrix4 *matrix = reinterpret_cast(CurrentBufferPos); + float scaleZ = lbl_8040B27C; + float scale = lbl_8040B278; + bool rotate = false; + float x = cameraMatrix->v3.x; + float y = cameraMatrix->v3.y; + float z = cameraMatrix->v3.z; + int view_id = view->GetID(); + + if (CurrentBufferEnd <= CurrentBufferPos + sizeof(bMatrix4)) { + FrameMallocFailed = 1; + FrameMallocFailAmount += sizeof(bMatrix4); + matrix = 0; + } else { + CurrentBufferPos += sizeof(bMatrix4); + } + + if (matrix != 0) { + PSMTX44Identity(*reinterpret_cast(matrix)); + ReplaceSkyTextures(layer); + if (layer == SKY_LAYER_BLUE) { + scale = lbl_8040B280; + } else if (layer == SKY_LAYER_CLOUDS) { + } else if (layer == SKY_LAYER_OVERCAST) { + rotate = true; + scale = lbl_8040B284; + } else if (layer == SKY_LAYER_REFLECTION) { + rotate = true; + matrix->v2.z = lbl_8040B28C; + scale = lbl_8040B288; + scaleZ = lbl_8040B290; + } + + if (view_id - 0x10U < 6) { + scale = lbl_8040B294; + if (DrawSkyEnvMap == 0) { + return; + } + } else if (view_id == 3) { + scale = lbl_8040B298; + } + + matrix->v3.x = x; + matrix->v3.y = y; + matrix->v3.z = z; + + if (view_id - 1U < 3) { + if (DrawSky != 0) { + float skyScale = MainSkyScale * scale; + + matrix->v3.z = z + scaleZ; + matrix->v0.x *= skyScale; + matrix->v1.y *= skyScale; + matrix->v2.z *= skyScale; + if (rotate) { + bMatrix4 rotation; + int angle = matAng_33578 + static_cast(WorldTimeElapsed * lbl_8040B29C); + + matAng_33578 = static_cast(angle); + eCreateRotationZ(&rotation, static_cast(angle)); + eMulMatrix(matrix, &rotation, matrix); + } + + Render(view, &SkydomeModel, matrix, 0, 0x20000, 0); + } + } else { + matrix->v0.x *= scale; + matrix->v1.y *= scale; + matrix->v2.z *= scale; + matrix->v3.z = z + scaleZ; + Render(view, &SkydomeModel, matrix, 0, 0x20000, 0); + } + } + } +} + +void RefreshCurrentSkyTextures() { + for (int n = 0; n < 10; n++) { + CurrentSkyTextures[n] = GetTextureInfo(SkyHash[n], 1, 0); + } +} + +void InitSkyHash(void (*callback)(int), int callback_param) { + if (bSkyTexturesLoaded == 0) { + eTimeOfDay tod; + + bSkyTexturesLoaded = 1; + UserSkyLoadCallback = callback; + UserSkyLoadCallbackParam = callback_param; + tod = GetCurrentTimeOfDay(); + bMemSet(SkyHash, 0, 0x28); + BaseSkyHash[0] = bStringHash(lbl_8040B110); + BaseSkyHash[1] = bStringHash(lbl_8040B128); + + SkyHash[0] = bStringHash(lbl_8040B13C); + SkyHash[1] = bStringHash(lbl_8040B128); + SkyHash[2] = bStringHash(lbl_8040B110); + SkyHash[3] = bStringHash(lbl_8040B128); + SkyHash[4] = 0; + SkyHash[5] = 0; + SkyHash[6] = bStringHash(lbl_8040B154); + SkyHash[7] = bStringHash(lbl_8040B170); + SkyHash[8] = bStringHash(lbl_8040B198); + SkyHash[9] = bStringHash(lbl_8040B198); + + eLoadStreamingTexture(SkyHash, 10, SkyLoadCallback, 0, 0); + } +} + +void SkyLoadCallback(unsigned int param) { + RefreshCurrentSkyTextures(); + UserSkyLoadCallback(UserSkyLoadCallbackParam); + UserSkyLoadCallback = 0; +} + +void NotifySkyLoader() { + SkyInitModel(&SkydomeModel, &SkydomeLocalWorld, 0xBE43EDBB); + SkyInitModel(&SkySpecularModel, &SkySpecularLocalWorld, 0x90F70174); + SkySpecularLocalWorld.v2.z = -1.0f; + PSMTX44Copy(*reinterpret_cast(&SkydomeLocalWorld), *reinterpret_cast(&SkydomeLocalReflectedWorld)); + SkydomeLocalReflectedWorld.v2.z = -1.0f; +} + +void UnloadSkyTextures() { + if (bSkyTexturesLoaded != 0) { + eUnloadStreamingTexture(SkyHash, 10); + bSkyTexturesLoaded = 0; + } +} + +void NotifySkyUnloader() { + SkydomeModel.UnInit(); + PSMTX44Identity(*reinterpret_cast(&SkydomeLocalWorld)); + SkySpecularModel.UnInit(); + PSMTX44Identity(*reinterpret_cast(&SkySpecularLocalWorld)); +} + +void ReplaceSkyTextures(SKY_LAYER layer) { + SKYtextable[0].hOldNameHash = BaseSkyHash[0]; + SKYtextable[1].hOldNameHash = BaseSkyHash[1]; + + if (SkyHash[layer * 2] != SKYtextable[0].hNewNameHash) { + SKYtextable[0].SetNewNameHash(SkyHash[layer * 2]); + } + + if (SkyHash[layer * 2 + 1] != SKYtextable[1].hNewNameHash) { + SKYtextable[1].SetNewNameHash(SkyHash[layer * 2 + 1]); + } + + SKYtextable[0].pTextureInfo = reinterpret_cast(-1); + SKYtextable[1].pTextureInfo = reinterpret_cast(-1); + SkydomeModel.AttachReplacementTextureTable(SKYtextable, 2, 0); +} + +void GetLayerMod(eView *view, SKY_LAYER l, float *r, float *g, float *b, float *a) { + float weight = lbl_8040B2B8; + float invW = lbl_8040B2BC; + int env_map = *reinterpret_cast(reinterpret_cast(view) + 0x60); + + if (env_map != 0) { + weight = *reinterpret_cast(env_map + 0x50); + invW -= weight; + } + + if (SkyRenderForceOvercast > lbl_8040B2B8) { + weight = SkyRenderForceOvercast; + invW = lbl_8040B2BC - weight; + } + + *r = skylayer[l][0] * weight + skylayer[l][1] * invW; + *g = skylayer[l][2] * weight + skylayer[l][3] * invW; + *b = skylayer[l][4] * weight + skylayer[l][5] * invW; + *a = skylayer[l][6] * weight + skylayer[l][7] * invW; +} diff --git a/src/Speed/Indep/Src/World/SmackableRender.cpp b/src/Speed/Indep/Src/World/SmackableRender.cpp index 57db418d5..59d1c0cd1 100644 --- a/src/Speed/Indep/Src/World/SmackableRender.cpp +++ b/src/Speed/Indep/Src/World/SmackableRender.cpp @@ -15,30 +15,45 @@ // UNSOLVED, i hate constructors SmackableRenderConn::SmackableRenderConn(const Sim::ConnectionData &data /* r27 */) - : Sim::Connection(data), mTarget(0), mModelHash((unsigned int)0), mLOD(0), mModelOffset(bVector4(0.0f, 0.0f, 0.0f, 0.0f)) { + : Sim::Connection(data), mModelHash(), mTarget((*reinterpret_cast(&mModelHash) = 0, 0)), mModel(0), mLOD(0), + mModelOffset(bVector4(0.0f, 0.0f, 0.0f, 0.0f)) { this->mList.AddTail(this); RenderConn::Pkt_Smackable_Open *oc = Sim::Packet::Cast(data.pkt); - this->mTarget.Set(oc->mModelHash.GetValue()); + this->mTarget.Set(oc->mObjectWUID); this->mHeirarchy = oc->mHeirarchy; this->mModelHash = oc->mModelHash; - this->mRenderNode = oc->mRenderNode; - this->mModelHash = oc->mModelHash; - const CollisionGeometry::Bounds *bounds = oc->mCollisionNode; + this->mRenderNode = oc->mRenderNode; UMath::Vector3 pivot; bounds->GetPivot(pivot); + this->mModelOffset.x = -pivot.z; + this->mModelOffset.y = pivot.x; + this->mModelOffset.z = -pivot.y; +} + +Sim::Connection *SmackableRenderConn::Construct(const Sim::ConnectionData &data) { + return new SmackableRenderConn(data); } SmackableRenderConn::~SmackableRenderConn() { - if (this->mModel) { - delete this->mModel; + mList.Remove(this); + if (mModel) { + delete mModel; + mModel = nullptr; } - this->mTarget.Set(0); + mTarget.Set(0); +} + +void SmackableRenderConn::OnClose() { delete this; } +Sim::ConnStatus SmackableRenderConn::OnStatusCheck() { + return Sim::CONNSTATUS_READY; +} + SlotPool *SpaceNodeSlotPool = nullptr; // move elsewhere SlotPool *WorldModelSlotPool = nullptr; // move elsewhere @@ -86,7 +101,7 @@ void SmackableRenderConn::Update(float dT) { } void SmackableRenderConn::UpdateAll(float dT) { - for (SmackableRenderConn *w = mList.GetHead(); w != mList.GetHead(); w = w->GetNext()) { + for (SmackableRenderConn *w = mList.GetHead(); w != mList.EndOfList(); w = w->GetNext()) { w->Update(dT); } } @@ -100,4 +115,5 @@ void SmackableRender_Service(float dT) { } bTList SmackableRenderConn::mList; -// Prototype _SmackableRenderConn; +UTL::COM::Factory::Prototype _SmackableRenderConn("SmackableRenderConn", + SmackableRenderConn::Construct); diff --git a/src/Speed/Indep/Src/World/SmackableRender.hpp b/src/Speed/Indep/Src/World/SmackableRender.hpp index 42a4f1114..8a503e5f0 100644 --- a/src/Speed/Indep/Src/World/SmackableRender.hpp +++ b/src/Speed/Indep/Src/World/SmackableRender.hpp @@ -15,11 +15,25 @@ struct SmackableRenderConn : public Sim::Connection, public bTNode { public: + static Sim::Connection *Construct(const Sim::ConnectionData &data); + SmackableRenderConn(const Sim::ConnectionData &data); virtual ~SmackableRenderConn(); + void OnClose() override; + Sim::ConnStatus OnStatusCheck() override; void Update(float dT); static void UpdateAll(float dT); + void *operator new(unsigned int size) { + return gFastMem.Alloc(size, nullptr); + } + + void operator delete(void *mem, unsigned int size) { + if (mem) { + gFastMem.Free(mem, size, nullptr); + } + } + static bTList mList; private: diff --git a/src/Speed/Indep/Src/World/SpaceNode.cpp b/src/Speed/Indep/Src/World/SpaceNode.cpp index e69de29bb..da6c9057c 100644 --- a/src/Speed/Indep/Src/World/SpaceNode.cpp +++ b/src/Speed/Indep/Src/World/SpaceNode.cpp @@ -0,0 +1,128 @@ +#include "SpaceNode.hpp" + +#include "Speed/Indep/bWare/Inc/bMemory.hpp" +#include "Speed/Indep/bWare/Inc/bSlotPool.hpp" + +extern SlotPool *SpaceNodeSlotPool; + +SpaceNode *SpaceNode_Construct(SpaceNode *space_node, SpaceNode *parent) asm("__9SpaceNodeP9SpaceNode"); + +bTList SpaceNodeTrashList; + +SpaceNode::SpaceNode(SpaceNode *parent) { + bVector3 zero; + + this->ReferenceCount = 0; + this->Dirty = 1; + + PSMTX44Identity(*reinterpret_cast(&this->LocalMatrix)); + + this->Parent = 0; + bFill(&zero, 0.0f, 0.0f, 0.0f); + bCopy(&this->LocalVelocity, &zero); + bFill(&zero, 0.0f, 0.0f, 0.0f); + bCopy(&this->WorldAngularVelocity, &zero); + bCopy(&this->LocalAngularVelocity, &zero); + + this->SetParent(parent); + this->BlendingMatrices = 0; +} + +SpaceNode::~SpaceNode() { + this->RemoveAllChildren(); + this->RemoveFromParent(); +} + +void SpaceNode::SetParent(SpaceNode *new_parent) { + if (new_parent != this->Parent) { + this->RemoveFromParent(); + if (new_parent != 0) { + new_parent->AddChild(this); + } + } +} + +void SpaceNode::RemoveFromParent() { + if (this->Parent != 0) { + this->Parent->RemoveChild(this); + } +} + +void SpaceNode::AddChild(SpaceNode *child) { + child = this->ChildrenList.AddTail(child); + child->Parent = this; + this->Lock(); +} + +void SpaceNode::RemoveChild(SpaceNode *child) { + child = child->Remove(); + child->Parent = 0; + this->Unlock(); +} + +void SpaceNode::RemoveAllChildren() { + while (this->ChildrenList.GetHead() != this->ChildrenList.EndOfList()) { + this->RemoveChild(this->ChildrenList.GetHead()); + } +} + +void SpaceNode::Lock() { + this->ReferenceCount++; +} + +void SpaceNode::Unlock() { + this->ReferenceCount--; + if (this->ReferenceCount == 0) { + this->RemoveFromParent(); + SpaceNodeTrashList.AddTail(this); + } +} + +void SpaceNode::ReallySetDirty() { + this->Dirty = 1; + + for (SpaceNode *child = this->ChildrenList.GetHead(); child != this->ChildrenList.EndOfList(); child = child->GetNext()) { + if (!child->Dirty) { + child->ReallySetDirty(); + } + } +} + +void SpaceNode::Update() { + bVector3 rotated_velocity; + + if (Parent) { + bMulMatrix(&WorldMatrix, Parent->GetWorldMatrix(), &LocalMatrix); + bMulMatrix(&rotated_velocity, Parent->GetWorldMatrix(), &LocalVelocity); + rotated_velocity -= *reinterpret_cast(&Parent->GetWorldMatrix()->v3); + WorldVelocity = rotated_velocity + *Parent->GetWorldVelocity(); + } else { + PSMTX44Copy(*reinterpret_cast(&LocalMatrix), *reinterpret_cast(&WorldMatrix)); + WorldVelocity = LocalVelocity; + } + + Dirty = 0; +} + +SpaceNode *CreateSpaceNode(SpaceNode *parent) { + SpaceNode *space_node = SpaceNode_Construct(static_cast(bOMalloc(SpaceNodeSlotPool)), parent); + + space_node->Lock(); + return space_node; +} + +void DeleteSpaceNode(SpaceNode *space_node) { + space_node->Unlock(); +} + +void ServiceSpaceNodes() { + while (!SpaceNodeTrashList.IsEmpty()) { + SpaceNode *head = SpaceNodeTrashList.RemoveHead(); + + delete head; + } +} + +void InitSpaceNodes() { + SpaceNodeSlotPool = bNewSlotPool(0xF4, 0x50, "SpaceNodeSlotPool", GetVirtualMemoryAllocParams()); +} diff --git a/src/Speed/Indep/Src/World/SpaceNode.hpp b/src/Speed/Indep/Src/World/SpaceNode.hpp index 2d1fe23a2..de37d0ca7 100644 --- a/src/Speed/Indep/Src/World/SpaceNode.hpp +++ b/src/Speed/Indep/Src/World/SpaceNode.hpp @@ -8,14 +8,21 @@ #include "Speed/Indep/Src/Misc/Replay.hpp" #include "Speed/Indep/bWare/Inc/bList.hpp" #include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "Speed/Indep/bWare/Inc/bSlotPool.hpp" #include +extern SlotPool *SpaceNodeSlotPool; + class SpaceNode : public bTNode { public: - // void *operator new(unsigned int size) {} + void *operator new(unsigned int size) { + return bOMalloc(SpaceNodeSlotPool); + } - // void operator delete(void *ptr) {} + void operator delete(void *ptr) { + bFree(SpaceNodeSlotPool, ptr); + } void SetNameHash(unsigned int name_hash) { NameHash = name_hash; @@ -134,6 +141,7 @@ class SpaceNode : public bTNode { SpaceNode *Parent; // offset 0xE0, size 0x4 unsigned int Dirty; // offset 0xE4, size 0x4 bMatrix4 *BlendingMatrices; // offset 0xE8, size 0x4 + unsigned int pad1; // offset 0xEC, size 0x4 }; void InitSpaceNodes(); diff --git a/src/Speed/Indep/Src/World/TimeOfDay.cpp b/src/Speed/Indep/Src/World/TimeOfDay.cpp index e69de29bb..97e4fa809 100644 --- a/src/Speed/Indep/Src/World/TimeOfDay.cpp +++ b/src/Speed/Indep/Src/World/TimeOfDay.cpp @@ -0,0 +1,43 @@ +#include "Speed/Indep/Src/World/World.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" + +eTimeOfDay gTheTimeOfDay; +static eTimeOfDay desiredTOD; +int TimeOfDaySwapEnable; + +const char* GetTimeOfDaySuffix(eTimeOfDay tod) { + if (tod == eTOD_SUNSET) { + return "_Sunset"; + } + if (tod == eTOD_MIDDAY) { + return ""; + } + return "THIS_IS_NOT_A_TIME_OF_DAY_SUFFIX"; +} + +bool NeedsSeperateTODStreamingFile(const char* platform_name) { + if (bStrICmp(platform_name, "PSX2") == 0 || bStrICmp(platform_name, "XBOX") == 0) { + return true; + } + return false; +} + +eTimeOfDay GetCurrentTimeOfDay() { + return gTheTimeOfDay; +} + +void TickOverTimeOfday() { +} + +void ApplyTimeOfDayTickOver() { + if (TimeOfDaySwapEnable == 0) { + return; + } + if (gTheTimeOfDay == desiredTOD) { + return; + } + gTheTimeOfDay = desiredTOD; +} + +void SetCurrentTimeOfDay(float tod) { +} 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..a4a348e97 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; + + 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 *last_zone = zone_info->pLastZone; + TrackPathZone *first_zone; + + 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..ca047b45a 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(i + pInfo->FileSize); + } + 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->AmountLoaded = 0; + 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; + } + + CurrentZoneFarLoad = true; + StartLoadingTime = 0.0f; + CurrentZoneOutOfMemory = false; + CurrentZoneAllocatedButIncomplete = false; + CurrentZoneNonReplayLoad = false; + LoadingPhase = LOADING_IDLE; + LoadingBacklog = 0.0f; + CurrentZoneName[0] = 0; + NumJettisonedSections = 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) { + if (section->Status == TrackStreamingSection::ACTIVATED) { + UnactivateSection(section); + } + + if (section->Status == TrackStreamingSection::LOADED) { + if (WillUnloadBlock(section)) { + WaitForFrameBufferSwapDisabled = 1; + eWaitUntilRenderingDone(); + WaitForFrameBufferSwapDisabled = 0; + LastWaitUntilRenderingDoneFrameCount = eFrameCounter; + } + + 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; + CurrentZoneOutOfMemory = false; + CurrentZoneAllocatedButIncomplete = false; + MemorySafetyMargin = 0; + AmountJettisoned = 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/VehicleFX.cpp b/src/Speed/Indep/Src/World/VehicleFX.cpp index e69de29bb..a589cc178 100644 --- a/src/Speed/Indep/Src/World/VehicleFX.cpp +++ b/src/Speed/Indep/Src/World/VehicleFX.cpp @@ -0,0 +1,32 @@ +#include "Speed/Indep/Src/Physics/PhysicsTypes.h" +#include "Speed/Indep/Tools/AttribSys/Runtime/AttribHash.h" +#include "Speed/Indep/Libs/Support/Utility/UCrc.h" + +namespace VehicleFX { + +struct Maps { + ID id; + Attrib::StringKey name; + unsigned int marker; +}; + +Maps vehicle_fx_maps[22] asm("_9VehicleFX.vehicle_fx_maps"); + +const Maps *GetMaps() { + return vehicle_fx_maps; +} + +ID LookupID(UCrc32 name) { + const Maps *fx = GetMaps(); + + while (fx->id != LIGHT_NONE) { + if (fx->name.GetHash32() == name.GetValue()) { + return fx->id; + } + fx++; + } + + return LIGHT_NONE; +} + +} diff --git a/src/Speed/Indep/Src/World/VehicleFragmentConn.cpp b/src/Speed/Indep/Src/World/VehicleFragmentConn.cpp index e69de29bb..c8542fbe3 100644 --- a/src/Speed/Indep/Src/World/VehicleFragmentConn.cpp +++ b/src/Speed/Indep/Src/World/VehicleFragmentConn.cpp @@ -0,0 +1,172 @@ +#include "./VehicleFragmentConn.h" +#include "./CarInfo.hpp" +#include "./WorldModel.hpp" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Src/Physics/Bounds.h" +#include "Speed/Indep/bWare/Inc/bMemory.hpp" + +int GetCarPartIDFromCrc(UCrc32 part_name); +const CollisionGeometry::Bounds *CollisionGeometry_Collection_GetBounds(const CollisionGeometry::Collection *collection, UCrc32 name_hash) + asm("GetBounds__CQ217CollisionGeometry10CollectionG6UCrc32"); +void OrthoInverse(UMath::Matrix4 &m); +extern CarTypeInfo *CarTypeInfoArray; + +Sim::Connection *VehicleFragmentConn::Construct(const Sim::ConnectionData &data) { + return new VehicleFragmentConn(data); +} + +VehicleFragmentConn::VehicleFragmentConn(const Sim::ConnectionData &data) + : Sim::Connection(data), // + mTarget(0), // + mColName(), // + mModelHash(0), // + mModel(0), // + mReplacementTextureTable(0), // + mLightMaterial(0) { + mList.AddTail(this); + + RenderConn::Pkt_VehicleFragment_Open *oc = + static_cast(data.pkt); + mTarget.Set(oc->mWorldID); + mVehicleWorldID = oc->mVehicleWorldID; + mPartSlot = static_cast(GetCarPartIDFromCrc(oc->mPartName)); + mColName = oc->mColName; + + bIdentity(&mModelOffset); +} + +VehicleFragmentConn::~VehicleFragmentConn() { + mList.Remove(this); + if (mPartSlot != CARPARTID_INVALID) { + mPartSlot = CARPARTID_INVALID; + } + if (mReplacementTextureTable) { + delete[] mReplacementTextureTable; + } + if (mModel) { + delete mModel; + mModel = nullptr; + } +} + +void VehicleFragmentConn::OnClose() { + delete this; +} + +Sim::ConnStatus VehicleFragmentConn::OnStatusCheck() { + return Sim::CONNSTATUS_READY; +} + +void VehicleFragmentConn::UpdateModel() { + if (this->mPartSlot == CARPARTID_INVALID) { + return; + } + + bool has_target_matrix = this->mTarget.GetMatrix() != 0; + if (!has_target_matrix || this->mVehicleWorldID == 0) { + return; + } + + if (this->mModelHash == 0) { + VehicleRenderConn *vehicle_render_conn = VehicleRenderConn::Find(this->mVehicleWorldID); + if (vehicle_render_conn == 0 || vehicle_render_conn->mState <= VehicleRenderConn::S_Loading) { + return; + } + + CarRenderInfo *car_render_info = vehicle_render_conn->GetRenderInfo(); + if (this->mReplacementTextureTable == 0) { + this->mReplacementTextureTable = new eReplacementTextureTable[CarRenderInfo::REPLACETEX_NUM]; + bMemCpy(this->mReplacementTextureTable, car_render_info->MasterReplacementTextureTable, sizeof(car_render_info->MasterReplacementTextureTable)); + } + if (this->mLightMaterial == 0) { + this->mLightMaterial = car_render_info->LightMaterial_CarSkin; + } + + this->mModelHash = vehicle_render_conn->FindPart(this->mPartSlot); + + CarTypeInfo *car_type_info = CarTypeInfoArray + vehicle_render_conn->GetCarType(); + const CollisionGeometry::Collection *col = + reinterpret_cast(CollisionGeometry::Lookup(UCrc32(car_type_info->GetName()))); + if (col != 0) { + const CollisionGeometry::Bounds *root = CollisionGeometry_Collection_GetBounds(col, this->mColName); + if (root != 0) { + UMath::Matrix4 tmp; + UMath::Vector4 root_orientation; + UMath::Vector3 root_pivot; + + root_orientation.x = static_cast(root->fOrientation.x) * 3.051851e-05f; + root_orientation.y = static_cast(root->fOrientation.y) * 3.051851e-05f; + root_orientation.z = static_cast(root->fOrientation.z) * 3.051851e-05f; + root_orientation.w = static_cast(root->fOrientation.w) * 3.051851e-05f; + root_pivot.x = static_cast(root->fPivot.x) * 0.001f; + root_pivot.y = static_cast(root->fPivot.y) * 0.001f; + root_pivot.z = static_cast(root->fPivot.z) * 0.001f; + + UMath::QuaternionToMatrix4(root_orientation, tmp); + tmp.v3 = UMath::Vector4Make(root_pivot, 1.0f); + OrthoInverse(tmp); + eSwizzleWorldMatrix(reinterpret_cast(tmp), this->mModelOffset); + } + } + + if (this->mModelHash == 0) { + this->mPartSlot = CARPARTID_INVALID; + return; + } + } + + PSMTX44Copy(*reinterpret_cast(this->mTarget.GetMatrix()), *reinterpret_cast(&this->mRenderMatrix)); + + bMatrix4 matrix; + eMulMatrix(&matrix, &this->mModelOffset, &this->mRenderMatrix); + + if (this->mModel == 0) { + this->mModel = new WorldModel(this->mModelHash, &matrix, true); + if (this->mReplacementTextureTable != 0) { + this->mModel->AttachReplacementTextureTable(this->mReplacementTextureTable, CarRenderInfo::REPLACETEX_NUM); + } + if (this->mLightMaterial != 0) { + this->mModel->AttachLightMaterial(this->mLightMaterial, 0xD6D6080A); + } + } else { + this->mModel->SetEnabledFlag(true); + this->mModel->SetMatrix(&matrix); + } +} + +void VehicleFragmentConn::Update(float dT) { + bool inview = false; + + if (mModel) { + if (mModel->GetLastVisibleFrame() >= mModel->GetLastRenderFrame() && mModel->GetLastRenderFrame() != 0) { + inview = true; + } + } + + float disttoview; + if (mModel) { + disttoview = mModel->DistanceToGameView(); + } else { + disttoview = 0.0f; + } + + RenderConn::Pkt_VehicleFragment_Service pkt(inview, disttoview); + if (Service(&pkt)) { + UpdateModel(); + } else { + if (mModel) { + mModel->SetEnabledFlag(false); + } + } +} + +void VehicleFragmentConn::FetchData(float dT) { + for (VehicleFragmentConn *conn = mList.GetHead(); conn != mList.EndOfList(); conn = conn->GetNext()) { + conn->Update(dT); + } +} + +bTList VehicleFragmentConn::mList asm("TheVehcileFrags"); + +UTL::COM::Factory::Prototype _VehicleFragmentConn("VehicleFragmentConn", + VehicleFragmentConn::Construct); diff --git a/src/Speed/Indep/Src/World/VehicleFragmentConn.h b/src/Speed/Indep/Src/World/VehicleFragmentConn.h index ccd2e827d..cebd31a05 100644 --- a/src/Speed/Indep/Src/World/VehicleFragmentConn.h +++ b/src/Speed/Indep/Src/World/VehicleFragmentConn.h @@ -5,6 +5,115 @@ #pragma once #endif +#include "Speed/Indep/Src/World/VehicleRenderConn.h" +class WorldModel; +struct eLightMaterial; +struct eReplacementTextureTable; + +namespace RenderConn { + +class Pkt_VehicleFragment_Open : public Sim::Packet { + public: + Pkt_VehicleFragment_Open(unsigned int worldid_part, unsigned int worldid_vehicle, UCrc32 partname, UCrc32 collision_node) + : mVehicleWorldID(worldid_vehicle), // + mWorldID(worldid_part), // + mPartName(partname), // + mColName(collision_node) {} + + UCrc32 ConnectionClass() override { + static UCrc32 hash("VehicleFragmentConn"); + return hash; + } + + unsigned int Size() override { + return 0x14; + } + + static unsigned int SType() { + static UCrc32 hash("Pkt_VehicleFragment_Open"); + return hash.GetValue(); + } + + unsigned int Type() override { + return SType(); + } + + ~Pkt_VehicleFragment_Open() override {} + + unsigned int mVehicleWorldID; // offset 0x4, size 0x4 + unsigned int mWorldID; // offset 0x8, size 0x4 + UCrc32 mPartName; // offset 0xC, size 0x4 + UCrc32 mColName; // offset 0x10, size 0x4 +}; + +class Pkt_VehicleFragment_Service : public Sim::Packet { + public: + Pkt_VehicleFragment_Service(bool inview, float disttoview) + : mInView(inview), // + mDistanceToView(disttoview) {} + + UCrc32 ConnectionClass() override { + static UCrc32 hash("VehicleFragmentConn"); + return hash; + } + + unsigned int Size() override { + return 0xC; + } + + static unsigned int SType() { + static UCrc32 hash("Pkt_VehicleFragment_Service"); + return hash.GetValue(); + } + + unsigned int Type() override { + return SType(); + } + + bool InView() const { + return this->mInView; + } + + float DistanceToView() const { + return this->mDistanceToView; + } + + ~Pkt_VehicleFragment_Service() override {} + + bool mInView; // offset 0x4, size 0x1 + float mDistanceToView; // offset 0x8, size 0x4 +}; + +} // namespace RenderConn + +class VehicleFragmentConn : public Sim::Connection, public bTNode { + public: + static Sim::Connection *Construct(const Sim::ConnectionData &data); + + VehicleFragmentConn(const Sim::ConnectionData &data); + ~VehicleFragmentConn() override; + + void OnClose() override; + Sim::ConnStatus OnStatusCheck() override; + + void Update(float dT); + static void FetchData(float dT); + void UpdateModel(); + + static bTList mList; + + private: + Reference mTarget; // offset 0x18, size 0x10 + unsigned int mVehicleWorldID; // offset 0x28, size 0x4 + CAR_PART_ID mPartSlot; // offset 0x2C, size 0x4 + UCrc32 mColName; // offset 0x30, size 0x4 + int mModelHash; // offset 0x34, size 0x4 + WorldModel *mModel; // offset 0x38, size 0x4 + bMatrix4 mModelOffset; // offset 0x3C, size 0x40 + bMatrix4 mRenderMatrix; // offset 0x7C, size 0x40 + eReplacementTextureTable *mReplacementTextureTable; // offset 0xBC, size 0x4 + eLightMaterial *mLightMaterial; // offset 0xC0, size 0x4 +}; #endif diff --git a/src/Speed/Indep/Src/World/VehiclePartDamage.cpp b/src/Speed/Indep/Src/World/VehiclePartDamage.cpp index e69de29bb..53cbcc67e 100644 --- a/src/Speed/Indep/Src/World/VehiclePartDamage.cpp +++ b/src/Speed/Indep/Src/World/VehiclePartDamage.cpp @@ -0,0 +1,639 @@ +#include "Speed/Indep/Src/World/Interfaces/IVehicleDamageBehaviour.h" +#include "Speed/Indep/Src/World/CarRender.hpp" +#include "Speed/Indep/Src/World/CarInfo.hpp" +#include "Speed/Indep/Libs/Support/Utility/UStandard.h" +#include "Speed/Indep/Libs/Support/Utility/UMath.h" +#include "Speed/Indep/bWare/Inc/bSlotPool.hpp" + +typedef float Mtx44[4][4]; + +extern SlotPool *VehicleDamagePartSlotPool; +extern SlotPool *VehiclePartDamageZoneSlotPool; + +struct VehiclePartDamageZone { + struct DamageZoneSlotMapDataType { + int ZoneId; + int SlotId; + }; + + int mZoneId; + unsigned short mDamageLevel; + unsigned short pad; + int *mSlotIdsStart; + int *mSlotIdsEnd; + int *mSlotIdsReserved; + int *mSlotIdsCapacityEnd; + + static void operator delete(void *ptr) { + bFree(VehiclePartDamageZoneSlotPool, ptr); + } + + VehiclePartDamageZone(int zoneId, DamageZoneSlotMapDataType *zoneSlotMappingDataList); + ~VehiclePartDamageZone(); + void Reset(); + int GetSlotNum() const; + int GetSlotID(int index) const; + void SetDamageLevel(unsigned short damageLevel); +}; + +struct CarPart; +struct CarPartDatabase; + +struct VehicleDamagePart { + unsigned short mDamageLevel; + unsigned short pad; + int mSlotId; + CarPart *mReplacementParts[2]; + float mAnimationPivot[3]; + bMatrix4 mMatrix; + int mAttached; + bool mHidden; + + static void operator delete(void *ptr) { + bFree(VehicleDamagePartSlotPool, ptr); + } + + inline void SetPivot(float x, float y, float z) { + mAnimationPivot[0] = x; + mAnimationPivot[1] = y; + mAnimationPivot[2] = z; + } + + inline bool IsHidden() const { + return mHidden; + } + + inline bMatrix4 *GetMatrix() { + return &mMatrix; + } + + inline unsigned short GetDamageLevel() const { + return mDamageLevel; + } + + inline void SetDamageLevel(unsigned short damageLevel) { + mDamageLevel = damageLevel; + } + + inline CarPart *GetDamagePart(int ix) { + return mReplacementParts[ix]; + } + + inline int GetSlotID() const { + return mSlotId; + } + + inline const UMath::Vector3 &GetPivot() const { + return *reinterpret_cast(mAnimationPivot); + } + + VehicleDamagePart(CarRenderInfo *carRenderInfo, int slotId); + ~VehicleDamagePart(); + void Reset(); +}; + +extern CarPartDatabase CarPartDB; +extern unsigned int unitTestDelay; +extern unsigned int uniTestLevel; +extern "C" void PSMTX44Copy(const Mtx44 src, Mtx44 dst); +extern const char lbl_8040BC8C[] asm("lbl_8040BC8C"); +extern const char lbl_8040D09C[] asm("lbl_8040D09C"); +extern const char lbl_8040D0A4[] asm("lbl_8040D0A4"); +extern const char lbl_8040D0B0[] asm("lbl_8040D0B0"); +extern const char lbl_8040D0BC[] asm("lbl_8040D0BC"); +extern const char lbl_8040D0CC[] asm("lbl_8040D0CC"); +extern const char lbl_8040D0DC[] asm("lbl_8040D0DC"); +extern float lbl_8040D0E8; +extern float lbl_8040D0EC; +extern float lbl_8040D0F0; +extern float lbl_8040D0F4; +extern float lbl_8040D0F8; +extern float lbl_8040D100; +extern float lbl_8040D104; +extern float lbl_8040D108; +extern float lbl_8040D10C; +extern float lbl_8040D110; +extern float lbl_8040D114; +extern float lbl_8040D118; +extern float lbl_8040D11C; +extern float lbl_8040D120; +extern float lbl_8040D124; +extern float lbl_8040D128; +extern float lbl_8040D12C; +extern float lbl_8040D130; +extern float lbl_8040D134; +extern float lbl_8040D138; +extern float lbl_8040D13C; +extern float lbl_8040D140; +extern float lbl_8040D144; +extern float lbl_8040D14C; +extern const char lbl_8040D154[] asm("lbl_8040D154"); +extern const char lbl_8040D170[] asm("lbl_8040D170"); +extern CarPart *CarPartDatabase_NewGetNextCarPart( + CarPartDatabase *carPartDB, + CarPart *car_part, + CarType car_type, + int car_slot_id, + unsigned int car_part_namehash, + int upgrade_level) + asm("NewGetNextCarPart__15CarPartDatabaseP7CarPart7CarTypeiUii"); + +extern VehicleDamagePart *VehicleDamagePart_ctor(VehicleDamagePart *part, CarRenderInfo *carRenderInfo, int slotId) + asm("__17VehicleDamagePartP13CarRenderInfoi"); +extern void VehicleDamagePart_dtor(VehicleDamagePart *part, int in_chrg) asm("_._17VehicleDamagePart"); +extern void VehicleDamagePart_Reset(VehicleDamagePart *part) asm("Reset__17VehicleDamagePart"); + +extern VehiclePartDamageZone *VehiclePartDamageZone_ctor( + VehiclePartDamageZone *zone, + int zoneId, + void *zoneSlotMappingDataList) + asm("__21VehiclePartDamageZoneiPQ221VehiclePartDamageZone25DamageZoneSlotMapDataType"); +extern void VehiclePartDamageZone_dtor(VehiclePartDamageZone *zone, int in_chrg) asm("_._21VehiclePartDamageZone"); +extern void VehiclePartDamageZone_Reset(VehiclePartDamageZone *zone) asm("Reset__21VehiclePartDamageZone"); +extern int VehiclePartDamageZone_GetSlotNum(const VehiclePartDamageZone *zone) asm("GetSlotNum__C21VehiclePartDamageZone"); +extern int VehiclePartDamageZone_GetSlotID(const VehiclePartDamageZone *zone, int index) asm("GetSlotID__C21VehiclePartDamageZonei"); +extern void VehiclePartDamageZone_SetDamageLevel(VehiclePartDamageZone *zone, unsigned short damageLevel) + asm("SetDamageLevel__21VehiclePartDamageZoneUs"); + +IVehiclePartDamageBehaviour::~IVehiclePartDamageBehaviour() {} + +VehiclePartDamageZone::VehiclePartDamageZone(int zoneId, DamageZoneSlotMapDataType *zoneSlotMappingDataList) { + int zoneSlotIndex; + int currentZoneId; + int slotId; + + mZoneId = zoneId; + mDamageLevel = 0; + mSlotIdsStart = 0; + mSlotIdsEnd = 0; + mSlotIdsCapacityEnd = 0; + reinterpret_cast *>(&mSlotIdsStart)->reserve(0x14); + + if (zoneSlotMappingDataList != 0) { + zoneSlotIndex = 0; + do { + currentZoneId = zoneSlotMappingDataList[zoneSlotIndex].ZoneId; + if (currentZoneId == mZoneId) { + slotId = zoneSlotMappingDataList[zoneSlotIndex].SlotId; + reinterpret_cast *>(&mSlotIdsStart)->push_back(slotId); + } + zoneSlotIndex++; + } while (currentZoneId != 0xFFFF); + } +} + +VehiclePartDamageZone::~VehiclePartDamageZone() { + reinterpret_cast *>(&mSlotIdsStart)->clear(); + reinterpret_cast *>(&mSlotIdsStart)->~vector(); +} + +void VehiclePartDamageZone::Reset() { + mDamageLevel = 0; +} + +int VehiclePartDamageZone::GetSlotNum() const { + return mSlotIdsEnd - mSlotIdsStart; +} + +int VehiclePartDamageZone::GetSlotID(int index) const { + int offset = index * sizeof(int); + unsigned char *slot_ids = reinterpret_cast(mSlotIdsStart); + return *reinterpret_cast(slot_ids + offset); +} + +void VehiclePartDamageZone::SetDamageLevel(unsigned short damageLevel) { + mDamageLevel = damageLevel; +} + +VehicleDamagePart::VehicleDamagePart(CarRenderInfo *carRenderInfo, int slotId) { + int replacementIndex; + RideInfo *rideInfo; + + mDamageLevel = 0; + mSlotId = slotId; + mAttached = 1; + mHidden = 0; + mAnimationPivot[0] = 0.0f; + mAnimationPivot[1] = 0.0f; + mAnimationPivot[2] = 0.0f; + PSMTX44Identity(*reinterpret_cast(&mMatrix)); + + if (carRenderInfo != 0 && (rideInfo = carRenderInfo->pRideInfo) != 0) { + replacementIndex = 1; + mReplacementParts[0] = rideInfo->GetPart(mSlotId); + do { + if (mReplacementParts[replacementIndex - 1] != 0) { + mReplacementParts[replacementIndex] = + CarPartDatabase_NewGetNextCarPart(&CarPartDB, mReplacementParts[replacementIndex - 1], rideInfo->Type, mSlotId, 0, -1); + } + replacementIndex++; + } while (replacementIndex < 2); + } +} + +VehicleDamagePart::~VehicleDamagePart() { + int replacementIndex; + + replacementIndex = 0; + do { + mReplacementParts[replacementIndex] = 0; + replacementIndex++; + } while (replacementIndex < 2); +} + +void VehicleDamagePart::Reset() { + mDamageLevel = 0; + mHidden = 0; + mAttached = 1; +} + +void InitVehicleDamage() { + VehicleDamagePartSlotPool = bNewSlotPool(0x68, 0xF0, lbl_8040D154, 0); + VehiclePartDamageZoneSlotPool = bNewSlotPool(0x18, 100, lbl_8040D170, 0); +} + +ePositionMarker *VehiclePartDamageBehaviour::FindPositionMarker(const char *findPivotName) { + return 0; +} + +VehiclePartDamageBehaviour::VehiclePartDamageBehaviour(CarRenderInfo *carRenderInfo) + : mCarRenderInfo(carRenderInfo), // + mDamageZoneNum(0), // + mTrunkVel(0.0f), // + mTrunkAngle(0.0f) { + unsigned int slotId; + int zoneId; + + for (slotId = 0; slotId < 0x17; slotId++) { + this->mDamagePartList[slotId] = VehicleDamagePart_ctor( + static_cast(bOMalloc(VehicleDamagePartSlotPool)), + carRenderInfo, + static_cast(slotId)); + } + + for (zoneId = 0; zoneId < 10; zoneId++) { + unsigned int zoneIndex = this->mDamageZoneNum; + this->mDamageZoneNum = zoneIndex + 1; + this->mDamageZoneList[zoneIndex] = + VehiclePartDamageZone_ctor( + static_cast(bOMalloc(VehiclePartDamageZoneSlotPool)), + zoneId, + mSlotZoneMapList); + } +} + +VehiclePartDamageBehaviour::~VehiclePartDamageBehaviour() { + unsigned int slotId; + + for (slotId = 0; slotId < 0x17; slotId++) { + if (this->mDamagePartList[slotId] != 0) { + VehicleDamagePart_dtor(this->mDamagePartList[slotId], 3); + this->mDamagePartList[slotId] = 0; + } + } + + for (slotId = 0; slotId < this->mDamageZoneNum; slotId++) { + if (this->mDamageZoneList[slotId] != 0) { + VehiclePartDamageZone_dtor(this->mDamageZoneList[slotId], 3); + this->mDamageZoneList[slotId] = 0; + } + } +} + +void VehiclePartDamageBehaviour::Reset() { + unsigned int slotId; + + for (slotId = 0; slotId < 0x17; slotId++) { + VehicleDamagePart_Reset(this->mDamagePartList[slotId]); + } + + for (slotId = 0; slotId < this->mDamageZoneNum; slotId++) { + VehiclePartDamageZone_Reset(this->mDamageZoneList[slotId]); + this->DamageZone(static_cast(slotId), 0); + } + + this->ApplyDamage(); +} + +void VehiclePartDamageBehaviour::DamageZone(int zone, int damageLevel) { + VehiclePartDamageZone *damageZone = this->mDamageZoneList[zone]; + + if (damageZone) { + damageZone->SetDamageLevel(static_cast(damageLevel)); + + for (int slotIx = 0; slotIx < damageZone->GetSlotNum(); slotIx++) { + int slotID = damageZone->GetSlotID(slotIx); + VehicleDamagePart *damagePart = this->mDamagePartList[slotID]; + + if (damagePart) { + int partDamageLevel = UMath::Max(damageLevel, static_cast(damagePart->GetDamageLevel())); + partDamageLevel = UMath::Min(partDamageLevel, 1); + damagePart->SetDamageLevel(static_cast(partDamageLevel)); + + if (this->mCarRenderInfo->pRideInfo) { + CarPart *newCarPart = damagePart->GetDamagePart(partDamageLevel); + this->mCarRenderInfo->pRideInfo->SetPart(slotID, newCarPart, false); + } + + this->ManageGlassDamage(); + } + } + } +} + +void VehiclePartDamageBehaviour::ApplyDamage() { + if (this->mCarRenderInfo != 0) { + if (this->mCarRenderInfo->pRideInfo != 0) { + this->mCarRenderInfo->pRideInfo->UpdatePartsEnabled(); + } + + this->mCarRenderInfo->UpdateCarParts(); + } +} + +bMatrix4 *VehiclePartDamageBehaviour::GetPartMatrix(unsigned int slotId) { + if (slotId <= 0x17) { + return mDamagePartList[slotId]->GetMatrix(); + } + return nullptr; +} + +bool VehiclePartDamageBehaviour::IsPartHidden(unsigned int slotId) { + if (slotId <= 0x16) { + return mDamagePartList[slotId]->IsHidden(); + } + return true; +} + +void VehiclePartDamageBehaviour::HidePart(unsigned int slotId) { + if (slotId > 0x17) { + return; + } + + *reinterpret_cast(reinterpret_cast(this->mDamagePartList[slotId]) + 0x60) = 1; +} + +void VehiclePartDamageBehaviour::Init() { + float *pivot; + + this->InitAnimationPivot(CARSLOTID_DAMAGE_HOOD, lbl_8040BC8C); + this->InitAnimationPivot(CARSLOTID_DAMAGE_TRUNK, lbl_8040D09C); + this->InitAnimationPivot(CARSLOTID_DAMAGE_LEFT_DOOR, lbl_8040D0A4); + this->InitAnimationPivot(CARSLOTID_DAMAGE_RIGHT_DOOR, lbl_8040D0B0); + this->InitAnimationPivot(CARSLOTID_DAMAGE_LEFT_REAR_DOOR, lbl_8040D0BC); + this->InitAnimationPivot(CARSLOTID_DAMAGE_RIGHT_REAR_DOOR, lbl_8040D0CC); + this->InitAnimationPivot(CARSLOTID_DAMAGE_REAR_BUMPER, lbl_8040D0DC); + + pivot = reinterpret_cast(reinterpret_cast(this->mDamagePartList[CARSLOTID_DAMAGE_TRUNK]) + 0x10); + pivot[1] = lbl_8040D0EC; + pivot[0] = lbl_8040D0E8; + pivot[2] = lbl_8040D0F0; + + pivot = reinterpret_cast(reinterpret_cast(this->mDamagePartList[CARSLOTID_DAMAGE_HOOD]) + 0x10); + pivot[1] = lbl_8040D0F8; + pivot[0] = lbl_8040D0F4; + pivot[2] = lbl_8040D0F0; +} + +void VehiclePartDamageBehaviour::ManageGlassDamage() { + int windowIx; + + for (windowIx = 0; windowIx <= 4; windowIx++) { + const BreakableWindowInfoDataType &windowInfo = mBreakableWindowInfoList[windowIx]; + VehicleDamagePart *damagePart = this->mDamagePartList[windowInfo.mPartSlotId]; + int currentDamageState = static_cast(*reinterpret_cast(damagePart)); + int damageState = 1; + + if (damageState > currentDamageState) { + damageState = currentDamageState; + } + + if (this->mCarRenderInfo != 0) { + if (damageState > 0) { + unsigned int damageHash = 0x0A155545; + this->mCarRenderInfo->MasterReplacementTextureTable[windowInfo.mReplaceTexId] + .SetNewNameHash(damageHash); + this->mCarRenderInfo->MasterReplacementTextureTable[CarRenderInfo::REPLACETEX_WINDOW_REAR_DEFOST] + .SetNewNameHash(damageHash); + } else { + this->mCarRenderInfo->MasterReplacementTextureTable[windowInfo.mReplaceTexId] + .SetNewNameHash(windowInfo.mOriginalHash); + } + } + } +} + +void VehiclePartDamageBehaviour::InitAnimationPivot(unsigned int slotId, const char * markerName) { + if (this->FindPositionMarker(markerName) != nullptr) { + this->mDamagePartList[slotId]->SetPivot(0.0f, 0.0f, 0.0f); + } +} + +void VehiclePartDamageBehaviour::Pose(bMatrix4 *worldMatrix) { + unsigned int partIx; + + for (partIx = 0; partIx < 0x17; partIx++) { + PSMTX44Copy( + *reinterpret_cast(worldMatrix), + *reinterpret_cast(reinterpret_cast(this->mDamagePartList[partIx]) + 0x1C)); + } +} + +void VehiclePartDamageBehaviour::DamageVehicle(const DamageZone::Info &damageInfo) { + unsigned int zoneIx; + + for (zoneIx = 0; zoneIx < this->mDamageZoneNum; zoneIx++) { + this->DamageZone(static_cast(zoneIx), static_cast(damageInfo.Get(static_cast(zoneIx)))); + } + + this->ApplyDamage(); +} + +void VehiclePartDamageBehaviour::Update(bMatrix4 *worldMatrix) { + this->Pose(worldMatrix); + + if (*reinterpret_cast(this->mDamagePartList[CARSLOTID_DAMAGE_TRUNK]) == 1) { + float angle = this->CalcPartRotation( + worldMatrix, + lbl_8040D100 * lbl_8040D104, + lbl_8040D108, + lbl_8040D10C, + lbl_8040D110) * + lbl_8040D114; + const bVector3 trunkRotation(lbl_8040D118, angle, lbl_8040D118); + this->AnimatePart(CARSLOTID_DAMAGE_TRUNK, trunkRotation, worldMatrix); + } + + if (*reinterpret_cast(this->mDamagePartList[CARSLOTID_DAMAGE_HOOD]) == 1) { + float angle = -this->CalcPartRotation( + worldMatrix, + lbl_8040D100 * lbl_8040D104, + lbl_8040D108, + lbl_8040D10C, + lbl_8040D11C) * + lbl_8040D114; + const bVector3 hoodRotation(lbl_8040D118, angle, lbl_8040D118); + this->AnimatePart(CARSLOTID_DAMAGE_HOOD, hoodRotation, worldMatrix); + } + + if (*reinterpret_cast(this->mDamagePartList[CARSLOTID_DAMAGE_REAR_BUMPER]) == 1) { + float angle = this->CalcPartRotation( + worldMatrix, + lbl_8040D100 * lbl_8040D104, + lbl_8040D108, + lbl_8040D10C, + lbl_8040D110) * + lbl_8040D114; + const bVector3 trunkRotation(angle, lbl_8040D118, lbl_8040D118); + } +} + +float VehiclePartDamageBehaviour::CalcPartRotation(bMatrix4 *worldMatrix, float maxAngle, float spring, float damper, float inertia) { + static float tCs; + static float tCd; + static float tI; + static bool tCsInited = false; + static bool tCdInited = false; + static bool tIInited = false; + + bMatrix4 localInverse; + bMatrix4 localMatrix; + + PSMTX44Copy(*reinterpret_cast(worldMatrix), *reinterpret_cast(&localInverse)); + bInvertMatrix(&localInverse, &localInverse); + eMulMatrix(&localMatrix, worldMatrix, &localInverse); + + this->angularVel_ch = this->mCarRenderInfo->mAngularVelocity; + this->linearVel_ch = this->mCarRenderInfo->mVelocity; + this->linearAcc_ch = this->mCarRenderInfo->mAcceleration; + eMulVector(&this->angularVel_ch, &localMatrix, &this->mCarRenderInfo->mAngularVelocity); + eMulVector(&this->linearVel_ch, &localMatrix, &this->mCarRenderInfo->mVelocity); + eMulVector(&this->linearAcc_ch, &localMatrix, &this->mCarRenderInfo->mAcceleration); + + this->angularVel_ch.x *= lbl_8040D120; + this->angularVel_ch.y *= lbl_8040D120; + this->angularVel_ch.z *= lbl_8040D120; + + if (lbl_8040D124 < maxAngle) { + float dT = this->mCarRenderInfo->mDeltaTime; + float desttheta = lbl_8040D124; + float acc; + float absValue; + + if (worldMatrix->v2.z * lbl_8040D128 < lbl_8040D124) { + desttheta = worldMatrix->v2.z * lbl_8040D128; + } + + if (!tCsInited) { + tCs = spring; + tCsInited = true; + } + + if (!tCdInited) { + tCd = damper; + tCdInited = true; + } + + if (!tIInited) { + tI = inertia; + tIInited = true; + } + + acc = tCs * (-desttheta - this->mTrunkAngle) + tCd * (-this->mTrunkVel); + + absValue = this->linearAcc_ch.x; + if (absValue < lbl_8040D124) { + absValue = -absValue; + } + if (lbl_8040D12C < absValue) { + acc -= this->linearAcc_ch.x / tI; + } + + absValue = this->linearVel_ch.z; + if (absValue < lbl_8040D124) { + absValue = -absValue; + } + if (lbl_8040D130 < absValue) { + acc += (-this->linearVel_ch.z * lbl_8040D134) / tI; + } + + absValue = -this->angularVel_ch.y; + if (absValue < lbl_8040D124) { + absValue = this->angularVel_ch.y; + } + if (lbl_8040D138 < absValue) { + acc += -this->angularVel_ch.y * lbl_8040D13C; + } + + this->mTrunkVel = acc * dT + this->mTrunkVel; + this->mTrunkAngle = this->mTrunkVel * dT + this->mTrunkAngle; + + if (lbl_8040D124 <= this->mTrunkAngle) { + if (this->mTrunkAngle > maxAngle) { + this->mTrunkAngle = maxAngle; + this->mTrunkVel = -this->mTrunkVel * lbl_8040D144; + } + } else { + this->mTrunkAngle = lbl_8040D124; + this->mTrunkVel = -this->mTrunkVel * lbl_8040D140; + } + } else { + this->mTrunkAngle = lbl_8040D124; + } + + return this->mTrunkAngle; +} + +void VehiclePartDamageBehaviour::AnimatePart(unsigned int slotId, const bVector3 &rotation, bMatrix4 *worldMatrix) { + this->Init(); + + if (slotId < 0x18) { + VehicleDamagePart *damagePart = this->mDamagePartList[slotId]; + CarRenderInfo *cri = this->mCarRenderInfo; + int lodIx = cri->GetMinLodLevel(); + + while (lodIx <= cri->GetMaxLodLevel()) { + CarPartModel *carPartModel = &cri->mCarPartModels[slotId][0][lodIx]; + + if (!carPartModel->IsHidden()) { + if (damagePart != nullptr) { + bMatrix4 *partMatrix = damagePart->GetMatrix(); + bMatrix4 localInverse(*worldMatrix); + bInvertMatrix(&localInverse, &localInverse); + eMulMatrix(partMatrix, worldMatrix, &localInverse); + const UMath::Vector3 pivot(damagePart->GetPivot()); + bVector3 model_pivot_translate(pivot.x, pivot.y, pivot.z); + eTranslate(partMatrix, partMatrix, &model_pivot_translate); + eRotateX(partMatrix, partMatrix, bDegToAng(rotation.x)); + eRotateY(partMatrix, partMatrix, bDegToAng(rotation.y)); + eRotateZ(partMatrix, partMatrix, bDegToAng(rotation.z)); + bVector3 pviot_model_translate(-pivot.x, -pivot.y, -pivot.z); + eTranslate(partMatrix, partMatrix, &pviot_model_translate); + eMulMatrix(partMatrix, partMatrix, worldMatrix); + } + } + + lodIx++; + } + } +} + +void VehiclePartDamageBehaviour::UnitTest() { + unitTestDelay--; + + if (unitTestDelay == 0) { + unsigned int zoneIx; + + unitTestDelay = 0x1E; + uniTestLevel++; + if (uniTestLevel > 1) { + uniTestLevel = 0; + } + + for (zoneIx = 0; zoneIx < this->mDamageZoneNum; zoneIx++) { + this->DamageZone(static_cast(zoneIx), static_cast(uniTestLevel)); + } + + this->ApplyDamage(); + } +} diff --git a/src/Speed/Indep/Src/World/VehicleRenderConn.cpp b/src/Speed/Indep/Src/World/VehicleRenderConn.cpp index e69de29bb..807fd88a9 100644 --- a/src/Speed/Indep/Src/World/VehicleRenderConn.cpp +++ b/src/Speed/Indep/Src/World/VehicleRenderConn.cpp @@ -0,0 +1,563 @@ +#include "./VehicleRenderConn.h" +#include "./CarLoader.hpp" +#include "./CarInfo.hpp" +#include "./WCollider.h" +#include "Speed/Indep/Libs/Support/Utility/UCrc.h" +#include "Speed/Indep/Src/Camera/CameraMover.hpp" +#include "Speed/Indep/Src/Ecstasy/EmitterSystem.h" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Src/Ecstasy/eMath.hpp" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/emittergroup.h" +#include "Speed/Indep/Src/Generated/Events/ECommitRenderAssets.hpp" +#include "Speed/Indep/Src/Physics/Bounds.h" + +int CarLoader_Load(CarLoader *car_loader, RideInfo *ride_info) asm("Load__9CarLoaderP8RideInfo"); +int CarLoader_IsLoaded(CarLoader *car_loader, int handle) asm("IsLoaded__9CarLoaderi"); +void CarLoader_Unload(CarLoader *car_loader, int handle) asm("Unload__9CarLoaderi"); +void FECustomizationRecord_WriteRecordIntoRide(const FECustomizationRecord *customizations, RideInfo *ride_info) + asm("WriteRecordIntoRide__C21FECustomizationRecordP8RideInfo"); +unsigned int CameraMover_GetAnchorID() asm("GetAnchorID__11CameraMover"); +const CollisionGeometry::Bounds *CollisionGeometry_Collection_GetRoot(const CollisionGeometry::Collection *collection) + asm("GetRoot__CQ217CollisionGeometry10Collection"); +extern CarTypeInfo *CarTypeInfoArray; +extern eView eViews[]; +extern EmitterSystem gEmitterSystem; +extern int UsePrecompositeVinyls; +extern int FlareDiv; +bool CarInfo_IsSkinned(CarType car_type); + +struct RideInfoLoaderMirror { + CarType Type; + char InstanceIndex; + char HasDash; + char CanBeVertexDamaged; + char SkinType; + char pad[0x2FC]; + int mMyCarLoaderHandle; +}; + +struct ReferenceMirror { + unsigned int mWorldID; + const bMatrix4 *mMatrix; + const bVector3 *mVelocity; + const bVector3 *mAcceleration; +}; + +void HandleEmitterGroupDelete(void *effect, EmitterGroup *grp) { + VehicleRenderConn::Effect *car_fx = static_cast(effect); + car_fx->ResetEmitterGroup(); +} + +UTL::Collections::Listable::List UTL::Collections::Listable::_mTable; + +int SkinSlotToMask(int slot) { + return 1 << (slot - 1); +} + +VehicleRenderConn::Effect::Effect(const bMatrix4 *matrix) { + PSMTX44Copy(*reinterpret_cast(matrix), *reinterpret_cast(&this->mLocalMatrix)); + this->mEmitterGroup = 0; + this->mKey = 0; +} + +VehicleRenderConn::Effect::~Effect() { + if (this->mEmitterGroup != 0) { + this->mEmitterGroup->UnSubscribe(); + if (this->mEmitterGroup != 0) { + delete this->mEmitterGroup; + } + this->mEmitterGroup = 0; + } +} + +void VehicleRenderConn::Effect::Stop() { + if (this->mEmitterGroup != 0) { + this->mEmitterGroup->Disable(); + } +} + +void VehicleRenderConn::Effect::Fire(const bMatrix4 *worldmatrix, unsigned int emitterkey, float emitter_intensity, + const bVector3 *inherit_velocity) const { + Attrib::Gen::emittergroup emitter_group_spec(emitterkey, 0, nullptr); + + if (emitter_group_spec.IsValid()) { + EmitterGroup *emitter_group = gEmitterSystem.CreateEmitterGroup(emitter_group_spec.GetConstCollection(), 0x10c00000); + if (emitter_group != 0) { + emitter_group->SetIntensity(emitter_intensity); + emitter_group->MakeOneShot(true); + if (worldmatrix == 0) { + emitter_group->SetLocalWorld(&this->mLocalMatrix); + } else { + bMatrix4 local_world; + eMulMatrix(&local_world, const_cast(&this->mLocalMatrix), const_cast(worldmatrix)); + emitter_group->SetLocalWorld(&local_world); + } + if (inherit_velocity != 0) { + emitter_group->SetInheritVelocity(inherit_velocity); + } + } + } +} + +void VehicleRenderConn::Effect::Update(const bMatrix4 *worldmatrix, unsigned int emitterkey, float dT, float emitter_intensity, + const bVector3 *inherit_velocity) { + if (emitterkey != this->mKey) { + if (this->mEmitterGroup != 0) { + this->mEmitterGroup->UnSubscribe(); + if (this->mEmitterGroup != 0) { + delete this->mEmitterGroup; + } + this->mEmitterGroup = 0; + } + this->mKey = emitterkey; + if (emitterkey != 0) { + Attrib::Gen::emittergroup emitter_group_spec(emitterkey, 0, nullptr); + if (emitter_group_spec.IsValid()) { + this->mEmitterGroup = gEmitterSystem.CreateEmitterGroup(emitter_group_spec.GetConstCollection(), 0x10800000); + if (this->mEmitterGroup != 0) { + this->mEmitterGroup->SubscribeToDeletion(this, HandleEmitterGroupDelete); + } + } + } + } + if (this->mEmitterGroup != 0) { + if (worldmatrix == 0) { + this->mEmitterGroup->SetLocalWorld(&this->mLocalMatrix); + } else { + bMatrix4 local_world; + eMulMatrix(&local_world, const_cast(&this->mLocalMatrix), const_cast(worldmatrix)); + this->mEmitterGroup->SetLocalWorld(&local_world); + } + this->mEmitterGroup->Enable(); + this->mEmitterGroup->SetIntensity(emitter_intensity); + this->mEmitterGroup->MakeOneShot(false); + if (inherit_velocity != 0) { + this->mEmitterGroup->SetInheritVelocity(inherit_velocity); + } + this->mEmitterGroup->Update(dT); + } +} + +VehicleRenderConn::VehicleRenderConn(const Sim::ConnectionData &data, CarType type) + : Sim::Connection(data), // + mAttributes(static_cast(0), 0, 0), // + mState(S_None), // + mCarType(type), // + mWorldRef(0) +{ + const char *base_model_name = CarTypeInfoArray[type].BaseModelName; + float zero = 0.0f; + + this->mRideInfo = 0; + this->mRenderInfo = 0; + *reinterpret_cast(&this->mHide) = 0; + this->mWCollider = 0; + this->mModelOffset.x = zero; + this->mModelOffset.y = zero; + this->mModelOffset.z = zero; + this->mModelOffset.w = zero; + this->mSkinSlot = 0; + this->mAttributes.ChangeWithDefault(Attrib::StringToLowerCaseKey(base_model_name)); +} + +VehicleRenderConn::~VehicleRenderConn() { + if (this->mRenderInfo != 0) { + this->mRenderInfo->SetCollider(0); + } + if (this->mWCollider != 0) { + WCollider::Destroy(this->mWCollider); + this->mWCollider = 0; + } + this->Unload(); +} + +void VehicleRenderConn::OnClose() { + delete this; +} + +Sim::ConnStatus VehicleRenderConn::OnStatusCheck() { + return this->mState > S_Loading ? Sim::CONNSTATUS_READY : Sim::CONNSTATUS_CONNECTING; +} + +bool VehicleRenderConn::IsViewAnchor(eView *view) const { + CameraMover *camera_mover; + CameraAnchor *anchor; + const ReferenceMirror *world_ref = reinterpret_cast(&this->mWorldRef); + + if (view != 0) { + camera_mover = view->GetCameraMover(); + if (camera_mover != 0) { + anchor = camera_mover->GetAnchor(); + if (anchor != 0 && anchor->GetWorldID() == world_ref->mWorldID) { + return true; + } + } + } + + return false; +} + +bool VehicleRenderConn::IsViewAnchor() const { + eView *views = eViews; + return this->IsViewAnchor(&views[1]) || this->IsViewAnchor(&views[2]); +} + +bool VehicleRenderConn::CanRender() const { + return this->mHide == false && this->mState == S_Updated; +} + +VehicleRenderConn *VehicleRenderConn::Find(unsigned int worldid) { + const UTL::Collections::Listable::List &list = + UTL::Collections::Listable::GetList(); + + for (UTL::Collections::Listable::List::const_iterator iter = list.begin(); iter != list.end(); ++iter) { + VehicleRenderConn *vehicle_render_conn = *iter; + + if (vehicle_render_conn->mWorldRef.GetWorldID() == worldid) { + return vehicle_render_conn; + } + } + + return 0; +} + +unsigned int VehicleRenderConn::FindPart(CAR_PART_ID slot) { + if (slot < 0) { + return 0; + } + + unsigned int modelid = 0; + + if (this->mRenderInfo != 0) { + modelid = this->mRenderInfo->FindCarPart(slot); + } + + return modelid; +} + +unsigned int VehicleRenderConn::HidePart(CAR_PART_ID slot) { + if (slot < 0) { + return 0; + } + + unsigned int modelid = 0; + + if (this->mRenderInfo != 0) { + modelid = this->mRenderInfo->HideCarPart(slot, true); + } + + return modelid; +} + +void VehicleRenderConn::ShowPart(CAR_PART_ID slot) { + if (this->mRenderInfo != 0) { + this->mRenderInfo->HideCarPart(slot, false); + } +} + +bool VehicleRenderConn::CheckForRain(eView *view) const { + const ReferenceMirror *world_ref = reinterpret_cast(&this->mWorldRef); + Rain *precipitation; + CameraMover *camera_mover; + bVector3 *camera_position; + float dx; + float dy; + float dz; + float distance_squared; + float distance; + + if (view != 0) { + precipitation = view->Precipitation; + if (precipitation != 0 && 0.0f < precipitation->GetRainIntensity()) { + camera_mover = view->GetCameraMover(); + if (camera_mover != 0) { + camera_position = camera_mover->GetPosition(); + dx = camera_position->y - world_ref->mMatrix->v3.y; + dy = camera_position->x - world_ref->mMatrix->v3.x; + dz = camera_position->z - world_ref->mMatrix->v3.z; + distance_squared = dz * dz + dy * dy + dx * dx; + distance = 0.0f; + if (0.0f < distance_squared) { + distance = bSqrt(distance_squared); + } + if (distance < 60.0f) { + if (10.0f <= distance && CameraMover_GetAnchorID() != world_ref->mWorldID) { + return precipitation->NoRainAhead == 0; + } + return precipitation->NoRain == 0; + } + } + } + } + + return false; +} + +bool VehicleRenderConn::CheckForRain() const { + eView *views = eViews; + return this->CheckForRain(&views[1]) || this->CheckForRain(&views[2]); +} + +bool VehicleRenderConn::CanUpdate() const { + return this->mState > S_Loading; +} + +void VehicleRenderConn::HandleEvent(EventID id) { + if (this->CanUpdate()) { + this->OnEvent(id); + } +} + +void VehicleRenderConn::FetchData(float dT) { + const UTL::Collections::Listable::List &carlist = VehicleRenderConn::GetList(); + { + VehicleRenderConn *const *iter = carlist.begin(); + + while (iter != carlist.end()) { + (*iter)->OnFetch(dT); + ++iter; + } + } +} + +void VehicleRenderConn::UpdateLoading() { + const UTL::Collections::Listable::List &carlist = VehicleRenderConn::GetList(); + { + VehicleRenderConn *const *iter = carlist.begin(); + + while (iter != carlist.end()) { + VehicleRenderConn *conn = *iter; + + if (conn->mState == S_Loading && CarLoader_IsLoaded(&TheCarLoader, conn->mRideInfo->GetCarLoaderHandle())) { + conn->OnLoaded(new (__FILE__, __LINE__) CarRenderInfo(conn->mRideInfo)); + } + + ++iter; + } + } +} + +bool VehicleRenderConn::Load(unsigned int worldID, CarRenderUsage usage, bool commit, const FECustomizationRecord *customizations) { + if (this->mRenderInfo != 0 || this->mCarType == CARTYPE_NONE) { + return false; + } + + this->mWorldRef.Set(worldID); + this->mRideInfo = new (__FILE__, __LINE__) RideInfo; + this->mRideInfo->Init(this->mCarType, usage, 0, 0); + + if (CarInfo_IsSkinned(this->mCarType)) { + bool precomposite = false; + + if (0) { + customizations->IsPreset(); + } + + if (UsePrecompositeVinyls != 0) { + precomposite = true; + } + + if (!precomposite) { + for (int i = 1; i < 5; i++) { + int mask = SkinSlotToMask(i); + if (mOpenSkinSlots & mask) { + mOpenSkinSlots &= ~mask; + this->mSkinSlot = i; + break; + } + } + } else { + for (int i = 5; i < 13; i++) { + int mask = SkinSlotToMask(i); + if (mOpenSkinSlots & mask) { + mOpenSkinSlots &= ~mask; + this->mSkinSlot = i; + break; + } + } + } + + if (this->mSkinSlot != 0) { + this->mRideInfo->SetCompositeNameHash(this->mSkinSlot); + } + } + + this->mRideInfo->SetStockParts(); + if (customizations != 0) { + FECustomizationRecord_WriteRecordIntoRide(customizations, this->mRideInfo); + } + this->SetupLoading(commit); + return true; +} + +void VehicleRenderConn::Unload() { + if (this->mState != S_None) { + if (this->mRideInfo != 0) { + RideInfoLoaderMirror *ride_info = reinterpret_cast(this->mRideInfo); + + CarLoader_Unload(&TheCarLoader, ride_info->mMyCarLoaderHandle); + delete this->mRideInfo; + this->mRideInfo = 0; + if (this->mSkinSlot != 0) { + mOpenSkinSlots |= SkinSlotToMask(this->mSkinSlot); + this->mSkinSlot = 0; + } + } + this->mWorldRef.Unlock(); + if (this->mRenderInfo != 0) { + delete this->mRenderInfo; + this->mRenderInfo = 0; + } + this->mState = S_None; + } +} + +void VehicleRenderConn::Update(float) { + if (this->CanUpdate()) { + this->mState = S_Updated; + *reinterpret_cast(&this->mHide) = 0; + } +} + +void VehicleRenderConn::SetupLoading(bool commit) { + int car_loader_handle = CarLoader_Load(&TheCarLoader, this->mRideInfo); + this->mRideInfo->SetCarLoaderHandle(car_loader_handle); + reinterpret_cast(this->mRideInfo)->InstanceIndex = 0; + + if (commit) { + new ECommitRenderAssets(); + } + + this->mState = S_Loading; +} + +void VehicleRenderConn::OnEvent(EventID) {} + +void VehicleRenderConn::OnLoaded(CarRenderInfo *render_info) { + const CollisionGeometry::Collection *col; + + this->mState = S_Loaded; + if (render_info != 0) { + if (this->mWCollider == 0) { + const ReferenceMirror *world_ref = reinterpret_cast(&this->mWorldRef); + this->mWCollider = WCollider::Create(world_ref->mWorldID, WCollider::kColliderShape_Cylinder, 0x1C, 0); + } + render_info->SetCollider(this->mWCollider); + } + this->mRenderInfo = render_info; + render_info->Init(); + + col = reinterpret_cast( + CollisionGeometry::Lookup(UCrc32(stringhash32(CarTypeInfoArray[this->mCarType].CarTypeName)))); + if (col) { + const CollisionGeometry::Bounds *root = CollisionGeometry_Collection_GetRoot(col); + if (root) { + UMath::Vector3 pivot; + root->GetPivot(pivot); + this->mModelOffset.x = pivot.z; + this->mModelOffset.y = -pivot.x; + this->mModelOffset.z = pivot.y; + this->mRenderInfo->SetRadius(root->fRadius); + } + } else { + this->mModelOffset = bVector4(this->mRenderInfo->ModelOffset.x, this->mRenderInfo->ModelOffset.y, this->mRenderInfo->ModelOffset.z, 0.0f); + } +} + +void VehicleRenderConn::GetRenderMatrix(bMatrix4 *matrix) { + const ReferenceMirror *world_ref = reinterpret_cast(&this->mWorldRef); + bVector4 model_offset; + bVector4 transformed_offset; + + PSMTX44Copy(*reinterpret_cast(world_ref->mMatrix), *reinterpret_cast(matrix)); + model_offset = this->mModelOffset; + eMulVector(&transformed_offset, world_ref->mMatrix, &model_offset); + matrix->v3.x -= transformed_offset.x; + matrix->v3.y -= transformed_offset.y; + matrix->v3.z -= transformed_offset.z; +} + +void VehicleRenderConn::RenderAll(eView *view, int reflection) { + for (VehicleRenderConn *const *iter = VehicleRenderConn::GetList().begin(); + iter != VehicleRenderConn::GetList().end(); ++iter) { + VehicleRenderConn *conn = *iter; + conn->OnRender(view, reflection); + } +} + +void VehicleRenderConn::RenderFlares(eView *view, int reflection, int renderFlareFlags) { + for (VehicleRenderConn *const *iter = VehicleRenderConn::GetList().begin(); iter != VehicleRenderConn::GetList().end(); ++iter) { + VehicleRenderConn *conn = *iter; + + if (conn->CanRender() && conn->mRenderInfo != 0) { + bMatrix4 render_matrix; + bVector3 offset2; + CameraMover *mover = nullptr; + CarRenderInfo *info = conn->mRenderInfo; + + conn->GetRenderMatrix(&render_matrix); + offset2.x = render_matrix.v3.x; + offset2.y = render_matrix.v3.y; + offset2.z = render_matrix.v3.z; + + if (view->CameraMoverList.GetHead() != view->CameraMoverList.EndOfList()) { + mover = view->CameraMoverList.GetHead(); + } + + if (mover != 0 && !mover->RenderCarPOV()) { + const ReferenceMirror *world_ref = reinterpret_cast(&conn->mWorldRef); + CameraAnchor *anchor = mover->GetAnchor(); + + if (anchor != 0 && anchor->GetWorldID() == world_ref->mWorldID) { + continue; + } + } + + info->RenderFlaresOnCar(view, &offset2, &render_matrix, 0, reflection, renderFlareFlags); + info = conn->mRenderInfo; + + if (reflection == 0) { + if (view->GetID() == 1 || view->GetID() == 2) { + if (info->matrixIndex < 0) { + info->matrixIndex = 0; + } + info->matrixIndex++; + if (info->matrixIndex > 2) { + info->matrixIndex = 0; + } + bCopy(&info->LastFewMatrices[info->matrixIndex], &render_matrix); + info->LastFewPositions[info->matrixIndex] = offset2; + } + + { + float NOSamount = bLength(conn->GetVelocity()); + + if (0.1f < NOSamount && info->NOSstate != 0) { + for (int streak = 3; streak > 1; --streak) { + int history_index = streak + info->matrixIndex; + int next_index = (history_index + 1) % 3; + int current_index = (history_index + 2) % 3; + bVector3 flare_position = info->LastFewPositions[current_index] - info->LastFewPositions[next_index]; + + for (int div = 0; div < FlareDiv; div++) { + float t = static_cast(div + 1) / static_cast(FlareDiv); + bVector3 *point = static_cast(eFrameMalloc(sizeof(bVector3))); + + if (point != 0) { + *point = flare_position; + *point *= t; + *point += info->LastFewPositions[next_index]; + info->RenderFlaresOnCar(view, point, &info->LastFewMatrices[next_index], 8, reflection, 2); + } + } + } + } + } + } + } + } +} + +void VehicleRenderConn::RefreshRenderInfo() { + this->mRenderInfo->Refresh(); +} diff --git a/src/Speed/Indep/Src/World/VehicleRenderConn.h b/src/Speed/Indep/Src/World/VehicleRenderConn.h index d8dcdf5db..335969aeb 100644 --- a/src/Speed/Indep/Src/World/VehicleRenderConn.h +++ b/src/Speed/Indep/Src/World/VehicleRenderConn.h @@ -5,6 +5,184 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/UListable.h" +#include "Speed/Indep/Libs/Support/Utility/UStandard.h" +#include "Speed/Indep/Libs/Support/Utility/FastMem.h" +#include "Speed/Indep/Src/Frontend/Database/VehicleDB.hpp" +#include "Speed/Indep/Src/Sim/SimServer.h" +#include "Speed/Indep/Src/World/CarRender.hpp" +struct EmitterGroup; + +// TODO: Reference likely belongs in a shared world/ref header; keep it here until its original home is recovered. +class Reference { + public: + Reference(); + Reference(unsigned int worldid); + Reference(const Reference &from); + ~Reference(); + + bool IsValid() const; + void operator=(const Reference &from); + void operator=(unsigned int id); + unsigned int GetWorldID() const { + return this->mWorldID; + } + const bMatrix4 *GetMatrix() const { + return this->mMatrix; + } + const bVector3 *GetVelocity() const { + return this->mVelocity; + } + const bVector3 *GetAcceleration() const { + return this->mAcceleration; + } + + void Set(unsigned int worldid); + void Lock(); + void Unlock(); + + private: + unsigned int mWorldID; // offset 0x0, size 0x4 + const bMatrix4 *mMatrix; // offset 0x4, size 0x4 + const bVector3 *mVelocity; // offset 0x8, size 0x4 + const bVector3 *mAcceleration; // offset 0xC, size 0x4 +}; + +class VehicleRenderConn : public Sim::Connection, public UTL::Collections::Listable { + public: + enum eState { + S_None = 0, + S_Loading = 1, + S_Loaded = 2, + S_Updated = 3, + }; + + enum EventID { + E_MISS_SHIFT = 0, + E_PERFECT_SHIFT = 1, + E_PERFECT_LAUNCH = 2, + E_UPSHIFT = 3, + E_DOWNSHIFT = 4, + }; + + class Effect : public bTNode { + public: + Effect(const bMatrix4 *matrix); + ~Effect(); + + void Stop(); + void Fire(const bMatrix4 *worldmatrix, unsigned int emitterkey, float emitter_intensity, + const bVector3 *inherit_velocity) const; + void Update(const bMatrix4 *worldmatrix, unsigned int emitterkey, float dT, float emitter_intensity, + const bVector3 *inherit_velocity); + + void *operator new(unsigned int size) { + return gFastMem.Alloc(size, nullptr); + } + + void operator delete(void *mem, unsigned int size) { + if (mem) { + gFastMem.Free(mem, size, nullptr); + } + } + + void ResetEmitterGroup() { + mEmitterGroup = 0; + mKey = 0; + } + + bMatrix4 mLocalMatrix; // offset 0x8, size 0x40 + EmitterGroup *mEmitterGroup; // offset 0x48, size 0x4 + unsigned int mKey; // offset 0x4C, size 0x4 + }; + + typedef UTL::Std::vector LoaderList; + + VehicleRenderConn(const Sim::ConnectionData &data, CarType type); + virtual ~VehicleRenderConn(); + + bool IsViewAnchor(eView *view) const; + bool IsViewAnchor() const; + bool CheckForRain(eView *view) const; + bool CheckForRain() const; + static VehicleRenderConn *Find(unsigned int worldid); + void HandleEvent(EventID id); + static void FetchData(float dT); + static void UpdateLoading(); + unsigned int FindPart(CAR_PART_ID slot); + unsigned int HidePart(CAR_PART_ID slot); + void ShowPart(CAR_PART_ID slot); + void Update(float dT); + void SetupLoading(bool commit); + void RefreshRenderInfo(); + bool Load(unsigned int worldID, CarRenderUsage usage, bool commit, const FECustomizationRecord *customizations); + void Unload(); + static void RenderAll(eView *view, int reflection); + static void RenderFlares(eView *view, int reflection, int renderFlareFlags); + + CarRenderInfo *GetRenderInfo() const { + return this->mRenderInfo; + } + const bVector3 *GetPosition() const; + const bMatrix4 *GetBodyMatrix() const { + return this->mWorldRef.GetMatrix(); + } + const bVector3 *GetVelocity() const { + return this->mWorldRef.GetVelocity(); + } + const bVector3 *GetAcceleration() const { + return this->mWorldRef.GetAcceleration(); + } + bool IsLoaded() const { + return this->mState == S_Loaded; + } + eState GetState() const { + return this->mState; + } + unsigned int GetWorldID() const { + return this->mWorldRef.GetWorldID(); + } + CarType GetCarType() const { + return this->mCarType; + } + WCollider *GetWCollider() const { + return this->mWCollider; + } + + protected: + bool CanUpdate() const; + bool CanRender() const; + void OnClose() override; + Sim::ConnStatus OnStatusCheck() override; + virtual void OnRender(eView *view, int reflection); + virtual void OnFetch(float dT); + virtual void OnLoaded(CarRenderInfo *render_info); + virtual void GetRenderMatrix(bMatrix4 *matrix); + virtual void OnEvent(EventID id); + const bVector4 &GetModelOffset() const { + return this->mModelOffset; + } + const Attrib::Gen::ecar &GetAttributes() const { + return this->mAttributes; + } + + public: + static LoaderList sLoaderList; // size: 0x10 + static int mOpenSkinSlots; // size: 0x4, address: 0x80437620 + + Attrib::Gen::ecar mAttributes; // offset 0x14, size 0x14 + eState mState; // offset 0x28, size 0x4 + const CarType mCarType; // offset 0x2C, size 0x4 + Reference mWorldRef; // offset 0x30, size 0x10 + RideInfo *mRideInfo; // offset 0x40, size 0x4 + CarRenderInfo *mRenderInfo; // offset 0x44, size 0x4 + bVector4 mModelOffset; // offset 0x48, size 0x10 + bool mHide; // offset 0x58, size 0x1 + WCollider *mWCollider; // offset 0x5C, size 0x4 + int mSkinSlot; // offset 0x60, size 0x4 +}; + +int SkinSlotToMask(int slot); #endif 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/VisualTreatment.cpp b/src/Speed/Indep/Src/World/VisualTreatment.cpp index e69de29bb..0a91b7e80 100644 --- a/src/Speed/Indep/Src/World/VisualTreatment.cpp +++ b/src/Speed/Indep/Src/World/VisualTreatment.cpp @@ -0,0 +1,458 @@ +#include "VisualTreatment.h" + +#include "Speed/Indep/Src/Camera/Camera.hpp" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Src/Interfaces/SimActivities/IGameState.h" +#include "Speed/Indep/Src/Interfaces/SimEntities/IPlayer.h" +#include "Speed/Indep/Src/Interfaces/Simables/IAI.h" +#include "Speed/Indep/Src/Interfaces/Simables/IEngine.h" +#include "Speed/Indep/Src/Misc/Timer.hpp" +#include "Speed/Indep/Src/World/World.hpp" +#include "Speed/Indep/bWare/Inc/bMemory.hpp" + +extern Timer RealTimer; +extern float WorldTimeSeconds; +void SetMiddleGrayValue(float val) {} + +float GetValueFromSpline(float value, bMatrix4 *curve) { + float tm1 = 1.0f - value; + float tm13 = tm1 * tm1 * tm1; + float t3 = value * value * value; + + return tm13 * curve->v0.y + value * (tm1 * 3.0f) * tm1 * curve->v1.y + + value * (value * 3.0f) * tm1 * curve->v2.y + t3 * curve->v3.y; +} + +inline void VisualLookEffect::Reset() { + this->StartTime = 0.0f; + this->PulseLength = 0.0f; +} + +bool VisualLookEffect::IsActive() { + return this->StartTime != 0.0f; +} + +float VisualLookEffect::UpdateActive(float heatMeter) { + float secondsElapsed; + if (this->UseWorldTime != 0) { + secondsElapsed = WorldTimer.GetSeconds() - this->StartTime; + } else { + secondsElapsed = RealTimer.GetSeconds() - this->StartTime; + } + + if (this->StopIfHeatFalls != 0 && heatMeter < this->AttribEffect->heattrigger()) { + this->StartTime = 0.0f; + } + + if (this->StopAfterLength != 0 && secondsElapsed >= this->PulseLength) { + this->StartTime = 0.0f; + } + + bMatrix4 *curve = reinterpret_cast(&const_cast(this->AttribEffect->graph())); + float value; + if (secondsElapsed <= 0.0f) { + return curve->v0.y * this->AttribEffect->magnitude(); + } + + if (secondsElapsed >= this->PulseLength) { + return curve->v3.y * this->AttribEffect->magnitude(); + } + + value = GetValueFromSpline(secondsElapsed / this->PulseLength, curve); + return value * this->AttribEffect->magnitude(); +} + +inline void VisualLookEffectTarget::Reset() { + this->Target = 0.0f; + this->Current = 0.0f; + this->StartWorldTime = 0.0f; +} + +IVisualTreatment::IVisualTreatment() + : MiddayVisualLook(0xEEC2271A, 0, nullptr), // + SunsetVisualLook(0xCEDA4E4F, 0, nullptr), // + UvesVisualLook(0x681BEF75, 0, nullptr), // + CopCamVisualLook(0xEE6074A3, 0, nullptr), // + UvesPulse(::new (__FILE__, __LINE__) VisualLookEffect(new Attrib::Gen::visuallookeffect(0x334F1E4D, 0, nullptr))), // + UvesRadialBlur(::new (__FILE__, __LINE__) VisualLookEffect(new Attrib::Gen::visuallookeffect(0xBFA7B6AC, 0, nullptr))), // + UvesTransition(::new (__FILE__, __LINE__) VisualLookEffect(new Attrib::Gen::visuallookeffect(0x15746132, 0, nullptr))), // + CameraFlash(::new (__FILE__, __LINE__) VisualLookEffect(new Attrib::Gen::visuallookeffect(0x30656612, 0, nullptr))), // + PursuitBreaker(::new (__FILE__, __LINE__) VisualLookEffectTarget(new Attrib::Gen::visuallookeffect(0x90D06C71, 0, nullptr))), // + NosRadialBlur(::new (__FILE__, __LINE__) VisualLookEffectTarget(new Attrib::Gen::visuallookeffect(0x6B40EB80, 0, nullptr))) +{ + this->State = HEAT_LOOK; + this->PulseBrightness = 1.0f; + this->IsBeingPursued = -1; + this->NosRadialBlur->StartWorldTime = 0.0f; + this->PursuitBreakerBlend = 0.0f; + this->CurrentTarget = -1.0f; + this->DesaturationTarget = -1.0f; + this->HeatMeter = 0.0f; + this->NosRadialBlur->Target = 0.0f; + this->NosRadialBlur->Current = 0.0f; + this->RadialBlur = 0.0f; + this->NosRadialBlurAmount = 0.0f; +} + +IVisualTreatment::~IVisualTreatment() { + delete this->UvesPulse; + delete this->UvesRadialBlur; + delete this->UvesTransition; + delete this->NosRadialBlur; + delete this->CameraFlash; + delete this->PursuitBreaker; +} + +void IVisualTreatment::Reset() { + this->State = HEAT_LOOK; + this->PulseBrightness = 1.0f; + this->CurrentTarget = -1.0f; + this->IsBeingPursued = -1; + this->DesaturationTarget = -1.0f; + this->RadialBlur = 0.0f; + this->NosRadialBlurAmount = 0.0f; + this->PursuitBreakerBlend = 0.0f; + + PursuitBreaker->Reset(); + UvesPulse->Reset(); + UvesRadialBlur->Reset(); + UvesTransition->Reset(); + CameraFlash->Reset(); + NosRadialBlur->Reset(); +} + +void IVisualTreatment::TriggerPulse(float length) { + CameraFlash->Trigger(length, false, false, true); +} + +void IVisualTreatment::SetNosEngaged(bool isNosEngaged) { + if (isNosEngaged) { + NosRadialBlur->SetCurrent(1.0f); + } + + NosRadialBlur->SetTarget(isNosEngaged ? 1.0f : 0.0f); +} + +void IVisualTreatment::SetPursuitBreakerTarget(float blendTarget) { + VisualLookEffectTarget *pursuitBreaker = this->PursuitBreaker; + + if (pursuitBreaker->Target == blendTarget) { + return; + } + + pursuitBreaker->Target = blendTarget; + pursuitBreaker->StartWorldTime = WorldTimeSeconds; +} + +void IVisualTreatment::BlendVisualLookAttribute( + bMatrix4 &result, float defaultUves, float uves, + const UMath::Matrix4 &(Attrib::Gen::visuallook::*funcPtr)() const) { + const Attrib::Gen::visuallook *currVisualLook; + + bMemSet(&result, 0, 0x40); + + if (GetCurrentTimeOfDay() == eTOD_MIDDAY) { + if (defaultUves != 0.0f) { + currVisualLook = &this->MiddayVisualLook; + const bMatrix4 &currCurve = *reinterpret_cast(&(currVisualLook->*funcPtr)()); + result.v0.x += defaultUves * currCurve.v0.x; + result.v0.y += defaultUves * currCurve.v0.y; + result.v1.x += defaultUves * currCurve.v1.x; + result.v1.y += defaultUves * currCurve.v1.y; + result.v2.x += defaultUves * currCurve.v2.x; + result.v2.y += defaultUves * currCurve.v2.y; + result.v3.x += defaultUves * currCurve.v3.x; + result.v3.y += defaultUves * currCurve.v3.y; + } + } else if (defaultUves != 0.0f) { + currVisualLook = &this->SunsetVisualLook; + const bMatrix4 &currCurve = *reinterpret_cast(&(currVisualLook->*funcPtr)()); + result.v0.x += defaultUves * currCurve.v0.x; + result.v0.y += defaultUves * currCurve.v0.y; + result.v1.x += defaultUves * currCurve.v1.x; + result.v1.y += defaultUves * currCurve.v1.y; + result.v2.x += defaultUves * currCurve.v2.x; + result.v2.y += defaultUves * currCurve.v2.y; + result.v3.x += defaultUves * currCurve.v3.x; + result.v3.y += defaultUves * currCurve.v3.y; + } + + if (uves != 0.0f) { + const bMatrix4 &currCurve = *reinterpret_cast(&(this->UvesVisualLook.*funcPtr)()); + result.v0.x += uves * currCurve.v0.x; + result.v0.y += uves * currCurve.v0.y; + result.v1.x += uves * currCurve.v1.x; + result.v1.y += uves * currCurve.v1.y; + result.v2.x += uves * currCurve.v2.x; + result.v2.y += uves * currCurve.v2.y; + result.v3.x += uves * currCurve.v3.x; + result.v3.y += uves * currCurve.v3.y; + } +} + +void IVisualTreatment::BlendVisualLookAttribute( + float &result, float defaultUves, float uves, + const float &(Attrib::Gen::visuallook::*funcPtr)() const) { + result = 0.0f; + + if (defaultUves != 0.0f) { + int tod = GetCurrentTimeOfDay(); + const Attrib::Gen::visuallook *currVisualLook = &this->SunsetVisualLook; + if (tod == eTOD_MIDDAY) { + currVisualLook = &this->MiddayVisualLook; + } + + result += (currVisualLook->*funcPtr)() * defaultUves; + } + + if (uves != 0.0f) { + result += (this->UvesVisualLook.*funcPtr)() * uves; + } +} + +void IVisualTreatment::BlendVisualLookAttribute( + bVector4 &result, float defaultUves, float uves, + const UMath::Vector4 &(Attrib::Gen::visuallook::*funcPtr)() const) { + const Attrib::Gen::visuallook *currVisualLook; + + bMemSet(&result, 0, 0x10); + + if (defaultUves != 0.0f) { + currVisualLook = &this->SunsetVisualLook; + if (GetCurrentTimeOfDay() == eTOD_MIDDAY) { + currVisualLook = &this->MiddayVisualLook; + } + + const bVector4 &currTint = *reinterpret_cast(&(currVisualLook->*funcPtr)()); + result.x += defaultUves * currTint.x; + result.y += defaultUves * currTint.y; + result.z += defaultUves * currTint.z; + result.w += defaultUves * currTint.w; + } + + if (uves != 0.0f) { + const bVector4 &currTint = *reinterpret_cast(&(this->UvesVisualLook.*funcPtr)()); + result.x += uves * currTint.x; + result.y += uves * currTint.y; + result.z += uves * currTint.z; + result.w += uves * currTint.w; + } +} + +void IVisualTreatment::UpdateVisualLook() { + Attrib::Gen::visuallook *currVisualLook = 0; + + if (this->State == COPCAM_LOOK) { + currVisualLook = &this->CopCamVisualLook; + this->PulseBrightness = 0.0f; + this->RadialBlur = 0.0f; + } else if (this->State == FE_LOOK) { + currVisualLook = &this->SunsetVisualLook; + if (GetCurrentTimeOfDay() == eTOD_MIDDAY) { + currVisualLook = &this->MiddayVisualLook; + } + + this->PulseBrightness = 0.0f; + this->RadialBlur = 0.0f; + this->PursuitBreakerBlend = 0.0f; + } + + PSMTX44Copy(*reinterpret_cast(&currVisualLook->BlackBloomCurve()), + *reinterpret_cast(&this->BlackBloomCurve)); + PSMTX44Copy(*reinterpret_cast(&currVisualLook->ColourBloomCurve()), + *reinterpret_cast(&this->ColourBloomCurve)); + PSMTX44Copy(*reinterpret_cast(&currVisualLook->DetailMapCurve()), + *reinterpret_cast(&this->DetailMapCurve)); + + this->ColourBloomTint = *reinterpret_cast(&currVisualLook->ColourBloomTint()); + this->BlackBloomIntensity = currVisualLook->BlackBloomIntensity(); + this->ColourBloomIntensity = currVisualLook->ColourBloomIntensity(); + this->Desaturation = currVisualLook->Desaturation(); + this->DetailMapIntensity = currVisualLook->DetailMapIntensity(); +} + +void IVisualTreatment::TriggerUves() { + const float kUseAttribLength = 0.0f; + UvesTransition->Trigger(kUseAttribLength, true, true, false); + UvesRadialBlur->Trigger(kUseAttribLength, true, true, false); + UvesPulse->Trigger(kUseAttribLength, false, true, false); +} + +void IVisualTreatment::UpdateHeat(eView *view, float targetHeat, bool isBeingPursued) { + targetHeat = static_cast< float >(static_cast< int >(targetHeat)); + + bool hasIni = UTL::Collections::Singleton::Get() != 0; + if (hasIni) { + this->IsBeingPursued = -1; + this->CurrentTarget = -1.0f; + this->UvesPulse->PulseLength = 0.0f; + this->UvesPulse->StartTime = 0.0f; + this->UvesTransition->PulseLength = 0.0f; + this->UvesTransition->StartTime = 0.0f; + } + + if (((targetHeat > this->CurrentTarget && this->CurrentTarget != -1.0f)) || (this->IsBeingPursued == 0 && isBeingPursued)) { + this->TriggerUves(); + } + + this->IsBeingPursued = isBeingPursued; + this->CurrentTarget = targetHeat; + float currentTarget = this->CurrentTarget; + + VisualLookEffect *uvesTransition = this->UvesTransition; + float uves = 0.0f; + if (uvesTransition->IsActive()) { + uves = uvesTransition->UpdateActive(targetHeat); + } + + float defaultUves = 1.0f - uves; + this->BlendVisualLookAttribute(this->BlackBloomCurve, defaultUves, uves, &Attrib::Gen::visuallook::BlackBloomCurve); + this->BlendVisualLookAttribute(this->ColourBloomCurve, defaultUves, uves, &Attrib::Gen::visuallook::ColourBloomCurve); + this->BlendVisualLookAttribute(this->BlackBloomIntensity, defaultUves, uves, &Attrib::Gen::visuallook::BlackBloomIntensity); + this->BlendVisualLookAttribute(this->ColourBloomIntensity, defaultUves, uves, &Attrib::Gen::visuallook::ColourBloomIntensity); + this->BlendVisualLookAttribute(this->ColourBloomTint, defaultUves, uves, &Attrib::Gen::visuallook::ColourBloomTint); + this->BlendVisualLookAttribute(this->Desaturation, defaultUves, uves, &Attrib::Gen::visuallook::Desaturation); + this->BlendVisualLookAttribute(this->DetailMapCurve, defaultUves, uves, &Attrib::Gen::visuallook::DetailMapCurve); + this->BlendVisualLookAttribute(this->DetailMapIntensity, defaultUves, uves, &Attrib::Gen::visuallook::DetailMapIntensity); + + VisualLookEffect *uvesPulse = this->UvesPulse; + float pulseBrightness = 0.0f; + if (uvesPulse->IsActive()) { + pulseBrightness = uvesPulse->UpdateActive(currentTarget); + } + this->PulseBrightness = pulseBrightness; + + VisualLookEffect *cameraFlash = this->CameraFlash; + float cameraFlashValue; + if (cameraFlash->IsActive()) { + cameraFlashValue = cameraFlash->UpdateActive(currentTarget); + } else { + cameraFlashValue = 0.0f; + } + this->PulseBrightness += cameraFlashValue; + + VisualLookEffect *uvesRadialBlurEffect = this->UvesRadialBlur; + float uvesRadialBlur; + if (uvesRadialBlurEffect->IsActive()) { + uvesRadialBlur = uvesRadialBlurEffect->UpdateActive(currentTarget); + } else { + uvesRadialBlur = 0.0f; + } + + VisualLookEffectTarget *pursuitBreaker = this->PursuitBreaker; + if (pursuitBreaker->StartWorldTime != 0.0f) { + float elapsed = WorldTimeSeconds - pursuitBreaker->StartWorldTime; + float length = pursuitBreaker->GetAttrib()->length(); + if (elapsed > length) { + pursuitBreaker->StartWorldTime = 0.0f; + pursuitBreaker->Current = pursuitBreaker->Target; + } else if (elapsed >= 0.0f) { + float normalized = elapsed / length; + if (pursuitBreaker->Current > pursuitBreaker->Target) { + normalized = 1.0f - normalized; + } + pursuitBreaker->Current = + GetValueFromSpline(normalized, reinterpret_cast(&const_cast(pursuitBreaker->GetAttrib()->graph()))); + } + } + + VisualLookEffectTarget *nosRadialBlur = this->NosRadialBlur; + this->PursuitBreakerBlend = pursuitBreaker->Current; + if (nosRadialBlur->StartWorldTime != 0.0f) { + float elapsed = WorldTimeSeconds - nosRadialBlur->StartWorldTime; + float length = nosRadialBlur->GetAttrib()->length(); + if (elapsed > length) { + nosRadialBlur->StartWorldTime = 0.0f; + nosRadialBlur->Current = nosRadialBlur->Target; + } else if (elapsed >= 0.0f) { + float normalized = elapsed / length; + if (nosRadialBlur->Current > nosRadialBlur->Target) { + normalized = 1.0f - normalized; + } + nosRadialBlur->Current = + GetValueFromSpline(normalized, reinterpret_cast(&const_cast(nosRadialBlur->GetAttrib()->graph()))); + } + } + + float pursuitBreakerRadialBlur = 0.0f; + float nosRadialBlurAmount = nosRadialBlur->Current; + float nosRadialBlurValue = 0.0f; + if (this->PursuitBreakerBlend > 0.0f) { + pursuitBreakerRadialBlur = this->PursuitBreakerBlend * this->PursuitBreaker->GetAttrib()->radialblur_scale(); + pursuitBreakerRadialBlur = bMax(pursuitBreakerRadialBlur, 0.0f); + pursuitBreakerRadialBlur = bMin(pursuitBreakerRadialBlur, 1.0f); + } + + this->NosRadialBlurAmount = 0.0f; + if (nosRadialBlurAmount > 0.0f) { + nosRadialBlurValue = nosRadialBlurAmount * this->NosRadialBlur->GetAttrib()->radialblur_scale(); + nosRadialBlurValue = bMax(nosRadialBlurValue, 0.0f); + nosRadialBlurValue = bMin(nosRadialBlurValue, 0.5f); + this->NosRadialBlurAmount = nosRadialBlurValue; + } + + float radialBlur = bMax(pursuitBreakerRadialBlur, nosRadialBlurValue); + radialBlur = bMax(uvesRadialBlur, radialBlur); + this->RadialBlur = radialBlur; + + if (this->DesaturationTarget >= 0.0f) { + this->Desaturation = this->DesaturationTarget; + } + if (this->ColourBloomIntensityTarget >= 0.0f) { + this->ColourBloomIntensity = this->ColourBloomIntensityTarget; + } + + (void)view; +} + +void IVisualTreatment::Update(eView *view) { + bool inGameBreaker = false; + IGameState *gameState = IGameState::Get(); + if (gameState != 0 && gameState->InGameBreaker()) { + inGameBreaker = true; + } + + IPerpetrator *perpetrator = 0; + IEngine *engine = 0; + for (IPlayer::List::const_iterator iter = IPlayer::GetList(PLAYER_ALL).begin(); iter != IPlayer::GetList(PLAYER_ALL).end(); ++iter) { + IPlayer *player = *iter; + if (player->GetRenderPort() == view->GetID()) { + ISimable *simable = player->GetSimable(); + if (simable != 0) { + simable->QueryInterface(&perpetrator); + simable->QueryInterface(&engine); + } + break; + } + } + + if (UTL::Collections::Singleton::Get() == 0) { + if (inGameBreaker || gCinematicMomementCamera) { + this->SetPursuitBreakerTarget(1.0f); + } else { + this->SetPursuitBreakerTarget(0.0f); + } + } else if (this->PursuitBreaker != 0) { + this->PursuitBreaker->StartWorldTime = 0.0f; + this->PursuitBreaker->Current = 0.0f; + this->PursuitBreaker->Target = 0.0f; + } + + if (this->State == HEAT_LOOK) { + float targetHeat = 0.0f; + bool isBeingPursued = false; + if (perpetrator != 0) { + targetHeat = perpetrator->GetHeat(); + isBeingPursued = perpetrator->IsBeingPursued(); + } + + this->UpdateHeat(view, targetHeat, isBeingPursued); + this->HeatMeter = targetHeat; + } else { + this->UpdateVisualLook(); + } + + if (engine != 0) { + this->SetNosEngaged(engine->IsNOSEngaged()); + } +} diff --git a/src/Speed/Indep/Src/World/VisualTreatment.h b/src/Speed/Indep/Src/World/VisualTreatment.h index de5af1f7b..9f83c032f 100644 --- a/src/Speed/Indep/Src/World/VisualTreatment.h +++ b/src/Speed/Indep/Src/World/VisualTreatment.h @@ -5,8 +5,98 @@ #pragma once #endif -#include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "Speed/Indep/Src/Ecstasy/EcstasyE.hpp" #include "Speed/Indep/Src/Generated/AttribSys/Classes/visuallook.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/visuallookeffect.h" +#include "Speed/Indep/Src/Misc/Timer.hpp" +#include "Speed/Indep/bWare/Inc/bMath.hpp" + +extern float WorldTimeSeconds; + +class VisualLookEffect { + friend class IVisualTreatment; + + public: + explicit VisualLookEffect(Attrib::Gen::visuallookeffect *attribEffect) + : AttribEffect(attribEffect), // + StartTime(0.0f), // + PulseLength(0.0f) + { + StopIfHeatFalls = 1; + StopAfterLength = 0; + UseWorldTime = 1; + } + + float Update(float heatMeter); + void Reset(); + + Attrib::Gen::visuallookeffect *GetAttrib() { + return this->AttribEffect; + } + + void Trigger(float length, bool useWorldTime, bool stopIfHeatFalls, bool stopAfterLength) { + if (length == 0.0f) { + length = AttribEffect->length(); + if (length == 0.0f) { + return; + } + } + this->StartTime = useWorldTime ? WorldTimer.GetSeconds() : RealTimer.GetSeconds(); + this->PulseLength = length; + this->UseWorldTime = useWorldTime; + this->StopIfHeatFalls = stopIfHeatFalls; + this->StopAfterLength = stopAfterLength; + } + + protected: + bool IsActive(); + float UpdateActive(float heatMeter); + + Attrib::Gen::visuallookeffect *AttribEffect; // offset 0x0, size 0x4 + float StartTime; // offset 0x4, size 0x4 + float PulseLength; // offset 0x8, size 0x4 + int UseWorldTime; // offset 0xC, size 0x4 + int StopIfHeatFalls; // offset 0x10, size 0x4 + int StopAfterLength; // offset 0x14, size 0x4 +}; + +class VisualLookEffectTarget { + friend class IVisualTreatment; + + public: + explicit VisualLookEffectTarget(Attrib::Gen::visuallookeffect *attribEffect) + : AttribEffect(attribEffect) + { + Target = 0.0f; + Current = 0.0f; + StartWorldTime = 0.0f; + } + + void Reset(); + float Update(); + void SetTarget(float target) { + if (this->Target == target) { + return; + } + this->Target = target; + this->StartWorldTime = WorldTimeSeconds; + } + void SetCurrent(float value) { + this->Current = value; + this->Target = value; + this->StartWorldTime = 0.0f; + } + + Attrib::Gen::visuallookeffect *GetAttrib() { + return this->AttribEffect; + } + + private: + Attrib::Gen::visuallookeffect *AttribEffect; // offset 0x0, size 0x4 + float StartWorldTime; // offset 0x4, size 0x4 + float Current; // offset 0x8, size 0x4 + float Target; // offset 0xC, size 0x4 +}; class IVisualTreatment { public: @@ -16,55 +106,91 @@ class IVisualTreatment { FE_LOOK = 2, }; + IVisualTreatment(); + ~IVisualTreatment(); + static IVisualTreatment *Get(); - void Reset(); void SetState(eVisualLookState state) { - Reset(); this->State = state; } + void Update(eView *view); + void SetPursuitBreakerTarget(float blendTarget); + void SetNosEngaged(bool isNosEngaged); + + void SetDesaturationTarget(float setTarget) { + this->DesaturationTarget = setTarget; + } + + void SetColourBloomIntensityTarget(float setTarget) { + this->ColourBloomIntensityTarget = setTarget; + } + + float GetRadialBlur() { + return this->RadialBlur; + } + + float GetNosRadialBlur() const { + return this->NosRadialBlurAmount; + } + + float GetPursuitBreakerBlend() { + return this->PursuitBreakerBlend; + } + + void TriggerPulse(float length); + void Reset(); + void PrintValues(); + protected: - eVisualLookState State; // offset 0x0, size 0x4 - bMatrix4 BlackBloomCurve; // offset 0x4, size 0x40 - bMatrix4 ColourBloomCurve; // offset 0x44, size 0x40 - float BlackBloomIntensity; // offset 0x84, size 0x4 - float ColourBloomIntensity; // offset 0x88, size 0x4 - float Desaturation; // offset 0x8C, size 0x4 - float CombinedBrightness; // offset 0x90, size 0x4 - bVector4 ColourBloomTint; // offset 0x94, size 0x10 - bMatrix4 DetailMapCurve; // offset 0xA4, size 0x40 - bVector4 vCurveCoeffs0; // offset 0xE4, size 0x10 - bVector4 vCurveCoeffs1; // offset 0xF4, size 0x10 - bVector4 vCurveCoeffs2; // offset 0x104, size 0x10 - bVector4 vCurveCoeffs3; // offset 0x114, size 0x10 - bVector4 vCoeffs0; // offset 0x124, size 0x10 - bVector4 vCoeffs1; // offset 0x134, size 0x10 - bVector4 vCoeffs2; // offset 0x144, size 0x10 - bVector4 vCoeffs3; // offset 0x154, size 0x10 - float DetailMapIntensity; // offset 0x164, size 0x4 - float PulseBrightness; // offset 0x168, size 0x4 - float RadialBlur; // offset 0x16C, size 0x4 - float PursuitBreakerBlend; // offset 0x170, size 0x4 - float NosRadialBlurAmount; // offset 0x174, size 0x4 - Attrib::Gen::visuallook MiddayVisualLook; // offset 0x178, size 0x14 - Attrib::Gen::visuallook SunsetVisualLook; // offset 0x18C, size 0x14 - Attrib::Gen::visuallook UvesVisualLook; // offset 0x1A0, size 0x14 - Attrib::Gen::visuallook CopCamVisualLook; // offset 0x1B4, size 0x14 - struct VisualLookEffect *UvesPulse; // offset 0x1C8, size 0x4 - struct VisualLookEffect *UvesRadialBlur; // offset 0x1CC, size 0x4 - struct VisualLookEffect *UvesTransition; // offset 0x1D0, size 0x4 - struct VisualLookEffect *CameraFlash; // offset 0x1D4, size 0x4 - struct VisualLookEffectTarget *PursuitBreaker; // offset 0x1D8, size 0x4 - struct VisualLookEffectTarget *NosRadialBlur; // offset 0x1DC, size 0x4 - float CurrentTarget; // offset 0x1E0, size 0x4 - float DesaturationTarget; // offset 0x1E4, size 0x4 - float ColourBloomIntensityTarget; // offset 0x1E8, size 0x4 - float HeatMeter; // offset 0x1EC, size 0x4 - int IsBeingPursued; // offset 0x1F0, size 0x4 -}; + void UpdateVisualLook(); + void BlendVisualLookAttribute(bMatrix4 &result, float defaultUves, float uves, + const UMath::Matrix4 &(Attrib::Gen::visuallook::*funcPtr)() const); + void BlendVisualLookAttribute(float &result, float defaultUves, float uves, + const float &(Attrib::Gen::visuallook::*funcPtr)() const); + void BlendVisualLookAttribute(bVector4 &result, float defaultUves, float uves, + const UMath::Vector4 &(Attrib::Gen::visuallook::*funcPtr)() const); + void TriggerUves(); + void UpdateHeat(eView *view, float targetHeat, bool isBeingPursued); -void OpenVisualTreatment(); -void CloseVisualTreatment(); + eVisualLookState State; // offset 0x0, size 0x4 + bMatrix4 BlackBloomCurve; // offset 0x4, size 0x40 + bMatrix4 ColourBloomCurve; // offset 0x44, size 0x40 + float BlackBloomIntensity; // offset 0x84, size 0x4 + float ColourBloomIntensity; // offset 0x88, size 0x4 + float Desaturation; // offset 0x8C, size 0x4 + float CombinedBrightness; // offset 0x90, size 0x4 + bVector4 ColourBloomTint; // offset 0x94, size 0x10 + bMatrix4 DetailMapCurve; // offset 0xA4, size 0x40 + bVector4 vCurveCoeffs0; // offset 0xE4, size 0x10 + bVector4 vCurveCoeffs1; // offset 0xF4, size 0x10 + bVector4 vCurveCoeffs2; // offset 0x104, size 0x10 + bVector4 vCurveCoeffs3; // offset 0x114, size 0x10 + bVector4 vCoeffs0; // offset 0x124, size 0x10 + bVector4 vCoeffs1; // offset 0x134, size 0x10 + bVector4 vCoeffs2; // offset 0x144, size 0x10 + bVector4 vCoeffs3; // offset 0x154, size 0x10 + float DetailMapIntensity; // offset 0x164, size 0x4 + float PulseBrightness; // offset 0x168, size 0x4 + float RadialBlur; // offset 0x16C, size 0x4 + float PursuitBreakerBlend; // offset 0x170, size 0x4 + float NosRadialBlurAmount; // offset 0x174, size 0x4 + Attrib::Gen::visuallook MiddayVisualLook; // offset 0x178, size 0x14 + Attrib::Gen::visuallook SunsetVisualLook; // offset 0x18C, size 0x14 + Attrib::Gen::visuallook UvesVisualLook; // offset 0x1A0, size 0x14 + Attrib::Gen::visuallook CopCamVisualLook; // offset 0x1B4, size 0x14 + VisualLookEffect *UvesPulse; // offset 0x1C8, size 0x4 + VisualLookEffect *UvesRadialBlur; // offset 0x1CC, size 0x4 + VisualLookEffect *UvesTransition; // offset 0x1D0, size 0x4 + VisualLookEffect *CameraFlash; // offset 0x1D4, size 0x4 + VisualLookEffectTarget *PursuitBreaker; // offset 0x1D8, size 0x4 + VisualLookEffectTarget *NosRadialBlur; // offset 0x1DC, size 0x4 + float CurrentTarget; // offset 0x1E0, size 0x4 + float DesaturationTarget; // offset 0x1E4, size 0x4 + float ColourBloomIntensityTarget; // offset 0x1E8, size 0x4 + float HeatMeter; // offset 0x1EC, size 0x4 + int IsBeingPursued; // offset 0x1F0, size 0x4 +}; #endif diff --git a/src/Speed/Indep/Src/World/WCollider.h b/src/Speed/Indep/Src/World/WCollider.h index fcafc2d6f..b3c96dff7 100644 --- a/src/Speed/Indep/Src/World/WCollider.h +++ b/src/Speed/Indep/Src/World/WCollider.h @@ -6,6 +6,7 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/FastMem.h" #include "Speed/Indep/Libs/Support/Utility/UListable.h" #include "Speed/Indep/Libs/Support/Utility/UStandard.h" #include "Speed/Indep/Libs/Support/Utility/UTypes.h" @@ -30,16 +31,37 @@ class WCollider : public UTL::Collections::Listable { // total size: 0x10 }; + WCollider(eColliderShape colliderShape, unsigned int typeMask, unsigned int exclusionMask); + ~WCollider(); + + static WCollider *Get(unsigned int wuid); + static WCollider *Create(unsigned int wuid, eColliderShape shape, unsigned int typeCheckMask, unsigned int exclusionMask); static void Destroy(WCollider *col); + static void InvalidateIntersectingColliders(const UMath::Vector4 &posRad); + static void InvalidateAllCachedData(); void Clear(); bool IsEmpty() const; void Refresh(const UMath::Vector3 &pt, float radius, bool predictiveSizing); + void AddRef() { ++fRefCount; } + void RemoveRef() { --fRefCount; } + + static void *operator new(unsigned int size) { return gFastMem.Alloc(size, nullptr); } + static void operator delete(void *mem, unsigned int size) { if (mem) gFastMem.Free(mem, size, nullptr); } + WCollisionInstanceCacheList &GetInstanceList() { return fInstanceCacheList; } + const WCollisionTriList &GetTriList() const { + return fTriList; + } + + WCollisionTriList &GetTriList() { + return fTriList; + } + ALIGN_16 UMath::Vector3 fRequestedPosition; // offset 0x4, size 0xC float fRequestedRadius; // offset 0x10, size 0x4 ALIGN_16 UMath::Vector3 fLastRequestedPosition; // offset 0x14, size 0xC @@ -57,6 +79,17 @@ class WCollider : public UTL::Collections::Listable { unsigned int fRefCount; // offset 0x90, size 0x4 unsigned int fWorldID; // offset 0x94, size 0x4 unsigned int fExclusionFlags; // offset 0x98, size 0x4 + + static UTL::Std::map fWuidMap; + + private: + void PrepareRegion(unsigned int updateMask); + void ClearLists(unsigned int typeMask); + void EmptyLists(unsigned int typeMask); + void ReserveLists(unsigned int typeMask); + unsigned int Validate() const; + unsigned int GetUpdateMask(const UMath::Vector3 &pt, float radius); + bool InRegion(const UMath::Vector3 &pt, float radius) const; }; #endif diff --git a/src/Speed/Indep/Src/World/WCollision.h b/src/Speed/Indep/Src/World/WCollision.h index cf8e7cef8..c4d276646 100644 --- a/src/Speed/Indep/Src/World/WCollision.h +++ b/src/Speed/Indep/Src/World/WCollision.h @@ -11,10 +11,102 @@ #include "Speed/Indep/Src/Physics/Dynamics/Collision.h" #include "Speed/Indep/bWare/Inc/bMath.hpp" -struct WSurface : public CollisionSurface {}; +struct WCollisionPackedVert { + short x; + short y; + short z; + CollisionSurface surface; +}; + +struct WSurface : CollisionSurface { + static void InitSystem(); + static WSurface kNull; + + WSurface() { + fSurface = 0; + fFlags = 0; + } + WSurface(const CollisionSurface &surface) { + fSurface = surface.fSurface; + fFlags = surface.fFlags; + } + WSurface(unsigned char surface, unsigned char flags) { + fSurface = surface; + fFlags = flags; + } + + unsigned int Surface() const { + return fSurface; + } + + unsigned char &FlagsRef() { + return fFlags; + } + + unsigned char Flags() const { + return fFlags; + } + + bool HasFlag(unsigned char flag) const { + return (fFlags & flag) != 0; + } +}; + +struct WCollisionBarrier; +struct WCollisionTri; + +struct WCollisionStripSphere { + // total size: 0x10 + unsigned int Offset() const { + return static_cast(fOffset) + 0x10; + } + + UMath::Vector3 fPos; // offset 0x0, size 0xC + unsigned short fRadius; // offset 0xC, size 0x2 + unsigned short fOffset; // offset 0xE, size 0x2 +}; + +struct WCollisionStrip { + // total size: 0x1 + const WCollisionPackedVert *Verts() const { + return reinterpret_cast(this); + } + + unsigned int NumVerts() const { + const WCollisionPackedVert *v = Verts(); + return *reinterpret_cast(&v[0].surface); + } + + unsigned int NumTris() const { + return NumVerts() - 2; + } + + unsigned int Flags() const { + return *reinterpret_cast(&Verts()[1].surface); + } + + void MakeFace(unsigned int ind, const UMath::Vector3 &cp, WCollisionTri &retFace) const; + void MakeNextFace(unsigned int ind, const UMath::Vector3 &cp, WCollisionTri &retFace) const; +}; struct WCollisionArticle { // total size: 0x10 + void Resolve(); + + const Attrib::Collection *GetSurface(unsigned int ind) const { + unsigned int ref = fStripsSize + 0x10; + const char *dataStart = reinterpret_cast(this) + ref + fEdgesSize; + return reinterpret_cast( + *reinterpret_cast(dataStart + ind * 4)); + } + + inline const WCollisionBarrier *GetBarrier(unsigned int ind) const; + + const WCollisionStripSphere *GetStripSphere(unsigned int ind) const { + const char *dataStart = reinterpret_cast(this) + 0x10; + return reinterpret_cast(dataStart + ind * sizeof(WCollisionStripSphere)); + } + unsigned short fNumStrips; // offset 0x0, size 0x2 unsigned short fStripsSize; // offset 0x2, size 0x2 unsigned short fNumEdges; // offset 0x4, size 0x2 @@ -28,9 +120,90 @@ struct WCollisionArticle { // total size: 0x20 struct WCollisionBarrier { + const WSurface &GetWSurface() const { + return *reinterpret_cast( + reinterpret_cast(this) + 0xC); + } + + const WCollisionBarrier *Next() const { + return this + 1; + } + + const UMath::Vector4 *GetPts() const { + return fPts; + } + + const UMath::Vector4 *GetPt(int ptInd) const { + return &fPts[ptInd]; + } + + const float YBot() const { + return fPts[0].y; + } + + const float YTop() const { + return fPts[1].y; + } + + float GetInvXZLength() const { + return fPts[1].w; + } + + void GetNormal(UMath::Vector3 &norm) const { + float invLen = GetInvXZLength(); + norm.x = (fPts[1].z - fPts[0].z) * invLen; + norm.y = 0.0f; + norm.z = (fPts[0].x - fPts[1].x) * invLen; + } + + void GetCenter(UMath::Vector4 &cp) const { + cp.x = (fPts[0].x + fPts[1].x) * 0.5f; + cp.y = (fPts[0].y + fPts[1].y) * 0.5f; + cp.z = (fPts[0].z + fPts[1].z) * 0.5f; + } + + float GetWidth() const { + float rz = fPts[0].z - fPts[1].z; + float rx = fPts[0].x - fPts[1].x; + return UMath::Sqrt(rx * rx + rz * rz); + } + + float GetHeight() const { + return UMath::Abs(fPts[0].y - fPts[1].y); + } + + float DistSq(const UMath::Vector3 &pt) const { + float invLen = GetInvXZLength(); + float z1 = fPts[0].z; + float z2 = fPts[1].z; + float pz = pt.z; + float x1 = fPts[0].x; + float x2 = fPts[1].x; + float px = pt.x; + float u = ((pz - z1) * (z2 - z1) + (px - x1) * (x2 - x1)) * invLen * invLen; + float nearZ; + float nearX; + if (u < 0.0f) { + nearZ = z1 - pz; + nearX = x1 - px; + } else if (u > 1.0f) { + nearZ = z2 - pz; + nearX = x2 - px; + } else { + nearZ = u * (z2 - z1) + z1 - pz; + nearX = u * (x2 - x1) + x1 - px; + } + return nearX * nearX + nearZ * nearZ; + } + UMath::Vector4 fPts[2]; // offset 0x0, size 0x20 }; +inline const WCollisionBarrier *WCollisionArticle::GetBarrier(unsigned int ind) const { + const char *dataStart = reinterpret_cast(this) + (fStripsSize + 0x10); + return reinterpret_cast(dataStart + ind * 0x20); +} + // total size: 0x28 struct WCollisionBarrierListEntry { WCollisionBarrier fB; // offset 0x0, size 0x20 @@ -41,14 +214,57 @@ struct WCollisionBarrierListEntry { : fB(), // fSurfaceRef(nullptr), // fDistanceToSq(0.0f) {} + + WCollisionBarrierListEntry(const WCollisionBarrier &b, const Attrib::Collection *surfHash, float disttosq) + : fB(b), // + fSurfaceRef(surfHash), // + fDistanceToSq(disttosq) {} + + bool operator<(const WCollisionBarrierListEntry &rhs) const { + return fDistanceToSq < rhs.fDistanceToSq; + } }; struct WCollisionObject : public CollisionObject { // total size: 0x70 + enum Types { + kBox = 0, + kCylinder = 1, + }; + + const WSurface GetWSurface() const { + return WSurface(fSurface.fSurface, fSurface.fFlags); + } + + bool IsDynamic() const { return (fFlags & 1) != 0; } + + void MakeMatrix(UMath::Matrix4 &m, bool addXLate) const; }; +extern signed char SceneryGroupEnabledTable[]; + +inline int IsSceneryGroupEnabled(int group_number) { + return SceneryGroupEnabledTable[group_number]; +} + struct WCollisionInstance : public CollisionInstance { // total size: 0x40 + + inline bool NeedsCrossProduct() const { + return (fFlags & 3) != 0; + } + + inline bool IsYVecNotUp() const { + return (fFlags & 1) != 0; + } + + inline bool IsDynamic() const { + return (fFlags & 2) != 0; + } + + float CalcSphericalRadius() const; + void CalcPosition(UMath::Vector3 &pos) const; + void MakeMatrix(UMath::Matrix4 &m, bool addXLate) const; }; #endif diff --git a/src/Speed/Indep/Src/World/WCollisionAssets.h b/src/Speed/Indep/Src/World/WCollisionAssets.h index ce5d3b5e9..c66a5ce2e 100644 --- a/src/Speed/Indep/Src/World/WCollisionAssets.h +++ b/src/Speed/Indep/Src/World/WCollisionAssets.h @@ -6,10 +6,15 @@ #endif #include "Speed/Indep/Libs/Support/Utility/UGroup.hpp" +#include "Speed/Indep/Libs/Support/Utility/UStandard.h" #include "Speed/Indep/Libs/Support/Utility/UTypes.h" #include "Speed/Indep/bWare/Inc/bChunk.hpp" #include "WCollision.h" +struct ManagedCollisionInstance; +typedef struct UTL::Std::map CollisionInstanceMap; +typedef struct UTL::Std::map CollisionObjectMap; + // total size: 0x3C class WCollisionAssets { public: @@ -29,7 +34,7 @@ class WCollisionAssets { const WCollisionInstance *Instance(unsigned int ind) const; const WCollisionObject *Object(unsigned int ind) const; unsigned int AddObject(WCollisionObject *obj); - struct WCollisionObject *CreateObject(UMath::Vector3 &dim, const UMath::Matrix4 &mat, bool dynamicFlag); + struct WCollisionObject *CreateObject(const UMath::Vector3 &dim, const UMath::Matrix4 &mat, bool dynamicFlag); struct WTrigger &Trigger(unsigned int tag) const; void AddTrigger(struct WTrigger *trig); void RemoveTrigger(struct WTrigger *trigger); @@ -41,7 +46,7 @@ class WCollisionAssets { // static bool Exists() {} - // unsigned int NumTriggers() const {} + unsigned int NumTriggers() const { return fStaticTriggersCount; } static WCollisionAssets *sWCollisionAssets; // size: 0x4, address: 0x80438F58 static unsigned int sTriggerDataSize; // size: 0x4, address: 0xFFFFFFFF @@ -51,11 +56,11 @@ class WCollisionAssets { const WCollisionInstance *fStaticCollisionInstances; // offset 0x0, size 0x4 unsigned int fStaticCollisionInstancesCount; // offset 0x4, size 0x4 - struct CollisionInstanceMap *fManagedCollisionInstances; // offset 0x8, size 0x4 + CollisionInstanceMap *fManagedCollisionInstances; // offset 0x8, size 0x4 unsigned int fManagedCollisionInstancesInd; // offset 0xC, size 0x4 const WCollisionObject *fStaticCollisionObjects; // offset 0x10, size 0x4 unsigned int fStaticCollisionObjectsCount; // offset 0x14, size 0x4 - struct CollisionObjectMap *fManagedCollisionObjects; // offset 0x18, size 0x4 + CollisionObjectMap *fManagedCollisionObjects; // offset 0x18, size 0x4 unsigned int fManagedCollisionObjectsInd; // offset 0x1C, size 0x4 unsigned int fNumPackLoadCallbacks; // offset 0x20, size 0x4 void (*fPackLoadCallback[4])(int, bool); // offset 0x24, size 0x10 diff --git a/src/Speed/Indep/Src/World/WCollisionMgr.h b/src/Speed/Indep/Src/World/WCollisionMgr.h index 9f44211f1..76bfd5a18 100644 --- a/src/Speed/Indep/Src/World/WCollisionMgr.h +++ b/src/Speed/Indep/Src/World/WCollisionMgr.h @@ -34,7 +34,7 @@ class WCollisionMgr { fPad(0), // fCInst(nullptr) {} - bool HitSomething() const {} + bool HitSomething() const { return fType != 0; } }; class ICollisionHandler { @@ -80,6 +80,18 @@ class WCollisionMgr { bool GetBarrierNormal(const WCollisionBarrierList &barrierList, const UMath::Vector4 *testSegment, WorldCollisionInfo &cInfo); void GetTriList(const WCollisionInstanceCacheList &instList, const UMath::Vector3 &pt, float radius, WCollisionTriList &triList); + bool StripPassesExclusion(const WCollisionStrip &strip) const { + return (fSurfaceExclusionMask & strip.Flags()) == 0; + } + + bool InstancePassesExclusion(const WCollisionInstance &inst) const { + return (fSurfaceExclusionMask & inst.fFlags) == 0; + } + + bool SurfacePassesExclusion(const WSurface &surface) const { + return (fSurfaceExclusionMask & surface.fFlags) == 0; + } + WCollisionMgr(unsigned int surfaceExclMask, unsigned int primitiveExclMask) { this->fSurfaceExclusionMask = surfaceExclMask; this->fPrimitiveMask = primitiveExclMask; diff --git a/src/Speed/Indep/Src/World/WCollisionPack.h b/src/Speed/Indep/Src/World/WCollisionPack.h index e4e6db8cc..bf9972465 100644 --- a/src/Speed/Indep/Src/World/WCollisionPack.h +++ b/src/Speed/Indep/Src/World/WCollisionPack.h @@ -5,6 +5,43 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/UGroup.hpp" +#include "Speed/Indep/Libs/Support/Utility/FastMem.h" +#include "Speed/Indep/Src/World/WCollision.h" +#include "Speed/Indep/bWare/Inc/bChunk.hpp" +struct WCollisionPack { + // total size: 0x18 + inline unsigned int InstanceCount() { + return mInstanceNum; + } + + void *operator new(size_t size) { + return gFastMem.Alloc(size, nullptr); + } + + void operator delete(void *mem, size_t size) { + if (mem) { + gFastMem.Free(mem, size, nullptr); + } + } + + WCollisionPack(bChunk *chunk); + ~WCollisionPack(); + + void Init(bChunk *chunk); + void DeInit(); + void Resolve(const UGroup *cGroup, unsigned int deltaAddress); + + const WCollisionInstance *Instance(unsigned short index) const; + const WCollisionObject *Object(unsigned short index) const; + + unsigned int mSectionNumber; // offset 0x0, size 0x4 + unsigned int mInstanceNum; // offset 0x4, size 0x4 + const WCollisionInstance *mInstanceList; // offset 0x8, size 0x4 + unsigned int mObjectNum; // offset 0xC, size 0x4 + const WCollisionObject *mObjectList; // offset 0x10, size 0x4 + bChunkCarpHeader *mCarpChunkHeader; // offset 0x14, size 0x4 +}; #endif diff --git a/src/Speed/Indep/Src/World/WCollisionTri.h b/src/Speed/Indep/Src/World/WCollisionTri.h index b88fd9b2b..dc470984a 100644 --- a/src/Speed/Indep/Src/World/WCollisionTri.h +++ b/src/Speed/Indep/Src/World/WCollisionTri.h @@ -6,9 +6,12 @@ #endif #include "./WCollision.h" +#include "Speed/Indep/Libs/Support/Utility/UMath.h" #include "Speed/Indep/Libs/Support/Utility/UStandard.h" #include "Speed/Indep/Libs/Support/Utility/UTypes.h" +void v3unit(const UMath::Vector3 *v, UMath::Vector3 *result); + struct WCollisionTri { // total size: 0x30 UMath::Vector3 fPt0; // offset 0x0, size 0xC @@ -18,6 +21,33 @@ struct WCollisionTri { UMath::Vector3 fPt2; // offset 0x20, size 0xC WSurface fSurface; // offset 0x2C, size 0x2 unsigned short PAD; // offset 0x2E, size 0x2 + + WCollisionTri() {} + + float MinY() const { + float minY = UMath::Min(fPt0.y, fPt1.y); + return UMath::Min(minY, fPt2.y); + } + + inline void GetNormal(UMath::Vector3 *norm) const { + UMath::Vector3 vecX; + UMath::Vector3 vecZ; + vecZ.x = fPt1.x - fPt0.x; + vecZ.y = fPt1.y - fPt0.y; + vecZ.z = fPt1.z - fPt0.z; + vecX.x = fPt0.x - fPt2.x; + vecX.y = fPt0.y - fPt2.y; + vecX.z = fPt0.z - fPt2.z; + UMath::Vector3 normal; + UMath::Cross(vecZ, vecX, normal); + if (normal.x == 0.0f && normal.y == 0.0f && normal.z == 0.0f) { + norm->x = 0.0f; + norm->y = 1.0f; + norm->z = 0.0f; + } else { + v3unit(&normal, norm); + } + } }; DECLARE_CONTAINER_TYPE(WCollisionWarnVector); @@ -36,9 +66,40 @@ struct WCollisionBarrierList : public WCollisionVector {}; +struct WCollisionTriBlock : public WCollisionVector { + static void *operator new(unsigned int size) { return gFastMem.Alloc(size, nullptr); } + static void operator delete(void *mem, unsigned int size) { if (mem) gFastMem.Free(mem, size, nullptr); } +}; -struct WCollisionTriList : public WCollisionVector {}; +struct WCollisionTriList : public WCollisionVector { + // total size: 0x14 + WCollisionTriList() : mCurrBlock(nullptr) {} + ~WCollisionTriList() { clear_all(); } + + inline void clear_all() { +#ifdef _MSC_VER + for (unsigned int i = 0; i < size(); ++i) { + delete (*this)[i]; + } +#else + for (WCollisionTriBlock **i = begin(); i != end(); ++i) { + delete *i; + } +#endif + clear(); + mCurrBlock = nullptr; + } + inline void add_tri(const WCollisionTri &tri) { + if (mCurrBlock == nullptr || mCurrBlock->size() == mCurrBlock->capacity()) { + mCurrBlock = new WCollisionTriBlock(); + mCurrBlock->reserve(0x15); + push_back(mCurrBlock); + } + mCurrBlock->push_back(tri); + } + + WCollisionTriBlock *mCurrBlock; // offset 0x10, size 0x4 +}; struct WCollisionObjectList : public WCollisionVector {}; diff --git a/src/Speed/Indep/Src/World/WGridManagedDynamicElem.h b/src/Speed/Indep/Src/World/WGridManagedDynamicElem.h index ebbe9b8d5..d53340ba7 100644 --- a/src/Speed/Indep/Src/World/WGridManagedDynamicElem.h +++ b/src/Speed/Indep/Src/World/WGridManagedDynamicElem.h @@ -5,6 +5,7 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/UStandard.h" #include "Speed/Indep/Libs/Support/Utility/UTypes.h" enum WGridNode_ElemType { @@ -17,6 +18,11 @@ enum WGridNode_ElemType { // total size: 0x8 struct WGridNodeElem { + WGridNodeElem() {} + WGridNodeElem(unsigned int ind, WGridNode_ElemType type) + : fInd(ind), // + fType(type) {} + unsigned int fInd; // offset 0x0, size 0x4 WGridNode_ElemType fType; // offset 0x4, size 0x4 }; @@ -24,9 +30,24 @@ struct WGridNodeElem { // total size: 0x40 class WGridManagedDynamicElem { public: + WGridManagedDynamicElem(); + WGridManagedDynamicElem(UMath::Vector4 *dstPosRad, const UMath::Vector4 *srcPosRad, const WGridNodeElem &elem); + WGridManagedDynamicElem(struct CollisionInstance *dst, const WGridNodeElem &elem); + WGridManagedDynamicElem(struct Trigger *dst, const UMath::Vector4 &offsetVec, const WGridNodeElem &elem); + + void Update(); + + static void AddElem(const UMath::Vector4 *oldPosRad, const UMath::Vector4 *newPosRad, WGridNode_ElemType type, unsigned int dataInd); + static void Init() { fgManagedDynamicElemList.clear(); } + static void Shutdown(); static void UpdateElems(); - private: + static std::list > &DynamicElemList() { + return fgManagedDynamicElemList; + } + + static std::list > fgManagedDynamicElemList; + unsigned int fType; // offset 0x0, size 0x4 const UMath::Vector4 *fPosRad; // offset 0x4, size 0x4 UMath::Vector4 fOffsetVec; // offset 0x8, size 0x10 diff --git a/src/Speed/Indep/Src/World/WPathFinder.h b/src/Speed/Indep/Src/World/WPathFinder.h new file mode 100644 index 000000000..541fd60ac --- /dev/null +++ b/src/Speed/Indep/Src/World/WPathFinder.h @@ -0,0 +1,111 @@ +#ifndef _WPATHFINDER +#define _WPATHFINDER + +#include "Speed/Indep/Src/Sim/SimActivity.h" +#include "Speed/Indep/Src/Sim/SimTypes.h" +#include "Speed/Indep/Src/World/WRoadNetwork.h" +#include "Speed/Indep/bWare/Inc/bList.hpp" +#include "Speed/Indep/bWare/Inc/bSlotPool.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +struct HSIMTASK__; + +extern float ASTAR_METRIC_SCALE; +extern SlotPool *AStarSearchSlotPool; +extern SlotPool *AStarNodeSlotPool; + +struct AStarNode : public bTNode { + static void *operator new(unsigned int size) { return bMalloc(AStarNodeSlotPool); } + static void operator delete(void *ptr); + + AStarNode() {} + AStarNode(AStarNode *parent, const WRoadNode *road_node, int segment, float actual_cost, float estimated_cost) + : nParentSlot(parent != nullptr ? bGetSlotNumber(AStarNodeSlotPool, parent) : -1), // + nSegmentIndex(static_cast(segment)), // + nRoadNode(road_node->fIndex), // + fActualCost(static_cast(static_cast(actual_cost / ASTAR_METRIC_SCALE + 0.5f))), + fEstimatedCost(static_cast(static_cast(estimated_cost / ASTAR_METRIC_SCALE + 0.5f))) {} + + AStarNode *GetParent() { + if (nParentSlot < 0) { + return nullptr; + } + return static_cast(bGetSlot(AStarNodeSlotPool, nParentSlot)); + } + + const WRoadNode *GetRoadNode() { return WRoadNetwork::Get().GetNode(nRoadNode); } + int GetSegmentIndex() { return nSegmentIndex; } + float GetActualCost() { return static_cast(fActualCost) * ASTAR_METRIC_SCALE; } + float GetEstimatedCost() { return static_cast(fEstimatedCost) * ASTAR_METRIC_SCALE; } + float GetTotalCost() { return GetActualCost() + GetEstimatedCost(); } + + short nParentSlot; + short nSegmentIndex; + short nRoadNode; + unsigned short fActualCost; + unsigned short fEstimatedCost; +}; + +enum AStarSearchState {}; + +struct AStarSearch : public bTNode { + static void *operator new(unsigned int size) { return bMalloc(AStarSearchSlotPool); } + static void operator delete(void *ptr) { bFree(AStarSearchSlotPool, ptr); } + + AStarSearch(WRoadNav *road_nav, const UMath::Vector3 *goal_position, const UMath::Vector3 *goal_direction, const char *shortcut_allowed); + virtual ~AStarSearch(); + bool IsGoal(AStarNode *node); + AStarNode *FindOpenNode(const WRoadNode *road_node, int segment_number); + AStarNode *FindClosedNode(const WRoadNode *road_node, int segment_number); + static int AStarCheckFlip(AStarNode *before, AStarNode *after); + bool Admissible(const WRoadSegment *segment, bool forward, WRoadNav::EPathType path_type); + float Service(float time); + bool IsFinished() { return nState > 0; } + bool IsActive() { return nState == 0; } + WRoadNav *GetRoadNav() { return pRoadNav; } + + AStarSearchState nState; + AStarNode *pSolution; + int nServices; + int nSteps; + float fSearchTime; + unsigned int nShortcutCached; + unsigned int nShortcutAllowed; + const char *pShortcutAllowed; + WRoadNav *pRoadNav; + const WRoadNode *pGoalNode; + int nGoalSegment; + UMath::Vector3 vGoalPosition; + bTList lOpen; + bTList lClosed; +}; + +class PathFinder : public Sim::Activity { + public: + PathFinder(); + ~PathFinder() override; + + static PathFinder *Get() { + return pInstance; + } + + static void Set(PathFinder *instance) { + pInstance = instance; + } + + static Sim::IActivity *Construct(Sim::Param params); + void Service(float time_limit_ms); + void ServiceAll(); + bool OnTask(HSIMTASK__ *htask, float elapsed_seconds) override; + void Cancel(WRoadNav *road_nav); + AStarSearch *Pending(WRoadNav *road_nav); + AStarSearch *Submit(WRoadNav *road_nav, const UMath::Vector3 *goal_position, const UMath::Vector3 *goal_direction, const char *shortcut_allowed); + + private: + static PathFinder *pInstance; + + HSIMTASK__ *mSimTask; + bTList lSearches; +}; + +#endif diff --git a/src/Speed/Indep/Src/World/WRoadElem.h b/src/Speed/Indep/Src/World/WRoadElem.h index 3cdbd826f..3ea2e1f49 100644 --- a/src/Speed/Indep/Src/World/WRoadElem.h +++ b/src/Speed/Indep/Src/World/WRoadElem.h @@ -9,7 +9,10 @@ // total size: 0x8 struct WRoad { - // float GetScale() const {} + float GetScale() const { + unsigned int s = static_cast< unsigned int >(nScale) << 8; + return static_cast< float >(s) * (1.0f / 65536.0f); + } // float GetLength() const {} @@ -30,7 +33,14 @@ struct WRoad { // total size: 0x20 struct WRoadNode { - // bool IsSegment(unsigned short segment_id) const {} + bool IsSegment(unsigned short segment_id) const { + for (int i = 0; i < static_cast< int >(fNumSegments); i++) { + if (fSegmentIndex[i] == segment_id) { + return true; + } + } + return false; + } UMath::Vector3 fPosition; // offset 0x0, size 0xC short fIndex; // offset 0xC, size 0x2 @@ -41,100 +51,129 @@ struct WRoadNode { // total size: 0x4 struct WRoadLane { - // int GetType() const {} - - // float GetWidth() const {} - - // float GetOffset() const {} - - // void SetType(int type) {} - - // void SetWidth(float width) {} - - // void SetOffset(float offset) {} - - // unsigned int &GetBits() {} - - // void SwapEndian() {} + int GetType() const { + return GetBits(0, 4); + } - // unsigned int GetBits(int n_offset, int n_bits) const {} + unsigned int GetBits(int n_offset, int n_bits) const; + int GetBitsSigned(int n_offset, int n_bits) const; - // int GetBitsSigned(int n_offset, int n_bits) const {} + float GetWidth() const { + return static_cast< float >(GetBitsSigned(4, 14)) * (100.0f / 8191.0f); + } - // void SetBits(int n_offset, int n_bits, int n_value) {} + float GetOffset() const { + return static_cast< float >(GetBitsSigned(18, 14)) * (100.0f / 8191.0f); + } unsigned int nBits; // offset 0x0, size 0x4 }; // total size: 0x40 struct WRoadProfile { - // int GetMiddleZone(bool inverted) const {} - - // int GetLaneNumber(int lane, bool inverted) const {} - - // float GetLaneWidth(int lane, bool inverted) const {} - - // void SetLaneWidth(int lane, float width, bool inverted) {} - - // float GetLaneOffset(int lane, bool inverted) const {} - - // void SetLaneOffset(int lane, float width, bool inverted) {} - - // int GetLaneType(int lane, bool inverted) const {} - - // void SetLaneType(int lane, int type, bool inverted) {} - - // float GetRawLaneOffset(int lane) const {} - - // float GetRawLaneWidth(int lane) const {} - - // float GetRelativeLaneOffset(int lane, bool inverted) const {} - - // unsigned int &GetLaneBits(int lane, bool inverted) {} - - // int GetNumForwardLanes() const {} - - // int GetNumBackwardLanes() const {} - - // int GetNumForwardLanes(bool inverted) const {} - - // int GetNumBackwardLanes(bool inverted) const {} - - // int GetNthForwardLane(int n) const {} - - // int GetNthBackwardLane(int n) const {} - - // int GetNthForwardLane(int n, bool inverted) const {} - - // int GetNthBackwardLane(int n, bool inverted) const {} - - // int GetNumTrafficLanes(bool forward, bool inverted) const {} - - // int GetNthTrafficLane(int n, bool forward, bool inverted) const {} - - // int GetNthTrafficLaneFromCurb(int n, bool forward, bool inverted) const {} - - // int GetNumLanes(bool forward) const {} - - // int GetNumLanes(bool forward, bool inverted) const {} - - // int GetNthLane(int n, bool forward) const {} - - // int GetNthLane(int n, bool forward, bool inverted) const {} - - // float GetNthOffset(int n, bool forward) const {} - - // float GetNthOffset(int n, bool forward, bool inverted) const {} - - // float GetNthWidth(int n, bool forward) const {} - - // float GetNthWidth(int n, bool forward, bool inverted) const {} - - // float GetEntireWidth() const {} + int GetLaneType(int lane, bool inverted) const { + int lane_number; + if (inverted) { + lane_number = fNumZones - lane - 1; + } else { + lane_number = lane; + } + return mLanes[lane_number].GetType(); + } - // static short ScaleToProfile(float value) {} + int GetNumForwardLanes() const { return fNumZones - fMiddleZone; } + int GetNumBackwardLanes() const { return fMiddleZone; } + int GetNumLanes(bool forward) const { + if (forward) { + return GetNumForwardLanes(); + } + return GetNumBackwardLanes(); + } + int GetMiddleZone(bool inverted) const { + if (inverted) { + return fNumZones - fMiddleZone; + } + return fMiddleZone; + } + int GetNthForwardLane(int n) const { + int ret = fMiddleZone - n - 1; + if (ret < 0) { + ret = 0; + } + return ret; + } + int GetNthBackwardLane(int n) const { + int ret = fMiddleZone + n; + if (ret >= fNumZones) { + ret = fNumZones - 1; + } + return ret; + } + int GetNthLane(int n, bool forward) const { + if (forward) { + return GetNthForwardLane(n); + } + return GetNthBackwardLane(n); + } + int GetNthLane(int n, bool forward, bool inverted) const { + if (inverted) { + return GetNthLane(n, !forward); + } + return GetNthLane(n, forward); + } + int GetNumLanes(bool forward, bool inverted) const { + if (inverted) { + return GetNumLanes(!forward); + } + return GetNumLanes(forward); + } + int GetLaneNumber(int lane, bool inverted) const { + return inverted ? fNumZones - lane - 1 : lane; + } + int GetNumTrafficLanes(bool forward) const; + int GetNthTrafficLane(int n, bool forward) const; + int GetNthTrafficLaneFromCurb(int n, bool forward) const; + + int GetNumTrafficLanes(bool forward, bool inverted) const { + if (inverted) { + return GetNumTrafficLanes(!forward); + } + return GetNumTrafficLanes(forward); + } + int GetNthTrafficLane(int n, bool forward, bool inverted) const { + if (inverted) { + return GetNthTrafficLane(n, !forward); + } + return GetNthTrafficLane(n, forward); + } + int GetNthTrafficLaneFromCurb(int n, bool forward, bool inverted) const { + if (inverted) { + return GetNthTrafficLaneFromCurb(n, !forward); + } + return GetNthTrafficLaneFromCurb(n, forward); + } + float GetLaneOffset(int lane, bool inverted) const { + return mLanes[GetLaneNumber(lane, inverted)].GetOffset(); + } + float GetLaneWidth(int lane, bool inverted) const { + int lane_number = GetLaneNumber(lane, inverted); + return mLanes[lane_number].GetWidth(); + } + float GetRelativeLaneOffset(int lane, bool inverted) const { + int real_middle = GetMiddleZone(inverted); + float offset = GetLaneOffset(lane, inverted); + if (lane < real_middle) { + offset = -offset; + } + return offset; + } - // void InvertProfile(WRoadProfile &dest) const {} + float GetRawLaneOffset(int lane) const { + return mLanes[lane].GetOffset(); + } + float GetRawLaneWidth(int lane) const { + return mLanes[lane].GetWidth(); + } unsigned char fNumZones; // offset 0x0, size 0x1 unsigned char fMiddleZone; // offset 0x1, size 0x1 @@ -154,6 +193,17 @@ struct WRoadIntersection { char fPad[9]; // offset 0x35, size 0x9 }; +// total size: 0xE +struct WRoadNetworkInfo { + unsigned short fNumProfiles; // offset 0x0, size 0x2 + unsigned short fNumNodes; // offset 0x2, size 0x2 + unsigned short fNumSegments; // offset 0x4, size 0x2 + unsigned short fNumIntersections; // offset 0x6, size 0x2 + unsigned short fNumRoads; // offset 0x8, size 0x2 + unsigned short fNumJunctions; // offset 0xA, size 0x2 + char fPad[2]; // offset 0xC, size 0x2 +}; + // total size: 0x16 struct WRoadSegment { bool IsDecision() const { @@ -174,7 +224,9 @@ struct WRoadSegment { return IsTrafficAllowed() ^ CopsXorTraffic(); } - // bool RaceRouteForward() const {} + bool RaceRouteForward() const { + return fFlags & (1 << 2); + } // void SetRaceRouteForward(bool forward) {} @@ -190,7 +242,9 @@ struct WRoadSegment { // void SetCrossesDriveThroughBarrier(bool violates) {} - // float GetLength() const {} + float GetLength() const { + return static_cast< float >(nLength) * (1000.0f / 65535.0f); + } // void SetLength(float length) {} @@ -198,21 +252,90 @@ struct WRoadSegment { return fFlags & (1 << 15); } - // void SetInRace(bool in_race) {} + void SetInRace(bool in_race) { + if (in_race) { + fFlags |= (1 << 15); + } else { + fFlags &= ~(1 << 15); + } + } + + void SetRaceRouteForward(bool forward) { + if (forward) { + fFlags |= (1 << 2); + } else { + fFlags &= ~(1 << 2); + } + } + + bool CrossesBarrier() const { + return fFlags & (1 << 12); + } - // bool IsShortcut() const {} + bool CrossesDriveThroughBarrier() const { + return fFlags & (1 << 13); + } - // void SetShortcut(bool shortcut) {} + bool CrossesBarrier(bool player) const { + bool ret = CrossesBarrier(); + if (player) { + ret = CrossesBarrier() | CrossesDriveThroughBarrier(); + } + return ret; + } - // bool IsOneWay() const {} + void SetCrossesBarrier(bool violates) { + if (violates) { + fFlags |= (1 << 12); + } else { + fFlags &= ~(1 << 12); + } + } + + void SetCrossesDriveThroughBarrier(bool violates) { + if (violates) { + fFlags |= (1 << 13); + } else { + fFlags &= ~(1 << 13); + } + } + + bool IsShortcut() const { + return (fFlags & 0x80) != 0; + } + + void SetShortcut(bool shortcut) { + if (shortcut) { + fFlags |= 0x80; + } else { + fFlags &= ~0x80; + } + } + + bool IsOneWay() const { + return (fFlags & 0x40) != 0; + } + + bool IsCurved() const { + return (fFlags & 0x100) != 0; + } // void SetOneWay(bool one_way) {} - // bool IsEndInverted() const {} + bool IsStartInverted() const { + return (fFlags >> 9) & 1; + } - // bool IsStartInverted() const {} + bool IsEndInverted() const { + return (fFlags >> 10) & 1; + } - // bool IsProfileInverted(int which_end) const {} + bool IsProfileInverted(int which_end) const { + if (!which_end) { + return IsEndInverted(); + } + return IsStartInverted(); + } // void SetEndInverted(bool inverted) {} @@ -220,35 +343,103 @@ struct WRoadSegment { // void SetProfileInverted(int which_end, bool inverted) {} - // void GetEndControl(UMath::Vector3 &v) const {} + void GetEndControl(UMath::Vector3 &v) const { + float scale = static_cast< float >(fEndHandleLength) * (500.0f / (127.0f * 65535.0f)); + float x = scale * static_cast< float >(vEndHandle[0]); + float y = scale * static_cast< float >(vEndHandle[1]); + float z = scale * static_cast< float >(vEndHandle[2]); + v = UMath::Vector3Make(x, y, z); + } - // void GetStartControl(UMath::Vector3 &v) const {} + void GetStartControl(UMath::Vector3 &v) const { + float scale = static_cast< float >(fStartHandleLength) * (500.0f / (127.0f * 65535.0f)); + float x = scale * static_cast< float >(vStartHandle[0]); + float y = scale * static_cast< float >(vStartHandle[1]); + float z = scale * static_cast< float >(vStartHandle[2]); + v = UMath::Vector3Make(x, y, z); + } - // void GetEndRightVec(UMath::Vector3 &v) const {} + void GetEndRightVec(UMath::Vector3 &v) const { + const float scale = -1.0f / 127.0f; + float x = scale * static_cast< float >(vEndHandle[0]); + float z = scale * static_cast< float >(vEndHandle[2]); + v = UMath::Vector3Make(x, 0.0f, z); + } // void GetEndRightVec(UMath::Vector2 &v) const {} - // void GetStartRightVec(UMath::Vector3 &v) const {} + void GetStartRightVec(UMath::Vector3 &v) const { + const float scale = 1.0f / 127.0f; + float x = scale * static_cast< float >(vStartHandle[0]); + float z = scale * static_cast< float >(vStartHandle[2]); + v = UMath::Vector3Make(x, 0.0f, z); + } // void GetStartRightVec(UMath::Vector2 &v) const {} - // void GetEndForwardVec(UMath::Vector2 &v) const {} + void GetEndForwardVec(UMath::Vector2 &v) const { + const float scale = -1.0f / 127.0f; + float x = scale * static_cast< float >(vEndHandle[0]); + float z = scale * static_cast< float >(vEndHandle[2]); + v = UMath::Vector2Make(x, z); + } - // void GetEndForwardVec(UMath::Vector3 &v) const {} + void GetStartForwardVec(UMath::Vector2 &v) const { + const float scale = 1.0f / 127.0f; + float x = scale * static_cast< float >(vStartHandle[0]); + float z = scale * static_cast< float >(vStartHandle[2]); + v = UMath::Vector2Make(x, z); + } - // void GetStartForwardVec(UMath::Vector2 &v) const {} + void GetEndForwardVec(UMath::Vector3 &v) const { + const float scale = -1.0f / 127.0f; + float x = scale * static_cast< float >(vEndHandle[0]); + float y = scale * static_cast< float >(vEndHandle[1]); + float z = scale * static_cast< float >(vEndHandle[2]); + v = UMath::Vector3Make(x, y, z); + } - // void GetStartForwardVec(UMath::Vector3 &v) const {} + void GetStartForwardVec(UMath::Vector3 &v) const { + const float scale = 1.0f / 127.0f; + float x = scale * static_cast< float >(vStartHandle[0]); + float y = scale * static_cast< float >(vStartHandle[1]); + float z = scale * static_cast< float >(vStartHandle[2]); + v = UMath::Vector3Make(x, y, z); + } - // void GetControl(int which_end, UMath::Vector3 &v) const {} + void GetControl(int which_end, UMath::Vector3 &v) const { + if (which_end == 0) { + GetStartControl(v); + } else { + GetEndControl(v); + } + } // void GetRightVec(int which_end, UMath::Vector2 &v) const {} - // void GetRightVec(int which_end, UMath::Vector3 &v) const {} + void GetRightVec(int which_end, UMath::Vector3 &v) const { + if (which_end == 0) { + GetStartRightVec(v); + } else { + GetEndRightVec(v); + } + } - // void GetForwardVec(int which_end, UMath::Vector2 &v) const {} + void GetForwardVec(int which_end, UMath::Vector3 &v) const { + if (which_end == 0) { + GetStartForwardVec(v); + } else { + GetEndForwardVec(v); + } + } - // void GetForwardVec(int which_end, UMath::Vector3 &v) const {} + void GetForwardVec(int which_end, UMath::Vector2 &v) const { + if (which_end == 0) { + GetStartForwardVec(v); + } else { + GetEndForwardVec(v); + } + } // void SetEndControl(UMath::Vector3 &v) {} diff --git a/src/Speed/Indep/Src/World/WRoadNetwork.h b/src/Speed/Indep/Src/World/WRoadNetwork.h index e6a3b125a..1903ba370 100644 --- a/src/Speed/Indep/Src/World/WRoadNetwork.h +++ b/src/Speed/Indep/Src/World/WRoadNetwork.h @@ -16,33 +16,54 @@ extern class WRoadNetwork *fgRoadNetwork; +class WRoadNav; +struct TrackPathBarrier; +class IBody; + // total size: 0x1 class WRoadNetwork : public Debugable { public: - // static inline void *operator new(unsigned int size, void *ptr) {} - - // static inline void operator delete(void *mem, void *ptr) {} - - // static inline void *operator new(unsigned int size) {} - - // static inline void operator delete(void *mem, unsigned int size) {} - - // static inline void *operator new(unsigned int size, const char *name) {} - - // static inline void operator delete(void *mem, const char *name) {} - - // static inline void operator delete(void *mem, unsigned int size, const char *name) {} + static void *operator new(unsigned int size) { return gFastMem.Alloc(size, nullptr); } static WRoadNetwork &Get() { return *fgRoadNetwork; } - // static unsigned int GetTotalMemoryUsage() {} - WRoadNetwork() {} ~WRoadNetwork() {} + static void Init(); + static void Shutdown(); + void ResetRaceSegments(); + void ResetBarriers(); + void GetSegmentNodes(const WRoadSegment &segment, const WRoadNode **node); + const WRoadProfile *GetSegmentProfile(const WRoadSegment &segment, int node_index); + void GetSegmentForwardVector(int segInd, UMath::Vector3 &forwardVector); + void GetSegmentForwardVector(const WRoadSegment &segment, UMath::Vector3 &forwardVector); + const WRoadNode *GetSegmentOppNode(int segInd, const WRoadNode *node); + const WRoadNode *GetSegmentOppNode(const WRoadSegment &segment, const WRoadNode *node); + unsigned char GetSegmentShortcutNumber(const WRoadSegment *segment); + bool GetSegmentTrafficLaneRightSide(const WRoadSegment &segment, int laneInd); + int GetRightMostTrafficEntrance(int node_number, int onto_segment); + bool GetSegmentProfiles(const WRoadSegment &segment, const WRoadProfile **profile); + int GetSegmentNumTrafficLanes(const WRoadSegment &segment); + int GetSegmentTrafficLaneInd(const WRoadSegment &segment, int lane_count); + void GetSegmentEndPoints(const WRoadSegment &segment, UMath::Vector3 &start, UMath::Vector3 &end); + void GetPointOnSegment(const WRoadSegment &segment, float d, UMath::Vector3 &point); + void GetPointOnSegment(const UMath::Vector3 &start, const UMath::Vector3 &end, const WRoadSegment &segment, float d, UMath::Vector3 &point); + void GetSegmentCurveStep(const UMath::Vector3 &start, const UMath::Vector3 &end, const WRoadSegment &segment, float d, UMath::Vector3 &point); + void FlagSegmentRaceDirection(int FirstSegIndex, int SecondSegIndex); + void AddRaceSegments(WRoadNav *road_nav); + void ResetShortcuts(); + void ResolveShortcuts(); + void ResolveBarriers(); + void GetPointAndVecOnSegment(const WRoadSegment &segment, float d, UMath::Vector3 &point, UMath::Vector3 &vec); + float GetSegmentPointIntersect(const WRoadSegment &segment, const UMath::Vector3 &pt, UMath::Vector3 &intersect, bool checkBound); + float GetLinePointIntersect(const UMath::Vector3 &start, const UMath::Vector3 &end, const UMath::Vector3 &pt, UMath::Vector3 &intersect, bool checkBound); + void BuildSegmentSpline(const WRoadSegment &segment, USpline &spline); + bool SegmentCrossesBarrier(WRoadSegment *segment, TrackPathBarrier *barrier); + // void SetRaceFilterValid(bool b) {} bool IsRaceFilterValid() { @@ -53,11 +74,15 @@ class WRoadNetwork : public Debugable { // bool HasValidTrafficRoads() {} - // const WRoadNode *GetNode(int index) {} + const WRoadNode *GetNode(int index) { + return &fNodes[index]; + } - // const WRoad *GetRoad(int index) {} + const WRoad *GetRoad(int index) const { return &fRoads[index]; } - // const WRoadProfile *GetProfile(int index) {} + const WRoadProfile *GetProfile(int index) { + return &fProfiles[index]; + } const WRoadSegment *GetSegment(int index) { return &fSegments[index]; @@ -65,17 +90,23 @@ class WRoadNetwork : public Debugable { // const WRoad *GetSegmentRoad(int segment_index) {} - // WRoad *GetRoadNonConst(int index) {} + WRoad *GetRoadNonConst(int index) { return &fRoads[index]; } - // WRoadSegment *GetSegmentNonConst(int index) {} + WRoadSegment *GetSegmentNonConst(int index) { + return &fSegments[index]; + } - // unsigned int GetNumRoads() {} + unsigned int GetNumSegments() { + return fNumSegments; + } - // unsigned int GetNumNodes() {} + unsigned int GetNumRoads() { + return fNumRoads; + } - // unsigned int GetNumSegments() {} + // unsigned int GetNumNodes() {} - // short GetSegRoadInd(int index) {} + short GetSegRoadInd(int index) { return fSegments[index].fRoadID; } // void IncSegmentStamp() {} @@ -121,9 +152,8 @@ class WRoadNav { kPathRacer = 0x0002, kPathGPS = 0x0003, kPathPlayer = 0x0004, - kPathPathy = 0x0005, - kPathChopper = 0x0006, - kPathRaceRoute = 0x0007, + kPathChopper = 0x0005, + kPathRaceRoute = 0x0006, }; enum ELaneType { @@ -166,6 +196,7 @@ class WRoadNav { bool OnPath() const; float GetSegmentCentreShift(int segment_number, int which_node); short GetNextOffset(const UMath::Vector3 &to, float &nextLaneOffset, char &nodeInd, bool &useOldStartPos); + short GetNextTraffic(const UMath::Vector3 &to, float &nextLaneOffset, char &nodeInd, bool &useOldStartPos); void SnapToSelectableLane(); float SnapToSelectableLane(float input_offset); @@ -173,6 +204,7 @@ class WRoadNav { void InitAtPoint(const UMath::Vector3 &pos, const UMath::Vector3 &dir, bool forceCenterLane, float dirWeight); void InitFromOtherNav(WRoadNav *other_nav, bool flip_direction); + void InitLaneOffset(const UMath::Vector3 &vehicle_pos); void InitAtSegment(short segInd, char laneInd, float timeStep); void InitAtSegment(short segInd, float timeStep, const UMath::Vector3 &pos, const UMath::Vector3 &dir, bool forceCenterLane); void InitAtSegment(short segInd, const UMath::Vector3 &pos, const UMath::Vector3 &dir, bool forceCenterLane); @@ -182,12 +214,15 @@ class WRoadNav { void IncNavPosition(float dist, const UMath::Vector3 &to, float max_lookahead); void PrivateIncNavPosition(float dist, const UMath::Vector3 &to); void ClampCookieCentres(NavCookie *cookies, int num_cookies); + void HolePunchAvoidables(NavCookie *cookies, int num_cookies, float current_offset, float delta_offset); + int FetchAvoidables(IBody **avoidables, const int listsize) const; bool IsWrongWay() const; bool IsOnShortcut(); unsigned char GetShortcutNumber(); bool IsOnLegalRoad(); bool MakeShortcutDecision(int shortcut_number, unsigned int *cached, unsigned int *allowed); void CancelPathFinding(); + unsigned char FirstShortcutInPath(); bool FindPath(const UMath::Vector3 *goal_position, const UMath::Vector3 *goal_direction, char *shortcut_allowed); bool FindPathNow(const UMath::Vector3 *goal_position, const UMath::Vector3 *goal_direction, char *shortcut_allowed); bool FindingPath(); @@ -199,8 +234,14 @@ class WRoadNav { void SetVehicle(class AIVehicle *ai_vehicle); void UpdateOccludedPosition(bool occlude_avoidables); void ChangeDragLanes(int left_right); - - bool IsValid() { + bool ChangeDragDecision(int left_right); + void SetStartEndControls(const WRoadSegment &segment); + void SetControlPos(const WRoadSegment &segment, bool is_start); + void UpdateCookieTrail(float time); + bool IsDrivable(int lane_type) const; + bool IsSelectable(int lane_type) const; + + bool IsValid() const { return fValid; } @@ -220,6 +261,10 @@ class WRoadNav { return fForwardVector; } + float GetCurvature() { + return fCurvature; + } + const NavCookie &GetCurrentCookie() { return mCurrentCookie; } @@ -252,6 +297,14 @@ class WRoadNav { return fPathType; } + bool RespectFullBarriers() { + return fPathType != kPathCop && fPathType != kPathChopper; + } + + bool RespectDriveThroughBarriers() { + return fPathType == kPathRacer || fPathType == kPathPlayer || fPathType == kPathGPS || fPathType == kPathRaceRoute; + } + ENavType GetNavType() const { return fNavType; } @@ -285,7 +338,11 @@ class WRoadNav { } bool IsOccluded() const { - return bOccludedFromBehind; + return bCookieTrail && (nRoadOcclusion != 0 || nAvoidableOcclusion != 0); + } + + bool IsOccludedFromBehind() const { + return IsOccludedByAvoidable() != 0 && bOccludedFromBehind; } const WRoadSegment *GetSegment() const { @@ -296,10 +353,41 @@ class WRoadNav { return fSegmentInd; } + short GetRoadInd() const { + return WRoadNetwork::Get().GetSegRoadInd(fSegmentInd); + } + + char GetNodeInd() const { + return fNodeInd; + } + char HitDeadEnd() const { return fDeadEnd; } + float GetSegmentTime() const { + return fSegTime; + } + + char GetLaneInd() const { + return fLaneInd; + } + + void SetLaneInd(char ind) { + fToLaneInd = ind; + fLaneInd = ind; + } + + float GetLaneOffset() const { + return fLaneOffset; + } + + void SetLaneOffset(float offset) { + fLaneOffset = offset; + fToLaneOffset = offset; + fFromLaneOffset = offset; + } + float GetOutOfBounds() { return mOutOfBounds; } @@ -308,6 +396,63 @@ class WRoadNav { ChangeDragLanes(0); } + bool HasCookieTrail() const { + return pCookieTrail != nullptr; + } + + AIVehicle *GetVehicle() { + return pAIVehicle; + } + + float GetVehicleHalfWidth() { + return fVehicleHalfWidth; + } + + int GetNumPathSegments() { + return nPathSegments; + } + + void SetNumPathSegments(int n) { + nPathSegments = n; + } + + int GetMaxPathSegments() { + return 0x3FC / sizeof(unsigned short); + } + + unsigned short *GetPathSegments() { + return pPathSegments; + } + + void SetPathGoal(unsigned short segment_number, float param) { + bCrossedPathGoal = false; + nPathGoalSegment = segment_number; + fPathGoalParam = param; + } + + unsigned short GetPathSegment(int n) { + return pPathSegments[n]; + } + + void ChangeLanes(float newOffset, float dist); + bool IncLane(int direction); + bool UpdateLaneChange(float distance); + void InitAtPath(const UMath::Vector3 &position, bool forceCenterLane); + int FindClosestOnPath(const UMath::Vector3 &position, UMath::Vector3 *found_position, UMath::Vector3 *found_direction, unsigned short *found_segment, float *found_interval) const; + float FindClosestOnSpline(const UMath::Vector3 &point, UMath::Vector3 &intersectPoint, float &timeStep, float incStep, int segInd); + void RebuildSplines(const WRoadSegment *segment); + void EvaluateSplines(const WRoadSegment *segment); + + void DetermineVehicleHalfWidth(); + void SetBoundPos(const WRoadSegment &segment, float offset, bool start); + void SetStartEndPos(const WRoadSegment &segment, float startOffset, float endOffset); + + void SetStartEndPos(const WRoadSegment &segment, float offset) { + SetStartEndPos(segment, offset, offset); + } + int FindClosestSegmentInd(const UMath::Vector3 &point, const UMath::Vector3 &dir, float dirWeight, UMath::Vector3 &closestPoint, float &time); + unsigned int GetRoadSpeechId(); + private: // total size: 0x2F0 int nCookieIndex; // offset 0x0, size 0x4 CookieTrail *pCookieTrail; // offset 0x4, size 0x4 @@ -499,4 +644,6 @@ enum RoadNames { MAX_ROADNAMES = 107, }; +const WRoadSegment *GetAttachedDirectionalSegment(const WRoadNode *node, short segInd); + #endif diff --git a/src/Speed/Indep/Src/World/WTrigger.h b/src/Speed/Indep/Src/World/WTrigger.h index 2bc77fe21..9608e216b 100644 --- a/src/Speed/Indep/Src/World/WTrigger.h +++ b/src/Speed/Indep/Src/World/WTrigger.h @@ -5,33 +5,166 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/FastMem.h" +#include "Speed/Indep/Libs/Support/Utility/UMath.h" +#include "Speed/Indep/Libs/Support/Utility/UStandard.h" #include "Speed/Indep/Src/Interfaces/Simables/ISimable.h" -// total size: 0x8 +namespace CARP { +struct EventStaticData; +struct EventList; +} + +struct IRigidBody; +struct WTrigger; + +// total size: 0x40 +struct Trigger { + bool ValidateMatrix() const; + void MakeMatrix(UMath::Matrix4 &m, bool addXLate) const; + + UMath::Vector4 fMatRow0Width; // offset 0x0, size 0x10 + unsigned int fType : 4; // offset 0x10, size 0x4 + unsigned int fShape : 4; // offset 0x10, size 0x4 + unsigned int fFlags : 24; // offset 0x10, size 0x4 + float fHeight; // offset 0x14, size 0x4 + CARP::EventList *fEvents; // offset 0x18, size 0x4 + unsigned short fIterStamp; // offset 0x1C, size 0x2 + unsigned short fFingerprint; // offset 0x1E, size 0x2 + UMath::Vector4 fMatRow2Length; // offset 0x20, size 0x10 + UMath::Vector4 fPosRadius; // offset 0x30, size 0x10 +}; + +// total size: 0x40 +struct WTrigger : public Trigger { + WTrigger(); + WTrigger(const UMath::Matrix4 &boxMat, const UMath::Vector3 ¢er, CARP::EventList *events, unsigned int type); + ~WTrigger(); + bool HasEvent(unsigned int eventID, const CARP::EventStaticData** foundEvent) const; + bool TestDirection(const UMath::Vector3& vec) const; + void FireEvents(HSIMABLE__ *hSimable); + void UpdateBox(const UMath::Matrix4& boxMat, const UMath::Vector3& center); + bool UpdatePos(const UMath::Vector3 &newPos, unsigned int triggerInd); + + void Enable() { fFlags |= 1; } + void Disable() { fFlags &= ~1; } + bool IsEnabled() const { return (fFlags & 1) != 0; } + + bool IsEnabled(bool allowSilencables) const { + unsigned int flags = (static_cast(reinterpret_cast(this)[0x11]) << 16) + | (static_cast(reinterpret_cast(this)[0x12]) << 8) + | static_cast(reinterpret_cast(this)[0x13]); + if (flags & 1) { + if ((flags & 0x400) && !allowSilencables) { + return false; + } + return true; + } + return false; + } + + void MakeMatrix(UMath::Matrix4 &m, bool addXLate, bool frombase) const { + m[0][0] = fMatRow0Width.x; + m[0][1] = fMatRow0Width.y; + m[0][2] = fMatRow0Width.z; + m[0][3] = 0.0f; + if ((static_cast(reinterpret_cast(this)[0x12]) << 8) & 0x1000) { + m[1][0] = fMatRow2Length.y * fMatRow0Width.z - fMatRow2Length.z * fMatRow0Width.y; + m[1][1] = fMatRow2Length.z * fMatRow0Width.x - fMatRow2Length.x * fMatRow0Width.z; + m[1][2] = fMatRow2Length.x * fMatRow0Width.y - fMatRow2Length.y * fMatRow0Width.x; + } else { + m[1][0] = 0.0f; + m[1][1] = 1.0f; + m[1][2] = 0.0f; + } + m[1][3] = 0.0f; + m[2][0] = fMatRow2Length.x; + m[2][1] = fMatRow2Length.y; + m[2][2] = fMatRow2Length.z; + m[2][3] = 0.0f; + if (addXLate) { + m[3][0] = fPosRadius.x; + m[3][2] = fPosRadius.z; + if (frombase) { + m[3][1] = fPosRadius.y - fHeight * 0.5f; + } else { + m[3][1] = fPosRadius.y; + } + } else { + m[3][0] = 0.0f; + m[3][1] = 0.0f; + m[3][2] = 0.0f; + } + m[3][3] = 1.0f; + } + + static void operator delete(void *mem, unsigned int size) { if (mem) gFastMem.Free(mem, size, nullptr); } +}; + +struct WTriggerList : public UTL::Std::vector {}; + struct FireOnExitRec { - class WTrigger &mTrigger; // offset 0x0, size 0x4 - HSIMABLE mhSimable; // offset 0x4, size 0x4 + FireOnExitRec(WTrigger &trigger, HSIMABLE__ *hSimable) : mTrigger(trigger) + , mhSimable(hSimable) {} + + bool operator==(const FireOnExitRec &rhs) const { + return &mTrigger == &rhs.mTrigger && mhSimable == rhs.mhSimable; + } + + bool operator<(const FireOnExitRec &rhs) const { + if (mhSimable < rhs.mhSimable) return true; + return &mTrigger < &rhs.mTrigger; + } + + WTrigger &mTrigger; // offset 0x0, size 0x4 + HSIMABLE__ *mhSimable; // offset 0x4, size 0x4 }; // total size: 0x10 -class FireOnExitList : public std::set {}; +class FireOnExitList : public std::set { + public: + static void *operator new(unsigned int size) { return gFastMem.Alloc(size, nullptr); } + static void operator delete(void *mem, unsigned int size) { if (mem) gFastMem.Free(mem, size, nullptr); } +}; // total size: 0x10 class WTriggerManager { public: - void Update(float dT); + static void *operator new(unsigned int size) { return gFastMem.Alloc(size, nullptr); } + static void operator delete(void *mem, unsigned int size) { if (mem) gFastMem.Free(mem, size, nullptr); } + + WTriggerManager(); + ~WTriggerManager(); + + static void Init(); + static void Shutdown(); + static void Restart(); + static bool Exists() { return fgTriggerManager != nullptr; } static WTriggerManager &Get() { return *fgTriggerManager; } - private: + void EnableSilencables() { fSilencableEnabled = true; } + void DisableSilencables() { fSilencableEnabled = false; } + int GetCurrentStimulus() const { return fProcessingStimulus; } + + void SubmitForFire(WTrigger &trig, HSIMABLE__ *hSimable); + void ProcessRB(IRigidBody *rBody, float dT); + void ProcessSRB(IRigidBody *srBody, float dT); + bool CheckCollideRB(const IRigidBody *rBody, const WTrigger *trig, float dT) const; + bool CheckCollideSRB(const IRigidBody *srBody, const WTrigger *trig, float dT) const; + void GetIntersectingTriggers(const UMath::Vector3 &pt, float radius, WTriggerList *triggerList) const; + void DeleteRefs(const WTrigger *trig); + void ClearAllFireOnExit(); + void Update(float dT); + static WTriggerManager *fgTriggerManager; bool fSilencableEnabled; // offset 0x0, size 0x1 int fProcessingStimulus; // offset 0x4, size 0x4 FireOnExitList *fgFireOnExitList; // offset 0x8, size 0x4 - unsigned short fIterCount; // offset 0xC, size 0x2 + mutable unsigned short fIterCount; // offset 0xC, size 0x2 }; #endif diff --git a/src/Speed/Indep/Src/World/WWorld.h b/src/Speed/Indep/Src/World/WWorld.h index 698ad89d2..1397efac4 100644 --- a/src/Speed/Indep/Src/World/WWorld.h +++ b/src/Speed/Indep/Src/World/WWorld.h @@ -29,9 +29,8 @@ class WWorld { void Close(); - // static void *operator new(unsigned int size, void *ptr) {} - - // static void operator delete(void *mem, void *ptr) {} + static void *operator new(unsigned int size) { return gFastMem.Alloc(size, nullptr); } + static void operator delete(void *mem, unsigned int size) { gFastMem.Free(mem, size, nullptr); } // static void *operator new(unsigned int size) {} @@ -45,19 +44,21 @@ class WWorld { // static bool IsPresent() {} - static WWorld &Get() { - return *fgWorld; - } - // static void Shutdown() {} // const struct world &GetAttributes() const {} - // bool IsValid() {} + bool IsValid() { + return fRootWorldGroup != nullptr; + } - // const struct UGroup &GetMapGroup() const {} + const UGroup &GetMapGroup() const { + return *fRootWorldGroup; + } - // const struct UGroup *GetMapGroup() {} + const UGroup *GetMapGroup() { + return fRootWorldGroup; + } private: static WWorld *fgWorld; // size: 0x4 diff --git a/src/Speed/Indep/Src/World/WWorldMath.h b/src/Speed/Indep/Src/World/WWorldMath.h index aa0211bc1..bc859cec4 100644 --- a/src/Speed/Indep/Src/World/WWorldMath.h +++ b/src/Speed/Indep/Src/World/WWorldMath.h @@ -5,9 +5,99 @@ #pragma once #endif +#include "Speed/Indep/bWare/Inc/bMath.hpp" + +void VU0_v4crossprodxyz(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &r); + namespace WWorldMath { +inline float pow2(float a) { return a * a; } + +inline float wmin(const float &a, const float &b) { + if (a < b) return a; + return b; +} + +inline float wmax(const float &a, const float &b) { + if (a < b) return b; + return a; +} + +inline bool InCircle(float x, float y, float cx, float cy, float r) { + return pow2(cx - x) + pow2(cy - y) < pow2(r); +} + +inline float InvSqrt(const float f) { + return VU0_rsqrt(f); +} + +inline float wwfabs(float a) { +#if defined(__GNUC__) && !defined(EA_PLATFORM_PLAYSTATION2) + float r; + asm("fabs %0, %1" : "=f"(r) : "f"(a)); + return r; +#else + return a < 0.0f ? -a : a; +#endif +} + +inline bool PtsEqual(const UMath::Vector3 &p0, const UMath::Vector3 &p1, float tolerance) { + const float kTolerance = tolerance; + return wwfabs(p0.x - p1.x) < kTolerance && wwfabs(p0.y - p1.y) < kTolerance && wwfabs(p0.z - p1.z) < kTolerance; +} + +inline void Crossxyz(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &r) { + VU0_v4crossprodxyz(a, b, r); +} + +inline float PtDir4(const UMath::Vector4 &p1, const UMath::Vector4 &p2, const UMath::Vector3 &tp) { + return (p1.x - p2.x) * (tp.z - p2.z) - (tp.x - p2.x) * (p1.z - p2.z); +} + +inline bool InTri(const UMath::Vector3 &pt, const UMath::Vector4 *pts) { + bool result; + float d = PtDir4(pts[0], pts[1], pt); + if (d <= 0.0f) { + result = false; + if (PtDir4(pts[1], pts[2], pt) <= 0.0f) { + result = PtDir4(pts[2], pts[0], pt) <= 0.0f; + } + } else { + result = false; + if (0.0f <= PtDir4(pts[1], pts[2], pt)) { + result = 0.0f <= PtDir4(pts[2], pts[0], pt); + } + } + return result; +} + +inline bool InTriR(const UMath::Vector3 &pt, const UMath::Vector4 *pts) { + bool result = false; + if (PtDir4(pts[0], pts[1], pt) <= 0.0f) { + if (PtDir4(pts[1], pts[2], pt) <= 0.0f) { + result = PtDir4(pts[2], pts[0], pt) <= 0.0f; + } + } + return result; +} + +inline bool InTriL(const UMath::Vector3 &pt, const UMath::Vector4 *pts) { + bool result = false; + if (0.0f <= PtDir4(pts[0], pts[1], pt)) { + if (0.0f <= PtDir4(pts[1], pts[2], pt)) { + result = 0.0f <= PtDir4(pts[2], pts[0], pt); + } + } + return result; +} + bool IntersectCircle(float x1, float y1, float x2, float y2, float cx, float cy, float r, float &u1, float &u2); +bool MakeSegSpaceMatrix(const UMath::Vector3 &startPt, const UMath::Vector3 &endPt, UMath::Matrix4 &mat); +float GetPlaneY(const UMath::Vector3 &normal, const UMath::Vector3 &pointOnPlane, const UMath::Vector3 &testPoint); +void NearestPointLine2D(const UMath::Vector4 &pt, const UMath::Vector4 *line, UMath::Vector4 &nearPt); +void NearestPointLine2D3(const UMath::Vector3 &pt, const UMath::Vector3 &p0, const UMath::Vector3 &p1, UMath::Vector3 &nearPt); +bool IntersectSegPlane(const UMath::Vector3 &P1, const UMath::Vector3 &P2, const UMath::Vector3 &PtOnPlane, const UMath::Vector3 &Normal, UMath::Vector3 &intersectionPt, float &t); +bool SegmentIntersect(const UMath::Vector4 *line1, const UMath::Vector4 *line2, UMath::Vector4 *intersectPt); }; diff --git a/src/Speed/Indep/Src/World/WWorldPos.h b/src/Speed/Indep/Src/World/WWorldPos.h index b1c11652d..5a8264e92 100644 --- a/src/Speed/Indep/Src/World/WWorldPos.h +++ b/src/Speed/Indep/Src/World/WWorldPos.h @@ -32,21 +32,27 @@ class WWorldPos { } } - WWorldPos(float yOffset) { - this->fFaceValid = 0; - this->fMissCount = 0; - this->fUsageCount = 0; - this->fYOffset = yOffset; - this->fSurface = nullptr; + WWorldPos(float yOffset) + : fFace(), // + fYOffset(yOffset) { + fFaceValid = 0; + fMissCount = 0; + fFace.fSurface.fSurface = 0; + fFace.fSurface.fFlags = 0; + fUsageCount = 0; + fFace.fPt0 = UMath::Vector3::kZero; + fFace.fPt1 = UMath::Vector3::kZero; + fFace.fPt2 = UMath::Vector3::kZero; + fSurface = nullptr; } ~WWorldPos() {} // bool OffEdge() const {} - // bool OnValidFace() const {} + bool OnValidFace() const { return fFaceValid; } - void ForceFaceValidity() {} + void ForceFaceValidity() { fFaceValid = 1; } // const WSurface &Surface() const {} @@ -54,11 +60,32 @@ class WWorldPos { fYOffset = liftAmount; } - void UNormal(UMath::Vector3 *norm) const {} + void UNormal(UMath::Vector3 *norm) const { + if (fFaceValid) { + fFace.GetNormal(norm); + if (norm->y < 0.0f) { + norm->y = -norm->y; + norm->x = -norm->x; + norm->z = -norm->z; + } + if (0.9999f <= norm->y) { + norm->y = 0.9999f; + } + } else { + norm->z = 0.0f; + norm->x = 0.0f; + norm->y = 1.0f; + } + } - void UNormal(UMath::Vector4 *norm) const {} + void UNormal(UMath::Vector4 *norm) const { + UNormal(&UMath::Vector4To3(*norm)); + norm->w = 0.0f; + } - // const UMath::Vector4 &FacePoint(int ptInd) const {} + const UMath::Vector4 &FacePoint(int ptInd) const { + return reinterpret_cast(&fFace)[ptInd]; + } const Attrib::Collection *GetSurface() const { return fSurface; 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/Src/World/World.cpp b/src/Speed/Indep/Src/World/World.cpp index a328384f3..c17afae35 100644 --- a/src/Speed/Indep/Src/World/World.cpp +++ b/src/Speed/Indep/Src/World/World.cpp @@ -5,6 +5,7 @@ #include "./TrackStreamer.hpp" #include "CarRender.hpp" #include "Clans.hpp" +#include "OnlineManager.hpp" #include "RaceParameters.hpp" #include "Rain.hpp" #include "Scenery.hpp" @@ -33,10 +34,31 @@ #include "types.h" static void World_Init(); +static void World_Shutdown(); + +OnlineManager TheOnlineManager; + +namespace { + +struct RaceParametersAutoInit { + RaceParametersAutoInit() { + TheRaceParameters.InitWithDefaults(); + } +}; + +struct OnlineManagerQuantizerAutoInit { + OnlineManagerQuantizerAutoInit() { + TheOnlineManager.InitQuantizers(); + } +}; + +} // namespace + +int SuperEasyAIMode = 0; // BSS Class Init bVector3 ZeroVector = bVector3(0, 0, 0); -Sim::SubSystem _Physics_System_World = Sim::SubSystem(nullptr, World_Init, nullptr); +Sim::SubSystem _Physics_System_World = Sim::SubSystem(nullptr, World_Init, World_Shutdown); float UglyTimestepHack = 0.016666668f; World *pCurrentWorld = nullptr; @@ -211,6 +233,8 @@ void World_Service() { } RaceParameters TheRaceParameters; +RaceParametersAutoInit gRaceParametersAutoInit; +OnlineManagerQuantizerAutoInit gOnlineManagerQuantizerAutoInit; static void World_Init() { ResetWorldTime(); diff --git a/src/Speed/Indep/Src/World/WorldConn.cpp b/src/Speed/Indep/Src/World/WorldConn.cpp index e69de29bb..b4630d004 100644 --- a/src/Speed/Indep/Src/World/WorldConn.cpp +++ b/src/Speed/Indep/Src/World/WorldConn.cpp @@ -0,0 +1,513 @@ +#include "Speed/Indep/Src/World/WorldConn.h" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Src/Ecstasy/EmitterSystem.h" + +extern unsigned int eFrameCounter; +extern EmitterSystem gEmitterSystem; + +void bRotateVector(bVector3 *dest, const bMatrix4 *m, bVector3 *v); + +namespace Sound { + +struct AudioEventParams { + bVector3 position; // offset 0x0, size 0x10 + bVector3 normal; // offset 0x10, size 0x10 + bVector3 velocity; // offset 0x20, size 0x10 + float magnitude; // offset 0x30, size 0x4 + Attrib::RefSpec attributes; // offset 0x34, size 0xC + unsigned int object; // offset 0x40, size 0x4 + unsigned int other_object; // offset 0x44, size 0x4 +}; + +class AudioEvent : public UTL::COM::Factory { + AudioEventParams mParams; // offset 0x4, size 0x48 + Attrib::Instance mAttributes; // offset 0x4C, size 0x14 + + public: + static AudioEvent *CreateInstance(const AudioEventParams ¶ms) { + return UTL::COM::Factory::CreateInstance( + params.attributes.GetClassKey(), params); + } + + virtual ~AudioEvent() {} + virtual void Stop() = 0; + virtual void _unk() = 0; + virtual void Update(const bVector3 &p, const bVector3 &n, const bVector3 &v, float mag) = 0; +}; + +float DistanceToView(const bVector3 *position); + +} // namespace Sound + +inline float Quadratic(float x, float A, float B, float C, float D) { + return A + B * x + C * x * x + D * x * x * x; +} + +inline float SolveEffectQuadratic(float x, const UMath::Vector4 &v) { + float y = Quadratic(x, v[0], v[1], v[2], v[3]); + return UMath::Clamp(y, 0.0f, 1.0f); +} + +static Attrib::RefSpec ChooseAudioAttributes(const Attrib::Gen::effects &effect, const bMatrix4 *matrix, const bVector3 *normal); + +namespace WorldConn { + +Server *_Server; +int world_refcount; + +Server::Server() { +} + +Server::~Server() { + mBodies.clear(); +} + +Server::Body* Server::LockID(unsigned int id) { + BodyMap::iterator iter = mBodies.find(id); + if (iter == mBodies.end()) { + Body *body = new Body(); + body->refcount = 1; + body->time = 0.0f; + bIdentity(&body->matrix); + body->velocity.x = 0.0f; + body->velocity.y = 0.0f; + body->velocity.z = 0.0f; + body->acceleration.x = 0.0f; + body->acceleration.y = 0.0f; + body->acceleration.z = 0.0f; + mBodies[id] = body; + return body; + } + iter->second->refcount++; + return iter->second; +} + +void Server::UnlockID(unsigned int id) { + BodyMap::iterator iter = mBodies.find(id); + iter->second->refcount--; + if (iter->second->refcount == 0) { + delete iter->second; + mBodies.erase(iter); + } +} + +unsigned int Server::GetFrame() const { + return eFrameCounter; +} + +void InitServices() { + _Server = new Server(); +} + +void RestoreServices() { + if (_Server != nullptr) { + delete _Server; + } + _Server = nullptr; +} + +void UpdateServices(float dT) { + WorldBodyConn::FetchData(dT); + WorldEffectConn::FetchData(dT); +} + +Reference::Reference(unsigned int worldid) + : mWorldID(worldid), // + mMatrix(nullptr), // + mVelocity(nullptr) { + Lock(); + world_refcount++; +} + +Reference::~Reference() { + Unlock(); + world_refcount--; +} + +void Reference::Set(unsigned int worldid) { + if (worldid != mWorldID) { + Unlock(); + mWorldID = worldid; + } + Lock(); +} + +void Reference::Lock() { + if (mMatrix == nullptr && mWorldID != 0) { + const Server::Body *body = _Server->LockID(mWorldID); + mMatrix = &body->matrix; + mVelocity = &body->velocity; + mAcceleration = &body->acceleration; + } +} + +void Reference::Unlock() { + if (mMatrix != nullptr && mWorldID != 0) { + _Server->UnlockID(mWorldID); + mMatrix = nullptr; + mVelocity = nullptr; + mAcceleration = nullptr; + } +} + +} // namespace WorldConn + +bTList WorldBodyConn::mList; +bTList WorldEffectConn::mList; + +UTL::COM::Factory::Prototype _WorldBodyConn("WorldBodyConn", WorldBodyConn::Construct); +UTL::COM::Factory::Prototype _WorldEffectConn("WorldEffectConn", WorldEffectConn::Construct); + +int *World_OneShotEffect(Sim::Packet *pkt); +int *World_UpdateBody(Sim::Packet *pkt); + +UTL::COM::Factory::Prototype _World_OneShotEffect("World_OneShotEffect", World_OneShotEffect); +UTL::COM::Factory::Prototype _World_UpdateBody("World_UpdateBody", World_UpdateBody); + +Sim::Connection *WorldBodyConn::Construct(const Sim::ConnectionData &data) { + return new WorldBodyConn(data); +} + +void WorldBodyConn::OnClose() { + delete this; +} + +WorldBodyConn::~WorldBodyConn() { + WorldConn::_Server->UnlockID(mID); + mList.Remove(this); +} + +WorldBodyConn::WorldBodyConn(const Sim::ConnectionData &data) + : Connection(data), // + mID(0) { + mList.AddTail(this); + WorldConn::Pkt_Body_Open *oc = Sim::Packet::Cast(data.pkt); + mID = oc->mID; + mDest = WorldConn::_Server->LockID(mID); + bConvertFromBond(mDest->matrix, *reinterpret_cast(&oc->mMatrix)); +} + +void WorldBodyConn::Update(float dT) { + WorldConn::Pkt_Body_Service pkt; + pkt.mMatrix = UMath::Matrix4::kIdentity; + int result = Service(&pkt); + if (result == 0) { + bFill(&mDest->acceleration, 0.0f, 0.0f, 0.0f); + mDest->time = 0.0f; + return; + } + bVector3 prevvel(mDest->velocity); + eSwizzleWorldMatrix(reinterpret_cast(pkt.mMatrix), mDest->matrix); + eSwizzleWorldVector(reinterpret_cast(pkt.mVelocity), mDest->velocity); + if (0.0f < mDest->time) { + mDest->acceleration.x = (mDest->velocity.x - prevvel.x) / dT; + mDest->acceleration.y = (mDest->velocity.y - prevvel.y) / dT; + mDest->acceleration.z = (mDest->velocity.z - prevvel.z) / dT; + } + mDest->time = mDest->time + dT; +} + +void WorldBodyConn::FetchData(float dT) { + for (WorldBodyConn *pconn = mList.GetHead(); pconn != mList.EndOfList(); pconn = pconn->GetNext()) { + pconn->Update(dT); + } +} + +void WorldEffectConn::Update(float dT) { + WorldConn::Pkt_Effect_Service pkt; + pkt.mPosition = UMath::Vector3::kZero; + pkt.mTracking = false; + pkt.mMagnitude = UMath::Vector3::kZero; + int result = Service(&pkt); + if (result == 0) { + if (!mPaused) { + if (mAudioEvent != nullptr) { + static_cast(mAudioEvent)->Stop(); + mAudioEvent = nullptr; + } + mPaused = true; + if (mEmitters != nullptr) { + mEmitters->Disable(); + } + } + return; + } + mPaused = false; + UMath::Vector3 simnormal; + simnormal = pkt.mMagnitude; + float intensity = UMath::Normalize(simnormal); + float emitter_intensity = SolveEffectQuadratic(intensity, mAttributes.EmitterQuadratic()); + float audio_intensity = SolveEffectQuadratic(intensity, mAttributes.AudioQuadratic()); + bVector3 velocity(0.0f, 0.0f, 0.0f); + bVector3 normal; + bVector3 position; + eSwizzleWorldVector(reinterpret_cast(pkt.mPosition), position); + eSwizzleWorldVector(reinterpret_cast(simnormal), normal); + if (mOwnerRef.GetVelocity() != nullptr) { + if (0.0f < mAttributes.InheritVelocity()) { + bScale(&velocity, mOwnerRef.GetVelocity(), mAttributes.InheritVelocity()); + } + } + if (pkt.mTracking && mOwnerRef.IsValid()) { + bVector3 world_pos; + bVector3 world_mag; + bMulMatrix(&world_pos, mOwnerRef.GetMatrix(), &position); + bRotateVector(&world_mag, mOwnerRef.GetMatrix(), &normal); + position = world_pos; + normal = world_mag; + } + float distance = Sound::DistanceToView(&position); + if (mEmitters != nullptr) { + static const UMath::Vector3 up = {0.0f, 1.0f, 0.0f}; + UQuat quat; + bMatrix4 matrix; + bVector3 sim_dir; + quat.BuildDeltaAxis(up, reinterpret_cast(normal)); + UMath::QuaternionToMatrix4(quat, reinterpret_cast(matrix)); + bCopy(&matrix.v3, &position, 1.0f); + if (0.0f < emitter_intensity) { + if (distance < mAttributes.VisualCullDist()) { + mEmitters->Enable(); + mEmitters->SetLocalWorld(&matrix); + mEmitters->SetInheritVelocity(&velocity); + mEmitters->SetIntensity(emitter_intensity); + mEmitters->Update(dT); + goto audio_section; + } + } + mEmitters->Disable(); + } +audio_section: + if (mAudioEvent == nullptr) { + if (!mSilent && 0.0f < audio_intensity) { + if (distance < mAttributes.AudioCullDist()) { + Sound::AudioEventParams params; + params.position = position; + params.normal = normal; + params.velocity = velocity; + params.magnitude = audio_intensity; + params.attributes = ChooseAudioAttributes(mAttributes, mOwnerRef.GetMatrix(), &normal); + params.object = mOwnerRef.GetWorldID(); + params.other_object = mActee; + mAudioEvent = Sound::AudioEvent::CreateInstance(params); + if (mAudioEvent == nullptr) { + mSilent = true; + } + } + } + } else { + if (0.0f < audio_intensity) { + if (distance < mAttributes.AudioCullDist()) { + static_cast(mAudioEvent)->Update(position, normal, velocity, audio_intensity); + return; + } + } + static_cast(mAudioEvent)->Stop(); + mAudioEvent = nullptr; + } +} + +void WorldEffectConn::FetchData(float dT) { + for (WorldEffectConn *pconn = mList.GetHead(); pconn != mList.EndOfList(); pconn = pconn->GetNext()) { + pconn->Update(dT); + } +} + +Sim::Connection *WorldEffectConn::Construct(const Sim::ConnectionData &data) { + WorldConn::Pkt_Effect_Open *oc = Sim::Packet::Cast(data.pkt); + return new WorldEffectConn(data, oc); +} + +void WorldEffectConn::OnClose() { + delete this; +} + +void HandleWorldEffectEmitterGroupDelete(void *subscriber, EmitterGroup *grp) { + WorldEffectConn *fx_conn = static_cast(subscriber); + fx_conn->ResetEmitterGroup(); +} + +int *World_OneShotEffect(Sim::Packet *pkt) { + WorldConn::Pkt_Effect_Send *pe = Sim::Packet::Cast(pkt); + Attrib::Gen::effects effects(pe->mEffectGroup, 0, nullptr); + if (effects.IsValid()) { + Attrib::Instance owner_attribs(pe->mOwnerAttributes, 0, nullptr); + unsigned int effect_creation_flags = 0; + unsigned int owner_class = owner_attribs.GetClass(); + if (owner_class == 0x4a97ec8f) { + effect_creation_flags = 0x10000000; + } else if (owner_class == 0xce70d7db) { + effect_creation_flags = 0x20000000; + } + Attrib::Instance context_attribs(pe->mContext, 0, nullptr); + unsigned int context_class = context_attribs.GetClass(); + if (context_class == 0xfb111fef) { + effect_creation_flags |= 0x01000000; + } else { + effect_creation_flags |= 0x02000000; + } + bVector3 position; + eSwizzleWorldVector(reinterpret_cast(pe->mPosition), position); + float distance = Sound::DistanceToView(&position); + float audioCullDist = effects.AudioCullDist(); + bool noaudio = distance > audioCullDist; + float visualCullDist = effects.VisualCullDist(); + bool novideo = distance > visualCullDist; + if (!noaudio || !novideo) { + UMath::Vector4 mEmitterQuadratic = UMath::Vector4Make(0.0f, 1.0f, 0.0f, 0.0f); + UMath::Vector4 mAudioQuadratic = UMath::Vector4Make(0.0f, 1.0f, 0.0f, 0.0f); + Attrib::TAttrib attrib; + attrib = Attrib::TAttrib(effects.Get(0xa9402c33)); + if (attrib.IsValid()) { + mEmitterQuadratic = attrib.Get(0); + } + attrib = Attrib::TAttrib(effects.Get(0x15e6552f)); + if (attrib.IsValid()) { + mAudioQuadratic = attrib.Get(0); + } + UMath::Vector3 simnormal; + simnormal.x = pe->mMagnitude.x; + simnormal.y = pe->mMagnitude.z; + simnormal.z = pe->mMagnitude.y; + float intensity = UMath::Normalize(simnormal); + float emitter_intensity = SolveEffectQuadratic(intensity, mEmitterQuadratic); + float audio_intensity = SolveEffectQuadratic(intensity, mAudioQuadratic); + if (0.0f < emitter_intensity || 0.0f < audio_intensity) { + WorldConn::Reference effect_object(pe->mOwner); + bVector3 velocity(0.0f, 0.0f, 0.0f); + float inherit = effects.InheritVelocity(); + if (effect_object.IsValid() && 0.0f < inherit) { + bScale(&velocity, effect_object.GetVelocity(), inherit); + } + bVector3 normal; + eSwizzleWorldVector(reinterpret_cast(simnormal), normal); + if (0.0f < emitter_intensity && !novideo) { + const Attrib::Collection *emitter_group_spec = effects.emittergroup().GetCollection(); + EmitterGroup *emitter_group = gEmitterSystem.CreateEmitterGroup(emitter_group_spec, effect_creation_flags | 0x400000); + if (emitter_group != nullptr) { + static const UMath::Vector3 up = {0.0f, 1.0f, 0.0f}; + UQuat quat; + UMath::Matrix4 mat; + bMatrix4 matrix; + quat.BuildDeltaAxis(up, reinterpret_cast(normal)); + UMath::QuaternionToMatrix4(quat, mat); + bConvertFromBond(matrix, reinterpret_cast(mat)); + bCopy(&matrix.v3, &position, 1.0f); + emitter_group->SetIntensity(emitter_intensity); + emitter_group->MakeOneShot(true); + emitter_group->SetAutoUpdate(true); + emitter_group->SetLocalWorld(&matrix); + emitter_group->SetInheritVelocity(&velocity); + } + } + if (0.0f < audio_intensity && !noaudio) { + Sound::AudioEventParams params; + params.position = position; + params.normal = normal; + params.velocity = velocity; + params.magnitude = audio_intensity; + params.attributes = ChooseAudioAttributes(effects, effect_object.IsValid() ? effect_object.GetMatrix() : nullptr, &normal); + params.object = pe->mOwner; + params.other_object = pe->mActee; + Sound::AudioEvent *event = Sound::AudioEvent::CreateInstance(params); + if (event != nullptr) { + event->Stop(); + } + } + } + } + } + return 0; +} + +int *World_UpdateBody(Sim::Packet *pkt) { + WorldConn::Pkt_Body_Open *data = static_cast(pkt); + WorldConn::Server::Body *body = WorldConn::_Server->LockID(data->mID); + eSwizzleWorldMatrix(reinterpret_cast(data->mMatrix), body->matrix); + WorldConn::_Server->UnlockID(data->mID); + return 0; +} + +WorldEffectConn::WorldEffectConn(const Sim::ConnectionData &data, const WorldConn::Pkt_Effect_Open *oc) + : Connection(data), // + mAttributes(oc->mEffectGroup, 0, nullptr), // + mOwnerRef(oc->mOwner) +{ + mPaused = false; + mSilent = false; + mAudioEvent = nullptr; + mActee = oc->mActee; + + unsigned int effect_creation_flags = 0; + + Attrib::Instance owner_attribs(oc->mOwnerAttributes, 0, nullptr); + unsigned int owner_class = owner_attribs.GetClass(); + switch (owner_class) { + case 0x4a97ec8f: + effect_creation_flags = 0x10000000; + break; + case 0xce70d7db: + effect_creation_flags = 0x20000000; + break; + } + + Attrib::Instance context_attribs(oc->mContext, 0, nullptr); + unsigned int context_class = context_attribs.GetClass(); + if (context_class == 0xfb111fef) { + effect_creation_flags |= 0x01000000; + } else { + effect_creation_flags |= 0x02000000; + } + + mList.AddTail(this); + + const Attrib::Collection *fxspec = mAttributes.emittergroup().GetCollection(); + mEmitters = nullptr; + if (fxspec != nullptr) { + mEmitters = gEmitterSystem.CreateEmitterGroup(fxspec, effect_creation_flags | 0x800000); + if (mEmitters != nullptr) { + mEmitters->SubscribeToDeletion(this, HandleWorldEffectEmitterGroupDelete); + mEmitters->Disable(); + } + } +} + +WorldEffectConn::~WorldEffectConn() { + if (mEmitters != nullptr) { + mEmitters->UnSubscribe(); + if (mEmitters != nullptr) { + delete mEmitters; + } + } + if (mAudioEvent != nullptr) { + static_cast(mAudioEvent)->Stop(); + mAudioEvent = nullptr; + } + mList.Remove(this); +} + +static Attrib::RefSpec ChooseAudioAttributes(const Attrib::Gen::effects &effect, const bMatrix4 *matrix, const bVector3 *normal) { + if (matrix != nullptr && normal != nullptr) { + Attrib::RefSpec zone_spec; + float AngleDiff_Front = bDot(reinterpret_cast(&matrix->v0), normal); + float AngleDiff_Top = bDot(reinterpret_cast(&matrix->v2), normal); + + if (AngleDiff_Front < -0.707f) { + zone_spec = effect.AudioFX_FRONT(); + } else if (AngleDiff_Front > 0.707f) { + zone_spec = effect.AudioFX_REAR(); + } else if (AngleDiff_Top < -0.707f) { + zone_spec = effect.AudioFX_TOP(); + } else if (AngleDiff_Top > 0.707f) { + zone_spec = effect.AudioFX_BOTTOM(); + } else { + zone_spec = effect.AudioFX_SIDE(); + } + + if (zone_spec.GetCollectionKey() != 0 && zone_spec.GetClassKey() != 0) { + return zone_spec; + } + } + + return effect.AudioFX_DEFAULT(); +} diff --git a/src/Speed/Indep/Src/World/WorldConn.h b/src/Speed/Indep/Src/World/WorldConn.h index 0021f2b36..6b1ec5467 100644 --- a/src/Speed/Indep/Src/World/WorldConn.h +++ b/src/Speed/Indep/Src/World/WorldConn.h @@ -5,30 +5,95 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/UStandard.h" #include "Speed/Indep/Libs/Support/Utility/UTypes.h" +#include "Speed/Indep/Libs/Support/Utility/UCrc.h" +#include "Speed/Indep/Libs/Support/Miscellaneous/stringhash.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/effects.h" #include "Speed/Indep/Src/Sim/SimConn.h" +#include "Speed/Indep/Src/Sim/SimServer.h" #include "Speed/Indep/Tools/AttribSys/Runtime/Common/AttribPrivate.h" +#include "Speed/Indep/bWare/Inc/bList.hpp" #include "Speed/Indep/bWare/Inc/bMath.hpp" #include "WorldTypes.h" #include +class EmitterGroup; + +DECLARE_CONTAINER_TYPE(WorldConnServerMap); + namespace WorldConn { +// total size: 0x14 +class Server { + public: + struct Body { + void *operator new(std::size_t size) { + return gFastMem.Alloc(size, nullptr); + } + + void operator delete(void *mem, std::size_t size) { + if (mem) { + gFastMem.Free(mem, size, nullptr); + } + } + + bMatrix4 matrix; // offset 0x0, size 0x40 + bVector3 velocity; // offset 0x40, size 0x10 + bVector3 acceleration; // offset 0x50, size 0x10 + float time; // offset 0x60, size 0x4 + int refcount; // offset 0x64, size 0x4 + }; + + typedef UTL::Std::map BodyMap; + + void *operator new(std::size_t size) { + return gFastMem.Alloc(size, nullptr); + } + + void operator delete(void *mem, std::size_t size) { + if (mem) { + gFastMem.Free(mem, size, nullptr); + } + } + + Server(); + virtual ~Server(); + + Body *LockID(unsigned int id); + void UnlockID(unsigned int id); + + virtual unsigned int GetFrame() const; + + BodyMap mBodies; // offset 0x0, size 0x10 +}; + // total size: 0x10 class Reference { public: Reference(unsigned int); ~Reference(); void Set(unsigned int); + void Lock(); + void Unlock(); + bool IsValid() const { - return this->mMatrix != nullptr; + return mMatrix != nullptr; } const bMatrix4 *GetMatrix() const { return mMatrix; } + const bVector3 *GetVelocity() const { + return mVelocity; + } + + unsigned int GetWorldID() const { + return mWorldID; + } + private: unsigned int mWorldID; // offset 0x0, size 0x4 const bMatrix4 *mMatrix; // offset 0x4, size 0x4 @@ -62,6 +127,41 @@ class Pkt_Effect_Send : public Sim::Packet { WUID mActee; // offset 0x2C, size 0x4 }; +// total size: 0x50 +class Pkt_Body_Service : public Sim::Packet { + public: + void SetMatrix(const UMath::Matrix4 &matrix) { + mMatrix = matrix; + } + + void SetVelocity(const UMath::Vector3 &velocity) { + mVelocity = velocity; + } + + UCrc32 ConnectionClass() override { + static UCrc32 hash("WorldBodyConn"); + return hash; + } + + unsigned int Size() override { + return 0x50; + } + + static unsigned int SType() { + static UCrc32 hash("Pkt_Body_Service"); + return hash.GetValue(); + } + + unsigned int Type() override { + return SType(); + } + + ~Pkt_Body_Service() override {} + + UMath::Matrix4 mMatrix; // offset 0x4, size 0x40 + UMath::Vector3 mVelocity; // offset 0x44, size 0xC +}; + // total size: 0x20 class Pkt_Effect_Service : public Sim::Packet { public: @@ -77,30 +177,75 @@ class Pkt_Effect_Service : public Sim::Packet { mTracking = b; } - // Virtual functions - // Packet + UCrc32 ConnectionClass() override { + static UCrc32 hash("WorldEffectConn"); + return hash; + } + + unsigned int Size() override { + return 0x20; + } + + static unsigned int SType() { + static UCrc32 hash("Pkt_Effect_Service"); + return hash.GetValue(); + } + + unsigned int Type() override { + return SType(); + } + ~Pkt_Effect_Service() override {} - private: ALIGN_16 UMath::Vector3 mPosition; // offset 0x4, size 0xC bool mTracking; // offset 0x10, size 0x1 ALIGN_16 UMath::Vector3 mMagnitude; // offset 0x14, size 0xC }; +// total size: 0x48 +class Pkt_Body_Open : public Sim::Packet { + public: + Pkt_Body_Open(unsigned int id, const UMath::Matrix4 &matrix) + : mMatrix(matrix), // + mID(id) {} + + UCrc32 ConnectionClass() override { + static UCrc32 hash("WorldBodyConn"); + return hash; + } + + unsigned int Size() override { + return 0x48; + } + + static unsigned int SType() { + static UCrc32 hash("Pkt_Body_Open"); + return hash.GetValue(); + } + + unsigned int Type() override { + return SType(); + } + + ~Pkt_Body_Open() override {} + + UMath::Matrix4 mMatrix; // offset 0x4, size 0x40 + unsigned int mID; // offset 0x44, size 0x4 +}; + // total size: 0x18 class Pkt_Effect_Open : public Sim::Packet { public: - Pkt_Effect_Open(const Attrib::Collection *effect_group, WUID owner, const Attrib::Collection *owner_attrib, const Attrib::Collection *context, - WUID actee) + Pkt_Effect_Open(const Attrib::Collection *effect_group, unsigned int owner, const Attrib::Collection *owner_attrib, const Attrib::Collection *context, + unsigned int actee) : mEffectGroup(effect_group), // mOwner(owner), // mOwnerAttributes(owner_attrib), // mContext(context), // mActee(actee) {} - ~Pkt_Effect_Open() {} + ~Pkt_Effect_Open() override {} - private: const Attrib::Collection *mEffectGroup; // offset 0x4, size 0x4 unsigned int mOwner; // offset 0x8, size 0x4 const Attrib::Collection *mOwnerAttributes; // offset 0xC, size 0x4 @@ -108,10 +253,64 @@ class Pkt_Effect_Open : public Sim::Packet { unsigned int mActee; // offset 0x14, size 0x4 }; +extern Server *_Server; +extern int world_refcount; + void InitServices(); -void UpdateServices(float dT); void RestoreServices(); +void UpdateServices(float dT); } // namespace WorldConn +class WorldBodyConn : public Sim::Connection, public bTNode { + public: + static Sim::Connection *Construct(const Sim::ConnectionData &data); + + WorldBodyConn(const Sim::ConnectionData &data); + ~WorldBodyConn() override; + + void OnClose() override; + Sim::ConnStatus OnStatusCheck() override { + return Sim::CONNSTATUS_READY; + } + + void Update(float dT); + static void FetchData(float dT); + + static bTList mList; + + unsigned int mID; // offset 0x18, size 0x4 + WorldConn::Server::Body *mDest; // offset 0x1C, size 0x4 +}; + +class WorldEffectConn : public Sim::Connection, public bTNode { + public: + static Sim::Connection *Construct(const Sim::ConnectionData &data); + + WorldEffectConn(const Sim::ConnectionData &data, const WorldConn::Pkt_Effect_Open *oc); + ~WorldEffectConn() override; + + void OnClose() override; + Sim::ConnStatus OnStatusCheck() override { + return Sim::CONNSTATUS_READY; + } + + void ResetEmitterGroup() { + mEmitters = nullptr; + } + + void Update(float dT); + static void FetchData(float dT); + + static bTList mList; + + Attrib::Gen::effects mAttributes; // offset 0x18, size 0x14 + WorldConn::Reference mOwnerRef; // offset 0x2C, size 0x10 + EmitterGroup *mEmitters; // offset 0x3C, size 0x4 + bool mPaused; // offset 0x40, size 0x1 + bool mSilent; // offset 0x44, size 0x1 + void *mAudioEvent; // offset 0x48, size 0x4 + unsigned int mActee; // offset 0x4C, size 0x4 +}; + #endif diff --git a/src/Speed/Indep/Src/World/WorldModel.cpp b/src/Speed/Indep/Src/World/WorldModel.cpp index e69de29bb..f2fbdbf72 100644 --- a/src/Speed/Indep/Src/World/WorldModel.cpp +++ b/src/Speed/Indep/Src/World/WorldModel.cpp @@ -0,0 +1,457 @@ +#include "./WorldModel.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/eLight.hpp" +#include "Speed/Indep/Src/Ecstasy/eMath.hpp" + +extern unsigned int FrameMallocFailed; +extern unsigned int FrameMallocFailAmount; +extern float lbl_8040CD80; +extern float lbl_8040CD84; +extern float lbl_8040CD88; +extern float lbl_8040CD8C; +extern float lbl_8040CD90; +extern float lbl_8040CD94; +extern eShaperLightRig ShaperLightsCarsInGame; +extern eShaperLightRig ShaperLightsCharacters; +extern int bBoundingBoxIsInside(const bVector3 *bbox_min, const bVector3 *bbox_max, const bVector3 *point, float extra_width); +extern void RenderAnimSceneEffects(eView *view, int exc_flag) asm("RenderAnimSceneEffects__FP5eViewi"); + +bTList WorldModelList; + +int elSetupLightContext(eDynamicLightContext *light_context, eShaperLightRig *shaper_lights, bMatrix4 *local_world, bMatrix4 *world_view, + bVector4 *camera_world_position, eView *view); +void AdjustQuickDynamicLight(eShaperLightRig *ShaperRigP, bVector3 *MyPosition); + +namespace { + +void Render(eViewPlatInterface *view, eModel *model, bMatrix4 *local_to_world, eLightContext *light_context, unsigned int flags, + unsigned int exc_flag) asm("Render__18eViewPlatInterfaceP6eModelP8bMatrix4P13eLightContextUiT2"); + +inline void *eFrameMalloc(unsigned int size) { + unsigned char *address = CurrentBufferPos; + + if (CurrentBufferEnd <= CurrentBufferPos + size) { + FrameMallocFailed = 1; + FrameMallocFailAmount += size; + address = 0; + } else { + CurrentBufferPos += size; + } + + return address; +} + +struct AABBAdjustor { + bVector4 *mMin; + bVector4 *mMax; + bVector4 *mAdjustment; + + void Adjust(float scaler) { + if (this->mAdjustment != 0) { + this->mMin->x += this->mAdjustment->x * scaler; + this->mMax->x += this->mAdjustment->x * scaler; + this->mMin->y += this->mAdjustment->y * scaler; + this->mMax->y += this->mAdjustment->y * scaler; + this->mMin->z += this->mAdjustment->z * scaler; + this->mMax->z += this->mAdjustment->z * scaler; + } + } + + AABBAdjustor(eModel *m, bMatrix4 *adjustment) { + if (adjustment != 0) { + eSolid *solid = m->GetSolid(); + + if (solid != 0) { + this->mMin = reinterpret_cast(&solid->AABBMinX); + this->mMax = reinterpret_cast(&solid->AABBMaxX); + this->mAdjustment = &adjustment[1].v3; + this->Adjust(1.0f); + return; + } + } + this->mMin = 0; + this->mMax = 0; + this->mAdjustment = 0; + } + + ~AABBAdjustor() { + this->Adjust(-1.0f); + } +}; + +} // namespace + +WorldModel::WorldModel(unsigned int name_hash, bMatrix4 *matrix, bool add_lighting) { + eModel *model = static_cast(bOMalloc(eModelSlotPool)); + model->NameHash = 0; + model->Solid = 0; + model->Init(name_hash); + this->pModel = model; + this->pReflectionModel = 0; + this->Construct(0, matrix, 0, 0, add_lighting); +} + +WorldModel::WorldModel(SpaceNode *spacenode, unsigned int *lod_name_hash, bool add_lighting) { + eModel *model = static_cast(bOMalloc(eModelSlotPool)); + unsigned int model_name_hash = *lod_name_hash; + model->NameHash = 0; + model->Solid = 0; + model->Init(model_name_hash); + this->pModel = model; + + if (lod_name_hash[3] != 0) { + eModel *reflection_model = static_cast(bOMalloc(eModelSlotPool)); + unsigned int reflection_name_hash = lod_name_hash[3]; + reflection_model->NameHash = 0; + reflection_model->Solid = 0; + reflection_model->Init(reflection_name_hash); + this->pReflectionModel = reflection_model; + } else { + this->pReflectionModel = 0; + } + + this->Construct(spacenode, 0, 0, 0, add_lighting); +} + +WorldModel::WorldModel(const ModelHeirarchy *heirarchy, unsigned int heirarchy_index, bool add_lighting) { + this->pModel = 0; + this->pReflectionModel = 0; + this->Construct(0, 0, heirarchy, heirarchy_index, add_lighting); +} + +void WorldModel::Construct(SpaceNode *spacenode, bMatrix4 *matrix, const ModelHeirarchy *heirarchy, unsigned int rootnode, bool add_lighting) { + this->mDistanceToGameView = lbl_8040CD80; + this->mLastRenderFrame = 0; + this->mLastVisibleFrame = 0; + this->mLightMaterial = 0; + this->mLightMaterialSkinHash = 0; + + if (heirarchy != 0 && rootnode < heirarchy->mNumNodes) { + this->mHeirarchyIndex = rootnode; + this->mHeirarchy = heirarchy; + this->mChildVisibility = 0xFFFFFF; + } else { + this->mChildVisibility = 0; + this->mHeirarchyIndex = 0; + this->mHeirarchy = 0; + } + + this->mInvisibleInside = false; + this->mEnabled = true; + this->mRenderInSplitScreen = true; + this->mCastsShadow = 1; + this->pSpaceNode = spacenode; + + if (spacenode != 0) { + spacenode->Lock(); + } + + if (matrix != 0) { + this->SetMatrix(matrix); + } + + WorldModelList.AddTail(this); + this->mAddLighting = add_lighting; +} + +WorldModel::~WorldModel() { + if (this->pModel != 0) { + delete this->pModel; + } + + if (this->pReflectionModel != 0) { + delete this->pReflectionModel; + } + + if (this->pSpaceNode != 0) { + this->pSpaceNode->Unlock(); + } + + this->Remove(); +} + +eModel *WorldModel::GetModel() { + if (this->pModel != 0) { + return this->pModel; + } + + if (this->mHeirarchy != 0) { + return this->mHeirarchy->GetNodes()[this->mHeirarchyIndex].mModel; + } + + return 0; +} + +void WorldModel::AttachReplacementTextureTable(eReplacementTextureTable *replacement_texture_table, int num_textures) { + if (this->pModel != 0) { + this->pModel->AttachReplacementTextureTable(replacement_texture_table, num_textures, 0); + } + + if (this->pReflectionModel != 0) { + this->pReflectionModel->AttachReplacementTextureTable(replacement_texture_table, num_textures, 0); + } +} + +void WorldModel::GetLocalBoundingBox(bVector3 *min_ext, bVector3 *max_ext) { + eModel *model = this->GetModel(); + + if (model != 0) { + model->GetBoundingBox(min_ext, max_ext); + } else { + bVector3 zero; + bFill(&zero, 0.0f, 0.0f, 0.0f); + bCopy(min_ext, &zero); + bCopy(max_ext, &zero); + } +} + +void InitWorldModels() { + WorldModelSlotPool = bNewSlotPool(0x88, 0x80, "WorldModelSlotPool", 0); +} + +void CloseWorldModels() { + WorldModel *end = WorldModelList.EndOfList(); + + if (WorldModelList.GetHead() != end) { + for (;;) { + WorldModel *world_model = WorldModelList.GetHead(); + if (world_model == end) { + break; + } + if (world_model != 0) { + delete world_model; + } + } + } + + bDeleteSlotPool(WorldModelSlotPool); + WorldModelSlotPool = 0; +} + +void WorldModel::RenderNode(const ModelHeirarchy *heirarchy, unsigned int nodeIndex, eView *view, int exc_flag, bMatrix4 *blended_matrices, + const bMatrix4 *matrix) { + const ModelHeirarchy::Node *node = &heirarchy->GetNodes()[nodeIndex]; + + if (node->mModel != 0 && node->mModel->GetSolid() != 0) { + this->RenderModel(node->mModel, view, exc_flag, blended_matrices, matrix); + } + + for (unsigned int i = 0; i < node->mNumChildren; i++) { + if (this->mHeirarchyIndex != nodeIndex || (this->mChildVisibility & (1 << i))) { + if ((heirarchy->GetNodes()[node->mChildIndex + i].mFlags & ModelHeirarchy::F_INTERNAL) == 0) { + this->RenderNode(heirarchy, node->mChildIndex + i, view, exc_flag, blended_matrices, matrix); + } + } + } +} + +void WorldModel::RenderModel(eModel *render_model, eView *view, int exc_flag, bMatrix4 *blended_matrices, const bMatrix4 *matrix) { + unsigned int flags = static_cast(exc_flag); + AABBAdjustor adjustor(render_model, blended_matrices); + bMatrix4 *frame_matrix = static_cast(eFrameMalloc(sizeof(bMatrix4))); + + if (frame_matrix != 0) { + eDynamicLightContext *light_context = 0; + + *frame_matrix = *matrix; + if ((flags & 0x800) != 0) { + frame_matrix->v2.z = -frame_matrix->v2.z; + } + + if (this->mAddLighting) { + light_context = static_cast(eFrameMalloc(0x130)); + if (light_context == 0) { + return; + } + + Camera *camera = view->GetCamera(); + bVector3 *eye = camera->GetPosition(); + bMatrix4 *world_view = camera->GetCameraMatrix(); + bVector4 camera_world_position; + + camera_world_position.x = eye->x; + camera_world_position.y = eye->y; + camera_world_position.z = eye->z; + camera_world_position.w = 1.0f; + if (blended_matrices != 0) { + bMatrix4 *actual_frame_matrix; + bMatrix4 moved_frame_matrix; + bVector4 pelvis_pos = blended_matrices[1].v3; + + eMulVector(&pelvis_pos, frame_matrix, &pelvis_pos); + moved_frame_matrix = *frame_matrix; + camera_world_position = pelvis_pos; + actual_frame_matrix = &moved_frame_matrix; + AdjustQuickDynamicLight(&ShaperLightsCharacters, reinterpret_cast(&camera_world_position)); + elSetupLightContext(light_context, &ShaperLightsCharacters, actual_frame_matrix, world_view, &camera_world_position, view); + ShaperLightsCharacters.NumOverideSlots = 0; + } else { + elSetupLightContext(light_context, &ShaperLightsCarsInGame, frame_matrix, world_view, &camera_world_position, view); + } + } + + if (this->mLightMaterial != 0) { + render_model->ReplaceLightMaterial(this->mLightMaterialSkinHash, this->mLightMaterial); + } + + ::Render(view, render_model, frame_matrix, light_context, 0, flags); + } +} + +void WorldModel::Render(eView *view, int exc_flag) { + if (this->mLastRenderFrame != eFrameCounter) { + this->mLastRenderFrame = eFrameCounter; + this->mDistanceToGameView = lbl_8040CD80; + } + + CameraMover *camera_mover = 0; + if (!view->CameraMoverList.IsEmpty()) { + camera_mover = view->CameraMoverList.GetHead(); + } + + if (camera_mover != 0 && (view->GetID() - 1U) < 3U) { + const bMatrix4 *world_matrix = &this->mMatrix; + + if (this->pSpaceNode != 0) { + world_matrix = + reinterpret_cast(reinterpret_cast(this->pSpaceNode) + 0x50); + } + + { + bVector3 *camera_position = camera_mover->GetPosition(); + bVector3 delta; + float distance_sq; + float distance_scale = lbl_8040CD90; + + delta.y = camera_position->y - world_matrix->v3.y; + delta.x = camera_position->x - world_matrix->v3.x; + delta.z = camera_position->z - world_matrix->v3.z; + distance_sq = delta.x * delta.x + delta.y * delta.y + delta.z * delta.z; + if (lbl_8040CD84 < distance_sq) { + distance_scale = bSqrt(distance_sq); + } + + if (this->mDistanceToGameView < distance_scale) { + distance_scale = this->mDistanceToGameView; + } + this->mDistanceToGameView = distance_scale; + } + } + + eModel *render_model = this->GetModel(); + if ((static_cast(exc_flag) & 0x800) != 0 && (render_model = this->pReflectionModel) == 0) { + return; + } + if (render_model == 0) { + return; + } + + eSolid *solid = render_model->Solid; + if (solid == 0) { + return; + } + + if ((static_cast(exc_flag) & 0x200000) != 0) { + if (!this->mCastsShadow) { + return; + } + if ((static_cast(exc_flag) & 0x2000) != 0) { + if (*reinterpret_cast(reinterpret_cast(solid) + 0x18) != '\0') { + return; + } + } else if (*reinterpret_cast(reinterpret_cast(solid) + 0x18) == '\0') { + return; + } + } + + bMatrix4 world_matrix; + bMatrix4 *blended_matrices = 0; + const bMatrix4 *render_matrix; + if (this->pSpaceNode != 0) { + SpaceNode *space_node = this->pSpaceNode; + + if (*reinterpret_cast(reinterpret_cast(space_node) + 0xE4) != 0) { + space_node->Update(); + } + render_matrix = reinterpret_cast(reinterpret_cast(space_node) + 0x10); + blended_matrices = *reinterpret_cast(reinterpret_cast(this->pSpaceNode) + 0xE8); + if (blended_matrices != 0) { + bMulMatrix(&world_matrix, render_matrix, &blended_matrices[1]); + goto have_world_matrix; + } + } else { + render_matrix = &this->mMatrix; + } + + PSMTX44Copy(*reinterpret_cast(render_matrix), *reinterpret_cast(&world_matrix)); + + have_world_matrix: + if (view->GetPixelSize(reinterpret_cast(&world_matrix.v3), lbl_8040CD94) < view->PixelMinSize) { + return; + } + + { + int visible = view->GetVisibleState(render_model, &world_matrix) != 0; + + if (visible == 0) { + return; + } + } + + if (this->mHeirarchy != 0) { + this->RenderNode(this->mHeirarchy, this->mHeirarchyIndex, view, exc_flag, blended_matrices, render_matrix); + } else { + this->RenderModel(render_model, view, exc_flag, blended_matrices, render_matrix); + } + + this->mLastVisibleFrame = eFrameCounter; +} + +void RenderWorldModels(eView *view, int exc_flag) { + int view_mode = eGetCurrentViewMode(); + + for (WorldModel *world_model = WorldModelList.GetHead(); world_model != WorldModelList.EndOfList(); world_model = world_model->GetNext()) { + unsigned char *world_model_bytes = reinterpret_cast(world_model); + unsigned int *world_model_words = reinterpret_cast(world_model); + + if (world_model_words[10] != 0 && (view_mode != 3 || world_model_words[11] != 0)) { + if (world_model_words[12] != 0) { + const bMatrix4 *matrix = reinterpret_cast(world_model_bytes + 0x40); + const bVector3 *position = reinterpret_cast(world_model_bytes + 0x70); + SpaceNode *space_node = *reinterpret_cast(world_model_bytes + 0x3C); + + if (space_node != 0) { + matrix = reinterpret_cast(reinterpret_cast(space_node) + 0x50); + position = reinterpret_cast(reinterpret_cast(space_node) + 0x80); + } + + bMatrix4 local_matrix; + bVector3 bbox_min; + bVector3 bbox_max; + bVector3 local_camera_position; + + local_matrix.v0 = matrix->v0; + local_matrix.v1 = matrix->v1; + local_matrix.v2 = matrix->v2; + local_matrix.v3.x = position->x; + local_matrix.v3.y = position->y; + local_matrix.v3.z = position->z; + local_matrix.v3.w = 1.0f; + + eInvertTransformationMatrix(&local_matrix, &local_matrix); + world_model->GetLocalBoundingBox(&bbox_min, &bbox_max); + eMulVector(&local_camera_position, &local_matrix, reinterpret_cast(reinterpret_cast(view->pCamera) + 0x40)); + + if (bBoundingBoxIsInside(&bbox_min, &bbox_max, &local_camera_position, 0.0f) != 0) { + continue; + } + } + + world_model->Render(view, exc_flag); + } + } + + RenderAnimSceneEffects(view, exc_flag); +} diff --git a/src/Speed/Indep/Src/World/WorldModel.hpp b/src/Speed/Indep/Src/World/WorldModel.hpp index 73da3f985..2ff01b032 100644 --- a/src/Speed/Indep/Src/World/WorldModel.hpp +++ b/src/Speed/Indep/Src/World/WorldModel.hpp @@ -7,6 +7,7 @@ #include "SpaceNode.hpp" #include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Libs/Support/Utility/UCrc.h" #include "Speed/Indep/bWare/Inc/bList.hpp" #include "Speed/Indep/bWare/Inc/bMath.hpp" #include "Speed/Indep/bWare/Inc/bSlotPool.hpp" @@ -16,6 +17,39 @@ extern SlotPool *SpaceNodeSlotPool; extern SlotPool *WorldModelSlotPool; +struct ModelHeirarchy { + enum Flags { + F_INTERNAL = 1, + }; + + 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 { + return reinterpret_cast(this + 1); + } + + Node *GetNodes() { + return reinterpret_cast(this + 1); + } + + unsigned int GetSize() const { + return sizeof(*this) + sizeof(Node) * this->mNumNodes; + } + + 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: 0x88 class WorldModel : public bTNode { @@ -40,6 +74,10 @@ class WorldModel : public bTNode { return bOMalloc(WorldModelSlotPool); } + void operator delete(void *ptr) { + bFree(WorldModelSlotPool, ptr); + } + bool IsEnabled() { return this->mEnabled; } @@ -81,6 +119,11 @@ class WorldModel : public bTNode { void AttachReplacementTextureTable(eReplacementTextureTable *replacement_texture_table, int num_textures); + void AttachLightMaterial(eLightMaterial *lm, unsigned int toskin) { + this->mLightMaterial = lm; + this->mLightMaterialSkinHash = toskin; + } + unsigned int GetNameHash(); const char *GetName(); diff --git a/src/Speed/Indep/Src/World/rain.cpp b/src/Speed/Indep/Src/World/rain.cpp index e69de29bb..ddde529f9 100644 --- a/src/Speed/Indep/Src/World/rain.cpp +++ b/src/Speed/Indep/Src/World/rain.cpp @@ -0,0 +1,191 @@ +#include "Speed/Indep/Src/World/Rain.hpp" +#include "Speed/Indep/Src/World/OnlineManager.hpp" +#include "Speed/Indep/Src/World/ParameterMaps.hpp" +#include "Speed/Indep/Src/Ecstasy/Texture.hpp" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/tires.h" +#include "Speed/Indep/bWare/Inc/Strings.hpp" + +extern float RAINRadiusX; +extern float RAINRadiusY; +extern float RAINRadiusZ; +extern float RAINwindEffect; +extern float RAINX; +extern float RAINY; +extern float RAINZ; +extern float RAINZconstant; +extern float twkCloudsMinAmount; +extern float windAng; +extern float swayMax; + +ParameterAccessor RainAccessor("Rain"); +ParameterAccessor CloudAccessor("Clouds"); + +bVector3 windAxis; + +float rainOverrideIntensity; +extern bool EnableRainIn2P; + +void TempInits() {} + +OnScreenRain::OnScreenRain() {} + +namespace Attrib { +namespace Gen { + +const tires &tires::operator=(const Instance &rhs) { + GetBase() = rhs; + return *this; +} + +} // namespace Gen +} // namespace Attrib + +Rain::Rain(eView *view, RainType StartType) { + this->CloudIntensity = twkCloudsMinAmount; + this->RoadDampness = 0.0f; + this->intensity = 0.0f; + this->percentPrecip[RAIN] = 0.0f; + this->percentPrecip[INACTIVE] = 0.0f; + this->percentPrecip[StartType] = 1.0f; + this->NumRainPoints = -1; + this->MyView = view; + this->NoRain = 0; + this->NoRainAhead = 0; + this->PRECIPpoly[0].UVs[0][0] = 0.0f; + this->PRECIPpoly[0].UVs[0][1] = 1.0f; + this->PRECIPpoly[0].UVs[0][2] = 0.1f; + this->PRECIPpoly[0].UVs[0][3] = 1.0f; + this->PRECIPpoly[0].UVs[1][0] = 0.1f; + this->PRECIPpoly[0].UVs[1][1] = 0.0f; + this->PRECIPpoly[0].UVs[1][2] = 0.0f; + this->PRECIPpoly[0].UVs[1][3] = 0.0f; + this->PRECIPpoly[0].Colours[0][0] = 0x80; + this->PRECIPpoly[0].Colours[0][1] = 0x80; + this->PRECIPpoly[0].Colours[0][2] = 0x80; + this->PRECIPpoly[0].Colours[0][3] = 0x80; + this->PRECIPpoly[0].Colours[1][0] = 0x80; + this->PRECIPpoly[0].Colours[1][1] = 0x80; + this->PRECIPpoly[0].Colours[1][2] = 0x80; + this->PRECIPpoly[0].Colours[1][3] = 0x80; + this->PRECIPpoly[0].Colours[2][0] = 0x80; + this->PRECIPpoly[0].Colours[2][1] = 0x80; + this->PRECIPpoly[0].Colours[2][2] = 0x80; + this->PRECIPpoly[0].Colours[2][3] = 0x80; + this->PRECIPpoly[0].Colours[3][0] = 0x80; + this->PRECIPpoly[0].Colours[3][1] = 0x80; + this->PRECIPpoly[0].Colours[3][2] = 0x80; + this->PRECIPpoly[0].Colours[3][3] = 0x80; + this->PRECIPpoly[1].UVs[0][0] = 0.0f; + this->PRECIPpoly[1].UVs[0][1] = 1.0f; + this->PRECIPpoly[1].UVs[0][2] = 0.1f; + this->PRECIPpoly[1].UVs[0][3] = 1.0f; + this->PRECIPpoly[1].UVs[1][0] = 0.1f; + this->PRECIPpoly[1].UVs[1][1] = 0.0f; + this->PRECIPpoly[1].UVs[1][2] = 0.0f; + this->PRECIPpoly[1].UVs[1][3] = 0.0f; + this->PRECIPpoly[1].Colours[0][0] = 100; + this->PRECIPpoly[1].Colours[0][1] = 100; + this->PRECIPpoly[1].Colours[0][2] = 100; + this->PRECIPpoly[1].Colours[0][3] = 0x1C; + this->PRECIPpoly[1].Colours[1][0] = 100; + this->PRECIPpoly[1].Colours[1][1] = 100; + this->PRECIPpoly[1].Colours[1][2] = 100; + this->PRECIPpoly[1].Colours[1][3] = 0x1C; + this->PRECIPpoly[1].Colours[2][0] = 100; + this->PRECIPpoly[1].Colours[2][1] = 100; + this->PRECIPpoly[1].Colours[2][2] = 100; + this->PRECIPpoly[1].Colours[2][3] = 0x1C; + this->PRECIPpoly[1].Colours[3][0] = 100; + this->PRECIPpoly[1].Colours[3][1] = 100; + this->PRECIPpoly[1].Colours[3][2] = 100; + this->PRECIPpoly[1].Colours[3][3] = 0x1C; + this->fogR = 0; + this->fogG = 0; + this->fogB = 0; + this->inTunnel = 0; + this->inOverpass = 0; + this->IsValidRainCurtainPos = CT_INACTIVE; +} + +void CreateWindRotMatrix(eView *view, bMatrix4 *windrot, int offset, bMatrix4 *l2w) { + int index = offset; + bMatrix4 local2world(*l2w); + float sway = bSin(bDegToAng(windAng + static_cast(index))) * swayMax; + + bIdentity(windrot); + windAxis = bVector3(1.0f, 0.0f, 0.0f); + + if (view->Precipitation != 0) { + bNormalize(&windAxis, view->Precipitation->GetWind()); + } + + local2world.v1.x = -local2world.v1.x; + local2world.v0.y = -local2world.v0.y; + local2world.v3.x = 0.0f; + local2world.v3.y = 0.0f; + local2world.v3.z = 0.0f; + local2world.v3.w = 1.0f; + eMulVector(&windAxis, &local2world, &windAxis); + eCreateAxisRotationMatrix(windrot, windAxis, bDegToAng(sway)); + eRotateZ(windrot, windrot, bDegToAng(sway)); +} + +void Rain::Init(RainType type, float percent) { + int j; + + TempInits(); + + this->texture_info[RAIN] = GetTextureInfo(bStringHash("RAINDROP"), 0, 0); + this->NumRainPoints = 350; + + for (j = 0; j < 2; j++) { + this->NumOfType[j] = 0; + this->DesiredNumOfType[j] = 0; + } + + this->NewSwapBuffer = 0; + this->OldSwapBuffer = 1; + this->NumOfType[type] = this->NumRainPoints; + this->CloudIntensity = twkCloudsMinAmount; + this->precipWindEffect[RAIN][1] = RAINwindEffect * 0.5f; + this->precipWindEffect[INACTIVE][1] = 1.0f; + this->precipRadius[RAIN] = bVector3(RAINRadiusX, RAINRadiusY, RAINRadiusZ); + this->precipWindEffect[INACTIVE][0] = 1.0f; + this->precipWindEffect[RAIN][0] = RAINwindEffect; + this->precipSpeedRange[RAIN] = bVector3(RAINX, RAINY, RAINZ); + this->precipZconstant[RAIN] = RAINZconstant; + this->windType[RAIN] = VECTOR_WIND; + this->windSpeed = bVector3(0.0f, 0.0f, 0.0f); + this->windTime = 0.0f; +} + +void SetOverRideRainIntensity(float rov) { + rainOverrideIntensity = rov; +} + +void SetRainBase() { + eGetView(1, false)->Precipitation->SetRoadDampness(0.0f); + eGetView(1, false)->Precipitation->Init(INACTIVE, 1.0f); + + if (EnableRainIn2P != 0) { + eGetView(2, false)->Precipitation->SetRoadDampness(0.0f); + eGetView(2, false)->Precipitation->Init(INACTIVE, 1.0f); + } +} + +int AmIinATunnel(eView *view, int CheckOverPass) { + if (view->Precipitation == 0) { + return 0; + } + + if (CheckOverPass != 0) { + return view->Precipitation->inTunnel != 0 || view->Precipitation->inOverpass != 0; + } + + return view->Precipitation->inTunnel; +} + +void Rain::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) {} + +void OnlineManager::InitQuantizers() {} diff --git a/src/Speed/Indep/Tools/AttribSys/Runtime/AttribHash.h b/src/Speed/Indep/Tools/AttribSys/Runtime/AttribHash.h index 6633f4de8..e83eae794 100644 --- a/src/Speed/Indep/Tools/AttribSys/Runtime/AttribHash.h +++ b/src/Speed/Indep/Tools/AttribSys/Runtime/AttribHash.h @@ -26,6 +26,12 @@ class StringKey { mString = str; } + StringKey(const StringKey &src) + : mHash64(src.mHash64) // + , mHash32(src.mHash32) // + , mString(src.mString) { + } + bool operator==(const StringKey &rhs) const { return mHash64 == rhs.mHash64; } diff --git a/src/Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h b/src/Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h index f458308a4..c18f7b407 100644 --- a/src/Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h +++ b/src/Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h @@ -608,6 +608,7 @@ class RefSpec { const Collection *GetCollection() const; const Collection *GetCollectionWithDefault() const; RefSpec &operator=(const RefSpec &rhs); + RefSpec &operator=(int rhs) { mClassKey = 0; mCollectionKey = 0; mCollectionPtr = nullptr; return *this; } void Clean() const; void operator delete(void *ptr, std::size_t bytes) { @@ -773,9 +774,9 @@ class Instance { return mCollection; } - void SetDefaultLayout(unsigned int bytes) { + void SetDefaultLayout(unsigned int bytes) const { if (mLayoutPtr == nullptr) { - mLayoutPtr = const_cast(DefaultDataArea(bytes)); + const_cast(this)->mLayoutPtr = const_cast(DefaultDataArea(bytes)); } } @@ -908,10 +909,11 @@ template class TAttrib : public Attribute { Free(ptr, bytes, "Attrib::TAttrib"); } + TAttrib() {} TAttrib(const Attribute &src) : Attribute(src) {} ~TAttrib() {} - bool &Get(unsigned int index) const; + const t &Get(unsigned int index) const; }; } // namespace Attrib 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/bChunk.hpp b/src/Speed/Indep/bWare/Inc/bChunk.hpp index ffa98e669..171b603ad 100644 --- a/src/Speed/Indep/bWare/Inc/bChunk.hpp +++ b/src/Speed/Indep/bWare/Inc/bChunk.hpp @@ -109,6 +109,10 @@ class bChunkLoader { class bChunkCarpHeader { // total size: 0x10 + enum kCarpHeaderFlags { + kResolved = 1, + }; + int mCrpSize; // offset 0x0, size 0x4 int mSectionNumber; // offset 0x4, size 0x4 int mFlags; // offset 0x8, size 0x4 @@ -121,25 +125,31 @@ class bChunkCarpHeader { ~bChunkCarpHeader() {} - int GetCarpSize() { + int GetCarpSize() const { return mCrpSize; } - int GetSectionNumber() { + int GetSectionNumber() const { return mSectionNumber; } - int GetFlags() { + int GetFlags() const { return mFlags; } - bool IsResolved() {} + bool IsResolved() const { + return (mFlags & kResolved) != 0; + } - void SetResolved() {} + void SetResolved() { + mFlags |= kResolved; + } - void SetNotResolved() {} + void SetNotResolved() { + mFlags &= ~kResolved; + } - bChunkCarpHeader *GetLastAddress() { + bChunkCarpHeader *GetLastAddress() const { return this->mLastAddress; } diff --git a/src/Speed/Indep/bWare/Inc/bList.hpp b/src/Speed/Indep/bWare/Inc/bList.hpp index 70079e26d..adb5fb331 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; @@ -305,6 +305,18 @@ template class bTList : public bList { } }; +template +T *bTList::AddSorted(SortFuncT check_flip, T *node) { + T *current = static_cast(HeadNode.Next); + while (current != reinterpret_cast(&HeadNode)) { + if (check_flip(node, current) == 0) { + return static_cast(node->AddBefore(current)); + } + current = static_cast(current->Next); + } + return AddTail(node); +} + // total size: 0xC class bPNode : public bTNode { public: @@ -315,8 +327,10 @@ class bPNode : public bTNode { this->Object = object; } - bPNode *GetObject() { - return reinterpret_cast(Object); + ~bPNode() {} + + void *GetObject() { + return Object; } void *GetpObject() { @@ -347,7 +361,7 @@ template class bPList : public bTList { void Remove(bNode *node) { bList::Remove(node); - delete node; + delete reinterpret_cast(node); } void RemoveHead() { @@ -365,7 +379,6 @@ template class bSNode { return Next; } - private: T *Next; }; @@ -388,7 +401,14 @@ template class bSList { return (T *)this; } - T *AddTail(T *node) {} + T *AddTail(T *node) { + T *prev_tail = Tail; + + Tail = node; + prev_tail->Next = node; + node->Next = EndOfList(); + return node; + } private: T *Head; // offset 0x0, size 0x4 diff --git a/src/Speed/Indep/bWare/Inc/bMath.hpp b/src/Speed/Indep/bWare/Inc/bMath.hpp index 67c82ebef..34b967be5 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 @@ -347,6 +373,7 @@ struct ALIGN_16 bVector3 { bVector3 *bNormalize(bVector3 *dest, const bVector3 *v); bVector3 *bNormalize(bVector3 *dest, const bVector3 *v, float length); bVector3 *bScaleAdd(bVector3 *dest, const bVector3 *v1, const bVector3 *v2, float scale); +bVector3 *bCross(bVector3 *dest, const bVector3 *v1, const bVector3 *v2); inline bVector3 *bFill(bVector3 *dest, float x, float y, float z) { dest->x = x; @@ -478,6 +505,8 @@ inline bVector3 bNeg(const bVector3 &v) { inline bVector3 bCross(const bVector3 &v1, const bVector3 &v2) { bVector3 dest; + bCross(&dest, &v1, &v2); + return dest; } inline float bDot(const bVector3 &v1, const bVector3 &v2) { @@ -512,10 +541,14 @@ inline bVector3 bScaleAdd(const bVector3 &v1, const bVector3 &v2, float scale) { inline bVector3 bNormalize(const bVector3 &v) { bVector3 dest; + bNormalize(&dest, &v); + return dest; } inline bVector3 bNormalize(const bVector3 &v, float length) { bVector3 dest; + bNormalize(&dest, &v, length); + return dest; } inline bVector3 bMin(const bVector3 &v1, const bVector3 &v2) { @@ -563,21 +596,7 @@ struct bVector4 { return reinterpret_cast(this)[index]; } - bVector4 operator+(const bVector4 &v) { - bVector4 *pv; - float x1; - float y1; - float z1; - float w1; - float x2; - float y2; - float z2; - float w2; - float _x; - float _y; - float _z; - float _w; - } + bVector4 operator+(const bVector4 &v) const; bVector4 operator-() { float x1; @@ -655,14 +674,17 @@ inline bVector4 &bVector4::operator+=(const bVector4 &v) { } inline bVector4 *bSub(bVector4 *dest, const bVector4 *v1, const bVector4 *v2) { - float x1; - float y1; - float z1; - float w1; - float x2; - float y2; - float z2; - float w2; + float x1 = v1->x; + float y1 = v1->y; + float z1 = v1->z; + float w1 = v1->w; + float x2 = v2->x; + float y2 = v2->y; + float z2 = v2->z; + float w2 = v2->w; + + bFill(dest, x1 - x2, y1 - y2, z1 - z2, w1 - w2); + return dest; } inline bVector4 *bNeg(bVector4 *dest, const bVector4 *v) { @@ -773,6 +795,24 @@ inline bVector4 &bVector4::operator*=(float scale) { return *this; } +inline bVector4 bVector4::operator+(const bVector4 &v) const { + bVector4 *pv; + float x1 = x; + float y1 = y; + float z1 = z; + float w1 = w; + float x2 = v.x; + float y2 = v.y; + float z2 = v.z; + float w2 = v.w; + float _x = x1 + x2; + float _y = y1 + y2; + float _z = z1 + z2; + float _w = w1 + w2; + + return bVector4(_x, _y, _z, _w); +} + inline bVector4 bVector4::operator-(const bVector4 &v) { bVector4 *pv; float x1 = x; @@ -885,6 +925,9 @@ inline void bIdentity(bMatrix4 *a) { #endif } +void bConvertFromBond(bMatrix4 &dest, const bMatrix4 &m); +void bConvertToBond(bMatrix4 &dest, const bMatrix4 &m); + inline void eIdentity(bMatrix4 *a) { bIdentity(a); } @@ -930,15 +973,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/bSlotPool.hpp b/src/Speed/Indep/bWare/Inc/bSlotPool.hpp index d612a6f96..55d6374cd 100644 --- a/src/Speed/Indep/bWare/Inc/bSlotPool.hpp +++ b/src/Speed/Indep/bWare/Inc/bSlotPool.hpp @@ -104,6 +104,7 @@ struct SlotPoolManager { int bCountFreeSlots(SlotPool *slot_pool); int bCountTotalSlots(SlotPool *slot_pool); +int bIsSlotPoolFull(SlotPool *slot_pool); SlotPool *bNewSlotPool(int slot_size, int num_slots, const char *debug_name, int memory_pool); void bDeleteSlotPool(SlotPool *slot_pool); void *bOMalloc(SlotPool *slot_pool); @@ -112,6 +113,9 @@ void bFree(SlotPool *slot_pool, void *first_slot, void *last_slot); extern SlotPool *ePolySlotPool; +inline int bGetSlotNumber(SlotPool *pool, void *p) { return pool->GetSlotNumber(p); } +inline void *bGetSlot(SlotPool *pool, int n) { return pool->GetSlot(n); } + extern unsigned char *CurrentBufferStart; extern unsigned char *CurrentBufferPos; extern unsigned char *CurrentBufferEnd; diff --git a/src/Speed/Indep/bWare/Inc/bWare.hpp b/src/Speed/Indep/bWare/Inc/bWare.hpp index 28202994b..f79285c79 100644 --- a/src/Speed/Indep/bWare/Inc/bWare.hpp +++ b/src/Speed/Indep/bWare/Inc/bWare.hpp @@ -33,6 +33,12 @@ inline void *bMalloc(int size, const char *debug_text, int debug_line, int alloc #endif +#ifdef EA_PLATFORM_PLAYSTATION2 +inline void *bMalloc(unsigned int size, int allocation_params) { + return bMalloc(static_cast(size), allocation_params); +} +#endif + void *bMalloc(SlotPool *slot_pool); void *bMalloc(SlotPool *slot_pool, int num_slots, void **last_slot); void bFree(void *ptr); @@ -133,4 +139,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..61c3f2ee3 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_]*" @@ -441,6 +452,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 +492,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 +534,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)