OpenGothic
Open source reimplementation of Gothic I and II
Loading...
Searching...
No Matches
resources.cpp
Go to the documentation of this file.
1#include "resources.h"
2
3#include <Tempest/MemReader>
4#include <Tempest/Pixmap>
5#include <Tempest/Device>
6#include <Tempest/Dir>
7#include <Tempest/Application>
8#include <Tempest/Sound>
9#include <Tempest/SoundEffect>
10#include <Tempest/TextCodec>
11#include <Tempest/Log>
12#include <Tempest/Color>
13
14#include <zenkit/MultiResolutionMesh.hh>
15#include <zenkit/ModelHierarchy.hh>
16#include <zenkit/Model.hh>
17#include <zenkit/ModelScript.hh>
18#include <zenkit/Material.hh>
19#include <zenkit/Texture.hh>
20#include <zenkit/addon/texcvt.hh>
21#include <zenkit/Texture.hh>
22
29#include "graphics/material.h"
30#include "dmusic/directmusic.h"
31#include "utils/fileext.h"
32#include "utils/gthfont.h"
33
34#include "gothic.h"
35#include "utils/string_frm.h"
36
37#include <dmusic.h>
38
39using namespace Tempest;
40
41Resources* Resources::inst=nullptr;
42
43static void emplaceTag(char* buf, char tag){
44 for(size_t i=1;buf[i];++i){
45 if(buf[i]==tag && buf[i-1]=='_' && buf[i+1]=='0'){
46 buf[i ]='%';
47 buf[i+1]='s';
48 ++i;
49 return;
50 }
51 }
52 }
53
54Resources::Resources(Tempest::Device &device)
55 : dev(device) {
56 inst=this;
57
58 static const uint16_t index[] = {
59 0, 1, 2, 0, 2, 3,
60 4, 6, 5, 4, 7, 6,
61 1, 5, 2, 2, 5, 6,
62 4, 0, 7, 7, 0, 3,
63 3, 2, 7, 7, 2, 6,
64 4, 5, 0, 0, 5, 1
65 };
66 cube = device.ibo(index, sizeof(index)/sizeof(index[0]));
67
68 dxMusic.reset(new Dx8::DirectMusic());
69 // G2
70 dxMusic->addPath(Gothic::nestedPath({u"_work",u"Data",u"Music",u"newworld"}, Dir::FT_Dir));
71 dxMusic->addPath(Gothic::nestedPath({u"_work",u"Data",u"Music",u"AddonWorld"},Dir::FT_Dir));
72 // G1
73 dxMusic->addPath(Gothic::nestedPath({u"_work",u"Data",u"Music",u"dungeon"}, Dir::FT_Dir));
74 dxMusic->addPath(Gothic::nestedPath({u"_work",u"Data",u"Music",u"menu_men"}, Dir::FT_Dir));
75 dxMusic->addPath(Gothic::nestedPath({u"_work",u"Data",u"Music",u"orchestra"},Dir::FT_Dir));
76 // switch-build
77 dxMusic->addPath(Gothic::nestedPath({u"_work",u"Data",u"Music"},Dir::FT_Dir));
78
79 fBuff .reserve(8*1024*1024);
80 ddsBuf.reserve(8*1024*1024);
81
82 {
83 Pixmap pm(1,1,TextureFormat::RGBA8);
84 uint8_t* pix = reinterpret_cast<uint8_t*>(pm.data());
85 pix[0]=255;
86 pix[3]=255;
87 fallback = device.texture(pm);
88 }
89
90 {
91 Pixmap pm(1,1,TextureFormat::RGBA8);
92 fbZero = device.texture(pm);
93 }
94
95 fbImg = device.image2d(TextureFormat::R32U,1,1);
96
97 // Set up the DirectMusic loader
98 DmResult rv = DmLoader_create(&dmLoader, DmLoader_DOWNLOAD);
99 if(rv != DmResult_SUCCESS) {
100 Log::e("Failed to created DmLoader object. Out of memory?");
101 }
102
103 DmLoader_addResolver(dmLoader, [](void* ctx, char const* name, size_t* len) -> void* {
104 auto* slf = reinterpret_cast<Resources*>(ctx);
105 auto* node = slf->gothicAssets.find(name);
106
107 if (node == nullptr) {
108 return nullptr;
109 }
110
111 auto reader = node->open_read();
112
113 reader->seek(0, zenkit::Whence::END);
114 *len = reader->tell();
115
116 reader->seek(0, zenkit::Whence::BEG);
117 auto* bytes = static_cast<uint8_t *>(malloc(*len));
118 reader->read(bytes, *len);
119
120 return bytes;
121 }, this);
122 }
123
124void Resources::mountWork(const std::filesystem::path& path) {
125 inst->gothicAssets.mkdir("/_work");
126 inst->gothicAssets.mount_host(path, "/_work", zenkit::VfsOverwriteBehavior::NONE);
127 }
128
129void Resources::loadVdfs(const std::vector<std::u16string>& modvdfs, bool modFilter) {
130 std::vector<Archive> archives;
131 inst->detectVdf(archives,Gothic::inst().nestedPath({u"Data"},Dir::FT_Dir));
132
133 // Remove all mod files, that are not listed in modvdfs
134 if(modFilter) {
135 // NOTE: apparently in CoM there is no mods list declaration. In such case - assume all modes
136 archives.erase(std::remove_if(archives.begin(), archives.end(),
137 [&modvdfs](const Archive& a){
138 return a.isMod && modvdfs.end() == std::find_if(modvdfs.begin(), modvdfs.end(),
139 [&a](const std::u16string& modname) {
140 const std::u16string_view& full_path = a.name;
141 const std::u16string_view& file_name = modname;
142 return (0 == full_path.compare(full_path.length() - file_name.length(),
143 file_name.length(), file_name));
144 });
145 }), archives.end());
146 }
147
148 // addon archives first!
149 std::stable_sort(archives.begin(),archives.end(),[](const Archive& a,const Archive& b){
150 int aIsMod = a.isMod ? 1 : -1;
151 int bIsMod = b.isMod ? 1 : -1;
152 return std::make_tuple(aIsMod,a.time,int(a.ord)) >=
153 std::make_tuple(bIsMod,b.time,int(b.ord));
154 });
155
156 for(auto& i:archives) {
157 try {
158 auto in = zenkit::Read::from(i.name);
159#ifdef __IOS__
160 // causes OOM on iPhone7
161 if(i.name.find(u"Speech")!=std::string::npos)
162 continue;
163#endif
164 inst->gothicAssets.mount_disk(i.name, zenkit::VfsOverwriteBehavior::OLDER);
165 }
166 catch(const zenkit::VfsBrokenDiskError& err) {
167 Log::e("unable to load archive: \"", TextCodec::toUtf8(i.name), "\", reason: ", err.what());
168 }
169 catch(const std::exception& err) {
170 Log::e("unable to load archive: \"", TextCodec::toUtf8(i.name), "\", reason: ", err.what());
171 }
172 }
173
174 //for(auto& i:inst->gothicAssets.getKnownFiles())
175 // Log::i(i);
176
177 // auto v = getFileData("DRAGONISLAND.ZEN");
178 // Tempest::WFile f("../../internal/DRAGONISLAND.ZEN");
179 // f.write(v.data(),v.size());
180 }
181
183 DmLoader_release(dmLoader);
184 inst=nullptr;
185 }
186
187bool Resources::hasFile(std::string_view name) {
188 std::lock_guard<std::recursive_mutex> g(inst->sync);
189 return inst->gothicAssets.find(name) != nullptr;
190 }
191
192bool Resources::getFileData(std::string_view name, std::vector<uint8_t> &dat) {
193 dat.clear();
194
195 const auto* entry = Resources::vdfsIndex().find(name);
196 if(entry==nullptr)
197 return false;
198
199 // TODO: This should return a buffer!
200 auto reader = entry->open_read();
201
202 reader->seek(0, zenkit::Whence::END);
203 dat.resize(reader->tell());
204 reader->seek(0, zenkit::Whence::BEG);
205 reader->read(dat.data(), dat.size());
206
207 return true;
208 }
209
210std::vector<uint8_t> Resources::getFileData(std::string_view name) {
211 std::vector<uint8_t> data;
212 getFileData(name,data);
213 return data;
214 }
215
216std::unique_ptr<zenkit::Read> Resources::getFileBuffer(std::string_view name) {
217 const auto* entry = Resources::vdfsIndex().find(name);
218 if (entry == nullptr)
219 throw std::runtime_error("failed to open resource: " + std::string{name});
220 return entry->open_read();
221 }
222
223std::unique_ptr<zenkit::ReadArchive> Resources::openReader(std::string_view name, std::unique_ptr<zenkit::Read>& read) {
224 const auto* entry = Resources::vdfsIndex().find(name);
225 if(entry == nullptr)
226 throw std::runtime_error("failed to open resource: " + std::string{name});
227 auto buf = entry->open_read();
228 if(buf == nullptr)
229 throw std::runtime_error("failed to open resource: " + std::string{name});
230 auto zen = zenkit::ReadArchive::from(buf.get());
231 if(zen == nullptr)
232 throw std::runtime_error("failed to open resource: " + std::string{name});
233 read = std::move(buf);
234 return zen;
235 }
236
237const char* Resources::renderer() {
238 return inst->dev.properties().name;
239 }
240
241static Sampler implShadowSampler() {
242 Tempest::Sampler smp;
243 smp.setFiltration(Tempest::Filter::Nearest);
244 smp.setClamping(Tempest::ClampMode::ClampToEdge);
245 smp.anisotropic = false;
246 return smp;
247 }
248
249const Sampler& Resources::shadowSampler() {
250 static Tempest::Sampler smp = implShadowSampler();
251 return smp;
252 }
253
254void Resources::detectVdf(std::vector<Archive>& ret, const std::u16string &root) {
255 Dir::scan(root,[this,&root,&ret](const std::u16string& vdf, Dir::FileType t){
256 if(t==Dir::FT_File) {
257 auto file = root + vdf;
258 Archive ar;
259 ar.name = root+vdf;
260 ar.time = vdfTimestamp(ar.name);
261 ar.ord = uint16_t(ret.size());
262 ar.isMod = vdf.rfind(u".mod")==vdf.size()-4;
263
264 if(std::filesystem::file_size(ar.name)>0)
265 ret.emplace_back(std::move(ar));
266 return;
267 }
268
269 // switch uses NX extension for language dependant stuff
270 if(vdf.find(u".NX.")!=std::string::npos)
271 return;
272
273 if(t==Dir::FT_Dir && vdf!=u".." && vdf!=u".") {
274 auto dir = root + vdf + u"/";
275 detectVdf(ret,dir);
276 }
277 });
278 }
279
280const GthFont& Resources::dialogFont(const float scale) {
281 return font("font_old_10_white.tga",FontType::Normal,scale);
282 }
283
284const GthFont& Resources::font(const float scale) {
285 return font("font_old_10_white.tga",FontType::Normal,scale);
286 }
287
288const GthFont& Resources::font(Resources::FontType type, const float scale) {
289 return font("font_old_10_white.tga",type,scale);
290 }
291
292const GthFont& Resources::font(std::string_view fname, FontType type, const float scale) {
293 if(fname.empty())
294 return font(scale);
295 return inst->implLoadFont(fname, type, scale);
296 }
297
298const Texture2d& Resources::fallbackTexture() {
299 return inst->fallback;
300 }
301
302const Texture2d &Resources::fallbackBlack() {
303 return inst->fbZero;
304 }
305
306const Tempest::StorageImage& Resources::fallbackImage() {
307 return inst->fbImg;
308 }
309
310const zenkit::Vfs& Resources::vdfsIndex() {
311 return inst->gothicAssets;
312 }
313
314const Tempest::IndexBuffer<uint16_t>& Resources::cubeIbo() {
315 return inst->cube;
316 }
317
318int64_t Resources::vdfTimestamp(const std::u16string& name) {
319 enum {
320 VDF_COMMENT_LENGTH = 256,
321 VDF_SIGNATURE_LENGTH = 16,
322 };
323 uint32_t count=0, timestamp=0;
324 char sign[VDF_SIGNATURE_LENGTH]={};
325
326 try {
327 RFile fin(name);
328 fin.seek(VDF_COMMENT_LENGTH);
329 fin.read(sign,VDF_SIGNATURE_LENGTH);
330 fin.read(&count,sizeof(count));
331 fin.seek(4);
332 fin.read(&timestamp,sizeof(timestamp));
333
334 return int64_t(timestamp);
335 }
336 catch(...) {
337 return -1;
338 }
339 }
340
341Tempest::Texture2d* Resources::implLoadTexture(std::string_view cname, bool forceMips) {
342 if(cname.empty())
343 return nullptr;
344
345 //TODO: __cpp_lib_generic_unordered_lookup
346 std::string name = std::string(cname);
347 auto it=texCache.find(name);
348 if(it!=texCache.end())
349 return it->second.get();
350
351 auto tex = implLoadTextureUncached(cname, forceMips);
352 if(!tex.isEmpty()) {
353 std::unique_ptr<Texture2d> t{new Texture2d(std::move(tex))};
354 Texture2d* ret=t.get();
355 texCache[std::move(name)] = std::move(t);
356 return ret;
357 }
358 texCache[std::move(name)] = nullptr;
359 return nullptr;
360 }
361
362Texture2d Resources::implLoadTextureUncached(std::string_view name, bool forceMips) {
363 if(name.empty())
364 return Texture2d();
365
366 if(FileExt::hasExt(name,"TGA")) {
367 auto nameAlt = std::string(name);
368 nameAlt.resize(nameAlt.size() + 2);
369 std::memcpy(&nameAlt[0]+nameAlt.size()-6, "-C.TEX", 6);
370
371 if(const auto* entry = Resources::vdfsIndex().find(nameAlt)) {
372 zenkit::Texture tex;
373
374 auto reader = entry->open_read();
375 tex.load(reader.get());
376
377 if(tex.format() == zenkit::TextureFormat::DXT1 ||
378 tex.format() == zenkit::TextureFormat::DXT2 ||
379 tex.format() == zenkit::TextureFormat::DXT3 ||
380 tex.format() == zenkit::TextureFormat::DXT4 ||
381 tex.format() == zenkit::TextureFormat::DXT5) {
382 auto dds = zenkit::to_dds(tex);
383 auto ddsRead = zenkit::Read::from(dds);
384
385 return implLoadTextureUncached(name, *ddsRead, forceMips);
386 } else {
387 auto rgba = tex.as_rgba8(0);
388
389 try {
390 Tempest::Pixmap pm(tex.width(), tex.height(), TextureFormat::RGBA8);
391 std::memcpy(pm.data(), rgba.data(), rgba.size());
392 return dev.texture(pm);
393 }
394 catch (...) {
395 }
396 }
397 }
398 }
399
400 if(auto* entry = Resources::vdfsIndex().find(name)) {
401 auto reader = entry->open_read();
402 return implLoadTextureUncached(name, *reader, forceMips);
403 }
404 return Texture2d();
405 }
406
407Texture2d Resources::implLoadTextureUncached(std::string_view name, zenkit::Read& data, bool forceMips) {
408 try {
409 std::vector<uint8_t> raw;
410 data.seek(0, zenkit::Whence::END);
411 raw.resize(data.tell());
412 data.seek(0, zenkit::Whence::BEG);
413 data.read(raw.data(), raw.size());
414
415 Tempest::MemReader rd((uint8_t*)raw.data(), raw.size());
416 Tempest::Pixmap pm(rd);
417
418 const bool useMipmap = forceMips || (pm.mipCount()>1); // do not generate mips, if original texture has has none
419 return dev.texture(pm, useMipmap);
420 }
421 catch(...){
422 return Texture2d();
423 }
424 }
425
426ProtoMesh* Resources::implLoadMesh(std::string_view name) {
427 if(name.size()==0)
428 return nullptr;
429
430 auto cname = std::string(name);
431 auto it = aniMeshCache.find(cname);
432 if(it!=aniMeshCache.end())
433 return it->second.get();
434
435 auto t = implLoadMeshMain(cname);
436 auto ret = t.get();
437 aniMeshCache[cname] = std::move(t);
438 if(ret==nullptr)
439 Log::e("unable to load mesh \"",cname,"\"");
440 return ret;
441 }
442
443std::unique_ptr<ProtoMesh> Resources::implLoadMeshMain(std::string name) {
444 if(FileExt::hasExt(name,"3DS")) {
445 FileExt::exchangeExt(name,"3DS","MRM");
446
447 const auto* entry = Resources::vdfsIndex().find(name);
448 if(entry == nullptr)
449 return nullptr;
450
451 zenkit::MultiResolutionMesh zmsh;
452
453 auto reader = entry->open_read();
454 zmsh.load(reader.get());
455
456 if(zmsh.sub_meshes.empty())
457 return nullptr;
458
460 return std::unique_ptr<ProtoMesh>{new ProtoMesh(std::move(packed),name)};
461 }
462
463 if(FileExt::hasExt(name,"MMS") || FileExt::hasExt(name,"MMB")) {
464 FileExt::exchangeExt(name,"MMS","MMB");
465
466 const auto* entry = Resources::vdfsIndex().find(name);
467 if(entry == nullptr)
468 throw std::runtime_error("failed to open resource: " + name);
469
470 zenkit::MorphMesh zmm;
471 auto reader = entry->open_read();
472 zmm.load(reader.get());
473
474 if(zmm.mesh.sub_meshes.empty())
475 return nullptr;
476
477 PackedMesh packed(zmm.mesh,PackedMesh::PK_VisualMorph);
478 return std::unique_ptr<ProtoMesh>{new ProtoMesh(std::move(packed),zmm.animations,name)};
479 }
480
481 if(FileExt::hasExt(name,"MDS")) {
482 auto anim = Resources::loadAnimation(name);
483 if(anim==nullptr)
484 return nullptr;
485
486 auto mesh = std::string(anim->defaultMesh());
487
488 FileExt::exchangeExt(mesh,nullptr,"MDM") ||
489 FileExt::exchangeExt(mesh,"ASC", "MDM");
490
491 auto mdhName = std::string(anim->defaultMesh());
492 if(mdhName.empty())
493 mdhName = name;
494 FileExt::assignExt(mdhName,"MDH");
495
496 if(const auto* entryMdh = Resources::vdfsIndex().find(mdhName)) {
497 // Find a MDH hirarchy file and separate mesh
498 zenkit::ModelHierarchy mdh;
499 auto reader = entryMdh->open_read();
500 mdh.load(reader.get());
501
502 std::unique_ptr<Skeleton> sk{new Skeleton(mdh,anim,name)};
503 std::unique_ptr<ProtoMesh> t;
504
505 if(const auto* entry = Resources::vdfsIndex().find(mesh)) {
506 auto reader = entry->open_read();
507
508 zenkit::ModelMesh mdm {};
509 mdm.load(reader.get());
510 t.reset(new ProtoMesh(mdm,std::move(sk),name));
511 }
512 else
513 t.reset(new ProtoMesh(mdh,std::move(sk),name));
514
515 return t;
516 }
517
518 // MDL files contain both the hirarchy and mesh, try to find one as a fallback
519 FileExt::exchangeExt(mdhName,"MDH","MDL");
520 if(const auto* entryMdl = Resources::vdfsIndex().find(mdhName)) {
521 if(entryMdl==nullptr)
522 throw std::runtime_error("failed to open resource: " + mdhName);
523
524 zenkit::Model mdm;
525 auto reader = entryMdl->open_read();
526 mdm.load(reader.get());
527
528 std::unique_ptr<Skeleton> sk{new Skeleton(mdm.hierarchy,anim,name)};
529 std::unique_ptr<ProtoMesh> t{new ProtoMesh(mdm,std::move(sk),name)};
530 return t;
531 }
532 }
533
534 if(FileExt::hasExt(name,"MDM") || FileExt::hasExt(name,"ASC")) {
535 FileExt::exchangeExt(name,"ASC","MDM");
536
537 if(!hasFile(name))
538 return nullptr;
539
540 const auto* entry = Resources::vdfsIndex().find(name);
541 if(entry == nullptr)
542 return nullptr;
543
544 zenkit::ModelMesh mdm;
545 auto reader = entry->open_read();
546 mdm.load(reader.get());
547
548 std::unique_ptr<ProtoMesh> t{new ProtoMesh(std::move(mdm),nullptr,name)};
549 return t;
550 }
551
552 if(FileExt::hasExt(name,"MDL")) {
553 if(!hasFile(name))
554 return nullptr;
555
556 const auto* entry = Resources::vdfsIndex().find(name);
557 if(entry == nullptr)
558 throw std::runtime_error("failed to open resource: " + name);
559 zenkit::Model mdm;
560 auto reader = entry->open_read();
561 mdm.load(reader.get());
562
563 std::unique_ptr<Skeleton> sk{new Skeleton(mdm.hierarchy,nullptr,name)};
564 std::unique_ptr<ProtoMesh> t{new ProtoMesh(mdm,std::move(sk),name)};
565 return t;
566 }
567
568 if(FileExt::hasExt(name,"TGA")) {
569 Log::e("decals should be loaded by Resources::implDecalMesh instead");
570 }
571
572 return nullptr;
573 }
574
575PfxEmitterMesh* Resources::implLoadEmiterMesh(std::string_view name) {
576 // TODO: reuse code from Resources::implLoadMeshMain
577 auto cname = std::string(name);
578 auto it = emiMeshCache.find(cname);
579 if(it!=emiMeshCache.end())
580 return it->second.get();
581
582 auto& ret = emiMeshCache[cname];
583
584 if(FileExt::hasExt(cname,"3DS")) {
585 FileExt::exchangeExt(cname,"3DS","MRM");
586
587 const auto* entry = Resources::vdfsIndex().find(cname);
588 if (entry == nullptr) return nullptr;
589 zenkit::MultiResolutionMesh zmsh;
590 auto reader = entry->open_read();
591 zmsh.load(reader.get());
592
593 if(zmsh.sub_meshes.empty())
594 return nullptr;
595
597 ret = std::unique_ptr<PfxEmitterMesh>(new PfxEmitterMesh(packed));
598 return ret.get();
599 }
600
601 if(FileExt::hasExt(name,"MDM")) {
602 if(!hasFile(name))
603 return nullptr;
604
605 const auto* entry = Resources::vdfsIndex().find(cname);
606 if(entry == nullptr)
607 throw std::runtime_error("failed to open resource: " + cname);
608 zenkit::ModelMesh mdm;
609 auto reader = entry->open_read();
610 mdm.load(reader.get());
611
612 ret = std::unique_ptr<PfxEmitterMesh>(new PfxEmitterMesh(std::move(mdm)));
613 return ret.get();
614 }
615
616 return nullptr;
617 }
618
619ProtoMesh* Resources::implDecalMesh(const zenkit::VisualDecal& decal) {
620 DecalK key;
621 key.mat = Material(decal);
622 key.sX = decal.dimension.x;
623 key.sY = decal.dimension.y;
624 key.decal2Sided = decal.two_sided;
625
626 if(key.mat.tex==nullptr)
627 return nullptr;
628
629 auto it = decalMeshCache.find(key);
630 if(it!=decalMeshCache.end())
631 return it->second.get();
632
634 {{-1.f, -1.f, 0.f},{0,0,-1},{0,1}, 0xFFFFFFFF},
635 {{ 1.f, -1.f, 0.f},{0,0,-1},{1,1}, 0xFFFFFFFF},
636 {{ 1.f, 1.f, 0.f},{0,0,-1},{1,0}, 0xFFFFFFFF},
637 {{-1.f, 1.f, 0.f},{0,0,-1},{0,0}, 0xFFFFFFFF},
638
639 {{-1.f, -1.f, 0.f},{0,0, 1},{0,1}, 0xFFFFFFFF},
640 {{ 1.f, -1.f, 0.f},{0,0, 1},{1,1}, 0xFFFFFFFF},
641 {{ 1.f, 1.f, 0.f},{0,0, 1},{1,0}, 0xFFFFFFFF},
642 {{-1.f, 1.f, 0.f},{0,0, 1},{0,0}, 0xFFFFFFFF},
643 };
644 for(auto& i:vbo) {
645 i.pos[0]*=key.sX;
646 i.pos[1]*=key.sY;
647 }
648
649 std::vector<Resources::Vertex> cvbo(vbo,vbo+8);
650 std::vector<uint32_t> cibo;
651 if(key.decal2Sided)
652 cibo = { 0,1,2, 0,2,3, 4,6,5, 4,7,6 }; else
653 cibo = { 0,1,2, 0,2,3 };
654
655 std::unique_ptr<ProtoMesh> t{new ProtoMesh(key.mat, std::move(cvbo), std::move(cibo))};
656
657 auto ret = t.get();
658 decalMeshCache[key] = std::move(t);
659 return ret;
660 }
661
662std::unique_ptr<Animation> Resources::implLoadAnimation(std::string name) {
663 if(name.size()<4)
664 return nullptr;
665
666 if(Gothic::inst().version().game==2)
667 FileExt::exchangeExt(name,"MDS","MSB");
668
669 const auto* entry = Resources::vdfsIndex().find(name);
670 if(entry == nullptr)
671 return nullptr;
672
673 if(FileExt::hasExt(name,"MSB") || FileExt::hasExt(name,"MDS")) {
674 zenkit::ModelScript mds;
675 auto reader = entry->open_read();
676 mds.load(reader.get());
677 return std::unique_ptr<Animation>{new Animation(mds,name.substr(0,name.size()-4),false)};
678 }
679
680 return nullptr;
681 }
682
683Dx8::PatternList Resources::implLoadDxMusic(std::string_view name) {
684 auto u = Tempest::TextCodec::toUtf16(name);
685 return dxMusic->load(u.c_str());
686 }
687
688DmSegment* Resources::implLoadMusicSegment(char const* name) {
689 DmSegment* sgt = nullptr;
690 DmResult rv = DmLoader_getSegment(dmLoader, name, &sgt);
691 if(rv != DmResult_SUCCESS) {
692 Log::e("Music segment not found: ", name);
693 return nullptr;
694 }
695 return sgt;
696 }
697
698Tempest::Sound Resources::implLoadSoundBuffer(std::string_view name) {
699 if(name.empty())
700 return Tempest::Sound();
701
702 if(!getFileData(name,fBuff))
703 return Tempest::Sound();
704 try {
705 Tempest::MemReader rd(fBuff.data(),fBuff.size());
706 return Tempest::Sound(rd);
707 }
708 catch(...) {
709 auto cname = std::string (name);
710 Log::e("unable to load sound \"",cname,"\"");
711 return Tempest::Sound();
712 }
713 }
714
715GthFont &Resources::implLoadFont(std::string_view name, FontType type, const float scale) {
716 std::lock_guard<std::recursive_mutex> g(inst->syncFont);
717
718 auto key = FontK(std::string(name), type, scale);
719 auto it = gothicFnt.find(key);
720 if(it!=gothicFnt.end())
721 return *(*it).second;
722
723 std::string_view file = name;
724 for(size_t i=0; i<name.size();++i) {
725 if(name[i]=='.') {
726 file = name.substr(0, i);
727 break;
728 }
729 }
730
731 string_frm tex, fnt;
732 switch(type) {
733 case FontType::Normal:
735 case FontType::Yellow:
736 case FontType::Red:
737 tex = string_frm(file,".tga");
738 fnt = string_frm(file,".fnt");
739 break;
740 case FontType::Hi:
741 tex = string_frm(file,"_hi.tga");
742 fnt = string_frm(file,"_hi.fnt");
743 break;
744 }
745
746 auto color = Tempest::Color(1.f);
747 switch(type) {
748 case FontType::Normal:
749 case FontType::Hi:
750 color = Tempest::Color(1.f);
751 break;
753 color = Tempest::Color(1.f,1.f,1.f,0.6f);
754 break;
755 case FontType::Yellow:
756 color = Tempest::Color(1.f,1.f,0.1f,1.f);
757 //color = Tempest::Color(0.81f,0.78f,0.01f,1.f);
758 break;
759 case FontType::Red:
760 color = Tempest::Color(1.f,0.f,0.f,1.f);
761 break;
762 }
763
764 const auto* entry = Resources::vdfsIndex().find(fnt);
765 if(entry == nullptr) {
766 Log::e("failed to open resource: ", fnt);
767 // throw std::runtime_error("failed to open resource: " + std::string{fnt});
768 auto ptr = std::make_unique<GthFont>();
769 GthFont* f = ptr.get();
770 gothicFnt[key] = std::move(ptr);
771 return *f;
772 }
773
774 auto ptr = std::make_unique<GthFont>(*entry->open_read(),tex,color);
775 GthFont* f = ptr.get();
776 f->setScale(scale);
777 gothicFnt[key] = std::move(ptr);
778 return *f;
779 }
780
781Texture2d Resources::loadTextureUncached(std::string_view name, bool forceMips) {
782 return inst->implLoadTextureUncached(name, forceMips);
783 }
784
785const Texture2d* Resources::loadTexture(std::string_view name, bool forceMips) {
786 std::lock_guard<std::recursive_mutex> g(inst->sync);
787 return inst->implLoadTexture(name,forceMips);
788 }
789
790const Texture2d* Resources::loadTexture(Tempest::Color color) {
791 if(color==Color())
792 return nullptr;
793 std::lock_guard<std::recursive_mutex> g(inst->sync);
794 auto& cache = inst->pixCache;
795 auto it = cache.find(color);
796 if(it!=cache.end())
797 return it->second.get();
798
799 uint8_t iv[4] = { uint8_t(255.f*color.r()), uint8_t(255.f*color.g()), uint8_t(255.f*color.b()), uint8_t(255.f*color.a()) };
800 Pixmap p2(1,1,TextureFormat::RGBA8);
801 std::memcpy(p2.data(),iv,4);
802
803 auto t = std::make_unique<Texture2d>(inst->dev.texture(p2));
804 auto ret = t.get();
805 cache[color] = std::move(t);
806 return ret;
807 }
808
809const Texture2d *Resources::loadTexture(std::string_view name, int32_t iv, int32_t ic) {
810 if(name.size()>=128)
811 return loadTexture(name);
812
813 char v[16]={};
814 char c[16]={};
815 char buf1[128]={};
816 char buf2[128]={};
817
818 std::snprintf(v,sizeof(v),"V%d",iv);
819 std::snprintf(c,sizeof(c),"C%d",ic);
820 std::snprintf(buf1,sizeof(buf1),"%.*s",int(name.size()),name.data());
821
822 emplaceTag(buf1,'V');
823 std::snprintf(buf2,sizeof(buf2),buf1,v);
824
825 emplaceTag(buf2,'C');
826 std::snprintf(buf1,sizeof(buf1),buf2,c);
827
828 return loadTexture(buf1);
829 }
830
831std::vector<const Texture2d*> Resources::loadTextureAnim(std::string_view name) {
832 std::vector<const Texture2d*> ret;
833 if(name.find("_A0")==std::string::npos &&
834 name.find("_a0")==std::string::npos)
835 return ret;
836
837 for(int id=0; ; ++id) {
838 size_t at = 0;
839 char buf[128]={};
840 for(size_t i=0;i<name.size();++i) {
841 if(i+2<name.size() && name[i]=='_' && (name[i+1]=='A' || name[i+1]=='a') && name[i+2]=='0'){
842 at += size_t(std::snprintf(buf+at,sizeof(buf)-at,"_A%d",id));
843 i+=2;
844 } else {
845 buf[at] = name[i];
846 if('a'<=buf[at] && buf[at]<='z')
847 buf[at] = char(buf[at]+'A'-'a');
848 at++;
849 }
850 if(at>sizeof(buf))
851 return ret;
852 }
853 auto t = loadTexture(buf);
854 if(t==nullptr) {
855 string_frm buf2(buf,".TGA");
856 t = loadTexture(buf2);
857 if(t==nullptr)
858 return ret;
859 }
860 ret.push_back(t);
861 }
862 }
863
864Texture2d Resources::loadTexturePm(const Pixmap &pm) {
865 if(pm.isEmpty()) {
866 Pixmap p2(1,1,TextureFormat::R8);
867 std::memset(p2.data(),0,1);
868 return inst->dev.texture(p2);
869 }
870 return inst->dev.texture(pm);
871 }
872
873Material Resources::loadMaterial(const zenkit::Material& src, bool enableAlphaTest) {
874 return Material(src,enableAlphaTest);
875 }
876
877const ProtoMesh* Resources::loadMesh(std::string_view name) {
878 if(name.size()==0)
879 return nullptr;
880 std::lock_guard<std::recursive_mutex> g(inst->sync);
881 return inst->implLoadMesh(name);
882 }
883
884const PfxEmitterMesh* Resources::loadEmiterMesh(std::string_view name) {
885 if(name.empty())
886 return nullptr;
887 std::lock_guard<std::recursive_mutex> g(inst->sync);
888 return inst->implLoadEmiterMesh(name);
889 }
890
891const Skeleton* Resources::loadSkeleton(std::string_view name) {
892 auto s = Resources::loadMesh(name);
893 if(s==nullptr)
894 return nullptr;
895 return s->skeleton.get();
896 }
897
898const Animation* Resources::loadAnimation(std::string_view name) {
899 auto cname = std::string(name);
900
901 std::lock_guard<std::recursive_mutex> g(inst->sync);
902 auto& cache = inst->animCache;
903 auto it=cache.find(cname);
904 if(it!=cache.end())
905 return it->second.get();
906 auto t = inst->implLoadAnimation(cname);
907 auto ret = t.get();
908 cache[cname] = std::move(t);
909 return ret;
910 }
911
912Tempest::Sound Resources::loadSoundBuffer(std::string_view name) {
913 std::lock_guard<std::recursive_mutex> g(inst->sync);
914 return inst->implLoadSoundBuffer(name);
915 }
916
918 std::lock_guard<std::recursive_mutex> g(inst->sync);
919 return inst->implLoadDxMusic(name);
920 }
921
922DmSegment* Resources::loadMusicSegment(char const* name) {
923 std::lock_guard<std::recursive_mutex> g(inst->sync);
924 return inst->implLoadMusicSegment(name);
925 }
926
927const ProtoMesh* Resources::decalMesh(const zenkit::VisualDecal& decal) {
928 std::lock_guard<std::recursive_mutex> g(inst->sync);
929 return inst->implDecalMesh(decal);
930 }
931
932const Resources::VobTree* Resources::loadVobBundle(std::string_view name) {
933 std::lock_guard<std::recursive_mutex> g(inst->sync);
934 return inst->implLoadVobBundle(name);
935 }
936
937void Resources::resetRecycled(uint8_t fId) {
938 std::lock_guard<std::recursive_mutex> g(inst->sync);
939 inst->recycledId = fId;
940 inst->recycled[fId].ssbo.clear();
941 inst->recycled[fId].img.clear();
942 inst->recycled[fId].att.clear();
943 inst->recycled[fId].zb.clear();
944 inst->recycled[fId].arr.clear();
945 inst->recycled[fId].rtas.clear();
946 }
947
948void Resources::recycle(Tempest::DescriptorArray &&arr) {
949 if(arr.isEmpty())
950 return;
951 std::lock_guard<std::recursive_mutex> g(inst->sync);
952 inst->recycled[inst->recycledId].arr.emplace_back(std::move(arr));
953 }
954
955void Resources::recycle(Tempest::StorageBuffer&& ssbo) {
956 if(ssbo.isEmpty())
957 return;
958 std::lock_guard<std::recursive_mutex> g(inst->sync);
959 inst->recycled[inst->recycledId].ssbo.emplace_back(std::move(ssbo));
960 }
961
962void Resources::recycle(Tempest::StorageImage&& img) {
963 if(img.isEmpty())
964 return;
965 std::lock_guard<std::recursive_mutex> g(inst->sync);
966 inst->recycled[inst->recycledId].img.emplace_back(std::move(img));
967 }
968
969void Resources::recycle(Tempest::Attachment&& img) {
970 if(img.isEmpty())
971 return;
972 std::lock_guard<std::recursive_mutex> g(inst->sync);
973 inst->recycled[inst->recycledId].att.emplace_back(std::move(img));
974 }
975
976void Resources::recycle(Tempest::ZBuffer&& img) {
977 if(img.isEmpty())
978 return;
979 std::lock_guard<std::recursive_mutex> g(inst->sync);
980 inst->recycled[inst->recycledId].zb.emplace_back(std::move(img));
981 }
982
983void Resources::recycle(Tempest::AccelerationStructure&& rtas) {
984 if(rtas.isEmpty())
985 return;
986 std::lock_guard<std::recursive_mutex> g(inst->sync);
987 inst->recycled[inst->recycledId].rtas.emplace_back(std::move(rtas));
988 }
989
990const Resources::VobTree* Resources::implLoadVobBundle(std::string_view filename) {
991 auto cname = std::string(filename);
992 auto i = zenCache.find(cname);
993 if(i!=zenCache.end())
994 return i->second.get();
995
996 std::vector<std::shared_ptr<zenkit::VirtualObject>> bundle;
997 try {
998 const auto* entry = Resources::vdfsIndex().find(cname);
999 if(entry == nullptr)
1000 throw std::runtime_error("failed to open resource: " + cname);
1001
1002 zenkit::World wrld;
1003
1004 auto reader = entry->open_read();
1005 wrld.load(reader.get(), Gothic::inst().version().game==1 ? zenkit::GameVersion::GOTHIC_1
1006 : zenkit::GameVersion::GOTHIC_2);
1007
1008 bundle = std::move(wrld.world_vobs);
1009 }
1010 catch(...) {
1011 Log::e("unable to load Zen-file: \"",cname,"\"");
1012 }
1013
1014 auto ret = zenCache.insert(std::make_pair(filename,std::make_unique<VobTree>(std::move(bundle))));
1015 return ret.first->second.get();
1016 }
1017
1019 std::lock_guard<std::recursive_mutex> g(inst->sync);
1020
1021 if(anim.submeshId.size()==0){
1022 static AttachBinder empty;
1023 return &empty;
1024 }
1025 BindK k = BindK(&s,&anim);
1026
1027 auto it = inst->bindCache.find(k);
1028 if(it!=inst->bindCache.end())
1029 return it->second.get();
1030
1031 std::unique_ptr<AttachBinder> ret(new AttachBinder(s,anim));
1032 auto p = ret.get();
1033 inst->bindCache[k] = std::move(ret);
1034 return p;
1035 }
1036
1037Tempest::VertexBuffer<Resources::Vertex> Resources::sphere(int passCount, float R){
1038 std::vector<Resources::Vertex> r;
1039 r.reserve( size_t(4*pow(3, passCount+1)) );
1040
1041 static const double pi = 3.141592654;
1042
1043 Resources::Vertex v1 = {
1044 {1, 0, -0.5},
1045 {0,0,0},{0,0},0
1046 };
1047 Resources::Vertex v2 = {
1048 {float(cos(2*pi/3)), float(sin(2*pi/3)), -0.5},
1049 {0,0,0},{0,0},0
1050 };
1051 Resources::Vertex v3 = {
1052 {float(cos(2*pi/3)), -float(sin(2*pi/3)), -0.5},
1053 {0,0,0},{0,0},0
1054 };
1055
1056 Resources::Vertex v4 = {
1057 {0, 0, 0.5},
1058 {0,0,0},{0,0},0
1059 };
1060
1061 r.push_back(v1);
1062 r.push_back(v3);
1063 r.push_back(v2);
1064
1065 r.push_back(v1);
1066 r.push_back(v2);
1067 r.push_back(v4);
1068
1069 r.push_back(v2);
1070 r.push_back(v3);
1071 r.push_back(v4);
1072
1073 r.push_back(v1);
1074 r.push_back(v4);
1075 r.push_back(v3);
1076
1077 for(size_t i=0; i<r.size(); ++i){
1078 Resources::Vertex& v = r[i];
1079 float l = std::sqrt(v.pos[0]*v.pos[0] + v.pos[1]*v.pos[1] + v.pos[2]*v.pos[2]);
1080
1081 v.pos[0] /= l;
1082 v.pos[1] /= l;
1083 v.pos[2] /= l;
1084 }
1085
1086 for(int c=0; c<passCount; ++c){
1087 size_t maxI = r.size();
1088 for( size_t i=0; i<maxI; i+=3 ){
1089 Resources::Vertex x = {
1090 {
1091 0.5f*(r[i].pos[0]+r[i+1].pos[0]),
1092 0.5f*(r[i].pos[1]+r[i+1].pos[1]),
1093 0.5f*(r[i].pos[2]+r[i+1].pos[2])
1094 },
1095 {0,0,0},{0,0},0
1096 };
1097 Resources::Vertex y = {
1098 {
1099 0.5f*(r[i+2].pos[0]+r[i+1].pos[0]),
1100 0.5f*(r[i+2].pos[1]+r[i+1].pos[1]),
1101 0.5f*(r[i+2].pos[2]+r[i+1].pos[2])
1102 },
1103 {0,0,0},{0,0},0
1104 };
1105 Resources::Vertex z = {
1106 {
1107 0.5f*(r[i].pos[0]+r[i+2].pos[0]),
1108 0.5f*(r[i].pos[1]+r[i+2].pos[1]),
1109 0.5f*(r[i].pos[2]+r[i+2].pos[2])
1110 },
1111 {0,0,0},{0,0},0
1112 };
1113
1114 r.push_back( r[i] );
1115 r.push_back( x );
1116 r.push_back( z );
1117
1118 r.push_back( x );
1119 r.push_back( r[i+1] );
1120 r.push_back( y );
1121
1122 r.push_back( y );
1123 r.push_back( r[i+2] );
1124 r.push_back( z );
1125
1126 r[i] = x;
1127 r[i+1] = y;
1128 r[i+2] = z;
1129 }
1130
1131 for(size_t i=0; i<r.size(); ++i){
1132 Resources::Vertex & v = r[i];
1133 float l = std::sqrt(v.pos[0]*v.pos[0] + v.pos[1]*v.pos[1] + v.pos[2]*v.pos[2]);
1134
1135 v.pos[0] /= l;
1136 v.pos[1] /= l;
1137 v.pos[2] /= l;
1138 }
1139 }
1140
1141 for(size_t i=0; i<r.size(); ++i){
1142 Resources::Vertex & v = r[i];
1143 v.norm[0] = v.pos[0];
1144 v.norm[1] = v.pos[1];
1145 v.norm[2] = v.pos[2];
1146
1147 v.pos[0] *= R;
1148 v.pos[1] *= R;
1149 v.pos[2] *= R;
1150 }
1151
1152 return dev.vbo(r);
1153 }
static std::u16string nestedPath(const std::initializer_list< const char16_t * > &name, Tempest::Dir::FileType type)
Definition gothic.cpp:949
static Gothic & inst()
Definition gothic.cpp:249
auto version() const -> const VersionInfo &
Definition gothic.cpp:263
void setScale(float s)
Definition gthfont.cpp:22
std::vector< SubMeshId > submeshId
Definition protomesh.h:84
static void loadVdfs(const std::vector< std::u16string > &modvdfs, bool modFilter)
static const PfxEmitterMesh * loadEmiterMesh(std::string_view name)
static Tempest::StorageBuffer ssbo(const void *data, size_t size)
Definition resources.h:125
std::vector< std::shared_ptr< zenkit::VirtualObject > > VobTree
Definition resources.h:81
static const Tempest::Texture2d & fallbackTexture()
static void resetRecycled(uint8_t fId)
static auto openReader(std::string_view name, std::unique_ptr< zenkit::Read > &read) -> std::unique_ptr< zenkit::ReadArchive >
static const Animation * loadAnimation(std::string_view name)
static const Skeleton * loadSkeleton(std::string_view name)
static const Tempest::IndexBuffer< uint16_t > & cubeIbo()
static const Tempest::Sampler & shadowSampler()
static Tempest::Texture2d loadTextureUncached(std::string_view name, bool forceMips=false)
static const GthFont & font(const float scale)
static const GthFont & dialogFont(const float scale)
static bool hasFile(std::string_view fname)
static const VobTree * loadVobBundle(std::string_view name)
static auto fallbackImage() -> const Tempest::StorageImage &
static Material loadMaterial(const zenkit::Material &src, bool enableAlphaTest)
static std::vector< uint8_t > getFileData(std::string_view name)
static const ProtoMesh * loadMesh(std::string_view name)
static Tempest::Device & device()
Definition resources.h:83
static Dx8::PatternList loadDxMusic(std::string_view name)
static const Tempest::Texture2d * loadTexture(std::string_view name, bool forceMips=false)
static std::unique_ptr< zenkit::Read > getFileBuffer(std::string_view name)
static const char * renderer()
Resources(Tempest::Device &device)
Definition resources.cpp:54
static Tempest::Sound loadSoundBuffer(std::string_view name)
static const Tempest::Texture2d & fallbackBlack()
static DmSegment * loadMusicSegment(char const *name)
static const zenkit::Vfs & vdfsIndex()
static const AttachBinder * bindMesh(const ProtoMesh &anim, const Skeleton &s)
static auto loadTextureAnim(std::string_view name) -> std::vector< const Tempest::Texture2d * >
static Tempest::Texture2d loadTexturePm(const Tempest::Pixmap &pm)
static void recycle(Tempest::DescriptorArray &&arr)
static void mountWork(const std::filesystem::path &path)
static Tempest::VertexBuffer< V > vbo(const V *data, size_t sz)
Definition resources.h:120
static const ProtoMesh * decalMesh(const zenkit::VisualDecal &decal)
bool exchangeExt(std::string &s, const char *extIn, const char *extOut)
Definition fileext.h:43
bool hasExt(std::string_view s, const char *extIn)
Definition fileext.h:8
void assignExt(std::string &s, const char *extOut)
Definition fileext.h:64
static Sampler implShadowSampler()
static void emplaceTag(char *buf, char tag)
Definition resources.cpp:43