layout: restructure into src/ tests/ android/ scripts/ tools/
- extension/src/{port,platform,codepage} -> src/; native_render -> src/host
(+ dxt, shaders/); libgr2 -> src/gr2; extension/third_party -> third_party
- extension/tests -> tests/port, libgr2/tests -> tests/gr2
- android-native -> android (build.sh, push-client.sh moved in)
- script -> scripts; tools/40250 -> tools/server; oracle -> tools/granny-oracle;
perf tools -> tools/perf
- all build trees under build/ (native, release, android, port-gate)
- xrender:: CMake aliases -> mt::; port-map ledger impl paths rewritten
No code changes. ctest 15/15, port_gate macos+android PASS, port_map check 0 errors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
70710477cf
commit
a46093104c
@@ -0,0 +1,8 @@
|
||||
# mt_codepage — Windows ANSI code pages (874/932/936/949/950/1250-1258) from Microsoft's WindowsBestFit
|
||||
# tables; codepage_tables.inc is generated by scripts/gen_codepage_tables.py. Used by port_logic
|
||||
# (Win32Crt MultiByteToWideChar / WideCharToMultiByte) and mtnet (40250 CP936 wire text), in place of
|
||||
# the host iconv.
|
||||
add_library(mt_codepage STATIC codepage.cpp)
|
||||
target_include_directories(mt_codepage PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_compile_features(mt_codepage PUBLIC cxx_std_17)
|
||||
set_target_properties(mt_codepage PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
@@ -0,0 +1,169 @@
|
||||
#include "codepage.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
namespace mt_codepage {
|
||||
namespace {
|
||||
|
||||
constexpr uint16_t kNone = 0xFFFF;
|
||||
|
||||
struct DbcsRow {
|
||||
uint8_t lead;
|
||||
uint8_t trail_lo;
|
||||
uint8_t trail_hi;
|
||||
uint32_t offset; // into the Dbcs array, trail_lo first
|
||||
};
|
||||
|
||||
// A WCTABLE entry the first-match inverse of the byte tables gets wrong.
|
||||
struct EncodeOverride {
|
||||
uint16_t wc;
|
||||
uint16_t mb;
|
||||
uint8_t kind; // 0 exact (round-trips), 1 best fit only, 2 not encodable
|
||||
};
|
||||
|
||||
struct CodePageTable {
|
||||
unsigned cp;
|
||||
uint16_t mb_default; // Unicode char for an unmapped byte / pair (CPINFO)
|
||||
const uint16_t* single;
|
||||
const uint8_t* leads; // lo, hi pairs
|
||||
size_t lead_count;
|
||||
const DbcsRow* rows;
|
||||
size_t row_count;
|
||||
const uint16_t* dbcs;
|
||||
const EncodeOverride* overrides;
|
||||
size_t override_count;
|
||||
};
|
||||
|
||||
#include "codepage_tables.inc"
|
||||
|
||||
constexpr size_t kTableCount = sizeof(kTables) / sizeof(kTables[0]);
|
||||
|
||||
const CodePageTable* find(unsigned cp)
|
||||
{
|
||||
for (const CodePageTable& t : kTables)
|
||||
if (t.cp == cp) return &t;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool lead(const CodePageTable& t, unsigned char b)
|
||||
{
|
||||
for (size_t i = 0; i < t.lead_count; ++i)
|
||||
if (b >= t.leads[i * 2] && b <= t.leads[i * 2 + 1]) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t pair(const CodePageTable& t, unsigned char l, unsigned char trail)
|
||||
{
|
||||
for (size_t i = 0; i < t.row_count; ++i) {
|
||||
const DbcsRow& r = t.rows[i];
|
||||
if (r.lead != l) continue;
|
||||
if (trail < r.trail_lo || trail > r.trail_hi) return kNone;
|
||||
return t.dbcs[r.offset + (trail - r.trail_lo)];
|
||||
}
|
||||
return kNone;
|
||||
}
|
||||
|
||||
// Exact encoder (BMP code point -> bytes, kNone when unmapped), built on first use per code page.
|
||||
struct Encoder {
|
||||
std::once_flag once;
|
||||
std::unique_ptr<uint16_t[]> exact;
|
||||
};
|
||||
Encoder g_encoders[kTableCount];
|
||||
|
||||
const uint16_t* exact_encoder(const CodePageTable& t)
|
||||
{
|
||||
Encoder& e = g_encoders[&t - kTables];
|
||||
std::call_once(e.once, [&] {
|
||||
e.exact.reset(new uint16_t[0x10000]);
|
||||
std::fill_n(e.exact.get(), 0x10000, kNone);
|
||||
uint16_t* map = e.exact.get();
|
||||
// First match in ascending byte order, as the generator assumed.
|
||||
for (unsigned b = 0; b < 256; ++b)
|
||||
if (t.single[b] != kNone && map[t.single[b]] == kNone) map[t.single[b]] = uint16_t(b);
|
||||
for (size_t i = 0; i < t.row_count; ++i) {
|
||||
const DbcsRow& r = t.rows[i];
|
||||
for (unsigned trail = r.trail_lo; trail <= r.trail_hi; ++trail) {
|
||||
const uint16_t wc = t.dbcs[r.offset + (trail - r.trail_lo)];
|
||||
if (wc != kNone && map[wc] == kNone) map[wc] = uint16_t(r.lead << 8 | trail);
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < t.override_count; ++i) {
|
||||
const EncodeOverride& o = t.overrides[i];
|
||||
if (o.kind == 0) map[o.wc] = o.mb;
|
||||
else if (o.kind == 2) map[o.wc] = kNone;
|
||||
}
|
||||
});
|
||||
return e.exact.get();
|
||||
}
|
||||
|
||||
uint16_t best_fit(const CodePageTable& t, char32_t c)
|
||||
{
|
||||
const EncodeOverride* end = t.overrides + t.override_count;
|
||||
const EncodeOverride* it = std::lower_bound(t.overrides, end, c,
|
||||
[](const EncodeOverride& o, char32_t v) { return o.wc < v; });
|
||||
return it != end && it->wc == c && it->kind == 1 ? it->mb : kNone;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool supported(unsigned cp)
|
||||
{
|
||||
return find(cp) != nullptr;
|
||||
}
|
||||
|
||||
bool is_lead_byte(unsigned cp, unsigned char b)
|
||||
{
|
||||
const CodePageTable* t = find(cp);
|
||||
return t && lead(*t, b);
|
||||
}
|
||||
|
||||
bool decode(unsigned cp, const unsigned char* p, size_t n, std::u32string& out)
|
||||
{
|
||||
const CodePageTable* t = find(cp);
|
||||
if (!t) return false;
|
||||
bool ok = true;
|
||||
for (size_t i = 0; i < n;) {
|
||||
const unsigned char b = p[i];
|
||||
uint16_t wc = kNone;
|
||||
if (lead(*t, b) && i + 1 < n && p[i + 1] != 0) {
|
||||
wc = pair(*t, b, p[i + 1]);
|
||||
i += 2;
|
||||
} else {
|
||||
wc = t->single[b];
|
||||
++i;
|
||||
}
|
||||
if (wc == kNone) {
|
||||
wc = t->mb_default;
|
||||
ok = false;
|
||||
}
|
||||
out.push_back(wc);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
void encode(unsigned cp, std::u32string_view in, std::string& out, bool allow_best_fit, bool* used_default)
|
||||
{
|
||||
const CodePageTable* t = find(cp);
|
||||
const uint16_t* exact = t ? exact_encoder(*t) : nullptr;
|
||||
for (char32_t c : in) {
|
||||
uint16_t mb = kNone;
|
||||
if (exact && c < 0x10000) {
|
||||
mb = exact[c];
|
||||
if (mb == kNone && allow_best_fit) mb = best_fit(*t, c);
|
||||
}
|
||||
if (mb == kNone) {
|
||||
out.push_back('?');
|
||||
if (used_default) *used_default = true;
|
||||
} else if (mb > 0xFF) {
|
||||
out.push_back(char(mb >> 8));
|
||||
out.push_back(char(mb & 0xFF));
|
||||
} else {
|
||||
out.push_back(char(mb));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mt_codepage
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
// Windows ANSI code pages without the host iconv: MultiByteToWideChar / WideCharToMultiByte semantics
|
||||
// driven by Microsoft's WindowsBestFit tables (scripts/gen_codepage_tables.py). One implementation for
|
||||
// macOS, Linux, Android and iOS, so every platform decodes the 40250 locale and wire text the same way.
|
||||
//
|
||||
// Covered: 874, 932, 936, 949, 950, 1250-1258 (every code page 40250 EterLib/Util.cpp names).
|
||||
// UTF-8 is not handled here.
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace mt_codepage {
|
||||
|
||||
bool supported(unsigned cp);
|
||||
|
||||
// Byte string -> code points, as MultiByteToWideChar. A lead byte takes the next non-NUL byte as its
|
||||
// trail; an unmapped byte or pair becomes the code page's default char (U+30FB for 932, else '?').
|
||||
// Returns false when a default char was substituted (MB_ERR_INVALID_CHARS would fail).
|
||||
bool decode(unsigned cp, const unsigned char* p, size_t n, std::u32string& out);
|
||||
|
||||
// Code points -> bytes, as WideCharToMultiByte. With best_fit (no WC_NO_BEST_FIT_CHARS) a character
|
||||
// without an exact mapping may take its WCTABLE best fit ("Ā" -> "A" on 1252); otherwise, and for
|
||||
// anything unmapped, '?' is written and *used_default set.
|
||||
void encode(unsigned cp, std::u32string_view in, std::string& out, bool best_fit, bool* used_default);
|
||||
|
||||
bool is_lead_byte(unsigned cp, unsigned char b);
|
||||
|
||||
} // namespace mt_codepage
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
# libgr2 —— gr2 v6 只读读取器(M0)。零第三方依赖。
|
||||
add_library(libgr2 STATIC
|
||||
src/gr2_file.cpp # T1 header + section table + fixup
|
||||
src/gr2_decompress.cpp # T2 section 解压分派
|
||||
src/oodle1.c # T2 Granny Oodle1 解码(端口自泄露 SDK)
|
||||
src/gr2_typetree.cpp # T3 自描述类型树遍历
|
||||
src/gr2_fileinfo.cpp # T4 FileInfo 汇总
|
||||
src/gr2_skeleton.cpp # T5 骨架 + bind pose 自洽检查
|
||||
src/gr2_mesh.cpp # T6 顶点 / 索引 / 三角组 / BoneBindings
|
||||
src/gr2_anim.cpp # T7a/T7b 曲线分类 + 解码子集
|
||||
src/gr2_material.cpp # 材质视图(附加):root.Materials → 贴图文件名
|
||||
)
|
||||
add_library(mt::libgr2 ALIAS libgr2)
|
||||
|
||||
target_include_directories(libgr2
|
||||
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
|
||||
target_compile_features(libgr2 PUBLIC cxx_std_20)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_executable(libgr2_loader_errors_test ${PROJECT_SOURCE_DIR}/tests/gr2/loader_errors.cpp)
|
||||
target_link_libraries(libgr2_loader_errors_test PRIVATE mt::libgr2)
|
||||
add_test(NAME libgr2.loader_errors COMMAND libgr2_loader_errors_test)
|
||||
|
||||
add_executable(libgr2_animation_test ${PROJECT_SOURCE_DIR}/tests/gr2/animation.cpp)
|
||||
target_link_libraries(libgr2_animation_test PRIVATE mt::libgr2)
|
||||
add_test(NAME libgr2.animation COMMAND libgr2_animation_test)
|
||||
endif()
|
||||
@@ -0,0 +1,42 @@
|
||||
# libgr2
|
||||
|
||||
自研 gr2 v6/v7 **只读**读取器。零第三方依赖。施工文档 [`../docs/reference/steps/M0-gr2-reader.md`](../docs/reference/steps/M0-gr2-reader.md)。
|
||||
|
||||
## 能做什么
|
||||
|
||||
- 容器:magic / header / section / pointer-fixup(懒解引用,不物化 32 位指针)
|
||||
- section 解压:Oodle1(`oodle1.c`)
|
||||
- 自描述类型树遍历(不硬编码 struct 布局)
|
||||
- `FileInfo`:skeletons / meshes / materials / textures / models / animations
|
||||
- 骨架 + `granny_transform` → 4x4 组合(`BuildCompositeTransform4x4` 语义)
|
||||
- 蒙皮网格:顶点(按顶点格式自描述解)/ 索引 / 三角组 / BoneBindings(多 skeleton 取最优匹配)
|
||||
- 材质:`granny_material` 链(直接 `.Texture` 或递归 `.Maps[].Map`)→ `granny_texture.FromFileName`;
|
||||
`Mesh.material_textures` 与 `MaterialBindings` 平行,`tri_group.material_index` 索引它
|
||||
- 动画曲线(`OldCurveType` B 样条,degree 0/1/2)解码 + `gr2::sample_pose`(跨文件按骨骼名 retarget)
|
||||
|
||||
## 验证
|
||||
|
||||
- 全量 9166 个 Metin2 `.gr2`:**0 崩溃、0 非退化谓词失败**(`tools/gr2fuzz`)
|
||||
- **对拍真 Granny 2.9.12**:骨骼世界矩阵 ≤ 4.6e-5、蒙皮顶点 ≤ 6.5e-5(`tools/oracle_diff`,`tools/granny-oracle/m2-numeric.json`,23 用例含 v7 / 双 root / 刚体 / 多动画帧)
|
||||
- 坏输入(空 / 错 magic / 截断 / 越界索引)一律干净报错,不崩
|
||||
|
||||
## 明确不做(Metin2 语料里 0 出现;要「任意 gr2」才需要)
|
||||
|
||||
| 缺口 | 现状 | 补的成本 |
|
||||
|---|---|---|
|
||||
| **writer** | 无(设计上只读)| 大,PoC 不需要 |
|
||||
| Oodle0 解压(`Format==1`)| `decompress_status=1`,不崩 | 中,同族算法变体 |
|
||||
| BitKnit 解压(`Format==4`)| `decompress_status=4`,不崩 | 大,无公开实现;退路:Windows 侧离线转未压缩 |
|
||||
| 压缩曲线(`curve2` / `CurveData*` 变体)| `classify_curve` 标 `Identity`/`Unknown`,`read_old_curve` 跳过 | 中,`granny_curve.cpp` 各 `Da*` 分支 |
|
||||
| B 样条 degree ≥ 3 | `eval` 退化到 degree 2 | 小,加 `CubicCoefficients` |
|
||||
| 64 位 / big-endian gr2 | `load` 直接拒(`only 32-bit little-endian`)| 中 |
|
||||
| 多 skeleton 文件里刚体挂件的 skeleton 归属 | mesh binding 取「悬空最少」的 skeleton;`oracle_diff` 对这种 mesh 会误判(不是 libgr2 的问题)| 需要 FileInfo 暴露 per-model skeleton 索引 |
|
||||
|
||||
## ⚠ 许可
|
||||
|
||||
`src/oodle1.c` 是从泄露的 Granny SDK **直接端口**的解码路径(约 450 行)。
|
||||
|
||||
- **内部研究 / 不发布 / 非商用**:可用(和「拿泄露 SDK 当字节级规格」同一风险类别,全项目都这么做)。
|
||||
- **任何对外发布 / 商用**:必须 clean-room 重写(照公开的「granny2 Oodle1」算法描述从零写,不看这份端口)。
|
||||
|
||||
其余代码(容器 / 类型树 / 骨架 / 网格 / 曲线 / 姿势)是照泄露 SDK **头文件的 struct 定义和公式注释**自己写的,不是复制实现。
|
||||
@@ -0,0 +1,106 @@
|
||||
// libgr2 —— gr2 v6 只读读取器(demo 子集)。
|
||||
// 施工文档:docs/reference/steps/M0-gr2-reader.md
|
||||
#pragma once
|
||||
#include "types.h"
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace gr2 {
|
||||
|
||||
enum class Magic { Unknown, Bit32_LE, Bit32_BE, Bit64_LE, Bit64_BE };
|
||||
|
||||
struct LoadError {
|
||||
std::string stage; // "header" / "section" / "typetree" / "fileinfo" / ...
|
||||
std::string message;
|
||||
};
|
||||
|
||||
// T3:通用类型树遍历(gr2dump 打印成员用;不硬编码 struct 布局)
|
||||
struct MemberDump { std::string name; int member_type; int array_width; };
|
||||
class File;
|
||||
std::vector<MemberDump> dump_root_members(const File&);
|
||||
|
||||
// 材质视图(类型树遍历 root.Materials → granny_material)。
|
||||
// 只读文件名,不解码贴图。diffuse_texture 取 Usage 含 "diffuse" 的 map 的
|
||||
// 贴图,退而取第一张;all_textures 是从该材质可达的全部 FromFileName。
|
||||
struct MaterialInfo {
|
||||
std::string name;
|
||||
std::string diffuse_texture;
|
||||
std::vector<std::string> all_textures;
|
||||
// Metin2/EterGrnLib 渲染态推断(gr2 不带 D3DRS,靠名字 + map 数 + ExtendedData):
|
||||
int map_count = 0; // granny_material.MapCount(可达子材质的 Texture 数)
|
||||
bool alpha_blend = false;// Name 以 "Blend" 开头且 map_count>1(EterGrnLib Material.cpp:233)
|
||||
bool two_sided = false; // 名字含 2side/twoside/double/leaf/tree/ivy/fence(ExtendedData "Two-sided" 的近似)
|
||||
};
|
||||
std::vector<MaterialInfo> dump_materials(const File&);
|
||||
|
||||
// 已解析、fixup 后的文件。析构释放所有 section 缓冲。
|
||||
class File {
|
||||
public:
|
||||
static std::optional<File> load(const uint8_t* bytes, size_t len, LoadError* err);
|
||||
static std::optional<File> load_path(const std::string& path, LoadError* err);
|
||||
|
||||
// T1
|
||||
Magic magic() const { return magic_; }
|
||||
uint32_t format_version()const { return version_; } // 期望 6
|
||||
uint32_t total_size() const { return total_size_; }
|
||||
const std::vector<SectionInfo>& sections() const { return sections_; }
|
||||
|
||||
// T4 —— 完整汇总(内部会按需触发 T5/T6/T7a)
|
||||
const FileInfo& file_info() const { return info_; }
|
||||
|
||||
// 展开后的 section 字节(T2 产出)。空 section 返回空 span。调试 / 校验用。
|
||||
std::span<const uint8_t> section_bytes(size_t i) const;
|
||||
|
||||
// T5 —— bind pose 自洽检查(不依赖 oracle)。返回 max|world_bind·invBind − I|
|
||||
// 阈值策略见 M0-gr2-reader.md T5(noise_floor 前用 1e-3)
|
||||
double bind_pose_self_check(int skeleton = 0) const;
|
||||
|
||||
// M2 便捷封装(同文件内的 skeleton + animation)。跨文件(model gr2 + anim gr2)
|
||||
// 用自由函数 gr2::sample_pose(skeleton, animation, t, ...)。anim/skeleton 是 file_info() 索引。
|
||||
bool sample_pose(int anim, float t, int skeleton,
|
||||
std::vector<Mat4>& world_pose, std::vector<Mat4>& skin) const;
|
||||
bool bind_pose(int skeleton, std::vector<Mat4>& world_pose, std::vector<Mat4>& skin) const;
|
||||
|
||||
private:
|
||||
File() = default;
|
||||
Magic magic_ = Magic::Unknown;
|
||||
uint32_t version_ = 0;
|
||||
uint32_t total_size_ = 0;
|
||||
std::vector<SectionInfo> sections_;
|
||||
FileInfo info_;
|
||||
// 内部:section 缓冲、root object 指针、类型树 —— 放 .cpp 的 Impl
|
||||
struct Impl;
|
||||
std::shared_ptr<Impl> impl_;
|
||||
|
||||
friend std::vector<MemberDump> dump_root_members(const File&);
|
||||
friend std::vector<MaterialInfo> dump_materials(const File&);
|
||||
};
|
||||
|
||||
// ── M2:姿势采样(model 的 skeleton + anim 的 tracks 可来自不同 gr2)──────
|
||||
// Granny 语义、行主序、平移在 elem 12..14:
|
||||
// world[i] = Composite(local[i]) · (parent<0 ? root_offset : world[parent])
|
||||
// skin[i] = sk.bones[i].inverse_world · world[i]
|
||||
// tracks 按骨骼名匹配 sk;无匹配的骨骼用 sk 的 bind LocalTransform。
|
||||
//
|
||||
// root_offset = GrannyBuildWorldPose 的 Offset4x4。默认(nullptr)用
|
||||
// sk.initial_placement —— 与 tools/granny-oracle/oracle.c 的 GrannyGetModelInitialPlacement4x4
|
||||
// 一致,是 bind pose 与 inverse_world 自洽所要求的那一个(bind_pose 只用这一种)。
|
||||
// 渲染动画时客户端另有取法:EterGrnLib 对 Offset4x4 传身位矩阵而不是导出期的
|
||||
// InitialPlacement,而 Metin2 的 .gr2 动画 track 里根骨的位移已经含了骨盆高度,
|
||||
// 所以再乘一次 InitialPlacement 会把整个人抬高一个骨盆高。要那条语义就显式传
|
||||
// 单位阵(见 extension/src/metin2_anim.cpp 与 test/rendering/README.md 的实测)。
|
||||
void sample_pose(const Skeleton& sk, const Animation& an, float t,
|
||||
std::vector<Mat4>& world, std::vector<Mat4>& skin,
|
||||
const Mat4* root_offset = nullptr);
|
||||
void bind_pose(const Skeleton& sk, std::vector<Mat4>& world, std::vector<Mat4>& skin);
|
||||
|
||||
// 打印辅助
|
||||
const char* magic_name(Magic);
|
||||
const char* compression_name(uint32_t section_format); // 0=none 1=Oodle0 2=Oodle1
|
||||
|
||||
} // namespace gr2
|
||||
@@ -0,0 +1,189 @@
|
||||
// libgr2 —— 对外 POD 视图。不泄露内部指针 / SDK 类型。
|
||||
// 规格来源:MobileSource/Cross Platform/Granny-3D-SDK-main/source/
|
||||
// granny_file_format.h / granny_data_type_definition.h / granny_transform.h / granny_file_info.h
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <span>
|
||||
#include <array>
|
||||
|
||||
namespace gr2 {
|
||||
|
||||
using Mat4 = std::array<float, 16>; // 行主序,Granny 原生。坐标约定见 docs/reference/steps/M0-gr2-reader.md「约定」
|
||||
|
||||
// granny_transform(SRT 分量原样;Flags: 1=HasPosition 2=HasOrientation 4=HasScaleShear)。
|
||||
// 动画混合要在分量上做(GrannySampleModelAnimations 累加 SRT 再归一),不能在 4x4 上做。
|
||||
struct Transform {
|
||||
uint32_t flags = 0;
|
||||
float position[3] = {0, 0, 0};
|
||||
float orientation[4] = {0, 0, 0, 1}; // x,y,z,w
|
||||
float scale_shear[9] = {1,0,0, 0,1,0, 0,0,1}; // 行主序 3x3
|
||||
};
|
||||
// BuildCompositeTransform4x4:上 3x3 = transpose(R3·SS3),平移在 elem 12..14。
|
||||
Mat4 compose(const Transform& t);
|
||||
|
||||
// ── 骨架 ──────────────────────────────────────────────────────────────
|
||||
struct Bone {
|
||||
std::string name;
|
||||
int32_t parent = -1; // < 自身索引,或 -1 = 根
|
||||
Mat4 local_transform{}; // granny_transform SRT → 4x4(组合顺序照 granny_transform.cpp)
|
||||
Mat4 inverse_world{}; // InverseWorld4x4,来自文件
|
||||
uint32_t lt_flags = 0; // granny_transform.Flags:哪部分非单位
|
||||
Transform local; // LocalTransform 的 SRT 分量(local_transform = compose(local))
|
||||
};
|
||||
|
||||
struct Skeleton {
|
||||
std::vector<Bone> bones;
|
||||
Mat4 initial_placement{1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1}; // 绑定它的 model 的 InitialPlacement
|
||||
};
|
||||
|
||||
// ── 网格 ──────────────────────────────────────────────────────────────
|
||||
enum class VertexKind { PNT332, PNT3322, PNT332_Skinned, PNT3322_Skinned, Unknown };
|
||||
|
||||
struct Vertex { // demo 统一打包结构
|
||||
float pos[3];
|
||||
float normal[3];
|
||||
float uv0[2];
|
||||
float uv1[2]; // PNT3322 才有效
|
||||
uint8_t bone_index[4] = {0}; // *_Skinned 才有效
|
||||
uint8_t bone_weight[4] = {0}; // 归一 u8
|
||||
};
|
||||
|
||||
struct TriGroup {
|
||||
int32_t material_index;
|
||||
int32_t tri_first; // 三角序号
|
||||
int32_t tri_count;
|
||||
};
|
||||
|
||||
// 顶点类型的一个成员(granny_data_type_definition 的平铺子集;type = granny_member_type 序数)。
|
||||
struct VertexMember {
|
||||
std::string name;
|
||||
int32_t type = 0;
|
||||
int32_t array_width = 0; // 0 => 1
|
||||
uint32_t offset = 0; // 在顶点里的字节偏移(打包)
|
||||
};
|
||||
|
||||
struct Mesh {
|
||||
struct BoneBounds {
|
||||
float min[3] = {}, max[3] = {};
|
||||
bool valid = false;
|
||||
};
|
||||
std::string name;
|
||||
VertexKind kind = VertexKind::Unknown;
|
||||
std::vector<Vertex> vertices;
|
||||
std::vector<uint32_t> indices; // 三角形列表
|
||||
std::vector<TriGroup> tri_groups;
|
||||
std::vector<int32_t> bone_bindings; // mesh 局部骨骼槽 → skeleton 骨骼索引
|
||||
std::vector<BoneBounds> bone_bounds; // Parallel source BoneBindings OBBMin/OBBMax.
|
||||
uint32_t source_bone_weight_slots = 0; // 顶点类型声明值;审计 >4/8 权重
|
||||
uint32_t source_bone_index_slots = 0; // packed UInt32 记为 4
|
||||
// 与 granny_mesh.MaterialBindings 平行:每项 = 解析出的漫反射贴图文件名
|
||||
// (granny_texture.FromFileName 原样,通常是 "D:\Ymir Work\...\x.dds";空 = 未解析)。
|
||||
// tri_groups[i].material_index 索引本表。
|
||||
std::vector<std::string> material_textures;
|
||||
bool rigid = false; // GrannyMeshIsRigid
|
||||
std::vector<std::string> bone_binding_names; // granny_bone_binding.BoneName,与 bone_bounds 平行
|
||||
std::vector<int32_t> material_bindings; // granny_mesh.MaterialBindings[i].Material → FileInfo::materials 索引(-1 = 空)
|
||||
// 原始顶点:文件声明的顶点类型 + 按它打包的字节(Granny 运行时按名字做布局转换用)
|
||||
std::vector<VertexMember> vertex_members;
|
||||
uint32_t vertex_stride = 0;
|
||||
std::vector<uint8_t> raw_vertices; // vertices.size() * vertex_stride
|
||||
};
|
||||
|
||||
// ── 动画 ──────────────────────────────────────────────────────────────
|
||||
// 曲线子类型:T7a 分类。Metin2 = Granny 2.4,全是 OldCurveType(granny_curve.cpp):
|
||||
// {Int32 Degree; RefToArray Knots(Real32[]); RefToArray Controls(Real32[])}
|
||||
// 无压缩变体。label 形如 "Old(d=2,dim=4)"。
|
||||
enum class CurveFormat { Keyframes32f, /* T7a 跑 fuzz 后补齐 */ Unknown };
|
||||
|
||||
// T7b:解出的曲线(B 样条:knots 时间轴 + controls 控制点,行优先 [knot][dim])
|
||||
struct Curve {
|
||||
int32_t degree = 0;
|
||||
uint32_t dim = 0; // 3=vec, 4=quat, 9=3x3 scaleshear
|
||||
std::vector<float> knots; // KnotCount 个
|
||||
std::vector<float> controls; // KnotCount * dim 个
|
||||
bool empty() const { return knots.empty() || dim == 0; }
|
||||
// t 处求值,写 dim 个 float 到 out(identity 由调用方给)。
|
||||
void eval(float t, float* out) const;
|
||||
};
|
||||
|
||||
struct TextTrackEntry { float time_stamp = 0.0f; std::string text; };
|
||||
struct TextTrack { std::string name; std::vector<TextTrackEntry> entries; };
|
||||
|
||||
struct BoneTrack {
|
||||
std::string bone_name;
|
||||
int32_t bone_index = -1; // 映射到 skeleton(未映射 = -1)
|
||||
std::string pos_type = "none";
|
||||
std::string rot_type = "none";
|
||||
std::string scale_type = "none";
|
||||
Curve position; // dim 3
|
||||
Curve orientation; // dim 4(四元数 x,y,z,w)
|
||||
Curve scale_shear; // dim 9(3x3)或 dim 3
|
||||
// t 处的 SRT(缺的曲线取单位分量,flags 只标出有曲线的分量)。sample_local 的 4x4 = compose(这个)。
|
||||
void sample(float t, Transform& out) const;
|
||||
};
|
||||
|
||||
struct Animation {
|
||||
std::string name;
|
||||
float duration = 0.0f;
|
||||
float time_step = 0.0f;
|
||||
float oversampling = 1.0f;
|
||||
int32_t track_group_count = 0;
|
||||
std::vector<TextTrack> text_tracks; // TrackGroups[0].TextTracks
|
||||
// TrackGroups[0].AccumulationFlags / LoopTranslation(root motion:
|
||||
// 1 = AccumulationExtracted,4 = AccumulationIsVDA)。
|
||||
int32_t accumulation_flags = 0;
|
||||
float loop_translation[3] = {0.0f, 0.0f, 0.0f};
|
||||
std::vector<BoneTrack> tracks;
|
||||
// t 处每个 track 的局部 4x4(granny_transform 组合语义,行主序)。
|
||||
// out 按 tracks 顺序;bone_index 见各 track。
|
||||
void sample_local(float t, std::vector<Mat4>& out) const;
|
||||
};
|
||||
|
||||
// 顶层 granny_material(driven by root Materials[])。name + 解析出的漫反射贴图。
|
||||
// root Materials[] 之外、只经 Maps[] / mesh MaterialBindings 可达的子材质追加在后面。
|
||||
struct MaterialMap {
|
||||
std::string usage; // granny_material_map.Usage("Diffuse Color" 等)
|
||||
int32_t material = -1; // FileInfo::materials 索引
|
||||
};
|
||||
struct Material {
|
||||
std::string name;
|
||||
std::string diffuse_texture; // granny_texture.FromFileName 原样
|
||||
std::string texture; // 本材质自己的 Texture.FromFileName(空 = 无 Texture)
|
||||
std::vector<MaterialMap> maps; // granny_material.Maps
|
||||
int32_t two_sided = 0; // ExtendedData 的 Int32 "Two-sided"
|
||||
bool has_two_sided = false;
|
||||
};
|
||||
|
||||
// granny_model:Skeleton + InitialPlacement + MeshBindings。
|
||||
struct Model {
|
||||
std::string name;
|
||||
int32_t skeleton = -1; // FileInfo::skeletons 索引
|
||||
Transform initial_placement;
|
||||
std::vector<int32_t> mesh_bindings; // FileInfo::meshes 索引(-1 = 未解析)
|
||||
};
|
||||
|
||||
// ── FileInfo 汇总(T4)────────────────────────────────────────────────
|
||||
struct FileInfo {
|
||||
std::string from_file_name;
|
||||
std::vector<Skeleton> skeletons;
|
||||
std::vector<Mesh> meshes;
|
||||
std::vector<Animation> animations;
|
||||
std::vector<Material> materials;
|
||||
std::vector<Model> models;
|
||||
uint32_t material_count = 0;
|
||||
uint32_t texture_count = 0;
|
||||
uint32_t model_count = 0;
|
||||
};
|
||||
|
||||
// ── section 表(T1,gr2dump --sections 用)────────────────────────────
|
||||
struct SectionInfo {
|
||||
uint32_t compression; // 0=none 1/2=Oodle0/1 4=BitKnit
|
||||
uint32_t data_size; // 压缩后
|
||||
uint32_t expanded_size; // 展开后
|
||||
uint32_t pointer_fixup_count;
|
||||
int decompress_status; // 0=ok,非 0=解压失败/未实现(T2)
|
||||
};
|
||||
|
||||
} // namespace gr2
|
||||
@@ -0,0 +1,301 @@
|
||||
// M0 T7a —— 曲线子类型分类(gr2fuzz 直方图依赖)。T7b(解码)见文末。
|
||||
// 见 docs/reference/steps/M0-gr2-reader.md
|
||||
// 规格:granny_track_group.cpp (TrackGroupType/TransformTrackType/Curve2Type) /
|
||||
// granny_curve.cpp (CurveData*Type,均以 CurveDataHeaderType 开头) /
|
||||
// granny_curve.h (curve_data_header{ u8 Format; u8 Degree })
|
||||
#include "internal.h"
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#define ADBG(...) do { if (getenv("GR2_DEBUG")) fprintf(stderr, "[anim] " __VA_ARGS__); } while (0)
|
||||
|
||||
namespace gr2 {
|
||||
|
||||
using detail::Ref;
|
||||
using detail::TypeMember;
|
||||
|
||||
// Metin2 = Granny 2.4:曲线是 OldCurveType(granny_curve.cpp):
|
||||
// {Int32 Degree; RefToArray Knots(Real32[]); RefToArray Controls(Real32[])} —— 20B,无压缩变体。
|
||||
// curve_obj 指向 transform_track 里 inline 的那个 curve 对象本体。
|
||||
// 新版(2.6+)是 curve2{variant CurveData},本函数一并识别、打标签。
|
||||
std::string File::Impl::classify_curve_obj(Ref curve_obj, Ref curve_type) {
|
||||
if (!curve_obj.valid() || !curve_type.valid()) return "none";
|
||||
const auto& ms = parse_type(curve_type);
|
||||
if (ms.empty()) return "Unknown";
|
||||
|
||||
// 老格式:第一个成员是 "Degree"
|
||||
if (ms[0].name == "Degree") {
|
||||
const TypeMember* mD = &ms[0];
|
||||
const TypeMember* mK = find_member(ms, "Knots");
|
||||
const TypeMember* mC = find_member(ms, "Controls");
|
||||
int32_t degree = rd_i32(member_slot(curve_obj, *mD));
|
||||
uint32_t kn = mK ? uint32_t(std::max(0, rd_i32(member_slot(curve_obj, *mK)))) : 0;
|
||||
uint32_t cn = mC ? uint32_t(std::max(0, rd_i32(member_slot(curve_obj, *mC)))) : 0;
|
||||
uint32_t dim = kn ? cn / kn : 0;
|
||||
char buf[64];
|
||||
std::snprintf(buf, sizeof buf, "Old(d=%d,dim=%u)", degree, dim);
|
||||
return buf;
|
||||
}
|
||||
// 新格式:curve2{variant CurveData}
|
||||
if (ms[0].name == "CurveData") {
|
||||
Ref type_ref = follow(member_slot(curve_obj, ms[0])); // variant.Type
|
||||
if (!type_ref.valid()) return "Identity";
|
||||
const auto& dm = parse_type(type_ref);
|
||||
if (dm.empty()) return "Unknown";
|
||||
const std::string& n = dm[0].name;
|
||||
const char* pfx = "CurveDataHeader_";
|
||||
return (n.rfind(pfx, 0) == 0) ? n.substr(std::strlen(pfx)) : n;
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
// 读一条 OldCurveType 曲线({Int32 Degree; RefToArray Knots; RefToArray Controls})到 Curve。
|
||||
void File::Impl::read_old_curve(Ref curve_obj, Ref curve_type, Curve& out) {
|
||||
if (!curve_obj.valid() || !curve_type.valid()) return;
|
||||
const auto& ms = parse_type(curve_type);
|
||||
if (ms.empty() || ms[0].name != "Degree") return; // 新格式暂不解
|
||||
const TypeMember* mD = &ms[0];
|
||||
const TypeMember* mK = find_member(ms, "Knots");
|
||||
const TypeMember* mC = find_member(ms, "Controls");
|
||||
if (!mK || !mC) return;
|
||||
|
||||
out.degree = rd_i32(member_slot(curve_obj, *mD));
|
||||
|
||||
auto read_farr = [&](const TypeMember& m, std::vector<float>& dst) {
|
||||
Ref slot = member_slot(curve_obj, m);
|
||||
int32_t cnt = rd_i32(slot);
|
||||
if (cnt <= 0 || cnt > (1 << 24)) return;
|
||||
Ref base = follow({slot.section, slot.offset + 4});
|
||||
const uint8_t* p = at(base, size_t(cnt) * 4);
|
||||
if (!p) return;
|
||||
dst.resize(cnt);
|
||||
for (int32_t k = 0; k < cnt; ++k) std::memcpy(&dst[k], p + size_t(k) * 4, 4);
|
||||
};
|
||||
read_farr(*mK, out.knots);
|
||||
read_farr(*mC, out.controls);
|
||||
out.dim = out.knots.empty() ? 0 : uint32_t(out.controls.size() / out.knots.size());
|
||||
if (out.dim && out.knots.size() * out.dim != out.controls.size()) out.dim = 0; // 不一致 → 弃
|
||||
|
||||
// 有些资产的曲线控制点本身是 NaN(如 redthief_general/*_damage.gr2 的 finger track)。
|
||||
// 不 sanitize 会把 NaN 灌进整条蒙皮链。→ 整条曲线弃(eval 返回空,调用方回退到 bind)。
|
||||
for (float v : out.knots) if (!std::isfinite(v)) { out.dim = 0; return; }
|
||||
for (float v : out.controls) if (!std::isfinite(v)) { out.dim = 0; return; }
|
||||
}
|
||||
|
||||
// animation: {String Name; Real32 Duration; Real32 TimeStep; Real32 Oversampling;
|
||||
// ArrayOfRefs TrackGroups; ...}
|
||||
// track_group: {String Name; RefToArray VectorTracks; RefToArray TransformTracks; ...}
|
||||
// transform_track: {String Name; Int32 Flags; Inline OrientationCurve(curve2);
|
||||
// Inline PositionCurve(curve2); Inline ScaleShearCurve(curve2)}
|
||||
bool File::Impl::extract_animation(Ref anim_obj, Ref anim_type, const Skeleton* skel, Animation& out) {
|
||||
if (!anim_obj.valid() || !anim_type.valid()) return false;
|
||||
const auto& ams = parse_type(anim_type);
|
||||
|
||||
if (const TypeMember* m = find_member(ams, "Name"))
|
||||
out.name = rd_str(member_slot(anim_obj, *m));
|
||||
if (const TypeMember* m = find_member(ams, "Duration"))
|
||||
out.duration = rd_f32(member_slot(anim_obj, *m));
|
||||
if (const TypeMember* m = find_member(ams, "TimeStep"))
|
||||
out.time_step = rd_f32(member_slot(anim_obj, *m));
|
||||
if (const TypeMember* m = find_member(ams, "Oversampling"))
|
||||
out.oversampling = rd_f32(member_slot(anim_obj, *m));
|
||||
|
||||
const TypeMember* mTG = find_member(ams, "TrackGroups");
|
||||
if (!mTG) return true;
|
||||
auto groups = read_array_of_refs(anim_obj, *mTG);
|
||||
out.track_group_count = int32_t(groups.size());
|
||||
const auto& tgms = parse_type(mTG->ref_type);
|
||||
|
||||
// TrackGroups[0].AccumulationFlags (Int32) + LoopTranslation (Real32[3])
|
||||
if (!groups.empty() && groups[0].valid()) {
|
||||
if (const TypeMember* m = find_member(tgms, "AccumulationFlags"))
|
||||
out.accumulation_flags = rd_i32(member_slot(groups[0], *m));
|
||||
if (const TypeMember* m = find_member(tgms, "LoopTranslation")) {
|
||||
Ref r = member_slot(groups[0], *m);
|
||||
for (int i = 0; i < 3; ++i)
|
||||
out.loop_translation[i] = rd_f32(Ref{r.section, r.offset + uint32_t(i) * 4u});
|
||||
}
|
||||
}
|
||||
|
||||
// TrackGroups[0].TextTracks: RefToArray text_track{String Name; RefToArray Entries(
|
||||
// text_track_entry{Real32 TimeStamp; String Text})}(motion event 用)
|
||||
if (!groups.empty() && groups[0].valid())
|
||||
if (const TypeMember* mTx = find_member(tgms, "TextTracks")) {
|
||||
Ref xb; uint32_t xc = 0, xs = 0;
|
||||
if (read_ref_to_array(groups[0], *mTx, xb, xc, xs) && xc && xs) {
|
||||
const auto& xms = parse_type(mTx->ref_type);
|
||||
const TypeMember* mXN = find_member(xms, "Name");
|
||||
const TypeMember* mXE = find_member(xms, "Entries");
|
||||
for (uint32_t i = 0; i < xc && i < 4096; ++i) {
|
||||
Ref x = {xb.section, xb.offset + i * xs};
|
||||
TextTrack tt;
|
||||
if (mXN) tt.name = rd_str(member_slot(x, *mXN));
|
||||
Ref eb; uint32_t ec = 0, es = 0;
|
||||
if (mXE && read_ref_to_array(x, *mXE, eb, ec, es) && ec && es) {
|
||||
const auto& ems = parse_type(mXE->ref_type);
|
||||
const TypeMember* mTS = find_member(ems, "TimeStamp");
|
||||
const TypeMember* mTT = find_member(ems, "Text");
|
||||
for (uint32_t k = 0; k < ec && k < 65536; ++k) {
|
||||
Ref e = {eb.section, eb.offset + k * es};
|
||||
TextTrackEntry te;
|
||||
if (mTS) te.time_stamp = rd_f32(member_slot(e, *mTS));
|
||||
if (mTT) te.text = rd_str(member_slot(e, *mTT));
|
||||
tt.entries.push_back(std::move(te));
|
||||
}
|
||||
}
|
||||
out.text_tracks.push_back(std::move(tt));
|
||||
}
|
||||
}
|
||||
}
|
||||
const TypeMember* mTT = find_member(tgms, "TransformTracks");
|
||||
if (!mTT) return true;
|
||||
|
||||
const auto& ttms = parse_type(mTT->ref_type);
|
||||
if (getenv("GR2_DEBUG")) {
|
||||
fprintf(stderr, "[anim] transform_track type members=%zu:\n", ttms.size());
|
||||
for (auto& m : ttms)
|
||||
fprintf(stderr, "[anim] %-20s type=%d aw=%d off=%u size=%u reftype=(%d,%u)\n",
|
||||
m.name.c_str(), m.type, m.array_width, m.offset, m.size,
|
||||
m.ref_type.section, m.ref_type.offset);
|
||||
}
|
||||
const TypeMember* mName = find_member(ttms, "Name");
|
||||
const TypeMember* mOri = find_member(ttms, "OrientationCurve");
|
||||
const TypeMember* mPos = find_member(ttms, "PositionCurve");
|
||||
const TypeMember* mScl = find_member(ttms, "ScaleShearCurve");
|
||||
|
||||
for (Ref g : groups) {
|
||||
if (!g.valid()) continue;
|
||||
Ref base; uint32_t count = 0, stride = 0;
|
||||
if (!read_ref_to_array(g, *mTT, base, count, stride) || count == 0) continue;
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
Ref t = {base.section, base.offset + i * stride};
|
||||
BoneTrack bt;
|
||||
if (mName) bt.bone_name = rd_str(member_slot(t, *mName));
|
||||
if (mPos) {
|
||||
bt.pos_type = classify_curve_obj(member_slot(t, *mPos), mPos->ref_type);
|
||||
read_old_curve(member_slot(t, *mPos), mPos->ref_type, bt.position);
|
||||
}
|
||||
if (mOri) {
|
||||
bt.rot_type = classify_curve_obj(member_slot(t, *mOri), mOri->ref_type);
|
||||
read_old_curve(member_slot(t, *mOri), mOri->ref_type, bt.orientation);
|
||||
}
|
||||
if (mScl) {
|
||||
bt.scale_type = classify_curve_obj(member_slot(t, *mScl), mScl->ref_type);
|
||||
read_old_curve(member_slot(t, *mScl), mScl->ref_type, bt.scale_shear);
|
||||
}
|
||||
if (skel) for (size_t k = 0; k < skel->bones.size(); ++k)
|
||||
if (skel->bones[k].name == bt.bone_name) { bt.bone_index = int32_t(k); break; }
|
||||
out.tracks.push_back(std::move(bt));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── T7b:曲线解码 ─────────────────────────────────────────────────────
|
||||
// Metin2 曲线全是 OldCurveType(B 样条:Degree + Knots[] + Controls[knot*dim])。
|
||||
// 求值照 granny_bspline.cpp SampleBSpline{0,1,2}xN + granny_bspline_inlines.h
|
||||
// {Linear,Quadratic}Coefficients。degree 0=常量,1=线性,2=二次 B 样条。
|
||||
// KnotIndex = "第一个 > t 的 knot"(granny CurveFindKnot),窗口越界按端点 clamp。
|
||||
//
|
||||
// dim==4(四元数):blend 之前对本 span 参与的 degree+1 个控制点做
|
||||
// EnsureQuaternionContinuity(granny_math.cpp)—— 逐点若与前一点点积 < 0 则整体取反。
|
||||
// 少了这一步,跨半球(q vs -q)的控制点线性混合会收缩到近零,归一后得到一个
|
||||
// 合法但方向错约 180° 的旋转(表情动作里脖子/头会折叠塌陷)。
|
||||
void Curve::eval(float t, float* out) const {
|
||||
const uint32_t n = (uint32_t)knots.size();
|
||||
if (n == 0 || dim == 0) return;
|
||||
auto Kc = [&](int i) -> float {
|
||||
i = i < 0 ? 0 : (i >= (int)n ? (int)n - 1 : i);
|
||||
return knots[(uint32_t)i];
|
||||
};
|
||||
|
||||
if (n == 1 || degree <= 0) { // 常量
|
||||
for (uint32_t d = 0; d < dim; ++d) out[d] = controls[d];
|
||||
return;
|
||||
}
|
||||
if (t <= knots[0]) t = knots[0];
|
||||
if (t >= knots[n - 1]) t = knots[n - 1];
|
||||
int i = (int)n - 1;
|
||||
for (uint32_t k = 0; k < n; ++k) if (knots[k] > t) { i = (int)k; break; }
|
||||
if (i < 1) i = 1;
|
||||
|
||||
const int span = (degree == 1) ? 2 : 3; // 本 span 的控制点数 = degree+1
|
||||
const int first = (degree == 1) ? (i - 1) : (i - 2);
|
||||
|
||||
// 拷本 span 的控制点(越界按端点 clamp),四元数做半球连续化
|
||||
// Metin2 的 ScaleShearCurve 是 9 维;旧的 3*4 缓冲区会在这里发生
|
||||
// 栈溢出(degree=2 时写入 27 个 float),随后被 __stack_chk_fail 终止。
|
||||
// 目前支持的曲线维度为 position=3、orientation=4、scale/shear=9。
|
||||
if (dim > 9) return;
|
||||
float cp[3 * 9];
|
||||
for (int s = 0; s < span; ++s) {
|
||||
int ci = first + s;
|
||||
ci = ci < 0 ? 0 : (ci >= (int)n ? (int)n - 1 : ci);
|
||||
for (uint32_t d = 0; d < dim; ++d) cp[s * dim + d] = controls[(uint32_t)ci * dim + d];
|
||||
}
|
||||
if (dim == 4) {
|
||||
for (int s = 1; s < span; ++s) {
|
||||
const float* p = &cp[(s - 1) * 4];
|
||||
float* q = &cp[s * 4];
|
||||
if (p[0]*q[0] + p[1]*q[1] + p[2]*q[2] + p[3]*q[3] < 0.0f)
|
||||
for (int d = 0; d < 4; ++d) q[d] = -q[d];
|
||||
}
|
||||
}
|
||||
|
||||
if (degree == 1) {
|
||||
float denom = Kc(i) - Kc(i - 1);
|
||||
float c0 = denom != 0.0f ? (t - Kc(i - 1)) / denom : 0.0f;
|
||||
c0 = c0 < 0 ? 0 : (c0 > 1 ? 1 : c0);
|
||||
float c1 = 1.0f - c0;
|
||||
for (uint32_t d = 0; d < dim; ++d)
|
||||
out[d] = c1 * cp[0 * dim + d] + c0 * cp[1 * dim + d];
|
||||
} else { // degree >= 2 → 二次(Metin2 无 degree 3)
|
||||
float ti_2 = Kc(i - 2), ti_1 = Kc(i - 1), ti = Kc(i), ti1 = Kc(i + 1);
|
||||
auto fdiv = [](float a, float b) { return b != 0.0f ? a / b : 0.0f; };
|
||||
float L0 = fdiv(t - ti_1, ti - ti_1);
|
||||
float L1_1 = fdiv(t - ti_2, ti - ti_2);
|
||||
float L1_2 = fdiv(t - ti_1, ti1 - ti_1);
|
||||
float tmp = (L1_1 + L0) - L0 * L1_1;
|
||||
float c0 = L0 * L1_2; // ci
|
||||
float c1 = tmp - c0; // ci_1
|
||||
float c2 = 1.0f - tmp; // ci_2
|
||||
for (uint32_t d = 0; d < dim; ++d)
|
||||
out[d] = c2 * cp[0 * dim + d] + c1 * cp[1 * dim + d] + c0 * cp[2 * dim + d];
|
||||
}
|
||||
|
||||
if (dim == 4) { // 四元数:归一
|
||||
float m = std::sqrt(out[0]*out[0] + out[1]*out[1] + out[2]*out[2] + out[3]*out[3]);
|
||||
if (m > 1e-12f) for (int d = 0; d < 4; ++d) out[d] /= m;
|
||||
}
|
||||
}
|
||||
|
||||
// t 处的 SRT。position 缺省 (0,0,0),orientation 缺省单位四元数,scaleshear 缺省 I。
|
||||
void BoneTrack::sample(float t, Transform& out) const {
|
||||
out = Transform{};
|
||||
if (!position.empty()) { position.eval(t, out.position); out.flags |= 0x1; }
|
||||
if (!orientation.empty()) { orientation.eval(t, out.orientation); out.flags |= 0x2; }
|
||||
if (!scale_shear.empty()) {
|
||||
if (scale_shear.dim == 9) { scale_shear.eval(t, out.scale_shear); out.flags |= 0x4; }
|
||||
else if (scale_shear.dim == 3) {
|
||||
float s[3]; scale_shear.eval(t, s);
|
||||
out.scale_shear[0] = s[0]; out.scale_shear[4] = s[1]; out.scale_shear[8] = s[2];
|
||||
out.flags |= 0x4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// t 处每个 track 的局部 4x4(BuildCompositeTransform4x4 语义:C 上 3x3 = transpose(R3*SS3),
|
||||
// 平移在 elem 12..14)。
|
||||
void Animation::sample_local(float t, std::vector<Mat4>& out) const {
|
||||
out.resize(tracks.size());
|
||||
Transform x;
|
||||
for (size_t ti = 0; ti < tracks.size(); ++ti) {
|
||||
tracks[ti].sample(t, x);
|
||||
out[ti] = compose(x);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gr2
|
||||
@@ -0,0 +1,58 @@
|
||||
// M0 T2 —— section 解压分派。见 docs/reference/steps/M0-gr2-reader.md
|
||||
#include "internal.h"
|
||||
#include "oodle1.h"
|
||||
#include <cstring>
|
||||
|
||||
namespace gr2 {
|
||||
|
||||
// src 指向文件里该 section 的压缩字节(长度 s.data_size)。
|
||||
// 本函数会做一份可写的、尾部留 32B 零的副本(Oodle1 解码器会写它、读过界)。
|
||||
bool File::Impl::decompress_section(const detail::GrnSection& s, const uint8_t* src,
|
||||
detail::Section& out, LoadError* err) {
|
||||
out.info = {s.compression, s.data_size, s.expanded_data_size, s.pointer_fixup_count, 0};
|
||||
|
||||
if (s.expanded_data_size == 0) { // 空 section
|
||||
out.data.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (s.compression) {
|
||||
case 0: { // NoCompression
|
||||
out.data.assign(src, src + s.data_size);
|
||||
out.data.resize(s.expanded_data_size);
|
||||
return true;
|
||||
}
|
||||
case 1: // Oodle0 —— 同一族解码器的变体,暂未实现(T2 后续;样本里极少)
|
||||
out.info.decompress_status = 1;
|
||||
if (err) *err = {"section", "Oodle0 not implemented (M0 T2)"};
|
||||
return false;
|
||||
case 2: { // Oodle1
|
||||
// 4 字节对齐的可写副本 + 32B 零尾
|
||||
std::vector<uint8_t> buf;
|
||||
buf.reserve(s.data_size + 32);
|
||||
buf.assign(src, src + s.data_size);
|
||||
buf.resize(s.data_size + 32, 0);
|
||||
out.data.assign(s.expanded_data_size, 0);
|
||||
int rc = gr2_oodle1_decompress(/*byte_reversed=*/0,
|
||||
s.data_size, buf.data(),
|
||||
s.first16bit, s.first8bit, s.expanded_data_size,
|
||||
out.data.data());
|
||||
out.info.decompress_status = rc;
|
||||
if (rc != 0) {
|
||||
if (err) *err = {"section", "Oodle1 decompress failed"};
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case 4: // BitKnit —— 无公开实现(全样本 0 个)
|
||||
out.info.decompress_status = 4;
|
||||
if (err) *err = {"section", "BitKnit unsupported (M0 T2)"};
|
||||
return false;
|
||||
default:
|
||||
out.info.decompress_status = -1;
|
||||
if (err) *err = {"section", "unknown compression format"};
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gr2
|
||||
@@ -0,0 +1,205 @@
|
||||
// M0 T1 —— header + section table + fixup。见 docs/reference/steps/M0-gr2-reader.md
|
||||
// 规格:MobileSource/Cross Platform/Granny-3D-SDK-main/source/granny_file_format.h + .cpp
|
||||
//
|
||||
// 本次实现范围:magic 识别 + grn_file_header + section 表解析(--sections 用)。
|
||||
// section 解压(Oodle0/1)是 T2;类型树 / FileInfo 是 T3/T4 —— 仍是 stub。
|
||||
#include "internal.h"
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
namespace gr2 {
|
||||
namespace {
|
||||
|
||||
// 小端读取 + 边界检查
|
||||
inline bool rd32(const uint8_t* p, size_t len, size_t off, uint32_t& out) {
|
||||
if (off + 4 > len) return false;
|
||||
out = uint32_t(p[off]) | (uint32_t(p[off + 1]) << 8) |
|
||||
(uint32_t(p[off + 2]) << 16) | (uint32_t(p[off + 3]) << 24);
|
||||
return true;
|
||||
}
|
||||
|
||||
// granny_file_format.cpp 里的 5 个已知 magic(LE u32 序列)
|
||||
struct KnownMagic { uint32_t v[4]; Magic tag; const char* name; };
|
||||
constexpr KnownMagic kMagics[] = {
|
||||
// GRNFileMV_Old —— Metin2 的 gr2(v6 era)用这个
|
||||
{{0xCAB067B8, 0x0FB16DF8, 0x7E8C7284, 0x1E00195E}, Magic::Bit32_LE, "Old(32LE)"},
|
||||
{{0xC06CDE29, 0x2B53A4BA, 0xA5B7F525, 0xEEE266F6}, Magic::Bit32_LE, "32Bit_LittleEndian"},
|
||||
{{0xB595110E, 0x4BB5A56A, 0x502828EB, 0x04B37825}, Magic::Bit32_BE, "32Bit_BigEndian"},
|
||||
{{0x5E499BE5, 0x141F636F, 0xA9EB131E, 0xC4EDBE90}, Magic::Bit64_LE, "64Bit_LittleEndian"},
|
||||
{{0xE3D49531, 0x624FDC20, 0x3AD036CC, 0x89FF82B1}, Magic::Bit64_BE, "64Bit_BigEndian"},
|
||||
};
|
||||
|
||||
constexpr size_t kMagicStructSize = 32; // grn_file_magic_value:4*u32 magic + HeaderSize + HeaderFormat + 2*u32 reserved
|
||||
constexpr size_t kSectionSize = 44; // grn_section:11 * u32
|
||||
|
||||
} // namespace
|
||||
|
||||
const char* magic_name(Magic m) {
|
||||
switch (m) {
|
||||
case Magic::Bit32_LE: return "32-bit LE";
|
||||
case Magic::Bit32_BE: return "32-bit BE";
|
||||
case Magic::Bit64_LE: return "64-bit LE";
|
||||
case Magic::Bit64_BE: return "64-bit BE";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
const char* compression_name(uint32_t f) {
|
||||
switch (f) {
|
||||
case 0: return "none";
|
||||
case 1: return "Oodle0";
|
||||
case 2: return "Oodle1";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<File> File::load(const uint8_t* bytes, size_t len, LoadError* err) {
|
||||
if (err) *err = {};
|
||||
auto fail = [&](const char* stage, const char* msg) {
|
||||
if (err) *err = {stage, msg};
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
if (!bytes || len < kMagicStructSize + 32)
|
||||
return fail("header", "file too small");
|
||||
|
||||
// ── magic ────────────────────────────────────────────────────────────
|
||||
uint32_t m[4];
|
||||
for (int i = 0; i < 4; ++i) rd32(bytes, len, size_t(i) * 4, m[i]);
|
||||
Magic magic = Magic::Unknown;
|
||||
for (const auto& k : kMagics) {
|
||||
if (m[0] == k.v[0] && m[1] == k.v[1] && m[2] == k.v[2] && m[3] == k.v[3]) {
|
||||
magic = k.tag;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (magic == Magic::Unknown)
|
||||
return fail("header", "not a Granny file (unknown magic)");
|
||||
if (magic != Magic::Bit32_LE)
|
||||
return fail("header", "only 32-bit little-endian supported (M0 range)");
|
||||
|
||||
uint32_t header_size = 0, header_format = 0;
|
||||
rd32(bytes, len, 16, header_size);
|
||||
rd32(bytes, len, 20, header_format); // 注意:这不是 section 压缩,section 压缩看每段 Format
|
||||
|
||||
// ── grn_file_header(在 magic 结构之后)─────────────────────────────
|
||||
const size_t H = kMagicStructSize; // header 起点
|
||||
// grn_file_header: Version(0) TotalSize(4) CRC(8) SectionArrayOffset(12)
|
||||
// SectionArrayCount(16) RootObjectTypeDefinition{Section(20) Offset(24)}
|
||||
// RootObject{Section(28) Offset(32)} TypeTag(36) ...
|
||||
uint32_t version = 0, total_size = 0, sec_off = 0, sec_count = 0;
|
||||
uint32_t rt_sec = 0, rt_off = 0, ro_sec = 0, ro_off = 0;
|
||||
if (!rd32(bytes, len, H + 0, version) ||
|
||||
!rd32(bytes, len, H + 4, total_size) ||
|
||||
!rd32(bytes, len, H + 12, sec_off) ||
|
||||
!rd32(bytes, len, H + 16, sec_count) ||
|
||||
!rd32(bytes, len, H + 20, rt_sec) ||
|
||||
!rd32(bytes, len, H + 24, rt_off) ||
|
||||
!rd32(bytes, len, H + 28, ro_sec) ||
|
||||
!rd32(bytes, len, H + 32, ro_off))
|
||||
return fail("header", "truncated grn_file_header");
|
||||
|
||||
// ── section 表 ──────────────────────────────────────────────────────
|
||||
// SectionArrayOffset 相对 grn_file_header 起点(H)
|
||||
const size_t sec_base = H + sec_off;
|
||||
if (sec_count > 64)
|
||||
return fail("section", "implausible section count");
|
||||
if (sec_base + size_t(sec_count) * kSectionSize > len)
|
||||
return fail("section", "section array out of range");
|
||||
|
||||
// ── AC 自检(不致命,仅 GR2_DEBUG 打日志)──────────────────────────
|
||||
if (std::getenv("GR2_DEBUG")) {
|
||||
if (version != 6 && version != 7)
|
||||
std::fprintf(stderr, "gr2: warn: format_version=%u (期望 6/7)\n", version);
|
||||
if (total_size != len)
|
||||
std::fprintf(stderr, "gr2: warn: total_size=%u != file size %zu\n", total_size, len);
|
||||
}
|
||||
|
||||
File out;
|
||||
out.magic_ = magic;
|
||||
out.version_ = version;
|
||||
out.total_size_ = total_size;
|
||||
out.impl_ = std::make_shared<Impl>();
|
||||
out.impl_->sections.resize(sec_count);
|
||||
out.impl_->raw.resize(sec_count);
|
||||
out.sections_.reserve(sec_count);
|
||||
|
||||
// ── 逐 section:读头 + T2 展开 ─────────────────────────────────────
|
||||
for (uint32_t i = 0; i < sec_count; ++i) {
|
||||
const size_t s = sec_base + size_t(i) * kSectionSize;
|
||||
uint32_t f[11];
|
||||
for (int j = 0; j < 11; ++j) rd32(bytes, len, s + size_t(j) * 4, f[j]);
|
||||
// f: Format DataOffset DataSize ExpandedDataSize InternalAlignment
|
||||
// First16Bit First8Bit PtrFixupOff PtrFixupCount MixedFixupOff MixedFixupCount
|
||||
detail::GrnSection gs{};
|
||||
gs.compression = f[0];
|
||||
gs.data_offset = f[1];
|
||||
gs.data_size = f[2];
|
||||
gs.expanded_data_size = f[3];
|
||||
gs.internal_alignment = f[4];
|
||||
gs.first16bit = f[5];
|
||||
gs.first8bit = f[6];
|
||||
gs.pointer_fixup_off = f[7];
|
||||
gs.pointer_fixup_count= f[8];
|
||||
gs.mixed_fixup_off = f[9];
|
||||
gs.mixed_fixup_count = f[10];
|
||||
|
||||
if (size_t(gs.data_offset) > len || size_t(gs.data_size) > len - size_t(gs.data_offset))
|
||||
return fail("section", "section data out of range");
|
||||
|
||||
out.impl_->raw[i] = gs;
|
||||
LoadError sec_err;
|
||||
if (!out.impl_->decompress_section(gs, bytes + gs.data_offset,
|
||||
out.impl_->sections[i], &sec_err)) {
|
||||
if (err) {
|
||||
*err = sec_err;
|
||||
err->message += " (section " + std::to_string(i) + ")";
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
out.sections_.push_back(out.impl_->sections[i].info);
|
||||
}
|
||||
|
||||
// ── T1:fixup 索引(需要展开后的 section 数据 + 原始 section 头)──────
|
||||
out.impl_->build_fixups(bytes, len);
|
||||
out.impl_->root_type = { int32_t(rt_sec), rt_off };
|
||||
out.impl_->root_obj = { int32_t(ro_sec), ro_off };
|
||||
|
||||
// ── T3/T4:类型树 + FileInfo 汇总。失败必须传播;返回一个看似成功但
|
||||
// FileInfo 为空的对象会让上层把损坏文件误判成合法的空模型。
|
||||
LoadError fi_err;
|
||||
if (!out.impl_->build_fileinfo(out.info_, &fi_err)) {
|
||||
if (err) *err = fi_err;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
(void)header_size; (void)header_format;
|
||||
return out;
|
||||
}
|
||||
|
||||
std::optional<File> File::load_path(const std::string& path, LoadError* err) {
|
||||
FILE* f = std::fopen(path.c_str(), "rb");
|
||||
if (!f) { if (err) *err = {"open", "cannot open " + path}; return std::nullopt; }
|
||||
std::fseek(f, 0, SEEK_END);
|
||||
long n = std::ftell(f);
|
||||
std::fseek(f, 0, SEEK_SET);
|
||||
std::vector<uint8_t> buf(n > 0 ? size_t(n) : 0);
|
||||
if (!buf.empty()) {
|
||||
size_t rd = std::fread(buf.data(), 1, buf.size(), f);
|
||||
if (rd != buf.size()) { std::fclose(f); if (err) *err = {"open", "short read"}; return std::nullopt; }
|
||||
}
|
||||
std::fclose(f);
|
||||
return load(buf.data(), buf.size(), err);
|
||||
}
|
||||
|
||||
double File::bind_pose_self_check(int skeleton) const {
|
||||
return impl_ ? impl_->skeleton_self_check(skeleton) : 1e9;
|
||||
}
|
||||
|
||||
std::span<const uint8_t> File::section_bytes(size_t i) const {
|
||||
if (!impl_ || i >= impl_->sections.size()) return {};
|
||||
const auto& d = impl_->sections[i].data;
|
||||
return {d.data(), d.size()};
|
||||
}
|
||||
|
||||
} // namespace gr2
|
||||
@@ -0,0 +1,153 @@
|
||||
// M0 T4 —— FileInfo 汇总(驱动 T5 骨架 / T6 网格 / T7a 曲线分类)。
|
||||
// 见 docs/reference/steps/M0-gr2-reader.md
|
||||
// 规格:granny_file_info.cpp (FileInfoType) / granny_animation.cpp (AnimationType)
|
||||
#include "internal.h"
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace gr2 {
|
||||
|
||||
using detail::Ref;
|
||||
using detail::TypeMember;
|
||||
|
||||
bool File::Impl::build_fileinfo(FileInfo& out, LoadError* err) {
|
||||
if (!root_obj.valid() || !root_type.valid()) {
|
||||
if (err) *err = {"fileinfo", "no root object / type"};
|
||||
return false;
|
||||
}
|
||||
const auto& rms = parse_type(root_type);
|
||||
if (rms.empty()) {
|
||||
if (err) *err = {"fileinfo", "root type has no members"};
|
||||
return false;
|
||||
}
|
||||
|
||||
auto count_of = [&](const char* name) -> uint32_t {
|
||||
const TypeMember* m = find_member(rms, name);
|
||||
if (!m) return 0;
|
||||
int32_t c = rd_i32(member_slot(root_obj, *m));
|
||||
return (c > 0 && c < (1 << 24)) ? uint32_t(c) : 0;
|
||||
};
|
||||
|
||||
if (const TypeMember* m = find_member(rms, "FromFileName"))
|
||||
out.from_file_name = rd_str(member_slot(root_obj, *m));
|
||||
|
||||
out.texture_count = count_of("Textures");
|
||||
out.material_count = count_of("Materials");
|
||||
out.model_count = count_of("Models");
|
||||
|
||||
// ── 材质图:root Materials[] 先入表(索引 = 文件顺序),子材质 / 只经 mesh 可达的追加 ──
|
||||
mats_out = &out.materials;
|
||||
mat_refs.clear();
|
||||
if (const TypeMember* mMa = find_member(rms, "Materials")) {
|
||||
auto refs = read_array_of_refs(root_obj, *mMa);
|
||||
for (Ref r : refs) intern_material(r, mMa->ref_type);
|
||||
}
|
||||
|
||||
// ── 骨架(T5)────────────────────────────────────────────────────
|
||||
skels_cache.clear();
|
||||
skel_refs.clear();
|
||||
if (const TypeMember* mSk = find_member(rms, "Skeletons")) {
|
||||
auto refs = read_array_of_refs(root_obj, *mSk);
|
||||
for (Ref r : refs) {
|
||||
Skeleton sk;
|
||||
if (extract_skeleton(r, mSk->ref_type, sk)) {
|
||||
skels_cache.push_back(std::move(sk));
|
||||
skel_refs.push_back(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 模型 → 骨架关联:把 model.InitialPlacement 写进对应 skeleton ─────
|
||||
// (很多 Metin2 骨架把 model 空间偏移放在 InitialPlacement 而非 root 骨骼的
|
||||
// LocalTransform;不带上它,bind pose 自洽检查会对这些文件失败)
|
||||
if (const TypeMember* mMo = find_member(rms, "Models")) {
|
||||
auto refs = read_array_of_refs(root_obj, *mMo);
|
||||
const auto& moms = parse_type(mMo->ref_type);
|
||||
const TypeMember* mSkRef = find_member(moms, "Skeleton");
|
||||
const TypeMember* mIP = find_member(moms, "InitialPlacement");
|
||||
for (Ref r : refs) {
|
||||
if (!r.valid() || !mSkRef) continue;
|
||||
Ref sref = read_reference(r, *mSkRef);
|
||||
for (size_t k = 0; k < skel_refs.size(); ++k) {
|
||||
if (skel_refs[k].section == sref.section && skel_refs[k].offset == sref.offset) {
|
||||
if (mIP) {
|
||||
uint32_t fl = 0;
|
||||
transform_to_composite(member_slot(r, *mIP), skels_cache[k].initial_placement, fl);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out.skeletons = skels_cache;
|
||||
|
||||
// ── 网格(T6)────────────────────────────────────────────────────
|
||||
std::vector<Ref> mesh_refs; // 与 out.meshes 平行(model.MeshBindings 关联用)
|
||||
if (const TypeMember* mMe = find_member(rms, "Meshes")) {
|
||||
auto refs = read_array_of_refs(root_obj, *mMe);
|
||||
for (Ref r : refs) {
|
||||
Mesh me;
|
||||
bool ok = extract_mesh(r, mMe->ref_type, skels_cache, me);
|
||||
if (getenv("GR2_DEBUG"))
|
||||
fprintf(stderr, "[gr2] mesh ref (%d,%u) -> %s name=%s verts=%zu idx=%zu\n",
|
||||
r.section, r.offset, ok ? "OK" : "FAIL", me.name.c_str(),
|
||||
me.vertices.size(), me.indices.size());
|
||||
if (ok) { out.meshes.push_back(std::move(me)); mesh_refs.push_back(r); }
|
||||
}
|
||||
}
|
||||
|
||||
// ── 模型:granny_model{String Name; Reference Skeleton; Transform InitialPlacement;
|
||||
// RefToArray MeshBindings(model_mesh_binding{Reference Mesh}); VariantRef ExtendedData}
|
||||
if (const TypeMember* mMo = find_member(rms, "Models")) {
|
||||
auto refs = read_array_of_refs(root_obj, *mMo);
|
||||
const auto& moms = parse_type(mMo->ref_type);
|
||||
const TypeMember* mN = find_member(moms, "Name");
|
||||
const TypeMember* mSk = find_member(moms, "Skeleton");
|
||||
const TypeMember* mIP = find_member(moms, "InitialPlacement");
|
||||
const TypeMember* mMB = find_member(moms, "MeshBindings");
|
||||
auto same = [](Ref a, Ref b) { return a.section == b.section && a.offset == b.offset; };
|
||||
for (Ref r : refs) {
|
||||
if (!r.valid()) continue;
|
||||
Model mo;
|
||||
if (mN) mo.name = rd_str(member_slot(r, *mN));
|
||||
if (mSk) {
|
||||
Ref sref = read_reference(r, *mSk);
|
||||
for (size_t k = 0; k < skel_refs.size(); ++k)
|
||||
if (same(skel_refs[k], sref)) { mo.skeleton = int32_t(k); break; }
|
||||
}
|
||||
if (mIP) read_transform(member_slot(r, *mIP), mo.initial_placement);
|
||||
Ref bb; uint32_t bc = 0, bs = 0;
|
||||
if (mMB && read_ref_to_array(r, *mMB, bb, bc, bs) && bc && bs) {
|
||||
const auto& bms = parse_type(mMB->ref_type);
|
||||
const TypeMember* mMesh = find_member(bms, "Mesh");
|
||||
for (uint32_t i = 0; i < bc && i < 4096; ++i) {
|
||||
int32_t idx = -1;
|
||||
if (mMesh) {
|
||||
Ref me = read_reference({bb.section, bb.offset + i * bs}, *mMesh);
|
||||
for (size_t k = 0; k < mesh_refs.size(); ++k)
|
||||
if (same(mesh_refs[k], me)) { idx = int32_t(k); break; }
|
||||
}
|
||||
mo.mesh_bindings.push_back(idx);
|
||||
}
|
||||
}
|
||||
out.models.push_back(std::move(mo));
|
||||
}
|
||||
}
|
||||
|
||||
// ── 动画(T7a:名字 + 时长 + 每 track 的曲线子类型)─────────────────
|
||||
if (const TypeMember* mAn = find_member(rms, "Animations")) {
|
||||
auto refs = read_array_of_refs(root_obj, *mAn);
|
||||
const Skeleton* sk0 = skels_cache.empty() ? nullptr : &skels_cache[0];
|
||||
for (Ref r : refs) {
|
||||
if (!r.valid()) continue;
|
||||
Animation an;
|
||||
if (extract_animation(r, mAn->ref_type, sk0, an))
|
||||
out.animations.push_back(std::move(an));
|
||||
}
|
||||
}
|
||||
|
||||
mats_out = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace gr2
|
||||
@@ -0,0 +1,123 @@
|
||||
// libgr2 —— 材质视图(附加,不改动既有解析)。
|
||||
// root.Materials (ArrayOfReferences) → granny_material:
|
||||
// Name (String)
|
||||
// Texture (Reference → granny_texture{ FromFileName (String) })
|
||||
// Maps (RefToArray → granny_material_map{ Usage (String); Material (Reference → granny_material) })
|
||||
// diffuse 取 Usage 含 "diffuse" 的 map,退而取第一张贴图。
|
||||
#include "gr2/gr2.h"
|
||||
#include "internal.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
namespace gr2 {
|
||||
|
||||
using detail::TypeMember;
|
||||
using Ref = detail::Ref;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string lower(std::string s) {
|
||||
for (auto& c : s) c = char(std::tolower((unsigned char)c));
|
||||
return s;
|
||||
}
|
||||
|
||||
// helpers 用 `auto&` 模板参数,避免命名私有的 File::Impl(只有 dump_materials 是 friend)。
|
||||
|
||||
// 从一个 granny_texture 对象读 FromFileName。
|
||||
std::string read_texture_name(auto& im, Ref tex_obj, Ref tex_type) {
|
||||
if (!tex_obj.valid() || !tex_type.valid()) return {};
|
||||
const auto& ms = im.parse_type(tex_type);
|
||||
if (const TypeMember* m = im.find_member(ms, "FromFileName"))
|
||||
return im.rd_str(im.member_slot(tex_obj, *m));
|
||||
return {};
|
||||
}
|
||||
|
||||
struct WalkState {
|
||||
MaterialInfo* out;
|
||||
std::vector<std::string> encounter; // 按遇到顺序
|
||||
std::string diffuse; // 首个 Usage~=diffuse 的贴图
|
||||
};
|
||||
|
||||
// 递归遍历一个 granny_material,收集 FromFileName。
|
||||
void walk_material(auto& im, Ref mat_obj, Ref mat_type, int depth,
|
||||
WalkState& st, bool parent_is_diffuse) {
|
||||
if (depth > 4 || !mat_obj.valid() || !mat_type.valid()) return;
|
||||
const auto& ms = im.parse_type(mat_type);
|
||||
|
||||
if (st.out->name.empty()) {
|
||||
if (const TypeMember* mn = im.find_member(ms, "Name"))
|
||||
st.out->name = im.rd_str(im.member_slot(mat_obj, *mn));
|
||||
}
|
||||
|
||||
if (const TypeMember* mt = im.find_member(ms, "Texture")) {
|
||||
Ref tex = im.read_reference(mat_obj, *mt);
|
||||
std::string fn = read_texture_name(im, tex, mt->ref_type);
|
||||
if (!fn.empty()) {
|
||||
st.encounter.push_back(fn);
|
||||
if (parent_is_diffuse && st.diffuse.empty()) st.diffuse = fn;
|
||||
}
|
||||
}
|
||||
|
||||
if (const TypeMember* mm = im.find_member(ms, "Maps")) {
|
||||
Ref base;
|
||||
uint32_t count = 0, stride = 0;
|
||||
if (im.read_ref_to_array(mat_obj, *mm, base, count, stride) && count && stride) {
|
||||
const auto& map_ms = im.parse_type(mm->ref_type);
|
||||
const TypeMember* mu = im.find_member(map_ms, "Usage");
|
||||
const TypeMember* msub = im.find_member(map_ms, "Material");
|
||||
for (uint32_t i = 0; i < count && i < 64; ++i) {
|
||||
Ref map_obj{ base.section, base.offset + i * stride };
|
||||
bool is_diffuse = parent_is_diffuse;
|
||||
if (mu) {
|
||||
std::string u = lower(im.rd_str(im.member_slot(map_obj, *mu)));
|
||||
is_diffuse = u.find("diffuse") != std::string::npos || u.empty();
|
||||
}
|
||||
if (msub) {
|
||||
Ref sub = im.read_reference(map_obj, *msub);
|
||||
walk_material(im, sub, msub->ref_type, depth + 1, st, is_diffuse);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<MaterialInfo> dump_materials(const File& f) {
|
||||
std::vector<MaterialInfo> out;
|
||||
if (!f.impl_ || !f.impl_->root_type.valid() || !f.impl_->root_obj.valid()) return out;
|
||||
|
||||
File::Impl& im = *f.impl_;
|
||||
const auto& root_ms = im.parse_type(im.root_type);
|
||||
const TypeMember* mats = im.find_member(root_ms, "Materials");
|
||||
if (!mats) return out;
|
||||
|
||||
std::vector<Ref> mat_refs = im.read_array_of_refs(im.root_obj, *mats);
|
||||
out.reserve(mat_refs.size());
|
||||
for (Ref mr : mat_refs) {
|
||||
MaterialInfo mi;
|
||||
WalkState st{ &mi, {}, {} };
|
||||
walk_material(im, mr, mats->ref_type, 0, st, /*parent_is_diffuse=*/true);
|
||||
|
||||
mi.diffuse_texture = !st.diffuse.empty() ? st.diffuse
|
||||
: (st.encounter.empty() ? std::string{} : st.encounter.front());
|
||||
mi.map_count = (int)st.encounter.size();
|
||||
mi.all_textures = st.encounter;
|
||||
std::sort(mi.all_textures.begin(), mi.all_textures.end());
|
||||
mi.all_textures.erase(std::unique(mi.all_textures.begin(), mi.all_textures.end()),
|
||||
mi.all_textures.end());
|
||||
|
||||
// 渲染态推断(EterGrnLib Material.cpp 的名字/map 逻辑)
|
||||
std::string ln = lower(mi.name);
|
||||
mi.alpha_blend = ln.rfind("blend", 0) == 0 && mi.map_count > 1;
|
||||
for (const char* kw : {"2side", "twoside", "two_side", "double", "leaf", "tree",
|
||||
"ivy", "fence", "grass", "branch", "flag"})
|
||||
if (ln.find(kw) != std::string::npos) { mi.two_sided = true; break; }
|
||||
|
||||
out.push_back(std::move(mi));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace gr2
|
||||
@@ -0,0 +1,379 @@
|
||||
// M0 T6 —— 顶点 / 索引 / 三角组 / BoneBindings。见 docs/reference/steps/M0-gr2-reader.md
|
||||
// 规格:granny_mesh.cpp (MeshType/BoneBindingType) / granny_vertex_data.cpp (VertexDataType
|
||||
// + 各顶点格式) / granny_tri_topology.cpp (TriTopologyType/TriMaterialGroupType)
|
||||
// 顶点格式是自描述的(Vertices 是 ReferenceToVariantArray,带 data_type_definition*),
|
||||
// 按成员名(Position/Normal/TextureCoordinates0/1/BoneWeights/BoneIndices)取分量,不硬编码布局。
|
||||
#include "internal.h"
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#define MDBG(...) do { if (getenv("GR2_DEBUG")) fprintf(stderr, "[mesh] " __VA_ARGS__); } while (0)
|
||||
|
||||
namespace gr2 {
|
||||
|
||||
using detail::Ref;
|
||||
using detail::TypeMember;
|
||||
|
||||
namespace {
|
||||
|
||||
// member_type → (元素是浮点?, 元素字节数, 是否归一化整型)
|
||||
struct Elem { bool is_float; int bytes; bool normalized; bool is_signed; };
|
||||
Elem elem_of(int mt) {
|
||||
switch (mt) {
|
||||
case detail::MT_Real32: return {true, 4, false, true};
|
||||
case detail::MT_Real16: return {false, 2, false, true}; // 半精度,少见,按需扩展
|
||||
case detail::MT_Int8: return {false, 1, false, true};
|
||||
case detail::MT_UInt8: return {false, 1, false, false};
|
||||
case detail::MT_BinormInt8: return {false, 1, true, true};
|
||||
case detail::MT_NormUInt8: return {false, 1, true, false};
|
||||
case detail::MT_Int16: return {false, 2, false, true};
|
||||
case detail::MT_UInt16: return {false, 2, false, false};
|
||||
case detail::MT_BinormInt16: return {false, 2, true, true};
|
||||
case detail::MT_NormUInt16: return {false, 2, true, false};
|
||||
case detail::MT_Int32: return {false, 4, false, true};
|
||||
case detail::MT_UInt32: return {false, 4, false, false};
|
||||
default: return {false, 0, false, false};
|
||||
}
|
||||
}
|
||||
|
||||
float read_scalar(const uint8_t* p, const Elem& e) {
|
||||
switch (e.bytes) {
|
||||
case 4:
|
||||
if (e.is_float) { float v; std::memcpy(&v, p, 4); return v; }
|
||||
if (e.is_signed) { int32_t v; std::memcpy(&v, p, 4); return float(v); }
|
||||
{ uint32_t v; std::memcpy(&v, p, 4); return float(v); }
|
||||
case 2: {
|
||||
if (e.is_signed) { int16_t v; std::memcpy(&v, p, 2); return e.normalized ? v / 32767.0f : float(v); }
|
||||
uint16_t v; std::memcpy(&v, p, 2); return e.normalized ? v / 65535.0f : float(v);
|
||||
}
|
||||
case 1: {
|
||||
if (e.is_signed) { int8_t v = (int8_t)p[0]; return e.normalized ? v / 127.0f : float(v); }
|
||||
return e.normalized ? p[0] / 255.0f : float(p[0]);
|
||||
}
|
||||
default: return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// mesh: {String Name; Reference PrimaryVertexData; RefToArray MorphTargets;
|
||||
// Reference PrimaryTopology; RefToArray MaterialBindings; RefToArray BoneBindings; ...}
|
||||
bool File::Impl::extract_mesh(Ref mesh_obj, Ref mesh_type,
|
||||
const std::vector<Skeleton>& skels, Mesh& out) {
|
||||
if (!mesh_obj.valid() || !mesh_type.valid()) return false;
|
||||
const auto& mms = parse_type(mesh_type);
|
||||
if (getenv("GR2_DEBUG")) {
|
||||
fprintf(stderr, "[mesh] mesh_type=(%d,%u) members=%zu:\n",
|
||||
mesh_type.section, mesh_type.offset, mms.size());
|
||||
for (auto& m : mms)
|
||||
fprintf(stderr, "[mesh] %-20s type=%d aw=%d off=%u size=%u reftype=(%d,%u)\n",
|
||||
m.name.c_str(), m.type, m.array_width, m.offset, m.size,
|
||||
m.ref_type.section, m.ref_type.offset);
|
||||
}
|
||||
|
||||
if (const TypeMember* mName = find_member(mms, "Name"))
|
||||
out.name = rd_str(member_slot(mesh_obj, *mName));
|
||||
|
||||
// ── 顶点 ──────────────────────────────────────────────────────────
|
||||
const TypeMember* mVD = find_member(mms, "PrimaryVertexData");
|
||||
if (!mVD) { MDBG("no PrimaryVertexData member\n"); return false; }
|
||||
MDBG("PrimaryVertexData m.offset=%u ref_type=(%d,%u)\n",
|
||||
mVD->offset, mVD->ref_type.section, mVD->ref_type.offset);
|
||||
Ref vd = read_reference(mesh_obj, *mVD);
|
||||
if (!vd.valid()) { MDBG("PrimaryVertexData ref -> NULL (slot %d,%u)\n",
|
||||
member_slot(mesh_obj, *mVD).section, member_slot(mesh_obj, *mVD).offset); return false; }
|
||||
|
||||
const auto& vdms = parse_type(mVD->ref_type);
|
||||
const TypeMember* mVerts = find_member(vdms, "Vertices");
|
||||
if (!mVerts) { MDBG("no Vertices member (vdms=%zu)\n", vdms.size()); return false; }
|
||||
// ReferenceToVariantArray on-disk (32-bit): {def* Type; int32 Count; void* Ptr}
|
||||
// (granny_data_type_definition.cpp: Ptr32 + int32 + Ptr32)
|
||||
Ref vslot = member_slot(vd, *mVerts);
|
||||
Ref vtype = follow({vslot.section, vslot.offset + 0});
|
||||
int32_t vcount = rd_i32({vslot.section, vslot.offset + 4});
|
||||
Ref vdata = follow({vslot.section, vslot.offset + 8});
|
||||
MDBG("Vertices count=%d vtype=(%d,%u) vdata=(%d,%u)\n",
|
||||
vcount, vtype.section, vtype.offset, vdata.section, vdata.offset);
|
||||
if (vcount <= 0 || vcount > (1 << 24) || !vtype.valid() || !vdata.valid()) return false;
|
||||
|
||||
const auto& vfmt = parse_type(vtype);
|
||||
uint32_t stride = 0;
|
||||
for (const auto& m : vfmt) stride += m.size;
|
||||
MDBG("vfmt members=%zu stride=%u\n", vfmt.size(), stride);
|
||||
if (stride == 0) return false;
|
||||
|
||||
struct Comp { const TypeMember* m; Elem e; };
|
||||
auto comp = [&](const char* n) -> Comp {
|
||||
const TypeMember* m = find_member(vfmt, n);
|
||||
if (!m) return {nullptr, {}};
|
||||
return {m, elem_of(m->type)};
|
||||
};
|
||||
Comp cPos = comp("Position");
|
||||
Comp cNrm = comp("Normal");
|
||||
Comp cUv0 = comp("TextureCoordinates0");
|
||||
Comp cUv1 = comp("TextureCoordinates1");
|
||||
Comp cBW = comp("BoneWeights");
|
||||
Comp cBI = comp("BoneIndices");
|
||||
|
||||
const bool skinned = (cBW.m && cBI.m);
|
||||
out.rigid = !skinned;
|
||||
if (skinned) {
|
||||
out.source_bone_weight_slots = uint32_t(cBW.m->array_width > 0 ? cBW.m->array_width : 1);
|
||||
out.source_bone_index_slots = (cBI.e.bytes == 4 && cBI.m->array_width <= 1)
|
||||
? 4u
|
||||
: uint32_t(cBI.m->array_width > 0 ? cBI.m->array_width : 1);
|
||||
}
|
||||
if (skinned && cUv1.m) out.kind = VertexKind::PNT3322_Skinned;
|
||||
else if (skinned) out.kind = VertexKind::PNT332_Skinned;
|
||||
else if (cUv1.m) out.kind = VertexKind::PNT3322;
|
||||
else if (cPos.m && cNrm.m) out.kind = VertexKind::PNT332;
|
||||
else out.kind = VertexKind::Unknown;
|
||||
|
||||
if ((uint64_t)vdata.offset + (uint64_t)vcount * stride > sections[vdata.section].data.size())
|
||||
return false;
|
||||
|
||||
out.vertex_stride = stride;
|
||||
for (const auto& m : vfmt)
|
||||
out.vertex_members.push_back({m.name, m.type, m.array_width, m.offset});
|
||||
{
|
||||
const uint8_t* vb = sections[vdata.section].data.data() + vdata.offset;
|
||||
out.raw_vertices.assign(vb, vb + size_t(vcount) * stride);
|
||||
}
|
||||
out.vertices.resize(vcount);
|
||||
for (int32_t i = 0; i < vcount; ++i) {
|
||||
const uint8_t* base = sections[vdata.section].data.data() + vdata.offset + size_t(i) * stride;
|
||||
Vertex& v = out.vertices[i];
|
||||
auto rd = [&](const Comp& c, float* dst, int n) {
|
||||
if (!c.m || !c.e.bytes) return;
|
||||
int aw = c.m->array_width <= 0 ? 1 : c.m->array_width;
|
||||
n = n < aw ? n : aw;
|
||||
for (int k = 0; k < n; ++k)
|
||||
dst[k] = read_scalar(base + c.m->offset + size_t(k) * c.e.bytes, c.e);
|
||||
};
|
||||
rd(cPos, v.pos, 3);
|
||||
rd(cNrm, v.normal, 3);
|
||||
rd(cUv0, v.uv0, 2);
|
||||
rd(cUv1, v.uv1, 2);
|
||||
if (skinned) {
|
||||
float w[4] = {0,0,0,0}, bi[4] = {0,0,0,0};
|
||||
rd(cBW, w, 4);
|
||||
// BoneIndices 可能是 UInt8[4] 或 UInt32[N](PWN* 系列是单 uint32 packed 4×u8)
|
||||
if (cBI.e.bytes == 1) {
|
||||
rd(cBI, bi, 4);
|
||||
} else if (cBI.e.bytes == 4 && cBI.e.is_float == false) {
|
||||
uint32_t packed;
|
||||
std::memcpy(&packed, base + cBI.m->offset, 4);
|
||||
for (int k = 0; k < 4; ++k) bi[k] = float((packed >> (k * 8)) & 0xff);
|
||||
}
|
||||
for (int k = 0; k < 4; ++k) {
|
||||
v.bone_index[k] = uint8_t(std::lround(bi[k]));
|
||||
float wn = cBW.e.normalized ? w[k] : w[k]; // 已在 read_scalar 归一
|
||||
v.bone_weight[k] = uint8_t(std::lround(std::fmin(std::fmax(wn, 0.0f), 1.0f) * 255.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 索引 + 三角组 ────────────────────────────────────────────────
|
||||
const TypeMember* mTopo = find_member(mms, "PrimaryTopology");
|
||||
if (mTopo) {
|
||||
Ref tt = read_reference(mesh_obj, *mTopo);
|
||||
if (tt.valid()) {
|
||||
const auto& tms = parse_type(mTopo->ref_type);
|
||||
if (const TypeMember* mG = find_member(tms, "Groups")) {
|
||||
Ref gb; uint32_t gc = 0, gs = 0;
|
||||
if (read_ref_to_array(tt, *mG, gb, gc, gs) && gc) {
|
||||
const auto& gmT = parse_type(mG->ref_type);
|
||||
const TypeMember* mMI = find_member(gmT, "MaterialIndex");
|
||||
const TypeMember* mTF = find_member(gmT, "TriFirst");
|
||||
const TypeMember* mTC = find_member(gmT, "TriCount");
|
||||
for (uint32_t i = 0; i < gc; ++i) {
|
||||
Ref g = {gb.section, gb.offset + i * gs};
|
||||
TriGroup tg{};
|
||||
if (mMI) tg.material_index = rd_i32(member_slot(g, *mMI));
|
||||
if (mTF) tg.tri_first = rd_i32(member_slot(g, *mTF));
|
||||
if (mTC) tg.tri_count = rd_i32(member_slot(g, *mTC));
|
||||
out.tri_groups.push_back(tg);
|
||||
}
|
||||
}
|
||||
}
|
||||
auto load_idx = [&](const char* name, int elem_bytes) -> bool {
|
||||
const TypeMember* mI = find_member(tms, name);
|
||||
if (!mI) return false;
|
||||
Ref ib; uint32_t ic = 0, is = 0;
|
||||
if (!read_ref_to_array(tt, *mI, ib, ic, is) || ic == 0) return false;
|
||||
const uint8_t* p = at(ib, size_t(ic) * elem_bytes);
|
||||
if (!p) return false;
|
||||
out.indices.resize(ic);
|
||||
for (uint32_t i = 0; i < ic; ++i) {
|
||||
if (elem_bytes == 2) { uint16_t v; std::memcpy(&v, p + i*2, 2); out.indices[i] = v; }
|
||||
else { uint32_t v; std::memcpy(&v, p + i*4, 4); out.indices[i] = v; }
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if (!load_idx("Indices", 4)) load_idx("Indices16", 2);
|
||||
}
|
||||
}
|
||||
|
||||
// ── MaterialBindings:与 tri_group.material_index 平行的贴图名表 ───
|
||||
// granny_mesh.MaterialBindings = RefToArray of {Reference Material}。
|
||||
if (const TypeMember* mMB = find_member(mms, "MaterialBindings")) {
|
||||
Ref mb; uint32_t mc = 0, ms = 0;
|
||||
if (read_ref_to_array(mesh_obj, *mMB, mb, mc, ms) && mc) {
|
||||
const auto& bT = parse_type(mMB->ref_type);
|
||||
const TypeMember* mMat = find_member(bT, "Material");
|
||||
out.material_textures.reserve(mc);
|
||||
for (uint32_t i = 0; i < mc; ++i) {
|
||||
Ref e = {mb.section, mb.offset + i * ms};
|
||||
std::string tn;
|
||||
int32_t mi = -1;
|
||||
if (mMat) {
|
||||
Ref mat = read_reference(e, *mMat);
|
||||
tn = material_texture_name(mat, mMat->ref_type, 0);
|
||||
mi = intern_material(mat, mMat->ref_type);
|
||||
}
|
||||
out.material_textures.push_back(std::move(tn));
|
||||
out.material_bindings.push_back(mi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── BoneBindings:mesh 局部骨骼槽 → skeleton 骨骼索引 ─────────────
|
||||
// 文件可能带多个 skeleton(本体 + 武器挂点等)。对每个 skeleton 试解析,
|
||||
// 取"悬空最少"的那个。
|
||||
if (const TypeMember* mBB = find_member(mms, "BoneBindings")) {
|
||||
Ref bb; uint32_t bc = 0, bs = 0;
|
||||
if (read_ref_to_array(mesh_obj, *mBB, bb, bc, bs) && bc) {
|
||||
const auto& bbT = parse_type(mBB->ref_type);
|
||||
const TypeMember* mBN = find_member(bbT, "BoneName");
|
||||
const TypeMember* mMin = find_member(bbT, "OBBMin");
|
||||
const TypeMember* mMax = find_member(bbT, "OBBMax");
|
||||
out.bone_bounds.resize(bc);
|
||||
std::vector<std::string> names(bc);
|
||||
for (uint32_t i = 0; i < bc; ++i) {
|
||||
Ref e = {bb.section, bb.offset + i * bs};
|
||||
names[i] = mBN ? rd_str(member_slot(e, *mBN)) : std::string();
|
||||
auto &bounds = out.bone_bounds[i];
|
||||
if (mMin && mMax && mMin->type == detail::MT_Real32 && mMax->type == detail::MT_Real32 && mMin->array_width == 3 && mMax->array_width == 3) {
|
||||
Ref lo = member_slot(e, *mMin), hi = member_slot(e, *mMax);
|
||||
bounds.valid = true;
|
||||
for (int axis = 0; axis < 3; ++axis) {
|
||||
bounds.min[axis] = rd_f32({lo.section, lo.offset + uint32_t(axis * 4)});
|
||||
bounds.max[axis] = rd_f32({hi.section, hi.offset + uint32_t(axis * 4)});
|
||||
bounds.valid &= std::isfinite(bounds.min[axis]) && std::isfinite(bounds.max[axis]) && bounds.min[axis] <= bounds.max[axis];
|
||||
}
|
||||
}
|
||||
}
|
||||
std::vector<int32_t> best(bc, -1);
|
||||
int best_dangling = int(bc) + 1;
|
||||
for (const auto& sk : skels) {
|
||||
std::vector<int32_t> cur(bc, -1);
|
||||
int dangling = 0;
|
||||
for (uint32_t i = 0; i < bc; ++i) {
|
||||
for (size_t k = 0; k < sk.bones.size(); ++k)
|
||||
if (sk.bones[k].name == names[i]) { cur[i] = int32_t(k); break; }
|
||||
if (cur[i] < 0) ++dangling;
|
||||
}
|
||||
if (dangling < best_dangling) { best_dangling = dangling; best = cur; }
|
||||
if (dangling == 0) break;
|
||||
}
|
||||
out.bone_bindings = std::move(best);
|
||||
out.bone_binding_names = std::move(names);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// granny_material {String Name; int MapCount; material_map* Maps; texture* Texture; ...}
|
||||
// granny_material_map {String Usage; material* Material}
|
||||
// granny_texture {String FromFileName; ...}
|
||||
std::string File::Impl::material_texture_name(Ref mat_obj, Ref mat_type, int depth) {
|
||||
if (!mat_obj.valid() || !mat_type.valid() || depth > 8) return {};
|
||||
const auto& mms = parse_type(mat_type);
|
||||
|
||||
// 1) 直接挂 texture
|
||||
if (const TypeMember* mTex = find_member(mms, "Texture")) {
|
||||
Ref tex = read_reference(mat_obj, *mTex);
|
||||
if (tex.valid()) {
|
||||
const auto& tms = parse_type(mTex->ref_type);
|
||||
if (const TypeMember* mFN = find_member(tms, "FromFileName")) {
|
||||
std::string s = rd_str(member_slot(tex, *mFN));
|
||||
if (!s.empty()) return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2) 递归 Maps[].Map(复合材质:granny_material_map = {String Usage; material* Map})
|
||||
if (const TypeMember* mMaps = find_member(mms, "Maps")) {
|
||||
Ref b; uint32_t c = 0, s = 0;
|
||||
if (read_ref_to_array(mat_obj, *mMaps, b, c, s) && c) {
|
||||
const auto& mapT = parse_type(mMaps->ref_type);
|
||||
const TypeMember* mSub = find_member(mapT, "Map");
|
||||
if (!mSub) mSub = find_member(mapT, "Material");
|
||||
for (uint32_t i = 0; i < c && mSub; ++i) {
|
||||
Ref e = {b.section, b.offset + i * s};
|
||||
Ref sub = read_reference(e, *mSub);
|
||||
std::string r = material_texture_name(sub, mSub->ref_type, depth + 1);
|
||||
if (!r.empty()) return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// granny_material {String Name; RefToArray Maps(material_map{String Usage; Reference Material});
|
||||
// Reference Texture; VariantRef ExtendedData}
|
||||
int32_t File::Impl::intern_material(Ref mat_obj, Ref mat_type, int depth) {
|
||||
if (!mats_out || !mat_obj.valid() || !mat_type.valid() || depth > 8) return -1;
|
||||
for (size_t k = 0; k < mat_refs.size(); ++k)
|
||||
if (mat_refs[k].section == mat_obj.section && mat_refs[k].offset == mat_obj.offset)
|
||||
return int32_t(k);
|
||||
|
||||
const int32_t idx = int32_t(mats_out->size());
|
||||
mats_out->emplace_back();
|
||||
mat_refs.push_back(mat_obj);
|
||||
|
||||
const auto& mms = parse_type(mat_type);
|
||||
Material mat;
|
||||
if (const TypeMember* m = find_member(mms, "Name")) mat.name = rd_str(member_slot(mat_obj, *m));
|
||||
mat.diffuse_texture = material_texture_name(mat_obj, mat_type, 0);
|
||||
if (const TypeMember* mTex = find_member(mms, "Texture")) {
|
||||
Ref tex = read_reference(mat_obj, *mTex);
|
||||
if (tex.valid())
|
||||
if (const TypeMember* mFN = find_member(parse_type(mTex->ref_type), "FromFileName"))
|
||||
mat.texture = rd_str(member_slot(tex, *mFN));
|
||||
}
|
||||
if (const TypeMember* mE = find_member(mms, "ExtendedData"))
|
||||
mat.has_two_sided = extended_i32(member_slot(mat_obj, *mE), "Two-sided", mat.two_sided);
|
||||
if (const TypeMember* mMaps = find_member(mms, "Maps")) {
|
||||
Ref b; uint32_t c = 0, s = 0;
|
||||
if (read_ref_to_array(mat_obj, *mMaps, b, c, s) && c && s) {
|
||||
const auto& mapT = parse_type(mMaps->ref_type);
|
||||
const TypeMember* mU = find_member(mapT, "Usage");
|
||||
const TypeMember* mSub = find_member(mapT, "Material");
|
||||
if (!mSub) mSub = find_member(mapT, "Map");
|
||||
for (uint32_t i = 0; i < c && i < 64; ++i) {
|
||||
Ref e = {b.section, b.offset + i * s};
|
||||
MaterialMap mm;
|
||||
if (mU) mm.usage = rd_str(member_slot(e, *mU));
|
||||
if (mSub) mm.material = intern_material(read_reference(e, *mSub), mSub->ref_type, depth + 1);
|
||||
mat.maps.push_back(std::move(mm));
|
||||
}
|
||||
}
|
||||
}
|
||||
(*mats_out)[idx] = std::move(mat);
|
||||
return idx;
|
||||
}
|
||||
|
||||
// VariantRef 在 32 位文件里 = {ptr Type; ptr Object}。
|
||||
bool File::Impl::extended_i32(Ref slot, const char* name, int32_t& out) {
|
||||
Ref type = follow(slot);
|
||||
Ref obj = follow({slot.section, slot.offset + 4});
|
||||
if (!type.valid() || !obj.valid()) return false;
|
||||
const TypeMember* m = find_member(parse_type(type), name);
|
||||
if (!m || (m->type != detail::MT_Int32 && m->type != detail::MT_UInt32)) return false;
|
||||
out = rd_i32(member_slot(obj, *m));
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace gr2
|
||||
@@ -0,0 +1,238 @@
|
||||
// M0 T5 —— 骨架提取 + bind pose 自洽检查。见 docs/reference/steps/M0-gr2-reader.md
|
||||
// 规格:granny_transform.cpp BuildCompositeTransform4x4 / granny_math.cpp
|
||||
// MatrixEqualsQuaternion3x3 + MatrixMultiply3x3 /
|
||||
// granny_matrix_operations.cpp ColumnMatrixMultiply4x3Impl /
|
||||
// granny_world_pose.cpp BuildWorldPoseNoCompositeLOD + granny_bone_operations.cpp
|
||||
// BuildFullWorldPoseOnly_Generic
|
||||
#include "internal.h"
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
namespace gr2 {
|
||||
|
||||
using detail::Ref;
|
||||
using detail::TypeMember;
|
||||
|
||||
namespace {
|
||||
|
||||
// 行主序 4x4:R = A * B(ColumnMatrixMultiply4x3Impl 语义,平移在第 3 行 elem 12..14)
|
||||
Mat4 mul4x3(const Mat4& A, const Mat4& B) {
|
||||
Mat4 R{};
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
for (int k = 0; k < 3; ++k)
|
||||
R[i * 4 + k] = A[i * 4 + 0] * B[0 * 4 + k]
|
||||
+ A[i * 4 + 1] * B[1 * 4 + k]
|
||||
+ A[i * 4 + 2] * B[2 * 4 + k];
|
||||
R[i * 4 + 3] = 0.0f;
|
||||
}
|
||||
for (int k = 0; k < 3; ++k)
|
||||
R[12 + k] = A[12] * B[0 * 4 + k]
|
||||
+ A[13] * B[1 * 4 + k]
|
||||
+ A[14] * B[2 * 4 + k]
|
||||
+ B[12 + k];
|
||||
R[15] = 1.0f;
|
||||
return R;
|
||||
}
|
||||
|
||||
// granny_math.cpp MatrixEqualsQuaternion3x3(行主序 3x3)
|
||||
void quat_to_m3(const float q[4], float d[9]) {
|
||||
const float xx = q[0] * q[0], yy = q[1] * q[1], zz = q[2] * q[2];
|
||||
const float xy = q[0] * q[1], xz = q[0] * q[2], yz = q[1] * q[2];
|
||||
const float wx = q[3] * q[0], wy = q[3] * q[1], wz = q[3] * q[2];
|
||||
d[0] = 1 - 2 * (yy + zz); d[1] = 2 * (xy - wz); d[2] = 2 * (xz + wy);
|
||||
d[3] = 2 * (xy + wz); d[4] = 1 - 2 * (xx + zz); d[5] = 2 * (yz - wx);
|
||||
d[6] = 2 * (xz - wy); d[7] = 2 * (yz + wx); d[8] = 1 - 2 * (xx + yy);
|
||||
}
|
||||
|
||||
void m3_mul(const float a[9], const float b[9], float r[9]) {
|
||||
for (int i = 0; i < 3; ++i)
|
||||
for (int j = 0; j < 3; ++j)
|
||||
r[i * 3 + j] = a[i * 3 + 0] * b[0 * 3 + j]
|
||||
+ a[i * 3 + 1] * b[1 * 3 + j]
|
||||
+ a[i * 3 + 2] * b[2 * 3 + j];
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// granny_transform (68B: u32 Flags; f32 Position[3]; f32 Orientation[4]; f32 ScaleShear[3][3])
|
||||
void File::Impl::read_transform(Ref tf, Transform& out) const {
|
||||
out = Transform{};
|
||||
const uint8_t* p = at(tf, 68);
|
||||
if (!p) return;
|
||||
std::memcpy(&out.flags, p, 4);
|
||||
std::memcpy(out.position, p + 4, 12);
|
||||
std::memcpy(out.orientation, p + 16, 16);
|
||||
std::memcpy(out.scale_shear, p + 32, 36);
|
||||
}
|
||||
|
||||
// → 行主序 4x4,上 3x3 = transpose(U3),平移在 elem 12..14。BuildCompositeTransform4x4 语义。
|
||||
Mat4 compose(const Transform& t) {
|
||||
float q3[9];
|
||||
quat_to_m3(t.orientation, q3);
|
||||
float u3[9];
|
||||
if (t.flags & 0x4 /* HasScaleShear */) m3_mul(q3, t.scale_shear, u3);
|
||||
else std::memcpy(u3, q3, sizeof u3);
|
||||
|
||||
Mat4 out{};
|
||||
// C 的上 3x3 = transpose(U3)
|
||||
out[0] = u3[0]; out[1] = u3[3]; out[2] = u3[6]; out[3] = 0;
|
||||
out[4] = u3[1]; out[5] = u3[4]; out[6] = u3[7]; out[7] = 0;
|
||||
out[8] = u3[2]; out[9] = u3[5]; out[10] = u3[8]; out[11] = 0;
|
||||
out[12] = t.position[0]; out[13] = t.position[1]; out[14] = t.position[2]; out[15] = 1;
|
||||
return out;
|
||||
}
|
||||
|
||||
void File::Impl::transform_to_composite(Ref tf, Mat4& out, uint32_t& flags) const {
|
||||
Transform t;
|
||||
read_transform(tf, t);
|
||||
if (!at(tf, 68)) {
|
||||
out = Mat4{1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1};
|
||||
flags = 0;
|
||||
return;
|
||||
}
|
||||
out = compose(t);
|
||||
flags = t.flags;
|
||||
}
|
||||
|
||||
// skeleton: {String Name; RefToArray Bones(bone[]); int32 LODType; VariantRef ExtendedData}
|
||||
// bone: {String Name; int32 ParentIndex; Transform LocalTransform;
|
||||
// Real32[16] InverseWorldTransform; Real32 LODError; VariantRef ExtendedData}
|
||||
// 全程按类型树取偏移,不硬编码。
|
||||
bool File::Impl::extract_skeleton(Ref skel_obj, Ref skel_type, Skeleton& out) {
|
||||
out.bones.clear();
|
||||
if (!skel_obj.valid() || !skel_type.valid()) return false;
|
||||
|
||||
const auto& sms = parse_type(skel_type);
|
||||
const TypeMember* mBones = find_member(sms, "Bones");
|
||||
if (!mBones) return false;
|
||||
|
||||
Ref bones_base;
|
||||
uint32_t bone_count = 0, stride = 0;
|
||||
if (!read_ref_to_array(skel_obj, *mBones, bones_base, bone_count, stride)) return false;
|
||||
if (bone_count == 0) return false;
|
||||
if (bone_count > 8192 || stride == 0) return false;
|
||||
|
||||
const auto& bms = parse_type(mBones->ref_type);
|
||||
const TypeMember* mName = find_member(bms, "Name");
|
||||
const TypeMember* mParent = find_member(bms, "ParentIndex");
|
||||
const TypeMember* mLocal = find_member(bms, "LocalTransform");
|
||||
if (!mLocal) mLocal = find_member(bms, "Transform");
|
||||
const TypeMember* mInvW = find_member(bms, "InverseWorldTransform");
|
||||
if (!mInvW) mInvW = find_member(bms, "InverseWorld4x4");
|
||||
if (!mLocal || !mInvW) return false;
|
||||
|
||||
out.bones.resize(bone_count);
|
||||
for (uint32_t i = 0; i < bone_count; ++i) {
|
||||
Ref b = { bones_base.section, bones_base.offset + i * stride };
|
||||
Bone& bo = out.bones[i];
|
||||
if (mName) bo.name = rd_str(member_slot(b, *mName));
|
||||
if (mParent) bo.parent = rd_i32(member_slot(b, *mParent));
|
||||
read_transform(member_slot(b, *mLocal), bo.local);
|
||||
bo.local_transform = compose(bo.local);
|
||||
bo.lt_flags = bo.local.flags;
|
||||
const uint8_t* iw = at(member_slot(b, *mInvW), 64);
|
||||
if (iw) for (int k = 0; k < 16; ++k) std::memcpy(&bo.inverse_world[k], iw + k * 4, 4);
|
||||
else bo.inverse_world = Mat4{};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── M2:姿势采样(自由函数 = 主 primitive;File::* 是便捷封装)──────────
|
||||
namespace {
|
||||
void accumulate_world(const Skeleton& sk, const std::vector<Mat4>& local,
|
||||
std::vector<Mat4>& world, std::vector<Mat4>& skin,
|
||||
const Mat4& root_offset) {
|
||||
const size_t n = sk.bones.size();
|
||||
const Mat4 I{1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1};
|
||||
world.assign(n, I);
|
||||
skin.assign(n, I);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
int32_t par = sk.bones[i].parent;
|
||||
const Mat4& parent_w = (par < 0 || (size_t)par >= i) ? root_offset : world[par];
|
||||
world[i] = mul4x3((i < local.size()) ? local[i] : sk.bones[i].local_transform, parent_w);
|
||||
skin[i] = mul4x3(sk.bones[i].inverse_world, world[i]);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void bind_pose(const Skeleton& sk, std::vector<Mat4>& world, std::vector<Mat4>& skin) {
|
||||
std::vector<Mat4> local(sk.bones.size());
|
||||
for (size_t i = 0; i < sk.bones.size(); ++i) local[i] = sk.bones[i].local_transform;
|
||||
accumulate_world(sk, local, world, skin, sk.initial_placement);
|
||||
}
|
||||
|
||||
void sample_pose(const Skeleton& sk, const Animation& an, float t,
|
||||
std::vector<Mat4>& world, std::vector<Mat4>& skin,
|
||||
const Mat4* root_offset) {
|
||||
std::vector<Mat4> track_local;
|
||||
an.sample_local(t, track_local);
|
||||
|
||||
std::vector<Mat4> local(sk.bones.size());
|
||||
for (size_t i = 0; i < sk.bones.size(); ++i) local[i] = sk.bones[i].local_transform; // 默认 = bind
|
||||
|
||||
// track 按骨骼名匹配(bone_index 若已在 anim 提取时对某个 skeleton 映射过,可能不是这个 sk)
|
||||
for (size_t ti = 0; ti < an.tracks.size() && ti < track_local.size(); ++ti) {
|
||||
const BoneTrack& tr = an.tracks[ti];
|
||||
int bi = -1;
|
||||
for (size_t k = 0; k < sk.bones.size(); ++k)
|
||||
if (sk.bones[k].name == tr.bone_name) { bi = int(k); break; }
|
||||
if (bi >= 0) local[bi] = track_local[ti];
|
||||
}
|
||||
accumulate_world(sk, local, world, skin, root_offset ? *root_offset : sk.initial_placement);
|
||||
}
|
||||
|
||||
bool File::bind_pose(int skeleton, std::vector<Mat4>& world, std::vector<Mat4>& skin) const {
|
||||
if (!impl_ || skeleton < 0 || (size_t)skeleton >= impl_->skels_cache.size()) return false;
|
||||
gr2::bind_pose(impl_->skels_cache[skeleton], world, skin);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::sample_pose(int anim, float t, int skeleton,
|
||||
std::vector<Mat4>& world, std::vector<Mat4>& skin) const {
|
||||
if (!impl_ || skeleton < 0 || (size_t)skeleton >= impl_->skels_cache.size()) return false;
|
||||
if (anim < 0 || (size_t)anim >= info_.animations.size())
|
||||
return bind_pose(skeleton, world, skin);
|
||||
gr2::sample_pose(impl_->skels_cache[skeleton], info_.animations[anim], t, world, skin);
|
||||
return true;
|
||||
}
|
||||
|
||||
double File::Impl::skeleton_self_check(int skeleton) const {
|
||||
if (skeleton < 0 || (size_t)skeleton >= skels_cache.size()) return 1e9;
|
||||
const auto& sk = skels_cache[skeleton];
|
||||
const size_t n = sk.bones.size();
|
||||
if (n == 0) return 1e9;
|
||||
|
||||
std::vector<Mat4> world(n);
|
||||
double dev = 0.0;
|
||||
size_t worst = 0;
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
int32_t par = sk.bones[i].parent;
|
||||
if (par < 0 || (size_t)par >= i) {
|
||||
// 根:world = local * InitialPlacement(BuildWorldPose 的 Offset4x4)
|
||||
world[i] = mul4x3(sk.bones[i].local_transform, sk.initial_placement);
|
||||
} else {
|
||||
world[i] = mul4x3(sk.bones[i].local_transform, world[par]);
|
||||
}
|
||||
// E = InverseWorld * world ≈ I
|
||||
Mat4 E = mul4x3(sk.bones[i].inverse_world, world[i]);
|
||||
double bd = 0.0;
|
||||
for (int k = 0; k < 16; ++k) {
|
||||
float want = (k % 5 == 0) ? 1.0f : 0.0f;
|
||||
bd = std::max(bd, (double)std::fabs(E[k] - want));
|
||||
}
|
||||
if (bd > dev) { dev = bd; worst = i; }
|
||||
}
|
||||
if (getenv("GR2_DEBUG")) {
|
||||
fprintf(stderr, "[skel] self-check dev=%.5f worst bone %zu '%s' parent=%d\n",
|
||||
dev, worst, sk.bones[worst].name.c_str(), sk.bones[worst].parent);
|
||||
const Mat4& E = mul4x3(sk.bones[worst].inverse_world, world[worst]);
|
||||
fprintf(stderr, "[skel] E[worst]:");
|
||||
for (float x : E) fprintf(stderr, " %.3f", x);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
return dev;
|
||||
}
|
||||
|
||||
} // namespace gr2
|
||||
@@ -0,0 +1,204 @@
|
||||
// M0 T1(fixup 索引)+ T3(自描述类型树遍历)。见 docs/reference/steps/M0-gr2-reader.md
|
||||
// 规格:granny_file_format.h(fixup)+ granny_data_type_definition.h/.cpp(成员类型 + stride)
|
||||
#include "internal.h"
|
||||
#include <cstring>
|
||||
|
||||
namespace gr2 {
|
||||
|
||||
using detail::Ref;
|
||||
using detail::TypeMember;
|
||||
using detail::kTypeDefEntrySize;
|
||||
|
||||
// ── member_type → 文件里的单元字节数(32 位指针)──────────────────────
|
||||
// 序数见 internal.h MemberType。InlineMember 需递归(这里在 member_unit_size 处理)。
|
||||
static const int8_t kUnit32[detail::MT_OnePastLast] = {
|
||||
/* End */ 0,
|
||||
/* Inline */ 0, /* 特判 */
|
||||
/* Reference */ 4,
|
||||
/* RefToArray */ 8, /* int32 + ptr */
|
||||
/* ArrayOfRefs */ 8,
|
||||
/* VariantRef */ 8, /* ptr + ptr */
|
||||
/* Removed */ 0,
|
||||
/* RefToVariantArr*/ 12,
|
||||
/* String */ 4,
|
||||
/* Transform */ 68, /* u32 flags + f32[3] + f32[4] + f32[3][3] */
|
||||
/* Real32 */ 4,
|
||||
/* Int8 */ 1,
|
||||
/* UInt8 */ 1,
|
||||
/* BinormInt8 */ 1,
|
||||
/* NormUInt8 */ 1,
|
||||
/* Int16 */ 2,
|
||||
/* UInt16 */ 2,
|
||||
/* BinormInt16 */ 2,
|
||||
/* NormUInt16 */ 2,
|
||||
/* Int32 */ 4,
|
||||
/* UInt32 */ 4,
|
||||
/* Real16 */ 2,
|
||||
/* EmptyRef */ 4,
|
||||
};
|
||||
|
||||
// ── section 内指针 + 边界检查 ────────────────────────────────────────
|
||||
const uint8_t* File::Impl::at(Ref r, size_t need) const {
|
||||
if (r.section < 0 || (size_t)r.section >= sections.size()) return nullptr;
|
||||
const auto& d = sections[r.section].data;
|
||||
if ((size_t)r.offset + need > d.size()) return nullptr;
|
||||
return d.data() + r.offset;
|
||||
}
|
||||
|
||||
uint32_t File::Impl::rd_u32(Ref r) const {
|
||||
const uint8_t* p = at(r, 4);
|
||||
if (!p) return 0;
|
||||
return uint32_t(p[0]) | (uint32_t(p[1]) << 8) | (uint32_t(p[2]) << 16) | (uint32_t(p[3]) << 24);
|
||||
}
|
||||
float File::Impl::rd_f32(Ref r) const {
|
||||
uint32_t u = rd_u32(r);
|
||||
float f;
|
||||
std::memcpy(&f, &u, 4);
|
||||
return f;
|
||||
}
|
||||
|
||||
// 解一个"指针槽":查 fixup 表;查不到 => NULL(返回 invalid Ref)
|
||||
Ref File::Impl::follow(Ref slot) const {
|
||||
if (slot.section < 0) return {};
|
||||
auto it = fixups.find((uint64_t(uint32_t(slot.section)) << 32) | slot.offset);
|
||||
if (it == fixups.end()) return {};
|
||||
return it->second;
|
||||
}
|
||||
|
||||
std::string File::Impl::rd_str(Ref slot) const {
|
||||
Ref s = follow(slot);
|
||||
const uint8_t* p = at(s, 1);
|
||||
if (!p) return {};
|
||||
const auto& d = sections[s.section].data;
|
||||
size_t maxn = d.size() - s.offset;
|
||||
size_t n = 0;
|
||||
while (n < maxn && p[n]) ++n;
|
||||
return std::string((const char*)p, n);
|
||||
}
|
||||
|
||||
// ── T1:建 fixup 索引 ────────────────────────────────────────────────
|
||||
// grn_pointer_fixup 数组在文件里(PointerFixupArrayOffset 从文件起点算,Count 项,每 12B)。
|
||||
// 每项 { u32 FromOffset(本 section 展开数据内偏移); u32 DestSection; u32 DestOffset }。
|
||||
void File::Impl::build_fixups(const uint8_t* file, size_t file_len) {
|
||||
auto le32 = [&](size_t off) -> uint32_t {
|
||||
if (off + 4 > file_len) return 0;
|
||||
return uint32_t(file[off]) | (uint32_t(file[off + 1]) << 8) |
|
||||
(uint32_t(file[off + 2]) << 16) | (uint32_t(file[off + 3]) << 24);
|
||||
};
|
||||
fixups.clear();
|
||||
for (size_t si = 0; si < raw.size(); ++si) {
|
||||
const GrnSection& gs = raw[si];
|
||||
size_t base = gs.pointer_fixup_off;
|
||||
for (uint32_t k = 0; k < gs.pointer_fixup_count; ++k) {
|
||||
size_t p = base + size_t(k) * 12;
|
||||
if (p + 12 > file_len) break;
|
||||
uint32_t from = le32(p + 0);
|
||||
uint32_t dsec = le32(p + 4);
|
||||
uint32_t doff = le32(p + 8);
|
||||
if (dsec >= sections.size()) continue;
|
||||
fixups[(uint64_t(uint32_t(si)) << 32) | from] =
|
||||
Ref{int32_t(dsec), doff};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── T3:走 data_type_definition 数组 ─────────────────────────────────
|
||||
uint32_t File::Impl::member_unit_size(const TypeMember& m) {
|
||||
if (m.type == detail::MT_Inline) return object_size(m.ref_type);
|
||||
if (m.type <= 0 || m.type >= detail::MT_OnePastLast) return 0;
|
||||
return (uint32_t)kUnit32[m.type];
|
||||
}
|
||||
|
||||
uint32_t File::Impl::object_size(Ref type_ref) {
|
||||
const auto& ms = parse_type(type_ref);
|
||||
uint32_t sz = 0;
|
||||
for (const auto& m : ms) sz += m.size;
|
||||
return sz;
|
||||
}
|
||||
|
||||
const std::vector<TypeMember>& File::Impl::parse_type(Ref type_ref) {
|
||||
uint64_t key = (uint64_t(uint32_t(type_ref.section)) << 32) | type_ref.offset;
|
||||
auto it = type_cache.find(key);
|
||||
if (it != type_cache.end()) return it->second;
|
||||
|
||||
std::vector<TypeMember> out;
|
||||
// 先占位(防递归 Inline 死循环)
|
||||
auto& slot = type_cache[key];
|
||||
|
||||
Ref cur = type_ref;
|
||||
uint32_t inst_off = 0;
|
||||
for (int guard = 0; guard < 4096; ++guard) {
|
||||
const uint8_t* e = at(cur, kTypeDefEntrySize);
|
||||
if (!e) break;
|
||||
int32_t type = (int32_t)rd_u32(cur);
|
||||
if (type == detail::MT_End) break;
|
||||
|
||||
TypeMember m;
|
||||
m.type = type;
|
||||
// Name @ +4(指针槽)
|
||||
m.name = rd_str({cur.section, cur.offset + 4});
|
||||
// ReferenceType @ +8(指针槽 → data_type_definition 数组)
|
||||
m.ref_type = follow({cur.section, cur.offset + 8});
|
||||
// ArrayWidth @ +12
|
||||
m.array_width = (int32_t)rd_u32({cur.section, cur.offset + 12});
|
||||
|
||||
uint32_t width = (m.array_width <= 0) ? 1u : (uint32_t)m.array_width;
|
||||
m.offset = inst_off;
|
||||
m.size = member_unit_size(m) * width;
|
||||
inst_off += m.size;
|
||||
|
||||
out.push_back(std::move(m));
|
||||
cur.offset += kTypeDefEntrySize;
|
||||
}
|
||||
slot = std::move(out);
|
||||
return slot;
|
||||
}
|
||||
|
||||
const TypeMember* File::Impl::find_member(const std::vector<TypeMember>& ms, const char* name) const {
|
||||
for (const auto& m : ms) if (m.name == name) return &m;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ArrayOfReferences 成员:{int32 count; ptr → count 个指针槽}
|
||||
std::vector<Ref> File::Impl::read_array_of_refs(Ref obj, const TypeMember& m) const {
|
||||
std::vector<Ref> out;
|
||||
Ref slot = member_slot(obj, m);
|
||||
int32_t count = rd_i32(slot);
|
||||
if (count <= 0 || count > (1 << 24)) return out;
|
||||
Ref arr = follow({slot.section, slot.offset + 4}); // 指针槽在 count 之后
|
||||
if (!arr.valid()) return out;
|
||||
out.reserve(count);
|
||||
for (int32_t i = 0; i < count; ++i) {
|
||||
Ref elem = follow({arr.section, arr.offset + uint32_t(i) * 4});
|
||||
out.push_back(elem); // 可能 invalid(NULL 项)
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// ReferenceToArray 成员:{int32 count; ptr → count 个连续对象}
|
||||
bool File::Impl::read_ref_to_array(Ref obj, const TypeMember& m,
|
||||
Ref& base, uint32_t& count, uint32_t& stride) {
|
||||
Ref slot = member_slot(obj, m);
|
||||
int32_t c = rd_i32(slot);
|
||||
base = {};
|
||||
count = 0;
|
||||
stride = m.ref_type.valid() ? object_size(m.ref_type) : 0;
|
||||
if (c <= 0 || c > (1 << 24)) return c == 0; // 空数组不算错
|
||||
base = follow({slot.section, slot.offset + 4});
|
||||
if (!base.valid()) return false;
|
||||
count = uint32_t(c);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── gr2dump 用:打印 root object 的类型成员(不硬编码 struct 布局)─────
|
||||
std::vector<MemberDump> dump_root_members(const File& f) {
|
||||
std::vector<MemberDump> out;
|
||||
if (!f.impl_ || !f.impl_->root_type.valid()) return out;
|
||||
const auto& ms = f.impl_->parse_type(f.impl_->root_type);
|
||||
out.reserve(ms.size());
|
||||
for (const auto& m : ms)
|
||||
out.push_back({m.name, m.type, m.array_width});
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace gr2
|
||||
@@ -0,0 +1,144 @@
|
||||
// libgr2 内部。SDK struct 镜像从 granny_file_format.h / granny_data_type_definition.h 抄。
|
||||
#pragma once
|
||||
#include "gr2/gr2.h"
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace gr2 {
|
||||
|
||||
namespace detail {
|
||||
|
||||
// ── granny_file_format.h 镜像 ──────────────────────────────────────────
|
||||
#pragma pack(push, 1)
|
||||
struct GrnFileMagic { // grn_file_magic_value
|
||||
uint32_t magic[4];
|
||||
uint32_t header_size;
|
||||
uint32_t header_format;
|
||||
uint32_t reserved[2];
|
||||
};
|
||||
struct GrnReference { uint32_t section; uint32_t offset; };
|
||||
struct GrnPointerFixup { uint32_t from_offset; GrnReference to; }; // 12B
|
||||
struct GrnSection { // grn_section —— 44B
|
||||
uint32_t compression;
|
||||
uint32_t data_offset, data_size;
|
||||
uint32_t expanded_data_size;
|
||||
uint32_t internal_alignment;
|
||||
uint32_t first16bit, first8bit;
|
||||
uint32_t pointer_fixup_off, pointer_fixup_count;
|
||||
uint32_t mixed_fixup_off, mixed_fixup_count;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
struct Section {
|
||||
std::vector<uint8_t> data; // 展开后
|
||||
SectionInfo info;
|
||||
};
|
||||
|
||||
// section 内的位置。不物化指针(文件是 32 位指针,本进程 64 位),
|
||||
// 全程用 (section, offset) + fixup 表按需解引用。
|
||||
struct Ref {
|
||||
int32_t section = -1;
|
||||
uint32_t offset = 0;
|
||||
bool valid() const { return section >= 0; }
|
||||
};
|
||||
|
||||
// ── granny_data_type_definition.h:member_type 枚举(序数不可变)────────
|
||||
enum MemberType {
|
||||
MT_End = 0, MT_Inline, MT_Reference, MT_RefToArray, MT_ArrayOfRefs,
|
||||
MT_VariantRef, MT_Removed, MT_RefToVariantArray, MT_String, MT_Transform,
|
||||
MT_Real32, MT_Int8, MT_UInt8, MT_BinormInt8, MT_NormUInt8,
|
||||
MT_Int16, MT_UInt16, MT_BinormInt16, MT_NormUInt16,
|
||||
MT_Int32, MT_UInt32, MT_Real16, MT_EmptyRef, MT_OnePastLast
|
||||
};
|
||||
|
||||
// data_type_definition 在文件里的字节数(32 位指针):
|
||||
// Type(4) Name(ptr4) ReferenceType(ptr4) ArrayWidth(4) Extra[3](12) Ignored(ptr4)
|
||||
constexpr uint32_t kTypeDefEntrySize = 32;
|
||||
|
||||
struct TypeMember {
|
||||
std::string name;
|
||||
int32_t type = MT_End;
|
||||
int32_t array_width = 0; // 0 => 1
|
||||
Ref ref_type; // Inline/Reference/... 的元素类型
|
||||
uint32_t offset = 0; // 在对象实例里的字节偏移(打包,无对齐 pad)
|
||||
uint32_t size = 0; // 本 member 的总字节数
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
struct File::Impl {
|
||||
std::vector<detail::Section> sections; // 展开后数据 + info
|
||||
std::vector<detail::GrnSection> raw; // 原始 section 头(fixup 用)
|
||||
using Ref = detail::Ref;
|
||||
using TypeMember = detail::TypeMember;
|
||||
using GrnSection = detail::GrnSection;
|
||||
|
||||
// (section<<32 | from_offset) -> 目标 Ref
|
||||
std::unordered_map<uint64_t, Ref> fixups;
|
||||
|
||||
Ref root_obj; // grn_file_header.RootObject
|
||||
Ref root_type; // grn_file_header.RootObjectTypeDefinition
|
||||
bool typetree_ready = false;
|
||||
|
||||
// 类型解析缓存:typedef 的 Ref -> 成员列表
|
||||
std::unordered_map<uint64_t, std::vector<TypeMember>> type_cache;
|
||||
|
||||
// ── T2 / T1 ──
|
||||
bool decompress_section(const GrnSection&, const uint8_t* src, detail::Section& out, LoadError*);
|
||||
void build_fixups(const uint8_t* file, size_t file_len); // T1
|
||||
|
||||
// ── T3 ──
|
||||
const uint8_t* at(Ref r, size_t need = 0) const; // section 内指针 + 边界检查
|
||||
Ref follow(Ref slot) const; // 解一个"指针槽"
|
||||
const std::vector<TypeMember>& parse_type(Ref type_ref); // 走 data_type_definition 数组
|
||||
uint32_t object_size(Ref type_ref);
|
||||
uint32_t member_unit_size(const TypeMember&);
|
||||
const TypeMember* find_member(const std::vector<TypeMember>& ms, const char* name) const;
|
||||
|
||||
// 便捷读取
|
||||
uint32_t rd_u32(Ref r) const;
|
||||
int32_t rd_i32(Ref r) const { return (int32_t)rd_u32(r); }
|
||||
float rd_f32(Ref r) const;
|
||||
std::string rd_str(Ref slot) const; // 解引用一个 String 槽
|
||||
|
||||
// 成员访问(obj = 对象实例的 Ref,m = 其类型的某个成员)
|
||||
Ref member_slot(Ref obj, const TypeMember& m) const { return {obj.section, obj.offset + m.offset}; }
|
||||
Ref read_reference(Ref obj, const TypeMember& m) const { return follow(member_slot(obj, m)); }
|
||||
// ArrayOfReferences: {int32 count; ptr → [count] 个指针槽 → 各自的对象}
|
||||
std::vector<Ref> read_array_of_refs(Ref obj, const TypeMember& m) const;
|
||||
// ReferenceToArray: {int32 count; ptr → [count] 个连续对象(elem_type)}
|
||||
bool read_ref_to_array(Ref obj, const TypeMember& m, Ref& base, uint32_t& count, uint32_t& stride);
|
||||
|
||||
// ── T4/T5/T6/T7a ──
|
||||
bool build_fileinfo(FileInfo& out, LoadError*);
|
||||
bool extract_skeleton(Ref skel_obj, Ref skel_type, Skeleton& out);
|
||||
bool extract_mesh(Ref mesh_obj, Ref mesh_type, const std::vector<Skeleton>& skels, Mesh& out);
|
||||
// granny_material 链:直接 .Texture.FromFileName,或递归 .Maps[].Material。返回 dds 文件名(原样)。
|
||||
std::string material_texture_name(Ref mat_obj, Ref mat_type, int depth);
|
||||
bool extract_animation(Ref anim_obj, Ref anim_type, const Skeleton* skel, Animation& out);
|
||||
// 曲线子类型分类(T7a)。curve_obj 指向 inline 曲线对象本体,curve_type 是它的类型。
|
||||
std::string classify_curve_obj(Ref curve_obj, Ref curve_type);
|
||||
void read_old_curve(Ref curve_obj, Ref curve_type, Curve& out); // T7b
|
||||
double skeleton_self_check(int skeleton) const;
|
||||
|
||||
// granny_transform (68B) → 行主序 4x4(BuildCompositeTransform4x4 语义)
|
||||
void transform_to_composite(Ref tf, Mat4& out, uint32_t& flags) const;
|
||||
void read_transform(Ref tf, Transform& out) const; // 68B → SRT 分量
|
||||
|
||||
// granny_material 图:按对象 Ref 去重,缺的追加到 mats_out(子材质 / 只经 mesh 可达的材质)。
|
||||
// 返回 FileInfo::materials 索引;无效 / 递归太深 = -1。
|
||||
int32_t intern_material(Ref mat_obj, Ref mat_type, int depth = 0);
|
||||
std::vector<Material>* mats_out = nullptr;
|
||||
std::vector<Ref> mat_refs; // 与 *mats_out 平行
|
||||
// ExtendedData(VariantRef = {type ptr; object ptr})里取名为 name 的 Int32。
|
||||
bool extended_i32(Ref variant_slot, const char* name, int32_t& out);
|
||||
|
||||
std::vector<Skeleton> skels_cache; // self-check 复用
|
||||
std::vector<Ref> skel_refs; // 与 skels_cache 平行:各 skeleton 对象的 Ref(model 关联用)
|
||||
|
||||
mutable FileInfo* info_cache = nullptr; // 指向 File::info_
|
||||
};
|
||||
|
||||
} // namespace gr2
|
||||
@@ -0,0 +1,611 @@
|
||||
/* Granny "Oodle1" section decompressor (M0 T2).
|
||||
*
|
||||
* Ported (decode path only) from the leaked Granny 2.9.12 SDK:
|
||||
* granny_oodle1_compression.cpp + radlz.c + radarith.c + arithbit.c
|
||||
* Used as algorithm spec; RAD's build machinery / compressor are NOT included.
|
||||
* See docs/reference/steps/M0-gr2-reader.md T2.
|
||||
*/
|
||||
#include "oodle1.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef uint8_t U8;
|
||||
typedef uint16_t U16;
|
||||
typedef uint32_t U32;
|
||||
typedef int32_t S32;
|
||||
typedef uintptr_t UINTa;
|
||||
|
||||
#define radassert(x) ((void)0)
|
||||
|
||||
/* ================================================================= */
|
||||
/* arithbit.c — byte-aligned range decoder */
|
||||
/* ================================================================= */
|
||||
|
||||
typedef struct ARITHBITS {
|
||||
U32 low;
|
||||
U32 range;
|
||||
U8 *ptr;
|
||||
U32 underflow; /* temp between Get()/Remove() to skip re-divide */
|
||||
U8 *start; /* low bit used as scratch */
|
||||
} ARITHBITS;
|
||||
|
||||
static void ArithBitsGetStart(ARITHBITS *ab, const void *ptr) {
|
||||
U32 buf;
|
||||
ab->ptr = (U8 *)ptr + 1;
|
||||
buf = ((const U8 *)ptr)[0];
|
||||
ab->start = (U8 *)((UINTa)ptr + (buf & 1));
|
||||
ab->low = buf >> 1;
|
||||
ab->range = 1u << 7;
|
||||
}
|
||||
|
||||
static void ArithBitsDecRenorm(ARITHBITS *ab) {
|
||||
U32 range = ab->range;
|
||||
if (range <= 0x800000u) {
|
||||
U32 low = ab->low;
|
||||
U32 buf = (U32)((UINTa)ab->start & 1u);
|
||||
U8 *ptr = ab->ptr;
|
||||
do {
|
||||
low = (low + low + buf) << 7;
|
||||
buf = *ptr++;
|
||||
low |= (buf >> 1);
|
||||
buf &= 1u;
|
||||
range <<= 8;
|
||||
} while (range <= 0x800000u);
|
||||
ab->low = low;
|
||||
ab->start = (U8 *)(((UINTa)ab->start & ~(UINTa)1) + buf);
|
||||
ab->range = range;
|
||||
ab->ptr = ptr;
|
||||
}
|
||||
}
|
||||
|
||||
static U32 ArithBitsGet(ARITHBITS *ab, U32 scale) {
|
||||
U32 tmp;
|
||||
ArithBitsDecRenorm(ab);
|
||||
ab->underflow = ab->range / scale;
|
||||
tmp = ab->low / ab->underflow;
|
||||
return (tmp >= scale) ? (scale - 1) : tmp;
|
||||
}
|
||||
|
||||
static void ArithBitsRemove(ARITHBITS *ab, U32 start, U32 range, U32 scale) {
|
||||
U32 tmp = ab->underflow * start;
|
||||
ab->low -= tmp;
|
||||
if ((start + range) < scale) ab->range = ab->underflow * range;
|
||||
else ab->range -= tmp;
|
||||
}
|
||||
|
||||
static U32 ArithBitsGetValue(ARITHBITS *ab, U32 scale) {
|
||||
U32 tmp, div, start;
|
||||
ArithBitsDecRenorm(ab);
|
||||
div = ab->range / scale;
|
||||
start = ab->low / div;
|
||||
if (start >= scale) start = scale - 1;
|
||||
tmp = div * start;
|
||||
ab->low -= tmp;
|
||||
if ((start + 1) < scale) ab->range = div;
|
||||
else ab->range -= tmp;
|
||||
return start;
|
||||
}
|
||||
|
||||
static U32 ArithBitsGetBits(ARITHBITS *ab, U32 bits, U32 scale) {
|
||||
U32 tmp;
|
||||
ArithBitsDecRenorm(ab);
|
||||
ab->underflow = ab->range >> bits;
|
||||
tmp = ab->low / ab->underflow;
|
||||
return (tmp >= scale) ? (scale - 1) : tmp;
|
||||
}
|
||||
|
||||
static U32 ArithBitsGetBitsValue(ARITHBITS *ab, U32 bits, U32 scale) {
|
||||
U32 tmp, div, start;
|
||||
ArithBitsDecRenorm(ab);
|
||||
div = ab->range >> bits;
|
||||
start = ab->low / div;
|
||||
if (start >= scale) start = scale - 1;
|
||||
tmp = div * start;
|
||||
ab->low -= tmp;
|
||||
if ((start + 1) < scale) ab->range = div;
|
||||
else ab->range -= tmp;
|
||||
return start;
|
||||
}
|
||||
|
||||
/* ================================================================= */
|
||||
/* radarith.c — adaptive arithmetic modeller (decode path) */
|
||||
/* ================================================================= */
|
||||
|
||||
#define NORM_BITS 14
|
||||
#define NORM_COUNT (1 << NORM_BITS)
|
||||
#define ADJ_SUMS (NORM_COUNT << 1)
|
||||
#define OVERFLOW_COUNT ((NORM_BITS / 2) - 1) /* 6 */
|
||||
#define COUNTTYPE U16
|
||||
|
||||
#define COUNTS_SIZE(v) (sizeof(COUNTTYPE) * (((v) + 1 + 1 + 3) & ~3u))
|
||||
#define VALUES_SIZE(v) COUNTS_SIZE(v)
|
||||
|
||||
typedef struct ARITHDATA {
|
||||
COUNTTYPE singles_tot;
|
||||
COUNTTYPE update_tot;
|
||||
COUNTTYPE update_max;
|
||||
COUNTTYPE update_range;
|
||||
COUNTTYPE rescale_tot;
|
||||
COUNTTYPE singles_length;
|
||||
COUNTTYPE summed_length;
|
||||
COUNTTYPE unique_count;
|
||||
COUNTTYPE *values;
|
||||
COUNTTYPE *single_counts;
|
||||
U32 table_walks[NORM_BITS];
|
||||
COUNTTYPE summed_counts[OVERFLOW_COUNT]; /* deliberately overflows into the gap before single_counts */
|
||||
} ARITHDATA;
|
||||
typedef ARITHDATA *ARITH;
|
||||
|
||||
#define Arith_was_escaped(val) (((UINTa)(val)) > 65536u)
|
||||
#define Arith_set_decompressed_symbol(val, v) (*((U16 *)(val)) = (U16)(v))
|
||||
|
||||
static U32 Arith_decompress_alloc_size(U32 unique_values) {
|
||||
return (U32)(sizeof(ARITHDATA) + COUNTS_SIZE(unique_values)
|
||||
+ COUNTS_SIZE(unique_values) + VALUES_SIZE(unique_values));
|
||||
}
|
||||
|
||||
static ARITH Arith_open(void *ptr, void *compress_temp_buf, U32 max_value, U32 unique_values) {
|
||||
ARITH a = (ARITH)ptr;
|
||||
U32 u;
|
||||
(void)compress_temp_buf; /* always NULL on the decode path */
|
||||
if (!a) return a;
|
||||
memset(a, 0, Arith_decompress_alloc_size(unique_values));
|
||||
a->single_counts = (COUNTTYPE *)((char *)a + (sizeof(ARITHDATA) + COUNTS_SIZE(unique_values)));
|
||||
a->values = (COUNTTYPE *)((char *)a->single_counts + COUNTS_SIZE(unique_values));
|
||||
a->unique_count = (COUNTTYPE)unique_values;
|
||||
a->singles_tot = 4;
|
||||
a->summed_counts[0] = ADJ_SUMS;
|
||||
a->summed_counts[1] = NORM_COUNT + ADJ_SUMS;
|
||||
a->summed_counts[2] = NORM_COUNT + ADJ_SUMS;
|
||||
a->summed_counts[3] = NORM_COUNT + ADJ_SUMS;
|
||||
a->summed_counts[4] = NORM_COUNT + ADJ_SUMS;
|
||||
a->summed_counts[5] = NORM_COUNT + ADJ_SUMS;
|
||||
a->single_counts[0] = 4;
|
||||
a->update_tot = 8;
|
||||
a->update_range = 4;
|
||||
u = max_value * 32;
|
||||
if (u < 256) u = 256;
|
||||
else if (u > 15160) u = 15160;
|
||||
a->rescale_tot = (COUNTTYPE)u;
|
||||
u = max_value * 2;
|
||||
if (u < 128) u = 128;
|
||||
else if (u >= (((U32)a->rescale_tot >> 1) - 32)) u = (U32)(((U32)a->rescale_tot >> 1) - 32);
|
||||
a->update_max = (COUNTTYPE)u;
|
||||
return a;
|
||||
}
|
||||
|
||||
static void rescale_decompress(ARITH a) {
|
||||
unsigned long i;
|
||||
U32 max = 0, pos = (U32)-1;
|
||||
a->single_counts[0] >>= 1;
|
||||
a->singles_tot = a->single_counts[0];
|
||||
for (i = 1; i <= a->singles_length; i++) {
|
||||
while (a->single_counts[i] <= 1) {
|
||||
if (i < a->singles_length) {
|
||||
a->single_counts[i] = a->single_counts[a->singles_length];
|
||||
a->single_counts[a->singles_length] = 0;
|
||||
a->values[i] = a->values[a->singles_length];
|
||||
--a->singles_length;
|
||||
} else {
|
||||
a->single_counts[i] = 0;
|
||||
--a->singles_length;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
a->single_counts[i] >>= 1;
|
||||
a->singles_tot = (COUNTTYPE)(a->singles_tot + a->single_counts[i]);
|
||||
if (a->single_counts[i] > max) { max = a->single_counts[i]; pos = (U32)i; }
|
||||
}
|
||||
done:
|
||||
if (max && a->singles_length) {
|
||||
U32 j = a->singles_length;
|
||||
if (pos != j) {
|
||||
U32 t;
|
||||
t = a->single_counts[j]; a->single_counts[j] = a->single_counts[pos]; a->single_counts[pos] = (COUNTTYPE)t;
|
||||
t = a->values[j]; a->values[j] = a->values[pos]; a->values[pos] = (COUNTTYPE)t;
|
||||
}
|
||||
}
|
||||
if ((a->singles_length != a->unique_count) && (a->single_counts[0] == 0)) {
|
||||
++a->single_counts[0];
|
||||
++a->singles_tot;
|
||||
}
|
||||
}
|
||||
|
||||
static void update_counts(ARITH a) {
|
||||
U32 i, tot, adj;
|
||||
adj = (NORM_COUNT * 8) / a->singles_tot;
|
||||
tot = ((((U32)a->single_counts[0]) * adj) >> 3) + ADJ_SUMS;
|
||||
a->summed_counts[0] = ADJ_SUMS;
|
||||
i = 1;
|
||||
for (;;) {
|
||||
a->summed_counts[i] = (COUNTTYPE)tot;
|
||||
if (i > a->singles_length) break;
|
||||
tot += (((U32)a->single_counts[i]) * adj) >> 3;
|
||||
++i;
|
||||
}
|
||||
tot = (U32)a->update_range << 1;
|
||||
if (tot > a->update_max) {
|
||||
a->update_tot = (COUNTTYPE)(a->singles_tot + a->update_max);
|
||||
} else {
|
||||
a->update_range = (COUNTTYPE)tot;
|
||||
a->update_tot = (COUNTTYPE)(a->singles_tot + tot);
|
||||
}
|
||||
a->summed_length = a->singles_length;
|
||||
a->summed_counts[a->summed_length + 1] = NORM_COUNT + ADJ_SUMS;
|
||||
}
|
||||
|
||||
static void build_walk_table(U32 *wtable, U32 v) {
|
||||
U32 num = 0, *m = wtable;
|
||||
++v;
|
||||
do {
|
||||
v = (v + 1) >> 1;
|
||||
++num;
|
||||
*m++ = v;
|
||||
} while (v > 4);
|
||||
num -= 1;
|
||||
if (num) {
|
||||
memmove(wtable + NORM_BITS - 1 - num + 1, wtable + 1, num * sizeof(wtable[0]));
|
||||
}
|
||||
wtable[1] = wtable[0];
|
||||
wtable[0] = num;
|
||||
}
|
||||
|
||||
static UINTa Arith_decompress(ARITH a, ARITHBITS *ab) {
|
||||
U32 offset;
|
||||
S32 index;
|
||||
|
||||
if (a->singles_tot >= a->update_tot) {
|
||||
if (a->update_tot >= a->rescale_tot) rescale_decompress(a);
|
||||
update_counts(a);
|
||||
a->summed_counts[a->summed_length + 2] = NORM_COUNT + ADJ_SUMS;
|
||||
a->summed_counts[a->summed_length + 3] = NORM_COUNT + ADJ_SUMS;
|
||||
a->summed_counts[a->summed_length + 4] = NORM_COUNT + ADJ_SUMS;
|
||||
a->summed_counts[a->summed_length + 5] = NORM_COUNT + ADJ_SUMS;
|
||||
a->summed_counts[a->summed_length + 6] = NORM_COUNT + ADJ_SUMS;
|
||||
build_walk_table(a->table_walks, a->summed_length);
|
||||
}
|
||||
|
||||
offset = ArithBitsGetBits(ab, NORM_BITS, NORM_COUNT) + ADJ_SUMS;
|
||||
|
||||
{
|
||||
index = (S32)a->table_walks[1];
|
||||
#define check_level(lev) \
|
||||
if (a->summed_counts[index] > (COUNTTYPE)offset) \
|
||||
index -= (S32)a->table_walks[NORM_BITS - 1 - (lev)]; \
|
||||
else \
|
||||
index += (S32)a->table_walks[NORM_BITS - 1 - (lev)];
|
||||
switch (a->table_walks[0]) {
|
||||
case 11: check_level(10) /* fallthrough */
|
||||
case 10: check_level(9) /* fallthrough */
|
||||
case 9: check_level(8) /* fallthrough */
|
||||
case 8: check_level(7) /* fallthrough */
|
||||
case 7: check_level(6) /* fallthrough */
|
||||
case 6: check_level(5) /* fallthrough */
|
||||
case 5: check_level(4) /* fallthrough */
|
||||
case 4: check_level(3) /* fallthrough */
|
||||
case 3: check_level(2) /* fallthrough */
|
||||
case 2: check_level(1) /* fallthrough */
|
||||
case 1: check_level(0) /* fallthrough */
|
||||
case 0: break;
|
||||
default: break;
|
||||
}
|
||||
#undef check_level
|
||||
|
||||
if (a->summed_counts[index] > (COUNTTYPE)offset) index -= 2;
|
||||
else index += 2;
|
||||
|
||||
if (a->summed_counts[index] > (COUNTTYPE)offset) {
|
||||
if (a->summed_counts[index - 1] > (COUNTTYPE)offset) index -= 2;
|
||||
else --index;
|
||||
} else {
|
||||
if (a->summed_counts[index + 1] <= (COUNTTYPE)offset) ++index;
|
||||
}
|
||||
}
|
||||
|
||||
ArithBitsRemove(ab, a->summed_counts[index] - ADJ_SUMS,
|
||||
a->summed_counts[index + 1] - a->summed_counts[index], NORM_COUNT);
|
||||
|
||||
++a->single_counts[index];
|
||||
++a->singles_tot;
|
||||
|
||||
if (index <= 0) {
|
||||
if (a->singles_length == a->summed_length) {
|
||||
new_index:
|
||||
index = ++a->singles_length;
|
||||
a->single_counts[index] += 2;
|
||||
a->singles_tot += 2;
|
||||
if (a->singles_length == a->unique_count) {
|
||||
a->singles_tot = (COUNTTYPE)(a->singles_tot - a->single_counts[0]);
|
||||
a->single_counts[0] = 0;
|
||||
}
|
||||
return (UINTa)&a->values[index];
|
||||
} else {
|
||||
if (ArithBitsGetBitsValue(ab, 1, 2)) {
|
||||
index = (S32)(ArithBitsGetValue(ab, a->singles_length - a->summed_length)
|
||||
+ a->summed_length + 1);
|
||||
a->single_counts[index] += 2;
|
||||
a->singles_tot += 2;
|
||||
} else {
|
||||
goto new_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return a->values[index];
|
||||
}
|
||||
|
||||
/* ================================================================= */
|
||||
/* radlz.c — LZ decoder (decode path) */
|
||||
/* ================================================================= */
|
||||
|
||||
#define LOW_BITS 2
|
||||
#define MED_BITS 8
|
||||
#define TOP_BITS 8
|
||||
#define LARGEST_POSSIBLE_OFFSET ((1 << (LOW_BITS + MED_BITS + TOP_BITS)) - 1)
|
||||
#define GRANNY_OFFSET LARGEST_POSSIBLE_OFFSET
|
||||
|
||||
#define MAX_LENS 64
|
||||
#define LMAX (1 << LOW_BITS) /* 4 */
|
||||
#define MMAX (1 << MED_BITS) /* 256 */
|
||||
#define GETT(v) ((v) >> (LOW_BITS + MED_BITS))
|
||||
#define ADDRESS_MASK 3
|
||||
|
||||
static const U32 long_lengths[4] = { MAX_LENS * 2, MAX_LENS * 3, MAX_LENS * 4, MAX_LENS * 8 };
|
||||
|
||||
typedef struct LZ_HEADER {
|
||||
U32 max_offset_and_byte;
|
||||
U32 uniq_offset_and_byte;
|
||||
U32 uniq_lens;
|
||||
} LZ_HEADER;
|
||||
|
||||
typedef struct LZDDATA {
|
||||
ARITH bytes[ADDRESS_MASK + 1];
|
||||
ARITH lens[MAX_LENS + 1];
|
||||
ARITH offsl, offst;
|
||||
ARITH offsm[256];
|
||||
U32 max_bytes, max_offs, max_offsL, max_offsM, max_offsT;
|
||||
U32 bytes_decompressed;
|
||||
U32 last_len;
|
||||
} LZDDATA;
|
||||
typedef LZDDATA *LZD;
|
||||
|
||||
static void get_max_offsets(U32 max_offset, U32 *low_max, U32 *med_max, U32 *high_max) {
|
||||
*low_max = (max_offset >= LMAX) ? LMAX : (max_offset + 1);
|
||||
*med_max = ((max_offset >> LOW_BITS) >= MMAX) ? MMAX : ((max_offset >> LOW_BITS) + 1);
|
||||
*high_max = GETT(max_offset) + 1;
|
||||
}
|
||||
|
||||
static void copy_bytes(void *d, const void *s, U32 length) {
|
||||
U8 *dest = (U8 *)d;
|
||||
const U8 *src = (const U8 *)s;
|
||||
/* LZ back-refs always have src < dest, so the fast path never triggers here;
|
||||
* byte copy is required for correct RLE-style overlap. */
|
||||
if (length >= 4 && (src - dest) >= 4) {
|
||||
do {
|
||||
length -= 4;
|
||||
((U32 *)dest)[0] = ((const U32 *)src)[0];
|
||||
dest += 4; src += 4;
|
||||
} while (length > 4);
|
||||
if (length == 0) return;
|
||||
}
|
||||
do { length--; *dest++ = *src++; } while (length);
|
||||
}
|
||||
|
||||
static U32 LZ_decompress_alloc_size(U32 max_byte_value, U32 uniq_byte_values, U32 max_offset) {
|
||||
U32 size, h, low_max, med_max, high_max;
|
||||
(void)max_byte_value;
|
||||
size = (U32)sizeof(LZDDATA);
|
||||
get_max_offsets(max_offset, &low_max, &med_max, &high_max);
|
||||
h = Arith_decompress_alloc_size(uniq_byte_values);
|
||||
size += h * (ADDRESS_MASK + 1);
|
||||
size += Arith_decompress_alloc_size(MAX_LENS + 1) * (MAX_LENS + 1);
|
||||
size += Arith_decompress_alloc_size(low_max);
|
||||
size += high_max * Arith_decompress_alloc_size(med_max);
|
||||
size += Arith_decompress_alloc_size(high_max);
|
||||
return size;
|
||||
}
|
||||
|
||||
static LZD LZ_decompress_open_from_header(void *ptr, const LZ_HEADER *h) {
|
||||
U32 i, j, size, uniq = 0;
|
||||
U8 *addr;
|
||||
LZD l = (LZD)ptr;
|
||||
|
||||
l->max_bytes = h->max_offset_and_byte & 511;
|
||||
l->max_offs = h->max_offset_and_byte >> 9;
|
||||
get_max_offsets(l->max_offs, &l->max_offsL, &l->max_offsM, &l->max_offsT);
|
||||
l->last_len = 0;
|
||||
l->bytes_decompressed = 0;
|
||||
|
||||
j = h->uniq_offset_and_byte & 511;
|
||||
addr = (U8 *)(l + 1);
|
||||
size = Arith_decompress_alloc_size(j);
|
||||
for (i = 0; i <= ADDRESS_MASK; i++) {
|
||||
l->bytes[i] = Arith_open(addr, 0, l->max_bytes - 1, j);
|
||||
addr += size;
|
||||
}
|
||||
|
||||
for (j = 0; j < 4; j++) {
|
||||
uniq = (h->uniq_lens >> ((3 - j) * 8)) & 255;
|
||||
size = Arith_decompress_alloc_size(uniq);
|
||||
for (i = 0; i < (MAX_LENS / 4); i++) {
|
||||
l->lens[(j * (MAX_LENS / 4)) + i] = Arith_open(addr, 0, MAX_LENS, uniq);
|
||||
addr += size;
|
||||
}
|
||||
}
|
||||
for (i = (MAX_LENS / 4) * 4; i <= MAX_LENS; i++) {
|
||||
l->lens[i] = Arith_open(addr, 0, MAX_LENS, uniq); /* inherits last uniq/size */
|
||||
addr += size;
|
||||
}
|
||||
|
||||
size = Arith_decompress_alloc_size(l->max_offsM);
|
||||
for (i = 0; i < l->max_offsT; i++) {
|
||||
l->offsm[i] = Arith_open(addr, 0, l->max_offsM - 1, l->max_offsM);
|
||||
addr += size;
|
||||
}
|
||||
|
||||
l->offsl = Arith_open(addr, 0, l->max_offsL - 1, l->max_offsL);
|
||||
l->offst = Arith_open(addr + Arith_decompress_alloc_size(l->max_offsL),
|
||||
0, l->max_offsT - 1, GETT(h->uniq_offset_and_byte >> 9) + 1);
|
||||
return l;
|
||||
}
|
||||
|
||||
static U32 LZ_decompress(LZD l, ARITHBITS *ab, U8 *output) {
|
||||
UINTa v;
|
||||
U32 escaped;
|
||||
|
||||
v = Arith_decompress(l->lens[l->last_len], ab);
|
||||
if (Arith_was_escaped(v)) {
|
||||
escaped = ArithBitsGetValue(ab, MAX_LENS + 1);
|
||||
Arith_set_decompressed_symbol(v, escaped);
|
||||
v = escaped;
|
||||
}
|
||||
l->last_len = (U32)v;
|
||||
|
||||
if (v) {
|
||||
U32 len, off, max_ofs;
|
||||
UINTa m;
|
||||
|
||||
max_ofs = l->max_offs;
|
||||
if (max_ofs > l->bytes_decompressed) max_ofs = l->bytes_decompressed;
|
||||
|
||||
len = (v >= (MAX_LENS - 3)) ? long_lengths[v - (MAX_LENS - 3)] : (U32)(v + 1);
|
||||
|
||||
v = Arith_decompress(l->offsl, ab);
|
||||
if (Arith_was_escaped(v)) {
|
||||
escaped = ArithBitsGetValue(ab, l->max_offsL);
|
||||
Arith_set_decompressed_symbol(v, escaped);
|
||||
v = escaped;
|
||||
}
|
||||
off = (U32)(v + 1);
|
||||
|
||||
v = Arith_decompress(l->offst, ab);
|
||||
if (Arith_was_escaped(v)) {
|
||||
escaped = ArithBitsGetValue(ab, GETT(max_ofs) + 1);
|
||||
Arith_set_decompressed_symbol(v, escaped);
|
||||
v = escaped;
|
||||
}
|
||||
|
||||
m = Arith_decompress(l->offsm[v], ab);
|
||||
if (Arith_was_escaped(m)) {
|
||||
U32 offsm_used;
|
||||
if (max_ofs >= (MMAX << LOW_BITS)) offsm_used = MMAX;
|
||||
else offsm_used = (max_ofs >> LOW_BITS) + 1;
|
||||
escaped = ArithBitsGetValue(ab, offsm_used);
|
||||
Arith_set_decompressed_symbol(m, escaped);
|
||||
m = escaped;
|
||||
}
|
||||
|
||||
off = (U32)(off + ((U32)m << LOW_BITS) + ((U32)v << (LOW_BITS + MED_BITS)));
|
||||
l->bytes_decompressed += len;
|
||||
copy_bytes(output, output - off, len);
|
||||
return len;
|
||||
} else {
|
||||
v = Arith_decompress(l->bytes[((UINTa)output) & ADDRESS_MASK], ab);
|
||||
if (Arith_was_escaped(v)) {
|
||||
escaped = ArithBitsGetValue(ab, l->max_bytes);
|
||||
Arith_set_decompressed_symbol(v, escaped);
|
||||
v = escaped;
|
||||
}
|
||||
*output = (U8)v;
|
||||
++l->bytes_decompressed;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================================================= */
|
||||
/* granny_oodle1_compression.cpp — the wrapper */
|
||||
/* ================================================================= */
|
||||
|
||||
#define ALIGN32(x) (((x) + 3u) & ~3u)
|
||||
|
||||
static void reverse32(void *p, U32 nbytes) {
|
||||
U8 *b = (U8 *)p;
|
||||
U32 i;
|
||||
for (i = 0; i + 4 <= nbytes; i += 4) {
|
||||
U8 t0 = b[i], t1 = b[i + 1];
|
||||
b[i] = b[i + 3];
|
||||
b[i + 1] = b[i + 2];
|
||||
b[i + 2] = t1;
|
||||
b[i + 3] = t0;
|
||||
}
|
||||
}
|
||||
|
||||
int gr2_oodle1_decompress(int file_is_byte_reversed,
|
||||
U32 comp_size, void *comp_bytes,
|
||||
U32 stop0, U32 stop1, U32 stop2, void *out) {
|
||||
LZ_HEADER headers[3];
|
||||
ARITHBITS ab;
|
||||
U32 temp_size, size, b;
|
||||
U32 stops[3];
|
||||
void *temp;
|
||||
U8 *to;
|
||||
|
||||
/* hanging-bit fix: zero-pad up to 32-bit alignment (caller also over-allocates). */
|
||||
{
|
||||
U32 rounded = ALIGN32(comp_size) - comp_size;
|
||||
while (rounded--) ((U8 *)comp_bytes)[comp_size + rounded] = 0;
|
||||
}
|
||||
|
||||
if (comp_size < sizeof(headers)) return -1;
|
||||
memcpy(headers, comp_bytes, sizeof(headers));
|
||||
if (file_is_byte_reversed) reverse32(headers, sizeof(headers));
|
||||
|
||||
ArithBitsGetStart(&ab, (const U8 *)comp_bytes + sizeof(headers));
|
||||
|
||||
temp_size = LZ_decompress_alloc_size(255, 256, GRANNY_OFFSET);
|
||||
temp = malloc(temp_size);
|
||||
if (!temp) return -2;
|
||||
|
||||
size = 0;
|
||||
stops[0] = stop0; stops[1] = stop1; stops[2] = stop2;
|
||||
to = (U8 *)out;
|
||||
for (b = 0; b < 3; ++b) {
|
||||
U32 stop = stops[b];
|
||||
LZD lz = LZ_decompress_open_from_header(temp, &headers[b]);
|
||||
U32 guard = 0;
|
||||
while (size < stop) {
|
||||
U32 len = LZ_decompress(lz, &ab, to);
|
||||
if (len == 0 || size + len > stop2 + 512) { free(temp); return -3; } /* corruption guard */
|
||||
size += len;
|
||||
to += len;
|
||||
if (++guard > stop2 + 16) { free(temp); return -4; }
|
||||
}
|
||||
if (size != stop) { free(temp); return -5; }
|
||||
}
|
||||
|
||||
free(temp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gr2_oodle1_decompress_chunk(int file_is_byte_reversed,
|
||||
U32 comp_size, void *comp_bytes,
|
||||
U32 decompressed_size, void *out) {
|
||||
LZ_HEADER header;
|
||||
ARITHBITS ab;
|
||||
U32 temp_size, size;
|
||||
void *temp;
|
||||
U8 *to;
|
||||
|
||||
if (comp_size < sizeof(header)) return -1;
|
||||
memcpy(&header, comp_bytes, sizeof(header));
|
||||
if (file_is_byte_reversed) reverse32(&header, sizeof(header));
|
||||
|
||||
ArithBitsGetStart(&ab, (const U8 *)comp_bytes + sizeof(header));
|
||||
temp_size = LZ_decompress_alloc_size(255, 256, GRANNY_OFFSET);
|
||||
temp = malloc(temp_size);
|
||||
if (!temp) return -2;
|
||||
|
||||
size = 0;
|
||||
to = (U8 *)out;
|
||||
{
|
||||
LZD lz = LZ_decompress_open_from_header(temp, &header);
|
||||
while (size < decompressed_size) {
|
||||
U32 len = LZ_decompress(lz, &ab, to);
|
||||
if (len == 0) { free(temp); return -3; }
|
||||
size += len;
|
||||
to += len;
|
||||
}
|
||||
}
|
||||
free(temp);
|
||||
return (size == decompressed_size) ? 0 : -5;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Granny "Oodle1" section 解压(M0 T2)。
|
||||
// 端口自泄露 SDK 的 granny_oodle1_compression.cpp + radlz.c + radarith.c + arithbit.c
|
||||
// (只移植解码路径,作算法规格 —— 不含压缩,不含 RAD 的构建机制)。
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// comp_bytes:可写缓冲,长度 >= comp_size + 32,尾部已清零(解码器会读过界)。
|
||||
// 起始地址必须 4 字节对齐。
|
||||
// stop0/1/2 :section 的 First16Bit / First8Bit / ExpandedDataSize(累积边界)。
|
||||
// out :至少 stop2 字节。
|
||||
// 返回 0 成功,非 0 失败。
|
||||
int gr2_oodle1_decompress(int file_is_byte_reversed,
|
||||
uint32_t comp_size,
|
||||
void* comp_bytes,
|
||||
uint32_t stop0, uint32_t stop1, uint32_t stop2,
|
||||
void* out);
|
||||
|
||||
// 单块版(NoCompression 之外走不到;保留对称)
|
||||
int gr2_oodle1_decompress_chunk(int file_is_byte_reversed,
|
||||
uint32_t comp_size,
|
||||
void* comp_bytes,
|
||||
uint32_t decompressed_size,
|
||||
void* out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,84 @@
|
||||
find_package(Vulkan REQUIRED)
|
||||
find_package(SDL3 CONFIG REQUIRED)
|
||||
find_program(GLSLC glslc REQUIRED)
|
||||
|
||||
set(MT_NATIVE_VERT_SPV "${CMAKE_CURRENT_BINARY_DIR}/native.vert.spv")
|
||||
set(MT_NATIVE_FRAG_SPV "${CMAKE_CURRENT_BINARY_DIR}/native.frag.spv")
|
||||
add_custom_command(OUTPUT "${MT_NATIVE_VERT_SPV}"
|
||||
COMMAND "${GLSLC}" -fshader-stage=vert "${CMAKE_CURRENT_SOURCE_DIR}/shaders/native.vert" -o "${MT_NATIVE_VERT_SPV}"
|
||||
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/shaders/native.vert")
|
||||
add_custom_command(OUTPUT "${MT_NATIVE_FRAG_SPV}"
|
||||
COMMAND "${GLSLC}" -fshader-stage=frag "${CMAKE_CURRENT_SOURCE_DIR}/shaders/native.frag" -o "${MT_NATIVE_FRAG_SPV}"
|
||||
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/shaders/native.frag")
|
||||
add_custom_target(mt_native_shaders DEPENDS "${MT_NATIVE_VERT_SPV}" "${MT_NATIVE_FRAG_SPV}")
|
||||
|
||||
set(MT_NATIVE_RENDER_SOURCES
|
||||
main.cpp
|
||||
stb_image_impl.cpp
|
||||
dxt.cpp
|
||||
)
|
||||
if(TARGET port_platform AND TARGET mtpython)
|
||||
list(APPEND MT_NATIVE_RENDER_SOURCES
|
||||
"${PROJECT_SOURCE_DIR}/tests/port/port_login_flow_server.cpp"
|
||||
"${PROJECT_SOURCE_DIR}/tests/port/classic/classic_cipher.cpp"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(ANDROID)
|
||||
# SDLActivity loads libmain.so; Android does not launch a native executable.
|
||||
set(MT_NATIVE_TARGET main)
|
||||
add_library(${MT_NATIVE_TARGET} SHARED ${MT_NATIVE_RENDER_SOURCES})
|
||||
else()
|
||||
set(MT_NATIVE_TARGET mt_native_render)
|
||||
add_executable(${MT_NATIVE_TARGET} ${MT_NATIVE_RENDER_SOURCES})
|
||||
endif()
|
||||
if(APPLE)
|
||||
set_target_properties(${MT_NATIVE_TARGET} PROPERTIES
|
||||
OSX_ARCHITECTURES "${CMAKE_HOST_SYSTEM_PROCESSOR}"
|
||||
)
|
||||
endif()
|
||||
add_dependencies(${MT_NATIVE_TARGET} mt_native_shaders)
|
||||
target_include_directories(${MT_NATIVE_TARGET} PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/third_party"
|
||||
"${PROJECT_SOURCE_DIR}/src"
|
||||
"${PROJECT_SOURCE_DIR}/src/platform/EterLib")
|
||||
if(NOT ANDROID)
|
||||
add_custom_command(TARGET ${MT_NATIVE_TARGET} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${MT_NATIVE_VERT_SPV}" "$<TARGET_FILE_DIR:${MT_NATIVE_TARGET}>/native.vert.spv"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${MT_NATIVE_FRAG_SPV}" "$<TARGET_FILE_DIR:${MT_NATIVE_TARGET}>/native.frag.spv")
|
||||
endif()
|
||||
target_link_libraries(${MT_NATIVE_TARGET} PRIVATE Vulkan::Vulkan SDL3::SDL3)
|
||||
if(APPLE)
|
||||
target_link_libraries(${MT_NATIVE_TARGET} PRIVATE
|
||||
"-framework AudioToolbox")
|
||||
endif()
|
||||
if(TARGET port_platform AND TARGET mtpython)
|
||||
target_compile_definitions(${MT_NATIVE_TARGET} PRIVATE MT_NATIVE_HAS_LIVE_CLIENT=1)
|
||||
target_link_libraries(${MT_NATIVE_TARGET} PRIVATE port_platform)
|
||||
if(TARGET mtpython_stdlib)
|
||||
add_dependencies(${MT_NATIVE_TARGET} mtpython_stdlib)
|
||||
if(NOT ANDROID)
|
||||
add_custom_command(TARGET ${MT_NATIVE_TARGET} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${MT_PYTHON_STDLIB_ZIP}" "$<TARGET_FILE_DIR:${MT_NATIVE_TARGET}>/python27.zip")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(ANDROID)
|
||||
# Add this directory to the SDL Android app's assets.srcDirs.
|
||||
set(MT_NATIVE_ANDROID_ASSETS "${CMAKE_CURRENT_BINARY_DIR}/android-assets")
|
||||
add_custom_target(mt_native_android_assets ALL
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${MT_NATIVE_ANDROID_ASSETS}"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${MT_NATIVE_VERT_SPV}" "${MT_NATIVE_ANDROID_ASSETS}/native.vert.spv"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${MT_NATIVE_FRAG_SPV}" "${MT_NATIVE_ANDROID_ASSETS}/native.frag.spv"
|
||||
DEPENDS mt_native_shaders)
|
||||
if(TARGET mtpython_stdlib)
|
||||
add_dependencies(mt_native_android_assets mtpython_stdlib)
|
||||
add_custom_command(TARGET mt_native_android_assets POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${MT_PYTHON_STDLIB_ZIP}" "${MT_NATIVE_ANDROID_ASSETS}/python27.zip")
|
||||
endif()
|
||||
endif()
|
||||
@@ -0,0 +1,151 @@
|
||||
# Native Vulkan client
|
||||
|
||||
This is the standalone renderer path for the 40250 `Render3DDraw` and `UIRenderCommand` command streams, plus direct `--live-client` execution of the ported 40250 client (`PythonBoot`). SDL3 owns the window and input forwarding; Vulkan owns the swapchain (`FIFO` or `IMMEDIATE`/`MAILBOX`), `VK_FORMAT_D32_SFLOAT` depth buffer, `VK_QUERY_TYPE_TIMESTAMP` hardware GPU timer, key-indexed persistent vertex/index buffers, GPU skeletal skinning bone palette SSBO (`set = 1, binding = 0`), DDS/TGA/`mem:` glyph-page texture sampler descriptors, fixed-function 3D + 2D UI state pipelines, and draw submission. On macOS, the Vulkan loader uses MoltenVK over Metal. This is the active macOS and Android client path.
|
||||
|
||||
## Build and run on macOS
|
||||
|
||||
Install Vulkan headers/loader, MoltenVK, SDL3 and `glslc` (shaderc). With Homebrew:
|
||||
|
||||
```sh
|
||||
brew install vulkan-headers vulkan-loader molten-vk sdl3 shaderc
|
||||
|
||||
# Optimized Release build (includes port_platform, embedded Python and FakeLoginServer)
|
||||
cmake -S . -B build/release -DCMAKE_BUILD_TYPE=Release -DMT_BUILD_NATIVE_RENDER=ON \
|
||||
-DMT_EMBED_PYTHON=ON -DCMAKE_PREFIX_PATH=/opt/homebrew
|
||||
cmake --build build/release --target mt_native_render -j8
|
||||
```
|
||||
|
||||
On macOS, `mt_native_render` automatically detects `/opt/homebrew/etc/vulkan/icd.d/MoltenVK_icd.json` (or `/usr/local/etc/vulkan/icd.d/MoltenVK_icd.json`) when `VK_ICD_FILENAMES` is not set in the environment.
|
||||
The build copies `native.vert.spv`, `native.frag.spv`, and, for the live client,
|
||||
`python27.zip` beside the executable. Keep these files together when moving the
|
||||
binary. In a macOS `.app`, put them in `Contents/Resources` (the directory
|
||||
returned by SDL's `SDL_GetBasePath`). `MT_PYTHON_STDLIB` can override the zip path.
|
||||
|
||||
## Android integration
|
||||
|
||||
`./android/build.sh [Debug|Release] [--install]` builds the arm64 APK
|
||||
(`android/app/build/outputs/apk/debug/app-debug.apk`, package
|
||||
`org.metin2port.client`): it downloads SDL 3.4.16 (sha256-checked) into
|
||||
`build/android/_deps`, builds `libSDL3.so`, cross-builds `libmain.so`
|
||||
(this target + `port_platform` + embedded Python) at API 24, and wraps both in the
|
||||
SDLActivity shell under `android/` (`MainActivity` passes the launch intent's
|
||||
`args` extra as argv). `mt_native_android_assets` supplies `native.vert.spv`,
|
||||
`native.frag.spv` and `python27.zip` as APK assets.
|
||||
|
||||
The 40250 `Client` directory is not packaged. `./android/push-client.sh` copies it
|
||||
to the app's `files/Client` (`SDL_GetPrefPath`) with `run-as`, so it needs the
|
||||
debug-signed APK installed first. Without arguments the app runs against the
|
||||
in-process fake server; for a real server:
|
||||
|
||||
```sh
|
||||
adb shell am start -n org.metin2port.client/.MainActivity \
|
||||
--es args "--live-server HOST:AUTH_PORT:GAME_PORT --login-screen"
|
||||
```
|
||||
|
||||
Code page conversion does not use iconv: `src/codepage` carries Microsoft's
|
||||
WindowsBestFit tables for 874/932/936/949/950/1250-1258 (regenerate with
|
||||
`python3 scripts/gen_codepage_tables.py`), so Android converts the same as macOS.
|
||||
|
||||
The Vulkan portability enumeration extension is selected only when advertised
|
||||
by the loader. DDS, TGA, and memory textures retain their existing decoders;
|
||||
JPEG, PNG, and BMP use the same portable decoder on macOS and Android. The
|
||||
vendored `stb_image.h` is upstream v2.30 (SHA-256
|
||||
`594c2fe35d49488b4382dbfaec8f98366defca819d916ac95becf3e75f4200b3`).
|
||||
|
||||
### Interactive Playable Modes
|
||||
|
||||
Run the full 40250 client interactively (infinite frame loop until window close, resizable SDL3 window with automatic Vulkan swapchain recreation and `PythonBoot::SetUISize` sync, full keyboard/IME text input, SDL hardware cursor built from the original cursor images, and SDL3 + `AudioToolbox` `.wav`/`.mp3` audio):
|
||||
|
||||
```sh
|
||||
# Interactive outdoor map session (auto-login via loopback FakeLoginServer)
|
||||
./build/release/src/host/mt_native_render \
|
||||
--live-client "/path/to/40250/Server Client TMP4/Client" \
|
||||
--interactive --fake-mobs 24 --width 1280 --height 800
|
||||
|
||||
# Interactive login screen (stops at introLogin.LoginWindow for manual typing/login)
|
||||
./build/release/src/host/mt_native_render \
|
||||
--live-client "/path/to/40250/Server Client TMP4/Client" \
|
||||
--login-screen --width 1024 --height 768
|
||||
|
||||
# Connect to an external 40250 Auth + Game server
|
||||
./build/release/src/host/mt_native_render \
|
||||
--live-client "/path/to/40250/Server Client TMP4/Client" \
|
||||
--live-server 127.0.0.1:11002:13000 --login-screen
|
||||
```
|
||||
|
||||
### Synthetic benchmark
|
||||
|
||||
```sh
|
||||
./build/release/src/host/mt_native_render --frames 60 --draws 64 --triangles-per-draw 333 --no-vsync
|
||||
```
|
||||
|
||||
Timing metrics printed by `mt_native_render`:
|
||||
- `p95_frame_ms`, `p99_frame_ms`, `max_frame_ms`: wall-clock time for each measured update/render iteration, excluding startup and final GPU drain. These include vsync wait when enabled; use them alongside `gpu_ms` and the CPU breakdown.
|
||||
- `game_update_ms`: mean CPU time spent in `PythonBoot::UIUpdate()`, `PythonBoot::UIRender()`, and audio command draining per frame in `--live-client` mode.
|
||||
- `prepare_ms`: mean CPU draw-preparation time across all frames (including frame 0 cold-start geometry/texture uploads and pipeline creation).
|
||||
- `steady_prepare_ms`: mean CPU draw-preparation time on frames after frame 0 (bone palette copy, UI quad batching, command recording).
|
||||
- `submit_ms`: host CPU time around `vkQueueSubmit` (in `FIFO` mode this includes swapchain backpressure; pass `--no-vsync` to switch to `IMMEDIATE`/`MAILBOX`).
|
||||
- `gpu_ms`: true hardware GPU execution time between top-of-pipe `vkCmdBeginRenderPass` and bottom-of-pipe `vkCmdEndRenderPass` measured via `VK_QUERY_TYPE_TIMESTAMP`.
|
||||
|
||||
### macOS real-server acceptance
|
||||
|
||||
Build the Release live-client target above, then start the evidence runner from the repository root:
|
||||
|
||||
```sh
|
||||
# First verify the runner and renderer using the local fake server.
|
||||
node scripts/native_mac_acceptance.mjs --fake --frames 180
|
||||
|
||||
# Use the real server's shared host, auth port, and game channel port.
|
||||
node scripts/native_mac_acceptance.mjs --server HOST:AUTH_PORT:GAME_PORT
|
||||
```
|
||||
|
||||
Real-server mode checks both ports before starting, opens the native login screen,
|
||||
and collects a redacted client log, one-second process RSS samples, and a JSON
|
||||
report under `build/native-acceptance/`. Enter credentials in the app, then
|
||||
exercise login, character selection, movement, combat, map changes, inventory,
|
||||
chat, window resize/focus, and visual comparison with the current client. Close
|
||||
the window after at least 30 minutes. The report records whether that minimum
|
||||
was met, but remains `NEEDS_MANUAL_REVIEW` until those actions and visual results
|
||||
are checked by a person. It does not assert that RSS alone proves no GPU leak.
|
||||
|
||||
`--live-server` currently accepts one shared host for the auth and game ports.
|
||||
If those endpoints use different hosts, update the native connection setup
|
||||
before claiming a real-server pass. Credentials are entered in the client UI;
|
||||
the runner never puts them in arguments or the report.
|
||||
|
||||
## Run the real 40250 client benchmark in native Vulkan (`--live-client`)
|
||||
|
||||
When built with `port_platform`, `mt_native_render` boots `system.py`, logs in via loopback `FakeLoginServer`, enters the outdoor map with 1..64 monsters, renders the complete 3D scene (40250 hardware-transform terrain splats, animated water patches, gradient skybox & scrolling clouds, SpeedTree forest bark/leaf geometry, GPU-skinned characters with stage-1 specular sphere-maps, and 2D UI/minimap/text-tails/software cursor), and optionally writes a Version 5 `.mtdr` capture:
|
||||
|
||||
```sh
|
||||
./build/release/src/host/mt_native_render \
|
||||
--live-client "/path/to/40250/Server Client TMP4/Client" \
|
||||
--fake-mobs 64 --frames 180 --gpu-skinning --no-vsync \
|
||||
--capture-out /tmp/mt_full_64mobs.mtdr
|
||||
```
|
||||
|
||||
Compare against CPU skinning (`GrannyDeformVertices` on CPU + per-frame vertex buffer re-uploads) with `--no-gpu-skinning`:
|
||||
|
||||
```sh
|
||||
./build/release/src/host/mt_native_render \
|
||||
--live-client "/path/to/40250/Server Client TMP4/Client" \
|
||||
--fake-mobs 64 --frames 180 --no-gpu-skinning --no-vsync
|
||||
```
|
||||
|
||||
## Replay a captured frame (`.mtdr` v1 / v2 / v3 / v4 / v5)
|
||||
|
||||
```sh
|
||||
./build/release/src/host/mt_native_render \
|
||||
--capture /tmp/mt_full_64mobs.mtdr --frames 180 --animate-bones --no-vsync
|
||||
```
|
||||
|
||||
- Pass `--animate-bones` to animate the bone palette SSBO each frame without re-uploading any vertex buffers (`uploads` stays equal to unique static geometries uploaded on frame 0).
|
||||
- Pass `--animate-first-draw` to increment the first draw's `geometry_revision` each frame after frame 0 and verify incremental GPU buffer re-uploads.
|
||||
|
||||
Capture format Version 5 (backward-compatible with Versions 1–4) stores:
|
||||
1. 3D draws (`Render3DDraw`): `geometry_key`, `geometry_revision`, matrices, positions, normals, UVs, diffuse colors, indices, D3D8 fixed-function states, `texture0` / `texture1` names, and GPU skinning data (`bone_indices`, `bone_weights`, `bone_matrices`).
|
||||
2. Self-contained textures: `.dds`, `.tga`, `.jpg`, `.png`, and `.bmp` pack bytes plus `"MTRA"` raw RGBA memory textures (`mem:<id>@<revision>` font glyph pages).
|
||||
3. 2D UI stream (`UIRenderCommand`): canvas size (`ui_width`, `ui_height`) and all `Bar`, `GradientBar`, `Line`, and `Image` commands (including `behind_3d`, clip rects, minimap mask UV coordinates, and software mouse cursor quads).
|
||||
4. Per-draw fog color, vertex/table mode, range flag, start/end distance and density. Older captures omit these fields and replay without fog.
|
||||
|
||||
The native shader now evaluates recorded D3D8 stage 0/1 color and alpha operations, including the original cloud operation (`D3DTOP_MODULATEINVALPHA_ADDCOLOR = 20`), and applies the captured linear or exponential fog. Expanded UI image modes use the 40250 blend factors for screen/color-dodge and modulate. These state fixes do not by themselves establish pixel parity with a Windows 40250 screenshot; compare the same map, time, camera and UI state before treating a color difference as resolved.
|
||||
@@ -0,0 +1,206 @@
|
||||
#pragma once
|
||||
// Android frame-rate and CPU-clock plumbing for the live client's render loop.
|
||||
//
|
||||
// - display_refresh_rate(): the panel's refresh rate now (MainActivity.currentRefreshRate), and
|
||||
// render_rate(): the app-vsync rate the system actually gives the app (MainActivity.currentRenderRate),
|
||||
// for the perf log's refresh_hz / render_hz columns.
|
||||
// - request_frame_rate(): ANativeWindow_setFrameRate (API 30), the surface's preferred rate. The display
|
||||
// mode itself is chosen by MainActivity (--refresh-rate -> preferredDisplayModeId); the vendor
|
||||
// (ColorOS etc.) can still cap an app it does not know.
|
||||
// - PerformanceHint: ADPF APerformanceHint (API 33). The loop reports each frame's CPU work against the
|
||||
// frame budget, so the governor raises the clocks before a frame misses instead of idling the CPU at
|
||||
// its lowest step between short bursts (the 556-748 MHz seen in perf-20260929-153205.csv).
|
||||
// prefer_power_efficiency(): APerformanceHint_setPreferPowerEfficiency (API 35), set by the frame-rate
|
||||
// policy (main.cpp FrameRatePolicy) except in the highest mode.
|
||||
// - set_display_refresh_rate() / max_refresh_rate(): MainActivity switches the display mode at run time
|
||||
// for the frame-rate setting (60 Hz unless the setting wants more).
|
||||
// - battery_power(): BatteryManager current + voltage for the perf log's power columns; the power is only
|
||||
// meaningful off the charger (USB adb counts as a charger: test over wireless adb).
|
||||
//
|
||||
// Every NDK entry point is looked up at run time: minSdk is 24. On other platforms all of this is a no-op.
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <android/native_window.h>
|
||||
#include <dlfcn.h>
|
||||
#include <jni.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace android_perf {
|
||||
|
||||
#ifdef __ANDROID__
|
||||
inline void* libandroid() {
|
||||
static void* lib = dlopen("libandroid.so", RTLD_NOW);
|
||||
return lib;
|
||||
}
|
||||
|
||||
inline double call_activity_float(const char* name) {
|
||||
auto* env = static_cast<JNIEnv*>(SDL_GetAndroidJNIEnv());
|
||||
auto activity = static_cast<jobject>(SDL_GetAndroidActivity());
|
||||
if (!env || !activity) return -1.0;
|
||||
double hz = -1.0;
|
||||
jclass cls = env->GetObjectClass(activity);
|
||||
if (jmethodID method = env->GetMethodID(cls, name, "()F"))
|
||||
hz = env->CallFloatMethod(activity, method);
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionClear();
|
||||
hz = -1.0;
|
||||
}
|
||||
env->DeleteLocalRef(cls);
|
||||
env->DeleteLocalRef(activity);
|
||||
return hz;
|
||||
}
|
||||
|
||||
inline void call_activity_set_float(const char* name, float value) {
|
||||
auto* env = static_cast<JNIEnv*>(SDL_GetAndroidJNIEnv());
|
||||
auto activity = static_cast<jobject>(SDL_GetAndroidActivity());
|
||||
if (!env || !activity) return;
|
||||
jclass cls = env->GetObjectClass(activity);
|
||||
if (jmethodID method = env->GetMethodID(cls, name, "(F)V"))
|
||||
env->CallVoidMethod(activity, method, jfloat(value));
|
||||
if (env->ExceptionCheck()) env->ExceptionClear();
|
||||
env->DeleteLocalRef(cls);
|
||||
env->DeleteLocalRef(activity);
|
||||
}
|
||||
|
||||
inline double display_refresh_rate() { return call_activity_float("currentRefreshRate"); }
|
||||
// The highest refresh rate the display offers at its current resolution; -1 if unknown.
|
||||
inline double max_refresh_rate() { return call_activity_float("maxRefreshRate"); }
|
||||
// Switches the display mode to the refresh rate closest to `hz` (MainActivity.preferredDisplayModeId).
|
||||
inline void set_display_refresh_rate(float hz) { call_activity_set_float("setDisplayRefreshRate", hz); }
|
||||
// The app-vsync rate (Choreographer); -1 until the first measurement.
|
||||
inline double render_rate() { return call_activity_float("currentRenderRate"); }
|
||||
inline double battery_celsius() { return call_activity_float("batteryTemperature"); }
|
||||
|
||||
struct BatteryPower {
|
||||
double current_ma = 0; // drawn from the battery, positive while discharging
|
||||
double voltage_mv = -1;
|
||||
double power_mw = -1; // current x voltage; -1 while plugged in or unknown
|
||||
bool plugged = false;
|
||||
};
|
||||
|
||||
// BatteryManager's CURRENT_NOW is uA by the docs but mA on some vendors, and its sign convention varies:
|
||||
// a game draws well over 20 mA, so a magnitude above 20000 can only be uA; unplugged, the whole current is
|
||||
// the draw. Plugged in, the charger supplies part of it and the reading says nothing about the game.
|
||||
inline BatteryPower battery_power() {
|
||||
BatteryPower out;
|
||||
const double raw = call_activity_float("batteryCurrentRaw");
|
||||
out.current_ma = std::fabs(raw) > 20000.0 ? std::fabs(raw) / 1000.0 : std::fabs(raw);
|
||||
out.voltage_mv = call_activity_float("batteryVoltage");
|
||||
out.plugged = call_activity_float("batteryPlugged") > 0.5;
|
||||
if (!out.plugged && out.current_ma > 0 && out.voltage_mv > 0)
|
||||
out.power_mw = out.current_ma * out.voltage_mv / 1000.0;
|
||||
return out;
|
||||
}
|
||||
|
||||
// false when the call is unavailable (API < 30) or rejected.
|
||||
inline bool request_frame_rate(SDL_Window* window, float fps) {
|
||||
using SetFrameRate = int32_t (*)(ANativeWindow*, float, int8_t);
|
||||
static const auto set_frame_rate =
|
||||
libandroid() ? reinterpret_cast<SetFrameRate>(dlsym(libandroid(), "ANativeWindow_setFrameRate")) : nullptr;
|
||||
if (!set_frame_rate || !window) return false;
|
||||
auto* native = static_cast<ANativeWindow*>(SDL_GetPointerProperty(
|
||||
SDL_GetWindowProperties(window), SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER, nullptr));
|
||||
if (!native) return false;
|
||||
// ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT: a game, not fixed-rate video.
|
||||
return set_frame_rate(native, fps, 0) == 0;
|
||||
}
|
||||
|
||||
class PerformanceHint {
|
||||
public:
|
||||
// `thread_ids`: the threads that do each frame's work (render thread + script thread).
|
||||
bool open(const std::vector<int32_t>& thread_ids, std::int64_t target_ns) {
|
||||
void* lib = libandroid();
|
||||
if (!lib || thread_ids.empty()) return false;
|
||||
get_manager_ = reinterpret_cast<GetManager>(dlsym(lib, "APerformanceHint_getManager"));
|
||||
create_ = reinterpret_cast<CreateSession>(dlsym(lib, "APerformanceHint_createSession"));
|
||||
update_target_ = reinterpret_cast<UpdateTarget>(dlsym(lib, "APerformanceHint_updateTargetWorkDuration"));
|
||||
report_ = reinterpret_cast<Report>(dlsym(lib, "APerformanceHint_reportActualWorkDuration"));
|
||||
close_ = reinterpret_cast<Close>(dlsym(lib, "APerformanceHint_closeSession"));
|
||||
prefer_efficiency_ = reinterpret_cast<PreferEfficiency>(
|
||||
dlsym(lib, "APerformanceHint_setPreferPowerEfficiency"));
|
||||
if (!get_manager_ || !create_ || !report_ || !close_) return false;
|
||||
void* manager = get_manager_();
|
||||
if (!manager) return false;
|
||||
session_ = create_(manager, thread_ids.data(), thread_ids.size(), target_ns);
|
||||
target_ns_ = target_ns;
|
||||
return session_ != nullptr;
|
||||
}
|
||||
|
||||
~PerformanceHint() {
|
||||
if (session_ && close_) close_(session_);
|
||||
}
|
||||
|
||||
bool active() const { return session_ != nullptr; }
|
||||
|
||||
void set_target(std::int64_t target_ns) {
|
||||
if (!session_ || !update_target_ || target_ns == target_ns_ || target_ns <= 0) return;
|
||||
update_target_(session_, target_ns);
|
||||
target_ns_ = target_ns;
|
||||
}
|
||||
|
||||
void report(std::int64_t actual_ns) {
|
||||
if (session_ && actual_ns > 0) report_(session_, actual_ns);
|
||||
}
|
||||
|
||||
// API 35: the scheduler may favour efficiency cores and lower clocks for the session's threads.
|
||||
// false when unavailable.
|
||||
bool prefer_power_efficiency(bool enabled) {
|
||||
if (!session_ || !prefer_efficiency_) return false;
|
||||
if (enabled == prefer_efficiency_on_) return true;
|
||||
prefer_efficiency_on_ = enabled;
|
||||
return prefer_efficiency_(session_, enabled) == 0;
|
||||
}
|
||||
|
||||
private:
|
||||
using GetManager = void* (*)();
|
||||
using CreateSession = void* (*)(void*, const int32_t*, size_t, int64_t);
|
||||
using UpdateTarget = int (*)(void*, int64_t);
|
||||
using Report = int (*)(void*, int64_t);
|
||||
using Close = void (*)(void*);
|
||||
using PreferEfficiency = int (*)(void*, bool);
|
||||
GetManager get_manager_ = nullptr;
|
||||
CreateSession create_ = nullptr;
|
||||
UpdateTarget update_target_ = nullptr;
|
||||
Report report_ = nullptr;
|
||||
Close close_ = nullptr;
|
||||
PreferEfficiency prefer_efficiency_ = nullptr;
|
||||
bool prefer_efficiency_on_ = false;
|
||||
void* session_ = nullptr;
|
||||
std::int64_t target_ns_ = 0;
|
||||
};
|
||||
|
||||
inline int32_t current_thread_id() { return int32_t(gettid()); }
|
||||
#else
|
||||
inline double display_refresh_rate() {
|
||||
const SDL_DisplayMode* mode = SDL_GetCurrentDisplayMode(SDL_GetPrimaryDisplay());
|
||||
return mode ? double(mode->refresh_rate) : -1.0;
|
||||
}
|
||||
inline double render_rate() { return display_refresh_rate(); }
|
||||
inline double max_refresh_rate() { return display_refresh_rate(); }
|
||||
inline void set_display_refresh_rate(float) {}
|
||||
inline double battery_celsius() { return -1.0; }
|
||||
struct BatteryPower {
|
||||
double current_ma = 0, voltage_mv = -1, power_mw = -1;
|
||||
bool plugged = false;
|
||||
};
|
||||
inline BatteryPower battery_power() { return {}; }
|
||||
inline bool request_frame_rate(SDL_Window*, float) { return false; }
|
||||
class PerformanceHint {
|
||||
public:
|
||||
bool open(const std::vector<int32_t>&, std::int64_t) { return false; }
|
||||
bool active() const { return false; }
|
||||
void set_target(std::int64_t) {}
|
||||
void report(std::int64_t) {}
|
||||
bool prefer_power_efficiency(bool) { return false; }
|
||||
};
|
||||
inline int32_t current_thread_id() { return 0; }
|
||||
#endif
|
||||
|
||||
} // namespace android_perf
|
||||
@@ -0,0 +1,417 @@
|
||||
#pragma once
|
||||
|
||||
#include "../platform/EterLib/RenderCommands3D.h"
|
||||
#include "../platform/EterLib/UIRenderCommands.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
// Diagnostic, little-endian arm64 format:
|
||||
// - Version 1: matrices, positions, diffuse, indices.
|
||||
// - Version 2: adds stable geometry_key and geometry_revision.
|
||||
// - Version 3: adds normals, UVs, D3D8 fixed-function states, and embedded pack texture bytes.
|
||||
// - Version 4: adds GPU skeletal skinning streams (bone_indices, bone_weights, bone_matrices)
|
||||
// and 2D UIRenderCommand stream + memory textures ("MTRA").
|
||||
// - Version 5: adds per-draw fog color, mode, range and distance/density state.
|
||||
// - Version 6: adds texture address and filtering state for both stages.
|
||||
// - Version 7: adds two fixed-function lights and material color sources.
|
||||
// - Version 8: eight lights, back-buffer clear entries, viewport MinZ/MaxZ, border color,
|
||||
// TEXCOORDINDEX / TEXTURETRANSFORMFLAGS / D3DTS_TEXTUREn per stage, emissive
|
||||
// material source, NORMALIZENORMALS and LOCALVIEWER; UVs are raw vertex sets.
|
||||
// - Version 9: adds XYZRHW vertex fog (specular alpha).
|
||||
// Capture is opt-in and never runs in the normal game path.
|
||||
namespace native_draw_capture {
|
||||
|
||||
struct Capture {
|
||||
std::uint32_t version = 10;
|
||||
std::vector<Render3DDraw> draws;
|
||||
std::unordered_map<std::string, std::vector<std::uint8_t>> textures;
|
||||
std::uint32_t ui_width = 960;
|
||||
std::uint32_t ui_height = 640;
|
||||
std::vector<UIRenderCommand> ui_commands;
|
||||
};
|
||||
|
||||
inline std::vector<std::uint8_t> encode_raw_argb_as_mtra(
|
||||
std::uint32_t width,
|
||||
std::uint32_t height,
|
||||
const std::uint32_t* argb) {
|
||||
std::vector<std::uint8_t> out;
|
||||
if (!width || !height || !argb)
|
||||
return out;
|
||||
const std::size_t pixel_count = std::size_t(width) * std::size_t(height);
|
||||
out.resize(12 + pixel_count * 4);
|
||||
out[0] = 'M'; out[1] = 'T'; out[2] = 'R'; out[3] = 'A';
|
||||
std::memcpy(out.data() + 4, &width, 4);
|
||||
std::memcpy(out.data() + 8, &height, 4);
|
||||
std::uint8_t* dst = out.data() + 12;
|
||||
for (std::size_t i = 0; i < pixel_count; ++i) {
|
||||
const std::uint32_t c = argb[i];
|
||||
dst[i * 4 + 0] = static_cast<std::uint8_t>((c >> 16) & 0xffu);
|
||||
dst[i * 4 + 1] = static_cast<std::uint8_t>((c >> 8) & 0xffu);
|
||||
dst[i * 4 + 2] = static_cast<std::uint8_t>(c & 0xffu);
|
||||
dst[i * 4 + 3] = static_cast<std::uint8_t>((c >> 24) & 0xffu);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T> void write_scalar(std::ofstream& file, const T& value) {
|
||||
file.write(reinterpret_cast<const char*>(&value), sizeof(value));
|
||||
}
|
||||
|
||||
template <typename T> void read_scalar(std::ifstream& file, T& value) {
|
||||
file.read(reinterpret_cast<char*>(&value), sizeof(value));
|
||||
if (!file) throw std::runtime_error("truncated native draw capture");
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void write_vector(std::ofstream& file, const std::vector<T>& values, std::size_t max_count = 4'000'000) {
|
||||
if (values.size() > max_count) throw std::runtime_error("native draw capture array too large");
|
||||
const auto count = static_cast<std::uint32_t>(values.size());
|
||||
write_scalar(file, count);
|
||||
if (count) file.write(reinterpret_cast<const char*>(values.data()), count * sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void read_vector(std::ifstream& file, std::vector<T>& values, std::size_t max_count = 4'000'000) {
|
||||
std::uint32_t count = 0;
|
||||
read_scalar(file, count);
|
||||
if (count > max_count) throw std::runtime_error("native draw capture array too large");
|
||||
values.resize(count);
|
||||
if (count) file.read(reinterpret_cast<char*>(values.data()), count * sizeof(T));
|
||||
if (!file) throw std::runtime_error("truncated native draw capture array");
|
||||
}
|
||||
|
||||
inline void write_string(std::ofstream& file, const std::string& value) {
|
||||
if (value.size() > 4096) throw std::runtime_error("native draw capture string too large");
|
||||
const auto size = static_cast<std::uint32_t>(value.size());
|
||||
write_scalar(file, size);
|
||||
if (size) file.write(value.data(), size);
|
||||
}
|
||||
|
||||
inline void read_string(std::ifstream& file, std::string& value) {
|
||||
std::uint32_t size = 0;
|
||||
read_scalar(file, size);
|
||||
if (size > 4096) throw std::runtime_error("native draw capture string too large");
|
||||
value.resize(size);
|
||||
if (size) file.read(value.data(), size);
|
||||
if (!file) throw std::runtime_error("truncated native draw capture string");
|
||||
}
|
||||
|
||||
inline void write(
|
||||
const std::string& path,
|
||||
const std::vector<Render3DDraw>& draws,
|
||||
const std::unordered_map<std::string, std::vector<std::uint8_t>>& textures = {},
|
||||
std::uint32_t ui_width = 960,
|
||||
std::uint32_t ui_height = 640,
|
||||
const std::vector<UIRenderCommand>& ui_commands = {}) {
|
||||
if (draws.size() > 10'000 || textures.size() > 8'192 || ui_commands.size() > 100'000)
|
||||
throw std::runtime_error("native draw capture has too many draws, textures, or UI commands");
|
||||
std::ofstream file(path, std::ios::binary | std::ios::trunc);
|
||||
if (!file) throw std::runtime_error("cannot create native draw capture: " + path);
|
||||
const std::uint32_t magic = 0x4d544452; // MTDR
|
||||
const std::uint32_t version = 10;
|
||||
const auto count = static_cast<std::uint32_t>(draws.size());
|
||||
write_scalar(file, magic); write_scalar(file, version); write_scalar(file, count);
|
||||
for (const auto& draw : draws) {
|
||||
file.write(reinterpret_cast<const char*>(draw.world), sizeof(draw.world));
|
||||
file.write(reinterpret_cast<const char*>(draw.view), sizeof(draw.view));
|
||||
file.write(reinterpret_cast<const char*>(draw.proj), sizeof(draw.proj));
|
||||
write_scalar(file, draw.geometry_key);
|
||||
write_scalar(file, draw.geometry_revision);
|
||||
const std::uint32_t flags =
|
||||
(draw.lines ? 1u : 0u) | (draw.pretransformed ? 2u : 0u) | (draw.light0 ? 4u : 0u);
|
||||
write_scalar(file, flags);
|
||||
write_vector(file, draw.positions);
|
||||
write_vector(file, draw.diffuse);
|
||||
write_vector(file, draw.indices);
|
||||
file.write(reinterpret_cast<const char*>(draw.viewport), sizeof(draw.viewport));
|
||||
write_string(file, draw.texture0);
|
||||
write_string(file, draw.texture1);
|
||||
write_vector(file, draw.rhw);
|
||||
write_vector(file, draw.normals);
|
||||
write_vector(file, draw.uv0);
|
||||
write_vector(file, draw.uv1);
|
||||
write_scalar(file, draw.alpha_blend);
|
||||
write_scalar(file, draw.src_blend);
|
||||
write_scalar(file, draw.dest_blend);
|
||||
write_scalar(file, draw.alpha_test);
|
||||
write_scalar(file, draw.alpha_ref);
|
||||
write_scalar(file, draw.alpha_func);
|
||||
write_scalar(file, draw.cull_mode);
|
||||
write_scalar(file, draw.z_enable);
|
||||
write_scalar(file, draw.z_write);
|
||||
write_scalar(file, draw.z_func);
|
||||
write_scalar(file, draw.lighting);
|
||||
write_scalar(file, draw.texture_factor);
|
||||
write_scalar(file, draw.fog_enable);
|
||||
file.write(reinterpret_cast<const char*>(draw.color_op), sizeof(draw.color_op));
|
||||
file.write(reinterpret_cast<const char*>(draw.color_arg1), sizeof(draw.color_arg1));
|
||||
file.write(reinterpret_cast<const char*>(draw.color_arg2), sizeof(draw.color_arg2));
|
||||
file.write(reinterpret_cast<const char*>(draw.alpha_op), sizeof(draw.alpha_op));
|
||||
file.write(reinterpret_cast<const char*>(draw.alpha_arg1), sizeof(draw.alpha_arg1));
|
||||
file.write(reinterpret_cast<const char*>(draw.alpha_arg2), sizeof(draw.alpha_arg2));
|
||||
file.write(reinterpret_cast<const char*>(draw.material_diffuse), sizeof(draw.material_diffuse));
|
||||
file.write(reinterpret_cast<const char*>(draw.material_ambient), sizeof(draw.material_ambient));
|
||||
file.write(reinterpret_cast<const char*>(draw.material_emissive), sizeof(draw.material_emissive));
|
||||
file.write(reinterpret_cast<const char*>(draw.light0_direction), sizeof(draw.light0_direction));
|
||||
file.write(reinterpret_cast<const char*>(draw.light0_diffuse), sizeof(draw.light0_diffuse));
|
||||
file.write(reinterpret_cast<const char*>(draw.light0_ambient), sizeof(draw.light0_ambient));
|
||||
write_scalar(file, draw.ambient);
|
||||
write_scalar(file, draw.fog_color);
|
||||
write_scalar(file, draw.fog_vertex_mode);
|
||||
write_scalar(file, draw.fog_table_mode);
|
||||
write_scalar(file, draw.fog_range_enable);
|
||||
write_scalar(file, draw.fog_start);
|
||||
write_scalar(file, draw.fog_end);
|
||||
write_scalar(file, draw.fog_density);
|
||||
file.write(reinterpret_cast<const char*>(draw.address_u), sizeof(draw.address_u));
|
||||
file.write(reinterpret_cast<const char*>(draw.address_v), sizeof(draw.address_v));
|
||||
file.write(reinterpret_cast<const char*>(draw.min_filter), sizeof(draw.min_filter));
|
||||
file.write(reinterpret_cast<const char*>(draw.mag_filter), sizeof(draw.mag_filter));
|
||||
file.write(reinterpret_cast<const char*>(draw.mip_filter), sizeof(draw.mip_filter));
|
||||
file.write(reinterpret_cast<const char*>(draw.lights), sizeof(draw.lights));
|
||||
write_scalar(file, draw.diffuse_material_source);
|
||||
write_scalar(file, draw.ambient_material_source);
|
||||
write_scalar(file, draw.color_vertex);
|
||||
file.write(reinterpret_cast<const char*>(draw.viewport_z), sizeof(draw.viewport_z));
|
||||
write_scalar(file, draw.clear_flags);
|
||||
write_scalar(file, draw.clear_color);
|
||||
write_scalar(file, draw.clear_z);
|
||||
file.write(reinterpret_cast<const char*>(draw.border_color), sizeof(draw.border_color));
|
||||
file.write(reinterpret_cast<const char*>(draw.texcoord_index), sizeof(draw.texcoord_index));
|
||||
file.write(reinterpret_cast<const char*>(draw.texture_transform_flags), sizeof(draw.texture_transform_flags));
|
||||
file.write(reinterpret_cast<const char*>(draw.texture_matrix), sizeof(draw.texture_matrix));
|
||||
write_scalar(file, draw.emissive_material_source);
|
||||
write_scalar(file, draw.normalize_normals);
|
||||
write_scalar(file, draw.local_viewer);
|
||||
write_vector(file, draw.vertex_fog);
|
||||
write_vector(file, draw.bone_indices);
|
||||
write_vector(file, draw.bone_weights);
|
||||
write_vector(file, draw.bone_matrices);
|
||||
write_scalar(file, draw.render_target);
|
||||
write_scalar(file, draw.target_width);
|
||||
write_scalar(file, draw.target_height);
|
||||
}
|
||||
const auto texture_count = static_cast<std::uint32_t>(textures.size());
|
||||
write_scalar(file, texture_count);
|
||||
for (const auto& [name, bytes] : textures) {
|
||||
write_string(file, name);
|
||||
write_vector(file, bytes, 16'777'216);
|
||||
}
|
||||
write_scalar(file, ui_width);
|
||||
write_scalar(file, ui_height);
|
||||
const auto ui_count = static_cast<std::uint32_t>(ui_commands.size());
|
||||
write_scalar(file, ui_count);
|
||||
for (const auto& cmd : ui_commands) {
|
||||
const auto kind = static_cast<std::uint32_t>(cmd.kind);
|
||||
write_scalar(file, kind);
|
||||
write_scalar(file, cmd.x1);
|
||||
write_scalar(file, cmd.y1);
|
||||
write_scalar(file, cmd.x2);
|
||||
write_scalar(file, cmd.y2);
|
||||
write_scalar(file, cmd.argb);
|
||||
write_scalar(file, cmd.end_argb);
|
||||
write_scalar(file, cmd.clip_x1);
|
||||
write_scalar(file, cmd.clip_y1);
|
||||
write_scalar(file, cmd.clip_x2);
|
||||
write_scalar(file, cmd.clip_y2);
|
||||
write_string(file, cmd.text);
|
||||
const std::uint32_t uiflags = (cmd.quad ? 1u : 0u) | (cmd.behind_3d ? 2u : 0u);
|
||||
write_scalar(file, uiflags);
|
||||
file.write(reinterpret_cast<const char*>(cmd.qx), sizeof(cmd.qx));
|
||||
file.write(reinterpret_cast<const char*>(cmd.qy), sizeof(cmd.qy));
|
||||
write_scalar(file, cmd.su);
|
||||
write_scalar(file, cmd.sv);
|
||||
write_scalar(file, cmd.eu);
|
||||
write_scalar(file, cmd.ev);
|
||||
write_scalar(file, cmd.blend);
|
||||
write_string(file, cmd.mask);
|
||||
file.write(reinterpret_cast<const char*>(cmd.mu), sizeof(cmd.mu));
|
||||
file.write(reinterpret_cast<const char*>(cmd.mv), sizeof(cmd.mv));
|
||||
}
|
||||
if (!file) throw std::runtime_error("failed to write native draw capture: " + path);
|
||||
}
|
||||
|
||||
inline Capture read_capture(const std::string& path) {
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
if (!file) throw std::runtime_error("cannot open native draw capture: " + path);
|
||||
std::uint32_t magic = 0, version = 0, count = 0;
|
||||
read_scalar(file, magic); read_scalar(file, version); read_scalar(file, count);
|
||||
if (magic != 0x4d544452 || (version < 1 || version > 10) || count > 10'000)
|
||||
throw std::runtime_error("unsupported native draw capture format");
|
||||
Capture capture;
|
||||
capture.version = version;
|
||||
capture.draws.resize(count);
|
||||
for (auto& draw : capture.draws) {
|
||||
file.read(reinterpret_cast<char*>(draw.world), sizeof(draw.world));
|
||||
file.read(reinterpret_cast<char*>(draw.view), sizeof(draw.view));
|
||||
file.read(reinterpret_cast<char*>(draw.proj), sizeof(draw.proj));
|
||||
if (!file) throw std::runtime_error("truncated native draw capture matrices");
|
||||
if (version >= 2) {
|
||||
read_scalar(file, draw.geometry_key);
|
||||
read_scalar(file, draw.geometry_revision);
|
||||
}
|
||||
std::uint32_t flags = 0;
|
||||
read_scalar(file, flags);
|
||||
draw.lines = (flags & 1u) != 0;
|
||||
draw.pretransformed = (flags & 2u) != 0;
|
||||
draw.light0 = (flags & 4u) != 0;
|
||||
read_vector(file, draw.positions);
|
||||
read_vector(file, draw.diffuse);
|
||||
read_vector(file, draw.indices);
|
||||
if (version >= 3) {
|
||||
file.read(reinterpret_cast<char*>(draw.viewport), sizeof(draw.viewport));
|
||||
read_string(file, draw.texture0);
|
||||
read_string(file, draw.texture1);
|
||||
read_vector(file, draw.rhw);
|
||||
read_vector(file, draw.normals);
|
||||
read_vector(file, draw.uv0);
|
||||
read_vector(file, draw.uv1);
|
||||
read_scalar(file, draw.alpha_blend);
|
||||
read_scalar(file, draw.src_blend);
|
||||
read_scalar(file, draw.dest_blend);
|
||||
read_scalar(file, draw.alpha_test);
|
||||
read_scalar(file, draw.alpha_ref);
|
||||
read_scalar(file, draw.alpha_func);
|
||||
read_scalar(file, draw.cull_mode);
|
||||
read_scalar(file, draw.z_enable);
|
||||
read_scalar(file, draw.z_write);
|
||||
read_scalar(file, draw.z_func);
|
||||
read_scalar(file, draw.lighting);
|
||||
read_scalar(file, draw.texture_factor);
|
||||
read_scalar(file, draw.fog_enable);
|
||||
file.read(reinterpret_cast<char*>(draw.color_op), sizeof(draw.color_op));
|
||||
file.read(reinterpret_cast<char*>(draw.color_arg1), sizeof(draw.color_arg1));
|
||||
file.read(reinterpret_cast<char*>(draw.color_arg2), sizeof(draw.color_arg2));
|
||||
file.read(reinterpret_cast<char*>(draw.alpha_op), sizeof(draw.alpha_op));
|
||||
file.read(reinterpret_cast<char*>(draw.alpha_arg1), sizeof(draw.alpha_arg1));
|
||||
file.read(reinterpret_cast<char*>(draw.alpha_arg2), sizeof(draw.alpha_arg2));
|
||||
file.read(reinterpret_cast<char*>(draw.material_diffuse), sizeof(draw.material_diffuse));
|
||||
file.read(reinterpret_cast<char*>(draw.material_ambient), sizeof(draw.material_ambient));
|
||||
file.read(reinterpret_cast<char*>(draw.material_emissive), sizeof(draw.material_emissive));
|
||||
file.read(reinterpret_cast<char*>(draw.light0_direction), sizeof(draw.light0_direction));
|
||||
file.read(reinterpret_cast<char*>(draw.light0_diffuse), sizeof(draw.light0_diffuse));
|
||||
file.read(reinterpret_cast<char*>(draw.light0_ambient), sizeof(draw.light0_ambient));
|
||||
read_scalar(file, draw.ambient);
|
||||
if (version >= 5) {
|
||||
read_scalar(file, draw.fog_color);
|
||||
read_scalar(file, draw.fog_vertex_mode);
|
||||
read_scalar(file, draw.fog_table_mode);
|
||||
read_scalar(file, draw.fog_range_enable);
|
||||
read_scalar(file, draw.fog_start);
|
||||
read_scalar(file, draw.fog_end);
|
||||
read_scalar(file, draw.fog_density);
|
||||
}
|
||||
if (version >= 6) {
|
||||
file.read(reinterpret_cast<char*>(draw.address_u), sizeof(draw.address_u));
|
||||
file.read(reinterpret_cast<char*>(draw.address_v), sizeof(draw.address_v));
|
||||
file.read(reinterpret_cast<char*>(draw.min_filter), sizeof(draw.min_filter));
|
||||
file.read(reinterpret_cast<char*>(draw.mag_filter), sizeof(draw.mag_filter));
|
||||
file.read(reinterpret_cast<char*>(draw.mip_filter), sizeof(draw.mip_filter));
|
||||
}
|
||||
if (version >= 7) {
|
||||
file.read(reinterpret_cast<char*>(draw.lights),
|
||||
(version >= 8 ? 8 : 2) * sizeof(draw.lights[0]));
|
||||
read_scalar(file, draw.diffuse_material_source);
|
||||
read_scalar(file, draw.ambient_material_source);
|
||||
read_scalar(file, draw.color_vertex);
|
||||
}
|
||||
if (version >= 8) {
|
||||
file.read(reinterpret_cast<char*>(draw.viewport_z), sizeof(draw.viewport_z));
|
||||
read_scalar(file, draw.clear_flags);
|
||||
read_scalar(file, draw.clear_color);
|
||||
read_scalar(file, draw.clear_z);
|
||||
file.read(reinterpret_cast<char*>(draw.border_color), sizeof(draw.border_color));
|
||||
file.read(reinterpret_cast<char*>(draw.texcoord_index), sizeof(draw.texcoord_index));
|
||||
file.read(reinterpret_cast<char*>(draw.texture_transform_flags), sizeof(draw.texture_transform_flags));
|
||||
file.read(reinterpret_cast<char*>(draw.texture_matrix), sizeof(draw.texture_matrix));
|
||||
read_scalar(file, draw.emissive_material_source);
|
||||
read_scalar(file, draw.normalize_normals);
|
||||
read_scalar(file, draw.local_viewer);
|
||||
if (version >= 9) read_vector(file, draw.vertex_fog);
|
||||
} else if (draw.light0) {
|
||||
auto& light = draw.lights[0];
|
||||
light.type = 3;
|
||||
std::memcpy(light.direction, draw.light0_direction, sizeof(light.direction));
|
||||
std::memcpy(light.diffuse, draw.light0_diffuse, sizeof(light.diffuse));
|
||||
std::memcpy(light.ambient, draw.light0_ambient, sizeof(light.ambient));
|
||||
}
|
||||
if (!file) throw std::runtime_error("truncated native draw capture state");
|
||||
} else {
|
||||
draw.z_enable = 1;
|
||||
draw.z_write = 1;
|
||||
}
|
||||
if (version >= 4) {
|
||||
read_vector(file, draw.bone_indices);
|
||||
read_vector(file, draw.bone_weights);
|
||||
read_vector(file, draw.bone_matrices);
|
||||
}
|
||||
if (version >= 10) {
|
||||
read_scalar(file, draw.render_target);
|
||||
read_scalar(file, draw.target_width);
|
||||
read_scalar(file, draw.target_height);
|
||||
}
|
||||
}
|
||||
if (version >= 3) {
|
||||
std::uint32_t texture_count = 0;
|
||||
read_scalar(file, texture_count);
|
||||
if (texture_count > 8'192) throw std::runtime_error("native draw capture has too many textures");
|
||||
for (std::uint32_t i = 0; i < texture_count; ++i) {
|
||||
std::string name;
|
||||
std::vector<std::uint8_t> bytes;
|
||||
read_string(file, name);
|
||||
read_vector(file, bytes, 16'777'216);
|
||||
capture.textures.emplace(std::move(name), std::move(bytes));
|
||||
}
|
||||
}
|
||||
if (version >= 4) {
|
||||
read_scalar(file, capture.ui_width);
|
||||
read_scalar(file, capture.ui_height);
|
||||
std::uint32_t ui_count = 0;
|
||||
read_scalar(file, ui_count);
|
||||
if (ui_count > 100'000) throw std::runtime_error("native draw capture has too many UI commands");
|
||||
capture.ui_commands.resize(ui_count);
|
||||
for (auto& cmd : capture.ui_commands) {
|
||||
std::uint32_t kind = 0, uiflags = 0;
|
||||
read_scalar(file, kind);
|
||||
cmd.kind = static_cast<UIRenderCommand::Kind>(kind);
|
||||
read_scalar(file, cmd.x1);
|
||||
read_scalar(file, cmd.y1);
|
||||
read_scalar(file, cmd.x2);
|
||||
read_scalar(file, cmd.y2);
|
||||
read_scalar(file, cmd.argb);
|
||||
read_scalar(file, cmd.end_argb);
|
||||
read_scalar(file, cmd.clip_x1);
|
||||
read_scalar(file, cmd.clip_y1);
|
||||
read_scalar(file, cmd.clip_x2);
|
||||
read_scalar(file, cmd.clip_y2);
|
||||
read_string(file, cmd.text);
|
||||
read_scalar(file, uiflags);
|
||||
cmd.quad = (uiflags & 1u) != 0;
|
||||
cmd.behind_3d = (uiflags & 2u) != 0;
|
||||
file.read(reinterpret_cast<char*>(cmd.qx), sizeof(cmd.qx));
|
||||
file.read(reinterpret_cast<char*>(cmd.qy), sizeof(cmd.qy));
|
||||
read_scalar(file, cmd.su);
|
||||
read_scalar(file, cmd.sv);
|
||||
read_scalar(file, cmd.eu);
|
||||
read_scalar(file, cmd.ev);
|
||||
read_scalar(file, cmd.blend);
|
||||
read_string(file, cmd.mask);
|
||||
file.read(reinterpret_cast<char*>(cmd.mu), sizeof(cmd.mu));
|
||||
file.read(reinterpret_cast<char*>(cmd.mv), sizeof(cmd.mv));
|
||||
if (!file) throw std::runtime_error("truncated native draw capture UI command");
|
||||
}
|
||||
}
|
||||
return capture;
|
||||
}
|
||||
|
||||
inline std::vector<Render3DDraw> read(const std::string& path) {
|
||||
return read_capture(path).draws;
|
||||
}
|
||||
|
||||
} // namespace native_draw_capture
|
||||
@@ -0,0 +1,212 @@
|
||||
// engine/dxt —— DDS(DXT1/3/5) + 未压缩 BGRA8 → RGBA8 软解。M1 T3。
|
||||
#include "dxt.h"
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace mtimage {
|
||||
namespace {
|
||||
|
||||
inline uint32_t rd_le32(const uint8_t* p) {
|
||||
return uint32_t(p[0]) | (uint32_t(p[1]) << 8) | (uint32_t(p[2]) << 16) | (uint32_t(p[3]) << 24);
|
||||
}
|
||||
|
||||
// 5:6:5 → r,g,b (0..255)
|
||||
inline void unpack565(uint16_t c, int& r, int& g, int& b) {
|
||||
r = ((c >> 11) & 0x1f); r = (r << 3) | (r >> 2);
|
||||
g = ((c >> 5) & 0x3f); g = (g << 2) | (g >> 4);
|
||||
b = (c & 0x1f); b = (b << 3) | (b >> 2);
|
||||
}
|
||||
|
||||
// 解一个 DXT1 颜色块(8B)→ 16 像素 RGB(不写 alpha;dxt1_alpha=true 时按 1-bit alpha 写)
|
||||
void decode_color_block(const uint8_t* blk, uint8_t out[16][4], bool dxt1_alpha) {
|
||||
uint16_t c0 = uint16_t(blk[0] | (blk[1] << 8));
|
||||
uint16_t c1 = uint16_t(blk[2] | (blk[3] << 8));
|
||||
int r[4], g[4], b[4], a[4] = {255, 255, 255, 255};
|
||||
unpack565(c0, r[0], g[0], b[0]);
|
||||
unpack565(c1, r[1], g[1], b[1]);
|
||||
if (c0 > c1 || !dxt1_alpha) {
|
||||
r[2] = (2 * r[0] + r[1]) / 3; g[2] = (2 * g[0] + g[1]) / 3; b[2] = (2 * b[0] + b[1]) / 3;
|
||||
r[3] = (r[0] + 2 * r[1]) / 3; g[3] = (g[0] + 2 * g[1]) / 3; b[3] = (b[0] + 2 * b[1]) / 3;
|
||||
} else {
|
||||
r[2] = (r[0] + r[1]) / 2; g[2] = (g[0] + g[1]) / 2; b[2] = (b[0] + b[1]) / 2;
|
||||
r[3] = g[3] = b[3] = 0; a[3] = 0; // 透明
|
||||
}
|
||||
uint32_t bits = rd_le32(blk + 4);
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
int idx = (bits >> (i * 2)) & 3;
|
||||
out[i][0] = uint8_t(r[idx]); out[i][1] = uint8_t(g[idx]);
|
||||
out[i][2] = uint8_t(b[idx]); out[i][3] = uint8_t(a[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
// DXT3:alpha 块(8B)= 16 个 4-bit alpha,直接展开
|
||||
void decode_dxt3_alpha(const uint8_t* blk, uint8_t out[16][4]) {
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
int a0 = blk[i] & 0x0f, a1 = (blk[i] >> 4) & 0x0f;
|
||||
out[i * 2 + 0][3] = uint8_t(a0 * 17); // 0..15 → 0..255
|
||||
out[i * 2 + 1][3] = uint8_t(a1 * 17);
|
||||
}
|
||||
}
|
||||
|
||||
// DXT5:alpha 块(8B)= 2 端点 + 16×3-bit 索引
|
||||
void decode_dxt5_alpha(const uint8_t* blk, uint8_t out[16][4]) {
|
||||
int a0 = blk[0], a1 = blk[1];
|
||||
int a[8];
|
||||
a[0] = a0; a[1] = a1;
|
||||
if (a0 > a1) {
|
||||
for (int i = 1; i < 7; ++i) a[i + 1] = ((7 - i) * a0 + i * a1) / 7;
|
||||
} else {
|
||||
for (int i = 1; i < 5; ++i) a[i + 1] = ((5 - i) * a0 + i * a1) / 5;
|
||||
a[6] = 0; a[7] = 255;
|
||||
}
|
||||
uint64_t bits = 0;
|
||||
for (int i = 0; i < 6; ++i) bits |= uint64_t(blk[2 + i]) << (8 * i);
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
int idx = int((bits >> (i * 3)) & 7);
|
||||
out[i][3] = uint8_t(a[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
enum Fmt { F_NONE, F_DXT1, F_DXT3, F_DXT5, F_RGB };
|
||||
|
||||
bool valid_mask(uint32_t mask, uint32_t bits) {
|
||||
if (!mask || (bits < 32 && (mask >> bits))) return false;
|
||||
while (!(mask & 1)) mask >>= 1;
|
||||
return (mask & (mask + 1)) == 0; // contiguous channel bits
|
||||
}
|
||||
|
||||
uint8_t channel(uint32_t pixel, uint32_t mask) {
|
||||
if (!mask) return 255;
|
||||
while (!(mask & 1)) { mask >>= 1; pixel >>= 1; }
|
||||
return uint8_t((uint64_t(pixel & mask) * 255 + mask / 2) / mask);
|
||||
}
|
||||
|
||||
void decode_dxt_level(Fmt fmt, const uint8_t* src, uint32_t w, uint32_t h, uint8_t* rgba) {
|
||||
const int block_bytes = (fmt == F_DXT1) ? 8 : 16;
|
||||
const size_t bx = (w + 3) / 4, by = (h + 3) / 4;
|
||||
for (size_t byi = 0; byi < by; ++byi) {
|
||||
for (size_t bxi = 0; bxi < bx; ++bxi) {
|
||||
const uint8_t* blk = src + (byi * bx + bxi) * block_bytes;
|
||||
uint8_t px[16][4];
|
||||
if (fmt == F_DXT1) {
|
||||
decode_color_block(blk, px, /*dxt1_alpha=*/true);
|
||||
} else {
|
||||
decode_color_block(blk + 8, px, /*dxt1_alpha=*/false);
|
||||
if (fmt == F_DXT3) decode_dxt3_alpha(blk, px);
|
||||
else decode_dxt5_alpha(blk, px);
|
||||
}
|
||||
for (int py = 0; py < 4; ++py) {
|
||||
size_t y = byi * 4 + py;
|
||||
if (y >= h) break;
|
||||
for (int pxx = 0; pxx < 4; ++pxx) {
|
||||
size_t x = bxi * 4 + pxx;
|
||||
if (x >= w) break;
|
||||
uint8_t* o = &rgba[(y * w + x) * 4];
|
||||
const uint8_t* s = px[py * 4 + pxx];
|
||||
o[0] = s[0]; o[1] = s[1]; o[2] = s[2]; o[3] = s[3];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Image decode(const uint8_t* d, size_t len) {
|
||||
Image img;
|
||||
if (len < 128 || std::memcmp(d, "DDS ", 4) != 0) return img;
|
||||
uint32_t hsize = rd_le32(d + 4);
|
||||
if (hsize != 124) return img;
|
||||
uint32_t h = rd_le32(d + 12);
|
||||
uint32_t w = rd_le32(d + 16);
|
||||
uint32_t pf_flags = rd_le32(d + 80);
|
||||
const uint8_t* fourcc = d + 84;
|
||||
uint32_t rgb_bitcount = rd_le32(d + 88);
|
||||
|
||||
Fmt fmt = F_NONE;
|
||||
if (pf_flags & 0x4) { // DDPF_FOURCC
|
||||
if (!std::memcmp(fourcc, "DXT1", 4)) fmt = F_DXT1;
|
||||
else if (!std::memcmp(fourcc, "DXT3", 4)) fmt = F_DXT3;
|
||||
else if (!std::memcmp(fourcc, "DXT5", 4)) fmt = F_DXT5;
|
||||
} else if ((pf_flags & 0x40) && (rgb_bitcount == 16 || rgb_bitcount == 24 || rgb_bitcount == 32)) {
|
||||
fmt = F_RGB;
|
||||
}
|
||||
if (fmt == F_NONE || w == 0 || h == 0 || w > 8192 || h > 8192) return img;
|
||||
|
||||
const uint8_t* src = d + 128;
|
||||
size_t avail = len - 128;
|
||||
img.w = uint16_t(w);
|
||||
img.h = uint16_t(h);
|
||||
img.rgba.assign(size_t(w) * h * 4, 0);
|
||||
|
||||
if (fmt == F_RGB) {
|
||||
const uint32_t r = rd_le32(d + 92), g = rd_le32(d + 96), b = rd_le32(d + 100);
|
||||
const uint32_t a = (pf_flags & 1) ? rd_le32(d + 104) : 0;
|
||||
if (!valid_mask(r, rgb_bitcount) || !valid_mask(g, rgb_bitcount) || !valid_mask(b, rgb_bitcount) ||
|
||||
(a && !valid_mask(a, rgb_bitcount)) || ((pf_flags & 1) && !a) ||
|
||||
(r & g) || (r & b) || (g & b) || (a & (r | g | b))) return {};
|
||||
const size_t bytes = rgb_bitcount / 8, row_bytes = size_t(w) * bytes;
|
||||
const size_t pitch = (rd_le32(d + 8) & 8) ? rd_le32(d + 20) : row_bytes;
|
||||
if (pitch < row_bytes || avail < row_bytes || pitch > avail || (h - 1) > (avail - row_bytes) / pitch) return {};
|
||||
for (size_t y = 0; y < h; ++y) {
|
||||
for (size_t x = 0; x < w; ++x) {
|
||||
uint32_t pixel = 0;
|
||||
for (size_t k = 0; k < bytes; ++k) pixel |= uint32_t(src[y * pitch + x * bytes + k]) << (8 * k);
|
||||
auto* out = &img.rgba[(y * w + x) * 4];
|
||||
out[0] = channel(pixel, r); out[1] = channel(pixel, g);
|
||||
out[2] = channel(pixel, b); out[3] = channel(pixel, a);
|
||||
}
|
||||
}
|
||||
img.format = rgb_bitcount == 32 && r == 0xff0000 && b == 0xff ? "BGRA8" : "RGB_MASKED";
|
||||
return img;
|
||||
}
|
||||
|
||||
const int block_bytes = (fmt == F_DXT1) ? 8 : 16;
|
||||
const size_t bx = (w + 3) / 4, by = (h + 3) / 4;
|
||||
if (avail < bx * by * block_bytes) { img = Image{}; return img; }
|
||||
decode_dxt_level(fmt, src, uint32_t(w), uint32_t(h), img.rgba.data());
|
||||
|
||||
// 40250 CDXTCImage::LoadHeaderFromMemory + CGraphicImageTexture::CreateDDSTexture:
|
||||
// mipmapCount = max(1, min(dwMipMapCount, MAX_MIPLEVELS)); level i starts at
|
||||
// sum(dwLinearSize >> 2k) and Copy() memcpy's dwLinearSize >> 2i bytes of it. Only
|
||||
// with DDSD_MIPMAPCOUNT and without DDSD_PITCH are levels > 0 filled — otherwise 40250
|
||||
// creates them uninitialized; here that case is one level.
|
||||
const uint32_t dds_flags = rd_le32(d + 8);
|
||||
const uint32_t linear_size = rd_le32(d + 20);
|
||||
uint32_t mip_count = std::min<uint32_t>(rd_le32(d + 28), 12u);
|
||||
if (mip_count == 0) mip_count = 1;
|
||||
if (!(dds_flags & 0x20000u) || (dds_flags & 0x8u) || linear_size == 0) mip_count = 1;
|
||||
size_t offset = linear_size;
|
||||
for (uint32_t i = 1; i < mip_count; ++i) {
|
||||
const uint32_t lw = std::max<uint32_t>(1, w >> i), lh = std::max<uint32_t>(1, h >> i);
|
||||
const size_t level_bytes = ((lw + 3) / 4) * ((lh + 3) / 4) * size_t(block_bytes);
|
||||
const size_t copy_bytes = std::min<size_t>(size_t(linear_size) >> (2 * i), level_bytes);
|
||||
std::vector<uint8_t> blocks(level_bytes, 0);
|
||||
if (offset < avail) std::memcpy(blocks.data(), src + offset, std::min(copy_bytes, avail - offset));
|
||||
offset += size_t(linear_size) >> (2 * i);
|
||||
std::vector<uint8_t> level(size_t(lw) * lh * 4, 0);
|
||||
decode_dxt_level(fmt, blocks.data(), lw, lh, level.data());
|
||||
img.mips.push_back(std::move(level));
|
||||
}
|
||||
img.file_mip_levels = uint8_t(mip_count);
|
||||
img.format = (fmt == F_DXT1) ? "DXT1" : (fmt == F_DXT3) ? "DXT3" : "DXT5";
|
||||
return img;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Image load_dds(const uint8_t* bytes, size_t len) { return decode(bytes, len); }
|
||||
|
||||
Image load_dds_path(const char* path) {
|
||||
FILE* f = std::fopen(path, "rb");
|
||||
if (!f) return {};
|
||||
std::fseek(f, 0, SEEK_END);
|
||||
long n = std::ftell(f);
|
||||
std::fseek(f, 0, SEEK_SET);
|
||||
std::vector<uint8_t> buf(n > 0 ? size_t(n) : 0);
|
||||
size_t rd = buf.empty() ? 0 : std::fread(buf.data(), 1, buf.size(), f);
|
||||
std::fclose(f);
|
||||
if (rd != buf.size()) return {};
|
||||
return decode(buf.data(), buf.size());
|
||||
}
|
||||
|
||||
} // namespace mtimage
|
||||
@@ -0,0 +1,27 @@
|
||||
// dxt.{h,cpp} — ported verbatim from xrender-poc/engine/dxt.{h,cpp}
|
||||
// (namespace engine -> mtimage). DDS DXT1/3/5 + masked 16/24/32-bit RGB -> RGBA8, level 0.
|
||||
// engine/dxt —— DDS(DXT1/3/5) → RGBA8 软解。见 docs/steps/M1-static-render.md T3
|
||||
// 自己写 ~150 行绕开 reuse/EterImageLib 的 windows.h 依赖(M1 风险表允许)。
|
||||
// 对拍时两侧都喂软解 RGBA,不被 GPU S3TC 的 bit 级差异污染。
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace mtimage {
|
||||
|
||||
struct Image {
|
||||
uint16_t w = 0, h = 0;
|
||||
std::vector<uint8_t> rgba; // w*h*4,level 0
|
||||
// DXT DDS only: level count 40250 CreateDDSTexture gives the texture (0 = not a DXT
|
||||
// DDS, caller follows the D3DX_DEFAULT full-chain path) and levels 1.. decoded to RGBA8.
|
||||
uint8_t file_mip_levels = 0;
|
||||
std::vector<std::vector<uint8_t>> mips;
|
||||
const char* format = ""; // "DXT1" / "DXT3" / "DXT5" / "BGRA8" / ""
|
||||
bool ok() const { return w && h && rgba.size() == size_t(w) * h * 4; }
|
||||
};
|
||||
|
||||
// 读整个 .dds 文件字节,解 level 0 到 RGBA8(DXT 另解文件内 mip 到 mips)。失败返回 !ok()。
|
||||
Image load_dds(const uint8_t* bytes, size_t len);
|
||||
Image load_dds_path(const char* path);
|
||||
|
||||
} // namespace mtimage
|
||||
+4528
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,461 @@
|
||||
#pragma once
|
||||
// Performance telemetry for the live client (--perf-log or MT_PERF_LOG=1).
|
||||
//
|
||||
// Every frame the host hands PerfLog the frame's wall times and the renderer's running totals; the
|
||||
// script thread's section times come from platform/EterBase/PerfCounters.h. Every interval (2 s) one CSV
|
||||
// row with the window's per-frame averages is appended to perf-YYYYMMDD-HHMMSS.csv and a short summary
|
||||
// goes to logcat (stderr on desktop). A frame over the hitch threshold additionally writes a "# hitch"
|
||||
// comment line with that frame's own breakdown.
|
||||
//
|
||||
// Files: Android <external files dir>/perf (/sdcard/Android/data/<package>/files/perf, pulled with
|
||||
// tools/perf/pull_perf_logs.sh), desktop $MT_PERF_DIR or ./perf. The newest kKeepFiles files are kept.
|
||||
|
||||
#include "platform/EterBase/PerfCounters.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#if defined(__linux__) || defined(__ANDROID__)
|
||||
#include <sched.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#ifdef __ANDROID__
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
namespace native_perf {
|
||||
|
||||
// Running totals the renderer keeps (VulkanWindow::Timings and its upload counters); PerfLog differences
|
||||
// them itself, so they never need resetting.
|
||||
struct RendererTotals {
|
||||
double sync_ms = 0, fence_ms = 0, prepare_ms = 0, geometry_upload_ms = 0, texture_upload_ms = 0;
|
||||
double submit_ms = 0, present_ms = 0, gpu_ms = 0;
|
||||
std::uint64_t gpu_samples = 0;
|
||||
std::uint64_t uploads = 0, uploaded_bytes = 0, texture_uploads = 0, texture_bytes = 0;
|
||||
// The last frame's counts.
|
||||
std::uint64_t draws = 0, skinned_draws = 0, ui_batches = 0, vertices = 0;
|
||||
const std::string* last_texture = nullptr; // name of the texture uploaded last
|
||||
};
|
||||
|
||||
// One battery reading (android_perf::battery_power). power_mw < 0: unknown or on a charger.
|
||||
struct PowerSample {
|
||||
double current_ma = 0, voltage_mv = -1, power_mw = -1;
|
||||
bool plugged = false;
|
||||
};
|
||||
|
||||
struct FrameInput {
|
||||
double frame_ms = 0; // whole loop iteration
|
||||
double app_ms = 0; // PythonBoot::UIUpdate: the script thread's Process() plus the baton handoff
|
||||
double host_ms = 0; // cursor sync, UIRender, audio pump
|
||||
double render_ms = 0; // VulkanWindow::render
|
||||
double pace_ms = 0; // sleep of the frame-rate cap (--fps-cap), outside frame_ms
|
||||
RendererTotals renderer;
|
||||
};
|
||||
|
||||
class PerfLog {
|
||||
public:
|
||||
static constexpr double kIntervalS = 2.0;
|
||||
static constexpr double kHitchMs = 100.0;
|
||||
static constexpr int kKeepFiles = 10;
|
||||
static constexpr double kPowerSampleS = 0.25;
|
||||
|
||||
static bool requested_by_env() {
|
||||
const char* env = std::getenv("MT_PERF_LOG");
|
||||
return env && *env && std::string(env) != "0";
|
||||
}
|
||||
|
||||
bool enabled() const { return file_ != nullptr; }
|
||||
// Display refresh rate in Hz as the OS reports it now (Android Display.getRefreshRate), sampled
|
||||
// once per row; -1 when unknown.
|
||||
void set_refresh_rate_source(std::function<double()> source) { refresh_rate_ = std::move(source); }
|
||||
// The rate the system actually drives the app at (Android Choreographer); can be a divisor of refresh_hz.
|
||||
void set_render_rate_source(std::function<double()> source) { render_rate_ = std::move(source); }
|
||||
// Battery temperature in C (Android: the ACTION_BATTERY_CHANGED sticky intent; sysfs is SELinux-denied).
|
||||
void set_battery_source(std::function<double()> source) { battery_ = std::move(source); }
|
||||
// Sampled every kPowerSampleS and averaged over the row: one reading swings with each frame's burst.
|
||||
void set_power_source(std::function<PowerSample()> source) { power_ = std::move(source); }
|
||||
void set_fps_cap(int cap) { fps_cap_ = cap; }
|
||||
// The frame-rate setting (FrameRateMode.h) and whether the idle rate is in effect, as of the row's end.
|
||||
void set_frame_policy(int mode, bool idle) { fps_mode_ = mode; idle_ = idle; }
|
||||
const std::string& path() const { return path_; }
|
||||
|
||||
// `context` goes into the file header (device, present mode, MSAA, size...).
|
||||
bool open(const std::string& context) {
|
||||
const std::string dir = directory();
|
||||
std::error_code ec;
|
||||
std::filesystem::create_directories(dir, ec);
|
||||
prune(dir);
|
||||
char stamp[32];
|
||||
const std::time_t now = std::time(nullptr);
|
||||
std::tm local{};
|
||||
localtime_r(&now, &local);
|
||||
std::strftime(stamp, sizeof(stamp), "%Y%m%d-%H%M%S", &local);
|
||||
path_ = dir + "/perf-" + stamp + ".csv";
|
||||
file_ = std::fopen(path_.c_str(), "w");
|
||||
if (!file_) {
|
||||
log("perf log: cannot write %s", path_.c_str());
|
||||
return false;
|
||||
}
|
||||
std::fprintf(file_, "# mt_native_render perf log %s\n# %s\n", stamp, context.c_str());
|
||||
std::fprintf(file_, "# per-frame averages over each %.0f s window; *_ms game sections run on the script thread "
|
||||
"inside app_ms; py_ms includes update_game/render_game\n", kIntervalS);
|
||||
std::fprintf(file_,
|
||||
"t_s,map,actors,frames,fps,refresh_hz,render_hz,fps_cap,frame_ms,frame_p95_ms,frame_max_ms,jank33,jank50,"
|
||||
"app_ms,process_ms,handoff_ms,net_ms,camera_ms,ui_update_ms,update_game_ms,render_begin_ms,ui_render_ms,"
|
||||
"render_game_ms,shadow_raster_ms,py_ms,py_calls,py_missing,host_ms,render_ms,pace_ms,sync_ms,fence_ms,"
|
||||
"prepare_ms,geo_upload_ms,tex_upload_ms,"
|
||||
"submit_ms,present_ms,gpu_ms,draws,skinned_draws,ui_batches,vertices,geo_uploads,geo_kb,tex_uploads,tex_kb,"
|
||||
"script_cpu,main_cpu,cpu_mhz,cpu_max_mhz,thermal,batt_c,rss_mb,batt_ma,batt_mv,power_mw,plugged,fps_mode,idle,"
|
||||
"last_texture\n");
|
||||
std::fflush(file_);
|
||||
MtPerf::Enabled() = true;
|
||||
MtPerf::Take();
|
||||
start_ = window_start_ = std::chrono::steady_clock::now();
|
||||
log("perf log: %s", path_.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
~PerfLog() {
|
||||
if (file_) std::fclose(file_);
|
||||
MtPerf::Enabled() = false;
|
||||
}
|
||||
|
||||
// Call once per frame. `map_name` is only asked for when a row is written.
|
||||
template <typename MapName>
|
||||
void frame(const FrameInput& in, MapName&& map_name) {
|
||||
if (!file_) return;
|
||||
const MtPerf::SCounters game = MtPerf::Take();
|
||||
const RendererTotals& r = in.renderer;
|
||||
const RendererTotals& p = have_prev_ ? prev_ : r;
|
||||
Window f;
|
||||
f.frames = 1;
|
||||
f.frame_ms = in.frame_ms;
|
||||
f.app_ms = in.app_ms;
|
||||
f.host_ms = in.host_ms;
|
||||
f.render_ms = in.render_ms;
|
||||
f.pace_ms = in.pace_ms;
|
||||
for (int i = 0; i < MtPerf::SECTION_COUNT; ++i) f.game_ms[i] = game.ms[i];
|
||||
f.py_calls = game.calls[MtPerf::SECTION_PYTHON];
|
||||
f.py_missing = game.pyMissing;
|
||||
f.sync_ms = r.sync_ms - p.sync_ms;
|
||||
f.fence_ms = r.fence_ms - p.fence_ms;
|
||||
f.prepare_ms = r.prepare_ms - p.prepare_ms;
|
||||
f.geo_upload_ms = r.geometry_upload_ms - p.geometry_upload_ms;
|
||||
f.tex_upload_ms = r.texture_upload_ms - p.texture_upload_ms;
|
||||
f.submit_ms = r.submit_ms - p.submit_ms;
|
||||
f.present_ms = r.present_ms - p.present_ms;
|
||||
f.gpu_ms = r.gpu_ms - p.gpu_ms;
|
||||
f.gpu_samples = r.gpu_samples - p.gpu_samples;
|
||||
f.geo_uploads = r.uploads - p.uploads;
|
||||
f.geo_bytes = r.uploaded_bytes - p.uploaded_bytes;
|
||||
f.tex_uploads = r.texture_uploads - p.texture_uploads;
|
||||
f.tex_bytes = r.texture_bytes - p.texture_bytes;
|
||||
prev_ = r;
|
||||
have_prev_ = true;
|
||||
|
||||
if (in.frame_ms >= kHitchMs) write_hitch(f, game);
|
||||
window_.add(f);
|
||||
frame_times_.push_back(in.frame_ms);
|
||||
draws_ = r.draws;
|
||||
skinned_draws_ = r.skinned_draws;
|
||||
ui_batches_ = r.ui_batches;
|
||||
vertices_ = r.vertices;
|
||||
if (f.tex_uploads && r.last_texture) last_texture_ = *r.last_texture;
|
||||
actors_ = game.actorCount;
|
||||
script_cpu_ = game.scriptCpu;
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (power_ && std::chrono::duration<double>(now - power_sampled_at_).count() >= kPowerSampleS) {
|
||||
power_sampled_at_ = now;
|
||||
const PowerSample sample = power_();
|
||||
power_sum_.current_ma += sample.current_ma;
|
||||
power_sum_.voltage_mv += sample.voltage_mv;
|
||||
if (sample.power_mw >= 0) {
|
||||
power_sum_.power_mw += sample.power_mw;
|
||||
++power_valid_;
|
||||
}
|
||||
power_sum_.plugged = power_sum_.plugged || sample.plugged;
|
||||
++power_samples_;
|
||||
}
|
||||
const double elapsed = std::chrono::duration<double>(now - window_start_).count();
|
||||
if (elapsed < kIntervalS) return;
|
||||
write_row(elapsed, map_name());
|
||||
power_sum_ = {};
|
||||
power_sum_.voltage_mv = 0;
|
||||
power_sum_.power_mw = 0;
|
||||
power_samples_ = power_valid_ = 0;
|
||||
window_ = {};
|
||||
frame_times_.clear();
|
||||
window_start_ = now;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Window {
|
||||
std::uint64_t frames = 0;
|
||||
double frame_ms = 0, app_ms = 0, host_ms = 0, render_ms = 0, pace_ms = 0;
|
||||
double game_ms[MtPerf::SECTION_COUNT] = {};
|
||||
std::uint64_t py_calls = 0, py_missing = 0;
|
||||
double sync_ms = 0, fence_ms = 0, prepare_ms = 0, geo_upload_ms = 0, tex_upload_ms = 0, submit_ms = 0;
|
||||
double present_ms = 0, gpu_ms = 0;
|
||||
std::uint64_t gpu_samples = 0;
|
||||
std::uint64_t geo_uploads = 0, geo_bytes = 0, tex_uploads = 0, tex_bytes = 0;
|
||||
|
||||
void add(const Window& o) {
|
||||
frames += o.frames;
|
||||
frame_ms += o.frame_ms; app_ms += o.app_ms; host_ms += o.host_ms; render_ms += o.render_ms;
|
||||
pace_ms += o.pace_ms; fence_ms += o.fence_ms;
|
||||
for (int i = 0; i < MtPerf::SECTION_COUNT; ++i) game_ms[i] += o.game_ms[i];
|
||||
py_calls += o.py_calls; py_missing += o.py_missing;
|
||||
sync_ms += o.sync_ms; prepare_ms += o.prepare_ms; geo_upload_ms += o.geo_upload_ms;
|
||||
tex_upload_ms += o.tex_upload_ms; submit_ms += o.submit_ms; present_ms += o.present_ms;
|
||||
gpu_ms += o.gpu_ms; gpu_samples += o.gpu_samples;
|
||||
geo_uploads += o.geo_uploads; geo_bytes += o.geo_bytes;
|
||||
tex_uploads += o.tex_uploads; tex_bytes += o.tex_bytes;
|
||||
}
|
||||
};
|
||||
|
||||
static std::string directory() {
|
||||
if (const char* env = std::getenv("MT_PERF_DIR"); env && *env) return env;
|
||||
#ifdef __ANDROID__
|
||||
if (const char* ext = SDL_GetAndroidExternalStoragePath(); ext && *ext) return std::string(ext) + "/perf";
|
||||
if (const char* internal = SDL_GetAndroidInternalStoragePath(); internal && *internal)
|
||||
return std::string(internal) + "/perf";
|
||||
#endif
|
||||
return "perf";
|
||||
}
|
||||
|
||||
static void prune(const std::string& dir) {
|
||||
std::error_code ec;
|
||||
std::vector<std::filesystem::path> files;
|
||||
for (const auto& entry : std::filesystem::directory_iterator(dir, ec)) {
|
||||
const std::string name = entry.path().filename().string();
|
||||
if (name.rfind("perf-", 0) == 0 && entry.path().extension() == ".csv") files.push_back(entry.path());
|
||||
}
|
||||
std::sort(files.begin(), files.end());
|
||||
// Leave room for the file about to be opened.
|
||||
for (std::size_t i = 0; i + kKeepFiles <= files.size(); ++i) std::filesystem::remove(files[i], ec);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
static void log(const char* format, Args... args) {
|
||||
#ifdef __ANDROID__
|
||||
SDL_Log(format, args...);
|
||||
#else
|
||||
std::fprintf(stderr, format, args...);
|
||||
std::fputc('\n', stderr);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int current_cpu() {
|
||||
#if defined(__linux__) || defined(__ANDROID__)
|
||||
return sched_getcpu();
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
static long read_long(const char* path) {
|
||||
std::FILE* f = std::fopen(path, "r");
|
||||
if (!f) return -1;
|
||||
long value = -1;
|
||||
if (std::fscanf(f, "%ld", &value) != 1) value = -1;
|
||||
std::fclose(f);
|
||||
return value;
|
||||
}
|
||||
|
||||
// Current clock of every CPU in MHz, "cpu0/cpu1/...", or "" where sysfs is closed.
|
||||
static std::string cpu_mhz() {
|
||||
std::string out;
|
||||
#if defined(__linux__) || defined(__ANDROID__)
|
||||
for (int cpu = 0; cpu < 16; ++cpu) {
|
||||
char path[96];
|
||||
std::snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_cur_freq", cpu);
|
||||
const long khz = read_long(path);
|
||||
if (khz < 0) {
|
||||
if (cpu >= 8 || access(path, F_OK) != 0) break;
|
||||
continue;
|
||||
}
|
||||
if (!out.empty()) out += '/';
|
||||
out += std::to_string(khz / 1000);
|
||||
}
|
||||
#endif
|
||||
return out;
|
||||
}
|
||||
|
||||
// Each cpufreq policy's current ceiling (scaling_max_freq): the vendor's thermal throttling shows up
|
||||
// here long before AThermal reports anything.
|
||||
static std::string cpu_max_mhz() {
|
||||
std::string out;
|
||||
#if defined(__linux__) || defined(__ANDROID__)
|
||||
for (int policy = 0; policy < 16; ++policy) {
|
||||
char path[96];
|
||||
std::snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpufreq/policy%d/scaling_max_freq", policy);
|
||||
const long khz = read_long(path);
|
||||
if (khz < 0) continue;
|
||||
if (!out.empty()) out += '/';
|
||||
out += std::to_string(khz / 1000);
|
||||
}
|
||||
#endif
|
||||
return out;
|
||||
}
|
||||
|
||||
// AThermal_getCurrentThermalStatus (API 30+, looked up at run time since minSdk is lower):
|
||||
// 0 none, 1 light, 2 moderate, 3 severe, 4 critical, 5 emergency, 6 shutdown; -1 unknown.
|
||||
static int thermal_status() {
|
||||
#ifdef __ANDROID__
|
||||
using Acquire = void* (*)();
|
||||
using Status = int (*)(void*);
|
||||
static void* manager = nullptr;
|
||||
static Status status = nullptr;
|
||||
static bool looked_up = false;
|
||||
if (!looked_up) {
|
||||
looked_up = true;
|
||||
if (void* lib = dlopen("libandroid.so", RTLD_NOW)) {
|
||||
const auto acquire = reinterpret_cast<Acquire>(dlsym(lib, "AThermal_acquireManager"));
|
||||
status = reinterpret_cast<Status>(dlsym(lib, "AThermal_getCurrentThermalStatus"));
|
||||
if (acquire && status) manager = acquire();
|
||||
}
|
||||
}
|
||||
if (manager && status) return status(manager);
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
|
||||
double battery_celsius() const {
|
||||
if (battery_) return battery_();
|
||||
#if defined(__linux__) || defined(__ANDROID__)
|
||||
const long tenths = read_long("/sys/class/power_supply/battery/temp");
|
||||
if (tenths > 0) return double(tenths) / 10.0; // -1: unreadable (SELinux on most phones)
|
||||
#endif
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
static double rss_mb() {
|
||||
#if defined(__linux__) || defined(__ANDROID__)
|
||||
std::FILE* f = std::fopen("/proc/self/statm", "r");
|
||||
if (!f) return -1.0;
|
||||
long pages = 0, resident = 0;
|
||||
const int read = std::fscanf(f, "%ld %ld", &pages, &resident);
|
||||
std::fclose(f);
|
||||
if (read == 2) return double(resident) * double(sysconf(_SC_PAGESIZE)) / (1024.0 * 1024.0);
|
||||
#endif
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
static std::string csv_field(std::string text) {
|
||||
for (char& c : text)
|
||||
if (c == ',' || c == '\n' || c == '\r') c = ' ';
|
||||
return text;
|
||||
}
|
||||
|
||||
void write_row(double elapsed_s, const std::string& map) {
|
||||
const Window& w = window_;
|
||||
if (!w.frames) return;
|
||||
const double n = double(w.frames);
|
||||
std::vector<double> sorted = frame_times_;
|
||||
std::sort(sorted.begin(), sorted.end());
|
||||
const double p95 = sorted[std::min(sorted.size() - 1, std::size_t(0.95 * double(sorted.size())))];
|
||||
int jank33 = 0, jank50 = 0;
|
||||
for (double ms : frame_times_) {
|
||||
jank33 += ms > 33.4;
|
||||
jank50 += ms > 50.0;
|
||||
}
|
||||
const auto g = [&](MtPerf::ESection s) { return w.game_ms[s] / n; };
|
||||
const double fps = n / elapsed_s;
|
||||
const double gpu = w.gpu_samples ? w.gpu_ms / double(w.gpu_samples) : 0.0;
|
||||
const double t = std::chrono::duration<double>(std::chrono::steady_clock::now() - start_).count();
|
||||
const std::string mhz = cpu_mhz();
|
||||
const std::string max_mhz = cpu_max_mhz();
|
||||
const int thermal = thermal_status();
|
||||
const double batt = battery_celsius();
|
||||
const double rss = rss_mb();
|
||||
const int main_cpu = current_cpu();
|
||||
const double refresh = refresh_rate_ ? refresh_rate_() : -1.0;
|
||||
const double render_rate = render_rate_ ? render_rate_() : -1.0;
|
||||
const double ns = double(power_samples_);
|
||||
const double batt_ma = power_samples_ ? power_sum_.current_ma / ns : -1.0;
|
||||
const double batt_mv = power_samples_ ? power_sum_.voltage_mv / ns : -1.0;
|
||||
// Only when every sample of the row was off the charger.
|
||||
const double power_mw = power_valid_ && power_valid_ == power_samples_ ? power_sum_.power_mw / ns : -1.0;
|
||||
std::fprintf(file_,
|
||||
"%.1f,%s,%d,%llu,%.1f,%.1f,%.1f,%d,%.2f,%.2f,%.2f,%d,%d,"
|
||||
"%.2f,%.2f,%.2f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,"
|
||||
"%.3f,%.3f,%.3f,%.1f,%.1f,%.3f,%.2f,%.2f,%.2f,%.2f,"
|
||||
"%.2f,%.3f,%.3f,"
|
||||
"%.3f,%.3f,%.2f,%llu,%llu,%llu,%llu,%.2f,%.1f,%.2f,%.1f,"
|
||||
"%d,%d,%s,%s,%d,%.1f,%.0f,%.0f,%.0f,%.0f,%d,%d,%d,%s\n",
|
||||
t, csv_field(map).c_str(), actors_, (unsigned long long)w.frames, fps, refresh, render_rate, fps_cap_, w.frame_ms / n,
|
||||
p95, sorted.back(), jank33, jank50,
|
||||
w.app_ms / n, g(MtPerf::SECTION_PROCESS), (w.app_ms - w.game_ms[MtPerf::SECTION_PROCESS]) / n,
|
||||
g(MtPerf::SECTION_NETWORK), g(MtPerf::SECTION_CAMERA), g(MtPerf::SECTION_UI_UPDATE),
|
||||
g(MtPerf::SECTION_UPDATE_GAME), g(MtPerf::SECTION_RENDER_BEGIN), g(MtPerf::SECTION_UI_RENDER),
|
||||
g(MtPerf::SECTION_RENDER_GAME), g(MtPerf::SECTION_SHADOW_RASTER), g(MtPerf::SECTION_PYTHON),
|
||||
double(w.py_calls) / n,
|
||||
double(w.py_missing) / n, w.host_ms / n, w.render_ms / n, w.pace_ms / n, w.sync_ms / n, w.fence_ms / n,
|
||||
w.prepare_ms / n, w.geo_upload_ms / n, w.tex_upload_ms / n,
|
||||
w.submit_ms / n, w.present_ms / n, gpu, (unsigned long long)draws_, (unsigned long long)skinned_draws_,
|
||||
(unsigned long long)ui_batches_, (unsigned long long)vertices_, double(w.geo_uploads) / n,
|
||||
double(w.geo_bytes) / 1024.0 / n, double(w.tex_uploads) / n, double(w.tex_bytes) / 1024.0 / n,
|
||||
script_cpu_, main_cpu, mhz.c_str(), max_mhz.c_str(), thermal, batt, rss, batt_ma, batt_mv, power_mw,
|
||||
int(power_sum_.plugged), fps_mode_, int(idle_), csv_field(last_texture_).c_str());
|
||||
std::fflush(file_);
|
||||
if (!w.tex_uploads) last_texture_.clear();
|
||||
log("perf fps=%.1f @%.0fHz/%.0f cap=%d frame=%.1f/p95 %.1f/max %.1f jank33=%d | app=%.1f (upd=%.1f rnd=%.1f "
|
||||
"shadow=%.1f py=%.1f) | render=%.1f (sync=%.1f fence=%.1f prep=%.1f up=%.1f/%.1f) pace=%.1f gpu=%.1f | "
|
||||
"draws=%llu actors=%d map=%s thermal=%d batt=%.1f cpu=%s max=%s tex_uploads=%.2f | pwr=%.0fmW %.0fmA%s mode=%d%s "
|
||||
"last_tex=%s",
|
||||
fps, refresh, render_rate, fps_cap_, w.frame_ms / n, p95, sorted.back(), jank33, w.app_ms / n,
|
||||
g(MtPerf::SECTION_UPDATE_GAME), g(MtPerf::SECTION_RENDER_GAME), g(MtPerf::SECTION_SHADOW_RASTER),
|
||||
g(MtPerf::SECTION_PYTHON), w.render_ms / n, w.sync_ms / n, w.fence_ms / n, w.prepare_ms / n,
|
||||
w.geo_upload_ms / n, w.tex_upload_ms / n, w.pace_ms / n, gpu, (unsigned long long)draws_, actors_,
|
||||
map.c_str(), thermal, batt, mhz.c_str(), max_mhz.c_str(), double(w.tex_uploads) / n, power_mw, batt_ma,
|
||||
power_sum_.plugged ? " plugged" : "", fps_mode_, idle_ ? " idle" : "", last_texture_.c_str());
|
||||
}
|
||||
|
||||
void write_hitch(const Window& f, const MtPerf::SCounters& game) {
|
||||
const double t = std::chrono::duration<double>(std::chrono::steady_clock::now() - start_).count();
|
||||
std::fprintf(file_,
|
||||
"# hitch t=%.2f frame=%.1f app=%.1f process=%.1f net=%.1f camera=%.1f ui_update=%.1f update_game=%.1f "
|
||||
"ui_render=%.1f render_game=%.1f shadow=%.1f py=%.1f host=%.1f render=%.1f sync=%.1f prepare=%.1f geo_upload=%.1f(%llu) "
|
||||
"tex_upload=%.1f(%llu, %.0f KB) submit=%.1f present=%.1f\n",
|
||||
t, f.frame_ms, f.app_ms, game.ms[MtPerf::SECTION_PROCESS], game.ms[MtPerf::SECTION_NETWORK],
|
||||
game.ms[MtPerf::SECTION_CAMERA], game.ms[MtPerf::SECTION_UI_UPDATE], game.ms[MtPerf::SECTION_UPDATE_GAME],
|
||||
game.ms[MtPerf::SECTION_UI_RENDER], game.ms[MtPerf::SECTION_RENDER_GAME],
|
||||
game.ms[MtPerf::SECTION_SHADOW_RASTER], game.ms[MtPerf::SECTION_PYTHON],
|
||||
f.host_ms, f.render_ms, f.sync_ms, f.prepare_ms, f.geo_upload_ms, (unsigned long long)f.geo_uploads,
|
||||
f.tex_upload_ms, (unsigned long long)f.tex_uploads, double(f.tex_bytes) / 1024.0, f.submit_ms, f.present_ms);
|
||||
}
|
||||
|
||||
std::FILE* file_ = nullptr;
|
||||
std::string path_;
|
||||
std::chrono::steady_clock::time_point start_{}, window_start_{};
|
||||
Window window_;
|
||||
std::vector<double> frame_times_;
|
||||
RendererTotals prev_;
|
||||
bool have_prev_ = false;
|
||||
std::uint64_t draws_ = 0, skinned_draws_ = 0, ui_batches_ = 0, vertices_ = 0;
|
||||
int actors_ = 0, script_cpu_ = -1;
|
||||
int fps_cap_ = 0;
|
||||
std::function<double()> refresh_rate_;
|
||||
std::function<double()> render_rate_;
|
||||
std::function<double()> battery_;
|
||||
std::function<PowerSample()> power_;
|
||||
std::chrono::steady_clock::time_point power_sampled_at_{};
|
||||
PowerSample power_sum_{0, 0, 0, false};
|
||||
int power_samples_ = 0, power_valid_ = 0;
|
||||
int fps_mode_ = -1;
|
||||
bool idle_ = false;
|
||||
std::string last_texture_;
|
||||
};
|
||||
|
||||
} // namespace native_perf
|
||||
@@ -0,0 +1,130 @@
|
||||
#version 450
|
||||
layout(location = 0) in vec4 in_color;
|
||||
layout(location = 1) in vec2 in_uv;
|
||||
layout(location = 2) in vec2 in_mask_uv;
|
||||
layout(location = 3) in float in_fog;
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
layout(set = 0, binding = 0) uniform sampler2D tex_sampler;
|
||||
layout(set = 0, binding = 1) uniform sampler2D mask_sampler;
|
||||
|
||||
layout(push_constant) uniform DrawConstants {
|
||||
mat4 mvp;
|
||||
vec4 params; // bone base + 1, pretransformed, UI mask, unused
|
||||
} draw;
|
||||
|
||||
layout(set = 1, binding = 1, std430) readonly buffer FixedFunctionState {
|
||||
uvec4 stage_color[2];
|
||||
uvec4 stage_alpha[2];
|
||||
vec4 fog_color;
|
||||
vec4 fog_params;
|
||||
vec4 texture_factor;
|
||||
mat4 world_view;
|
||||
uvec4 flags;
|
||||
mat4 normal_matrix;
|
||||
uvec4 lighting_flags;
|
||||
uvec4 material_sources;
|
||||
uvec4 alpha_test; // ALPHATESTENABLE, ALPHAFUNC, ALPHAREF, unused
|
||||
} fixed_state;
|
||||
|
||||
vec4 stage_arg(uint selector, vec4 diffuse, vec4 current, vec4 texel) {
|
||||
uint source = selector & 15u;
|
||||
vec4 value = source == 0u ? diffuse :
|
||||
source == 1u ? current :
|
||||
source == 2u ? texel :
|
||||
source == 3u ? fixed_state.texture_factor : vec4(1.0);
|
||||
if ((selector & 16u) != 0u) value = vec4(1.0) - value;
|
||||
if ((selector & 32u) != 0u) value.rgb = vec3(value.a);
|
||||
return value;
|
||||
}
|
||||
|
||||
vec4 apply_op(uint op, vec4 a, vec4 b, vec4 current, vec4 diffuse, vec4 texel) {
|
||||
if (op == 2u) return a; // SELECTARG1
|
||||
if (op == 3u) return b; // SELECTARG2
|
||||
if (op == 4u) return a * b; // MODULATE
|
||||
if (op == 5u) return 2.0 * a * b; // MODULATE2X
|
||||
if (op == 6u) return 4.0 * a * b; // MODULATE4X
|
||||
if (op == 7u) return a + b; // ADD
|
||||
if (op == 8u) return a + b - vec4(0.5); // ADDSIGNED
|
||||
if (op == 9u) return 2.0 * (a + b - vec4(0.5)); // ADDSIGNED2X
|
||||
if (op == 10u) return a - b; // SUBTRACT
|
||||
if (op == 11u) return a + b - a * b; // ADDSMOOTH
|
||||
if (op == 12u) return mix(b, a, diffuse.a);
|
||||
if (op == 13u) return mix(b, a, texel.a);
|
||||
if (op == 14u) return mix(b, a, fixed_state.texture_factor.a);
|
||||
if (op == 15u) return a + b * (1.0 - texel.a);
|
||||
if (op == 16u) return mix(b, a, current.a);
|
||||
if (op == 18u) return vec4(a.rgb + a.a * b.rgb, a.a);
|
||||
if (op == 19u) return vec4(a.rgb * b.rgb + vec3(a.a), a.a);
|
||||
if (op == 20u) return vec4((1.0 - a.a) * b.rgb + a.rgb, a.a);
|
||||
if (op == 21u) return vec4((vec3(1.0) - a.rgb) * b.rgb + vec3(a.a), a.a);
|
||||
return current;
|
||||
}
|
||||
|
||||
vec4 apply_stage(uvec4 color_state, uvec4 alpha_state,
|
||||
vec4 diffuse, vec4 current, vec4 texel) {
|
||||
if (color_state.x <= 1u) return current;
|
||||
vec4 color_arg1 = stage_arg(color_state.y, diffuse, current, texel);
|
||||
vec4 color_arg2 = stage_arg(color_state.z, diffuse, current, texel);
|
||||
vec4 result = apply_op(color_state.x, color_arg1, color_arg2, current, diffuse, texel);
|
||||
if (alpha_state.x > 1u) {
|
||||
vec4 alpha_arg1 = stage_arg(alpha_state.y, diffuse, current, texel);
|
||||
vec4 alpha_arg2 = stage_arg(alpha_state.z, diffuse, current, texel);
|
||||
result.a = apply_op(alpha_state.x, alpha_arg1, alpha_arg2, current, diffuse, texel).a;
|
||||
} else if (alpha_state.x == 1u) {
|
||||
result.a = current.a;
|
||||
}
|
||||
return clamp(result, 0.0, 1.0);
|
||||
}
|
||||
|
||||
void main() {
|
||||
if (fixed_state.flags.x != 0u) {
|
||||
// D3D8 texture stage cascade: stage 1 runs unless its COLOROP is D3DTOP_DISABLE.
|
||||
vec4 diffuse = in_color;
|
||||
vec4 color = apply_stage(fixed_state.stage_color[0], fixed_state.stage_alpha[0],
|
||||
diffuse, diffuse, texture(tex_sampler, in_uv));
|
||||
if (fixed_state.stage_color[0].x > 1u && fixed_state.stage_color[1].x > 1u)
|
||||
color = apply_stage(fixed_state.stage_color[1], fixed_state.stage_alpha[1],
|
||||
diffuse, color, texture(mask_sampler, in_mask_uv));
|
||||
if (fixed_state.alpha_test.x != 0u) {
|
||||
// D3DRS_ALPHAREF is an 8-bit reference compared against the 8-bit pixel alpha.
|
||||
uint a = uint(clamp(color.a, 0.0, 1.0) * 255.0 + 0.5);
|
||||
uint ref = fixed_state.alpha_test.z & 255u;
|
||||
uint func = fixed_state.alpha_test.y;
|
||||
bool passes = func == 1u ? false :
|
||||
func == 2u ? a < ref :
|
||||
func == 3u ? a == ref :
|
||||
func == 4u ? a <= ref :
|
||||
func == 5u ? a > ref :
|
||||
func == 6u ? a != ref :
|
||||
func == 7u ? a >= ref : true;
|
||||
if (!passes) discard;
|
||||
}
|
||||
color.rgb = mix(fixed_state.fog_color.rgb, color.rgb, in_fog);
|
||||
out_color = color;
|
||||
return;
|
||||
}
|
||||
// 2D UI batch (UIRenderCommand): vertex color times texture, optionally clipped by a mask.
|
||||
// The UI is laid out in logical pixels and magnified onto a HiDPI swapchain. Plain bilinear
|
||||
// smears each texel over the scale factor; "sharp bilinear" keeps texels square and only blends
|
||||
// across a one-pixel seam. Minified or 1:1 texels keep the original bilinear lookup.
|
||||
vec2 uv = in_uv;
|
||||
vec2 tex_size = vec2(textureSize(tex_sampler, 0));
|
||||
vec2 texel = uv * tex_size;
|
||||
vec2 texels_per_pixel = fwidth(texel);
|
||||
if (texels_per_pixel.x > 0.0 && texels_per_pixel.y > 0.0 &&
|
||||
texels_per_pixel.x < 0.99 && texels_per_pixel.y < 0.99) {
|
||||
vec2 seam = floor(texel + 0.5);
|
||||
texel = seam + clamp((texel - seam) / texels_per_pixel, -0.5, 0.5);
|
||||
uv = texel / tex_size;
|
||||
}
|
||||
vec4 color = in_color * texture(tex_sampler, uv);
|
||||
if (draw.params.z > 0.5) {
|
||||
if (in_mask_uv.x < 0.0 || in_mask_uv.x > 1.0 || in_mask_uv.y < 0.0 || in_mask_uv.y > 1.0)
|
||||
discard;
|
||||
vec4 mask_val = texture(mask_sampler, in_mask_uv);
|
||||
color.rgb *= mask_val.rgb;
|
||||
color.a = mask_val.a * in_color.a;
|
||||
}
|
||||
out_color = color;
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
#version 450
|
||||
layout(location = 0) in vec3 in_position;
|
||||
layout(location = 1) in vec3 in_normal;
|
||||
layout(location = 2) in vec2 in_uv;
|
||||
layout(location = 3) in vec4 in_color;
|
||||
layout(location = 4) in uvec4 in_joints;
|
||||
layout(location = 5) in vec4 in_weights;
|
||||
layout(location = 6) in vec2 in_mask_uv;
|
||||
layout(location = 7) in float in_rhw;
|
||||
layout(location = 8) in float in_vertex_fog;
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
layout(location = 1) out vec2 out_uv;
|
||||
layout(location = 2) out vec2 out_mask_uv;
|
||||
layout(location = 3) out float out_fog;
|
||||
|
||||
layout(set = 1, binding = 0, std430) readonly buffer BonePalette {
|
||||
mat4 bones[];
|
||||
} bone_palette;
|
||||
|
||||
layout(push_constant) uniform DrawConstants {
|
||||
mat4 mvp;
|
||||
vec4 params; // bone base + 1 (0 = none), pretransformed, UI mask, unused
|
||||
} draw;
|
||||
|
||||
// D3D8 fixed-function vertex state; see FixedFunctionState in main.cpp. Lights are in camera space.
|
||||
layout(set = 1, binding = 1, std430) readonly buffer FixedFunctionState {
|
||||
uvec4 stage_color[2]; // op, arg1, arg2, D3DTSS_TEXCOORDINDEX
|
||||
uvec4 stage_alpha[2]; // op, arg1, arg2, D3DTSS_TEXTURETRANSFORMFLAGS
|
||||
vec4 fog_color;
|
||||
vec4 fog_params;
|
||||
vec4 texture_factor;
|
||||
mat4 world_view;
|
||||
uvec4 flags;
|
||||
mat4 normal_matrix;
|
||||
uvec4 lighting_flags; // LIGHTING, COLORVERTEX, vertex has diffuse, NORMALIZENORMALS
|
||||
uvec4 material_sources; // DIFFUSE, AMBIENT, EMISSIVE material source, LOCALVIEWER
|
||||
uvec4 alpha_test;
|
||||
vec4 material_diffuse;
|
||||
vec4 material_ambient;
|
||||
vec4 material_emissive;
|
||||
vec4 global_ambient;
|
||||
mat4 texture_matrix[2];
|
||||
vec4 light_position_type[8];
|
||||
vec4 light_direction_range[8];
|
||||
vec4 light_diffuse[8];
|
||||
vec4 light_ambient[8];
|
||||
vec4 light_attenuation[8];
|
||||
vec4 light_spot[8];
|
||||
} fixed_state;
|
||||
|
||||
// D3DMCS_COLOR1 picks the vertex diffuse color when COLORVERTEX is on and the vertex has one.
|
||||
vec4 material_color(uint source, vec4 material) {
|
||||
if (fixed_state.lighting_flags.y != 0u && fixed_state.lighting_flags.z != 0u && source == 1u)
|
||||
return in_color;
|
||||
return material;
|
||||
}
|
||||
|
||||
// D3D8 texture coordinate processing: D3DTSS_TEXCOORDINDEX generation, then D3DTS_TEXTUREn.
|
||||
vec2 stage_texcoord(int stage, bool pretransformed, vec3 eye, vec3 n) {
|
||||
uint tci = fixed_state.stage_color[stage].w;
|
||||
uint gen = tci & 0xFFFF0000u;
|
||||
uint set_index = tci & 0xFFFFu;
|
||||
vec4 coord = vec4(0.0, 0.0, 1.0, 0.0);
|
||||
if (gen == 0u || pretransformed) {
|
||||
if (set_index == 0u) coord.xy = in_uv;
|
||||
else if (set_index == 1u) coord.xy = in_mask_uv;
|
||||
} else if (gen == 0x10000u) { // CAMERASPACENORMAL
|
||||
coord = vec4(n, 1.0);
|
||||
} else if (gen == 0x20000u) { // CAMERASPACEPOSITION
|
||||
coord = vec4(eye, 1.0);
|
||||
} else if (gen == 0x30000u) { // CAMERASPACEREFLECTIONVECTOR
|
||||
vec3 e = fixed_state.material_sources.w != 0u
|
||||
? (length(eye) > 0.0 ? -normalize(eye) : vec3(0.0)) : vec3(0.0, 0.0, -1.0);
|
||||
coord = vec4(2.0 * dot(e, n) * n - e, 1.0);
|
||||
}
|
||||
// Pretransformed (XYZRHW) vertices bypass D3D8 T&L, texture transforms included
|
||||
// (RecordingDevice's CPU texcoord path does the same).
|
||||
uint transform_flags = pretransformed ? 0u : fixed_state.stage_alpha[stage].w;
|
||||
uint elements = transform_flags & 0xFFu;
|
||||
vec4 r = coord;
|
||||
if (elements != 0u)
|
||||
r = fixed_state.texture_matrix[stage] * coord;
|
||||
if ((transform_flags & 0x100u) != 0u && elements >= 2u && elements <= 4u && r[elements - 1u] != 0.0)
|
||||
r.xy /= r[elements - 1u];
|
||||
return r.xy;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec3 pos = in_position;
|
||||
vec3 nrm = in_normal;
|
||||
if (draw.params.x > 0.5) {
|
||||
uint bone_base = uint(draw.params.x - 0.5);
|
||||
vec3 skinned_pos = vec3(0.0);
|
||||
vec3 skinned_nrm = vec3(0.0);
|
||||
float total_w = 0.0;
|
||||
for (int k = 0; k < 4; ++k) {
|
||||
float w = in_weights[k];
|
||||
if (w > 0.0) {
|
||||
mat4 B = bone_palette.bones[bone_base + in_joints[k]];
|
||||
skinned_pos += w * (B * vec4(pos, 1.0)).xyz;
|
||||
skinned_nrm += w * (mat3(B) * nrm);
|
||||
total_w += w;
|
||||
}
|
||||
}
|
||||
if (total_w > 0.0) {
|
||||
pos = skinned_pos;
|
||||
nrm = skinned_nrm;
|
||||
}
|
||||
}
|
||||
bool pretransformed = draw.params.y > 0.5;
|
||||
// D3D row-vector matrices are uploaded row-major. GLSL reads the bytes as their transpose.
|
||||
gl_Position = draw.mvp * vec4(pos, 1.0);
|
||||
if (pretransformed) {
|
||||
// Vertices behind the eye carry a negative rhw (STP terrain): rebuilding clip space
|
||||
// with w = 1/rhw < 0 lets the clipper drop them instead of drawing w = 1 spikes.
|
||||
float clip_w = in_rhw != 0.0 ? 1.0 / in_rhw : 1.0;
|
||||
gl_Position.xyz *= clip_w;
|
||||
gl_Position.w = clip_w;
|
||||
}
|
||||
gl_Position.y = -gl_Position.y;
|
||||
|
||||
// A vertex without D3DFVF_DIFFUSE carries opaque white (upload_geometry).
|
||||
out_color = in_color;
|
||||
if (fixed_state.flags.x == 0u) {
|
||||
// 2D UI batch.
|
||||
out_uv = in_uv;
|
||||
out_mask_uv = in_mask_uv;
|
||||
out_fog = 1.0;
|
||||
return;
|
||||
}
|
||||
|
||||
vec3 eye = (fixed_state.world_view * vec4(pos, 1.0)).xyz;
|
||||
vec3 n = mat3(fixed_state.normal_matrix) * nrm;
|
||||
if (fixed_state.lighting_flags.w != 0u && length(n) > 0.0)
|
||||
n = normalize(n);
|
||||
|
||||
if (fixed_state.lighting_flags.x != 0u && !pretransformed) {
|
||||
vec4 mat_diffuse = material_color(fixed_state.material_sources.x, fixed_state.material_diffuse);
|
||||
vec4 mat_ambient = material_color(fixed_state.material_sources.y, fixed_state.material_ambient);
|
||||
vec4 mat_emissive = material_color(fixed_state.material_sources.z, fixed_state.material_emissive);
|
||||
vec3 ambient_sum = fixed_state.global_ambient.rgb;
|
||||
vec3 diffuse_sum = vec3(0.0);
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
int type = int(fixed_state.light_position_type[i].w + 0.5);
|
||||
if (type == 0) continue;
|
||||
vec3 L;
|
||||
float strength = 1.0;
|
||||
if (type == 3) { // D3DLIGHT_DIRECTIONAL
|
||||
L = -fixed_state.light_direction_range[i].xyz;
|
||||
} else { // D3DLIGHT_POINT / D3DLIGHT_SPOT
|
||||
vec3 to_light = fixed_state.light_position_type[i].xyz - eye;
|
||||
float d = length(to_light);
|
||||
if (d > fixed_state.light_direction_range[i].w)
|
||||
continue;
|
||||
L = d > 0.0 ? to_light / d : vec3(0.0);
|
||||
vec4 attenuation = fixed_state.light_attenuation[i];
|
||||
float denominator = attenuation.x + attenuation.y * d + attenuation.z * d * d;
|
||||
strength = denominator != 0.0 ? 1.0 / denominator : 1.0;
|
||||
if (type == 2) {
|
||||
float rho = dot(-L, fixed_state.light_direction_range[i].xyz);
|
||||
float cos_theta = fixed_state.light_spot[i].x;
|
||||
float cos_phi = fixed_state.light_spot[i].y;
|
||||
float spot = rho > cos_theta ? 1.0 : (rho <= cos_phi ? 0.0 :
|
||||
pow((rho - cos_phi) / (cos_theta - cos_phi), attenuation.w));
|
||||
strength *= spot;
|
||||
}
|
||||
}
|
||||
ambient_sum += fixed_state.light_ambient[i].rgb * strength;
|
||||
diffuse_sum += fixed_state.light_diffuse[i].rgb * max(dot(n, L), 0.0) * strength;
|
||||
}
|
||||
vec3 lit = mat_emissive.rgb + mat_ambient.rgb * ambient_sum + mat_diffuse.rgb * diffuse_sum;
|
||||
out_color = vec4(clamp(lit, 0.0, 1.0), clamp(mat_diffuse.a, 0.0, 1.0));
|
||||
}
|
||||
|
||||
out_uv = stage_texcoord(0, pretransformed, eye, n);
|
||||
out_mask_uv = stage_texcoord(1, pretransformed, eye, n);
|
||||
|
||||
out_fog = 1.0;
|
||||
if (fixed_state.flags.w == 4u) {
|
||||
// XYZRHW with FOGTABLEMODE NONE: D3D8 takes the vertex fog from the specular alpha.
|
||||
out_fog = in_vertex_fog;
|
||||
} else if (fixed_state.flags.w != 0u) {
|
||||
// Table fog on XYZRHW vertices runs on eye depth w = 1/rhw.
|
||||
float distance_to_eye = pretransformed ? gl_Position.w
|
||||
: (fixed_state.fog_params.w > 0.5 ? length(eye) : abs(eye.z));
|
||||
if (fixed_state.flags.w == 3u) {
|
||||
float span = fixed_state.fog_params.y - fixed_state.fog_params.x;
|
||||
if (span > 0.0001)
|
||||
out_fog = clamp((fixed_state.fog_params.y - distance_to_eye) / span, 0.0, 1.0);
|
||||
} else if (fixed_state.flags.w == 1u) {
|
||||
out_fog = clamp(exp(-fixed_state.fog_params.z * distance_to_eye), 0.0, 1.0);
|
||||
} else if (fixed_state.flags.w == 2u) {
|
||||
float fog_distance = fixed_state.fog_params.z * distance_to_eye;
|
||||
out_fog = clamp(exp(-fog_distance * fog_distance), 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#define STBI_NO_STDIO
|
||||
#define STBI_ONLY_JPEG
|
||||
#define STBI_ONLY_PNG
|
||||
#define STBI_ONLY_BMP
|
||||
#define STBI_MAX_DIMENSIONS 4096
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "stb_image.h"
|
||||
Vendored
+7988
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,892 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "UIRenderCommands.h"
|
||||
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
#include "platform/ScriptLib/PythonBoot.h"
|
||||
#endif
|
||||
|
||||
class TouchController {
|
||||
public:
|
||||
struct ButtonDef {
|
||||
int id; // 1 = attack, 2..7 = quick slots 1..6, 8 = auto hunt
|
||||
int dik;
|
||||
float x, y, radius;
|
||||
const char* label;
|
||||
uint32_t color_idle;
|
||||
uint32_t color_pressed;
|
||||
bool pressed = false;
|
||||
int64_t finger_id = -1;
|
||||
};
|
||||
|
||||
TouchController() {
|
||||
init_buttons();
|
||||
}
|
||||
|
||||
void set_enabled(bool enabled) {
|
||||
enabled_ = enabled;
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::SetTouchInput(enabled);
|
||||
#endif
|
||||
}
|
||||
bool is_enabled() const { return enabled_; }
|
||||
// Short vibration when a long press fires (the host wires it to the platform).
|
||||
void set_haptic(void (*haptic)()) { haptic_ = haptic; }
|
||||
|
||||
// The system keyboard is up only after the player taps the focused EditLine, not when a
|
||||
// script focuses one on its own (intrologin.py focuses the ID field as the board opens).
|
||||
// The serial changes on every such tap so the host can re-show a keyboard the user dismissed.
|
||||
bool wants_screen_keyboard() const { return keyboard_requested_; }
|
||||
unsigned screen_keyboard_serial() const { return keyboard_serial_; }
|
||||
|
||||
// safe_inset: logical pixels kept clear at the left and right edges (rounded corners, cutouts).
|
||||
void update_screen_size(int width, int height, int safe_inset = 0) {
|
||||
if (width <= 0 || height <= 0) return;
|
||||
screen_w_ = width;
|
||||
screen_h_ = height;
|
||||
safe_inset_ = float(std::max(0, safe_inset));
|
||||
// Position joystick on lower-left
|
||||
joystick_base_x_ = safe_inset_ + std::max(90.0f, float(width) * 0.12f);
|
||||
joystick_base_y_ = float(height) - std::max(90.0f, float(height) * 0.22f);
|
||||
if (!joystick_active_) {
|
||||
joystick_knob_x_ = joystick_base_x_;
|
||||
joystick_knob_y_ = joystick_base_y_;
|
||||
}
|
||||
layout_buttons();
|
||||
}
|
||||
|
||||
// Called once per frame to maintain continuous analog movement
|
||||
void update() {
|
||||
if (!enabled_) return;
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
if (keyboard_requested_ && !PythonBoot::TextInputFocused())
|
||||
keyboard_requested_ = false;
|
||||
// A world tap presses and releases on later frames than its mouse move so
|
||||
// 40250's per-frame actor picking has already hit the target under the finger.
|
||||
if (tap_stage_ == 1) {
|
||||
PythonBoot::UIMouseMove(int(tap_x_), int(tap_y_));
|
||||
PythonBoot::UIMouseButton(1, true, int(tap_x_), int(tap_y_));
|
||||
tap_stage_ = 2;
|
||||
} else if (tap_stage_ == 2) {
|
||||
PythonBoot::UIMouseButton(1, false, int(tap_x_), int(tap_y_));
|
||||
tap_stage_ = 0;
|
||||
}
|
||||
// A UI finger held still turns into a right click (use item, equip, skill up...).
|
||||
if (ui_touch_finger_id_ >= 0 && ui_gesture_ == UiGesture::Pending &&
|
||||
get_time_sec() - ui_down_time_ >= kLongPressSec) {
|
||||
ui_gesture_ = UiGesture::LongPressed;
|
||||
last_tap_time_ = -1.0;
|
||||
PythonBoot::UIMouseButton(2, true, int(ui_start_x_), int(ui_start_y_));
|
||||
PythonBoot::UIMouseButton(2, false, int(ui_start_x_), int(ui_start_y_));
|
||||
if (haptic_) haptic_();
|
||||
}
|
||||
for (auto& btn : buttons_) {
|
||||
if (btn.id == kAutoHuntButton && btn.pressed && !auto_hunt_long_pressed_ &&
|
||||
get_time_sec() - auto_hunt_down_time_ >= kLongPressSec) {
|
||||
auto_hunt_long_pressed_ = true;
|
||||
PythonBoot::AutoHuntOpenSettings();
|
||||
if (haptic_) haptic_();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
const bool controls_visible = gameplay_controls_visible();
|
||||
if (!controls_visible) {
|
||||
if (controls_were_visible_)
|
||||
release_gameplay_controls();
|
||||
controls_were_visible_ = false;
|
||||
return;
|
||||
}
|
||||
controls_were_visible_ = true;
|
||||
if (joystick_active_) {
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::SetMoveDirection(move_angle_, true);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Handles finger touch down (norm_x, norm_y in 0.0 .. 1.0)
|
||||
bool on_finger_down(int64_t finger_id, float norm_x, float norm_y) {
|
||||
if (!enabled_) return false;
|
||||
const float px = norm_x * float(screen_w_);
|
||||
const float py = norm_y * float(screen_h_);
|
||||
const bool controls_visible = gameplay_controls_visible();
|
||||
|
||||
// The mobile overlay owns only the movement and combat control regions.
|
||||
if (controls_visible) {
|
||||
const int btn_idx = find_button(px, py);
|
||||
if (btn_idx >= 0) {
|
||||
auto& btn = buttons_[btn_idx];
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
if (is_quick_slot_button(btn.id) && PythonBoot::TryAssignAttachedObjectToLocalQuickSlot(btn.id - 2))
|
||||
return true;
|
||||
#endif
|
||||
btn.pressed = true;
|
||||
btn.finger_id = finger_id;
|
||||
if (btn.id == kAutoHuntButton) {
|
||||
// Tap toggles; holding opens the settings window (update()).
|
||||
auto_hunt_down_time_ = get_time_sec();
|
||||
auto_hunt_long_pressed_ = false;
|
||||
return true;
|
||||
}
|
||||
trigger_button(btn.dik, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Everything else uses the unchanged 40250 UI and its normal mouse path.
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
// Only hover here (the tooltip shows while the finger rests); what the finger does next picks
|
||||
// the gesture: lift = click, slide = press + drag, hold = right click, second tap = double click.
|
||||
if (PythonBoot::IsPointInsideActiveUI(int(px), int(py))) {
|
||||
ui_touch_finger_id_ = finger_id;
|
||||
ui_gesture_ = UiGesture::Pending;
|
||||
ui_start_x_ = px;
|
||||
ui_start_y_ = py;
|
||||
ui_down_time_ = get_time_sec();
|
||||
PythonBoot::UIMouseMove(int(px), int(py));
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Mobile-game layout: the left half moves (floating stick), the right half looks.
|
||||
if (controls_visible && norm_x < kMoveZoneRight && norm_y > kMoveZoneTop && !joystick_active_) {
|
||||
joystick_active_ = true;
|
||||
joystick_finger_id_ = finger_id;
|
||||
joystick_base_x_ = px;
|
||||
joystick_base_y_ = py;
|
||||
joystick_knob_x_ = px;
|
||||
joystick_knob_y_ = py;
|
||||
update_joystick_motion(px, py);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!controls_visible)
|
||||
return false;
|
||||
|
||||
const bool in_look_zone = norm_x >= kMoveZoneRight;
|
||||
if (look_finger_id_ < 0) {
|
||||
// Outside the look zone the finger can only tap-select.
|
||||
look_finger_id_ = finger_id;
|
||||
look_can_rotate_ = in_look_zone;
|
||||
look_rotating_ = false;
|
||||
look_moved_ = false;
|
||||
look_start_x_ = look_last_x_ = px;
|
||||
look_start_y_ = look_last_y_ = py;
|
||||
look_down_time_ = get_time_sec();
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::UIMouseMove(int(px), int(py)); // start picking under the finger
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
if (pinch_finger_id_ < 0 && look_can_rotate_ && in_look_zone) {
|
||||
pinch_finger_id_ = finger_id;
|
||||
pinch_x_ = px;
|
||||
pinch_y_ = py;
|
||||
pinch_last_dist_ = std::hypot(px - look_last_x_, py - look_last_y_);
|
||||
look_rotating_ = false;
|
||||
look_moved_ = true; // a pinch never ends as a tap
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handles finger motion
|
||||
bool on_finger_motion(int64_t finger_id, float norm_x, float norm_y) {
|
||||
if (!enabled_) return false;
|
||||
const float px = norm_x * float(screen_w_);
|
||||
const float py = norm_y * float(screen_h_);
|
||||
|
||||
// 1. UI Touch dragging (e.g. dragging item in inventory or scrollbar)
|
||||
if (ui_touch_finger_id_ == finger_id) {
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
if (ui_gesture_ == UiGesture::Pending &&
|
||||
std::hypot(px - ui_start_x_, py - ui_start_y_) > kUiDragSlop) {
|
||||
// 40250 drag: press where the finger landed (a slot attaches its icon, a title bar
|
||||
// or scroll bar captures), then follow the finger.
|
||||
ui_gesture_ = UiGesture::Dragging;
|
||||
last_tap_time_ = -1.0;
|
||||
PythonBoot::UIMouseButton(1, true, int(ui_start_x_), int(ui_start_y_));
|
||||
}
|
||||
PythonBoot::UIMouseMove(int(px), int(py));
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. Virtual Joystick finger
|
||||
if (joystick_active_ && finger_id == joystick_finger_id_) {
|
||||
update_joystick_motion(px, py);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. Button drag tracking (check if finger slid off)
|
||||
for (auto& btn : buttons_) {
|
||||
if (btn.finger_id == finger_id) {
|
||||
const float dist = std::hypot(px - btn.x, py - btn.y);
|
||||
if (dist > btn.radius * 1.5f && btn.pressed) {
|
||||
btn.pressed = false;
|
||||
trigger_button(btn.dik, false);
|
||||
} else if (dist <= btn.radius * 1.5f && !btn.pressed) {
|
||||
btn.pressed = true;
|
||||
trigger_button(btn.dik, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Two-finger pinch zoom (look finger + second right-half finger)
|
||||
if (pinch_finger_id_ >= 0 && (finger_id == pinch_finger_id_ || finger_id == look_finger_id_)) {
|
||||
if (finger_id == pinch_finger_id_) {
|
||||
pinch_x_ = px;
|
||||
pinch_y_ = py;
|
||||
} else {
|
||||
look_last_x_ = px;
|
||||
look_last_y_ = py;
|
||||
}
|
||||
const float cur_dist = std::hypot(pinch_x_ - look_last_x_, pinch_y_ - look_last_y_);
|
||||
const float delta_d = cur_dist - pinch_last_dist_;
|
||||
if (std::abs(delta_d) > 2.0f) {
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::UIMouseWheel(int(delta_d * 8.0f));
|
||||
#endif
|
||||
pinch_last_dist_ = cur_dist;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 5. Single-finger look drag: yaw by dx, pitch by dy, stops the moment the finger stops.
|
||||
if (finger_id == look_finger_id_) {
|
||||
if (!look_moved_ && std::hypot(px - look_start_x_, py - look_start_y_) > kLookDeadZone) {
|
||||
look_moved_ = true;
|
||||
look_rotating_ = look_can_rotate_;
|
||||
look_last_x_ = px; // start from here so the dead zone does not jump the camera
|
||||
look_last_y_ = py;
|
||||
}
|
||||
if (look_rotating_) {
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::CameraRotateBy((px - look_last_x_) * kLookYawDegPerPx,
|
||||
(py - look_last_y_) * kLookPitchDegPerPx);
|
||||
#endif
|
||||
}
|
||||
look_last_x_ = px;
|
||||
look_last_y_ = py;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handles finger touch up
|
||||
bool on_finger_up(int64_t finger_id, float norm_x, float norm_y) {
|
||||
if (!enabled_) return false;
|
||||
const float px = norm_x * float(screen_w_);
|
||||
const float py = norm_y * float(screen_h_);
|
||||
|
||||
// 1. UI Touch release (e.g. dropped item in inventory or clicked button)
|
||||
if (ui_touch_finger_id_ == finger_id) {
|
||||
ui_touch_finger_id_ = -1;
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
if (ui_gesture_ == UiGesture::Dragging) {
|
||||
// Dropped on a skill button: that quick slot takes the dragged icon.
|
||||
const int btn_idx = gameplay_controls_visible() ? find_button(px, py) : -1;
|
||||
if (btn_idx >= 0 && is_quick_slot_button(buttons_[btn_idx].id) && PythonBoot::UIIsAttaching()) {
|
||||
PythonBoot::TryAssignAttachedObjectToLocalQuickSlot(buttons_[btn_idx].id - 2);
|
||||
PythonBoot::UIMouseButton(1, false, int(ui_start_x_), int(ui_start_y_));
|
||||
} else {
|
||||
PythonBoot::UIMouseButton(1, false, int(px), int(py));
|
||||
}
|
||||
} else if (ui_gesture_ == UiGesture::Pending) {
|
||||
const double now = get_time_sec();
|
||||
const int x = int(ui_start_x_), y = int(ui_start_y_);
|
||||
if (last_tap_time_ >= 0.0 && now - last_tap_time_ < kDoubleTapSec &&
|
||||
std::hypot(ui_start_x_ - last_tap_x_, ui_start_y_ - last_tap_y_) < kDoubleTapSlop) {
|
||||
// Win32 sends DOWN, UP, DBLCLK, UP; the first click picked the icon up, which a
|
||||
// mouse user would have seen and a finger hides, so put it back first.
|
||||
if (last_tap_attached_ && PythonBoot::UIIsAttaching())
|
||||
PythonBoot::UIDeattachObject();
|
||||
PythonBoot::UIMouseDoubleClick(x, y);
|
||||
PythonBoot::UIMouseButton(1, false, x, y);
|
||||
last_tap_time_ = -1.0;
|
||||
} else {
|
||||
const bool was_attaching = PythonBoot::UIIsAttaching();
|
||||
PythonBoot::UIMouseButton(1, true, x, y);
|
||||
PythonBoot::UIMouseButton(1, false, x, y);
|
||||
last_tap_time_ = now;
|
||||
last_tap_x_ = ui_start_x_;
|
||||
last_tap_y_ = ui_start_y_;
|
||||
last_tap_attached_ = !was_attaching && PythonBoot::UIIsAttaching();
|
||||
}
|
||||
keyboard_requested_ = PythonBoot::TextInputFocused() &&
|
||||
PythonBoot::IsPointInsideFocusedWindow(x, y);
|
||||
if (keyboard_requested_) ++keyboard_serial_;
|
||||
}
|
||||
ui_gesture_ = UiGesture::None;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. Joystick release
|
||||
if (joystick_active_ && finger_id == joystick_finger_id_) {
|
||||
joystick_active_ = false;
|
||||
joystick_finger_id_ = -1;
|
||||
joystick_knob_x_ = joystick_base_x_;
|
||||
joystick_knob_y_ = joystick_base_y_;
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::SetMoveDirection(0.0f, false);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. Button release
|
||||
for (auto& btn : buttons_) {
|
||||
if (btn.finger_id == finger_id) {
|
||||
if (btn.pressed) {
|
||||
btn.pressed = false;
|
||||
trigger_button(btn.dik, false);
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
if (btn.id == kAutoHuntButton && !auto_hunt_long_pressed_)
|
||||
PythonBoot::AutoHuntToggle();
|
||||
#endif
|
||||
}
|
||||
btn.finger_id = -1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Pinch end: the finger left down keeps looking (never a tap).
|
||||
if (pinch_finger_id_ >= 0 && (finger_id == pinch_finger_id_ || finger_id == look_finger_id_)) {
|
||||
if (finger_id == look_finger_id_) {
|
||||
look_finger_id_ = pinch_finger_id_;
|
||||
look_last_x_ = pinch_x_;
|
||||
look_last_y_ = pinch_y_;
|
||||
}
|
||||
pinch_finger_id_ = -1;
|
||||
pinch_last_dist_ = 0.0f;
|
||||
look_rotating_ = look_can_rotate_;
|
||||
look_moved_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 5. Look finger release; a short still touch selects a target (mob, NPC, ground).
|
||||
if (finger_id == look_finger_id_) {
|
||||
look_finger_id_ = -1;
|
||||
look_rotating_ = false;
|
||||
keyboard_requested_ = false;
|
||||
if (!look_moved_ && (get_time_sec() - look_down_time_) < kTapMaxSec && tap_stage_ == 0) {
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::UIMouseMove(int(px), int(py));
|
||||
tap_x_ = px;
|
||||
tap_y_ = py;
|
||||
tap_stage_ = 1;
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Overlay only the controls that have no practical desktop-UI touch equivalent.
|
||||
void append_ui_commands(std::vector<UIRenderCommand>& commands) const {
|
||||
if (!enabled_ || !gameplay_controls_visible()) return;
|
||||
|
||||
// Right-side attack and quick-slot buttons.
|
||||
for (const auto& btn : buttons_) {
|
||||
const uint32_t col = btn.pressed ? btn.color_pressed : btn.color_idle;
|
||||
draw_filled_disc(commands, btn.x, btn.y, btn.radius, col);
|
||||
draw_circle(commands, btn.x, btn.y, btn.radius * 0.85f, btn.pressed ? 0xFFFFFFFF : 0x70FFFFFF, 16);
|
||||
|
||||
const uint32_t icon_col = btn.pressed ? 0xFFFFFFFF : 0xDDFFFFFF;
|
||||
const float r = btn.radius;
|
||||
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
if (btn.id == kAutoHuntButton) {
|
||||
draw_auto_hunt_button(commands, btn);
|
||||
continue;
|
||||
}
|
||||
if (is_quick_slot_button(btn.id)) {
|
||||
std::string skill_icon;
|
||||
float uv[4];
|
||||
int count = 0;
|
||||
if (PythonBoot::LocalQuickSlotIcon(btn.id - 2, &skill_icon, uv, &count)) {
|
||||
UIRenderCommand icon{};
|
||||
icon.kind = UIRenderCommand::Image;
|
||||
icon.x1 = btn.x - r * 0.68f;
|
||||
icon.y1 = btn.y - r * 0.68f;
|
||||
icon.x2 = btn.x + r * 0.68f;
|
||||
icon.y2 = btn.y + r * 0.68f;
|
||||
icon.argb = icon_col;
|
||||
icon.text = skill_icon;
|
||||
icon.su = uv[0];
|
||||
icon.sv = uv[1];
|
||||
icon.eu = uv[2];
|
||||
icon.ev = uv[3];
|
||||
commands.push_back(std::move(icon));
|
||||
if (count > 0) {
|
||||
const std::string digits = std::to_string(std::min(count, 9999));
|
||||
const float h = r * 0.26f;
|
||||
draw_segment_text(commands, digits, btn.x + r * 0.62f - segment_text_width(digits, h),
|
||||
btn.y + r * 0.62f - h, h);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
switch (btn.id) {
|
||||
case 1: { // ATK: crossed swords
|
||||
const float s = r * 0.35f;
|
||||
draw_line(commands, btn.x - s, btn.y - s, btn.x + s, btn.y + s, icon_col);
|
||||
draw_line(commands, btn.x + s, btn.y - s, btn.x - s, btn.y + s, icon_col);
|
||||
const float g = s * 0.35f;
|
||||
draw_line(commands, btn.x - s*0.4f - g, btn.y - s*0.4f + g, btn.x - s*0.4f + g, btn.y - s*0.4f - g, icon_col);
|
||||
draw_line(commands, btn.x + s*0.4f - g, btn.y - s*0.4f - g, btn.x + s*0.4f + g, btn.y - s*0.4f + g, icon_col);
|
||||
break;
|
||||
}
|
||||
case 2: { // S1: I
|
||||
const float h = r * 0.35f;
|
||||
draw_line(commands, btn.x, btn.y - h, btn.x, btn.y + h, icon_col);
|
||||
draw_line(commands, btn.x - 4.0f, btn.y - h, btn.x + 4.0f, btn.y - h, icon_col);
|
||||
draw_line(commands, btn.x - 4.0f, btn.y + h, btn.x + 4.0f, btn.y + h, icon_col);
|
||||
break;
|
||||
}
|
||||
case 3: { // S2: II
|
||||
const float h = r * 0.35f;
|
||||
draw_line(commands, btn.x - 4.0f, btn.y - h, btn.x - 4.0f, btn.y + h, icon_col);
|
||||
draw_line(commands, btn.x + 4.0f, btn.y - h, btn.x + 4.0f, btn.y + h, icon_col);
|
||||
break;
|
||||
}
|
||||
case 4: { // S3: III
|
||||
const float h = r * 0.35f;
|
||||
draw_line(commands, btn.x - 6.0f, btn.y - h, btn.x - 6.0f, btn.y + h, icon_col);
|
||||
draw_line(commands, btn.x, btn.y - h, btn.x, btn.y + h, icon_col);
|
||||
draw_line(commands, btn.x + 6.0f, btn.y - h, btn.x + 6.0f, btn.y + h, icon_col);
|
||||
break;
|
||||
}
|
||||
case 5: { // S4: IV
|
||||
const float h = r * 0.35f;
|
||||
draw_line(commands, btn.x - 7.0f, btn.y - h, btn.x - 7.0f, btn.y + h, icon_col);
|
||||
draw_line(commands, btn.x, btn.y - h, btn.x + 7.0f, btn.y + h, icon_col);
|
||||
draw_line(commands, btn.x + 14.0f, btn.y - h, btn.x + 7.0f, btn.y + h, icon_col);
|
||||
break;
|
||||
}
|
||||
case 6: { // S5: V
|
||||
const float h = r * 0.35f;
|
||||
draw_line(commands, btn.x - 8.0f, btn.y - h, btn.x, btn.y + h, icon_col);
|
||||
draw_line(commands, btn.x + 8.0f, btn.y - h, btn.x, btn.y + h, icon_col);
|
||||
break;
|
||||
}
|
||||
case 7: { // S6: VI
|
||||
const float h = r * 0.35f;
|
||||
draw_line(commands, btn.x - 11.0f, btn.y - h, btn.x - 3.0f, btn.y + h, icon_col);
|
||||
draw_line(commands, btn.x + 5.0f, btn.y - h, btn.x - 3.0f, btn.y + h, icon_col);
|
||||
draw_line(commands, btn.x + 11.0f, btn.y - h, btn.x + 11.0f, btn.y + h, icon_col);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Left-side movement joystick.
|
||||
draw_circle(commands, joystick_base_x_, joystick_base_y_, joystick_radius_, 0x8080C0FF, 24);
|
||||
draw_circle(commands, joystick_base_x_, joystick_base_y_, joystick_radius_ * 0.45f, 0x4080C0FF, 16);
|
||||
draw_line(commands, joystick_base_x_ - joystick_radius_, joystick_base_y_,
|
||||
joystick_base_x_ - joystick_radius_ + 8.0f, joystick_base_y_, 0x90FFFFFF);
|
||||
draw_line(commands, joystick_base_x_ + joystick_radius_ - 8.0f, joystick_base_y_,
|
||||
joystick_base_x_ + joystick_radius_, joystick_base_y_, 0x90FFFFFF);
|
||||
draw_line(commands, joystick_base_x_, joystick_base_y_ - joystick_radius_,
|
||||
joystick_base_x_, joystick_base_y_ - joystick_radius_ + 8.0f, 0x90FFFFFF);
|
||||
draw_line(commands, joystick_base_x_, joystick_base_y_ + joystick_radius_ - 8.0f,
|
||||
joystick_base_x_, joystick_base_y_ + joystick_radius_, 0x90FFFFFF);
|
||||
|
||||
if (joystick_active_) {
|
||||
draw_line(commands, joystick_base_x_, joystick_base_y_, joystick_knob_x_, joystick_knob_y_, 0xB000FFFF);
|
||||
}
|
||||
const uint32_t knob_color = joystick_active_ ? 0xB040A0FF : 0x6040A0FF;
|
||||
draw_filled_disc(commands, joystick_knob_x_, joystick_knob_y_, joystick_knob_radius_, knob_color);
|
||||
draw_circle(commands, joystick_knob_x_, joystick_knob_y_, joystick_knob_radius_ * 0.5f, 0x80FFFFFF, 12);
|
||||
}
|
||||
|
||||
// Desktop mouse testing simulation
|
||||
bool on_mouse_button(int button, bool pressed, int x, int y) {
|
||||
if (!enabled_) return false;
|
||||
const float norm_x = float(x) / float(screen_w_);
|
||||
const float norm_y = float(y) / float(screen_h_);
|
||||
if (pressed) {
|
||||
if (button == 1) {
|
||||
return on_finger_down(101, norm_x, norm_y);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
if (button == 1) {
|
||||
return on_finger_up(101, norm_x, norm_y);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool on_mouse_motion(int x, int y) {
|
||||
if (!enabled_) return false;
|
||||
const float norm_x = float(x) / float(screen_w_);
|
||||
const float norm_y = float(y) / float(screen_h_);
|
||||
bool handled = false;
|
||||
if (ui_touch_finger_id_ == 101) {
|
||||
handled |= on_finger_motion(101, norm_x, norm_y);
|
||||
}
|
||||
if (joystick_active_ && joystick_finger_id_ == 101) {
|
||||
handled |= on_finger_motion(101, norm_x, norm_y);
|
||||
}
|
||||
if (look_finger_id_ == 101) {
|
||||
handled |= on_finger_motion(101, norm_x, norm_y);
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
// Seven-segment text (digits, 'F', 'P', 'S', ' ') with its top-left at (left, top), each glyph h
|
||||
// tall, on a dark backing. Used for quick-slot counts and the test-build FPS readout.
|
||||
static void draw_segment_text(std::vector<UIRenderCommand>& commands, const std::string& text,
|
||||
float left, float top, float h, std::uint32_t color = 0xFFFFFFFF) {
|
||||
static constexpr std::uint8_t kDigits[10] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};
|
||||
const float w = h * 0.55f, gap = h * 0.3f;
|
||||
UIRenderCommand back{};
|
||||
back.kind = UIRenderCommand::Bar;
|
||||
back.x1 = left - 3.0f;
|
||||
back.y1 = top - 3.0f;
|
||||
back.x2 = left + segment_text_width(text, h) + 3.0f;
|
||||
back.y2 = top + h + 3.0f;
|
||||
back.argb = 0xA0000000;
|
||||
commands.push_back(back);
|
||||
float x = left;
|
||||
for (char c : text) {
|
||||
std::uint8_t seg = 0;
|
||||
if (c >= '0' && c <= '9') seg = kDigits[c - '0'];
|
||||
else if (c == 'F') seg = 0x71;
|
||||
else if (c == 'P') seg = 0x73;
|
||||
else if (c == 'S') seg = 0x6D;
|
||||
const float t = top, m = top + h * 0.5f, b = top + h;
|
||||
if (seg & 0x01) draw_line(commands, x, t, x + w, t, color);
|
||||
if (seg & 0x02) draw_line(commands, x + w, t, x + w, m, color);
|
||||
if (seg & 0x04) draw_line(commands, x + w, m, x + w, b, color);
|
||||
if (seg & 0x08) draw_line(commands, x, b, x + w, b, color);
|
||||
if (seg & 0x10) draw_line(commands, x, m, x, b, color);
|
||||
if (seg & 0x20) draw_line(commands, x, t, x, m, color);
|
||||
if (seg & 0x40) draw_line(commands, x, m, x + w, m, color);
|
||||
x += w + gap;
|
||||
}
|
||||
}
|
||||
static float segment_text_width(const std::string& text, float h) {
|
||||
return text.empty() ? 0.0f : float(text.size()) * h * 0.85f - h * 0.3f;
|
||||
}
|
||||
|
||||
private:
|
||||
bool gameplay_controls_visible() const {
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
return !PythonBoot::CurrentMapName().empty();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void release_gameplay_controls() {
|
||||
if (joystick_active_) {
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::SetMoveDirection(0.0f, false);
|
||||
#endif
|
||||
}
|
||||
joystick_active_ = false;
|
||||
joystick_finger_id_ = -1;
|
||||
joystick_knob_x_ = joystick_base_x_;
|
||||
joystick_knob_y_ = joystick_base_y_;
|
||||
for (auto& btn : buttons_) {
|
||||
if (btn.pressed)
|
||||
trigger_button(btn.dik, false);
|
||||
btn.pressed = false;
|
||||
btn.finger_id = -1;
|
||||
}
|
||||
look_finger_id_ = -1;
|
||||
look_rotating_ = false;
|
||||
pinch_finger_id_ = -1;
|
||||
pinch_last_dist_ = 0.0f;
|
||||
}
|
||||
|
||||
static constexpr int kAutoHuntButton = 8;
|
||||
static bool is_quick_slot_button(int id) { return id >= 2 && id <= 7; }
|
||||
|
||||
// A circular arrow; green while the hunt runs.
|
||||
static void draw_auto_hunt_button(std::vector<UIRenderCommand>& commands, const ButtonDef& btn) {
|
||||
bool on = false;
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
on = PythonBoot::AutoHuntIsEnabled();
|
||||
#endif
|
||||
const uint32_t disc = btn.pressed ? btn.color_pressed : (on ? 0xC040C040 : btn.color_idle);
|
||||
draw_filled_disc(commands, btn.x, btn.y, btn.radius, disc);
|
||||
const uint32_t col = on ? 0xFFB0FFB0 : 0xDDFFFFFF;
|
||||
const float r = btn.radius * 0.45f;
|
||||
const float kPi = 3.14159265f;
|
||||
const int segments = 12;
|
||||
const float a0 = -kPi * 0.35f, a1 = a0 + kPi * 1.6f;
|
||||
for (int i = 0; i < segments; ++i) {
|
||||
const float t0 = a0 + (a1 - a0) * float(i) / float(segments);
|
||||
const float t1 = a0 + (a1 - a0) * float(i + 1) / float(segments);
|
||||
draw_line(commands, btn.x + std::cos(t0) * r, btn.y + std::sin(t0) * r,
|
||||
btn.x + std::cos(t1) * r, btn.y + std::sin(t1) * r, col);
|
||||
}
|
||||
// Arrowhead at the arc's end, pointing along the direction of travel.
|
||||
const float ex = btn.x + std::cos(a1) * r, ey = btn.y + std::sin(a1) * r;
|
||||
const float tx = -std::sin(a1), ty = std::cos(a1);
|
||||
const float nx = std::cos(a1), ny = std::sin(a1);
|
||||
const float h = r * 0.55f;
|
||||
draw_line(commands, ex, ey, ex - tx * h + nx * h * 0.6f, ey - ty * h + ny * h * 0.6f, col);
|
||||
draw_line(commands, ex, ey, ex - tx * h - nx * h * 0.6f, ey - ty * h - ny * h * 0.6f, col);
|
||||
if (on) {
|
||||
// A play dot in the middle while running.
|
||||
draw_rect_bar(commands, btn.x - 2.0f, btn.y - 2.0f, btn.x + 2.0f, btn.y + 2.0f, col);
|
||||
}
|
||||
}
|
||||
|
||||
void init_buttons() {
|
||||
buttons_.clear();
|
||||
// Lower-right combat controls. All menus and status UI remain the original 40250 UI.
|
||||
buttons_.push_back({1, 0x39, 0, 0, 42.0f, "ATK", 0x80D48820, 0xD0FFB040});
|
||||
buttons_.push_back({2, 0x02, 0, 0, 26.0f, "S1", 0x803060C0, 0xD05080FF});
|
||||
buttons_.push_back({3, 0x03, 0, 0, 26.0f, "S2", 0x80903090, 0xD0D050D0});
|
||||
buttons_.push_back({4, 0x04, 0, 0, 26.0f, "S3", 0x80309060, 0xD050D080});
|
||||
buttons_.push_back({5, 0x05, 0, 0, 26.0f, "S4", 0x80906030, 0xD0D08050});
|
||||
buttons_.push_back({6, 0x06, 0, 0, 26.0f, "S5", 0x80603090, 0xD08050D0});
|
||||
buttons_.push_back({7, 0x07, 0, 0, 26.0f, "S6", 0x80308090, 0xD050C0D0});
|
||||
buttons_.push_back({kAutoHuntButton, 0, 0, 0, 24.0f, "AUTO", 0x80707070, 0xD0FFFFFF});
|
||||
}
|
||||
|
||||
void layout_buttons() {
|
||||
const float W = float(screen_w_) - safe_inset_;
|
||||
const float H = float(screen_h_);
|
||||
for (auto& btn : buttons_) {
|
||||
switch (btn.id) {
|
||||
case 1:
|
||||
btn.x = W - 75.0f;
|
||||
btn.y = H - 85.0f;
|
||||
btn.radius = std::min(46.0f, H * 0.12f);
|
||||
break;
|
||||
case 2:
|
||||
btn.x = W - 360.0f;
|
||||
btn.y = H - 75.0f;
|
||||
btn.radius = std::min(28.0f, H * 0.08f);
|
||||
break;
|
||||
case 3:
|
||||
btn.x = W - 295.0f;
|
||||
btn.y = H - 75.0f;
|
||||
btn.radius = std::min(28.0f, H * 0.08f);
|
||||
break;
|
||||
case 4:
|
||||
btn.x = W - 230.0f;
|
||||
btn.y = H - 75.0f;
|
||||
btn.radius = std::min(28.0f, H * 0.08f);
|
||||
break;
|
||||
case 5:
|
||||
btn.x = W - 165.0f;
|
||||
btn.y = H - 85.0f;
|
||||
btn.radius = std::min(28.0f, H * 0.08f);
|
||||
break;
|
||||
case 6:
|
||||
btn.x = W - 125.0f;
|
||||
btn.y = H - 155.0f;
|
||||
btn.radius = std::min(28.0f, H * 0.08f);
|
||||
break;
|
||||
case 7:
|
||||
btn.x = W - 75.0f;
|
||||
btn.y = H - 225.0f;
|
||||
btn.radius = std::min(28.0f, H * 0.08f);
|
||||
break;
|
||||
case kAutoHuntButton:
|
||||
btn.x = W - 75.0f;
|
||||
btn.y = H - 295.0f;
|
||||
btn.radius = std::min(24.0f, H * 0.07f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int find_button(float x, float y) {
|
||||
for (size_t i = 0; i < buttons_.size(); ++i) {
|
||||
const auto& btn = buttons_[i];
|
||||
const float dist = std::hypot(x - btn.x, y - btn.y);
|
||||
if (dist <= btn.radius * 1.25f) {
|
||||
return int(i);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void trigger_button(int dik, bool pressed) {
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
if (dik == 0x39) {
|
||||
PythonBoot::SetAttackKey(pressed);
|
||||
}
|
||||
if (dik >= 0x02 && dik <= 0x07) {
|
||||
if (pressed)
|
||||
PythonBoot::UseLocalQuickSlot(dik - 0x02);
|
||||
return;
|
||||
}
|
||||
if (dik != 0) {
|
||||
PythonBoot::UIKey(dik, pressed);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void update_joystick_motion(float px, float py) {
|
||||
const float dx = px - joystick_base_x_;
|
||||
const float dy = py - joystick_base_y_;
|
||||
const float dist = std::hypot(dx, dy);
|
||||
|
||||
if (dist <= joystick_radius_) {
|
||||
joystick_knob_x_ = px;
|
||||
joystick_knob_y_ = py;
|
||||
} else if (dist > 0.0f) {
|
||||
joystick_knob_x_ = joystick_base_x_ + (dx / dist) * joystick_radius_;
|
||||
joystick_knob_y_ = joystick_base_y_ + (dy / dist) * joystick_radius_;
|
||||
}
|
||||
|
||||
if (dist > 8.0f) {
|
||||
const float rad = std::atan2(-dx, -dy);
|
||||
move_angle_ = rad * 180.0f / 3.14159265358979323846f;
|
||||
if (move_angle_ < 0.0f) move_angle_ += 360.0f;
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::SetMoveDirection(move_angle_, true);
|
||||
#endif
|
||||
} else {
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::SetMoveDirection(0.0f, false);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_line(std::vector<UIRenderCommand>& commands,
|
||||
float x1, float y1, float x2, float y2, uint32_t argb) {
|
||||
UIRenderCommand cmd{};
|
||||
cmd.kind = UIRenderCommand::Line;
|
||||
cmd.x1 = x1; cmd.y1 = y1;
|
||||
cmd.x2 = x2; cmd.y2 = y2;
|
||||
cmd.argb = argb;
|
||||
commands.push_back(cmd);
|
||||
}
|
||||
|
||||
static void draw_rect_bar(std::vector<UIRenderCommand>& commands,
|
||||
float x1, float y1, float x2, float y2, uint32_t argb) {
|
||||
UIRenderCommand bar{};
|
||||
bar.kind = UIRenderCommand::Bar;
|
||||
bar.x1 = x1; bar.y1 = y1;
|
||||
bar.x2 = x2; bar.y2 = y2;
|
||||
bar.argb = argb;
|
||||
commands.push_back(bar);
|
||||
}
|
||||
|
||||
static void draw_rect_lines(std::vector<UIRenderCommand>& commands,
|
||||
float x1, float y1, float x2, float y2, uint32_t argb) {
|
||||
draw_line(commands, x1, y1, x2, y1, argb);
|
||||
draw_line(commands, x2, y1, x2, y2, argb);
|
||||
draw_line(commands, x2, y2, x1, y2, argb);
|
||||
draw_line(commands, x1, y2, x1, y1, argb);
|
||||
}
|
||||
|
||||
static void draw_circle(std::vector<UIRenderCommand>& commands,
|
||||
float cx, float cy, float radius, uint32_t argb, int segments = 16) {
|
||||
const float step = 2.0f * 3.14159265f / float(segments);
|
||||
for (int i = 0; i < segments; ++i) {
|
||||
const float a1 = float(i) * step;
|
||||
const float a2 = float(i + 1) * step;
|
||||
draw_line(commands,
|
||||
cx + std::cos(a1) * radius, cy + std::sin(a1) * radius,
|
||||
cx + std::cos(a2) * radius, cy + std::sin(a2) * radius,
|
||||
argb);
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_filled_disc(std::vector<UIRenderCommand>& commands,
|
||||
float cx, float cy, float radius, uint32_t argb) {
|
||||
// Base rectangular fill
|
||||
draw_rect_bar(commands, cx - radius * 0.65f, cy - radius * 0.65f, cx + radius * 0.65f, cy + radius * 0.65f, (argb & 0x00FFFFFF) | 0x55000000);
|
||||
// Cross fills for roundness
|
||||
draw_rect_bar(commands, cx - radius * 0.85f, cy - radius * 0.35f, cx + radius * 0.85f, cy + radius * 0.35f, (argb & 0x00FFFFFF) | 0x55000000);
|
||||
draw_rect_bar(commands, cx - radius * 0.35f, cy - radius * 0.85f, cx + radius * 0.35f, cy + radius * 0.85f, (argb & 0x00FFFFFF) | 0x55000000);
|
||||
// Border rings
|
||||
draw_circle(commands, cx, cy, radius, argb, 20);
|
||||
draw_circle(commands, cx, cy, radius - 1.0f, (argb & 0x00FFFFFF) | 0x40000000, 20);
|
||||
}
|
||||
|
||||
static double get_time_sec() {
|
||||
using namespace std::chrono;
|
||||
return duration_cast<duration<double>>(steady_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
bool enabled_ = false;
|
||||
int screen_w_ = 1280;
|
||||
int screen_h_ = 720;
|
||||
float safe_inset_ = 0.0f;
|
||||
|
||||
bool controls_were_visible_ = false;
|
||||
int64_t ui_touch_finger_id_ = -1;
|
||||
enum class UiGesture { None, Pending, Dragging, LongPressed };
|
||||
UiGesture ui_gesture_ = UiGesture::None;
|
||||
float ui_start_x_ = 0.0f;
|
||||
float ui_start_y_ = 0.0f;
|
||||
double ui_down_time_ = 0.0;
|
||||
double last_tap_time_ = -1.0;
|
||||
float last_tap_x_ = 0.0f;
|
||||
float last_tap_y_ = 0.0f;
|
||||
bool last_tap_attached_ = false;
|
||||
void (*haptic_)() = nullptr;
|
||||
static constexpr float kUiDragSlop = 10.0f;
|
||||
static constexpr double kLongPressSec = 0.5;
|
||||
static constexpr double kDoubleTapSec = 0.35;
|
||||
static constexpr float kDoubleTapSlop = 24.0f;
|
||||
bool keyboard_requested_ = false;
|
||||
unsigned keyboard_serial_ = 0;
|
||||
|
||||
bool joystick_active_ = false;
|
||||
int64_t joystick_finger_id_ = -1;
|
||||
float joystick_base_x_ = 140.0f;
|
||||
float joystick_base_y_ = 580.0f;
|
||||
float joystick_knob_x_ = 140.0f;
|
||||
float joystick_knob_y_ = 580.0f;
|
||||
float joystick_radius_ = 65.0f;
|
||||
float joystick_knob_radius_ = 28.0f;
|
||||
float move_angle_ = 0.0f;
|
||||
|
||||
// Zones are fractions of the screen; distances are logical UI pixels.
|
||||
static constexpr float kMoveZoneRight = 0.5f;
|
||||
static constexpr float kMoveZoneTop = 0.2f;
|
||||
static constexpr float kLookDeadZone = 8.0f;
|
||||
static constexpr float kLookYawDegPerPx = 0.25f;
|
||||
static constexpr float kLookPitchDegPerPx = 0.15f;
|
||||
static constexpr double kTapMaxSec = 0.3;
|
||||
|
||||
int64_t look_finger_id_ = -1;
|
||||
bool look_can_rotate_ = false;
|
||||
bool look_rotating_ = false;
|
||||
bool look_moved_ = false;
|
||||
float look_start_x_ = 0.0f;
|
||||
float look_start_y_ = 0.0f;
|
||||
float look_last_x_ = 0.0f;
|
||||
float look_last_y_ = 0.0f;
|
||||
double look_down_time_ = 0.0;
|
||||
|
||||
int64_t pinch_finger_id_ = -1;
|
||||
float pinch_x_ = 0.0f;
|
||||
float pinch_y_ = 0.0f;
|
||||
float pinch_last_dist_ = 0.0f;
|
||||
|
||||
int tap_stage_ = 0; // 1: press next frame, 2: release next frame
|
||||
float tap_x_ = 0.0f;
|
||||
float tap_y_ = 0.0f;
|
||||
|
||||
std::vector<ButtonDef> buttons_;
|
||||
double auto_hunt_down_time_ = 0.0;
|
||||
bool auto_hunt_long_pressed_ = false;
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
# port_platform — platform-layer implementations of the mirrored 40250 headers
|
||||
# (src/platform/<Lib>/<File>.cpp for src/port/<Lib>/<File>.h). Starts as the empty
|
||||
# skeleton generated by platform_stub.py; see docs/PORT-PLAN.md batch 2A. Added from port/CMakeLists.txt
|
||||
# so it shares the per-library definitions.
|
||||
|
||||
file(GLOB_RECURSE MT_PLATFORM_SOURCES CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
|
||||
# Same rule as port_logic: without the embedded interpreter there is no ScriptLib to back
|
||||
# (PORT-PLAN 批次 2P step 4 — Windows).
|
||||
if(NOT TARGET mtpython)
|
||||
list(FILTER MT_PLATFORM_SOURCES EXCLUDE REGEX "/ScriptLib/")
|
||||
endif()
|
||||
add_library(port_platform STATIC ${MT_PLATFORM_SOURCES})
|
||||
target_link_libraries(port_platform PUBLIC port_logic PRIVATE mt3p::freetype mt::libgr2)
|
||||
foreach(src IN LISTS MT_PLATFORM_SOURCES)
|
||||
mt_port_apply_defines(${src})
|
||||
endforeach()
|
||||
# ScriptLib/PythonBoot.cpp is UserInterface's RunMainScript; it sits under ScriptLib only because that is
|
||||
# the directory dropped when there is no embedded interpreter. Its `#ifdef _DISTRIBUTE` (__DEBUG__ = 0)
|
||||
# has to see the UserInterface library's definitions, not ScriptLib's.
|
||||
if(TARGET mtpython)
|
||||
set_property(SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/ScriptLib/PythonBoot.cpp
|
||||
PROPERTY COMPILE_DEFINITIONS ${MT_PORT_DEFINES_UserInterface})
|
||||
endif()
|
||||
@@ -0,0 +1,146 @@
|
||||
// Platform implementation of EterBase/CRC32.h (40250 EterBase/CRC32.cpp). GetCRC32/GetCaseCRC32 are the 40250
|
||||
// loops; CRCTable is the reflected IEEE 802.3 table (polynomial 0xEDB88320), which is what 40250 spells out
|
||||
// literally (checked entry for entry). The file variants read through a CFileBase-style FILE* handle.
|
||||
#include "EterBase/StdAfx.h"
|
||||
#include "EterBase/CRC32.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
struct CrcTable
|
||||
{
|
||||
DWORD v[256];
|
||||
CrcTable()
|
||||
{
|
||||
for (DWORD n = 0; n < 256; ++n)
|
||||
{
|
||||
DWORD c = n;
|
||||
for (int k = 0; k < 8; ++k)
|
||||
c = (c & 1) ? (0xEDB88320u ^ (c >> 1)) : (c >> 1);
|
||||
v[n] = c;
|
||||
}
|
||||
}
|
||||
};
|
||||
const CrcTable s_crcTable;
|
||||
}
|
||||
|
||||
#define CRCTable (s_crcTable.v)
|
||||
|
||||
#define DO1(buf, i) crc = CRCTable[(crc ^ buf[i]) & 0xff] ^ (crc >> 8)
|
||||
#define DO2(buf, i) DO1(buf, i); DO1(buf, i + 1);
|
||||
#define DO4(buf, i) DO2(buf, i); DO2(buf, i + 2);
|
||||
#define DO8(buf, i) DO4(buf, i); DO4(buf, i + 4);
|
||||
#define DO16(buf, i) DO8(buf, i); DO8(buf, i + 8);
|
||||
|
||||
DWORD GetCRC32(const char * buf, size_t len)
|
||||
{
|
||||
DWORD crc = 0xffffffff;
|
||||
|
||||
if (len >= 16)
|
||||
{
|
||||
do
|
||||
{
|
||||
DO16(buf, 0);
|
||||
|
||||
buf += 16;
|
||||
len -= 16;
|
||||
} while (len >= 16);
|
||||
}
|
||||
|
||||
if (len != 0)
|
||||
{
|
||||
do
|
||||
{
|
||||
DO1(buf, 0);
|
||||
++buf;
|
||||
--len;
|
||||
} while (len > 0);
|
||||
}
|
||||
|
||||
crc ^= 0xffffffff;
|
||||
return crc;
|
||||
}
|
||||
|
||||
#ifndef UPPER
|
||||
#define UPPER(c) (((c)>='a' && (c) <= 'z') ? ((c)+('A'-'a')) : (c))
|
||||
#endif
|
||||
|
||||
#define DO1CI(buf, i) crc = CRCTable[(crc ^ UPPER(buf[i])) & 0xff] ^ (crc >> 8)
|
||||
#define DO2CI(buf, i) DO1CI(buf, i); DO1CI(buf, i + 1);
|
||||
#define DO4CI(buf, i) DO2CI(buf, i); DO2CI(buf, i + 2);
|
||||
#define DO8CI(buf, i) DO4CI(buf, i); DO4CI(buf, i + 4);
|
||||
#define DO16CI(buf, i) DO8CI(buf, i); DO8CI(buf, i + 8);
|
||||
|
||||
DWORD GetCaseCRC32(const char * buf, size_t len)
|
||||
{
|
||||
DWORD crc = 0xffffffff;
|
||||
|
||||
if (16 <= len)
|
||||
{
|
||||
do
|
||||
{
|
||||
DO16CI(buf, 0);
|
||||
|
||||
buf += 16;
|
||||
len -= 16;
|
||||
} while (len >= 16);
|
||||
}
|
||||
|
||||
if (0 != len)
|
||||
{
|
||||
do
|
||||
{
|
||||
DO1CI(buf, 0);
|
||||
|
||||
++buf;
|
||||
--len;
|
||||
} while (len > 0);
|
||||
}
|
||||
|
||||
crc ^= 0xffffffff;
|
||||
return crc;
|
||||
}
|
||||
|
||||
DWORD GetHFILECRC32(HANDLE hFile)
|
||||
{
|
||||
// 40250 maps the whole file from offset 0 and CRCs it; the stream position is left where it was.
|
||||
FILE* fp = static_cast<FILE*>(hFile);
|
||||
const long pos = std::ftell(fp);
|
||||
std::fseek(fp, 0, SEEK_END);
|
||||
const long size = std::ftell(fp);
|
||||
std::fseek(fp, 0, SEEK_SET);
|
||||
std::vector<char> data(size > 0 ? size : 0);
|
||||
const size_t got = data.empty() ? 0 : std::fread(data.data(), 1, data.size(), fp);
|
||||
std::fseek(fp, pos, SEEK_SET);
|
||||
if (got != data.size() || data.empty())
|
||||
return 0;
|
||||
return GetCRC32(data.data(), data.size());
|
||||
}
|
||||
|
||||
DWORD GetFileCRC32(const char* c_szFileName)
|
||||
{
|
||||
FILE* fp = std::fopen(c_szFileName, "rb");
|
||||
if (!fp)
|
||||
return 0;
|
||||
|
||||
DWORD dwRetCRC32=GetHFILECRC32(fp);
|
||||
|
||||
std::fclose(fp);
|
||||
|
||||
return dwRetCRC32;
|
||||
}
|
||||
|
||||
DWORD GetFileSize(const char* c_szFileName)
|
||||
{
|
||||
FILE* fp = std::fopen(c_szFileName, "rb");
|
||||
if (!fp)
|
||||
return 0;
|
||||
|
||||
std::fseek(fp, 0, SEEK_END);
|
||||
const long size = std::ftell(fp);
|
||||
std::fclose(fp);
|
||||
|
||||
return size < 0 ? 0 : static_cast<DWORD>(size);
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
// Platform skeleton for EterBase/Debug.h (40250 EterBase/Debug.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation. Implemented
|
||||
// bodies follow the 40250 non-_DEBUG build (batch 2D: Trace*, TraceError to stderr).
|
||||
#include "EterBase/StdAfx.h"
|
||||
#include "EterBase/Debug.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
#include "EterBase/Timer.h"
|
||||
#include "LogBox.h"
|
||||
#include "TraceErrorObserver.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
|
||||
const DWORD DEBUG_STRING_MAX_LEN = 1024;
|
||||
|
||||
namespace
|
||||
{
|
||||
TraceErrorObserver g_trace_error_observer = nullptr;
|
||||
}
|
||||
|
||||
void SetTraceErrorObserver(TraceErrorObserver observer)
|
||||
{
|
||||
g_trace_error_observer = observer;
|
||||
}
|
||||
|
||||
auto SetLogLevel(UINT) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto Log(UINT, const char *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto Logn(UINT, const char *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto Logf(UINT, const char *, ...) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto Lognf(UINT, const char *, ...) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
void Trace(const char *)
|
||||
{
|
||||
// Non-_DEBUG 40250: written only to the log file (OpenLogFile, not implemented yet).
|
||||
}
|
||||
|
||||
void Tracen(const char *)
|
||||
{
|
||||
// Non-_DEBUG 40250: written only to the log file (OpenLogFile, not implemented yet).
|
||||
}
|
||||
|
||||
void Tracenf(const char *, ...)
|
||||
{
|
||||
// Non-_DEBUG 40250: written only to the log file (OpenLogFile, not implemented yet).
|
||||
}
|
||||
|
||||
void Tracef(const char *, ...)
|
||||
{
|
||||
// Non-_DEBUG 40250: written only to the log file (OpenLogFile, not implemented yet).
|
||||
}
|
||||
|
||||
void TraceError(const char* c_szFormat, ...)
|
||||
{
|
||||
#ifndef _DISTRIBUTE
|
||||
char szBuf[DEBUG_STRING_MAX_LEN+2];
|
||||
|
||||
strncpy(szBuf, "SYSERR: ", DEBUG_STRING_MAX_LEN);
|
||||
int len = strlen(szBuf);
|
||||
|
||||
va_list args;
|
||||
va_start(args, c_szFormat);
|
||||
len = _vsnprintf(szBuf + len, sizeof(szBuf) - (len + 1), c_szFormat, args) + len;
|
||||
va_end(args);
|
||||
|
||||
// PORT: _vsnprintf returns -1 on truncation; 40250 then writes the terminator before the buffer.
|
||||
if (len < 8)
|
||||
len = DEBUG_STRING_MAX_LEN;
|
||||
|
||||
szBuf[len] = '\n';
|
||||
szBuf[len + 1] = '\0';
|
||||
|
||||
time_t ct = time(0);
|
||||
struct tm ctm = *localtime(&ct);
|
||||
|
||||
fprintf(stderr, "%02d%02d %02d:%02d:%05d :: %s",
|
||||
ctm.tm_mon + 1,
|
||||
ctm.tm_mday,
|
||||
ctm.tm_hour,
|
||||
ctm.tm_min,
|
||||
ELTimer_GetMSec() % 60000,
|
||||
szBuf + 8);
|
||||
fflush(stderr);
|
||||
if (g_trace_error_observer)
|
||||
g_trace_error_observer(szBuf + 8);
|
||||
#endif
|
||||
}
|
||||
|
||||
auto TraceErrorWithoutEnter(const char *, ...) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string& log_box_message()
|
||||
{
|
||||
static std::string message;
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& LastLogBoxMessage()
|
||||
{
|
||||
return log_box_message();
|
||||
}
|
||||
|
||||
void ClearLogBoxMessage()
|
||||
{
|
||||
log_box_message().clear();
|
||||
}
|
||||
|
||||
void LogBox(const char * c_szMsg, const char * c_szCaption, HWND)
|
||||
{
|
||||
// 40250 pops a MessageBox on g_PopupHwnd and Tracen()s the text. There is no window here and no
|
||||
// log file yet, so stderr is the whole of it; the text is also kept for the caller (LogBox.h),
|
||||
// because it is the only place a script traceback survives.
|
||||
std::string& message = log_box_message();
|
||||
message = c_szCaption ? c_szCaption : "LOG";
|
||||
message += ": ";
|
||||
message += c_szMsg ? c_szMsg : "";
|
||||
|
||||
fprintf(stderr, "%s\n", message.c_str());
|
||||
fflush(stderr);
|
||||
Tracen(c_szMsg);
|
||||
}
|
||||
|
||||
void LogBoxf(const char * c_szFormat, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, c_szFormat);
|
||||
|
||||
char szBuf[2048];
|
||||
_vsnprintf(szBuf, sizeof(szBuf), c_szFormat, args);
|
||||
va_end(args); // PORT: 40250 leaks the va_list; on this ABI va_end is not optional
|
||||
|
||||
// PORT: _vsnprintf does not terminate on truncation.
|
||||
szBuf[sizeof(szBuf) - 1] = '\0';
|
||||
LogBox(szBuf);
|
||||
}
|
||||
|
||||
auto LogFile(const char *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto LogFilef(const char *, ...) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto OpenConsoleWindow() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CloseConsoleWindow() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto SetupLog() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto OpenLogFile(bool) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CloseLogFile() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
// Platform implementation of EterBase/FileBase.h (40250 EterBase/FileBase.cpp). 40250 wraps a Win32 file
|
||||
// HANDLE; here m_hFile holds a stdio FILE* on every target, with the same open modes (read: existing file,
|
||||
// shared for reading; write: read/write, created when missing, not truncated) and the same size bookkeeping.
|
||||
// Names are case-insensitive as on NTFS: 40250's pack/Index says "Etc" for ETC.eix, which a case-sensitive
|
||||
// file system (Android, Linux) only finds through the fallback below.
|
||||
#include "EterBase/StdAfx.h"
|
||||
#include "EterBase/FileBase.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#if !defined(_WIN32)
|
||||
#include <dirent.h>
|
||||
#include <strings.h>
|
||||
#endif
|
||||
|
||||
// fopen, then (POSIX) resolve each path component case-insensitively when the exact spelling is missing.
|
||||
static FILE* open_nocase(const char* filename, const char* mode)
|
||||
{
|
||||
FILE* fp = std::fopen(filename, mode);
|
||||
#if !defined(_WIN32)
|
||||
if (fp || !filename[0])
|
||||
return fp;
|
||||
|
||||
std::string path(filename);
|
||||
for (char& c : path)
|
||||
if (c == '\\')
|
||||
c = '/';
|
||||
|
||||
std::string resolved = path[0] == '/' ? "/" : "";
|
||||
size_t pos = path[0] == '/' ? 1 : 0;
|
||||
while (pos <= path.size())
|
||||
{
|
||||
size_t end = path.find('/', pos);
|
||||
if (end == std::string::npos)
|
||||
end = path.size();
|
||||
const std::string part = path.substr(pos, end - pos);
|
||||
pos = end + 1;
|
||||
if (part.empty() || part == "." || part == "..")
|
||||
{
|
||||
if (!part.empty())
|
||||
resolved += part + "/";
|
||||
continue;
|
||||
}
|
||||
|
||||
DIR* dir = opendir(resolved.empty() ? "." : resolved.c_str());
|
||||
if (!dir)
|
||||
return NULL;
|
||||
std::string match;
|
||||
while (dirent* e = readdir(dir))
|
||||
{
|
||||
if (strcasecmp(e->d_name, part.c_str()) == 0)
|
||||
{
|
||||
match = e->d_name;
|
||||
if (match == part)
|
||||
break;
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
if (match.empty())
|
||||
return NULL;
|
||||
resolved += match;
|
||||
if (end != path.size())
|
||||
resolved += "/";
|
||||
}
|
||||
fp = std::fopen(resolved.c_str(), mode);
|
||||
#endif
|
||||
return fp;
|
||||
}
|
||||
|
||||
static FILE* file_of(HANDLE h)
|
||||
{
|
||||
return static_cast<FILE*>(h);
|
||||
}
|
||||
|
||||
static DWORD stream_size(FILE* fp)
|
||||
{
|
||||
const long pos = std::ftell(fp);
|
||||
std::fseek(fp, 0, SEEK_END);
|
||||
const long size = std::ftell(fp);
|
||||
std::fseek(fp, pos, SEEK_SET);
|
||||
return size < 0 ? 0 : static_cast<DWORD>(size);
|
||||
}
|
||||
|
||||
CFileBase::CFileBase() : m_hFile(NULL), m_dwSize(0)
|
||||
{
|
||||
}
|
||||
|
||||
CFileBase::~CFileBase()
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
|
||||
char * CFileBase::GetFileName()
|
||||
{
|
||||
return m_filename;
|
||||
}
|
||||
|
||||
void CFileBase::Destroy()
|
||||
{
|
||||
Close();
|
||||
m_dwSize = 0;
|
||||
}
|
||||
|
||||
void CFileBase::Close()
|
||||
{
|
||||
if (m_hFile)
|
||||
{
|
||||
std::fclose(file_of(m_hFile));
|
||||
m_hFile = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL CFileBase::Create(const char* filename, EFileMode mode)
|
||||
{
|
||||
Destroy();
|
||||
|
||||
strncpy(m_filename, filename, MAX_PATH);
|
||||
|
||||
FILE* fp = NULL;
|
||||
if (mode == FILEMODE_WRITE)
|
||||
{
|
||||
// OPEN_ALWAYS + GENERIC_READ|GENERIC_WRITE: open without truncating, create when missing.
|
||||
fp = open_nocase(filename, "r+b");
|
||||
if (!fp)
|
||||
fp = std::fopen(filename, "w+b");
|
||||
}
|
||||
else
|
||||
fp = open_nocase(filename, "rb");
|
||||
|
||||
if (fp)
|
||||
{
|
||||
m_hFile = fp;
|
||||
m_dwSize = stream_size(fp);
|
||||
m_mode = mode;
|
||||
return true;
|
||||
}
|
||||
|
||||
m_hFile = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD CFileBase::Size()
|
||||
{
|
||||
return (m_dwSize);
|
||||
}
|
||||
|
||||
void CFileBase::SeekCur(DWORD size)
|
||||
{
|
||||
// SetFilePointer takes the low DWORD as a signed distance when the high part is NULL.
|
||||
std::fseek(file_of(m_hFile), static_cast<LONG>(size), SEEK_CUR);
|
||||
}
|
||||
|
||||
void CFileBase::Seek(DWORD offset)
|
||||
{
|
||||
if (offset > m_dwSize)
|
||||
offset = m_dwSize;
|
||||
|
||||
std::fseek(file_of(m_hFile), static_cast<long>(offset), SEEK_SET);
|
||||
}
|
||||
|
||||
DWORD CFileBase::GetPosition()
|
||||
{
|
||||
return static_cast<DWORD>(std::ftell(file_of(m_hFile)));
|
||||
}
|
||||
|
||||
BOOL CFileBase::Write(const void* src, int bytes)
|
||||
{
|
||||
FILE* fp = file_of(m_hFile);
|
||||
if (std::fwrite(src, 1, bytes, fp) != static_cast<size_t>(bytes))
|
||||
return false;
|
||||
|
||||
std::fflush(fp);
|
||||
m_dwSize = stream_size(fp);
|
||||
return true;
|
||||
}
|
||||
|
||||
BOOL CFileBase::Read(void* dest, int bytes)
|
||||
{
|
||||
// ReadFile succeeds on a short read (end of file); only an I/O error fails.
|
||||
FILE* fp = file_of(m_hFile);
|
||||
std::fread(dest, 1, bytes, fp);
|
||||
return !std::ferror(fp);
|
||||
}
|
||||
|
||||
BOOL CFileBase::IsNull()
|
||||
{
|
||||
return !m_hFile ? true : false;
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// Platform implementation of EterBase/FileDir.h (40250 EterBase/FileDir.cpp): the 40250 code as is, over the
|
||||
// FindFirstFile/FindNextFile/FindClose of common/Win32Crt.h.
|
||||
#include "EterBase/StdAfx.h"
|
||||
#include "EterBase/FileDir.h"
|
||||
#include <string>
|
||||
|
||||
CDir::CDir()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
CDir::~CDir()
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void CDir::Destroy()
|
||||
{
|
||||
if (m_hFind)
|
||||
FindClose(m_hFind);
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
bool CDir::Create(const char * c_szFilter, const char* c_szPath, BOOL bCheckedExtension)
|
||||
{
|
||||
Destroy();
|
||||
|
||||
std::string stPath = c_szPath;
|
||||
|
||||
if (stPath.length())
|
||||
{
|
||||
char end = stPath[stPath.length() - 1];
|
||||
|
||||
if (end != '\\')
|
||||
stPath+='\\';
|
||||
}
|
||||
|
||||
std::string stQuery;
|
||||
stQuery += stPath;
|
||||
stQuery += "*.*";
|
||||
|
||||
m_wfd.dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
|
||||
m_hFind = FindFirstFile(stQuery.c_str(), &m_wfd);
|
||||
|
||||
if (m_hFind == INVALID_HANDLE_VALUE)
|
||||
return true;
|
||||
|
||||
do
|
||||
{
|
||||
if (*m_wfd.cFileName == '.')
|
||||
continue;
|
||||
|
||||
if (IsFolder())
|
||||
{
|
||||
if (!OnFolder(c_szFilter, stPath.c_str(), m_wfd.cFileName))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
const char * c_szExtension = strchr(m_wfd.cFileName, '.');
|
||||
if (!c_szExtension)
|
||||
continue;
|
||||
|
||||
// NOTE : 임시 변수 - [levites]
|
||||
// 최종적으로는 무조건 TRUE 형태로 만든다.
|
||||
// 그전에 전 프로젝트의 CDir을 사용하는 곳에서 Extension을 "wav", "gr2" 이런식으로 넣게끔 한다. - [levites]
|
||||
if (bCheckedExtension)
|
||||
{
|
||||
std::string strFilter = c_szFilter;
|
||||
int iPos = strFilter.find_first_of(';', 0);
|
||||
if (iPos > 0)
|
||||
{
|
||||
std::string strFirstFilter = std::string(c_szFilter).substr(0, iPos);
|
||||
std::string strSecondFilter = std::string(c_szFilter).substr(iPos+1, strlen(c_szFilter));
|
||||
if (0 != strFirstFilter.compare(c_szExtension+1) && 0 != strSecondFilter.compare(c_szExtension+1))
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (0 != stricmp(c_szExtension+1, c_szFilter))
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!OnFile(stPath.c_str(), m_wfd.cFileName))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
while (FindNextFile(m_hFind, &m_wfd));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDir::IsFolder()
|
||||
{
|
||||
if (m_wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CDir::Initialize()
|
||||
{
|
||||
memset(&m_wfd, 0, sizeof(m_wfd));
|
||||
m_hFind = NULL;
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
// Platform implementation of EterBase/FileLoader.h: the 40250 EterBase/FileLoader.cpp, which is portable C/stdio.
|
||||
// Only change: the CP949 lead-byte test casts to signed char (char is unsigned on Android/Linux arm64).
|
||||
#include "EterBase/StdAfx.h"
|
||||
#include "EterBase/FileLoader.h"
|
||||
#include <assert.h>
|
||||
|
||||
CMemoryTextFileLoader::CMemoryTextFileLoader()
|
||||
{
|
||||
}
|
||||
|
||||
CMemoryTextFileLoader::~CMemoryTextFileLoader()
|
||||
{
|
||||
}
|
||||
|
||||
bool CMemoryTextFileLoader::SplitLineByTab(DWORD dwLine, CTokenVector* pstTokenVector)
|
||||
{
|
||||
pstTokenVector->reserve(10);
|
||||
pstTokenVector->clear();
|
||||
|
||||
const std::string & c_rstLine = GetLineString(dwLine);
|
||||
const int c_iLineLength = c_rstLine.length();
|
||||
|
||||
if (0 == c_iLineLength)
|
||||
return false;
|
||||
|
||||
int basePos = 0;
|
||||
|
||||
do
|
||||
{
|
||||
int beginPos = c_rstLine.find_first_of("\t", basePos);
|
||||
|
||||
pstTokenVector->push_back(c_rstLine.substr(basePos, beginPos-basePos));
|
||||
|
||||
basePos = beginPos+1;
|
||||
} while (basePos < c_iLineLength && basePos > 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int CMemoryTextFileLoader::SplitLine2(DWORD dwLine, CTokenVector* pstTokenVector, const char * c_szDelimeter)
|
||||
{
|
||||
pstTokenVector->reserve(10);
|
||||
pstTokenVector->clear();
|
||||
|
||||
std::string stToken;
|
||||
const std::string & c_rstLine = GetLineString(dwLine);
|
||||
|
||||
DWORD basePos = 0;
|
||||
|
||||
do
|
||||
{
|
||||
int beginPos = c_rstLine.find_first_not_of(c_szDelimeter, basePos);
|
||||
|
||||
if (beginPos < 0)
|
||||
return -1;
|
||||
|
||||
int endPos;
|
||||
|
||||
if (c_rstLine[beginPos] == '"')
|
||||
{
|
||||
++beginPos;
|
||||
endPos = c_rstLine.find_first_of("\"", beginPos);
|
||||
|
||||
if (endPos < 0)
|
||||
return -2;
|
||||
|
||||
basePos = endPos + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
endPos = c_rstLine.find_first_of(c_szDelimeter, beginPos);
|
||||
basePos = endPos;
|
||||
}
|
||||
|
||||
pstTokenVector->push_back(c_rstLine.substr(beginPos, endPos - beginPos));
|
||||
|
||||
// 추가 코드. 맨뒤에 탭이 있는 경우를 체크한다. - [levites]
|
||||
if (int(c_rstLine.find_first_not_of(c_szDelimeter, basePos)) < 0)
|
||||
break;
|
||||
} while (basePos < c_rstLine.length());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool CMemoryTextFileLoader::SplitLine(DWORD dwLine, CTokenVector* pstTokenVector, const char * c_szDelimeter)
|
||||
{
|
||||
pstTokenVector->reserve(10);
|
||||
pstTokenVector->clear();
|
||||
|
||||
std::string stToken;
|
||||
const std::string & c_rstLine = GetLineString(dwLine);
|
||||
|
||||
DWORD basePos = 0;
|
||||
|
||||
do
|
||||
{
|
||||
int beginPos = c_rstLine.find_first_not_of(c_szDelimeter, basePos);
|
||||
if (beginPos < 0)
|
||||
return false;
|
||||
|
||||
int endPos;
|
||||
|
||||
if (c_rstLine[beginPos] == '"')
|
||||
{
|
||||
++beginPos;
|
||||
endPos = c_rstLine.find_first_of("\"", beginPos);
|
||||
|
||||
if (endPos < 0)
|
||||
return false;
|
||||
|
||||
basePos = endPos + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
endPos = c_rstLine.find_first_of(c_szDelimeter, beginPos);
|
||||
basePos = endPos;
|
||||
}
|
||||
|
||||
pstTokenVector->push_back(c_rstLine.substr(beginPos, endPos - beginPos));
|
||||
|
||||
// 추가 코드. 맨뒤에 탭이 있는 경우를 체크한다. - [levites]
|
||||
if (int(c_rstLine.find_first_not_of(c_szDelimeter, basePos)) < 0)
|
||||
break;
|
||||
} while (basePos < c_rstLine.length());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DWORD CMemoryTextFileLoader::GetLineCount()
|
||||
{
|
||||
return m_stLineVector.size();
|
||||
}
|
||||
|
||||
bool CMemoryTextFileLoader::CheckLineIndex(DWORD dwLine)
|
||||
{
|
||||
if (dwLine >= m_stLineVector.size())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string & CMemoryTextFileLoader::GetLineString(DWORD dwLine)
|
||||
{
|
||||
assert(CheckLineIndex(dwLine));
|
||||
return m_stLineVector[dwLine];
|
||||
}
|
||||
|
||||
void CMemoryTextFileLoader::Bind(int bufSize, const void* c_pvBuf)
|
||||
{
|
||||
m_stLineVector.reserve(128);
|
||||
m_stLineVector.clear();
|
||||
|
||||
const char * c_pcBuf = (const char *)c_pvBuf;
|
||||
std::string stLine;
|
||||
int pos = 0;
|
||||
|
||||
while (pos < bufSize)
|
||||
{
|
||||
const char c = c_pcBuf[pos++];
|
||||
|
||||
if ('\n' == c || '\r' == c)
|
||||
{
|
||||
if (pos < bufSize)
|
||||
if ('\n' == c_pcBuf[pos] || '\r' == c_pcBuf[pos])
|
||||
++pos;
|
||||
|
||||
m_stLineVector.push_back(stLine);
|
||||
stLine = "";
|
||||
}
|
||||
else if (static_cast<signed char>(c) < 0) // PORT: Win32 char is signed
|
||||
{
|
||||
stLine.append(c_pcBuf + (pos-1), 2);
|
||||
++pos;
|
||||
}
|
||||
else
|
||||
{
|
||||
stLine += c;
|
||||
}
|
||||
}
|
||||
|
||||
m_stLineVector.push_back(stLine);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
int CMemoryFileLoader::GetSize()
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
|
||||
int CMemoryFileLoader::GetPosition()
|
||||
{
|
||||
return m_pos;
|
||||
}
|
||||
|
||||
bool CMemoryFileLoader::IsReadableSize(int size)
|
||||
{
|
||||
if (m_pos + size > m_size)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CMemoryFileLoader::Read(int size, void* pvDst)
|
||||
{
|
||||
if (!IsReadableSize(size))
|
||||
return false;
|
||||
|
||||
memcpy(pvDst, GetCurrentPositionPointer(), size);
|
||||
m_pos += size;
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* CMemoryFileLoader::GetCurrentPositionPointer()
|
||||
{
|
||||
assert(m_pcBase != NULL);
|
||||
return (m_pcBase + m_pos);
|
||||
}
|
||||
|
||||
CMemoryFileLoader::CMemoryFileLoader(int size, const void* c_pvMemoryFile)
|
||||
{
|
||||
assert(c_pvMemoryFile != NULL);
|
||||
|
||||
m_pos = 0;
|
||||
m_size = size;
|
||||
m_pcBase = (const char *) c_pvMemoryFile;
|
||||
}
|
||||
|
||||
CMemoryFileLoader::~CMemoryFileLoader()
|
||||
{
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
int CDiskFileLoader::GetSize()
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
|
||||
bool CDiskFileLoader::Read(int size, void* pvDst)
|
||||
{
|
||||
assert(m_fp != NULL);
|
||||
|
||||
int ret = fread(pvDst, size, 1, m_fp);
|
||||
|
||||
if (ret <= 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDiskFileLoader::Open(const char* c_szFileName)
|
||||
{
|
||||
Close();
|
||||
|
||||
if (!c_szFileName[0])
|
||||
return false;
|
||||
|
||||
m_fp = fopen(c_szFileName, "rb");
|
||||
|
||||
if (!m_fp)
|
||||
return false;
|
||||
|
||||
fseek(m_fp, 0, SEEK_END);
|
||||
m_size = ftell(m_fp);
|
||||
fseek(m_fp, 0, SEEK_SET);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CDiskFileLoader::Close()
|
||||
{
|
||||
if (m_fp)
|
||||
fclose(m_fp);
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
void CDiskFileLoader::Initialize()
|
||||
{
|
||||
m_fp = NULL;
|
||||
m_size = 0;
|
||||
}
|
||||
|
||||
CDiskFileLoader::CDiskFileLoader()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
CDiskFileLoader::~CDiskFileLoader()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
// PORT: the player's frame-rate setting (system option dialog, app.Get/SetFrameRateMode), shared between
|
||||
// the script thread and the host's render loop (src/host/main.cpp), which paces frames by it and
|
||||
// saves it. 40250 has no counterpart: the PC client ran at the monitor's rate.
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace MtFrameRate {
|
||||
|
||||
enum EMode {
|
||||
MODE_SAVER = 0, // 30 fps
|
||||
MODE_STANDARD = 1, // 60 fps, 30 after a while without input
|
||||
MODE_HIGH = 2, // the display's highest rate (120 on current phones), 30 after a while without input
|
||||
MODE_COUNT
|
||||
};
|
||||
|
||||
inline std::atomic<int>& Mode()
|
||||
{
|
||||
static std::atomic<int> mode{MODE_STANDARD};
|
||||
return mode;
|
||||
}
|
||||
|
||||
inline int Get() { return Mode().load(std::memory_order_relaxed); }
|
||||
|
||||
inline void Set(int mode)
|
||||
{
|
||||
if (mode >= 0 && mode < MODE_COUNT)
|
||||
Mode().store(mode, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
} // namespace MtFrameRate
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
// The text 40250 would have put in a message box (EterBase/Debug.cpp LogBox/LogBoxf).
|
||||
//
|
||||
// LogBox is the client's fatal-error channel: Traceback() in ScriptLib/PythonLauncher.cpp reports every
|
||||
// failed script line through it, and RunMainScript's callers show it and quit. There is no message box
|
||||
// on this platform, so the implementation writes the text to stderr and keeps it here for whoever asked
|
||||
// for the work — that is how a caller learns *why* a script failed, since Traceback() fetches the
|
||||
// exception (and so clears it) before returning.
|
||||
|
||||
#include <string>
|
||||
|
||||
// The last LogBox/LogBoxf text, "" when none since the last ClearLogBoxMessage().
|
||||
const std::string& LastLogBoxMessage();
|
||||
void ClearLogBoxMessage();
|
||||
@@ -0,0 +1,272 @@
|
||||
// Platform implementation of EterBase/MappedFile.h (40250 EterBase/MappedFile.cpp). Everything but Map/Unmap
|
||||
// is the 40250 code; the view is a POSIX mmap (CreateFileMapping/MapViewOfFile on Windows) of the CFileBase
|
||||
// stream, aligned down to the allocation granularity exactly as 40250 does.
|
||||
#include "EterBase/StdAfx.h"
|
||||
#include "EterBase/MappedFile.h"
|
||||
#include "EterBase/Debug.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <io.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
static DWORD allocation_granularity()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
SYSTEM_INFO SysInfo;
|
||||
GetSystemInfo(&SysInfo);
|
||||
return SysInfo.dwAllocationGranularity;
|
||||
#else
|
||||
return static_cast<DWORD>(sysconf(_SC_PAGESIZE));
|
||||
#endif
|
||||
}
|
||||
|
||||
CMappedFile::CMappedFile() :
|
||||
m_hFM(NULL),
|
||||
m_lpMapData(NULL),
|
||||
m_dataOffset(0),
|
||||
m_mapSize(0),
|
||||
m_seekPosition(0),
|
||||
m_pLZObj(NULL),
|
||||
m_pbBufLinkData(NULL),
|
||||
m_dwBufLinkSize(0),
|
||||
m_pbAppendResultDataBlock(NULL),
|
||||
m_dwAppendResultDataSize(0)
|
||||
{
|
||||
}
|
||||
|
||||
CMappedFile::~CMappedFile()
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
|
||||
BOOL CMappedFile::Create(const char * filename)
|
||||
{
|
||||
Destroy();
|
||||
return CFileBase::Create(filename, FILEMODE_READ);
|
||||
}
|
||||
|
||||
BOOL CMappedFile::Create(const char * filename, const void** dest, int offset, int size)
|
||||
{
|
||||
if (!CMappedFile::Create(filename))
|
||||
return FALSE;
|
||||
|
||||
int ret = Map(dest, offset, size);
|
||||
return (ret) > 0;
|
||||
}
|
||||
|
||||
LPCVOID CMappedFile::Get()
|
||||
{
|
||||
return m_lpData;
|
||||
}
|
||||
|
||||
void CMappedFile::Link(DWORD dwBufSize, const void* c_pvBufData)
|
||||
{
|
||||
m_dwBufLinkSize=dwBufSize;
|
||||
m_pbBufLinkData=(BYTE*)c_pvBufData;
|
||||
}
|
||||
|
||||
void CMappedFile::BindLZObject(CLZObject * pLZObj)
|
||||
{
|
||||
assert(m_pLZObj == NULL);
|
||||
m_pLZObj = pLZObj;
|
||||
|
||||
Link(m_pLZObj->GetSize(), m_pLZObj->GetBuffer());
|
||||
}
|
||||
|
||||
void CMappedFile::BindLZObjectWithBufferedSize(CLZObject * pLZObj)
|
||||
{
|
||||
assert(m_pLZObj == NULL);
|
||||
m_pLZObj = pLZObj;
|
||||
|
||||
Link(m_pLZObj->GetBufferSize(), m_pLZObj->GetBuffer());
|
||||
}
|
||||
|
||||
BYTE* CMappedFile::AppendDataBlock( const void* pBlock, DWORD dwBlockSize )
|
||||
{
|
||||
if( m_pbAppendResultDataBlock )
|
||||
{
|
||||
delete []m_pbAppendResultDataBlock;
|
||||
}
|
||||
|
||||
//realloc
|
||||
m_dwAppendResultDataSize = m_dwBufLinkSize+dwBlockSize;
|
||||
m_pbAppendResultDataBlock = new BYTE[m_dwAppendResultDataSize];
|
||||
|
||||
memcpy(m_pbAppendResultDataBlock, m_pbBufLinkData, m_dwBufLinkSize );
|
||||
memcpy(m_pbAppendResultDataBlock + m_dwBufLinkSize, pBlock, dwBlockSize );
|
||||
|
||||
//redirect
|
||||
Link(m_dwAppendResultDataSize, m_pbAppendResultDataBlock);
|
||||
|
||||
return m_pbAppendResultDataBlock;
|
||||
}
|
||||
|
||||
void CMappedFile::Destroy()
|
||||
{
|
||||
if (m_pLZObj)
|
||||
{
|
||||
delete m_pLZObj;
|
||||
m_pLZObj = NULL;
|
||||
}
|
||||
|
||||
if (NULL != m_lpMapData)
|
||||
{
|
||||
Unmap(m_lpMapData);
|
||||
m_lpMapData = NULL;
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
if (NULL != m_hFM)
|
||||
{
|
||||
CloseHandle(m_hFM);
|
||||
m_hFM = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
if( m_pbAppendResultDataBlock )
|
||||
{
|
||||
delete []m_pbAppendResultDataBlock;
|
||||
m_pbAppendResultDataBlock = NULL;
|
||||
}
|
||||
|
||||
m_dwAppendResultDataSize = 0;
|
||||
|
||||
m_pbBufLinkData = NULL;
|
||||
m_dwBufLinkSize = 0;
|
||||
|
||||
m_seekPosition = 0;
|
||||
m_dataOffset = 0;
|
||||
m_mapSize = 0;
|
||||
|
||||
CFileBase::Destroy();
|
||||
}
|
||||
|
||||
int CMappedFile::Seek(DWORD offset, int iSeekType)
|
||||
{
|
||||
switch (iSeekType)
|
||||
{
|
||||
case SEEK_TYPE_BEGIN:
|
||||
if (offset > m_dwSize)
|
||||
offset = m_dwSize;
|
||||
|
||||
m_seekPosition = offset;
|
||||
break;
|
||||
|
||||
case SEEK_TYPE_CURRENT:
|
||||
m_seekPosition = min(m_seekPosition + offset, Size());
|
||||
break;
|
||||
|
||||
case SEEK_TYPE_END:
|
||||
m_seekPosition = max(0, Size() - offset);
|
||||
break;
|
||||
}
|
||||
|
||||
return m_seekPosition;
|
||||
}
|
||||
|
||||
int CMappedFile::Map(const void **dest, int offset, int size)
|
||||
{
|
||||
m_dataOffset = offset;
|
||||
|
||||
if (size == 0)
|
||||
m_mapSize = m_dwSize;
|
||||
else
|
||||
m_mapSize = size;
|
||||
|
||||
if (m_dataOffset + m_mapSize > m_dwSize)
|
||||
return 0;
|
||||
|
||||
DWORD dwSysGran = allocation_granularity();
|
||||
DWORD dwFileMapStart = (m_dataOffset / dwSysGran) * dwSysGran;
|
||||
DWORD dwMapViewSize = (m_dataOffset % dwSysGran) + m_mapSize;
|
||||
INT iViewDelta = m_dataOffset - dwFileMapStart;
|
||||
|
||||
FILE* fp = static_cast<FILE*>(m_hFile);
|
||||
#if defined(_WIN32)
|
||||
m_hFM = CreateFileMapping((HANDLE) _get_osfhandle(_fileno(fp)), NULL, PAGE_READONLY, 0,
|
||||
m_dataOffset + m_mapSize, NULL);
|
||||
if (!m_hFM)
|
||||
{
|
||||
OutputDebugString("CMappedFile::Map !m_hFM\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
m_lpMapData = MapViewOfFile(m_hFM, FILE_MAP_READ, 0, dwFileMapStart, dwMapViewSize);
|
||||
if (!m_lpMapData)
|
||||
{
|
||||
TraceError("CMappedFile::Map !m_lpMapData %lu", GetLastError());
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
// A zero-length view (empty file) is a valid 40250 map; mmap rejects length 0.
|
||||
if (dwMapViewSize == 0)
|
||||
dwMapViewSize = 1;
|
||||
void* p = mmap(NULL, dwMapViewSize, PROT_READ, MAP_PRIVATE, fileno(fp), static_cast<off_t>(dwFileMapStart));
|
||||
if (p == MAP_FAILED)
|
||||
{
|
||||
TraceError("CMappedFile::Map !m_lpMapData %s", GetFileName());
|
||||
return 0;
|
||||
}
|
||||
m_lpMapData = p;
|
||||
#endif
|
||||
|
||||
m_lpData = (char*) m_lpMapData + iViewDelta;
|
||||
*dest = (char*) m_lpData;
|
||||
m_seekPosition = 0;
|
||||
|
||||
Link(m_mapSize, m_lpData);
|
||||
|
||||
return (m_mapSize);
|
||||
}
|
||||
|
||||
BYTE * CMappedFile::GetCurrentSeekPoint()
|
||||
{
|
||||
return m_pbBufLinkData+m_seekPosition;
|
||||
}
|
||||
|
||||
DWORD CMappedFile::Size()
|
||||
{
|
||||
return m_dwBufLinkSize;
|
||||
}
|
||||
|
||||
DWORD CMappedFile::GetPosition()
|
||||
{
|
||||
return m_dataOffset;
|
||||
}
|
||||
|
||||
BOOL CMappedFile::Read(void * dest, int bytes)
|
||||
{
|
||||
if (m_seekPosition + bytes > Size())
|
||||
return FALSE;
|
||||
|
||||
memcpy(dest, GetCurrentSeekPoint(), bytes);
|
||||
m_seekPosition += bytes;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
DWORD CMappedFile::GetSeekPosition(void)
|
||||
{
|
||||
return m_seekPosition;
|
||||
}
|
||||
|
||||
void CMappedFile::Unmap(LPCVOID data)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
if (!UnmapViewOfFile(data))
|
||||
TraceError("CMappedFile::Unmap - Error");
|
||||
#else
|
||||
// Same view length Map computed from the (unchanged) offset and size.
|
||||
const DWORD dwSysGran = allocation_granularity();
|
||||
DWORD dwMapViewSize = (m_dataOffset % dwSysGran) + m_mapSize;
|
||||
if (dwMapViewSize == 0)
|
||||
dwMapViewSize = 1;
|
||||
if (munmap(const_cast<void*>(data), dwMapViewSize) != 0)
|
||||
TraceError("CMappedFile::Unmap - Error");
|
||||
#endif
|
||||
m_lpData = NULL;
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
#pragma once
|
||||
// PORT: performance telemetry (40250 has no counterpart). The script thread adds the wall time of the
|
||||
// sections of CPythonApplication::Process() and of the Python callbacks here; the native host
|
||||
// (src/host/perf_log.h) reads and clears the totals every sample window. The two threads never run
|
||||
// at the same time (PythonBoot hands one baton between them), so plain fields are enough.
|
||||
//
|
||||
// Nothing is timed until MtPerf::Enabled() is set, so a build without --perf-log pays one branch per
|
||||
// section.
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace MtPerf
|
||||
{
|
||||
enum ESection
|
||||
{
|
||||
SECTION_PROCESS, // the whole CPythonApplication::Process()
|
||||
SECTION_NETWORK, // CPythonNetworkStream + CAccountConnector Process
|
||||
SECTION_CAMERA, // __UpdateCamera, CResourceManager::Update, OnCameraUpdate, OnMouseUpdate
|
||||
SECTION_UI_UPDATE, // OnUIUpdate (WindowManager Update -> Python OnUpdate)
|
||||
SECTION_UPDATE_GAME, // app.UpdateGame, inside UI_UPDATE
|
||||
SECTION_RENDER_BEGIN, // CCullingManager::Update + the command-list restarts
|
||||
SECTION_UI_RENDER, // OnUIRender (Python OnRender) + OnMouseRender
|
||||
SECTION_RENDER_GAME, // app.RenderGame, inside UI_RENDER
|
||||
SECTION_PYTHON, // outermost C++ -> Python callbacks (includes UPDATE_GAME/RENDER_GAME)
|
||||
SECTION_SHADOW_RASTER, // RecordingDevice's CPU rasterization of the shadow texture, inside RENDER_GAME
|
||||
SECTION_COUNT,
|
||||
};
|
||||
|
||||
struct SCounters
|
||||
{
|
||||
double ms[SECTION_COUNT] = {};
|
||||
unsigned calls[SECTION_COUNT] = {};
|
||||
unsigned pyMissing = 0; // callbacks whose method does not exist
|
||||
int actorCount = 0; // CPythonCharacterManager alive instances, last Process()
|
||||
int scriptCpu = -1; // CPU the script thread last ran on (Linux/Android), else -1
|
||||
};
|
||||
|
||||
inline bool& Enabled()
|
||||
{
|
||||
static bool s_bEnabled = false;
|
||||
return s_bEnabled;
|
||||
}
|
||||
|
||||
inline SCounters& Counters()
|
||||
{
|
||||
static SCounters s_kCounters;
|
||||
return s_kCounters;
|
||||
}
|
||||
|
||||
inline int& PythonDepth()
|
||||
{
|
||||
static int s_iDepth = 0;
|
||||
return s_iDepth;
|
||||
}
|
||||
|
||||
class CScope
|
||||
{
|
||||
public:
|
||||
explicit CScope(ESection eSection) : m_eSection(eSection), m_bActive(Enabled())
|
||||
{
|
||||
if (m_bActive)
|
||||
m_kStart = std::chrono::steady_clock::now();
|
||||
}
|
||||
~CScope()
|
||||
{
|
||||
if (!m_bActive)
|
||||
return;
|
||||
SCounters& rkCounters = Counters();
|
||||
rkCounters.ms[m_eSection] += std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - m_kStart).count();
|
||||
++rkCounters.calls[m_eSection];
|
||||
}
|
||||
|
||||
private:
|
||||
ESection m_eSection;
|
||||
bool m_bActive;
|
||||
std::chrono::steady_clock::time_point m_kStart;
|
||||
};
|
||||
|
||||
// Times only the outermost Python callback, so nested callbacks are not counted twice.
|
||||
class CPythonScope
|
||||
{
|
||||
public:
|
||||
CPythonScope() : m_bActive(Enabled() && PythonDepth()++ == 0)
|
||||
{
|
||||
if (m_bActive)
|
||||
m_kStart = std::chrono::steady_clock::now();
|
||||
}
|
||||
~CPythonScope()
|
||||
{
|
||||
if (!Enabled())
|
||||
return;
|
||||
if (PythonDepth() > 0)
|
||||
--PythonDepth();
|
||||
if (!m_bActive)
|
||||
return;
|
||||
SCounters& rkCounters = Counters();
|
||||
rkCounters.ms[SECTION_PYTHON] += std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - m_kStart).count();
|
||||
++rkCounters.calls[SECTION_PYTHON];
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_bActive;
|
||||
std::chrono::steady_clock::time_point m_kStart;
|
||||
};
|
||||
|
||||
inline void CountPythonMissing()
|
||||
{
|
||||
if (Enabled())
|
||||
++Counters().pyMissing;
|
||||
}
|
||||
|
||||
// The totals since the last call, which clears them.
|
||||
inline SCounters Take()
|
||||
{
|
||||
SCounters kTaken = Counters();
|
||||
SCounters& rkCounters = Counters();
|
||||
const int iActors = rkCounters.actorCount;
|
||||
const int iCpu = rkCounters.scriptCpu;
|
||||
rkCounters = SCounters();
|
||||
rkCounters.actorCount = iActors;
|
||||
rkCounters.scriptCpu = iCpu;
|
||||
return kTaken;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Platform implementation of EterBase/TempFile.h (40250 EterBase/TempFile.cpp): the 40250 code as is; the file
|
||||
// name comes from the platform CreateTempFileName and DeleteFile is the common/Win32Crt.h shim.
|
||||
#include "EterBase/StdAfx.h"
|
||||
#include "EterBase/TempFile.h"
|
||||
#include "EterBase/Utils.h"
|
||||
#include "EterBase/Debug.h"
|
||||
|
||||
CTempFile::~CTempFile()
|
||||
{
|
||||
Destroy();
|
||||
DeleteFile(m_szFileName);
|
||||
}
|
||||
|
||||
CTempFile::CTempFile(const char * c_pszPrefix)
|
||||
{
|
||||
strncpy(m_szFileName, CreateTempFileName(c_pszPrefix), MAX_PATH);
|
||||
|
||||
if (!Create(m_szFileName, CFileBase::FILEMODE_WRITE))
|
||||
{
|
||||
TraceError("CTempFile::CTempFile cannot create temporary file. (filename: %s)", m_szFileName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
// Test hook: sees every TraceError line (without the "SYSERR: " prefix) after it is written to stderr.
|
||||
// The 40250 client only writes these to syserr.txt; tests use it to fail on, e.g., the stream's
|
||||
// "Unknown packet header" (CPythonNetworkStream::CheckPacket), which otherwise only clears the buffer.
|
||||
|
||||
using TraceErrorObserver = void (*)(const char* line);
|
||||
|
||||
void SetTraceErrorObserver(TraceErrorObserver observer);
|
||||
@@ -0,0 +1,616 @@
|
||||
// Platform implementation of EterBase/Utils.h (40250 EterBase/Utils.cpp). The pure string/path helpers are the
|
||||
// 40250 bodies verbatim (PORT-PLAN §3); Win32 calls are swapped for POSIX ones where marked PORT.
|
||||
// stl_lowers lives in EterBase/Stl.cpp.
|
||||
#include "EterBase/StdAfx.h"
|
||||
#include "EterBase/Utils.h"
|
||||
#include "EterBase/Stl.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
|
||||
// PORT: GetTempPath/GetTempFileName -> $TMPDIR + mkstemp (same "<prefix>XXXX" unique-file contract).
|
||||
const char * CreateTempFileName(const char * c_pszPrefix)
|
||||
{
|
||||
static char szTempName[MAX_PATH + 1];
|
||||
|
||||
const char * tmp = getenv("TMPDIR");
|
||||
snprintf(szTempName, sizeof(szTempName), "%s/%sXXXXXX", tmp && *tmp ? tmp : "/tmp", c_pszPrefix ? c_pszPrefix : "etb");
|
||||
int fd = mkstemp(szTempName);
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
|
||||
return (szTempName);
|
||||
}
|
||||
|
||||
void GetFilePathNameExtension(const char * c_szFile, int len, std::string * pstPath, std::string * pstName, std::string * pstExt)
|
||||
{
|
||||
assert(pstPath != NULL);
|
||||
assert(pstName != NULL);
|
||||
assert(pstExt != NULL);
|
||||
|
||||
int ext = len;
|
||||
int pos = len;
|
||||
|
||||
while (pos > 0)
|
||||
{
|
||||
--pos;
|
||||
char c = c_szFile[pos];
|
||||
|
||||
if (ext == len && c == '.')
|
||||
{
|
||||
ext = pos;
|
||||
break;
|
||||
}
|
||||
|
||||
if (c == '/' || c == '\\')
|
||||
break;
|
||||
}
|
||||
|
||||
while (pos > 0)
|
||||
{
|
||||
--pos;
|
||||
char c = c_szFile[pos];
|
||||
|
||||
if (c == '/' || c == '\\')
|
||||
break;
|
||||
}
|
||||
|
||||
if (pos)
|
||||
{
|
||||
++pos;
|
||||
pstPath->append(c_szFile, pos);
|
||||
}
|
||||
|
||||
if (ext > pos)
|
||||
pstName->append(c_szFile + pos, ext - pos);
|
||||
|
||||
++ext;
|
||||
|
||||
if (len > ext)
|
||||
pstExt->append(c_szFile + ext, len - ext);
|
||||
}
|
||||
|
||||
void GetFileExtension(const char* c_szFile, int len, std::string* pstExt)
|
||||
{
|
||||
int ext = len;
|
||||
int pos = len;
|
||||
while (pos > 0)
|
||||
{
|
||||
--pos;
|
||||
char c=c_szFile[pos];
|
||||
if (ext==len && c=='.')
|
||||
{
|
||||
ext=pos;
|
||||
break;
|
||||
}
|
||||
|
||||
if (c=='/') break;
|
||||
else if (c=='\\') break;
|
||||
}
|
||||
|
||||
++ext;
|
||||
if (len>ext)
|
||||
pstExt->append(c_szFile+ext, len-ext);
|
||||
}
|
||||
|
||||
void GetFileNameParts(const char* c_szFile, int len, char* pszPath, char* pszName, char* pszExt)
|
||||
{
|
||||
assert(pszPath!=NULL);
|
||||
assert(pszName!=NULL);
|
||||
assert(pszExt!=NULL);
|
||||
|
||||
int ext=len;
|
||||
int pos=len;
|
||||
while (pos>0)
|
||||
{
|
||||
--pos;
|
||||
char c=c_szFile[pos];
|
||||
if (ext==len && c=='.')
|
||||
{
|
||||
ext=pos;
|
||||
break;
|
||||
}
|
||||
|
||||
if (c=='/') break;
|
||||
else if (c=='\\') break;
|
||||
}
|
||||
|
||||
while (pos>0)
|
||||
{
|
||||
--pos;
|
||||
char c=c_szFile[pos];
|
||||
|
||||
if (c=='/') break;
|
||||
else if (c=='\\') break;
|
||||
}
|
||||
|
||||
if (pos)
|
||||
{
|
||||
++pos;
|
||||
for (int i = 0; i < pos; ++i)
|
||||
{
|
||||
pszPath[i] = c_szFile[i];
|
||||
}
|
||||
pszPath[pos] = '\0';
|
||||
}
|
||||
|
||||
if (ext>pos)
|
||||
{
|
||||
int count = 0;
|
||||
for (int i = pos; i < ext; ++i)
|
||||
{
|
||||
pszName[count++] = c_szFile[i];
|
||||
}
|
||||
pszName[count] = '\0';
|
||||
}
|
||||
|
||||
++ext;
|
||||
if (len > ext)
|
||||
{
|
||||
int count = 0;
|
||||
for (int i = ext; i < len; ++i)
|
||||
{
|
||||
pszExt[count++] = c_szFile[i];
|
||||
}
|
||||
pszExt[count] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
// PORT: _ecvt(Index, 256, &dec, &sign) truncated at dec is the unsigned decimal digits of Index.
|
||||
void GetOldIndexingName(char * szName, int Index)
|
||||
{
|
||||
char Temp[512];
|
||||
|
||||
snprintf(Temp, sizeof(Temp), "%u", Index < 0 ? 0u - (unsigned) Index : (unsigned) Index);
|
||||
|
||||
strcat(szName, Temp);
|
||||
}
|
||||
|
||||
void GetIndexingName(char * szName, DWORD Index)
|
||||
{
|
||||
sprintf(szName + strlen(szName), "%u", Index);
|
||||
}
|
||||
|
||||
void GetOnlyFileName(const char * sz_Name, std::string & strFileName)
|
||||
{
|
||||
strFileName = "";
|
||||
|
||||
int i;
|
||||
for (i=strlen(sz_Name)-1; i>=0; --i)
|
||||
{
|
||||
if ('\\' == sz_Name[i] || '/' == sz_Name[i])
|
||||
{
|
||||
++i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == -1)
|
||||
i = 0;
|
||||
|
||||
for (size_t j = i; j < strlen(sz_Name); ++j)
|
||||
{
|
||||
strFileName += sz_Name[j];
|
||||
}
|
||||
|
||||
strFileName += "\0";
|
||||
}
|
||||
|
||||
void GetOnlyPathName(const char * sz_Name, std::string & OnlyPathName)
|
||||
{
|
||||
int i;
|
||||
for (i = strlen(sz_Name) - 1; i >= 0; --i)
|
||||
{
|
||||
if ('\\' == sz_Name[i] || '/' == sz_Name[i])
|
||||
{
|
||||
++i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == -1)
|
||||
i = 0;
|
||||
|
||||
OnlyPathName.reserve(strlen(sz_Name));
|
||||
OnlyPathName = "";
|
||||
|
||||
for (int j=0; j<i; ++j)
|
||||
{
|
||||
OnlyPathName += sz_Name[j];
|
||||
}
|
||||
OnlyPathName += "\0";
|
||||
}
|
||||
|
||||
const char * GetOnlyPathName(const char * c_szName)
|
||||
{
|
||||
static std::string strPathName;
|
||||
GetOnlyPathName(c_szName, strPathName);
|
||||
|
||||
return strPathName.c_str();
|
||||
}
|
||||
|
||||
bool GetLocalFileName(const char * c_szGlobalPath, const char * c_szFullPathFileName, std::string * pstrLocalFileName)
|
||||
{
|
||||
std::string strLocalFileName;
|
||||
|
||||
std::string strGlobalPath;
|
||||
std::string strFullPathFileName;
|
||||
StringPath(c_szGlobalPath, strGlobalPath);
|
||||
StringPath(c_szFullPathFileName, strFullPathFileName);
|
||||
|
||||
if (strGlobalPath.length() >= strFullPathFileName.length())
|
||||
return false;
|
||||
|
||||
DWORD length = std::min(strGlobalPath.length(), strFullPathFileName.length()); // PORT: min -> std::min
|
||||
for (DWORD dwPos = 0; dwPos < length; ++dwPos)
|
||||
{
|
||||
if (strGlobalPath[dwPos] != strFullPathFileName[dwPos])
|
||||
return false;
|
||||
}
|
||||
|
||||
*pstrLocalFileName = &c_szFullPathFileName[length];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GetExceptionPathName(const char * sz_Name, std::string & OnlyFileName)
|
||||
{
|
||||
GetOnlyFileName(sz_Name, OnlyFileName);
|
||||
}
|
||||
|
||||
void GetWorkingFolder(std::string & strFileName)
|
||||
{
|
||||
char buf[128+1];
|
||||
if (!getcwd(buf, 128)) // PORT: _getcwd -> getcwd
|
||||
buf[0] = '\0';
|
||||
strcat(buf, "/");
|
||||
strFileName = buf;
|
||||
}
|
||||
|
||||
void StringLowers(char * String)
|
||||
{
|
||||
for (DWORD i = 0; i < strlen(String); ++i)
|
||||
{
|
||||
String[i] = korean_tolower(String[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void StringPath(std::string & rString)
|
||||
{
|
||||
for (DWORD i = 0; i < rString.length(); ++i)
|
||||
{
|
||||
if (rString[i] == '\\')
|
||||
rString[i] = '/';
|
||||
else
|
||||
rString[i] = korean_tolower(rString[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void StringPath(char * pString)
|
||||
{
|
||||
for (DWORD i = 0; i < strlen(pString); ++i)
|
||||
{
|
||||
if (pString[i] == '\\')
|
||||
pString[i] = '/';
|
||||
else
|
||||
pString[i] = korean_tolower(pString[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void StringPath(const char * c_szSrc, char * szDest)
|
||||
{
|
||||
for (DWORD i = 0; i < strlen(c_szSrc); ++i)
|
||||
{
|
||||
if (c_szSrc[i] == '\\')
|
||||
szDest[i] = '/';
|
||||
else
|
||||
szDest[i] = korean_tolower(c_szSrc[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void StringPath(const char * c_szSrc, std::string & rString)
|
||||
{
|
||||
rString = "";
|
||||
rString.resize(strlen(c_szSrc));
|
||||
|
||||
for (DWORD i = 0; i < strlen(c_szSrc); ++i)
|
||||
{
|
||||
if (c_szSrc[i] == '\\')
|
||||
rString[i] = '/';
|
||||
else
|
||||
rString[i] = korean_tolower(c_szSrc[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#define ishprint(x) ((((x) & 0xE0) > 0x90) || isprint(x))
|
||||
|
||||
void PrintAsciiData(const void* void_data, int bytes)
|
||||
{
|
||||
int i, j, k;
|
||||
const unsigned char* p;
|
||||
const unsigned char* data;
|
||||
|
||||
data = (const unsigned char*) void_data;
|
||||
|
||||
fprintf(stdout, "------------------------------------------------------------------\n");
|
||||
j = bytes;
|
||||
while (1)
|
||||
{
|
||||
k = j >= 16 ? 16 : j;
|
||||
|
||||
p = data;
|
||||
for (i = 0; i < 16; ++i)
|
||||
{
|
||||
if (i >= k)
|
||||
fprintf(stdout, " ");
|
||||
else
|
||||
fprintf(stdout, "%02x ", *p);
|
||||
p++;
|
||||
}
|
||||
|
||||
fprintf(stdout, "| ");
|
||||
|
||||
p = data;
|
||||
for (i = 0; i < k; ++i)
|
||||
{
|
||||
if (i >= k)
|
||||
fprintf(stdout, " ");
|
||||
else
|
||||
fprintf(stdout, "%c", ishprint(*p) ? *p : '.');
|
||||
p++;
|
||||
}
|
||||
|
||||
fprintf(stdout, "\n");
|
||||
|
||||
j -= 16;
|
||||
data += 16;
|
||||
|
||||
if (j <= 0)
|
||||
break;
|
||||
}
|
||||
|
||||
fprintf(stdout, "------------------------------------------------------------------\n");
|
||||
}
|
||||
|
||||
bool IsFile(const char* filename)
|
||||
{
|
||||
return access(filename, 0) == 0 ? true : false; // PORT: _access -> access
|
||||
}
|
||||
|
||||
bool IsGlobalFileName(const char * c_szFileName)
|
||||
{
|
||||
return strchr(c_szFileName, ':') != NULL;
|
||||
}
|
||||
|
||||
auto MIN(int a, int b) -> int
|
||||
{
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
auto MAX(int a, int b) -> int
|
||||
{
|
||||
return a > b ? a : b;
|
||||
}
|
||||
|
||||
auto MINMAX(int min, int value, int max) -> int
|
||||
{
|
||||
if (max < min)
|
||||
return MAX(min, value);
|
||||
int tv;
|
||||
tv = (min > value ? min : value);
|
||||
return (max < tv) ? max : tv;
|
||||
}
|
||||
|
||||
auto fMIN(float a, float b) -> float
|
||||
{
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
auto fMAX(float a, float b) -> float
|
||||
{
|
||||
return a > b ? a : b;
|
||||
}
|
||||
|
||||
auto fMINMAX(float min, float value, float max) -> float
|
||||
{
|
||||
float tv;
|
||||
tv = (min > value ? min : value);
|
||||
return (max < tv) ? max : tv;
|
||||
}
|
||||
|
||||
void MyCreateDirectory(const char* path)
|
||||
{
|
||||
if (!path || !*path)
|
||||
return;
|
||||
|
||||
char * dir;
|
||||
const char * p;
|
||||
|
||||
if (strlen(path) >= 3)
|
||||
{
|
||||
if (*(path + 1) == ':') // C:, D: drive prefix
|
||||
path += 3;
|
||||
}
|
||||
|
||||
p = path;
|
||||
|
||||
int len = strlen(path) + 1;
|
||||
dir = new char[len];
|
||||
|
||||
while (*p)
|
||||
{
|
||||
if (*p == '/' || *p == '\\')
|
||||
{
|
||||
memset(dir, 0, len);
|
||||
strncpy(dir, path, p - path);
|
||||
mkdir(dir, 0755); // PORT: CreateDirectory(dir, NULL) -> mkdir
|
||||
}
|
||||
|
||||
++p;
|
||||
}
|
||||
|
||||
delete [] dir;
|
||||
}
|
||||
|
||||
// PORT: CDirRemover (CDir walk + DeleteFile/RemoveDirectory) -> std::filesystem::remove_all.
|
||||
void RemoveAllDirectory(const char * c_szDirectoryName)
|
||||
{
|
||||
std::error_code ec;
|
||||
std::filesystem::remove_all(c_szDirectoryName, ec);
|
||||
}
|
||||
|
||||
bool SplitLine(const char * c_szLine, const char * c_szDelimeter, std::vector<std::string> * pkVec_strToken)
|
||||
{
|
||||
pkVec_strToken->reserve(10);
|
||||
pkVec_strToken->clear();
|
||||
|
||||
std::string strLine = c_szLine;
|
||||
|
||||
DWORD basePos = 0;
|
||||
|
||||
do
|
||||
{
|
||||
int beginPos = strLine.find_first_not_of(c_szDelimeter, basePos);
|
||||
if (beginPos < 0)
|
||||
return false;
|
||||
|
||||
int endPos;
|
||||
|
||||
if (strLine[beginPos] == '"')
|
||||
{
|
||||
++beginPos;
|
||||
endPos = strLine.find_first_of("\"", beginPos);
|
||||
|
||||
if (endPos < 0)
|
||||
return false;
|
||||
|
||||
basePos = endPos + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
endPos = strLine.find_first_of(c_szDelimeter, beginPos);
|
||||
basePos = endPos;
|
||||
}
|
||||
|
||||
pkVec_strToken->push_back(strLine.substr(beginPos, endPos - beginPos));
|
||||
} while (basePos < strLine.length());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const char * _getf(const char* c_szFormat, ...)
|
||||
{
|
||||
static char szBuf[256];
|
||||
|
||||
va_list args;
|
||||
va_start(args, c_szFormat);
|
||||
vsnprintf(szBuf, sizeof(szBuf), c_szFormat, args); // PORT: _vsnprintf -> vsnprintf
|
||||
va_end(args);
|
||||
|
||||
return szBuf;
|
||||
}
|
||||
|
||||
PCHAR* CommandLineToArgv( PCHAR CmdLine, int* _argc )
|
||||
{
|
||||
PCHAR* argv;
|
||||
PCHAR _argv;
|
||||
ULONG len;
|
||||
ULONG argc;
|
||||
CHAR a;
|
||||
ULONG i, j;
|
||||
|
||||
BOOL in_QM; // PORT: BOOLEAN/PUCHAR -> BOOL/unsigned char * (not in the platform windows.h)
|
||||
BOOL in_TEXT;
|
||||
BOOL in_SPACE;
|
||||
|
||||
len = strlen(CmdLine);
|
||||
i = ((len+2)/2)*sizeof(PVOID) + sizeof(PVOID);
|
||||
|
||||
argv = (PCHAR*)malloc( // PORT: GlobalAlloc(GMEM_FIXED, ...) -> malloc
|
||||
i + (len+2)*sizeof(CHAR));
|
||||
|
||||
_argv = (PCHAR)(((unsigned char *)argv)+i);
|
||||
|
||||
argc = 0;
|
||||
argv[argc] = _argv;
|
||||
in_QM = FALSE;
|
||||
in_TEXT = FALSE;
|
||||
in_SPACE = TRUE;
|
||||
i = 0;
|
||||
j = 0;
|
||||
|
||||
while( (a = CmdLine[i]) ) {
|
||||
if(in_QM) {
|
||||
if(a == '\"') {
|
||||
in_QM = FALSE;
|
||||
} else {
|
||||
_argv[j] = a;
|
||||
j++;
|
||||
}
|
||||
} else {
|
||||
switch(a) {
|
||||
case '\"':
|
||||
in_QM = TRUE;
|
||||
in_TEXT = TRUE;
|
||||
if(in_SPACE) {
|
||||
argv[argc] = _argv+j;
|
||||
argc++;
|
||||
}
|
||||
in_SPACE = FALSE;
|
||||
break;
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\r':
|
||||
if(in_TEXT) {
|
||||
_argv[j] = '\0';
|
||||
j++;
|
||||
}
|
||||
in_TEXT = FALSE;
|
||||
in_SPACE = TRUE;
|
||||
break;
|
||||
default:
|
||||
in_TEXT = TRUE;
|
||||
if(in_SPACE) {
|
||||
argv[argc] = _argv+j;
|
||||
argc++;
|
||||
}
|
||||
_argv[j] = a;
|
||||
j++;
|
||||
in_SPACE = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
_argv[j] = '\0';
|
||||
argv[argc] = NULL;
|
||||
|
||||
(*_argc) = argc;
|
||||
return argv;
|
||||
}
|
||||
|
||||
void StringExceptCharacter(std::string * pstrString, const char * c_szCharacter)
|
||||
{
|
||||
int icurPos = 0;
|
||||
int iNextPos = 0;
|
||||
|
||||
while((iNextPos = pstrString->find_first_of(c_szCharacter, icurPos)) >= 0)
|
||||
{
|
||||
std::string strFront = pstrString->substr(icurPos, iNextPos - icurPos);
|
||||
std::string strBack = pstrString->substr(iNextPos+1, pstrString->length() - iNextPos - 1);
|
||||
*pstrString = strFront + strBack;
|
||||
}
|
||||
}
|
||||
|
||||
// PORT: GetModuleFileName(NULL, ...) -> the platform has no executable-path query; report the working folder.
|
||||
void GetExcutedFileName(std::string & r_str)
|
||||
{
|
||||
GetWorkingFolder(r_str);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,85 @@
|
||||
// Platform skeleton for EterImageLib/DXTCImage.h (40250 EterImageLib/DXTCImage.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "EterImageLib/StdAfx.h"
|
||||
#include "EterImageLib/DXTCImage.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
CDXTCImage::CDXTCImage()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CDXTCImage::~CDXTCImage()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CDXTCImage::Initialize() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CDXTCImage::Clear() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CDXTCImage::LoadFromFile(const char *) -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CDXTCImage::LoadFromMemory(const BYTE *) -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CDXTCImage::LoadHeaderFromMemory(const BYTE *) -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CDXTCImage::Copy(int, BYTE *, long) -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CDXTCImage::Decompress(int, DWORD *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CDXTCImage::DecompressDXT1(int, DWORD *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CDXTCImage::DecompressDXT3(int, DWORD *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CDXTCImage::DecompressDXT5(int, DWORD *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CDXTCImage::DecompressARGB(int, DWORD *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CDXTCImage::DecodePixelFormat(CHAR *, XDDPIXELFORMAT *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CDXTCImage::Unextract(BYTE *, int, int, int) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#include <il/il.h>
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
void ilInit() { MT_PLATFORM_STUB(); }
|
||||
void ilShutDown() { MT_PLATFORM_STUB(); }
|
||||
void ilEnable(ILenum) { MT_PLATFORM_STUB(); }
|
||||
void ilDisable(ILenum) { MT_PLATFORM_STUB(); }
|
||||
void ilOriginFunc(ILenum) { MT_PLATFORM_STUB(); }
|
||||
|
||||
void ilGenImages(ILsizei count, ILuint* images)
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
for (ILsizei i = 0; i < count; ++i)
|
||||
images[i] = 0;
|
||||
}
|
||||
void ilBindImage(ILuint) { MT_PLATFORM_STUB(); }
|
||||
void ilDeleteImages(ILsizei, const ILuint*) { MT_PLATFORM_STUB(); }
|
||||
|
||||
bool ilLoad(ILenum, const ILstring) { MT_PLATFORM_STUB(); return false; }
|
||||
bool ilSave(ILenum, const ILstring) { MT_PLATFORM_STUB(); return false; }
|
||||
int ilGetInteger(ILenum) { MT_PLATFORM_STUB(); return 0; }
|
||||
bool ilConvertImage(ILenum, ILenum) { MT_PLATFORM_STUB(); return false; }
|
||||
bool ilTexImage(ILuint, ILuint, ILuint, ILubyte, ILenum, ILenum, void*) { MT_PLATFORM_STUB(); return false; }
|
||||
ILuint ilCopyPixels(ILuint, ILuint, ILuint, ILuint, ILuint, ILuint, ILenum, ILenum, void*) { MT_PLATFORM_STUB(); return 0; }
|
||||
void ilSetPixels(ILint, ILint, ILint, ILuint, ILuint, ILuint, ILenum, ILenum, void*) { MT_PLATFORM_STUB(); }
|
||||
@@ -0,0 +1,92 @@
|
||||
// Platform skeleton for EterImageLib/Image.h (40250 EterImageLib/Image.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "EterImageLib/StdAfx.h"
|
||||
#include "EterImageLib/Image.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
CImage::CImage()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CImage::CImage(CImage &)
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CImage::~CImage()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CImage::Destroy() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CImage::Create(int, int) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CImage::Clear(DWORD) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CImage::GetWidth() const -> int
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<int>();
|
||||
}
|
||||
|
||||
auto CImage::GetHeight() const -> int
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<int>();
|
||||
}
|
||||
|
||||
auto CImage::GetBasePointer() -> DWORD *
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<DWORD *>();
|
||||
}
|
||||
|
||||
auto CImage::GetLinePointer(int) -> DWORD *
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<DWORD *>();
|
||||
}
|
||||
|
||||
auto CImage::PutImage(int, int, CImage *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CImage::FlipTopToBottom() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CImage::SetFileName(const char *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CImage::GetFileNameString() -> const std::string &
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<const std::string &>();
|
||||
}
|
||||
|
||||
auto CImage::IsEmpty() const -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CImage::Initialize() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Platform skeleton for EterImageLib/TGAImage.h (40250 EterImageLib/TGAImage.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "EterImageLib/StdAfx.h"
|
||||
#include "EterImageLib/TGAImage.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
CTGAImage::CTGAImage()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CTGAImage::CTGAImage(CImage &)
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CTGAImage::~CTGAImage()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CTGAImage::Create(int, int) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CTGAImage::LoadFromMemory(int, const BYTE *) -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CTGAImage::LoadFromDiskFile(const char *) -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CTGAImage::SaveToDiskFile(const char *) -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CTGAImage::SetCompressed(bool) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CTGAImage::SetAlphaChannel(bool) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CTGAImage::GetHeader() -> TGA_HEADER &
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<TGA_HEADER &>();
|
||||
}
|
||||
|
||||
auto CTGAImage::GetRawPixelCount(const DWORD *) -> int
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<int>();
|
||||
}
|
||||
|
||||
auto CTGAImage::GetRLEPixelCount(const DWORD *) -> int
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<int>();
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
// CPU-memory vertex/index buffers behind the D3D8 buffer handles. 40250 fills its buffers through
|
||||
// Lock/Unlock (CGrannyModel::__LoadVertices, CGrannyModelInstance deform) and the renderer later reads
|
||||
// them back, so the platform keeps the bytes in memory instead of creating device objects.
|
||||
#include "EterLib/StdAfx.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
|
||||
inline std::atomic<std::uint64_t> mt_next_cpu_buffer_id{1};
|
||||
|
||||
struct MtCpuVertexBuffer : IDirect3DVertexBuffer8
|
||||
{
|
||||
const std::uint64_t id = mt_next_cpu_buffer_id.fetch_add(1, std::memory_order_relaxed);
|
||||
std::uint64_t revision = 0;
|
||||
std::vector<uint8_t> bytes;
|
||||
DWORD fvf = 0;
|
||||
|
||||
HRESULT Lock(UINT offset, UINT size, BYTE** data, DWORD) override
|
||||
{
|
||||
if (size_t(offset) + size > bytes.size())
|
||||
return E_FAIL;
|
||||
*data = bytes.data() + offset;
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT Unlock() override { ++revision; return S_OK; }
|
||||
};
|
||||
|
||||
struct MtCpuIndexBuffer : IDirect3DIndexBuffer8
|
||||
{
|
||||
const std::uint64_t id = mt_next_cpu_buffer_id.fetch_add(1, std::memory_order_relaxed);
|
||||
std::uint64_t revision = 0;
|
||||
std::vector<uint8_t> bytes;
|
||||
D3DFORMAT format = D3DFMT_INDEX16;
|
||||
|
||||
HRESULT Lock(UINT offset, UINT size, BYTE** data, DWORD) override
|
||||
{
|
||||
if (size_t(offset) + size > bytes.size())
|
||||
return E_FAIL;
|
||||
*data = bytes.data() + offset;
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT Unlock() override { ++revision; return S_OK; }
|
||||
};
|
||||
|
||||
// D3DXGetFVFVertexSize.
|
||||
inline unsigned mt_fvf_vertex_size(DWORD fvf)
|
||||
{
|
||||
unsigned size = 0;
|
||||
switch (fvf & D3DFVF_POSITION_MASK)
|
||||
{
|
||||
case D3DFVF_XYZ: size += 12; break;
|
||||
case D3DFVF_XYZRHW: size += 16; break;
|
||||
case D3DFVF_XYZB1: size += 16; break;
|
||||
case D3DFVF_XYZB2: size += 20; break;
|
||||
case D3DFVF_XYZB3: size += 24; break;
|
||||
case D3DFVF_XYZB4: size += 28; break;
|
||||
case D3DFVF_XYZB5: size += 32; break;
|
||||
}
|
||||
if (fvf & D3DFVF_NORMAL) size += 12;
|
||||
if (fvf & D3DFVF_PSIZE) size += 4;
|
||||
if (fvf & D3DFVF_DIFFUSE) size += 4;
|
||||
if (fvf & D3DFVF_SPECULAR) size += 4;
|
||||
const unsigned texCount = (fvf & D3DFVF_TEXCOUNT_MASK) >> D3DFVF_TEXCOUNT_SHIFT;
|
||||
for (unsigned i = 0; i < texCount; ++i)
|
||||
{
|
||||
static const unsigned coordSize[4] = { 8, 12, 16, 4 };
|
||||
size += coordSize[(fvf >> (16 + i * 2)) & 3];
|
||||
}
|
||||
return size;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Platform skeleton for EterLib/DibBar.h (40250 EterLib/DibBar.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/DibBar.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
CDibBar::CDibBar()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CDibBar::~CDibBar()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CDibBar::Create(HDC, DWORD, DWORD) -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CDibBar::Invalidate() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CDibBar::SetClipRect(const RECT &) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CDibBar::ClearBar() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CDibBar::Render(int, int) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CDibBar::__NearTextureSize(DWORD) -> DWORD
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<DWORD>();
|
||||
}
|
||||
|
||||
auto CDibBar::__DivideTextureSize(DWORD, DWORD, DWORD *, DWORD *, DWORD *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CDibBar::__BuildTextureBlock(DWORD, DWORD, DWORD, DWORD, DWORD, DWORD) -> CBlockTexture *
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<CBlockTexture *>();
|
||||
}
|
||||
|
||||
auto CDibBar::__BuildTextureBlockList(DWORD, DWORD, DWORD) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Platform skeleton for EterLib/FileLoaderThread.h (40250 EterLib/FileLoaderThread.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/FileLoaderThread.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
CFileLoaderThread::CFileLoaderThread()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CFileLoaderThread::~CFileLoaderThread()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CFileLoaderThread::Create(void *) -> int
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<int>();
|
||||
}
|
||||
|
||||
auto CFileLoaderThread::Request(std::string &) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CFileLoaderThread::Fetch(TData **) -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CFileLoaderThread::Shutdown() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CFileLoaderThread::EntryPoint(void *) -> UINT
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<UINT>();
|
||||
}
|
||||
|
||||
auto CFileLoaderThread::Run(void *) -> UINT
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<UINT>();
|
||||
}
|
||||
|
||||
auto CFileLoaderThread::Setup() -> UINT
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<UINT>();
|
||||
}
|
||||
|
||||
auto CFileLoaderThread::Execute(void *) -> UINT
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<UINT>();
|
||||
}
|
||||
|
||||
auto CFileLoaderThread::Destroy() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CFileLoaderThread::Process() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Platform skeleton for EterLib/GrpColor.h (40250 EterLib/GrpColor.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/GrpColor.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
CGraphicColor::CGraphicColor(const CGraphicColor &)
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CGraphicColor::CGraphicColor(float, float, float, float)
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CGraphicColor::CGraphicColor(DWORD)
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CGraphicColor::CGraphicColor()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CGraphicColor::~CGraphicColor()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicColor::Clear() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicColor::Set(float, float, float, float) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicColor::Set(const CGraphicColor &) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicColor::Set(DWORD) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicColor::Blend(float, const CGraphicColor &, const CGraphicColor &) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicColor::GetPackValue() const -> DWORD
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<DWORD>();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Platform skeleton for EterLib/GrpColorInstance.h (40250 EterLib/GrpColorInstance.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/GrpColorInstance.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
CGraphicColorInstance::CGraphicColorInstance()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CGraphicColorInstance::~CGraphicColorInstance()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicColorInstance::Clear() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicColorInstance::SetColorReference(const CGraphicColor &) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicColorInstance::BlendColorReference(DWORD, const CGraphicColor &) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicColorInstance::Update() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicColorInstance::GetCurrentColorReference() const -> const CGraphicColor &
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<const CGraphicColor &>();
|
||||
}
|
||||
|
||||
auto CGraphicColorInstance::GetCurrentTime() -> DWORD
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<DWORD>();
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
// 40250 EterLib/GrpDIB.cpp, verbatim. The GDI calls are the FreeType-backed platform/Win32Gdi.cpp.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/GrpDIB.h"
|
||||
|
||||
CGraphicDib::CGraphicDib()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
CGraphicDib::~CGraphicDib()
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void CGraphicDib::Initialize()
|
||||
{
|
||||
m_hDC=NULL;
|
||||
m_hBmp=NULL;
|
||||
m_pvBuf=NULL;
|
||||
m_width=0;
|
||||
m_height=0;
|
||||
}
|
||||
|
||||
void CGraphicDib::Destroy()
|
||||
{
|
||||
if (m_hBmp) DeleteObject(m_hBmp);
|
||||
if (m_hDC) DeleteDC(m_hDC);
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
bool CGraphicDib::Create(HDC hDC, int width, int height)
|
||||
{
|
||||
Destroy();
|
||||
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
|
||||
ZeroMemory(&m_bmi.bmiHeader, sizeof(BITMAPINFOHEADER));
|
||||
m_bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
m_bmi.bmiHeader.biWidth = m_width;
|
||||
m_bmi.bmiHeader.biHeight = -m_height;
|
||||
m_bmi.bmiHeader.biPlanes = 1;
|
||||
m_bmi.bmiHeader.biBitCount = 32;
|
||||
m_bmi.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
m_hDC=CreateCompatibleDC(hDC);
|
||||
if (!m_hDC)
|
||||
{
|
||||
assert(!"CGraphicDib::Create CreateCompatibleDC Error");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_hBmp=CreateDIBSection(m_hDC, &m_bmi, DIB_RGB_COLORS, &m_pvBuf, NULL, 0);
|
||||
if (!m_hBmp)
|
||||
{
|
||||
assert(!"CGraphicDib::Create CreateDIBSection Error");
|
||||
return false;
|
||||
}
|
||||
|
||||
SelectObject(m_hDC, m_hBmp);
|
||||
|
||||
::SetTextColor(m_hDC, RGB(255, 255, 255));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
HDC CGraphicDib::GetDCHandle()
|
||||
{
|
||||
return m_hDC;
|
||||
}
|
||||
|
||||
void CGraphicDib::SetBkMode(int iBkMode)
|
||||
{
|
||||
::SetBkMode(m_hDC, iBkMode);
|
||||
}
|
||||
|
||||
void CGraphicDib::TextOut(int ix, int iy, const char * c_szText)
|
||||
{
|
||||
::SetBkColor(m_hDC, 0);
|
||||
::TextOut(m_hDC, ix, iy, c_szText, strlen(c_szText));
|
||||
}
|
||||
|
||||
void CGraphicDib::Put(HDC hDC, int x, int y)
|
||||
{
|
||||
SetDIBitsToDevice(
|
||||
hDC,
|
||||
x,
|
||||
y,
|
||||
m_width,
|
||||
m_height,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
m_height,
|
||||
m_pvBuf,
|
||||
&m_bmi,
|
||||
DIB_RGB_COLORS
|
||||
);
|
||||
}
|
||||
|
||||
void* CGraphicDib::GetPointer()
|
||||
{
|
||||
return m_pvBuf;
|
||||
}
|
||||
|
||||
int CGraphicDib::GetWidth()
|
||||
{
|
||||
return m_width;
|
||||
}
|
||||
|
||||
int CGraphicDib::GetHeight()
|
||||
{
|
||||
return m_height;
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
// Platform skeleton for EterLib/GrpDetector.h (40250 EterLib/GrpDetector.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/GrpDetector.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
auto D3D_SModeInfo::GetString(std::string *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto D3D_CAdapterDisplayModeList::Build(IDirect3D8 &, D3DFORMAT, UINT) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto D3D_CAdapterDisplayModeList::GetDisplayModeNum() -> UINT
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<UINT>();
|
||||
}
|
||||
|
||||
auto D3D_CAdapterDisplayModeList::GetPixelFormatNum() -> UINT
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<UINT>();
|
||||
}
|
||||
|
||||
auto D3D_CAdapterDisplayModeList::GetDisplayModer(UINT) -> const D3DDISPLAYMODE &
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<const D3DDISPLAYMODE &>();
|
||||
}
|
||||
|
||||
auto D3D_CAdapterDisplayModeList::GetPixelFormatr(UINT) -> const D3DFORMAT &
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<const D3DFORMAT &>();
|
||||
}
|
||||
|
||||
auto D3D_CDeviceInfo::Build(IDirect3D8 &, UINT, UINT, D3D_CAdapterDisplayModeList &, PFNCONFIRMDEVICE) -> BOOL
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<BOOL>();
|
||||
}
|
||||
|
||||
auto D3D_CDeviceInfo::Find(UINT, UINT, UINT, BOOL, UINT *) -> BOOL
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<BOOL>();
|
||||
}
|
||||
|
||||
auto D3D_CDeviceInfo::GetD3DModeInfoNum() -> UINT
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<UINT>();
|
||||
}
|
||||
|
||||
auto D3D_CDeviceInfo::GetString(std::string *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto D3D_CDeviceInfo::FindDepthStencilFormat(IDirect3D8 &, UINT, D3DDEVTYPE, D3DFORMAT, D3DFORMAT *) -> BOOL
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<BOOL>();
|
||||
}
|
||||
|
||||
auto D3D_CDeviceInfo::GetD3DModeInfor(UINT) -> D3D_SModeInfo &
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<D3D_SModeInfo &>();
|
||||
}
|
||||
|
||||
auto D3D_CDeviceInfo::GetD3DModeInfop(UINT) -> D3D_SModeInfo *
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<D3D_SModeInfo *>();
|
||||
}
|
||||
|
||||
decltype(D3D_CDeviceInfo::msc_aszD3DDevDesc) D3D_CDeviceInfo::msc_aszD3DDevDesc{};
|
||||
|
||||
decltype(D3D_CDeviceInfo::msc_aeD3DDevType) D3D_CDeviceInfo::msc_aeD3DDevType{};
|
||||
|
||||
auto D3D_CAdapterInfo::Find(UINT, UINT, UINT, BOOL, UINT *, UINT *) -> BOOL
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<BOOL>();
|
||||
}
|
||||
|
||||
auto D3D_CAdapterInfo::Build(IDirect3D8 &, UINT, PFNCONFIRMDEVICE) -> BOOL
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<BOOL>();
|
||||
}
|
||||
|
||||
auto D3D_CAdapterInfo::GetString(std::string *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto D3D_CAdapterInfo::GetDesktopD3DDisplayModer() -> D3DDISPLAYMODE &
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<D3DDISPLAYMODE &>();
|
||||
}
|
||||
|
||||
auto D3D_CAdapterInfo::GetDesktopD3DDisplayModep() -> D3DDISPLAYMODE *
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<D3DDISPLAYMODE *>();
|
||||
}
|
||||
|
||||
auto D3D_CAdapterInfo::GetD3DDeviceInfop(UINT) -> D3D_CDeviceInfo *
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<D3D_CDeviceInfo *>();
|
||||
}
|
||||
|
||||
auto D3D_CAdapterInfo::GetD3DModeInfop(UINT, UINT) -> D3D_SModeInfo *
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<D3D_SModeInfo *>();
|
||||
}
|
||||
|
||||
D3D_CDisplayModeAutoDetector::D3D_CDisplayModeAutoDetector()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
D3D_CDisplayModeAutoDetector::~D3D_CDisplayModeAutoDetector()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto D3D_CDisplayModeAutoDetector::Find(UINT, UINT, UINT, BOOL, UINT *, UINT *, UINT *) -> BOOL
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<BOOL>();
|
||||
}
|
||||
|
||||
auto D3D_CDisplayModeAutoDetector::Build(IDirect3D8 &, PFNCONFIRMDEVICE) -> BOOL
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<BOOL>();
|
||||
}
|
||||
|
||||
auto D3D_CDisplayModeAutoDetector::GetD3DAdapterInfop(UINT) -> D3D_CAdapterInfo *
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<D3D_CAdapterInfo *>();
|
||||
}
|
||||
|
||||
auto D3D_CDisplayModeAutoDetector::GetD3DModeInfop(UINT, UINT, UINT) -> D3D_SModeInfo *
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<D3D_SModeInfo *>();
|
||||
}
|
||||
|
||||
auto D3D_CDisplayModeAutoDetector::GetString(std::string *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
// 40250 EterLib/GrpDevice.cpp over the platform's recording device (RecordingDevice.h): there is no
|
||||
// Direct3D object, adapter detection or window; Create makes the device, CStateManager, the matrix
|
||||
// stack and the default buffers as 40250 does. The remaining MT_PLATFORM_STUB() bodies (device reset,
|
||||
// web-browser mode, driver warnings) have no platform counterpart yet.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/GrpDevice.h"
|
||||
#include "EterLib/StateManager.h"
|
||||
#include "EterBase/Stl.h"
|
||||
|
||||
#include "CpuBuffer.h"
|
||||
#include "RecordingDevice.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
bool GRAPHICS_CAPS_CAN_NOT_DRAW_LINE = false;
|
||||
bool GRAPHICS_CAPS_CAN_NOT_DRAW_SHADOW = false;
|
||||
bool GRAPHICS_CAPS_HALF_SIZE_IMAGE = false;
|
||||
bool GRAPHICS_CAPS_CAN_NOT_TEXTURE_ADDRESS_BORDER = false;
|
||||
bool GRAPHICS_CAPS_SOFTWARE_TILING = false;
|
||||
|
||||
CGraphicDevice::CGraphicDevice()
|
||||
: m_uBackBufferCount(0)
|
||||
{
|
||||
__Initialize();
|
||||
}
|
||||
|
||||
CGraphicDevice::~CGraphicDevice()
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void CGraphicDevice::InitBackBufferCount(UINT uBackBufferCount)
|
||||
{
|
||||
m_uBackBufferCount=uBackBufferCount;
|
||||
}
|
||||
|
||||
void CGraphicDevice::Destroy()
|
||||
{
|
||||
__DestroyPDTVertexBufferList();
|
||||
__DestroyDefaultIndexBufferList();
|
||||
|
||||
// PORT: no DC, vertex shader objects or D3DX meshes; the stream "shaders" are FVF codes.
|
||||
ms_ptVS = 0;
|
||||
ms_pntVS = 0;
|
||||
ms_pnt2VS = 0;
|
||||
|
||||
safe_release(ms_lpd3dMatStack);
|
||||
safe_release(ms_lpd3dDevice);
|
||||
|
||||
if (m_pStateManager)
|
||||
{
|
||||
delete m_pStateManager;
|
||||
m_pStateManager = NULL;
|
||||
}
|
||||
|
||||
__Initialize();
|
||||
}
|
||||
|
||||
int CGraphicDevice::Create(HWND hWnd, int iHres, int iVres, bool Windowed, int /*iBit*/, int iReflashRate)
|
||||
{
|
||||
int iRet = CREATE_OK;
|
||||
|
||||
Destroy();
|
||||
|
||||
ms_iWidth = iHres;
|
||||
ms_iHeight = iVres;
|
||||
|
||||
// PORT: in place of Direct3DCreate8, the display-mode detection and CreateDevice: the recording
|
||||
// device renders through Godot, which supports DXT and has no refresh-rate or T&L failures.
|
||||
ms_hWnd = hWnd;
|
||||
ms_lpd3dDevice = MtCreateRecordingDevice(iHres, iVres);
|
||||
ms_dwD3DBehavior = D3DCREATE_HARDWARE_VERTEXPROCESSING;
|
||||
ms_d3dPresentParameter.BackBufferWidth = iHres;
|
||||
ms_d3dPresentParameter.BackBufferHeight = iVres;
|
||||
ms_d3dPresentParameter.Windowed = Windowed;
|
||||
|
||||
if (FAILED((ms_hLastResult = ms_lpd3dDevice->GetDeviceCaps(&ms_d3dCaps))))
|
||||
{
|
||||
Tracenf("IDirect3DDevice.GetDeviceCaps - ERROR %d", ms_hLastResult);
|
||||
return CREATE_GET_DEVICE_CAPS2;
|
||||
}
|
||||
|
||||
ms_lpd3dDevice->GetViewport(&ms_Viewport);
|
||||
|
||||
m_pStateManager = new CStateManager(ms_lpd3dDevice);
|
||||
|
||||
D3DXCreateMatrixStack(0, &ms_lpd3dMatStack);
|
||||
ms_lpd3dMatStack->LoadIdentity();
|
||||
|
||||
ms_ptVS = CreatePTStreamVertexShader();
|
||||
ms_pntVS = CreatePNTStreamVertexShader();
|
||||
ms_pnt2VS = CreatePNT2StreamVertexShader();
|
||||
|
||||
D3DXMatrixIdentity(&ms_matIdentity);
|
||||
D3DXMatrixIdentity(&ms_matView);
|
||||
D3DXMatrixIdentity(&ms_matProj);
|
||||
D3DXMatrixIdentity(&ms_matInverseView);
|
||||
D3DXMatrixIdentity(&ms_matInverseViewYAxis);
|
||||
D3DXMatrixIdentity(&ms_matWorld);
|
||||
D3DXMatrixIdentity(&ms_matWorldView);
|
||||
D3DXMatrixIdentity(&ms_matScreen0);
|
||||
D3DXMatrixIdentity(&ms_matScreen1);
|
||||
D3DXMatrixIdentity(&ms_matScreen2);
|
||||
|
||||
ms_matScreen0._11 = 1;
|
||||
ms_matScreen0._22 = -1;
|
||||
|
||||
ms_matScreen1._41 = 1;
|
||||
ms_matScreen1._42 = 1;
|
||||
|
||||
ms_matScreen2._11 = (float) iHres / 2;
|
||||
ms_matScreen2._22 = (float) iVres / 2;
|
||||
|
||||
// PORT: no D3DXCreateSphere/D3DXCreateCylinder (debug collision rendering) and no Clear.
|
||||
|
||||
if (!__CreateDefaultIndexBufferList())
|
||||
return false;
|
||||
|
||||
if (!__CreatePDTVertexBufferList())
|
||||
return false;
|
||||
|
||||
DWORD dwTexMemSize = GetAvailableTextureMemory();
|
||||
|
||||
if (dwTexMemSize < 64 * 1024 * 1024)
|
||||
ms_isLowTextureMemory = true;
|
||||
else
|
||||
ms_isLowTextureMemory = false;
|
||||
|
||||
if (dwTexMemSize > 100 * 1024 * 1024)
|
||||
ms_isHighTextureMemory = true;
|
||||
else
|
||||
ms_isHighTextureMemory = false;
|
||||
|
||||
if (ms_d3dCaps.TextureAddressCaps & D3DPTADDRESSCAPS_BORDER)
|
||||
GRAPHICS_CAPS_CAN_NOT_TEXTURE_ADDRESS_BORDER=false;
|
||||
else
|
||||
GRAPHICS_CAPS_CAN_NOT_TEXTURE_ADDRESS_BORDER=true;
|
||||
|
||||
// PORT: the SIS/3dfx driver checks have no adapter identifier to test.
|
||||
return (iRet);
|
||||
}
|
||||
|
||||
auto CGraphicDevice::GetDeviceState() -> EDeviceState
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<EDeviceState>();
|
||||
}
|
||||
|
||||
auto CGraphicDevice::Reset() -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CGraphicDevice::EnableWebBrowserMode(const RECT &) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicDevice::DisableWebBrowserMode() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicDevice::MoveWebBrowserRect(const RECT &) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicDevice::ResizeBackBuffer(UINT uWidth, UINT uHeight) -> bool
|
||||
{
|
||||
ms_iWidth = uWidth;
|
||||
ms_iHeight = uHeight;
|
||||
ms_d3dPresentParameter.BackBufferWidth = uWidth;
|
||||
ms_d3dPresentParameter.BackBufferHeight = uHeight;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto CGraphicDevice::RegisterWarningString(UINT, const char *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
void CGraphicDevice::__Initialize()
|
||||
{
|
||||
ms_iD3DAdapterInfo=D3DADAPTER_DEFAULT;
|
||||
ms_iD3DDevInfo=D3DADAPTER_DEFAULT;
|
||||
ms_iD3DModeInfo=D3DADAPTER_DEFAULT;
|
||||
|
||||
ms_lpd3d = NULL;
|
||||
ms_lpd3dDevice = NULL;
|
||||
ms_lpd3dMatStack = NULL;
|
||||
|
||||
ms_dwWavingEndTime = 0;
|
||||
ms_dwFlashingEndTime = 0;
|
||||
|
||||
m_pStateManager = NULL;
|
||||
|
||||
__InitializeDefaultIndexBufferList();
|
||||
__InitializePDTVertexBufferList();
|
||||
}
|
||||
|
||||
auto CGraphicDevice::__IsInDriverBlackList(D3D_CAdapterInfo &) -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CGraphicDevice::__WarningMessage(HWND, UINT) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
void CGraphicDevice::__InitializeDefaultIndexBufferList()
|
||||
{
|
||||
for (UINT i=0; i<DEFAULT_IB_NUM; ++i)
|
||||
ms_alpd3dDefIB[i]=NULL;
|
||||
}
|
||||
|
||||
void CGraphicDevice::__DestroyDefaultIndexBufferList()
|
||||
{
|
||||
for (UINT i=0; i<DEFAULT_IB_NUM; ++i)
|
||||
if (ms_alpd3dDefIB[i])
|
||||
{
|
||||
delete ms_alpd3dDefIB[i]; // PORT: CPU buffer, no Release
|
||||
ms_alpd3dDefIB[i]=NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool CGraphicDevice::__CreateDefaultIndexBufferList()
|
||||
{
|
||||
static const WORD c_awLineIndices[2] = { 0, 1, };
|
||||
static const WORD c_awLineTriIndices[6] = { 0, 1, 0, 2, 1, 2, };
|
||||
static const WORD c_awLineRectIndices[8] = { 0, 1, 0, 2, 1, 3, 2, 3,};
|
||||
static const WORD c_awLineCubeIndices[24] = {
|
||||
0, 1, 0, 2, 1, 3, 2, 3,
|
||||
0, 4, 1, 5, 2, 6, 3, 7,
|
||||
4, 5, 4, 6, 5, 7, 6, 7,
|
||||
};
|
||||
static const WORD c_awFillTriIndices[3]= { 0, 1, 2, };
|
||||
static const WORD c_awFillRectIndices[6] = { 0, 2, 1, 2, 3, 1, };
|
||||
static const WORD c_awFillCubeIndices[36] = {
|
||||
0, 1, 2, 1, 3, 2,
|
||||
2, 0, 6, 0, 4, 6,
|
||||
0, 1, 4, 1, 5, 4,
|
||||
1, 3, 5, 3, 7, 5,
|
||||
3, 2, 7, 2, 6, 7,
|
||||
4, 5, 6, 5, 7, 6,
|
||||
};
|
||||
|
||||
if (!__CreateDefaultIndexBuffer(DEFAULT_IB_LINE, 2, c_awLineIndices))
|
||||
return false;
|
||||
if (!__CreateDefaultIndexBuffer(DEFAULT_IB_LINE_TRI, 6, c_awLineTriIndices))
|
||||
return false;
|
||||
if (!__CreateDefaultIndexBuffer(DEFAULT_IB_LINE_RECT, 8, c_awLineRectIndices))
|
||||
return false;
|
||||
if (!__CreateDefaultIndexBuffer(DEFAULT_IB_LINE_CUBE, 24, c_awLineCubeIndices))
|
||||
return false;
|
||||
if (!__CreateDefaultIndexBuffer(DEFAULT_IB_FILL_TRI, 3, c_awFillTriIndices))
|
||||
return false;
|
||||
if (!__CreateDefaultIndexBuffer(DEFAULT_IB_FILL_RECT, 6, c_awFillRectIndices))
|
||||
return false;
|
||||
if (!__CreateDefaultIndexBuffer(DEFAULT_IB_FILL_CUBE, 36, c_awFillCubeIndices))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGraphicDevice::__CreateDefaultIndexBuffer(UINT eDefIB, UINT uIdxCount, const WORD* c_awIndices)
|
||||
{
|
||||
assert(ms_alpd3dDefIB[eDefIB]==NULL);
|
||||
|
||||
// PORT: a CPU buffer in place of CreateIndexBuffer (CpuBuffer.h).
|
||||
MtCpuIndexBuffer* pIB = new MtCpuIndexBuffer;
|
||||
pIB->bytes.resize(sizeof(WORD)*uIdxCount);
|
||||
pIB->format = D3DFMT_INDEX16;
|
||||
ms_alpd3dDefIB[eDefIB] = pIB;
|
||||
|
||||
WORD* dstIndices;
|
||||
if (FAILED(
|
||||
ms_alpd3dDefIB[eDefIB]->Lock(0, 0, (BYTE**)&dstIndices, 0)
|
||||
)) return false;
|
||||
|
||||
memcpy(dstIndices, c_awIndices, sizeof(WORD)*uIdxCount);
|
||||
|
||||
ms_alpd3dDefIB[eDefIB]->Unlock();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CGraphicDevice::__InitializePDTVertexBufferList()
|
||||
{
|
||||
for (UINT i=0; i<PDT_VERTEXBUFFER_NUM; ++i)
|
||||
ms_alpd3dPDTVB[i]=NULL;
|
||||
}
|
||||
|
||||
void CGraphicDevice::__DestroyPDTVertexBufferList()
|
||||
{
|
||||
for (UINT i=0; i<PDT_VERTEXBUFFER_NUM; ++i)
|
||||
{
|
||||
if (ms_alpd3dPDTVB[i])
|
||||
{
|
||||
delete ms_alpd3dPDTVB[i]; // PORT: CPU buffer, no Release
|
||||
ms_alpd3dPDTVB[i]=NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CGraphicDevice::__CreatePDTVertexBufferList()
|
||||
{
|
||||
for (UINT i=0; i<PDT_VERTEXBUFFER_NUM; ++i)
|
||||
{
|
||||
// PORT: a CPU buffer in place of CreateVertexBuffer (CpuBuffer.h).
|
||||
MtCpuVertexBuffer* pVB = new MtCpuVertexBuffer;
|
||||
pVB->bytes.resize(sizeof(TPDTVertex)*PDT_VERTEX_NUM);
|
||||
pVB->fvf = D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1;
|
||||
ms_alpd3dPDTVB[i] = pVB;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// PORT: the stream vertex shaders are declarations for the fixed-function pipeline; the recording
|
||||
// device decodes vertices by FVF, so each returns the FVF matching its declaration. PT keeps its
|
||||
// texture coordinates in stream 1, which the device does not record: stream 0 is position only.
|
||||
DWORD CGraphicDevice::CreatePTStreamVertexShader()
|
||||
{
|
||||
assert(ms_lpd3dDevice != NULL);
|
||||
return D3DFVF_XYZ;
|
||||
}
|
||||
|
||||
DWORD CGraphicDevice::CreatePNTStreamVertexShader()
|
||||
{
|
||||
assert(ms_lpd3dDevice != NULL);
|
||||
return D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1;
|
||||
}
|
||||
|
||||
DWORD CGraphicDevice::CreatePNT2StreamVertexShader()
|
||||
{
|
||||
assert(ms_lpd3dDevice != NULL);
|
||||
return D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX2;
|
||||
}
|
||||
|
||||
auto CGraphicDevice::CreateDoublePNTStreamVertexShader() -> DWORD
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<DWORD>();
|
||||
}
|
||||
|
||||
// 40250 GrpDevice.cpp:298-306; s_MaxTextureWidth/Height come from D3DCAPS8 in __CheckD3DCaps.
|
||||
// PORT: 4096 is the texture size every Godot renderer on the target GPUs supports.
|
||||
static DWORD s_MaxTextureWidth = 4096, s_MaxTextureHeight = 4096;
|
||||
|
||||
DWORD GetMaxTextureWidth()
|
||||
{
|
||||
return s_MaxTextureWidth;
|
||||
}
|
||||
|
||||
DWORD GetMaxTextureHeight()
|
||||
{
|
||||
return s_MaxTextureHeight;
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
// Platform skeleton for EterLib/GrpExpandedImageInstance.h (40250 EterLib/GrpExpandedImageInstance.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/GrpExpandedImageInstance.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
#include "EterBase/CRC32.h"
|
||||
#include "UIRenderCommands.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
auto CGraphicExpandedImageInstance::Type() -> DWORD
|
||||
{
|
||||
static DWORD type = GetCRC32("CGraphicExpandedImageInstance", sizeof("CGraphicExpandedImageInstance") - 1);
|
||||
return type;
|
||||
}
|
||||
|
||||
CGraphicExpandedImageInstance::CGraphicExpandedImageInstance()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
CGraphicExpandedImageInstance::~CGraphicExpandedImageInstance()
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
|
||||
auto CGraphicExpandedImageInstance::Destroy() -> void
|
||||
{
|
||||
CGraphicImageInstance::Destroy();
|
||||
Initialize();
|
||||
}
|
||||
|
||||
auto CGraphicExpandedImageInstance::SetDepth(float depth) -> void
|
||||
{
|
||||
m_fDepth = depth;
|
||||
}
|
||||
|
||||
auto CGraphicExpandedImageInstance::SetOrigin() -> void
|
||||
{
|
||||
m_v2Origin = D3DXVECTOR2(GetWidth() * 0.5f, GetHeight() * 0.5f);
|
||||
}
|
||||
|
||||
auto CGraphicExpandedImageInstance::SetOrigin(float x, float y) -> void
|
||||
{
|
||||
m_v2Origin = D3DXVECTOR2(x, y);
|
||||
}
|
||||
|
||||
auto CGraphicExpandedImageInstance::SetRotation(float rotation) -> void
|
||||
{
|
||||
m_fRotation = rotation;
|
||||
}
|
||||
|
||||
auto CGraphicExpandedImageInstance::SetScale(float x, float y) -> void
|
||||
{
|
||||
m_v2Scale = D3DXVECTOR2(x, y);
|
||||
}
|
||||
|
||||
// In 40250, the arguments are fractions of the image size (uiToolTip gauges, SetPercentage).
|
||||
auto CGraphicExpandedImageInstance::SetRenderingRect(float fLeft, float fTop, float fRight, float fBottom) -> void
|
||||
{
|
||||
if (IsEmpty())
|
||||
return;
|
||||
|
||||
float fWidth = float(GetWidth());
|
||||
float fHeight = float(GetHeight());
|
||||
|
||||
m_RenderingRect.left = fWidth * fLeft;
|
||||
m_RenderingRect.top = fHeight * fTop;
|
||||
m_RenderingRect.right = fWidth * fRight;
|
||||
m_RenderingRect.bottom = fHeight * fBottom;
|
||||
}
|
||||
|
||||
auto CGraphicExpandedImageInstance::SetRenderingMode(int mode) -> void
|
||||
{
|
||||
m_iRenderingMode = mode;
|
||||
}
|
||||
|
||||
auto CGraphicExpandedImageInstance::Initialize() -> void
|
||||
{
|
||||
m_fDepth = 0;
|
||||
m_v2Origin = D3DXVECTOR2(0, 0);
|
||||
m_v2Scale = D3DXVECTOR2(1, 1);
|
||||
m_fRotation = 0;
|
||||
m_RenderingRect = {0, 0, 0, 0};
|
||||
m_iRenderingMode = RENDERING_MODE_NORMAL;
|
||||
}
|
||||
|
||||
// 40250 GrpExpandedImageInstance.cpp:29-142. The vertex and texture-coordinate arithmetic is
|
||||
// verbatim; the D3D stream/blend-state calls become one UIRenderCommand with the four vertices.
|
||||
auto CGraphicExpandedImageInstance::OnRender() -> void
|
||||
{
|
||||
CGraphicImage * pImage = m_roImage.GetPointer();
|
||||
CGraphicTexture * pTexture = pImage->GetTexturePointer();
|
||||
if (pTexture->GetWidth() <= 0 || pTexture->GetHeight() <= 0)
|
||||
return;
|
||||
|
||||
const RECT& c_rRect = pImage->GetRectReference();
|
||||
float texReverseWidth = 1.0f / float(pTexture->GetWidth());
|
||||
float texReverseHeight = 1.0f / float(pTexture->GetHeight());
|
||||
float su = (c_rRect.left - m_RenderingRect.left) * texReverseWidth;
|
||||
float sv = (c_rRect.top - m_RenderingRect.top) * texReverseHeight;
|
||||
float eu = (c_rRect.left + m_RenderingRect.right + (c_rRect.right-c_rRect.left)) * texReverseWidth;
|
||||
float ev = (c_rRect.top + m_RenderingRect.bottom + (c_rRect.bottom-c_rRect.top)) * texReverseHeight;
|
||||
|
||||
float x[4], y[4];
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
x[i] = m_v2Position.x-0.5f;
|
||||
y[i] = m_v2Position.y-0.5f;
|
||||
}
|
||||
|
||||
if (0.0f == m_fRotation)
|
||||
{
|
||||
float fimgWidth = float(pImage->GetWidth()) * m_v2Scale.x;
|
||||
float fimgHeight = float(pImage->GetHeight()) * m_v2Scale.y;
|
||||
|
||||
x[0] -= m_RenderingRect.left;
|
||||
y[0] -= m_RenderingRect.top;
|
||||
x[1] += fimgWidth + m_RenderingRect.right;
|
||||
y[1] -= m_RenderingRect.top;
|
||||
x[2] -= m_RenderingRect.left;
|
||||
y[2] += fimgHeight + m_RenderingRect.bottom;
|
||||
x[3] += fimgWidth + m_RenderingRect.right;
|
||||
y[3] += fimgHeight + m_RenderingRect.bottom;
|
||||
// PORT: 40250 flips D3DRS_CULLMODE for a mirrored scale; the Godot canvas has no culling.
|
||||
}
|
||||
else
|
||||
{
|
||||
float fimgHalfWidth = float(pImage->GetWidth())/2.0f * m_v2Scale.x;
|
||||
float fimgHalfHeight = float(pImage->GetHeight())/2.0f * m_v2Scale.y;
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
x[i] += m_v2Origin.x;
|
||||
y[i] += m_v2Origin.y;
|
||||
}
|
||||
|
||||
float fRadian = D3DXToRadian(m_fRotation);
|
||||
x[0] += (-fimgHalfWidth*cosf(fRadian)) - (-fimgHalfHeight*sinf(fRadian));
|
||||
y[0] += (-fimgHalfWidth*sinf(fRadian)) + (-fimgHalfHeight*cosf(fRadian));
|
||||
x[1] += (+fimgHalfWidth*cosf(fRadian)) - (-fimgHalfHeight*sinf(fRadian));
|
||||
y[1] += (+fimgHalfWidth*sinf(fRadian)) + (-fimgHalfHeight*cosf(fRadian));
|
||||
x[2] += (-fimgHalfWidth*cosf(fRadian)) - (+fimgHalfHeight*sinf(fRadian));
|
||||
y[2] += (-fimgHalfWidth*sinf(fRadian)) + (+fimgHalfHeight*cosf(fRadian));
|
||||
x[3] += (+fimgHalfWidth*cosf(fRadian)) - (+fimgHalfHeight*sinf(fRadian));
|
||||
y[3] += (+fimgHalfWidth*sinf(fRadian)) + (+fimgHalfHeight*cosf(fRadian));
|
||||
}
|
||||
|
||||
UIRenderAddImage(pTexture, x, y, su, sv, eu, ev, UIRenderColor(m_DiffuseColor), m_iRenderingMode);
|
||||
}
|
||||
|
||||
auto CGraphicExpandedImageInstance::OnSetImagePointer() -> void
|
||||
{
|
||||
if (!IsEmpty()) SetOrigin();
|
||||
}
|
||||
|
||||
auto CGraphicExpandedImageInstance::OnIsType(DWORD type) -> BOOL
|
||||
{
|
||||
return type == Type() || CGraphicImageInstance::OnIsType(type);
|
||||
}
|
||||
|
||||
auto CGraphicExpandedImageInstance::CreateSystem(UINT) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicExpandedImageInstance::DestroySystem() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicExpandedImageInstance::New() -> CGraphicExpandedImageInstance *
|
||||
{
|
||||
return new CGraphicExpandedImageInstance();
|
||||
}
|
||||
|
||||
auto CGraphicExpandedImageInstance::Delete(CGraphicExpandedImageInstance *instance) -> void
|
||||
{
|
||||
if (!instance) return;
|
||||
instance->Destroy();
|
||||
delete instance;
|
||||
}
|
||||
|
||||
decltype(CGraphicExpandedImageInstance::ms_kPool) CGraphicExpandedImageInstance::ms_kPool{};
|
||||
@@ -0,0 +1,386 @@
|
||||
// 40250 EterLib/GrpFontTexture.cpp, verbatim over the GDI emulation in platform/Win32Gdi.cpp. The
|
||||
// A4R4G4B4 page textures are CGraphicImageTexture memory textures the Godot canvas samples.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/GrpText.h"
|
||||
#include "EterBase/Stl.h"
|
||||
|
||||
#include "EterLib/Util.h"
|
||||
#include "UIRenderCommands.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
// PORT: HiDPI glyph pages (UISetFontOversample). With a scale s > 1 the page DIB and texture are
|
||||
// s times larger and each glyph is drawn with an s-times font into the s-scaled cell, while the
|
||||
// 40250 metrics (width, height, advance), the packing and therefore the page UVs stay the logical
|
||||
// 1x values: text layout is unchanged, only the texels get finer.
|
||||
namespace {
|
||||
int g_font_oversample = 1;
|
||||
bool g_font_oversample_latched = false;
|
||||
// The 1x HFONT a CGraphicFontTexture measures with -> its s-times twin that draws.
|
||||
std::unordered_map<HFONT, HFONT> g_oversampled_fonts;
|
||||
|
||||
int FontOversample()
|
||||
{
|
||||
g_font_oversample_latched = true;
|
||||
return g_font_oversample;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void UISetFontOversample(int scale)
|
||||
{
|
||||
if (g_font_oversample_latched)
|
||||
return;
|
||||
g_font_oversample = scale < 1 ? 1 : (scale > 4 ? 4 : scale);
|
||||
}
|
||||
|
||||
int UIFontOversample()
|
||||
{
|
||||
return g_font_oversample;
|
||||
}
|
||||
|
||||
CGraphicFontTexture::CGraphicFontTexture()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
CGraphicFontTexture::~CGraphicFontTexture()
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void CGraphicFontTexture::Initialize()
|
||||
{
|
||||
CGraphicTexture::Initialize();
|
||||
m_hFontOld = NULL;
|
||||
m_hFont = NULL;
|
||||
m_isDirty = false;
|
||||
m_bItalic = false;
|
||||
}
|
||||
|
||||
bool CGraphicFontTexture::IsEmpty() const
|
||||
{
|
||||
return m_fontMap.size() == 0;
|
||||
}
|
||||
|
||||
void CGraphicFontTexture::Destroy()
|
||||
{
|
||||
HDC hDC = m_dib.GetDCHandle();
|
||||
if (hDC)
|
||||
SelectObject(hDC, m_hFontOld);
|
||||
|
||||
m_dib.Destroy();
|
||||
|
||||
m_lpd3dTexture = NULL;
|
||||
CGraphicTexture::Destroy();
|
||||
stl_wipe(m_pFontTextureVector);
|
||||
m_charInfoMap.clear();
|
||||
|
||||
if (m_fontMap.size())
|
||||
{
|
||||
TFontMap::iterator i = m_fontMap.begin();
|
||||
|
||||
while(i != m_fontMap.end())
|
||||
{
|
||||
TFontMap::mapped_type hFont = i->second;
|
||||
auto hi = g_oversampled_fonts.find(hFont);
|
||||
if (hi != g_oversampled_fonts.end())
|
||||
{
|
||||
DeleteObject((HGDIOBJ)hi->second);
|
||||
g_oversampled_fonts.erase(hi);
|
||||
}
|
||||
DeleteObject((HGDIOBJ)i->second);
|
||||
++i;
|
||||
}
|
||||
|
||||
m_fontMap.clear();
|
||||
}
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
bool CGraphicFontTexture::CreateDeviceObjects()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void CGraphicFontTexture::DestroyDeviceObjects()
|
||||
{
|
||||
}
|
||||
|
||||
bool CGraphicFontTexture::Create(const char* c_szFontName, int fontSize, bool bItalic)
|
||||
{
|
||||
Destroy();
|
||||
|
||||
strncpy(m_fontName, c_szFontName, sizeof(m_fontName)-1);
|
||||
m_fontSize = fontSize;
|
||||
m_bItalic = bItalic;
|
||||
|
||||
m_x = 0;
|
||||
m_y = 0;
|
||||
m_step = 0;
|
||||
|
||||
DWORD width = 256,height = 256;
|
||||
if (GetMaxTextureWidth() > 512)
|
||||
width = 512;
|
||||
if (GetMaxTextureHeight() > 512)
|
||||
height = 512;
|
||||
|
||||
const int s = FontOversample();
|
||||
if (!m_dib.Create(ms_hDC, width * s, height * s))
|
||||
return false;
|
||||
|
||||
HDC hDC = m_dib.GetDCHandle();
|
||||
|
||||
m_hFont = GetFont(GetDefaultCodePage());
|
||||
|
||||
m_hFontOld=(HFONT)SelectObject(hDC, m_hFont);
|
||||
SetTextColor(hDC, RGB(255, 255, 255));
|
||||
SetBkColor(hDC, 0);
|
||||
|
||||
if (!AppendTexture())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
HFONT CGraphicFontTexture::GetFont(WORD codePage)
|
||||
{
|
||||
HFONT hFont = NULL;
|
||||
TFontMap::iterator i = m_fontMap.find(codePage);
|
||||
|
||||
if(i != m_fontMap.end())
|
||||
{
|
||||
hFont = i->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGFONT logFont;
|
||||
|
||||
memset(&logFont, 0, sizeof(LOGFONT));
|
||||
|
||||
logFont.lfHeight = m_fontSize;
|
||||
logFont.lfEscapement = 0;
|
||||
logFont.lfOrientation = 0;
|
||||
logFont.lfWeight = FW_NORMAL;
|
||||
logFont.lfItalic = (BYTE) m_bItalic;
|
||||
logFont.lfUnderline = FALSE;
|
||||
logFont.lfStrikeOut = FALSE;
|
||||
logFont.lfCharSet = GetCharsetFromCodePage(codePage);
|
||||
logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
|
||||
logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
|
||||
logFont.lfQuality = ANTIALIASED_QUALITY;
|
||||
logFont.lfPitchAndFamily = DEFAULT_PITCH;
|
||||
//Tracenf("font: %s", GetFontFaceFromCodePage(codePage));
|
||||
strcpy(logFont.lfFaceName, m_fontName); //GetFontFaceFromCodePage(codePage));
|
||||
//strcpy(logFont.lfFaceName, GetFontFaceFromCodePage(codePage));
|
||||
|
||||
hFont = CreateFontIndirect(&logFont);
|
||||
|
||||
m_fontMap.insert(TFontMap::value_type(codePage, hFont));
|
||||
|
||||
if (const int s = FontOversample(); s > 1 && hFont)
|
||||
{
|
||||
logFont.lfHeight = m_fontSize * s;
|
||||
if (HFONT hHiFont = CreateFontIndirect(&logFont))
|
||||
g_oversampled_fonts[hFont] = hHiFont;
|
||||
}
|
||||
}
|
||||
|
||||
return hFont;
|
||||
}
|
||||
|
||||
bool CGraphicFontTexture::AppendTexture()
|
||||
{
|
||||
CGraphicImageTexture * pNewTexture = new CGraphicImageTexture;
|
||||
|
||||
if (!pNewTexture->Create(m_dib.GetWidth(), m_dib.GetHeight(), D3DFMT_A4R4G4B4))
|
||||
{
|
||||
delete pNewTexture;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_pFontTextureVector.push_back(pNewTexture);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGraphicFontTexture::UpdateTexture()
|
||||
{
|
||||
if(!m_isDirty)
|
||||
return true;
|
||||
|
||||
m_isDirty = false;
|
||||
|
||||
CGraphicImageTexture * pFontTexture = m_pFontTextureVector.back();
|
||||
|
||||
if (!pFontTexture)
|
||||
return false;
|
||||
|
||||
WORD* pwDst;
|
||||
int pitch;
|
||||
|
||||
if (!pFontTexture->Lock(&pitch, (void**)&pwDst))
|
||||
return false;
|
||||
|
||||
pitch /= 2;
|
||||
|
||||
int width = m_dib.GetWidth();
|
||||
int height = m_dib.GetHeight();
|
||||
|
||||
DWORD * pdwSrc = (DWORD*)m_dib.GetPointer();
|
||||
|
||||
for (int y = 0; y < height; ++y, pwDst += pitch, pdwSrc += width)
|
||||
for (int x = 0; x < width; ++x)
|
||||
pwDst[x]=pdwSrc[x];
|
||||
|
||||
pFontTexture->Unlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::GetCharacterInfomation(WORD codePage, wchar_t keyValue)
|
||||
{
|
||||
TCharacterKey code(codePage, keyValue);
|
||||
|
||||
TCharacterInfomationMap::iterator f = m_charInfoMap.find(code);
|
||||
|
||||
if (m_charInfoMap.end() == f)
|
||||
{
|
||||
return UpdateCharacterInfomation(code);
|
||||
}
|
||||
else
|
||||
{
|
||||
return &f->second;
|
||||
}
|
||||
}
|
||||
|
||||
CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::UpdateCharacterInfomation(TCharacterKey code)
|
||||
{
|
||||
HDC hDC = m_dib.GetDCHandle();
|
||||
HFONT hFont = GetFont(code.first);
|
||||
SelectObject(hDC, hFont);
|
||||
|
||||
wchar_t keyValue = code.second;
|
||||
|
||||
if (keyValue == 0x08)
|
||||
keyValue = L' '; // 탭은 공백으로 바꾼다 (아랍 출력시 탭 사용: NAME:\tTEXT -> TEXT\t:NAME 로 전환됨 )
|
||||
|
||||
ABCFLOAT stABC;
|
||||
SIZE size;
|
||||
|
||||
if (!GetTextExtentPoint32W(hDC, &keyValue, 1, &size) || !GetCharABCWidthsFloatW(hDC, keyValue, keyValue, &stABC))
|
||||
return NULL;
|
||||
|
||||
size.cx = stABC.abcfB;
|
||||
if( stABC.abcfA > 0.0f )
|
||||
size.cx += ceilf(stABC.abcfA);
|
||||
if( stABC.abcfC > 0.0f )
|
||||
size.cx += ceilf(stABC.abcfC);
|
||||
size.cx++;
|
||||
|
||||
LONG lAdvance = ceilf( stABC.abcfA + stABC.abcfB + stABC.abcfC );
|
||||
|
||||
const int s = FontOversample();
|
||||
int width = m_dib.GetWidth() / s;
|
||||
int height = m_dib.GetHeight() / s;
|
||||
|
||||
if (m_x + size.cx >= (width - 1))
|
||||
{
|
||||
m_y += (m_step + 1);
|
||||
m_step = 0;
|
||||
m_x = 0;
|
||||
|
||||
if (m_y + size.cy >= (height - 1))
|
||||
{
|
||||
if (!UpdateTexture())
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!AppendTexture())
|
||||
return NULL;
|
||||
|
||||
m_y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto hi = s > 1 ? g_oversampled_fonts.find(hFont) : g_oversampled_fonts.end();
|
||||
if (hi != g_oversampled_fonts.end())
|
||||
{
|
||||
// Draw with the s-times font into the s-scaled cell and keep its anti-aliasing as
|
||||
// A4R4G4B4 white with coverage alpha (1x keeps 40250's bilevel threshold below).
|
||||
SelectObject(hDC, hi->second);
|
||||
TextOutW(hDC, m_x * s, m_y * s, &keyValue, 1);
|
||||
SelectObject(hDC, hFont);
|
||||
|
||||
const int nDIBWidth = m_dib.GetWidth();
|
||||
DWORD* pdwRow = (DWORD*)m_dib.GetPointer() + nDIBWidth * (m_y * s) + m_x * s;
|
||||
for (int y = 0; y < size.cy * s; ++y, pdwRow += nDIBWidth)
|
||||
for (int x = 0; x < size.cx * s; ++x)
|
||||
{
|
||||
const DWORD alpha = ((pdwRow[x] & 0xff) * 15 + 127) / 255;
|
||||
pdwRow[x] = alpha ? ((alpha << 12) | 0x0fff) : 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TextOutW(hDC, m_x, m_y, &keyValue, 1);
|
||||
|
||||
int nChrX;
|
||||
int nChrY;
|
||||
int nChrWidth = size.cx;
|
||||
int nChrHeight = size.cy;
|
||||
int nDIBWidth = m_dib.GetWidth();
|
||||
|
||||
|
||||
DWORD*pdwDIBData=(DWORD*)m_dib.GetPointer();
|
||||
DWORD*pdwDIBBase=pdwDIBData+nDIBWidth*m_y+m_x;
|
||||
DWORD*pdwDIBRow;
|
||||
|
||||
pdwDIBRow=pdwDIBBase;
|
||||
for (nChrY=0; nChrY<nChrHeight; ++nChrY, pdwDIBRow+=nDIBWidth)
|
||||
{
|
||||
for (nChrX=0; nChrX<nChrWidth; ++nChrX)
|
||||
{
|
||||
pdwDIBRow[nChrX]=(pdwDIBRow[nChrX]&0xff) ? 0xffff : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float rhwidth = 1.0f / float(width);
|
||||
float rhheight = 1.0f / float(height);
|
||||
|
||||
TCharacterInfomation& rNewCharInfo = m_charInfoMap[code];
|
||||
|
||||
rNewCharInfo.index = static_cast<short>(m_pFontTextureVector.size() - 1);
|
||||
rNewCharInfo.width = size.cx;
|
||||
rNewCharInfo.height = size.cy;
|
||||
rNewCharInfo.left = float(m_x) * rhwidth;
|
||||
rNewCharInfo.top = float(m_y) * rhheight;
|
||||
rNewCharInfo.right = float(m_x+size.cx) * rhwidth;
|
||||
rNewCharInfo.bottom = float(m_y+size.cy) * rhheight;
|
||||
rNewCharInfo.advance = (float) lAdvance;
|
||||
|
||||
m_x += size.cx;
|
||||
|
||||
if (m_step < size.cy)
|
||||
m_step = size.cy;
|
||||
|
||||
m_isDirty = true;
|
||||
|
||||
return &rNewCharInfo;
|
||||
}
|
||||
|
||||
bool CGraphicFontTexture::CheckTextureIndex(DWORD dwTexture)
|
||||
{
|
||||
if (dwTexture >= m_pFontTextureVector.size())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CGraphicFontTexture::SelectTexture(DWORD dwTexture)
|
||||
{
|
||||
assert(CheckTextureIndex(dwTexture));
|
||||
m_lpd3dTexture = m_pFontTextureVector[dwTexture]->GetD3DTexture();
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// Platform skeleton for EterLib/GrpImage.h (40250 EterLib/GrpImage.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/GrpImage.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
auto CGraphicImage::Type() -> TType
|
||||
{
|
||||
static TType type = StringToType("CGraphicImage");
|
||||
return type;
|
||||
}
|
||||
|
||||
CGraphicImage::CGraphicImage(const char * c_szFileName, DWORD dwFilter) : CResource(c_szFileName), m_dwFilter(dwFilter) // initializers as in 40250
|
||||
{
|
||||
m_rect = {0, 0, 0, 0};
|
||||
}
|
||||
|
||||
CGraphicImage::~CGraphicImage()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicImage::CreateDeviceObjects() -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CGraphicImage::DestroyDeviceObjects() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicImage::GetWidth() const -> int
|
||||
{
|
||||
return m_rect.right - m_rect.left;
|
||||
}
|
||||
|
||||
auto CGraphicImage::GetHeight() const -> int
|
||||
{
|
||||
return m_rect.bottom - m_rect.top;
|
||||
}
|
||||
|
||||
auto CGraphicImage::GetRectReference() const -> const RECT &
|
||||
{
|
||||
return m_rect;
|
||||
}
|
||||
|
||||
auto CGraphicImage::GetTextureReference() const -> const CGraphicTexture &
|
||||
{
|
||||
return m_imageTexture;
|
||||
}
|
||||
|
||||
auto CGraphicImage::GetTexturePointer() -> CGraphicTexture *
|
||||
{
|
||||
return &m_imageTexture;
|
||||
}
|
||||
|
||||
auto CGraphicImage::OnLoad(int size, const void *data) -> bool
|
||||
{
|
||||
m_imageTexture.SetFileName(GetFileName());
|
||||
if (!m_imageTexture.CreateFromMemoryFile(size, data, D3DFMT_UNKNOWN, m_dwFilter)) return false;
|
||||
m_rect = {0, 0, m_imageTexture.GetWidth(), m_imageTexture.GetHeight()};
|
||||
return true;
|
||||
}
|
||||
|
||||
auto CGraphicImage::OnClear() -> void
|
||||
{
|
||||
m_imageTexture.Destroy();
|
||||
m_rect = {0, 0, 0, 0};
|
||||
}
|
||||
|
||||
auto CGraphicImage::OnIsEmpty() const -> bool
|
||||
{
|
||||
return m_imageTexture.IsEmpty();
|
||||
}
|
||||
|
||||
auto CGraphicImage::OnIsType(TType type) -> bool
|
||||
{
|
||||
return type == Type() || CResource::OnIsType(type);
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
// Platform skeleton for EterLib/GrpImageInstance.h (40250 EterLib/GrpImageInstance.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/GrpImageInstance.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
#include "UIRenderCommands.h"
|
||||
#include "EterBase/CRC32.h"
|
||||
|
||||
auto CGraphicImageInstance::Type() -> DWORD
|
||||
{
|
||||
static DWORD type = GetCRC32("CGraphicImageInstance", sizeof("CGraphicImageInstance") - 1);
|
||||
return type;
|
||||
}
|
||||
|
||||
auto CGraphicImageInstance::IsType(DWORD type) -> BOOL
|
||||
{
|
||||
return OnIsType(type);
|
||||
}
|
||||
|
||||
CGraphicImageInstance::CGraphicImageInstance()
|
||||
{
|
||||
m_DiffuseColor = D3DXCOLOR(1, 1, 1, 1);
|
||||
m_v2Position = D3DXVECTOR2(0, 0);
|
||||
}
|
||||
|
||||
CGraphicImageInstance::~CGraphicImageInstance()
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
|
||||
// 40250 Destroy(): drop the image reference, then Initialize() (white, at the origin).
|
||||
auto CGraphicImageInstance::Destroy() -> void
|
||||
{
|
||||
m_roImage.Clear();
|
||||
m_DiffuseColor = D3DXCOLOR(1, 1, 1, 1);
|
||||
m_v2Position = D3DXVECTOR2(0, 0);
|
||||
}
|
||||
|
||||
auto CGraphicImageInstance::Render() -> void
|
||||
{
|
||||
if (!IsEmpty()) OnRender();
|
||||
}
|
||||
|
||||
auto CGraphicImageInstance::SetDiffuseColor(float r, float g, float b, float a) -> void
|
||||
{
|
||||
m_DiffuseColor = D3DXCOLOR(r, g, b, a);
|
||||
}
|
||||
|
||||
auto CGraphicImageInstance::SetPosition(float x, float y) -> void
|
||||
{
|
||||
m_v2Position = D3DXVECTOR2(x, y);
|
||||
}
|
||||
|
||||
auto CGraphicImageInstance::SetImagePointer(CGraphicImage *image) -> void
|
||||
{
|
||||
m_roImage.SetPointer(image);
|
||||
OnSetImagePointer();
|
||||
}
|
||||
|
||||
auto CGraphicImageInstance::ReloadImagePointer(CGraphicImage *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicImageInstance::IsEmpty() const -> bool
|
||||
{
|
||||
return m_roImage.IsNull() || m_roImage->IsEmpty();
|
||||
}
|
||||
|
||||
auto CGraphicImageInstance::GetWidth() -> int
|
||||
{
|
||||
return IsEmpty() ? 0 : m_roImage->GetWidth();
|
||||
}
|
||||
|
||||
auto CGraphicImageInstance::GetHeight() -> int
|
||||
{
|
||||
return IsEmpty() ? 0 : m_roImage->GetHeight();
|
||||
}
|
||||
|
||||
auto CGraphicImageInstance::GetTexturePointer() -> CGraphicTexture *
|
||||
{
|
||||
CGraphicImage* pkImage = m_roImage.GetPointer();
|
||||
return pkImage ? pkImage->GetTexturePointer() : NULL;
|
||||
}
|
||||
|
||||
auto CGraphicImageInstance::GetTextureReference() const -> const CGraphicTexture &
|
||||
{
|
||||
return m_roImage->GetTextureReference();
|
||||
}
|
||||
|
||||
auto CGraphicImageInstance::GetGraphicImagePointer() -> CGraphicImage *
|
||||
{
|
||||
return m_roImage.GetPointer();
|
||||
}
|
||||
|
||||
auto CGraphicImageInstance::operator==(const CGraphicImageInstance &) const -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CGraphicImageInstance::Initialize() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
// 40250 GrpImageInstance.cpp:44-96: the image's rect inside its texture (a .sub shares its parent's)
|
||||
// becomes the texture coordinates of one quad.
|
||||
auto CGraphicImageInstance::OnRender() -> void
|
||||
{
|
||||
CGraphicImage * pImage = m_roImage.GetPointer();
|
||||
CGraphicTexture * pTexture = pImage->GetTexturePointer();
|
||||
if (pTexture->GetWidth() <= 0 || pTexture->GetHeight() <= 0)
|
||||
return;
|
||||
|
||||
float fimgWidth = pImage->GetWidth();
|
||||
float fimgHeight = pImage->GetHeight();
|
||||
|
||||
const RECT& c_rRect = pImage->GetRectReference();
|
||||
float texReverseWidth = 1.0f / float(pTexture->GetWidth());
|
||||
float texReverseHeight = 1.0f / float(pTexture->GetHeight());
|
||||
float su = c_rRect.left * texReverseWidth;
|
||||
float sv = c_rRect.top * texReverseHeight;
|
||||
float eu = (c_rRect.left + (c_rRect.right-c_rRect.left)) * texReverseWidth;
|
||||
float ev = (c_rRect.top + (c_rRect.bottom-c_rRect.top)) * texReverseHeight;
|
||||
|
||||
const float x[4] = {m_v2Position.x-0.5f, m_v2Position.x + fimgWidth-0.5f,
|
||||
m_v2Position.x-0.5f, m_v2Position.x + fimgWidth-0.5f};
|
||||
const float y[4] = {m_v2Position.y-0.5f, m_v2Position.y-0.5f,
|
||||
m_v2Position.y + fimgHeight-0.5f, m_v2Position.y + fimgHeight-0.5f};
|
||||
UIRenderAddImage(pTexture, x, y, su, sv, eu, ev, UIRenderColor(m_DiffuseColor));
|
||||
}
|
||||
|
||||
auto CGraphicImageInstance::OnSetImagePointer() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicImageInstance::OnIsType(DWORD type) -> BOOL
|
||||
{
|
||||
return type == Type();
|
||||
}
|
||||
|
||||
auto CGraphicImageInstance::CreateSystem(UINT) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicImageInstance::DestroySystem() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicImageInstance::New() -> CGraphicImageInstance *
|
||||
{
|
||||
return new CGraphicImageInstance();
|
||||
}
|
||||
|
||||
auto CGraphicImageInstance::Delete(CGraphicImageInstance *instance) -> void
|
||||
{
|
||||
if (!instance) return;
|
||||
instance->Destroy();
|
||||
delete instance;
|
||||
}
|
||||
|
||||
decltype(CGraphicImageInstance::ms_kPool) CGraphicImageInstance::ms_kPool{};
|
||||
@@ -0,0 +1,338 @@
|
||||
// Platform skeleton for EterLib/GrpImageTexture.h (40250 EterLib/GrpImageTexture.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/GrpImageTexture.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
#include "UIRenderCommands.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
|
||||
namespace {
|
||||
unsigned little16(const unsigned char* data) { return data[0] | (unsigned(data[1]) << 8); }
|
||||
unsigned little32(const unsigned char* data) { return little16(data) | (little16(data + 2) << 16); }
|
||||
unsigned big16(const unsigned char* data) { return (unsigned(data[0]) << 8) | data[1]; }
|
||||
unsigned big32(const unsigned char* data) { return (unsigned(data[0]) << 24) | (unsigned(data[1]) << 16) | (unsigned(data[2]) << 8) | data[3]; }
|
||||
|
||||
// Walks the JPEG markers to the first SOFn (the frame header holds the size), as D3DX does when it
|
||||
// sniffs a .jpg; login/loading backgrounds (serverlist.jpg, loading/*.jpg) are JPEGs.
|
||||
bool jpeg_size(const unsigned char* data, unsigned size, unsigned* width, unsigned* height)
|
||||
{
|
||||
if (size < 4 || data[0] != 0xFF || data[1] != 0xD8) return false;
|
||||
unsigned pos = 2;
|
||||
while (pos + 4 <= size) {
|
||||
if (data[pos] != 0xFF) return false;
|
||||
const unsigned char marker = data[pos + 1];
|
||||
if (marker == 0xFF) { ++pos; continue; }
|
||||
if (marker == 0xD8 || marker == 0x01 || (marker >= 0xD0 && marker <= 0xD7)) { pos += 2; continue; }
|
||||
const unsigned length = big16(data + pos + 2);
|
||||
if (length < 2) return false;
|
||||
const bool sof = marker >= 0xC0 && marker <= 0xCF && marker != 0xC4 && marker != 0xC8 && marker != 0xCC;
|
||||
if (sof) {
|
||||
if (pos + 9 > size) return false;
|
||||
*height = big16(data + pos + 5);
|
||||
*width = big16(data + pos + 7);
|
||||
return true;
|
||||
}
|
||||
pos += 2 + length;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// PORT: the IDirect3DTexture8 behind a CGraphicImageTexture::Create texture. Lock hands out the
|
||||
// pixels in the texture's own format (40250 writes A4R4G4B4 words into the font pages); Unlock
|
||||
// converts them to ARGB32 for the canvas and bumps the revision in the texture's name.
|
||||
struct MemoryTexture : IDirect3DTexture8
|
||||
{
|
||||
unsigned id = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
D3DFORMAT format = D3DFMT_UNKNOWN;
|
||||
std::vector<unsigned char> native;
|
||||
std::vector<std::uint32_t> argb;
|
||||
std::uint32_t revision = 0;
|
||||
};
|
||||
|
||||
std::mutex g_memory_mutex;
|
||||
std::map<unsigned, MemoryTexture*> g_memory;
|
||||
unsigned g_memory_next_id = 1;
|
||||
|
||||
int bytes_per_pixel(D3DFORMAT format)
|
||||
{
|
||||
switch (format) {
|
||||
case D3DFMT_A4R4G4B4: case D3DFMT_R5G6B5: case D3DFMT_A1R5G5B5: case D3DFMT_X1R5G5B5: case D3DFMT_X4R4G4B4:
|
||||
return 2;
|
||||
default:
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
std::uint32_t expand(unsigned value, int bits)
|
||||
{
|
||||
return bits == 4 ? value * 0x11 : bits == 5 ? (value << 3) | (value >> 2) : bits == 6 ? (value << 2) | (value >> 4) : value;
|
||||
}
|
||||
|
||||
std::uint32_t native_to_argb(D3DFORMAT format, const unsigned char* p)
|
||||
{
|
||||
const unsigned w = p[0] | (unsigned(p[1]) << 8);
|
||||
switch (format) {
|
||||
case D3DFMT_A4R4G4B4:
|
||||
return (expand(w >> 12, 4) << 24) | (expand((w >> 8) & 15, 4) << 16) | (expand((w >> 4) & 15, 4) << 8) | expand(w & 15, 4);
|
||||
case D3DFMT_X4R4G4B4:
|
||||
return 0xFF000000u | (expand((w >> 8) & 15, 4) << 16) | (expand((w >> 4) & 15, 4) << 8) | expand(w & 15, 4);
|
||||
case D3DFMT_R5G6B5:
|
||||
return 0xFF000000u | (expand(w >> 11, 5) << 16) | (expand((w >> 5) & 63, 6) << 8) | expand(w & 31, 5);
|
||||
case D3DFMT_A1R5G5B5: case D3DFMT_X1R5G5B5:
|
||||
return ((format == D3DFMT_X1R5G5B5 || (w & 0x8000)) ? 0xFF000000u : 0) | (expand((w >> 10) & 31, 5) << 16) |
|
||||
(expand((w >> 5) & 31, 5) << 8) | expand(w & 31, 5);
|
||||
case D3DFMT_X8R8G8B8:
|
||||
return 0xFF000000u | (p[2] << 16) | (p[1] << 8) | p[0];
|
||||
default:
|
||||
return (std::uint32_t(p[3]) << 24) | (p[2] << 16) | (p[1] << 8) | p[0];
|
||||
}
|
||||
}
|
||||
|
||||
MemoryTexture* live_memory_texture(const IDirect3DTexture8* texture)
|
||||
{
|
||||
for (const auto& entry : g_memory)
|
||||
if (entry.second == texture) return entry.second;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// PORT: the IDirect3DTexture8 behind a file texture (CreateFromMemoryFile). The canvas and the 3D
|
||||
// renderer decode the file themselves; the handle only carries the pack path so a texture bound
|
||||
// through STATEMANAGER.SetTexture(stage, GetD3DTexture()) can be named.
|
||||
struct FileTexture : IDirect3DTexture8
|
||||
{
|
||||
std::string name;
|
||||
};
|
||||
|
||||
std::set<const FileTexture*> g_file_textures; // guarded by g_memory_mutex
|
||||
|
||||
IDirect3DTexture8* new_file_texture(const std::string& name)
|
||||
{
|
||||
auto* file = new FileTexture();
|
||||
file->name = name;
|
||||
std::lock_guard<std::mutex> lock(g_memory_mutex);
|
||||
g_file_textures.insert(file);
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
void UIRenderReleaseMemoryTexture(IDirect3DTexture8* texture)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_memory_mutex);
|
||||
if (MemoryTexture* memory = live_memory_texture(texture)) {
|
||||
g_memory.erase(memory->id);
|
||||
delete memory;
|
||||
} else if (g_file_textures.erase(static_cast<const FileTexture*>(texture))) {
|
||||
delete static_cast<FileTexture*>(texture);
|
||||
}
|
||||
}
|
||||
|
||||
std::string UIRenderTextureNameFromHandle(const IDirect3DBaseTexture8* handle)
|
||||
{
|
||||
if (!handle) return {};
|
||||
const auto* texture = static_cast<const IDirect3DTexture8*>(handle);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_memory_mutex);
|
||||
if (const MemoryTexture* memory = live_memory_texture(texture))
|
||||
return "mem:" + std::to_string(memory->id) + "@" + std::to_string(memory->revision);
|
||||
const auto* file = static_cast<const FileTexture*>(texture);
|
||||
if (g_file_textures.count(file))
|
||||
return file->name;
|
||||
}
|
||||
return MtCpuTextureNameFromHandle(handle);
|
||||
}
|
||||
|
||||
bool UIRenderMemoryTexture(const std::string& name, UIMemoryTexture* out)
|
||||
{
|
||||
if (name.compare(0, 4, "mem:") != 0 || !out) return false;
|
||||
if (name.compare(0, 8, "mem:cpu_") == 0)
|
||||
return MtCpuMemoryTexture(name, out);
|
||||
const unsigned id = unsigned(std::strtoul(name.c_str() + 4, nullptr, 10));
|
||||
std::lock_guard<std::mutex> lock(g_memory_mutex);
|
||||
auto it = g_memory.find(id);
|
||||
if (it == g_memory.end()) return false;
|
||||
out->width = it->second->width;
|
||||
out->height = it->second->height;
|
||||
out->revision = it->second->revision;
|
||||
out->argb = it->second->argb;
|
||||
return true;
|
||||
}
|
||||
|
||||
CGraphicImageTexture::CGraphicImageTexture()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
CGraphicImageTexture::~CGraphicImageTexture()
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
|
||||
auto CGraphicImageTexture::Destroy() -> void
|
||||
{
|
||||
CGraphicTexture::Destroy();
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
auto CGraphicImageTexture::Create(UINT width, UINT height, D3DFORMAT d3dFmt, DWORD dwFilter) -> bool
|
||||
{
|
||||
Destroy();
|
||||
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
m_d3dFmt = d3dFmt;
|
||||
m_dwFilter = dwFilter;
|
||||
|
||||
return CreateDeviceObjects();
|
||||
}
|
||||
|
||||
auto CGraphicImageTexture::CreateDeviceObjects() -> bool
|
||||
{
|
||||
if (m_stFileName.empty())
|
||||
{
|
||||
// PORT: ms_lpd3dDevice->CreateTexture(m_width, m_height, 1, 0, m_d3dFmt, D3DPOOL_MANAGED) becomes a
|
||||
// memory texture the canvas fetches by name.
|
||||
if (m_width <= 0 || m_height <= 0)
|
||||
return false;
|
||||
auto* memory = new MemoryTexture();
|
||||
memory->width = m_width;
|
||||
memory->height = m_height;
|
||||
memory->format = m_d3dFmt;
|
||||
memory->native.assign(size_t(m_width) * size_t(m_height) * size_t(bytes_per_pixel(m_d3dFmt)), 0);
|
||||
memory->argb.assign(size_t(m_width) * size_t(m_height), 0);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_memory_mutex);
|
||||
memory->id = g_memory_next_id++;
|
||||
g_memory[memory->id] = memory;
|
||||
}
|
||||
m_lpd3dTexture = memory;
|
||||
}
|
||||
else
|
||||
{
|
||||
// PORT: file textures are decoded by the canvas from the pack path; only their size is read here
|
||||
// (CreateFromMemoryFile), so there is no device object to recreate.
|
||||
return !m_bEmpty;
|
||||
}
|
||||
|
||||
m_bEmpty = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto CGraphicImageTexture::CreateFromTexturePointer(const CGraphicTexture *source) -> void
|
||||
{
|
||||
if (!source) { Destroy(); return; }
|
||||
m_width = source->GetWidth();
|
||||
m_height = source->GetHeight();
|
||||
m_bEmpty = source->IsEmpty();
|
||||
// 40250 shares the source's D3D texture; the canvas names textures by pack path instead.
|
||||
if (const auto* image = dynamic_cast<const CGraphicImageTexture*>(source))
|
||||
m_stFileName = image->m_stFileName;
|
||||
// PORT: 40250 AddRefs the source's texture; this handle is not reference counted, so the copy gets
|
||||
// its own named handle.
|
||||
DestroyDeviceObjects();
|
||||
if (!m_bEmpty && !m_stFileName.empty())
|
||||
m_lpd3dTexture = new_file_texture(m_stFileName);
|
||||
}
|
||||
|
||||
auto CGraphicImageTexture::CreateFromDiskFile(const char *, D3DFORMAT, DWORD) -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CGraphicImageTexture::CreateFromMemoryFile(UINT size, const void *bytes, D3DFORMAT, DWORD) -> bool
|
||||
{
|
||||
if (!bytes) return false;
|
||||
const auto* data = static_cast<const unsigned char*>(bytes);
|
||||
unsigned width = 0, height = 0;
|
||||
if (size >= 20 && std::memcmp(data, "DDS ", 4) == 0) {
|
||||
height = little32(data + 12); width = little32(data + 16);
|
||||
} else if (size >= 24 && std::memcmp(data, "\x89PNG\r\n\x1a\n", 8) == 0) {
|
||||
width = big32(data + 16); height = big32(data + 20);
|
||||
} else if (jpeg_size(data, size, &width, &height)) {
|
||||
} else if (size >= 26 && data[0] == 'B' && data[1] == 'M') {
|
||||
width = little32(data + 18);
|
||||
const int bmp_height = static_cast<int>(little32(data + 22)); // negative: top-down rows
|
||||
height = static_cast<unsigned>(bmp_height < 0 ? -bmp_height : bmp_height);
|
||||
} else if (size >= 18 && (data[2] == 2 || data[2] == 10 || data[2] == 3)) {
|
||||
width = little16(data + 12); height = little16(data + 14);
|
||||
}
|
||||
if (!width || !height || width > 16384 || height > 16384) return false;
|
||||
m_width = static_cast<int>(width); m_height = static_cast<int>(height);
|
||||
m_bEmpty = false;
|
||||
DestroyDeviceObjects();
|
||||
m_lpd3dTexture = new_file_texture(m_stFileName);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto CGraphicImageTexture::CreateDDSTexture(CDXTCImage &, const BYTE *) -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CGraphicImageTexture::SetFileName(const char *name) -> void
|
||||
{
|
||||
m_stFileName = name ? name : "";
|
||||
}
|
||||
|
||||
// PORT: where 40250 binds pTexture->GetD3DTexture(), the canvas gets the pack path of the file the
|
||||
// texture was decoded from (m_stFileName is protected, hence the derived accessor), or the name of the
|
||||
// memory texture bound to it (a CGraphicFontTexture after SelectTexture).
|
||||
std::string UIRenderTextureName(const CGraphicTexture* texture)
|
||||
{
|
||||
if (!texture) return {};
|
||||
if (texture->GetD3DTexture()) {
|
||||
std::lock_guard<std::mutex> lock(g_memory_mutex);
|
||||
if (const MemoryTexture* memory = live_memory_texture(texture->GetD3DTexture()))
|
||||
return "mem:" + std::to_string(memory->id) + "@" + std::to_string(memory->revision);
|
||||
}
|
||||
struct Access : CGraphicImageTexture {
|
||||
static const std::string& name(const CGraphicImageTexture* t) { return t->*(&Access::m_stFileName); }
|
||||
};
|
||||
const auto* image = dynamic_cast<const CGraphicImageTexture*>(texture);
|
||||
return image ? Access::name(image) : std::string();
|
||||
}
|
||||
|
||||
// PORT: LockRect/UnlockRect on the memory texture (one level; the lock is on the whole surface).
|
||||
auto CGraphicImageTexture::Lock(int* pRetPitch, void** ppRetPixels, int) -> bool
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_memory_mutex);
|
||||
MemoryTexture* memory = live_memory_texture(m_lpd3dTexture);
|
||||
if (!memory)
|
||||
return false;
|
||||
|
||||
*pRetPitch = memory->width * bytes_per_pixel(memory->format);
|
||||
*ppRetPixels = memory->native.data();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto CGraphicImageTexture::Unlock(int) -> void
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_memory_mutex);
|
||||
MemoryTexture* memory = live_memory_texture(m_lpd3dTexture);
|
||||
assert(memory != NULL);
|
||||
if (!memory)
|
||||
return;
|
||||
const int bpp = bytes_per_pixel(memory->format);
|
||||
for (size_t i = 0; i < memory->argb.size(); ++i)
|
||||
memory->argb[i] = native_to_argb(memory->format, memory->native.data() + i * size_t(bpp));
|
||||
++memory->revision;
|
||||
}
|
||||
|
||||
auto CGraphicImageTexture::Initialize() -> void
|
||||
{
|
||||
CGraphicTexture::Initialize();
|
||||
|
||||
m_stFileName = "";
|
||||
|
||||
m_d3dFmt=D3DFMT_UNKNOWN;
|
||||
m_dwFilter=0;
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// 40250 EterLib/GrpIndexBuffer.cpp over CPU memory (CpuBuffer.h).
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/GrpIndexBuffer.h"
|
||||
#include "EterLib/StateManager.h"
|
||||
|
||||
#include "CpuBuffer.h"
|
||||
|
||||
namespace {
|
||||
BYTE* index_bytes(LPDIRECT3DINDEXBUFFER8 buffer)
|
||||
{
|
||||
return static_cast<MtCpuIndexBuffer*>(buffer)->bytes.data();
|
||||
}
|
||||
}
|
||||
|
||||
LPDIRECT3DINDEXBUFFER8 CGraphicIndexBuffer::GetD3DIndexBuffer() const
|
||||
{
|
||||
assert(m_lpd3dIdxBuf!=NULL);
|
||||
return m_lpd3dIdxBuf;
|
||||
}
|
||||
|
||||
void CGraphicIndexBuffer::SetIndices(int startIndex) const
|
||||
{
|
||||
STATEMANAGER.SetIndices(m_lpd3dIdxBuf, startIndex);
|
||||
}
|
||||
|
||||
bool CGraphicIndexBuffer::Lock(void** pretIndices) const
|
||||
{
|
||||
assert(m_lpd3dIdxBuf!=NULL);
|
||||
|
||||
*pretIndices = index_bytes(m_lpd3dIdxBuf);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CGraphicIndexBuffer::Unlock() const
|
||||
{
|
||||
assert(m_lpd3dIdxBuf!=NULL);
|
||||
static_cast<MtCpuIndexBuffer*>(m_lpd3dIdxBuf)->revision++;
|
||||
}
|
||||
|
||||
bool CGraphicIndexBuffer::Lock(void** pretIndices)
|
||||
{
|
||||
assert(m_lpd3dIdxBuf!=NULL);
|
||||
|
||||
*pretIndices = index_bytes(m_lpd3dIdxBuf);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CGraphicIndexBuffer::Unlock()
|
||||
{
|
||||
assert(m_lpd3dIdxBuf!=NULL);
|
||||
static_cast<MtCpuIndexBuffer*>(m_lpd3dIdxBuf)->revision++;
|
||||
}
|
||||
|
||||
bool CGraphicIndexBuffer::Copy(int bufSize, const void* srcIndices)
|
||||
{
|
||||
assert(m_lpd3dIdxBuf!=NULL);
|
||||
|
||||
memcpy(index_bytes(m_lpd3dIdxBuf), srcIndices, bufSize);
|
||||
static_cast<MtCpuIndexBuffer*>(m_lpd3dIdxBuf)->revision++;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGraphicIndexBuffer::Create(int faceCount, TFace* faces)
|
||||
{
|
||||
int idxCount = faceCount * 3;
|
||||
m_iidxCount = idxCount;
|
||||
if (!Create(idxCount, D3DFMT_INDEX16))
|
||||
return false;
|
||||
|
||||
WORD* dstIndices = reinterpret_cast<WORD*>(index_bytes(m_lpd3dIdxBuf));
|
||||
|
||||
for (int i = 0; i<faceCount; ++i, dstIndices+=3)
|
||||
{
|
||||
TFace * curFace=faces+i;
|
||||
dstIndices[0]=curFace->indices[0];
|
||||
dstIndices[1]=curFace->indices[1];
|
||||
dstIndices[2]=curFace->indices[2];
|
||||
}
|
||||
static_cast<MtCpuIndexBuffer*>(m_lpd3dIdxBuf)->revision++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGraphicIndexBuffer::CreateDeviceObjects()
|
||||
{
|
||||
MtCpuIndexBuffer* buffer = new MtCpuIndexBuffer;
|
||||
buffer->bytes.assign(m_dwBufferSize, 0);
|
||||
buffer->format = m_d3dFmt;
|
||||
m_lpd3dIdxBuf = buffer;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CGraphicIndexBuffer::DestroyDeviceObjects()
|
||||
{
|
||||
delete static_cast<MtCpuIndexBuffer*>(m_lpd3dIdxBuf);
|
||||
m_lpd3dIdxBuf = NULL;
|
||||
}
|
||||
|
||||
bool CGraphicIndexBuffer::Create(int idxCount, D3DFORMAT d3dFmt)
|
||||
{
|
||||
Destroy();
|
||||
|
||||
m_iidxCount = idxCount;
|
||||
m_dwBufferSize = sizeof(WORD) * idxCount;
|
||||
m_d3dFmt = d3dFmt;
|
||||
|
||||
return CreateDeviceObjects();
|
||||
}
|
||||
|
||||
void CGraphicIndexBuffer::Destroy()
|
||||
{
|
||||
DestroyDeviceObjects();
|
||||
}
|
||||
|
||||
void CGraphicIndexBuffer::Initialize()
|
||||
{
|
||||
m_lpd3dIdxBuf=NULL;
|
||||
m_iidxCount=0;
|
||||
m_dwBufferSize=0;
|
||||
}
|
||||
|
||||
CGraphicIndexBuffer::CGraphicIndexBuffer()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
CGraphicIndexBuffer::~CGraphicIndexBuffer()
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Platform skeleton for EterLib/GrpMath.h (40250 EterLib/GrpMath.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/GrpMath.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
auto CrossProduct2D(float, float, float, float) -> float
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<float>();
|
||||
}
|
||||
|
||||
auto IsInTriangle2D(float, float, float, float, float, float, float, float) -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto D3DXVec3Rotation(D3DXVECTOR3 *, const D3DXVECTOR3 *, const D3DXQUATERNION *) -> D3DXVECTOR3 *
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<D3DXVECTOR3 *>();
|
||||
}
|
||||
|
||||
auto D3DXVec3Translation(D3DXVECTOR3 *, const D3DXVECTOR3 *, const D3DXVECTOR3 *) -> D3DXVECTOR3 *
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<D3DXVECTOR3 *>();
|
||||
}
|
||||
|
||||
auto GetRotationFromMatrix(D3DXVECTOR3 *, const D3DXMATRIX *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto GetPivotAndRotationFromMatrix(D3DXMATRIX *, D3DXVECTOR3 *, D3DXVECTOR3 *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto ExtractMovement(D3DXMATRIX *, D3DXMATRIX *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
@@ -0,0 +1,435 @@
|
||||
// Platform skeleton for EterLib/GrpScreen.h (40250 EterLib/GrpScreen.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/GrpScreen.h"
|
||||
#include "EterLib/StateManager.h"
|
||||
#include "EterLib/Camera.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
#include "UIRenderCommands.h"
|
||||
#include <algorithm>
|
||||
|
||||
// 40250 GrpScreen.cpp:838
|
||||
CScreen::CScreen()
|
||||
{
|
||||
}
|
||||
|
||||
CScreen::~CScreen()
|
||||
{
|
||||
}
|
||||
|
||||
auto CScreen::ClearDepthBuffer() -> void
|
||||
{
|
||||
if (ms_lpd3dDevice)
|
||||
ms_lpd3dDevice->Clear(0L, NULL, D3DCLEAR_ZBUFFER, ms_clearColor, ms_clearDepth, ms_clearStencil);
|
||||
}
|
||||
|
||||
auto CScreen::Clear() -> void
|
||||
{
|
||||
if (ms_lpd3dDevice)
|
||||
ms_lpd3dDevice->Clear(0L, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, ms_clearColor, ms_clearDepth, ms_clearStencil);
|
||||
}
|
||||
|
||||
// 40250 GrpScreen.cpp:691. CPythonApplication::Process brackets the frame's render with Begin/End; only
|
||||
// inside the scene does CStateManager hand transforms to the recording device.
|
||||
bool CScreen::Begin()
|
||||
{
|
||||
assert(ms_lpd3dDevice != NULL);
|
||||
ResetFaceCount();
|
||||
|
||||
if (!STATEMANAGER.BeginScene())
|
||||
{
|
||||
Tracenf("BeginScene FAILED\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 40250 GrpScreen.cpp:705
|
||||
void CScreen::End()
|
||||
{
|
||||
STATEMANAGER.EndScene();
|
||||
}
|
||||
|
||||
auto CScreen::Show(HWND) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CScreen::Show(RECT *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CScreen::Show(RECT *, HWND) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CScreen::RenderLine2d(float sx, float sy, float ex, float ey, float) -> void
|
||||
{
|
||||
UIRenderAdd({UIRenderCommand::Line, sx, sy, ex, ey, ms_diffuseColor});
|
||||
}
|
||||
|
||||
auto CScreen::RenderBox2d(float sx, float sy, float ex, float ey, float) -> void
|
||||
{
|
||||
// 40250's four line pairs, including its one-pixel extension of the bottom edge.
|
||||
UIRenderAdd({UIRenderCommand::Line, sx, sy, ex, sy, ms_diffuseColor});
|
||||
UIRenderAdd({UIRenderCommand::Line, sx, sy, sx, ey, ms_diffuseColor});
|
||||
UIRenderAdd({UIRenderCommand::Line, ex, sy, ex, ey, ms_diffuseColor});
|
||||
UIRenderAdd({UIRenderCommand::Line, sx, ey, ex + 1.0f, ey, ms_diffuseColor});
|
||||
}
|
||||
|
||||
auto CScreen::RenderBar2d(float sx, float sy, float ex, float ey, float) -> void
|
||||
{
|
||||
UIRenderAdd({UIRenderCommand::Bar, sx, sy, ex, ey, ms_diffuseColor});
|
||||
}
|
||||
|
||||
auto CScreen::RenderGradationBar2d(float sx, float sy, float ex, float ey, DWORD start, DWORD end, float) -> void
|
||||
{
|
||||
if (sx == ex || sy == ey)
|
||||
return;
|
||||
UIRenderCommand command{UIRenderCommand::GradientBar, sx, sy, ex, ey, start};
|
||||
command.end_argb = end;
|
||||
UIRenderAdd(std::move(command));
|
||||
}
|
||||
|
||||
auto CScreen::RenderCircle2d(float, float, float, float, int) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CScreen::RenderCircle3d(float, float, float, float, int) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CScreen::RenderLine3d(float, float, float, float, float, float) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CScreen::RenderBox3d(float, float, float, float, float, float) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CScreen::RenderBar3d(float, float, float, float, float, float) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CScreen::RenderBar3d(const D3DXVECTOR3 *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CScreen::RenderGradationBar3d(float, float, float, float, float, float, DWORD, DWORD) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CScreen::RenderLineCube(float, float, float, float, float, float) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CScreen::RenderCube(float, float, float, float, float, float) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CScreen::RenderCube(float, float, float, float, float, float, D3DXMATRIX) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CScreen::RenderTextureBox(float, float, float, float, float, float, float, float, float) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CScreen::RenderBillboard(D3DXVECTOR3 *, D3DXCOLOR &) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CScreen::DrawMinorGrid(float, float, float, float, float, float, float) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CScreen::DrawGrid(float, float, float, float, float, float, float, float, float) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CScreen::RenderD3DXMesh(LPD3DXMESH, const D3DXMATRIX *, float, float, float, float, D3DFILLMODE) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CScreen::RenderSphere(const D3DXMATRIX *, float, float, float, float, D3DFILLMODE) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CScreen::RenderCylinder(const D3DXMATRIX *, float, float, float, float, float, D3DFILLMODE) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
// 40250 GrpScreen.cpp
|
||||
void CScreen::SetColorOperation()
|
||||
{
|
||||
STATEMANAGER.SetTexture(0, NULL);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
|
||||
}
|
||||
|
||||
// 40250 GrpScreen.cpp
|
||||
void CScreen::SetDiffuseOperation()
|
||||
{
|
||||
STATEMANAGER.SetTexture(0, NULL);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
|
||||
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
|
||||
}
|
||||
|
||||
// 40250 GrpScreen.cpp
|
||||
void CScreen::SetBlendOperation()
|
||||
{
|
||||
STATEMANAGER.SetTexture(0, NULL);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_CURRENT);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_CURRENT);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
|
||||
}
|
||||
|
||||
auto CScreen::SetOneColorOperation(D3DXCOLOR &) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CScreen::SetAddColorOperation(D3DXCOLOR &) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CScreen::SetDiffuseColor(DWORD color) -> void
|
||||
{
|
||||
ms_diffuseColor = color;
|
||||
}
|
||||
|
||||
auto CScreen::SetDiffuseColor(float r, float g, float b, float a) -> void
|
||||
{
|
||||
auto channel = [](float value) -> DWORD {
|
||||
return static_cast<DWORD>(std::clamp(value, 0.0f, 1.0f) * 255.0f + 0.5f);
|
||||
};
|
||||
ms_diffuseColor = (channel(a) << 24) | (channel(r) << 16) | (channel(g) << 8) | channel(b);
|
||||
}
|
||||
|
||||
auto CScreen::SetClearColor(float, float, float, float) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CScreen::SetClearDepth(float) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CScreen::SetClearStencil(DWORD) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
// 40250 GrpScreen.cpp:487, verbatim: the pick ray through the cursor.
|
||||
void CScreen::SetCursorPosition(int x, int y, int hres, int vres)
|
||||
{
|
||||
D3DXVECTOR3 v;
|
||||
v.x = -(((2.0f * x) / hres) - 1) / ms_matProj._11;
|
||||
v.y = (((2.0f * y) / vres) - 1) / ms_matProj._22;
|
||||
v.z = 1.0f;
|
||||
|
||||
D3DXMATRIX matViewInverse=ms_matInverseView;
|
||||
//D3DXMatrixInverse(&matViewInverse, NULL, &ms_matView);
|
||||
|
||||
ms_vtPickRayDir.x = v.x * matViewInverse._11 +
|
||||
v.y * matViewInverse._21 +
|
||||
v.z * matViewInverse._31;
|
||||
|
||||
ms_vtPickRayDir.y = v.x * matViewInverse._12 +
|
||||
v.y * matViewInverse._22 +
|
||||
v.z * matViewInverse._32;
|
||||
|
||||
ms_vtPickRayDir.z = v.x * matViewInverse._13 +
|
||||
v.y * matViewInverse._23 +
|
||||
v.z * matViewInverse._33;
|
||||
|
||||
ms_vtPickRayOrig.x = matViewInverse._41;
|
||||
ms_vtPickRayOrig.y = matViewInverse._42;
|
||||
ms_vtPickRayOrig.z = matViewInverse._43;
|
||||
|
||||
ms_Ray.SetStartPoint(ms_vtPickRayOrig);
|
||||
ms_Ray.SetDirection(-ms_vtPickRayDir, 51200.0f);
|
||||
}
|
||||
|
||||
auto CScreen::GetCursorPosition(float * px, float * py, float * pz) -> bool
|
||||
{
|
||||
if (!GetCursorXYPosition(px, py)) return false;
|
||||
if (!GetCursorZPosition(pz)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto CScreen::GetCursorXYPosition(float * px, float * py) -> bool
|
||||
{
|
||||
D3DXVECTOR3 v3Eye = CCameraManager::Instance().GetCurrentCamera()->GetEye();
|
||||
|
||||
TPosition posVertices[4];
|
||||
posVertices[0] = TPosition(v3Eye.x - 90000000.0f, v3Eye.y + 90000000.0f, 0.0f);
|
||||
posVertices[1] = TPosition(v3Eye.x - 90000000.0f, v3Eye.y - 90000000.0f, 0.0f);
|
||||
posVertices[2] = TPosition(v3Eye.x + 90000000.0f, v3Eye.y + 90000000.0f, 0.0f);
|
||||
posVertices[3] = TPosition(v3Eye.x + 90000000.0f, v3Eye.y - 90000000.0f, 0.0f);
|
||||
|
||||
static const WORD sc_awFillRectIndices[6] = { 0, 2, 1, 2, 3, 1, };
|
||||
|
||||
float u, v, t;
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
if (IntersectTriangle(ms_vtPickRayOrig, ms_vtPickRayDir,
|
||||
posVertices[sc_awFillRectIndices[i * 3]],
|
||||
posVertices[sc_awFillRectIndices[i * 3 + 1]],
|
||||
posVertices[sc_awFillRectIndices[i * 3 + 2]],
|
||||
&u, &v, &t))
|
||||
{
|
||||
*px = u;
|
||||
*py = v;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto CScreen::GetCursorZPosition(float * pz) -> bool
|
||||
{
|
||||
D3DXVECTOR3 v3Eye = CCameraManager::Instance().GetCurrentCamera()->GetEye();
|
||||
|
||||
TPosition posVertices[4];
|
||||
posVertices[0] = TPosition(v3Eye.x - 90000000.0f, 0.0f, v3Eye.z + 90000000.0f);
|
||||
posVertices[1] = TPosition(v3Eye.x - 90000000.0f, 0.0f, v3Eye.z - 90000000.0f);
|
||||
posVertices[2] = TPosition(v3Eye.x + 90000000.0f, 0.0f, v3Eye.z + 90000000.0f);
|
||||
posVertices[3] = TPosition(v3Eye.x + 90000000.0f, 0.0f, v3Eye.z - 90000000.0f);
|
||||
|
||||
static const WORD sc_awFillRectIndices[6] = { 0, 2, 1, 2, 3, 1, };
|
||||
|
||||
float u, v, t;
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
if (IntersectTriangle(ms_vtPickRayOrig, ms_vtPickRayDir,
|
||||
posVertices[sc_awFillRectIndices[i * 3]],
|
||||
posVertices[sc_awFillRectIndices[i * 3 + 1]],
|
||||
posVertices[sc_awFillRectIndices[i * 3 + 2]],
|
||||
&u, &v, &t))
|
||||
{
|
||||
*pz = t;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto CScreen::GetPickingPosition(float t, float * x, float * y, float * z) -> void
|
||||
{
|
||||
*x = ms_vtPickRayOrig.x + ms_vtPickRayDir.x * t;
|
||||
*y = ms_vtPickRayOrig.y + ms_vtPickRayDir.y * t;
|
||||
*z = ms_vtPickRayOrig.z + ms_vtPickRayDir.z * t;
|
||||
}
|
||||
|
||||
// 40250 GrpScreen.cpp:749-779.
|
||||
void CScreen::ProjectPosition(float x, float y, float z, float * pfX, float * pfY)
|
||||
{
|
||||
D3DXVECTOR3 Input(x, y, z);
|
||||
D3DXVECTOR3 Output;
|
||||
D3DXVec3Project(&Output, &Input, &ms_Viewport, &ms_matProj, &ms_matView, &ms_matIdentity);
|
||||
|
||||
*pfX = Output.x;
|
||||
*pfY = Output.y;
|
||||
}
|
||||
|
||||
void CScreen::ProjectPosition(float x, float y, float z, float * pfX, float * pfY, float * pfZ)
|
||||
{
|
||||
D3DXVECTOR3 Input(x, y, z);
|
||||
D3DXVECTOR3 Output;
|
||||
D3DXVec3Project(&Output, &Input, &ms_Viewport, &ms_matProj, &ms_matView, &ms_matIdentity);
|
||||
|
||||
*pfX = Output.x;
|
||||
*pfY = Output.y;
|
||||
*pfZ = Output.z;
|
||||
}
|
||||
|
||||
void CScreen::UnprojectPosition(float x, float y, float z, float * pfX, float * pfY, float * pfZ)
|
||||
{
|
||||
D3DXVECTOR3 Input(x, y, z);
|
||||
D3DXVECTOR3 Output;
|
||||
D3DXVec3Unproject(&Output, &Input, &ms_Viewport, &ms_matProj, &ms_matView, &ms_matIdentity);
|
||||
|
||||
*pfX = Output.x;
|
||||
*pfY = Output.y;
|
||||
*pfZ = Output.z;
|
||||
}
|
||||
|
||||
auto CScreen::IsLostDevice() -> BOOL
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<BOOL>();
|
||||
}
|
||||
|
||||
auto CScreen::RestoreDevice() -> BOOL
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<BOOL>();
|
||||
}
|
||||
|
||||
auto CScreen::BuildViewFrustum() -> void
|
||||
{
|
||||
CCamera* pkCamera = CCameraManager::Instance().GetCurrentCamera();
|
||||
if (!pkCamera)
|
||||
return;
|
||||
const D3DXVECTOR3& c_rv3Eye = pkCamera->GetEye();
|
||||
const D3DXVECTOR3& c_rv3View = pkCamera->GetView();
|
||||
auto vv = ms_matView * ms_matProj;
|
||||
ms_frustum.BuildViewFrustum2(
|
||||
vv,
|
||||
ms_fNearY,
|
||||
ms_fFarY,
|
||||
ms_fFieldOfView,
|
||||
ms_fAspect,
|
||||
c_rv3Eye, c_rv3View);
|
||||
}
|
||||
|
||||
auto CScreen::Identity() -> void
|
||||
{
|
||||
STATEMANAGER.SetTransform(D3DTS_WORLD, &ms_matIdentity);
|
||||
}
|
||||
|
||||
decltype(CScreen::ms_diffuseColor) CScreen::ms_diffuseColor = 0xffffffff;
|
||||
|
||||
decltype(CScreen::ms_clearColor) CScreen::ms_clearColor{};
|
||||
|
||||
decltype(CScreen::ms_clearStencil) CScreen::ms_clearStencil{};
|
||||
|
||||
decltype(CScreen::ms_clearDepth) CScreen::ms_clearDepth = 1.0f;
|
||||
|
||||
decltype(CScreen::ms_frustum) CScreen::ms_frustum{};
|
||||
@@ -0,0 +1,100 @@
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/GrpSubImage.h"
|
||||
#include "EterLib/ResourceManager.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
char CGraphicSubImage::m_SearchPath[256] = "D:/Ymir Work/UI/";
|
||||
|
||||
auto CGraphicSubImage::Type() -> TType
|
||||
{
|
||||
static TType type = StringToType("CGraphicSubImage");
|
||||
return type;
|
||||
}
|
||||
|
||||
CGraphicSubImage::CGraphicSubImage(const char* name) : CGraphicImage(name) {}
|
||||
CGraphicSubImage::~CGraphicSubImage() { m_roImage.Clear(); }
|
||||
|
||||
bool CGraphicSubImage::CreateDeviceObjects()
|
||||
{
|
||||
if (m_roImage.IsNull()) return false;
|
||||
m_imageTexture.CreateFromTexturePointer(m_roImage->GetTexturePointer());
|
||||
return true;
|
||||
}
|
||||
|
||||
void CGraphicSubImage::SetImagePointer(CGraphicImage* image)
|
||||
{
|
||||
m_roImage.SetPointer(image);
|
||||
CreateDeviceObjects();
|
||||
}
|
||||
|
||||
bool CGraphicSubImage::SetImageFileName(const char* name)
|
||||
{
|
||||
CResource* resource = CResourceManager::Instance().GetResourcePointer(name);
|
||||
if (!resource || !resource->IsType(CGraphicImage::Type())) return false;
|
||||
SetImagePointer(static_cast<CGraphicImage*>(resource));
|
||||
return true;
|
||||
}
|
||||
|
||||
void CGraphicSubImage::SetRectPosition(int left, int top, int right, int bottom)
|
||||
{
|
||||
m_rect = {left, top, right, bottom};
|
||||
}
|
||||
|
||||
void CGraphicSubImage::SetRectReference(const RECT& rect) { m_rect = rect; }
|
||||
|
||||
void CGraphicSubImage::SetSearchPath(const char* path)
|
||||
{
|
||||
if (!path) return;
|
||||
std::strncpy(m_SearchPath, path, sizeof(m_SearchPath) - 1);
|
||||
m_SearchPath[sizeof(m_SearchPath) - 1] = 0;
|
||||
}
|
||||
|
||||
bool CGraphicSubImage::OnLoad(int size, const void* bytes)
|
||||
{
|
||||
if (!bytes || size <= 0) return false;
|
||||
std::istringstream input(std::string(static_cast<const char*>(bytes), static_cast<size_t>(size)));
|
||||
std::map<std::string, std::string> tokens;
|
||||
std::string line;
|
||||
while (std::getline(input, line)) {
|
||||
std::istringstream fields(line);
|
||||
std::string key, value;
|
||||
if (!(fields >> key >> value)) continue;
|
||||
if (!value.empty() && value.front() == '"') {
|
||||
value.erase(0, 1);
|
||||
if (!value.empty() && value.back() == '"') value.pop_back();
|
||||
}
|
||||
std::transform(key.begin(), key.end(), key.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||
tokens[key] = value;
|
||||
}
|
||||
if (tokens["title"] != "subimage" || tokens["image"].empty()) return false;
|
||||
std::string image_path;
|
||||
if (tokens["version"] == "2.0") {
|
||||
const std::string parent = GetFileNameString();
|
||||
const auto slash = parent.find_last_of("\\/");
|
||||
image_path = slash == std::string::npos ? tokens["image"] : parent.substr(0, slash + 1) + tokens["image"];
|
||||
} else {
|
||||
image_path = std::string(m_SearchPath) + tokens["image"];
|
||||
}
|
||||
if (!SetImageFileName(image_path.c_str())) return false;
|
||||
SetRectPosition(std::atoi(tokens["left"].c_str()), std::atoi(tokens["top"].c_str()),
|
||||
std::atoi(tokens["right"].c_str()), std::atoi(tokens["bottom"].c_str()));
|
||||
return true;
|
||||
}
|
||||
|
||||
void CGraphicSubImage::OnClear()
|
||||
{
|
||||
m_roImage.Clear();
|
||||
m_imageTexture.Destroy();
|
||||
m_rect = {0, 0, 0, 0};
|
||||
}
|
||||
|
||||
bool CGraphicSubImage::OnIsEmpty() const { return m_roImage.IsNull() || m_roImage->IsEmpty(); }
|
||||
bool CGraphicSubImage::OnIsType(TType type) { return type == Type() || CGraphicImage::OnIsType(type); }
|
||||
@@ -0,0 +1,102 @@
|
||||
// 40250 EterLib/GrpText.cpp, verbatim.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterBase/Utils.h"
|
||||
#include "EterLib/GrpText.h"
|
||||
|
||||
CGraphicText::CGraphicText(const char* c_szFileName) : CResource(c_szFileName)
|
||||
{
|
||||
}
|
||||
|
||||
CGraphicText::~CGraphicText()
|
||||
{
|
||||
}
|
||||
|
||||
bool CGraphicText::CreateDeviceObjects()
|
||||
{
|
||||
return m_fontTexture.CreateDeviceObjects();
|
||||
}
|
||||
|
||||
void CGraphicText::DestroyDeviceObjects()
|
||||
{
|
||||
m_fontTexture.DestroyDeviceObjects();
|
||||
}
|
||||
|
||||
CGraphicFontTexture* CGraphicText::GetFontTexturePointer()
|
||||
{
|
||||
return &m_fontTexture;
|
||||
}
|
||||
|
||||
CGraphicText::TType CGraphicText::Type()
|
||||
{
|
||||
static TType s_type = StringToType("CGraphicText");
|
||||
return s_type;
|
||||
}
|
||||
|
||||
bool CGraphicText::OnLoad(int /*iSize*/, const void* /*c_pvBuf*/)
|
||||
{
|
||||
static char strName[32];
|
||||
int size;
|
||||
bool bItalic = false;
|
||||
|
||||
// format
|
||||
// 굴림.fnt "굴림" 폰트 기본 사이즈 12 로 로딩
|
||||
// 굴림:18.fnt "굴림" 폰트 사이즈 18 로 로딩
|
||||
// 굴림:14i.fnt "굴림" 폰트 사이즈 14 & 이탤릭으로 로딩
|
||||
const char * p = strrchr(GetFileName(), ':');
|
||||
|
||||
if (p)
|
||||
{
|
||||
strncpy(strName, GetFileName(), MIN(31, p - GetFileName()));
|
||||
++p;
|
||||
|
||||
static char num[8];
|
||||
|
||||
int i = 0;
|
||||
while (*p && isdigit(*p))
|
||||
{
|
||||
num[i++] = *(p++);
|
||||
}
|
||||
|
||||
num[i] = '\0';
|
||||
if(*p == 'i')
|
||||
bItalic = true;
|
||||
size = atoi(num);
|
||||
}
|
||||
else
|
||||
{
|
||||
p = strrchr(GetFileName(), '.');
|
||||
|
||||
if (!p)
|
||||
{
|
||||
assert(!"CGraphicText::OnLoadFromFile there is no extension (ie: .fnt)");
|
||||
strName[0] = '\0';
|
||||
}
|
||||
else
|
||||
strncpy(strName, GetFileName(), MIN(31, p - GetFileName()));
|
||||
|
||||
size = 12;
|
||||
}
|
||||
|
||||
if (!m_fontTexture.Create(strName, size, bItalic))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CGraphicText::OnClear()
|
||||
{
|
||||
m_fontTexture.Destroy();
|
||||
}
|
||||
|
||||
bool CGraphicText::OnIsEmpty() const
|
||||
{
|
||||
return m_fontTexture.IsEmpty();
|
||||
}
|
||||
|
||||
bool CGraphicText::OnIsType(TType type)
|
||||
{
|
||||
if (CGraphicText::Type() == type)
|
||||
return true;
|
||||
|
||||
return CResource::OnIsType(type);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,64 @@
|
||||
// Platform skeleton for EterLib/GrpTexture.h (40250 EterLib/GrpTexture.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/GrpTexture.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
#include "UIRenderCommands.h"
|
||||
|
||||
auto CGraphicTexture::IsEmpty() const -> bool
|
||||
{
|
||||
return m_bEmpty;
|
||||
}
|
||||
|
||||
auto CGraphicTexture::GetWidth() const -> int
|
||||
{
|
||||
return m_width;
|
||||
}
|
||||
|
||||
auto CGraphicTexture::GetHeight() const -> int
|
||||
{
|
||||
return m_height;
|
||||
}
|
||||
|
||||
auto CGraphicTexture::SetTextureStage(int) const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicTexture::GetD3DTexture() const -> LPDIRECT3DTEXTURE8
|
||||
{
|
||||
return m_lpd3dTexture;
|
||||
}
|
||||
|
||||
// PORT: safe_release(m_lpd3dTexture); the platform textures are memory textures and file-texture handles.
|
||||
auto CGraphicTexture::DestroyDeviceObjects() -> void
|
||||
{
|
||||
if (m_lpd3dTexture)
|
||||
UIRenderReleaseMemoryTexture(m_lpd3dTexture);
|
||||
m_lpd3dTexture = NULL;
|
||||
}
|
||||
|
||||
CGraphicTexture::CGraphicTexture()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
CGraphicTexture::~CGraphicTexture()
|
||||
{
|
||||
}
|
||||
|
||||
auto CGraphicTexture::Destroy() -> void
|
||||
{
|
||||
DestroyDeviceObjects();
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
auto CGraphicTexture::Initialize() -> void
|
||||
{
|
||||
m_lpd3dTexture = NULL;
|
||||
m_width = 0;
|
||||
m_height = 0;
|
||||
m_bEmpty = true;
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
// 40250 EterLib/GrpVertexBuffer.cpp over CPU memory (CpuBuffer.h): the device buffer is a byte vector
|
||||
// that Lock hands out directly, so Granny vertex loading and deform write real data.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/GrpVertexBuffer.h"
|
||||
#include "EterLib/StateManager.h"
|
||||
|
||||
#include "CpuBuffer.h"
|
||||
|
||||
int CGraphicVertexBuffer::GetVertexStride() const
|
||||
{
|
||||
int retSize = mt_fvf_vertex_size(m_dwFVF);
|
||||
return retSize;
|
||||
}
|
||||
|
||||
DWORD CGraphicVertexBuffer::GetFlexibleVertexFormat() const
|
||||
{
|
||||
return m_dwFVF;
|
||||
}
|
||||
|
||||
int CGraphicVertexBuffer::GetVertexCount() const
|
||||
{
|
||||
return m_vtxCount;
|
||||
}
|
||||
|
||||
void CGraphicVertexBuffer::SetStream(int stride, int layer) const
|
||||
{
|
||||
STATEMANAGER.SetStreamSource(layer, m_lpd3dVB, stride);
|
||||
}
|
||||
|
||||
bool CGraphicVertexBuffer::LockRange(unsigned count, void** pretVertices) const
|
||||
{
|
||||
if (!m_lpd3dVB)
|
||||
return false;
|
||||
|
||||
*pretVertices = static_cast<MtCpuVertexBuffer*>(m_lpd3dVB)->bytes.data();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGraphicVertexBuffer::Lock(void ** pretVertices) const
|
||||
{
|
||||
if (!m_lpd3dVB)
|
||||
return false;
|
||||
|
||||
*pretVertices = static_cast<MtCpuVertexBuffer*>(m_lpd3dVB)->bytes.data();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGraphicVertexBuffer::Unlock() const
|
||||
{
|
||||
if (!m_lpd3dVB)
|
||||
return false;
|
||||
static_cast<MtCpuVertexBuffer*>(m_lpd3dVB)->revision++;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns true when a buffer exists, as 40250 does.
|
||||
bool CGraphicVertexBuffer::IsEmpty() const
|
||||
{
|
||||
if (m_lpd3dVB)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CGraphicVertexBuffer::LockDynamic(void** pretVertices)
|
||||
{
|
||||
if (!m_lpd3dVB)
|
||||
return false;
|
||||
|
||||
*pretVertices = static_cast<MtCpuVertexBuffer*>(m_lpd3dVB)->bytes.data();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGraphicVertexBuffer::Lock(void ** pretVertices)
|
||||
{
|
||||
if (!m_lpd3dVB)
|
||||
return false;
|
||||
|
||||
*pretVertices = static_cast<MtCpuVertexBuffer*>(m_lpd3dVB)->bytes.data();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGraphicVertexBuffer::Unlock()
|
||||
{
|
||||
if (!m_lpd3dVB)
|
||||
return false;
|
||||
static_cast<MtCpuVertexBuffer*>(m_lpd3dVB)->revision++;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGraphicVertexBuffer::Copy(int bufSize, const void* srcVertices)
|
||||
{
|
||||
void * dstVertices;
|
||||
|
||||
if (!Lock(&dstVertices))
|
||||
return false;
|
||||
|
||||
memcpy(dstVertices, srcVertices, bufSize);
|
||||
|
||||
Unlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CGraphicVertexBuffer::CreateDeviceObjects()
|
||||
{
|
||||
assert(m_lpd3dVB == NULL);
|
||||
|
||||
MtCpuVertexBuffer* buffer = new MtCpuVertexBuffer;
|
||||
buffer->bytes.assign(m_dwBufferSize, 0);
|
||||
buffer->fvf = m_dwFVF;
|
||||
m_lpd3dVB = buffer;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CGraphicVertexBuffer::DestroyDeviceObjects()
|
||||
{
|
||||
delete static_cast<MtCpuVertexBuffer*>(m_lpd3dVB);
|
||||
m_lpd3dVB = NULL;
|
||||
}
|
||||
|
||||
bool CGraphicVertexBuffer::Create(int vtxCount, DWORD fvf, DWORD usage, D3DPOOL d3dPool)
|
||||
{
|
||||
assert(vtxCount > 0);
|
||||
|
||||
Destroy();
|
||||
|
||||
m_vtxCount = vtxCount;
|
||||
m_dwBufferSize = mt_fvf_vertex_size(fvf) * m_vtxCount;
|
||||
m_d3dPool = d3dPool;
|
||||
m_dwUsage = usage;
|
||||
m_dwFVF = fvf;
|
||||
|
||||
if (usage == D3DUSAGE_WRITEONLY || usage == D3DUSAGE_DYNAMIC)
|
||||
m_dwLockFlag = 0;
|
||||
else
|
||||
m_dwLockFlag = D3DLOCK_READONLY;
|
||||
|
||||
return CreateDeviceObjects();
|
||||
}
|
||||
|
||||
void CGraphicVertexBuffer::Destroy()
|
||||
{
|
||||
DestroyDeviceObjects();
|
||||
}
|
||||
|
||||
void CGraphicVertexBuffer::Initialize()
|
||||
{
|
||||
m_lpd3dVB = NULL;
|
||||
m_vtxCount = 0;
|
||||
m_dwBufferSize = 0;
|
||||
m_dwFVF = 0;
|
||||
}
|
||||
|
||||
CGraphicVertexBuffer::CGraphicVertexBuffer()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
CGraphicVertexBuffer::~CGraphicVertexBuffer()
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Platform skeleton for EterLib/GrpVertexBufferDynamic.h (40250 EterLib/GrpVertexBufferDynamic.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/GrpVertexBufferDynamic.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
CDynamicVertexBuffer::CDynamicVertexBuffer()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CDynamicVertexBuffer::~CDynamicVertexBuffer()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CDynamicVertexBuffer::Create(int, int) -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
// The cursor in client coordinates, as the host (Godot) last pushed it. 40250 CMSWindow::GetMousePosition
|
||||
// asks Win32 (GetCursorPos + ScreenToClient); here the host is the only source of mouse input.
|
||||
void MtHostSetCursor(int x, int y);
|
||||
@@ -0,0 +1,752 @@
|
||||
// Platform implementation of EterLib/IME.h (40250 EterLib/IME.cpp).
|
||||
// The edit buffer (m_wText, cursor, SetText/GetText, OnChar/InsertString, IsMax, the colour-tag aware cursor
|
||||
// moves) and WMChar are the 40250 bodies verbatim. What 40250 gets from Win32 IMM/TSF — composition strings,
|
||||
// the candidate list, the reading window, keyboard-layout code pages — has no counterpart under Godot: the
|
||||
// host delivers committed characters only (Metin2PythonHost.ui_char -> WMChar, or InsertString for text the
|
||||
// input code page cannot hold, which is what 40250's ResultProcess does with a GCS_RESULTSTR). Those entry
|
||||
// points keep 40250's "no IME present" behaviour: no composition, empty candidate/reading lists.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/IME.h"
|
||||
#include "EterLib/TextTag.h"
|
||||
#include "EterLib/GrpTextInstance.h"
|
||||
#include "EterLib/Util.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cwctype>
|
||||
|
||||
namespace
|
||||
{
|
||||
// 40250 s_aszIndicator[INDICATOR_NON_IME] (IME.cpp:67); CheckInputLocale overwrites it with the locale's
|
||||
// two-letter abbreviation, which here is the locale code page's language.
|
||||
wchar_t s_aszNonImeIndicator[3] = L"En";
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/ /* Public */
|
||||
CIME::CIME()
|
||||
{
|
||||
ms_hWnd = NULL;
|
||||
|
||||
ms_bCandidateList = false;
|
||||
ms_bReadingInformation = false;
|
||||
|
||||
Clear();
|
||||
|
||||
m_max = 0;
|
||||
m_userMax = 0;
|
||||
|
||||
m_bOnlyNumberMode = FALSE;
|
||||
m_hOrgIMC = NULL;
|
||||
|
||||
m_bEnablePaste = false;
|
||||
m_bUseDefaultIME = false;
|
||||
}
|
||||
|
||||
CIME::~CIME()
|
||||
{
|
||||
}
|
||||
|
||||
// PORT: 40250 loads imm32.dll, disables Cicero, reads the keyboard layout and sets up the TSF sinks. The
|
||||
// Godot host has no IMM context; only the part the edit buffer depends on — the input/output code page from
|
||||
// CheckInputLocale and SetSupportLevel(2) — is kept.
|
||||
bool CIME::Initialize(HWND hWnd)
|
||||
{
|
||||
if(ms_bInitialized)
|
||||
return true;
|
||||
ms_hWnd = hWnd;
|
||||
|
||||
ms_bDisableIMECompletely = true;
|
||||
ms_bInitialized = true;
|
||||
|
||||
CheckInputLocale();
|
||||
ChangeInputLanguageWorker();
|
||||
SetSupportLevel(2);
|
||||
|
||||
ms_bUILessMode = false;
|
||||
CheckToggleState();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CIME::Uninitialize()
|
||||
{
|
||||
if ( !ms_bInitialized )
|
||||
return;
|
||||
ms_hWnd = NULL;
|
||||
m_hOrgIMC = NULL;
|
||||
ms_bInitialized = false;
|
||||
}
|
||||
|
||||
void CIME::UseDefaultIME()
|
||||
{
|
||||
m_bUseDefaultIME = true;
|
||||
}
|
||||
|
||||
bool CIME::IsIMEEnabled()
|
||||
{
|
||||
return ms_bImeEnabled;
|
||||
}
|
||||
|
||||
// PORT: 40250 ImmAssociateContext()s the window; ms_bDisableIMECompletely (no imm32) forces bEnable=false,
|
||||
// which is always the case here.
|
||||
void CIME::EnableIME(bool bEnable)
|
||||
{
|
||||
if (!ms_bInitialized || !ms_hWnd)
|
||||
return;
|
||||
if (ms_bDisableIMECompletely)
|
||||
bEnable = false;
|
||||
ms_bImeEnabled = bEnable;
|
||||
if (bEnable)
|
||||
CheckToggleState();
|
||||
}
|
||||
|
||||
void CIME::DisableIME()
|
||||
{
|
||||
EnableIME(false);
|
||||
}
|
||||
|
||||
void CIME::EnableCaptureInput()
|
||||
{
|
||||
ms_bCaptureInput = true;
|
||||
}
|
||||
|
||||
void CIME::DisableCaptureInput()
|
||||
{
|
||||
ms_bCaptureInput = false;
|
||||
}
|
||||
|
||||
bool CIME::IsCaptureEnabled()
|
||||
{
|
||||
return ms_bCaptureInput;
|
||||
}
|
||||
|
||||
void CIME::Clear()
|
||||
{
|
||||
ms_lastpos = 0;
|
||||
ms_curpos = 0;
|
||||
|
||||
ms_compLen = 0;
|
||||
ms_ulbegin = 0;
|
||||
ms_ulend = 0;
|
||||
}
|
||||
|
||||
int CIME::GetReading(std::string & rstrText)
|
||||
{
|
||||
char reading[IMEREADING_MAXLEN];
|
||||
|
||||
if(ms_wstrReading.size() == 0)
|
||||
return 0;
|
||||
int readingLen = WideCharToMultiByte(ms_uOutputCodePage, 0, &ms_wstrReading[0], ms_wstrReading.size(), reading, sizeof(reading), NULL, NULL);
|
||||
|
||||
rstrText.append(GetCodePageText());
|
||||
rstrText.append(reading, reading + readingLen);
|
||||
|
||||
return rstrText.size();
|
||||
}
|
||||
|
||||
int CIME::GetReadingError()
|
||||
{
|
||||
return ms_iReadingError;
|
||||
}
|
||||
|
||||
void CIME::SetMax(int iMax)
|
||||
{
|
||||
m_max = iMax;
|
||||
}
|
||||
|
||||
void CIME::SetUserMax(int iMax)
|
||||
{
|
||||
m_userMax = iMax;
|
||||
}
|
||||
|
||||
void CIME::SetText(const char* szText, int len)
|
||||
{
|
||||
ms_compLen = 0;
|
||||
ms_ulbegin = 0;
|
||||
ms_ulend = 0;
|
||||
|
||||
const char* begin = szText;
|
||||
const char* end = begin + len;
|
||||
const char* iter = FindToken(begin, end);
|
||||
|
||||
int m_wTextLen = sizeof(m_wText)/sizeof(wchar_t);
|
||||
|
||||
ms_lastpos = MultiByteToWideChar(ms_uInputCodePage, 0, begin, iter-begin, m_wText, m_wTextLen);
|
||||
|
||||
if (iter < end)
|
||||
ms_lastpos += MultiByteToWideChar(ReadToken(iter), 0, (iter+5), end-(iter+5), m_wText+ms_lastpos, m_wTextLen-ms_lastpos);
|
||||
|
||||
ms_curpos = std::min(ms_curpos, ms_lastpos);
|
||||
}
|
||||
|
||||
int CIME::GetText(std::string & rstrText, bool addCodePage)
|
||||
{
|
||||
int outCodePage = ms_uOutputCodePage;
|
||||
int dataCodePage;
|
||||
switch (outCodePage)
|
||||
{
|
||||
//case 1256: // ARABIC
|
||||
case 1268: // VIETNAM
|
||||
dataCodePage = CP_UTF8;
|
||||
break;
|
||||
default:
|
||||
dataCodePage = outCodePage;
|
||||
}
|
||||
|
||||
int len = 0;
|
||||
char text[IMESTR_MAXLEN];
|
||||
|
||||
len += WideCharToMultiByte(dataCodePage, 0, m_wText, ms_curpos, text, sizeof(text)-len, NULL, NULL);
|
||||
len += WideCharToMultiByte(dataCodePage, 0, m_wszComposition, ms_compLen, text+len, sizeof(text)-len, NULL, NULL);
|
||||
len += WideCharToMultiByte(dataCodePage, 0, m_wText+ms_curpos, ms_lastpos-ms_curpos, text+len, sizeof(text)-len, NULL, NULL);
|
||||
|
||||
int i;
|
||||
for(i=0; i<len; ++i)
|
||||
if((BYTE)text[i] > 0x7F) break;
|
||||
|
||||
if(i == len)
|
||||
{
|
||||
rstrText.append(text, text+len);
|
||||
}
|
||||
else
|
||||
{
|
||||
rstrText.append(text, text+i);
|
||||
|
||||
//if (addCodePage)
|
||||
// rstrText.append(GetCodePageText());
|
||||
|
||||
rstrText.append(text+i, text+len);
|
||||
}
|
||||
|
||||
return rstrText.size();
|
||||
}
|
||||
|
||||
const char* CIME::GetCodePageText()
|
||||
{
|
||||
static char szCodePage[16];
|
||||
|
||||
const int defCodePage = GetDefaultCodePage();
|
||||
const int outCodePage = ms_uOutputCodePage;
|
||||
|
||||
if (outCodePage != defCodePage)
|
||||
{
|
||||
sprintf(szCodePage, "@%04d", outCodePage);
|
||||
}
|
||||
else
|
||||
{
|
||||
szCodePage[0] = 0;
|
||||
}
|
||||
|
||||
return szCodePage;
|
||||
}
|
||||
|
||||
int CIME::GetCodePage()
|
||||
{
|
||||
return ms_uOutputCodePage;
|
||||
}
|
||||
|
||||
int CIME::GetCandidatePageCount()
|
||||
{
|
||||
return ms_dwCandidatePageSize;
|
||||
}
|
||||
|
||||
int CIME::GetCandidateCount()
|
||||
{
|
||||
return ms_dwCandidateCount;
|
||||
}
|
||||
|
||||
int CIME::GetCandidate(DWORD index, std::string & rstrText)
|
||||
{
|
||||
if(index >= MAX_CANDLIST)
|
||||
return 0;
|
||||
|
||||
LPCWSTR wszText = ms_wszCandidate[index];
|
||||
if(wszText == NULL)
|
||||
return 0;
|
||||
|
||||
int wTextLen = wcslen(wszText);
|
||||
if(wTextLen == 0)
|
||||
return 0;
|
||||
|
||||
char text[IMESTR_MAXLEN];
|
||||
int len = ::WideCharToMultiByte(CP_UTF8, 0, wszText, wTextLen, text, sizeof(text), 0, 0);
|
||||
|
||||
rstrText.append("@9999");
|
||||
rstrText.append(text, text+len);
|
||||
|
||||
return wTextLen;
|
||||
}
|
||||
|
||||
int CIME::GetCandidateSelection()
|
||||
{
|
||||
return ms_dwCandidateSelection;
|
||||
}
|
||||
|
||||
// PORT: 40250 ImmSetConversionStatus / ImmGetConversionStatus. Without an IMM context the conversion mode is
|
||||
// the alphanumeric one (IME_CMODE_ALPHANUMERIC == 0) and cannot be changed.
|
||||
void CIME::SetInputMode(DWORD)
|
||||
{
|
||||
}
|
||||
|
||||
DWORD CIME::GetInputMode()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CIME::SetNumberMode()
|
||||
{
|
||||
m_bOnlyNumberMode = TRUE;
|
||||
}
|
||||
|
||||
void CIME::SetStringMode()
|
||||
{
|
||||
m_bOnlyNumberMode = FALSE;
|
||||
}
|
||||
|
||||
void CIME::AddExceptKey(wchar_t key)
|
||||
{
|
||||
m_exceptKey.push_back(key);
|
||||
}
|
||||
|
||||
void CIME::ClearExceptKey()
|
||||
{
|
||||
m_exceptKey.clear();
|
||||
}
|
||||
|
||||
bool CIME::__IsWritable(wchar_t key)
|
||||
{
|
||||
if ( m_exceptKey.end() == std::find(m_exceptKey.begin(),m_exceptKey.end(),key) )
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void CIME::EnablePaste(bool bFlag)
|
||||
{
|
||||
m_bEnablePaste = bFlag;
|
||||
}
|
||||
|
||||
// PORT: 40250 reads CF_TEXT from the Win32 clipboard. The host clipboard is not wired to the extension yet,
|
||||
// so this pastes nothing (the same as an empty clipboard).
|
||||
void CIME::PasteTextFromClipBoard()
|
||||
{
|
||||
if (!m_bEnablePaste)
|
||||
return;
|
||||
}
|
||||
|
||||
// PORT: 40250 cancels the IMM composition string and closes the candidate list. There is no composition
|
||||
// here; the candidate-list close (and its event) is kept.
|
||||
void CIME::FinalizeString(bool)
|
||||
{
|
||||
static bool s_bProcessing = false; // to avoid infinite recursion
|
||||
if ( !ms_bInitialized || s_bProcessing )
|
||||
return;
|
||||
s_bProcessing = true;
|
||||
CloseCandidateList();
|
||||
s_bProcessing = false;
|
||||
}
|
||||
|
||||
int CIME::GetCompLen()
|
||||
{
|
||||
return ms_compLen;
|
||||
}
|
||||
|
||||
int CIME::GetULBegin()
|
||||
{
|
||||
return ms_ulbegin;
|
||||
}
|
||||
|
||||
int CIME::GetULEnd()
|
||||
{
|
||||
return ms_ulend;
|
||||
}
|
||||
|
||||
void CIME::CloseCandidateList()
|
||||
{
|
||||
ms_bCandidateList = false;
|
||||
ms_dwCandidateCount = 0;
|
||||
memset(&ms_wszCandidate, 0, sizeof(ms_wszCandidate));
|
||||
if(ms_pEvent)
|
||||
ms_pEvent->OnCloseCandidateList();
|
||||
}
|
||||
|
||||
void CIME::CloseReadingInformation()
|
||||
{
|
||||
CIME::ms_bReadingInformation = false;
|
||||
if(CIME::ms_pEvent)
|
||||
CIME::ms_pEvent->OnCloseReadingWnd();
|
||||
}
|
||||
|
||||
// PORT: 40250 re-reads the keyboard layout (GETLANG) and re-applies the IME support level when the language
|
||||
// changed. The host has one fixed input locale, so only the toggle check and the code-page event remain.
|
||||
void CIME::ChangeInputLanguage()
|
||||
{
|
||||
CheckToggleState();
|
||||
ChangeInputLanguageWorker();
|
||||
|
||||
if(ms_pEvent)
|
||||
ms_pEvent->OnChangeCodePage();
|
||||
}
|
||||
|
||||
void CIME::ChangeInputLanguageWorker()
|
||||
{
|
||||
if ( !ms_bUILessMode )
|
||||
ms_iCandListIndexBase = 1;
|
||||
SetupImeApi();
|
||||
}
|
||||
|
||||
// PORT: 40250 forces level 3 for the Korean keyboard layout; there is no keyboard layout here.
|
||||
void CIME::SetSupportLevel( DWORD dwImeLevel )
|
||||
{
|
||||
if ( dwImeLevel < 2 || 3 < dwImeLevel )
|
||||
return;
|
||||
ms_dwIMELevel = dwImeLevel;
|
||||
// cancel current composition string.
|
||||
FinalizeString();
|
||||
}
|
||||
|
||||
LRESULT CIME::WMInputLanguage(HWND, UINT, WPARAM, LPARAM)
|
||||
{
|
||||
ChangeInputLanguage();
|
||||
return 0;
|
||||
}
|
||||
|
||||
LRESULT CIME::WMStartComposition(HWND, UINT, WPARAM, LPARAM)
|
||||
{
|
||||
return 1L;
|
||||
}
|
||||
|
||||
// PORT: 40250 pulls GCS_RESULTSTR/GCS_COMPSTR out of the IMM context. The host never sends compositions.
|
||||
LRESULT CIME::WMComposition(HWND, UINT, WPARAM, LPARAM)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
LRESULT CIME::WMEndComposition(HWND, UINT, WPARAM, LPARAM)
|
||||
{
|
||||
ms_compLen = 0;
|
||||
ms_ulbegin = 0;
|
||||
ms_ulend = 0;
|
||||
|
||||
if(ms_pEvent)
|
||||
ms_pEvent->OnUpdate();
|
||||
|
||||
return 0L;
|
||||
}
|
||||
|
||||
// PORT: 40250 handles IMN_* candidate/reading/toggle notifications from IMM; none are sent here.
|
||||
LRESULT CIME::WMNotify(HWND, UINT, WPARAM, LPARAM)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
LRESULT CIME::WMChar(HWND /*hWnd*/, UINT /*uiMsg*/, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
unsigned char c = (unsigned char)(wParam & 0xff);
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case 8:
|
||||
if(ms_bCaptureInput == false)
|
||||
return 0;
|
||||
if (ms_curpos > 0)
|
||||
{
|
||||
DecCurPos();
|
||||
DelCurPos();
|
||||
}
|
||||
if(ms_pEvent)
|
||||
ms_pEvent->OnUpdate();
|
||||
return 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
if(ms_pEvent) {
|
||||
if (ms_pEvent->OnWM_CHAR(wParam, lParam))
|
||||
break;
|
||||
}
|
||||
if(ms_bCaptureInput == false)
|
||||
return 0;
|
||||
wchar_t w[10];
|
||||
MultiByteToWideChar(ms_uInputCodePage, 0, (char*)&c, 1, w, 1);
|
||||
|
||||
OnChar(w[0]);
|
||||
if (w[0] == L'|')
|
||||
OnChar(w[0]);
|
||||
if(ms_pEvent)
|
||||
ms_pEvent->OnUpdate();
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/ /* Protected */
|
||||
void CIME::IncCurPos()
|
||||
{
|
||||
if (ms_curpos < ms_lastpos)
|
||||
{
|
||||
int pos = FindColorTagEndPosition(m_wText + ms_curpos, ms_lastpos - ms_curpos);
|
||||
|
||||
if (pos > 0)
|
||||
ms_curpos = std::min(ms_lastpos, std::max(0, ms_curpos + (pos + 1)));
|
||||
else
|
||||
++ms_curpos;
|
||||
//++ms_curpos;
|
||||
}
|
||||
}
|
||||
|
||||
void CIME::DecCurPos()
|
||||
{
|
||||
if (ms_curpos > 0)
|
||||
{
|
||||
int pos = FindColorTagStartPosition(m_wText + ms_curpos - 1, ms_curpos);
|
||||
|
||||
if (pos > 0)
|
||||
ms_curpos = std::min(ms_lastpos, std::max(0, ms_curpos - (pos + 1)));
|
||||
else
|
||||
--ms_curpos;
|
||||
//--ms_curpos;
|
||||
}
|
||||
}
|
||||
|
||||
int CIME::GetCurPos()
|
||||
{
|
||||
int pos = GetTextTagOutputLen(m_wText, ms_curpos);
|
||||
return pos;
|
||||
//return ms_curpos;
|
||||
}
|
||||
|
||||
void CIME::SetCurPos(int offset)
|
||||
{
|
||||
if (offset < 0 || offset > ms_lastpos)
|
||||
{
|
||||
ms_curpos = ms_lastpos;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// offset is a position in the rendered text, so it has to be mapped back to the buffer.
|
||||
//ms_curpos = min(ms_lastpos, offset);
|
||||
ms_curpos = std::min(ms_lastpos, GetTextTagInternalPosFromRenderPos(m_wText, ms_lastpos, offset));
|
||||
}
|
||||
}
|
||||
|
||||
void CIME::DelCurPos()
|
||||
{
|
||||
if (ms_curpos < ms_lastpos)
|
||||
{
|
||||
int eraseCount = FindColorTagEndPosition(m_wText + ms_curpos, ms_lastpos - ms_curpos) + 1;
|
||||
// PORT: 40250 wcscpy()s the overlapping tail down; that is undefined for overlapping buffers, and the
|
||||
// buffer is not guaranteed NUL-terminated at ms_lastpos, so move exactly the live tail.
|
||||
memmove(m_wText + ms_curpos, m_wText + ms_curpos + eraseCount, sizeof(wchar_t) * (ms_lastpos - ms_curpos - eraseCount));
|
||||
ms_lastpos -= eraseCount;
|
||||
ms_curpos = std::min(ms_lastpos, ms_curpos);
|
||||
}
|
||||
}
|
||||
|
||||
void CIME::PasteString(const char * str)
|
||||
{
|
||||
const char * begin = str;
|
||||
const char * end = str + strlen(str);
|
||||
wchar_t m_wText[IMESTR_MAXLEN];
|
||||
int wstrLen = MultiByteToWideChar(ms_uInputCodePage, 0, begin, end - begin, m_wText, IMESTR_MAXLEN);
|
||||
InsertString(m_wText, wstrLen);
|
||||
if(ms_pEvent)
|
||||
ms_pEvent->OnUpdate();
|
||||
}
|
||||
|
||||
// PORT: 40250 reads the keyboard layout's default ANSI code page (GetLocaleInfoA LOCALE_IDEFAULTANSICODEPAGE).
|
||||
// The host's input locale is the client locale, so its code page (SetDefaultCodePage from locale.cfg) is used.
|
||||
void CIME::CheckInputLocale()
|
||||
{
|
||||
static UINT s_uPrevCodePage = 0xFFFF;
|
||||
|
||||
ms_uInputCodePage = GetDefaultCodePage();
|
||||
if ( s_uPrevCodePage == ms_uInputCodePage )
|
||||
return;
|
||||
s_uPrevCodePage = ms_uInputCodePage;
|
||||
|
||||
ms_bVerticalCandidate = false;
|
||||
ms_wszCurrentIndicator = s_aszNonImeIndicator;
|
||||
|
||||
if(ms_uOutputCodePage != 1256) {
|
||||
ms_uOutputCodePage = ms_uInputCodePage;
|
||||
Clear();
|
||||
}
|
||||
}
|
||||
|
||||
// PORT: 40250 asks IMM whether an IME is open; there is none, so the state is IMEUI_STATE_OFF (0).
|
||||
void CIME::CheckToggleState()
|
||||
{
|
||||
CheckInputLocale();
|
||||
ms_bChineseIME = false;
|
||||
ms_dwImeState = 0;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/ /* Private */
|
||||
void CIME::InsertString(wchar_t* wString, int iSize)
|
||||
{
|
||||
if (IsMax(wString, iSize))
|
||||
return;
|
||||
|
||||
if (ms_curpos < ms_lastpos)
|
||||
memmove(m_wText+ms_curpos+iSize, m_wText+ms_curpos, sizeof(wchar_t)*(ms_lastpos-ms_curpos));
|
||||
|
||||
memcpy(m_wText+ms_curpos, wString, sizeof(wchar_t)*iSize);
|
||||
|
||||
ms_curpos += iSize;
|
||||
ms_lastpos += iSize;
|
||||
}
|
||||
|
||||
void CIME::OnChar(wchar_t c)
|
||||
{
|
||||
if (m_bOnlyNumberMode)
|
||||
if (!iswdigit(c))
|
||||
return;
|
||||
|
||||
if (!__IsWritable(c))
|
||||
return;
|
||||
|
||||
InsertString(&c, 1);
|
||||
}
|
||||
|
||||
// PORT: 40250 maps a keyboard-layout LANGID to its ANSI code page. Only CheckInputLocale's code page is used
|
||||
// here (see there), so this answers with it.
|
||||
UINT CIME::GetCodePageFromLang(LANGID)
|
||||
{
|
||||
return GetDefaultCodePage();
|
||||
}
|
||||
|
||||
// PORT: the IMM composition/candidate/reading processors (40250 IME.cpp:951-1227). No IMM context exists.
|
||||
void CIME::ResultProcess(HIMC) {}
|
||||
void CIME::CompositionProcessBuilding(HIMC) {}
|
||||
void CIME::CompositionProcess(HIMC) {}
|
||||
void CIME::AttributeProcess(HIMC) {}
|
||||
void CIME::CandidateProcess(HIMC) {}
|
||||
void CIME::ReadingProcess(HIMC) {}
|
||||
|
||||
bool CIME::IsMax(const wchar_t* wInput, int len)
|
||||
{
|
||||
if (ms_lastpos + len > IMESTR_MAXLEN)
|
||||
return true;
|
||||
|
||||
int textLen = WideCharToMultiByte(ms_uOutputCodePage, 0, m_wText, ms_lastpos, 0, 0, NULL, NULL);
|
||||
int inputLen = WideCharToMultiByte(ms_uOutputCodePage, 0, wInput, len, 0, 0, NULL, NULL);
|
||||
//return textLen + inputLen > m_max;
|
||||
|
||||
if (textLen + inputLen > m_max)
|
||||
return true;
|
||||
else if (m_userMax != 0 && m_max != m_userMax)
|
||||
{
|
||||
std::wstring str = GetTextTagOutputString(m_wText, ms_lastpos);
|
||||
std::wstring input = GetTextTagOutputString(wInput, len);
|
||||
int textLen = WideCharToMultiByte(ms_uOutputCodePage, 0, str.c_str(), str.length(), 0, 0, NULL, NULL);
|
||||
int inputLen = WideCharToMultiByte(ms_uOutputCodePage, 0, input.c_str(), input.length(), 0, 0, NULL, NULL);
|
||||
return textLen + inputLen > m_userMax;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// PORT: 40250 identifies the CHT/CHS IME DLL version and the reading-window orientation. No IME DLL here.
|
||||
DWORD CIME::GetImeId(UINT)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool CIME::GetReadingWindowOrientation()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void CIME::SetupImeApi()
|
||||
{
|
||||
_GetReadingString = NULL;
|
||||
_ShowReadingWindow = NULL;
|
||||
}
|
||||
|
||||
decltype(CIME::_ImmLockIMC) CIME::_ImmLockIMC{};
|
||||
|
||||
decltype(CIME::_ImmUnlockIMC) CIME::_ImmUnlockIMC{};
|
||||
|
||||
decltype(CIME::_ImmLockIMCC) CIME::_ImmLockIMCC{};
|
||||
|
||||
decltype(CIME::_ImmUnlockIMCC) CIME::_ImmUnlockIMCC{};
|
||||
|
||||
decltype(CIME::_GetReadingString) CIME::_GetReadingString{};
|
||||
|
||||
decltype(CIME::_ShowReadingWindow) CIME::_ShowReadingWindow{};
|
||||
|
||||
decltype(CIME::ms_bInitialized) CIME::ms_bInitialized{};
|
||||
|
||||
decltype(CIME::ms_bDisableIMECompletely) CIME::ms_bDisableIMECompletely{};
|
||||
|
||||
decltype(CIME::ms_bUILessMode) CIME::ms_bUILessMode{};
|
||||
|
||||
decltype(CIME::ms_bImeEnabled) CIME::ms_bImeEnabled{};
|
||||
|
||||
decltype(CIME::ms_bCaptureInput) CIME::ms_bCaptureInput{};
|
||||
|
||||
decltype(CIME::ms_bChineseIME) CIME::ms_bChineseIME{};
|
||||
|
||||
decltype(CIME::ms_bUseIMMCandidate) CIME::ms_bUseIMMCandidate{};
|
||||
|
||||
decltype(CIME::ms_hWnd) CIME::ms_hWnd{};
|
||||
|
||||
decltype(CIME::ms_hklCurrent) CIME::ms_hklCurrent{};
|
||||
|
||||
decltype(CIME::ms_szKeyboardLayout) CIME::ms_szKeyboardLayout{};
|
||||
|
||||
decltype(CIME::ms_stOSVI) CIME::ms_stOSVI{};
|
||||
|
||||
decltype(CIME::ms_hImm32Dll) CIME::ms_hImm32Dll{};
|
||||
|
||||
decltype(CIME::ms_hCurrentImeDll) CIME::ms_hCurrentImeDll{};
|
||||
|
||||
decltype(CIME::ms_dwImeState) CIME::ms_dwImeState{};
|
||||
|
||||
decltype(CIME::ms_adwId) CIME::ms_adwId{};
|
||||
|
||||
decltype(CIME::ms_dwIMELevel) CIME::ms_dwIMELevel{};
|
||||
|
||||
decltype(CIME::ms_dwIMELevelSaved) CIME::ms_dwIMELevelSaved{};
|
||||
|
||||
decltype(CIME::ms_bCandidateList) CIME::ms_bCandidateList{};
|
||||
|
||||
decltype(CIME::ms_dwCandidateCount) CIME::ms_dwCandidateCount{};
|
||||
|
||||
decltype(CIME::ms_bVerticalCandidate) CIME::ms_bVerticalCandidate{};
|
||||
|
||||
decltype(CIME::ms_iCandListIndexBase) CIME::ms_iCandListIndexBase{};
|
||||
|
||||
decltype(CIME::ms_wszCandidate) CIME::ms_wszCandidate{};
|
||||
|
||||
decltype(CIME::ms_dwCandidateSelection) CIME::ms_dwCandidateSelection{};
|
||||
|
||||
decltype(CIME::ms_dwCandidatePageSize) CIME::ms_dwCandidatePageSize{};
|
||||
|
||||
decltype(CIME::ms_bReadingInformation) CIME::ms_bReadingInformation{};
|
||||
|
||||
decltype(CIME::ms_iReadingError) CIME::ms_iReadingError{};
|
||||
|
||||
decltype(CIME::ms_bHorizontalReading) CIME::ms_bHorizontalReading{};
|
||||
|
||||
decltype(CIME::ms_wstrReading) CIME::ms_wstrReading{};
|
||||
|
||||
decltype(CIME::ms_wszCurrentIndicator) CIME::ms_wszCurrentIndicator{};
|
||||
|
||||
decltype(CIME::ms_pEvent) CIME::ms_pEvent{};
|
||||
|
||||
decltype(CIME::m_wText) CIME::m_wText{};
|
||||
|
||||
decltype(CIME::ms_compLen) CIME::ms_compLen{};
|
||||
|
||||
decltype(CIME::ms_curpos) CIME::ms_curpos{};
|
||||
|
||||
decltype(CIME::ms_lastpos) CIME::ms_lastpos{};
|
||||
|
||||
decltype(CIME::ms_ulbegin) CIME::ms_ulbegin{};
|
||||
|
||||
decltype(CIME::ms_ulend) CIME::ms_ulend{};
|
||||
|
||||
decltype(CIME::ms_uOutputCodePage) CIME::ms_uOutputCodePage{};
|
||||
|
||||
decltype(CIME::ms_uInputCodePage) CIME::ms_uInputCodePage{};
|
||||
@@ -0,0 +1,81 @@
|
||||
// Platform skeleton for EterLib/Input.h (40250 EterLib/Input.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/Input.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
CInputDevice::CInputDevice()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CInputDevice::~CInputDevice()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CInputDevice::CreateDevice(HWND) -> HRESULT
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<HRESULT>();
|
||||
}
|
||||
|
||||
decltype(CInputDevice::ms_lpDI) CInputDevice::ms_lpDI{};
|
||||
|
||||
CInputKeyboard::CInputKeyboard()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CInputKeyboard::~CInputKeyboard()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CInputKeyboard::InitializeKeyboard(HWND) -> bool
|
||||
{
|
||||
ResetKeyboard();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto CInputKeyboard::UpdateKeyboard() -> void
|
||||
{
|
||||
}
|
||||
|
||||
auto CInputKeyboard::ResetKeyboard() -> void
|
||||
{
|
||||
memset(ms_bPressedKey, 0, sizeof(ms_bPressedKey));
|
||||
memset(ms_diks, 0, sizeof(ms_diks));
|
||||
}
|
||||
|
||||
auto CInputKeyboard::IsPressed(int iIndex) -> bool
|
||||
{
|
||||
if (iIndex >= 0 && iIndex < 256)
|
||||
return (ms_diks[iIndex] & 0x80) != 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto CInputKeyboard::KeyDown(int iIndex) -> void
|
||||
{
|
||||
if (iIndex >= 0 && iIndex < 256)
|
||||
{
|
||||
ms_bPressedKey[iIndex] = true;
|
||||
ms_diks[iIndex] = (char)0x80;
|
||||
}
|
||||
}
|
||||
|
||||
auto CInputKeyboard::KeyUp(int iIndex) -> void
|
||||
{
|
||||
if (iIndex >= 0 && iIndex < 256)
|
||||
{
|
||||
ms_bPressedKey[iIndex] = false;
|
||||
ms_diks[iIndex] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
decltype(CInputKeyboard::ms_lpKeyboard) CInputKeyboard::ms_lpKeyboard{};
|
||||
|
||||
decltype(CInputKeyboard::ms_bPressedKey) CInputKeyboard::ms_bPressedKey{};
|
||||
|
||||
decltype(CInputKeyboard::ms_diks) CInputKeyboard::ms_diks{};
|
||||
@@ -0,0 +1,49 @@
|
||||
// Platform skeleton for EterLib/MSApplication.h (40250 EterLib/MSApplication.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/MSApplication.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
CMSApplication::CMSApplication()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CMSApplication::~CMSApplication()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CMSApplication::Initialize(HINSTANCE) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CMSApplication::MessageLoop() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CMSApplication::IsMessage() -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CMSApplication::MessageProcess() -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CMSApplication::ClearWindowClass() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CMSApplication::WindowProcedure(HWND, UINT, WPARAM, LPARAM) -> LRESULT
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<LRESULT>();
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
// Platform skeleton for EterLib/MSWindow.h (40250 EterLib/MSWindow.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/MSWindow.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
#include "HostCursor.h"
|
||||
|
||||
CMSWindow::CMSWindow()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CMSWindow::~CMSWindow()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CMSWindow::Destroy() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CMSWindow::Create(const char *, int, DWORD, DWORD, HICON, int) -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CMSWindow::Show() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CMSWindow::Hide() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CMSWindow::SetVisibleMode(bool) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CMSWindow::SetPosition(int, int) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CMSWindow::SetCenterPosition() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CMSWindow::SetText(const char *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CMSWindow::AdjustSize(int, int) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CMSWindow::SetSize(int, int) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CMSWindow::IsVisible() -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CMSWindow::IsActive() -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
namespace {
|
||||
POINT g_host_cursor = {0, 0};
|
||||
}
|
||||
|
||||
void MtHostSetCursor(int x, int y)
|
||||
{
|
||||
g_host_cursor.x = x;
|
||||
g_host_cursor.y = y;
|
||||
}
|
||||
|
||||
auto CMSWindow::GetMousePosition(POINT * ppt) -> void
|
||||
{
|
||||
*ppt = g_host_cursor;
|
||||
}
|
||||
|
||||
auto CMSWindow::GetClientRect(RECT *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CMSWindow::GetWindowRect(RECT *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CMSWindow::GetScreenWidth() -> int
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<int>();
|
||||
}
|
||||
|
||||
auto CMSWindow::GetScreenHeight() -> int
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<int>();
|
||||
}
|
||||
|
||||
auto CMSWindow::GetWindowHandle() -> HWND
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<HWND>();
|
||||
}
|
||||
|
||||
auto CMSWindow::GetInstance() -> HINSTANCE
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<HINSTANCE>();
|
||||
}
|
||||
|
||||
auto CMSWindow::WindowProcedure(HWND, UINT, WPARAM, LPARAM) -> LRESULT
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<LRESULT>();
|
||||
}
|
||||
|
||||
auto CMSWindow::OnSize(WPARAM, LPARAM) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CMSWindow::RegisterWindowClass(DWORD, int, WNDPROC, HICON, int) -> const char *
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<const char *>();
|
||||
}
|
||||
|
||||
decltype(CMSWindow::ms_stWCSet) CMSWindow::ms_stWCSet{};
|
||||
|
||||
decltype(CMSWindow::ms_hInstance) CMSWindow::ms_hInstance{};
|
||||
@@ -0,0 +1,32 @@
|
||||
// Platform skeleton for EterLib/Mutex.h (40250 EterLib/Mutex.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/Mutex.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
Mutex::Mutex()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
Mutex::~Mutex()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto Mutex::Lock() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto Mutex::Unlock() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto Mutex::Trylock() -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
// The platform's IDirect3DDevice8: CGraphicDevice::Create hands it to CStateManager, which keeps
|
||||
// 40250's state caching; the device remembers the state it is given and turns every draw call into a
|
||||
// Render3DDraw (RenderCommands3D.h) for the Godot renderer.
|
||||
#include "EterLib/StdAfx.h"
|
||||
|
||||
IDirect3DDevice8* MtCreateRecordingDevice(int width, int height);
|
||||
@@ -0,0 +1,141 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// The 3D draw calls the ported game render (CPythonApplication::RenderGame) issues through
|
||||
// CStateManager, recorded by the platform's IDirect3DDevice8 (RecordingDevice.cpp) and consumed by
|
||||
// Godot. Like UIRenderCommands.h this header stays free of D3D and godot-cpp types.
|
||||
//
|
||||
// Matrices are D3D8's row-vector layout (translation in elements 12..14), exactly as 40250 set them.
|
||||
struct Render3DDraw {
|
||||
// Stable source-buffer identity and content version. Zero key means an immediate-mode draw
|
||||
// without a reusable D3D buffer. Geometry may be reused only while both values match.
|
||||
std::uint64_t geometry_key = 0;
|
||||
std::uint64_t geometry_revision = 0;
|
||||
float world[16];
|
||||
float view[16];
|
||||
float proj[16];
|
||||
float viewport[4] = {}; // x, y, width, height
|
||||
float viewport_z[2] = {0, 1}; // MinZ, MaxZ
|
||||
// PORT: UIRenderOffsetX() when recorded (a draw from a shifted window layer); the host moves the
|
||||
// viewport (and a pretransformed draw's screen positions) right by it.
|
||||
float ui_offset_x = 0;
|
||||
|
||||
// IDirect3DDevice8::Clear on the back buffer, kept in draw order. A clear entry has no geometry;
|
||||
// clear_flags holds D3DCLEAR_TARGET / D3DCLEAR_ZBUFFER / D3DCLEAR_STENCIL.
|
||||
std::uint32_t clear_flags = 0;
|
||||
std::uint32_t clear_color = 0; // 0xAARRGGBB
|
||||
float clear_z = 1;
|
||||
|
||||
// Non-zero: the draw (or clear) targets an offscreen render-target texture instead of the back
|
||||
// buffer -- the 40250 character shadow map. The renderer draws these into the texture named
|
||||
// Render3DRenderTargetName(render_target, ...) ahead of the back-buffer pass, in recorded order.
|
||||
// viewport is then in the target's pixels. Only emitted while GPU render targets are enabled.
|
||||
std::uint32_t render_target = 0;
|
||||
std::uint32_t target_width = 0, target_height = 0;
|
||||
|
||||
// Stage 0/1 textures, named like UIRenderTextureName: the pack path of a file texture or
|
||||
// "mem:<id>@<revision>"; empty when the stage has no texture.
|
||||
std::string texture0;
|
||||
std::string texture1;
|
||||
|
||||
// The referenced vertices, rebased so indices start at 0. normals/uv0/uv1/diffuse are empty when
|
||||
// the vertex format has no such element. pretransformed: D3DFVF_XYZRHW (screen-space x, y, z, rhw
|
||||
// in positions + rhw).
|
||||
bool pretransformed = false;
|
||||
std::vector<float> positions; // x, y, z
|
||||
std::vector<float> rhw;
|
||||
// D3D8 vertex fog of an XYZRHW draw: the D3DFVF_SPECULAR alpha (0..1); empty otherwise.
|
||||
std::vector<float> vertex_fog;
|
||||
std::vector<float> normals; // x, y, z
|
||||
std::vector<float> uv0; // u, v
|
||||
std::vector<float> uv1; // u, v
|
||||
std::vector<std::uint32_t> diffuse; // 0xAARRGGBB
|
||||
std::vector<std::uint32_t> indices; // triangle list (strips and fans are expanded), or line list
|
||||
std::vector<std::uint8_t> bone_indices; // 4 uint8 per vertex (mesh-local bone palette index)
|
||||
std::vector<float> bone_weights; // 4 floats per vertex
|
||||
std::vector<float> bone_matrices; // 16 floats per bone in the mesh's palette (D3D row-vector layout)
|
||||
bool lines = false;
|
||||
|
||||
// D3DRS_* / D3DTSS_* values in effect for the draw.
|
||||
std::uint32_t alpha_blend = 0, src_blend = 0, dest_blend = 0;
|
||||
std::uint32_t alpha_test = 0, alpha_ref = 0, alpha_func = 0;
|
||||
std::uint32_t cull_mode = 0, z_enable = 0, z_write = 0, z_func = 0;
|
||||
std::uint32_t lighting = 0, texture_factor = 0, fog_enable = 0;
|
||||
std::uint32_t fog_color = 0, fog_vertex_mode = 0, fog_table_mode = 0, fog_range_enable = 0;
|
||||
float fog_start = 0, fog_end = 0, fog_density = 0;
|
||||
std::uint32_t color_op[2] = {}, color_arg1[2] = {}, color_arg2[2] = {};
|
||||
std::uint32_t alpha_op[2] = {}, alpha_arg1[2] = {}, alpha_arg2[2] = {};
|
||||
std::uint32_t address_u[2] = {}, address_v[2] = {};
|
||||
std::uint32_t min_filter[2] = {}, mag_filter[2] = {}, mip_filter[2] = {};
|
||||
std::uint32_t border_color[2] = {};
|
||||
// D3DTSS_TEXCOORDINDEX (set index | D3DTSS_TCI_* generation mode), D3DTSS_TEXTURETRANSFORMFLAGS and
|
||||
// D3DTS_TEXTURE0/1 per stage. The renderer generates and transforms texture coordinates from
|
||||
// these the way the D3D8 fixed-function vertex pipeline does; uv0/uv1 stay the raw vertex sets.
|
||||
std::uint32_t texcoord_index[2] = {0, 1};
|
||||
std::uint32_t texture_transform_flags[2] = {};
|
||||
float texture_matrix[2][16] = {{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1},
|
||||
{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}};
|
||||
// D3DMATERIAL8 diffuse/ambient/emissive (r, g, b, a) and light 0 when enabled.
|
||||
float material_diffuse[4] = {1, 1, 1, 1};
|
||||
float material_ambient[4] = {};
|
||||
float material_emissive[4] = {};
|
||||
bool light0 = false;
|
||||
float light0_direction[3] = {};
|
||||
float light0_diffuse[4] = {};
|
||||
float light0_ambient[4] = {};
|
||||
std::uint32_t ambient = 0; // D3DRS_AMBIENT
|
||||
struct Light {
|
||||
std::uint32_t type = 0; // zero when disabled; D3DLIGHT_POINT/SPOT/DIRECTIONAL otherwise
|
||||
float position[3] = {};
|
||||
float direction[3] = {};
|
||||
float diffuse[4] = {};
|
||||
float ambient[4] = {};
|
||||
float attenuation[3] = {};
|
||||
float range = 0;
|
||||
float theta = 0, phi = 0, falloff = 0;
|
||||
} lights[8];
|
||||
std::uint32_t diffuse_material_source = 0;
|
||||
std::uint32_t ambient_material_source = 0;
|
||||
std::uint32_t emissive_material_source = 0;
|
||||
std::uint32_t color_vertex = 0;
|
||||
std::uint32_t normalize_normals = 0; // D3DRS_NORMALIZENORMALS
|
||||
std::uint32_t local_viewer = 1; // D3DRS_LOCALVIEWER
|
||||
};
|
||||
|
||||
// The texture coordinates stage 0/1 samples at each vertex, computed on the CPU with the same
|
||||
// D3D8 fixed-function rules the native renderer's vertex shader applies (TEXCOORDINDEX generation,
|
||||
// then D3DTS_TEXTUREn under TEXTURETRANSFORMFLAGS). For consumers without that shader (Godot).
|
||||
void Render3DStageTexcoords(const Render3DDraw& draw, int stage, std::vector<float>& out);
|
||||
|
||||
struct GpuSkinSubrangeView {
|
||||
std::uint64_t source_mesh_key = 0;
|
||||
std::uint32_t mesh_base_vertex = 0;
|
||||
std::uint32_t mesh_vertex_count = 0;
|
||||
const std::uint8_t* bone_indices = nullptr; // mesh_vertex_count * 4
|
||||
const float* bone_weights = nullptr; // mesh_vertex_count * 4
|
||||
const float* bone_matrices = nullptr; // bone_count * 16
|
||||
std::uint32_t bone_count = 0;
|
||||
};
|
||||
|
||||
void SetNativeTerrainRenderEnabled(bool enabled);
|
||||
bool IsNativeTerrainRenderEnabled();
|
||||
|
||||
void SetGpuSkinningEnabled(bool enabled);
|
||||
bool IsGpuSkinningEnabled();
|
||||
bool LookupGpuSkinSubrange(const void* vertex_buffer_base, std::uint32_t stride,
|
||||
std::uint32_t lo_vertex, std::uint32_t hi_vertex,
|
||||
GpuSkinSubrangeView* out_view);
|
||||
|
||||
// GPU render targets: shadow-map draws are recorded with render_target set and sampled as
|
||||
// "rt:<id>:<w>x<h>" instead of being rasterized on the CPU into a "mem:cpu_<id>" texture. Off by
|
||||
// default (the Godot consumer has no offscreen pass); the native Vulkan renderer turns it on.
|
||||
void SetGpuRenderTargetsEnabled(bool enabled);
|
||||
bool IsGpuRenderTargetsEnabled();
|
||||
std::string Render3DRenderTargetName(std::uint32_t id, std::uint32_t width, std::uint32_t height);
|
||||
|
||||
void Render3DBeginFrame();
|
||||
void Render3DAdd(Render3DDraw draw);
|
||||
const std::vector<Render3DDraw>& Render3DDraws();
|
||||
@@ -0,0 +1,787 @@
|
||||
// Platform implementation of EterLib/SkyBox.cpp (40250 EterLib/SkyBox.cpp).
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/SkyBox.h"
|
||||
#include "EterLib/Camera.h"
|
||||
#include "EterLib/StateManager.h"
|
||||
#include "EterLib/ResourceManager.h"
|
||||
#include "EterBase/Timer.h"
|
||||
#include "RenderCommands3D.h"
|
||||
|
||||
CSkyObjectQuad::CSkyObjectQuad()
|
||||
{
|
||||
m_Indices[0] = 0;
|
||||
m_Indices[1] = 2;
|
||||
m_Indices[2] = 1;
|
||||
m_Indices[3] = 3;
|
||||
|
||||
for (unsigned char uci = 0; uci < 4; ++uci)
|
||||
{
|
||||
memset(&m_Vertex[uci], 0, sizeof(TPDTVertex));
|
||||
}
|
||||
}
|
||||
|
||||
CSkyObjectQuad::~CSkyObjectQuad()
|
||||
{
|
||||
}
|
||||
|
||||
void CSkyObjectQuad::Clear(const unsigned char & c_rucNumVertex,
|
||||
const float & c_rfRed,
|
||||
const float & c_rfGreen,
|
||||
const float & c_rfBlue,
|
||||
const float & c_rfAlpha)
|
||||
{
|
||||
if (c_rucNumVertex > 3)
|
||||
return;
|
||||
m_Helper[c_rucNumVertex].Clear(c_rfRed, c_rfGreen, c_rfBlue, c_rfAlpha);
|
||||
}
|
||||
|
||||
void CSkyObjectQuad::SetSrcColor(const unsigned char & c_rucNumVertex,
|
||||
const float & c_rfRed,
|
||||
const float & c_rfGreen,
|
||||
const float & c_rfBlue,
|
||||
const float & c_rfAlpha)
|
||||
{
|
||||
if (c_rucNumVertex > 3)
|
||||
return;
|
||||
m_Helper[c_rucNumVertex].SetSrcColor(c_rfRed, c_rfGreen, c_rfBlue, c_rfAlpha);
|
||||
}
|
||||
|
||||
void CSkyObjectQuad::SetTransition(const unsigned char & c_rucNumVertex,
|
||||
const float & c_rfRed,
|
||||
const float & c_rfGreen,
|
||||
const float & c_rfBlue,
|
||||
const float & c_rfAlpha,
|
||||
DWORD dwDuration)
|
||||
{
|
||||
if (c_rucNumVertex > 3)
|
||||
return;
|
||||
m_Helper[c_rucNumVertex].SetTransition(c_rfRed, c_rfGreen, c_rfBlue, c_rfAlpha, dwDuration);
|
||||
}
|
||||
|
||||
void CSkyObjectQuad::SetVertex(const unsigned char & c_rucNumVertex, const TPDTVertex & c_rPDTVertex)
|
||||
{
|
||||
if (c_rucNumVertex > 3)
|
||||
return;
|
||||
memcpy(&m_Vertex[m_Indices[c_rucNumVertex]], &c_rPDTVertex, sizeof(TPDTVertex));
|
||||
}
|
||||
|
||||
void CSkyObjectQuad::StartTransition()
|
||||
{
|
||||
for (unsigned char uci = 0; uci < 4; ++uci)
|
||||
{
|
||||
m_Helper[uci].StartTransition();
|
||||
}
|
||||
}
|
||||
|
||||
bool CSkyObjectQuad::Update()
|
||||
{
|
||||
bool bResult = false;
|
||||
for (unsigned char uci = 0; uci < 4; ++uci)
|
||||
{
|
||||
bResult = m_Helper[uci].Update() || bResult;
|
||||
m_Vertex[m_Indices[uci]].diffuse = m_Helper[uci].GetCurColor();
|
||||
}
|
||||
return bResult;
|
||||
}
|
||||
|
||||
void CSkyObjectQuad::Render()
|
||||
{
|
||||
if (CGraphicBase::SetPDTStream(m_Vertex, 4))
|
||||
STATEMANAGER.DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
|
||||
}
|
||||
|
||||
CSkyObject::CSkyObject() :
|
||||
m_v3Position(0.0f, 0.0f, 0.0f),
|
||||
m_fScaleX(1.0f),
|
||||
m_fScaleY(1.0f),
|
||||
m_fScaleZ(1.0f)
|
||||
{
|
||||
D3DXMatrixIdentity(&m_matWorld);
|
||||
D3DXMatrixIdentity(&m_matTranslation);
|
||||
D3DXMatrixIdentity(&m_matWorldCloud);
|
||||
D3DXMatrixIdentity(&m_matTranslationCloud);
|
||||
D3DXMatrixIdentity(&m_matTextureCloud);
|
||||
|
||||
m_dwlastTime = CTimer::Instance().GetCurrentMillisecond();
|
||||
|
||||
m_fCloudPositionU = 0.0f;
|
||||
m_fCloudPositionV = 0.0f;
|
||||
m_fCloudScaleX = 1.0f;
|
||||
m_fCloudScaleY = 1.0f;
|
||||
m_fCloudHeight = 0.0f;
|
||||
m_fCloudTextureScaleX = 1.0f;
|
||||
m_fCloudTextureScaleY = 1.0f;
|
||||
m_fCloudScrollSpeedU = 0.0f;
|
||||
m_fCloudScrollSpeedV = 0.0f;
|
||||
m_ucRenderMode = SKY_RENDER_MODE_DEFAULT;
|
||||
|
||||
m_bTransitionStarted = false;
|
||||
m_bSkyMatrixUpdated = false;
|
||||
}
|
||||
|
||||
CSkyObject::~CSkyObject()
|
||||
{
|
||||
}
|
||||
|
||||
void CSkyObject::Destroy()
|
||||
{
|
||||
}
|
||||
|
||||
void CSkyObject::Update()
|
||||
{
|
||||
CCamera* pCamera = CCameraManager::Instance().GetCurrentCamera();
|
||||
if (!pCamera)
|
||||
return;
|
||||
D3DXVECTOR3 v3Eye = pCamera->GetEye();
|
||||
|
||||
if (m_v3Position == v3Eye)
|
||||
if (m_bSkyMatrixUpdated == false)
|
||||
return;
|
||||
|
||||
m_v3Position = v3Eye;
|
||||
|
||||
m_matWorld._41 = m_v3Position.x;
|
||||
m_matWorld._42 = m_v3Position.y;
|
||||
m_matWorld._43 = m_v3Position.z;
|
||||
|
||||
m_matWorldCloud._41 = m_v3Position.x;
|
||||
m_matWorldCloud._42 = m_v3Position.y;
|
||||
m_matWorldCloud._43 = m_v3Position.z + m_fCloudHeight;
|
||||
|
||||
if (m_bSkyMatrixUpdated)
|
||||
m_bSkyMatrixUpdated = false;
|
||||
}
|
||||
|
||||
void CSkyObject::Render()
|
||||
{
|
||||
}
|
||||
|
||||
void CSkyObject::StartTransition()
|
||||
{
|
||||
}
|
||||
|
||||
CGraphicImageInstance * CSkyObject::GenerateTexture(const char * szfilename)
|
||||
{
|
||||
if (!szfilename || !*szfilename)
|
||||
return NULL;
|
||||
|
||||
CResource * pResource = CResourceManager::Instance().GetResourcePointer(szfilename);
|
||||
if (!pResource || !pResource->IsType(CGraphicImage::Type()))
|
||||
return NULL;
|
||||
|
||||
CGraphicImageInstance * pImageInstance = CGraphicImageInstance::New();
|
||||
pImageInstance->SetImagePointer(static_cast<CGraphicImage *>(pResource));
|
||||
return pImageInstance;
|
||||
}
|
||||
|
||||
void CSkyObject::DeleteTexture(CGraphicImageInstance * pImageInstance)
|
||||
{
|
||||
if (pImageInstance)
|
||||
CGraphicImageInstance::Delete(pImageInstance);
|
||||
}
|
||||
|
||||
void CSkyObject::TSkyObjectFace::StartTransition()
|
||||
{
|
||||
for (unsigned char uci = 0; uci < m_SkyObjectQuadVector.size(); ++uci)
|
||||
{
|
||||
m_SkyObjectQuadVector[uci].StartTransition();
|
||||
}
|
||||
}
|
||||
|
||||
bool CSkyObject::TSkyObjectFace::Update()
|
||||
{
|
||||
bool bResult = false;
|
||||
for (DWORD dwi = 0; dwi < m_SkyObjectQuadVector.size(); ++dwi)
|
||||
bResult = m_SkyObjectQuadVector[dwi].Update() || bResult;
|
||||
return bResult;
|
||||
}
|
||||
|
||||
void CSkyObject::TSkyObjectFace::Render()
|
||||
{
|
||||
for (unsigned char uci = 0; uci < m_SkyObjectQuadVector.size(); ++uci)
|
||||
{
|
||||
m_SkyObjectQuadVector[uci].Render();
|
||||
}
|
||||
}
|
||||
|
||||
CSkyBox::CSkyBox()
|
||||
{
|
||||
m_ucVirticalGradientLevelUpper = 0;
|
||||
m_ucVirticalGradientLevelLower = 0;
|
||||
}
|
||||
|
||||
CSkyBox::~CSkyBox()
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void CSkyBox::Destroy()
|
||||
{
|
||||
Unload();
|
||||
}
|
||||
|
||||
void CSkyBox::Unload()
|
||||
{
|
||||
TGraphicImageInstanceMap::iterator itor = m_GraphicImageInstanceMap.begin();
|
||||
|
||||
while (itor != m_GraphicImageInstanceMap.end())
|
||||
{
|
||||
DeleteTexture(itor->second);
|
||||
++itor;
|
||||
}
|
||||
|
||||
m_GraphicImageInstanceMap.clear();
|
||||
}
|
||||
|
||||
void CSkyBox::SetSkyBoxScale(const D3DXVECTOR3 & c_rv3Scale)
|
||||
{
|
||||
m_fScaleX = c_rv3Scale.x;
|
||||
m_fScaleY = c_rv3Scale.y;
|
||||
m_fScaleZ = c_rv3Scale.z;
|
||||
|
||||
m_bSkyMatrixUpdated = true;
|
||||
D3DXMatrixScaling(&m_matWorld, m_fScaleX, m_fScaleY, m_fScaleZ);
|
||||
}
|
||||
|
||||
void CSkyBox::SetGradientLevel(BYTE byUpper, BYTE byLower)
|
||||
{
|
||||
m_ucVirticalGradientLevelUpper = byUpper;
|
||||
m_ucVirticalGradientLevelLower = byLower;
|
||||
}
|
||||
|
||||
void CSkyBox::SetFaceTexture(const char* c_szFileName, int iFaceIndex)
|
||||
{
|
||||
if (iFaceIndex < 0 || iFaceIndex > 5 || !c_szFileName || !*c_szFileName)
|
||||
return;
|
||||
|
||||
TGraphicImageInstanceMap::iterator itor = m_GraphicImageInstanceMap.find(c_szFileName);
|
||||
if (m_GraphicImageInstanceMap.end() != itor)
|
||||
return;
|
||||
|
||||
m_Faces[iFaceIndex].m_strFaceTextureFileName = c_szFileName;
|
||||
|
||||
CGraphicImageInstance * pGraphicImageInstance = GenerateTexture(c_szFileName);
|
||||
m_GraphicImageInstanceMap.insert(TGraphicImageInstanceMap::value_type(c_szFileName, pGraphicImageInstance));
|
||||
}
|
||||
|
||||
void CSkyBox::SetCloudTexture(const char * c_szFileName)
|
||||
{
|
||||
if (!c_szFileName || !*c_szFileName)
|
||||
return;
|
||||
|
||||
TGraphicImageInstanceMap::iterator itor = m_GraphicImageInstanceMap.find(c_szFileName);
|
||||
if (m_GraphicImageInstanceMap.end() != itor)
|
||||
return;
|
||||
|
||||
m_FaceCloud.m_strfacename = c_szFileName;
|
||||
CGraphicImageInstance * pGraphicImageInstance = GenerateTexture(c_szFileName);
|
||||
m_GraphicImageInstanceMap.insert(TGraphicImageInstanceMap::value_type(m_FaceCloud.m_strfacename, pGraphicImageInstance));
|
||||
}
|
||||
|
||||
void CSkyBox::SetCloudScale(const D3DXVECTOR2 & c_rv2CloudScale)
|
||||
{
|
||||
m_fCloudScaleX = c_rv2CloudScale.x;
|
||||
m_fCloudScaleY = c_rv2CloudScale.y;
|
||||
|
||||
D3DXMatrixScaling(&m_matWorldCloud, m_fCloudScaleX, m_fCloudScaleY, 1.0f);
|
||||
}
|
||||
|
||||
void CSkyBox::SetCloudHeight(float fHeight)
|
||||
{
|
||||
m_fCloudHeight = fHeight;
|
||||
}
|
||||
|
||||
void CSkyBox::SetCloudTextureScale(const D3DXVECTOR2 & c_rv2CloudTextureScale)
|
||||
{
|
||||
m_fCloudTextureScaleX = c_rv2CloudTextureScale.x;
|
||||
m_fCloudTextureScaleY = c_rv2CloudTextureScale.y;
|
||||
|
||||
m_matTextureCloud._11 = m_fCloudTextureScaleX;
|
||||
m_matTextureCloud._22 = m_fCloudTextureScaleY;
|
||||
}
|
||||
|
||||
void CSkyBox::SetCloudScrollSpeed(const D3DXVECTOR2 & c_rv2CloudScrollSpeed)
|
||||
{
|
||||
m_fCloudScrollSpeedU = c_rv2CloudScrollSpeed.x;
|
||||
m_fCloudScrollSpeedV = c_rv2CloudScrollSpeed.y;
|
||||
}
|
||||
|
||||
void CSkyBox::SetSkyObjectQuadVertical(TSkyObjectQuadVector * pSkyObjectQuadVector, const D3DXVECTOR2 * c_pv2QuadPoints)
|
||||
{
|
||||
TPDTVertex aPDTVertex;
|
||||
|
||||
DWORD dwIndex = 0;
|
||||
|
||||
pSkyObjectQuadVector->clear();
|
||||
pSkyObjectQuadVector->resize(m_ucVirticalGradientLevelUpper + m_ucVirticalGradientLevelLower);
|
||||
|
||||
unsigned char ucY;
|
||||
for (ucY = 0; ucY < m_ucVirticalGradientLevelUpper; ++ucY)
|
||||
{
|
||||
CSkyObjectQuad & rSkyObjectQuad = pSkyObjectQuadVector->at(dwIndex++);
|
||||
|
||||
aPDTVertex.position.x = c_pv2QuadPoints[0].x;
|
||||
aPDTVertex.position.y = c_pv2QuadPoints[0].y;
|
||||
aPDTVertex.position.z = 1.0f - (float)(ucY + 1) / (float)(m_ucVirticalGradientLevelUpper);
|
||||
aPDTVertex.texCoord.x = 0.0f;
|
||||
aPDTVertex.texCoord.y = (float)(ucY + 1) / (float)(m_ucVirticalGradientLevelUpper) * 0.5f;
|
||||
rSkyObjectQuad.SetVertex(0, aPDTVertex);
|
||||
aPDTVertex.position.x = c_pv2QuadPoints[0].x;
|
||||
aPDTVertex.position.y = c_pv2QuadPoints[0].y;
|
||||
aPDTVertex.position.z = 1.0f - (float)(ucY) / (float)(m_ucVirticalGradientLevelUpper);
|
||||
aPDTVertex.texCoord.x = 0.0f;
|
||||
aPDTVertex.texCoord.y = (float)(ucY) / (float)(m_ucVirticalGradientLevelUpper) * 0.5f;
|
||||
rSkyObjectQuad.SetVertex(1, aPDTVertex);
|
||||
aPDTVertex.position.x = c_pv2QuadPoints[1].x;
|
||||
aPDTVertex.position.y = c_pv2QuadPoints[1].y;
|
||||
aPDTVertex.position.z = 1.0f - (float)(ucY + 1) / (float)(m_ucVirticalGradientLevelUpper);
|
||||
aPDTVertex.texCoord.x = 1.0f;
|
||||
aPDTVertex.texCoord.y = (float)(ucY + 1) / (float)(m_ucVirticalGradientLevelUpper) * 0.5f;
|
||||
rSkyObjectQuad.SetVertex(2, aPDTVertex);
|
||||
aPDTVertex.position.x = c_pv2QuadPoints[1].x;
|
||||
aPDTVertex.position.y = c_pv2QuadPoints[1].y;
|
||||
aPDTVertex.position.z = 1.0f - (float)(ucY) / (float)(m_ucVirticalGradientLevelUpper);
|
||||
aPDTVertex.texCoord.x = 1.0f;
|
||||
aPDTVertex.texCoord.y = (float)(ucY) / (float)(m_ucVirticalGradientLevelUpper) * 0.5f;
|
||||
rSkyObjectQuad.SetVertex(3, aPDTVertex);
|
||||
}
|
||||
for (ucY = 0; ucY < m_ucVirticalGradientLevelLower; ++ucY)
|
||||
{
|
||||
CSkyObjectQuad & rSkyObjectQuad = pSkyObjectQuadVector->at(dwIndex++);
|
||||
|
||||
aPDTVertex.position.x = c_pv2QuadPoints[0].x;
|
||||
aPDTVertex.position.y = c_pv2QuadPoints[0].y;
|
||||
aPDTVertex.position.z = -(float)(ucY + 1) / (float)(m_ucVirticalGradientLevelLower);
|
||||
aPDTVertex.texCoord.x = 0.0f;
|
||||
aPDTVertex.texCoord.y = 0.5f + (float)(ucY + 1) / (float)(m_ucVirticalGradientLevelUpper) * 0.5f;
|
||||
rSkyObjectQuad.SetVertex(0, aPDTVertex);
|
||||
aPDTVertex.position.x = c_pv2QuadPoints[0].x;
|
||||
aPDTVertex.position.y = c_pv2QuadPoints[0].y;
|
||||
aPDTVertex.position.z = -(float)(ucY) / (float)(m_ucVirticalGradientLevelLower);
|
||||
aPDTVertex.texCoord.x = 0.0f;
|
||||
aPDTVertex.texCoord.y = 0.5f + (float)(ucY) / (float)(m_ucVirticalGradientLevelUpper) * 0.5f;
|
||||
rSkyObjectQuad.SetVertex(1, aPDTVertex);
|
||||
aPDTVertex.position.x = c_pv2QuadPoints[1].x;
|
||||
aPDTVertex.position.y = c_pv2QuadPoints[1].y;
|
||||
aPDTVertex.position.z = -(float)(ucY + 1) / (float)(m_ucVirticalGradientLevelLower);
|
||||
aPDTVertex.texCoord.x = 1.0f;
|
||||
aPDTVertex.texCoord.y = 0.5f + (float)(ucY + 1) / (float)(m_ucVirticalGradientLevelUpper) * 0.5f;
|
||||
rSkyObjectQuad.SetVertex(2, aPDTVertex);
|
||||
aPDTVertex.position.x = c_pv2QuadPoints[1].x;
|
||||
aPDTVertex.position.y = c_pv2QuadPoints[1].y;
|
||||
aPDTVertex.position.z = -(float)(ucY) / (float)(m_ucVirticalGradientLevelLower);
|
||||
aPDTVertex.texCoord.x = 1.0f;
|
||||
aPDTVertex.texCoord.y = 0.5f + (float)(ucY) / (float)(m_ucVirticalGradientLevelUpper) * 0.5f;
|
||||
rSkyObjectQuad.SetVertex(3, aPDTVertex);
|
||||
}
|
||||
}
|
||||
|
||||
void CSkyBox::SetSkyObjectQuadHorizon(TSkyObjectQuadVector * pSkyObjectQuadVector, const D3DXVECTOR3 * c_pv3QuadPoints)
|
||||
{
|
||||
pSkyObjectQuadVector->clear();
|
||||
pSkyObjectQuadVector->resize(1);
|
||||
CSkyObjectQuad & rSkyObjectQuad = pSkyObjectQuadVector->at(0);
|
||||
|
||||
TPDTVertex aPDTVertex{};
|
||||
aPDTVertex.position = c_pv3QuadPoints[0];
|
||||
aPDTVertex.texCoord.x = 0.0f;
|
||||
aPDTVertex.texCoord.y = 1.0f;
|
||||
rSkyObjectQuad.SetVertex(0, aPDTVertex);
|
||||
|
||||
aPDTVertex.position = c_pv3QuadPoints[1];
|
||||
aPDTVertex.texCoord.x = 0.0f;
|
||||
aPDTVertex.texCoord.y = 0.0f;
|
||||
rSkyObjectQuad.SetVertex(1, aPDTVertex);
|
||||
|
||||
aPDTVertex.position = c_pv3QuadPoints[2];
|
||||
aPDTVertex.texCoord.x = 1.0f;
|
||||
aPDTVertex.texCoord.y = 1.0f;
|
||||
rSkyObjectQuad.SetVertex(2, aPDTVertex);
|
||||
|
||||
aPDTVertex.position = c_pv3QuadPoints[3];
|
||||
aPDTVertex.texCoord.x = 1.0f;
|
||||
aPDTVertex.texCoord.y = 0.0f;
|
||||
rSkyObjectQuad.SetVertex(3, aPDTVertex);
|
||||
}
|
||||
|
||||
void CSkyBox::Refresh()
|
||||
{
|
||||
D3DXVECTOR3 v3QuadPoints[4];
|
||||
|
||||
if (m_ucRenderMode == CSkyObject::SKY_RENDER_MODE_DEFAULT || m_ucRenderMode == CSkyObject::SKY_RENDER_MODE_DIFFUSE)
|
||||
{
|
||||
if (m_ucVirticalGradientLevelUpper + m_ucVirticalGradientLevelLower <= 0)
|
||||
return;
|
||||
|
||||
D3DXVECTOR2 v2QuadPoints[2];
|
||||
|
||||
v2QuadPoints[0] = D3DXVECTOR2(1.0f, -1.0f);
|
||||
v2QuadPoints[1] = D3DXVECTOR2(-1.0f, -1.0f);
|
||||
SetSkyObjectQuadVertical(&m_Faces[0].m_SkyObjectQuadVector, v2QuadPoints);
|
||||
m_Faces[0].m_strfacename = "front";
|
||||
|
||||
v2QuadPoints[0] = D3DXVECTOR2(-1.0f, 1.0f);
|
||||
v2QuadPoints[1] = D3DXVECTOR2(1.0f, 1.0f);
|
||||
SetSkyObjectQuadVertical(&m_Faces[1].m_SkyObjectQuadVector, v2QuadPoints);
|
||||
m_Faces[1].m_strfacename = "back";
|
||||
|
||||
v2QuadPoints[0] = D3DXVECTOR2(-1.0f, -1.0f);
|
||||
v2QuadPoints[1] = D3DXVECTOR2(-1.0f, 1.0f);
|
||||
SetSkyObjectQuadVertical(&m_Faces[2].m_SkyObjectQuadVector, v2QuadPoints);
|
||||
m_Faces[2].m_strfacename = "left";
|
||||
|
||||
v2QuadPoints[0] = D3DXVECTOR2(1.0f, 1.0f);
|
||||
v2QuadPoints[1] = D3DXVECTOR2(1.0f, -1.0f);
|
||||
SetSkyObjectQuadVertical(&m_Faces[3].m_SkyObjectQuadVector, v2QuadPoints);
|
||||
m_Faces[3].m_strfacename = "right";
|
||||
|
||||
v3QuadPoints[0] = D3DXVECTOR3(1.0f, 1.0f, 1.0f);
|
||||
v3QuadPoints[1] = D3DXVECTOR3(-1.0f, 1.0f, 1.0f);
|
||||
v3QuadPoints[2] = D3DXVECTOR3(1.0f, -1.0f, 1.0f);
|
||||
v3QuadPoints[3] = D3DXVECTOR3(-1.0f, -1.0f, 1.0f);
|
||||
SetSkyObjectQuadHorizon(&m_Faces[4].m_SkyObjectQuadVector, v3QuadPoints);
|
||||
m_Faces[4].m_strfacename = "top";
|
||||
|
||||
v3QuadPoints[0] = D3DXVECTOR3(-1.0f, 1.0f, -1.0f);
|
||||
v3QuadPoints[1] = D3DXVECTOR3(1.0f, 1.0f, -1.0f);
|
||||
v3QuadPoints[2] = D3DXVECTOR3(-1.0f, -1.0f, -1.0f);
|
||||
v3QuadPoints[3] = D3DXVECTOR3(1.0f, -1.0f, -1.0f);
|
||||
SetSkyObjectQuadHorizon(&m_Faces[5].m_SkyObjectQuadVector, v3QuadPoints);
|
||||
m_Faces[5].m_strfacename = "bottom";
|
||||
}
|
||||
else if (m_ucRenderMode == CSkyObject::SKY_RENDER_MODE_TEXTURE)
|
||||
{
|
||||
v3QuadPoints[0] = D3DXVECTOR3(1.0f, -1.0f, -1.0f);
|
||||
v3QuadPoints[1] = D3DXVECTOR3(1.0f, -1.0f, 1.0f);
|
||||
v3QuadPoints[2] = D3DXVECTOR3(-1.0f, -1.0f, -1.0f);
|
||||
v3QuadPoints[3] = D3DXVECTOR3(-1.0f, -1.0f, 1.0f);
|
||||
SetSkyObjectQuadHorizon(&m_Faces[0].m_SkyObjectQuadVector, v3QuadPoints);
|
||||
m_Faces[0].m_strfacename = "front";
|
||||
|
||||
v3QuadPoints[0] = D3DXVECTOR3(-1.0f, 1.0f, -1.0f);
|
||||
v3QuadPoints[1] = D3DXVECTOR3(-1.0f, 1.0f, 1.0f);
|
||||
v3QuadPoints[2] = D3DXVECTOR3(1.0f, 1.0f, -1.0f);
|
||||
v3QuadPoints[3] = D3DXVECTOR3(1.0f, 1.0f, 1.0f);
|
||||
SetSkyObjectQuadHorizon(&m_Faces[1].m_SkyObjectQuadVector, v3QuadPoints);
|
||||
m_Faces[1].m_strfacename = "back";
|
||||
|
||||
v3QuadPoints[0] = D3DXVECTOR3(1.0f, 1.0f, -1.0f);
|
||||
v3QuadPoints[1] = D3DXVECTOR3(1.0f, 1.0f, 1.0f);
|
||||
v3QuadPoints[2] = D3DXVECTOR3(1.0f, -1.0f, -1.0f);
|
||||
v3QuadPoints[3] = D3DXVECTOR3(1.0f, -1.0f, 1.0f);
|
||||
SetSkyObjectQuadHorizon(&m_Faces[2].m_SkyObjectQuadVector, v3QuadPoints);
|
||||
m_Faces[2].m_strfacename = "left";
|
||||
|
||||
v3QuadPoints[0] = D3DXVECTOR3(-1.0f, -1.0f, -1.0f);
|
||||
v3QuadPoints[1] = D3DXVECTOR3(-1.0f, -1.0f, 1.0f);
|
||||
v3QuadPoints[2] = D3DXVECTOR3(-1.0f, 1.0f, -1.0f);
|
||||
v3QuadPoints[3] = D3DXVECTOR3(-1.0f, 1.0f, 1.0f);
|
||||
SetSkyObjectQuadHorizon(&m_Faces[3].m_SkyObjectQuadVector, v3QuadPoints);
|
||||
m_Faces[3].m_strfacename = "right";
|
||||
|
||||
v3QuadPoints[0] = D3DXVECTOR3(1.0f, -1.0f, 1.0f);
|
||||
v3QuadPoints[1] = D3DXVECTOR3(1.0f, 1.0f, 1.0f);
|
||||
v3QuadPoints[2] = D3DXVECTOR3(-1.0f, -1.0f, 1.0f);
|
||||
v3QuadPoints[3] = D3DXVECTOR3(-1.0f, 1.0f, 1.0f);
|
||||
SetSkyObjectQuadHorizon(&m_Faces[4].m_SkyObjectQuadVector, v3QuadPoints);
|
||||
m_Faces[4].m_strfacename = "top";
|
||||
|
||||
v3QuadPoints[0] = D3DXVECTOR3(1.0f, -1.0f, -1.0f);
|
||||
v3QuadPoints[1] = D3DXVECTOR3(1.0f, 1.0f, -1.0f);
|
||||
v3QuadPoints[2] = D3DXVECTOR3(-1.0f, -1.0f, -1.0f);
|
||||
v3QuadPoints[3] = D3DXVECTOR3(-1.0f, 1.0f, -1.0f);
|
||||
SetSkyObjectQuadHorizon(&m_Faces[5].m_SkyObjectQuadVector, v3QuadPoints);
|
||||
m_Faces[5].m_strfacename = "bottom";
|
||||
}
|
||||
|
||||
v3QuadPoints[0] = D3DXVECTOR3(1.0f, 1.0f, 0.0f);
|
||||
v3QuadPoints[1] = D3DXVECTOR3(-1.0f, 1.0f, 0.0f);
|
||||
v3QuadPoints[2] = D3DXVECTOR3(1.0f, -1.0f, 0.0f);
|
||||
v3QuadPoints[3] = D3DXVECTOR3(-1.0f, -1.0f, 0.0f);
|
||||
SetSkyObjectQuadHorizon(&m_FaceCloud.m_SkyObjectQuadVector, v3QuadPoints);
|
||||
}
|
||||
|
||||
void CSkyBox::SetCloudColor(const TGradientColor & c_rColor, const TGradientColor & c_rNextColor, const DWORD & dwTransitionTime)
|
||||
{
|
||||
TSkyObjectFace & aFaceCloud = m_FaceCloud;
|
||||
for (DWORD dwk = 0; dwk < aFaceCloud.m_SkyObjectQuadVector.size(); ++dwk)
|
||||
{
|
||||
CSkyObjectQuad & aSkyObjectQuad = aFaceCloud.m_SkyObjectQuadVector[dwk];
|
||||
for (unsigned char v = 0; v < 4; ++v)
|
||||
{
|
||||
aSkyObjectQuad.SetSrcColor(v,
|
||||
c_rColor.m_FirstColor.r,
|
||||
c_rColor.m_FirstColor.g,
|
||||
c_rColor.m_FirstColor.b,
|
||||
c_rColor.m_FirstColor.a);
|
||||
aSkyObjectQuad.SetTransition(v,
|
||||
c_rNextColor.m_FirstColor.r,
|
||||
c_rNextColor.m_FirstColor.g,
|
||||
c_rNextColor.m_FirstColor.b,
|
||||
c_rNextColor.m_FirstColor.a,
|
||||
dwTransitionTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CSkyBox::SetSkyColor(const TVectorGradientColor & c_rColorVector, const TVectorGradientColor & c_rNextColorVector, long lTransitionTime)
|
||||
{
|
||||
if (c_rColorVector.empty() || c_rNextColorVector.empty())
|
||||
return;
|
||||
|
||||
unsigned long ulVectorGradientColornum = 0;
|
||||
unsigned long uck;
|
||||
for (unsigned char ucj = 0; ucj < 4; ++ucj)
|
||||
{
|
||||
TSkyObjectFace & aFace = m_Faces[ucj];
|
||||
ulVectorGradientColornum = 0;
|
||||
for (uck = 0; uck < aFace.m_SkyObjectQuadVector.size(); ++uck)
|
||||
{
|
||||
if (ulVectorGradientColornum >= c_rColorVector.size() || ulVectorGradientColornum >= c_rNextColorVector.size())
|
||||
break;
|
||||
CSkyObjectQuad & aSkyObjectQuad = aFace.m_SkyObjectQuadVector[uck];
|
||||
|
||||
aSkyObjectQuad.SetSrcColor(0,
|
||||
c_rColorVector[ulVectorGradientColornum].m_SecondColor.r,
|
||||
c_rColorVector[ulVectorGradientColornum].m_SecondColor.g,
|
||||
c_rColorVector[ulVectorGradientColornum].m_SecondColor.b,
|
||||
c_rColorVector[ulVectorGradientColornum].m_SecondColor.a);
|
||||
aSkyObjectQuad.SetTransition(0,
|
||||
c_rNextColorVector[ulVectorGradientColornum].m_SecondColor.r,
|
||||
c_rNextColorVector[ulVectorGradientColornum].m_SecondColor.g,
|
||||
c_rNextColorVector[ulVectorGradientColornum].m_SecondColor.b,
|
||||
c_rNextColorVector[ulVectorGradientColornum].m_SecondColor.a,
|
||||
lTransitionTime);
|
||||
aSkyObjectQuad.SetSrcColor(1,
|
||||
c_rColorVector[ulVectorGradientColornum].m_FirstColor.r,
|
||||
c_rColorVector[ulVectorGradientColornum].m_FirstColor.g,
|
||||
c_rColorVector[ulVectorGradientColornum].m_FirstColor.b,
|
||||
c_rColorVector[ulVectorGradientColornum].m_FirstColor.a);
|
||||
aSkyObjectQuad.SetTransition(1,
|
||||
c_rNextColorVector[ulVectorGradientColornum].m_FirstColor.r,
|
||||
c_rNextColorVector[ulVectorGradientColornum].m_FirstColor.g,
|
||||
c_rNextColorVector[ulVectorGradientColornum].m_FirstColor.b,
|
||||
c_rNextColorVector[ulVectorGradientColornum].m_FirstColor.a,
|
||||
lTransitionTime);
|
||||
aSkyObjectQuad.SetSrcColor(2,
|
||||
c_rColorVector[ulVectorGradientColornum].m_SecondColor.r,
|
||||
c_rColorVector[ulVectorGradientColornum].m_SecondColor.g,
|
||||
c_rColorVector[ulVectorGradientColornum].m_SecondColor.b,
|
||||
c_rColorVector[ulVectorGradientColornum].m_SecondColor.a);
|
||||
aSkyObjectQuad.SetTransition(2,
|
||||
c_rNextColorVector[ulVectorGradientColornum].m_SecondColor.r,
|
||||
c_rNextColorVector[ulVectorGradientColornum].m_SecondColor.g,
|
||||
c_rNextColorVector[ulVectorGradientColornum].m_SecondColor.b,
|
||||
c_rNextColorVector[ulVectorGradientColornum].m_SecondColor.a,
|
||||
lTransitionTime);
|
||||
aSkyObjectQuad.SetSrcColor(3,
|
||||
c_rColorVector[ulVectorGradientColornum].m_FirstColor.r,
|
||||
c_rColorVector[ulVectorGradientColornum].m_FirstColor.g,
|
||||
c_rColorVector[ulVectorGradientColornum].m_FirstColor.b,
|
||||
c_rColorVector[ulVectorGradientColornum].m_FirstColor.a);
|
||||
aSkyObjectQuad.SetTransition(3,
|
||||
c_rNextColorVector[ulVectorGradientColornum].m_FirstColor.r,
|
||||
c_rNextColorVector[ulVectorGradientColornum].m_FirstColor.g,
|
||||
c_rNextColorVector[ulVectorGradientColornum].m_FirstColor.b,
|
||||
c_rNextColorVector[ulVectorGradientColornum].m_FirstColor.a,
|
||||
lTransitionTime);
|
||||
|
||||
ulVectorGradientColornum++;
|
||||
}
|
||||
}
|
||||
|
||||
TSkyObjectFace & aFaceTop = m_Faces[4];
|
||||
ulVectorGradientColornum = 0;
|
||||
for (uck = 0; uck < aFaceTop.m_SkyObjectQuadVector.size(); ++uck)
|
||||
{
|
||||
CSkyObjectQuad & aSkyObjectQuad = aFaceTop.m_SkyObjectQuadVector[uck];
|
||||
for (unsigned char v = 0; v < 4; ++v)
|
||||
{
|
||||
aSkyObjectQuad.SetSrcColor(v,
|
||||
c_rColorVector[ulVectorGradientColornum].m_FirstColor.r,
|
||||
c_rColorVector[ulVectorGradientColornum].m_FirstColor.g,
|
||||
c_rColorVector[ulVectorGradientColornum].m_FirstColor.b,
|
||||
c_rColorVector[ulVectorGradientColornum].m_FirstColor.a);
|
||||
aSkyObjectQuad.SetTransition(v,
|
||||
c_rNextColorVector[ulVectorGradientColornum].m_FirstColor.r,
|
||||
c_rNextColorVector[ulVectorGradientColornum].m_FirstColor.g,
|
||||
c_rNextColorVector[ulVectorGradientColornum].m_FirstColor.b,
|
||||
c_rNextColorVector[ulVectorGradientColornum].m_FirstColor.a,
|
||||
lTransitionTime);
|
||||
}
|
||||
}
|
||||
|
||||
TSkyObjectFace & aFaceBottom = m_Faces[5];
|
||||
ulVectorGradientColornum = c_rColorVector.size() - 1;
|
||||
for (uck = 0; uck < aFaceBottom.m_SkyObjectQuadVector.size(); ++uck)
|
||||
{
|
||||
CSkyObjectQuad & aSkyObjectQuad = aFaceBottom.m_SkyObjectQuadVector[uck];
|
||||
for (unsigned char v = 0; v < 4; ++v)
|
||||
{
|
||||
aSkyObjectQuad.SetSrcColor(v,
|
||||
c_rColorVector[ulVectorGradientColornum].m_SecondColor.r,
|
||||
c_rColorVector[ulVectorGradientColornum].m_SecondColor.g,
|
||||
c_rColorVector[ulVectorGradientColornum].m_SecondColor.b,
|
||||
c_rColorVector[ulVectorGradientColornum].m_SecondColor.a);
|
||||
aSkyObjectQuad.SetTransition(v,
|
||||
c_rNextColorVector[ulVectorGradientColornum].m_SecondColor.r,
|
||||
c_rNextColorVector[ulVectorGradientColornum].m_SecondColor.g,
|
||||
c_rNextColorVector[ulVectorGradientColornum].m_SecondColor.b,
|
||||
c_rNextColorVector[ulVectorGradientColornum].m_SecondColor.a,
|
||||
lTransitionTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CSkyBox::StartTransition()
|
||||
{
|
||||
m_bTransitionStarted = true;
|
||||
for (unsigned char ucj = 0; ucj < 6; ++ucj)
|
||||
m_Faces[ucj].StartTransition();
|
||||
m_FaceCloud.StartTransition();
|
||||
}
|
||||
|
||||
void CSkyBox::Update()
|
||||
{
|
||||
CSkyObject::Update();
|
||||
|
||||
if (!m_bTransitionStarted)
|
||||
return;
|
||||
|
||||
bool bResult = false;
|
||||
for (unsigned char uci = 0; uci < 6; ++uci)
|
||||
bResult = m_Faces[uci].Update() || bResult;
|
||||
bResult = m_FaceCloud.Update() || bResult;
|
||||
|
||||
m_bTransitionStarted = bResult;
|
||||
}
|
||||
|
||||
void CSkyBox::Render()
|
||||
{
|
||||
if (!IsNativeTerrainRenderEnabled())
|
||||
return;
|
||||
|
||||
STATEMANAGER.SaveRenderState(D3DRS_ZENABLE, TRUE);
|
||||
STATEMANAGER.SaveRenderState(D3DRS_ZWRITEENABLE, FALSE);
|
||||
STATEMANAGER.SaveRenderState(D3DRS_LIGHTING, FALSE);
|
||||
STATEMANAGER.SaveRenderState(D3DRS_FOGENABLE, FALSE);
|
||||
STATEMANAGER.SaveRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
|
||||
STATEMANAGER.SaveRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
|
||||
|
||||
STATEMANAGER.SaveTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG2);
|
||||
STATEMANAGER.SaveTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SaveTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
|
||||
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
|
||||
|
||||
STATEMANAGER.SetTexture(1, NULL);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
|
||||
|
||||
STATEMANAGER.SetVertexShader(D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1);
|
||||
|
||||
STATEMANAGER.SetTransform(D3DTS_WORLD, &m_matWorld);
|
||||
|
||||
if (m_ucRenderMode == CSkyObject::SKY_RENDER_MODE_TEXTURE)
|
||||
{
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SaveTextureStageState(0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP);
|
||||
STATEMANAGER.SaveTextureStageState(0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP);
|
||||
|
||||
for (unsigned int i = 0; i < 6; ++i)
|
||||
{
|
||||
CGraphicImageInstance * pFaceImageInstance = m_GraphicImageInstanceMap[m_Faces[i].m_strFaceTextureFileName];
|
||||
if (!pFaceImageInstance)
|
||||
break;
|
||||
|
||||
STATEMANAGER.SetTexture(0, pFaceImageInstance->GetTextureReference().GetD3DTexture());
|
||||
m_Faces[i].Render();
|
||||
}
|
||||
|
||||
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_ADDRESSU);
|
||||
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_ADDRESSV);
|
||||
}
|
||||
else
|
||||
{
|
||||
STATEMANAGER.SetTexture(0, NULL);
|
||||
for (unsigned int i = 0; i < 6; ++i)
|
||||
{
|
||||
m_Faces[i].Render();
|
||||
}
|
||||
}
|
||||
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_CULLMODE);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_LIGHTING);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_ZENABLE);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_ZWRITEENABLE);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_FOGENABLE);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_ALPHABLENDENABLE);
|
||||
|
||||
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_COLOROP);
|
||||
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_COLORARG1);
|
||||
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_COLORARG2);
|
||||
}
|
||||
|
||||
void CSkyBox::RenderCloud()
|
||||
{
|
||||
if (!IsNativeTerrainRenderEnabled())
|
||||
return;
|
||||
|
||||
CGraphicImageInstance * pCloudGraphicImageInstance = m_GraphicImageInstanceMap[m_FaceCloud.m_strfacename];
|
||||
if (!pCloudGraphicImageInstance || pCloudGraphicImageInstance->IsEmpty() || !pCloudGraphicImageInstance->GetTexturePointer())
|
||||
return;
|
||||
|
||||
STATEMANAGER.SaveRenderState(D3DRS_ZENABLE, TRUE);
|
||||
STATEMANAGER.SaveRenderState(D3DRS_ZWRITEENABLE, FALSE);
|
||||
STATEMANAGER.SaveRenderState(D3DRS_LIGHTING, FALSE);
|
||||
STATEMANAGER.SaveRenderState(D3DRS_FOGENABLE, FALSE);
|
||||
STATEMANAGER.SaveRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
|
||||
STATEMANAGER.SaveRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
|
||||
STATEMANAGER.SaveRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCCOLOR);
|
||||
STATEMANAGER.SaveRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
|
||||
|
||||
STATEMANAGER.SaveTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2);
|
||||
|
||||
m_matTextureCloud._31 = m_fCloudPositionU;
|
||||
m_matTextureCloud._32 = m_fCloudPositionV;
|
||||
|
||||
DWORD dwCurTime = CTimer::Instance().GetCurrentMillisecond();
|
||||
|
||||
m_fCloudPositionU += m_fCloudScrollSpeedU * (float)(dwCurTime - m_dwlastTime) * 0.001f;
|
||||
if (m_fCloudPositionU >= 1.0f)
|
||||
m_fCloudPositionU = 0.0f;
|
||||
|
||||
m_fCloudPositionV += m_fCloudScrollSpeedV * (float)(dwCurTime - m_dwlastTime) * 0.001f;
|
||||
if (m_fCloudPositionV >= 1.0f)
|
||||
m_fCloudPositionV = 0.0f;
|
||||
|
||||
m_dwlastTime = dwCurTime;
|
||||
|
||||
STATEMANAGER.SaveTransform(D3DTS_TEXTURE0, &m_matTextureCloud);
|
||||
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATEINVALPHA_ADDCOLOR);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||
|
||||
D3DXMATRIX matProjCloud;
|
||||
D3DXMatrixPerspectiveFovRH(&matProjCloud, D3DX_PI * 0.25f, 1.33333f, 50.0f, 999999.0f);
|
||||
STATEMANAGER.SetTransform(D3DTS_WORLD, &m_matWorldCloud);
|
||||
STATEMANAGER.SaveTransform(D3DTS_PROJECTION, &matProjCloud);
|
||||
STATEMANAGER.SetTexture(0, pCloudGraphicImageInstance->GetTexturePointer()->GetD3DTexture());
|
||||
m_FaceCloud.Render();
|
||||
STATEMANAGER.RestoreTransform(D3DTS_PROJECTION);
|
||||
|
||||
STATEMANAGER.RestoreTransform(D3DTS_TEXTURE0);
|
||||
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS);
|
||||
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_CULLMODE);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_LIGHTING);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_ZENABLE);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_ZWRITEENABLE);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_FOGENABLE);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_ALPHABLENDENABLE);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_SRCBLEND);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_DESTBLEND);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Platform skeleton for EterLib/TextBar.h (40250 EterLib/TextBar.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/TextBar.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
CTextBar::CTextBar(int, bool)
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CTextBar::~CTextBar()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CTextBar::TextOut(int, int, const char *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CTextBar::SetTextColor(int, int, int) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CTextBar::GetTextExtent(const char *, SIZE *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CTextBar::__SetFont(int, bool) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CTextBar::OnCreate() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Platform skeleton for EterLib/Thread.h (40250 EterLib/Thread.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/Thread.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
CThread::CThread()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CThread::Create(void *) -> int
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<int>();
|
||||
}
|
||||
|
||||
auto CThread::EntryPoint(void *) -> UINT
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<UINT>();
|
||||
}
|
||||
|
||||
auto CThread::Run(void *) -> UINT
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<UINT>();
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "UIRenderCommands.h"
|
||||
#include "RenderCommands3D.h"
|
||||
#include "EterLib/StateManager.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
struct Point { float x, y; };
|
||||
std::vector<Point> clip_polygon(std::vector<Point> polygon, int edge, float bound) {
|
||||
std::vector<Point> result;
|
||||
if (polygon.empty()) return result;
|
||||
auto inside = [edge, bound](Point p) {
|
||||
return edge == 0 ? p.x >= bound : edge == 1 ? p.x <= bound
|
||||
: edge == 2 ? p.y >= bound : p.y <= bound;
|
||||
};
|
||||
auto intersect = [edge, bound](Point a, Point b) {
|
||||
const float t = edge < 2 ? (bound - a.x) / (b.x - a.x)
|
||||
: (bound - a.y) / (b.y - a.y);
|
||||
return Point{a.x + t * (b.x - a.x), a.y + t * (b.y - a.y)};
|
||||
};
|
||||
Point previous = polygon.back();
|
||||
for (Point current : polygon) {
|
||||
const bool was_inside = inside(previous), is_inside = inside(current);
|
||||
if (was_inside != is_inside) result.push_back(intersect(previous, current));
|
||||
if (is_inside) result.push_back(current);
|
||||
previous = current;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
std::vector<UIRenderCommand> commands;
|
||||
std::uint64_t frame_id = 0;
|
||||
int canvas_width = 0;
|
||||
int canvas_height = 0;
|
||||
float clip_x1 = 0, clip_y1 = 0, clip_x2 = 0, clip_y2 = 0;
|
||||
float saved_x1 = 0, saved_y1 = 0, saved_x2 = 0, saved_y2 = 0;
|
||||
float offset_x = 0;
|
||||
}
|
||||
|
||||
void UIRenderSetOffsetX(float x) { offset_x = x; }
|
||||
float UIRenderOffsetX() { return offset_x; }
|
||||
|
||||
void UIRenderSetSize(int width, int height) { canvas_width = width; canvas_height = height; }
|
||||
void UIRenderGetSize(unsigned* width, unsigned* height) {
|
||||
if (width) *width = canvas_width;
|
||||
if (height) *height = canvas_height;
|
||||
}
|
||||
void UIRenderBeginFrame() {
|
||||
++frame_id;
|
||||
commands.clear();
|
||||
clip_x1 = clip_y1 = 0;
|
||||
clip_x2 = canvas_width;
|
||||
clip_y2 = canvas_height;
|
||||
}
|
||||
std::uint64_t UIRenderFrameId() { return frame_id; }
|
||||
void UIRenderAdd(UIRenderCommand command) {
|
||||
if (offset_x != 0) {
|
||||
command.x1 += offset_x;
|
||||
command.x2 += offset_x;
|
||||
if (command.quad)
|
||||
for (float& x : command.qx) x += offset_x;
|
||||
}
|
||||
command.clip_x1 = clip_x1; command.clip_y1 = clip_y1;
|
||||
command.clip_x2 = clip_x2; command.clip_y2 = clip_y2;
|
||||
command.behind_3d = Render3DDraws().empty();
|
||||
if (command.kind == UIRenderCommand::Bar && command.quad) {
|
||||
// Cooldown fans are triangles. Clip their geometry before either renderer consumes it.
|
||||
std::vector<Point> polygon{{command.qx[0], command.qy[0]},
|
||||
{command.qx[1], command.qy[1]},
|
||||
{command.qx[2], command.qy[2]}};
|
||||
const float bounds[4] = {clip_x1, clip_x2, clip_y1, clip_y2};
|
||||
for (int edge = 0; edge < 4; ++edge)
|
||||
polygon = clip_polygon(std::move(polygon), edge, bounds[edge]);
|
||||
for (std::size_t i = 1; i + 1 < polygon.size(); ++i) {
|
||||
UIRenderCommand piece = command;
|
||||
const Point tri[3] = {polygon[0], polygon[i], polygon[i + 1]};
|
||||
const float area = (tri[1].x - tri[0].x) * (tri[2].y - tri[0].y) -
|
||||
(tri[1].y - tri[0].y) * (tri[2].x - tri[0].x);
|
||||
if (area == 0.0f) continue;
|
||||
for (int k = 0; k < 4; ++k) {
|
||||
piece.qx[k] = tri[k < 3 ? k : 2].x;
|
||||
piece.qy[k] = tri[k < 3 ? k : 2].y;
|
||||
}
|
||||
piece.x1 = std::min({tri[0].x, tri[1].x, tri[2].x});
|
||||
piece.y1 = std::min({tri[0].y, tri[1].y, tri[2].y});
|
||||
piece.x2 = std::max({tri[0].x, tri[1].x, tri[2].x});
|
||||
piece.y2 = std::max({tri[0].y, tri[1].y, tri[2].y});
|
||||
commands.push_back(std::move(piece));
|
||||
}
|
||||
return;
|
||||
}
|
||||
commands.push_back(command);
|
||||
}
|
||||
const std::vector<UIRenderCommand>& UIRenderCommands() { return commands; }
|
||||
void UIRenderSetClip(float x, float y, float width, float height) {
|
||||
saved_x1 = clip_x1; saved_y1 = clip_y1; saved_x2 = clip_x2; saved_y2 = clip_y2;
|
||||
clip_x1 = x + offset_x; clip_y1 = y;
|
||||
clip_x2 = x + offset_x + width; clip_y2 = y + height;
|
||||
}
|
||||
void UIRenderRestoreClip() {
|
||||
clip_x1 = saved_x1; clip_y1 = saved_y1;
|
||||
clip_x2 = saved_x2; clip_y2 = saved_y2;
|
||||
}
|
||||
|
||||
void UIRenderAddImage(const CGraphicTexture* texture, const float x[4], const float y[4],
|
||||
float su, float sv, float eu, float ev, std::uint32_t argb, int blend) {
|
||||
// 40250 draws the image quad through the device: the UI pass's world transform places it
|
||||
// (identity except where a caller sets one, e.g. CPythonMiniMap::RenderAtlas's window offset) and a
|
||||
// stage-0 MODULATE with D3DTA_TFACTOR tints it (the atlas's NPC/warp/waypoint marks).
|
||||
D3DXMATRIX world;
|
||||
D3DXMatrixIdentity(&world);
|
||||
if (CStateManager* state = CStateManager::InstancePtr()) {
|
||||
state->GetTransform(D3DTS_WORLD, &world);
|
||||
DWORD op = 0, arg1 = 0, arg2 = 0;
|
||||
state->GetTextureStageState(0, D3DTSS_COLOROP, &op);
|
||||
state->GetTextureStageState(0, D3DTSS_COLORARG1, &arg1);
|
||||
state->GetTextureStageState(0, D3DTSS_COLORARG2, &arg2);
|
||||
if (op == D3DTOP_MODULATE && (arg1 == D3DTA_TFACTOR || arg2 == D3DTA_TFACTOR)) {
|
||||
// TFACTOR x TEXTURE drops the vertex colour; TFACTOR x DIFFUSE scales it. The canvas
|
||||
// multiplies the texture in.
|
||||
const std::uint32_t factor = state->GetRenderState(D3DRS_TEXTUREFACTOR) & 0x00FFFFFFu;
|
||||
const DWORD other = arg1 == D3DTA_TFACTOR ? arg2 : arg1;
|
||||
if (other == D3DTA_DIFFUSE) {
|
||||
std::uint32_t tinted = argb & 0xFF000000u;
|
||||
for (int shift = 0; shift < 24; shift += 8)
|
||||
tinted |= ((((argb >> shift) & 255) * ((factor >> shift) & 255) + 127) / 255) << shift;
|
||||
argb = tinted;
|
||||
} else {
|
||||
argb = (argb & 0xFF000000u) | factor;
|
||||
}
|
||||
}
|
||||
}
|
||||
UIRenderCommand command{UIRenderCommand::Image, 0, 0, 0, 0, argb};
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
command.qx[i] = x[i] * world._11 + y[i] * world._21 + world._41 + 0.5f;
|
||||
command.qy[i] = x[i] * world._12 + y[i] * world._22 + world._42 + 0.5f;
|
||||
}
|
||||
command.x1 = std::min({command.qx[0], command.qx[1], command.qx[2], command.qx[3]});
|
||||
command.y1 = std::min({command.qy[0], command.qy[1], command.qy[2], command.qy[3]});
|
||||
command.x2 = std::max({command.qx[0], command.qx[1], command.qx[2], command.qx[3]});
|
||||
command.y2 = std::max({command.qy[0], command.qy[1], command.qy[2], command.qy[3]});
|
||||
command.text = UIRenderTextureName(texture);
|
||||
command.quad = true;
|
||||
command.su = su; command.sv = sv; command.eu = eu; command.ev = ev;
|
||||
command.blend = blend;
|
||||
UIRenderAdd(std::move(command));
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Commands emitted by the 40250 UI drawing calls, consumed by Godot's CanvasItem.
|
||||
// The port layer stays independent of godot-cpp.
|
||||
struct UIRenderCommand {
|
||||
enum Kind { Bar, Line, Text, Image, GradientBar } kind;
|
||||
float x1, y1, x2, y2;
|
||||
std::uint32_t argb;
|
||||
std::uint32_t end_argb = 0; // GradientBar only: colour on the bottom edge.
|
||||
float clip_x1 = 0, clip_y1 = 0, clip_x2 = 0, clip_y2 = 0;
|
||||
std::string text;
|
||||
// Image only: text is the texture's file, drawn as the quad (vertices 0..3 = TL, TR, BL, BR)
|
||||
// with texture coordinates su,sv..eu,ev; x1..y2 is the quad's bounding box.
|
||||
bool quad = false;
|
||||
float qx[4] = {}, qy[4] = {};
|
||||
float su = 0, sv = 0, eu = 1, ev = 1;
|
||||
int blend = 0; // CGraphicExpandedImageInstance::RENDERING_MODE_*
|
||||
// A second texture stage that modulates the colour and replaces the alpha (40250 CPythonMiniMap's
|
||||
// circular minimap_image_filter): the mask's texture coordinates at the quad's four corners.
|
||||
std::string mask;
|
||||
float mu[4] = {}, mv[4] = {};
|
||||
bool behind_3d = false;
|
||||
};
|
||||
|
||||
// D3DXCOLOR (0..1 floats) -> the 0xAARRGGBB the commands carry.
|
||||
template <class Color>
|
||||
std::uint32_t UIRenderColor(const Color& color) {
|
||||
auto channel = [](float value) -> std::uint32_t {
|
||||
return static_cast<std::uint32_t>((value < 0 ? 0 : value > 1 ? 1 : value) * 255.0f + 0.5f);
|
||||
};
|
||||
return (channel(color.a) << 24) | (channel(color.r) << 16) | (channel(color.g) << 8) | channel(color.b);
|
||||
}
|
||||
|
||||
class CGraphicTexture;
|
||||
// A file-backed texture is named by its pack path; a memory texture (CGraphicImageTexture::Create,
|
||||
// filled through Lock/Unlock: the CGraphicFontTexture glyph pages) by "mem:<id>@<revision>", the
|
||||
// revision counting Unlocks so the canvas knows when to fetch the pixels again.
|
||||
std::string UIRenderTextureName(const CGraphicTexture* texture);
|
||||
|
||||
// The pixels of memory texture "mem:<id>" (the part of the name before '@'), as 0xAARRGGBB.
|
||||
struct UIMemoryTexture {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
std::uint32_t revision = 0;
|
||||
std::vector<std::uint32_t> argb;
|
||||
};
|
||||
bool UIRenderMemoryTexture(const std::string& name, UIMemoryTexture* out);
|
||||
struct IDirect3DTexture8;
|
||||
struct IDirect3DBaseTexture8;
|
||||
// Frees the platform texture behind a CGraphicTexture: a memory texture or a file texture's handle.
|
||||
void UIRenderReleaseMemoryTexture(IDirect3DTexture8* texture);
|
||||
// The name (as UIRenderTextureName) of the texture a D3D handle belongs to; "" for null or unknown.
|
||||
std::string UIRenderTextureNameFromHandle(const IDirect3DBaseTexture8* handle);
|
||||
std::string MtCpuTextureNameFromHandle(const IDirect3DBaseTexture8* handle);
|
||||
bool MtCpuMemoryTexture(const std::string& name, UIMemoryTexture* out);
|
||||
|
||||
// A textured quad from 40250's TPDTVertex[4] (TL, TR, BL, BR) positions and texture coordinates.
|
||||
// PORT: D3D8 puts pixel centres on integers, which is why 40250 subtracts 0.5 from every vertex; the
|
||||
// Godot canvas puts them on .5, so the quad is shifted back by +0.5 here.
|
||||
void UIRenderAddImage(const CGraphicTexture* texture, const float x[4], const float y[4],
|
||||
float su, float sv, float eu, float ev, std::uint32_t argb, int blend = 0);
|
||||
|
||||
void UIRenderSetSize(int width, int height);
|
||||
void UIRenderGetSize(unsigned* width, unsigned* height);
|
||||
void UIRenderBeginFrame();
|
||||
std::uint64_t UIRenderFrameId();
|
||||
void UIRenderAdd(UIRenderCommand command);
|
||||
const std::vector<UIRenderCommand>& UIRenderCommands();
|
||||
void UIRenderSetClip(float x, float y, float width, float height);
|
||||
// PORT (mobile safe area): shifts the commands (and clips) recorded after it right by x canvas
|
||||
// pixels, so the 40250 window layers can sit inside the rounded screen corners while the GAME layer
|
||||
// (3D world, text tails) keeps the full canvas. 0 is the verbatim 40250 path.
|
||||
void UIRenderSetOffsetX(float x);
|
||||
float UIRenderOffsetX();
|
||||
void UIRenderRestoreClip();
|
||||
|
||||
// HiDPI text: CGraphicFontTexture rasterises its glyph pages at this multiple of the logical font
|
||||
// size (the swapchain's pixels per UI pixel) while keeping the 40250 glyph metrics and page UVs in
|
||||
// logical pixels. 1 (the default) is the verbatim 40250 path. Latched when the first font page is
|
||||
// created, so set it before the game creates fonts.
|
||||
void UISetFontOversample(int scale);
|
||||
int UIFontOversample();
|
||||
@@ -0,0 +1,109 @@
|
||||
// 40250 EterLib/Util.cpp:140-297, the default code page and font face.
|
||||
//
|
||||
// The rest of Util.cpp (LoadTextData / base64 / TokenTo*) is logic and not here. The 9x face table has
|
||||
// CP949/Shift-JIS/GBK/Big5 literals, so this block is copied by hand instead of port_copy.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/Util.h"
|
||||
#include "EterLocale/CodePageId.h"
|
||||
|
||||
static std::string gs_fontFace="";
|
||||
static DWORD gs_codePage=0;
|
||||
|
||||
int GetCharsetFromCodePage(WORD codePage)
|
||||
{
|
||||
switch( codePage )
|
||||
{
|
||||
case CP_932:
|
||||
return SHIFTJIS_CHARSET;
|
||||
case CP_949:
|
||||
return HANGUL_CHARSET;
|
||||
case CP_936:
|
||||
return GB2312_CHARSET;
|
||||
case CP_950:
|
||||
return CHINESEBIG5_CHARSET;
|
||||
case CP_1253:
|
||||
return GREEK_CHARSET;
|
||||
case CP_1254:
|
||||
return TURKISH_CHARSET;
|
||||
case CP_1255:
|
||||
return HEBREW_CHARSET;
|
||||
case CP_1256:
|
||||
return ARABIC_CHARSET;
|
||||
case CP_1257:
|
||||
return BALTIC_CHARSET;
|
||||
case CP_1258:
|
||||
return VIETNAMESE_CHARSET;
|
||||
case CP_874:
|
||||
return THAI_CHARSET;
|
||||
case CP_1250:
|
||||
return EASTEUROPE_CHARSET;
|
||||
case CP_1251:
|
||||
return RUSSIAN_CHARSET;
|
||||
default:
|
||||
return DEFAULT_CHARSET;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char* GetFontFaceFromCodePageNT(WORD codePage)
|
||||
{
|
||||
switch( codePage )
|
||||
{
|
||||
case CP_932:
|
||||
return "MS PGothic";
|
||||
case CP_949:
|
||||
return "GulimChe";
|
||||
case CP_936:
|
||||
return "SimSun";
|
||||
case CP_950:
|
||||
return "MingLiU";
|
||||
case CP_874:
|
||||
return "Tahoma";
|
||||
case CP_1252:
|
||||
return "Arial";
|
||||
case CP_1256:
|
||||
return "Tahoma";
|
||||
case CP_1258:
|
||||
return "Tahoma";
|
||||
case CP_65001:
|
||||
return "Arial";
|
||||
default:
|
||||
return "Arial";
|
||||
}
|
||||
}
|
||||
|
||||
DWORD GetDefaultCodePage()
|
||||
{
|
||||
return gs_codePage;
|
||||
}
|
||||
|
||||
const char * GetDefaultFontFace()
|
||||
{
|
||||
return gs_fontFace.c_str();
|
||||
}
|
||||
|
||||
// PORT: 40250 asks EnumFontFamiliesEx for the 9x (native-script) face name, then the NT one. The native
|
||||
// names never match a font file here; CreateFontIndirect (platform/Win32Gdi.cpp) substitutes a missing
|
||||
// face, so the NT name is the answer.
|
||||
const char* GetFontFaceFromCodePage(WORD codePage)
|
||||
{
|
||||
return GetFontFaceFromCodePageNT(codePage);
|
||||
}
|
||||
|
||||
void SetDefaultFontFace(const char* fontFace)
|
||||
{
|
||||
gs_fontFace=fontFace;
|
||||
}
|
||||
|
||||
bool SetDefaultCodePage(DWORD codePage)
|
||||
{
|
||||
gs_codePage=codePage;
|
||||
|
||||
std::string fontFace=GetFontFaceFromCodePage(codePage);
|
||||
if (fontFace.empty())
|
||||
return false;
|
||||
|
||||
SetDefaultFontFace(fontFace.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
// CPythonGraphic(40250 EterPythonLib/PythonGraphic.cpp)的平台骨架 —— 目前只有窗口树链接时真正需要
|
||||
// 的那一个函数。
|
||||
//
|
||||
// CPythonGraphic 是 UI 的 2D 绘制层(D3D8 的正交投影、按钮的立体边框、冷却圈、截图),整份实现属于
|
||||
// 2V0-f;platform_stub.py 不认 EterPythonLib(port_map 把它划成 python 层),所以这里按手写平台桩的
|
||||
// 惯例来:先只放被引用到的符号,链接器会告诉我们下一个是谁。
|
||||
//
|
||||
// RenderCoolTimeBox:40250 的扇形顶点原样搬来,画法改成 UIRenderCommands(见函数注释)。
|
||||
#include "EterPythonLib/StdAfx.h"
|
||||
#include "EterPythonLib/PythonGraphic.h"
|
||||
#include "EterLib/StateManager.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
#include "../EterLib/UIRenderCommands.h"
|
||||
#include <algorithm>
|
||||
|
||||
// 40250 PythonGraphic.cpp:611
|
||||
CPythonGraphic::CPythonGraphic()
|
||||
{
|
||||
m_lightColor = GetColor(1.0f, 1.0f, 1.0f);
|
||||
m_darkColor = GetColor(0.0f, 0.0f, 0.0f);
|
||||
|
||||
memset(&m_backupViewport, 0, sizeof(D3DVIEWPORT8));
|
||||
|
||||
m_fOrthoDepth = 1000.0f;
|
||||
}
|
||||
|
||||
CPythonGraphic::~CPythonGraphic()
|
||||
{
|
||||
Tracef("Python Graphic Clear\n");
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
class CDummyDirect3D8 : public IDirect3D8
|
||||
{
|
||||
public:
|
||||
HRESULT GetAdapterIdentifier(UINT, DWORD, D3DADAPTER_IDENTIFIER8* pIdentifier) override
|
||||
{
|
||||
if (!pIdentifier) return E_FAIL;
|
||||
memset(pIdentifier, 0, sizeof(D3DADAPTER_IDENTIFIER8));
|
||||
strncpy(pIdentifier->Driver, "dummy", sizeof(pIdentifier->Driver));
|
||||
strncpy(pIdentifier->Description, "Dummy Direct3D8", sizeof(pIdentifier->Description));
|
||||
return S_OK;
|
||||
}
|
||||
HRESULT GetAdapterDisplayMode(UINT, D3DDISPLAYMODE* pMode) override
|
||||
{
|
||||
if (!pMode) return E_FAIL;
|
||||
pMode->Width = 1024;
|
||||
pMode->Height = 768;
|
||||
pMode->RefreshRate = 60;
|
||||
pMode->Format = D3DFMT_X8R8G8B8;
|
||||
return S_OK;
|
||||
}
|
||||
UINT GetAdapterModeCount(UINT) override
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
HRESULT EnumAdapterModes(UINT, UINT Mode, D3DDISPLAYMODE* pMode) override
|
||||
{
|
||||
if (!pMode) return E_FAIL;
|
||||
static const D3DDISPLAYMODE s_modes[] = {
|
||||
{ 800, 600, 60, D3DFMT_X8R8G8B8 },
|
||||
{ 1024, 768, 60, D3DFMT_X8R8G8B8 },
|
||||
{ 1280, 1024, 60, D3DFMT_X8R8G8B8 },
|
||||
};
|
||||
if (Mode >= 3) return E_FAIL;
|
||||
*pMode = s_modes[Mode];
|
||||
return S_OK;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
LPDIRECT3D8 CPythonGraphic::GetD3D()
|
||||
{
|
||||
static CDummyDirect3D8 s_dummyD3D;
|
||||
return &s_dummyD3D;
|
||||
}
|
||||
|
||||
void CPythonGraphic::SetViewport(float x, float y, float width, float height)
|
||||
{
|
||||
if (ms_lpd3dDevice)
|
||||
{
|
||||
ms_lpd3dDevice->GetViewport(&m_backupViewport);
|
||||
D3DVIEWPORT8 vp = { (DWORD)x, (DWORD)y, (DWORD)width, (DWORD)height, 0.0f, 1.0f };
|
||||
ms_lpd3dDevice->SetViewport(&vp);
|
||||
}
|
||||
UIRenderSetClip(x, y, width, height);
|
||||
}
|
||||
|
||||
void CPythonGraphic::RestoreViewport()
|
||||
{
|
||||
if (ms_lpd3dDevice)
|
||||
ms_lpd3dDevice->SetViewport(&m_backupViewport);
|
||||
UIRenderRestoreClip();
|
||||
}
|
||||
void CPythonGraphic::SetOmniLight()
|
||||
{
|
||||
D3DMATERIAL8 Material;
|
||||
Material.Ambient = D3DXCOLOR(0.3f, 0.3f, 0.3f, 1.0f);
|
||||
Material.Diffuse = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
Material.Emissive = D3DXCOLOR(0.1f, 0.1f, 0.1f, 1.0f);
|
||||
STATEMANAGER.SetMaterial(&Material);
|
||||
|
||||
D3DLIGHT8 Light;
|
||||
Light.Type = D3DLIGHT_SPOT;
|
||||
Light.Position = D3DXVECTOR3(50.0f, 150.0f, 350.0f);
|
||||
Light.Direction = D3DXVECTOR3(-0.15f, -0.3f, -0.9f);
|
||||
Light.Theta = D3DXToRadian(30.0f);
|
||||
Light.Phi = D3DXToRadian(45.0f);
|
||||
Light.Falloff = 1.0f;
|
||||
Light.Attenuation0 = 0.0f;
|
||||
Light.Attenuation1 = 0.005f;
|
||||
Light.Attenuation2 = 0.0f;
|
||||
Light.Diffuse.r = 1.0f;
|
||||
Light.Diffuse.g = 1.0f;
|
||||
Light.Diffuse.b = 1.0f;
|
||||
Light.Diffuse.a = 1.0f;
|
||||
Light.Ambient.r = 1.0f;
|
||||
Light.Ambient.g = 1.0f;
|
||||
Light.Ambient.b = 1.0f;
|
||||
Light.Ambient.a = 1.0f;
|
||||
Light.Range = 500.0f;
|
||||
if (ms_lpd3dDevice)
|
||||
{
|
||||
ms_lpd3dDevice->SetLight(0, &Light);
|
||||
ms_lpd3dDevice->LightEnable(0, TRUE);
|
||||
|
||||
Light.Type = D3DLIGHT_POINT;
|
||||
Light.Position = D3DXVECTOR3(0.0f, 200.0f, 200.0f);
|
||||
Light.Attenuation0 = 0.1f;
|
||||
Light.Attenuation1 = 0.01f;
|
||||
Light.Attenuation2 = 0.0f;
|
||||
ms_lpd3dDevice->SetLight(1, &Light);
|
||||
ms_lpd3dDevice->LightEnable(1, TRUE);
|
||||
}
|
||||
}
|
||||
long CPythonGraphic::GenerateColor(float r, float g, float b, float a) { return GetColor(r, g, b, a); }
|
||||
// 40250 PythonGraphic.cpp:580
|
||||
void CPythonGraphic::RenderDownButton(float sx, float sy, float ex, float ey)
|
||||
{
|
||||
RenderBox2d(sx, sy, ex, ey);
|
||||
|
||||
SetDiffuseColor(m_darkColor);
|
||||
RenderLine2d(sx, sy, ex, sy);
|
||||
RenderLine2d(sx, sy, sx, ey);
|
||||
|
||||
SetDiffuseColor(m_lightColor);
|
||||
RenderLine2d(sx, ey, ex, ey);
|
||||
RenderLine2d(ex, sy, ex, ey);
|
||||
}
|
||||
|
||||
// 40250 PythonGraphic.cpp:593
|
||||
void CPythonGraphic::RenderUpButton(float sx, float sy, float ex, float ey)
|
||||
{
|
||||
RenderBox2d(sx, sy, ex, ey);
|
||||
|
||||
SetDiffuseColor(m_lightColor);
|
||||
RenderLine2d(sx, sy, ex, sy);
|
||||
RenderLine2d(sx, sy, sx, ey);
|
||||
|
||||
SetDiffuseColor(m_darkColor);
|
||||
RenderLine2d(sx, ey, ex, ey);
|
||||
RenderLine2d(ex, sy, ex, ey);
|
||||
}
|
||||
bool CPythonGraphic::SaveScreenShot(const char*) { MT_PLATFORM_STUB(); return false; }
|
||||
// 40250 PythonGraphic.cpp:52.
|
||||
void CPythonGraphic::SetCursorPosition(int x, int y)
|
||||
{
|
||||
CScreen::SetCursorPosition(x, y, ms_iWidth, ms_iHeight);
|
||||
}
|
||||
// 40250 PythonGraphic.cpp:606
|
||||
DWORD CPythonGraphic::GetAvailableMemory()
|
||||
{
|
||||
return ms_lpd3dDevice->GetAvailableTextureMem();
|
||||
}
|
||||
// 40250 PythonGraphic.cpp:124. The recording device reports no D3DCAPS2_FULLSCREENGAMMA (the frame
|
||||
// goes to a window), so this returns at the caps check like 40250 does on such a device.
|
||||
void CPythonGraphic::SetGamma(float fGammaFactor)
|
||||
{
|
||||
D3DCAPS8 d3dCaps;
|
||||
D3DGAMMARAMP NewRamp;
|
||||
int ui, val;
|
||||
|
||||
ms_lpd3dDevice->GetDeviceCaps(&d3dCaps);
|
||||
|
||||
if (D3DCAPS2_FULLSCREENGAMMA != (d3dCaps.Caps2 & D3DCAPS2_FULLSCREENGAMMA))
|
||||
return;
|
||||
|
||||
for (int i = 0; i < 256; ++i)
|
||||
{
|
||||
val = (int) (i * fGammaFactor * 255.0f);
|
||||
ui = 0;
|
||||
|
||||
if (val > 32767)
|
||||
{
|
||||
val = val - 32767;
|
||||
ui = 1;
|
||||
}
|
||||
|
||||
if (val > 32767)
|
||||
val = 32767;
|
||||
|
||||
NewRamp.red[i] = (WORD) (val | (32768 * ui));
|
||||
NewRamp.green[i] = (WORD) (val | (32768 * ui));
|
||||
NewRamp.blue[i] = (WORD) (val | (32768 * ui));
|
||||
}
|
||||
|
||||
ms_lpd3dDevice->SetGammaRamp(D3DSGR_NO_CALIBRATION, &NewRamp);
|
||||
}
|
||||
|
||||
// 40250 PythonGraphic.cpp:491. The fan's vertices are built verbatim.
|
||||
// PORT: 40250 draws them as a D3DPT_TRIANGLEFAN with untextured SELECTARG1(DIFFUSE) colour and alpha;
|
||||
// the UI here is drawn from UIRenderCommands, so each fan triangle goes out as one untextured quad
|
||||
// command (vertices centre, j, j+1, j+1) in the same half-transparent black.
|
||||
void CPythonGraphic::RenderCoolTimeBox(float fxCenter, float fyCenter, float fRadius, float fTime)
|
||||
{
|
||||
if (fTime >= 1.0f)
|
||||
return;
|
||||
|
||||
fTime = max(0.0f, fTime);
|
||||
|
||||
static D3DXCOLOR color = D3DXCOLOR(0.0f, 0.0f, 0.0f, 0.5f);
|
||||
static D3DXVECTOR2 s_v2BoxPos[8] =
|
||||
{
|
||||
D3DXVECTOR2( -1.0f, -1.0f ),
|
||||
D3DXVECTOR2( -1.0f, 0.0f ),
|
||||
D3DXVECTOR2( -1.0f, +1.0f ),
|
||||
D3DXVECTOR2( 0.0f, +1.0f ),
|
||||
D3DXVECTOR2( +1.0f, +1.0f ),
|
||||
D3DXVECTOR2( +1.0f, 0.0f ),
|
||||
D3DXVECTOR2( +1.0f, -1.0f ),
|
||||
D3DXVECTOR2( 0.0f, -1.0f ),
|
||||
};
|
||||
|
||||
int iTriCount = int(8 - 8.0f * fTime);
|
||||
float fLastPercentage = (8 - 8.0f * fTime) - iTriCount;
|
||||
|
||||
std::vector<TPDTVertex> vertices;
|
||||
TPDTVertex vertex;
|
||||
vertex.position.x = fxCenter;
|
||||
vertex.position.y = fyCenter;
|
||||
vertex.position.z = 0.0f;
|
||||
vertex.diffuse = color;
|
||||
vertex.texCoord.x = 0.0f;
|
||||
vertex.texCoord.x = 0.0f;
|
||||
vertices.push_back(vertex);
|
||||
vertex.position.x = fxCenter;
|
||||
vertex.position.y = fyCenter - fRadius;
|
||||
vertex.position.z = 0.0f;
|
||||
vertex.diffuse = color;
|
||||
vertex.texCoord.x = 0.0f;
|
||||
vertex.texCoord.x = 0.0f;
|
||||
vertices.push_back(vertex);
|
||||
|
||||
for (int j = 0; j < iTriCount; ++j)
|
||||
{
|
||||
vertex.position.x = fxCenter + s_v2BoxPos[j].x * fRadius;
|
||||
vertex.position.y = fyCenter + s_v2BoxPos[j].y * fRadius;
|
||||
vertices.push_back(vertex);
|
||||
}
|
||||
|
||||
if (fLastPercentage > 0.0f)
|
||||
{
|
||||
D3DXVECTOR2 * pv2Pos;
|
||||
D3DXVECTOR2 * pv2LastPos;
|
||||
|
||||
assert((iTriCount-1+8)%8 >= 0 && (iTriCount-1+8)%8 < 8);
|
||||
assert((iTriCount+8)%8 >= 0 && (iTriCount+8)%8 < 8);
|
||||
pv2LastPos = &s_v2BoxPos[(iTriCount-1+8)%8];
|
||||
pv2Pos = &s_v2BoxPos[(iTriCount+8)%8];
|
||||
|
||||
vertex.position.x = fxCenter + ((pv2Pos->x-pv2LastPos->x) * fLastPercentage + pv2LastPos->x) * fRadius;
|
||||
vertex.position.y = fyCenter + ((pv2Pos->y-pv2LastPos->y) * fLastPercentage + pv2LastPos->y) * fRadius;
|
||||
vertices.push_back(vertex);
|
||||
++iTriCount;
|
||||
}
|
||||
|
||||
if (vertices.empty())
|
||||
return;
|
||||
|
||||
// PORT: the D3DPT_TRIANGLEFAN of iTriCount triangles, see the function comment.
|
||||
const std::uint32_t argb = UIRenderColor(color);
|
||||
for (int j = 0; j < iTriCount && j + 2 < int(vertices.size()); ++j)
|
||||
{
|
||||
const TPDTVertex* fan[4] = { &vertices[0], &vertices[j + 1], &vertices[j + 2], &vertices[j + 2] };
|
||||
UIRenderCommand command{UIRenderCommand::Bar, 0, 0, 0, 0, argb};
|
||||
for (int k = 0; k < 4; ++k)
|
||||
{
|
||||
command.qx[k] = fan[k]->position.x;
|
||||
command.qy[k] = fan[k]->position.y;
|
||||
}
|
||||
command.x1 = std::min({command.qx[0], command.qx[1], command.qx[2]});
|
||||
command.y1 = std::min({command.qy[0], command.qy[1], command.qy[2]});
|
||||
command.x2 = std::max({command.qx[0], command.qx[1], command.qx[2]});
|
||||
command.y2 = std::max({command.qy[0], command.qy[1], command.qy[2]});
|
||||
command.quad = true;
|
||||
UIRenderAdd(std::move(command));
|
||||
}
|
||||
}
|
||||
|
||||
// 40250 PythonGraphic.cpp:17
|
||||
float CPythonGraphic::GetOrthoDepth()
|
||||
{
|
||||
return m_fOrthoDepth;
|
||||
}
|
||||
|
||||
// 40250 PythonGraphic.cpp:22
|
||||
void CPythonGraphic::SetInterfaceRenderState()
|
||||
{
|
||||
STATEMANAGER.SetTransform(D3DTS_PROJECTION, &ms_matIdentity);
|
||||
STATEMANAGER.SetTransform(D3DTS_VIEW, &ms_matIdentity);
|
||||
STATEMANAGER.SetTransform(D3DTS_WORLD, &ms_matIdentity);
|
||||
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_MINFILTER, D3DTEXF_NONE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_MAGFILTER, D3DTEXF_NONE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_MIPFILTER, D3DTEXF_NONE);
|
||||
|
||||
STATEMANAGER.SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
|
||||
STATEMANAGER.SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
|
||||
STATEMANAGER.SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
|
||||
|
||||
CPythonGraphic::Instance().SetBlendOperation();
|
||||
CPythonGraphic::Instance().SetOrtho2D(ms_iWidth, ms_iHeight, GetOrthoDepth());
|
||||
|
||||
STATEMANAGER.SetRenderState(D3DRS_LIGHTING, FALSE);
|
||||
}
|
||||
|
||||
// 40250 PythonGraphic.cpp:42
|
||||
void CPythonGraphic::SetGameRenderState()
|
||||
{
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_MINFILTER, D3DTEXF_LINEAR);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_MIPFILTER, D3DTEXF_LINEAR);
|
||||
|
||||
STATEMANAGER.SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
|
||||
STATEMANAGER.SetRenderState(D3DRS_LIGHTING, TRUE);
|
||||
}
|
||||
|
||||
// 40250 PythonGraphic.cpp:387
|
||||
void CPythonGraphic::PushState()
|
||||
{
|
||||
TState curState;
|
||||
|
||||
curState.matProj = ms_matProj;
|
||||
curState.matView = ms_matView;
|
||||
//STATEMANAGER.SaveTransform(D3DTS_WORLD, &m_SaveWorldMatrix);
|
||||
|
||||
m_stateStack.push(curState);
|
||||
//CCamera::Instance().PushParams();
|
||||
}
|
||||
|
||||
// 40250 PythonGraphic.cpp:399
|
||||
void CPythonGraphic::PopState()
|
||||
{
|
||||
if (m_stateStack.empty())
|
||||
{
|
||||
assert(!"PythonGraphic::PopState StateStack is EMPTY");
|
||||
return;
|
||||
}
|
||||
|
||||
TState & rState = m_stateStack.top();
|
||||
|
||||
//STATEMANAGER.RestoreTransform(D3DTS_WORLD);
|
||||
ms_matProj = rState.matProj;
|
||||
ms_matView = rState.matView;
|
||||
|
||||
UpdatePipeLineMatrix();
|
||||
|
||||
m_stateStack.pop();
|
||||
//CCamera::Instance().PopParams();
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// Platform implementation of 40250 GameLib/MapOutdoorCharacterShadow.cpp.
|
||||
#include "GameLib/StdAfx.h"
|
||||
#include "EterLib/StateManager.h"
|
||||
#include "EterLib/Camera.h"
|
||||
|
||||
#include "GameLib/MapOutdoor.h"
|
||||
|
||||
static int recreate = false;
|
||||
|
||||
void CMapOutdoor::SetShadowTextureSize(WORD size)
|
||||
{
|
||||
if (m_wShadowMapSize != size)
|
||||
{
|
||||
recreate = true;
|
||||
Tracenf("ShadowTextureSize changed %d -> %d", m_wShadowMapSize, size);
|
||||
}
|
||||
|
||||
m_wShadowMapSize = size;
|
||||
}
|
||||
|
||||
void CMapOutdoor::CreateCharacterShadowTexture()
|
||||
{
|
||||
extern bool GRAPHICS_CAPS_CAN_NOT_DRAW_SHADOW;
|
||||
|
||||
if (GRAPHICS_CAPS_CAN_NOT_DRAW_SHADOW)
|
||||
return;
|
||||
|
||||
ReleaseCharacterShadowTexture();
|
||||
|
||||
if (IsLowTextureMemory())
|
||||
SetShadowTextureSize(128);
|
||||
|
||||
m_ShadowMapViewport.X = 1;
|
||||
m_ShadowMapViewport.Y = 1;
|
||||
m_ShadowMapViewport.Width = m_wShadowMapSize - 2;
|
||||
m_ShadowMapViewport.Height = m_wShadowMapSize - 2;
|
||||
m_ShadowMapViewport.MinZ = 0.0f;
|
||||
m_ShadowMapViewport.MaxZ = 1.0f;
|
||||
|
||||
if (FAILED(ms_lpd3dDevice->CreateTexture(m_wShadowMapSize, m_wShadowMapSize, 1, D3DUSAGE_RENDERTARGET, D3DFMT_R5G6B5, D3DPOOL_DEFAULT, &m_lpCharacterShadowMapTexture)))
|
||||
{
|
||||
TraceError("CMapOutdoor Unable to create Character Shadow render target texture\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (FAILED(m_lpCharacterShadowMapTexture->GetSurfaceLevel(0, &m_lpCharacterShadowMapRenderTargetSurface)))
|
||||
{
|
||||
TraceError("CMapOutdoor Unable to GetSurfaceLevel Character Shadow render target texture\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (FAILED(ms_lpd3dDevice->CreateDepthStencilSurface(m_wShadowMapSize, m_wShadowMapSize, D3DFMT_D16, D3DMULTISAMPLE_NONE, &m_lpCharacterShadowMapDepthSurface)))
|
||||
{
|
||||
TraceError("CMapOutdoor Unable to create Character Shadow depth Surface\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void CMapOutdoor::ReleaseCharacterShadowTexture()
|
||||
{
|
||||
SAFE_RELEASE(m_lpCharacterShadowMapRenderTargetSurface);
|
||||
SAFE_RELEASE(m_lpCharacterShadowMapDepthSurface);
|
||||
SAFE_RELEASE(m_lpCharacterShadowMapTexture);
|
||||
}
|
||||
|
||||
static DWORD dwLightEnable = FALSE;
|
||||
static bool shadowPassBegun = false;
|
||||
|
||||
bool CMapOutdoor::BeginRenderCharacterShadowToTexture()
|
||||
{
|
||||
CCamera* camera = CCameraManager::Instance().GetCurrentCamera();
|
||||
if (!camera)
|
||||
return false;
|
||||
if (recreate)
|
||||
{
|
||||
CreateCharacterShadowTexture();
|
||||
recreate = false;
|
||||
}
|
||||
if (!m_lpCharacterShadowMapRenderTargetSurface || !m_lpCharacterShadowMapDepthSurface)
|
||||
return false;
|
||||
shadowPassBegun = true;
|
||||
|
||||
const D3DXVECTOR3 target = camera->GetTarget();
|
||||
const D3DXVECTOR3 eye(target.x - 1.732f * 1250.0f,
|
||||
target.y - 1250.0f, target.z + 2.0f * 1.732f * 1250.0f);
|
||||
const D3DXVECTOR3 up(0.0f, 0.0f, 1.0f);
|
||||
D3DXMATRIX lightView, lightProj;
|
||||
D3DXMatrixLookAtRH(&lightView, &eye, &target, &up);
|
||||
D3DXMatrixOrthoRH(&lightProj, 2550.0f, 2550.0f, 1.0f, 15000.0f);
|
||||
STATEMANAGER.SaveTransform(D3DTS_VIEW, &lightView);
|
||||
STATEMANAGER.SaveTransform(D3DTS_PROJECTION, &lightProj);
|
||||
dwLightEnable = STATEMANAGER.GetRenderState(D3DRS_LIGHTING);
|
||||
STATEMANAGER.SetRenderState(D3DRS_LIGHTING, FALSE);
|
||||
STATEMANAGER.SaveRenderState(D3DRS_TEXTUREFACTOR, 0xFF808080);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TFACTOR);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
|
||||
|
||||
bool success = SUCCEEDED(ms_lpd3dDevice->GetRenderTarget(&m_lpBackupRenderTargetSurface));
|
||||
success = SUCCEEDED(ms_lpd3dDevice->GetDepthStencilSurface(&m_lpBackupDepthSurface)) && success;
|
||||
success = SUCCEEDED(ms_lpd3dDevice->SetRenderTarget(m_lpCharacterShadowMapRenderTargetSurface,
|
||||
m_lpCharacterShadowMapDepthSurface)) && success;
|
||||
success = SUCCEEDED(ms_lpd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
|
||||
0xFFFFFFFFu, 1.0f, 0)) && success;
|
||||
success = SUCCEEDED(ms_lpd3dDevice->GetViewport(&m_BackupViewport)) && success;
|
||||
success = SUCCEEDED(ms_lpd3dDevice->SetViewport(&m_ShadowMapViewport)) && success;
|
||||
return success;
|
||||
}
|
||||
|
||||
void CMapOutdoor::EndRenderCharacterShadowToTexture()
|
||||
{
|
||||
if (!shadowPassBegun)
|
||||
return;
|
||||
shadowPassBegun = false;
|
||||
ms_lpd3dDevice->SetViewport(&m_BackupViewport);
|
||||
if (m_lpBackupRenderTargetSurface && m_lpBackupDepthSurface)
|
||||
ms_lpd3dDevice->SetRenderTarget(m_lpBackupRenderTargetSurface, m_lpBackupDepthSurface);
|
||||
SAFE_RELEASE(m_lpBackupRenderTargetSurface);
|
||||
SAFE_RELEASE(m_lpBackupDepthSurface);
|
||||
STATEMANAGER.RestoreTransform(D3DTS_VIEW);
|
||||
STATEMANAGER.RestoreTransform(D3DTS_PROJECTION);
|
||||
STATEMANAGER.SetRenderState(D3DRS_LIGHTING, dwLightEnable);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_TEXTUREFACTOR);
|
||||
}
|
||||
@@ -0,0 +1,705 @@
|
||||
// Platform copy of 40250 GameLib/MapOutdoorRenderHTP.cpp (hardware-transform terrain patches), as is
|
||||
// apart from the includes and the native-terrain gate: the Godot Metin2World adapter draws the terrain
|
||||
// (PORT-PLAN §3) unless IsNativeTerrainRenderEnabled() (MT_NATIVE_TERRAIN=1 or the standalone native
|
||||
// renderer). 40250 takes this path on a fast-T&L device with auto tiling, or SOFTWARE_TILING 0.
|
||||
#include "GameLib/StdAfx.h"
|
||||
#include "GameLib/MapOutdoor.h"
|
||||
|
||||
#include "EterLib/StateManager.h"
|
||||
#include "../EterLib/RenderCommands3D.h"
|
||||
|
||||
void CMapOutdoor::__RenderTerrain_RenderHardwareTransformPatch()
|
||||
{
|
||||
// PORT: see the file comment.
|
||||
if (!IsNativeTerrainRenderEnabled())
|
||||
return;
|
||||
|
||||
DWORD dwFogColor;
|
||||
float fFogFarDistance;
|
||||
float fFogNearDistance;
|
||||
if (mc_pEnvironmentData)
|
||||
{
|
||||
dwFogColor=mc_pEnvironmentData->FogColor;
|
||||
fFogNearDistance=mc_pEnvironmentData->GetFogNearDistance();
|
||||
fFogFarDistance=mc_pEnvironmentData->GetFogFarDistance();
|
||||
}
|
||||
else
|
||||
{
|
||||
dwFogColor=0xffffffff;
|
||||
fFogNearDistance=5000.0f;
|
||||
fFogFarDistance=10000.0f;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Render State & TextureStageState
|
||||
|
||||
STATEMANAGER.SaveTextureStageState(0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION);
|
||||
STATEMANAGER.SaveTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2);
|
||||
STATEMANAGER.SaveTextureStageState(1, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION);
|
||||
STATEMANAGER.SaveTextureStageState(1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2);
|
||||
|
||||
STATEMANAGER.SaveRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
|
||||
STATEMANAGER.SaveRenderState(D3DRS_ALPHATESTENABLE, TRUE);
|
||||
STATEMANAGER.SaveRenderState(D3DRS_ALPHAREF, 0x00000000);
|
||||
STATEMANAGER.SaveRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATER);
|
||||
|
||||
STATEMANAGER.SaveRenderState(D3DRS_TEXTUREFACTOR, dwFogColor);
|
||||
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_CURRENT);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP);
|
||||
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP);
|
||||
|
||||
#ifdef WORLD_EDITOR
|
||||
if (GetAsyncKeyState(VK_CAPITAL))
|
||||
{
|
||||
CSpeedTreeWrapper::ms_bSelfShadowOn = false;
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_MINFILTER, D3DTEXF_GAUSSIANCUBIC);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_MAGFILTER, D3DTEXF_GAUSSIANCUBIC);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_MIPFILTER, D3DTEXF_GAUSSIANCUBIC);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_MINFILTER, D3DTEXF_GAUSSIANCUBIC);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_MAGFILTER, D3DTEXF_GAUSSIANCUBIC);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_MIPFILTER, D3DTEXF_GAUSSIANCUBIC);
|
||||
}
|
||||
else
|
||||
{
|
||||
CSpeedTreeWrapper::ms_bSelfShadowOn = true;
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_MINFILTER, D3DTEXF_LINEAR);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_MIPFILTER, D3DTEXF_LINEAR);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_MINFILTER, D3DTEXF_LINEAR);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_MAGFILTER, D3DTEXF_LINEAR);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_MIPFILTER, D3DTEXF_LINEAR);
|
||||
}
|
||||
#else
|
||||
CSpeedTreeWrapper::ms_bSelfShadowOn = true;
|
||||
STATEMANAGER.SetBestFiltering(0);
|
||||
STATEMANAGER.SetBestFiltering(1);
|
||||
#endif
|
||||
|
||||
m_matWorldForCommonUse._41 = 0.0f;
|
||||
m_matWorldForCommonUse._42 = 0.0f;
|
||||
STATEMANAGER.SetTransform(D3DTS_WORLD, &m_matWorldForCommonUse);
|
||||
|
||||
STATEMANAGER.SaveTransform(D3DTS_TEXTURE0, &m_matWorldForCommonUse);
|
||||
STATEMANAGER.SaveTransform(D3DTS_TEXTURE1, &m_matWorldForCommonUse);
|
||||
|
||||
// Render State & TextureStageState
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
STATEMANAGER.SetVertexShader(D3DFVF_XYZ | D3DFVF_NORMAL);
|
||||
|
||||
m_iRenderedSplatNumSqSum = 0;
|
||||
m_iRenderedPatchNum = 0;
|
||||
m_iRenderedSplatNum = 0;
|
||||
m_RenderedTextureNumVector.clear();
|
||||
|
||||
std::pair<float, long> fog_far(fFogFarDistance+1600.0f, 0);
|
||||
std::pair<float, long> fog_near(fFogNearDistance-3200.0f, 0);
|
||||
|
||||
std::vector<std::pair<float ,long> >::iterator far_it = std::upper_bound(m_PatchVector.begin(),m_PatchVector.end(),fog_far);
|
||||
std::vector<std::pair<float ,long> >::iterator near_it = std::upper_bound(m_PatchVector.begin(),m_PatchVector.end(),fog_near);
|
||||
|
||||
// NOTE: Word Editor 툴에서는 fog far보다 멀리있는 물체를 텍스쳐 없이 그리는 작업을 하지 않음
|
||||
#ifdef WORLD_EDITOR
|
||||
near_it = m_PatchVector.begin();
|
||||
far_it = m_PatchVector.end();
|
||||
#endif
|
||||
|
||||
WORD wPrimitiveCount;
|
||||
D3DPRIMITIVETYPE ePrimitiveType;
|
||||
|
||||
BYTE byCUrrentLODLevel = 0;
|
||||
|
||||
float fLODLevel1Distance = __GetNoFogDistance();
|
||||
float fLODLevel2Distance = __GetFogDistance();
|
||||
|
||||
SelectIndexBuffer(0, &wPrimitiveCount, &ePrimitiveType);
|
||||
|
||||
DWORD dwFogEnable = STATEMANAGER.GetRenderState(D3DRS_FOGENABLE);
|
||||
std::vector<std::pair<float, long> >::iterator it = m_PatchVector.begin();
|
||||
|
||||
// NOTE: 맵툴에서는 view ~ fog near 사이의 지형을 fog disabled 상태로 그리는 작업을 하지 않음.
|
||||
#ifndef WORLD_EDITOR
|
||||
STATEMANAGER.SetRenderState(D3DRS_FOGENABLE, FALSE);
|
||||
|
||||
for( ; it != near_it; ++it)
|
||||
{
|
||||
if (byCUrrentLODLevel == 0 && fLODLevel1Distance <= it->first)
|
||||
{
|
||||
byCUrrentLODLevel = 1;
|
||||
SelectIndexBuffer(1, &wPrimitiveCount, &ePrimitiveType);
|
||||
}
|
||||
else if (byCUrrentLODLevel == 1 && fLODLevel2Distance <= it->first)
|
||||
{
|
||||
byCUrrentLODLevel = 2;
|
||||
SelectIndexBuffer(2, &wPrimitiveCount, &ePrimitiveType);
|
||||
}
|
||||
|
||||
__HardwareTransformPatch_RenderPatchSplat(it->second, wPrimitiveCount, ePrimitiveType);
|
||||
if (m_iRenderedSplatNum >= m_iSplatLimit)
|
||||
break;
|
||||
|
||||
if (m_bDrawWireFrame)
|
||||
DrawWireFrame(it->second, wPrimitiveCount, ePrimitiveType);
|
||||
}
|
||||
#endif
|
||||
|
||||
STATEMANAGER.SetRenderState(D3DRS_FOGENABLE, dwFogEnable);
|
||||
|
||||
if (m_iRenderedSplatNum < m_iSplatLimit)
|
||||
{
|
||||
for(it = near_it; it != far_it; ++it)
|
||||
{
|
||||
if (byCUrrentLODLevel == 0 && fLODLevel1Distance <= it->first)
|
||||
{
|
||||
byCUrrentLODLevel = 1;
|
||||
SelectIndexBuffer(1, &wPrimitiveCount, &ePrimitiveType);
|
||||
}
|
||||
else if (byCUrrentLODLevel == 1 && fLODLevel2Distance <= it->first)
|
||||
{
|
||||
byCUrrentLODLevel = 2;
|
||||
SelectIndexBuffer(2, &wPrimitiveCount, &ePrimitiveType);
|
||||
}
|
||||
|
||||
__HardwareTransformPatch_RenderPatchSplat(it->second, wPrimitiveCount, ePrimitiveType);
|
||||
|
||||
if (m_iRenderedSplatNum >= m_iSplatLimit)
|
||||
break;
|
||||
|
||||
if (m_bDrawWireFrame)
|
||||
DrawWireFrame(it->second, wPrimitiveCount, ePrimitiveType);
|
||||
}
|
||||
}
|
||||
|
||||
STATEMANAGER.SetRenderState(D3DRS_FOGENABLE, FALSE);
|
||||
STATEMANAGER.SetRenderState(D3DRS_LIGHTING, FALSE);
|
||||
|
||||
STATEMANAGER.SetTexture(0, NULL);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, FALSE);
|
||||
|
||||
STATEMANAGER.SetTexture(1, NULL);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_TEXTURETRANSFORMFLAGS, FALSE);
|
||||
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TFACTOR);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
|
||||
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
|
||||
|
||||
if (m_iRenderedSplatNum < m_iSplatLimit)
|
||||
{
|
||||
for(it = far_it; it != m_PatchVector.end(); ++it)
|
||||
{
|
||||
if (byCUrrentLODLevel == 0 && fLODLevel1Distance <= it->first)
|
||||
{
|
||||
byCUrrentLODLevel = 1;
|
||||
SelectIndexBuffer(1, &wPrimitiveCount, &ePrimitiveType);
|
||||
}
|
||||
else if (byCUrrentLODLevel == 1 && fLODLevel2Distance <= it->first)
|
||||
{
|
||||
byCUrrentLODLevel = 2;
|
||||
SelectIndexBuffer(2, &wPrimitiveCount, &ePrimitiveType);
|
||||
}
|
||||
|
||||
__HardwareTransformPatch_RenderPatchNone(it->second, wPrimitiveCount, ePrimitiveType);
|
||||
|
||||
if (m_iRenderedSplatNum >= m_iSplatLimit)
|
||||
break;
|
||||
|
||||
if (m_bDrawWireFrame)
|
||||
DrawWireFrame(it->second, wPrimitiveCount, ePrimitiveType);
|
||||
}
|
||||
}
|
||||
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_CURRENT);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||
|
||||
STATEMANAGER.SetRenderState(D3DRS_FOGENABLE, dwFogEnable);
|
||||
STATEMANAGER.SetRenderState(D3DRS_LIGHTING, TRUE);
|
||||
|
||||
std::sort(m_RenderedTextureNumVector.begin(),m_RenderedTextureNumVector.end());
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Render State & TextureStageState
|
||||
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_TEXTUREFACTOR);
|
||||
|
||||
STATEMANAGER.RestoreTransform(D3DTS_TEXTURE0);
|
||||
STATEMANAGER.RestoreTransform(D3DTS_TEXTURE1);
|
||||
|
||||
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_TEXCOORDINDEX);
|
||||
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS);
|
||||
STATEMANAGER.RestoreTextureStageState(1, D3DTSS_TEXCOORDINDEX);
|
||||
STATEMANAGER.RestoreTextureStageState(1, D3DTSS_TEXTURETRANSFORMFLAGS);
|
||||
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_ALPHABLENDENABLE);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_ALPHATESTENABLE);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_ALPHAREF);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_ALPHAFUNC);
|
||||
|
||||
// Render State & TextureStageState
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
|
||||
void CMapOutdoor::__HardwareTransformPatch_RenderPatchSplat(long patchnum, WORD wPrimitiveCount, D3DPRIMITIVETYPE ePrimitiveType)
|
||||
{
|
||||
assert(NULL!=m_pTerrainPatchProxyList && "__HardwareTransformPatch_RenderPatchSplat");
|
||||
CTerrainPatchProxy * pTerrainPatchProxy = &m_pTerrainPatchProxyList[patchnum];
|
||||
|
||||
if (!pTerrainPatchProxy->isUsed())
|
||||
return;
|
||||
|
||||
long sPatchNum = pTerrainPatchProxy->GetPatchNum();
|
||||
if (sPatchNum < 0)
|
||||
return;
|
||||
|
||||
BYTE ucTerrainNum = pTerrainPatchProxy->GetTerrainNum();
|
||||
if (0xFF == ucTerrainNum)
|
||||
return;
|
||||
|
||||
CTerrain * pTerrain;
|
||||
if (!GetTerrainPointer(ucTerrainNum, &pTerrain))
|
||||
return;
|
||||
|
||||
DWORD dwFogColor;
|
||||
if (mc_pEnvironmentData)
|
||||
dwFogColor=mc_pEnvironmentData->FogColor;
|
||||
else
|
||||
dwFogColor=0xffffffff;
|
||||
|
||||
WORD wCoordX, wCoordY;
|
||||
pTerrain->GetCoordinate(&wCoordX, &wCoordY);
|
||||
|
||||
TTerrainSplatPatch & rTerrainSplatPatch = pTerrain->GetTerrainSplatPatch();
|
||||
|
||||
D3DXMATRIX matTexTransform, matSplatAlphaTexTransform, matSplatColorTexTransform;
|
||||
m_matWorldForCommonUse._41 = -(float) (wCoordX * CTerrainImpl::TERRAIN_XSIZE);
|
||||
m_matWorldForCommonUse._42 = (float) (wCoordY * CTerrainImpl::TERRAIN_YSIZE);
|
||||
D3DXMatrixMultiply(&matTexTransform, &m_matViewInverse, &m_matWorldForCommonUse);
|
||||
D3DXMatrixMultiply(&matSplatAlphaTexTransform, &matTexTransform, &m_matSplatAlpha);
|
||||
STATEMANAGER.SetTransform(D3DTS_TEXTURE1, &matSplatAlphaTexTransform);
|
||||
|
||||
D3DXMATRIX matTiling;
|
||||
D3DXMatrixScaling(&matTiling, 1.0f/640.0f, -1.0f/640.0f, 0.0f);
|
||||
matTiling._41=0.0f;
|
||||
matTiling._42=0.0f;
|
||||
|
||||
D3DXMatrixMultiply(&matSplatColorTexTransform, &m_matViewInverse, &matTiling);
|
||||
STATEMANAGER.SetTransform(D3DTS_TEXTURE0, &matSplatColorTexTransform);
|
||||
|
||||
CGraphicVertexBuffer* pkVB=pTerrainPatchProxy->HardwareTransformPatch_GetVertexBufferPtr();
|
||||
if (!pkVB)
|
||||
return;
|
||||
|
||||
STATEMANAGER.SetStreamSource(0, pkVB->GetD3DVertexBuffer(), m_iPatchTerrainVertexSize);
|
||||
|
||||
STATEMANAGER.SetRenderState(D3DRS_LIGHTING, FALSE);
|
||||
|
||||
int iPrevRenderedSplatNum=m_iRenderedSplatNum;
|
||||
|
||||
#ifdef WORLD_EDITOR
|
||||
|
||||
int nRenderTextureCount = 0;
|
||||
|
||||
// if (!m_bShowEntirePatchTextureCount && !(GetAsyncKeyState(VK_LCONTROL) & 0x8000) )
|
||||
if (1)
|
||||
{
|
||||
for (DWORD j = 1; j < pTerrain->GetNumTextures(); ++j)
|
||||
{
|
||||
TTerainSplat & rSplat = rTerrainSplatPatch.Splats[j];
|
||||
|
||||
if (!rSplat.Active)
|
||||
continue;
|
||||
|
||||
if (rTerrainSplatPatch.PatchTileCount[sPatchNum][j] == 0)
|
||||
continue;
|
||||
|
||||
++nRenderTextureCount;
|
||||
}
|
||||
|
||||
DWORD dwTextureFactor = STATEMANAGER.GetRenderState(D3DRS_TEXTUREFACTOR);
|
||||
|
||||
int TextureCountThreshold = 8;
|
||||
DWORD dwTFactor = 0xFFFFFFFF;
|
||||
|
||||
if (GetAsyncKeyState(VK_LSHIFT) & 0x8000)
|
||||
{
|
||||
if (GetAsyncKeyState(VK_1) & 0x8000)
|
||||
{
|
||||
TextureCountThreshold = 2;
|
||||
dwTFactor = 0xFF0000FF;
|
||||
}
|
||||
else if (GetAsyncKeyState(VK_2) & 0x8000)
|
||||
{
|
||||
TextureCountThreshold = 3;
|
||||
dwTFactor = 0xFF00FF00;
|
||||
}
|
||||
else if (GetAsyncKeyState(VK_3) & 0x8000)
|
||||
{
|
||||
TextureCountThreshold = 4;
|
||||
dwTFactor = 0xFF00FFFF;
|
||||
}
|
||||
else if (GetAsyncKeyState(VK_4) & 0x8000)
|
||||
{
|
||||
TextureCountThreshold = 5;
|
||||
dwTFactor = 0xFFFF0000;
|
||||
}
|
||||
else if (GetAsyncKeyState(VK_5) & 0x8000)
|
||||
{
|
||||
TextureCountThreshold = 6;
|
||||
dwTFactor = 0xFFFFFF00;
|
||||
}
|
||||
else if (GetAsyncKeyState(VK_6) & 0x8000)
|
||||
{
|
||||
TextureCountThreshold = 7;
|
||||
dwTFactor = 0xFFFF00ff;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (nRenderTextureCount>=TextureCountThreshold)
|
||||
{
|
||||
STATEMANAGER.SetRenderState(D3DRS_TEXTUREFACTOR, dwTFactor);
|
||||
STATEMANAGER.SaveTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TFACTOR);
|
||||
STATEMANAGER.SaveTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount);
|
||||
STATEMANAGER.SetRenderState(D3DRS_TEXTUREFACTOR, dwTextureFactor);
|
||||
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_COLORARG1);
|
||||
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_COLOROP);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 0번 텍스처
|
||||
if ( 0 < rTerrainSplatPatch.PatchTileCount[sPatchNum][0] )
|
||||
{
|
||||
DWORD dwTextureFactorFor0Texture = STATEMANAGER.GetRenderState(D3DRS_TEXTUREFACTOR);
|
||||
STATEMANAGER.SetRenderState(D3DRS_TEXTUREFACTOR, 0xFF88FF88);
|
||||
STATEMANAGER.SaveTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TFACTOR);
|
||||
STATEMANAGER.SaveTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount);
|
||||
STATEMANAGER.SetRenderState(D3DRS_TEXTUREFACTOR, dwTextureFactorFor0Texture);
|
||||
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_COLORARG1);
|
||||
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_COLOROP);
|
||||
}
|
||||
|
||||
for (DWORD j = 1; j < pTerrain->GetNumTextures(); ++j)
|
||||
{
|
||||
TTerainSplat & rSplat = rTerrainSplatPatch.Splats[j];
|
||||
|
||||
if (!rSplat.Active)
|
||||
continue;
|
||||
|
||||
DWORD dwTextureCount = rTerrainSplatPatch.PatchTileCount[sPatchNum][j];
|
||||
if (dwTextureCount == 0)
|
||||
continue;
|
||||
|
||||
DWORD dwTextureFactorForTextureBalance = 0xFFFFFFFF;
|
||||
|
||||
if (!(GetAsyncKeyState(VK_LSHIFT) & 0x8000))
|
||||
{
|
||||
const TTerrainTexture & rTexture = m_TextureSet.GetTexture(j);
|
||||
|
||||
D3DXMatrixMultiply(&matSplatColorTexTransform, &m_matViewInverse, &rTexture.m_matTransform);
|
||||
STATEMANAGER.SetTransform(D3DTS_TEXTURE0, &matSplatColorTexTransform);
|
||||
|
||||
STATEMANAGER.SetTexture(0, rTexture.pd3dTexture);
|
||||
STATEMANAGER.SetTexture(1, rSplat.pd3dTexture);
|
||||
STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dwTextureCount < 71)
|
||||
{
|
||||
dwTextureFactorForTextureBalance = STATEMANAGER.GetRenderState(D3DRS_TEXTUREFACTOR);
|
||||
if (dwTextureCount < 51)
|
||||
STATEMANAGER.SetRenderState(D3DRS_TEXTUREFACTOR, 0xFFFF0000);
|
||||
else
|
||||
STATEMANAGER.SetRenderState(D3DRS_TEXTUREFACTOR, 0xFF0000FF);
|
||||
STATEMANAGER.SaveTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TFACTOR);
|
||||
STATEMANAGER.SaveTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTexture(0, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
const TTerrainTexture & rTexture = m_TextureSet.GetTexture(j);
|
||||
|
||||
D3DXMatrixMultiply(&matSplatColorTexTransform, &m_matViewInverse, &rTexture.m_matTransform);
|
||||
STATEMANAGER.SetTransform(D3DTS_TEXTURE0, &matSplatColorTexTransform);
|
||||
|
||||
STATEMANAGER.SetTexture(0, rTexture.pd3dTexture);
|
||||
}
|
||||
STATEMANAGER.SetTexture(1, rSplat.pd3dTexture);
|
||||
STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount);
|
||||
if (dwTextureCount < 71)
|
||||
{
|
||||
STATEMANAGER.SetRenderState(D3DRS_TEXTUREFACTOR, dwTextureFactorForTextureBalance);
|
||||
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_COLORARG1);
|
||||
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_COLOROP);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int>::iterator aIterator = std::find(m_RenderedTextureNumVector.begin(), m_RenderedTextureNumVector.end(), (int)j);
|
||||
if (aIterator == m_RenderedTextureNumVector.end())
|
||||
m_RenderedTextureNumVector.push_back(j);
|
||||
++m_iRenderedSplatNum;
|
||||
if (m_iRenderedSplatNum >= m_iSplatLimit)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int TextureCountThreshold = 6;
|
||||
DWORD dwTFactor = 0xFFFF00FF;
|
||||
|
||||
if (GetAsyncKeyState(VK_LSHIFT) & 0x8000)
|
||||
{
|
||||
if (GetAsyncKeyState(VK_1) & 0x8000)
|
||||
{
|
||||
TextureCountThreshold = 1;
|
||||
dwTFactor = 0xFF0000FF;
|
||||
}
|
||||
else if (GetAsyncKeyState(VK_2) & 0x8000)
|
||||
{
|
||||
TextureCountThreshold = 2;
|
||||
dwTFactor = 0xFF00FF00;
|
||||
}
|
||||
else if (GetAsyncKeyState(VK_3) & 0x8000)
|
||||
{
|
||||
TextureCountThreshold = 3;
|
||||
dwTFactor = 0xFF00FFFF;
|
||||
}
|
||||
else if (GetAsyncKeyState(VK_4) & 0x8000)
|
||||
{
|
||||
TextureCountThreshold = 4;
|
||||
dwTFactor = 0xFFFF0000;
|
||||
}
|
||||
else if (GetAsyncKeyState(VK_5) & 0x8000)
|
||||
{
|
||||
TextureCountThreshold = 5;
|
||||
dwTFactor = 0xFFFFFF00;
|
||||
}
|
||||
}
|
||||
|
||||
for (DWORD j = 1; j < pTerrain->GetNumTextures(); ++j)
|
||||
{
|
||||
TTerainSplat & rSplat = rTerrainSplatPatch.Splats[j];
|
||||
|
||||
if (!rSplat.Active)
|
||||
continue;
|
||||
|
||||
if (rTerrainSplatPatch.PatchTileCount[sPatchNum][j] == 0)
|
||||
continue;
|
||||
|
||||
DWORD dwTextureFactor;
|
||||
|
||||
if (nRenderTextureCount>=TextureCountThreshold)
|
||||
{
|
||||
dwTextureFactor = STATEMANAGER.GetRenderState(D3DRS_TEXTUREFACTOR);
|
||||
STATEMANAGER.SetRenderState(D3DRS_TEXTUREFACTOR, dwTFactor);
|
||||
STATEMANAGER.SaveTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TFACTOR);
|
||||
STATEMANAGER.SaveTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTexture(0, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
const TTerrainTexture & rTexture = m_TextureSet.GetTexture(j);
|
||||
|
||||
D3DXMatrixMultiply(&matSplatColorTexTransform, &m_matViewInverse, &rTexture.m_matTransform);
|
||||
STATEMANAGER.SetTransform(D3DTS_TEXTURE0, &matSplatColorTexTransform);
|
||||
|
||||
STATEMANAGER.SetTexture(0, rTexture.pd3dTexture);
|
||||
}
|
||||
STATEMANAGER.SetTexture(1, rSplat.pd3dTexture);
|
||||
STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount);
|
||||
if (nRenderTextureCount>=TextureCountThreshold)
|
||||
{
|
||||
STATEMANAGER.SetRenderState(D3DRS_TEXTUREFACTOR, dwTextureFactor);
|
||||
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_COLORARG1);
|
||||
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_COLOROP);
|
||||
}
|
||||
|
||||
++nRenderTextureCount;
|
||||
|
||||
std::vector<int>::iterator aIterator = std::find(m_RenderedTextureNumVector.begin(), m_RenderedTextureNumVector.end(), (int)j);
|
||||
if (aIterator == m_RenderedTextureNumVector.end())
|
||||
m_RenderedTextureNumVector.push_back(j);
|
||||
++m_iRenderedSplatNum;
|
||||
if (m_iRenderedSplatNum >= m_iSplatLimit)
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
bool isFirst=true;
|
||||
for (DWORD j = 1; j < pTerrain->GetNumTextures(); ++j)
|
||||
{
|
||||
TTerainSplat & rSplat = rTerrainSplatPatch.Splats[j];
|
||||
|
||||
if (!rSplat.Active)
|
||||
continue;
|
||||
|
||||
if (rTerrainSplatPatch.PatchTileCount[sPatchNum][j] == 0)
|
||||
continue;
|
||||
|
||||
const TTerrainTexture & rTexture = m_TextureSet.GetTexture(j);
|
||||
|
||||
D3DXMatrixMultiply(&matSplatColorTexTransform, &m_matViewInverse, &rTexture.m_matTransform);
|
||||
STATEMANAGER.SetTransform(D3DTS_TEXTURE0, &matSplatColorTexTransform);
|
||||
if (isFirst)
|
||||
{
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
|
||||
STATEMANAGER.SetTexture(0, rTexture.pd3dTexture);
|
||||
STATEMANAGER.SetTexture(1, rSplat.pd3dTexture);
|
||||
STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||
isFirst=false;
|
||||
}
|
||||
else
|
||||
{
|
||||
STATEMANAGER.SetTexture(0, rTexture.pd3dTexture);
|
||||
STATEMANAGER.SetTexture(1, rSplat.pd3dTexture);
|
||||
STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount);
|
||||
}
|
||||
|
||||
std::vector<int>::iterator aIterator = std::find(m_RenderedTextureNumVector.begin(), m_RenderedTextureNumVector.end(), (int)j);
|
||||
if (aIterator == m_RenderedTextureNumVector.end())
|
||||
m_RenderedTextureNumVector.push_back(j);
|
||||
++m_iRenderedSplatNum;
|
||||
if (m_iRenderedSplatNum >= m_iSplatLimit)
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
if (GetAsyncKeyState(VK_CAPITAL) & 0x8000)
|
||||
{
|
||||
TTerainSplat & rSplat = rTerrainSplatPatch.Splats[200];
|
||||
|
||||
if (rSplat.Active)
|
||||
{
|
||||
const TTerrainTexture & rTexture = m_TextureSet.GetTexture(1);
|
||||
|
||||
D3DXMatrixMultiply(&matSplatColorTexTransform, &m_matViewInverse, &rTexture.m_matTransform);
|
||||
STATEMANAGER.SetTransform(D3DTS_TEXTURE0, &matSplatColorTexTransform);
|
||||
|
||||
STATEMANAGER.SetTexture(0, NULL);
|
||||
STATEMANAGER.SetTexture(1, rSplat.pd3dTexture);
|
||||
STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount);
|
||||
}
|
||||
}
|
||||
*/
|
||||
#endif
|
||||
|
||||
// 그림자
|
||||
if (m_bDrawShadow)
|
||||
{
|
||||
STATEMANAGER.SetRenderState(D3DRS_LIGHTING, TRUE);
|
||||
|
||||
STATEMANAGER.SetRenderState(D3DRS_FOGCOLOR, 0xFFFFFFFF);
|
||||
STATEMANAGER.SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);
|
||||
STATEMANAGER.SetRenderState(D3DRS_DESTBLEND, D3DBLEND_SRCCOLOR);
|
||||
|
||||
D3DXMATRIX matShadowTexTransform;
|
||||
D3DXMatrixMultiply(&matShadowTexTransform, &matTexTransform, &m_matStaticShadow);
|
||||
|
||||
STATEMANAGER.SetTransform(D3DTS_TEXTURE0, &matShadowTexTransform);
|
||||
STATEMANAGER.SetTexture(0, pTerrain->GetShadowTexture());
|
||||
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_CURRENT);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_CURRENT);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP);
|
||||
|
||||
if (m_bDrawChrShadow)
|
||||
{
|
||||
STATEMANAGER.SetTransform(D3DTS_TEXTURE1, &m_matDynamicShadow);
|
||||
|
||||
STATEMANAGER.SetTexture(1, m_lpCharacterShadowMapTexture);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CURRENT);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_MODULATE);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP);
|
||||
}
|
||||
else
|
||||
{
|
||||
STATEMANAGER.SetTexture(1, NULL);
|
||||
}
|
||||
|
||||
ms_faceCount += wPrimitiveCount;
|
||||
STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount);
|
||||
++m_iRenderedSplatNum;
|
||||
|
||||
if (m_bDrawChrShadow)
|
||||
{
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||
}
|
||||
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_CURRENT);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP);
|
||||
|
||||
|
||||
STATEMANAGER.SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
|
||||
STATEMANAGER.SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
|
||||
STATEMANAGER.SetRenderState(D3DRS_FOGCOLOR, dwFogColor);
|
||||
|
||||
STATEMANAGER.SetRenderState(D3DRS_LIGHTING, FALSE);
|
||||
}
|
||||
++m_iRenderedPatchNum;
|
||||
|
||||
int iCurRenderedSplatNum=m_iRenderedSplatNum-iPrevRenderedSplatNum;
|
||||
|
||||
m_iRenderedSplatNumSqSum+=iCurRenderedSplatNum*iCurRenderedSplatNum;
|
||||
|
||||
}
|
||||
|
||||
void CMapOutdoor::__HardwareTransformPatch_RenderPatchNone(long patchnum, WORD wPrimitiveCount, D3DPRIMITIVETYPE ePrimitiveType)
|
||||
{
|
||||
assert(NULL!=m_pTerrainPatchProxyList && "__HardwareTransformPatch_RenderPatchNone");
|
||||
CTerrainPatchProxy * pTerrainPatchProxy = &m_pTerrainPatchProxyList[patchnum];
|
||||
|
||||
if (!pTerrainPatchProxy->isUsed())
|
||||
return;
|
||||
|
||||
CGraphicVertexBuffer* pkVB=pTerrainPatchProxy->HardwareTransformPatch_GetVertexBufferPtr();
|
||||
if (!pkVB)
|
||||
return;
|
||||
|
||||
STATEMANAGER.SetStreamSource(0, pkVB->GetD3DVertexBuffer(), m_iPatchTerrainVertexSize);
|
||||
STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount);
|
||||
}
|
||||
@@ -0,0 +1,815 @@
|
||||
// Platform copy of 40250 GameLib/MapOutdoorRenderSTP.cpp (software-transform terrain patches), as is
|
||||
// apart from the includes and the native-terrain gate: the Godot Metin2World adapter draws the terrain
|
||||
// unless IsNativeTerrainRenderEnabled() (see MapOutdoorRenderHTP.cpp). 40250 takes this path when
|
||||
// SOFTWARE_TILING is 1 in metin2.cfg, or with auto tiling on a device without fast T&L.
|
||||
#include "GameLib/StdAfx.h"
|
||||
#include "GameLib/MapOutdoor.h"
|
||||
#include "GameLib/TerrainPatch.h"
|
||||
#include "GameLib/TerrainQuadtree.h"
|
||||
|
||||
#include "EterLib/Camera.h"
|
||||
#include "EterLib/StateManager.h"
|
||||
#include "../EterLib/RenderCommands3D.h"
|
||||
|
||||
struct SoftwareTransformPatch_SSplatVertex
|
||||
{
|
||||
D3DXVECTOR4 kPosition;
|
||||
DWORD dwDiffuse;
|
||||
DWORD dwSpecular;
|
||||
D3DXVECTOR2 kTex1;
|
||||
D3DXVECTOR2 kTex2;
|
||||
};
|
||||
|
||||
|
||||
|
||||
void CMapOutdoor::__RenderTerrain_RenderSoftwareTransformPatch()
|
||||
{
|
||||
// PORT: see the file comment.
|
||||
if (!IsNativeTerrainRenderEnabled())
|
||||
return;
|
||||
|
||||
SoftwareTransformPatch_SRenderState kTPRS;
|
||||
|
||||
DWORD dwFogEnable = STATEMANAGER.GetRenderState(D3DRS_FOGENABLE);
|
||||
|
||||
__SoftwareTransformPatch_ApplyRenderState();
|
||||
|
||||
__SoftwareTransformPatch_BuildPipeline(kTPRS);
|
||||
|
||||
std::pair<float, long> fog_far(kTPRS.m_fFogFarDistance+800.0f, 0);
|
||||
std::pair<float, long> fog_near(kTPRS.m_fFogNearDistance-3200.0f, 0);
|
||||
|
||||
std::vector<std::pair<float ,long> >::iterator far_it = std::upper_bound(m_PatchVector.begin(),m_PatchVector.end(),fog_far);
|
||||
std::vector<std::pair<float ,long> >::iterator near_it = std::upper_bound(m_PatchVector.begin(),m_PatchVector.end(),fog_near);
|
||||
|
||||
WORD wPrimitiveCount;
|
||||
D3DPRIMITIVETYPE ePrimitiveType;
|
||||
|
||||
BYTE byCUrrentLODLevel = 0;
|
||||
|
||||
float fLODLevel1Distance = __GetNoFogDistance();
|
||||
float fLODLevel2Distance = __GetFogDistance();
|
||||
|
||||
SelectIndexBuffer(0, &wPrimitiveCount, &ePrimitiveType);
|
||||
|
||||
STATEMANAGER.SetVertexShader(D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_SPECULAR|D3DFVF_TEX2);
|
||||
|
||||
std::vector<std::pair<float, long> >::iterator it = m_PatchVector.begin();
|
||||
|
||||
for( ; it != near_it; ++it)
|
||||
{
|
||||
if (byCUrrentLODLevel == 0 && fLODLevel1Distance <= it->first)
|
||||
{
|
||||
byCUrrentLODLevel = 1;
|
||||
SelectIndexBuffer(1, &wPrimitiveCount, &ePrimitiveType);
|
||||
}
|
||||
else if (byCUrrentLODLevel == 1 && fLODLevel2Distance <= it->first)
|
||||
{
|
||||
byCUrrentLODLevel = 2;
|
||||
SelectIndexBuffer(2, &wPrimitiveCount, &ePrimitiveType);
|
||||
}
|
||||
|
||||
__SoftwareTransformPatch_RenderPatchSplat(kTPRS, it->second, wPrimitiveCount, ePrimitiveType, false);
|
||||
if (m_iRenderedSplatNum >= m_iSplatLimit)
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if (m_iRenderedSplatNum < m_iSplatLimit)
|
||||
{
|
||||
for(it = near_it; it != far_it; ++it)
|
||||
{
|
||||
if (byCUrrentLODLevel == 0 && fLODLevel1Distance <= it->first)
|
||||
{
|
||||
byCUrrentLODLevel = 1;
|
||||
SelectIndexBuffer(1, &wPrimitiveCount, &ePrimitiveType);
|
||||
}
|
||||
else if (byCUrrentLODLevel == 1 && fLODLevel2Distance <= it->first)
|
||||
{
|
||||
byCUrrentLODLevel = 2;
|
||||
SelectIndexBuffer(2, &wPrimitiveCount, &ePrimitiveType);
|
||||
}
|
||||
|
||||
__SoftwareTransformPatch_RenderPatchSplat(kTPRS, it->second, wPrimitiveCount, ePrimitiveType, true);
|
||||
|
||||
if (m_iRenderedSplatNum >= m_iSplatLimit)
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
STATEMANAGER.SetTexture(0, NULL);
|
||||
STATEMANAGER.SetTexture(1, NULL);
|
||||
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TFACTOR);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
|
||||
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
|
||||
|
||||
STATEMANAGER.SetVertexShader(D3DFVF_XYZRHW);
|
||||
|
||||
if (IsFastTNL())
|
||||
{
|
||||
if (byCUrrentLODLevel != 2)
|
||||
{
|
||||
byCUrrentLODLevel = 2;
|
||||
SelectIndexBuffer(2, &wPrimitiveCount, &ePrimitiveType);
|
||||
}
|
||||
|
||||
if (m_iRenderedSplatNum < m_iSplatLimit)
|
||||
{
|
||||
for(it = far_it; it != m_PatchVector.end(); ++it)
|
||||
{
|
||||
__SoftwareTransformPatch_RenderPatchNone(kTPRS, it->second, wPrimitiveCount, ePrimitiveType);
|
||||
|
||||
if (m_iRenderedSplatNum >= m_iSplatLimit)
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Render State & TextureStageState
|
||||
__SoftwareTransformPatch_RestoreRenderState(dwFogEnable);
|
||||
}
|
||||
|
||||
void CMapOutdoor::__SoftwareTransformPatch_RenderPatchSplat(SoftwareTransformPatch_SRenderState& rkTPRS, long patchnum, WORD wPrimitiveCount, D3DPRIMITIVETYPE ePrimitiveType, bool isFogEnable)
|
||||
{
|
||||
assert(NULL!=m_pTerrainPatchProxyList && "CMapOutdoor::__SoftwareTransformPatch_RenderPatchSplat");
|
||||
|
||||
CTerrainPatchProxy * pTerrainPatchProxy = &m_pTerrainPatchProxyList[patchnum];
|
||||
|
||||
if (!pTerrainPatchProxy->isUsed())
|
||||
return;
|
||||
|
||||
bool isDynamicShadow = pTerrainPatchProxy->IsIn(rkTPRS.m_v3Player, 3000.0f);
|
||||
|
||||
if (!m_bDrawChrShadow)
|
||||
isDynamicShadow = false;
|
||||
|
||||
long sPatchNum = pTerrainPatchProxy->GetPatchNum();
|
||||
|
||||
if (sPatchNum < 0)
|
||||
return;
|
||||
|
||||
BYTE ucTerrainNum = pTerrainPatchProxy->GetTerrainNum();
|
||||
|
||||
if (0xFF == ucTerrainNum)
|
||||
return;
|
||||
|
||||
CTerrain * pTerrain;
|
||||
if (!GetTerrainPointer(ucTerrainNum, &pTerrain))
|
||||
return;
|
||||
|
||||
WORD wCoordX, wCoordY;
|
||||
pTerrain->GetCoordinate(&wCoordX, &wCoordY);
|
||||
|
||||
TTerrainSplatPatch & rTerrainSplatPatch = pTerrain->GetTerrainSplatPatch();
|
||||
|
||||
SoftwareTransformPatch_STLVertex akTransVertex[CTerrainPatch::TERRAIN_VERTEX_COUNT];
|
||||
if (!__SoftwareTransformPatch_SetTransform(rkTPRS, akTransVertex, *pTerrainPatchProxy, wCoordX, wCoordY, isFogEnable, isDynamicShadow))
|
||||
return;
|
||||
|
||||
if (!__SoftwareTransformPatch_SetSplatStream(akTransVertex))
|
||||
return;
|
||||
|
||||
if (isFogEnable)
|
||||
{
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TFACTOR);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_BLENDDIFFUSEALPHA);
|
||||
}
|
||||
else
|
||||
{
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
}
|
||||
|
||||
int iPrevRenderedSplatNum=m_iRenderedSplatNum;
|
||||
|
||||
bool isFirst=true;
|
||||
for (DWORD j = 1; j < pTerrain->GetNumTextures(); ++j)
|
||||
{
|
||||
TTerainSplat & rSplat = rTerrainSplatPatch.Splats[j];
|
||||
|
||||
if (!rSplat.Active)
|
||||
continue;
|
||||
|
||||
if (rTerrainSplatPatch.PatchTileCount[sPatchNum][j] == 0)
|
||||
continue;
|
||||
|
||||
const TTerrainTexture & rTexture = m_TextureSet.GetTexture(j);
|
||||
|
||||
if (isFirst)
|
||||
{
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2);
|
||||
STATEMANAGER.SetTexture(0, rTexture.pd3dTexture);
|
||||
STATEMANAGER.SetTexture(1, rSplat.pd3dTexture);
|
||||
STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||
isFirst=false;
|
||||
}
|
||||
else
|
||||
{
|
||||
STATEMANAGER.SetTexture(0, rTexture.pd3dTexture);
|
||||
STATEMANAGER.SetTexture(1, rSplat.pd3dTexture);
|
||||
STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount);
|
||||
}
|
||||
|
||||
std::vector<int>::iterator aIterator = std::find(m_RenderedTextureNumVector.begin(), m_RenderedTextureNumVector.end(), (int)j);
|
||||
if (aIterator == m_RenderedTextureNumVector.end())
|
||||
m_RenderedTextureNumVector.push_back(j);
|
||||
++m_iRenderedSplatNum;
|
||||
if (m_iRenderedSplatNum >= m_iSplatLimit)
|
||||
break;
|
||||
}
|
||||
|
||||
// 그림자
|
||||
if (m_bDrawShadow)
|
||||
{
|
||||
__SoftwareTransformPatch_SetShadowStream(akTransVertex);
|
||||
__SoftwareTransformPatch_ApplyStaticShadowRenderState();
|
||||
|
||||
if (isDynamicShadow)
|
||||
__SoftwareTransformPatch_ApplyDynamicShadowRenderState();
|
||||
else
|
||||
__SoftwareTransformPatch_ApplyFogShadowRenderState();
|
||||
|
||||
if (isFogEnable)
|
||||
{
|
||||
STATEMANAGER.SetRenderState(D3DRS_FOGENABLE, TRUE);
|
||||
STATEMANAGER.SetRenderState(D3DRS_FOGCOLOR, 0xFFFFFFFF);
|
||||
STATEMANAGER.SetTexture(0, pTerrain->GetShadowTexture());
|
||||
STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount);
|
||||
STATEMANAGER.SetRenderState(D3DRS_FOGCOLOR, rkTPRS.m_dwFogColor);
|
||||
STATEMANAGER.SetRenderState(D3DRS_FOGENABLE, FALSE);
|
||||
}
|
||||
else
|
||||
{
|
||||
STATEMANAGER.SetTexture(0, pTerrain->GetShadowTexture());
|
||||
STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount);
|
||||
}
|
||||
|
||||
if (isDynamicShadow)
|
||||
__SoftwareTransformPatch_RestoreDynamicShadowRenderState();
|
||||
else
|
||||
__SoftwareTransformPatch_RestoreFogShadowRenderState();
|
||||
|
||||
ms_faceCount += wPrimitiveCount;
|
||||
++m_iRenderedSplatNum;
|
||||
|
||||
|
||||
|
||||
__SoftwareTransformPatch_RestoreStaticShadowRenderState();
|
||||
}
|
||||
|
||||
++m_iRenderedPatchNum;
|
||||
|
||||
int iCurRenderedSplatNum=m_iRenderedSplatNum-iPrevRenderedSplatNum;
|
||||
|
||||
m_iRenderedSplatNumSqSum+=iCurRenderedSplatNum*iCurRenderedSplatNum;
|
||||
}
|
||||
|
||||
void CMapOutdoor::__SoftwareTransformPatch_RenderPatchNone(SoftwareTransformPatch_SRenderState& rkTPRS, long patchnum, WORD wPrimitiveCount, D3DPRIMITIVETYPE ePrimitiveType)
|
||||
{
|
||||
assert(NULL!=m_pTerrainPatchProxyList && "CMapOutdoor::__SoftwareTransformPatch_RenderPatchNone");
|
||||
|
||||
CTerrainPatchProxy * pTerrainPatchProxy = &m_pTerrainPatchProxyList[patchnum];
|
||||
|
||||
if (!pTerrainPatchProxy->isUsed())
|
||||
return;
|
||||
|
||||
long sPatchNum = pTerrainPatchProxy->GetPatchNum();
|
||||
if (sPatchNum < 0)
|
||||
return;
|
||||
|
||||
BYTE ucTerrainNum = pTerrainPatchProxy->GetTerrainNum();
|
||||
if (0xFF == ucTerrainNum)
|
||||
return;
|
||||
|
||||
CTerrain * pTerrain;
|
||||
if (!GetTerrainPointer(ucTerrainNum, &pTerrain))
|
||||
return;
|
||||
|
||||
WORD wCoordX, wCoordY;
|
||||
pTerrain->GetCoordinate(&wCoordX, &wCoordY);
|
||||
|
||||
SoftwareTransformPatch_SSourceVertex* akSrcVertex=pTerrainPatchProxy->SoftwareTransformPatch_GetTerrainVertexDataPtr();
|
||||
if (!akSrcVertex)
|
||||
return;
|
||||
|
||||
float fScreenHalfWidth=rkTPRS.m_fScreenHalfWidth;
|
||||
float fScreenHalfHeight=rkTPRS.m_fScreenHalfHeight;
|
||||
|
||||
D3DXMATRIX m4Frustum=rkTPRS.m_m4Frustum;
|
||||
|
||||
SoftwareTransformPatch_STVertex akTransVertex[CTerrainPatch::TERRAIN_VERTEX_COUNT];
|
||||
|
||||
D3DXVECTOR4* akPosition=(D3DXVECTOR4*)akTransVertex;
|
||||
D3DXVECTOR4* pkPosition;
|
||||
for (UINT uIndex=0; uIndex!=CTerrainPatch::TERRAIN_VERTEX_COUNT; ++uIndex)
|
||||
{
|
||||
pkPosition=akPosition+uIndex;
|
||||
D3DXVec3Transform(pkPosition, &akSrcVertex[uIndex].kPosition, &m4Frustum);
|
||||
pkPosition->w=1.0f/pkPosition->w;
|
||||
pkPosition->z*=pkPosition->w;
|
||||
pkPosition->y=(pkPosition->y*pkPosition->w-1.0f)*fScreenHalfHeight;
|
||||
pkPosition->x=(pkPosition->x*pkPosition->w+1.0f)*fScreenHalfWidth;
|
||||
}
|
||||
|
||||
|
||||
IDirect3DVertexBuffer8* pkVB=m_kSTPD.m_pkVBNone[m_kSTPD.m_dwNonePos++];
|
||||
m_kSTPD.m_dwNonePos%=SoftwareTransformPatch_SData::NONE_VB_NUM;
|
||||
if (!pkVB)
|
||||
return;
|
||||
|
||||
DWORD dwVBSize=sizeof(SoftwareTransformPatch_STVertex)*CTerrainPatch::TERRAIN_VERTEX_COUNT;
|
||||
SoftwareTransformPatch_STVertex* akDstVertex;
|
||||
if (FAILED(
|
||||
pkVB->Lock(0, dwVBSize, (BYTE**)&akDstVertex, D3DLOCK_DISCARD)
|
||||
)) return;
|
||||
|
||||
memcpy(akDstVertex, akTransVertex, dwVBSize);
|
||||
|
||||
pkVB->Unlock();
|
||||
|
||||
STATEMANAGER.SetStreamSource(0, pkVB, sizeof(SoftwareTransformPatch_STVertex));
|
||||
STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount);
|
||||
ms_faceCount += wPrimitiveCount;
|
||||
}
|
||||
|
||||
void CMapOutdoor::__SoftwareTransformPatch_ApplyStaticShadowRenderState()
|
||||
{
|
||||
STATEMANAGER.SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);
|
||||
STATEMANAGER.SetRenderState(D3DRS_DESTBLEND, D3DBLEND_SRCCOLOR);
|
||||
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP);
|
||||
|
||||
}
|
||||
|
||||
void CMapOutdoor::__SoftwareTransformPatch_ApplyDynamicShadowRenderState()
|
||||
{
|
||||
STATEMANAGER.SetTexture(1, m_lpCharacterShadowMapTexture);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CURRENT);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_MODULATE);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_CURRENT);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP);
|
||||
}
|
||||
|
||||
void CMapOutdoor::__SoftwareTransformPatch_ApplyFogShadowRenderState()
|
||||
{
|
||||
STATEMANAGER.SetTexture(1, NULL);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_CURRENT);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||
}
|
||||
void CMapOutdoor::__SoftwareTransformPatch_RestoreStaticShadowRenderState()
|
||||
{
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP);
|
||||
|
||||
STATEMANAGER.SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
|
||||
STATEMANAGER.SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void CMapOutdoor::__SoftwareTransformPatch_RestoreDynamicShadowRenderState()
|
||||
{
|
||||
STATEMANAGER.SetTexture(1, NULL);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void CMapOutdoor::__SoftwareTransformPatch_RestoreFogShadowRenderState()
|
||||
{
|
||||
STATEMANAGER.SetRenderState(D3DRS_FOGENABLE, FALSE);
|
||||
|
||||
STATEMANAGER.SetTexture(1, NULL);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||
}
|
||||
|
||||
void CMapOutdoor::__SoftwareTransformPatch_ApplyRenderState()
|
||||
{
|
||||
DWORD dwFogColor=0xffffffff;
|
||||
if (mc_pEnvironmentData)
|
||||
dwFogColor=mc_pEnvironmentData->FogColor;
|
||||
|
||||
BOOL isSoftwareVertexClipping=FALSE;
|
||||
if (!IsTLVertexClipping())
|
||||
isSoftwareVertexClipping=TRUE;
|
||||
|
||||
STATEMANAGER.SaveRenderState(D3DRS_SOFTWAREVERTEXPROCESSING, isSoftwareVertexClipping);
|
||||
|
||||
STATEMANAGER.SaveRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
|
||||
STATEMANAGER.SaveRenderState(D3DRS_ALPHATESTENABLE, TRUE);
|
||||
STATEMANAGER.SaveRenderState(D3DRS_ALPHAREF, 0x00000000);
|
||||
STATEMANAGER.SaveRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATER);
|
||||
|
||||
STATEMANAGER.SaveRenderState(D3DRS_TEXTUREFACTOR, dwFogColor);
|
||||
STATEMANAGER.SetRenderState(D3DRS_LIGHTING, FALSE);
|
||||
STATEMANAGER.SetRenderState(D3DRS_FOGENABLE, FALSE);
|
||||
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP);
|
||||
STATEMANAGER.SetBestFiltering(0);
|
||||
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAARG2, D3DTA_CURRENT);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP);
|
||||
STATEMANAGER.SetBestFiltering(1);
|
||||
|
||||
CSpeedTreeWrapper::ms_bSelfShadowOn = true;
|
||||
|
||||
// Render State & TextureStageState
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
m_iRenderedSplatNumSqSum = 0;
|
||||
m_iRenderedPatchNum = 0;
|
||||
m_iRenderedSplatNum = 0;
|
||||
m_RenderedTextureNumVector.clear();
|
||||
|
||||
m_matWorldForCommonUse._41 = 0.0f;
|
||||
m_matWorldForCommonUse._42 = 0.0f;
|
||||
STATEMANAGER.SetTransform(D3DTS_WORLD, &m_matWorldForCommonUse);
|
||||
}
|
||||
|
||||
void CMapOutdoor::__SoftwareTransformPatch_RestoreRenderState(DWORD dwFogEnable)
|
||||
{
|
||||
STATEMANAGER.SetRenderState(D3DRS_LIGHTING, TRUE);
|
||||
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_CURRENT);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||
|
||||
STATEMANAGER.SetRenderState(D3DRS_FOGENABLE, dwFogEnable);
|
||||
|
||||
std::sort(m_RenderedTextureNumVector.begin(),m_RenderedTextureNumVector.end());
|
||||
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_TEXTUREFACTOR);
|
||||
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_ALPHABLENDENABLE);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_ALPHATESTENABLE);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_ALPHAREF);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_ALPHAFUNC);
|
||||
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_SOFTWAREVERTEXPROCESSING);
|
||||
|
||||
// Render State & TextureStageState
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
|
||||
void CMapOutdoor::__SoftwareTransformPatch_BuildPipeline(SoftwareTransformPatch_SRenderState& rkTPRS)
|
||||
{
|
||||
memset(&rkTPRS, 0, sizeof(rkTPRS));
|
||||
|
||||
if (mc_pEnvironmentData)
|
||||
{
|
||||
rkTPRS.m_dwFogColor = mc_pEnvironmentData->FogColor;
|
||||
rkTPRS.m_fFogNearDistance = mc_pEnvironmentData->GetFogNearDistance();
|
||||
rkTPRS.m_fFogFarDistance = mc_pEnvironmentData->GetFogFarDistance();
|
||||
}
|
||||
else
|
||||
{
|
||||
rkTPRS.m_dwFogColor = 0xffffffff;
|
||||
rkTPRS.m_fFogNearDistance = 5000.0f;
|
||||
rkTPRS.m_fFogFarDistance = 10000.0f;
|
||||
}
|
||||
|
||||
UINT uScreenWidth;
|
||||
UINT uScreenHeight;
|
||||
CScreen::GetBackBufferSize(&uScreenWidth, &uScreenHeight);
|
||||
|
||||
rkTPRS.m_fScreenHalfWidth = +float(uScreenWidth) / 2.0f;
|
||||
rkTPRS.m_fScreenHalfHeight = -float(uScreenHeight) / 2.0f;
|
||||
|
||||
STATEMANAGER.GetLight(0, &rkTPRS.m_kLight);
|
||||
STATEMANAGER.GetMaterial(&rkTPRS.m_kMtrl);
|
||||
|
||||
D3DXMATRIX m4View;STATEMANAGER.GetTransform(D3DTS_VIEW, &m4View);
|
||||
D3DXMATRIX m4Proj;STATEMANAGER.GetTransform(D3DTS_PROJECTION, &m4Proj);
|
||||
|
||||
D3DXMatrixMultiply(&rkTPRS.m_m4Frustum, &m4View, &m4Proj);
|
||||
|
||||
rkTPRS.m_v3Player.x = +m_v3Player.x;
|
||||
rkTPRS.m_v3Player.y = -m_v3Player.y;
|
||||
rkTPRS.m_v3Player.z = +m_v3Player.z;
|
||||
|
||||
rkTPRS.m_m4Proj = m4Proj;
|
||||
rkTPRS.m_m4DynamicShadow = m_matLightView * m_matDynamicShadowScale;
|
||||
|
||||
D3DXVECTOR3 kFogNearVector;
|
||||
const auto farvv = D3DXVECTOR3(0.0f, 0.0f, -rkTPRS.m_fFogNearDistance);
|
||||
D3DXVec3TransformCoord(&kFogNearVector, &farvv, &rkTPRS.m_m4Proj);
|
||||
|
||||
D3DXVECTOR3 kFogFarVector;
|
||||
const auto nearvv = D3DXVECTOR3(0.0f, 0.0f, -rkTPRS.m_fFogFarDistance);
|
||||
D3DXVec3TransformCoord(&kFogFarVector, &nearvv, &rkTPRS.m_m4Proj);
|
||||
|
||||
float fFogNear = kFogNearVector.z;
|
||||
float fFogFar = kFogFarVector.z;
|
||||
float fFogLenInv = 1.0f / (fFogFar-fFogNear);
|
||||
|
||||
rkTPRS.m_fFogNearTransZ = fFogNear;
|
||||
rkTPRS.m_fFogFarTransZ = fFogFar;
|
||||
rkTPRS.m_fFogLenInv = fFogLenInv;
|
||||
|
||||
}
|
||||
|
||||
bool CMapOutdoor::__SoftwareTransformPatch_SetTransform(SoftwareTransformPatch_SRenderState& rkTPRS, SoftwareTransformPatch_STLVertex* akTransVertex, CTerrainPatchProxy& rkTerrainPatchProxy, UINT uTerrainX, UINT uTerrainY, bool isFogEnable, bool isDynamicShadow)
|
||||
{
|
||||
SoftwareTransformPatch_SSourceVertex* akSrcVertex=rkTerrainPatchProxy.SoftwareTransformPatch_GetTerrainVertexDataPtr();
|
||||
if (!akSrcVertex)
|
||||
return false;
|
||||
|
||||
rkTerrainPatchProxy.SoftwareTransformPatch_UpdateTerrainLighting(
|
||||
m_kSTPD.m_dwLightVersion,
|
||||
rkTPRS.m_kLight, rkTPRS.m_kMtrl);
|
||||
|
||||
D3DXVECTOR3* pkSrcPosition;
|
||||
|
||||
float fTilePatternX=+1/640.0f;
|
||||
float fTilePatternY=-1/640.0f;
|
||||
|
||||
float fTerrainBaseX=-(float) (uTerrainX * CTerrainImpl::TERRAIN_XSIZE)+m_fTerrainTexCoordBase * 12.30769f;
|
||||
float fTerrainBaseY=+(float) (uTerrainY * CTerrainImpl::TERRAIN_YSIZE)+m_fTerrainTexCoordBase * 12.30769f;
|
||||
|
||||
float fScreenHalfWidth=rkTPRS.m_fScreenHalfWidth;
|
||||
float fScreenHalfHeight=rkTPRS.m_fScreenHalfHeight;
|
||||
|
||||
float fAlphaPatternX=m_matSplatAlpha._11;
|
||||
float fAlphaPatternY=m_matSplatAlpha._22;
|
||||
float fAlphaBiasX=m_matSplatAlpha._41;
|
||||
float fAlphaBiasY=m_matSplatAlpha._42;
|
||||
float fShadowPatternX=+m_fTerrainTexCoordBase * ((float) CTerrainImpl::PATCH_XSIZE / static_cast<float>(CTerrainImpl::XSIZE));
|
||||
float fShadowPatternY=-m_fTerrainTexCoordBase * ((float) CTerrainImpl::PATCH_YSIZE / static_cast<float>(CTerrainImpl::YSIZE));
|
||||
|
||||
D3DXMATRIX m4Frustum=rkTPRS.m_m4Frustum;
|
||||
|
||||
if (isFogEnable)
|
||||
{
|
||||
float fFogCur;
|
||||
float fFogFar=rkTPRS.m_fFogFarTransZ;
|
||||
float fFogLenInv=rkTPRS.m_fFogLenInv;
|
||||
|
||||
float fLocalX;
|
||||
float fLocalY;
|
||||
|
||||
SoftwareTransformPatch_STLVertex kWorkVertex;
|
||||
for (UINT uIndex=0; uIndex!=CTerrainPatch::TERRAIN_VERTEX_COUNT; ++uIndex)
|
||||
{
|
||||
pkSrcPosition=&akSrcVertex[uIndex].kPosition;
|
||||
D3DXVec3Transform(&kWorkVertex.kPosition, pkSrcPosition, &m4Frustum);
|
||||
fLocalX=pkSrcPosition->x+fTerrainBaseX;
|
||||
fLocalY=pkSrcPosition->y+fTerrainBaseY;
|
||||
kWorkVertex.kPosition.w=1.0f/kWorkVertex.kPosition.w;
|
||||
kWorkVertex.kPosition.x*=kWorkVertex.kPosition.w;
|
||||
kWorkVertex.kPosition.y*=kWorkVertex.kPosition.w;
|
||||
kWorkVertex.kPosition.z*=kWorkVertex.kPosition.w;
|
||||
kWorkVertex.kPosition.x=(kWorkVertex.kPosition.x+1.0f)*fScreenHalfWidth;
|
||||
kWorkVertex.kPosition.y=(kWorkVertex.kPosition.y-1.0f)*fScreenHalfHeight;
|
||||
kWorkVertex.dwDiffuse=akSrcVertex[uIndex].dwDiffuse;
|
||||
kWorkVertex.kTexTile.x=pkSrcPosition->x*fTilePatternX;
|
||||
kWorkVertex.kTexTile.y=pkSrcPosition->y*fTilePatternY;
|
||||
kWorkVertex.kTexAlpha.x=fLocalX*fAlphaPatternX+fAlphaBiasX;
|
||||
kWorkVertex.kTexAlpha.y=fLocalY*fAlphaPatternY+fAlphaBiasY;
|
||||
kWorkVertex.kTexStaticShadow.x=fLocalX*fShadowPatternX;
|
||||
kWorkVertex.kTexStaticShadow.y=fLocalY*fShadowPatternY;
|
||||
kWorkVertex.kTexDynamicShadow.x=0.0f;
|
||||
kWorkVertex.kTexDynamicShadow.y=0.0f;
|
||||
|
||||
fFogCur=(fFogFar-kWorkVertex.kPosition.z)*fFogLenInv;
|
||||
if (fFogCur<0.0f)
|
||||
kWorkVertex.dwFog=kWorkVertex.dwDiffuse=0x0000000|(kWorkVertex.dwDiffuse&0xffffff);
|
||||
else if (fFogCur>1.0f)
|
||||
kWorkVertex.dwFog=kWorkVertex.dwDiffuse=0xFF000000|(kWorkVertex.dwDiffuse&0xffffff);
|
||||
else
|
||||
kWorkVertex.dwFog=kWorkVertex.dwDiffuse=BYTE(255.0f*fFogCur)<<24|(kWorkVertex.dwDiffuse&0xffffff);
|
||||
|
||||
*(akTransVertex+uIndex)=kWorkVertex;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float fLocalX;
|
||||
float fLocalY;
|
||||
|
||||
SoftwareTransformPatch_STLVertex kWorkVertex;
|
||||
for (UINT uIndex=0; uIndex!=CTerrainPatch::TERRAIN_VERTEX_COUNT; ++uIndex)
|
||||
{
|
||||
pkSrcPosition=&akSrcVertex[uIndex].kPosition;
|
||||
D3DXVec3Transform(&kWorkVertex.kPosition, pkSrcPosition, &m4Frustum);
|
||||
fLocalX=pkSrcPosition->x+fTerrainBaseX;
|
||||
fLocalY=pkSrcPosition->y+fTerrainBaseY;
|
||||
kWorkVertex.kPosition.w=1.0f/kWorkVertex.kPosition.w;
|
||||
kWorkVertex.kPosition.x*=kWorkVertex.kPosition.w;
|
||||
kWorkVertex.kPosition.y*=kWorkVertex.kPosition.w;
|
||||
kWorkVertex.kPosition.z*=kWorkVertex.kPosition.w;
|
||||
kWorkVertex.kPosition.x=(kWorkVertex.kPosition.x+1.0f)*fScreenHalfWidth;
|
||||
kWorkVertex.kPosition.y=(kWorkVertex.kPosition.y-1.0f)*fScreenHalfHeight;
|
||||
kWorkVertex.dwDiffuse=akSrcVertex[uIndex].dwDiffuse;
|
||||
kWorkVertex.dwFog=0xffffffff;
|
||||
kWorkVertex.kTexTile.x=pkSrcPosition->x*fTilePatternX;
|
||||
kWorkVertex.kTexTile.y=pkSrcPosition->y*fTilePatternY;
|
||||
kWorkVertex.kTexAlpha.x=fLocalX*fAlphaPatternX+fAlphaBiasX;
|
||||
kWorkVertex.kTexAlpha.y=fLocalY*fAlphaPatternY+fAlphaBiasY;
|
||||
kWorkVertex.kTexStaticShadow.x=fLocalX*fShadowPatternX;
|
||||
kWorkVertex.kTexStaticShadow.y=fLocalY*fShadowPatternY;
|
||||
kWorkVertex.kTexDynamicShadow.x=0.0f;
|
||||
kWorkVertex.kTexDynamicShadow.y=0.0f;
|
||||
|
||||
*(akTransVertex+uIndex)=kWorkVertex;
|
||||
}
|
||||
}
|
||||
|
||||
if (isDynamicShadow)
|
||||
{
|
||||
D3DXMATRIX m4DynamicShadow=rkTPRS.m_m4DynamicShadow;
|
||||
|
||||
D3DXVECTOR3 v3Shadow;
|
||||
for (UINT uIndex=0; uIndex!=CTerrainPatch::TERRAIN_VERTEX_COUNT; ++uIndex)
|
||||
{
|
||||
D3DXVec3TransformCoord(&v3Shadow, &akSrcVertex[uIndex].kPosition, &m4DynamicShadow);
|
||||
akTransVertex[uIndex].kTexDynamicShadow.x=v3Shadow.x;
|
||||
akTransVertex[uIndex].kTexDynamicShadow.y=v3Shadow.y;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CMapOutdoor::__SoftwareTransformPatch_SetSplatStream(SoftwareTransformPatch_STLVertex* akSrcVertex)
|
||||
{
|
||||
IDirect3DVertexBuffer8* pkVB=m_kSTPD.m_pkVBSplat[m_kSTPD.m_dwSplatPos++];
|
||||
m_kSTPD.m_dwSplatPos%=SoftwareTransformPatch_SData::SPLAT_VB_NUM;
|
||||
if (!pkVB)
|
||||
return false;
|
||||
|
||||
DWORD dwVBSize=sizeof(SoftwareTransformPatch_SSplatVertex)*CTerrainPatch::TERRAIN_VERTEX_COUNT;
|
||||
SoftwareTransformPatch_SSplatVertex* akDstVertex;
|
||||
if (FAILED(
|
||||
pkVB->Lock(0, dwVBSize, (BYTE**)&akDstVertex, 0)//D3DLOCK_DISCARD)
|
||||
)) return false;
|
||||
|
||||
for (UINT uIndex=0; uIndex!=CTerrainPatch::TERRAIN_VERTEX_COUNT; ++uIndex)
|
||||
*(akDstVertex+uIndex)=*((SoftwareTransformPatch_SSplatVertex*)(akSrcVertex+uIndex));
|
||||
|
||||
pkVB->Unlock();
|
||||
|
||||
STATEMANAGER.SetStreamSource(0, pkVB, sizeof(SoftwareTransformPatch_SSplatVertex));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CMapOutdoor::__SoftwareTransformPatch_SetShadowStream(SoftwareTransformPatch_STLVertex* akSrcVertex)
|
||||
{
|
||||
IDirect3DVertexBuffer8* pkVB=m_kSTPD.m_pkVBSplat[m_kSTPD.m_dwSplatPos++];
|
||||
m_kSTPD.m_dwSplatPos%=SoftwareTransformPatch_SData::SPLAT_VB_NUM;
|
||||
if (!pkVB)
|
||||
return false;
|
||||
|
||||
DWORD dwVBSize=sizeof(SoftwareTransformPatch_SSplatVertex)*CTerrainPatch::TERRAIN_VERTEX_COUNT;
|
||||
SoftwareTransformPatch_SSplatVertex* akDstVertex;
|
||||
if (FAILED(
|
||||
pkVB->Lock(0, dwVBSize, (BYTE**)&akDstVertex, 0)//D3DLOCK_DISCARD)
|
||||
)) return false;
|
||||
|
||||
SoftwareTransformPatch_STLVertex* pkSrcVertex;
|
||||
SoftwareTransformPatch_SSplatVertex* pkDstVertex;
|
||||
for (UINT uIndex=0; uIndex!=CTerrainPatch::TERRAIN_VERTEX_COUNT; ++uIndex)
|
||||
{
|
||||
pkSrcVertex=akSrcVertex+uIndex;
|
||||
pkDstVertex=akDstVertex+uIndex;
|
||||
pkDstVertex->kPosition=pkSrcVertex->kPosition;
|
||||
pkDstVertex->dwDiffuse=pkSrcVertex->dwDiffuse;
|
||||
pkDstVertex->dwSpecular=pkSrcVertex->dwFog;
|
||||
pkDstVertex->kTex1=pkSrcVertex->kTexStaticShadow;
|
||||
pkDstVertex->kTex2=pkSrcVertex->kTexDynamicShadow;
|
||||
}
|
||||
pkVB->Unlock();
|
||||
|
||||
|
||||
ms_lpd3dDevice->SetStreamSource(0, pkVB, sizeof(SoftwareTransformPatch_SSplatVertex));
|
||||
return true;
|
||||
}
|
||||
|
||||
void CMapOutdoor::__SoftwareTransformPatch_Initialize()
|
||||
{
|
||||
{
|
||||
for (UINT uIndex=0; uIndex!=SoftwareTransformPatch_SData::SPLAT_VB_NUM; ++uIndex)
|
||||
m_kSTPD.m_pkVBSplat[uIndex]=NULL;
|
||||
m_kSTPD.m_dwSplatPos=0;
|
||||
}
|
||||
|
||||
{
|
||||
for (UINT uIndex=0; uIndex!=SoftwareTransformPatch_SData::NONE_VB_NUM; ++uIndex)
|
||||
m_kSTPD.m_pkVBNone[uIndex]=NULL;
|
||||
m_kSTPD.m_dwNonePos=0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool CMapOutdoor::__SoftwareTransformPatch_Create()
|
||||
{
|
||||
{
|
||||
for (UINT uIndex=0; uIndex!=SoftwareTransformPatch_SData::SPLAT_VB_NUM; ++uIndex)
|
||||
{
|
||||
assert(NULL==m_kSTPD.m_pkVBSplat[uIndex]);
|
||||
if (FAILED(
|
||||
ms_lpd3dDevice->CreateVertexBuffer(
|
||||
sizeof(SoftwareTransformPatch_SSplatVertex)*CTerrainPatch::TERRAIN_VERTEX_COUNT,
|
||||
D3DUSAGE_DYNAMIC|D3DUSAGE_WRITEONLY,
|
||||
D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_SPECULAR|D3DFVF_TEX2,
|
||||
D3DPOOL_SYSTEMMEM,
|
||||
&m_kSTPD.m_pkVBSplat[uIndex]
|
||||
)
|
||||
)) return false;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
for (UINT uIndex=0; uIndex!=SoftwareTransformPatch_SData::NONE_VB_NUM; ++uIndex)
|
||||
{
|
||||
assert(NULL==m_kSTPD.m_pkVBNone[uIndex]);
|
||||
if (FAILED(
|
||||
ms_lpd3dDevice->CreateVertexBuffer(
|
||||
sizeof(SoftwareTransformPatch_STVertex)*CTerrainPatch::TERRAIN_VERTEX_COUNT,
|
||||
D3DUSAGE_DYNAMIC|D3DUSAGE_WRITEONLY,
|
||||
D3DFVF_XYZRHW,
|
||||
D3DPOOL_SYSTEMMEM,
|
||||
&m_kSTPD.m_pkVBNone[uIndex]
|
||||
)
|
||||
)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CMapOutdoor::__SoftwareTransformPatch_Destroy()
|
||||
{
|
||||
{
|
||||
for (UINT uIndex=0; uIndex!=SoftwareTransformPatch_SData::SPLAT_VB_NUM; ++uIndex)
|
||||
{
|
||||
if (m_kSTPD.m_pkVBSplat[uIndex])
|
||||
m_kSTPD.m_pkVBSplat[uIndex]->Release();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
for (UINT uIndex=0; uIndex!=SoftwareTransformPatch_SData::NONE_VB_NUM; ++uIndex)
|
||||
{
|
||||
if (m_kSTPD.m_pkVBNone[uIndex])
|
||||
m_kSTPD.m_pkVBNone[uIndex]->Release();
|
||||
}
|
||||
}
|
||||
__SoftwareTransformPatch_Initialize();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
// Platform copy of 40250 GameLib/MapOutdoorWater.cpp, as is apart from the includes and the
|
||||
// native-terrain gate: the Godot water adapter draws the surface (PORT-PLAN §3) unless
|
||||
// IsNativeTerrainRenderEnabled().
|
||||
#include "GameLib/StdAfx.h"
|
||||
#include "EterLib/StateManager.h"
|
||||
#include "EterLib/ResourceManager.h"
|
||||
#include "EterBase/Timer.h"
|
||||
#include "../EterLib/RenderCommands3D.h"
|
||||
|
||||
#include "GameLib/MapOutdoor.h"
|
||||
#include "GameLib/TerrainPatch.h"
|
||||
|
||||
void CMapOutdoor::LoadWaterTexture()
|
||||
{
|
||||
UnloadWaterTexture();
|
||||
char buf[256];
|
||||
for (int i = 0; i < 30; ++i)
|
||||
{
|
||||
sprintf(buf, "d:/ymir Work/special/water/%02d.dds", i+1);
|
||||
m_WaterInstances[i].SetImagePointer((CGraphicImage *) CResourceManager::Instance().GetResourcePointer(buf));
|
||||
}
|
||||
}
|
||||
|
||||
void CMapOutdoor::UnloadWaterTexture()
|
||||
{
|
||||
for (int i = 0; i < 30; ++i)
|
||||
m_WaterInstances[i].Destroy();
|
||||
}
|
||||
|
||||
void CMapOutdoor::RenderWater()
|
||||
{
|
||||
if (m_PatchVector.empty())
|
||||
return;
|
||||
|
||||
if (!IsVisiblePart(PART_WATER))
|
||||
return;
|
||||
|
||||
// PORT: see the file comment.
|
||||
if (!IsNativeTerrainRenderEnabled())
|
||||
return;
|
||||
CGraphicTexture* pWaterTexture = m_WaterInstances[((ELTimer_GetMSec() / 70) % 30)].GetTexturePointer();
|
||||
if (!pWaterTexture)
|
||||
return;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// RenderState
|
||||
D3DXMATRIX matTexTransformWater;
|
||||
|
||||
STATEMANAGER.SaveRenderState(D3DRS_ZWRITEENABLE, FALSE);
|
||||
STATEMANAGER.SaveRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
|
||||
STATEMANAGER.SaveRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
|
||||
STATEMANAGER.SaveRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_COLOR1);
|
||||
STATEMANAGER.SaveRenderState(D3DRS_COLORVERTEX, TRUE);
|
||||
|
||||
STATEMANAGER.SetTexture(0, pWaterTexture->GetD3DTexture());
|
||||
|
||||
D3DXMatrixScaling(&matTexTransformWater, m_fWaterTexCoordBase, -m_fWaterTexCoordBase, 0.0f);
|
||||
D3DXMatrixMultiply(&matTexTransformWater, &m_matViewInverse, &matTexTransformWater);
|
||||
|
||||
STATEMANAGER.SaveTransform(D3DTS_TEXTURE0, &matTexTransformWater);
|
||||
STATEMANAGER.SaveVertexShader(D3DFVF_XYZ|D3DFVF_DIFFUSE);
|
||||
|
||||
STATEMANAGER.SaveTextureStageState(0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION);
|
||||
STATEMANAGER.SaveTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2);
|
||||
STATEMANAGER.SaveTextureStageState(0, D3DTSS_MINFILTER, D3DTEXF_LINEAR);
|
||||
STATEMANAGER.SaveTextureStageState(0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR);
|
||||
STATEMANAGER.SaveTextureStageState(0, D3DTSS_MIPFILTER, D3DTEXF_LINEAR);
|
||||
STATEMANAGER.SaveTextureStageState(0, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP);
|
||||
STATEMANAGER.SaveTextureStageState(0, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP);
|
||||
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE);
|
||||
STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||
|
||||
|
||||
STATEMANAGER.SetTexture(1,NULL);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
|
||||
STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
|
||||
|
||||
// RenderState
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// 물 위 아래 애니시키기...
|
||||
static float s_fWaterHeightCurrent = 0;
|
||||
static float s_fWaterHeightBegin = 0;
|
||||
static float s_fWaterHeightEnd = 0;
|
||||
static DWORD s_dwLastHeightChangeTime = CTimer::Instance().GetCurrentMillisecond();
|
||||
static DWORD s_dwBlendtime = 300;
|
||||
|
||||
// 1.5초 마다 변경
|
||||
if ((CTimer::Instance().GetCurrentMillisecond() - s_dwLastHeightChangeTime) > s_dwBlendtime)
|
||||
{
|
||||
s_dwBlendtime = random_range(1000, 3000);
|
||||
|
||||
if (s_fWaterHeightEnd == 0)
|
||||
s_fWaterHeightEnd = -random_range(0, 15);
|
||||
else
|
||||
s_fWaterHeightEnd = 0;
|
||||
|
||||
s_fWaterHeightBegin = s_fWaterHeightCurrent;
|
||||
s_dwLastHeightChangeTime = CTimer::Instance().GetCurrentMillisecond();
|
||||
}
|
||||
|
||||
s_fWaterHeightCurrent = s_fWaterHeightBegin + (s_fWaterHeightEnd - s_fWaterHeightBegin) * (float) ((CTimer::Instance().GetCurrentMillisecond() - s_dwLastHeightChangeTime) / (float) s_dwBlendtime);
|
||||
m_matWorldForCommonUse._43 = s_fWaterHeightCurrent;
|
||||
|
||||
m_matWorldForCommonUse._41 = 0.0f;
|
||||
m_matWorldForCommonUse._42 = 0.0f;
|
||||
STATEMANAGER.SetTransform(D3DTS_WORLD, &m_matWorldForCommonUse);
|
||||
|
||||
float fFogDistance = __GetFogDistance();
|
||||
|
||||
std::vector<std::pair<float, long> >::iterator i;
|
||||
|
||||
for(i = m_PatchVector.begin();i != m_PatchVector.end(); ++i)
|
||||
{
|
||||
if (i->first<fFogDistance)
|
||||
DrawWater(i->second);
|
||||
}
|
||||
|
||||
STATEMANAGER.SetTexture(0, NULL);
|
||||
STATEMANAGER.SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
|
||||
|
||||
for(i = m_PatchVector.begin();i != m_PatchVector.end(); ++i)
|
||||
{
|
||||
if (i->first>=fFogDistance)
|
||||
DrawWater(i->second);
|
||||
}
|
||||
|
||||
// 렌더링 한 후에는 물 z 위치를 복구
|
||||
m_matWorldForCommonUse._43 = 0.0f;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// RenderState
|
||||
STATEMANAGER.RestoreVertexShader();
|
||||
STATEMANAGER.RestoreTransform(D3DTS_TEXTURE0);
|
||||
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_MINFILTER);
|
||||
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_MAGFILTER);
|
||||
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_MIPFILTER);
|
||||
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_ADDRESSU);
|
||||
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_ADDRESSV);
|
||||
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_TEXCOORDINDEX);
|
||||
STATEMANAGER.RestoreTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS);
|
||||
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_DIFFUSEMATERIALSOURCE);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_COLORVERTEX);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_ZWRITEENABLE);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_ALPHABLENDENABLE);
|
||||
STATEMANAGER.RestoreRenderState(D3DRS_CULLMODE);
|
||||
}
|
||||
|
||||
void CMapOutdoor::DrawWater(long patchnum)
|
||||
{
|
||||
assert(NULL!=m_pTerrainPatchProxyList);
|
||||
if (!m_pTerrainPatchProxyList)
|
||||
return;
|
||||
|
||||
CTerrainPatchProxy& rkTerrainPatchProxy = m_pTerrainPatchProxyList[patchnum];
|
||||
|
||||
if (!rkTerrainPatchProxy.isUsed())
|
||||
return;
|
||||
|
||||
if (!rkTerrainPatchProxy.isWaterExists())
|
||||
return;
|
||||
|
||||
CGraphicVertexBuffer* pkVB=rkTerrainPatchProxy.GetWaterVertexBufferPointer();
|
||||
if (!pkVB)
|
||||
return;
|
||||
|
||||
if (!pkVB->GetD3DVertexBuffer())
|
||||
return;
|
||||
|
||||
UINT uPriCount=rkTerrainPatchProxy.GetWaterFaceCount();
|
||||
if (!uPriCount)
|
||||
return;
|
||||
|
||||
STATEMANAGER.SetStreamSource(0, pkVB->GetD3DVertexBuffer(), sizeof(SWaterVertex));
|
||||
STATEMANAGER.DrawPrimitive(D3DPT_TRIANGLELIST, 0, uPriCount);
|
||||
|
||||
ms_faceCount += uPriCount;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#include "AudioCommands.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::mutex g_audio_mutex;
|
||||
std::vector<AudioCommand> g_audio_commands;
|
||||
}
|
||||
|
||||
void PushAudioCommand(const AudioCommand& cmd)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_audio_mutex);
|
||||
g_audio_commands.push_back(cmd);
|
||||
}
|
||||
|
||||
std::vector<AudioCommand> DrainAudioCommands()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_audio_mutex);
|
||||
std::vector<AudioCommand> out;
|
||||
out.swap(g_audio_commands);
|
||||
return out;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct AudioCommand
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
PlaySound2D = 1,
|
||||
PlaySound3D = 2,
|
||||
StopSound3D = 3,
|
||||
StopAllSound3D = 4,
|
||||
PlayMusic = 5,
|
||||
FadeOutMusic = 6,
|
||||
FadeOutAllMusic = 7,
|
||||
SetSoundVolume = 8,
|
||||
SetMusicVolume = 9,
|
||||
FadeInMusic = 10,
|
||||
FadeLimitOutMusic = 11,
|
||||
SetSoundVolume3D = 12,
|
||||
};
|
||||
|
||||
Type type = PlaySound2D;
|
||||
std::string filename;
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
float z = 0.0f;
|
||||
float volume = 1.0f;
|
||||
float speed = 0.0f;
|
||||
int play_count = 1;
|
||||
int id = 0;
|
||||
};
|
||||
|
||||
void PushAudioCommand(const AudioCommand& cmd);
|
||||
std::vector<AudioCommand> DrainAudioCommands();
|
||||
@@ -0,0 +1,50 @@
|
||||
// Platform skeleton for MilesLib/SoundBase.h (40250 MilesLib/SoundBase.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "MilesLib/Stdafx.h"
|
||||
#include "MilesLib/SoundBase.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
CSoundBase::CSoundBase()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CSoundBase::~CSoundBase()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundBase::Initialize() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundBase::Destroy() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundBase::AddFile(DWORD, const char *) -> CSoundData *
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<CSoundData *>();
|
||||
}
|
||||
|
||||
auto CSoundBase::GetFileCRC(const char *) -> DWORD
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<DWORD>();
|
||||
}
|
||||
|
||||
decltype(CSoundBase::ms_iRefCount) CSoundBase::ms_iRefCount{};
|
||||
|
||||
decltype(CSoundBase::ms_DIGDriver) CSoundBase::ms_DIGDriver{};
|
||||
|
||||
decltype(CSoundBase::ms_pProviderDefault) CSoundBase::ms_pProviderDefault{};
|
||||
|
||||
decltype(CSoundBase::ms_ProviderVector) CSoundBase::ms_ProviderVector{};
|
||||
|
||||
decltype(CSoundBase::ms_dataMap) CSoundBase::ms_dataMap{};
|
||||
|
||||
decltype(CSoundBase::ms_bInitialized) CSoundBase::ms_bInitialized{};
|
||||
@@ -0,0 +1,116 @@
|
||||
// Platform skeleton for MilesLib/SoundData.h (40250 MilesLib/SoundData.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "MilesLib/Stdafx.h"
|
||||
#include "MilesLib/SoundData.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
auto CSoundData::SetPackMode() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CSoundData::CSoundData()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CSoundData::~CSoundData()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundData::Assign(const char *) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundData::Get() -> LPVOID
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<LPVOID>();
|
||||
}
|
||||
|
||||
auto CSoundData::GetSize() -> ULONG
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<ULONG>();
|
||||
}
|
||||
|
||||
auto CSoundData::Release() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundData::GetAccessTime() -> DWORD
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<DWORD>();
|
||||
}
|
||||
|
||||
auto CSoundData::GetFileName() -> const char *
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<const char *>();
|
||||
}
|
||||
|
||||
auto CSoundData::SetPlayTime(DWORD) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundData::GetPlayTime() -> DWORD
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<DWORD>();
|
||||
}
|
||||
|
||||
auto CSoundData::ReadFromDisk() -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CSoundData::Destroy() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundData::open_callback(const char *, U32 *) -> U32
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<U32>();
|
||||
}
|
||||
|
||||
auto CSoundData::close_callback(U32) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundData::seek_callback(U32, S32, U32) -> S32
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<S32>();
|
||||
}
|
||||
|
||||
auto CSoundData::read_callback(U32, void *, U32) -> U32
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<U32>();
|
||||
}
|
||||
|
||||
auto CSoundData::isSlotIndex(DWORD) -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CSoundData::GetEmptySlotIndex() -> int
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<int>();
|
||||
}
|
||||
|
||||
decltype(CSoundData::ms_isSoundFile) CSoundData::ms_isSoundFile{};
|
||||
|
||||
decltype(CSoundData::ms_SoundFile) CSoundData::ms_SoundFile{};
|
||||
@@ -0,0 +1,256 @@
|
||||
// Platform skeleton for MilesLib/SoundInstance.h, generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "MilesLib/Stdafx.h"
|
||||
#include "MilesLib/SoundInstance.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
CSoundInstance2D::CSoundInstance2D()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CSoundInstance2D::~CSoundInstance2D()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstance2D::Initialize() -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CSoundInstance2D::Destroy() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstance2D::SetSound(CSoundData *) -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CSoundInstance2D::Play(int, DWORD) const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstance2D::Pause() const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstance2D::Resume() const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstance2D::Stop() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstance2D::GetVolume(float &) const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstance2D::SetVolume(float) const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstance2D::IsDone() const -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CSoundInstance2D::SetPosition(float, float, float) const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstance2D::SetOrientation(float, float, float, float, float, float) const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstance2D::SetVelocity(float, float, float, float) const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CSoundInstance3D::CSoundInstance3D()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CSoundInstance3D::~CSoundInstance3D()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstance3D::Initialize() -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CSoundInstance3D::Destroy() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstance3D::SetSound(CSoundData *) -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CSoundInstance3D::Play(int, DWORD) const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstance3D::Pause() const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstance3D::Resume() const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstance3D::Stop() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstance3D::GetVolume(float &) const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstance3D::SetVolume(float) const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstance3D::IsDone() const -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CSoundInstance3D::SetPosition(float, float, float) const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstance3D::SetOrientation(float, float, float, float, float, float) const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstance3D::SetVelocity(float, float, float, float) const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstance3D::UpdatePosition(float) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CSoundInstanceStream::CSoundInstanceStream()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CSoundInstanceStream::~CSoundInstanceStream()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstanceStream::Initialize() -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CSoundInstanceStream::Destroy() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstanceStream::SetStream(HSTREAM) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstanceStream::SetSound(CSoundData *) -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CSoundInstanceStream::Play(int, DWORD) const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstanceStream::Pause() const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstanceStream::Resume() const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstanceStream::Stop() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstanceStream::GetVolume(float &) const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstanceStream::SetVolume(float) const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstanceStream::IsDone() const -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CSoundInstanceStream::IsData() const -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CSoundInstanceStream::SetPosition(float, float, float) const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstanceStream::SetOrientation(float, float, float, float, float, float) const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundInstanceStream::SetVelocity(float, float, float, float) const -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
// Platform implementation of MilesLib/SoundManager.h for port/headless execution.
|
||||
#include "MilesLib/Stdafx.h"
|
||||
#include "MilesLib/SoundManager.h"
|
||||
#include "AudioCommands.h"
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
decltype(CSoundManager::ms_SoundManager2D) CSoundManager::ms_SoundManager2D{};
|
||||
decltype(CSoundManager::ms_SoundManager3D) CSoundManager::ms_SoundManager3D{};
|
||||
decltype(CSoundManager::ms_SoundManagerStream) CSoundManager::ms_SoundManagerStream{};
|
||||
|
||||
CSoundManager::CSoundManager()
|
||||
{
|
||||
m_bInitialized = FALSE;
|
||||
m_isSoundDisable = FALSE;
|
||||
|
||||
m_fxPosition = 0.0f;
|
||||
m_fyPosition = 0.0f;
|
||||
m_fzPosition = 0.0f;
|
||||
|
||||
m_fSoundScale = 200.0f;
|
||||
m_fAmbienceSoundScale = 1000.0f;
|
||||
m_fSoundVolume = 1.0f;
|
||||
m_fMusicVolume = 1.0f;
|
||||
|
||||
m_fBackupMusicVolume = 0.0f;
|
||||
m_fBackupSoundVolume = 0.0f;
|
||||
|
||||
for (int i = 0; i < CSoundManagerStream::MUSIC_INSTANCE_MAX_NUM; ++i)
|
||||
{
|
||||
TMusicInstance & rInstance = m_MusicInstances[i];
|
||||
rInstance.dwMusicFileNameCRC = 0;
|
||||
rInstance.MusicState = MUSIC_STATE_OFF;
|
||||
rInstance.fVolume = 0.0f;
|
||||
rInstance.fLimitVolume = 0.0f;
|
||||
rInstance.fVolumeSpeed = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
CSoundManager::~CSoundManager()
|
||||
{
|
||||
}
|
||||
|
||||
BOOL CSoundManager::Create()
|
||||
{
|
||||
m_bInitialized = TRUE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void CSoundManager::Destroy()
|
||||
{
|
||||
m_PlaySoundHistoryMap.clear();
|
||||
m_bInitialized = FALSE;
|
||||
}
|
||||
|
||||
void CSoundManager::SetPosition(float fx, float fy, float fz)
|
||||
{
|
||||
m_fxPosition = fx;
|
||||
m_fyPosition = fy;
|
||||
m_fzPosition = fz;
|
||||
}
|
||||
|
||||
void CSoundManager::SetDirection(float, float, float, float, float, float)
|
||||
{
|
||||
}
|
||||
|
||||
void CSoundManager::Update()
|
||||
{
|
||||
}
|
||||
|
||||
float CSoundManager::GetSoundScale()
|
||||
{
|
||||
return m_fSoundScale;
|
||||
}
|
||||
|
||||
void CSoundManager::SetSoundScale(float fScale)
|
||||
{
|
||||
m_fSoundScale = fScale;
|
||||
}
|
||||
|
||||
void CSoundManager::SetAmbienceSoundScale(float fScale)
|
||||
{
|
||||
m_fAmbienceSoundScale = fScale;
|
||||
}
|
||||
|
||||
float CSoundManager::__ConvertRatioVolumeToApplyVolume(float fRatioVolume)
|
||||
{
|
||||
if (0.1f > fRatioVolume)
|
||||
return fRatioVolume;
|
||||
|
||||
return (float)pow(10.0f, (-1.0f + fRatioVolume));
|
||||
}
|
||||
|
||||
float CSoundManager::__ConvertGradeVolumeToApplyVolume(int nGradeVolume)
|
||||
{
|
||||
return __ConvertRatioVolumeToApplyVolume(nGradeVolume / 5.0f);
|
||||
}
|
||||
|
||||
void CSoundManager::SetSoundVolume(float fVolume)
|
||||
{
|
||||
if (m_isSoundDisable)
|
||||
{
|
||||
m_fBackupSoundVolume = fVolume;
|
||||
return;
|
||||
}
|
||||
|
||||
if (fVolume < 0.0f) fVolume = 0.0f;
|
||||
if (fVolume > 1.0f) fVolume = 1.0f;
|
||||
m_fSoundVolume = fVolume;
|
||||
|
||||
if (!m_isSoundDisable)
|
||||
{
|
||||
m_fBackupSoundVolume = fVolume;
|
||||
}
|
||||
AudioCommand cmd;
|
||||
cmd.type = AudioCommand::SetSoundVolume;
|
||||
cmd.volume = m_fSoundVolume;
|
||||
PushAudioCommand(cmd);
|
||||
}
|
||||
|
||||
void CSoundManager::SetSoundVolumeRatio(float fRatio)
|
||||
{
|
||||
float fVolume = __ConvertRatioVolumeToApplyVolume(fRatio);
|
||||
SetSoundVolume(fVolume);
|
||||
}
|
||||
|
||||
void CSoundManager::SetSoundVolumeGrade(int iGrade)
|
||||
{
|
||||
float fVolume = __ConvertGradeVolumeToApplyVolume(iGrade);
|
||||
SetSoundVolume(fVolume);
|
||||
}
|
||||
|
||||
void CSoundManager::__SetMusicVolume(float fVolume)
|
||||
{
|
||||
if (m_isSoundDisable)
|
||||
{
|
||||
m_fBackupMusicVolume = fVolume;
|
||||
return;
|
||||
}
|
||||
|
||||
if (fVolume < 0.0f) fVolume = 0.0f;
|
||||
if (fVolume > 1.0f) fVolume = 1.0f;
|
||||
m_fMusicVolume = fVolume;
|
||||
|
||||
if (!m_isSoundDisable)
|
||||
{
|
||||
m_fBackupMusicVolume = fVolume;
|
||||
}
|
||||
AudioCommand cmd;
|
||||
cmd.type = AudioCommand::SetMusicVolume;
|
||||
cmd.volume = m_fMusicVolume;
|
||||
PushAudioCommand(cmd);
|
||||
}
|
||||
|
||||
void CSoundManager::SetMusicVolume(float fVolume)
|
||||
{
|
||||
__SetMusicVolume(fVolume);
|
||||
}
|
||||
|
||||
void CSoundManager::SetMusicVolumeRatio(float fRatio)
|
||||
{
|
||||
float fVolume = __ConvertRatioVolumeToApplyVolume(fRatio);
|
||||
SetMusicVolume(fVolume);
|
||||
}
|
||||
|
||||
void CSoundManager::SetMusicVolumeGrade(int iGrade)
|
||||
{
|
||||
float fVolume = __ConvertGradeVolumeToApplyVolume(iGrade);
|
||||
SetMusicVolume(fVolume);
|
||||
}
|
||||
|
||||
void CSoundManager::SaveVolume()
|
||||
{
|
||||
if (m_isSoundDisable)
|
||||
return;
|
||||
const float musicVolume = m_fMusicVolume;
|
||||
const float soundVolume = m_fSoundVolume;
|
||||
SetMusicVolume(0.0f);
|
||||
SetSoundVolume(0.0f);
|
||||
m_fBackupMusicVolume = musicVolume;
|
||||
m_fBackupSoundVolume = soundVolume;
|
||||
m_isSoundDisable = TRUE;
|
||||
}
|
||||
|
||||
void CSoundManager::RestoreVolume()
|
||||
{
|
||||
m_isSoundDisable = FALSE;
|
||||
SetMusicVolume(m_fBackupMusicVolume);
|
||||
SetSoundVolume(m_fBackupSoundVolume);
|
||||
}
|
||||
|
||||
float CSoundManager::GetSoundVolume()
|
||||
{
|
||||
return m_fSoundVolume;
|
||||
}
|
||||
|
||||
float CSoundManager::GetMusicVolume()
|
||||
{
|
||||
return m_fMusicVolume;
|
||||
}
|
||||
|
||||
void CSoundManager::PlaySound2D(const char * c_szFileName)
|
||||
{
|
||||
if (m_isSoundDisable || !c_szFileName || !c_szFileName[0])
|
||||
return;
|
||||
AudioCommand cmd;
|
||||
cmd.type = AudioCommand::PlaySound2D;
|
||||
cmd.filename = c_szFileName;
|
||||
cmd.volume = m_fSoundVolume;
|
||||
PushAudioCommand(cmd);
|
||||
}
|
||||
|
||||
void CSoundManager::PlaySound3D(float fx, float fy, float fz, const char * c_szFileName, int iPlayCount)
|
||||
{
|
||||
if (m_isSoundDisable || !c_szFileName || !c_szFileName[0])
|
||||
return;
|
||||
AudioCommand cmd;
|
||||
cmd.type = AudioCommand::PlaySound3D;
|
||||
cmd.filename = c_szFileName;
|
||||
cmd.x = fx;
|
||||
cmd.y = fy;
|
||||
cmd.z = fz;
|
||||
cmd.play_count = iPlayCount;
|
||||
cmd.volume = m_fSoundVolume;
|
||||
PushAudioCommand(cmd);
|
||||
}
|
||||
|
||||
void CSoundManager::StopSound3D(int iIndex)
|
||||
{
|
||||
AudioCommand cmd;
|
||||
cmd.type = AudioCommand::StopSound3D;
|
||||
cmd.id = iIndex;
|
||||
PushAudioCommand(cmd);
|
||||
}
|
||||
|
||||
int CSoundManager::PlayAmbienceSound3D(float fx, float fy, float fz, const char * c_szFileName, int iPlayCount)
|
||||
{
|
||||
if (m_isSoundDisable || !c_szFileName || !c_szFileName[0])
|
||||
return -1;
|
||||
static int s_next_id = 1;
|
||||
int id = s_next_id++;
|
||||
AudioCommand cmd;
|
||||
cmd.type = AudioCommand::PlaySound3D;
|
||||
cmd.filename = c_szFileName;
|
||||
cmd.x = fx;
|
||||
cmd.y = fy;
|
||||
cmd.z = fz;
|
||||
cmd.play_count = iPlayCount;
|
||||
cmd.volume = m_fSoundVolume;
|
||||
cmd.id = id;
|
||||
PushAudioCommand(cmd);
|
||||
return id;
|
||||
}
|
||||
|
||||
void CSoundManager::PlayCharacterSound3D(float fx, float fy, float fz, const char * c_szFileName, BOOL)
|
||||
{
|
||||
PlaySound3D(fx, fy, fz, c_szFileName, 1);
|
||||
}
|
||||
|
||||
void CSoundManager::SetSoundVolume3D(int index, float volume)
|
||||
{
|
||||
AudioCommand cmd;
|
||||
cmd.type = AudioCommand::SetSoundVolume3D;
|
||||
cmd.id = index;
|
||||
cmd.volume = volume;
|
||||
PushAudioCommand(cmd);
|
||||
}
|
||||
|
||||
void CSoundManager::StopAllSound3D()
|
||||
{
|
||||
m_PlaySoundHistoryMap.clear();
|
||||
AudioCommand cmd;
|
||||
cmd.type = AudioCommand::StopAllSound3D;
|
||||
PushAudioCommand(cmd);
|
||||
}
|
||||
|
||||
void CSoundManager::PlayMusic(const char * c_szFileName)
|
||||
{
|
||||
if (!c_szFileName || !c_szFileName[0])
|
||||
return;
|
||||
AudioCommand cmd;
|
||||
cmd.type = AudioCommand::PlayMusic;
|
||||
cmd.filename = c_szFileName;
|
||||
cmd.volume = m_fMusicVolume;
|
||||
PushAudioCommand(cmd);
|
||||
}
|
||||
|
||||
void CSoundManager::FadeInMusic(const char * c_szFileName, float speed)
|
||||
{
|
||||
if (!c_szFileName || !c_szFileName[0])
|
||||
return;
|
||||
AudioCommand cmd;
|
||||
cmd.type = AudioCommand::FadeInMusic;
|
||||
cmd.filename = c_szFileName;
|
||||
cmd.volume = m_fMusicVolume;
|
||||
cmd.speed = speed;
|
||||
PushAudioCommand(cmd);
|
||||
}
|
||||
|
||||
void CSoundManager::FadeOutMusic(const char * c_szFileName, float fSpeed)
|
||||
{
|
||||
AudioCommand cmd;
|
||||
cmd.type = AudioCommand::FadeOutMusic;
|
||||
cmd.filename = c_szFileName ? c_szFileName : "";
|
||||
cmd.speed = fSpeed;
|
||||
PushAudioCommand(cmd);
|
||||
}
|
||||
|
||||
void CSoundManager::FadeLimitOutMusic(const char * c_szFileName, float limit, float speed)
|
||||
{
|
||||
AudioCommand cmd;
|
||||
cmd.type = AudioCommand::FadeLimitOutMusic;
|
||||
cmd.filename = c_szFileName ? c_szFileName : "";
|
||||
cmd.volume = __ConvertRatioVolumeToApplyVolume(limit);
|
||||
cmd.speed = speed;
|
||||
PushAudioCommand(cmd);
|
||||
}
|
||||
|
||||
void CSoundManager::FadeOutAllMusic()
|
||||
{
|
||||
AudioCommand cmd;
|
||||
cmd.type = AudioCommand::FadeOutAllMusic;
|
||||
PushAudioCommand(cmd);
|
||||
}
|
||||
|
||||
void CSoundManager::FadeAll()
|
||||
{
|
||||
FadeOutAllMusic();
|
||||
}
|
||||
|
||||
void CSoundManager::UpdateSoundData(DWORD, const NSound::TSoundDataVector *)
|
||||
{
|
||||
}
|
||||
|
||||
void CSoundManager::UpdateSoundData(float, float, float, DWORD, const NSound::TSoundDataVector *)
|
||||
{
|
||||
}
|
||||
|
||||
void CSoundManager::UpdateSoundInstance(float, float, float, DWORD, const NSound::TSoundInstanceVector *, BOOL)
|
||||
{
|
||||
}
|
||||
|
||||
void CSoundManager::UpdateSoundInstance(DWORD, const NSound::TSoundInstanceVector *)
|
||||
{
|
||||
}
|
||||
|
||||
void CSoundManager::PlayMusic(DWORD, const char *, float, float)
|
||||
{
|
||||
}
|
||||
|
||||
void CSoundManager::StopMusic(DWORD)
|
||||
{
|
||||
}
|
||||
|
||||
BOOL CSoundManager::GetMusicIndex(const char *, DWORD *)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BOOL CSoundManager::GetSoundInstance2D(const char *, ISoundInstance **)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BOOL CSoundManager::GetSoundInstance3D(const char *, ISoundInstance **)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Platform skeleton for MilesLib/SoundManager2D.h (40250 MilesLib/SoundManager2D.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "MilesLib/Stdafx.h"
|
||||
#include "MilesLib/SoundManager2D.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
CSoundManager2D::CSoundManager2D()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CSoundManager2D::~CSoundManager2D()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundManager2D::Initialize() -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CSoundManager2D::Destroy() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundManager2D::GetInstance(const char *) -> ISoundInstance *
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<ISoundInstance *>();
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// Platform skeleton for MilesLib/SoundManager3D.h (40250 MilesLib/SoundManager3D.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
#include "MilesLib/Stdafx.h"
|
||||
#include "MilesLib/SoundManager3D.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
CSoundManager3D::CSoundManager3D()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CSoundManager3D::~CSoundManager3D()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundManager3D::Initialize() -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CSoundManager3D::Destroy() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundManager3D::GetEmptyInstanceIndex() -> int
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<int>();
|
||||
}
|
||||
|
||||
auto CSoundManager3D::SetInstance(const char *) -> int
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<int>();
|
||||
}
|
||||
|
||||
auto CSoundManager3D::GetInstance(DWORD) -> ISoundInstance *
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<ISoundInstance *>();
|
||||
}
|
||||
|
||||
auto CSoundManager3D::SetListenerDirection(float, float, float, float, float, float) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundManager3D::SetListenerPosition(float, float, float) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundManager3D::SetListenerVelocity(float, float, float, float) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundManager3D::Lock(int) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundManager3D::Unlock(int) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CSoundManager3D::IsValidInstanceIndex(int) -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user